[
  {
    "path": ".branding",
    "content": "https://github.com/Keavon/graphite-branded-assets/archive/8ae15dc9c51a3855475d8cab1d0f29d9d9bc622c.tar.gz\nc19abe4ac848f3c835e43dc065c59e20e60233ae023ea0a064c5fed442be2d3d\n"
  },
  {
    "path": ".cargo/config.toml",
    "content": "[target.wasm32-unknown-unknown]\nrustflags = [\n\t# Currently disabled because of https://github.com/GraphiteEditor/Graphite/issues/1262\n\t# The current simd implementation leads to undefined behavior\n\t#\"-C\",\n\t#\"target-feature=+simd128\",\n\t\"-C\",\n\t\"target-feature=+bulk-memory\",\n\t\"-C\",\n\t\"link-arg=--max-memory=4294967296\",\n\t\"--cfg=web_sys_unstable_apis\",\n]\n\n[env]\nCARGO_WORKSPACE_DIR = { value = \"\", relative = true }\n"
  },
  {
    "path": ".devcontainer/devcontainer.json",
    "content": "{\n\t\"image\": \"mcr.microsoft.com/devcontainers/base:debian\",\n\t\"features\": {\n\t\t\"ghcr.io/devcontainers/features/rust:1\": {\n\t\t\t\"profile\": \"default\"\n\t\t},\n\t\t\"ghcr.io/devcontainers/features/node:1\": {}\n\t},\n\t\"onCreateCommand\": \"cargo install cargo-watch wasm-pack cargo-about && cargo install -f wasm-bindgen-cli@0.2.100\",\n\t\"customizations\": {\n\t\t\"vscode\": {\n\t\t\t// NOTE: Keep this in sync with `.vscode/extensions.json`\n\t\t\t\"extensions\": [\n\t\t\t\t// Rust\n\t\t\t\t\"rust-lang.rust-analyzer\",\n\t\t\t\t\"tamasfe.even-better-toml\",\n\t\t\t\t// Web\n\t\t\t\t\"dbaeumer.vscode-eslint\",\n\t\t\t\t\"svelte.svelte-vscode\",\n\t\t\t\t\"vitaliymaz.vscode-svg-previewer\",\n\t\t\t\t// Code quality\n\t\t\t\t\"wayou.vscode-todo-highlight\",\n\t\t\t\t\"streetsidesoftware.code-spell-checker\",\n\t\t\t\t// Helpful\n\t\t\t\t\"mhutchie.git-graph\",\n\t\t\t\t\"qezhu.gitlink\",\n\t\t\t\t\"wmaurer.change-case\"\n\t\t\t]\n\t\t}\n\t}\n}\n"
  },
  {
    "path": ".editorconfig",
    "content": "[*.{rs,js,ts,svelte,json,toml,svg,html,css,scss}]\nindent_style = tab\nindent_size = 4\nend_of_line = lf\ntrim_trailing_whitespace = true\ninsert_final_newline = true\nmax_line_length = 200\n"
  },
  {
    "path": ".envrc",
    "content": "use flake\n"
  },
  {
    "path": ".gitattributes",
    "content": "# Requires Git to check out files with the LF (not CRLF) line endings for files it automatically recognizes as being text-based\n# The `*` targets all files\n# The `text=auto` makes it apply a conversion only to files detected as text-based\n# The `eol=lf` sets the conversion to an LF line ending\n# https://git-scm.com/docs/gitattributes\n* text=auto eol=lf\n\n# Adds syntax highlighting to Graphite files on GitHub and minimizes diffs both locally and on GitHub\n*.graphite binary linguist-generated linguist-language=JSON\n/node-graph/graphene-cli/test_files/*.graphite text diff -linguist-generated\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: [GraphiteEditor]\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "<!--\nGraphite has ZERO-TOLERANCE for contributing undisclosed AI-generated content.\nIf your PR involves AI, you must read our AI contribution policy (it's short):\nhttps://graphite.art/volunteer/guide/starting-a-task/ai-contribution-policy\n\nREMEMBER:\n- You are responsible for thoroughly testing the successful implementation of your changes and ensuring no obvious regressions occur.\n- Egregiously dysfunctional PRs may be assumed to be undisclosed AI slop. If in doubt, ask on Discord before attempting a PR.\n- You are highly recommended to include a video showing the before-and-after behavior of your changes and screenshots of any new or modified UI.\n- Remember that Graphite maintains high standards for quality and the project is not a classroom for inexperienced developers to gain industry experience.\n- In this PR description, reference any relevant tasks by writing \"Closes\", \"Resolves\", or \"Fixes\" with the issue # or the URL of a Discord message documenting the task.\n- To acknowledge that you've read this, you must delete these rules and fill in the (strictly human-written) PR description in its place.\n-->\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: \"Build\"\n\non:\n  push:\n    branches:\n      - master\n    tags:\n      - latest-stable\n  workflow_dispatch:\n    inputs:\n      web:\n        description: \"Web\"\n        type: boolean\n      windows:\n        description: \"Windows\"\n        type: boolean\n      mac:\n        description: \"Mac\"\n        type: boolean\n      linux:\n        description: \"Linux\"\n        type: boolean\n      push_to_nix_cache:\n        description: \"Linux: push to Nix cache\"\n        type: boolean\n      debug:\n        description: \"Debug build\"\n        type: boolean\n  workflow_call:\n    inputs:\n      web:\n        type: boolean\n      windows:\n        type: boolean\n      mac:\n        type: boolean\n      linux:\n        type: boolean\n      push_to_nix_cache:\n        type: boolean\n      debug:\n        type: boolean\n      checkout_repo:\n        type: string\n      checkout_ref:\n        type: string\n      pr_number:\n        type: string\n\njobs:\n  web:\n    if: github.event_name == 'push' || inputs.web\n    runs-on: [self-hosted, target/wasm]\n    permissions:\n      contents: write\n      deployments: write\n      pull-requests: write\n      actions: write\n    env:\n      CARGO_TERM_COLOR: always\n      RUSTC_WRAPPER: /usr/bin/sccache\n      CARGO_INCREMENTAL: 0\n      SCCACHE_DIR: /var/lib/github-actions/.cache\n    steps:\n      - name: 📥 Clone repository\n        uses: actions/checkout@v6\n        with:\n          repository: ${{ inputs.checkout_repo || github.repository }}\n          ref: ${{ inputs.checkout_ref || '' }}\n\n      - name: 🗑 Clear wasm-bindgen cache\n        run: rm -r ~/.cache/.wasm-pack || true\n\n      - name: 🟢 Install Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version-file: .nvmrc\n\n      - name: 🚧 Install build dependencies\n        run: |\n          cd frontend\n          npm run setup\n\n      - name: 🦀 Install Rust\n        uses: actions-rust-lang/setup-rust-toolchain@v1\n        with:\n          toolchain: stable\n          override: true\n          cache: false\n          rustflags: \"\"\n          target: wasm32-unknown-unknown\n\n      - name: 🔀 Choose production deployment environment and insert template\n        id: production-env\n        if: github.event_name == 'push'\n        run: |\n          if [[ \"${{ github.ref }}\" == \"refs/tags/latest-stable\" ]]; then\n            echo \"cf_project=graphite-editor\" >> $GITHUB_OUTPUT\n            DOMAIN=\"editor.graphite.art\"\n          else\n            echo \"cf_project=graphite-dev\" >> $GITHUB_OUTPUT\n            DOMAIN=\"dev.graphite.art\"\n          fi\n          TEMPLATE=\"<script defer data-domain=\\\"$DOMAIN\\\" data-api=\\\"https://graphite.art/visit/event\\\" src=\\\"https://graphite.art/visit/script.hash.js\\\"></script>\"\n          echo \"template=$TEMPLATE\" >> $GITHUB_OUTPUT\n          sed -i \"s|<!-- INDEX_HTML_HEAD_REPLACEMENT -->|$TEMPLATE|\" frontend/index.html\n\n      - name: 🌐 Build Graphite web code\n        env:\n          NODE_ENV: production\n        run: mold -run cargo run build web${{ inputs.debug && ' debug' || '' }}\n\n      - name: 📤 Publish to Cloudflare Pages\n        id: cloudflare\n        continue-on-error: ${{ github.event_name != 'push' }}\n        env:\n          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}\n          CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}\n        run: |\n          if [ -z \"$CLOUDFLARE_API_TOKEN\" ]; then\n            echo \"No Cloudflare API token available (fork PR), skipping deploy.\"\n            exit 0\n          fi\n          MAX_ATTEMPTS=8\n          DELAY=15\n          for ATTEMPT in $(seq 1 $MAX_ATTEMPTS); do\n            echo \"Attempt $ATTEMPT of $MAX_ATTEMPTS...\"\n            npx wrangler@3 pages deploy \"frontend/dist\" --project-name=\"${{ steps.production-env.outputs.cf_project || 'graphite-dev' }}\" --commit-dirty=true 2>&1 | tee /tmp/wrangler_output\n            if [ ${PIPESTATUS[0]} -eq 0 ]; then\n              URL=$(grep -oP 'https://[^\\s]+\\.pages\\.dev' /tmp/wrangler_output | head -1)\n              echo \"url=$URL\" >> \"$GITHUB_OUTPUT\"\n              echo \"Published successfully: $URL\"\n              exit 0\n            fi\n            echo \"Attempt $ATTEMPT failed.\"\n            if [ \"$ATTEMPT\" -lt \"$MAX_ATTEMPTS\" ]; then\n              echo \"Retrying in ${DELAY}s...\"\n              sleep $DELAY\n              DELAY=$((DELAY * 2))\n            fi\n          done\n          echo \"All $MAX_ATTEMPTS Cloudflare Pages publish attempts failed.\"\n          exit 1\n\n      - name: 🚀 Create a GitHub environment deployment\n        if: inputs.checkout_repo == '' || inputs.checkout_repo == github.repository\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          CF_URL: ${{ steps.cloudflare.outputs.url }}\n        run: |\n          if [ -z \"$CF_URL\" ]; then\n            echo \"No Cloudflare URL available, skipping deployment.\"\n            exit 0\n          fi\n          if [ \"${{ github.ref }}\" = \"refs/tags/latest-stable\" ]; then\n            REF=\"latest-stable\"\n            ENVIRONMENT=\"graphite-editor (Production)\"\n          elif [ \"${{ github.event_name }}\" = \"push\" ]; then\n            REF=\"master\"\n            ENVIRONMENT=\"graphite-dev (Production)\"\n          else\n            REF=\"${{ inputs.checkout_ref || github.head_ref || github.ref_name }}\"\n            ENVIRONMENT=\"graphite-dev (Preview)\"\n          fi\n          DEPLOY_ID=$(gh api \\\n            -X POST \\\n            -H \"Accept: application/vnd.github+json\" \\\n            repos/${{ github.repository }}/deployments \\\n            --input - \\\n            --jq '.id' <<EOF\n            {\"ref\":\"$REF\",\"environment\":\"$ENVIRONMENT\",\"auto_merge\":false,\"required_contexts\":[]}\n          EOF\n          )\n          gh api \\\n            -X POST \\\n            -H \"Accept: application/vnd.github+json\" \\\n            repos/${{ github.repository }}/deployments/$DEPLOY_ID/statuses \\\n            -f state=success \\\n            -f environment_url=\"$CF_URL\" \\\n            -f log_url=\"https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}\"\n\n      - name: 💬 Comment with the build link\n        if: github.event_name != 'pull_request' && github.event_name != 'merge_group'\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          CF_URL: ${{ steps.cloudflare.outputs.url }}\n        run: |\n          if [ -z \"$CF_URL\" ]; then\n            echo \"No Cloudflare URL available, skipping comment.\"\n            exit 0\n          fi\n\n          COMMENT_BODY=\"| 📦 **Web Build Complete for** $(git rev-parse HEAD) |\n          |-|\n          | $CF_URL |\"\n\n          if [ \"${{ github.ref }}\" = \"refs/tags/latest-stable\" ]; then\n            # Push tag: skip commenting (commit was already commented on master merge)\n            echo \"Tag push, skipping comment.\"\n          elif [ \"${{ github.event_name }}\" = \"push\" ]; then\n            # Push master: comment on the commit hash page\n            gh api \\\n              -X POST \\\n              -H \"Accept: application/vnd.github+json\" \\\n              repos/${{ github.repository }}/commits/$(git rev-parse HEAD)/comments \\\n              -f body=\"$COMMENT_BODY\"\n          elif [ \"${{ github.event_name }}\" != \"pull_request\" ] && [ \"${{ github.event_name }}\" != \"merge_group\" ]; then\n            # Manual trigger (workflow_dispatch, !build): comment on the PR\n            PR_NUMBER=\"${{ inputs.pr_number }}\"\n            if [ -z \"$PR_NUMBER\" ]; then\n              BRANCH=$(git rev-parse --abbrev-ref HEAD)\n              PR_NUMBER=$(gh pr list --repo ${{ github.repository }} --head \"$BRANCH\" --json number --jq '.[0].number' 2>/dev/null || true)\n            fi\n\n            if [ -n \"$PR_NUMBER\" ]; then\n              gh pr comment \"$PR_NUMBER\" --repo ${{ github.repository }} --body \"$COMMENT_BODY\"\n            else\n              echo \"No open PR found, skipping comment.\"\n            fi\n          fi\n\n      - name: ✂ Strip template from completed build for a clean artifact\n        if: github.event_name == 'push'\n        env:\n          TEMPLATE: ${{ steps.production-env.outputs.template }}\n        run: sed -i \"s|$TEMPLATE||\" frontend/dist/index.html\n\n      - name: 📦 Upload web bundle artifact\n        if: github.event_name != 'pull_request'\n        uses: actions/upload-artifact@v6\n        with:\n          name: graphite-web-bundle\n          path: frontend/dist\n\n      - name: 👕 Lint Graphite web formatting\n        env:\n          NODE_ENV: production\n        run: |\n          cd frontend\n          npm run check\n\n      - name: 📃 Trigger website rebuild if auto-generated code docs are stale\n        if: github.event_name == 'push' && github.ref != 'refs/tags/latest-stable'\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          cargo run -p editor-message-tree -- website/generated\n          TREE=volunteer/guide/codebase-overview/hierarchical-message-system-tree\n          curl -sf \"https://graphite.art/$TREE.txt\" -o \"website/static/$TREE.live.txt\" \\\n            && diff -q \"website/static/$TREE.txt\" \"website/static/$TREE.live.txt\" > /dev/null \\\n            || gh workflow run website.yml --ref master\n\n  windows:\n    if: (github.event_name == 'push' && github.ref != 'refs/tags/latest-stable') || inputs.windows\n    runs-on: windows-latest\n    permissions:\n      contents: read\n      id-token: write\n      pull-requests: write\n\n    env:\n      WASM_BINDGEN_CLI_VERSION: \"0.2.100\"\n\n    steps:\n      - name: 📥 Clone repository\n        uses: actions/checkout@v6\n        with:\n          repository: ${{ inputs.checkout_repo || github.repository }}\n          ref: ${{ inputs.checkout_ref || '' }}\n\n      - name: 🦀 Install Rust\n        uses: actions-rust-lang/setup-rust-toolchain@v1\n        with:\n          toolchain: stable\n          override: true\n          cache: false\n          rustflags: \"\"\n          target: wasm32-unknown-unknown\n\n      - name: 💾 Set up Cargo cache\n        uses: actions/cache@v5\n        with:\n          path: |\n            ~/.cargo/registry\n            ~/.cargo/git\n            target\n          key: cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}\n\n      - name: 🟢 Install Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version-file: .nvmrc\n          cache: npm\n          cache-dependency-path: |\n            package-lock.json\n            frontend/package-lock.json\n\n      - name: 📦 Install Cargo-binstall\n        uses: cargo-bins/cargo-binstall@main\n\n      - name: 🚧 Install native dependencies\n        shell: pwsh\n        env:\n          GITHUB_TOKEN: ${{ github.token }}\n          BINSTALL_DISABLE_TELEMETRY: \"true\"\n        run: |\n          winget install --id LLVM.LLVM -e --accept-package-agreements --accept-source-agreements\n          winget install --id Kitware.CMake -e --accept-package-agreements --accept-source-agreements\n          winget install --id OpenSSL.OpenSSL -e --accept-package-agreements --accept-source-agreements\n          winget install --id WebAssembly.Binaryen -e --accept-package-agreements --accept-source-agreements\n          winget install --id GnuWin32.PkgConfig -e --accept-package-agreements --accept-source-agreements\n\n          \"OPENSSL_DIR=C:\\Program Files\\OpenSSL-Win64\" | Out-File -FilePath $env:GITHUB_ENV -Append\n          \"PKG_CONFIG_PATH=C:\\Program Files\\OpenSSL-Win64\\lib\\pkgconfig\" | Out-File -FilePath $env:GITHUB_ENV -Append\n\n          cargo binstall --no-confirm --force wasm-pack\n          cargo binstall --no-confirm --force cargo-about\n          cargo binstall --no-confirm --force \"wasm-bindgen-cli@$env:WASM_BINDGEN_CLI_VERSION\"\n\n      - name: 🏗 Build Windows bundle\n        shell: bash # `cargo-about` refuses to run in powershell\n        env:\n          CARGO_TERM_COLOR: always\n        run: cargo run build desktop${{ inputs.debug && ' debug' || '' }}\n\n      - name: 📁 Stage artifacts\n        shell: bash\n        run: |\n          PROFILE=${{ inputs.debug && 'debug' || 'release' }}\n          rm -rf target/artifacts\n          mkdir -p target/artifacts\n          cp -R target/$PROFILE/Graphite target/artifacts/Graphite\n\n      - name: 📦 Upload Windows bundle\n        if: github.event_name != 'push'\n        uses: actions/upload-artifact@v6\n        with:\n          name: graphite-windows-bundle\n          path: target/artifacts\n\n      - name: 💬 Comment artifact link on PR\n        if: github.event_name != 'push'\n        shell: bash\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          ARTIFACT_ID=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --jq '.artifacts[] | select(.name == \"graphite-windows-bundle\") | .id')\n          ARTIFACT_URL=\"https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts/$ARTIFACT_ID\"\n          PR_NUMBER=\"${{ inputs.pr_number }}\"\n          if [ -z \"$PR_NUMBER\" ]; then\n            BRANCH=$(git rev-parse --abbrev-ref HEAD)\n            PR_NUMBER=$(gh pr list --repo ${{ github.repository }} --head \"$BRANCH\" --json number --jq '.[0].number' 2>/dev/null || true)\n          fi\n          if [ -n \"$PR_NUMBER\" ] && [ -n \"$ARTIFACT_ID\" ]; then\n            BODY=\"| 📦 **Windows Build Complete for** $(git rev-parse HEAD) |\"$'\\n'\n            BODY+=\"|-|\"$'\\n'\n            BODY+=\"| [Download binary]($ARTIFACT_URL) |\"\n            gh pr comment \"$PR_NUMBER\" --repo ${{ github.repository }} --body \"$BODY\"\n          fi\n\n      - name: 🔑 Azure login\n        if: github.event_name == 'push'\n        uses: azure/login@v1\n        with:\n          client-id: ${{ secrets.AZURE_CLIENT_ID }}\n          tenant-id: ${{ secrets.AZURE_TENANT_ID }}\n          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}\n          enable-AzPSSession: true\n\n      - name: 🔏 Sign\n        if: github.event_name == 'push'\n        uses: azure/artifact-signing-action@v1\n        with:\n          endpoint: https://eus.codesigning.azure.net/\n          signing-account-name: Graphite\n          certificate-profile-name: Graphite\n          files: |\n            ${{ github.workspace }}\\target\\artifacts\\Graphite\\Graphite.exe\n            ${{ github.workspace }}\\target\\artifacts\\Graphite\\libcef.dll\n            ${{ github.workspace }}\\target\\artifacts\\Graphite\\chrome_elf.dll\n            ${{ github.workspace }}\\target\\artifacts\\Graphite\\vulkan-1.dll\n            ${{ github.workspace }}\\target\\artifacts\\Graphite\\dxcompiler.dll\n            ${{ github.workspace }}\\target\\artifacts\\Graphite\\libEGL.dll\n            ${{ github.workspace }}\\target\\artifacts\\Graphite\\libGLESv2.dll\n            ${{ github.workspace }}\\target\\artifacts\\Graphite\\vk_swiftshader.dll\n          file-digest: SHA256\n          timestamp-rfc3161: http://timestamp.acs.microsoft.com\n          timestamp-digest: SHA256\n          correlation-id: ${{ github.sha }}\n\n      - name: ✅ Verify signatures\n        if: github.event_name == 'push'\n        shell: pwsh\n        run: |\n          $ErrorActionPreference = \"Stop\"\n\n          $TargetDir = \"target\\artifacts\\Graphite\"\n\n          if (-not (Test-Path $TargetDir)) {\n              throw \"TargetDir not found: $TargetDir\"\n          }\n\n          $UnsignedOrBad = @()\n\n          Get-ChildItem -Path $TargetDir -Recurse -File -Include *.exe,*.dll | ForEach-Object {\n              $sig = Get-AuthenticodeSignature -FilePath $_.FullName\n\n              if ($sig.Status -ne 'Valid') {\n                  $UnsignedOrBad += \"$($_.FullName) (Status=$($sig.Status))\"\n              }\n          }\n\n          if ($UnsignedOrBad.Count -gt 0) {\n              Write-Host \"Unsigned or invalid binaries detected:\"\n              $UnsignedOrBad | ForEach-Object {\n                  Write-Host \"::error::$_\"\n              }\n\n              if ($env:GITHUB_STEP_SUMMARY) {\n                  \"### ❌ Unsigned or invalid binaries detected\" |\n                      Out-File $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8\n                  \"\" | Out-File $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8\n                  $UnsignedOrBad | ForEach-Object {\n                      \"* `$_\" | Out-File $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8\n                  }\n              }\n\n              exit 1\n          }\n\n          Write-Host \"All binaries are signed and valid.\"\n\n          if ($env:GITHUB_STEP_SUMMARY) {\n              \"### ✅ All binaries are signed and valid\" |\n                  Out-File $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8\n          }\n\n      - name: 📦 Upload signed Windows bundle\n        if: github.event_name == 'push'\n        uses: actions/upload-artifact@v6\n        with:\n          name: graphite-windows-bundle-signed\n          path: target/artifacts\n\n  mac:\n    if: (github.event_name == 'push' && github.ref != 'refs/tags/latest-stable') || inputs.mac\n    runs-on: macos-latest\n    permissions:\n      contents: read\n      pull-requests: write\n\n    env:\n      WASM_BINDGEN_CLI_VERSION: \"0.2.100\"\n\n    steps:\n      - name: 📥 Clone repository\n        uses: actions/checkout@v6\n        with:\n          repository: ${{ inputs.checkout_repo || github.repository }}\n          ref: ${{ inputs.checkout_ref || '' }}\n\n      - name: 🦀 Install Rust\n        uses: actions-rust-lang/setup-rust-toolchain@v1\n        with:\n          toolchain: stable\n          override: true\n          cache: false\n          rustflags: \"\"\n          target: wasm32-unknown-unknown\n\n      - name: 💾 Set up Cargo cache\n        uses: actions/cache@v5\n        with:\n          path: |\n            ~/.cargo/registry\n            ~/.cargo/git\n            target\n          key: cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}\n\n      - name: 🟢 Install Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version-file: .nvmrc\n          cache: npm\n          cache-dependency-path: |\n            package-lock.json\n            frontend/package-lock.json\n\n      - name: 🚧 Install native dependencies\n        env:\n          GITHUB_TOKEN: ${{ github.token }}\n          BINSTALL_DISABLE_TELEMETRY: \"true\"\n        run: |\n          brew update\n          brew install \\\n            pkg-config \\\n            openssl@3 \\\n            binaryen \\\n            llvm \\\n            cargo-binstall\n\n          echo \"OPENSSL_DIR=$(brew --prefix openssl@3)\" >> $GITHUB_ENV\n          echo \"PKG_CONFIG_PATH=$(brew --prefix openssl@3)/lib/pkgconfig\" >> $GITHUB_ENV\n          echo \"$(brew --prefix llvm)/bin\" >> $GITHUB_PATH\n\n          cargo binstall --no-confirm --force wasm-pack\n          cargo binstall --no-confirm --force cargo-about\n          cargo binstall --no-confirm --force \"wasm-bindgen-cli@${WASM_BINDGEN_CLI_VERSION}\"\n\n      - name: 🏗 Build Mac bundle\n        env:\n          CARGO_TERM_COLOR: always\n        run: cargo run build desktop${{ inputs.debug && ' debug' || '' }}\n\n      - name: 📁 Stage artifacts\n        shell: bash\n        run: |\n          PROFILE=${{ inputs.debug && 'debug' || 'release' }}\n          rm -rf target/artifacts\n          mkdir -p target/artifacts\n          cp -R target/$PROFILE/Graphite.app target/artifacts/Graphite.app\n\n      - name: 📦 Upload Mac bundle\n        if: github.event_name != 'push'\n        uses: actions/upload-artifact@v6\n        with:\n          name: graphite-mac-bundle\n          path: target/artifacts\n\n      - name: 💬 Comment artifact link on PR\n        if: github.event_name != 'push'\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          ARTIFACT_ID=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --jq '.artifacts[] | select(.name == \"graphite-mac-bundle\") | .id')\n          ARTIFACT_URL=\"https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts/$ARTIFACT_ID\"\n          PR_NUMBER=\"${{ inputs.pr_number }}\"\n          if [ -z \"$PR_NUMBER\" ]; then\n            BRANCH=$(git rev-parse --abbrev-ref HEAD)\n            PR_NUMBER=$(gh pr list --repo ${{ github.repository }} --head \"$BRANCH\" --json number --jq '.[0].number' 2>/dev/null || true)\n          fi\n          if [ -n \"$PR_NUMBER\" ] && [ -n \"$ARTIFACT_ID\" ]; then\n            BODY=\"| 📦 **Mac Build Complete for** $(git rev-parse HEAD) |\"$'\\n'\n            BODY+=\"|-|\"$'\\n'\n            BODY+=\"| [Download binary]($ARTIFACT_URL) |\"\n            gh pr comment \"$PR_NUMBER\" --repo ${{ github.repository }} --body \"$BODY\"\n          fi\n\n      - name: 🔏 Sign and notarize (preparation)\n        if: github.event_name == 'push'\n        env:\n          APPLE_CERT_BASE64: ${{ secrets.APPLE_CERT_BASE64 }}\n          APPLE_CERT_PASSWORD: ${{ secrets.APPLE_CERT_PASSWORD }}\n        run: |\n          mkdir -p .sign\n          echo \"$APPLE_CERT_BASE64\" | base64 --decode > .sign/certificate.p12\n\n          security create-keychain -p \"\" .sign/main.keychain\n          security default-keychain -s .sign/main.keychain\n          security unlock-keychain -p \"\" .sign/main.keychain\n          security set-keychain-settings -t 3600 -u .sign/main.keychain\n\n          security import .sign/certificate.p12 -k .sign/main.keychain -P \"$APPLE_CERT_PASSWORD\" -T /usr/bin/codesign -T /usr/bin/productsign\n          security set-key-partition-list -S apple-tool:,apple: -s -k \"\" .sign/main.keychain\n\n          cat > .sign/entitlements.plist <<'EOF'\n          <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n          <!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n          <plist version=\"1.0\">\n          <dict>\n              <key>com.apple.security.cs.allow-jit</key>\n              <true/>\n              <key>com.apple.security.cs.allow-unsigned-executable-memory</key>\n              <true/>\n              <key>com.apple.security.cs.disable-executable-page-protection</key>\n              <true/>\n              <key>com.apple.security.cs.disable-library-validation</key>\n              <true/>\n          </dict>\n          </plist>\n          EOF\n\n      - name: 🔏 Sign and notarize\n        if: github.event_name == 'push'\n        env:\n          APPLE_EMAIL: ${{ secrets.APPLE_EMAIL }}\n          APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}\n          APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}\n          APPLE_CERT_NAME: ${{ secrets.APPLE_CERT_NAME }}\n        run: |\n          CERTIFICATE=\"$APPLE_CERT_NAME\"\n          ENTITLEMENTS=\".sign/entitlements.plist\"\n          APP_PATH=\"target/artifacts/Graphite.app\"\n          ZIP_PATH=\".sign/Graphite.zip\"\n\n          codesign --force --options runtime --entitlements \"$ENTITLEMENTS\" --sign \"$CERTIFICATE\" \"$APP_PATH/Contents/Frameworks/Graphite Helper.app\"\n          codesign --force --options runtime --entitlements \"$ENTITLEMENTS\" --sign \"$CERTIFICATE\" \"$APP_PATH/Contents/Frameworks/Graphite Helper (GPU).app\"\n          codesign --force --options runtime --entitlements \"$ENTITLEMENTS\" --sign \"$CERTIFICATE\" \"$APP_PATH/Contents/Frameworks/Graphite Helper (Renderer).app\"\n          codesign --force --options runtime --entitlements \"$ENTITLEMENTS\" --sign \"$CERTIFICATE\" \"$APP_PATH/Contents/Frameworks/Chromium Embedded Framework.framework\"\n          codesign --force --options runtime --entitlements \"$ENTITLEMENTS\" --sign \"$CERTIFICATE\" \"$APP_PATH/Contents/Frameworks/Chromium Embedded Framework.framework/Libraries/libcef_sandbox.dylib\"\n          codesign --force --options runtime --entitlements \"$ENTITLEMENTS\" --sign \"$CERTIFICATE\" \"$APP_PATH/Contents/Frameworks/Chromium Embedded Framework.framework/Libraries/libEGL.dylib\"\n          codesign --force --options runtime --entitlements \"$ENTITLEMENTS\" --sign \"$CERTIFICATE\" \"$APP_PATH/Contents/Frameworks/Chromium Embedded Framework.framework/Libraries/libGLESv2.dylib\"\n          codesign --force --options runtime --entitlements \"$ENTITLEMENTS\" --sign \"$CERTIFICATE\" \"$APP_PATH/Contents/Frameworks/Chromium Embedded Framework.framework/Libraries/libvk_swiftshader.dylib\"\n          codesign --force --options runtime --entitlements \"$ENTITLEMENTS\" --sign \"$CERTIFICATE\" \"$APP_PATH\" --deep\n\n          codesign --verify --deep --strict --verbose=4 \"$APP_PATH\"\n\n          ditto -c -k --keepParent \"$APP_PATH\" \"$ZIP_PATH\"\n          xcrun notarytool submit \"$ZIP_PATH\" --wait --apple-id \"$APPLE_EMAIL\" --team-id \"$APPLE_TEAM_ID\" --password \"$APPLE_PASSWORD\"\n          rm \"$ZIP_PATH\"\n\n          xcrun stapler staple -v \"$APP_PATH\"\n\n          spctl -a -vv \"$APP_PATH\"\n\n      - name: 📦 Upload signed Mac bundle\n        if: github.event_name == 'push'\n        uses: actions/upload-artifact@v6\n        with:\n          name: graphite-mac-bundle-signed\n          path: target/artifacts\n\n  linux:\n    if: (github.event_name == 'push' && github.ref != 'refs/tags/latest-stable') || inputs.linux\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      pull-requests: write\n\n    steps:\n      - name: 📥 Clone repository\n        uses: actions/checkout@v6\n        with:\n          repository: ${{ inputs.checkout_repo || github.repository }}\n          ref: ${{ inputs.checkout_ref || '' }}\n\n      - name: ❄ Install Nix\n        uses: DeterminateSystems/nix-installer-action@main\n\n      - name: 🗑 Free disk space\n        run: sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache\n\n      - name: 📦 Build Nix package\n        run: nix build .#graphite${{ inputs.debug && '-dev' || '' }} --no-link --print-out-paths\n\n      - name: 📤 Push to Nix cache\n        if: (github.event_name == 'push' || inputs.push_to_nix_cache) && !inputs.debug\n        env:\n          NIX_CACHE_AUTH_TOKEN: ${{ secrets.NIX_CACHE_AUTH_TOKEN }}\n        run: |\n          nix run nixpkgs#cachix -- authtoken $NIX_CACHE_AUTH_TOKEN\n          nix build --no-link --print-out-paths | nix run nixpkgs#cachix -- push graphite\n\n      - name: 🏗 Build Linux bundle\n        run: nix build .#graphite${{ inputs.debug && '-dev' || '' }}-bundle.tar.xz && cp ./result ./graphite-linux-bundle.tar.xz\n\n      - name: 📦 Upload Linux bundle\n        uses: actions/upload-artifact@v6\n        with:\n          name: graphite-linux-bundle\n          path: graphite-linux-bundle.tar.xz\n          compression-level: 0\n\n      - name: 💬 Comment artifact link on PR\n        id: linux-comment\n        if: github.event_name != 'push'\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          ARTIFACT_ID=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --jq '.artifacts[] | select(.name == \"graphite-linux-bundle\") | .id')\n          ARTIFACT_URL=\"https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts/$ARTIFACT_ID\"\n          PR_NUMBER=\"${{ inputs.pr_number }}\"\n          if [ -z \"$PR_NUMBER\" ]; then\n            BRANCH=$(git rev-parse --abbrev-ref HEAD)\n            PR_NUMBER=$(gh pr list --repo ${{ github.repository }} --head \"$BRANCH\" --json number --jq '.[0].number' 2>/dev/null || true)\n          fi\n          if [ -n \"$PR_NUMBER\" ] && [ -n \"$ARTIFACT_ID\" ]; then\n            BODY=\"| 📦 **Linux Build Complete for** $(git rev-parse HEAD) |\"$'\\n'\n            BODY+=\"|-|\"$'\\n'\n            BODY+=\"| [Download binary]($ARTIFACT_URL) |\"\n            COMMENT_ID=$(gh api repos/${{ github.repository }}/issues/$PR_NUMBER/comments -f body=\"$BODY\" --jq '.id')\n            echo \"comment_id=$COMMENT_ID\" >> \"$GITHUB_OUTPUT\"\n          fi\n\n      - name: 🔧 Install Flatpak tooling\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y flatpak flatpak-builder\n          flatpak --user remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo\n\n      - name: 🏗 Build Flatpak\n        run: |\n          nix build .#graphite${{ inputs.debug && '-dev' || '' }}-flatpak-manifest\n\n          rm -rf .flatpak\n          mkdir -p .flatpak\n\n          cp ./result .flatpak/manifest.json\n\n          cd .flatpak\n          mkdir -p repo\n\n          flatpak-builder --user --force-clean --install-deps-from=flathub --repo=repo build ./manifest.json\n\n          flatpak build-bundle repo Graphite.flatpak art.graphite.Graphite --runtime-repo=https://flathub.org/repo/flathub.flatpakrepo\n\n      - name: 📦 Upload Flatpak package\n        uses: actions/upload-artifact@v6\n        with:\n          name: graphite-flatpak\n          path: .flatpak/Graphite.flatpak\n          compression-level: 0\n\n      - name: 💬 Update PR comment with Flatpak artifact link\n        if: github.event_name != 'push' && steps.linux-comment.outputs.comment_id\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          ARTIFACT_ID=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --jq '.artifacts[] | select(.name == \"graphite-flatpak\") | .id')\n          ARTIFACT_URL=\"https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts/$ARTIFACT_ID\"\n          COMMENT_ID=\"${{ steps.linux-comment.outputs.comment_id }}\"\n          if [ -n \"$ARTIFACT_ID\" ]; then\n            EXISTING_BODY=$(gh api repos/${{ github.repository }}/issues/comments/$COMMENT_ID --jq '.body')\n            BODY=\"$EXISTING_BODY\"$'\\n'\n            BODY+=\"| [Download Flatpak]($ARTIFACT_URL) |\"\n            gh api repos/${{ github.repository }}/issues/comments/$COMMENT_ID -X PATCH -f body=\"$BODY\"\n          fi\n"
  },
  {
    "path": ".github/workflows/cargo-deny.yml",
    "content": "name: \"Audit Security Advisories\"\n\non:\n  # Run once each week\n  schedule:\n    - cron: \"0 0 * * 0\"\n\njobs:\n  cargo-deny:\n    if: github.repository == 'GraphiteEditor/Graphite' # Don't run on forks by default\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: 📥 Clone repository\n        uses: actions/checkout@v6\n\n      - name: 🔒 Check crate security advisories for root workspace\n        uses: EmbarkStudios/cargo-deny-action@v2\n        with:\n          command: check advisories\n\n      - name: 🔒 Check crate security advisories for /libraries/rawkit\n        uses: EmbarkStudios/cargo-deny-action@v2\n        with:\n          command: check advisories\n          manifest-path: libraries/rawkit/Cargo.toml\n"
  },
  {
    "path": ".github/workflows/check.yml",
    "content": "name: \"Check\"\n\non:\n  pull_request: {}\n  merge_group: {}\n\nenv:\n  CARGO_TERM_COLOR: always\n\njobs:\n  # Check if CI can be skipped (for merge queue deduplication)\n  skip-check:\n    runs-on: ubuntu-latest\n    outputs:\n      skip: ${{ steps.check.outputs.skip-check }}\n    steps:\n      - name: 📥 Clone repository\n        uses: actions/checkout@v6\n\n      - name: 🚦 Check if CI can be skipped\n        id: check\n        uses: cariad-tech/merge-queue-ci-skipper@cf80db21fc70244e36487acc531b3f1118889b0a\n\n  # Build the web app via the shared build workflow\n  build:\n    needs: skip-check\n    if: needs.skip-check.outputs.skip != 'true'\n    uses: ./.github/workflows/build.yml\n    secrets: inherit\n    with:\n      web: true\n\n  # Run the Rust tests on the self-hosted native runner\n  test:\n    needs: skip-check\n    if: needs.skip-check.outputs.skip != 'true'\n    runs-on: [self-hosted, target/native]\n    env:\n      RUSTC_WRAPPER: /usr/bin/sccache\n      CARGO_INCREMENTAL: 0\n      SCCACHE_DIR: /var/lib/github-actions/.cache\n    steps:\n      - name: 📥 Clone repository\n        uses: actions/checkout@v6\n\n      - name: 🦀 Install Rust\n        uses: actions-rust-lang/setup-rust-toolchain@v1\n        with:\n          toolchain: stable\n          override: true\n          cache: false\n          rustflags: \"\"\n\n      - name: 🦀 Fetch Rust dependencies\n        run: cargo fetch --locked\n\n      - name: 🧪 Run Rust tests\n        env:\n          RUSTFLAGS: -Dwarnings\n        run: mold -run cargo test --all-features\n\n  # Rust format check on GitHub runner\n  rust-fmt:\n    needs: skip-check\n    if: needs.skip-check.outputs.skip != 'true'\n    runs-on: ubuntu-latest\n    steps:\n      - name: 📥 Clone repository\n        uses: actions/checkout@v6\n\n      - name: 🦀 Install Rust\n        uses: actions-rust-lang/setup-rust-toolchain@v1\n        with:\n          toolchain: stable\n          override: true\n          cache: false\n          rustflags: \"\"\n          components: rustfmt\n\n      - name: 🔬 Check Rust formatting\n        run: cargo fmt --all -- --check\n\n  # License compatibility check on GitHub runner\n  check-licenses:\n    needs: skip-check\n    if: needs.skip-check.outputs.skip != 'true'\n    runs-on: ubuntu-latest\n    steps:\n      - name: 📥 Clone repository\n        uses: actions/checkout@v6\n\n      - name: 📜 Check crate license compatibility for root workspace\n        uses: EmbarkStudios/cargo-deny-action@v2\n        with:\n          command: check bans licenses sources\n\n      - name: 📜 Check crate license compatibility for /libraries/rawkit\n        uses: EmbarkStudios/cargo-deny-action@v2\n        with:\n          command: check bans licenses sources\n          manifest-path: libraries/rawkit/Cargo.toml\n"
  },
  {
    "path": ".github/workflows/comment-!build-commands.yml",
    "content": "# USAGE:\n# After reviewing the code, core team members may comment on a PR with `!build` followed by optional `<target>` and `<profile>` arguments.\n# This matches the syntax of the `cargo run build` CLI command, but allows platforms to be specified.\n#\n# `<target>`: `web` (default), `desktop` (all platforms), or `desktop:<platforms>` (subset of `windows+mac+linux`)\n# `<profile>`: `release` (default) or `debug`\n#\n# Examples:\n# - !build\n# - !build debug\n# - !build desktop\n# - !build desktop:windows+mac\n# - !build desktop:linux debug\nname: \"!build PR Command\"\n\non:\n  issue_comment:\n    types:\n      - created\n\njobs:\n  setup:\n    # Command should be limited to core team members (those in the organization) for security.\n    # From the GitHub Actions docs:\n    # author_association = 'MEMBER': Author is a member of the organization that owns the repository.\n    if: >\n      github.event.issue.pull_request &&\n      github.event.comment.author_association == 'MEMBER' &&\n      startsWith(github.event.comment.body, '!build')\n    runs-on: ubuntu-latest\n    permissions:\n      pull-requests: write\n    outputs:\n      repo: ${{ steps.pr_info.outputs.repo }}\n      ref: ${{ steps.pr_info.outputs.ref }}\n      web: ${{ steps.pr_info.outputs.web }}\n      windows: ${{ steps.pr_info.outputs.windows }}\n      mac: ${{ steps.pr_info.outputs.mac }}\n      linux: ${{ steps.pr_info.outputs.linux }}\n      debug: ${{ steps.pr_info.outputs.debug }}\n\n    steps:\n      - name: 🔎 Parse command, find branch, and set build flags\n        id: pr_info\n        run: |\n          COMMENT=\"${{ github.event.comment.body }}\"\n\n          # Split into space-separated words\n          read -ra WORDS <<< \"$COMMENT\"\n\n          # First word must be \"!build\"\n          if [[ \"${WORDS[0]}\" != \"!build\" ]]; then\n            echo \"::error::Expected comment to start with !build\"\n            exit 1\n          fi\n\n          # Initialize build flags (web defaults to true, matching the CLI default target)\n          WEB=\"true\"\n          WINDOWS=\"false\"\n          MAC=\"false\"\n          LINUX=\"false\"\n          DEBUG=\"false\"\n\n          # Parse target (optional, defaults to `web` if omitted)\n          IDX=1\n          case \"${WORDS[$IDX]:-}\" in\n            # Target: `web` enables just the web build (already the default, but accepted explicitly)\n            \"web\")\n              ((IDX++)) ;;\n            # Target: `desktop` enables all three desktop platforms\n            \"desktop\")\n              WEB=\"false\"; WINDOWS=\"true\"; MAC=\"true\"; LINUX=\"true\"; ((IDX++)) ;;\n            # Target: `desktop:<platforms>` enables a subset of desktop platforms, split by `+`\n            desktop:*)\n              WEB=\"false\"\n              PLATFORMS=\"${WORDS[$IDX]#desktop:}\"\n              IFS='+' read -ra PARTS <<< \"$PLATFORMS\"\n              for PART in \"${PARTS[@]}\"; do\n                case \"$PART\" in\n                  \"windows\") WINDOWS=\"true\" ;;\n                  \"mac\")     MAC=\"true\" ;;\n                  \"linux\")   LINUX=\"true\" ;;\n                  *) echo \"::error::Unrecognized platform: $PART\"; exit 1 ;;\n                esac\n              done\n              ((IDX++))\n              ;;\n          esac\n\n          # Parse profile (optional, defaults to `release` if omitted)\n          case \"${WORDS[$IDX]:-}\" in\n            \"debug\")   DEBUG=\"true\"; ((IDX++)) ;;\n            \"release\") ((IDX++)) ;;\n          esac\n\n          # Reject any unexpected trailing words\n          if [[ $IDX -lt ${#WORDS[@]} ]]; then\n            echo \"::error::Unexpected argument: ${WORDS[$IDX]}\"\n            exit 1\n          fi\n\n          # Write parsed build flags to job outputs\n          echo \"web=$WEB\" >> $GITHUB_OUTPUT\n          echo \"windows=$WINDOWS\" >> $GITHUB_OUTPUT\n          echo \"mac=$MAC\" >> $GITHUB_OUTPUT\n          echo \"linux=$LINUX\" >> $GITHUB_OUTPUT\n          echo \"debug=$DEBUG\" >> $GITHUB_OUTPUT\n\n          # Fetch the PR's head branch and repo (needed for forked PRs where the code lives in another repo)\n          RESPONSE=$(curl -L -H 'Accept: application/vnd.github+json' -H 'X-GitHub-Api-Version: 2022-11-28' https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.issue.number }})\n          echo \"repo=$(echo $RESPONSE | jq -r '.head.repo.full_name')\" >> $GITHUB_OUTPUT\n          echo \"ref=$(echo $RESPONSE | jq -r '.head.ref')\" >> $GITHUB_OUTPUT\n\n      - name: 💬 Edit comment with workflow run link\n        uses: actions/github-script@v8\n        with:\n          script: |\n            github.rest.issues.updateComment({\n              comment_id: ${{ github.event.comment.id }},\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              body: '${{ github.event.comment.body }} ([Run ID ' + context.runId + '](https://github.com/GraphiteEditor/Graphite/actions/runs/' + context.runId + '))'\n            });\n\n  invoke-build:\n    needs: setup\n    uses: ./.github/workflows/build.yml\n    secrets: inherit\n    with:\n      web: ${{ needs.setup.outputs.web == 'true' }}\n      windows: ${{ needs.setup.outputs.windows == 'true' }}\n      mac: ${{ needs.setup.outputs.mac == 'true' }}\n      linux: ${{ needs.setup.outputs.linux == 'true' }}\n      debug: ${{ needs.setup.outputs.debug == 'true' }}\n      checkout_repo: ${{ needs.setup.outputs.repo }}\n      checkout_ref: ${{ needs.setup.outputs.ref }}\n      pr_number: ${{ github.event.issue.number }}\n"
  },
  {
    "path": ".github/workflows/comment-clippy-warnings.yaml",
    "content": "name: \"Clippy Check\"\n\non:\n  pull_request:\n    types: [opened, reopened, synchronize, ready_for_review]\n\njobs:\n  clippy:\n    name: Run Clippy\n    runs-on: ubuntu-latest\n    # TODO(Keavon): Find a workaround (passing the output text to a separate action with permission to read the secrets?) that allows this to work on fork PRs\n    if: false\n    # if: ${{ !github.event.pull_request.draft && !github.event.pull_request.head.repo.fork }}\n    permissions:\n      contents: read\n      pull-requests: write\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Install Rust\n        uses: actions-rs/toolchain@v1\n        with:\n          profile: minimal\n          toolchain: stable\n          override: true\n          components: clippy\n\n      - name: Install deps\n        run: |\n          sudo apt update\n          sudo apt install libgtk-3-dev libsoup2.4-dev libjavascriptcoregtk-4.0-dev libwebkit2gtk-4.0-dev\n\n      - name: Run Clippy\n        id: clippy\n        run: |\n          # Run Clippy and filter output for the root workspace\n          CLIPPY_OUTPUT=$(cargo clippy --all-targets --all-features -- -W clippy::all 2>&1 | grep -vE \"^(\\s*Updating|\\s*Download|\\s*Compiling|\\s*Checking|Finished)\")\n\n          # Run Clippy and filter output for /libraries/rawkit\n          cd libraries/rawkit\n          CLIPPY_OUTPUT+=$'\\n\\n'\n          CLIPPY_OUTPUT+=$(cargo clippy --all-targets --all-features -- -W clippy::all 2>&1 | grep -vE \"^(\\s*Updating|\\s*Download|\\s*Compiling|\\s*Checking|Finished)\")\n          cd ../..\n\n          # Escape special characters for JSON\n          ESCAPED_OUTPUT=$(echo \"$CLIPPY_OUTPUT\" | jq -sR .)\n          echo \"CLIPPY_OUTPUT=$ESCAPED_OUTPUT\" >> $GITHUB_OUTPUT\n          if echo \"$CLIPPY_OUTPUT\" | grep -qE \"^(warning|error)\"; then\n            echo \"CLIPPY_ISSUES_FOUND=true\" >> $GITHUB_OUTPUT\n          else\n            echo \"CLIPPY_ISSUES_FOUND=false\" >> $GITHUB_OUTPUT\n          fi\n\n      - name: Delete previous comments\n        uses: actions/github-script@v8\n        with:\n          github-token: ${{secrets.GITHUB_TOKEN}}\n          script: |\n            const { data: comments } = await github.rest.issues.listComments({\n              issue_number: context.issue.number,\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n            });\n\n            const botComments = comments.filter((comment) =>\n              comment.user.type === 'Bot' && comment.body.includes('Clippy Warnings/Errors')\n            );\n\n            for (const comment of botComments) {\n              await github.rest.issues.deleteComment({\n                owner: context.repo.owner,\n                repo: context.repo.repo,\n                comment_id: comment.id,\n              });\n            }\n\n      - name: Comment PR\n        if: steps.clippy.outputs.CLIPPY_ISSUES_FOUND == 'true'\n        uses: actions/github-script@v8\n        with:\n          github-token: ${{secrets.GITHUB_TOKEN}}\n          script: |\n            const clippy_output = ${{ steps.clippy.outputs.CLIPPY_OUTPUT }};\n            const output = `\n            <details open>\n\n            <summary>Found Clippy warnings</summary>\n\n            #### Clippy Warnings/Errors\n\n            \\`\\`\\`\n            ${clippy_output}\n            \\`\\`\\`\n\n            </details>\n            `;\n\n            github.rest.issues.createComment({\n              issue_number: context.issue.number,\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              body: output\n            })\n"
  },
  {
    "path": ".github/workflows/comment-profiling-changes.yaml",
    "content": "name: \"Profiling Changes\"\n\non:\n  pull_request:\n    paths:\n      - \"node-graph/**\"\n      - \"Cargo.toml\"\n      - \"Cargo.lock\"\n\nenv:\n  CARGO_TERM_COLOR: always\n\njobs:\n  profile:\n    runs-on: ubuntu-latest\n    continue-on-error: true\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n\n      - name: Install Rust\n        uses: dtolnay/rust-toolchain@stable\n\n      - name: Install Valgrind\n        run: |\n          sudo apt update\n          sudo apt install -y valgrind\n\n      - name: Cache Rust dependencies\n        uses: Swatinem/rust-cache@v2\n        with:\n          # Cache on Cargo.lock file\n          cache-on-failure: true\n\n      - name: Cache iai-callgrind binary\n        id: cache-iai\n        uses: actions/cache@v5\n        with:\n          path: ~/.cargo/bin/iai-callgrind-runner\n          key: ${{ runner.os }}-iai-callgrind-runner-0.16.1\n\n      - name: Install iai-callgrind\n        if: steps.cache-iai.outputs.cache-hit != 'true'\n        run: cargo install iai-callgrind-runner@0.16.1\n\n      - name: Checkout master branch\n        run: |\n          git fetch origin master:master\n          git checkout master\n\n      - name: Get master commit SHA\n        id: master-sha\n        run: echo \"sha=$(git rev-parse HEAD)\" >> $GITHUB_OUTPUT\n\n      - name: Get CPU info\n        id: cpu-info\n        run: |\n          # Get CPU model and create a short hash for cache key\n          CPU_MODEL=$(cat /proc/cpuinfo | grep \"model name\" | head -1 | cut -d: -f2 | xargs)\n          CPU_HASH=$(echo \"$CPU_MODEL\" | sha256sum | cut -c1-8)\n          echo \"cpu-hash=$CPU_HASH\" >> $GITHUB_OUTPUT\n          echo \"CPU: $CPU_MODEL (hash: $CPU_HASH)\"\n\n      - name: Cache benchmark baselines\n        id: cache-benchmark-baselines\n        uses: actions/cache@v5\n        with:\n          path: target/iai\n          key: ${{ runner.os }}-${{ runner.arch }}-${{ steps.cpu-info.outputs.cpu-hash }}-benchmark-baselines-master-${{ steps.master-sha.outputs.sha }}\n          restore-keys: |\n            ${{ runner.os }}-${{ runner.arch }}-${{ steps.cpu-info.outputs.cpu-hash }}-benchmark-baselines-master-\n\n      - name: Run baseline benchmarks\n        if: steps.cache-benchmark-baselines.outputs.cache-hit != 'true'\n        run: |\n          # Compile benchmarks\n          cargo bench --bench compile_demo_art_iai -- --save-baseline=master\n\n          # Runtime benchmarks\n          cargo bench --bench update_executor_iai -- --save-baseline=master\n          cargo bench --bench run_once_iai -- --save-baseline=master\n          cargo bench --bench run_cached_iai -- --save-baseline=master\n\n      - name: Checkout PR branch\n        run: git checkout ${{ github.event.pull_request.head.sha }}\n\n      - name: Run PR benchmarks\n        run: |\n          # Compile benchmarks\n          cargo bench --bench compile_demo_art_iai -- --baseline=master --output-format=json | jq -sc | sed 's/\\\\\"//g' > /tmp/compile_output.json\n\n          # Runtime benchmarks  \n          cargo bench --bench update_executor_iai -- --baseline=master --output-format=json | jq -sc | sed 's/\\\\\"//g' > /tmp/update_output.json\n          cargo bench --bench run_once_iai -- --baseline=master --output-format=json | jq -sc | sed 's/\\\\\"//g' > /tmp/run_once_output.json\n          cargo bench --bench run_cached_iai -- --baseline=master --output-format=json | jq -sc | sed 's/\\\\\"//g' > /tmp/run_cached_output.json\n\n      - name: Make old comments collapsed by default\n        # Only run if we have write permissions (not a fork)\n        if: github.event.pull_request.head.repo.full_name == github.repository\n        uses: actions/github-script@v8\n        with:\n          github-token: ${{secrets.GITHUB_TOKEN}}\n          script: |\n            const { data: comments } = await github.rest.issues.listComments({\n              issue_number: context.issue.number,\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n            });\n\n            const botComments = comments.filter((comment) =>\n              comment.user.type === 'Bot' && comment.body.includes('Performance Benchmark Results') && comment.body.includes('<details open>')\n            );\n\n            for (const comment of botComments) {\n              // Edit the comment to remove the \"open\" attribute from the <details> tag\n              await github.rest.issues.updateComment({\n                owner: context.repo.owner,\n                repo: context.repo.repo,\n                comment_id: comment.id,\n                body: comment.body.replace('<details open>', '<details>')\n              });\n            }\n\n      - name: Analyze profiling changes\n        id: analyze\n        uses: actions/github-script@v8\n        with:\n          script: |\n            const fs = require('fs');\n\n            function isSignificantChange(diffPct, absoluteChange, benchmarkType) {\n              const meetsPercentageThreshold = Math.abs(diffPct) > 5;\n              const meetsAbsoluteThreshold = absoluteChange > 200000;\n              const isCachedExecution = benchmarkType === 'run_cached' ||\n                                        benchmarkType.includes('Cached Execution');\n\n              return isCachedExecution\n                ? (meetsPercentageThreshold && meetsAbsoluteThreshold)\n                : meetsPercentageThreshold;\n            }\n\n            const allOutputs = [\n              JSON.parse(fs.readFileSync('/tmp/compile_output.json', 'utf8')),\n              JSON.parse(fs.readFileSync('/tmp/update_output.json', 'utf8')),\n              JSON.parse(fs.readFileSync('/tmp/run_once_output.json', 'utf8')),\n              JSON.parse(fs.readFileSync('/tmp/run_cached_output.json', 'utf8'))\n            ];\n            const outputNames = ['compile', 'update', 'run_once', 'run_cached'];\n            const sectionTitles = ['Compilation', 'Update', 'Run Once', 'Cached Execution'];\n\n            let hasSignificantChanges = false;\n            let regressionDetails = [];\n\n            for (let i = 0; i < allOutputs.length; i++) {\n              const benchmarkOutput = allOutputs[i];\n              const outputName = outputNames[i];\n              const sectionTitle = sectionTitles[i];\n\n              for (const benchmark of benchmarkOutput) {\n                if (benchmark.profiles?.[0]?.summaries?.parts?.[0]?.metrics_summary?.Callgrind?.Ir?.diffs?.diff_pct) {\n                  const diffPct = parseFloat(benchmark.profiles[0].summaries.parts[0].metrics_summary.Callgrind.Ir.diffs.diff_pct);\n                  const oldValue = benchmark.profiles[0].summaries.parts[0].metrics_summary.Callgrind.Ir.metrics.Both[1].Int;\n                  const newValue = benchmark.profiles[0].summaries.parts[0].metrics_summary.Callgrind.Ir.metrics.Both[0].Int;\n                  const absoluteChange = Math.abs(newValue - oldValue);\n\n                  if (isSignificantChange(diffPct, absoluteChange, outputName)) {\n                    hasSignificantChanges = true;\n                    regressionDetails.push({\n                      module_path: benchmark.module_path,\n                      id: benchmark.id,\n                      diffPct,\n                      absoluteChange,\n                      sectionTitle\n                    });\n                  }\n                }\n              }\n            }\n\n            core.setOutput('has-significant-changes', hasSignificantChanges);\n            core.setOutput('regression-details', JSON.stringify(regressionDetails));\n\n      - name: Comment PR\n        if: github.event.pull_request.head.repo.full_name == github.repository\n        uses: actions/github-script@v8\n        with:\n          github-token: ${{secrets.GITHUB_TOKEN}}\n          script: |\n            const fs = require('fs');\n\n            const compileOutput = JSON.parse(fs.readFileSync('/tmp/compile_output.json', 'utf8'));\n            const updateOutput = JSON.parse(fs.readFileSync('/tmp/update_output.json', 'utf8'));  \n            const runOnceOutput = JSON.parse(fs.readFileSync('/tmp/run_once_output.json', 'utf8'));\n            const runCachedOutput = JSON.parse(fs.readFileSync('/tmp/run_cached_output.json', 'utf8'));\n\n            const hasSignificantChanges = '${{ steps.analyze.outputs.has-significant-changes }}' === 'true';\n            let commentBody = \"\";\n\n            function formatNumber(num) {\n              return String(num).replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\");\n            }\n\n            function formatPercentage(pct) {\n              const sign = pct >= 0 ? '+' : '';\n              return `${sign}${pct.toFixed(2)}%`;\n            }\n\n            function padRight(str, len) {\n              return str.padEnd(len);\n            }\n\n            function padLeft(str, len) {\n              return str.padStart(len);\n            }\n\n            function processBenchmarkOutput(benchmarkOutput, sectionTitle, isLast = false) {\n              let sectionBody = \"\";\n              let hasResults = false;\n              let hasSignificantChanges = false;\n\n              function isSignificantChange(diffPct, absoluteChange, benchmarkType) {\n                const meetsPercentageThreshold = Math.abs(diffPct) > 5;\n                const meetsAbsoluteThreshold = absoluteChange > 200000;\n                const isCachedExecution = benchmarkType === 'run_cached' ||\n                                          benchmarkType.includes('Cached Execution');\n\n                return isCachedExecution\n                  ? (meetsPercentageThreshold && meetsAbsoluteThreshold)\n                  : meetsPercentageThreshold;\n              }\n              \n              for (const benchmark of benchmarkOutput) {\n                if (benchmark.profiles && benchmark.profiles.length > 0) {\n                  const profile = benchmark.profiles[0];\n                  if (profile.summaries && profile.summaries.parts && profile.summaries.parts.length > 0) {\n                    const part = profile.summaries.parts[0];\n                    if (part.metrics_summary && part.metrics_summary.Callgrind && part.metrics_summary.Callgrind.Ir) {\n                      const irData = part.metrics_summary.Callgrind.Ir;\n                      if (irData.diffs && irData.diffs.diff_pct !== null) {\n                        const irDiff = {\n                          diff_pct: parseFloat(irData.diffs.diff_pct),\n                          old: irData.metrics.Both[1].Int,\n                          new: irData.metrics.Both[0].Int\n                        };\n                    hasResults = true;\n                    const changePercentage = formatPercentage(irDiff.diff_pct);\n                    const color = irDiff.diff_pct > 0 ? \"red\" : \"lime\";\n                    \n                    sectionBody += `**${benchmark.module_path} ${benchmark.id}:${benchmark.details}**\\n`;\n                    sectionBody += `Instructions: \\`${formatNumber(irDiff.old)}\\` (master) → \\`${formatNumber(irDiff.new)}\\` (HEAD) : `;\n                    sectionBody += `$$\\\\color{${color}}${changePercentage.replace(\"%\", \"\\\\\\\\%\")}$$\\n\\n`;\n                    \n                    sectionBody += \"<details>\\n<summary>Detailed metrics</summary>\\n\\n```\\n\";\n                    sectionBody += `Baselines:                master|       HEAD\\n`;\n                    \n                    for (const [metricName, metricData] of Object.entries(part.metrics_summary.Callgrind)) {\n                      if (metricData.diffs && metricData.diffs.diff_pct !== null) {\n                        const changePercentage = formatPercentage(parseFloat(metricData.diffs.diff_pct));\n                        const oldValue = metricData.metrics.Both[1].Int || metricData.metrics.Both[1].Float;\n                        const newValue = metricData.metrics.Both[0].Int || metricData.metrics.Both[0].Float;\n                        const line = `${padRight(metricName, 20)} ${padLeft(formatNumber(Math.round(oldValue)), 11)}|${padLeft(formatNumber(Math.round(newValue)), 11)} ${padLeft(changePercentage, 15)}`;\n                        sectionBody += `${line}\\n`;\n                      }\n                    }\n                    \n                    sectionBody += \"```\\n</details>\\n\\n\";\n\n                        if (isSignificantChange(irDiff.diff_pct, Math.abs(irDiff.new - irDiff.old), sectionTitle)) {\n                          significantChanges = true;\n                          hasSignificantChanges = true;\n                        }\n                      }\n                    }\n                  }\n                }\n              }\n              \n              if (hasResults) {\n                // Wrap section in collapsible details, open only if there are significant changes\n                const openAttribute = hasSignificantChanges ? \" open\" : \"\";\n                const ruler = isLast ? \"\" : \"\\n\\n---\";\n                return `<details${openAttribute}>\\n<summary><h2>${sectionTitle}</h2></summary>\\n\\n${sectionBody}${ruler}\\n</details>`;\n              }\n              return \"\";\n            }\n\n            // Process each benchmark category\n            const sections = [\n              { output: compileOutput, title: \"🔧 Graph Compilation\" },\n              { output: updateOutput, title: \"🔄 Executor Update\" },\n              { output: runOnceOutput, title: \"🚀 Render: Cold Execution\" },\n              { output: runCachedOutput, title: \"⚡ Render: Cached Execution\" }\n            ];\n\n            // Generate sections and determine which ones have results\n            const generatedSections = sections.map(({ output, title }) => \n              processBenchmarkOutput(output, title, true) // temporarily mark all as last\n            ).filter(section => section.length > 0);\n\n            // Re-generate with correct isLast flags\n            let sectionIndex = 0;\n            const finalSections = sections.map(({ output, title }) => {\n              const section = processBenchmarkOutput(output, title, true); // check if it has results\n              if (section.length > 0) {\n                const isLast = sectionIndex === generatedSections.length - 1;\n                sectionIndex++;\n                return processBenchmarkOutput(output, title, isLast);\n              }\n              return \"\";\n            }).filter(section => section.length > 0);\n\n            // Combine all sections\n            commentBody = finalSections.join(\"\\n\\n\");\n\n            if (commentBody.length > 0) {\n              const output = `<details open>\\n<summary>Performance Benchmark Results</summary>\\n\\n${commentBody}\\n</details>`;\n\n              if (hasSignificantChanges) {\n                github.rest.issues.createComment({\n                  issue_number: context.issue.number,\n                  owner: context.repo.owner,\n                  repo: context.repo.repo,\n                  body: output\n                });\n              } else {\n                console.log(\"No significant performance changes detected. Skipping comment.\");\n                console.log(output);\n              }\n            } else {\n              console.log(\"No benchmark results to display.\");\n            }\n\n      - name: Fail on significant regressions\n        if: steps.analyze.outputs.has-significant-changes == 'true'\n        uses: actions/github-script@v8\n        with:\n          script: |\n            const regressionDetails = JSON.parse('${{ steps.analyze.outputs.regression-details }}');\n            const firstRegression = regressionDetails[0];\n\n            core.setFailed(`Significant performance regression detected: ${firstRegression.module_path} ${firstRegression.id} increased by ${firstRegression.absoluteChange.toLocaleString()} instructions (${firstRegression.diffPct.toFixed(2)}%)`);\n"
  },
  {
    "path": ".github/workflows/library-rawkit.yml",
    "content": "name: \"Library: Rawkit\"\n\non:\n  push:\n    branches:\n      - master\n    paths:\n      - \"libraries/rawkit/**\"\n  pull_request:\n    paths:\n      - \"libraries/rawkit/**\"\n\nenv:\n  CARGO_TERM_COLOR: always\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n    env:\n      SCCACHE_GHA_ENABLED: \"true\"\n      RUSTC_WRAPPER: \"sccache\"\n      CARGO_INCREMENTAL: 0\n      SCCACHE_DIR: /var/lib/github-actions/.cache\n\n    steps:\n      - name: 📥 Clone repository\n        uses: actions/checkout@v6\n\n      - name: 🦀 Install Rust\n        uses: actions-rust-lang/setup-rust-toolchain@v1\n        with:\n          toolchain: stable\n          override: true\n          cache: false\n          rustflags: \"\"\n\n      - name: 📦 Run sccache-cache\n        uses: mozilla-actions/sccache-action@v0.0.6\n        continue-on-error: true\n\n      - name: 🔧 Fallback if sccache fails\n        if: failure()\n        run: |\n          echo \"sccache failed, disabling it\"\n          echo \"RUSTC_WRAPPER=\" >> $GITHUB_ENV\n\n      - name: 🔬 Check Rust formatting\n        run: |\n          cd libraries/rawkit\n          cargo fmt --all -- --check\n\n      - name: 🦀 Build Rust code\n        run: |\n          cd libraries/rawkit\n          cargo build --release --all-features\n\n      - name: 🧪 Run Rust tests\n        run: |\n          cd libraries/rawkit\n          cargo test --release --all-features\n\n      - name: 📈 Run sccache stat for check\n        shell: bash\n        run: sccache --show-stats || echo \"sccache stats unavailable\"\n"
  },
  {
    "path": ".github/workflows/provide-shaders.yml",
    "content": "name: \"Provide Shaders\"\n\non:\n  push:\n    branches:\n      - master\n  workflow_dispatch: {}\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n    steps:\n      - name: 📥 Clone repository\n        uses: actions/checkout@v6\n\n      - name: ❄ Install Nix\n        uses: DeterminateSystems/nix-installer-action@main\n\n      - name: 💾 Set up Nix cache\n        uses: DeterminateSystems/magic-nix-cache-action@main\n\n      - name: 🏗 Build graphene raster nodes shaders\n        run: nix build .#graphite-raster-nodes-shaders && cp result raster_nodes_shaders_entrypoint.wgsl\n\n      - name: 📤 Upload graphene raster nodes shaders to artifacts repository\n        run: |\n          bash .github/workflows/scripts/artifact-upload.bash \\\n            ${{ vars.ARTIFACTS_REPO_OWNER }} \\\n            ${{ vars.ARTIFACTS_REPO_NAME }} \\\n            ${{ vars.ARTIFACTS_REPO_BRANCH }} \\\n            rev/${{ github.sha }}/raster_nodes_shaders_entrypoint.wgsl \\\n            raster_nodes_shaders_entrypoint.wgsl \\\n            \"${{ github.sha }} raster_nodes_shaders_entrypoint.wgsl\" \\\n            ${{ secrets.ARTIFACTS_REPO_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/scripts/artifact-upload.bash",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\nusage() {\n  cat <<EOF\nUsage: $0 <owner> <repo> <branch> <target-path> <artifact-file> <commit-message> <github-token>\n\nArguments:\n  owner           : GitHub user or organization of the target repo\n  repo            : Target repo name\n  branch          : Branch name (e.g. main)\n  target-path     : Full path (including folders + filename) in the target repo where to upload\n  artifact-file   : Local file path to upload\n  commit-message  : Commit message for creating/updating the file\n  github-token    : GitHub token (PAT or equivalent) with write access to the target repo\n\nThis will perform a GitHub API PUT to /repos/{owner}/{repo}/contents/{target-path}.\nIf a file already exists at that path, it will auto-detect the SHA and update; otherwise it will create a new one.\nEOF\n  exit 1\n}\n\nif [ $# -ne 7 ]; then\n  usage\nfi\n\nOWNER=\"$1\"\nREPO=\"$2\"\nBRANCH=\"$3\"\nTARGET_PATH=\"$4\"\nARTIFACT_PATH=\"$5\"\nCOMMIT_MSG=\"$6\"\nTOKEN=\"$7\"\n\nif [ ! -f \"$ARTIFACT_PATH\" ]; then\n  echo \"Error: artifact file not found: $ARTIFACT_PATH\" >&2\n  exit 1\nfi\n\nLOCAL_SHA=$(git hash-object \"$ARTIFACT_PATH\")\necho \"Local blob SHA: $LOCAL_SHA\"\n\nGET_URL=\"https://api.github.com/repos/${OWNER}/${REPO}/contents/${TARGET_PATH}?ref=${BRANCH}\"\nGET_RESPONSE=$(curl -s -H \"Authorization: token ${TOKEN}\" \"$GET_URL\")\n\nREMOTE_SHA=$(echo \"$GET_RESPONSE\" | jq -r .sha 2>/dev/null || echo \"\")\n\nif [ \"$REMOTE_SHA\" != \"null\" ] && [ -n \"$REMOTE_SHA\" ]; then\n  echo \"Remote blob SHA: $REMOTE_SHA\"\n  if [ \"$LOCAL_SHA\" = \"$REMOTE_SHA\" ]; then\n    echo \"The remote file is identical. Skipping upload.\"\n    exit 0\n  else\n    echo \"Remote file differs. Preparing to upload.\"\n  fi\nelse\n  echo \"No existing remote file or no SHA found. Creating.\"\nfi\n\nCONTENT_TMP_BASE64=$(mktemp)\nif base64 --help 2>&1 | grep -q -- \"-w\"; then\n  base64 -w 0 \"$ARTIFACT_PATH\" > \"$CONTENT_TMP_BASE64\"\nelse\n  base64 \"$ARTIFACT_PATH\" | tr -d '\\n' > \"$CONTENT_TMP_BASE64\"\nfi\n\nPAYLOAD_TMP=$(mktemp)\njq -n \\\n  --arg message \"$COMMIT_MSG\" \\\n  --arg branch \"$BRANCH\" \\\n  --arg sha \"$REMOTE_SHA\" \\\n  --rawfile content \"$CONTENT_TMP_BASE64\" \\\n  '{\n     message: $message,\n     content: $content,\n     branch: $branch\n   } + (if ($sha != \"\" and $sha != \"null\") then { sha: $sha } else {} end)' \\\n  > \"$PAYLOAD_TMP\"\n\nUPLOAD_RESPONSE=$(curl -s -X PUT \\\n  -H \"Authorization: token ${TOKEN}\" \\\n  -H \"Content-Type: application/json\" \\\n  -d @\"$PAYLOAD_TMP\" \\\n  \"https://api.github.com/repos/${OWNER}/${REPO}/contents/${TARGET_PATH}\")\n\necho \"Upload Response:\"\necho \"$UPLOAD_RESPONSE\"\n\nrm -f \"$CONTENT_TMP_BASE64\" \"$PAYLOAD_TMP\"\n"
  },
  {
    "path": ".github/workflows/website.yml",
    "content": "name: \"Website\"\n\non:\n  workflow_dispatch: {}\n  push:\n    branches:\n      - master\n    paths:\n      - website/**\n  pull_request:\n    paths:\n      - website/**\nenv:\n  CARGO_TERM_COLOR: always\n  INDEX_HTML_HEAD_INCLUSION: <script defer data-domain=\"graphite.art\" data-api=\"/visit/event\" src=\"/visit/script.hash.js\"></script>\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      deployments: write\n      pull-requests: write\n\n    steps:\n      - name: 📥 Clone repository\n        uses: actions/checkout@v6\n\n      - name: 🟢 Install Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version-file: .nvmrc\n\n      - name: 🕸 Install Zola\n        uses: taiki-e/install-action@v2\n        with:\n          tool: zola@0.22.0\n\n      - name: 🔍 Check if `website/other` directory changed\n        uses: dorny/paths-filter@v3\n        id: changes\n        with:\n          filters: |\n            website-other:\n              - \"website/other/**\"\n\n      - name: ✂ Replace template in <head> of index.html\n        run: |\n          # Remove the INDEX_HTML_HEAD_INCLUSION environment variable for build links (not master deploys)\n          git rev-parse --abbrev-ref HEAD | grep master > /dev/null || export INDEX_HTML_HEAD_INCLUSION=\"\"\n\n      - name: 🦀 Produce auto-generated code docs data\n        run: |\n          rustup update stable\n          cargo run -p crate-hierarchy-viz -- website/generated\n          cargo run -p editor-message-tree -- website/generated\n\n      - name: 🔧 Install website npm dependencies\n        run: |\n          cd website\n          npm ci\n\n      - name: 📃 Generate node catalog documentation\n        run: cargo run -p node-docs -- website/content/learn/node-catalog\n\n      - name: 🌐 Build Graphite website with Zola\n        env:\n          MODE: prod\n        run: |\n          cd website\n          npm run check\n          zola --config config.toml build --minify\n\n      - name: 📤 Publish to Cloudflare Pages\n        continue-on-error: true\n        env:\n          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}\n          CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}\n        run: npx wrangler@3 pages deploy \"website/public\" --project-name=\"graphite-website\" --commit-dirty=true\n"
  },
  {
    "path": ".gitignore",
    "content": "branding/\ntarget/\nthird-party-licenses.txt*\nresult/\n.flatpak-builder/\n*.spv\n*.exrc\nperf.data*\nprofile.json\nprofile.json.gz\nflamegraph.svg\n.idea/\n.direnv\n.DS_Store\n"
  },
  {
    "path": ".nix/default.nix",
    "content": "inputs:\n\nlet\n  systems = [\n    \"x86_64-linux\"\n    \"aarch64-linux\"\n  ];\n  forAllSystems = f: inputs.nixpkgs.lib.genAttrs systems (system: f system);\n  args =\n    system:\n    (\n      let\n        lib = inputs.nixpkgs.lib // {\n          call = p: import p args;\n        };\n\n        pkgs = import inputs.nixpkgs {\n          inherit system;\n          overlays = [ (import inputs.rust-overlay) ];\n        };\n\n        info = {\n          pname = \"graphite\";\n          version = \"unstable\";\n          src = inputs.nixpkgs.lib.cleanSourceWith {\n            src = ./..;\n            filter = path: type: !(type == \"directory\" && builtins.baseNameOf path == \".nix\");\n          };\n          cargoVendored = deps.crane.lib.vendorCargoDeps { inherit (info) src; };\n        };\n\n        deps = {\n          crane = lib.call ./deps/crane.nix;\n          cef = lib.call ./deps/cef.nix;\n          rustGPU = lib.call ./deps/rust-gpu.nix;\n        };\n\n        args = {\n          inherit system;\n          inherit (inputs) self;\n          inherit inputs;\n          inherit pkgs;\n          inherit lib;\n          inherit info;\n          inherit deps;\n        }\n        // inputs;\n      in\n      args\n    );\n  withArgs = f: forAllSystems (system: f (args system));\nin\n{\n  packages = withArgs (\n    { lib, ... }:\n    rec {\n      default = graphite;\n      graphite = (lib.call ./pkgs/graphite.nix) { };\n      graphite-dev = (lib.call ./pkgs/graphite.nix) { dev = true; };\n      graphite-raster-nodes-shaders = lib.call ./pkgs/graphite-raster-nodes-shaders.nix;\n      graphite-branding = lib.call ./pkgs/graphite-branding.nix;\n      graphite-bundle = (lib.call ./pkgs/graphite-bundle.nix) { };\n      graphite-dev-bundle = (lib.call ./pkgs/graphite-bundle.nix) { graphite = graphite-dev; };\n      graphite-flatpak-manifest = (lib.call ./pkgs/graphite-flatpak-manifest.nix) { };\n      graphite-dev-flatpak-manifest = (lib.call ./pkgs/graphite-flatpak-manifest.nix) { graphite-bundle = graphite-dev-bundle; };\n\n      # TODO: graphene-cli = lib.call ./pkgs/graphene-cli.nix;\n\n      tools = {\n        third-party-licenses = lib.call ./pkgs/tools/third-party-licenses.nix;\n      };\n    }\n  );\n\n  devShells = withArgs (\n    { lib, ... }:\n    {\n      default = lib.call ./dev.nix;\n    }\n  );\n\n  formatter = withArgs ({ pkgs, ... }: pkgs.nixfmt-tree);\n}\n"
  },
  {
    "path": ".nix/deps/cef.nix",
    "content": "{ pkgs, ... }:\n\nlet\n  cefPath = pkgs.cef-binary.overrideAttrs (finalAttrs: {\n    postInstall = ''\n      rm -r $out/* $out/.* || true\n      strip ./Release/*.so*\n      mv ./Release/* $out/\n      find \"./Resources/locales\" -maxdepth 1 -type f ! -name 'en-US.pak' -delete\n      mv ./Resources/* $out/\n      mv ./include $out/\n\n      cat ./CREDITS.html | ${pkgs.xz}/bin/xz -9 -e -c > $out/CREDITS.html.xz\n\n      echo '${\n        builtins.toJSON {\n          type = \"minimal\";\n          name = builtins.baseNameOf finalAttrs.src.url;\n          sha1 = \"\";\n        }\n      }' > $out/archive.json\n    '';\n  });\nin\n{\n  env.CEF_PATH = cefPath;\n}\n"
  },
  {
    "path": ".nix/deps/crane.nix",
    "content": "{ pkgs, inputs, ... }:\n\n{\n  lib = inputs.crane.mkLib pkgs;\n}\n"
  },
  {
    "path": ".nix/deps/rust-gpu.nix",
    "content": "{ pkgs, ... }:\n\nlet\n  extensions = [\n    \"rust-src\"\n    \"rust-analyzer\"\n    \"clippy\"\n    \"cargo\"\n    \"rustc-dev\"\n    \"llvm-tools\"\n  ];\n  toolchain = pkgs.rust-bin.nightly.\"2025-06-23\".default.override {\n    inherit extensions;\n  };\n  cargo = pkgs.writeShellScriptBin \"cargo\" ''\n    #!${pkgs.lib.getExe pkgs.bash}\n\n    filtered_args=()\n    for arg in \"$@\"; do\n      case \"$arg\" in\n        +nightly|+nightly-*) ;;\n        *) filtered_args+=(\"$arg\") ;;\n      esac\n    done\n\n    exec ${toolchain}/bin/cargo ${\"\\${filtered_args[@]}\"}\n  '';\n  rustc_codegen_spirv =\n    (pkgs.makeRustPlatform {\n      cargo = toolchain;\n      rustc = toolchain;\n    }).buildRustPackage\n      (finalAttrs: {\n        pname = \"rustc_codegen_spirv\";\n        version = \"0-unstable-2025-08-04\";\n        src = pkgs.fetchFromGitHub {\n          owner = \"Firestar99\";\n          repo = \"rust-gpu-new\";\n          rev = \"c12f216121820580731440ee79ebc7403d6ea04f\";\n          hash = \"sha256-rG1cZvOV0vYb1dETOzzbJ0asYdE039UZImobXZfKIno=\";\n        };\n        cargoHash = \"sha256-AEigcEc5wiBd3zLqWN/2HSbkfOVFneAqNvg9HsouZf4=\";\n        cargoBuildFlags = [\n          \"-p\"\n          \"rustc_codegen_spirv\"\n          \"--features=use-compiled-tools\"\n          \"--no-default-features\"\n        ];\n        doCheck = false;\n      });\nin\n{\n  toolchain = toolchain;\n  env = {\n    RUST_GPU_PATH_OVERRIDE = \"${cargo}/bin:${toolchain}/bin\";\n    RUSTC_CODEGEN_SPIRV_PATH = \"${rustc_codegen_spirv}/lib/librustc_codegen_spirv.so\";\n  };\n}\n"
  },
  {
    "path": ".nix/dev.nix",
    "content": "{ pkgs, deps, ... }:\n\nlet\n  libs = [\n    pkgs.wayland\n    pkgs.vulkan-loader\n    pkgs.libGL\n    pkgs.openssl\n    pkgs.libraw\n\n    # X11 Support\n    pkgs.libxkbcommon\n    pkgs.libXcursor\n    pkgs.libxcb\n    pkgs.libX11\n  ];\nin\npkgs.mkShell (\n  {\n    packages = libs ++ [\n      pkgs.pkg-config\n\n      pkgs.lld\n      pkgs.nodejs\n      pkgs.nodePackages.npm\n      pkgs.binaryen\n      pkgs.wasm-bindgen-cli_0_2_100\n      pkgs.wasm-pack\n      pkgs.cargo-about\n\n      pkgs.rustc\n      pkgs.cargo\n      pkgs.rust-analyzer\n      pkgs.clippy\n      pkgs.rustfmt\n\n      pkgs.git\n\n      pkgs.cargo-watch\n      pkgs.cargo-nextest\n      pkgs.cargo-expand\n\n      # Linker\n      pkgs.mold\n\n      # Profiling tools\n      pkgs.gnuplot\n      pkgs.samply\n      pkgs.cargo-flamegraph\n\n      # Plotting tools\n      pkgs.graphviz\n    ];\n\n    LD_LIBRARY_PATH = \"${pkgs.lib.makeLibraryPath libs}:${deps.cef.env.CEF_PATH}\";\n    XDG_DATA_DIRS = \"${pkgs.gsettings-desktop-schemas}/share/gsettings-schemas/${pkgs.gsettings-desktop-schemas.name}:${pkgs.gtk3}/share/gsettings-schemas/${pkgs.gtk3.name}:$XDG_DATA_DIRS\";\n\n    shellHook = ''\n      alias cargo='mold --run cargo'\n    '';\n  }\n  // deps.cef.env\n  // deps.rustGPU.env\n)\n"
  },
  {
    "path": ".nix/pkgs/graphite-branding.nix",
    "content": "{ info, pkgs, ... }:\n\nlet\n  brandingTar = pkgs.fetchurl (\n    let\n      lockContent = builtins.readFile \"${info.src}/.branding\";\n      lines = builtins.filter (s: s != [ ]) (builtins.split \"\\n\" lockContent);\n      url = builtins.elemAt lines 0;\n      hash = builtins.elemAt lines 1;\n    in\n    {\n      url = url;\n      sha256 = hash;\n    }\n  );\nin\npkgs.runCommand \"${info.pname}-branding\" { } ''\n  mkdir -p $out\n  tar -xvf ${brandingTar} -C $out --strip-components 1\n''\n"
  },
  {
    "path": ".nix/pkgs/graphite-bundle.nix",
    "content": "{\n  pkgs,\n  self,\n  system,\n  ...\n}:\n{\n  graphite ? self.packages.${system}.graphite,\n}:\nlet\n  bundle =\n    {\n      archive ? false,\n      compression ? null,\n      passthru ? { },\n    }:\n    (\n      let\n        tar = if compression == null then archive else true;\n        nameArchiveSuffix = if tar then \".tar\" else \"\";\n        nameCompressionSuffix = if compression == null then \"\" else \".\" + compression;\n        name = \"graphite-bundle${nameArchiveSuffix}${nameCompressionSuffix}\";\n        build = ''\n          mkdir -p out\n          mkdir -p out/bin\n          cp ${graphite}/bin/graphite out/bin/graphite\n          chmod -v +w out/bin/graphite\n          patchelf \\\n            --set-rpath '$ORIGIN/../lib:$ORIGIN/../lib/cef' \\\n            --set-interpreter '/lib64/ld-linux-x86-64.so.2' \\\n            --remove-needed libGL.so \\\n            out/bin/graphite\n          cp -r ${graphite}/share out/share\n          mkdir -p out/lib/cef\n          mkdir -p ./cef\n          tar -xvf ${pkgs.cef-binary.src} -C ./cef --strip-components=1\n          cp -r ./cef/Release/* out/lib/cef/\n          cp -r ./cef/Resources/* out/lib/cef/\n          find \"out/lib/cef/locales\" -type f ! -name 'en-US*' -delete\n          ${pkgs.bintools}/bin/strip out/lib/cef/*.so*\n        '';\n        install =\n          if tar then\n            ''\n              cd out\n              tar -c \\\n              --sort=name \\\n              --mtime='@1' --clamp-mtime \\\n              --owner=0 --group=0 --numeric-owner \\\n              --mode='u=rwX,go=rX' \\\n              --format=posix \\\n              --pax-option=delete=atime,delete=ctime \\\n              --no-acls --no-xattrs --no-selinux \\\n              * ${\n                if compression == \"xz\" then\n                  \"| xz \"\n                else if compression == \"gz\" then\n                  \"| gzip -n \"\n                else\n                  \"\"\n              }> $out\n            ''\n          else\n            ''\n              mkdir -p $out\n              cp -r out/* $out/\n            '';\n      in\n\n      pkgs.runCommand name\n        {\n          inherit passthru;\n        }\n        ''\n          ${build}\n          ${install}\n        ''\n    );\nin\nbundle {\n  passthru = {\n    tar = bundle {\n      archive = true;\n      passthru = {\n        gz = bundle {\n          compression = \"gz\";\n        };\n        xz = bundle {\n          compression = \"xz\";\n        };\n      };\n    };\n  };\n}\n"
  },
  {
    "path": ".nix/pkgs/graphite-flatpak-manifest.nix",
    "content": "{\n  pkgs,\n  self,\n  system,\n  ...\n}:\n{\n  graphite-bundle ? self.packages.${system}.graphite-bundle,\n}:\n\n(pkgs.formats.json { }).generate \"art.graphite.Graphite.json\" {\n  app-id = \"art.graphite.Graphite\";\n  runtime = \"org.freedesktop.Platform\";\n  runtime-version = \"25.08\";\n  sdk = \"org.freedesktop.Sdk\";\n  command = \"graphite\";\n  finish-args = [\n    \"--device=dri\"\n    \"--share=ipc\"\n    \"--socket=wayland\"\n    \"--socket=fallback-x11\"\n    \"--share=network\"\n  ];\n  modules = [\n    {\n      name = \"app\";\n      buildsystem = \"simple\";\n      build-commands = [\n        \"mkdir -p /app\"\n        \"cp -r ./* /app/\"\n        \"chmod +x /app/bin/*\"\n      ];\n      sources = [\n        {\n          type = \"archive\";\n          path = graphite-bundle.tar;\n          strip-components = 0;\n        }\n      ];\n    }\n  ];\n}\n"
  },
  {
    "path": ".nix/pkgs/graphite-raster-nodes-shaders.nix",
    "content": "{ info, deps, ... }:\n\n(deps.crane.lib.overrideToolchain (_: deps.rustGPU.toolchain)).buildPackage {\n  pname = \"raster-nodes-shaders\";\n  inherit (info) version src;\n\n  cargoVendorDir = deps.crane.lib.vendorMultipleCargoDeps {\n    inherit (deps.crane.lib.findCargoFiles (deps.crane.lib.cleanCargoSource info.src)) cargoConfigs;\n    cargoLockList = [\n      \"${info.src}/Cargo.lock\"\n      \"${deps.rustGPU.toolchain.availableComponents.rust-src}/lib/rustlib/src/rust/library/Cargo.lock\"\n    ];\n  };\n\n  strictDeps = true;\n\n  env = deps.rustGPU.env;\n\n  buildPhase = ''\n    cargo build -r -p raster-nodes-shaders\n  '';\n\n  installPhase = ''\n    cp target/spirv-builder/spirv-unknown-naga-wgsl/release/deps/raster_nodes_shaders_entrypoint.wgsl $out\n  '';\n\n  doCheck = false;\n}\n"
  },
  {
    "path": ".nix/pkgs/graphite.nix",
    "content": "{\n  info,\n  pkgs,\n  self,\n  deps,\n  system,\n  lib,\n  ...\n}:\n\n{\n  dev ? false,\n}:\n\nlet\n  branding = self.packages.${system}.graphite-branding;\n  cargoVendorDir = deps.crane.lib.vendorCargoDeps { inherit (info) src; };\n  resourcesCommon = {\n    pname = \"${info.pname}-resources\";\n    inherit (info) version src;\n    inherit cargoVendorDir;\n    strictDeps = true;\n    nativeBuildInputs = [\n      pkgs.pkg-config\n      pkgs.lld\n      pkgs.nodejs\n      pkgs.nodePackages.npm\n      pkgs.binaryen\n      pkgs.wasm-bindgen-cli_0_2_100\n      pkgs.wasm-pack\n      pkgs.cargo-about\n    ];\n    buildInputs = [ pkgs.openssl ];\n    env.CARGO_PROFILE = if dev then \"dev\" else \"release\";\n    cargoExtraArgs = \"--target wasm32-unknown-unknown -p graphite-wasm --no-default-features --features native\";\n    doCheck = false;\n  };\n  resources = deps.crane.lib.buildPackage (\n    resourcesCommon\n    // {\n      cargoArtifacts = deps.crane.lib.buildDepsOnly resourcesCommon;\n\n      npmDeps = pkgs.importNpmLock {\n        npmRoot = \"${info.src}/frontend\";\n      };\n\n      npmRoot = \"frontend\";\n      npmConfigScript = \"setup\";\n      makeCacheWritable = true;\n\n      nativeBuildInputs = [\n        pkgs.importNpmLock.npmConfigHook\n        pkgs.removeReferencesTo\n      ]\n      ++ resourcesCommon.nativeBuildInputs;\n\n      prePatch = ''\n        mkdir branding\n        cp -r ${branding}/* branding\n        cp ${info.src}/.branding branding/.branding\n      '';\n\n      buildPhase = ''\n        export HOME=\"$TMPDIR\"\n\n        pushd frontend\n        npm run native:build-${if dev then \"dev\" else \"production\"}\n        popd\n      '';\n\n      installPhase = ''\n        mkdir -p $out\n        cp -r frontend/dist/* $out/\n      '';\n\n      postFixup = ''\n        find \"$out\" -type f -exec remove-references-to -t \"${cargoVendorDir}\" '{}' +\n      '';\n    }\n  );\n  libs = [\n    pkgs.wayland\n    pkgs.vulkan-loader\n    pkgs.libGL\n    pkgs.openssl\n    pkgs.libraw\n\n    # X11 Support\n    pkgs.libxkbcommon\n    pkgs.libXcursor\n    pkgs.libxcb\n    pkgs.libX11\n  ];\n  common = {\n    inherit (info) pname version src;\n    inherit cargoVendorDir;\n    strictDeps = true;\n    buildInputs = libs;\n    nativeBuildInputs = [\n      pkgs.pkg-config\n      pkgs.cargo-about\n      pkgs.removeReferencesTo\n    ];\n    env = deps.cef.env // {\n      CARGO_PROFILE = if dev then \"dev\" else \"release\";\n    };\n    cargoExtraArgs = \"-p graphite-desktop\";\n    doCheck = false;\n  };\nin\n\ndeps.crane.lib.buildPackage (\n  common\n  // {\n    cargoArtifacts = deps.crane.lib.buildDepsOnly common;\n\n    env = common.env // {\n      RASTER_NODES_SHADER_PATH = self.packages.${system}.graphite-raster-nodes-shaders;\n      EMBEDDED_RESOURCES = resources;\n      GRAPHITE_GIT_COMMIT_HASH = self.rev or \"unknown\";\n      GRAPHITE_GIT_COMMIT_DATE = self.lastModified or \"unknown\";\n    };\n\n    npmDeps = pkgs.importNpmLock {\n      npmRoot = \"${info.src}/frontend\";\n    };\n    npmRoot = \"frontend\";\n    nativeBuildInputs = [\n      pkgs.importNpmLock.npmConfigHook\n      pkgs.nodePackages.npm\n    ]\n    ++ common.nativeBuildInputs;\n\n    preBuild = ''\n      ${lib.getExe self.packages.${system}.tools.third-party-licenses}\n    ''\n    + (\n      if self ? rev then\n        ''\n          export GRAPHITE_GIT_COMMIT_DATE=\"$(date -u -d \"@$GRAPHITE_GIT_COMMIT_DATE\" +\"%Y-%m-%dT%H:%M:%SZ\")\"\n        ''\n      else\n        \"\"\n    );\n\n    installPhase = ''\n      mkdir -p $out/bin\n      cp target/${if dev then \"debug\" else \"release\"}/graphite $out/bin/graphite\n\n      mkdir -p $out/share/applications\n      cp $src/desktop/assets/*.desktop $out/share/applications/\n\n      mkdir -p $out/share/icons/hicolor/scalable/apps\n      cp ${branding}/app-icons/graphite.svg $out/share/icons/hicolor/scalable/apps/art.graphite.Graphite.svg\n      mkdir -p $out/share/icons/hicolor/512x512/apps\n      cp ${branding}/app-icons/graphite-512.png $out/share/icons/hicolor/512x512/apps/art.graphite.Graphite.png\n      mkdir -p $out/share/icons/hicolor/256x256/apps\n      cp ${branding}/app-icons/graphite-256.png $out/share/icons/hicolor/256x256/apps/art.graphite.Graphite.png\n      mkdir -p $out/share/icons/hicolor/128x128/apps\n      cp ${branding}/app-icons/graphite-128.png $out/share/icons/hicolor/128x128/apps/art.graphite.Graphite.png\n    '';\n\n    postFixup = ''\n      remove-references-to -t \"${cargoVendorDir}\" $out/bin/graphite\n\n      patchelf \\\n        --set-rpath \"${pkgs.lib.makeLibraryPath libs}:${deps.cef.env.CEF_PATH}\" \\\n        --add-needed libGL.so \\\n        $out/bin/graphite\n    '';\n  }\n)\n"
  },
  {
    "path": ".nix/pkgs/tools/third-party-licenses.nix",
    "content": "{\n  info,\n  deps,\n  pkgs,\n  ...\n}:\n\nlet\n  cargoVendorDir = deps.crane.lib.vendorCargoDeps { inherit (info) src; };\n  common = {\n    pname = \"third-party-licenses\";\n    inherit (info) version src;\n    inherit cargoVendorDir;\n    nativeBuildInputs = [ pkgs.pkg-config ];\n    buildInputs = [ pkgs.openssl ];\n    strictDeps = true;\n    env = deps.cef.env // {\n      CARGO_PROFILE = \"dev\";\n    };\n    cargoExtraArgs = \"-p third-party-licenses --features desktop\";\n    doCheck = false;\n  };\nin\ndeps.crane.lib.buildPackage (\n  common\n  // {\n    inherit cargoVendorDir;\n    cargoArtifacts = deps.crane.lib.buildDepsOnly common;\n    meta.mainProgram = \"third-party-licenses\";\n  }\n)\n"
  },
  {
    "path": ".nvmrc",
    "content": "24\n"
  },
  {
    "path": ".prettierrc",
    "content": "{\n\t\"singleQuote\": false,\n\t\"useTabs\": true,\n\t\"tabWidth\": 4,\n\t\"printWidth\": 200,\n\t\"overrides\": [\n\t\t{\n\t\t\t\"files\": [\n\t\t\t\t\"*.yml\",\n\t\t\t\t\"*.yaml\"\n\t\t\t],\n\t\t\t\"options\": {\n\t\t\t\t\"useTabs\": false,\n\t\t\t\t\"tabWidth\": 2\n\t\t\t}\n\t\t}\n\t]\n}\n"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n\t// NOTE: Keep this in sync with `.devcontainer/devcontainer.json`\n\t\"recommendations\": [\n\t\t// Rust\n\t\t\"rust-lang.rust-analyzer\",\n\t\t\"tamasfe.even-better-toml\",\n\t\t// Web\n\t\t\"dbaeumer.vscode-eslint\",\n\t\t\"svelte.svelte-vscode\",\n\t\t\"vitaliymaz.vscode-svg-previewer\",\n\t\t// Code quality\n\t\t\"wayou.vscode-todo-highlight\",\n\t\t\"streetsidesoftware.code-spell-checker\",\n\t\t// Git\n\t\t\"mhutchie.git-graph\",\n\t\t\"qezhu.gitlink\",\n\t\t// Helpful\n\t\t\"wmaurer.change-case\"\n\t]\n}\n"
  },
  {
    "path": ".vscode/launch.json",
    "content": "{\n\t// Use IntelliSense to learn about possible attributes.\n\t// Hover to view descriptions of existing attributes.\n\t// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387\n\t\"version\": \"0.2.0\",\n\t\"configurations\": [\n\t\t{\n\t\t\t\"type\": \"lldb\",\n\t\t\t\"request\": \"launch\",\n\t\t\t\"name\": \"Graphite debug executable\",\n\t\t\t\"cargo\": {\n\t\t\t\t\"args\": [\n\t\t\t\t\t\"build\",\n\t\t\t\t\t\"--bin=graphite\",\n\t\t\t\t\t\"--package=graphite\",\n\t\t\t\t],\n\t\t\t\t\"filter\": {\n\t\t\t\t\t\"name\": \"graphite\",\n\t\t\t\t\t\"kind\": \"bin\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"args\": [],\n\t\t\t\"cwd\": \"${workspaceFolder}\",\n\t\t\t\"env\": {\n\t\t\t\t\"RUST_LOG\": \"error\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"type\": \"lldb\",\n\t\t\t\"request\": \"launch\",\n\t\t\t\"name\": \"Debug unit tests in executable 'graphite'\",\n\t\t\t\"cargo\": {\n\t\t\t\t\"args\": [\n\t\t\t\t\t\"test\",\n\t\t\t\t\t\"--no-run\",\n\t\t\t\t\t\"--bin=graphite\",\n\t\t\t\t\t\"--package=graphite\",\n\t\t\t\t],\n\t\t\t\t\"filter\": {\n\t\t\t\t\t\"name\": \"graphite\",\n\t\t\t\t\t\"kind\": \"bin\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"args\": [],\n\t\t\t\"cwd\": \"${workspaceFolder}\",\n\t\t},\n\t],\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n\t// Rust: save on format\n\t\"[rust]\": {\n\t\t\"editor.formatOnSave\": true,\n\t\t\"editor.formatOnPaste\": true,\n\t\t\"editor.defaultFormatter\": \"rust-lang.rust-analyzer\"\n\t},\n\t// Web: save on format\n\t\"[javascript][typescript][svelte]\": {\n\t\t\"editor.codeActionsOnSave\": {\n\t\t\t\"source.fixAll.eslint\": \"explicit\"\n\t\t},\n\t\t\"editor.formatOnSave\": true,\n\t\t\"editor.defaultFormatter\": \"dbaeumer.vscode-eslint\"\n\t},\n\t\"[scss]\": {\n\t\t\"editor.codeActionsOnSave\": {\n\t\t\t\"source.fixAll.eslint\": \"explicit\"\n\t\t},\n\t\t\"editor.formatOnSave\": true,\n\t\t// Configured in `.prettierrc`\n\t\t\"editor.defaultFormatter\": \"esbenp.prettier-vscode\"\n\t},\n\t\"[json][jsonc][yaml][github-actions-workflow]\": {\n\t\t\"editor.formatOnSave\": true,\n\t\t// Configured in `.prettierrc`\n\t\t\"editor.defaultFormatter\": \"esbenp.prettier-vscode\"\n\t},\n\t// Website: don't format Zola/Tera-templated HTML on save\n\t\"[html]\": {\n\t\t\"editor.formatOnSave\": false\n\t},\n\t// Handlebars: don't save on format\n\t// (`about.hbs` is used by Cargo About to encode license information)\n\t\"[handlebars]\": {\n\t\t\"editor.formatOnSave\": false\n\t},\n\t// Rust Analyzer config\n\t\"rust-analyzer.check.command\": \"clippy\",\n\t\"rust-analyzer.cargo.allTargets\": false,\n\t\"rust-analyzer.procMacro.ignored\": {\n\t\t\"serde_derive\": [\"Serialize\", \"Deserialize\"]\n\t},\n\t// ESLint config\n\t\"eslint.format.enable\": true,\n\t\"eslint.workingDirectories\": [\"./frontend\", \"./website\"],\n\t\"eslint.validate\": [\"javascript\", \"typescript\", \"svelte\"],\n\t// Git Graph config\n\t\"git-graph.repository.fetchAndPrune\": true,\n\t\"git-graph.repository.showRemoteHeads\": false,\n\t\"git-graph.repository.commits.fetchAvatars\": true,\n\t// VS Code Git config\n\t\"git.autofetch\": true,\n\t\"git.enableStatusBarSync\": false,\n\t\"git.showActionButton\": {\n\t\t\"sync\": false\n\t},\n\t// CSpell config\n\t\"cSpell.language\": \"en-US\",\n\t\"cSpell.logLevel\": \"Information\",\n\t\"cSpell.allowCompoundWords\": true,\n\t// Other extensions config\n\t\"evenBetterToml.formatter.alignComments\": false,\n\t\"package-json-upgrade.ignorePatterns\": [\"source-sans-pro\"],\n\t// VS Code config\n\t\"html.format.wrapLineLength\": 200,\n\t\"files.eol\": \"\\n\",\n\t\"files.insertFinalNewline\": true,\n\t\"files.associations\": {\n\t\t\"*.graphite\": \"json\"\n\t},\n\t\"editor.renderWhitespace\": \"boundary\",\n\t\"editor.minimap.markSectionHeaderRegex\": \"// ===+\\\\n\\\\s*//\\\\s*(?<label>[^\\\\n]{1,18})[^\\\\n]*(\\\\n\\\\s*//[^\\\\n]*)*\\\\n\\\\s*// ===+\"\n}\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[workspace]\nmembers = [\n\t\"desktop\",\n\t\"desktop/wrapper\",\n\t\"desktop/embedded-resources\",\n\t\"desktop/bundle\",\n\t\"desktop/platform/linux\",\n\t\"desktop/platform/mac\",\n\t\"desktop/platform/win\",\n\t\"editor\",\n\t\"frontend/wasm\",\n\t\"libraries/dyn-any\",\n\t\"libraries/math-parser\",\n\t\"node-graph/libraries/*\",\n\t\"node-graph/nodes/*\",\n\t\"node-graph/nodes/raster/shaders\",\n\t\"node-graph/nodes/raster/shaders/entrypoint\",\n\t\"node-graph/graph-craft\",\n\t\"node-graph/graphene-cli\",\n\t\"node-graph/nodes/gstd\",\n\t\"node-graph/interpreted-executor\",\n\t\"node-graph/node-macro\",\n\t\"node-graph/preprocessor\",\n\t\"proc-macros\",\n\t\"tools/cargo-run\",\n\t\"tools/crate-hierarchy-viz\",\n\t\"tools/third-party-licenses\",\n\t\"tools/editor-message-tree\",\n\t\"tools/node-docs\",\n]\ndefault-members = [\n\t\"editor\",\n\t\"frontend/wasm\",\n\t\"libraries/dyn-any\",\n\t\"libraries/math-parser\",\n\t\"node-graph/graph-craft\",\n\t\"node-graph/interpreted-executor\",\n\t\"node-graph/node-macro\",\n\t\"node-graph/preprocessor\",\n\t# blocked by https://github.com/rust-lang/cargo/issues/16000\n#\t\"proc-macros\",\n\t\"tools/cargo-run\",\n]\nresolver = \"2\"\n\n[workspace.package]\nrust-version = \"1.88\"\nedition = \"2024\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\nhomepage = \"https://graphite.art\"\nrepository = \"https://github.com/GraphiteEditor/Graphite\"\nlicense = \"Apache-2.0\"\nversion = \"0.0.0\"\nreadme = \"README.md\"\npublish = false\n\n[workspace.dependencies]\n# Local dependencies\ndyn-any = { path = \"libraries/dyn-any\", features = [\n\t\"derive\",\n\t\"glam\",\n\t\"reqwest\",\n\t\"log-bad-types\",\n\t\"rc\",\n] }\npreprocessor = { path = \"node-graph/preprocessor\" }\nmath-parser = { path = \"libraries/math-parser\" }\ngraphene-application-io = { path = \"node-graph/libraries/application-io\" }\ncore-types = { path = \"node-graph/libraries/core-types\" }\nno-std-types = { path = \"node-graph/libraries/no-std-types\" }\nraster-types = { path = \"node-graph/libraries/raster-types\" }\nvector-types = { path = \"node-graph/libraries/vector-types\" }\ngraphic-types = { path = \"node-graph/libraries/graphic-types\" }\nrendering = { path = \"node-graph/libraries/rendering\" }\nbrush-nodes = { path = \"node-graph/nodes/brush\" }\nblending-nodes = { path = \"node-graph/nodes/blending\" }\ngraphene-core = { path = \"node-graph/nodes/gcore\" }\ngraphic-nodes = { path = \"node-graph/nodes/graphic\" }\ntext-nodes = { path = \"node-graph/nodes/text\" }\ntransform-nodes = { path = \"node-graph/nodes/transform\" }\nvector-nodes = { path = \"node-graph/nodes/vector\" }\nrepeat-nodes = { path = \"node-graph/nodes/repeat\" }\nmath-nodes = { path = \"node-graph/nodes/math\" }\npath-bool-nodes = { path = \"node-graph/nodes/path-bool\" }\ngraph-craft = { path = \"node-graph/graph-craft\" }\nraster-nodes = { path = \"node-graph/nodes/raster\" }\ngraphene-std = { path = \"node-graph/nodes/gstd\" }\ninterpreted-executor = { path = \"node-graph/interpreted-executor\" }\nnode-macro = { path = \"node-graph/node-macro\" }\nwgpu-executor = { path = \"node-graph/libraries/wgpu-executor\" }\ngraphite-proc-macros = { path = \"proc-macros\" }\n\n# Workspace dependencies\nrustc-hash = \"2.0\"\nbytemuck = { version = \"1.13\", features = [\"derive\", \"min_const_generics\"] }\nserde = { version = \"1.0\", features = [\"derive\", \"rc\"] }\nserde_json = \"1.0\"\nserde_bytes = \"0.11\"\nserde-wasm-bindgen = \"0.6\"\nreqwest = { version = \"0.13\", features = [\"blocking\", \"json\"] }\nfutures = \"0.3\"\nenv_logger = \"0.11\"\nlog = \"0.4\"\nbitflags = { version = \"2.4\", features = [\"serde\"] }\nctor = \"0.2\"\nconvert_case = \"0.8\"\nindoc = \"2.0.5\"\nderivative = \"2.2\"\nthiserror = \"2\"\nanyhow = \"1.0\"\nproc-macro2 = { version = \"1\", features = [\"span-locations\"] }\nquote = \"1.0\"\nchrono = \"0.4\"\nron = \"0.12\"\nfastnoise-lite = \"1.1\"\nwgpu = { version = \"27.0\", features = [\n\t# We don't have wgpu on multiple threads (yet) https://github.com/gfx-rs/wgpu/blob/trunk/CHANGELOG.md#wgpu-types-now-send-sync-on-wasm\n\t\"fragile-send-sync-non-atomic-wasm\",\n\t\"spirv\",\n\t\"strict_asserts\",\n] }\nonce_cell = \"1.13\" # Remove and replace with `core::cell::LazyCell` (<https://doc.rust-lang.org/core/cell/struct.LazyCell.html>)\nwasm-bindgen = \"=0.2.100\" # NOTICE: ensure this stays in sync with the `wasm-bindgen-cli` version in `website/content/volunteer/guide/project-setup/_index.md`. We pin this version because wasm-bindgen upgrades may break various things.\nwasm-bindgen-futures = \"0.4\"\njs-sys = \"=0.3.77\"\nweb-sys = { version = \"=0.3.77\", features = [\n\t\"Document\",\n\t\"DomRect\",\n\t\"Element\",\n\t\"HtmlCanvasElement\",\n\t\"CanvasRenderingContext2d\",\n\t\"CanvasPattern\",\n\t\"OffscreenCanvas\",\n\t\"OffscreenCanvasRenderingContext2d\",\n\t\"TextMetrics\",\n\t\"Window\",\n\t\"IdleRequestOptions\",\n\t\"ImageData\",\n\t\"Navigator\",\n\t\"Gpu\",\n\t\"HtmlImageElement\",\n\t\"ImageBitmapRenderingContext\",\n] }\nwinit = { git = \"https://github.com/rust-windowing/winit.git\" }\nkeyboard-types = \"0.8\"\nurl = \"2.5\"\ntokio = { version = \"1.29\", features = [\"fs\", \"macros\", \"io-std\", \"rt\", \"rt-multi-thread\"] }\n# Linebender ecosystem (BEGIN)\nkurbo = { version = \"0.13\", features = [\"serde\"] }\nvello = \"0.7\"\nvello_encoding = \"0.7\"\nresvg = \"0.47\"\nusvg = \"0.47\"\nparley = \"0.6\"\nskrifa = \"0.40\"\npolycool = \"0.4\"\n# Linebender ecosystem (END)\nrand = { version = \"0.9\", default-features = false, features = [\"std_rng\"] }\nrand_chacha = \"0.9\"\nglam = { version = \"0.29\", default-features = false, features = [\n\t\"nostd-libm\",\n\t\"scalar-math\",\n\t\"bytemuck\",\n] }\nbase64 = \"0.22\"\nimage = { version = \"0.25\", default-features = false, features = [\n\t\"png\",\n\t\"jpeg\",\n\t\"bmp\",\n\t\"gif\",\n] }\npretty_assertions = \"1.4\"\nfern = { version = \"0.7\", features = [\"colored\"] }\nnum_enum = { version = \"0.7\", default-features = false }\nnum-derive = \"0.4\"\nnum-traits = { version = \"0.2\", default-features = false, features = [\"libm\"] }\ntsify = { version = \"0.5\", default-features = false, features = [\"js\"] }\nsyn = { version = \"2.0\", default-features = false, features = [\n\t\"full\",\n\t\"derive\",\n\t\"parsing\",\n\t\"printing\",\n\t\"visit-mut\",\n\t\"visit\",\n\t\"clone-impls\",\n\t\"extra-traits\",\n\t\"proc-macro\",\n] }\nlyon_geom = \"1.0\"\npetgraph = { version = \"0.7\", default-features = false, features = [\"graphmap\"] }\nhalf = { version = \"2.4\", default-features = false, features = [\"bytemuck\"] }\ntinyvec = { version = \"1\", features = [\"std\"] }\ncriterion = { version = \"0.7\", features = [\"html_reports\"] }\niai-callgrind = { version = \"0.16\" }\nndarray = \"0.16\"\nstrum = { version = \"0.27\", features = [\"derive\"] }\ndirs = \"6.0\"\ncef = \"142\"\ncef-dll-sys = \"142\"\ninclude_dir = \"0.7\"\ntracing-subscriber = { version = \"0.3\", features = [\"env-filter\"] }\ntracing = \"0.1\"\nrfd = \"0.15\"\nopen = \"5.3\"\nspin = \"0.10\"\nclap = \"4.5\"\nspirv-std = { git = \"https://github.com/Firestar99/rust-gpu-new\", rev = \"c12f216121820580731440ee79ebc7403d6ea04f\", features = [\"bytemuck\"] }\ncargo-gpu = { git = \"https://github.com/Firestar99/cargo-gpu\", rev = \"3952a22d16edbd38689f3a876e417899f21e1fe7\", default-features = false }\nqrcodegen = \"1.8\"\nlzma-rust2 = { version = \"0.16\", default-features = false, features = [\"std\", \"encoder\", \"optimization\", \"xz\"] }\nscraper = \"0.25\"\nlinesweeper = \"0.3\"\nsmallvec = \"1.13.2\"\n\n[workspace.lints.rust]\nunexpected_cfgs = { level = \"allow\", check-cfg = ['cfg(target_arch, values(\"spirv\"))'] }\n\n[profile.dev]\nopt-level = 1\n\n[profile.dev.package]\nno-std-types = { opt-level = 1 }\ncore-types= { opt-level = 1 }\ninterpreted-executor = { opt-level = 1 } # This is a mitigation for https://github.com/rustwasm/wasm-pack/issues/981 which is needed because the node_registry function is too large\ngraphite-proc-macros = { opt-level = 1 }\nimage = { opt-level = 2 }\nrustc-hash = { opt-level = 3 }\nserde_derive = { opt-level = 1 }\nsyn = { opt-level = 1 }\nnode-macro = { opt-level = 2 }\n\n[profile.release]\nlto = \"thin\"\ndebug = true\n\n[patch.crates-io]\n# Force cargo to use only one version of the dpi crate (vendoring breaks without this)\ndpi = { git = \"https://github.com/rust-windowing/winit.git\" }\ndownload-cef = { git = \"https://github.com/timon-schelling/cef-rs.git\", branch = \"graphite\" }\nvello = { git = \"https://github.com/Keavon/vello.git\", branch = \"0.7.0-fix-images-rendering-blurry\" }\nvello_encoding = { git = \"https://github.com/Keavon/vello.git\", branch = \"0.7.0-fix-images-rendering-blurry\" }\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "\n\n<a href=\"https://graphite.art/\">\n<picture>\n<source media=\"(prefers-color-scheme: dark)\" srcset=\"https://github.com/user-attachments/assets/9366c148-4405-484f-909a-9a3526eb9209\">\n<source media=\"(prefers-color-scheme: light)\" srcset=\"https://github.com/user-attachments/assets/791508ab-bcd5-4e31-a3b9-1187cfd7a2f6\">\n<img alt=\"Graphite logo\" src=\"https://github.com/user-attachments/assets/791508ab-bcd5-4e31-a3b9-1187cfd7a2f6\">\n</picture>\n</a>\n\n# Your procedural toolbox for 2D content creation\n\n**Graphite is a free, open source vector and raster graphics engine, [available now](https://editor.graphite.art) in alpha. Get creative with a fully nondestructive editing workflow that combines layer-based compositing with node-based generative design.**\n\nHaving begun life as a vector editor, Graphite continues evolving into a generalized, all-in-one graphics toolbox that's built more like a game engine than a conventional creative app. The editor's tools wrap its node graph core, providing user-friendly workflows for vector, raster, and beyond. Photo editing, motion graphics, digital painting, desktop publishing, and VFX compositing are additional competencies on the planned [roadmap](https://graphite.art/features/#roadmap) making Graphite into a highly versatile content creation tool.\n\nLearn more from the [website](https://graphite.art/), subscribe to the [newsletter](https://graphite.art/#newsletter), consider [volunteering](https://graphite.art/volunteer/) or [donating](https://graphite.art/donate/), and remember to give this repository a ⭐!\n\n<br />\n<a href=\"https://discord.graphite.art/\">\n<picture>\n<source media=\"(prefers-color-scheme: dark)\" srcset=\"https://github.com/user-attachments/assets/ad185fac-3b48-446d-863c-2bcb0724abee\">\n<source media=\"(prefers-color-scheme: light)\" srcset=\"https://github.com/user-attachments/assets/aa23f503-f3bf-444a-9080-8eaa19fa2fa8\">\n<img alt=\"Discord\" src=\"https://github.com/user-attachments/assets/aa23f503-f3bf-444a-9080-8eaa19fa2fa8\" width=\"48\" height=\"48\">\n</picture>\n</a>\n&nbsp;&nbsp;&nbsp;&nbsp;\n<a href=\"https://www.reddit.com/r/graphite/\">\n<picture>\n<source media=\"(prefers-color-scheme: dark)\" srcset=\"https://github.com/user-attachments/assets/d8c05686-2eb9-4ac1-8149-728c12b4e71a\">\n<source media=\"(prefers-color-scheme: light)\" srcset=\"https://github.com/user-attachments/assets/6f32329a-4d6f-42d8-9a2f-42977c0b3c05\">\n<img alt=\"Reddit\" src=\"https://github.com/user-attachments/assets/6f32329a-4d6f-42d8-9a2f-42977c0b3c05\" width=\"48\" height=\"48\">\n</picture>\n</a>\n&nbsp;&nbsp;&nbsp;&nbsp;\n<a href=\"https://bsky.app/profile/graphiteeditor.bsky.social\">\n<picture>\n<source media=\"(prefers-color-scheme: dark)\" srcset=\"https://github.com/user-attachments/assets/c736d80c-e9bf-4591-a7e0-a7723057a906\">\n<source media=\"(prefers-color-scheme: light)\" srcset=\"https://github.com/user-attachments/assets/3db9b0a1-5ab7-4bff-bfd3-8a4ade7b98bd\">\n<img alt=\"Bluesky\" src=\"https://github.com/user-attachments/assets/3db9b0a1-5ab7-4bff-bfd3-8a4ade7b98bd\" width=\"48\" height=\"48\">\n</picture>\n</a>\n&nbsp;&nbsp;&nbsp;&nbsp;\n<a href=\"https://twitter.com/graphiteeditor\">\n<picture>\n<source media=\"(prefers-color-scheme: dark)\" srcset=\"https://github.com/user-attachments/assets/115f04cc-e3c2-4f90-ac35-eb9edd3ca9be\">\n<source media=\"(prefers-color-scheme: light)\" srcset=\"https://github.com/user-attachments/assets/4ed4185d-a622-418c-bbf4-a0419e690ca9\">\n<img alt=\"Twitter\" src=\"https://github.com/user-attachments/assets/4ed4185d-a622-418c-bbf4-a0419e690ca9\" width=\"48\" height=\"48\">\n</picture>\n</a>\n&nbsp;&nbsp;&nbsp;&nbsp;\n<a href=\"https://www.youtube.com/@GraphiteEditor\">\n<picture>\n<source media=\"(prefers-color-scheme: dark)\" srcset=\"https://github.com/user-attachments/assets/cbc02fad-5cbc-4715-a8e5-860198e989c7\">\n<source media=\"(prefers-color-scheme: light)\" srcset=\"https://github.com/user-attachments/assets/d13b484d-97a8-4d9e-bbe4-c60348b3f676\">\n<img alt=\"YouTube\" src=\"https://github.com/user-attachments/assets/d13b484d-97a8-4d9e-bbe4-c60348b3f676\" width=\"48\" height=\"48\">\n</picture>\n</a>\n<br /><br />\n\nhttps://github.com/user-attachments/assets/f4604aea-e8f1-45ce-9218-46ddc666f11d\n\n## Support our mission ❤️\n\nGraphite is 100% community built and funded. Please become a part of keeping the project alive and thriving with a [donation](https://graphite.art/donate/) if you share a belief in our **mission**:\n\n> Graphite strives to unshackle the creativity of every budding artist and seasoned professional by building the best comprehensive art and design tool that's accessible to all.\n> \n> Mission success will come when Graphite is an industry standard. A cohesive product vision and focus on innovation over imitation is the strategy that will make that possible.\n\n## Screenshots\n\n![Made using nondestructive boolean operations and procedural polka dot patterns](https://github.com/user-attachments/assets/decb7011-18c2-4c68-82af-d1fa5064244a)\n\n![Mandelbrot fractal filled with a noise pattern, procedurally generated and infinitely scalable](https://github.com/user-attachments/assets/9e023997-185b-4f43-a724-797d308d9e7b)\n\n![Design for a magazine spread, a preview of the upcoming focus on desktop publishing](https://github.com/user-attachments/assets/90eca551-5868-4f8d-9016-33958bf96345)\n\n## Contributing/building the code\n\nAre you a graphics programmer or Rust developer? Graphite aims to be one of the most approachable projects for putting your engineering skills to use in the world of open source. See [instructions here](https://graphite.art/volunteer/guide/) for setting up the project and getting started.\n\n*By submitting code for inclusion in the project, you are agreeing to license your changes under the Apache 2.0 license, and that you have the authority to do so. Some directories may have other licenses, like dual-licensed MIT/Apache 2.0, and code submissions to those directories mean you agree to the applicable license(s).*\n"
  },
  {
    "path": "about.toml",
    "content": "accepted = [\n\t\"Apache-2.0 WITH LLVM-exception\", # Keep this list in sync with those in `/deny.toml`\n\t\"Apache-2.0\", # Keep this list in sync with those in `/deny.toml`\n\t\"BSD-2-Clause\", # Keep this list in sync with those in `/deny.toml`\n\t\"BSD-3-Clause\", # Keep this list in sync with those in `/deny.toml`\n\t\"BSL-1.0\", # Keep this list in sync with those in `/deny.toml`\n\t\"CC0-1.0\", # Keep this list in sync with those in `/deny.toml`\n\t\"CDLA-Permissive-2.0\", # Keep this list in sync with those in `/deny.toml`\n\t\"ISC\", # Keep this list in sync with those in `/deny.toml`\n\t\"MIT-0\", # Keep this list in sync with those in `/deny.toml`\n\t\"MIT\", # Keep this list in sync with those in `/deny.toml`\n\t\"MPL-2.0\", # Keep this list in sync with those in `/deny.toml`\n\t\"OpenSSL\", # Keep this list in sync with those in `/deny.toml`\n\t\"Unicode-3.0\", # Keep this list in sync with those in `/deny.toml`\n\t\"Unicode-DFS-2016\", # Keep this list in sync with those in `/deny.toml`\n\t\"Zlib\", # Keep this list in sync with those in `/deny.toml`\n\t\"NCSA\", # Keep this list in sync with those in `/deny.toml`\n\t\"bzip2-1.0.6\", # Keep this list in sync with those in `/deny.toml`\n\t\"OFL-1.1\", # Keep this list in sync with those in `/deny.toml`\n]\nworkarounds = [\"ring\"]\nignore-build-dependencies = true\nignore-dev-dependencies = true\n# Clearly Defined's API would occasionally (every few months) return errors for at least a full day (maybe some weird rate limiting?), but we can just disable to perform local checking (see #1653)\nno-clearly-defined = true\n\n# https://raw.githubusercontent.com/briansmith/webpki/main/LICENSE\n# is the ISC license but test code within the repo is BSD-3-Clause, but is not compiled into the crate when we use it\n[webpki.clarify]\nlicense = \"ISC\"\n[[webpki.clarify.files]]\npath = \"LICENSE\"\nchecksum = \"5b698ca13897be3afdb7174256fa1574f8c6892b8bea1a66dd6469d3fe27885a\"\n\n[rustls-webpki.clarify]\nlicense = \"ISC\"\n[[rustls-webpki.clarify.files]]\npath = \"LICENSE\"\nchecksum = \"5b698ca13897be3afdb7174256fa1574f8c6892b8bea1a66dd6469d3fe27885a\"\n"
  },
  {
    "path": "demo-artwork/changing-seasons.graphite",
    "content": "{\"network_interface\":{\"network\":{\"exports\":[{\"Node\":{\"node_id\":3143874172491239000,\"output_index\":0}}],\"nodes\":[[10316247453530667000,{\"inputs\":[{\"Node\":{\"node_id\":9079109751490757000,\"output_index\":0}},{\"Node\":{\"node_id\":1250460246919467000,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":false,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7954638344846060000,{\"inputs\":[{\"Node\":{\"node_id\":5991296268862790000,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.1875,\"green\":0.0,\"blue\":0.0,\"alpha\":0.203125}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.19140625,\"green\":0.0,\"blue\":0.0,\"alpha\":0.203125}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17021405646895729000,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"GradientTable\":{\"element\":[{\"position\":[0.0,0.5,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":1.0,\"green\":0.31764707,\"blue\":0.15686275,\"alpha\":1.0},{\"red\":1.0,\"green\":0.5686275,\"blue\":0.25490198,\"alpha\":1.0},{\"red\":1.0,\"green\":0.7294118,\"blue\":0.16078432,\"alpha\":1.0}]}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::GradientValueNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8366826746721323000,{\"inputs\":[{\"Node\":{\"node_id\":5591755359500854000,\"output_index\":0}},{\"Node\":{\"node_id\":13846904447064916285,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1250460246919467000,{\"inputs\":[{\"Node\":{\"node_id\":4742778578215475000,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"BooleanOperation\":\"Union\"},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::style::Fill\",\"alias\":null}},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"path_bool_nodes::BooleanOperationNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8876924567444570473,{\"inputs\":[{\"Node\":{\"node_id\":26023588519449590,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"ReferencePoint\":\"CenterLeft\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::MirrorNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14253625255053304000,{\"inputs\":[{\"Node\":{\"node_id\":1924303400883620400,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.9765625,\"green\":0.7715821,\"blue\":0.2861023,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.9765625,\"green\":0.7715821,\"blue\":0.2861023,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0},{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.09778450863315612,0.7264556766766779],\"end\":[0.09778450863315612,0.3634234796342139]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3143874172491239000,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Artboard\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":18233215297647862000,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[2000.0,1000.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":1}},{\"Value\":{\"tagged_value\":{\"String\":\"Artboard\"},\"exposed\":false}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":5}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::artboard::CreateArtboardNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Import\":{\"import_type\":{\"Fn\":[{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::artboard::Artboard>\",\"alias\":null}}]},\"import_index\":0}},{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15908863353600836000,{\"inputs\":[{\"Node\":{\"node_id\":7094974507355892337,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":350.0},\"exposed\":false}},{\"Node\":{\"node_id\":4373650744391914031,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":2}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::PoissonDiskPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8895289679682140000,{\"inputs\":[{\"Node\":{\"node_id\":213744308682803360,\"output_index\":0}},{\"Node\":{\"node_id\":7954638344846060000,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17725188707009528000,{\"inputs\":[{\"Node\":{\"node_id\":10316247453530667000,\"output_index\":0}},{\"Node\":{\"node_id\":17025512774010843000,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":false,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5591755359500854000,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":1789832635968548900,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3223387122603246085,{\"inputs\":[{\"Node\":{\"node_id\":3405958409855358559,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"String\":\"2 - 0.2A\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::MathNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1789832635968548900,{\"inputs\":[{\"Node\":{\"node_id\":1924303400883620400,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.99215686,\"green\":0.49019608,\"blue\":0.11764706,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":0.25}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17855766443650990000,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"GradientTable\":{\"element\":[{\"position\":[0.0,0.5,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.1764706,\"green\":0.44313726,\"blue\":0.05882353,\"alpha\":1.0},{\"red\":0.45490196,\"green\":0.627451,\"blue\":0.3254902,\"alpha\":1.0},{\"red\":1.0,\"green\":0.5529412,\"blue\":0.16078432,\"alpha\":1.0}]}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::GradientValueNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16084834641749443000,{\"inputs\":[{\"Node\":{\"node_id\":16141281339223525000,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.99215686,\"green\":0.49019608,\"blue\":0.11764706,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":0.25}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1924303400883620400,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[12399235852192450000,16255990754021933000,15620668684239604000,5432878891027338000],\"remove\":[5992115648840007000],\"delta\":[[16255990754021933000,[3.423868312757215,60.83950617283953]],[15620668684239604000,[-7.105427357601002e-15,61.89300411522633]],[12399235852192450000,[3.4492455418381667,-6.252914951989055]],[5432878891027338000,[-0.8525377229081244,-6.779663923182397]]]},\"segments\":{\"add\":[1366074222973177300,9911415907547690000,3820594103877681000,5933636287523951000],\"remove\":[16939395239973712000],\"start_point\":[[1366074222973177300,12399235852192450000],[5933636287523951000,5432878891027338000],[3820594103877681000,15620668684239604000],[9911415907547690000,16255990754021933000]],\"end_point\":[[9911415907547690000,15620668684239604000],[5933636287523951000,12399235852192450000],[1366074222973177300,16255990754021933000],[3820594103877681000,5432878891027338000]],\"handle_primary\":[[9911415907547690000,[-0.6380090646381761,1.00935799390129]],[3820594103877681000,[-1.2746024488136916,-0.5300005080526233]],[5933636287523951000,[0.0,0.0]],[1366074222973177300,[-12.729766803840905,34.6776406035666]]],\"handle_end\":[[3820594103877681000,[-11.281207133058956,31.692729766803836]],[1366074222973177300,[1.0502464055275982,-1.6615353350607336]],[9911415907547690000,[1.1025726645520375,0.4584677151070898]],[5933636287523951000,[0.0,0.0]]],\"stroke\":[[1366074222973177300,0],[5933636287523951000,0],[3820594103877681000,0],[9911415907547690000,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[[{\"ty\":\"Primary\",\"segment\":9911415907547690000},{\"ty\":\"End\",\"segment\":1366074222973177300}],[{\"ty\":\"Primary\",\"segment\":3820594103877681000},{\"ty\":\"End\",\"segment\":9911415907547690000}]],\"remove_g1_continuous\":[[{\"ty\":\"End\",\"segment\":3820594103877681000},{\"ty\":\"Primary\",\"segment\":16939395239973712000}],[{\"ty\":\"Primary\",\"segment\":1366074222973177300},{\"ty\":\"End\",\"segment\":5933636287523951000}]]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4040070953711778000,{\"inputs\":[{\"Node\":{\"node_id\":15908863353600836000,\"output_index\":0}},{\"Node\":{\"node_id\":2166474486859326700,\"output_index\":0}},{\"Node\":{\"node_id\":10690271318666670633,\"output_index\":0}},{\"Node\":{\"node_id\":12004715210677400127,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":360.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17025512774010843000,{\"inputs\":[{\"Node\":{\"node_id\":13712392741217151405,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.9490196,\"green\":0.38039216,\"blue\":0.10980392,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.9490196,\"green\":0.38039216,\"blue\":0.10980392,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.3155737704918033,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":0.0},{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":0.203125}]},\"gradient_type\":\"Radial\",\"start\":[0.5024847204000202,0.41434795196968177],\"end\":[0.9633481630366972,0.41434795196968194]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4742778578215475000,{\"inputs\":[{\"Node\":{\"node_id\":6102164880094062000,\"output_index\":0}},{\"Node\":{\"node_id\":8876924567444570473,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10463288500489480000,{\"inputs\":[{\"Node\":{\"node_id\":4600332392291315000,\"output_index\":0}},{\"Node\":{\"node_id\":4040070953711778000,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3602127523880426500,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Raster\":{\"element\":[{\"width\":0,\"height\":0,\"data\":[0,\"\"]}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Node\":{\"node_id\":17725188707009528000,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":false,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6102164880094062000,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":14253625255053304000,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4002029424845293600,{\"inputs\":[{\"Node\":{\"node_id\":16141281339223525000,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[],\"remove\":[],\"delta\":[[353992768245212100,[-42.27413685969588,-41.62581710194484]],[16211201987812043000,[-7.105427357601002e-15,-23.90123456790124]],[12582713598977278000,[9.333333333333268,-66.33333333333327]],[11874978858302702000,[5.843621399176955,-10.914951989026145]],[15620564416450861000,[-47.66529492455423,-18.042524005486992]],[4839542169175255000,[-127.45378793812174,-12.06205126188287]],[9664410344080632000,[-35.276890617605005,-25.55103086414088]],[10418348123687606000,[-61.3893894663837,-52.000301612710835]],[6358127410693457000,[-37.72290809327849,-10.54183813443079]],[5755835744378529000,[-17.333333333333343,-33.1358024691358]],[891169987742051100,[-4.148148148148122,-38.03703703703696]],[4570709177617499000,[-62.76543209876538,-6.827160493827164]],[7888691908524886000,[4.351165980795637,-8.105624142661181]],[4305429814263425000,[-22.598416051654286,-32.71009758017498]],[16594203120813726000,[-62.53607037678802,-36.02218510909459]],[9934671969500465000,[-43.209876543209866,-14.975308641975468]],[753144493519442600,[-50.013717421124845,21.17283950617286]],[11897064075526275000,[-61.65765794556601,-32.902083082000814]]]},\"segments\":{\"add\":[],\"remove\":[],\"start_point\":[],\"end_point\":[],\"handle_primary\":[[3359087961315235300,[-0.2881601545358876,-1.8891182956799923]],[8119312711427333000,[0.0,0.0]],[5680639457836474000,[0.0,0.0]],[2092445122112560000,[-7.308641975308667,2.9629629629629903]],[3138315255762406000,[4.038408779149492,-14.5733882030178]],[12441313998107066000,[-4.938271604938336,6.518518518518476]],[9552874240071498000,[-5.53086419753086,-0.9657064471879552]],[1562499453192082400,[-0.08779149519892826,-1.6680384087791111]],[4590600976245504500,[-6.49657064471878,1.2290809327846404]],[3270826560526153000,[-10.966434817733528,4.317749647005792]],[16362428386097514000,[-23.70370370370371,19.950617283950606]],[15866454419016458000,[-9.913311783442964,6.121455371873111]],[18320159308706247000,[0.0,0.0]],[2416974091592514600,[0.0,0.0]],[4183498485018509000,[-37.13580246913581,14.485596707818928]],[14351209823603001000,[0.0,0.0]],[17948338937502876000,[0.0,0.0]]],\"handle_end\":[[12441313998107066000,[5.171144805176233,13.506721506057374]],[4183498485018509000,[-21.94787379972564,36.565157750342905]],[7709585677887591000,[-8.603566529492472,0.8779149519890552]],[5680639457836474000,[0.2881601545359018,1.8891182956799923]],[2092445122112560000,[-9.086419753086416,21.135802469135797]],[9552874240071498000,[-7.286694101508885,7.55006858710567]],[14351209823603001000,[-7.374485596707803,5.70644718792866]],[15866454419016458000,[2.458161865569238,7.637860082304542]],[8119312711427333000,[-16.241426611796967,5.355281207133061]],[2416974091592514600,[37.06995884773662,-14.595336076817532]],[4590600976245504500,[14.222222222222207,7.989026063100134]],[3359087961315235300,[-7.813443072702299,7.725651577503442]],[16362428386097514000,[-6.962962962962983,53.17283950617292]],[18320159308706247000,[-41.26200274348419,15.978052126200277]],[3270826560526153000,[-49.33882030178324,27.10562414266127]],[17948338937502876000,[-50.30452674897121,26.776406035665325]],[3138315255762406000,[-8.098765432098759,2.947337553780912]],[1562499453192082400,[-0.6668238551234532,2.406364346749864]]],\"stroke\":[]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[366962978353611840,{\"inputs\":[{\"Node\":{\"node_id\":8366826746721323000,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"BooleanOperation\":\"Union\"},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::style::Fill\",\"alias\":null}},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"path_bool_nodes::BooleanOperationNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13314559752611565452,{\"inputs\":[{\"Node\":{\"node_id\":1250460246919467000,\"output_index\":0}},{\"Node\":{\"node_id\":366962978353611840,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16379524086934900000,{\"inputs\":[{\"Node\":{\"node_id\":9609388203059839318,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.41796875,\"green\":0.1028595,\"blue\":0.1028595,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.421875,\"green\":0.1038208,\"blue\":0.1038208,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4600332392291315000,{\"inputs\":[{\"Node\":{\"node_id\":4040070953711778000,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}},{\"Node\":{\"node_id\":11677958249556146000,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::AssignColorsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7386572856931342000,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":100.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::PercentageValueNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4373650744391914031,{\"inputs\":[{\"Node\":{\"node_id\":3405958409855358559,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::ToU32Node\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16141281339223525000,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[8940743774820468000,11897064075526275000,9664410344080632000,10418348123687606000,16594203120813726000,4839542169175255000,353992768245212100,4305429814263425000,15620564416450861000,4570709177617499000,9934671969500465000,6358127410693457000,753144493519442600,11874978858302702000,7888691908524886000,12582713598977278000,891169987742051100,5755835744378529000,16211201987812043000],\"remove\":[],\"delta\":[[9934671969500465000,[125.38271604938268,-124.67901234567891]],[7888691908524886000,[66.32098765432096,-154.30864197530863]],[15620564416450861000,[141.77777777777777,-86.60493827160491]],[11897064075526275000,[72.71938634062775,26.932261408475423]],[4839542169175255000,[157.56627079134535,-37.10118604950259]],[9664410344080632000,[64.51145851883956,11.32880864191867]],[11874978858302702000,[92.39506172839504,-161.41975308641972]],[6358127410693457000,[115.50617283950618,-132.38271604938265]],[4570709177617499000,[114.716049382716,-90.16049382716052]],[353992768245212100,[114.87770338918835,-41.33714586101814]],[5755835744378529000,[28.444444444444457,-210.4567901234568]],[16594203120813726000,[126.09711290079348,-8.575894451947931]],[16211201987812043000,[0.0,-231.0]],[4305429814263425000,[91.25136529719612,-51.92090379156713]],[12582713598977278000,[49.135802469135854,-136.33333333333334]],[753144493519442600,[136.8395061728395,-177.6172839506173]],[8940743774820468000,[0.0,0.0]],[891169987742051100,[63.802469135802426,-190.90123456790127]],[10418348123687606000,[112.8352056529406,14.952290638773944]]]},\"segments\":{\"add\":[7709585677887591000,14351209823603001000,18320159308706247000,8119312711427333000,4590600976245504500,17948338937502876000,1562499453192082400,3138315255762406000,2416974091592514600,3270826560526153000,5680639457836474000,3359087961315235300,9552874240071498000,15866454419016458000,4183498485018509000,2092445122112560000,16362428386097514000,12441313998107066000],\"remove\":[],\"start_point\":[[2092445122112560000,12582713598977278000],[3270826560526153000,4570709177617499000],[4590600976245504500,16594203120813726000],[8119312711427333000,10418348123687606000],[3138315255762406000,4305429814263425000],[4183498485018509000,7888691908524886000],[2416974091592514600,15620564416450861000],[9552874240071498000,753144493519442600],[18320159308706247000,9664410344080632000],[1562499453192082400,353992768245212100],[15866454419016458000,11874978858302702000],[5680639457836474000,9934671969500465000],[17948338937502876000,4839542169175255000],[7709585677887591000,8940743774820468000],[14351209823603001000,11897064075526275000],[3359087961315235300,6358127410693457000],[12441313998107066000,5755835744378529000],[16362428386097514000,891169987742051100]],\"end_point\":[[4590600976245504500,4839542169175255000],[5680639457836474000,6358127410693457000],[3270826560526153000,9934671969500465000],[18320159308706247000,10418348123687606000],[1562499453192082400,4305429814263425000],[3138315255762406000,15620564416450861000],[12441313998107066000,16211201987812043000],[7709585677887591000,11897064075526275000],[4183498485018509000,12582713598977278000],[9552874240071498000,11874978858302702000],[16362428386097514000,5755835744378529000],[2092445122112560000,891169987742051100],[14351209823603001000,9664410344080632000],[8119312711427333000,16594203120813726000],[15866454419016458000,7888691908524886000],[2416974091592514600,4570709177617499000],[3359087961315235300,753144493519442600],[17948338937502876000,353992768245212100]],\"handle_primary\":[[4590600976245504500,[-7.759646437163781,-18.695929243596197]],[14351209823603001000,[0.0,0.0]],[7709585677887591000,[0.0,0.0]],[3138315255762406000,[-2.469006630399008,-5.902063388586043]],[5680639457836474000,[0.0,0.0]],[4183498485018509000,[-6.617283950617207,15.308641975308689]],[9552874240071498000,[-21.33333333333331,12.049382716049422]],[8119312711427333000,[0.0,0.0]],[1562499453192082400,[-15.34246181071461,-3.7168967220297873]],[2092445122112560000,[-1.5802469135804245,-5.5308641975308035]],[12441313998107066000,[-4.938271604938336,6.518518518518533]],[17948338937502876000,[0.0,0.0]],[3359087961315235300,[2.7654320987655296,-14.419753086419746]],[16362428386097514000,[-14.913580246913511,12.641975308642031]],[2416974091592514600,[0.0,0.0]],[18320159308706247000,[0.0,0.0]],[15866454419016458000,[-10.469135802469168,13.432098765432102]],[3270826560526153000,[-9.086419753086432,-7.506172839506121]]],\"handle_end\":[[5680639457836474000,[-2.7654320987655296,14.419753086419746]],[12441313998107066000,[13.234567901234527,34.5679012345679]],[7709585677887591000,[-42.51530058403631,-10.382498109968708]],[2416974091592514600,[9.086419753086377,7.506172839506235]],[16362428386097514000,[-8.0,38.419753086419746]],[8119312711427333000,[-40.68684029964413,7.631779585305026]],[14351209823603001000,[5.742854988208705,6.327636391047918]],[2092445122112560000,[-20.345679012345613,18.5679012345679]],[3138315255762406000,[-21.33333333333337,3.753086419753061]],[17948338937502876000,[25.51458639999055,20.651563178633637]],[15866454419016458000,[0.19753086419757435,13.827160493827137]],[18320159308706247000,[-24.983271918092953,-7.776766653003392]],[1562499453192082400,[2.0974057383288596,5.01376604938514]],[3359087961315235300,[-27.099224819820336,19.753086419753146]],[9552874240071498000,[4.938271604938279,11.061728395061806]],[4183498485018509000,[4.938271604938336,-1.1851851851851052]],[4590600976245504500,[-5.92501398131742,10.665547436428083]],[3270826560526153000,[-13.03703703703701,9.679012345678984]]],\"stroke\":[[16362428386097514000,0],[1562499453192082400,0],[14351209823603001000,0],[3270826560526153000,0],[2092445122112560000,0],[7709585677887591000,0],[17948338937502876000,0],[12441313998107066000,0],[3138315255762406000,0],[4590600976245504500,0],[15866454419016458000,0],[4183498485018509000,0],[5680639457836474000,0],[3359087961315235300,0],[18320159308706247000,0],[2416974091592514600,0],[8119312711427333000,0],[9552874240071498000,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[[{\"ty\":\"End\",\"segment\":1562499453192082400},{\"ty\":\"Primary\",\"segment\":3138315255762406000}],[{\"ty\":\"End\",\"segment\":5680639457836474000},{\"ty\":\"Primary\",\"segment\":3359087961315235300}],[{\"ty\":\"End\",\"segment\":2416974091592514600},{\"ty\":\"Primary\",\"segment\":3270826560526153000}]],\"remove_g1_continuous\":[[{\"ty\":\"End\",\"segment\":4183498485018509000},{\"ty\":\"Primary\",\"segment\":2092445122112560000}]]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9609388203059839318,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2000.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1000.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::generator_nodes::RectangleNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9079109751490757000,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Raster\":{\"element\":[{\"width\":0,\"height\":0,\"data\":[0,\"\"]}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Node\":{\"node_id\":366962978353611840,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":false,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[799182088624980700,{\"inputs\":[{\"Node\":{\"node_id\":7386572856931342000,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":100.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::DivideNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11677958249556146000,{\"inputs\":[{\"Node\":{\"node_id\":17021405646895729000,\"output_index\":0}},{\"Node\":{\"node_id\":17855766443650990000,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"BlendMode\":\"Normal\"},\"exposed\":false}},{\"Node\":{\"node_id\":7386572856931342000,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"raster_nodes::blending_nodes::BlendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13846904447064916285,{\"inputs\":[{\"Node\":{\"node_id\":16084834641749443000,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"ReferencePoint\":\"CenterLeft\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::MirrorNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7094974507355892337,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2000.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1000.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::generator_nodes::RectangleNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10690271318666670633,{\"inputs\":[{\"Node\":{\"node_id\":12004715210677400127,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":0.2},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::SubtractNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2166474486859326700,{\"inputs\":[{\"Node\":{\"node_id\":13712392741217151405,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.6885245901639344,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":0.0},{\"red\":0.08984375,\"green\":0.08984375,\"blue\":0.08984375,\"alpha\":0.08775313},{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":0.20392157}]},\"gradient_type\":\"Radial\",\"start\":[0.5024847204000202,0.41434795196968177],\"end\":[0.9633481630366972,0.41434795196968194]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.9490196,\"green\":0.38039216,\"blue\":0.10980392,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.6885245901639344,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":0.0},{\"red\":0.08984375,\"green\":0.08984375,\"blue\":0.08984375,\"alpha\":0.08775313},{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":0.20392157}]},\"gradient_type\":\"Radial\",\"start\":[0.5024847204000202,0.41434795196968177],\"end\":[0.9633481630366972,0.41434795196968194]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3405958409855358559,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::ReadIndexNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"INDEX\",\"inject\":\"\"}}],[5348726859432207000,{\"inputs\":[{\"Node\":{\"node_id\":13314559752611565452,\"output_index\":0}},{\"Node\":{\"node_id\":799182088624980700,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::MorphNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3430686124240113700,{\"inputs\":[{\"Node\":{\"node_id\":3602127523880426500,\"output_index\":0}},{\"Node\":{\"node_id\":16379524086934900000,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12004715210677400127,{\"inputs\":[{\"Node\":{\"node_id\":3223387122603246085,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.1},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::MaxNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14337610765966946000,{\"inputs\":[{\"Node\":{\"node_id\":14250786159408925409,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[18233215297647862000,{\"inputs\":[{\"Node\":{\"node_id\":14337610765966946000,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1000.0,500.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13712392741217151405,{\"inputs\":[{\"Node\":{\"node_id\":5348726859432207000,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5991296268862790000,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2000.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1000.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::generator_nodes::RectangleNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13735151800058122834,{\"inputs\":[{\"Node\":{\"node_id\":8895289679682140000,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"U64\":8},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"repeat_nodes::repeat_nodes::RepeatNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[213744308682803360,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":10463288500489480000,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[26023588519449590,{\"inputs\":[{\"Node\":{\"node_id\":4002029424845293600,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.9765625,\"green\":0.7715821,\"blue\":0.2861023,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.9765625,\"green\":0.7715821,\"blue\":0.2861023,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0},{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.09778450863315612,0.7264556766766779],\"end\":[0.09778450863315612,0.3634234796342139]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14250786159408925409,{\"inputs\":[{\"Node\":{\"node_id\":3430686124240113700,\"output_index\":0}},{\"Node\":{\"node_id\":13735151800058122834,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[13735151800058122834,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Count\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Reverse\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[3602127523880426500,{\"persistent_metadata\":{\"display_name\":\"Individual Leaf Views\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":3}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[366962978353611840,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Operation\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-19,-16]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Boolean Operation\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16141281339223525000,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Modification\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-58,-22]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13846904447064916285,{\"persistent_metadata\":{\"display_name\":\"Mirror\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Relative To Bounds\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Angle\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Keep Original\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Graphic>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[10463288500489480000,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[191.5,23.5],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,542.0,545.0],\"node_graph_width\":0.0},\"selection_undo_history\":[[]],\"selection_redo_history\":[]}}}}],[13712392741217151405,{\"persistent_metadata\":{\"display_name\":\"Cache\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Data\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Data\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-5,-31]}}},\"network_metadata\":null}}],[15908863353600836000,{\"persistent_metadata\":{\"display_name\":\"Scatter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"range_min\":1.0,\"blank_assist\":true,\"is_integer\":false,\"range_max\":100.0,\"mode\":\"Range\",\"min\":0.01},\"widget_override\":\"number\",\"input_name\":\"Separation Disk Diameter\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":true,\"mode\":\"Increment\",\"blank_assist\":true,\"min\":0.0},\"widget_override\":\"number\",\"input_name\":\"Seed\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-19,-37]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Scatter Points\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Poisson-Disk Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[-312.5,-48.5],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,38.0,473.0],\"node_graph_width\":0.0},\"selection_undo_history\":[[]],\"selection_redo_history\":[]}}}}],[8895289679682140000,{\"persistent_metadata\":{\"display_name\":\"Depth Shadow\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[2,-45]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[17725188707009528000,{\"persistent_metadata\":{\"display_name\":\"Morph\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[9,-29]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7386572856931342000,{\"persistent_metadata\":{\"display_name\":\"Percentage Value\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Percentage\",\"input_description\":\"\"}}],\"output_names\":[\"f64\"],\"locked\":false,\"pinned\":true,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-26,-39]}}},\"network_metadata\":null}}],[4373650744391914031,{\"persistent_metadata\":{\"display_name\":\"To u32\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"\"}}],\"output_names\":[\"Future<u32>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-33,-36]}}},\"network_metadata\":null}}],[5348726859432207000,{\"persistent_metadata\":{\"display_name\":\"Morph\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The vector elements to interpolate between. Mixed graphic content is deeply flattened to keep only vector elements.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Progression\",\"input_description\":\"The factor from one vector element to the next in sequence. The whole number part selects the source element, and the decimal part determines the interpolation amount towards the next element.\\n\"}}],\"output_names\":[\"Table<Vector>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-12,-31]}}},\"network_metadata\":null}}],[3430686124240113700,{\"persistent_metadata\":{\"display_name\":\"Solid Backdrop\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":8}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[5991296268862790000,{\"persistent_metadata\":{\"display_name\":\"Rectangle\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Width\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Height\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Individual Corner Radii\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Corner Radius\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Clamped\",\"input_description\":\"\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[3223387122603246085,{\"persistent_metadata\":{\"display_name\":\"Math\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Operand A\",\"input_description\":\"The value of \\\"A\\\" when calculating the expression\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Expression\",\"input_description\":\"A math expression that may incorporate \\\"A\\\" and/or \\\"B\\\", such as \\\"sqrt(A + B) - B^2\\\"\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Operand B\",\"input_description\":\"The value of \\\"B\\\" when calculating the expression\\n\"}}],\"output_names\":[\"Future<f64>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-33,-34]}}},\"network_metadata\":null}}],[9609388203059839318,{\"persistent_metadata\":{\"display_name\":\"Rectangle\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Width\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Height\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Individual Corner Radii\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Corner Radius\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Clamped\",\"input_description\":\"\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[8366826746721323000,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-30,-13]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[5591755359500854000,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14253625255053304000,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[12004715210677400127,{\"persistent_metadata\":{\"display_name\":\"Max\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"One of the two numbers, of which the greater will be returned.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Other Value\",\"input_description\":\"The other of the two numbers, of which the greater will be returned.\\n\"}}],\"output_names\":[\"Future<f64>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-26,-34]}}},\"network_metadata\":null}}],[1789832635968548900,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[3143874172491239000,{\"persistent_metadata\":{\"display_name\":\"Artboard\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Artboards\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Contents\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":true,\"x\":\"X\",\"y\":\"Y\",\"unit\":\" px\"},\"widget_override\":\"vec2\",\"input_name\":\"Location\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"y\":\"H\",\"is_integer\":true,\"unit\":\" px\"},\"widget_override\":\"vec2\",\"input_name\":\"Dimensions\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"artboard_background\",\"input_name\":\"Background\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Clip\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[26,-56]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Artboard\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Create Artboard\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-4]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9079109751490757000,{\"persistent_metadata\":{\"display_name\":\"Maple Leaf\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":6}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14337610765966946000,{\"persistent_metadata\":{\"display_name\":\"NOTE: Change seasons with the \\\"Percentage Value\\\" parameter\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[14,-52]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11677958249556146000,{\"persistent_metadata\":{\"display_name\":\"Blend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Under\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Blend Mode\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Opacity\",\"input_description\":\"\"}}],\"output_names\":[\"Color\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-19,-41]}}},\"network_metadata\":null}}],[8876924567444570473,{\"persistent_metadata\":{\"display_name\":\"Mirror\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Relative To Bounds\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Angle\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Keep Original\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Graphic>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[16084834641749443000,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[17021405646895729000,{\"persistent_metadata\":{\"display_name\":\"Gradient Value\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Gradient\",\"input_description\":\"\"}}],\"output_names\":[\"GradientStops\"],\"locked\":false,\"pinned\":true,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-26,-41]}}},\"network_metadata\":null}}],[17855766443650990000,{\"persistent_metadata\":{\"display_name\":\"Gradient Value\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Gradient\",\"input_description\":\"\"}}],\"output_names\":[\"GradientStops\"],\"locked\":false,\"pinned\":true,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-26,-40]}}},\"network_metadata\":null}}],[13314559752611565452,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Base\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-19,-31]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Node Path\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Base\",\"input_description\":\"The table whose rows will appear at the start of the extended table.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"New\",\"input_description\":\"The table whose rows will appear at the end of the extended table.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"In\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4040070953711778000,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-12,-37]}}},\"network_metadata\":null}}],[1924303400883620400,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Modification\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-44,-19]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7954638344846060000,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[14250786159408925409,{\"persistent_metadata\":{\"display_name\":\"Leaf Levels\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[1250460246919467000,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Operation\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-19,-25]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Boolean Operation\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[-144.5,-36.5],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,206.0,485.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4002029424845293600,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Modification\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7094974507355892337,{\"persistent_metadata\":{\"display_name\":\"Rectangle\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Width\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Height\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Individual Corner Radii\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Corner Radius\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Clamped\",\"input_description\":\"\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-26,-37]}}},\"network_metadata\":null}}],[6102164880094062000,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[799182088624980700,{\"persistent_metadata\":{\"display_name\":\"Divide\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Numerator\",\"input_description\":\"The left-hand side of the division operation.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Denominator\",\"input_description\":\"The right-hand side of the division operation.\\n\"}}],\"output_names\":[\"Output\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-19,-33]}}},\"network_metadata\":null}}],[2166474486859326700,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[2,-31]}}},\"network_metadata\":null}}],[17025512774010843000,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[18233215297647862000,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"X\",\"y\":\"Y\",\"unit\":\" px\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"y\":\"H\",\"unit\":\"x\",\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"Data\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10690271318666670633,{\"persistent_metadata\":{\"display_name\":\"Subtract\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Minuend\",\"input_description\":\"The left-hand side of the subtraction operation.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Subtrahend\",\"input_description\":\"The right-hand side of the subtraction operation.\\n\"}}],\"output_names\":[\"Future<f64>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-19,-35]}}},\"network_metadata\":null}}],[10316247453530667000,{\"persistent_metadata\":{\"display_name\":\"Oak Leaf\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":1}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3405958409855358559,{\"persistent_metadata\":{\"display_name\":\"Read Index\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Loop Level\",\"input_description\":\"\"}}],\"output_names\":[\"f64\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-40,-35]}}},\"network_metadata\":null}}],[16379524086934900000,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[4600332392291315000,{\"persistent_metadata\":{\"display_name\":\"Assign Colors\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill and/or stroke style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"Whether to style the fill.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Stroke\",\"input_description\":\"Whether to style the stroke.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"assign_colors_gradient\",\"input_name\":\"Gradient\",\"input_description\":\"The range of colors to select from.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Reverse\",\"input_description\":\"Whether to reverse the gradient.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Randomize\",\"input_description\":\"Whether to randomize the color selection for each element from throughout the gradient.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"assign_colors_seed\",\"input_name\":\"Seed\",\"input_description\":\"The seed used for randomization.\\nSeed to determine unique variations on the randomized color selection.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"assign_colors_repeat_every\",\"input_name\":\"Repeat Every\",\"input_description\":\"The number of elements to span across the gradient before repeating. A 0 value will span the entire gradient once.\\n\"}}],\"output_names\":[\"Content\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[4742778578215475000,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-30,-22]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[213744308682803360,{\"persistent_metadata\":{\"display_name\":\"Leaves\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[26023588519449590,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[287.9366299999996,768.3549700000001],\"tilt\":0.0,\"zoom\":0.8523324275362318,\"flip\":false},\"node_graph_to_viewport\":[0.8523324275362318,0.0,0.0,0.8523324275362318,1236.0,1235.0],\"node_graph_width\":1981.0},\"selection_undo_history\":[[1924303400883620400,8366826746721323000,16084834641749443000,13846904447064916285,366962978353611840,14253625255053304000,4002029424845293600,6102164880094062000,1250460246919467000,26023588519449590,5591755359500854000,8876924567444570473,4742778578215475000,1789832635968548900,16141281339223525000],[799182088624980700,4002029424845293600,366962978353611840,14253625255053304000,5348726859432207000,4742778578215475000,16084834641749443000,8876924567444570473,1924303400883620400,26023588519449590,16141281339223525000,1789832635968548900,1250460246919467000,13846904447064916285,5591755359500854000,6102164880094062000,8366826746721323000],[8876924567444570473,799182088624980700,26023588519449590,8366826746721323000,16084834641749443000,5591755359500854000,6102164880094062000,16141281339223525000,366962978353611840,1250460246919467000,4002029424845293600,1789832635968548900,1924303400883620400,14253625255053304000,13712392741217151405,13846904447064916285,5348726859432207000,4742778578215475000],[1250460246919467000,16084834641749443000,8366826746721323000,4002029424845293600,799182088624980700,26023588519449590,5348726859432207000,366962978353611840,8876924567444570473,6102164880094062000,13712392741217151405,13846904447064916285,9079109751490757000,5591755359500854000,16141281339223525000,1789832635968548900,1924303400883620400,14253625255053304000,4742778578215475000],[1250460246919467000,5348726859432207000,8366826746721323000,14253625255053304000,5591755359500854000,13846904447064916285,26023588519449590,799182088624980700,4742778578215475000,8876924567444570473,6102164880094062000,1924303400883620400,10316247453530667000,13712392741217151405,366962978353611840,1789832635968548900,4002029424845293600,16084834641749443000,9079109751490757000,16141281339223525000],[4742778578215475000,13712392741217151405,9079109751490757000,8876924567444570473,8366826746721323000,10316247453530667000,5348726859432207000,16084834641749443000,13846904447064916285,5591755359500854000,1789832635968548900,16141281339223525000,1250460246919467000,14253625255053304000,799182088624980700,366962978353611840,4002029424845293600,1924303400883620400,17725188707009528000,26023588519449590,6102164880094062000,17025512774010843000],[5591755359500854000,366962978353611840,4002029424845293600,3602127523880426500,5348726859432207000,13846904447064916285,6102164880094062000,26023588519449590,17025512774010843000,14253625255053304000,17725188707009528000,13712392741217151405,4742778578215475000,1924303400883620400,1789832635968548900,1250460246919467000,16141281339223525000,799182088624980700,8366826746721323000,9079109751490757000,8876924567444570473,10316247453530667000,16084834641749443000],[5591755359500854000,8366826746721323000,366962978353611840,6102164880094062000],[8366826746721323000,6102164880094062000,5591755359500854000,14253625255053304000,1789832635968548900,8876924567444570473,4742778578215475000,13846904447064916285,366962978353611840],[1789832635968548900,8366826746721323000,366962978353611840,5591755359500854000,4742778578215475000,26023588519449590,8876924567444570473,6102164880094062000,13846904447064916285,16084834641749443000,14253625255053304000],[8366826746721323000,13846904447064916285,26023588519449590,8876924567444570473,366962978353611840,4002029424845293600,1789832635968548900,16084834641749443000,4742778578215475000,6102164880094062000,5591755359500854000,1250460246919467000,14253625255053304000],[8876924567444570473,799182088624980700,5348726859432207000,13846904447064916285,366962978353611840,8366826746721323000,5591755359500854000,4742778578215475000,26023588519449590,4002029424845293600,16084834641749443000,14253625255053304000,1250460246919467000,6102164880094062000,1789832635968548900],[6102164880094062000,16084834641749443000,4742778578215475000,366962978353611840,16141281339223525000,1789832635968548900,26023588519449590,5591755359500854000,8876924567444570473,13846904447064916285,1924303400883620400,799182088624980700,8366826746721323000,4002029424845293600,5348726859432207000,1250460246919467000,14253625255053304000],[1250460246919467000,26023588519449590,13846904447064916285,14253625255053304000,1924303400883620400,16141281339223525000,6102164880094062000,4742778578215475000,5348726859432207000,8366826746721323000,799182088624980700,1789832635968548900,4002029424845293600,16084834641749443000,8876924567444570473,366962978353611840,13712392741217151405,5591755359500854000],[8366826746721323000,8876924567444570473,1924303400883620400,1250460246919467000,26023588519449590,366962978353611840,14253625255053304000,6102164880094062000,4002029424845293600,16084834641749443000,4742778578215475000,13846904447064916285,1789832635968548900,5348726859432207000,5591755359500854000,799182088624980700,16141281339223525000],[799182088624980700,8366826746721323000,6102164880094062000,26023588519449590,1250460246919467000,1789832635968548900,16084834641749443000,8876924567444570473,4002029424845293600,4742778578215475000,5348726859432207000,13846904447064916285,366962978353611840,14253625255053304000,5591755359500854000],[16084834641749443000,4742778578215475000,6102164880094062000,13846904447064916285,5348726859432207000,1789832635968548900,1250460246919467000,14253625255053304000,799182088624980700,26023588519449590,5591755359500854000,366962978353611840,8366826746721323000,8876924567444570473],[8876924567444570473,26023588519449590,366962978353611840,1250460246919467000,4742778578215475000,14253625255053304000,5591755359500854000,13846904447064916285,6102164880094062000,4002029424845293600,16084834641749443000,8366826746721323000,5348726859432207000,799182088624980700,1789832635968548900],[5591755359500854000,8876924567444570473,16141281339223525000,4002029424845293600,1789832635968548900,1250460246919467000,5348726859432207000,4742778578215475000,6102164880094062000,366962978353611840,16084834641749443000,8366826746721323000,1924303400883620400,799182088624980700,26023588519449590,14253625255053304000,13846904447064916285],[1924303400883620400,8876924567444570473,1789832635968548900,4002029424845293600,799182088624980700,16084834641749443000,13846904447064916285,8366826746721323000,26023588519449590,6102164880094062000,13712392741217151405,5348726859432207000,366962978353611840,16141281339223525000,14253625255053304000,4742778578215475000,5591755359500854000,1250460246919467000],[5591755359500854000,8366826746721323000,366962978353611840,6102164880094062000],[8366826746721323000,366962978353611840,4742778578215475000,5591755359500854000,6102164880094062000],[14253625255053304000,8366826746721323000,366962978353611840,1789832635968548900,8876924567444570473,5591755359500854000,4742778578215475000,6102164880094062000,13846904447064916285],[4742778578215475000,5591755359500854000,13846904447064916285,366962978353611840,14253625255053304000,8876924567444570473,16084834641749443000,8366826746721323000,1789832635968548900,26023588519449590,6102164880094062000],[1789832635968548900,5591755359500854000,4742778578215475000,8366826746721323000,13846904447064916285,6102164880094062000,1250460246919467000,16084834641749443000,8876924567444570473,14253625255053304000,4002029424845293600,366962978353611840,26023588519449590],[1789832635968548900,5348726859432207000,5591755359500854000,8876924567444570473,8366826746721323000,26023588519449590,6102164880094062000,366962978353611840,16084834641749443000,4742778578215475000,13846904447064916285,14253625255053304000,1250460246919467000,4002029424845293600],[16084834641749443000,4002029424845293600,8366826746721323000,5591755359500854000,6102164880094062000,14253625255053304000,4742778578215475000,8876924567444570473,13846904447064916285,16141281339223525000,1924303400883620400,26023588519449590,1250460246919467000,366962978353611840,1789832635968548900,5348726859432207000],[1789832635968548900,13846904447064916285,1924303400883620400,6102164880094062000,4742778578215475000,8876924567444570473,16084834641749443000,5591755359500854000,13712392741217151405,366962978353611840,16141281339223525000,26023588519449590,8366826746721323000,1250460246919467000,5348726859432207000,4002029424845293600,14253625255053304000],[6102164880094062000,26023588519449590,8876924567444570473,4002029424845293600,4742778578215475000,8366826746721323000,5348726859432207000,5591755359500854000,16141281339223525000,13846904447064916285,1789832635968548900,1250460246919467000,14253625255053304000,16084834641749443000,366962978353611840,1924303400883620400],[1250460246919467000,13712392741217151405,1924303400883620400,16141281339223525000,6102164880094062000,8876924567444570473,366962978353611840,13846904447064916285,8366826746721323000,14253625255053304000,16084834641749443000,1789832635968548900,26023588519449590,4742778578215475000,5348726859432207000,4002029424845293600,5591755359500854000],[9079109751490757000],[9079109751490757000,10316247453530667000],[10316247453530667000,9079109751490757000,5348726859432207000],[10316247453530667000,4742778578215475000,8366826746721323000,1250460246919467000,6102164880094062000,366962978353611840,5591755359500854000,9079109751490757000,5348726859432207000],[4742778578215475000,9079109751490757000,17725188707009528000,10316247453530667000,5591755359500854000,8366826746721323000,5348726859432207000,366962978353611840,1250460246919467000,6102164880094062000],[1250460246919467000,14253625255053304000,1789832635968548900,17725188707009528000,5348726859432207000,5591755359500854000,8366826746721323000,9079109751490757000,10316247453530667000,8876924567444570473,4742778578215475000,13846904447064916285,366962978353611840,6102164880094062000],[4742778578215475000,5348726859432207000,14253625255053304000,10316247453530667000,9079109751490757000,366962978353611840,1789832635968548900,13846904447064916285,5591755359500854000,1250460246919467000,6102164880094062000,799182088624980700,8876924567444570473,8366826746721323000,17725188707009528000],[9079109751490757000,13846904447064916285,5591755359500854000,5348726859432207000,366962978353611840,799182088624980700,4742778578215475000,8876924567444570473,1789832635968548900,14253625255053304000,17725188707009528000,8366826746721323000,10316247453530667000,6102164880094062000,16084834641749443000,26023588519449590,1250460246919467000],[10316247453530667000,366962978353611840,17025512774010843000,799182088624980700,6102164880094062000,16084834641749443000,8366826746721323000,17725188707009528000,5348726859432207000,14253625255053304000,4002029424845293600,1250460246919467000,26023588519449590,9079109751490757000,13846904447064916285,1789832635968548900,5591755359500854000,4742778578215475000,13712392741217151405,8876924567444570473],[17725188707009528000,1250460246919467000,1789832635968548900,366962978353611840,5591755359500854000,9079109751490757000,14253625255053304000,5348726859432207000,4742778578215475000,6102164880094062000,10316247453530667000,799182088624980700,4002029424845293600,13846904447064916285,1924303400883620400,17025512774010843000,8876924567444570473,26023588519449590,13712392741217151405,16141281339223525000,16084834641749443000,8366826746721323000],[9641606876402405523,7094974507355892337,2166474486859326700,4373650744391914031,15908863353600836000,10690271318666670633,5348726859432207000,3223387122603246085,799182088624980700,17725188707009528000,4040070953711778000,12004715210677400127,13712392741217151405],[4040070953711778000,10690271318666670633,9641606876402405523,799182088624980700,12004715210677400127,13712392741217151405,17725188707009528000,2166474486859326700,4373650744391914031,7094974507355892337,3223387122603246085,15908863353600836000,17025512774010843000,5348726859432207000],[10690271318666670633,5348726859432207000,17725188707009528000,3223387122603246085,12004715210677400127,13712392741217151405,1250460246919467000,4373650744391914031,799182088624980700,9641606876402405523,4040070953711778000,7094974507355892337,15908863353600836000,17025512774010843000,2166474486859326700],[7094974507355892337,3223387122603246085,13712392741217151405,10316247453530667000,15908863353600836000,12004715210677400127,4040070953711778000,1250460246919467000,799182088624980700,17725188707009528000,2166474486859326700,5348726859432207000,9641606876402405523,10690271318666670633,17025512774010843000,4373650744391914031],[13712392741217151405,17025512774010843000,15908863353600836000,12004715210677400127,10690271318666670633,17725188707009528000,4373650744391914031,7094974507355892337,3602127523880426500,2166474486859326700,799182088624980700,10316247453530667000,5348726859432207000,3223387122603246085,4040070953711778000,1250460246919467000,9641606876402405523],[17025512774010843000,4040070953711778000,5348726859432207000,12004715210677400127,17725188707009528000,3602127523880426500,10690271318666670633,7094974507355892337,3223387122603246085,1250460246919467000,4742778578215475000,4002029424845293600,10316247453530667000,799182088624980700,4373650744391914031,9641606876402405523,2166474486859326700,13712392741217151405,15908863353600836000],[17725188707009528000,17025512774010843000,13712392741217151405,10690271318666670633,7094974507355892337,12004715210677400127,3223387122603246085,26023588519449590,15908863353600836000,10316247453530667000,3602127523880426500,4002029424845293600,799182088624980700,5348726859432207000,4373650744391914031,9641606876402405523,4742778578215475000,4040070953711778000,2166474486859326700,1250460246919467000],[2166474486859326700,1250460246919467000,3223387122603246085,9641606876402405523,8366826746721323000,799182088624980700,8876924567444570473,16141281339223525000,10316247453530667000,13846904447064916285,26023588519449590,7094974507355892337,15908863353600836000,3602127523880426500,4742778578215475000,5348726859432207000,17025512774010843000,6102164880094062000,4040070953711778000,14253625255053304000,17725188707009528000,10690271318666670633,4002029424845293600,12004715210677400127,13712392741217151405,16084834641749443000,4373650744391914031],[1250460246919467000,3223387122603246085,10316247453530667000,5348726859432207000,2166474486859326700,6102164880094062000,17725188707009528000,12004715210677400127,3602127523880426500,4742778578215475000,4373650744391914031,8876924567444570473,1789832635968548900,799182088624980700,17025512774010843000,4040070953711778000,7094974507355892337,9641606876402405523,5591755359500854000,4002029424845293600,26023588519449590,13712392741217151405,15908863353600836000,16141281339223525000,1924303400883620400,8366826746721323000,13846904447064916285,10690271318666670633,16084834641749443000,14253625255053304000],[13846904447064916285,4373650744391914031,4742778578215475000,366962978353611840,4002029424845293600,17725188707009528000,17025512774010843000,3223387122603246085,15908863353600836000,8876924567444570473,1924303400883620400,8366826746721323000,6102164880094062000,12004715210677400127,16141281339223525000,10316247453530667000,3602127523880426500,1789832635968548900,799182088624980700,7094974507355892337,2166474486859326700,26023588519449590,10690271318666670633,14253625255053304000,1250460246919467000,4040070953711778000,9641606876402405523,13712392741217151405,16084834641749443000,5348726859432207000,5591755359500854000],[9079109751490757000],[17725188707009528000],[13712392741217151405,17725188707009528000,5348726859432207000,17025512774010843000,2166474486859326700],[17725188707009528000,2166474486859326700,17025512774010843000,5348726859432207000,1250460246919467000,13712392741217151405],[2166474486859326700,10316247453530667000,1250460246919467000,17025512774010843000,17725188707009528000,13712392741217151405,5348726859432207000],[17025512774010843000,2166474486859326700,17725188707009528000,5348726859432207000,13712392741217151405,9079109751490757000,1250460246919467000,10316247453530667000],[17025512774010843000,13712392741217151405,8366826746721323000,2166474486859326700,1250460246919467000,5348726859432207000,9079109751490757000,10316247453530667000,5591755359500854000,17725188707009528000],[6102164880094062000,5591755359500854000,17025512774010843000,17725188707009528000,9079109751490757000,1250460246919467000,2166474486859326700,8366826746721323000,10316247453530667000,5348726859432207000,366962978353611840,13712392741217151405],[6102164880094062000,4742778578215475000,1250460246919467000,366962978353611840,1789832635968548900,14253625255053304000,5591755359500854000,2166474486859326700,13712392741217151405,17025512774010843000,17725188707009528000,9079109751490757000,10316247453530667000,5348726859432207000,13846904447064916285,8366826746721323000],[13846904447064916285,16084834641749443000,1789832635968548900,2166474486859326700,5348726859432207000,26023588519449590,1250460246919467000,6102164880094062000,366962978353611840,13712392741217151405,5591755359500854000,9079109751490757000,10316247453530667000,8876924567444570473,17725188707009528000,17025512774010843000,8366826746721323000,14253625255053304000,4742778578215475000,1924303400883620400],[9079109751490757000,366962978353611840,8876924567444570473,2166474486859326700,5348726859432207000,26023588519449590,13712392741217151405,14253625255053304000,10316247453530667000,1924303400883620400,17025512774010843000,6102164880094062000,16084834641749443000,5591755359500854000,1789832635968548900,13846904447064916285,1250460246919467000,17725188707009528000,8366826746721323000,4742778578215475000,4002029424845293600],[4002029424845293600,2166474486859326700,1924303400883620400,16084834641749443000,17725188707009528000,4742778578215475000,13846904447064916285,16141281339223525000,9079109751490757000,6102164880094062000,5591755359500854000,13712392741217151405,26023588519449590,10316247453530667000,17025512774010843000,5348726859432207000,1789832635968548900,366962978353611840,1250460246919467000,8876924567444570473,14253625255053304000,8366826746721323000],[16141281339223525000,4002029424845293600,4742778578215475000,8876924567444570473,799182088624980700,26023588519449590],[16141281339223525000,4742778578215475000,799182088624980700,4002029424845293600,1250460246919467000,26023588519449590,8876924567444570473,6102164880094062000],[14253625255053304000,8876924567444570473,1924303400883620400,1250460246919467000,799182088624980700,16141281339223525000,4742778578215475000,6102164880094062000,26023588519449590,4002029424845293600],[1924303400883620400,6102164880094062000,799182088624980700,1250460246919467000,26023588519449590,5348726859432207000,16141281339223525000,8876924567444570473,4002029424845293600,4742778578215475000,14253625255053304000],[8876924567444570473,799182088624980700,26023588519449590,14253625255053304000,6102164880094062000,1924303400883620400,16141281339223525000,4002029424845293600,366962978353611840,5348726859432207000,4742778578215475000,1250460246919467000],[26023588519449590,4742778578215475000,8366826746721323000,8876924567444570473,1250460246919467000,5348726859432207000,366962978353611840,14253625255053304000,799182088624980700,1924303400883620400,6102164880094062000,16141281339223525000,4002029424845293600],[1924303400883620400,799182088624980700,8366826746721323000,14253625255053304000,16141281339223525000,17725188707009528000,1250460246919467000,26023588519449590,5348726859432207000,4002029424845293600,6102164880094062000,4742778578215475000,8876924567444570473,366962978353611840],[1250460246919467000,8876924567444570473,14253625255053304000,16141281339223525000,1924303400883620400,17725188707009528000,4742778578215475000,13846904447064916285,6102164880094062000,8366826746721323000,16084834641749443000,17025512774010843000,4002029424845293600,799182088624980700,26023588519449590,366962978353611840,5348726859432207000],[14253625255053304000,26023588519449590,4742778578215475000,16084834641749443000,6102164880094062000,366962978353611840,17725188707009528000,799182088624980700,1924303400883620400,16141281339223525000,5591755359500854000,4002029424845293600,1250460246919467000,13846904447064916285,8876924567444570473,5348726859432207000,8366826746721323000,17025512774010843000],[799182088624980700,5591755359500854000,1789832635968548900,8366826746721323000,17025512774010843000,1250460246919467000,26023588519449590,13846904447064916285,1924303400883620400,16084834641749443000,5348726859432207000,14253625255053304000,4742778578215475000,6102164880094062000,8876924567444570473,366962978353611840,17725188707009528000,16141281339223525000,4002029424845293600],[8366826746721323000,4742778578215475000,10316247453530667000,26023588519449590,5348726859432207000,9079109751490757000,799182088624980700,13846904447064916285,14253625255053304000,1789832635968548900,366962978353611840,8876924567444570473,16084834641749443000,17725188707009528000,17025512774010843000,5591755359500854000,16141281339223525000,1924303400883620400,1250460246919467000,6102164880094062000,4002029424845293600],[16084834641749443000,17725188707009528000,16141281339223525000,799182088624980700,14253625255053304000,13712392741217151405,8366826746721323000,17025512774010843000,5348726859432207000,8876924567444570473,4002029424845293600,9079109751490757000,6102164880094062000,1250460246919467000,5591755359500854000,4742778578215475000,1789832635968548900,10316247453530667000,13846904447064916285,366962978353611840,26023588519449590,1924303400883620400],[16084834641749443000,14253625255053304000,1789832635968548900,8876924567444570473,5591755359500854000,366962978353611840,2166474486859326700,17725188707009528000,13712392741217151405,17025512774010843000,4002029424845293600,10316247453530667000,1924303400883620400,26023588519449590,1250460246919467000,9079109751490757000,8366826746721323000,16141281339223525000,799182088624980700,5348726859432207000,13846904447064916285,4742778578215475000,6102164880094062000],[3602127523880426500],[5591755359500854000,8366826746721323000],[6102164880094062000,2233138531352324200,5591755359500854000,8366826746721323000,366962978353611840],[8366826746721323000,13846904447064916285,6102164880094062000,8876924567444570473,5591755359500854000,366962978353611840,2233138531352324200,4742778578215475000,14253625255053304000,1789832635968548900],[13846904447064916285,2233138531352324200,8876924567444570473,366962978353611840,1924303400883620400,1789832635968548900,14253625255053304000,5591755359500854000,16084834641749443000,8366826746721323000,26023588519449590,4742778578215475000,6102164880094062000],[14253625255053304000,4742778578215475000,6102164880094062000,1789832635968548900,12796400626461303056,2233138531352324200,8876924567444570473,26023588519449590,8366826746721323000,366962978353611840,13846904447064916285,1924303400883620400,1250460246919467000,16084834641749443000,5591755359500854000],[4002029424845293600,366962978353611840,1789832635968548900,14253625255053304000,4742778578215475000,13846904447064916285,1924303400883620400,2233138531352324200,1250460246919467000,6102164880094062000,16084834641749443000,12796400626461303056,5591755359500854000,8366826746721323000,26023588519449590,8876924567444570473],[4002029424845293600,14253625255053304000,366962978353611840,1924303400883620400,12796400626461303056,5591755359500854000,16141281339223525000,1789832635968548900,16084834641749443000,26023588519449590,13846904447064916285,8366826746721323000,2233138531352324200,1250460246919467000,6102164880094062000,8876924567444570473,4742778578215475000],[9079109751490757000],[9079109751490757000,10316247453530667000],[10316247453530667000,17725188707009528000,9079109751490757000],[3602127523880426500],[9079109751490757000],[2233138531352324200,9079109751490757000],[2233138531352324200,8366826746721323000,366962978353611840,5591755359500854000,9079109751490757000],[366962978353611840,2233138531352324200,5591755359500854000,8366826746721323000,6102164880094062000,9079109751490757000],[5591755359500854000,9079109751490757000,13846904447064916285,2233138531352324200,366962978353611840,1789832635968548900,14253625255053304000,6102164880094062000,8366826746721323000],[1924303400883620400,13846904447064916285,366962978353611840,14253625255053304000,8366826746721323000,2233138531352324200,16084834641749443000,5591755359500854000,6102164880094062000,9079109751490757000,1789832635968548900],[366962978353611840,8366826746721323000,6102164880094062000,14253625255053304000,2233138531352324200,1924303400883620400,9079109751490757000,5591755359500854000,16084834641749443000,4742778578215475000,13846904447064916285,1789832635968548900],[14253625255053304000,1924303400883620400,26023588519449590,5591755359500854000,16084834641749443000,1789832635968548900,9079109751490757000,13846904447064916285,366962978353611840,6102164880094062000,4742778578215475000,4002029424845293600,2233138531352324200,8366826746721323000],[1789832635968548900,26023588519449590,5591755359500854000,1924303400883620400,16084834641749443000,13846904447064916285,4742778578215475000,9079109751490757000,366962978353611840,8876924567444570473,2233138531352324200,14253625255053304000,8366826746721323000,4002029424845293600,6102164880094062000],[5591755359500854000,16141281339223525000,4742778578215475000,14253625255053304000,26023588519449590,2233138531352324200,13846904447064916285,1789832635968548900,1924303400883620400,16084834641749443000,8366826746721323000,8876924567444570473,6102164880094062000,9079109751490757000,366962978353611840,4002029424845293600],[16141281339223525000,1789832635968548900,16084834641749443000,26023588519449590,8876924567444570473,13846904447064916285,1924303400883620400,2233138531352324200,8366826746721323000,10316247453530667000,5591755359500854000,4742778578215475000,6102164880094062000,366962978353611840,4002029424845293600,9079109751490757000,14253625255053304000],[4002029424845293600,2233138531352324200,1789832635968548900,1924303400883620400,16084834641749443000,1250460246919467000,5591755359500854000,4742778578215475000,14253625255053304000,9079109751490757000,8876924567444570473,10316247453530667000,13846904447064916285,6102164880094062000,8366826746721323000,26023588519449590,12796400626461303056,366962978353611840,16141281339223525000],[]],\"selection_redo_history\":[]}}},\"collapsed\":[17725188707009528001,9079109751490757001,3602127523880426501],\"commit_hash\":\"e647ca9f91a5e823137122126fe9e980f65d62ea\",\"document_ptz\":{\"pan\":[-999.7861718531644,-499.944688737096],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"render_mode\":\"Normal\",\"overlays_visibility_settings\":{\"all\":true,\"artboard_name\":true,\"compass_rose\":true,\"quick_measurement\":true,\"transform_measurement\":true,\"transform_cage\":true,\"hover_outline\":true,\"selection_outline\":true,\"layer_origin_cross\":true,\"pivot\":true,\"origin\":true,\"path\":true,\"anchors\":true,\"handles\":true},\"rulers_visible\":true,\"snapping_state\":{\"snapping_enabled\":true,\"grid_snapping\":false,\"artboards\":true,\"tolerance\":8.0,\"bounding_box\":{\"center_point\":true,\"corner_point\":true,\"edge_midpoint\":true,\"align_with_edges\":true,\"distribute_evenly\":true},\"path\":{\"anchor_point\":true,\"line_midpoint\":true,\"along_path\":true,\"normal_to_path\":true,\"tangent_to_path\":true,\"path_intersection_point\":true,\"align_with_anchor_point\":true,\"perpendicular_from_endpoint\":true},\"grid\":{\"origin\":[0.0,0.0],\"grid_type\":{\"Rectangular\":{\"spacing\":[1.0,1.0]}},\"rectangular_spacing\":[1.0,1.0],\"isometric_y_spacing\":1.0,\"isometric_angle_a\":30.0,\"isometric_angle_b\":30.0,\"grid_color\":{\"red\":0.6038274,\"green\":0.6038274,\"blue\":0.6038274,\"alpha\":1.0},\"dot_display\":false}},\"graph_view_overlay_open\":false,\"graph_fade_artwork_percentage\":80.0}"
  },
  {
    "path": "demo-artwork/isometric-fountain.graphite",
    "content": "{\"network_interface\":{\"network\":{\"exports\":[{\"Node\":{\"node_id\":15709488322180832347,\"output_index\":0}}],\"nodes\":[[10507084483235320484,{\"inputs\":[{\"Node\":{\"node_id\":9157963288496356916,\"output_index\":0}},{\"Node\":{\"node_id\":1396768435017101055,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4741515246389989284,{\"inputs\":[{\"Node\":{\"node_id\":14255588039347536657,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-261.2435565297001,-70.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4105329493214975815,{\"inputs\":[{\"Node\":{\"node_id\":12931264630175648107,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-261.24355652969916,-70.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16229837691656808412,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[1,[74.85684586229115,93.12923138495351]],[3,[130.01864188394373,17.639788893964138]],[4,[119.2038270691288,10.452135486817724]],[2,[76.83345669875837,107.20900827532364]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[1,1],[2,2],[4,4],[3,3]],\"end_point\":[[2,3],[1,2],[3,4],[4,1]],\"handle_primary\":[[1,[0.0,0.0]],[4,[0.0,0.0]],[3,[0.0,0.0]],[2,[0.0,0.0]]],\"handle_end\":[[4,[43.97876382175265,-36.84748630595526]],[3,[0.0,0.0]],[1,[0.0,0.0]],[2,[-7.85185185185199,56.395434425300664]]],\"stroke\":[[1,0],[2,0],[4,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12172015233077238737,{\"inputs\":[{\"Node\":{\"node_id\":13287180494862716983,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.9372549,\"green\":0.90588236,\"blue\":0.7764706,\"alpha\":0.7490196}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":3.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeAlign\":\"Center\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeCap\":\"Round\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeJoin\":\"Miter\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"PaintOrder\":\"StrokeAbove\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"VecF64\":[]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::StrokeNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15166516760575860563,{\"inputs\":[{\"Node\":{\"node_id\":18085100003956405261,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-36.95875397623445,-115.11453403741598]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.442673035713692,1.442673035713692]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10477328336261010694,{\"inputs\":[{\"Node\":{\"node_id\":10189927996178548902,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-261.24355652970024,-64.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3185536512640676801,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":4101813853952238986,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5258402282444994019,{\"inputs\":[{\"Node\":{\"node_id\":958845362613832240,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-299.97967195575075,-74.37931084632919]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.9999999999999984,0.9999999999999984]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3970872207068447290,{\"inputs\":[{\"Node\":{\"node_id\":2077983679740571162,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.2314781197853364,-154.7967075368743]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":-59.89430248166697},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.809730022247584,0.552568608414892]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-21.93845255979099,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9115451226763736660,{\"inputs\":[{\"Node\":{\"node_id\":7067047867039575315,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.4745098,\"green\":0.68235296,\"blue\":0.60784316,\"alpha\":1.0},{\"red\":0.5568628,\"green\":0.7529412,\"blue\":0.6392157,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.02103945787988068,0.9859744936226362],\"end\":[0.6072632276568447,0.28531051081023584]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.4745098,\"green\":0.68235296,\"blue\":0.60784316,\"alpha\":1.0},{\"red\":0.5568628,\"green\":0.7529412,\"blue\":0.6392157,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.02103945787988068,0.9859744936226362],\"end\":[0.6072632276568447,0.28531051081023584]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11860177410232537211,{\"inputs\":[{\"Node\":{\"node_id\":5882319123081134737,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"BooleanOperation\":\"SubtractFront\"},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::style::Fill\",\"alias\":null}},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"path_bool_nodes::BooleanOperationNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14019233912018234740,{\"inputs\":[{\"Node\":{\"node_id\":16069762220015310717,\"output_index\":0}},{\"Node\":{\"node_id\":17785019773455930267,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4197544064668946479,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7],\"remove\":[],\"delta\":[[2,[363.87495373796554,587.4999999999999]],[4,[419.8557158514987,612.5]],[6,[634.4486372867087,565.0]],[5,[615.7883832488644,560.0000000000001]],[7,[578.4678751731759,580.0]],[3,[345.21469970012123,592.5]],[1,[391.8653347947321,595.0]]]},\"segments\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"start_point\":[[1,1],[2,2],[4,4],[5,5],[6,6],[3,3]],\"end_point\":[[4,5],[5,6],[6,7],[1,2],[3,4],[2,3]],\"handle_primary\":[[6,[0.0,0.0]],[4,[0.0,0.0]],[3,[0.0,0.0]],[2,[0.0,0.0]],[5,[0.0,0.0]],[1,[0.0,0.0]]],\"handle_end\":[[2,[0.0,0.0]],[5,[0.0,0.0]],[1,[0.0,0.0]],[6,[0.0,0.0]],[3,[0.0,0.0]],[4,[0.0,0.0]]],\"stroke\":[[2,0],[1,0],[5,0],[6,0],[3,0],[4,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5448146793323825465,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[2,[597.1281292110198,599.9999999999999]],[1,[709.0896534380863,630.0]],[3,[783.730669589464,550.0]],[4,[895.6921938165308,580.0000000000001]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[4,4],[3,3],[2,2],[1,1]],\"end_point\":[[2,3],[3,4],[4,1],[1,2]],\"handle_primary\":[[2,[0.0,0.0]],[1,[0.0,0.0]],[4,[0.0,0.0]],[3,[0.0,0.0]]],\"handle_end\":[[1,[0.0,0.0]],[2,[0.0,0.0]],[4,[0.0,0.0]],[3,[0.0,0.0]]],\"stroke\":[[2,0],[1,0],[4,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4187349759243468746,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18],\"remove\":[],\"delta\":[[14,[650.6666666666667,398.2222222222223]],[5,[574.0246913580246,440.2962962962963]],[6,[594.4362139917694,493.6515775034293]],[11,[615.6378600823045,431.5390946502058]],[16,[616.0751917898693,491.077444156548]],[7,[596.3676268861453,491.9835390946502]],[15,[615.4183813443072,491.19341563786]],[9,[606.0246913580247,486.803840877915]],[18,[618.4910836762688,499.9725651577503]],[4,[591.18792866941,498.3045267489712]],[2,[533.9259259259258,420.7407407407407]],[8,[580.9382716049382,401.9753086419752]],[10,[607.8683127572016,488.3840877914952]],[13,[609.7997256515774,497.6899862825788]],[12,[609.0096021947874,496.4609053497942]],[3,[589.8710562414265,497.0754458161865]],[17,[668.3566529492455,433.2510288065844]],[1,[583.5500685871057,499.53360768175577]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18],\"remove\":[],\"start_point\":[[14,14],[5,5],[2,2],[9,9],[1,1],[15,15],[7,7],[3,3],[6,6],[13,13],[4,4],[10,10],[8,8],[16,16],[17,17],[12,12],[11,11],[18,18]],\"end_point\":[[16,17],[3,4],[7,8],[14,15],[13,14],[8,9],[6,7],[18,1],[17,18],[10,11],[11,12],[9,10],[15,16],[12,13],[5,6],[2,3],[1,2],[4,5]],\"handle_primary\":[[7,[0.0,0.0]],[8,[22.10150891632361,44.44444444444463]],[11,[-3.906721536351256,29.62962962962956]],[4,[0.0,0.0]],[12,[0.0,0.0]],[2,[44.005486968450214,35.16049382716062]],[6,[0.0,0.0]],[18,[-6.945585968035971,18.68277302655963]],[3,[0.0,0.0]],[17,[-38.27709190672158,34.5020576131688]],[9,[0.0,0.0]],[15,[0.0,0.0]],[14,[-18.3045267489714,37.31138545953348]],[13,[0.0,0.0]],[1,[0.0,0.0]],[16,[0.0,0.0]],[10,[0.0,0.0]],[5,[18.392318244170156,23.769547325102906]]],\"handle_end\":[[17,null],[15,[0.0,0.0]],[14,null],[9,[0.0,0.0]],[6,[0.0,0.0]],[18,[7.8075669002856785,13.340866152962064]],[4,[21.113854595336193,37.201646090535064]],[5,null],[3,[0.0,0.0]],[12,[0.0,0.0]],[7,[25.964334705075316,61.47599451303165]],[10,[-6.189300411522709,26.732510288065782]],[11,[0.0,0.0]],[1,[48.21947873799752,48.855967078189394]],[2,null],[13,[-23.747599451303245,35.4677640603565]],[8,null],[16,[-35.29218106995893,26.337448559670804]]],\"stroke\":[[5,0],[18,0],[2,0],[10,0],[8,0],[9,0],[1,0],[3,0],[15,0],[13,0],[6,0],[16,0],[11,0],[12,0],[17,0],[14,0],[7,0],[4,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":18}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9684750473849891261,{\"inputs\":[{\"Node\":{\"node_id\":70804263053697201,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.5772391174087621,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.96862745,\"green\":0.8352941,\"blue\":0.62352943,\"alpha\":1.0},{\"red\":0.7921569,\"green\":0.8019608,\"blue\":0.6450981,\"alpha\":1.0},{\"red\":0.6156863,\"green\":0.76862746,\"blue\":0.6666667,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.5772391174087621,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.96862745,\"green\":0.8352941,\"blue\":0.62352943,\"alpha\":1.0},{\"red\":0.7921569,\"green\":0.8019608,\"blue\":0.6450981,\"alpha\":1.0},{\"red\":0.6156863,\"green\":0.76862746,\"blue\":0.6666667,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13531127678140037818,{\"inputs\":[{\"Node\":{\"node_id\":3970872207068447290,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14055195208113082127,{\"inputs\":[{\"Node\":{\"node_id\":2510483139353274965,\"output_index\":0}},{\"Node\":{\"node_id\":12360435709959435360,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[18187802220803838247,{\"inputs\":[{\"Node\":{\"node_id\":11634445349252640936,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"BlendMode\":\"Screen\"},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"blending_nodes::BlendModeNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8015732980153557800,{\"inputs\":[{\"Node\":{\"node_id\":3806549994589872867,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-261.2435565297002,-70.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2791109467690716388,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"delta\":[[4,[615.5514854925251,605.0634765625002]],[3,[783.7306695894646,560.0]],[1,[1156.9357503463468,509.9999999999987]],[5,[596.8912314546803,600.0634765625]],[6,[1044.9742261192855,480.0]],[2,[877.0319397786863,584.9999999999998]]]},\"segments\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"start_point\":[[1,1],[2,2],[6,6],[3,3],[4,4],[5,5]],\"end_point\":[[6,1],[3,4],[2,3],[5,6],[4,5],[1,2]],\"handle_primary\":[[3,null],[1,[0.0,0.0]],[4,null],[6,[0.0,0.0]],[2,[0.0,0.0]],[5,[448.0829946646052,-120.0634765625]]],\"handle_end\":[[6,[0.0,0.0]],[5,null],[2,null],[3,null],[4,null],[1,[0.0,0.0]]],\"stroke\":[[1,0],[3,0],[6,0],[5,0],[2,0],[4,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":6}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13747030364552895864,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5],\"remove\":[],\"delta\":[[1,[709.0896534380864,650.0]],[2,[1231.576766497729,510.00000000000233]],[5,[709.0896534380863,660.0]],[3,[1231.6308657449686,540.0144958496094]],[4,[1156.9357503463516,540.0]]]},\"segments\":{\"add\":[1,2,3,4,5],\"remove\":[],\"start_point\":[[4,4],[5,5],[2,2],[1,1],[3,3]],\"end_point\":[[1,2],[5,1],[3,4],[4,5],[2,3]],\"handle_primary\":[[2,[0.0,0.0]],[4,[0.0,0.0]],[3,[-74.69511539861696,-0.014495849609375]],[5,[0.0,0.0]],[1,[0.0,0.0]]],\"handle_end\":[[2,null],[1,[0.0,0.0]],[4,[0.0,0.0]],[3,null],[5,[-2.273736754432321e-13,-2.273736754432321e-13]]],\"stroke\":[[1,0],[4,0],[2,0],[5,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":5}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8034980397175569257,{\"inputs\":[{\"Node\":{\"node_id\":4243146970185091100,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[55.4066256813212,48.820258260598735]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.093432966432927,1.093432966432927]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11776939455674933130,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":9684750473849891261,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4968550668755026811,{\"inputs\":[{\"Node\":{\"node_id\":585709295659496998,\"output_index\":0}},{\"Node\":{\"node_id\":13609749019463823009,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17533670083736420411,{\"inputs\":[{\"Node\":{\"node_id\":7005645574203740491,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.8397260273972602,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.27058825,\"green\":0.49411765,\"blue\":0.5137255,\"alpha\":1.0},{\"red\":0.27058825,\"green\":0.49411765,\"blue\":0.5137255,\"alpha\":1.0},{\"red\":0.27058825,\"green\":0.49411765,\"blue\":0.5137255,\"alpha\":0.0}]},\"gradient_type\":\"Radial\",\"start\":[0.9711254481326403,-0.313902431217137],\"end\":[1.0169100960157926,0.2104743282968058]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.8397260273972602,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.27058825,\"green\":0.49411765,\"blue\":0.5137255,\"alpha\":1.0},{\"red\":0.27058825,\"green\":0.49411765,\"blue\":0.5137255,\"alpha\":1.0},{\"red\":0.27058825,\"green\":0.49411765,\"blue\":0.5137255,\"alpha\":0.0}]},\"gradient_type\":\"Radial\",\"start\":[0.9711254481326403,-0.313902431217137],\"end\":[1.0169100960157926,0.2104743282968058]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12683405703338263457,{\"inputs\":[{\"Node\":{\"node_id\":12537712543904859919,\"output_index\":0}},{\"Node\":{\"node_id\":14449710315388146362,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1229809699395562135,{\"inputs\":[{\"Node\":{\"node_id\":2843751023378786714,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-36.95875397623445,-88.44786737074935]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.442673035713692,1.442673035713692]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12302362769310895852,{\"inputs\":[{\"Node\":{\"node_id\":15347111149235590492,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.4047792425110607,0.6425390774124099,0.8010456340133093,0.894708599277477,1.0],\"midpoint\":[0.5,0.5,0.5,0.5,0.5,0.5],\"color\":[{\"red\":0.8014369,\"green\":0.8203125,\"blue\":0.63446045,\"alpha\":1.0},{\"red\":0.70046544,\"green\":0.765625,\"blue\":0.57421875,\"alpha\":1.0},{\"red\":0.6,\"green\":0.70980394,\"blue\":0.56078434,\"alpha\":1.0},{\"red\":0.47058824,\"green\":0.6509804,\"blue\":0.53333336,\"alpha\":1.0},{\"red\":0.40392157,\"green\":0.6117647,\"blue\":0.5176471,\"alpha\":1.0},{\"red\":0.26666668,\"green\":0.5176471,\"blue\":0.4745098,\"alpha\":1.0}]},\"gradient_type\":\"Radial\",\"start\":[0.5027713289486062,0.6166666666666667],\"end\":[0.502771328948606,-0.08890027761186703]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.4047792425110607,0.6425390774124099,0.8010456340133093,0.894708599277477,1.0],\"midpoint\":[0.5,0.5,0.5,0.5,0.5,0.5],\"color\":[{\"red\":0.8014369,\"green\":0.8203125,\"blue\":0.63446045,\"alpha\":1.0},{\"red\":0.70046544,\"green\":0.765625,\"blue\":0.57421875,\"alpha\":1.0},{\"red\":0.6,\"green\":0.70980394,\"blue\":0.56078434,\"alpha\":1.0},{\"red\":0.47058824,\"green\":0.6509804,\"blue\":0.53333336,\"alpha\":1.0},{\"red\":0.40392157,\"green\":0.6117647,\"blue\":0.5176471,\"alpha\":1.0},{\"red\":0.26666668,\"green\":0.5176471,\"blue\":0.4745098,\"alpha\":1.0}]},\"gradient_type\":\"Radial\",\"start\":[0.5027713289486062,0.6166666666666667],\"end\":[0.502771328948606,-0.08890027761186703]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15488533792651297821,{\"inputs\":[{\"Node\":{\"node_id\":14019233912018234740,\"output_index\":0}},{\"Node\":{\"node_id\":183952488591282082,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14791465604033956302,{\"inputs\":[{\"Node\":{\"node_id\":18187802220803838247,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":50.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"blending_nodes::OpacityNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1147521068928676110,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":938033825024582130,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6821938959315178556,{\"inputs\":[{\"Node\":{\"node_id\":12683405703338263457,\"output_index\":0}},{\"Node\":{\"node_id\":5326013268137833446,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1490537476612110327,{\"inputs\":[{\"Node\":{\"node_id\":2900504420179573771,\"output_index\":0}},{\"Node\":{\"node_id\":429913874753911073,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3365825508845848745,{\"inputs\":[{\"Node\":{\"node_id\":7156963182187517674,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.4509804,\"green\":0.6745098,\"blue\":0.627451,\"alpha\":1.0},{\"red\":0.27058825,\"green\":0.49411765,\"blue\":0.5137255,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.008641111778101118,0.8823529411764692],\"end\":[0.9918085332369128,0.12352941176470456]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.4509804,\"green\":0.6745098,\"blue\":0.627451,\"alpha\":1.0},{\"red\":0.27058825,\"green\":0.49411765,\"blue\":0.5137255,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.008641111778101118,0.8823529411764692],\"end\":[0.9918085332369128,0.12352941176470456]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9157963288496356916,{\"inputs\":[{\"Node\":{\"node_id\":10188337730058049439,\"output_index\":0}},{\"Node\":{\"node_id\":1108089904278882840,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3679103217373457623,{\"inputs\":[{\"Node\":{\"node_id\":7910743362843097140,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.9372549,\"green\":0.90588236,\"blue\":0.7764706,\"alpha\":0.7490196}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":3.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeAlign\":\"Center\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeCap\":\"Round\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeJoin\":\"Miter\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"PaintOrder\":\"StrokeAbove\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"VecF64\":[]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::StrokeNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13442128106088307772,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":13700218159488557234,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2225749123534781340,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[2,[1156.9357503463518,545.0000000000001]],[4,[1110.285115251741,542.5]],[3,[1128.9453692895852,537.5000000000001]],[1,[1138.2754963085072,550.0000000000001]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[1,1],[3,3],[4,4],[2,2]],\"end_point\":[[2,3],[1,2],[4,1],[3,4]],\"handle_primary\":[[3,[0.0,0.0]],[4,[0.0,0.0]],[1,[0.0,0.0]],[2,[0.0,0.0]]],\"handle_end\":[[4,[0.0,0.0]],[2,[0.0,0.0]],[1,[0.0,0.0]],[3,[0.0,0.0]]],\"stroke\":[[4,0],[1,0],[3,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11490835759023283071,{\"inputs\":[{\"Node\":{\"node_id\":16923062582661131268,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-261.2435565297001,-64.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15517065353723874205,{\"inputs\":[{\"Node\":{\"node_id\":3616319631707471648,\"output_index\":0}},{\"Node\":{\"node_id\":12548387328300782726,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3014633976566537110,{\"inputs\":[{\"Node\":{\"node_id\":5346759588580719138,\"output_index\":0}},{\"Node\":{\"node_id\":11860177410232537211,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9981992739451603109,{\"inputs\":[{\"Node\":{\"node_id\":13852123721901366011,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.8744713248939212,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.16078432,\"green\":0.2901961,\"blue\":0.16078432,\"alpha\":0.59765625},{\"red\":0.16078432,\"green\":0.29411766,\"blue\":0.16078432,\"alpha\":0.4453125},{\"red\":0.16078432,\"green\":0.2784314,\"blue\":0.16078432,\"alpha\":0.2109375}]},\"gradient_type\":\"Radial\",\"start\":[0.4917953695426216,3.5168687748431413],\"end\":[0.4917953695426216,-0.06740196271505461]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.8744713248939212,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.16078432,\"green\":0.2901961,\"blue\":0.16078432,\"alpha\":0.59765625},{\"red\":0.16078432,\"green\":0.29411766,\"blue\":0.16078432,\"alpha\":0.4453125},{\"red\":0.16078432,\"green\":0.2784314,\"blue\":0.16078432,\"alpha\":0.2109375}]},\"gradient_type\":\"Radial\",\"start\":[0.4917953695426216,3.5168687748431413],\"end\":[0.4917953695426216,-0.06740196271505461]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[18128923159828618806,{\"inputs\":[{\"Node\":{\"node_id\":1229809699395562135,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.3267294443765037,0.5703803350862179,1.0],\"midpoint\":[0.5,0.5,0.5,0.5],\"color\":[{\"red\":0.39215687,\"green\":0.6392157,\"blue\":0.4745098,\"alpha\":1.0},{\"red\":0.45324707,\"green\":0.734375,\"blue\":0.5498848,\"alpha\":1.0},{\"red\":0.72745097,\"green\":0.7784314,\"blue\":0.5235294,\"alpha\":1.0},{\"red\":1.0,\"green\":0.84705883,\"blue\":0.4627451,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.4425774215957188,0.8669102822986486],\"end\":[0.40311512028827146,0.4061330859327766]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.3267294443765037,0.5703803350862179,1.0],\"midpoint\":[0.5,0.5,0.5,0.5],\"color\":[{\"red\":0.39215687,\"green\":0.6392157,\"blue\":0.4745098,\"alpha\":1.0},{\"red\":0.45324707,\"green\":0.734375,\"blue\":0.5498848,\"alpha\":1.0},{\"red\":0.72745097,\"green\":0.7784314,\"blue\":0.5235294,\"alpha\":1.0},{\"red\":1.0,\"green\":0.84705883,\"blue\":0.4627451,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.4425774215957188,0.8669102822986486],\"end\":[0.40311512028827146,0.4061330859327766]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1156213189397385283,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[1,[55.98076211353359,615.0]],[3,[83.97114317030051,612.4999999999999]],[2,[65.3108891324556,617.5]],[4,[74.64101615137773,610.0]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[3,3],[1,1],[4,4],[2,2]],\"end_point\":[[4,1],[3,4],[2,3],[1,2]],\"handle_primary\":[[3,[0.0,0.0]],[4,[0.0,0.0]],[1,[0.0,0.0]],[2,[0.0,0.0]]],\"handle_end\":[[1,[0.0,0.0]],[2,[0.0,0.0]],[4,[0.0,0.0]],[3,[0.0,0.0]]],\"stroke\":[[4,0],[3,0],[2,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8891726805381758817,{\"inputs\":[{\"Node\":{\"node_id\":17332567356044944766,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-74.42590421819692,41.71533421869417]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":38.68758978528783},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[38.26905454222045,23.541084128981048]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[23.61080512850412,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12998832508553378533,{\"inputs\":[{\"Node\":{\"node_id\":3122972215852775755,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.5,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.46666667,\"green\":0.7019608,\"blue\":0.654902,\"alpha\":1.0},{\"red\":0.44313726,\"green\":0.68235296,\"blue\":0.6313726,\"alpha\":1.0},{\"red\":0.40784314,\"green\":0.627451,\"blue\":0.6,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0051615000620980345,0.22838569993468072],\"end\":[0.9899367003601665,0.951087390077165]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.5,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.46666667,\"green\":0.7019608,\"blue\":0.654902,\"alpha\":1.0},{\"red\":0.44313726,\"green\":0.68235296,\"blue\":0.6313726,\"alpha\":1.0},{\"red\":0.40784314,\"green\":0.627451,\"blue\":0.6,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0051615000620980345,0.22838569993468072],\"end\":[0.9899367003601665,0.951087390077165]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9798215931018813676,{\"inputs\":[{\"Node\":{\"node_id\":10779665858841986661,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9210109719406330381,{\"inputs\":[{\"Node\":{\"node_id\":8612613134760093452,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-261.2435565297001,-64.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9409313765472227540,{\"inputs\":[{\"Node\":{\"node_id\":6821938959315178556,\"output_index\":0}},{\"Node\":{\"node_id\":8463468388280418154,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13920465562072008593,{\"inputs\":[{\"Node\":{\"node_id\":3670594928372882885,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-261.2435565297001,-63.99999999999994]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3122972215852775755,{\"inputs\":[{\"Node\":{\"node_id\":10431241258085047322,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-261.2435565297001,-64.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3564067978712674849,{\"inputs\":[{\"Node\":{\"node_id\":6777328619777499144,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-261.24355652970075,-64.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2189393878093040029,{\"inputs\":[{\"Node\":{\"node_id\":15478704582542175684,\"output_index\":0}},{\"Node\":{\"node_id\":17533670083736420411,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10917301734480569398,{\"inputs\":[{\"Node\":{\"node_id\":3455270778005546310,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-261.2435565297001,-64.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13946577152348504742,{\"inputs\":[{\"Node\":{\"node_id\":3021739385836969518,\"output_index\":0}},{\"Node\":{\"node_id\":15876464101883822838,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2510483139353274965,{\"inputs\":[{\"Node\":{\"node_id\":14202574750104046500,\"output_index\":0}},{\"Node\":{\"node_id\":18128923159828618806,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16847360882244487081,{\"inputs\":[{\"Node\":{\"node_id\":13817976820605296433,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.972549,\"green\":0.87058824,\"blue\":0.6666667,\"alpha\":1.0},{\"red\":0.9490196,\"green\":0.92156863,\"blue\":0.7411765,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.6476985249926699,0.10666666666666667],\"end\":[0.9920644403070608,0.4398792234469302]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.972549,\"green\":0.87058824,\"blue\":0.6666667,\"alpha\":1.0},{\"red\":0.9490196,\"green\":0.92156863,\"blue\":0.7411765,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.6476985249926699,0.10666666666666667],\"end\":[0.9920644403070608,0.4398792234469302]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7671691070850213967,{\"inputs\":[{\"Node\":{\"node_id\":1658032775659237960,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.6048571201787465,0.8131497297124398,1.0],\"midpoint\":[0.5,0.5,0.5,0.5],\"color\":[{\"red\":0.22091675,\"green\":0.4453125,\"blue\":0.44150904,\"alpha\":1.0},{\"red\":0.3202623,\"green\":0.5461857,\"blue\":0.5423232,\"alpha\":0.859375},{\"red\":0.41960785,\"green\":0.64705884,\"blue\":0.6431373,\"alpha\":0.71875},{\"red\":0.43137255,\"green\":0.61960787,\"blue\":0.5803922,\"alpha\":0.3984375}]},\"gradient_type\":\"Radial\",\"start\":[0.3691427845059252,-2.109375000000002],\"end\":[0.05794738050227899,1.089204545454546]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.6048571201787465,0.8131497297124398,1.0],\"midpoint\":[0.5,0.5,0.5,0.5],\"color\":[{\"red\":0.22091675,\"green\":0.4453125,\"blue\":0.44150904,\"alpha\":1.0},{\"red\":0.3202623,\"green\":0.5461857,\"blue\":0.5423232,\"alpha\":0.859375},{\"red\":0.41960785,\"green\":0.64705884,\"blue\":0.6431373,\"alpha\":0.71875},{\"red\":0.43137255,\"green\":0.61960787,\"blue\":0.5803922,\"alpha\":0.3984375}]},\"gradient_type\":\"Radial\",\"start\":[0.3691427845059252,-2.109375000000002],\"end\":[0.05794738050227899,1.089204545454546]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16244305414728361140,{\"inputs\":[{\"Node\":{\"node_id\":11547499603328872398,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.5294118,\"green\":0.7490196,\"blue\":0.65882355,\"alpha\":1.0},{\"red\":0.46666667,\"green\":0.69803923,\"blue\":0.62352943,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.008550171763040293,0.13076923076922986],\"end\":[0.99103054401049,0.7769230769230764]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.5294118,\"green\":0.7490196,\"blue\":0.65882355,\"alpha\":1.0},{\"red\":0.46666667,\"green\":0.69803923,\"blue\":0.62352943,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.008550171763040293,0.13076923076922986],\"end\":[0.99103054401049,0.7769230769230764]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9724746185253267560,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11],\"remove\":[],\"delta\":[[2,[1063.63448015713,453.99999999999994]],[4,[1184.9261314031187,496.5]],[1,[1091.6248612138966,461.5]],[7,[1138.2754963085074,473.99999999999994]],[8,[1184.9261314031187,486.5]],[5,[1259.567147554496,476.49999999999994]],[6,[1194.256258422041,458.99999999999994]],[10,[1194.2562584220411,469.00000000000006]],[9,[1222.2466394788075,476.5000000000001]],[11,[1175.5960043841962,474.00000000000006]],[3,[1044.9742261192855,459.0]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10],\"remove\":[],\"start_point\":[[5,5],[6,6],[3,3],[4,4],[7,7],[1,1],[2,2],[8,8],[10,10],[9,9]],\"end_point\":[[1,2],[3,4],[8,9],[4,5],[9,10],[6,7],[2,3],[10,11],[5,6],[7,8]],\"handle_primary\":[[3,[0.0,0.0]],[6,[0.0,0.0]],[9,[0.0,0.0]],[4,[0.0,0.0]],[2,[0.0,0.0]],[5,[0.0,0.0]],[10,[0.0,0.0]],[8,[0.0,0.0]],[1,[0.0,0.0]],[7,[0.0,0.0]]],\"handle_end\":[[5,[0.0,0.0]],[8,[0.0,0.0]],[9,[0.0,0.0]],[4,[0.0,0.0]],[10,[0.0,0.0]],[6,[0.0,0.0]],[7,[0.0,0.0]],[3,[0.0,0.0]],[2,[0.0,0.0]],[1,[0.0,0.0]]],\"stroke\":[[3,0],[1,0],[9,0],[6,0],[10,0],[8,0],[4,0],[2,0],[7,0],[5,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1108089904278882840,{\"inputs\":[{\"Node\":{\"node_id\":5317925967883407701,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13001069903842109798,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[2,[1.067391838882569,0.5169672994595966]],[3,[1.0311603768047983,1.0082447817061446]],[4,[0.15793848790232112,1.0756444843098496]],[1,[0.02964805558748984,0.4451546735104888]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[4,4],[1,1],[2,2],[3,3]],\"end_point\":[[3,4],[1,2],[4,1],[2,3]],\"handle_primary\":[[3,[-0.4856258676143469,-0.19200483651697595]],[4,[0.0,0.0]],[1,[0.0,0.0]],[2,[0.0,0.0]]],\"handle_end\":[[1,[0.0,0.0]],[3,[0.0,0.0]],[2,null],[4,[0.0,0.0]]],\"stroke\":[[1,0],[4,0],[2,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4837219841531371489,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[1,[933.0127018922194,430.0]],[4,[597.1281292110205,340.0000000000001]],[3,[1007.6537180435968,230.0]],[2,[1343.5382907247958,320.00000000000006]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[4,4],[1,1],[2,2],[3,3]],\"end_point\":[[4,1],[3,4],[1,2],[2,3]],\"handle_primary\":[[3,[0.0,0.0]],[2,[0.0,0.0]],[1,[0.0,0.0]],[4,[0.0,0.0]]],\"handle_end\":[[1,[0.0,0.0]],[3,[0.0,0.0]],[4,[0.0,0.0]],[2,[0.0,0.0]]],\"stroke\":[[3,0],[2,0],[1,0],[4,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17785019773455930267,{\"inputs\":[{\"Node\":{\"node_id\":17887542695709892422,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,-6.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10189927996178548902,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"delta\":[[1,[298.56406460551045,540.0000000000002]],[4,[597.1281292110198,630.0]],[2,[298.5640646055101,570.0000000000006]],[5,[709.0896534380867,660.0]],[6,[709.0896534380864,649.9999999999999]],[3,[597.1281292110203,649.9999999999999]]]},\"segments\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"start_point\":[[3,3],[6,6],[4,4],[5,5],[2,2],[1,1]],\"end_point\":[[2,3],[3,4],[5,6],[1,2],[6,1],[4,5]],\"handle_primary\":[[3,[-5.684341886080801e-13,-19.999999999999886]],[4,[0.0,0.0]],[1,[0.0,0.0]],[2,[0.0,0.0]],[6,[0.0,0.0]],[5,[-2.273736754432321e-13,-10.000000000000114]]],\"handle_end\":[[1,[0.0,0.0]],[6,[0.0,0.0]],[3,null],[5,null],[4,null],[2,null]],\"stroke\":[[5,0],[1,0],[4,0],[2,0],[3,0],[6,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":6}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13868917743026516656,{\"inputs\":[{\"Node\":{\"node_id\":5258402282444994019,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.5,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.9529412,\"green\":0.83137256,\"blue\":0.49411765,\"alpha\":1.0},{\"red\":0.6039216,\"green\":0.77254903,\"blue\":0.6117647,\"alpha\":1.0},{\"red\":0.48010254,\"green\":0.71875,\"blue\":0.58451086,\"alpha\":1.0}]},\"gradient_type\":\"Radial\",\"start\":[0.990188567383927,0.031432585832447346],\"end\":[0.003820820042059303,0.7823921936299838]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.5,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.9529412,\"green\":0.83137256,\"blue\":0.49411765,\"alpha\":1.0},{\"red\":0.6039216,\"green\":0.77254903,\"blue\":0.6117647,\"alpha\":1.0},{\"red\":0.48010254,\"green\":0.71875,\"blue\":0.58451086,\"alpha\":1.0}]},\"gradient_type\":\"Radial\",\"start\":[0.990188567383927,0.031432585832447346],\"end\":[0.003820820042059303,0.7823921936299838]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9393309733761233513,{\"inputs\":[{\"Node\":{\"node_id\":13946577152348504742,\"output_index\":0}},{\"Node\":{\"node_id\":11895211316848895241,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11634802583144606404,{\"inputs\":[{\"Node\":{\"node_id\":9226731772122225003,\"output_index\":0}},{\"Node\":{\"node_id\":6868877732348460627,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8165914767449151618,{\"inputs\":[{\"Node\":{\"node_id\":11158238411769751544,\"output_index\":0}},{\"Node\":{\"node_id\":13942146309185231085,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14400993470150734626,{\"inputs\":[{\"Node\":{\"node_id\":2088390810384907709,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-261.2435565297001,-69.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14202574750104046500,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":1831743139584171612,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15216519480392295991,{\"inputs\":[{\"Node\":{\"node_id\":12019361655085452072,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.6837652235970189,0.7972664177223364,1.0],\"midpoint\":[0.5,0.5,0.5,0.5],\"color\":[{\"red\":0.79607844,\"green\":0.7607843,\"blue\":0.57254905,\"alpha\":0.01},{\"red\":0.6666667,\"green\":0.6666667,\"blue\":0.6666667,\"alpha\":0.011764706},{\"red\":0.79607844,\"green\":0.7607843,\"blue\":0.57254905,\"alpha\":0.53515625},{\"red\":0.9647059,\"green\":0.7607843,\"blue\":0.5568628,\"alpha\":1.0}]},\"gradient_type\":\"Radial\",\"start\":[0.5377061694297174,0.1861799653787184],\"end\":[0.4291107938423666,0.936951921882358]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.6837652235970189,0.7972664177223364,1.0],\"midpoint\":[0.5,0.5,0.5,0.5],\"color\":[{\"red\":0.79607844,\"green\":0.7607843,\"blue\":0.57254905,\"alpha\":0.01},{\"red\":0.6666667,\"green\":0.6666667,\"blue\":0.6666667,\"alpha\":0.011764706},{\"red\":0.79607844,\"green\":0.7607843,\"blue\":0.57254905,\"alpha\":0.53515625},{\"red\":0.9647059,\"green\":0.7607843,\"blue\":0.5568628,\"alpha\":1.0}]},\"gradient_type\":\"Radial\",\"start\":[0.5377061694297174,0.1861799653787184],\"end\":[0.4291107938423666,0.936951921882358]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1831743139584171612,{\"inputs\":[{\"Node\":{\"node_id\":6569279146800941123,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.31019165912642926,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.44433594,\"green\":0.7109375,\"blue\":0.60144055,\"alpha\":1.0},{\"red\":0.42991638,\"green\":0.68359375,\"blue\":0.5723318,\"alpha\":0.85546875},{\"red\":0.41487122,\"green\":0.66796875,\"blue\":0.5466929,\"alpha\":0.3515625}]},\"gradient_type\":\"Radial\",\"start\":[0.49564744287268736,0.12680209698558276],\"end\":[0.6983677910772572,0.8876146788990837]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.31019165912642926,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.44433594,\"green\":0.7109375,\"blue\":0.60144055,\"alpha\":1.0},{\"red\":0.42991638,\"green\":0.68359375,\"blue\":0.5723318,\"alpha\":0.85546875},{\"red\":0.41487122,\"green\":0.66796875,\"blue\":0.5466929,\"alpha\":0.3515625}]},\"gradient_type\":\"Radial\",\"start\":[0.49564744287268736,0.12680209698558276],\"end\":[0.6983677910772572,0.8876146788990837]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12360435709959435360,{\"inputs\":[{\"Node\":{\"node_id\":15723520455917422372,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.47843137,\"green\":0.7294118,\"blue\":0.5686275,\"alpha\":1.0},{\"red\":0.39215687,\"green\":0.6392157,\"blue\":0.4745098,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.5005723020410011,0.4324498034559026],\"end\":[0.5007962736667897,0.9150264743070644]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.47843137,\"green\":0.7294118,\"blue\":0.5686275,\"alpha\":1.0},{\"red\":0.39215687,\"green\":0.6392157,\"blue\":0.4745098,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.5005723020410011,0.4324498034559026],\"end\":[0.5007962736667897,0.9150264743070644]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2077983679740571162,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[4,[-124.49987663457466,76.24680898300153]],[1,[-137.83320996790803,20.957167389569804]],[2,[-130.2776544123526,8.793446239014884]],[3,[-119.7591358938339,55.2367451774976]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[2,2],[4,4],[3,3],[1,1]],\"end_point\":[[4,1],[2,3],[3,4],[1,2]],\"handle_primary\":[[3,[0.0,0.0]],[4,[0.0,0.0]],[2,[0.0,0.0]],[1,[0.0,0.0]]],\"handle_end\":[[3,[0.0,0.0]],[2,[-5.037037037036939,-9.952135486817731]],[4,[6.814814814814781,36.49116345166567]],[1,[0.0,0.0]]],\"stroke\":[[3,0],[1,0],[2,0],[4,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2058192342619930156,{\"inputs\":[{\"Node\":{\"node_id\":2155997486525176376,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,-5.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10181153433637856462,{\"inputs\":[{\"Node\":{\"node_id\":9150078008481575131,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[187.102540378,187.10254037799996]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":180.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[373.205080756,373.205080756]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-1.8801215900696402e-15,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11579925754926059876,{\"inputs\":[{\"Node\":{\"node_id\":15670426414376277308,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1175.596004383839,384.99999999999983]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":180.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[410.52558883186134,109.99999999999974]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-1.8801215900696402e-15,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9138781233934614517,{\"inputs\":[{\"Node\":{\"node_id\":8073807569018624098,\"output_index\":0}},{\"Node\":{\"node_id\":4884180935153120645,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12370676490908282512,{\"inputs\":[{\"Node\":{\"node_id\":9666682009015049330,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.643741535074667,0.8214781907174007,1.0],\"midpoint\":[0.5,0.5,0.5,0.5],\"color\":[{\"red\":0.27058825,\"green\":0.49411765,\"blue\":0.5137255,\"alpha\":1.0},{\"red\":0.27450982,\"green\":0.49411765,\"blue\":0.5137255,\"alpha\":0.5019608},{\"red\":0.27450982,\"green\":0.5019608,\"blue\":0.52156866,\"alpha\":0.17254902},{\"red\":0.0,\"green\":1.0,\"blue\":1.0,\"alpha\":0.003921569}]},\"gradient_type\":\"Linear\",\"start\":[0.8142178455184718,0.014814814814815058],\"end\":[0.8217337510617708,0.33779273207824057]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.643741535074667,0.8214781907174007,1.0],\"midpoint\":[0.5,0.5,0.5,0.5],\"color\":[{\"red\":0.27058825,\"green\":0.49411765,\"blue\":0.5137255,\"alpha\":1.0},{\"red\":0.27450982,\"green\":0.49411765,\"blue\":0.5137255,\"alpha\":0.5019608},{\"red\":0.27450982,\"green\":0.5019608,\"blue\":0.52156866,\"alpha\":0.17254902},{\"red\":0.0,\"green\":1.0,\"blue\":1.0,\"alpha\":0.003921569}]},\"gradient_type\":\"Linear\",\"start\":[0.8142178455184718,0.014814814814815058],\"end\":[0.8217337510617708,0.33779273207824057]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16261620049358949344,{\"inputs\":[{\"Node\":{\"node_id\":16059265180575745658,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.9372549,\"green\":0.90588236,\"blue\":0.7764706,\"alpha\":0.7490196}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":3.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeAlign\":\"Center\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeCap\":\"Butt\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeJoin\":\"Miter\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"PaintOrder\":\"StrokeAbove\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"VecF64\":[]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::StrokeNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7340659059180155803,{\"inputs\":[{\"Node\":{\"node_id\":7171713123860587892,\"output_index\":0}},{\"Node\":{\"node_id\":13531127678140037818,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6589978257209505606,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3],\"remove\":[],\"delta\":[[1,[709.0896534380868,410.0000000000001]],[3,[821.0511776651532,440.0000000000001]],[2,[709.0896534380868,470.0]]]},\"segments\":{\"add\":[1,2,3],\"remove\":[],\"start_point\":[[1,1],[2,2],[3,3]],\"end_point\":[[3,1],[2,3],[1,2]],\"handle_primary\":[[3,[0.0,0.0]],[1,[0.0,0.0]],[2,[0.0,0.0]]],\"handle_end\":[[2,[0.0,0.0]],[1,[0.0,0.0]],[3,[0.0,0.0]]],\"stroke\":[[1,0],[3,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":3}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16398743435291795904,{\"inputs\":[{\"Node\":{\"node_id\":5574499968250848265,\"output_index\":0}},{\"Node\":{\"node_id\":4741515246389989284,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8181290118694677328,{\"inputs\":[{\"Node\":{\"node_id\":5540780316862276409,\"output_index\":0}},{\"Node\":{\"node_id\":17638504852426495381,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13837327017498431546,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12],\"remove\":[],\"delta\":[[11,[858.3716857408418,479.00000000000006]],[2,[774.4005425705421,566.5]],[4,[718.4197804570089,566.5]],[8,[727.7499074759312,594.0]],[1,[662.4390183434757,536.5]],[7,[634.4486372867094,569.0000000000001]],[9,[1063.63448015713,504.0000000000001]],[12,[1287.5575286112626,593.9999999999999]],[10,[914.3524478543748,464.0]],[3,[746.4101615137755,574.0]],[5,[802.3909236273088,543.9999999999999]],[6,[765.0704155516199,533.9999999999999]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11],\"remove\":[],\"start_point\":[[7,7],[9,9],[11,11],[3,3],[4,4],[5,5],[1,1],[6,6],[8,8],[2,2],[10,10]],\"end_point\":[[5,6],[8,9],[4,5],[6,7],[10,11],[7,8],[2,3],[9,10],[3,4],[11,12],[1,2]],\"handle_primary\":[[3,[0.0,0.0]],[10,[0.0,0.0]],[2,[0.0,0.0]],[4,[0.0,0.0]],[9,[0.0,0.0]],[6,[0.0,0.0]],[8,[0.0,0.0]],[5,[0.0,0.0]],[1,[0.0,0.0]],[7,[0.0,0.0]],[11,[0.0,0.0]]],\"handle_end\":[[5,[0.0,0.0]],[4,[0.0,0.0]],[7,[0.0,0.0]],[1,[0.0,0.0]],[10,[0.0,0.0]],[3,[0.0,0.0]],[11,[0.0,0.0]],[9,[0.0,0.0]],[2,[0.0,0.0]],[6,[0.0,0.0]],[8,[0.0,0.0]]],\"stroke\":[[4,0],[1,0],[5,0],[11,0],[3,0],[9,0],[8,0],[7,0],[6,0],[10,0],[2,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[183952488591282082,{\"inputs\":[{\"Node\":{\"node_id\":17965270694495451178,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,-6.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1032659476619711014,{\"inputs\":[{\"Node\":{\"node_id\":3014633976566537110,\"output_index\":0}},{\"Node\":{\"node_id\":2452294403891427489,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5670058004691708784,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[4,[-115.759135893834,30.3564064604534]],[2,[-117.83320996790816,-58.65991650497199]],[1,[-109.83320996790816,-37.64985269946783]],[3,[-130.2776544123526,8.793446239014884]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[3,3],[1,1],[2,2],[4,4]],\"end_point\":[[1,2],[4,1],[3,4],[2,3]],\"handle_primary\":[[4,[0.0,0.0]],[3,[-0.7407407407406481,44.23171327474574]],[1,[0.0,0.0]],[2,[0.0,0.0]]],\"handle_end\":[[2,[0.7407407407406481,-44.23171327474574]],[1,[0.0,0.0]],[3,[0.0,0.0]],[4,[-12.740740740740762,23.22164946924204]]],\"stroke\":[[3,0],[1,0],[4,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11221222899304956410,{\"inputs\":[{\"Node\":{\"node_id\":5448146793323825465,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-261.2435565297001,-64.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8242413775403456296,{\"inputs\":[{\"Node\":{\"node_id\":9138781233934614517,\"output_index\":0}},{\"Node\":{\"node_id\":9115451226763736660,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15157035456876170143,{\"inputs\":[{\"Node\":{\"node_id\":17059035448296015006,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-261.2435565297001,-63.99999999999994]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9150078008481575131,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[3,[0.5,1.0]],[4,[0.0,0.5]],[2,[1.0,0.5]],[1,[0.5,0.0]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[4,4],[1,1],[3,3],[2,2]],\"end_point\":[[1,2],[2,3],[4,1],[3,4]],\"handle_primary\":[[3,[-0.275892388889507,0.0]],[2,[0.0,0.27589238888950707]],[1,[0.27589238888950707,0.0]],[4,[0.0,-0.275892388889507]]],\"handle_end\":[[4,[-0.275892388889507,0.0]],[1,[0.0,-0.275892388889507]],[2,[0.27589238888950707,0.0]],[3,[0.0,0.27589238888950707]]],\"stroke\":[[1,0],[3,0],[4,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15775513677915164685,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18],\"remove\":[],\"delta\":[[8,[701.8247218411828,611.4580094497792]],[7,[692.1871665904588,615.6720012193263]],[1,[699.8518518518516,598.1234567901233]],[11,[713.6278006401462,612.5505258344765]],[9,[709.3552812071331,611.4970278920897]],[4,[682.3593964334707,606.4197530864195]],[5,[662.6063100137173,612.5432098765428]],[16,[726.2990397805214,601.9862825788753]],[14,[728.493827160494,609.9753086419753]],[18,[725.5089163237311,594.6117969821673]],[10,[709.413808870599,616.4718792866942]],[3,[699.7421124828531,603.0617283950616]],[2,[667.3909465020577,593.3827160493828]],[15,[763.4567901234569,605.6296296296294]],[13,[734.8148148148149,620.9492455418381]],[12,[721.9094650205761,611.3799725651577]],[17,[749.8271604938273,587.4567901234568]],[6,[695.3964334705074,609.7777777777776]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18],\"remove\":[],\"start_point\":[[18,18],[17,17],[15,15],[14,14],[13,13],[11,11],[2,2],[6,6],[5,5],[16,16],[9,9],[3,3],[7,7],[12,12],[8,8],[10,10],[4,4],[1,1]],\"end_point\":[[5,6],[3,4],[2,3],[18,1],[14,15],[6,7],[8,9],[11,12],[15,16],[1,2],[4,5],[9,10],[13,14],[17,18],[10,11],[16,17],[12,13],[7,8]],\"handle_primary\":[[15,[-15.992684042066571,-5.647919524462736]],[9,[1.0398530837227329,0.17152215813985094]],[1,[-13.651577503429507,-4.016460905349504]],[7,[0.0,0.0]],[4,[-6.038618148571572,2.2123832635880945]],[17,[-7.648317030623161,4.388047332817109]],[10,[2.71082158531226,-3.275875783141601]],[13,[-3.101966163694442,-4.096936442615402]],[12,[0.6886396813392821,0.35058484628291353]],[11,[0.9510922553114368,-0.40840938602514143]],[14,[-0.1771601410563335,-2.331086581557088]],[16,[0.0,0.0]],[2,[5.399176954732297,6.584362139917744]],[6,[0.0,0.0]],[8,[0.0,0.0]],[5,[0.0,0.0]],[3,[0.0,0.0]],[18,[-0.0877914951989851,0.0]]],\"handle_end\":[[12,[-6.90626428898031,-2.3996342021031296]],[13,[-0.5267489711934559,1.053497942386798]],[7,[-5.1358024691359105,5.530864197530718]],[16,[-5.977212841894016,7.381559383908893]],[18,[0.0,0.0]],[14,[-22.51851851851859,-0.614540466392441]],[4,[0.0,0.0]],[1,[8.559670781892919,2.502057613168745]],[15,[2.8483462886752022,1.1315348270080676]],[17,null],[8,[-3.7847889041303233,-0.6242950769699291]],[6,[4.13595488492615,-4.1749733272365575]],[11,[-1.2025737194898056,-0.6122274594622468]],[10,null],[5,[-8.69135802469134,3.3580246913579685]],[2,[0.0,0.0]],[9,null],[3,[6.038618148571345,-2.2123832635879808]]],\"stroke\":[[2,0],[8,0],[9,0],[3,0],[6,0],[13,0],[18,0],[7,0],[16,0],[4,0],[11,0],[10,0],[14,0],[15,0],[12,0],[17,0],[5,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":18}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6569279146800941123,{\"inputs\":[{\"Node\":{\"node_id\":15775513677915164685,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,26.66666666666663]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7799679303995308634,{\"inputs\":[{\"Node\":{\"node_id\":4323461535289334196,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-261.2435565297001,-64.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2881239077602364410,{\"inputs\":[{\"Node\":{\"node_id\":9641315149170593327,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.2777869967086046,0.36183495566130336,0.6507416725274494,1.0],\"midpoint\":[0.5,0.5,0.5,0.5,0.5],\"color\":[{\"red\":0.47058824,\"green\":0.73333335,\"blue\":0.6117647,\"alpha\":0.296875},{\"red\":0.46666667,\"green\":0.72156864,\"blue\":0.6,\"alpha\":0.70703125},{\"red\":0.46666667,\"green\":0.72156864,\"blue\":0.6039216,\"alpha\":0.80078125},{\"red\":0.4745098,\"green\":0.7294118,\"blue\":0.5921569,\"alpha\":0.23137255},{\"red\":0.47058824,\"green\":0.7176471,\"blue\":0.6039216,\"alpha\":0.18359375}]},\"gradient_type\":\"Linear\",\"start\":[0.94412535478592,0.7803313772362046],\"end\":[0.3197649256748516,0.1688059143071423]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.2777869967086046,0.36183495566130336,0.6507416725274494,1.0],\"midpoint\":[0.5,0.5,0.5,0.5,0.5],\"color\":[{\"red\":0.47058824,\"green\":0.73333335,\"blue\":0.6117647,\"alpha\":0.296875},{\"red\":0.46666667,\"green\":0.72156864,\"blue\":0.6,\"alpha\":0.70703125},{\"red\":0.46666667,\"green\":0.72156864,\"blue\":0.6039216,\"alpha\":0.80078125},{\"red\":0.4745098,\"green\":0.7294118,\"blue\":0.5921569,\"alpha\":0.23137255},{\"red\":0.47058824,\"green\":0.7176471,\"blue\":0.6039216,\"alpha\":0.18359375}]},\"gradient_type\":\"Linear\",\"start\":[0.94412535478592,0.7803313772362046],\"end\":[0.3197649256748516,0.1688059143071423]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2088390810384907709,{\"inputs\":[{\"Node\":{\"node_id\":14341957170885045113,\"output_index\":0}},{\"Node\":{\"node_id\":9323583246068171750,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2062662104423219162,{\"inputs\":[{\"Node\":{\"node_id\":10810157408196882043,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-261.24355652970144,-64.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6556170892691431702,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":3365825508845848745,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11595529463602678384,{\"inputs\":[{\"Node\":{\"node_id\":4398598693761352299,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-261.2435565297001,-63.99999999999994]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10810157408196882043,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[1,[1231.576766497731,510.00000000000233]],[2,[1231.5767664977286,540.0]],[4,[1306.2177826491084,530.0000000000023]],[3,[1306.217782649106,559.9999999999999]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[4,4],[3,3],[1,1],[2,2]],\"end_point\":[[3,4],[4,1],[2,3],[1,2]],\"handle_primary\":[[3,[0.0,0.0]],[1,[0.0,0.0]],[2,[0.0,0.0]],[4,[0.0,0.0]]],\"handle_end\":[[3,[0.0,0.0]],[2,[0.0,0.0]],[1,[0.0,0.0]],[4,[-6.821210263296962e-13,-1.1368683772161605e-13]]],\"stroke\":[[1,0],[2,0],[4,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4323461535289334196,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3],\"remove\":[],\"delta\":[[2,[597.1281292110198,630.0]],[3,[597.1281292110203,649.9999999999999]],[1,[634.4486372867091,639.9999999999999]]]},\"segments\":{\"add\":[1,2,3],\"remove\":[],\"start_point\":[[2,2],[1,1],[3,3]],\"end_point\":[[3,1],[2,3],[1,2]],\"handle_primary\":[[3,[0.0,0.0]],[1,[0.0,0.0]],[2,[0.0,0.0]]],\"handle_end\":[[1,[0.0,0.0]],[2,[0.0,0.0]],[3,[0.0,1.1368683772161605e-13]]],\"stroke\":[[3,0],[1,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":3}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16807867745126764195,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[1,[0.5,0.0]],[3,[0.5,1.0]],[4,[0.0,0.5]],[2,[1.0,0.5]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[3,3],[4,4],[2,2],[1,1]],\"end_point\":[[2,3],[1,2],[3,4],[4,1]],\"handle_primary\":[[3,[-0.275892388889507,0.0]],[2,[0.0,0.27589238888950707]],[4,[0.0,-0.275892388889507]],[1,[0.27589238888950707,0.0]]],\"handle_end\":[[2,[0.27589238888950707,0.0]],[4,[-0.275892388889507,0.0]],[1,[0.0,-0.275892388889507]],[3,[0.0,0.27589238888950707]]],\"stroke\":[[2,0],[3,0],[4,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17881728913029763313,{\"inputs\":[{\"Node\":{\"node_id\":16793555741218543212,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-261.2435565297001,-64.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15709488322180832347,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Artboard\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":7838724497953148309,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1270.0,635.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.5529412,\"green\":0.78039217,\"blue\":0.70980394,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":1}},{\"Value\":{\"tagged_value\":{\"String\":\"Artboard\"},\"exposed\":false}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":5}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::artboard::CreateArtboardNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Import\":{\"import_type\":{\"Fn\":[{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::artboard::Artboard>\",\"alias\":null}}]},\"import_index\":0}},{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14330881008352607546,{\"inputs\":[{\"Node\":{\"node_id\":11595529463602678384,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.7251131221719457,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.27450982,\"green\":0.50980395,\"blue\":0.54509807,\"alpha\":1.0},{\"red\":0.39215687,\"green\":0.6156863,\"blue\":0.5921569,\"alpha\":0.5019608},{\"red\":0.5058824,\"green\":0.7294118,\"blue\":0.6392157,\"alpha\":0.0}]},\"gradient_type\":\"Linear\",\"start\":[0.3284027974661107,0.17846479235419954],\"end\":[0.3331499206771569,0.5505113976358942]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.7251131221719457,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.27450982,\"green\":0.50980395,\"blue\":0.54509807,\"alpha\":1.0},{\"red\":0.39215687,\"green\":0.6156863,\"blue\":0.5921569,\"alpha\":0.5019608},{\"red\":0.5058824,\"green\":0.7294118,\"blue\":0.6392157,\"alpha\":0.0}]},\"gradient_type\":\"Linear\",\"start\":[0.3284027974661107,0.17846479235419954],\"end\":[0.3331499206771569,0.5505113976358942]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[70804263053697201,{\"inputs\":[{\"Node\":{\"node_id\":13942787566051910019,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[3.410605131648481e-13,3.410605131648481e-13]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1268.999999999999,634.9999999999992]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[194878846429432339,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[3,[410.5255888325765,410.0]],[2,[559.8076211353317,450.0000000000001]],[1,[671.7691453623979,420.00000000000006]],[4,[522.4871130596428,380.00000000000006]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[2,2],[4,4],[3,3],[1,1]],\"end_point\":[[1,2],[4,1],[3,4],[2,3]],\"handle_primary\":[[2,[0.0,0.0]],[1,[0.0,0.0]],[3,[0.0,0.0]],[4,[0.0,0.0]]],\"handle_end\":[[1,[0.0,0.0]],[4,[0.0,0.0]],[3,[0.0,0.0]],[2,[0.0,0.0]]],\"stroke\":[[4,0],[2,0],[3,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3806549994589872867,{\"inputs\":[{\"Node\":{\"node_id\":11429712783984224234,\"output_index\":0}},{\"Node\":{\"node_id\":11479492521093639512,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[326112971739898070,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[4,[578.4678751731759,605.0]],[1,[559.8076211353317,610.0]],[3,[597.1281292110207,610.0]],[2,[578.4678751731759,615.0]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[1,1],[3,3],[4,4],[2,2]],\"end_point\":[[2,3],[1,2],[4,1],[3,4]],\"handle_primary\":[[4,[0.0,0.0]],[1,[0.0,0.0]],[2,[0.0,0.0]],[3,[0.0,0.0]]],\"handle_end\":[[2,[0.0,0.0]],[3,[0.0,0.0]],[4,[-1.1368683772161605e-13,-1.1368683772161605e-13]],[1,[0.0,0.0]]],\"stroke\":[[4,0],[2,0],[1,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4898866541060902381,{\"inputs\":[{\"Node\":{\"node_id\":7799679303995308634,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.39520264,\"green\":0.72265625,\"blue\":0.56068987,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.39520264,\"green\":0.72265625,\"blue\":0.56068987,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10779665858841986661,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[2,[97.17913571110468,-93.86098831945704]],[1,[98.808765340734,-112.29086885060116]],[3,[122.7099999086348,-47.60198818628578]],[4,[127.45074064937567,-74.14101615113282]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[4,4],[2,2],[3,3],[1,1]],\"end_point\":[[2,3],[1,2],[3,4],[4,1]],\"handle_primary\":[[1,[0.0,0.0]],[4,[0.0,0.0]],[2,[0.0,0.0]],[3,[0.0,0.0]]],\"handle_end\":[[3,[0.0,0.0]],[2,[-5.92592592592608,-17.692685309898252]],[1,[0.0,0.0]],[4,[0.0,0.0]]],\"stroke\":[[2,0],[4,0],[3,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7952384394377946257,{\"inputs\":[{\"Node\":{\"node_id\":9935922395919478146,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.15303737473719153,0.45863166412588735,0.6476150394844262,1.0],\"midpoint\":[0.5,0.5,0.5,0.5,0.5],\"color\":[{\"red\":0.44705883,\"green\":0.654902,\"blue\":0.50980395,\"alpha\":1.0},{\"red\":0.44705883,\"green\":0.654902,\"blue\":0.50980395,\"alpha\":1.0},{\"red\":0.7921569,\"green\":0.8509804,\"blue\":0.58431375,\"alpha\":1.0},{\"red\":0.9764706,\"green\":0.92941177,\"blue\":0.54509807,\"alpha\":1.0},{\"red\":0.9764706,\"green\":0.92941177,\"blue\":0.54509807,\"alpha\":1.0}]},\"gradient_type\":\"Radial\",\"start\":[0.5550432633852689,0.9407795494396834],\"end\":[0.3799981368712242,0.012217625842737945]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.15303737473719153,0.45863166412588735,0.6476150394844262,1.0],\"midpoint\":[0.5,0.5,0.5,0.5,0.5],\"color\":[{\"red\":0.44705883,\"green\":0.654902,\"blue\":0.50980395,\"alpha\":1.0},{\"red\":0.44705883,\"green\":0.654902,\"blue\":0.50980395,\"alpha\":1.0},{\"red\":0.7921569,\"green\":0.8509804,\"blue\":0.58431375,\"alpha\":1.0},{\"red\":0.9764706,\"green\":0.92941177,\"blue\":0.54509807,\"alpha\":1.0},{\"red\":0.9764706,\"green\":0.92941177,\"blue\":0.54509807,\"alpha\":1.0}]},\"gradient_type\":\"Radial\",\"start\":[0.5550432633852689,0.9407795494396834],\"end\":[0.3799981368712242,0.012217625842737945]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9666682009015049330,{\"inputs\":[{\"Node\":{\"node_id\":2791109467690716388,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-261.2435565297001,-64.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[958845362613832240,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"delta\":[[1,[971.7488173182716,440.37931084632993]],[4,[1195.671865772399,520.3793108463286]],[2,[971.7488173182714,480.37931084632993]],[6,[1382.2744061508486,330.3793108463298]],[5,[1382.2744061508483,470.3793108463301]],[3,[1195.6718657723986,420.3793108463287]]]},\"segments\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"start_point\":[[6,6],[3,3],[5,5],[4,4],[1,1],[2,2]],\"end_point\":[[6,1],[2,3],[5,6],[4,5],[1,2],[3,4]],\"handle_primary\":[[5,[0.0,0.0]],[2,[0.0,0.0]],[1,[0.0,0.0]],[4,[0.0,0.0]],[6,[0.0,0.0]],[3,[0.0,0.0]]],\"handle_end\":[[6,[0.0,0.0]],[2,[0.0,0.0]],[5,[0.0,0.0]],[4,[0.0,0.0]],[3,[0.0,0.0]],[1,[0.0,0.0]]],\"stroke\":[[1,0],[5,0],[2,0],[6,0],[3,0],[4,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":6}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13817976820605296433,{\"inputs\":[{\"Node\":{\"node_id\":4837219841531371489,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-261.24355652969996,-64.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15595689026000825531,{\"inputs\":[{\"Node\":{\"node_id\":2785423879796980286,\"output_index\":0}},{\"Node\":{\"node_id\":12172015233077238737,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17433098630591807963,{\"inputs\":[{\"Node\":{\"node_id\":11490835759023283071,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.49019608,\"green\":0.69411767,\"blue\":0.627451,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.49019608,\"green\":0.69411767,\"blue\":0.627451,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3670594928372882885,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[3,[559.8076211353316,450.00000000000006]],[1,[671.7691453623979,459.99999999999994]],[2,[671.7691453623979,420.00000000000006]],[4,[559.8076211353317,490.00000000000006]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[4,4],[1,1],[3,3],[2,2]],\"end_point\":[[1,2],[4,1],[2,3],[3,4]],\"handle_primary\":[[1,[0.0,0.0]],[4,[0.0,0.0]],[2,[0.0,0.0]],[3,[0.0,0.0]]],\"handle_end\":[[1,[0.0,0.0]],[2,[0.0,0.0]],[4,[0.0,0.0]],[3,[0.0,0.0]]],\"stroke\":[[1,0],[3,0],[4,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15723520455917422372,{\"inputs\":[{\"Node\":{\"node_id\":4187349759243468746,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[55.4066256813212,75.48692492726542]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.093432966432927,1.093432966432927]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13743495762122910279,{\"inputs\":[{\"Node\":{\"node_id\":322234583139821148,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.30980393,\"green\":0.54901963,\"blue\":0.5568628,\"alpha\":1.0},{\"red\":0.27450982,\"green\":0.50980395,\"blue\":0.54509807,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0487255567160787,0.14016773560900295],\"end\":[0.9275384787415986,0.5063561479050618]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.30980393,\"green\":0.54901963,\"blue\":0.5568628,\"alpha\":1.0},{\"red\":0.27450982,\"green\":0.50980395,\"blue\":0.54509807,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0487255567160787,0.14016773560900295],\"end\":[0.9275384787415986,0.5063561479050618]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17250040304106119844,{\"inputs\":[{\"Node\":{\"node_id\":6142412830271644616,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,16.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12548387328300782726,{\"inputs\":[{\"Node\":{\"node_id\":764189229787475993,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.972549,\"green\":0.87058824,\"blue\":0.6666667,\"alpha\":1.0},{\"red\":0.98039216,\"green\":0.94509804,\"blue\":0.7058824,\"alpha\":1.0}]},\"gradient_type\":\"Radial\",\"start\":[0.7883568080179962,0.844472024944475],\"end\":[0.14631144508187544,0.21558322991428724]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.972549,\"green\":0.87058824,\"blue\":0.6666667,\"alpha\":1.0},{\"red\":0.98039216,\"green\":0.94509804,\"blue\":0.7058824,\"alpha\":1.0}]},\"gradient_type\":\"Radial\",\"start\":[0.7883568080179962,0.844472024944475],\"end\":[0.14631144508187544,0.21558322991428724]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7339104629465306715,{\"inputs\":[{\"Node\":{\"node_id\":16229837691656808412,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9226731772122225003,{\"inputs\":[{\"Node\":{\"node_id\":8884703330021429739,\"output_index\":0}},{\"Node\":{\"node_id\":9695624216919732577,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10188337730058049439,{\"inputs\":[{\"Node\":{\"node_id\":1268775104597510914,\"output_index\":0}},{\"Node\":{\"node_id\":11076863066321508991,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15898396405528650339,{\"inputs\":[{\"Node\":{\"node_id\":16807867745126764195,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1156.935750346027,389.9999999999999]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":180.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[373.2050807562376,99.99999999999976]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-1.8801215900696402e-15,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4046495708656778502,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[1,[106.87778599676416,75.02595252015249]],[4,[122.38078180163905,40.36997488237489]],[3,[130.0186418839436,64.45168544306966]],[2,[111.05567892098054,93.44803081206965]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[4,4],[3,3],[2,2],[1,1]],\"end_point\":[[4,1],[3,4],[1,2],[2,3]],\"handle_primary\":[[2,[0.0,0.0]],[3,[0.0,0.0]],[1,[0.0,0.0]],[4,[0.0,0.0]]],\"handle_end\":[[2,[0.0,0.0]],[3,[0.0,0.0]],[1,[0.0,0.0]],[4,[0.0,0.0]]],\"stroke\":[[1,0],[2,0],[4,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9771562518763748677,{\"inputs\":[{\"Node\":{\"node_id\":14234384001010789008,\"output_index\":0}},{\"Node\":{\"node_id\":12554549497938935061,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11479492521093639512,{\"inputs\":[{\"Node\":{\"node_id\":15216519480392295991,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"BlendMode\":\"Lighten\"},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"blending_nodes::BlendModeNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15300421479077882117,{\"inputs\":[{\"Node\":{\"node_id\":7030585744407664630,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.98039216,\"green\":0.94509804,\"blue\":0.7058824,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.98039216,\"green\":0.94509804,\"blue\":0.7058824,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4887570735033124574,{\"inputs\":[{\"Node\":{\"node_id\":9210109719406330381,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.39607844,\"green\":0.57322305,\"blue\":0.627451,\"alpha\":0.6313726},{\"red\":0.5882353,\"green\":0.627451,\"blue\":0.39607844,\"alpha\":0.0}]},\"gradient_type\":\"Linear\",\"start\":[0.7128143377734406,-0.305555555555558],\"end\":[0.6180751590210081,0.944444444444442]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.39607844,\"green\":0.57322305,\"blue\":0.627451,\"alpha\":0.6313726},{\"red\":0.5882353,\"green\":0.627451,\"blue\":0.39607844,\"alpha\":0.0}]},\"gradient_type\":\"Linear\",\"start\":[0.7128143377734406,-0.305555555555558],\"end\":[0.6180751590210081,0.944444444444442]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5365849201631468915,{\"inputs\":[{\"Node\":{\"node_id\":10849502918952703647,\"output_index\":0}},{\"Node\":{\"node_id\":2310170068575553369,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14483299526002574058,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[3,[933.0127018922192,649.9999999999999]],[4,[839.7114317029976,625.0000000000001]],[1,[1156.9357503463516,540.0]],[2,[1156.9357503463518,589.9999999999999]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[1,1],[4,4],[2,2],[3,3]],\"end_point\":[[3,4],[2,3],[1,2],[4,1]],\"handle_primary\":[[3,[0.0,0.0]],[2,[0.0,0.0]],[1,[0.0,0.0]],[4,[0.0,0.0]]],\"handle_end\":[[2,[0.0,0.0]],[4,[2.273736754432321e-13,0.0]],[3,[0.0,0.0]],[1,[0.0,0.0]]],\"stroke\":[[3,0],[1,0],[2,0],[4,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14234384001010789008,{\"inputs\":[{\"Node\":{\"node_id\":4757672276235057645,\"output_index\":0}},{\"Node\":{\"node_id\":8863202447825570192,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16793555741218543212,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"delta\":[[5,[298.56406460551005,480.00000000000006]],[1,[559.7618537735666,489.9877366723751]],[6,[410.5255888325765,450.0000000000001]],[4,[597.1281292110201,559.9999999999999]],[2,[634.4486372867091,470.00000000000006]],[3,[783.7306695894638,509.9999999999999]]]},\"segments\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"start_point\":[[5,5],[3,3],[1,1],[6,6],[2,2],[4,4]],\"end_point\":[[4,5],[2,3],[5,6],[1,2],[6,1],[3,4]],\"handle_primary\":[[4,[0.0,0.0]],[2,[149.28203230275471,39.99999999999983]],[6,[0.0,0.0]],[1,[0.0,0.0]],[3,[0.0,0.0]],[5,[0.0,0.0]]],\"handle_end\":[[4,[0.0,0.0]],[1,null],[5,[0.0,0.0]],[2,null],[3,[0.0,0.0]],[6,[0.0,0.0]]],\"stroke\":[[4,0],[3,0],[2,0],[5,0],[6,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":6}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2209276411833629008,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"delta\":[[3,[0.5,1.0]],[5,[0.1274934116812796,0.166664669692703]],[4,[0.0,0.5]],[1,[0.8725092774641316,0.16666767219504575]],[6,[0.5000000000000018,0.10816199860278752]],[2,[1.0,0.5]]]},\"segments\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"start_point\":[[6,6],[2,2],[1,1],[3,3],[4,4],[5,5]],\"end_point\":[[1,2],[5,6],[3,4],[2,3],[4,5],[6,1]],\"handle_primary\":[[4,[0.0,-0.12799231715991943]],[1,[0.07925873631249913,0.08849560350574948]],[3,[-0.275892388889507,0.0]],[6,[0.22701785858837376,9.09188805575667e-7]],[2,[0.0,0.27589238888950707]],[5,[0.14548887396141374,-0.05850358027814341]]],\"handle_end\":[[5,null],[6,null],[2,[0.27589238888950707,0.0]],[1,[0.0,-0.12799086965193351]],[4,[-0.07926033448372466,0.08849596308181285]],[3,[0.0,0.27589238888950707]]],\"stroke\":[[4,0],[2,0],[1,0],[3,0],[5,0],[6,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":6}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5434119356821575534,{\"inputs\":[{\"Node\":{\"node_id\":1490537476612110327,\"output_index\":0}},{\"Node\":{\"node_id\":16261620049358949344,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2032185045476767535,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":7340659059180155803,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15261165353096835967,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"delta\":[[5,[597.1281292110198,599.9999999999999]],[3,[783.7306695894646,560.0]],[1,[1156.9357503463468,509.9999999999987]],[6,[1044.9742261192855,480.0]],[4,[615.7883832488644,605.0000000000001]],[2,[877.0319397786863,584.9999999999998]]]},\"segments\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"start_point\":[[6,6],[1,1],[3,3],[4,4],[5,5],[2,2]],\"end_point\":[[5,6],[3,4],[4,5],[1,2],[2,3],[6,1]],\"handle_primary\":[[5,[447.84609690826574,-119.99999999999989]],[6,[0.0,0.0]],[1,[0.0,0.0]],[2,[0.0,0.0]],[3,null],[4,null]],\"handle_end\":[[3,null],[1,[0.0,0.0]],[2,null],[5,null],[4,null],[6,[0.0,0.0]]],\"stroke\":[[3,0],[5,0],[6,0],[4,0],[2,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":6}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1658032775659237960,{\"inputs\":[{\"Node\":{\"node_id\":14483299526002574058,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-261.2435565297003,-64.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[18185020559178852986,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":4847316728405535983,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6785205785632793666,{\"inputs\":[{\"Node\":{\"node_id\":10917301734480569398,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.49803922,\"green\":0.73333335,\"blue\":0.6666667,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.49803922,\"green\":0.73333335,\"blue\":0.6666667,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4884180935153120645,{\"inputs\":[{\"Node\":{\"node_id\":11221222899304956410,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.972549,\"green\":0.87058824,\"blue\":0.6666667,\"alpha\":1.0},{\"red\":0.98039216,\"green\":0.94509804,\"blue\":0.7058824,\"alpha\":1.0}]},\"gradient_type\":\"Radial\",\"start\":[0.4094933109340779,0.6607209771464397],\"end\":[0.921423168424672,0.39536705519277415]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.972549,\"green\":0.87058824,\"blue\":0.6666667,\"alpha\":1.0},{\"red\":0.98039216,\"green\":0.94509804,\"blue\":0.7058824,\"alpha\":1.0}]},\"gradient_type\":\"Radial\",\"start\":[0.4094933109340779,0.6607209771464397],\"end\":[0.921423168424672,0.39536705519277415]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7838724497953148309,{\"inputs\":[{\"Node\":{\"node_id\":9771562518763748677,\"output_index\":0}},{\"Node\":{\"node_id\":2058192342619930156,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17873337220577786871,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"delta\":[[5,[597.1281292110198,599.9999999999999]],[1,[1156.9357503463468,509.9999999999987]],[6,[1044.9742261192855,480.0]],[2,[877.0319397786863,584.9999999999998]],[3,[783.7306695894646,560.0]],[4,[615.7883832488646,605.0000000000001]]]},\"segments\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"start_point\":[[1,1],[2,2],[3,3],[5,5],[4,4],[6,6]],\"end_point\":[[4,5],[1,2],[5,6],[2,3],[3,4],[6,1]],\"handle_primary\":[[4,null],[5,[447.84609690826574,-119.99999999999989]],[2,[0.0,0.0]],[6,[0.0,0.0]],[1,[0.0,0.0]],[3,null]],\"handle_end\":[[2,null],[6,[0.0,0.0]],[5,null],[1,[0.0,0.0]],[3,null],[4,null]],\"stroke\":[[5,0],[3,0],[2,0],[6,0],[4,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":6}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7861616450605235840,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[3,[1268.8972745734186,460.00000000000233]],[1,[1343.3813269975649,420.0420583039525]],[4,[1231.5767664977302,450.00000000000233]],[2,[1343.5382907247954,439.792314581573]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[2,2],[3,3],[1,1],[4,4]],\"end_point\":[[3,4],[4,1],[1,2],[2,3]],\"handle_primary\":[[4,[0.0,0.0]],[3,[0.0,0.0]],[1,[0.0,0.0]],[2,[0.0,0.0]]],\"handle_end\":[[3,[0.0,0.0]],[1,[0.0,0.0]],[2,[0.0,0.0]],[4,[0.0,0.0]]],\"stroke\":[[4,0],[3,0],[1,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8463468388280418154,{\"inputs\":[{\"Node\":{\"node_id\":9993538712344947860,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":74.5472},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"blending_nodes::OpacityNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15670426414376277308,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[4,[0.0,0.5]],[1,[0.5,0.0]],[2,[1.0,0.5]],[3,[0.5,1.0]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[3,3],[4,4],[2,2],[1,1]],\"end_point\":[[3,4],[1,2],[2,3],[4,1]],\"handle_primary\":[[4,[0.0,-0.275892388889507]],[1,[0.27589238888950707,0.0]],[2,[0.0,0.27589238888950707]],[3,[-0.275892388889507,0.0]]],\"handle_end\":[[1,[0.0,-0.275892388889507]],[4,[-0.275892388889507,0.0]],[3,[0.0,0.27589238888950707]],[2,[0.27589238888950707,0.0]]],\"stroke\":[[4,0],[2,0],[3,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2292399603649738346,{\"inputs\":[{\"Node\":{\"node_id\":16510804133693080967,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.9127081032507663,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.27058825,\"green\":0.49411765,\"blue\":0.5137255,\"alpha\":1.0},{\"red\":0.27058825,\"green\":0.49411765,\"blue\":0.5137255,\"alpha\":1.0},{\"red\":0.27058825,\"green\":0.49411765,\"blue\":0.5137255,\"alpha\":0.0}]},\"gradient_type\":\"Radial\",\"start\":[0.6216293634586009,-3.518518518518513],\"end\":[0.4157051295501385,0.32812296023903675]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.9127081032507663,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.27058825,\"green\":0.49411765,\"blue\":0.5137255,\"alpha\":1.0},{\"red\":0.27058825,\"green\":0.49411765,\"blue\":0.5137255,\"alpha\":1.0},{\"red\":0.27058825,\"green\":0.49411765,\"blue\":0.5137255,\"alpha\":0.0}]},\"gradient_type\":\"Radial\",\"start\":[0.6216293634586009,-3.518518518518513],\"end\":[0.4157051295501385,0.32812296023903675]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4069478660487729695,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[1,[-22.15328312748764,98.9633432080564]],[4,[55.822325899541134,88.0419325229343]],[2,[-23.148253406408465,116.21917209054972]],[3,[59.855160808698834,104.19217420861494]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[4,4],[2,2],[3,3],[1,1]],\"end_point\":[[1,2],[3,4],[4,1],[2,3]],\"handle_primary\":[[3,[0.0,0.0]],[4,[0.0,0.0]],[2,[0.0,0.0]],[1,[0.0,0.0]]],\"handle_end\":[[2,[-27.645547736903644,17.139482007743737]],[1,[0.0,0.0]],[4,[24.844120782255654,5.22590107758802]],[3,[0.0,0.0]]],\"stroke\":[[2,0],[1,0],[4,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5326013268137833446,{\"inputs\":[{\"Node\":{\"node_id\":12875121980058869686,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.5005297191519419,0.8104119804726813,1.0],\"midpoint\":[0.5,0.5,0.5,0.5],\"color\":[{\"red\":0.48235294,\"green\":0.69803923,\"blue\":0.69411767,\"alpha\":1.0},{\"red\":0.48235294,\"green\":0.69803923,\"blue\":0.69411767,\"alpha\":1.0},{\"red\":0.5686275,\"green\":0.69411767,\"blue\":0.5372549,\"alpha\":0.87890625},{\"red\":0.3529412,\"green\":0.5647059,\"blue\":0.57254905,\"alpha\":0.296875}]},\"gradient_type\":\"Radial\",\"start\":[0.0015840517590797742,0.9627709247339196],\"end\":[1.0370671897237005,-1.554312234475219e-14]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.5005297191519419,0.8104119804726813,1.0],\"midpoint\":[0.5,0.5,0.5,0.5],\"color\":[{\"red\":0.48235294,\"green\":0.69803923,\"blue\":0.69411767,\"alpha\":1.0},{\"red\":0.48235294,\"green\":0.69803923,\"blue\":0.69411767,\"alpha\":1.0},{\"red\":0.5686275,\"green\":0.69411767,\"blue\":0.5372549,\"alpha\":0.87890625},{\"red\":0.3529412,\"green\":0.5647059,\"blue\":0.57254905,\"alpha\":0.296875}]},\"gradient_type\":\"Radial\",\"start\":[0.0015840517590797742,0.9627709247339196],\"end\":[1.0370671897237005,-1.554312234475219e-14]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12537712543904859919,{\"inputs\":[{\"Node\":{\"node_id\":4968550668755026811,\"output_index\":0}},{\"Node\":{\"node_id\":8508454285877707748,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12469956387875933942,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":2881239077602364410,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13207576193421440093,{\"inputs\":[{\"Node\":{\"node_id\":12930243402848966353,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.5686275,\"green\":0.7411765,\"blue\":0.654902,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.5686275,\"green\":0.7411765,\"blue\":0.654902,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5574499968250848265,{\"inputs\":[{\"Node\":{\"node_id\":11776939455674933130,\"output_index\":0}},{\"Node\":{\"node_id\":5925268772265373737,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[18371793711669837037,{\"inputs\":[{\"Node\":{\"node_id\":7861616450605235840,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-261.2435565297001,-64.00000000000006]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10742991645899166287,{\"inputs\":[{\"Node\":{\"node_id\":1104068854328504126,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-298.5640646053887,-114.0000000000026]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.9999999999999996,0.9999999999999996]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[18085100003956405261,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19],\"remove\":[],\"delta\":[[15,[556.7578325004595,420.13017723423656]],[3,[480.5925925925926,440.2962962962962]],[7,[515.1604938271604,486.716049382716]],[11,[531.5890484844431,432.90541944395505]],[14,[552.9705625612692,418.19081042008474]],[17,[565.2914244954804,444.3855996237166]],[5,[497.38271604938257,447.60493827160496]],[12,[533.8924329970387,433.46495092641567]],[16,[530.3703703703702,486.71604938271594]],[4,[511.55555555555554,490.2222222222222]],[9,[505.311372421164,416.4524664944526]],[1,[510.41975308641986,500.1481481481481]],[2,[476.9512618480758,444.1904085078117]],[8,[502.0960349862431,417.9179038759178]],[6,[499.77650172698026,445.9388618767647]],[18,[568.2339832275112,446.0155371619765]],[10,[524.0493827160495,487.1111111111112]],[13,[526.0246913580248,490.07407407407413]],[19,[530.7654320987656,499.55555555555566]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19],\"remove\":[],\"start_point\":[[9,9],[7,7],[15,15],[12,12],[11,11],[17,17],[6,6],[2,2],[4,4],[10,10],[18,18],[8,8],[13,13],[5,5],[3,3],[1,1],[14,14],[19,19],[16,16]],\"end_point\":[[8,9],[4,5],[6,7],[13,14],[19,1],[11,12],[15,16],[3,4],[12,13],[1,2],[2,3],[16,17],[9,10],[18,19],[7,8],[5,6],[14,15],[10,11],[17,18]],\"handle_primary\":[[11,[7.784237194372736,-22.222909722529664]],[1,[-9.283950617284065,-12.246913580246884]],[4,[0.0,0.0]],[5,[-10.016557406305251,-15.024836109457851]],[9,[11.197015798182122,18.39034937970939]],[8,[-8.936088730151937,-20.93774746663439]],[6,[3.9272019767233246,4.629039357803151]],[7,[0.0,0.0]],[13,[0.0,0.0]],[14,[15.260998855532309,-21.888591759537466]],[18,[-19.30865594294653,18.299752742610902]],[3,[21.32824309569804,17.329197515254634]],[10,[0.0,0.0]],[12,[-5.082537215847424,18.7049831985629]],[17,[27.324497401397252,-21.15032331824051]],[16,[0.0,0.0]],[19,[-1.5204271954055455,4.429070525747022]],[15,[-11.459519915000214,23.15277176924241]],[2,[-17.16805625160663,-20.28383937076643]]],\"handle_end\":[[7,[18.59187624528363,43.5617887769169]],[13,[-15.260998855532534,21.888591759537743]],[18,[4.54320987654296,-13.23456790123464]],[6,[0.7901234567898427,-16.197530864197745]],[19,[2.7053847913285836,3.5688054694122116]],[11,[5.082537215847424,-18.704983198562843]],[5,[-12.032856371187677,-14.183269935989983]],[8,[-11.197015798182008,-18.39034937970939]],[15,[4.54320987654296,-11.851851851852018]],[16,[-28.402535606591755,21.984770746653737]],[10,[-7.784237194372736,22.22290972252955]],[1,[28.522359190346833,33.698803394714844]],[9,[0.7901234567898427,-21.135802469135857]],[4,[13.03703703703701,19.555555555555543]],[3,[-3.111111111111086,-12.0]],[17,[19.435079545528083,-18.419570542198244]],[12,[3.7530864197531177,-16.59259259259261]],[14,[11.286699158099054,-22.803605349427187]],[2,[-22.123456790123555,-17.97530864197529]]],\"stroke\":[[14,0],[16,0],[4,0],[6,0],[11,0],[12,0],[1,0],[18,0],[3,0],[10,0],[7,0],[2,0],[8,0],[9,0],[17,0],[15,0],[5,0],[19,0],[13,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":19}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8612613134760093452,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[3,[802.3909236273086,635.0000000000001]],[2,[839.7114317029974,625.0000000000001]],[1,[933.0127018922192,650.0]],[4,[933.0127018922192,670.0000000000001]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[2,2],[1,1],[4,4],[3,3]],\"end_point\":[[3,4],[1,2],[4,1],[2,3]],\"handle_primary\":[[2,[0.0,0.0]],[1,[0.0,0.0]],[4,[0.0,0.0]],[3,[0.0,0.0]]],\"handle_end\":[[1,[0.0,0.0]],[4,[0.0,0.0]],[3,[0.0,0.0]],[2,[0.0,0.0]]],\"stroke\":[[2,0],[4,0],[1,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12931264630175648107,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11],\"remove\":[],\"delta\":[[4,[261.24355652969956,704.9999999999997]],[6,[298.56406460551034,520.0000000000002]],[2,[1531.2435565296985,470.3]],[11,[1492.8203230275508,480.0000000000001]],[7,[298.56406460551045,540.0000000000001]],[1,[1492.820323027551,460.0000000000025]],[3,[1531.243556529699,704.9999999999997]],[9,[1231.576766497731,510.0000000000024]],[10,[1306.2177826491086,530.0000000000023]],[8,[709.0896534380863,649.9999999999999]],[5,[261.2435565296994,530.0000000000001]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11],\"remove\":[],\"start_point\":[[4,4],[2,2],[5,5],[7,7],[10,10],[3,3],[6,6],[9,9],[1,1],[8,8],[11,11]],\"end_point\":[[4,5],[11,1],[6,7],[10,11],[7,8],[9,10],[2,3],[1,2],[5,6],[8,9],[3,4]],\"handle_primary\":[[1,[0.0,0.0]],[6,[0.0,0.0]],[10,[0.0,0.0]],[5,[0.0,0.0]],[4,[0.0,0.0]],[9,[0.0,0.0]],[8,[0.0,0.0]],[3,[0.0,0.0]],[7,[0.0,0.0]],[2,[0.0,0.0]],[11,[0.0,0.0]]],\"handle_end\":[[5,[0.0,0.0]],[11,[0.0,0.0]],[1,[0.0,0.0]],[4,[0.0,0.0]],[7,[0.0,0.0]],[6,[0.0,0.0]],[3,[0.0,0.0]],[10,[0.0,0.0]],[2,[0.0,0.0]],[9,[0.0,0.0]],[8,[0.0,0.0]]],\"stroke\":[[1,0],[11,0],[6,0],[9,0],[2,0],[8,0],[7,0],[5,0],[10,0],[3,0],[4,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":11}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4757672276235057645,{\"inputs\":[{\"Node\":{\"node_id\":4924169570021915606,\"output_index\":0}},{\"Node\":{\"node_id\":14400993470150734626,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15134939288287905620,{\"inputs\":[{\"Node\":{\"node_id\":8958782938691501404,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-261.2435565297001,-64.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10989897386232385465,{\"inputs\":[{\"Node\":{\"node_id\":10507084483235320484,\"output_index\":0}},{\"Node\":{\"node_id\":7339104629465306715,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5540780316862276409,{\"inputs\":[{\"Node\":{\"node_id\":1032659476619711014,\"output_index\":0}},{\"Node\":{\"node_id\":13743495762122910279,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9695624216919732577,{\"inputs\":[{\"Node\":{\"node_id\":1156213189397385283,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.9372549,\"green\":0.90588236,\"blue\":0.7764706,\"alpha\":0.7490196}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":3.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeAlign\":\"Center\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeCap\":\"Butt\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeJoin\":\"Miter\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"PaintOrder\":\"StrokeAbove\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"VecF64\":[]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::StrokeNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16852951849051795674,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8],\"remove\":[],\"delta\":[[3,[933.0127018922192,650.0]],[8,[1231.576766497731,510.0000000000022]],[6,[1343.5382907247958,520.0]],[5,[1343.5382907247958,560.0]],[2,[1156.9357503463518,590.0]],[1,[1156.9357503463525,530.0]],[4,[933.0127018922192,670.0]],[7,[1306.2177826491068,529.9999999999972]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8],\"remove\":[],\"start_point\":[[3,3],[5,5],[6,6],[2,2],[7,7],[1,1],[4,4],[8,8]],\"end_point\":[[7,8],[5,6],[3,4],[6,7],[4,5],[8,1],[1,2],[2,3]],\"handle_primary\":[[5,null],[2,null],[4,[0.0,0.0]],[6,null],[1,[0.0,0.0]],[8,[0.0,0.0]],[7,[-74.64101615137588,-19.99999999999494]],[3,[0.0,20.0]]],\"handle_end\":[[6,null],[2,null],[8,[0.0,0.0]],[4,null],[5,null],[3,null],[1,null],[7,null]],\"stroke\":[[6,0],[2,0],[5,0],[3,0],[1,0],[7,0],[4,0],[8,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":8}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14124486712683868036,{\"inputs\":[{\"Node\":{\"node_id\":14449527838292182035,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.31019165912642926,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.44433594,\"green\":0.7109375,\"blue\":0.60144055,\"alpha\":1.0},{\"red\":0.42991638,\"green\":0.68359375,\"blue\":0.5723318,\"alpha\":0.85546875},{\"red\":0.41487122,\"green\":0.66796875,\"blue\":0.5466929,\"alpha\":0.3515625}]},\"gradient_type\":\"Radial\",\"start\":[0.49564744287268736,0.12680209698558276],\"end\":[0.6983677910772572,0.8876146788990837]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.31019165912642926,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.44433594,\"green\":0.7109375,\"blue\":0.60144055,\"alpha\":1.0},{\"red\":0.42991638,\"green\":0.68359375,\"blue\":0.5723318,\"alpha\":0.85546875},{\"red\":0.41487122,\"green\":0.66796875,\"blue\":0.5466929,\"alpha\":0.3515625}]},\"gradient_type\":\"Radial\",\"start\":[0.49564744287268736,0.12680209698558276],\"end\":[0.6983677910772572,0.8876146788990837]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11450962621506425680,{\"inputs\":[{\"Node\":{\"node_id\":514222872092587805,\"output_index\":0}},{\"Node\":{\"node_id\":6006052038693767172,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11158238411769751544,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":14124486712683868036,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2310170068575553369,{\"inputs\":[{\"Node\":{\"node_id\":3564067978712674849,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.94921875,\"green\":0.8861847,\"blue\":0.5710144,\"alpha\":1.0},{\"red\":0.6594621,\"green\":0.859375,\"blue\":0.62438965,\"alpha\":1.0}]},\"gradient_type\":\"Radial\",\"start\":[0.9615262104062824,0.1999999999999842],\"end\":[0.047819464216158014,0.809999999999961]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.94921875,\"green\":0.8861847,\"blue\":0.5710144,\"alpha\":1.0},{\"red\":0.6594621,\"green\":0.859375,\"blue\":0.62438965,\"alpha\":1.0}]},\"gradient_type\":\"Radial\",\"start\":[0.9615262104062824,0.1999999999999842],\"end\":[0.047819464216158014,0.809999999999961]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6868877732348460627,{\"inputs\":[{\"Node\":{\"node_id\":7884283658260267478,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.9372549,\"green\":0.90588236,\"blue\":0.7764706,\"alpha\":0.7490196}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":3.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeAlign\":\"Center\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeCap\":\"Butt\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeJoin\":\"Miter\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"PaintOrder\":\"StrokeAbove\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"VecF64\":[]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::StrokeNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4191887059541031673,{\"inputs\":[{\"Node\":{\"node_id\":4046495708656778502,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13287180494862716983,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[2,[783.7306695894644,525.0]],[4,[830.3813046840752,527.5]],[1,[765.0704155516202,530.0]],[3,[811.7210506462309,532.5]]]},\"segments\":{\"add\":[1,2,3],\"remove\":[],\"start_point\":[[2,2],[1,1],[3,3]],\"end_point\":[[2,3],[3,4],[1,2]],\"handle_primary\":[[1,[0.0,0.0]],[2,[0.0,0.0]],[3,[0.0,0.0]]],\"handle_end\":[[1,[0.0,0.0]],[3,[0.0,0.0]],[2,[0.0,0.0]]],\"stroke\":[[2,0],[1,0],[3,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1396768435017101055,{\"inputs\":[{\"Node\":{\"node_id\":15914878146223026034,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16727310898641763441,{\"inputs\":[{\"Node\":{\"node_id\":13920465562072008593,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.4117647,\"green\":0.6392157,\"blue\":0.6117647,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.4117647,\"green\":0.6392157,\"blue\":0.6117647,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9278774434958175105,{\"inputs\":[{\"Node\":{\"node_id\":3927358878935116440,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-298.5640646053914,-74.00000000000011]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.9999999999999972,0.9999999999999972]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12930243402848966353,{\"inputs\":[{\"Node\":{\"node_id\":7948029953091985757,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[895.6921938163274,315.9999999999998]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":180.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[373.2050807562376,99.99999999999976]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-1.8801215900696402e-15,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14805036488257720752,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[4,[942.3428289111416,582.5]],[1,[849.0415587219195,597.5000000000001]],[3,[895.6921938165302,595.0]],[2,[877.0319397786858,590.0]]]},\"segments\":{\"add\":[1,2,3],\"remove\":[],\"start_point\":[[1,1],[2,2],[3,3]],\"end_point\":[[2,3],[1,2],[3,4]],\"handle_primary\":[[3,[0.0,0.0]],[1,[0.0,0.0]],[2,[0.0,0.0]]],\"handle_end\":[[1,[0.0,0.0]],[2,[0.0,0.0]],[3,[0.0,0.0]]],\"stroke\":[[3,0],[2,0],[1,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[18011777376689315137,{\"inputs\":[{\"Node\":{\"node_id\":10564228200140683112,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8343201730608263656,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"delta\":[[4,[709.0896534380868,470.0]],[5,[597.1281292110203,439.99999999999994]],[6,[597.1281292110205,340.0000000000001]],[2,[933.0127018922194,470.0]],[3,[709.0896534380868,410.0000000000001]],[1,[933.0127018922194,430.0]]]},\"segments\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"start_point\":[[4,4],[3,3],[6,6],[5,5],[2,2],[1,1]],\"end_point\":[[6,1],[3,4],[1,2],[4,5],[5,6],[2,3]],\"handle_primary\":[[5,[0.0,0.0]],[6,[0.0,0.0]],[3,[0.0,0.0]],[1,[0.0,0.0]],[2,[0.0,0.0]],[4,[0.0,0.0]]],\"handle_end\":[[4,[0.0,0.0]],[5,[0.0,0.0]],[2,[0.0,0.0]],[3,[0.0,0.0]],[6,[0.0,0.0]],[1,[0.0,0.0]]],\"stroke\":[[6,0],[2,0],[4,0],[1,0],[3,0],[5,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":6}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9935922395919478146,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],\"remove\":[],\"delta\":[[11,[1318.5843621399176,404.93827160493817]],[7,[1291.19341563786,379.78600823045264]],[3,[1276.181069958848,406.51851851851853]],[20,[1325.5637860082302,441.9423868312756]],[2,[1309.8491083676272,442.3520804755373]],[10,[1307.9176954732511,360.6913580246913]],[1,[1317.9649443682358,445.9417771681145]],[6,[1283.8189300411525,381.6296296296296]],[9,[1303.5720164609054,369.51440329218104]],[4,[1281.3168724279838,402.0411522633745]],[5,[1303.7037037037037,419.6872427983538]],[18,[1353.2181069958854,390.3209876543211]],[14,[1320.9547325102878,428.11522633744846]],[13,[1334.9135802469134,382.9465020576131]],[19,[1353.7448559670786,395.8518518518519]],[12,[1330.9629629629628,378.8641975308641]],[16,[1349.7942386831278,385.3168724279836]],[15,[1346.7654320987656,379.522633744856]],[8,[1311.341563786008,420.872427983539]],[17,[1331.2263374485594,418.633744855967]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],\"remove\":[],\"start_point\":[[14,14],[18,18],[20,20],[17,17],[10,10],[8,8],[13,13],[16,16],[2,2],[19,19],[15,15],[7,7],[5,5],[4,4],[1,1],[12,12],[3,3],[6,6],[11,11],[9,9]],\"end_point\":[[7,8],[20,1],[13,14],[16,17],[9,10],[2,3],[12,13],[18,19],[6,7],[8,9],[10,11],[14,15],[11,12],[17,18],[1,2],[4,5],[3,4],[5,6],[15,16],[19,20]],\"handle_primary\":[[13,[-7.506172839506235,20.67489711934155]],[3,[-25.9423868312756,-18.304526748971227]],[12,[8.691358024691226,-13.168724279835374]],[20,[-0.8876695625660886,2.750800182899013]],[4,[11.934313314187648,6.04743230718924]],[11,[0.0,0.0]],[19,[-17.792694511774243,22.05806648377495]],[6,[-13.958847736625785,-23.967078189300366]],[9,[-8.427983539094384,-34.502057613168745]],[15,[17.514403292181214,-18.96296296296299]],[16,[-13.168724279835487,17.382716049382793]],[17,[0.0,0.0]],[14,[0.0,0.0]],[7,[13.958847736626012,18.304526748971227]],[8,[0.0,0.0]],[5,[0.0,0.0]],[2,[-1.1927104603494172,-1.8387619597050957]],[1,[-5.73075474332677,0.190602104454058]],[18,[14.617283950616866,-16.32921810699594]],[10,[10.930041152263357,24.88888888888891]]],\"handle_end\":[[12,[7.506172839506235,-20.67489711934155]],[10,[0.0,0.0]],[6,[-21.35436745259517,-28.00242524443996]],[15,[22.902000000470935,-30.23064000062169]],[19,[2.149866795831258,-6.662224576095184]],[11,[-8.691358024691226,13.168724279835374]],[18,[19.22633744855989,-23.835390946502]],[3,[-29.366255144032948,-14.880658436213992]],[9,[-11.24897964853426,-25.61514642859032]],[8,[7.512329871725342,30.753600412376215]],[1,[1.8728852309102424,2.887364730986178]],[5,[8.275699533270199,14.209219953350214]],[2,[21.160177959832257,14.930277849830986]],[17,[-11.234881916731185,12.550678897970386]],[20,[6.571889866311722,-0.218577847920983]],[16,[0.0,0.0]],[4,[0.0,0.0]],[13,[0.0,0.0]],[14,[-16.17829033097064,17.51634441849427]],[7,[0.0,0.0]]],\"stroke\":[[15,0],[9,0],[14,0],[2,0],[16,0],[11,0],[20,0],[19,0],[4,0],[12,0],[5,0],[3,0],[7,0],[6,0],[17,0],[10,0],[1,0],[18,0],[13,0],[8,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":20}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8073807569018624098,{\"inputs\":[{\"Node\":{\"node_id\":8028812053913481975,\"output_index\":0}},{\"Node\":{\"node_id\":12998832508553378533,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12875121980058869686,{\"inputs\":[{\"Node\":{\"node_id\":13747030364552895864,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-261.2435565297001,-64.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14341957170885045113,{\"inputs\":[{\"Node\":{\"node_id\":12469956387875933942,\"output_index\":0}},{\"Node\":{\"node_id\":7952384394377946257,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[514222872092587805,{\"inputs\":[{\"Node\":{\"node_id\":5434119356821575534,\"output_index\":0}},{\"Node\":{\"node_id\":3679103217373457623,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9908869573449854874,{\"inputs\":[{\"Node\":{\"node_id\":16416441286881083283,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-261.2435565297001,-64.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17059035448296015006,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[3,[933.0127018922194,470.0]],[4,[821.0511776651532,440.0000000000001]],[1,[709.0896534380867,469.99999999999994]],[2,[821.0511776651531,500.0]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[1,1],[4,4],[3,3],[2,2]],\"end_point\":[[2,3],[4,1],[3,4],[1,2]],\"handle_primary\":[[3,[0.0,0.0]],[2,[0.0,0.0]],[1,[0.0,0.0]],[4,[0.0,0.0]]],\"handle_end\":[[2,[0.0,0.0]],[1,[0.0,0.0]],[4,[0.0,5.684341886080804e-14]],[3,[0.0,0.0]]],\"stroke\":[[3,0],[1,0],[2,0],[4,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8028812053913481975,{\"inputs\":[{\"Node\":{\"node_id\":15517065353723874205,\"output_index\":0}},{\"Node\":{\"node_id\":6785205785632793666,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9993538712344947860,{\"inputs\":[{\"Node\":{\"node_id\":15134939288287905620,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.38039216,\"green\":0.58431375,\"blue\":0.57254905,\"alpha\":1.0},{\"red\":0.38039216,\"green\":0.58431375,\"blue\":0.5764706,\"alpha\":0.0}]},\"gradient_type\":\"Radial\",\"start\":[0.9375072552322194,-0.022211489741806645],\"end\":[0.8621391458239049,0.9748376053312908]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.38039216,\"green\":0.58431375,\"blue\":0.57254905,\"alpha\":1.0},{\"red\":0.38039216,\"green\":0.58431375,\"blue\":0.5764706,\"alpha\":0.0}]},\"gradient_type\":\"Radial\",\"start\":[0.9375072552322194,-0.022211489741806645],\"end\":[0.8621391458239049,0.9748376053312908]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[456239140723765386,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[4,[0.0,0.5]],[2,[1.0,0.5]],[3,[0.5,1.0]],[1,[0.5,0.0]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[2,2],[1,1],[3,3],[4,4]],\"end_point\":[[1,2],[2,3],[3,4],[4,1]],\"handle_primary\":[[2,[0.0,0.27589238888950707]],[3,[-0.275892388889507,0.0]],[1,[0.27589238888950707,0.0]],[4,[0.0,-0.275892388889507]]],\"handle_end\":[[3,[0.0,0.27589238888950707]],[4,[-0.275892388889507,0.0]],[2,[0.27589238888950707,0.0]],[1,[0.0,-0.275892388889507]]],\"stroke\":[[3,0],[2,0],[4,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2465823993152870948,{\"inputs\":[{\"Node\":{\"node_id\":15898396405528650339,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.3778839,\"green\":0.76171875,\"blue\":0.72573423,\"alpha\":1.0},{\"red\":0.7411765,\"green\":0.87058824,\"blue\":0.7372549,\"alpha\":1.0}]},\"gradient_type\":\"Radial\",\"start\":[0.4840260278337376,0.5459259284371654],\"end\":[-0.00035013139300899,0.5459259284371651]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.3778839,\"green\":0.76171875,\"blue\":0.72573423,\"alpha\":1.0},{\"red\":0.7411765,\"green\":0.87058824,\"blue\":0.7372549,\"alpha\":1.0}]},\"gradient_type\":\"Radial\",\"start\":[0.4840260278337376,0.5459259284371654],\"end\":[-0.00035013139300899,0.5459259284371651]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14161755104759532162,{\"inputs\":[{\"Node\":{\"node_id\":13837327017498431546,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,16.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3021739385836969518,{\"inputs\":[{\"Node\":{\"node_id\":6556170892691431702,\"output_index\":0}},{\"Node\":{\"node_id\":13868917743026516656,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11547499603328872398,{\"inputs\":[{\"Node\":{\"node_id\":8343201730608263656,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-261.2435565297001,-64.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6777328619777499144,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[4,[1231.5767664977295,450.0]],[1,[1343.5382907246749,419.99978273075953]],[3,[1268.8972745734193,460.0000000000024]],[2,[1380.8587988003642,430.0]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[3,3],[2,2],[4,4],[1,1]],\"end_point\":[[4,1],[3,4],[1,2],[2,3]],\"handle_primary\":[[1,[0.0,0.0]],[2,[0.0,0.0]],[4,[0.0,0.0]],[3,[0.0,0.0]]],\"handle_end\":[[4,[-6.821210263296962e-13,-5.684341886080804e-14]],[3,[0.0,0.0]],[1,[0.0,0.0]],[2,[0.0,0.0]]],\"stroke\":[[3,0],[1,0],[2,0],[4,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3455270778005546310,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[3,[410.5255888325765,450.0000000000001]],[1,[559.8076211353316,449.99999999999994]],[4,[410.5255888325765,410.0]],[2,[559.7618537735666,489.9877366723752]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[3,3],[1,1],[2,2],[4,4]],\"end_point\":[[4,1],[2,3],[3,4],[1,2]],\"handle_primary\":[[4,[0.0,0.0]],[2,[0.0,0.0]],[1,[0.0,0.0]],[3,[0.0,0.0]]],\"handle_end\":[[4,[0.0,5.684341886080804e-14]],[3,[0.0,0.0]],[1,[0.0,0.0]],[2,[0.0,0.0]]],\"stroke\":[[1,0],[4,0],[2,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11076863066321508991,{\"inputs\":[{\"Node\":{\"node_id\":8891726805381758817,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5925268772265373737,{\"inputs\":[{\"Node\":{\"node_id\":4105329493214975815,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.49019608,\"green\":0.7882353,\"blue\":0.75686276,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.49019608,\"green\":0.7882353,\"blue\":0.75686276,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11895211316848895241,{\"inputs\":[{\"Node\":{\"node_id\":10742991645899166287,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.5019608,\"green\":0.72156864,\"blue\":0.62352943,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.5019608,\"green\":0.72156864,\"blue\":0.62352943,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7639490284239357347,{\"inputs\":[{\"Node\":{\"node_id\":14805036488257720752,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.9372549,\"green\":0.90588236,\"blue\":0.7764706,\"alpha\":0.7490196}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":3.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeAlign\":\"Center\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeCap\":\"Round\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeJoin\":\"Miter\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"PaintOrder\":\"StrokeAbove\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"VecF64\":[]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::StrokeNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7259756719760382667,{\"inputs\":[{\"Node\":{\"node_id\":15157035456876170143,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.27450982,\"green\":0.50980395,\"blue\":0.54509807,\"alpha\":1.0},{\"red\":0.27450982,\"green\":0.50980395,\"blue\":0.54509807,\"alpha\":0.0}]},\"gradient_type\":\"Linear\",\"start\":[0.4803107402195895,-0.1138575403671318],\"end\":[0.41147745365941946,0.9225580029175888]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.27450982,\"green\":0.50980395,\"blue\":0.54509807,\"alpha\":1.0},{\"red\":0.27450982,\"green\":0.50980395,\"blue\":0.54509807,\"alpha\":0.0}]},\"gradient_type\":\"Linear\",\"start\":[0.4803107402195895,-0.1138575403671318],\"end\":[0.41147745365941946,0.9225580029175888]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2036609094647228373,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5],\"remove\":[],\"delta\":[[3,[0.5,1.0]],[5,[0.12749341167028605,0.1666646696927025]],[1,[0.8725092774628217,0.1666676721950458]],[2,[1.0,0.5]],[4,[0.0,0.5]]]},\"segments\":{\"add\":[1,2,3,4,5],\"remove\":[],\"start_point\":[[1,1],[4,4],[3,3],[2,2],[5,5]],\"end_point\":[[1,2],[5,1],[4,5],[2,3],[3,4]],\"handle_primary\":[[1,[0.07925873631249913,0.08849560350574948]],[2,[0.0,0.27589238888950707]],[4,[0.0,-0.12799231715991943]],[5,null],[3,[-0.275892388889507,0.0]]],\"handle_end\":[[2,[0.27589238888950707,0.0]],[1,[0.0,-0.12799086965193351]],[5,null],[3,[0.0,0.27589238888950707]],[4,[-0.07926033448372466,0.0884959630818129]]],\"stroke\":[[3,0],[1,0],[5,0],[4,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":5}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14842592386831797498,{\"inputs\":[{\"Node\":{\"node_id\":664587514588499648,\"output_index\":0}},{\"Node\":{\"node_id\":14330881008352607546,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4243146970185091100,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18],\"remove\":[],\"delta\":[[3,[589.8710562414265,497.0754458161865]],[7,[596.3676268861453,491.9835390946502]],[8,[580.9382716049382,401.9753086419752]],[16,[616.0751917898693,491.077444156548]],[1,[583.5500685871057,499.53360768175577]],[2,[533.9259259259258,420.7407407407407]],[11,[615.6378600823045,431.5390946502058]],[13,[609.7997256515774,497.6899862825788]],[12,[609.0096021947874,496.4609053497942]],[18,[618.4910836762688,499.9725651577503]],[6,[594.4362139917694,493.6515775034293]],[4,[591.18792866941,498.3045267489712]],[5,[574.0246913580246,440.2962962962963]],[9,[606.0246913580247,486.803840877915]],[10,[607.8683127572016,488.3840877914952]],[17,[668.3566529492455,433.2510288065844]],[14,[650.6666666666667,398.2222222222223]],[15,[615.4183813443072,491.19341563786]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18],\"remove\":[],\"start_point\":[[15,15],[10,10],[17,17],[8,8],[4,4],[12,12],[3,3],[9,9],[11,11],[1,1],[13,13],[18,18],[14,14],[16,16],[2,2],[5,5],[7,7],[6,6]],\"end_point\":[[10,11],[8,9],[9,10],[6,7],[13,14],[4,5],[7,8],[5,6],[1,2],[17,18],[11,12],[2,3],[3,4],[18,1],[14,15],[16,17],[12,13],[15,16]],\"handle_primary\":[[11,[-3.906721536351256,29.62962962962956]],[16,[0.0,0.0]],[8,[22.10150891632361,44.44444444444463]],[5,[18.392318244170156,23.769547325102906]],[10,[0.0,0.0]],[7,[0.0,0.0]],[13,[0.0,0.0]],[9,[0.0,0.0]],[1,[0.0,0.0]],[14,[-18.3045267489714,37.31138545953348]],[4,[0.0,0.0]],[18,[-7.386678859929702,8.10852004267656]],[15,[0.0,0.0]],[6,[0.0,0.0]],[2,[44.005486968450214,35.16049382716062]],[12,[0.0,0.0]],[3,[0.0,0.0]],[17,[-38.27709190672158,34.5020576131688]]],\"handle_end\":[[8,null],[5,null],[18,[3.2434080170708057,6.03566529492457]],[7,[25.964334705075316,61.47599451303165]],[6,[0.0,0.0]],[15,[0.0,0.0]],[14,null],[9,[0.0,0.0]],[4,[21.113854595336193,37.201646090535064]],[16,[-35.29218106995893,26.337448559670804]],[13,[-23.747599451303245,35.4677640603565]],[12,[0.0,0.0]],[17,null],[2,null],[1,[48.21947873799752,48.855967078189394]],[10,[-6.189300411522709,26.732510288065782]],[3,[0.0,0.0]],[11,[0.0,0.0]]],\"stroke\":[[10,0],[15,0],[8,0],[3,0],[6,0],[1,0],[4,0],[18,0],[2,0],[9,0],[13,0],[12,0],[17,0],[5,0],[16,0],[11,0],[14,0],[7,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":18}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17965270694495451178,{\"inputs\":[{\"Node\":{\"node_id\":9409313765472227540,\"output_index\":0}},{\"Node\":{\"node_id\":4887570735033124574,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15167880819976070791,{\"inputs\":[{\"Node\":{\"node_id\":4898866541060902381,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":33.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"blending_nodes::OpacityNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16069762220015310717,{\"inputs\":[{\"Node\":{\"node_id\":16398743435291795904,\"output_index\":0}},{\"Node\":{\"node_id\":14791465604033956302,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16510804133693080967,{\"inputs\":[{\"Node\":{\"node_id\":3966971396176820223,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-261.24355652969996,-64.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2785423879796980286,{\"inputs\":[{\"Node\":{\"node_id\":11634802583144606404,\"output_index\":0}},{\"Node\":{\"node_id\":16591255610014418910,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2921219300441868542,{\"inputs\":[{\"Node\":{\"node_id\":11506204916439878896,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.49019608,\"green\":0.69411767,\"blue\":0.627451,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.49019608,\"green\":0.69411767,\"blue\":0.627451,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7067047867039575315,{\"inputs\":[{\"Node\":{\"node_id\":15261165353096835967,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-261.24355652969996,-64.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5261200785298607501,{\"inputs\":[{\"Node\":{\"node_id\":2465823993152870948,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"BlendMode\":\"Lighten\"},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"blending_nodes::BlendModeNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12554549497938935061,{\"inputs\":[{\"Node\":{\"node_id\":14055195208113082127,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[833.274364370262,-33.56362500933909]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":180.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.8,-0.6]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[7.016709298534876e-15,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8256712316698018135,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[3,[1343.538290724796,579.9999999999998]],[4,[1343.5382907247958,520.0]],[1,[1268.897274573418,500.00000000000006]],[2,[1268.8972745734184,559.9999999999999]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[4,4],[3,3],[2,2],[1,1]],\"end_point\":[[1,2],[2,3],[4,1],[3,4]],\"handle_primary\":[[2,[0.0,0.0]],[1,[0.0,0.0]],[4,[0.0,0.0]],[3,[0.0,0.0]]],\"handle_end\":[[1,[0.0,0.0]],[2,[0.0,0.0]],[3,[0.0,0.0]],[4,[0.0,0.0]]],\"stroke\":[[3,0],[1,0],[4,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[18046677540207938977,{\"inputs\":[{\"Node\":{\"node_id\":2189393878093040029,\"output_index\":0}},{\"Node\":{\"node_id\":2292399603649738346,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4134257789770357215,{\"inputs\":[{\"Node\":{\"node_id\":9640215309187299519,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-298.5640646053945,-71.86019325256757]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.9999999999999948,0.9999999999999948]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15914878146223026034,{\"inputs\":[{\"Node\":{\"node_id\":4069478660487729695,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[39.06014871394696,-80.31594690033606]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":-25.25081979598717},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.7021527212517815,1.4014617956106905]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-40.69754959903115,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2640491057355360805,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10],\"remove\":[],\"delta\":[[3,[559.8076211353318,500.00000000000017]],[5,[410.5255888325765,450.0000000000001]],[7,[671.7691453623979,459.99999999999994]],[4,[410.5255888325763,460.0]],[1,[783.7306695894642,510.0]],[8,[709.0896534380868,470.00000000000006]],[10,[933.0127018922194,470.0]],[9,[821.0511776651531,440.00000000000006]],[2,[653.1088913245535,475.0]],[6,[559.7618537735666,489.9877366723752]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10],\"remove\":[],\"start_point\":[[5,5],[2,2],[1,1],[9,9],[10,10],[8,8],[7,7],[4,4],[6,6],[3,3]],\"end_point\":[[1,2],[10,1],[9,10],[7,8],[3,4],[4,5],[5,6],[8,9],[2,3],[6,7]],\"handle_primary\":[[8,[0.0,0.0]],[9,[0.0,0.0]],[4,[0.0,0.0]],[6,null],[3,[-149.2820323027555,-40.00000000000017]],[2,null],[7,[37.32050807568885,10.000000000000114]],[1,null],[5,null],[10,[0.0,0.0]]],\"handle_end\":[[4,null],[8,[0.0,0.0]],[7,null],[9,[0.0,0.0]],[5,null],[1,null],[3,null],[6,null],[2,null],[10,null]],\"stroke\":[[6,0],[5,0],[2,0],[10,0],[3,0],[9,0],[1,0],[7,0],[8,0],[4,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":10}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3680957604830907751,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15],\"remove\":[],\"delta\":[[15,[1264.5486968449932,395.9981710105167]],[8,[1319.9451303155006,428.8614540466391]],[11,[1325.124828532236,431.14403292181055]],[7,[1336.2743484224964,374.25514403292175]],[13,[1322.315500685871,442.4691358024692]],[10,[1354.710562414266,375.5720164609054]],[1,[1304.1133973479657,421.12604785855825]],[4,[1311.6049382716046,423.2427983539094]],[6,[1317.750342935528,414.37585733882025]],[3,[1278.0100594421583,366.7343392775492]],[5,[1301.1577503429353,350.639231824417]],[2,[1304.6986739826243,420.69684499314127]],[9,[1321.5253772290812,429.56378600823047]],[14,[1313.0096021947877,442.29355281207137]],[12,[1360.3292181069958,385.0534979423868]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15],\"remove\":[],\"start_point\":[[11,11],[5,5],[7,7],[6,6],[10,10],[1,1],[13,13],[14,14],[8,8],[4,4],[12,12],[15,15],[3,3],[9,9],[2,2]],\"end_point\":[[14,15],[6,7],[13,14],[3,4],[10,11],[7,8],[12,13],[8,9],[1,2],[15,1],[2,3],[11,12],[5,6],[4,5],[9,10]],\"handle_primary\":[[5,[15.978052126200282,30.375857338820197]],[13,[-1.0925163846973192,0.8063811410860922]],[4,[0.0,0.0]],[14,[-1.4013919408635047,-1.0754868383370422]],[1,[0.0,0.0]],[2,[0.0,0.0]],[8,[0.0,0.0]],[15,[30.375857338820197,15.10013717421117]],[9,[0.0,0.0]],[7,[-16.182898948331285,35.4677640603565]],[12,[-18.78737997256485,20.455418381344316]],[10,[-22.650205761316556,26.60082304526742]],[3,[22.884316415180592,29.146776406035656]],[6,[0.0,0.0]],[11,[6.190926179952385,-9.64405832444237]]],\"handle_end\":[[12,[1.2603823499284772,-0.9302822106615168]],[4,[16.153635116598025,62.683127572016474]],[3,[0.0,0.0]],[9,[-20.894375857338673,18.52400548696835]],[6,[-20.484682213077576,38.013717421124625]],[5,[0.0,0.0]],[2,[26.044810242340873,43.48605395518973]],[13,[0.8337588052027058,0.639861408644208]],[14,[36.34567901234527,22.650205761316897]],[8,[0.0,0.0]],[7,[0.0,0.0]],[1,[0.0,0.0]],[15,[0.0,0.0]],[11,[-18.34842249657072,19.13854595336079]],[10,null]],\"stroke\":[[10,0],[13,0],[8,0],[7,0],[4,0],[15,0],[3,0],[9,0],[2,0],[11,0],[12,0],[5,0],[14,0],[6,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":15}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7910743362843097140,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11],\"remove\":[],\"delta\":[[5,[177.2724133595217,587.5]],[1,[-9.33012701892199,592.5]],[3,[83.97114317030001,547.5]],[11,[18.66025403784454,515.0]],[9,[326.55444566227675,562.5]],[2,[121.29165124598823,557.5000000000001]],[10,[261.2435565298214,580.0]],[4,[55.98076211353338,555.0000000000001]],[8,[261.24355652982126,545.0]],[6,[130.62177826491063,599.9999999999999]],[7,[93.30127018922188,590.0]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10],\"remove\":[],\"start_point\":[[3,3],[5,5],[6,6],[8,8],[9,9],[10,10],[7,7],[2,2],[4,4],[1,1]],\"end_point\":[[10,11],[5,6],[9,10],[6,7],[7,8],[2,3],[1,2],[4,5],[8,9],[3,4]],\"handle_primary\":[[4,[0.0,0.0]],[5,[0.0,0.0]],[7,[0.0,0.0]],[2,[0.0,0.0]],[8,[0.0,0.0]],[1,[0.0,0.0]],[10,[0.0,0.0]],[9,[0.0,0.0]],[6,[0.0,0.0]],[3,[0.0,0.0]]],\"handle_end\":[[8,[0.0,0.0]],[4,[0.0,0.0]],[7,[0.0,0.0]],[1,[0.0,0.0]],[5,[0.0,0.0]],[6,[0.0,0.0]],[2,[0.0,0.0]],[10,[0.0,0.0]],[3,[0.0,0.0]],[9,[0.0,0.0]]],\"stroke\":[[4,0],[10,0],[9,0],[2,0],[7,0],[6,0],[5,0],[8,0],[3,0],[1,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17887542695709892422,{\"inputs\":[{\"Node\":{\"node_id\":14842592386831797498,\"output_index\":0}},{\"Node\":{\"node_id\":7259756719760382667,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10564228200140683112,{\"inputs\":[{\"Node\":{\"node_id\":13001069903842109798,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-140.23409378379097,-66.17529531267506]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":-66.17233335779959},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[61.12160376625298,24.813625019997943]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-31.95064812348834,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11634445349252640936,{\"inputs\":[{\"Node\":{\"node_id\":2032185045476767535,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[708.5896534382083,269.5]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,0.267949192432]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-1.8801215900696402e-15,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3966971396176820223,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"delta\":[[1,[1156.9357503463468,509.9999999999987]],[2,[877.0319397786863,584.9999999999998]],[6,[1044.9742261192855,480.0]],[5,[597.1281292110198,599.9999999999999]],[4,[615.7883832488646,605.0000000000001]],[3,[783.7306695894646,560.0]]]},\"segments\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"start_point\":[[4,4],[1,1],[3,3],[6,6],[5,5],[2,2]],\"end_point\":[[1,2],[3,4],[5,6],[6,1],[2,3],[4,5]],\"handle_primary\":[[6,[0.0,0.0]],[2,[0.0,0.0]],[4,null],[3,null],[5,[447.84609690826574,-119.99999999999989]],[1,[0.0,0.0]]],\"handle_end\":[[1,[0.0,0.0]],[4,null],[5,null],[3,null],[6,[0.0,0.0]],[2,null]],\"stroke\":[[1,0],[3,0],[2,0],[4,0],[5,0],[6,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":6}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2900504420179573771,{\"inputs\":[{\"Node\":{\"node_id\":3185536512640676801,\"output_index\":0}},{\"Node\":{\"node_id\":10852750245702849075,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2843751023378786714,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19],\"remove\":[],\"delta\":[[1,[510.41975308641986,500.1481481481481]],[17,[565.2914244954804,444.3855996237166]],[14,[552.9705625612692,418.19081042008474]],[5,[497.38271604938257,447.60493827160496]],[16,[530.3703703703702,486.71604938271594]],[12,[533.8924329970387,433.46495092641567]],[8,[502.0960349862431,417.9179038759178]],[18,[568.2339832275112,446.0155371619765]],[13,[526.0246913580248,490.07407407407413]],[15,[556.7578325004595,420.13017723423656]],[10,[524.0493827160495,487.1111111111112]],[6,[499.77650172698026,445.9388618767647]],[9,[505.311372421164,416.4524664944526]],[2,[476.9512618480758,444.1904085078117]],[7,[515.1604938271604,486.716049382716]],[3,[480.5925925925926,440.2962962962962]],[11,[531.5890484844431,432.90541944395505]],[19,[530.7654320987656,499.55555555555566]],[4,[511.55555555555554,490.2222222222222]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19],\"remove\":[],\"start_point\":[[4,4],[6,6],[8,8],[10,10],[16,16],[5,5],[19,19],[1,1],[15,15],[9,9],[13,13],[12,12],[14,14],[7,7],[17,17],[3,3],[2,2],[18,18],[11,11]],\"end_point\":[[18,19],[13,14],[16,17],[17,18],[5,6],[8,9],[6,7],[9,10],[10,11],[7,8],[12,13],[15,16],[4,5],[1,2],[19,1],[2,3],[11,12],[3,4],[14,15]],\"handle_primary\":[[14,[15.260998855532309,-21.888591759537466]],[16,[0.0,0.0]],[7,[0.0,0.0]],[9,[11.197015798182122,18.39034937970939]],[5,[-10.016557406305251,-15.024836109457851]],[15,[-11.459519915000214,23.15277176924241]],[10,[0.0,0.0]],[18,[-19.30865594294653,18.299752742610902]],[1,[-9.283950617284065,-12.246913580246884]],[6,[3.9272019767233246,4.629039357803151]],[12,[-5.082537215847424,18.7049831985629]],[11,[7.784237194372736,-22.222909722529664]],[8,[-8.936088730151937,-20.93774746663439]],[3,[21.32824309569804,17.329197515254634]],[13,[0.0,0.0]],[17,[27.324497401397252,-21.15032331824051]],[4,[0.0,0.0]],[2,[-17.16805625160663,-20.28383937076643]],[19,[-1.5204271954055455,4.429070525747022]]],\"handle_end\":[[5,[-12.032856371187677,-14.183269935989983]],[6,[0.7901234567898427,-16.197530864197745]],[12,[3.7530864197531177,-16.59259259259261]],[4,[13.03703703703701,19.555555555555543]],[13,[-15.260998855532534,21.888591759537743]],[7,[18.59187624528363,43.5617887769169]],[1,[28.522359190346833,33.698803394714844]],[15,[4.54320987654296,-11.851851851852018]],[3,[-3.111111111111086,-12.0]],[2,[-22.123456790123555,-17.97530864197529]],[17,[19.435079545528083,-18.419570542198244]],[18,[4.54320987654296,-13.23456790123464]],[14,[11.286699158099054,-22.803605349427187]],[11,[5.082537215847424,-18.704983198562843]],[10,[-7.784237194372736,22.22290972252955]],[8,[-11.197015798182008,-18.39034937970939]],[19,[2.7053847913285836,3.5688054694122116]],[9,[0.7901234567898427,-21.135802469135857]],[16,[-28.402535606591755,21.984770746653737]]],\"stroke\":[[14,0],[7,0],[6,0],[17,0],[4,0],[3,0],[8,0],[16,0],[10,0],[1,0],[15,0],[19,0],[5,0],[13,0],[2,0],[12,0],[11,0],[18,0],[9,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":19}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7637119583909417127,{\"inputs\":[{\"Node\":{\"node_id\":4740496570730418920,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.9372549,\"green\":0.90588236,\"blue\":0.7764706,\"alpha\":0.7490196}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":3.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeAlign\":\"Center\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeCap\":\"Butt\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeJoin\":\"Miter\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"PaintOrder\":\"StrokeAbove\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"VecF64\":[]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::StrokeNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8166796652234334001,{\"inputs\":[{\"Node\":{\"node_id\":8034980397175569257,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.47937012,\"green\":0.73046875,\"blue\":0.56778514,\"alpha\":1.0},{\"red\":0.36862746,\"green\":0.57254905,\"blue\":0.45490196,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.4830458531002497,0.18799510852727777],\"end\":[0.5102584056336581,0.7583861422837161]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.47937012,\"green\":0.73046875,\"blue\":0.56778514,\"alpha\":1.0},{\"red\":0.36862746,\"green\":0.57254905,\"blue\":0.45490196,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.4830458531002497,0.18799510852727777],\"end\":[0.5102584056336581,0.7583861422837161]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16591255610014418910,{\"inputs\":[{\"Node\":{\"node_id\":2225749123534781340,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.9372549,\"green\":0.90588236,\"blue\":0.7764706,\"alpha\":0.7490196}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":3.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeAlign\":\"Center\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeCap\":\"Butt\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeJoin\":\"Miter\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"PaintOrder\":\"StrokeAbove\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"VecF64\":[]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::StrokeNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5891705401441266824,{\"inputs\":[{\"Node\":{\"node_id\":5670058004691708784,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17332567356044944766,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[3,[1.0,1.0]],[2,[1.0,0.0]],[1,[0.0,0.0]],[4,[-0.10921713655450987,1.0126086768123077]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[1,1],[2,2],[3,3],[4,4]],\"end_point\":[[3,4],[2,3],[4,1],[1,2]],\"handle_primary\":[[4,[0.0,0.0]],[1,[0.0,0.0]],[2,[0.0,0.0]],[3,[0.0,0.0]]],\"handle_end\":[[2,[0.0,0.0]],[3,[0.0,0.0]],[1,[0.0,0.0]],[4,[0.0,0.0]]],\"stroke\":[[4,0],[2,0],[1,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[429913874753911073,{\"inputs\":[{\"Node\":{\"node_id\":11236872744106223256,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.9372549,\"green\":0.9098039,\"blue\":0.7764706,\"alpha\":0.75}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":3.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeAlign\":\"Center\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeCap\":\"Round\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeJoin\":\"Miter\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"PaintOrder\":\"StrokeAbove\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"VecF64\":[]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::StrokeNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13701442050580061197,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"delta\":[[3,[1530.14083110324,469.9999999999986]],[1,[1268.897274573418,500.00000000000006]],[2,[1343.5382907247958,520.0000000000001]],[4,[1380.8587988003635,429.9999999999991]],[5,[1380.8587988003635,469.9997827307588]],[6,[1380.8587988003635,469.9999999999993]]]},\"segments\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"start_point\":[[1,1],[4,4],[2,2],[6,6],[5,5],[3,3]],\"end_point\":[[6,1],[2,3],[1,2],[5,6],[4,5],[3,4]],\"handle_primary\":[[4,[0.0,0.0]],[2,[0.0,0.0]],[1,[0.0,0.0]],[3,[0.0,0.0]],[6,[0.0,0.0]],[5,[0.0,0.0002172692405224552]]],\"handle_end\":[[4,null],[3,[0.0,0.0]],[1,[0.0,0.0]],[5,null],[2,[0.0,0.0]],[6,[4.547473508864641e-13,5.684341886080804e-14]]],\"stroke\":[[6,0],[2,0],[3,0],[5,0],[1,0],[4,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":6}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16059265180575745658,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5],\"remove\":[],\"delta\":[[2,[1044.9742261192855,599.9999999999999]],[5,[839.7114317029976,645.0]],[4,[1082.2947341949744,580.0]],[3,[1100.9549882328188,585.0]],[1,[1194.256258422041,640.0]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[3,3],[1,1],[2,2],[4,4]],\"end_point\":[[1,2],[4,5],[2,3],[3,4]],\"handle_primary\":[[4,[0.0,0.0]],[2,[0.0,0.0]],[1,[0.0,0.0]],[3,[0.0,0.0]]],\"handle_end\":[[4,[0.0,0.0]],[1,[0.0,0.0]],[2,[0.0,0.0]],[3,[0.0,0.0]]],\"stroke\":[[4,0],[1,0],[3,0],[2,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5882319123081134737,{\"inputs\":[{\"Node\":{\"node_id\":14102693648424950146,\"output_index\":0}},{\"Node\":{\"node_id\":13207576193421440093,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7030585744407664630,{\"inputs\":[{\"Node\":{\"node_id\":13701442050580061197,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-298.5640646053887,-113.99999999999903]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.9999999999999996,0.9999999999999996]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4740496570730418920,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[4,[298.56406460551034,610.0]],[2,[242.58330249197704,615.0]],[3,[261.24355652982155,620.0]],[1,[279.9038105676662,605.0000000000001]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[3,3],[1,1],[4,4],[2,2]],\"end_point\":[[4,1],[1,2],[2,3],[3,4]],\"handle_primary\":[[3,[0.0,0.0]],[4,[0.0,0.0]],[1,[0.0,0.0]],[2,[0.0,0.0]]],\"handle_end\":[[4,[0.0,0.0]],[3,[0.0,0.0]],[2,[0.0,0.0]],[1,[0.0,0.0]]],\"stroke\":[[3,0],[4,0],[2,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10651614176902312108,{\"inputs\":[{\"Node\":{\"node_id\":4134257789770357215,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.869076264787314,0.9670173672287944,1.0],\"midpoint\":[0.5,0.5,0.5,0.5],\"color\":[{\"red\":0.41568628,\"green\":0.6313726,\"blue\":0.6039216,\"alpha\":1.0},{\"red\":0.46666667,\"green\":0.69803923,\"blue\":0.6313726,\"alpha\":1.0},{\"red\":0.39215687,\"green\":0.6117647,\"blue\":0.6,\"alpha\":1.0},{\"red\":0.37254903,\"green\":0.5647059,\"blue\":0.5529412,\"alpha\":1.0}]},\"gradient_type\":\"Radial\",\"start\":[0.004591093221745557,0.9549938856995572],\"end\":[1.0010898078090742,0.23229219555707512]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.869076264787314,0.9670173672287944,1.0],\"midpoint\":[0.5,0.5,0.5,0.5],\"color\":[{\"red\":0.41568628,\"green\":0.6313726,\"blue\":0.6039216,\"alpha\":1.0},{\"red\":0.46666667,\"green\":0.69803923,\"blue\":0.6313726,\"alpha\":1.0},{\"red\":0.39215687,\"green\":0.6117647,\"blue\":0.6,\"alpha\":1.0},{\"red\":0.37254903,\"green\":0.5647059,\"blue\":0.5529412,\"alpha\":1.0}]},\"gradient_type\":\"Radial\",\"start\":[0.004591093221745557,0.9549938856995572],\"end\":[1.0010898078090742,0.23229219555707512]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[18431382379595272672,{\"inputs\":[{\"Node\":{\"node_id\":10989897386232385465,\"output_index\":0}},{\"Node\":{\"node_id\":4191887059541031673,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13609749019463823009,{\"inputs\":[{\"Node\":{\"node_id\":2062662104423219162,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.34901962,\"green\":0.5647059,\"blue\":0.5686275,\"alpha\":1.0},{\"red\":0.34901962,\"green\":0.5647059,\"blue\":0.5686275,\"alpha\":0.0}]},\"gradient_type\":\"Linear\",\"start\":[0.6190188337031772,0.2673182752249348],\"end\":[0.4910887633661796,0.9800526827918484]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.34901962,\"green\":0.5647059,\"blue\":0.5686275,\"alpha\":1.0},{\"red\":0.34901962,\"green\":0.5647059,\"blue\":0.5686275,\"alpha\":0.0}]},\"gradient_type\":\"Linear\",\"start\":[0.6190188337031772,0.2673182752249348],\"end\":[0.4910887633661796,0.9800526827918484]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4847316728405535983,{\"inputs\":[{\"Node\":{\"node_id\":11579925754926059876,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.5686275,\"green\":0.7411765,\"blue\":0.654902,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.5686275,\"green\":0.7411765,\"blue\":0.654902,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10431241258085047322,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"delta\":[[4,[709.0896534380863,630.0]],[3,[597.1281292110202,599.9999999999999]],[1,[298.56406460551034,479.99999999999983]],[6,[298.56406460551045,540.0000000000001]],[2,[597.1281292110192,559.9999999999998]],[5,[709.0896534380863,649.9999999999999]]]},\"segments\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"start_point\":[[2,2],[6,6],[4,4],[5,5],[3,3],[1,1]],\"end_point\":[[5,6],[6,1],[2,3],[3,4],[1,2],[4,5]],\"handle_primary\":[[1,[0.0,0.0]],[5,[0.0,0.0]],[2,[0.0,0.0]],[4,[0.0,0.0]],[3,[0.0,0.0]],[6,[0.0,0.0]]],\"handle_end\":[[5,[0.0,0.0]],[4,[0.0,0.0]],[6,[0.0,0.0]],[2,[0.0,0.0]],[3,[0.0,0.0]],[1,[0.0,0.0]]],\"stroke\":[[4,0],[1,0],[5,0],[2,0],[6,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":6}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5346759588580719138,{\"inputs\":[{\"Node\":{\"node_id\":57904581517036791,\"output_index\":0}},{\"Node\":{\"node_id\":16244305414728361140,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8508454285877707748,{\"inputs\":[{\"Node\":{\"node_id\":9908869573449854874,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.5137255,\"green\":0.7294118,\"blue\":0.68235296,\"alpha\":1.0},{\"red\":0.49019608,\"green\":0.7882353,\"blue\":0.75686276,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0033697141397510677,0.6230645063687077],\"end\":[0.058070316242295306,1.09924019568288]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.5137255,\"green\":0.7294118,\"blue\":0.68235296,\"alpha\":1.0},{\"red\":0.49019608,\"green\":0.7882353,\"blue\":0.75686276,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0033697141397510677,0.6230645063687077],\"end\":[0.058070316242295306,1.09924019568288]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11236872744106223256,{\"inputs\":[{\"Node\":{\"node_id\":9724746185253267560,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,16.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14102693648424950146,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":16847360882244487081,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6142412830271644616,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[4,[1250.2370205355735,424.00000000000006]],[2,[1175.5960043841962,429.0]],[1,[1278.2274015923404,401.49999999999994]],[3,[1203.586385440963,436.5]]]},\"segments\":{\"add\":[1,2,3],\"remove\":[],\"start_point\":[[3,3],[2,2],[1,1]],\"end_point\":[[3,4],[2,3],[1,2]],\"handle_primary\":[[1,[0.0,0.0]],[3,[0.0,0.0]],[2,[0.0,0.0]]],\"handle_end\":[[1,[0.0,0.0]],[2,[0.0,0.0]],[3,[0.0,0.0]]],\"stroke\":[[2,0],[3,0],[1,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[57904581517036791,{\"inputs\":[{\"Node\":{\"node_id\":9393309733761233513,\"output_index\":0}},{\"Node\":{\"node_id\":15300421479077882117,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10852750245702849075,{\"inputs\":[{\"Node\":{\"node_id\":17250040304106119844,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.9372549,\"green\":0.90588236,\"blue\":0.77254903,\"alpha\":0.75}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":3.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeAlign\":\"Center\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeCap\":\"Round\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeJoin\":\"Miter\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"PaintOrder\":\"StrokeAbove\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"VecF64\":[]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::StrokeNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14449710315388146362,{\"inputs\":[{\"Node\":{\"node_id\":7671691070850213967,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":81.1788},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"blending_nodes::OpacityNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6006052038693767172,{\"inputs\":[{\"Node\":{\"node_id\":4197544064668946479,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.9372549,\"green\":0.90588236,\"blue\":0.7764706,\"alpha\":0.7490196}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":3.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeAlign\":\"Center\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeCap\":\"Round\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeJoin\":\"Miter\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"PaintOrder\":\"StrokeAbove\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"VecF64\":[]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::StrokeNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16923062582661131268,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[4,[671.769145362398,420.0000000000001]],[3,[653.1088913245534,424.99999999999994]],[2,[597.1281292110202,409.99999999999994]],[1,[597.1281292110202,399.9999999999999]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[3,3],[1,1],[4,4],[2,2]],\"end_point\":[[4,1],[1,2],[3,4],[2,3]],\"handle_primary\":[[2,[0.0,0.0]],[3,[0.0,0.0]],[1,[0.0,0.0]],[4,[0.0,0.0]]],\"handle_end\":[[3,[0.0,0.0]],[4,[0.0,0.0]],[2,[0.0,0.0]],[1,[0.0,0.0]]],\"stroke\":[[3,0],[1,0],[2,0],[4,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8958782938691501404,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[1,[1156.9357503463525,530.0]],[2,[1231.576766497729,510.00000000000233]],[4,[1156.9357503463516,540.0]],[3,[1231.6308657449686,540.0144958496094]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[3,3],[1,1],[4,4],[2,2]],\"end_point\":[[3,4],[1,2],[2,3],[4,1]],\"handle_primary\":[[2,[0.0,0.0]],[1,[0.0,0.0]],[4,[0.0,0.0]],[3,[-74.69511539861696,-0.014495849609375]]],\"handle_end\":[[1,[0.0,0.0]],[2,null],[4,[0.0,-2.273736754432321e-13]],[3,null]],\"stroke\":[[1,0],[2,0],[4,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15478704582542175684,{\"inputs\":[{\"Node\":{\"node_id\":8242413775403456296,\"output_index\":0}},{\"Node\":{\"node_id\":13696921450692276893,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[532055960192543062,{\"inputs\":[{\"Node\":{\"node_id\":326112971739898070,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.9372549,\"green\":0.90588236,\"blue\":0.7764706,\"alpha\":0.7490196}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":3.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeAlign\":\"Center\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeCap\":\"Butt\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeJoin\":\"Miter\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"PaintOrder\":\"StrokeAbove\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"VecF64\":[]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::StrokeNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13942787566051910019,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[4,[0.0,1.0]],[3,[1.000788022064618,1.0000000000000009]],[2,[1.000788022064618,-6.266203653947348e-16]],[1,[0.0,0.0]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[4,4],[2,2],[3,3],[1,1]],\"end_point\":[[2,3],[1,2],[3,4],[4,1]],\"handle_primary\":[[1,[0.0,0.0]],[4,[0.0,0.0]],[2,[0.0,0.0]],[3,[0.0,0.0]]],\"handle_end\":[[1,[0.0,0.0]],[3,[0.0,0.0]],[2,[0.0,0.0]],[4,[0.0,0.0]]],\"stroke\":[[4,0],[3,0],[1,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13852123721901366011,{\"inputs\":[{\"Node\":{\"node_id\":456239140723765386,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[819.6941583984747,299.6363877833991]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":180.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[221.20900992053447,59.27277556679853]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-1.8801215900696402e-15,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7171713123860587892,{\"inputs\":[{\"Node\":{\"node_id\":18431382379595272672,\"output_index\":0}},{\"Node\":{\"node_id\":9798215931018813676,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7884283658260267478,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[4,[1203.5863854409629,557.4999999999999]],[2,[1231.5767664977295,540.0000000000001]],[1,[1184.9261314031187,552.5]],[3,[1250.2370205355735,545.0000000000001]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[1,1],[3,3],[2,2],[4,4]],\"end_point\":[[3,4],[4,1],[1,2],[2,3]],\"handle_primary\":[[4,[0.0,0.0]],[3,[0.0,0.0]],[2,[0.0,0.0]],[1,[0.0,0.0]]],\"handle_end\":[[1,[0.0,0.0]],[4,[0.0,0.0]],[3,[0.0,0.0]],[2,[0.0,0.0]]],\"stroke\":[[3,0],[4,0],[2,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16564941800301062922,{\"inputs\":[{\"Node\":{\"node_id\":18371793711669837037,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":0.03}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":0.03}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3616319631707471648,{\"inputs\":[{\"Node\":{\"node_id\":12852312236973354891,\"output_index\":0}},{\"Node\":{\"node_id\":16727310898641763441,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1104068854328504126,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[1,[1343.538290724796,580.0000000000001]],[3,[1530.14083110324,470.00000000000216]],[4,[1343.5382907247958,520.0]],[2,[1530.14083110324,530.000000000003]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[1,1],[4,4],[2,2],[3,3]],\"end_point\":[[1,2],[2,3],[4,1],[3,4]],\"handle_primary\":[[2,[0.0,0.0]],[1,[0.0,0.0]],[3,[0.0,0.0]],[4,[0.0,0.0]]],\"handle_end\":[[1,[0.0,0.0]],[3,[0.0,0.0]],[2,[0.0,0.0]],[4,[0.0,0.0]]],\"stroke\":[[3,0],[2,0],[1,0],[4,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13942146309185231085,{\"inputs\":[{\"Node\":{\"node_id\":15166516760575860563,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.2134053325596848,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.36862746,\"green\":0.57254905,\"blue\":0.45490196,\"alpha\":1.0},{\"red\":0.45490196,\"green\":0.70980394,\"blue\":0.58431375,\"alpha\":1.0},{\"red\":1.0,\"green\":0.84705883,\"blue\":0.4627451,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.4417705336480124,0.7894513882255817],\"end\":[0.4000315611112892,0.37243515464306]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.2134053325596848,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.36862746,\"green\":0.57254905,\"blue\":0.45490196,\"alpha\":1.0},{\"red\":0.45490196,\"green\":0.70980394,\"blue\":0.58431375,\"alpha\":1.0},{\"red\":1.0,\"green\":0.84705883,\"blue\":0.4627451,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.4417705336480124,0.7894513882255817],\"end\":[0.4000315611112892,0.37243515464306]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14449527838292182035,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18],\"remove\":[],\"delta\":[[6,[695.3964334705074,609.7777777777776]],[11,[713.6278006401462,612.5505258344765]],[12,[721.9094650205761,611.3799725651577]],[17,[749.8271604938273,587.4567901234568]],[8,[701.8247218411828,611.4580094497792]],[13,[734.8148148148149,620.9492455418381]],[16,[726.2990397805214,601.9862825788753]],[18,[725.5089163237311,594.6117969821673]],[15,[763.4567901234569,605.6296296296294]],[4,[682.3593964334707,606.4197530864195]],[14,[728.493827160494,609.9753086419753]],[7,[692.1871665904588,615.6720012193263]],[3,[699.7421124828531,603.0617283950616]],[1,[699.8518518518516,598.1234567901233]],[9,[709.3552812071331,611.4970278920897]],[5,[662.6063100137173,612.5432098765428]],[10,[709.413808870599,616.4718792866942]],[2,[667.3909465020577,593.3827160493828]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18],\"remove\":[],\"start_point\":[[4,4],[18,18],[2,2],[7,7],[1,1],[6,6],[9,9],[11,11],[17,17],[3,3],[8,8],[14,14],[15,15],[12,12],[5,5],[16,16],[10,10],[13,13]],\"end_point\":[[12,13],[6,7],[15,16],[11,12],[5,6],[13,14],[10,11],[18,1],[14,15],[7,8],[1,2],[4,5],[2,3],[9,10],[3,4],[16,17],[17,18],[8,9]],\"handle_primary\":[[14,[-0.1771601410563335,-2.331086581557088]],[4,[-6.038618148571572,2.2123832635880945]],[12,[0.6886396813392821,0.35058484628291353]],[16,[0.0,0.0]],[7,[0.0,0.0]],[9,[1.0398530837227329,0.17152215813985094]],[6,[0.0,0.0]],[15,[-15.992684042066571,-5.647919524462736]],[18,[-0.0877914951989851,0.0]],[11,[0.9510922553114368,-0.40840938602514143]],[10,[2.71082158531226,-3.275875783141601]],[13,[-3.101966163694442,-4.096936442615402]],[17,[-7.648317030623161,4.388047332817109]],[8,[0.0,0.0]],[1,[-13.651577503429507,-4.016460905349504]],[5,[0.0,0.0]],[2,[5.399176954732297,6.584362139917744]],[3,[0.0,0.0]]],\"handle_end\":[[14,[-22.51851851851859,-0.614540466392441]],[10,null],[1,[8.559670781892919,2.502057613168745]],[8,[-3.7847889041303233,-0.6242950769699291]],[6,[4.13595488492615,-4.1749733272365575]],[15,[2.8483462886752022,1.1315348270080676]],[3,[6.038618148571345,-2.2123832635879808]],[7,[-5.1358024691359105,5.530864197530718]],[16,[-5.977212841894016,7.381559383908893]],[9,null],[18,[0.0,0.0]],[12,[-6.90626428898031,-2.3996342021031296]],[5,[-8.69135802469134,3.3580246913579685]],[17,null],[4,[0.0,0.0]],[2,[0.0,0.0]],[11,[-1.2025737194898056,-0.6122274594622468]],[13,[-0.5267489711934559,1.053497942386798]]],\"stroke\":[[2,0],[14,0],[1,0],[10,0],[18,0],[13,0],[12,0],[6,0],[11,0],[4,0],[9,0],[8,0],[17,0],[5,0],[15,0],[3,0],[7,0],[16,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":18}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13861850149743924125,{\"inputs\":[{\"Node\":{\"node_id\":13442128106088307772,\"output_index\":0}},{\"Node\":{\"node_id\":5891705401441266824,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4398598693761352299,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[3,[933.0127018922194,470.0]],[2,[821.0511776651531,500.0]],[4,[821.0511776651532,440.0000000000001]],[1,[709.0896534380867,469.99999999999994]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[4,4],[3,3],[2,2],[1,1]],\"end_point\":[[1,2],[4,1],[2,3],[3,4]],\"handle_primary\":[[2,[0.0,0.0]],[1,[0.0,0.0]],[4,[0.0,0.0]],[3,[0.0,0.0]]],\"handle_end\":[[2,[0.0,0.0]],[1,[0.0,0.0]],[3,[0.0,0.0]],[4,[0.0,5.684341886080804e-14]]],\"stroke\":[[3,0],[1,0],[4,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16732345645494316637,{\"inputs\":[{\"Node\":{\"node_id\":11450962621506425680,\"output_index\":0}},{\"Node\":{\"node_id\":7637119583909417127,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[938033825024582130,{\"inputs\":[{\"Node\":{\"node_id\":10477328336261010694,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.5019608,\"green\":0.72156864,\"blue\":0.70980394,\"alpha\":1.0},{\"red\":0.5019608,\"green\":0.72156864,\"blue\":0.70980394,\"alpha\":0.0}]},\"gradient_type\":\"Linear\",\"start\":[0.44995556068938913,0.40889389103747775],\"end\":[0.42763248011625454,0.6979899795516591]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.5019608,\"green\":0.72156864,\"blue\":0.70980394,\"alpha\":1.0},{\"red\":0.5019608,\"green\":0.72156864,\"blue\":0.70980394,\"alpha\":0.0}]},\"gradient_type\":\"Linear\",\"start\":[0.44995556068938913,0.40889389103747775],\"end\":[0.42763248011625454,0.6979899795516591]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12019361655085452072,{\"inputs\":[{\"Node\":{\"node_id\":2036609094647228373,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1080.8351529382842,378.98352292316827]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":180.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[221.0038859407525,220.57244110912933]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-7.003011245448756e-15,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[585709295659496998,{\"inputs\":[{\"Node\":{\"node_id\":11990662272042254522,\"output_index\":0}},{\"Node\":{\"node_id\":12331680982485935376,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15347111149235590492,{\"inputs\":[{\"Node\":{\"node_id\":2209276411833629008,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1080.8351529382842,378.9835229231682]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":180.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[221.0038859407525,220.57244110912933]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-7.003011245448756e-15,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14752203606937854133,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[2,[597.1281292110197,559.9999999999997]],[4,[1044.9742261192855,480.0]],[3,[1044.9742261192855,439.99999999999994]],[1,[597.1281292110193,599.9999999999997]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[3,3],[2,2],[1,1],[4,4]],\"end_point\":[[3,4],[2,3],[1,2],[4,1]],\"handle_primary\":[[2,[0.0,0.0]],[3,[0.0,0.0]],[4,[0.0,0.0]],[1,[0.0,0.0]]],\"handle_end\":[[4,[0.0,0.0]],[2,[0.0,0.0]],[3,[0.0,0.0]],[1,[0.0,0.0]]],\"stroke\":[[2,0],[1,0],[4,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10849502918952703647,{\"inputs\":[{\"Node\":{\"node_id\":9663740787529879916,\"output_index\":0}},{\"Node\":{\"node_id\":17433098630591807963,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11506204916439878896,{\"inputs\":[{\"Node\":{\"node_id\":2640491057355360805,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-261.2435565297001,-64.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7005645574203740491,{\"inputs\":[{\"Node\":{\"node_id\":17873337220577786871,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-261.24355652969996,-64.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2452294403891427489,{\"inputs\":[{\"Node\":{\"node_id\":9278774434958175105,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.27058825,\"green\":0.49411765,\"blue\":0.5137255,\"alpha\":1.0},{\"red\":0.3372549,\"green\":0.5647059,\"blue\":0.5529412,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.027025122260555538,0.6600000000000135],\"end\":[0.9916422150126412,0.04000000000001247]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.27058825,\"green\":0.49411765,\"blue\":0.5137255,\"alpha\":1.0},{\"red\":0.3372549,\"green\":0.5647059,\"blue\":0.5529412,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.027025122260555538,0.6600000000000135],\"end\":[0.9916422150126412,0.04000000000001247]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17638504852426495381,{\"inputs\":[{\"Node\":{\"node_id\":17881728913029763313,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.972549,\"green\":0.87058824,\"blue\":0.6666667,\"alpha\":1.0},{\"red\":0.98039216,\"green\":0.94509804,\"blue\":0.7058824,\"alpha\":1.0}]},\"gradient_type\":\"Radial\",\"start\":[0.7363207208514658,0.8045676758185962],\"end\":[0.02104094774655124,0.19319223963735063]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.972549,\"green\":0.87058824,\"blue\":0.6666667,\"alpha\":1.0},{\"red\":0.98039216,\"green\":0.94509804,\"blue\":0.7058824,\"alpha\":1.0}]},\"gradient_type\":\"Radial\",\"start\":[0.7363207208514658,0.8045676758185962],\"end\":[0.02104094774655124,0.19319223963735063]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13696921450692276893,{\"inputs\":[{\"Node\":{\"node_id\":729026403095264425,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.9059757781988896,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.27058825,\"green\":0.49411765,\"blue\":0.5137255,\"alpha\":1.0},{\"red\":0.27058825,\"green\":0.49411765,\"blue\":0.5137255,\"alpha\":1.0},{\"red\":0.27058825,\"green\":0.49411765,\"blue\":0.5137255,\"alpha\":0.0}]},\"gradient_type\":\"Radial\",\"start\":[0.8930663763318565,-1.4466906873466163],\"end\":[1.0003976000579349,0.2239198862330611]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.9059757781988896,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.27058825,\"green\":0.49411765,\"blue\":0.5137255,\"alpha\":1.0},{\"red\":0.27058825,\"green\":0.49411765,\"blue\":0.5137255,\"alpha\":1.0},{\"red\":0.27058825,\"green\":0.49411765,\"blue\":0.5137255,\"alpha\":0.0}]},\"gradient_type\":\"Radial\",\"start\":[0.8930663763318565,-1.4466906873466163],\"end\":[1.0003976000579349,0.2239198862330611]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[764189229787475993,{\"inputs\":[{\"Node\":{\"node_id\":194878846429432339,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-261.2435565297001,-64.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[664587514588499648,{\"inputs\":[{\"Node\":{\"node_id\":5365849201631468915,\"output_index\":0}},{\"Node\":{\"node_id\":16564941800301062922,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4924169570021915606,{\"inputs\":[{\"Node\":{\"node_id\":972153153989181918,\"output_index\":0}},{\"Node\":{\"node_id\":8015732980153557800,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13853529851208960143,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[4,[-40.648024782723304,138.90840278889075]],[1,[-39.199465111941095,125.88462054688308]],[2,[28.09271595801783,129.8163283935271]],[3,[29.146213900404632,142.10291541428978]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[3,3],[4,4],[1,1],[2,2]],\"end_point\":[[2,3],[1,2],[4,1],[3,4]],\"handle_primary\":[[2,[0.0,0.0]],[4,[0.0,0.0]],[1,[0.0,0.0]],[3,[0.0,0.0]]],\"handle_end\":[[1,[-37.794238683127446,8.35487917411865]],[2,[0.0,0.0]],[4,[0.0,0.0]],[3,[27.65672806576993,9.286366023261053]]],\"stroke\":[[1,0],[2,0],[4,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16416441286881083283,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[3,[1492.8203230275506,480.00000000000006]],[1,[1306.217782649107,559.9999999999999]],[4,[1306.2177826491068,529.9999999999972]],[2,[1492.8203230275508,509.99999999999994]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[1,1],[2,2],[3,3],[4,4]],\"end_point\":[[4,1],[2,3],[3,4],[1,2]],\"handle_primary\":[[4,[0.0,0.0]],[3,[0.0,0.0]],[1,[0.0,0.0]],[2,[0.0,0.0]]],\"handle_end\":[[4,[0.0,0.0]],[2,[0.0,0.0]],[1,[0.0,0.0]],[3,[0.0,0.0]]],\"stroke\":[[3,0],[1,0],[2,0],[4,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7948029953091985757,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[4,[0.0,0.5]],[3,[0.5,1.0]],[2,[1.0,0.5]],[1,[0.5,0.0]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[4,4],[2,2],[1,1],[3,3]],\"end_point\":[[1,2],[2,3],[4,1],[3,4]],\"handle_primary\":[[3,[-0.275892388889507,0.0]],[4,[0.0,-0.275892388889507]],[1,[0.27589238888950707,0.0]],[2,[0.0,0.27589238888950707]]],\"handle_end\":[[4,[-0.275892388889507,0.0]],[3,[0.0,0.27589238888950707]],[1,[0.0,-0.275892388889507]],[2,[0.27589238888950707,0.0]]],\"stroke\":[[4,0],[3,0],[2,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9663740787529879916,{\"inputs\":[{\"Node\":{\"node_id\":14633096010607565334,\"output_index\":0}},{\"Node\":{\"node_id\":10651614176902312108,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9323583246068171750,{\"inputs\":[{\"Node\":{\"node_id\":3680957604830907751,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.54901963,\"green\":0.7372549,\"blue\":0.654902,\"alpha\":1.0},{\"red\":0.44705883,\"green\":0.654902,\"blue\":0.50980395,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.481496333638864,0.31809715494984925],\"end\":[0.5186182401466546,0.7659061379880119]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.54901963,\"green\":0.7372549,\"blue\":0.654902,\"alpha\":1.0},{\"red\":0.44705883,\"green\":0.654902,\"blue\":0.50980395,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.481496333638864,0.31809715494984925],\"end\":[0.5186182401466546,0.7659061379880119]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15876464101883822838,{\"inputs\":[{\"Node\":{\"node_id\":3227544593834141716,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.33333334,\"green\":0.5647059,\"blue\":0.5529412,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.33333334,\"green\":0.5647059,\"blue\":0.5529412,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5317925967883407701,{\"inputs\":[{\"Node\":{\"node_id\":13853529851208960143,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,2.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[729026403095264425,{\"inputs\":[{\"Node\":{\"node_id\":9392462024456293097,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-261.24355652969996,-64.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[972153153989181918,{\"inputs\":[{\"Node\":{\"node_id\":15488533792651297821,\"output_index\":0}},{\"Node\":{\"node_id\":9981992739451603109,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12852312236973354891,{\"inputs\":[{\"Node\":{\"node_id\":8181290118694677328,\"output_index\":0}},{\"Node\":{\"node_id\":2921219300441868542,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5017082804473894058,{\"inputs\":[{\"Node\":{\"node_id\":8165914767449151618,\"output_index\":0}},{\"Node\":{\"node_id\":8166796652234334001,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1268775104597510914,{\"inputs\":[{\"Node\":{\"node_id\":13861850149743924125,\"output_index\":0}},{\"Node\":{\"node_id\":18011777376689315137,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9641315149170593327,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],\"remove\":[],\"delta\":[[4,[1309.432098765432,385.3827160493827]],[20,[1326.2661179698216,436.1481481481482]],[2,[1297.047751590237,432.45710241679353]],[8,[1270.5185185185182,430.0]],[13,[1312.9218106995884,446.85871056241416]],[17,[1343.538290724795,449.4814814814814]],[5,[1280.0,432.79012345679007]],[1,[1313.111111111111,440.66666666666663]],[18,[1327.484998303753,444.1384489176408]],[9,[1269.113854595336,397.6954732510288]],[14,[1325.5637860082304,455.6378600823044]],[11,[1259.4489801206926,442.53166286845914]],[15,[1319.4403292181073,447.7366255144034]],[6,[1286.7160493827164,380.04205830395256]],[7,[1284.082304526749,376.2304526748971]],[16,[1323.5884773662551,445.96633567616453]],[19,[1353.7580246913572,440.1251028806582]],[10,[1267.5056400134067,396.48371220183463]],[3,[1311.0123456790122,389.1358024691358]],[12,[1302.7379972565157,443.9615912208504]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],\"remove\":[],\"start_point\":[[17,17],[11,11],[19,19],[7,7],[10,10],[4,4],[20,20],[16,16],[8,8],[13,13],[15,15],[5,5],[3,3],[12,12],[14,14],[18,18],[6,6],[9,9],[2,2],[1,1]],\"end_point\":[[2,3],[18,19],[11,12],[1,2],[8,9],[6,7],[17,18],[3,4],[4,5],[19,20],[15,16],[14,15],[16,17],[9,10],[20,1],[7,8],[10,11],[12,13],[5,6],[13,14]],\"handle_primary\":[[16,[19.75931834024595,4.512540664093933]],[14,[0.7997581306144639,-0.5767761645364544]],[2,[-7.975963753017595,-5.0295761250696955]],[17,[0.17673990847697496,-0.9253757795596016]],[1,[-1.6922078070856514,-5.274941522376082]],[9,[0.0,0.0]],[7,[-10.798353909464822,37.53086419753089]],[10,[-0.14088374697121253,12.961503560065635]],[11,[14.00629256044499,3.614815506799175]],[5,[0.0,-30.09785939152056]],[12,[9.763281342613707,0.018425375535628064]],[19,[-0.162851070793522,-0.9241759662677964]],[6,null],[20,null],[15,[0.05852766346606586,-1.7558299039780536]],[3,null],[4,[-16.197530864197688,14.61728395061732]],[18,[26.44161668001243,-3.0566001241026584]],[13,[0.0,0.0]],[8,[-2.1728395061727497,-5.1111111111111995]]],\"handle_end\":[[7,null],[9,null],[17,null],[16,null],[13,[-0.8413105648451165,0.6067432917530482]],[1,[3.0857646420445235,-0.2303077208630384]],[12,[0.0,0.0]],[20,null],[4,null],[15,null],[19,[1.229080932784882,6.672153635116501]],[10,[-4.337869009581482,-9.544008547471435]],[2,null],[18,null],[5,null],[11,[-11.251577773519555,-0.02123410547835647]],[14,[-0.022878726643284608,0.6863617992941045]],[3,null],[8,[0.0,0.0]],[6,null]],\"stroke\":[[14,0],[11,0],[20,0],[1,0],[6,0],[10,0],[8,0],[13,0],[19,0],[18,0],[7,0],[12,0],[17,0],[3,0],[16,0],[9,0],[4,0],[15,0],[5,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":20}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3927358878935116440,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[2,[1082.2947341949798,490.0000000000013]],[4,[1194.2562584220411,420.00000000000006]],[1,[1194.2562584220414,520.0000000000001]],[3,[1082.2947341949805,450.0000000000013]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[3,3],[4,4],[2,2],[1,1]],\"end_point\":[[4,1],[2,3],[3,4],[1,2]],\"handle_primary\":[[3,[0.0,0.0]],[2,[6.821210263296962e-13,-40.0]],[1,[0.0,0.0]],[4,[0.0,0.0]]],\"handle_end\":[[2,null],[3,[0.0,0.0]],[4,[0.0,-1.1368683772161605e-13]],[1,null]],\"stroke\":[[3,0],[2,0],[4,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14255588039347536657,{\"inputs\":[{\"Node\":{\"node_id\":18185020559178852986,\"output_index\":0}},{\"Node\":{\"node_id\":5261200785298607501,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9640215309187299519,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5],\"remove\":[],\"delta\":[[5,[1268.8972745734306,517.8601932525701]],[2,[1194.2562584220475,517.8601932525687]],[3,[746.4101615137856,637.8601932525706]],[1,[1268.8972745734306,457.8601932525698]],[4,[746.4101615137852,657.8601932525711]]]},\"segments\":{\"add\":[1,2,3,4,5],\"remove\":[],\"start_point\":[[4,4],[5,5],[2,2],[3,3],[1,1]],\"end_point\":[[3,4],[5,1],[4,5],[1,2],[2,3]],\"handle_primary\":[[2,[-447.8460969082618,120.00000000000192]],[4,[0.0,0.0]],[3,[0.0,0.0]],[1,[0.0,0.0]],[5,[0.0,-60.00000000000029]]],\"handle_end\":[[5,null],[2,null],[4,null],[3,[0.0,0.0]],[1,null]],\"stroke\":[[3,0],[4,0],[1,0],[5,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":5}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4101813853952238986,{\"inputs\":[{\"Node\":{\"node_id\":14161755104759532162,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.9372549,\"green\":0.90588236,\"blue\":0.7764706,\"alpha\":0.7490196}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":3.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeAlign\":\"Center\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeCap\":\"Round\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeJoin\":\"Miter\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"PaintOrder\":\"StrokeAbove\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"VecF64\":[]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::StrokeNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8884703330021429739,{\"inputs\":[{\"Node\":{\"node_id\":16732345645494316637,\"output_index\":0}},{\"Node\":{\"node_id\":532055960192543062,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2155997486525176376,{\"inputs\":[{\"Node\":{\"node_id\":15595689026000825531,\"output_index\":0}},{\"Node\":{\"node_id\":7639490284239357347,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13700218159488557234,{\"inputs\":[{\"Node\":{\"node_id\":10181153433637856462,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.4831746395427087,0.49908141270537343,0.5160486374122156,0.65708869278784,0.6729954659505047,0.6899626906573468,0.7291993977919214,0.7440457194104085,0.7588920410288952,1.0],\"midpoint\":[0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0},{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0},{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0},{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}]},\"gradient_type\":\"Radial\",\"start\":[0.5008896367526079,0.4999999999999998],\"end\":[1.0,0.5]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.4831746395427087,0.49908141270537343,0.5160486374122156,0.65708869278784,0.6729954659505047,0.6899626906573468,0.7291993977919214,0.7440457194104085,0.7588920410288952,1.0],\"midpoint\":[0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0},{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0},{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0},{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}]},\"gradient_type\":\"Radial\",\"start\":[0.5008896367526079,0.4999999999999998],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8863202447825570192,{\"inputs\":[{\"Node\":{\"node_id\":5017082804473894058,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-299.38891648776223,-111.69072674057747]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0616145921394244,1.0616145921394244]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7156963182187517674,{\"inputs\":[{\"Node\":{\"node_id\":14752203606937854133,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-261.2435565296996,-63.99999999999977]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[322234583139821148,{\"inputs\":[{\"Node\":{\"node_id\":6589978257209505606,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-261.2435565297001,-63.99999999999994]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11990662272042254522,{\"inputs\":[{\"Node\":{\"node_id\":1147521068928676110,\"output_index\":0}},{\"Node\":{\"node_id\":15167880819976070791,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14633096010607565334,{\"inputs\":[{\"Node\":{\"node_id\":18046677540207938977,\"output_index\":0}},{\"Node\":{\"node_id\":12370676490908282512,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3227544593834141716,{\"inputs\":[{\"Node\":{\"node_id\":8256712316698018135,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-298.5640646053871,-113.9999999999974]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.9999999999999996,0.9999999999999996]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2183401450260403525,{\"inputs\":[{\"Node\":{\"node_id\":16852951849051795674,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-261.2435565297001,-64.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11429712783984224234,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":12302362769310895852,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9392462024456293097,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"delta\":[[6,[1044.9742261192855,480.0]],[5,[597.1281292110198,599.9999999999999]],[1,[1156.9357503463468,509.9999999999987]],[2,[877.0319397786863,584.9999999999998]],[3,[783.7306695894646,560.0]],[4,[615.7883832488646,605.0000000000001]]]},\"segments\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"start_point\":[[1,1],[3,3],[5,5],[4,4],[6,6],[2,2]],\"end_point\":[[6,1],[4,5],[2,3],[1,2],[3,4],[5,6]],\"handle_primary\":[[4,null],[1,[0.0,0.0]],[2,[0.0,0.0]],[3,null],[5,[447.84609690826574,-119.99999999999989]],[6,[0.0,0.0]]],\"handle_end\":[[4,null],[3,null],[1,[0.0,0.0]],[2,null],[5,null],[6,[0.0,0.0]]],\"stroke\":[[5,0],[1,0],[6,0],[3,0],[4,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":6}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12331680982485935376,{\"inputs\":[{\"Node\":{\"node_id\":2183401450260403525,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.5882353,\"green\":0.627451,\"blue\":0.39607844,\"alpha\":0.6313726},{\"red\":0.5882353,\"green\":0.627451,\"blue\":0.39607844,\"alpha\":0.0}]},\"gradient_type\":\"Linear\",\"start\":[0.7293479177394283,-0.000903965337796908],\"end\":[0.7877909957441462,0.5587258938108439]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.5882353,\"green\":0.627451,\"blue\":0.39607844,\"alpha\":0.6313726},{\"red\":0.5882353,\"green\":0.627451,\"blue\":0.39607844,\"alpha\":0.0}]},\"gradient_type\":\"Linear\",\"start\":[0.7293479177394283,-0.000903965337796908],\"end\":[0.7877909957441462,0.5587258938108439]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[11895211316848895241,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[14019233912018234740,{\"persistent_metadata\":{\"display_name\":\"Structure\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":27}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10431241258085047322,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14330881008352607546,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[514222872092587805,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2465823993152870948,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[15898396405528650339,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\" px\",\"x\":\"X\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\"x\",\"x\":\"W\",\"y\":\"H\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14400993470150734626,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"x\":\"X\",\"y\":\"Y\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"x\":\"W\",\"unit\":\"x\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4898866541060902381,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[2900504420179573771,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4887570735033124574,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[4924169570021915606,{\"persistent_metadata\":{\"display_name\":\"Sphere\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":9}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12683405703338263457,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10188337730058049439,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11429712783984224234,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[322234583139821148,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\" px\",\"x\":\"X\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"unit\":\"x\",\"y\":\"H\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4134257789770357215,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"x\":\"X\",\"y\":\"Y\",\"unit\":\" px\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\"x\",\"x\":\"W\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15478704582542175684,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9210109719406330381,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"y\":\"Y\",\"x\":\"X\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\"x\",\"is_integer\":false,\"x\":\"W\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7067047867039575315,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"Y\",\"unit\":\" px\",\"x\":\"X\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"H\",\"is_integer\":false,\"unit\":\"x\",\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14449527838292182035,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15134939288287905620,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"x\":\"X\",\"unit\":\" px\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"y\":\"H\",\"unit\":\"x\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2077983679740571162,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[664587514588499648,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[183952488591282082,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\" px\",\"y\":\"Y\",\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"H\",\"x\":\"W\",\"unit\":\"x\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9684750473849891261,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[3680957604830907751,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[17332567356044944766,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15167880819976070791,{\"persistent_metadata\":{\"display_name\":\"Opacity\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Opacity\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[7030585744407664630,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"Y\",\"is_integer\":false,\"unit\":\" px\",\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"x\":\"W\",\"unit\":\"x\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15157035456876170143,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"x\":\"X\",\"is_integer\":false,\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\"x\",\"is_integer\":false,\"y\":\"H\",\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7884283658260267478,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9150078008481575131,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[8612613134760093452,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13287180494862716983,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11490835759023283071,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"is_integer\":false,\"x\":\"X\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"H\",\"is_integer\":false,\"unit\":\"x\",\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12370676490908282512,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[2881239077602364410,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[12360435709959435360,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[729026403095264425,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"X\",\"is_integer\":false,\"unit\":\" px\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"y\":\"H\",\"is_integer\":false,\"unit\":\"x\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[57904581517036791,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13609749019463823009,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[16229837691656808412,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16416441286881083283,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9409313765472227540,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[6589978257209505606,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9798215931018813676,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[14791465604033956302,{\"persistent_metadata\":{\"display_name\":\"Opacity\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Opacity\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[9935922395919478146,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3679103217373457623,{\"persistent_metadata\":{\"display_name\":\"Stroke\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the stroke style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Color\",\"input_description\":\"The stroke color.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Weight\",\"input_description\":\"The stroke weight.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Align\",\"input_description\":\"The alignment of stroke to the path's centerline or (for closed shapes) the inside or outside of the shape.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Cap\",\"input_description\":\"The shape of the stroke at open endpoints.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Join\",\"input_description\":\"The curvature of the bent stroke at sharp corners.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Miter Limit\",\"input_description\":\"The threshold for when a miter-joined stroke is converted to a bevel-joined stroke when a sharp angle becomes pointier than this ratio.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Paint Order\",\"input_description\":\"The order to paint the stroke on top of the fill, or the fill on top of the stroke.\\n<https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty>\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Lengths\",\"input_description\":\"The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Offset\",\"input_description\":\"The phase offset distance from the starting point of the dash pattern.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[4323461535289334196,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[6821938959315178556,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12548387328300782726,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[9226731772122225003,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2036609094647228373,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2510483139353274965,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2032185045476767535,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-36,216]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9724746185253267560,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9771562518763748677,{\"persistent_metadata\":{\"display_name\":\"Plant (Upper Left)\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":39}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11776939455674933130,{\"persistent_metadata\":{\"display_name\":\"Backdrop Gradient\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3970872207068447290,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"x\":\"X\",\"y\":\"Y\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"H\",\"unit\":\"x\",\"x\":\"W\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2183401450260403525,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"X\",\"y\":\"Y\",\"is_integer\":false,\"unit\":\" px\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\"x\",\"x\":\"W\",\"is_integer\":false,\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[18431382379595272672,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[8015732980153557800,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"y\":\"Y\",\"is_integer\":false,\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"x\":\"W\",\"unit\":\"x\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15300421479077882117,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[12852312236973354891,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[6006052038693767172,{\"persistent_metadata\":{\"display_name\":\"Stroke\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the stroke style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Color\",\"input_description\":\"The stroke color.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Weight\",\"input_description\":\"The stroke weight.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Align\",\"input_description\":\"The alignment of stroke to the path's centerline or (for closed shapes) the inside or outside of the shape.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Cap\",\"input_description\":\"The shape of the stroke at open endpoints.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Join\",\"input_description\":\"The curvature of the bent stroke at sharp corners.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Miter Limit\",\"input_description\":\"The threshold for when a miter-joined stroke is converted to a bevel-joined stroke when a sharp angle becomes pointier than this ratio.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Paint Order\",\"input_description\":\"The order to paint the stroke on top of the fill, or the fill on top of the stroke.\\n<https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty>\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Lengths\",\"input_description\":\"The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Offset\",\"input_description\":\"The phase offset distance from the starting point of the dash pattern.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[4884180935153120645,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[13700218159488557234,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[12931264630175648107,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16807867745126764195,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[1108089904278882840,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[12554549497938935061,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"x\":\"X\",\"y\":\"Y\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"H\",\"unit\":\"x\",\"x\":\"W\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3927358878935116440,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7948029953091985757,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9993538712344947860,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[7639490284239357347,{\"persistent_metadata\":{\"display_name\":\"Stroke\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the stroke style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Color\",\"input_description\":\"The stroke color.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Weight\",\"input_description\":\"The stroke weight.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Align\",\"input_description\":\"The alignment of stroke to the path's centerline or (for closed shapes) the inside or outside of the shape.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Cap\",\"input_description\":\"The shape of the stroke at open endpoints.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Join\",\"input_description\":\"The curvature of the bent stroke at sharp corners.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Miter Limit\",\"input_description\":\"The threshold for when a miter-joined stroke is converted to a bevel-joined stroke when a sharp angle becomes pointier than this ratio.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Paint Order\",\"input_description\":\"The order to paint the stroke on top of the fill, or the fill on top of the stroke.\\n<https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty>\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Lengths\",\"input_description\":\"The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Offset\",\"input_description\":\"The phase offset distance from the starting point of the dash pattern.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[3021739385836969518,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10989897386232385465,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15709488322180832347,{\"persistent_metadata\":{\"display_name\":\"Artboard\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Artboards\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Contents\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":true,\"x\":\"X\",\"unit\":\" px\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Location\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":true,\"y\":\"H\",\"unit\":\" px\",\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Dimensions\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"artboard_background\",\"input_name\":\"Background\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Clip\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-5,0]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Artboard\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Create Artboard\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-4]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[1032659476619711014,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13868917743026516656,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[1396768435017101055,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[1490537476612110327,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10651614176902312108,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[10742991645899166287,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\" px\",\"x\":\"X\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\"x\",\"is_integer\":false,\"x\":\"W\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16069762220015310717,{\"persistent_metadata\":{\"display_name\":\"Fountain Water Ripples\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":87}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16852951849051795674,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15723520455917422372,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"x\":\"X\",\"y\":\"Y\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"y\":\"H\",\"x\":\"W\",\"unit\":\"x\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16793555741218543212,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9640215309187299519,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14633096010607565334,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[8181290118694677328,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9641315149170593327,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[17873337220577786871,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9138781233934614517,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[1229809699395562135,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"Y\",\"is_integer\":false,\"unit\":\" px\",\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\"x\",\"x\":\"W\",\"y\":\"H\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4757672276235057645,{\"persistent_metadata\":{\"display_name\":\"Plant (Right)\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":9}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[5326013268137833446,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[14805036488257720752,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7339104629465306715,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[4069478660487729695,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4105329493214975815,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"X\",\"unit\":\" px\",\"y\":\"Y\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"is_integer\":false,\"unit\":\"x\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10477328336261010694,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"x\":\"X\",\"is_integer\":false,\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"is_integer\":false,\"unit\":\"x\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4847316728405535983,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[8884703330021429739,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12172015233077238737,{\"persistent_metadata\":{\"display_name\":\"Stroke\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the stroke style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Color\",\"input_description\":\"The stroke color.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Weight\",\"input_description\":\"The stroke weight.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Align\",\"input_description\":\"The alignment of stroke to the path's centerline or (for closed shapes) the inside or outside of the shape.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Cap\",\"input_description\":\"The shape of the stroke at open endpoints.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Join\",\"input_description\":\"The curvature of the bent stroke at sharp corners.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Miter Limit\",\"input_description\":\"The threshold for when a miter-joined stroke is converted to a bevel-joined stroke when a sharp angle becomes pointier than this ratio.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Paint Order\",\"input_description\":\"The order to paint the stroke on top of the fill, or the fill on top of the stroke.\\n<https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty>\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Lengths\",\"input_description\":\"The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Offset\",\"input_description\":\"The phase offset distance from the starting point of the dash pattern.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[8891726805381758817,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"y\":\"Y\",\"x\":\"X\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"is_integer\":false,\"unit\":\"x\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2921219300441868542,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[17059035448296015006,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4197544064668946479,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2452294403891427489,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[13701442050580061197,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[1831743139584171612,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[13861850149743924125,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11221222899304956410,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\" px\",\"y\":\"Y\",\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"H\",\"is_integer\":false,\"unit\":\"x\",\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2785423879796980286,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2640491057355360805,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2843751023378786714,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[764189229787475993,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"y\":\"Y\",\"is_integer\":false,\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\"x\",\"y\":\"H\",\"x\":\"W\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13920465562072008593,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"x\":\"X\",\"unit\":\" px\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"is_integer\":false,\"unit\":\"x\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9666682009015049330,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"Y\",\"is_integer\":false,\"unit\":\" px\",\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"H\",\"is_integer\":false,\"unit\":\"x\",\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[6777328619777499144,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9157963288496356916,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[17250040304106119844,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"Y\",\"unit\":\" px\",\"x\":\"X\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\"x\",\"x\":\"W\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16564941800301062922,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[5365849201631468915,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16059265180575745658,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13747030364552895864,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[5434119356821575534,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[18011777376689315137,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[12537712543904859919,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[1156213189397385283,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7156963182187517674,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"X\",\"y\":\"Y\",\"is_integer\":false,\"unit\":\" px\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\"x\",\"is_integer\":false,\"x\":\"W\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[8165914767449151618,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16261620049358949344,{\"persistent_metadata\":{\"display_name\":\"Stroke\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the stroke style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Color\",\"input_description\":\"The stroke color.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Weight\",\"input_description\":\"The stroke weight.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Align\",\"input_description\":\"The alignment of stroke to the path's centerline or (for closed shapes) the inside or outside of the shape.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Cap\",\"input_description\":\"The shape of the stroke at open endpoints.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Join\",\"input_description\":\"The curvature of the bent stroke at sharp corners.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Miter Limit\",\"input_description\":\"The threshold for when a miter-joined stroke is converted to a bevel-joined stroke when a sharp angle becomes pointier than this ratio.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Paint Order\",\"input_description\":\"The order to paint the stroke on top of the fill, or the fill on top of the stroke.\\n<https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty>\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Lengths\",\"input_description\":\"The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Offset\",\"input_description\":\"The phase offset distance from the starting point of the dash pattern.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[13837327017498431546,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4740496570730418920,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15775513677915164685,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[70804263053697201,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"Y\",\"unit\":\" px\",\"is_integer\":false,\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"y\":\"H\",\"x\":\"W\",\"unit\":\"x\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10507084483235320484,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[18046677540207938977,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11076863066321508991,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[17881728913029763313,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\" px\",\"y\":\"Y\",\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"unit\":\"x\",\"is_integer\":false,\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13696921450692276893,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[8028812053913481975,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[1147521068928676110,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[8508454285877707748,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[5261200785298607501,{\"persistent_metadata\":{\"display_name\":\"Blend Mode\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Blend Mode\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[15261165353096835967,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15876464101883822838,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[3564067978712674849,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"Y\",\"x\":\"X\",\"is_integer\":false,\"unit\":\" px\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\"x\",\"is_integer\":false,\"y\":\"H\",\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7952384394377946257,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[2058192342619930156,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"x\":\"X\",\"unit\":\" px\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"y\":\"H\",\"unit\":\"x\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3365825508845848745,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[10779665858841986661,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[326112971739898070,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2189393878093040029,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[938033825024582130,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[7910743362843097140,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15216519480392295991,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[14055195208113082127,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-22,48]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13531127678140037818,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[4741515246389989284,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\" px\",\"y\":\"Y\",\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"H\",\"is_integer\":false,\"unit\":\"x\",\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[5925268772265373737,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[3966971396176820223,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[456239140723765386,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12019361655085452072,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"x\":\"X\",\"y\":\"Y\",\"unit\":\" px\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"y\":\"H\",\"unit\":\"x\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10849502918952703647,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13942787566051910019,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15595689026000825531,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[5258402282444994019,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"Y\",\"unit\":\" px\",\"x\":\"X\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\"x\",\"x\":\"W\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7838724497953148309,{\"persistent_metadata\":{\"display_name\":\"Geometric Ripples\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-9,3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16244305414728361140,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[17433098630591807963,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[9908869573449854874,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"y\":\"Y\",\"x\":\"X\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"H\",\"is_integer\":false,\"x\":\"W\",\"unit\":\"x\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14102693648424950146,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[6142412830271644616,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[5317925967883407701,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"Y\",\"is_integer\":false,\"x\":\"X\",\"unit\":\" px\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"H\",\"x\":\"W\",\"unit\":\"x\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[17965270694495451178,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-22,96]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7171713123860587892,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16923062582661131268,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7005645574203740491,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"x\":\"X\",\"y\":\"Y\",\"unit\":\" px\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\"x\",\"x\":\"W\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[8343201730608263656,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2062662104423219162,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"X\",\"unit\":\" px\",\"is_integer\":false,\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\"x\",\"y\":\"H\",\"x\":\"W\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[6556170892691431702,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[6785205785632793666,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[3185536512640676801,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4046495708656778502,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[18128923159828618806,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[16732345645494316637,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[17785019773455930267,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\" px\",\"x\":\"X\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"H\",\"is_integer\":false,\"x\":\"W\",\"unit\":\"x\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14255588039347536657,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-22,250]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[18371793711669837037,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\" px\",\"y\":\"Y\",\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\"x\",\"y\":\"H\",\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[8034980397175569257,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"Y\",\"x\":\"X\",\"unit\":\" px\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"is_integer\":false,\"y\":\"H\",\"unit\":\"x\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13207576193421440093,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[4837219841531371489,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15166516760575860563,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"y\":\"Y\",\"unit\":\" px\",\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"unit\":\"x\",\"y\":\"H\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11579925754926059876,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"X\",\"y\":\"Y\",\"unit\":\" px\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\"x\",\"x\":\"W\",\"y\":\"H\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7637119583909417127,{\"persistent_metadata\":{\"display_name\":\"Stroke\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the stroke style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Color\",\"input_description\":\"The stroke color.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Weight\",\"input_description\":\"The stroke weight.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Align\",\"input_description\":\"The alignment of stroke to the path's centerline or (for closed shapes) the inside or outside of the shape.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Cap\",\"input_description\":\"The shape of the stroke at open endpoints.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Join\",\"input_description\":\"The curvature of the bent stroke at sharp corners.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Miter Limit\",\"input_description\":\"The threshold for when a miter-joined stroke is converted to a bevel-joined stroke when a sharp angle becomes pointier than this ratio.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Paint Order\",\"input_description\":\"The order to paint the stroke on top of the fill, or the fill on top of the stroke.\\n<https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty>\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Lengths\",\"input_description\":\"The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Offset\",\"input_description\":\"The phase offset distance from the starting point of the dash pattern.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[7861616450605235840,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3122972215852775755,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"is_integer\":false,\"x\":\"X\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\"x\",\"is_integer\":false,\"x\":\"W\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7259756719760382667,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[5574499968250848265,{\"persistent_metadata\":{\"display_name\":\"Water\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":6}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10852750245702849075,{\"persistent_metadata\":{\"display_name\":\"Stroke\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the stroke style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Color\",\"input_description\":\"The stroke color.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Weight\",\"input_description\":\"The stroke weight.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Align\",\"input_description\":\"The alignment of stroke to the path's centerline or (for closed shapes) the inside or outside of the shape.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Cap\",\"input_description\":\"The shape of the stroke at open endpoints.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Join\",\"input_description\":\"The curvature of the bent stroke at sharp corners.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Miter Limit\",\"input_description\":\"The threshold for when a miter-joined stroke is converted to a bevel-joined stroke when a sharp angle becomes pointier than this ratio.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Paint Order\",\"input_description\":\"The order to paint the stroke on top of the fill, or the fill on top of the stroke.\\n<https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty>\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Lengths\",\"input_description\":\"The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Offset\",\"input_description\":\"The phase offset distance from the starting point of the dash pattern.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[2225749123534781340,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12998832508553378533,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[1268775104597510914,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12331680982485935376,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[4398598693761352299,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3227544593834141716,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"X\",\"is_integer\":false,\"unit\":\" px\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\"x\",\"is_integer\":false,\"x\":\"W\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[429913874753911073,{\"persistent_metadata\":{\"display_name\":\"Stroke\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the stroke style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Color\",\"input_description\":\"The stroke color.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Weight\",\"input_description\":\"The stroke weight.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Align\",\"input_description\":\"The alignment of stroke to the path's centerline or (for closed shapes) the inside or outside of the shape.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Cap\",\"input_description\":\"The shape of the stroke at open endpoints.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Join\",\"input_description\":\"The curvature of the bent stroke at sharp corners.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Miter Limit\",\"input_description\":\"The threshold for when a miter-joined stroke is converted to a bevel-joined stroke when a sharp angle becomes pointier than this ratio.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Paint Order\",\"input_description\":\"The order to paint the stroke on top of the fill, or the fill on top of the stroke.\\n<https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty>\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Lengths\",\"input_description\":\"The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Offset\",\"input_description\":\"The phase offset distance from the starting point of the dash pattern.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[10564228200140683112,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\" px\",\"x\":\"X\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"x\":\"W\",\"y\":\"H\",\"unit\":\"x\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2292399603649738346,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[7799679303995308634,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"y\":\"Y\",\"x\":\"X\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\"x\",\"x\":\"W\",\"is_integer\":false,\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16727310898641763441,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[7340659059180155803,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-44,219]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[958845362613832240,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10810157408196882043,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11450962621506425680,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10917301734480569398,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"y\":\"Y\",\"x\":\"X\",\"unit\":\" px\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\"x\",\"x\":\"W\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7671691070850213967,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[13817976820605296433,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"X\",\"y\":\"Y\",\"unit\":\" px\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\"x\",\"y\":\"H\",\"x\":\"W\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[5017082804473894058,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-22,60]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11860177410232537211,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Operation\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Boolean Operation\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14341957170885045113,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11158238411769751544,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2088390810384907709,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-22,72]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13942146309185231085,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[13853529851208960143,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14483299526002574058,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2209276411833629008,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9393309733761233513,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4243146970185091100,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[6868877732348460627,{\"persistent_metadata\":{\"display_name\":\"Stroke\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the stroke style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Color\",\"input_description\":\"The stroke color.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Weight\",\"input_description\":\"The stroke weight.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Align\",\"input_description\":\"The alignment of stroke to the path's centerline or (for closed shapes) the inside or outside of the shape.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Cap\",\"input_description\":\"The shape of the stroke at open endpoints.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Join\",\"input_description\":\"The curvature of the bent stroke at sharp corners.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Miter Limit\",\"input_description\":\"The threshold for when a miter-joined stroke is converted to a bevel-joined stroke when a sharp angle becomes pointier than this ratio.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Paint Order\",\"input_description\":\"The order to paint the stroke on top of the fill, or the fill on top of the stroke.\\n<https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty>\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Lengths\",\"input_description\":\"The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Offset\",\"input_description\":\"The phase offset distance from the starting point of the dash pattern.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[13946577152348504742,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9115451226763736660,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[9278774434958175105,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"x\":\"X\",\"is_integer\":false,\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"H\",\"unit\":\"x\",\"is_integer\":false,\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[18085100003956405261,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[8958782938691501404,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2155997486525176376,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-22,6]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10181153433637856462,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"X\",\"y\":\"Y\",\"unit\":\" px\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"x\":\"W\",\"y\":\"H\",\"unit\":\"x\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12875121980058869686,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"y\":\"Y\",\"unit\":\" px\",\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\"x\",\"x\":\"W\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9392462024456293097,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12930243402848966353,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"Y\",\"x\":\"X\",\"is_integer\":false,\"unit\":\" px\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"y\":\"H\",\"is_integer\":false,\"unit\":\"x\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[18185020559178852986,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[5346759588580719138,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":6}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[532055960192543062,{\"persistent_metadata\":{\"display_name\":\"Stroke\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the stroke style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Color\",\"input_description\":\"The stroke color.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Weight\",\"input_description\":\"The stroke weight.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Align\",\"input_description\":\"The alignment of stroke to the path's centerline or (for closed shapes) the inside or outside of the shape.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Cap\",\"input_description\":\"The shape of the stroke at open endpoints.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Join\",\"input_description\":\"The curvature of the bent stroke at sharp corners.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Miter Limit\",\"input_description\":\"The threshold for when a miter-joined stroke is converted to a bevel-joined stroke when a sharp angle becomes pointier than this ratio.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Paint Order\",\"input_description\":\"The order to paint the stroke on top of the fill, or the fill on top of the stroke.\\n<https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty>\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Lengths\",\"input_description\":\"The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Offset\",\"input_description\":\"The phase offset distance from the starting point of the dash pattern.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[4191887059541031673,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[585709295659496998,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16847360882244487081,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[14124486712683868036,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[8166796652234334001,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[16591255610014418910,{\"persistent_metadata\":{\"display_name\":\"Stroke\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the stroke style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Color\",\"input_description\":\"The stroke color.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Weight\",\"input_description\":\"The stroke weight.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Align\",\"input_description\":\"The alignment of stroke to the path's centerline or (for closed shapes) the inside or outside of the shape.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Cap\",\"input_description\":\"The shape of the stroke at open endpoints.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Join\",\"input_description\":\"The curvature of the bent stroke at sharp corners.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Miter Limit\",\"input_description\":\"The threshold for when a miter-joined stroke is converted to a bevel-joined stroke when a sharp angle becomes pointier than this ratio.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Paint Order\",\"input_description\":\"The order to paint the stroke on top of the fill, or the fill on top of the stroke.\\n<https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty>\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Lengths\",\"input_description\":\"The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Offset\",\"input_description\":\"The phase offset distance from the starting point of the dash pattern.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[8463468388280418154,{\"persistent_metadata\":{\"display_name\":\"Opacity\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Opacity\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[11479492521093639512,{\"persistent_metadata\":{\"display_name\":\"Blend Mode\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Blend Mode\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[15347111149235590492,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\" px\",\"x\":\"X\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"unit\":\"x\",\"y\":\"H\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9323583246068171750,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[4101813853952238986,{\"persistent_metadata\":{\"display_name\":\"Stroke\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the stroke style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Color\",\"input_description\":\"The stroke color.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Weight\",\"input_description\":\"The stroke weight.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Align\",\"input_description\":\"The alignment of stroke to the path's centerline or (for closed shapes) the inside or outside of the shape.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Cap\",\"input_description\":\"The shape of the stroke at open endpoints.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Join\",\"input_description\":\"The curvature of the bent stroke at sharp corners.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Miter Limit\",\"input_description\":\"The threshold for when a miter-joined stroke is converted to a bevel-joined stroke when a sharp angle becomes pointier than this ratio.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Paint Order\",\"input_description\":\"The order to paint the stroke on top of the fill, or the fill on top of the stroke.\\n<https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty>\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Lengths\",\"input_description\":\"The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Offset\",\"input_description\":\"The phase offset distance from the starting point of the dash pattern.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[2791109467690716388,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15670426414376277308,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13001069903842109798,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9663740787529879916,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13442128106088307772,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[17638504852426495381,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[8256712316698018135,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11634802583144606404,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[194878846429432339,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10189927996178548902,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3806549994589872867,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-22,84]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[5670058004691708784,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[8073807569018624098,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14752203606937854133,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[1104068854328504126,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11990662272042254522,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[8242413775403456296,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14234384001010789008,{\"persistent_metadata\":{\"display_name\":\"Plant (Lower Left)\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":9}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[17533670083736420411,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[3455270778005546310,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14202574750104046500,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[18187802220803838247,{\"persistent_metadata\":{\"display_name\":\"Blend Mode\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Blend Mode\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[4187349759243468746,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11236872744106223256,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"is_integer\":false,\"y\":\"Y\",\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"is_integer\":false,\"unit\":\"x\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[5448146793323825465,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[972153153989181918,{\"persistent_metadata\":{\"display_name\":\"Sphere Shadow\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":6}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[8863202447825570192,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"is_integer\":false,\"x\":\"X\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"unit\":\"x\",\"y\":\"H\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13743495762122910279,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[3014633976566537110,{\"persistent_metadata\":{\"display_name\":\"Boolean Cut\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[6569279146800941123,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"x\":\"X\",\"unit\":\" px\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\"x\",\"y\":\"H\",\"is_integer\":false,\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11634445349252640936,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\" px\",\"y\":\"Y\",\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\"x\",\"is_integer\":false,\"x\":\"W\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3616319631707471648,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3670594928372882885,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9981992739451603109,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[2310170068575553369,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[15488533792651297821,{\"persistent_metadata\":{\"display_name\":\"Structure Reflection\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[5882319123081134737,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-35,189]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16510804133693080967,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"is_integer\":false,\"x\":\"X\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\"x\",\"x\":\"W\",\"is_integer\":false,\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11547499603328872398,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\" px\",\"x\":\"X\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"unit\":\"x\",\"is_integer\":false,\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15914878146223026034,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"X\",\"y\":\"Y\",\"is_integer\":false,\"unit\":\" px\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\"x\",\"is_integer\":false,\"y\":\"H\",\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13852123721901366011,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"Y\",\"x\":\"X\",\"unit\":\" px\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\"x\",\"x\":\"W\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9695624216919732577,{\"persistent_metadata\":{\"display_name\":\"Stroke\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the stroke style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Color\",\"input_description\":\"The stroke color.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Weight\",\"input_description\":\"The stroke weight.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Align\",\"input_description\":\"The alignment of stroke to the path's centerline or (for closed shapes) the inside or outside of the shape.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Cap\",\"input_description\":\"The shape of the stroke at open endpoints.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Join\",\"input_description\":\"The curvature of the bent stroke at sharp corners.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Miter Limit\",\"input_description\":\"The threshold for when a miter-joined stroke is converted to a bevel-joined stroke when a sharp angle becomes pointier than this ratio.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Paint Order\",\"input_description\":\"The order to paint the stroke on top of the fill, or the fill on top of the stroke.\\n<https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty>\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Lengths\",\"input_description\":\"The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Offset\",\"input_description\":\"The phase offset distance from the starting point of the dash pattern.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[1658032775659237960,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"X\",\"y\":\"Y\",\"is_integer\":false,\"unit\":\" px\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"H\",\"is_integer\":false,\"unit\":\"x\",\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[5891705401441266824,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[11506204916439878896,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"Y\",\"x\":\"X\",\"is_integer\":false,\"unit\":\" px\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\"x\",\"y\":\"H\",\"is_integer\":false,\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14161755104759532162,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"Y\",\"x\":\"X\",\"is_integer\":false,\"unit\":\" px\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"is_integer\":false,\"unit\":\"x\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11595529463602678384,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"Y\",\"x\":\"X\",\"is_integer\":false,\"unit\":\" px\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"H\",\"is_integer\":false,\"unit\":\"x\",\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[17887542695709892422,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-22,126]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14449710315388146362,{\"persistent_metadata\":{\"display_name\":\"Opacity\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Opacity\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[12469956387875933942,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15517065353723874205,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[5540780316862276409,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12302362769310895852,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[14842592386831797498,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16398743435291795904,{\"persistent_metadata\":{\"display_name\":\"Fountain Water\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":31}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4968550668755026811,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[509.0,-409.13666473966714],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,1500.0,171.0],\"node_graph_width\":1981.0},\"selection_undo_history\":[[],[],[],[],[],[15517065353723874205],[7340659059180155803],[7340659059180155803,13442128106088307772,7171713123860587892,18431382379595272672,10989897386232385465,10507084483235320484,9157963288496356916,10188337730058049439,1268775104597510914,13861850149743924125],[7340659059180155803,7171713123860587892,18431382379595272672,10989897386232385465,10507084483235320484,9157963288496356916,10188337730058049439,1268775104597510914,13861850149743924125,13442128106088307772],[7340659059180155803],[7340659059180155803,7171713123860587892,18431382379595272672,10989897386232385465,10507084483235320484,9157963288496356916,10188337730058049439,1268775104597510914,13861850149743924125,13442128106088307772],[16398743435291795904,4741515246389989284,14255588039347536657,5261200785298607501,2465823993152870948,15898396405528650339,16807867745126764195,18185020559178852986,4847316728405535983,11579925754926059876,15670426414376277308,5574499968250848265,5925268772265373737,4105329493214975815,12931264630175648107,11776939455674933130,9684750473849891261,70804263053697201,13942787566051910019],[7340659059180155803,7171713123860587892,18431382379595272672,10989897386232385465,10507084483235320484,9157963288496356916,10188337730058049439,1268775104597510914,13861850149743924125,13442128106088307772],[7340659059180155803],[7340659059180155803,7171713123860587892,18431382379595272672,10989897386232385465,10507084483235320484,9157963288496356916,10188337730058049439,1268775104597510914,13861850149743924125,13442128106088307772],[16398743435291795904,4741515246389989284,14255588039347536657,5261200785298607501,2465823993152870948,15898396405528650339,16807867745126764195,18185020559178852986,4847316728405535983,11579925754926059876,15670426414376277308,5574499968250848265,5925268772265373737,4105329493214975815,12931264630175648107,11776939455674933130,9684750473849891261,70804263053697201,13942787566051910019],[7340659059180155803,7171713123860587892,18431382379595272672,10989897386232385465,10507084483235320484,9157963288496356916,10188337730058049439,1268775104597510914,13861850149743924125,13442128106088307772],[7340659059180155803,13531127678140037818,3970872207068447290,2077983679740571162,7171713123860587892,9798215931018813676,10779665858841986661,18431382379595272672,4191887059541031673,4046495708656778502,10989897386232385465,7339104629465306715,16229837691656808412,10507084483235320484,1396768435017101055,15914878146223026034,4069478660487729695,9157963288496356916,1108089904278882840,5317925967883407701,13853529851208960143,10188337730058049439,11076863066321508991,8891726805381758817,17332567356044944766,1268775104597510914,18011777376689315137,10564228200140683112,13001069903842109798,13861850149743924125,5891705401441266824,5670058004691708784],[7340659059180155803,7171713123860587892,18431382379595272672,10989897386232385465,10507084483235320484,9157963288496356916,10188337730058049439,1268775104597510914,13861850149743924125,13442128106088307772],[13442128106088307772],[7340659059180155803,7171713123860587892,18431382379595272672,10989897386232385465,10507084483235320484,9157963288496356916,10188337730058049439,1268775104597510914,13861850149743924125,13442128106088307772],[7340659059180155803,13531127678140037818,3970872207068447290,2077983679740571162,7171713123860587892,9798215931018813676,10779665858841986661,18431382379595272672,4191887059541031673,4046495708656778502,10989897386232385465,7339104629465306715,16229837691656808412,10507084483235320484,1396768435017101055,15914878146223026034,4069478660487729695,9157963288496356916,1108089904278882840,5317925967883407701,13853529851208960143,10188337730058049439,11076863066321508991,8891726805381758817,17332567356044944766,1268775104597510914,18011777376689315137,10564228200140683112,13001069903842109798],[7340659059180155803,7171713123860587892,18431382379595272672,10989897386232385465,10507084483235320484,9157963288496356916,10188337730058049439,1268775104597510914,13861850149743924125,13442128106088307772],[13861850149743924125],[7340659059180155803,7171713123860587892,18431382379595272672,10989897386232385465,10507084483235320484,9157963288496356916,10188337730058049439,1268775104597510914,13861850149743924125,13442128106088307772],[7340659059180155803,13531127678140037818,3970872207068447290,2077983679740571162,7171713123860587892,9798215931018813676,10779665858841986661,18431382379595272672,4191887059541031673,4046495708656778502,10989897386232385465,7339104629465306715,16229837691656808412,10507084483235320484,1396768435017101055,15914878146223026034,4069478660487729695,9157963288496356916,1108089904278882840,5317925967883407701,13853529851208960143,10188337730058049439,11076863066321508991,8891726805381758817,17332567356044944766],[7340659059180155803,7171713123860587892,18431382379595272672,10989897386232385465,10507084483235320484,9157963288496356916,10188337730058049439,1268775104597510914,13861850149743924125,13442128106088307772],[1268775104597510914],[7340659059180155803,7171713123860587892,18431382379595272672,10989897386232385465,10507084483235320484,9157963288496356916,10188337730058049439,1268775104597510914,13861850149743924125,13442128106088307772],[7340659059180155803,13531127678140037818,3970872207068447290,2077983679740571162,7171713123860587892,9798215931018813676,10779665858841986661,18431382379595272672,4191887059541031673,4046495708656778502,10989897386232385465,7339104629465306715,16229837691656808412,10507084483235320484,1396768435017101055,15914878146223026034,4069478660487729695,9157963288496356916,1108089904278882840,5317925967883407701,13853529851208960143],[7340659059180155803,7171713123860587892,18431382379595272672,10989897386232385465,10507084483235320484,9157963288496356916,10188337730058049439,1268775104597510914,13861850149743924125,13442128106088307772],[10188337730058049439],[7340659059180155803,7171713123860587892,18431382379595272672,10989897386232385465,10507084483235320484,9157963288496356916,10188337730058049439,1268775104597510914,13861850149743924125,13442128106088307772],[7340659059180155803,13531127678140037818,3970872207068447290,2077983679740571162,7171713123860587892,9798215931018813676,10779665858841986661,18431382379595272672,4191887059541031673,4046495708656778502,10989897386232385465,7339104629465306715,16229837691656808412,10507084483235320484,1396768435017101055,15914878146223026034,4069478660487729695],[7340659059180155803,7171713123860587892,18431382379595272672,10989897386232385465,10507084483235320484,9157963288496356916,10188337730058049439,1268775104597510914,13861850149743924125,13442128106088307772],[9157963288496356916],[7340659059180155803,7171713123860587892,18431382379595272672,10989897386232385465,10507084483235320484,9157963288496356916,10188337730058049439,1268775104597510914,13861850149743924125,13442128106088307772],[7340659059180155803,13531127678140037818,3970872207068447290,2077983679740571162,7171713123860587892,9798215931018813676,10779665858841986661,18431382379595272672,4191887059541031673,4046495708656778502,10989897386232385465,7339104629465306715,16229837691656808412],[7340659059180155803,7171713123860587892,18431382379595272672,10989897386232385465,10507084483235320484,9157963288496356916,10188337730058049439,1268775104597510914,13861850149743924125,13442128106088307772],[10507084483235320484],[7340659059180155803,7171713123860587892,18431382379595272672,10989897386232385465,10507084483235320484,9157963288496356916,10188337730058049439,1268775104597510914,13861850149743924125,13442128106088307772],[7340659059180155803,13531127678140037818,3970872207068447290,2077983679740571162,7171713123860587892,9798215931018813676,10779665858841986661,18431382379595272672,4191887059541031673,4046495708656778502],[7340659059180155803,7171713123860587892,18431382379595272672,10989897386232385465,10507084483235320484,9157963288496356916,10188337730058049439,1268775104597510914,13861850149743924125,13442128106088307772],[10989897386232385465],[7340659059180155803,7171713123860587892,18431382379595272672,10989897386232385465,10507084483235320484,9157963288496356916,10188337730058049439,1268775104597510914,13861850149743924125,13442128106088307772],[7340659059180155803,13531127678140037818,3970872207068447290,2077983679740571162,7171713123860587892,9798215931018813676,10779665858841986661],[7340659059180155803,7171713123860587892,18431382379595272672,10989897386232385465,10507084483235320484,9157963288496356916,10188337730058049439,1268775104597510914,13861850149743924125,13442128106088307772],[18431382379595272672],[7340659059180155803,7171713123860587892,18431382379595272672,10989897386232385465,10507084483235320484,9157963288496356916,10188337730058049439,1268775104597510914,13861850149743924125,13442128106088307772],[7340659059180155803,13531127678140037818,3970872207068447290,2077983679740571162],[7340659059180155803,7171713123860587892,18431382379595272672,10989897386232385465,10507084483235320484,9157963288496356916,10188337730058049439,1268775104597510914,13861850149743924125,13442128106088307772],[7171713123860587892],[7340659059180155803,7171713123860587892,18431382379595272672,10989897386232385465,10507084483235320484,9157963288496356916,10188337730058049439,1268775104597510914,13861850149743924125,13442128106088307772],[2032185045476767535],[11776939455674933130],[9724746185253267560,6142412830271644616,17250040304106119844,11236872744106223256,3185536512640676801],[11776939455674933130,5574499968250848265],[11776939455674933130,16398743435291795904,5574499968250848265],[16398743435291795904],[]],\"selection_redo_history\":[]}}},\"collapsed\":[7838724497953148310,9771562518763748678,14234384001010789009,4757672276235057646,4924169570021915607,15488533792651297822,16398743435291795905,972153153989181919,14019233912018234741,5574499968250848266,11776939455674933131,2032185045476767536,16069762220015310718],\"commit_hash\":\"8d83fa707928a1c54fe10224695a0c4791ab3501\",\"document_ptz\":{\"pan\":[-666.8230997694343,-319.94980900906876],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"render_mode\":\"Normal\",\"overlays_visibility_settings\":{\"all\":true,\"artboard_name\":true,\"compass_rose\":true,\"quick_measurement\":true,\"transform_measurement\":true,\"transform_cage\":true,\"hover_outline\":true,\"selection_outline\":true,\"layer_origin_cross\":true,\"pivot\":true,\"origin\":true,\"path\":true,\"anchors\":true,\"handles\":true},\"rulers_visible\":true,\"snapping_state\":{\"snapping_enabled\":true,\"grid_snapping\":false,\"artboards\":true,\"tolerance\":8.0,\"bounding_box\":{\"center_point\":true,\"corner_point\":true,\"edge_midpoint\":true,\"align_with_edges\":true,\"distribute_evenly\":true},\"path\":{\"anchor_point\":true,\"line_midpoint\":true,\"along_path\":true,\"normal_to_path\":true,\"tangent_to_path\":true,\"path_intersection_point\":true,\"align_with_anchor_point\":true,\"perpendicular_from_endpoint\":true},\"grid\":{\"origin\":[0.0,0.0],\"grid_type\":{\"Isometric\":{\"y_axis_spacing\":20.0,\"angle_a\":15.0,\"angle_b\":15.0}},\"rectangular_spacing\":[1.0,1.0],\"isometric_y_spacing\":1.0,\"isometric_angle_a\":30.0,\"isometric_angle_b\":30.0,\"grid_color\":{\"red\":0.60784316,\"green\":0.60784316,\"blue\":0.60784316,\"alpha\":0.25},\"dot_display\":false}},\"graph_view_overlay_open\":false,\"graph_fade_artwork_percentage\":80.0}"
  },
  {
    "path": "demo-artwork/marbled-mandelbrot.graphite",
    "content": "{\"network_interface\":{\"network\":{\"exports\":[{\"Node\":{\"node_id\":12241147352993594415,\"output_index\":0}}],\"nodes\":[[4388711862172196665,{\"inputs\":[],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"raster_nodes::std_nodes::MandelbrotNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"FOOTPRINT\",\"inject\":\"\"}}],[3606681156406984991,{\"inputs\":[{\"Node\":{\"node_id\":6323350524796370485,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"GradientTable\":{\"element\":[{\"position\":[0.0,0.3237704918032787,0.5655737704918032,0.8155737704918032,1.0],\"midpoint\":[0.5,0.5,0.5,0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":0.23828125,\"green\":0.0,\"blue\":0.08377075,\"alpha\":1.0},{\"red\":0.92578125,\"green\":0.8676921,\"blue\":0.5569153,\"alpha\":1.0},{\"red\":0.17333984,\"green\":0.625,\"blue\":0.625,\"alpha\":1.0},{\"red\":0.106292725,\"green\":0.3359375,\"blue\":0.3359375,\"alpha\":1.0}]}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"raster_nodes::gradient_map::GradientMapNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6029481207635803402,{\"inputs\":[{\"Node\":{\"node_id\":4388711862172196665,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1000.0,1000.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7624113397561636853,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":3606681156406984991,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[80924370013313595,{\"inputs\":[{\"Node\":{\"node_id\":6029481207635803402,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"GradientTable\":{\"element\":[{\"position\":[0.0,0.12704918032786883,0.5,1.0],\"midpoint\":[0.5,0.5,0.5,0.5],\"color\":[{\"red\":0.28211805,\"green\":0.0,\"blue\":0.3046875,\"alpha\":1.0},{\"red\":0.13647461,\"green\":0.3989315,\"blue\":0.8125,\"alpha\":1.0},{\"red\":1.0,\"green\":0.9409449,\"blue\":0.625,\"alpha\":1.0},{\"red\":0.58431375,\"green\":0.92941177,\"blue\":0.92941177,\"alpha\":0.01}]}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"raster_nodes::gradient_map::GradientMapNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6323350524796370485,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":35.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"NoiseType\":\"OpenSimplex2\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DomainWarpType\":\"OpenSimplex2\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":100.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"FractalType\":\"PingPong\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":3},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.5},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"CellularDistanceFunction\":\"Hybrid\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"CellularReturnType\":\"CellValue\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"raster_nodes::std_nodes::NoisePatternNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6565638614909771142,{\"inputs\":[{\"Node\":{\"node_id\":7624113397561636853,\"output_index\":0}},{\"Node\":{\"node_id\":80924370013313595,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12241147352993594415,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Artboard\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":6565638614909771142,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1000.0,1000.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":1}},{\"Value\":{\"tagged_value\":{\"String\":\"Artboard\"},\"exposed\":false}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":5}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::artboard::CreateArtboardNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Import\":{\"import_type\":{\"Fn\":[{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::artboard::Artboard>\",\"alias\":null}}]},\"import_index\":0}},{\"Node\":{\"node_id\":2,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[7624113397561636853,{\"persistent_metadata\":{\"display_name\":\"Swirly Noise\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[6323350524796370485,{\"persistent_metadata\":{\"display_name\":\"Noise Pattern\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacer\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Clip\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Seed\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"noise_properties_scale\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"noise_properties_noise_type\",\"input_name\":\"Noise Type\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"noise_properties_domain_warp_type\",\"input_name\":\"Domain Warp Type\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"noise_properties_domain_warp_amplitude\",\"input_name\":\"Domain Warp Amplitude\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"noise_properties_fractal_type\",\"input_name\":\"Fractal Type\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"noise_properties_fractal_octaves\",\"input_name\":\"Fractal Octaves\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"noise_properties_fractal_lacunarity\",\"input_name\":\"Fractal Lacunarity\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"noise_properties_fractal_gain\",\"input_name\":\"Fractal Gain\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"noise_properties_fractal_weighted_strength\",\"input_name\":\"Fractal Weighted Strength\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"noise_properties_ping_pong_strength\",\"input_name\":\"Fractal Ping Pong Strength\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"noise_properties_cellular_distance_function\",\"input_name\":\"Cellular Distance Function\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"noise_properties_cellular_return_type\",\"input_name\":\"Cellular Return Type\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"noise_properties_cellular_jitter\",\"input_name\":\"Cellular Jitter\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Image\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[4388711862172196665,{\"persistent_metadata\":{\"display_name\":\"Mandelbrot\",\"input_metadata\":[],\"output_names\":[\"Raster\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[12241147352993594415,{\"persistent_metadata\":{\"display_name\":\"Artboard\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Artboards\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Contents\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":true,\"x\":\"X\",\"unit\":\" px\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Location\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"y\":\"H\",\"unit\":\" px\",\"is_integer\":true},\"widget_override\":\"vec2\",\"input_name\":\"Dimensions\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"artboard_background\",\"input_name\":\"Background\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Clip\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-8,3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Artboard\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-4]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Create Artboard\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[6029481207635803402,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"Y\",\"x\":\"X\",\"is_integer\":false,\"unit\":\" px\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"H\",\"x\":\"W\",\"is_integer\":false,\"unit\":\"x\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"Data\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3606681156406984991,{\"persistent_metadata\":{\"display_name\":\"Gradient Map\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Image\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Gradient\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Reverse\",\"input_description\":\"\"}}],\"output_names\":[\"Image\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[80924370013313595,{\"persistent_metadata\":{\"display_name\":\"Gradient Map\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Image\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Gradient\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Reverse\",\"input_description\":\"\"}}],\"output_names\":[\"Image\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[6565638614909771142,{\"persistent_metadata\":{\"display_name\":\"Mandelbrot Set\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-16,6]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[345.0,-187.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,1336.0,394.0],\"node_graph_width\":1981.0},\"selection_undo_history\":[[6565638614909771142],[7624113397561636853],[6565638614909771142],[7624113397561636853],[6565638614909771142],[]],\"selection_redo_history\":[]}}},\"collapsed\":[],\"commit_hash\":\"f6ffa45a8180183d70a67d3e41249934a8fcacc9\",\"document_ptz\":{\"pan\":[-339.3903349049215,-502.62390663267854],\"tilt\":0.0,\"zoom\":4.0,\"flip\":false},\"render_mode\":\"Normal\",\"overlays_visibility_settings\":{\"all\":true,\"artboard_name\":true,\"compass_rose\":true,\"quick_measurement\":true,\"transform_measurement\":true,\"transform_cage\":true,\"hover_outline\":true,\"selection_outline\":true,\"layer_origin_cross\":true,\"pivot\":true,\"origin\":true,\"path\":true,\"anchors\":true,\"handles\":true},\"rulers_visible\":true,\"snapping_state\":{\"snapping_enabled\":true,\"grid_snapping\":false,\"artboards\":true,\"tolerance\":8.0,\"bounding_box\":{\"center_point\":true,\"corner_point\":true,\"edge_midpoint\":true,\"align_with_edges\":true,\"distribute_evenly\":true},\"path\":{\"anchor_point\":true,\"line_midpoint\":true,\"along_path\":true,\"normal_to_path\":true,\"tangent_to_path\":true,\"path_intersection_point\":true,\"align_with_anchor_point\":true,\"perpendicular_from_endpoint\":true},\"grid\":{\"origin\":[0.0,0.0],\"grid_type\":{\"Rectangular\":{\"spacing\":[1.0,1.0]}},\"rectangular_spacing\":[1.0,1.0],\"isometric_y_spacing\":1.0,\"isometric_angle_a\":30.0,\"isometric_angle_b\":30.0,\"grid_color\":{\"red\":0.6038274,\"green\":0.6038274,\"blue\":0.6038274,\"alpha\":1.0},\"dot_display\":false}},\"graph_view_overlay_open\":false,\"graph_fade_artwork_percentage\":80.0}"
  },
  {
    "path": "demo-artwork/painted-dreams.graphite",
    "content": "{\"network_interface\":{\"network\":{\"exports\":[{\"Node\":{\"node_id\":392274448837115448,\"output_index\":0}}],\"nodes\":[[13353438235848911576,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[6379137305818664393,18442321214082298093,18118267825025549699,6351696498298648253,17863574903157697896,4691860614575868200,301778328144628917,10625296323957562985,533731991408535384,13357220652574593654,15992617814592812350],\"remove\":[],\"delta\":[[18442321214082298093,[565.6296296296297,122.96296296296298]],[13357220652574593654,[574.2222222222222,108.44444444444449]],[6351696498298648253,[646.8148148148149,0.0]],[6379137305818664393,[625.4814814814813,0.0]],[17863574903157697896,[606.8148151308641,95.19407372365433]],[18118267825025549699,[642.0740740740741,0.0]],[10625296323957562985,[473.4814814814815,272.5925925925926]],[533731991408535384,[501.6296296296296,175.40740740740742]],[4691860614575868200,[528.2962962962963,283.8518518518518]],[15992617814592812350,[621.0370370370368,0.0]],[301778328144628917,[493.5149940383244,200.29629629629628]]]},\"segments\":{\"add\":[13662059715350867364,3384102572540409881,12035095010819582754,10146665273368938971,318560030360358929,8462655819889292900,3303411878775644985,8661113083384527971,2035327810332855521,11346538709718766144,3710133387837274291],\"remove\":[],\"start_point\":[[3303411878775644985,301778328144628917],[13662059715350867364,6379137305818664393],[318560030360358929,17863574903157697896],[3384102572540409881,18442321214082298093],[12035095010819582754,18118267825025549699],[10146665273368938971,6351696498298648253],[11346538709718766144,13357220652574593654],[8462655819889292900,4691860614575868200],[8661113083384527971,10625296323957562985],[3710133387837274291,15992617814592812350],[2035327810332855521,533731991408535384]],\"end_point\":[[2035327810332855521,13357220652574593654],[12035095010819582754,6351696498298648253],[10146665273368938971,17863574903157697896],[318560030360358929,4691860614575868200],[11346538709718766144,15992617814592812350],[3303411878775644985,10625296323957562985],[3710133387837274291,6379137305818664393],[8462655819889292900,301778328144628917],[13662059715350867364,18442321214082298093],[8661113083384527971,533731991408535384],[3384102572540409881,18118267825025549699]],\"handle_primary\":[[8661113083384527971,[0.2962962962963047,0.2962962962963047]],[3710133387837274291,[0.0,0.0]],[10146665273368938971,[0.0,0.0]],[3303411878775644985,[0.0,0.0]],[13662059715350867364,[0.0,0.0]],[12035095010819582754,[0.0,0.0]],[11346538709718766144,[9.925839724176626,-9.951533978271286]],[8462655819889292900,[0.0,0.0]],[2035327810332855521,[42.96296296296293,-41.7777777777778]],[3384102572540409881,[0.0,0.0]],[318560030360358929,[-46.95465552079859,46.40007297527743]]],\"handle_end\":[[3384102572540409881,[0.5807410754567854,77.36888852918523]],[13662059715350867364,[68.68148181185177,-54.8859262904691]],[3710133387837274291,[0.0,0.0]],[11346538709718766144,[1.1377781018271662,63.71555518874077]],[8661113083384527971,[-42.34888386913234,41.180638796880466]],[2035327810332855521,[-8.299214014278164,8.32069754812484]],[12035095010819582754,[0.0,0.0]],[318560030360358929,[-97.48148148148152,-81.18518518518522]],[10146665273368938971,[48.16592595101224,-47.597037061827145]],[3303411878775644985,[-6.2222222222222285,-40.59259259259261]],[8462655819889292900,[-21.74426522093495,47.99999999999997]]],\"stroke\":[[3303411878775644985,0],[8661113083384527971,0],[12035095010819582754,0],[2035327810332855521,0],[3710133387837274291,0],[8462655819889292900,0],[3384102572540409881,0],[318560030360358929,0],[11346538709718766144,0],[10146665273368938971,0],[13662059715350867364,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[[{\"ty\":\"End\",\"segment\":8661113083384527971},{\"ty\":\"Primary\",\"segment\":2035327810332855521}],[{\"ty\":\"End\",\"segment\":10146665273368938971},{\"ty\":\"Primary\",\"segment\":318560030360358929}],[{\"ty\":\"Primary\",\"segment\":11346538709718766144},{\"ty\":\"End\",\"segment\":2035327810332855521}],[{\"ty\":\"End\",\"segment\":12035095010819582754},{\"ty\":\"Primary\",\"segment\":10146665273368938971}]],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16671141883125519098,{\"inputs\":[{\"Node\":{\"node_id\":16306737306999003555,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.92156863,\"green\":0.6039216,\"blue\":0.18039216,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.101960786,\"green\":0.13333334,\"blue\":0.13725491,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6445954214067437701,{\"inputs\":[{\"Node\":{\"node_id\":13670206802546093234,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"BooleanOperation\":\"SubtractFront\"},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::style::Fill\",\"alias\":null}},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"path_bool_nodes::BooleanOperationNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[140396870212231820,{\"inputs\":[{\"Node\":{\"node_id\":18095952297474762348,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"BooleanOperation\":\"Intersect\"},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::style::Fill\",\"alias\":null}},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"path_bool_nodes::BooleanOperationNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5449860184735415958,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[17520941196305861319,6029995238423674441,16590926169549948521,12817219955590128894,4723170318056755559,12317789037775437786,17339925811006567497,4659274885664969740,1541401134848201833,17321098837874422287,11084016020553554647,9729762716079513741,13279221198527484331,7727030009292816503,15701538241214188894],\"remove\":[],\"delta\":[[15701538241214188894,[0.0,0.0]],[17339925811006567497,[898.0,468.0]],[4659274885664969740,[896.0,523.0]],[6029995238423674441,[898.0,46.0]],[12317789037775437786,[902.0,394.0]],[11084016020553554647,[655.0,636.0]],[7727030009292816503,[0.0,768.0]],[17321098837874422287,[802.6666666666667,656.4444444444445]],[4723170318056755559,[923.0,349.0]],[13279221198527484331,[564.0,768.0]],[1541401134848201833,[886.0,572.0]],[17520941196305861319,[889.0,0.0]],[16590926169549948521,[869.0,147.0]],[12817219955590128894,[933.0,273.0]],[9729762716079513741,[585.0,666.0]]]},\"segments\":{\"add\":[862900208337901235,4806484166818510930,11569179934425192262,9808624553037921371,9730229894941201870,1248401493076165062,8832757535144158913,15056691877609602335,8919277736361106420,16631919016540861133,9314952320711038398,10137845066833192587,14740183693208469558,3307452201570141575,3876401232874291775],\"remove\":[],\"start_point\":[[10137845066833192587,9729762716079513741],[8832757535144158913,17339925811006567497],[16631919016540861133,17321098837874422287],[14740183693208469558,13279221198527484331],[15056691877609602335,4659274885664969740],[9808624553037921371,12817219955590128894],[3876401232874291775,15701538241214188894],[9314952320711038398,11084016020553554647],[11569179934425192262,16590926169549948521],[3307452201570141575,7727030009292816503],[4806484166818510930,6029995238423674441],[1248401493076165062,12317789037775437786],[9730229894941201870,4723170318056755559],[8919277736361106420,1541401134848201833],[862900208337901235,17520941196305861319]],\"end_point\":[[9730229894941201870,12317789037775437786],[15056691877609602335,1541401134848201833],[3307452201570141575,15701538241214188894],[9314952320711038398,9729762716079513741],[8919277736361106420,17321098837874422287],[9808624553037921371,4723170318056755559],[11569179934425192262,12817219955590128894],[16631919016540861133,11084016020553554647],[4806484166818510930,16590926169549948521],[862900208337901235,6029995238423674441],[10137845066833192587,13279221198527484331],[8832757535144158913,4659274885664969740],[1248401493076165062,17339925811006567497],[3876401232874291775,17520941196305861319],[14740183693208469558,7727030009292816503]],\"handle_primary\":[[1248401493076165062,[21.0,27.0]],[9808624553037921371,[18.0,36.0]],[9730229894941201870,[-24.00371914417652,11.366922485048674]],[862900208337901235,[0.0,0.0]],[4806484166818510930,[11.595886737767424,32.210796493798]],[16631919016540861133,[-63.996421719362885,-1.2074796550824587]],[10137845066833192587,[-19.0,43.0]],[15056691877609602335,[0.0,0.0]],[3307452201570141575,[0.0,0.0]],[8919277736361106420,[10.458809984597837,40.44073194044492]],[3876401232874291775,[0.0,0.0]],[9314952320711038398,[-34.0,-7.0]],[8832757535144158913,[14.888888888888914,4.8888888888888005]],[14740183693208469558,[0.0,0.0]],[11569179934425192262,[-4.0,51.0]]],\"handle_end\":[[4806484166818510930,[3.365858386031487,-42.91469442190146]],[3307452201570141575,[0.0,0.0]],[1248401493076165062,[30.59259259259261,-36.59259259259255]],[11569179934425192262,[-18.0,-36.0]],[10137845066833192587,[0.0,0.0]],[16631919016540861133,[34.0,7.0]],[8832757535144158913,[33.33333333333326,-9.666666666666742]],[862900208337901235,[-9.000000000000114,-25.0]],[3876401232874291775,[0.0,0.0]],[9808624553037921371,[28.703703703703695,-13.592592592592496]],[9730229894941201870,[-21.0,-27.0]],[8919277736361106420,[70.66666666666652,1.333333333333485]],[14740183693208469558,[0.0,0.0]],[9314952320711038398,[19.0,-43.0]],[15056691877609602335,[-10.0,-38.66666666666663]]],\"stroke\":[[15056691877609602335,0],[9730229894941201870,0],[9808624553037921371,0],[14740183693208469558,0],[4806484166818510930,0],[10137845066833192587,0],[3307452201570141575,0],[16631919016540861133,0],[9314952320711038398,0],[862900208337901235,0],[8919277736361106420,0],[3876401232874291775,0],[11569179934425192262,0],[1248401493076165062,0],[8832757535144158913,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[[{\"ty\":\"End\",\"segment\":3307452201570141575},{\"ty\":\"Primary\",\"segment\":3876401232874291775}],[{\"ty\":\"End\",\"segment\":8919277736361106420},{\"ty\":\"Primary\",\"segment\":16631919016540861133}],[{\"ty\":\"End\",\"segment\":9314952320711038398},{\"ty\":\"Primary\",\"segment\":10137845066833192587}],[{\"ty\":\"End\",\"segment\":4806484166818510930},{\"ty\":\"Primary\",\"segment\":11569179934425192262}],[{\"ty\":\"End\",\"segment\":11569179934425192262},{\"ty\":\"Primary\",\"segment\":9808624553037921371}],[{\"ty\":\"End\",\"segment\":10137845066833192587},{\"ty\":\"Primary\",\"segment\":14740183693208469558}],[{\"ty\":\"End\",\"segment\":15056691877609602335},{\"ty\":\"Primary\",\"segment\":8919277736361106420}],[{\"ty\":\"End\",\"segment\":9808624553037921371},{\"ty\":\"Primary\",\"segment\":9730229894941201870}],[{\"ty\":\"End\",\"segment\":9730229894941201870},{\"ty\":\"Primary\",\"segment\":1248401493076165062}],[{\"ty\":\"End\",\"segment\":862900208337901235},{\"ty\":\"Primary\",\"segment\":4806484166818510930}],[{\"ty\":\"End\",\"segment\":16631919016540861133},{\"ty\":\"Primary\",\"segment\":9314952320711038398}],[{\"ty\":\"End\",\"segment\":14740183693208469558},{\"ty\":\"Primary\",\"segment\":3307452201570141575}]],\"remove_g1_continuous\":[[{\"ty\":\"Primary\",\"segment\":8832757535144158913},{\"ty\":\"End\",\"segment\":1248401493076165062}]]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12709602171929957216,{\"inputs\":[{\"Node\":{\"node_id\":17753909951719808506,\"output_index\":0}},{\"Node\":{\"node_id\":12922148192688274227,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16609137733952262762,{\"inputs\":[{\"Node\":{\"node_id\":17397123104674848450,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.3137255,\"green\":0.44705883,\"blue\":0.45490196,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.3137255,\"green\":0.44705883,\"blue\":0.45490196,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7855094781869605606,{\"inputs\":[{\"Node\":{\"node_id\":14141479077115894852,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"BooleanOperation\":\"SubtractBack\"},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::style::Fill\",\"alias\":null}},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"path_bool_nodes::BooleanOperationNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8460565235419043665,{\"inputs\":[{\"Node\":{\"node_id\":3079923906392020295,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,412.1]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":27.6},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":8},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"repeat_nodes::repeat_nodes::RepeatArrayNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6194305264313730032,{\"inputs\":[{\"Node\":{\"node_id\":4560146526699152877,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"BooleanOperation\":\"Intersect\"},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::style::Fill\",\"alias\":null}},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"path_bool_nodes::BooleanOperationNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14950060858756810933,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[9346093213164033195,3249248431686392864,9577388580635291078],\"remove\":[],\"delta\":[[3249248431686392864,[1536.0,95.0]],[9346093213164033195,[1211.0,0.0]],[9577388580635291078,[1536.0,0.0]]]},\"segments\":{\"add\":[9585086796709645600,13980191878100735848,1147165232046305110],\"remove\":[],\"start_point\":[[13980191878100735848,3249248431686392864],[9585086796709645600,9346093213164033195],[1147165232046305110,9577388580635291078]],\"end_point\":[[9585086796709645600,3249248431686392864],[13980191878100735848,9577388580635291078],[1147165232046305110,9346093213164033195]],\"handle_primary\":[[1147165232046305110,[0.0,0.0]],[13980191878100735848,[4.547473508864641e-13,2.8421709430404014e-14]],[9585086796709645600,[0.0,0.0]]],\"handle_end\":[[13980191878100735848,[0.0,0.0]],[1147165232046305110,[0.0,0.0]],[9585086796709645600,[-281.0,-17.00000000000003]]],\"stroke\":[[13980191878100735848,0],[9585086796709645600,0],[1147165232046305110,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[[{\"ty\":\"End\",\"segment\":13980191878100735848},{\"ty\":\"Primary\",\"segment\":1147165232046305110}]],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14698962747138962125,{\"inputs\":[{\"Node\":{\"node_id\":17562801632450633291,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"BooleanOperation\":\"Intersect\"},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::style::Fill\",\"alias\":null}},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"path_bool_nodes::BooleanOperationNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[18031616785650843168,{\"inputs\":[{\"Node\":{\"node_id\":17952673493105230490,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SolidifyStrokeNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11196821089257149774,{\"inputs\":[{\"Node\":{\"node_id\":5888633415105234509,\"output_index\":0}},{\"Node\":{\"node_id\":18031616785650843168,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8359580532088731394,{\"inputs\":[{\"Node\":{\"node_id\":6726954210929537972,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.101960786,\"green\":0.13333334,\"blue\":0.13725491,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8269257328703012432,{\"inputs\":[{\"Node\":{\"node_id\":4102754869474520966,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.92156863,\"green\":0.6039216,\"blue\":0.18039216,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.92156863,\"green\":0.6039216,\"blue\":0.18039216,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13203761224559198689,{\"inputs\":[{\"Node\":{\"node_id\":4809200889774783438,\"output_index\":0}},{\"Node\":{\"node_id\":14690269209726153565,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13670206802546093234,{\"inputs\":[{\"Node\":{\"node_id\":17285637344898461972,\"output_index\":0}},{\"Node\":{\"node_id\":7855094781869605606,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7201841978411396053,{\"inputs\":[{\"Node\":{\"node_id\":4328376070224119511,\"output_index\":0}},{\"Node\":{\"node_id\":17735408893002232096,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11429506195623419966,{\"inputs\":[{\"Node\":{\"node_id\":11301831865756336526,\"output_index\":0}},{\"Node\":{\"node_id\":5449860184735415958,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9071802450034150503,{\"inputs\":[{\"Node\":{\"node_id\":9338394475379815879,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10063704933309776584,{\"inputs\":[{\"Node\":{\"node_id\":13838011362258067867,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":-1.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::MultiplyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3608604157153838227,{\"inputs\":[{\"Node\":{\"node_id\":17299978721726771610,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::BoundingBoxNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12185047359007423618,{\"inputs\":[{\"Node\":{\"node_id\":10080296672372912698,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.3137255,\"green\":0.44705883,\"blue\":0.45490196,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.92156863,\"green\":0.6039216,\"blue\":0.18039216,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7018444885869143173,{\"inputs\":[{\"Node\":{\"node_id\":12234961922142600898,\"output_index\":0}},{\"Node\":{\"node_id\":8426490990601560741,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1001728975241745659,{\"inputs\":[{\"Node\":{\"node_id\":12770183061753030023,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"BooleanOperation\":\"Intersect\"},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::style::Fill\",\"alias\":null}},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"path_bool_nodes::BooleanOperationNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[392274448837115448,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Artboard\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":6787585796949551500,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1536.0,768.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.93333334,\"green\":0.8627451,\"blue\":0.7254902,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":1}},{\"Value\":{\"tagged_value\":{\"String\":\"Artboard\"},\"exposed\":false}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":5}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::artboard::CreateArtboardNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Import\":{\"import_type\":{\"Fn\":[{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::artboard::Artboard>\",\"alias\":null}}]},\"import_index\":0}},{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17562801632450633291,{\"inputs\":[{\"Node\":{\"node_id\":10286817149456341619,\"output_index\":0}},{\"Node\":{\"node_id\":5449860184735415958,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3750439930725791025,{\"inputs\":[{\"Node\":{\"node_id\":12276520439585231336,\"output_index\":0}},{\"Node\":{\"node_id\":14228923746783465609,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[682567808439406093,{\"inputs\":[{\"Node\":{\"node_id\":7755499790391969923,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[300.0,50.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":15},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"repeat_nodes::repeat_nodes::RepeatArrayNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17740294143355019755,{\"inputs\":[{\"Node\":{\"node_id\":15301503532602557206,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.101960786,\"green\":0.13333334,\"blue\":0.13725491,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.101960786,\"green\":0.13333334,\"blue\":0.13725491,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16759836951269190891,{\"inputs\":[{\"Node\":{\"node_id\":11429506195623419966,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"BooleanOperation\":\"Intersect\"},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::style::Fill\",\"alias\":null}},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"path_bool_nodes::BooleanOperationNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17495267820524300686,{\"inputs\":[{\"Node\":{\"node_id\":13616602029989984195,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1248.4973005620557,-153.1867628889006]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":17.033756191102253},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0000000000000002,1.0000000000000002]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[3.180554681463516e-15,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12234961922142600898,{\"inputs\":[{\"Node\":{\"node_id\":1627123781166851142,\"output_index\":0}},{\"Node\":{\"node_id\":18351415092709164412,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11095670964487764044,{\"inputs\":[{\"Node\":{\"node_id\":17603523494627491590,\"output_index\":0}},{\"Node\":{\"node_id\":8359580532088731394,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11301831865756336526,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[3251241957527197760,11100418327549747778,8048619563638005414,1078998931754662073],\"remove\":[],\"delta\":[[11100418327549747778,[910.880658436214,243.0946502057613]],[1078998931754662073,[942.0246913580248,264.2962962962963]],[3251241957527197760,[875.8001811251517,185.6784024477276]],[8048619563638005414,[944.0577392578124,302.0703430175781]]]},\"segments\":{\"add\":[6165219920202055745,10795897848378052161,3439025775805007707,12551435438068218604],\"remove\":[],\"start_point\":[[10795897848378052161,11100418327549747778],[6165219920202055745,3251241957527197760],[12551435438068218604,1078998931754662073],[3439025775805007707,8048619563638005414]],\"end_point\":[[10795897848378052161,8048619563638005414],[6165219920202055745,11100418327549747778],[12551435438068218604,3251241957527197760],[3439025775805007707,1078998931754662073]],\"handle_primary\":[[10795897848378052161,[11.46027223334628,13.82427457744465]],[3439025775805007707,[-2.629814161991817e-6,5.151861046215345e-6]],[12551435438068218604,[-14.61728395061732,-27.456790123456813]],[6165219920202055745,[0.0,0.0]]],\"handle_end\":[[6165219920202055745,[-29.36625514403283,-35.42386831275718]],[10795897848378052161,[-4.732636377154108,-19.33783272951229]],[12551435438068218604,[23.33217876876199,5.204454965879364]],[3439025775805007707,[14.61728395061732,27.456790123456813]]],\"stroke\":[[10795897848378052161,0],[12551435438068218604,0],[3439025775805007707,0],[6165219920202055745,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[[{\"ty\":\"End\",\"segment\":6165219920202055745},{\"ty\":\"Primary\",\"segment\":10795897848378052161}],[{\"ty\":\"End\",\"segment\":3439025775805007707},{\"ty\":\"Primary\",\"segment\":12551435438068218604}]],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15301503532602557206,{\"inputs\":[{\"Node\":{\"node_id\":6728362629909402903,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"BooleanOperation\":\"Intersect\"},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::style::Fill\",\"alias\":null}},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"path_bool_nodes::BooleanOperationNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6787585796949551500,{\"inputs\":[{\"Node\":{\"node_id\":11095670964487764044,\"output_index\":0}},{\"Node\":{\"node_id\":16177422101884031678,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[18003287685830153881,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[4475009837548700636,4481635366652149569,1608608093193158603,4765910415851982345],\"remove\":[1647009925892902058],\"delta\":[[1608608093193158603,[609.7777777777777,620.0]],[4481635366652149569,[684.8888888888889,395.55555555555554]],[4765910415851982345,[283.33331298828125,354.6666564941406]],[4475009837548700636,[525.7325506063205,282.1042056224195]]]},\"segments\":{\"add\":[3045825504718908919,8024777209266880257,3983292299330016176,11103925991989272298],\"remove\":[13489289770112164650],\"start_point\":[[11103925991989272298,4765910415851982345],[3045825504718908919,4475009837548700636],[3983292299330016176,1608608093193158603],[8024777209266880257,4481635366652149569]],\"end_point\":[[3045825504718908919,4481635366652149569],[3983292299330016176,4765910415851982345],[8024777209266880257,1608608093193158603],[11103925991989272298,4475009837548700636]],\"handle_primary\":[[3045825504718908919,[62.22222222222217,43.111111111111086]],[11103925991989272298,[0.0,0.0]],[8024777209266880257,[11.111111111111086,62.22222222222217]],[3983292299330016176,[0.0,0.0]]],\"handle_end\":[[11103925991989272298,[-113.5844024581724,39.08097956276566]],[3045825504718908919,[-11.111111111111086,-62.22222222222217]],[3983292299330016176,[0.0,0.0]],[8024777209266880257,[34.66666666666674,-85.33333333333337]]],\"stroke\":[[11103925991989272298,0],[8024777209266880257,0],[3983292299330016176,0],[3045825504718908919,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[[{\"ty\":\"End\",\"segment\":3045825504718908919},{\"ty\":\"Primary\",\"segment\":8024777209266880257}],[{\"ty\":\"End\",\"segment\":3983292299330016176},{\"ty\":\"Primary\",\"segment\":11103925991989272298}]],\"remove_g1_continuous\":[[{\"ty\":\"End\",\"segment\":13489289770112164650},{\"ty\":\"Primary\",\"segment\":3045825504718908919}],[{\"ty\":\"End\",\"segment\":11103925991989272298},{\"ty\":\"Primary\",\"segment\":3045825504718908919}]]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12770183061753030023,{\"inputs\":[{\"Node\":{\"node_id\":17495267820524300686,\"output_index\":0}},{\"Node\":{\"node_id\":1009114585722052052,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17299978721726771610,{\"inputs\":[{\"Node\":{\"node_id\":7029437790788498388,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[459.4,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":-8.6},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":5},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"repeat_nodes::repeat_nodes::RepeatArrayNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9135110142507605216,{\"inputs\":[{\"Node\":{\"node_id\":11196821089257149774,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"BooleanOperation\":\"SubtractFront\"},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::style::Fill\",\"alias\":null}},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"path_bool_nodes::BooleanOperationNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7104261880154687267,{\"inputs\":[{\"Node\":{\"node_id\":2834866505092039323,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"BooleanOperation\":\"Intersect\"},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::style::Fill\",\"alias\":null}},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"path_bool_nodes::BooleanOperationNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[18271512507682813443,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[10872099980755217048,13049263474526562389,2409359561719399794,9175973472325454282,14263506369487797530,5496457317579994340,7125168137854779179,9658821733796680583,425878374398720476,5252668597335249558],\"remove\":[],\"delta\":[[7125168137854779179,[912.2962962962964,205.6296296296296]],[425878374398720476,[898.633744855967,207.99999999999997]],[5496457317579994340,[929.3607681755832,198.40877914951983]],[9175973472325454282,[881.4814814814815,152.5925925925926]],[13049263474526562389,[903.1111111111112,55.70370370370371]],[2409359561719399794,[892.7407407407408,111.70370370370372]],[14263506369487797530,[886.5185185185185,178.66666666666669]],[9658821733796680583,[920.5925925925926,208.0]],[10872099980755217048,[889.0,0.0]],[5252668597335249558,[888.2516734908237,208.88931872324497]]]},\"segments\":{\"add\":[16241193756011776206,13961346931609713434,6443321953815072406,13223251807271379465,6545223889678917273,9745298522639115232,8058164935346100149,9187894839116405225,5210886161307886529,11050813873332011119],\"remove\":[],\"start_point\":[[8058164935346100149,7125168137854779179],[9187894839116405225,9658821733796680583],[13223251807271379465,9175973472325454282],[16241193756011776206,10872099980755217048],[13961346931609713434,13049263474526562389],[5210886161307886529,425878374398720476],[6545223889678917273,14263506369487797530],[6443321953815072406,2409359561719399794],[9745298522639115232,5496457317579994340],[11050813873332011119,5252668597335249558]],\"end_point\":[[8058164935346100149,9658821733796680583],[11050813873332011119,10872099980755217048],[13223251807271379465,14263506369487797530],[5210886161307886529,5252668597335249558],[6545223889678917273,5496457317579994340],[13961346931609713434,2409359561719399794],[9187894839116405225,425878374398720476],[16241193756011776206,13049263474526562389],[9745298522639115232,7125168137854779179],[6443321953815072406,9175973472325454282]],\"handle_primary\":[[6545223889678917273,[0.0,0.0]],[6443321953815072406,[-9.144953020396894,24.145964868041293]],[13223251807271379465,[3.2812071330587287,8.329218106995853]],[9745298522639115232,[0.0,0.0]],[16241193756011776206,[0.0,0.0]],[5210886161307886529,[-1.1368683772161605e-13,-2.8421709430404014e-14]],[11050813873332011119,[-5.947146741852521,-2.1403475298293415]],[9187894839116405225,[-8.263374485596842,1.880201188843188]],[8058164935346100149,[0.0,0.0]],[13961346931609713434,[6.522611384544012,17.67970980547415]]],\"handle_end\":[[6545223889678917273,[-25.3424782807499,-1.3461362597164737]],[6443321953815072406,[-3.3276401397055917,-8.447086508484054]],[13223251807271379465,[0.7901234567900701,-9.492455418381354]],[11050813873332011119,[-86.26684502769479,250.37037037037035]],[16241193756011776206,[-7.506172839506348,-20.345679012345684]],[13961346931609713434,[10.984910836762538,-29.00411522633746]],[9745298522639115232,[7.703703703703695,-2.074074074074076]],[5210886161307886529,[5.947146741852521,2.1403475298293415]],[8058164935346100149,[-5.925925925925867,-2.370370370370381]],[9187894839116405225,[4.974851394604343,2.0557841792410443]]],\"stroke\":[[6545223889678917273,0],[6443321953815072406,0],[9745298522639115232,0],[13961346931609713434,0],[11050813873332011119,0],[9187894839116405225,0],[16241193756011776206,0],[8058164935346100149,0],[13223251807271379465,0],[5210886161307886529,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[[{\"ty\":\"End\",\"segment\":13961346931609713434},{\"ty\":\"Primary\",\"segment\":6443321953815072406}],[{\"ty\":\"End\",\"segment\":5210886161307886529},{\"ty\":\"Primary\",\"segment\":11050813873332011119}],[{\"ty\":\"End\",\"segment\":16241193756011776206},{\"ty\":\"Primary\",\"segment\":13961346931609713434}],[{\"ty\":\"End\",\"segment\":6443321953815072406},{\"ty\":\"Primary\",\"segment\":13223251807271379465}]],\"remove_g1_continuous\":[[{\"ty\":\"Primary\",\"segment\":9187894839116405225},{\"ty\":\"End\",\"segment\":8058164935346100149}]]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8511737864852441844,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":5.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::generator_nodes::CircleNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17696051535511578981,{\"inputs\":[{\"Node\":{\"node_id\":16780039553038473906,\"output_index\":0}},{\"Node\":{\"node_id\":9071802450034150503,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10286817149456341619,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[10786213048155734586,12599396287665855262,6164201091766276213,6781153531246981076,15695128662656633322,8241402276734357681,10333377922713208038,3749627916562964805,2535476870476707501],\"remove\":[],\"delta\":[[10333377922713208038,[843.4567901234569,550.1234567901236]],[2535476870476707501,[887.5061728395062,568.6913580246915]],[6164201091766276213,[902.9135802469136,506.8641975308643]],[8241402276734357681,[853.3333333333336,529.1851851851854]],[12599396287665855262,[902.5185185185188,498.56790123456807]],[3749627916562964805,[866.172839506173,558.2222222222224]],[6781153531246981076,[872.6913580246915,513.7777777777779]],[15695128662656633322,[825.6790123456792,509.4320987654323]],[10786213048155734586,[917.7283950617284,502.91358024691374]]]},\"segments\":{\"add\":[15700595221504820396,6369399239389505343,17761347836385237495,3021925106447084698,14489254209548096966,3461110970422306113,14847678502054094837,14515109677644612103,264894972572137143],\"remove\":[],\"start_point\":[[264894972572137143,2535476870476707501],[15700595221504820396,10786213048155734586],[14515109677644612103,3749627916562964805],[6369399239389505343,12599396287665855262],[14489254209548096966,15695128662656633322],[17761347836385237495,6164201091766276213],[3021925106447084698,6781153531246981076],[3461110970422306113,8241402276734357681],[14847678502054094837,10333377922713208038]],\"end_point\":[[14847678502054094837,3749627916562964805],[3461110970422306113,10333377922713208038],[3021925106447084698,15695128662656633322],[14515109677644612103,2535476870476707501],[14489254209548096966,8241402276734357681],[17761347836385237495,6781153531246981076],[15700595221504820396,12599396287665855262],[264894972572137143,10786213048155734586],[6369399239389505343,6164201091766276213]],\"handle_primary\":[[15700595221504820396,[0.0,0.0]],[3021925106447084698,[-16.59259259259261,2.370370370370324]],[264894972572137143,[0.0,0.0]],[14847678502054094837,[0.7901234567900701,2.765432098765473]],[6369399239389505343,[-7.1111111111111995,-5.530864197530889]],[14489254209548096966,[-0.3950434518873181,1.0617032941893854]],[3461110970422306113,[0.38915905346209456,1.493515242756871]],[17761347836385237495,[0.0,0.0]],[14515109677644612103,[8.888888888888914,0.0]]],\"handle_end\":[[14847678502054094837,[-8.888888888888914,0.0]],[3021925106447084698,[0.5488119028742631,-1.474965354794051]],[264894972572137143,[3.753086419753003,37.1358024691358]],[3461110970422306113,[-0.7901234567900701,-2.765432098765473]],[14489254209548096966,[-0.5712718875797691,-2.192428171126153]],[6369399239389505343,[0.0,0.0]],[15700595221504820396,[7.1111111111111995,5.530864197530889]],[14515109677644612103,[0.0,0.0]],[17761347836385237495,[16.59259259259261,-2.370370370370324]]],\"stroke\":[[14847678502054094837,0],[17761347836385237495,0],[6369399239389505343,0],[264894972572137143,0],[14515109677644612103,0],[14489254209548096966,0],[3461110970422306113,0],[15700595221504820396,0],[3021925106447084698,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[[{\"ty\":\"End\",\"segment\":14515109677644612103},{\"ty\":\"Primary\",\"segment\":264894972572137143}],[{\"ty\":\"End\",\"segment\":3021925106447084698},{\"ty\":\"Primary\",\"segment\":14489254209548096966}],[{\"ty\":\"End\",\"segment\":15700595221504820396},{\"ty\":\"Primary\",\"segment\":6369399239389505343}],[{\"ty\":\"End\",\"segment\":3461110970422306113},{\"ty\":\"Primary\",\"segment\":14847678502054094837}],[{\"ty\":\"End\",\"segment\":17761347836385237495},{\"ty\":\"Primary\",\"segment\":3021925106447084698}],[{\"ty\":\"End\",\"segment\":14847678502054094837},{\"ty\":\"Primary\",\"segment\":14515109677644612103}],[{\"ty\":\"End\",\"segment\":14489254209548096966},{\"ty\":\"Primary\",\"segment\":3461110970422306113}],[{\"ty\":\"End\",\"segment\":6369399239389505343},{\"ty\":\"Primary\",\"segment\":17761347836385237495}]],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6726954210929537972,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[177515045030371783,4609870404630548899,1458337690478625792,16230687409600568539,8055802412900565217,12390708657704154619,12909592102393222573,6873550371723398444,13416153271806375161,2492294980235986276,12448133448335459077,7624137673235413199],\"remove\":[],\"delta\":[[6873550371723398444,[538.6666666666667,136.0]],[4609870404630548899,[589.432098765432,64.79012345679011]],[177515045030371783,[604.2883706752018,0.0]],[12448133448335459077,[610.3703703703706,2.4308653429145085e-63]],[12390708657704154619,[596.6748971193416,72.42798353909465]],[16230687409600568539,[491.55555555555554,311.55555555555566]],[7624137673235413199,[617.8765432098766,2.465190328815662e-32]],[1458337690478625792,[473.1851851851851,213.62962962962965]],[2492294980235986276,[604.4434936307387,42.970116997735616]],[8055802412900565217,[480.4444444444445,219.7777777777778]],[12909592102393222573,[607.8024691358027,0.0]],[13416153271806375161,[531.8518518518517,131.2592592592593]]]},\"segments\":{\"add\":[12529947318087068663,5578827639764758986,1705877195608053135,2463526518527501592,9361245670457788924,921651403153242564,9825559201153691780,15749222431214672690,14294722277069184337,4341232355541306629,3818841110886669992],\"remove\":[17843645051722826880,16939098306153440303,5057709998225566481,11060716078587457450,17799038492181728804],\"start_point\":[[1705877195608053135,16230687409600568539],[5578827639764758986,1458337690478625792],[4341232355541306629,12390708657704154619],[9825559201153691780,13416153271806375161],[3818841110886669992,7624137673235413199],[12529947318087068663,177515045030371783],[2463526518527501592,8055802412900565217],[921651403153242564,4609870404630548899],[15749222431214672690,2492294980235986276],[9361245670457788924,6873550371723398444],[14294722277069184337,12448133448335459077]],\"end_point\":[[3818841110886669992,12448133448335459077],[2463526518527501592,6873550371723398444],[5578827639764758986,16230687409600568539],[4341232355541306629,7624137673235413199],[14294722277069184337,2492294980235986276],[1705877195608053135,8055802412900565217],[921651403153242564,13416153271806375161],[12529947318087068663,4609870404630548899],[15749222431214672690,12909592102393222573],[9361245670457788924,12390708657704154619],[9825559201153691780,1458337690478625792]],\"handle_primary\":[[9825559201153691780,[-30.00610951685011,28.39287782239575]],[3818841110886669992,[0.0,0.0]],[14294722277069184337,[-0.2633744855968416,28.576131687242796]],[5578827639764758986,[-19.25925925925924,60.44444444444443]],[9361245670457788924,[22.844654233974516,-21.876660410500975]],[4341232355541306629,[19.753086419753117,-26.73251028806584]],[12529947318087068663,[0.0,0.0]],[2463526518527501592,[9.74414620103056,-24.528368023283747]],[921651403153242564,[-7.513106110924076,12.956274823940417]],[1705877195608053135,[0.0,0.0]],[15749222431214672690,[3.877242131088792,-22.333682252383095]]],\"handle_end\":[[14294722277069184337,[0.0,0.0]],[9825559201153691780,[6.194184133897295,-19.440208666385132]],[5578827639764758986,[0.0,0.0]],[3818841110886669992,[0.0,0.0]],[1705877195608053135,[-19.33333333333331,48.66666666666666]],[12529947318087068663,[12.90534979423876,-22.255144032921805]],[921651403153242564,[27.555555555555657,-26.07407407407412]],[2463526518527501592,[-34.96296296296305,33.481481481481495]],[4341232355541306629,[0.0,0.0]],[9361245670457788924,[-16.14969885498249,21.855925783742933]],[15749222431214672690,[0.0,0.0]]],\"stroke\":[[12529947318087068663,0],[4341232355541306629,0],[1705877195608053135,0],[9361245670457788924,0],[2463526518527501592,0],[921651403153242564,0],[9825559201153691780,0],[15749222431214672690,0],[14294722277069184337,0],[5578827639764758986,0],[3818841110886669992,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[[{\"ty\":\"End\",\"segment\":11060716078587457450},{\"ty\":\"Primary\",\"segment\":5578827639764758986}],[{\"ty\":\"End\",\"segment\":17843645051722826880},{\"ty\":\"Primary\",\"segment\":15749222431214672690}],[{\"ty\":\"End\",\"segment\":1705877195608053135},{\"ty\":\"Primary\",\"segment\":2463526518527501592}],[{\"ty\":\"Primary\",\"segment\":16939098306153440303},{\"ty\":\"End\",\"segment\":9361245670457788924}],[{\"ty\":\"End\",\"segment\":2463526518527501592},{\"ty\":\"Primary\",\"segment\":9361245670457788924}],[{\"ty\":\"End\",\"segment\":12529947318087068663},{\"ty\":\"Primary\",\"segment\":921651403153242564}],[{\"ty\":\"End\",\"segment\":17799038492181728804},{\"ty\":\"Primary\",\"segment\":16939098306153440303}],[{\"ty\":\"Primary\",\"segment\":5578827639764758986},{\"ty\":\"End\",\"segment\":9825559201153691780}],[{\"ty\":\"End\",\"segment\":9361245670457788924},{\"ty\":\"Primary\",\"segment\":5057709998225566481}],[{\"ty\":\"End\",\"segment\":921651403153242564},{\"ty\":\"Primary\",\"segment\":9825559201153691780}],[{\"ty\":\"End\",\"segment\":12529947318087068663},{\"ty\":\"Primary\",\"segment\":11060716078587457450}],[{\"ty\":\"End\",\"segment\":1705877195608053135},{\"ty\":\"Primary\",\"segment\":17799038492181728804}],[{\"ty\":\"End\",\"segment\":9361245670457788924},{\"ty\":\"Primary\",\"segment\":17843645051722826880}],[{\"ty\":\"End\",\"segment\":9361245670457788924},{\"ty\":\"Primary\",\"segment\":4341232355541306629}]],\"remove_g1_continuous\":[[{\"ty\":\"Primary\",\"segment\":15749222431214672690},{\"ty\":\"End\",\"segment\":14294722277069184337}],[{\"ty\":\"End\",\"segment\":4341232355541306629},{\"ty\":\"Primary\",\"segment\":3818841110886669992}],[{\"ty\":\"Primary\",\"segment\":14294722277069184337},{\"ty\":\"End\",\"segment\":3818841110886669992}],[{\"ty\":\"End\",\"segment\":5057709998225566481},{\"ty\":\"Primary\",\"segment\":14294722277069184337}]]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17603523494627491590,{\"inputs\":[{\"Node\":{\"node_id\":10995640810984321903,\"output_index\":0}},{\"Node\":{\"node_id\":17740294143355019755,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13616602029989984195,{\"inputs\":[{\"Node\":{\"node_id\":3050731459444225191,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FlattenPathNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4301099429811409147,{\"inputs\":[{\"Node\":{\"node_id\":57390435731316553,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[81.3755165062,-154.9064700048801]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":23.8615898276},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.9999999999999998,0.9999999999999998]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3109716240255919254,{\"inputs\":[{\"Node\":{\"node_id\":5877930116725120460,\"output_index\":0}},{\"Node\":{\"node_id\":18053728639616073084,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2723198387862533596,{\"inputs\":[{\"Node\":{\"node_id\":14030142873804552388,\"output_index\":0}},{\"Node\":{\"node_id\":11201759760883367635,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[18351415092709164412,{\"inputs\":[{\"Node\":{\"node_id\":1009114585722052052,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.101960786,\"green\":0.13333334,\"blue\":0.13725491,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.3137255,\"green\":0.44705883,\"blue\":0.45490196,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6484183251661832039,{\"inputs\":[{\"Node\":{\"node_id\":294265135510952894,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[474.2,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":-7.2},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":5},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"repeat_nodes::repeat_nodes::RepeatArrayNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12922148192688274227,{\"inputs\":[{\"Node\":{\"node_id\":18188505856445531484,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7755499790391969923,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::generator_nodes::CircleNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6503655938154160104,{\"inputs\":[{\"Node\":{\"node_id\":17696051535511578981,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"BooleanOperation\":\"SubtractFront\"},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::style::Fill\",\"alias\":null}},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"path_bool_nodes::BooleanOperationNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16780039553038473906,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":7104088139635280554,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1508440849951861669,{\"inputs\":[{\"Node\":{\"node_id\":8566844905246185636,\"output_index\":0}},{\"Node\":{\"node_id\":7480253252288032958,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5856350938151339368,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[5138872293174440313,14935583009751134816,9014079831396927156,7008812441195504980,9603704700847490044,7238075805746621699,2355002738045707979,5021902985892894619,17629528758352690600,5995306636877552],\"remove\":[10768931421586254879],\"delta\":[[5138872293174440313,[922.9629629629628,422.22222222222223]],[17629528758352690600,[892.148148148148,480.2962962962963]],[7008812441195504980,[888.8888888888887,421.3333333333333]],[9014079831396927156,[908.4444444444443,429.9259259259259]],[7238075805746621699,[798.6831275720165,497.9094650205762]],[9603704700847490044,[821.037037037037,464.2962962962963]],[5995306636877552,[912.8888888888888,479.7037037037037]],[5021902985892894619,[857.679012345679,478.2880658436215]],[2355002738045707979,[826.2057613168724,485.0041152263375]],[14935583009751134816,[914.9629629629628,418.3703703703703]]]},\"segments\":{\"add\":[7250062683967197158,14131440324578863745,4499620435559196394,14571502160667941876,7203043366367198812,239843798079287870,7655921914272804429,12687760936062936386,7117413478945421556,9071432674597727163],\"remove\":[3746587001535987651],\"start_point\":[[14571502160667941876,7008812441195504980],[9071432674597727163,7238075805746621699],[7250062683967197158,5138872293174440313],[7203043366367198812,9603704700847490044],[239843798079287870,2355002738045707979],[7117413478945421556,5995306636877552],[4499620435559196394,9014079831396927156],[7655921914272804429,5021902985892894619],[14131440324578863745,14935583009751134816],[12687760936062936386,17629528758352690600]],\"end_point\":[[7117413478945421556,5138872293174440313],[14571502160667941876,9603704700847490044],[7203043366367198812,7238075805746621699],[7250062683967197158,14935583009751134816],[9071432674597727163,2355002738045707979],[12687760936062936386,5995306636877552],[4499620435559196394,7008812441195504980],[7655921914272804429,17629528758352690600],[239843798079287870,5021902985892894619],[14131440324578863745,9014079831396927156]],\"handle_primary\":[[7250062683967197158,[0.0,0.0]],[7655921914272804429,[18.172839506172863,-1.9753086419753456]],[14571502160667941876,[-0.5925925925926094,0.0]],[14131440324578863745,[0.0,0.0]],[4499620435559196394,[-0.2962962962963047,-0.2962962962963047]],[239843798079287870,[9.61316872427983,-1.843621399176982]],[9071432674597727163,[4.345679012345499,-3.555555555555543]],[7203043366367198812,[0.0,0.2962962962963047]],[7117413478945421556,[0.0,0.0]],[12687760936062936386,[10.666666666666742,3.555555555555543]]],\"handle_end\":[[14571502160667941876,[24.395061728395035,-0.36213991769540144]],[12687760936062936386,[0.0,0.0]],[14131440324578863745,[2.962962962963047,-3.259259259259238]],[239843798079287870,[-21.502976534539364,2.3372800581021456]],[7655921914272804429,[-10.666666666666742,-3.555555555555543]],[7117413478945421556,[15.703703703703695,33.48148148148147]],[4499620435559196394,[8.59259259259261,5.629629629629619]],[7203043366367198812,[-1.1851851851852189,-25.18518518518516]],[7250062683967197158,[0.0,0.0]],[9071432674597727163,[-9.913826570592164,1.9012818080587977]]],\"stroke\":[[7250062683967197158,0],[14131440324578863745,0],[14571502160667941876,0],[7655921914272804429,0],[4499620435559196394,0],[239843798079287870,0],[9071432674597727163,0],[7203043366367198812,0],[7117413478945421556,0],[12687760936062936386,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[[{\"ty\":\"Primary\",\"segment\":239843798079287870},{\"ty\":\"End\",\"segment\":9071432674597727163}],[{\"ty\":\"End\",\"segment\":12687760936062936386},{\"ty\":\"Primary\",\"segment\":7117413478945421556}],[{\"ty\":\"End\",\"segment\":239843798079287870},{\"ty\":\"Primary\",\"segment\":7655921914272804429}],[{\"ty\":\"End\",\"segment\":7250062683967197158},{\"ty\":\"Primary\",\"segment\":14131440324578863745}],[{\"ty\":\"End\",\"segment\":7655921914272804429},{\"ty\":\"Primary\",\"segment\":12687760936062936386}]],\"remove_g1_continuous\":[[{\"ty\":\"End\",\"segment\":9071432674597727163},{\"ty\":\"Primary\",\"segment\":3746587001535987651}],[{\"ty\":\"End\",\"segment\":3746587001535987651},{\"ty\":\"Primary\",\"segment\":239843798079287870}]]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14257963317028524134,{\"inputs\":[{\"Node\":{\"node_id\":1001728975241745659,\"output_index\":0}},{\"Node\":{\"node_id\":6503655938154160104,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1491840484128555837,{\"inputs\":[{\"Node\":{\"node_id\":1508440849951861669,\"output_index\":0}},{\"Node\":{\"node_id\":9808637865669223270,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5888633415105234509,{\"inputs\":[{\"Node\":{\"node_id\":3750439930725791025,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"BooleanOperation\":\"Intersect\"},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::style::Fill\",\"alias\":null}},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"path_bool_nodes::BooleanOperationNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1258994191538244490,{\"inputs\":[{\"Node\":{\"node_id\":5488285068107445023,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.101960786,\"green\":0.13333334,\"blue\":0.13725491,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5486211022469996717,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[266134622800431246,12075466065090700811,7115673652122600762,9852238672814254137],\"remove\":[134095410253559933],\"delta\":[[266134622800431246,[525.7325506063205,282.1042056224195]],[9852238672814254137,[879.7947439326053,110.54094004739208]],[12075466065090700811,[877.0370370370372,136.5925925925926]],[7115673652122600762,[646.5386352539062,343.99395751953125]]]},\"segments\":{\"add\":[17255728395700231433,3166693019207180063,10242199012993595528,2159953159628520134],\"remove\":[13980530488817762548,10196443086284452827,3769008694104161528],\"start_point\":[[17255728395700231433,12075466065090700811],[2159953159628520134,9852238672814254137],[10242199012993595528,266134622800431246],[3166693019207180063,7115673652122600762]],\"end_point\":[[10242199012993595528,9852238672814254137],[17255728395700231433,7115673652122600762],[2159953159628520134,12075466065090700811],[3166693019207180063,266134622800431246]],\"handle_primary\":[[2159953159628520134,[0.0,0.0]],[10242199012993595528,[137.37856050479058,-43.88198340019727]],[17255728395700231433,[0.0010773420832492775,-0.01370555995902123]],[3166693019207180063,[-126.67994767097883,-51.72627287889763]]],\"handle_end\":[[10242199012993595528,[-15.794743932605344,36.71831921186718]],[2159953159628520134,[0.0,0.0]],[17255728395700231433,[165.46136474609386,67.5615980360243]],[3166693019207180063,[0.0,0.0]]],\"stroke\":[[17255728395700231433,0],[2159953159628520134,0],[3166693019207180063,0],[10242199012993595528,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[[{\"ty\":\"Primary\",\"segment\":3166693019207180063},{\"ty\":\"End\",\"segment\":17255728395700231433}]],\"remove_g1_continuous\":[[{\"ty\":\"Primary\",\"segment\":10242199012993595528},{\"ty\":\"End\",\"segment\":3166693019207180063}],[{\"ty\":\"Primary\",\"segment\":3769008694104161528},{\"ty\":\"End\",\"segment\":10196443086284452827}],[{\"ty\":\"End\",\"segment\":10242199012993595528},{\"ty\":\"Primary\",\"segment\":2159953159628520134}],[{\"ty\":\"End\",\"segment\":3166693019207180063},{\"ty\":\"Primary\",\"segment\":13980530488817762548}]]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14139129879376457893,{\"inputs\":[{\"Node\":{\"node_id\":11373527190663101881,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.101960786,\"green\":0.13333334,\"blue\":0.13725491,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.101960786,\"green\":0.13333334,\"blue\":0.13725491,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5954536408321808728,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[8323145223085840944,87630150944518163,8212340198835025146,6057321578372831916,16788403743390653241,16686047298905200065,15635903913637220842,8652457572576966876,3124531241960914803,18307826411292029066,9969822516610975381,16995054111951247369,16808246968731723098],\"remove\":[],\"delta\":[[16808246968731723098,[891.5555555555554,768.0]],[87630150944518163,[879.1111111111111,595.8518518518517]],[6057321578372831916,[708.148148148148,616.2962962962962]],[8212340198835025146,[830.8148148148148,633.7777777777776]],[16686047298905200065,[283.3333333333333,354.66666666666663]],[16995054111951247369,[0.0,768.0]],[3124531241960914803,[589.9588477366256,64.65843621399176]],[15635903913637220842,[468.4799499511719,219.78570556640625]],[16788403743390653241,[467.99999999999994,487.3333333333333]],[9969822516610975381,[0.0,0.0]],[8652457572576966876,[514.4523315429688,146.72100830078125]],[18307826411292029066,[601.1756940654021,0.0]],[8323145223085840944,[891.5555555555554,569.3827160493826]]]},\"segments\":{\"add\":[14879480148926424885,5227512358023446442,17410347049685436697,9544743211426701912,14715945740984195653,515539069333222772,9242150930423817167,8823161072525575433,15885450050057549950,1662692131856898001,178608879920007638,12162371359208565273,16224358815792062223],\"remove\":[],\"start_point\":[[8823161072525575433,8652457572576966876],[16224358815792062223,16808246968731723098],[14879480148926424885,8323145223085840944],[1662692131856898001,18307826411292029066],[515539069333222772,16686047298905200065],[178608879920007638,9969822516610975381],[15885450050057549950,3124531241960914803],[9242150930423817167,15635903913637220842],[9544743211426701912,6057321578372831916],[5227512358023446442,87630150944518163],[12162371359208565273,16995054111951247369],[14715945740984195653,16788403743390653241],[17410347049685436697,8212340198835025146]],\"end_point\":[[12162371359208565273,16808246968731723098],[15885450050057549950,18307826411292029066],[14715945740984195653,16686047298905200065],[178608879920007638,16995054111951247369],[515539069333222772,15635903913637220842],[16224358815792062223,8323145223085840944],[5227512358023446442,8212340198835025146],[8823161072525575433,3124531241960914803],[1662692131856898001,9969822516610975381],[9242150930423817167,8652457572576966876],[14879480148926424885,87630150944518163],[17410347049685436697,6057321578372831916],[9544743211426701912,16788403743390653241]],\"handle_primary\":[[8823161072525575433,[11.259252477575274,-22.666663275824646]],[17410347049685436697,[-26.37037037037044,2.3703703703704377]],[14879480148926424885,[0.0,0.0]],[9544743211426701912,[-57.48148148148141,-21.629629629629676]],[16224358815792062223,[0.0,0.0]],[9242150930423817167,[27.25925925925918,-22.518518518518533]],[15885450050057549950,[11.193415637860312,-27.25925925925926]],[5227512358023446442,[-4.444444444444457,18.074074074074133]],[178608879920007638,[0.0,0.0]],[515539069333222772,[48.66666666666663,-23.999999999999943]],[1662692131856898001,[0.0,0.0]],[12162371359208565273,[0.0,0.0]],[14715945740984195653,[-92.66666666666656,-80.66666666666669]]],\"handle_end\":[[8823161072525575433,[-12.439135136214697,30.29295262583988]],[9544743211426701912,[92.66666666666656,80.66666666666669]],[515539069333222772,[-95.07216232621772,78.5378732260062]],[12162371359208565273,[0.0,0.0]],[15885450050057549950,[0.0,0.0]],[5227512358023446442,[26.37037037037044,-2.3703703703704377]],[1662692131856898001,[0.0,0.0]],[16224358815792062223,[0.0,0.0]],[14879480148926424885,[4.444444444444457,-18.074074074074133]],[17410347049685436697,[57.48148148148141,21.629629629629676]],[9242150930423817167,[-9.36826657882972,18.859808361471693]],[14715945740984195653,[0.0,0.0]],[178608879920007638,[0.0,0.0]]],\"stroke\":[[17410347049685436697,0],[9242150930423817167,0],[9544743211426701912,0],[515539069333222772,0],[5227512358023446442,0],[15885450050057549950,0],[178608879920007638,0],[14715945740984195653,0],[8823161072525575433,0],[14879480148926424885,0],[16224358815792062223,0],[1662692131856898001,0],[12162371359208565273,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[[{\"ty\":\"End\",\"segment\":15885450050057549950},{\"ty\":\"Primary\",\"segment\":1662692131856898001}],[{\"ty\":\"End\",\"segment\":9544743211426701912},{\"ty\":\"Primary\",\"segment\":14715945740984195653}],[{\"ty\":\"End\",\"segment\":14879480148926424885},{\"ty\":\"Primary\",\"segment\":5227512358023446442}],[{\"ty\":\"End\",\"segment\":5227512358023446442},{\"ty\":\"Primary\",\"segment\":17410347049685436697}],[{\"ty\":\"End\",\"segment\":178608879920007638},{\"ty\":\"Primary\",\"segment\":12162371359208565273}],[{\"ty\":\"End\",\"segment\":9242150930423817167},{\"ty\":\"Primary\",\"segment\":8823161072525575433}],[{\"ty\":\"End\",\"segment\":1662692131856898001},{\"ty\":\"Primary\",\"segment\":178608879920007638}],[{\"ty\":\"End\",\"segment\":12162371359208565273},{\"ty\":\"Primary\",\"segment\":16224358815792062223}],[{\"ty\":\"End\",\"segment\":17410347049685436697},{\"ty\":\"Primary\",\"segment\":9544743211426701912}],[{\"ty\":\"End\",\"segment\":8823161072525575433},{\"ty\":\"Primary\",\"segment\":15885450050057549950}],[{\"ty\":\"End\",\"segment\":515539069333222772},{\"ty\":\"Primary\",\"segment\":9242150930423817167}]],\"remove_g1_continuous\":[[{\"ty\":\"End\",\"segment\":14715945740984195653},{\"ty\":\"Primary\",\"segment\":515539069333222772}]]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[18053728639616073084,{\"inputs\":[{\"Node\":{\"node_id\":6616450276140292763,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.101960786,\"green\":0.13333334,\"blue\":0.13725491,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.101960786,\"green\":0.13333334,\"blue\":0.13725491,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17569892869974995307,{\"inputs\":[{\"Node\":{\"node_id\":3997031659711823213,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5534800796196967885,{\"inputs\":[{\"Node\":{\"node_id\":3608604157153838227,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"CentroidType\":\"Area\"},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CentroidNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7029437790788498388,{\"inputs\":[{\"Node\":{\"node_id\":1809704172129195322,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.3137255,\"green\":0.44705883,\"blue\":0.45490196,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.8509804,\"green\":0.24313726,\"blue\":0.18431373,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17351444026127625357,{\"inputs\":[{\"Node\":{\"node_id\":14808063168960305551,\"output_index\":0}},{\"Node\":{\"node_id\":13916027199283115943,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17735408893002232096,{\"inputs\":[{\"Node\":{\"node_id\":14228923746783465609,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.101960786,\"green\":0.13333334,\"blue\":0.13725491,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.101960786,\"green\":0.13333334,\"blue\":0.13725491,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17285637344898461972,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[16608607268690234590,17457877227823502997,13436129231170586617,5992570223148766873],\"remove\":[],\"delta\":[[5992570223148766873,[-2.273736754432321e-13,768.0]],[16608607268690234590,[0.0,340.0]],[13436129231170586617,[481.3333333333333,768.0]],[17457877227823502997,[273.0,476.0]]]},\"segments\":{\"add\":[13284808974014161135,5472571334856691465,8776124420595946733,4274372337254864190],\"remove\":[],\"start_point\":[[8776124420595946733,13436129231170586617],[13284808974014161135,16608607268690234590],[4274372337254864190,5992570223148766873],[5472571334856691465,17457877227823502997]],\"end_point\":[[8776124420595946733,5992570223148766873],[5472571334856691465,13436129231170586617],[13284808974014161135,17457877227823502997],[4274372337254864190,16608607268690234590]],\"handle_primary\":[[4274372337254864190,[0.0,0.0]],[5472571334856691465,[146.0,-13.0]],[8776124420595946733,[0.0,0.0]],[13284808974014161135,[161.73791370620617,43.57576470888159]]],\"handle_end\":[[13284808974014161135,[-165.43581782916667,14.730586519035386]],[8776124420595946733,[0.0,0.0]],[5472571334856691465,[0.4078646547782227,-253.307727480567]],[4274372337254864190,[0.0,0.0]]],\"stroke\":[[13284808974014161135,0],[4274372337254864190,0],[8776124420595946733,0],[5472571334856691465,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[[{\"ty\":\"End\",\"segment\":8776124420595946733},{\"ty\":\"Primary\",\"segment\":4274372337254864190}],[{\"ty\":\"End\",\"segment\":13284808974014161135},{\"ty\":\"Primary\",\"segment\":5472571334856691465}]],\"remove_g1_continuous\":[[{\"ty\":\"Primary\",\"segment\":13284808974014161135},{\"ty\":\"End\",\"segment\":4274372337254864190}]]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15900830679378240619,{\"inputs\":[{\"Node\":{\"node_id\":2287485748649359627,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"BooleanOperation\":\"Intersect\"},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::style::Fill\",\"alias\":null}},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"path_bool_nodes::BooleanOperationNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1662641269094032596,{\"inputs\":[{\"Node\":{\"node_id\":16304636129468583592,\"output_index\":0}},{\"Node\":{\"node_id\":14139765080256493579,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6728362629909402903,{\"inputs\":[{\"Node\":{\"node_id\":5856350938151339368,\"output_index\":0}},{\"Node\":{\"node_id\":5449860184735415958,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12276520439585231336,{\"inputs\":[{\"Node\":{\"node_id\":13795432594059356320,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[997.5029638869316,545.7213923090854]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10995640810984321903,{\"inputs\":[{\"Node\":{\"node_id\":12353675714904258944,\"output_index\":0}},{\"Node\":{\"node_id\":10265035897167064154,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15354358358546908017,{\"inputs\":[{\"Node\":{\"node_id\":14950060858756810933,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.92156863,\"green\":0.6039216,\"blue\":0.18039216,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.93333334,\"green\":0.8627451,\"blue\":0.7254902,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4328376070224119511,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":13446205009526451196,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1009114585722052052,{\"inputs\":[{\"Node\":{\"node_id\":15692102598187739001,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"BooleanOperation\":\"Intersect\"},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::style::Fill\",\"alias\":null}},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"path_bool_nodes::BooleanOperationNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7252918969430566594,{\"inputs\":[{\"Node\":{\"node_id\":5534800796196967885,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":-1.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::MultiplyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14684142559936015947,{\"inputs\":[{\"Node\":{\"node_id\":15930698052919171086,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.101960786,\"green\":0.13333334,\"blue\":0.13725491,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.101960786,\"green\":0.13333334,\"blue\":0.13725491,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4771789845668099116,{\"inputs\":[{\"Node\":{\"node_id\":17351444026127625357,\"output_index\":0}},{\"Node\":{\"node_id\":14684142559936015947,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5014806436727666175,{\"inputs\":[{\"Node\":{\"node_id\":7855094781869605606,\"output_index\":0}},{\"Node\":{\"node_id\":8863346544623578893,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14139765080256493579,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[3507505346656189771,2904729023031858224,854708012647259796,4175610638601362388,7821224334582795476,2195807584570698921],\"remove\":[1009888023123207923,16332448509429125699],\"delta\":[[7821224334582795476,[1093.0,600.0]],[4175610638601362388,[957.0,324.0]],[2904729023031858224,[491.3420376907663,506.7530051313737]],[854708012647259796,[740.0,298.0]],[2195807584570698921,[993.0,767.9999999999999]],[3507505346656189771,[552.0,768.0000000000001]]]},\"segments\":{\"add\":[4553965744616493549,13993263006398686359,6577640955869157325,17304574948462342226,8570276641842028192,17183285389582020412],\"remove\":[546565283439891712,5710832026764395735],\"start_point\":[[17304574948462342226,4175610638601362388],[17183285389582020412,7821224334582795476],[4553965744616493549,3507505346656189771],[13993263006398686359,2904729023031858224],[6577640955869157325,854708012647259796],[8570276641842028192,2195807584570698921]],\"end_point\":[[6577640955869157325,4175610638601362388],[17304574948462342226,7821224334582795476],[13993263006398686359,854708012647259796],[8570276641842028192,3507505346656189771],[17183285389582020412,2195807584570698921],[4553965744616493549,2904729023031858224]],\"handle_primary\":[[13993263006398686359,[0.0,-84.7537417270637]],[17183285389582020412,[-24.08196064282073,82.4624712920831]],[8570276641842028192,[0.0,0.0]],[6577640955869157325,[102.0,-45.0]],[17304574948462342226,[50.35246044959922,33.38607084805801]],[4553965744616493549,[0.0,0.0]]],\"handle_end\":[[13993263006398686359,[-102.0,45.0]],[6577640955869157325,[-50.35246044959922,-33.38607084805801]],[4553965744616493549,[0.0,135.2464055295568]],[17304574948462342226,[33.0,-113.0]],[8570276641842028192,[0.0,0.0]],[17183285389582020412,[0.0,0.0]]],\"stroke\":[[8570276641842028192,0],[4553965744616493549,0],[17183285389582020412,0],[13993263006398686359,0],[6577640955869157325,0],[17304574948462342226,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[[{\"ty\":\"Primary\",\"segment\":8570276641842028192},{\"ty\":\"End\",\"segment\":17183285389582020412}],[{\"ty\":\"End\",\"segment\":17304574948462342226},{\"ty\":\"Primary\",\"segment\":17183285389582020412}],[{\"ty\":\"End\",\"segment\":13993263006398686359},{\"ty\":\"Primary\",\"segment\":6577640955869157325}],[{\"ty\":\"End\",\"segment\":4553965744616493549},{\"ty\":\"Primary\",\"segment\":13993263006398686359}],[{\"ty\":\"End\",\"segment\":6577640955869157325},{\"ty\":\"Primary\",\"segment\":17304574948462342226}]],\"remove_g1_continuous\":[[{\"ty\":\"End\",\"segment\":546565283439891712},{\"ty\":\"Primary\",\"segment\":5710832026764395735}],[{\"ty\":\"End\",\"segment\":5710832026764395735},{\"ty\":\"Primary\",\"segment\":8570276641842028192}],[{\"ty\":\"End\",\"segment\":17183285389582020412},{\"ty\":\"Primary\",\"segment\":546565283439891712}],[{\"ty\":\"Primary\",\"segment\":8570276641842028192},{\"ty\":\"End\",\"segment\":546565283439891712}]]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10080296672372912698,{\"inputs\":[{\"Node\":{\"node_id\":12867379765049504290,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"BooleanOperation\":\"SubtractFront\"},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::style::Fill\",\"alias\":null}},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"path_bool_nodes::BooleanOperationNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2834866505092039323,{\"inputs\":[{\"Node\":{\"node_id\":18003287685830153881,\"output_index\":0}},{\"Node\":{\"node_id\":14139765080256493579,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14690269209726153565,{\"inputs\":[{\"Node\":{\"node_id\":7376049709233607419,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.101960786,\"green\":0.13333334,\"blue\":0.13725491,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.101960786,\"green\":0.13333334,\"blue\":0.13725491,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[18188505856445531484,{\"inputs\":[{\"Node\":{\"node_id\":8887924609778270360,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.92156863,\"green\":0.6039216,\"blue\":0.18039216,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.92156863,\"green\":0.6039216,\"blue\":0.18039216,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3410481056630111806,{\"inputs\":[{\"Node\":{\"node_id\":1097494158696050491,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.8509804,\"green\":0.24313726,\"blue\":0.18431373,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.3137255,\"green\":0.44705883,\"blue\":0.45490196,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12353675714904258944,{\"inputs\":[{\"Node\":{\"node_id\":13203761224559198689,\"output_index\":0}},{\"Node\":{\"node_id\":17271572793812678706,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17271572793812678706,{\"inputs\":[{\"Node\":{\"node_id\":15900830679378240619,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.101960786,\"green\":0.13333334,\"blue\":0.13725491,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.101960786,\"green\":0.13333334,\"blue\":0.13725491,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14228923746783465609,{\"inputs\":[{\"Node\":{\"node_id\":16304636129468583592,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[2003133867055127539,13758328146055368475],\"remove\":[],\"delta\":[[16569120368910754547,[-43.0,0.0]],[4648964341912884959,[324.66666666666646,0.0]],[2003133867055127539,[1011.111083984375,786.3993530273438]],[13758328146055368475,[1299.3064572949788,786.3993743175897]]]},\"segments\":{\"add\":[8214025514312603513,7334532063810723038,13431612844608018700],\"remove\":[3433930674303663828,6602880736207868665],\"start_point\":[[7334532063810723038,16569120368910754547],[13431612844608018700,13758328146055368475],[8214025514312603513,2003133867055127539]],\"end_point\":[[8214025514312603513,4648964341912884959],[7334532063810723038,13758328146055368475],[13431612844608018700,2003133867055127539]],\"handle_primary\":[[7334532063810723038,null],[8214025514312603513,null],[9030015329489789075,[69.08057198853999,-80.19415805947563]],[13431612844608018700,null]],\"handle_end\":[[7334532063810723038,null],[9030015329489789075,[0.0,0.0]],[8214025514312603513,null],[13431612844608018700,null],[2197140374690997530,[-24.333333333333485,-86.99999999999977]]],\"stroke\":[[7334532063810723038,0],[13431612844608018700,0],[8214025514312603513,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[[{\"ty\":\"End\",\"segment\":9030015329489789075},{\"ty\":\"Primary\",\"segment\":2197140374690997530}],[{\"ty\":\"Primary\",\"segment\":9030015329489789075},{\"ty\":\"End\",\"segment\":3433930674303663828}]]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11210964267417873667,{\"inputs\":[{\"Node\":{\"node_id\":4261249487994076490,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[91.7203910728,-99.9607940061]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":23.318559511400004},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7962101329808960965,{\"inputs\":[{\"Node\":{\"node_id\":16322546010403524636,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":-1.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::MultiplyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8887924609778270360,{\"inputs\":[{\"Node\":{\"node_id\":5014806436727666175,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"BooleanOperation\":\"Intersect\"},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::style::Fill\",\"alias\":null}},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"path_bool_nodes::BooleanOperationNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17036604842139972912,{\"inputs\":[{\"Node\":{\"node_id\":1491840484128555837,\"output_index\":0}},{\"Node\":{\"node_id\":11210964267417873667,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12145355397916841389,{\"inputs\":[{\"Node\":{\"node_id\":16759836951269190891,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.101960786,\"green\":0.13333334,\"blue\":0.13725491,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.92156863,\"green\":0.6039216,\"blue\":0.18039216,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4470272391975492611,{\"inputs\":[{\"Node\":{\"node_id\":7018444885869143173,\"output_index\":0}},{\"Node\":{\"node_id\":12145355397916841389,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1097494158696050491,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":15.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::generator_nodes::CircleNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5556372312033787775,{\"inputs\":[{\"Node\":{\"node_id\":6484183251661832039,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::BoundingBoxNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[294265135510952894,{\"inputs\":[{\"Node\":{\"node_id\":11264395591110193456,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.92156863,\"green\":0.6039216,\"blue\":0.18039216,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.8509804,\"green\":0.24313726,\"blue\":0.18431373,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2025899804080897524,{\"inputs\":[{\"Node\":{\"node_id\":17740496701763775226,\"output_index\":0}},{\"Node\":{\"node_id\":3214181946162459584,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[990192925663920333,{\"inputs\":[{\"Node\":{\"node_id\":3410481056630111806,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[466.8,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":-7.9},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":5},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"repeat_nodes::repeat_nodes::RepeatArrayNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11264395591110193456,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":10.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::generator_nodes::CircleNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13027689870767713939,{\"inputs\":[{\"Node\":{\"node_id\":7201841978411396053,\"output_index\":0}},{\"Node\":{\"node_id\":13340751444307201866,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9338394475379815879,{\"inputs\":[{\"Node\":{\"node_id\":7977952035097419157,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1327.0278641242007,11.102707365920756]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":13.64801326231709},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[230.36030209674013,67.6958526826066]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[2.2090991564182537e-15,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14496934933990319842,{\"inputs\":[{\"Node\":{\"node_id\":12709602171929957216,\"output_index\":0}},{\"Node\":{\"node_id\":12185047359007423618,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13739729101529293427,{\"inputs\":[{\"Node\":{\"node_id\":18003287685830153881,\"output_index\":0}},{\"Node\":{\"node_id\":14139765080256493579,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3214181946162459584,{\"inputs\":[{\"Node\":{\"node_id\":5449860184735415958,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.93333334,\"green\":0.8627451,\"blue\":0.7254902,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.93333334,\"green\":0.8627451,\"blue\":0.7254902,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16322546010403524636,{\"inputs\":[{\"Node\":{\"node_id\":5556372312033787775,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"CentroidType\":\"Area\"},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CentroidNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[804622576568168609,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[13654185056786459919,6952183320761642858,2802617302192982616,7639287212006638253,15117515618866904690,15750781936547583892,7025422618543191491,3831533579307185652,16937470333767479918,10412162547161259027,5269777039739103358],\"remove\":[],\"delta\":[[2802617302192982616,[911.7777777777776,334.88888888888886]],[15117515618866904690,[819.1111111111109,354.44444444444434]],[3831533579307185652,[880.4444444444443,357.3333333333333]],[6952183320761642858,[933.9259259259262,324.3456790123457]],[7639287212006638253,[862.8888888888888,349.3333333333333]],[7025422618543191491,[911.5555555555554,345.3333333333333]],[10412162547161259027,[887.3333333333333,376.0]],[5269777039739103358,[902.2222222222222,394.66666666666663]],[16937470333767479918,[876.6666666666665,359.55555555555554]],[15750781936547583892,[840.6666666666665,368.66666666666663]],[13654185056786459919,[946.1728395061732,330.8641975308642]]]},\"segments\":{\"add\":[10498175376126066982,10426255560432238561,11214998966879437330,1241631398419524272,15286139376878919277,15838384105575178137,12854270992100552972,379744301215040936,4069217348643062888,10438163357339042361,9330486019469919863],\"remove\":[],\"start_point\":[[10438163357339042361,10412162547161259027],[379744301215040936,3831533579307185652],[10498175376126066982,13654185056786459919],[4069217348643062888,16937470333767479918],[15286139376878919277,15117515618866904690],[10426255560432238561,6952183320761642858],[15838384105575178137,15750781936547583892],[12854270992100552972,7025422618543191491],[11214998966879437330,2802617302192982616],[1241631398419524272,7639287212006638253],[9330486019469919863,5269777039739103358]],\"end_point\":[[1241631398419524272,15117515618866904690],[10498175376126066982,6952183320761642858],[10426255560432238561,2802617302192982616],[11214998966879437330,7639287212006638253],[15838384105575178137,7025422618543191491],[379744301215040936,16937470333767479918],[4069217348643062888,10412162547161259027],[12854270992100552972,3831533579307185652],[9330486019469919863,13654185056786459919],[15286139376878919277,15750781936547583892],[10438163357339042361,5269777039739103358]],\"handle_primary\":[[10498175376126066982,[0.0,0.0]],[1241631398419524272,[-17.111111111111086,12.888888888888856]],[10426255560432238561,[-0.39506172839503506,-0.19753086419757435]],[379744301215040936,[-0.2222222222221717,0.0]],[15286139376878919277,[-0.5188510642573192,0.6084707935380038]],[10438163357339042361,[1.3333333333332575,10.888888888888856]],[15838384105575178137,[22.222222222222285,5.555555555555543]],[4069217348643062888,[0.0,0.0]],[11214998966879437330,[-16.666666666666515,-3.777777777777771]],[9330486019469919863,[5.555555555555429,-0.2222222222221717]],[12854270992100552972,[-0.4444444444443434,0.0]]],\"handle_end\":[[10438163357339042361,[-5.555555555555429,0.2222222222221717]],[10426255560432238561,[16.666666666666515,3.777777777777771]],[1241631398419524272,[0.5951379226228255,-0.6979344728938486]],[12854270992100552972,[33.77777777777783,2.2222222222222285]],[15286139376878919277,[-22.222222222222285,-5.555555555555543]],[4069217348643062888,[-1.3333333333332575,-10.888888888888856]],[9330486019469919863,[0.0,0.0]],[379744301215040936,[0.0,0.0]],[11214998966879437330,[17.111111111111086,-12.888888888888856]],[15838384105575178137,[-20.0,2.0]],[10498175376126066982,[4.9382716049382225,0.19753086419757435]]],\"stroke\":[[15286139376878919277,0],[15838384105575178137,0],[4069217348643062888,0],[12854270992100552972,0],[9330486019469919863,0],[10498175376126066982,0],[379744301215040936,0],[11214998966879437330,0],[10426255560432238561,0],[10438163357339042361,0],[1241631398419524272,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[[{\"ty\":\"End\",\"segment\":1241631398419524272},{\"ty\":\"Primary\",\"segment\":15286139376878919277}],[{\"ty\":\"End\",\"segment\":11214998966879437330},{\"ty\":\"Primary\",\"segment\":1241631398419524272}],[{\"ty\":\"End\",\"segment\":15286139376878919277},{\"ty\":\"Primary\",\"segment\":15838384105575178137}],[{\"ty\":\"End\",\"segment\":4069217348643062888},{\"ty\":\"Primary\",\"segment\":10438163357339042361}],[{\"ty\":\"End\",\"segment\":379744301215040936},{\"ty\":\"Primary\",\"segment\":4069217348643062888}],[{\"ty\":\"End\",\"segment\":10426255560432238561},{\"ty\":\"Primary\",\"segment\":11214998966879437330}],[{\"ty\":\"End\",\"segment\":10438163357339042361},{\"ty\":\"Primary\",\"segment\":9330486019469919863}]],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17753909951719808506,{\"inputs\":[{\"Node\":{\"node_id\":2185437945364824599,\"output_index\":0}},{\"Node\":{\"node_id\":9563008199132558110,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8566844905246185636,{\"inputs\":[{\"Node\":{\"node_id\":6852799892628327372,\"output_index\":0}},{\"Node\":{\"node_id\":17131529656312051452,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[18095952297474762348,{\"inputs\":[{\"Node\":{\"node_id\":5954536408321808728,\"output_index\":0}},{\"Node\":{\"node_id\":5449860184735415958,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4261249487994076490,{\"inputs\":[{\"Node\":{\"node_id\":14516211820212764316,\"output_index\":0}},{\"Node\":{\"node_id\":5534800796196967885,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8426490990601560741,{\"inputs\":[{\"Node\":{\"node_id\":10760002922115563021,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.93333334,\"green\":0.8627451,\"blue\":0.7254902,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.92156863,\"green\":0.6039216,\"blue\":0.18039216,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13795432594059356320,{\"inputs\":[{\"Node\":{\"node_id\":2871608309888343463,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FlattenPathNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7651693425519490419,{\"inputs\":[{\"Node\":{\"node_id\":4771789845668099116,\"output_index\":0}},{\"Node\":{\"node_id\":15817956847588799375,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6616450276140292763,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[14760110820354327749,9465415708990918986,13517856880347849115,12398030966924498647,2930103517622848405,9496337687212684441],\"remove\":[],\"delta\":[[12398030966924498647,[823.4074074074074,97.4814814814815]],[9496337687212684441,[732.148148148148,57.18518518518518]],[9465415708990918986,[706.3703703703703,86.51851851851853]],[2930103517622848405,[819.2592592592591,69.33333333333334]],[14760110820354327749,[614.8148148148148,129.1851851851852]],[13517856880347849115,[777.1851851851851,92.44444444444446]]]},\"segments\":{\"add\":[18226408581696793441,16628058317667581864,3809009367670994230,13496648265229692336,10686631697241750410,11696351847604335156],\"remove\":[],\"start_point\":[[11696351847604335156,9496337687212684441],[3809009367670994230,13517856880347849115],[13496648265229692336,12398030966924498647],[16628058317667581864,9465415708990918986],[10686631697241750410,2930103517622848405],[18226408581696793441,14760110820354327749]],\"end_point\":[[10686631697241750410,9496337687212684441],[18226408581696793441,9465415708990918986],[11696351847604335156,14760110820354327749],[16628058317667581864,13517856880347849115],[3809009367670994230,12398030966924498647],[13496648265229692336,2930103517622848405]],\"handle_primary\":[[10686631697241750410,[-13.037037037036953,-4.444444444444457]],[3809009367670994230,[21.33333333333337,4.740740740740733]],[13496648265229692336,[14.222222222222172,-0.8888888888888857]],[11696351847604335156,[-29.333333333333258,11.55555555555555]],[16628058317667581864,[41.185185185185105,-9.7777777777778]],[18226408581696793441,[0.0,0.0]]],\"handle_end\":[[16628058317667581864,[-21.33333333333337,-4.740740740740733]],[13496648265229692336,[13.037037037036953,4.444444444444457]],[11696351847604335156,[54.81481481481478,-42.37037037037035]],[18226408581696793441,[-41.185185185185105,9.7777777777778]],[10686631697241750410,[29.333333333333258,-11.55555555555555]],[3809009367670994230,[-14.222222222222172,0.8888888888888857]]],\"stroke\":[[13496648265229692336,0],[3809009367670994230,0],[10686631697241750410,0],[16628058317667581864,0],[11696351847604335156,0],[18226408581696793441,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[[{\"ty\":\"End\",\"segment\":13496648265229692336},{\"ty\":\"Primary\",\"segment\":10686631697241750410}],[{\"ty\":\"End\",\"segment\":16628058317667581864},{\"ty\":\"Primary\",\"segment\":3809009367670994230}],[{\"ty\":\"End\",\"segment\":10686631697241750410},{\"ty\":\"Primary\",\"segment\":11696351847604335156}],[{\"ty\":\"End\",\"segment\":18226408581696793441},{\"ty\":\"Primary\",\"segment\":16628058317667581864}],[{\"ty\":\"End\",\"segment\":3809009367670994230},{\"ty\":\"Primary\",\"segment\":13496648265229692336}]],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4560146526699152877,{\"inputs\":[{\"Node\":{\"node_id\":5486211022469996717,\"output_index\":0}},{\"Node\":{\"node_id\":13646498613066619660,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[57390435731316553,{\"inputs\":[{\"Node\":{\"node_id\":8460565235419043665,\"output_index\":0}},{\"Node\":{\"node_id\":13838011362258067867,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16785043320296790229,{\"inputs\":[{\"Node\":{\"node_id\":1009114585722052052,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":20.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeAlign\":\"Center\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeCap\":\"Butt\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeJoin\":\"Miter\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"PaintOrder\":\"StrokeAbove\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"VecF64\":[]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::StrokeNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9808637865669223270,{\"inputs\":[{\"Node\":{\"node_id\":140396870212231820,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.101960786,\"green\":0.13333334,\"blue\":0.13725491,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.101960786,\"green\":0.13333334,\"blue\":0.13725491,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16177422101884031678,{\"inputs\":[{\"Node\":{\"node_id\":13353438235848911576,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.101960786,\"green\":0.13333334,\"blue\":0.13725491,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.101960786,\"green\":0.13333334,\"blue\":0.13725491,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11201759760883367635,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[5173348374891662425,12314285668680405002,7388288026322342161,12796056970187696453],\"remove\":[],\"delta\":[[12314285668680405002,[1085.0,181.0]],[12796056970187696453,[1418.0,0.0]],[7388288026322342161,[1277.0,319.0]],[5173348374891662425,[969.0,0.0]]]},\"segments\":{\"add\":[10860608091363771974,9177623211202289793,18091265337503274797,9065140293539496953],\"remove\":[],\"start_point\":[[9065140293539496953,12796056970187696453],[9177623211202289793,12314285668680405002],[10860608091363771974,5173348374891662425],[18091265337503274797,7388288026322342161]],\"end_point\":[[18091265337503274797,12796056970187696453],[9177623211202289793,7388288026322342161],[10860608091363771974,12314285668680405002],[9065140293539496953,5173348374891662425]],\"handle_primary\":[[9177623211202289793,[-33.0,137.0]],[18091265337503274797,[178.0,-35.0]],[10860608091363771974,[0.0,0.0]],[9065140293539496953,[0.0,0.0]]],\"handle_end\":[[18091265337503274797,[168.99998492988766,81.0]],[10860608091363771974,[25.066898104916696,-104.0656072840481]],[9177623211202289793,[-77.81941867085992,15.301571086966838]],[9065140293539496953,[0.0,0.0]]],\"stroke\":[[9065140293539496953,0],[10860608091363771974,0],[9177623211202289793,0],[18091265337503274797,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[[{\"ty\":\"End\",\"segment\":9177623211202289793},{\"ty\":\"Primary\",\"segment\":18091265337503274797}],[{\"ty\":\"End\",\"segment\":10860608091363771974},{\"ty\":\"Primary\",\"segment\":9177623211202289793}]],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10760002922115563021,{\"inputs\":[{\"Node\":{\"node_id\":14257963317028524134,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"BooleanOperation\":\"SubtractFront\"},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::style::Fill\",\"alias\":null}},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"path_bool_nodes::BooleanOperationNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13588160462734303101,{\"inputs\":[{\"Node\":{\"node_id\":17036604842139972912,\"output_index\":0}},{\"Node\":{\"node_id\":4301099429811409147,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11373527190663101881,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[12058665768506126331,6802441093413090533,4854280308383915721,17729943149159368459,7436032950540776377,2189722519288419244,15817054697695831753,15744271344233846756,13877679667762731651,13567509413382199058,5664077098702810122,14178520291283306679,15575201098426093294],\"remove\":[],\"delta\":[[7436032950540776377,[815.4074074074074,203.25925925925927]],[15817054697695831753,[806.716049382716,206.22222222222223]],[13567509413382199058,[702.6831275720166,195.95061728395063]],[15575201098426093294,[636.7407407407406,173.33333333333331]],[5664077098702810122,[686.5185185185185,191.1111111111111]],[17729943149159368459,[801.1851851851852,199.90123456790124]],[13877679667762731651,[743.4074074074074,234.66666666666669]],[15744271344233846756,[785.7777777777777,205.62962962962965]],[4854280308383915721,[813.9259259259259,200.2962962962963]],[2189722519288419244,[796.0493827160494,202.2716049382716]],[12058665768506126331,[693.7283950617283,174.35390946502056]],[14178520291283306679,[668.4444444444443,183.1111111111111]],[6802441093413090533,[783.4074074074074,172.74074074074073]]]},\"segments\":{\"add\":[2336956120616260883,1639796012074210121,3918280827204321712,13932755083907220179,14941832641240060361,94092576912261794,18299135553255571870,3109091330258982137,11037018395008586448,16261506098907231989,8279051592565434787,2367379450383978497,5102625335252315850],\"remove\":[],\"start_point\":[[16261506098907231989,13567509413382199058],[2367379450383978497,14178520291283306679],[1639796012074210121,6802441093413090533],[14941832641240060361,7436032950540776377],[94092576912261794,2189722519288419244],[11037018395008586448,13877679667762731651],[13932755083907220179,17729943149159368459],[3918280827204321712,4854280308383915721],[5102625335252315850,15575201098426093294],[18299135553255571870,15817054697695831753],[3109091330258982137,15744271344233846756],[2336956120616260883,12058665768506126331],[8279051592565434787,5664077098702810122]],\"end_point\":[[13932755083907220179,7436032950540776377],[8279051592565434787,14178520291283306679],[1639796012074210121,4854280308383915721],[16261506098907231989,5664077098702810122],[2367379450383978497,15575201098426093294],[18299135553255571870,15744271344233846756],[11037018395008586448,13567509413382199058],[94092576912261794,15817054697695831753],[3918280827204321712,17729943149159368459],[3109091330258982137,13877679667762731651],[2336956120616260883,6802441093413090533],[5102625335252315850,12058665768506126331],[14941832641240060361,2189722519288419244]],\"handle_primary\":[[13932755083907220179,[0.0,0.0]],[1639796012074210121,[0.0,0.0]],[14941832641240060361,[0.0,0.0]],[2336956120616260883,[0.0,0.0]],[94092576912261794,[0.0,0.0]],[2367379450383978497,[-5.037037037036953,-1.7777777777777717]],[18299135553255571870,[0.0,0.0]],[16261506098907231989,[0.0,0.0]],[11037018395008586448,[0.0,0.0]],[3109091330258982137,[0.0,0.0]],[8279051592565434787,[-16.75720164609038,-2.1399176954732297]],[5102625335252315850,[0.0,0.0]],[3918280827204321712,[0.0,0.0]]],\"handle_end\":[[8279051592565434787,[5.037037037036953,1.7777777777777717]],[14941832641240060361,[7.1111111111111995,1.7777777777778]],[16261506098907231989,[12.1395654462896,1.5502392023748983]],[94092576912261794,[0.0,0.0]],[18299135553255571870,[10.07407407407402,2.7654320987654444]],[3109091330258982137,[18.962962962963047,2.074074074074048]],[11037018395008586448,[13.168724279835374,22.12345679012344]],[2367379450383978497,[12.740740740740648,5.037037037037038]],[13932755083907220179,[0.0,0.0]],[5102625335252315850,[-27.12757201646093,2.633744855967052]],[2336956120616260883,[-65.77777777777783,13.333333333333314]],[1639796012074210121,[-19.259259259259352,-3.259259259259238]],[3918280827204321712,[0.0,0.0]]],\"stroke\":[[2336956120616260883,0],[94092576912261794,0],[14941832641240060361,0],[13932755083907220179,0],[2367379450383978497,0],[8279051592565434787,0],[3109091330258982137,0],[11037018395008586448,0],[5102625335252315850,0],[18299135553255571870,0],[3918280827204321712,0],[16261506098907231989,0],[1639796012074210121,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[[{\"ty\":\"End\",\"segment\":94092576912261794},{\"ty\":\"Primary\",\"segment\":18299135553255571870}],[{\"ty\":\"End\",\"segment\":3918280827204321712},{\"ty\":\"Primary\",\"segment\":13932755083907220179}],[{\"ty\":\"End\",\"segment\":13932755083907220179},{\"ty\":\"Primary\",\"segment\":14941832641240060361}],[{\"ty\":\"End\",\"segment\":8279051592565434787},{\"ty\":\"Primary\",\"segment\":2367379450383978497}],[{\"ty\":\"End\",\"segment\":16261506098907231989},{\"ty\":\"Primary\",\"segment\":8279051592565434787}]],\"remove_g1_continuous\":[[{\"ty\":\"Primary\",\"segment\":16261506098907231989},{\"ty\":\"End\",\"segment\":11037018395008586448}]]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9563008199132558110,{\"inputs\":[{\"Node\":{\"node_id\":7855094781869605606,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.8509804,\"green\":0.24313726,\"blue\":0.18431373,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.8509804,\"green\":0.24313726,\"blue\":0.18431373,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[18081743490344004315,{\"inputs\":[{\"Node\":{\"node_id\":13588160462734303101,\"output_index\":0}},{\"Node\":{\"node_id\":18068340617333437755,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6852799892628327372,{\"inputs\":[{\"Node\":{\"node_id\":2025899804080897524,\"output_index\":0}},{\"Node\":{\"node_id\":16671141883125519098,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17952673493105230490,{\"inputs\":[{\"Node\":{\"node_id\":14228923746783465609,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.101960786,\"green\":0.13333334,\"blue\":0.13725491,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":20.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeAlign\":\"Center\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeCap\":\"Butt\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeJoin\":\"Miter\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"PaintOrder\":\"StrokeAbove\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"VecF64\":[]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::StrokeNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8863346544623578893,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[5480246971175127548,949359986970918930,6461719056721357962,6265491311473200676],\"remove\":[947514826240006203],\"delta\":[[5480246971175127548,[364.0,767.9999999999999]],[6461719056721357962,[173.33333333333331,506.66666666666674]],[949359986970918930,[0.0,608.0]],[6265491311473200676,[-2.273736754432321e-13,768.0000000000001]]]},\"segments\":{\"add\":[8951068186878308228,9717584104793611782,15710828508798332384,12480300844056666979],\"remove\":[11231973846952426191,15926511461198813522,6561081452252813685],\"start_point\":[[8951068186878308228,6461719056721357962],[15710828508798332384,5480246971175127548],[9717584104793611782,949359986970918930],[12480300844056666979,6265491311473200676]],\"end_point\":[[15710828508798332384,6265491311473200676],[9717584104793611782,6461719056721357962],[12480300844056666979,949359986970918930],[8951068186878308228,5480246971175127548]],\"handle_primary\":[[8951068186878308228,[121.00000000000006,-37.99999999999994]],[12480300844056666979,null],[15710828508798332384,null],[9717584104793611782,[0.0,0.0]]],\"handle_end\":[[15710828508798332384,null],[8951068186878308228,[-92.0,-81.99999999999989]],[9717584104793611782,[-63.1770926995265,19.84073985604961]],[12480300844056666979,null]],\"stroke\":[[8951068186878308228,0],[15710828508798332384,0],[12480300844056666979,0],[9717584104793611782,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[[{\"ty\":\"End\",\"segment\":6561081452252813685},{\"ty\":\"Primary\",\"segment\":9717584104793611782}],[{\"ty\":\"Primary\",\"segment\":9717584104793611782},{\"ty\":\"End\",\"segment\":12480300844056666979}],[{\"ty\":\"Primary\",\"segment\":8951068186878308228},{\"ty\":\"End\",\"segment\":9717584104793611782}]],\"remove_g1_continuous\":[[{\"ty\":\"Primary\",\"segment\":11231973846952426191},{\"ty\":\"End\",\"segment\":9717584104793611782}],[{\"ty\":\"End\",\"segment\":15926511461198813522},{\"ty\":\"Primary\",\"segment\":8951068186878308228}]]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15692102598187739001,{\"inputs\":[{\"Node\":{\"node_id\":14950060858756810933,\"output_index\":0}},{\"Node\":{\"node_id\":11201759760883367635,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4421418468606442725,{\"inputs\":[{\"Node\":{\"node_id\":8863346544623578893,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.93333334,\"green\":0.8627451,\"blue\":0.7254902,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.93333334,\"green\":0.8627451,\"blue\":0.7254902,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4807760870555738383,{\"inputs\":[{\"Node\":{\"node_id\":876963243827503916,\"output_index\":0}},{\"Node\":{\"node_id\":8269257328703012432,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4102754869474520966,{\"inputs\":[{\"Node\":{\"node_id\":2723198387862533596,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"BooleanOperation\":\"Intersect\"},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::style::Fill\",\"alias\":null}},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"path_bool_nodes::BooleanOperationNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2880630606834119505,{\"inputs\":[{\"Node\":{\"node_id\":8297015715799006244,\"output_index\":0}},{\"Node\":{\"node_id\":16322546010403524636,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10265035897167064154,{\"inputs\":[{\"Node\":{\"node_id\":14698962747138962125,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.101960786,\"green\":0.13333334,\"blue\":0.13725491,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.101960786,\"green\":0.13333334,\"blue\":0.13725491,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17131529656312051452,{\"inputs\":[{\"Node\":{\"node_id\":13646498613066619660,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.92156863,\"green\":0.6039216,\"blue\":0.18039216,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.92156863,\"green\":0.6039216,\"blue\":0.18039216,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7104088139635280554,{\"inputs\":[{\"Node\":{\"node_id\":16785043320296790229,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SolidifyStrokeNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[18068340617333437755,{\"inputs\":[{\"Node\":{\"node_id\":2880630606834119505,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[73.0306419396,-200.8521460039]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":24.404620143799995},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.9999999999999998,0.9999999999999998]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2185437945364824599,{\"inputs\":[{\"Node\":{\"node_id\":18081743490344004315,\"output_index\":0}},{\"Node\":{\"node_id\":4421418468606442725,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1627123781166851142,{\"inputs\":[{\"Node\":{\"node_id\":4807760870555738383,\"output_index\":0}},{\"Node\":{\"node_id\":15354358358546908017,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14141479077115894852,{\"inputs\":[{\"Node\":{\"node_id\":15433707377961038695,\"output_index\":0}},{\"Node\":{\"node_id\":17285637344898461972,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17397123104674848450,{\"inputs\":[{\"Node\":{\"node_id\":1662641269094032596,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"BooleanOperation\":\"Intersect\"},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::style::Fill\",\"alias\":null}},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"path_bool_nodes::BooleanOperationNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15877481873925059044,{\"inputs\":[{\"Node\":{\"node_id\":17299978721726771610,\"output_index\":0}},{\"Node\":{\"node_id\":7252918969430566594,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13340751444307201866,{\"inputs\":[{\"Node\":{\"node_id\":9135110142507605216,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.93333334,\"green\":0.8627451,\"blue\":0.7254902,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.93333334,\"green\":0.8627451,\"blue\":0.7254902,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12741076678082295759,{\"inputs\":[{\"Node\":{\"node_id\":14139765080256493579,\"output_index\":0}},{\"Node\":{\"node_id\":5449860184735415958,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13916027199283115943,{\"inputs\":[{\"Node\":{\"node_id\":6194305264313730032,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.101960786,\"green\":0.13333334,\"blue\":0.13725491,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15930698052919171086,{\"inputs\":[{\"Node\":{\"node_id\":13739729101529293427,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"BooleanOperation\":\"SubtractFront\"},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::style::Fill\",\"alias\":null}},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"path_bool_nodes::BooleanOperationNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4631655038168471552,{\"inputs\":[{\"Node\":{\"node_id\":990192925663920333,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::BoundingBoxNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17223836790030950966,{\"inputs\":[{\"Node\":{\"node_id\":14030142873804552388,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.8509804,\"green\":0.24313726,\"blue\":0.18431373,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.8509804,\"green\":0.24313726,\"blue\":0.18431373,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17056531964793634106,{\"inputs\":[{\"Node\":{\"node_id\":990192925663920333,\"output_index\":0}},{\"Node\":{\"node_id\":10063704933309776584,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13446205009526451196,{\"inputs\":[{\"Node\":{\"node_id\":14139765080256493579,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.92156863,\"green\":0.6039216,\"blue\":0.18039216,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.92156863,\"green\":0.6039216,\"blue\":0.18039216,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15433707377961038695,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[16723991032614525258,1623125309784127684,7209807874503344461,3123395482103162919,2668496811386192321],\"remove\":[12344391980636687302],\"delta\":[[1623125309784127684,[643.9258958566198,633.6997633965701]],[2668496811386192321,[0.0,768.0]],[7209807874503344461,[188.0,694.6666666666669]],[3123395482103162919,[-2.273736754432321e-13,689.0]],[16723991032614525258,[553.0000000000001,768.0]]]},\"segments\":{\"add\":[3656277643115996070,6789459806904610761,16287649713110748258,16456150086799119359,1652964032977338610],\"remove\":[1848051273241122771],\"start_point\":[[3656277643115996070,16723991032614525258],[6789459806904610761,1623125309784127684],[1652964032977338610,7209807874503344461],[16456150086799119359,2668496811386192321],[16287649713110748258,3123395482103162919]],\"end_point\":[[16456150086799119359,16723991032614525258],[16287649713110748258,2668496811386192321],[1652964032977338610,3123395482103162919],[3656277643115996070,1623125309784127684],[6789459806904610761,7209807874503344461]],\"handle_primary\":[[3656277643115996070,[0.0,0.0]],[6789459806904610761,[-213.92589585661983,-68.82675604113183]],[1652964032977338610,[-81.48609837852206,84.6759456460087]],[16287649713110748258,[0.0,0.0]],[16456150086799119359,[0.0,0.0]]],\"handle_end\":[[16456150086799119359,[0.0,0.0]],[3656277643115996070,[0.0,0.0]],[1652964032977338610,[56.00000000000023,11.0]],[6789459806904610761,[187.33333333333343,-194.6666666666667]],[16287649713110748258,[0.0,0.0]]],\"stroke\":[[1652964032977338610,0],[6789459806904610761,0],[16287649713110748258,0],[3656277643115996070,0],[16456150086799119359,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[[{\"ty\":\"End\",\"segment\":6789459806904610761},{\"ty\":\"Primary\",\"segment\":1652964032977338610}],[{\"ty\":\"End\",\"segment\":16287649713110748258},{\"ty\":\"Primary\",\"segment\":16456150086799119359}]],\"remove_g1_continuous\":[[{\"ty\":\"End\",\"segment\":3656277643115996070},{\"ty\":\"Primary\",\"segment\":6789459806904610761}],[{\"ty\":\"End\",\"segment\":1652964032977338610},{\"ty\":\"Primary\",\"segment\":1848051273241122771}]]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5877930116725120460,{\"inputs\":[{\"Node\":{\"node_id\":4470272391975492611,\"output_index\":0}},{\"Node\":{\"node_id\":1258994191538244490,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14516211820212764316,{\"inputs\":[{\"Node\":{\"node_id\":2124231869409556689,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,416.7]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":31.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":8},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"repeat_nodes::repeat_nodes::RepeatArrayNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3997031659711823213,{\"inputs\":[{\"Node\":{\"node_id\":6484183251661832039,\"output_index\":0}},{\"Node\":{\"node_id\":7962101329808960965,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3471746866096043087,{\"inputs\":[{\"Node\":{\"node_id\":5486211022469996717,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.3137255,\"green\":0.44705883,\"blue\":0.45490196,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7480253252288032958,{\"inputs\":[{\"Node\":{\"node_id\":7104261880154687267,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.8509804,\"green\":0.24313726,\"blue\":0.18431373,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.8509804,\"green\":0.24313726,\"blue\":0.18431373,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3079923906392020295,{\"inputs\":[{\"Node\":{\"node_id\":17056531964793634106,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4809200889774783438,{\"inputs\":[{\"Node\":{\"node_id\":3109716240255919254,\"output_index\":0}},{\"Node\":{\"node_id\":14139129879376457893,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16304636129468583592,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[4648964341912884959,10156053545530752213,16569120368910754547],\"remove\":[],\"delta\":[[16569120368910754547,[1361.0,768.0]],[4648964341912884959,[665.0000000000001,768.0]],[10156053545530752213,[1094.162353515625,594.2965698242188]]]},\"segments\":{\"add\":[9030015329489789075,2197140374690997530,3433930674303663828],\"remove\":[],\"start_point\":[[2197140374690997530,10156053545530752213],[9030015329489789075,4648964341912884959],[3433930674303663828,16569120368910754547]],\"end_point\":[[2197140374690997530,16569120368910754547],[3433930674303663828,4648964341912884959],[9030015329489789075,10156053545530752213]],\"handle_primary\":[[2197140374690997530,[79.20069951994813,30.807024746222492]],[9030015329489789075,[32.66666666666663,-95.99999999999989]],[3433930674303663828,[0.0,0.0]]],\"handle_end\":[[3433930674303663828,[0.0,0.0]],[2197140374690997530,[-24.333333333333485,-86.99999999999977]],[9030015329489789075,[-238.88331323750492,-92.9194336284096]]],\"stroke\":[[9030015329489789075,0],[2197140374690997530,0],[3433930674303663828,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[[{\"ty\":\"End\",\"segment\":9030015329489789075},{\"ty\":\"Primary\",\"segment\":2197140374690997530}]],\"remove_g1_continuous\":[[{\"ty\":\"Primary\",\"segment\":9030015329489789075},{\"ty\":\"End\",\"segment\":3433930674303663828}],[{\"ty\":\"End\",\"segment\":2197140374690997530},{\"ty\":\"Primary\",\"segment\":3433930674303663828}]]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2871608309888343463,{\"inputs\":[{\"Node\":{\"node_id\":12494428953087324640,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,200.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":8},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"repeat_nodes::repeat_nodes::RepeatArrayNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5488285068107445023,{\"inputs\":[{\"Node\":{\"node_id\":7667878689218439065,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"BooleanOperation\":\"SubtractFront\"},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::style::Fill\",\"alias\":null}},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"path_bool_nodes::BooleanOperationNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3050731459444225191,{\"inputs\":[{\"Node\":{\"node_id\":682567808439406093,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,200.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":12},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"repeat_nodes::repeat_nodes::RepeatArrayNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16306737306999003555,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[6082833770141706533,7903705226822768120,17147466439590042359,13341364271767916194],\"remove\":[],\"delta\":[[17147466439590042359,[526.0109927536641,282.1042175292969]],[13341364271767916194,[262.22222222222223,364.1481481481481]],[6082833770141706533,[504.0987654320987,184.6255144032922]],[7903705226822768120,[497.3827160493826,248.88888888888889]]]},\"segments\":{\"add\":[14649965831690031908,801877719748058643,9659144961849433642,2255088856072565305],\"remove\":[],\"start_point\":[[14649965831690031908,6082833770141706533],[2255088856072565305,13341364271767916194],[9659144961849433642,17147466439590042359],[801877719748058643,7903705226822768120]],\"end_point\":[[9659144961849433642,13341364271767916194],[801877719748058643,17147466439590042359],[14649965831690031908,7903705226822768120],[2255088856072565305,6082833770141706533]],\"handle_primary\":[[14649965831690031908,[0.0,0.0]],[2255088856072565305,[0.0,0.0]],[9659144961849433642,[0.0,0.0]],[801877719748058643,[13.173601585124231,23.615912208504938]]],\"handle_end\":[[9659144961849433642,[195.55555555555569,-16.14814814814804]],[801877719748058643,[0.0,0.0]],[14649965831690031908,[-17.920501046401128,-32.12553353079403]],[2255088856072565305,[-112.09876543209862,43.81893004115227]]],\"stroke\":[[2255088856072565305,0],[801877719748058643,0],[9659144961849433642,0],[14649965831690031908,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[[{\"ty\":\"End\",\"segment\":14649965831690031908},{\"ty\":\"Primary\",\"segment\":801877719748058643}],[{\"ty\":\"End\",\"segment\":801877719748058643},{\"ty\":\"Primary\",\"segment\":9659144961849433642}]],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8297015715799006244,{\"inputs\":[{\"Node\":{\"node_id\":17569892869974995307,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,407.5]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":24.2},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":8},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"repeat_nodes::repeat_nodes::RepeatArrayNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12867379765049504290,{\"inputs\":[{\"Node\":{\"node_id\":6445954214067437701,\"output_index\":0}},{\"Node\":{\"node_id\":8863346544623578893,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17740496701763775226,{\"inputs\":[{\"Node\":{\"node_id\":13027689870767713939,\"output_index\":0}},{\"Node\":{\"node_id\":16609137733952262762,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14808063168960305551,{\"inputs\":[{\"Node\":{\"node_id\":14496934933990319842,\"output_index\":0}},{\"Node\":{\"node_id\":3471746866096043087,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12494428953087324640,{\"inputs\":[{\"Node\":{\"node_id\":8511737864852441844,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[300.0,50.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":12},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"repeat_nodes::repeat_nodes::RepeatArrayNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15817956847588799375,{\"inputs\":[{\"Node\":{\"node_id\":11201759760883367635,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.3137255,\"green\":0.44705883,\"blue\":0.45490196,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.3137255,\"green\":0.44705883,\"blue\":0.45490196,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2287485748649359627,{\"inputs\":[{\"Node\":{\"node_id\":804622576568168609,\"output_index\":0}},{\"Node\":{\"node_id\":5449860184735415958,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13646498613066619660,{\"inputs\":[{\"Node\":{\"node_id\":12741076678082295759,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"BooleanOperation\":\"Intersect\"},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::style::Fill\",\"alias\":null}},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"path_bool_nodes::BooleanOperationNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1809704172129195322,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":20.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::generator_nodes::CircleNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2124231869409556689,{\"inputs\":[{\"Node\":{\"node_id\":15877481873925059044,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[876963243827503916,{\"inputs\":[{\"Node\":{\"node_id\":7651693425519490419,\"output_index\":0}},{\"Node\":{\"node_id\":17223836790030950966,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13838011362258067867,{\"inputs\":[{\"Node\":{\"node_id\":4631655038168471552,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"CentroidType\":\"Area\"},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CentroidNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7376049709233607419,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[16958489363675356032,7347160684180114694,4322398537034510509,3438797896265725901,16005438712872589849,1674257131044231065,10491326691824745018,9374180853656949931,13358295350421759755,15245407364463454564],\"remove\":[],\"delta\":[[16005438712872589849,[817.1851851851852,94.61728395061728]],[10491326691824745018,[811.851851851852,148.54320987654322]],[3438797896265725901,[795.4567901234569,94.41975308641976]],[1674257131044231065,[829.4320987654323,121.67901234567904]],[13358295350421759755,[778.4691358024693,177.1851851851852]],[4322398537034510509,[776.888888888889,117.53086419753087]],[15245407364463454564,[773.530864197531,141.4320987654321]],[16958489363675356032,[658.172839506173,169.08641975308643]],[9374180853656949931,[784.0000000000002,176.98765432098767]],[7347160684180114694,[772.1481481481483,138.66666666666669]]]},\"segments\":{\"add\":[11663644781855838202,198953627342130434,8327087349631976772,13015157349146777770,8528911024810728168,3235570761188242773,16847383896493391194,16846650637386857857,3784798568712268630,12873317883441611394],\"remove\":[],\"start_point\":[[12873317883441611394,15245407364463454564],[16846650637386857857,9374180853656949931],[3235570761188242773,1674257131044231065],[8528911024810728168,16005438712872589849],[8327087349631976772,4322398537034510509],[198953627342130434,7347160684180114694],[13015157349146777770,3438797896265725901],[11663644781855838202,16958489363675356032],[16847383896493391194,10491326691824745018],[3784798568712268630,13358295350421759755]],\"end_point\":[[12873317883441611394,16958489363675356032],[16847383896493391194,9374180853656949931],[3235570761188242773,10491326691824745018],[198953627342130434,4322398537034510509],[8528911024810728168,1674257131044231065],[8327087349631976772,3438797896265725901],[16846650637386857857,13358295350421759755],[13015157349146777770,16005438712872589849],[3784798568712268630,15245407364463454564],[11663644781855838202,7347160684180114694]],\"handle_primary\":[[198953627342130434,[0.0,0.3950617283950635]],[8327087349631976772,[-0.19753086419757435,-13.82716049382715]],[11663644781855838202,[0.0,0.0]],[16847383896493391194,[-17.185185185185105,13.234567901234584]],[12873317883441611394,[-19.75308641975323,2.172839506172835]],[16846650637386857857,[0.0,0.0]],[3784798568712268630,[-2.962962962963161,-10.864197530864232]],[3235570761188242773,[0.1975308641974607,7.703703703703709]],[13015157349146777770,[0.0,0.0]],[8528911024810728168,[5.530864197530946,5.925925925925924]]],\"handle_end\":[[13015157349146777770,[-5.530864197530946,-5.925925925925924]],[12873317883441611394,[33.382716049382566,14.222222222222172]],[3235570761188242773,[17.185185185185105,-13.234567901234584]],[8327087349631976772,[0.0,0.0]],[16846650637386857857,[0.0,0.0]],[8528911024810728168,[-0.1975308641974607,-7.703703703703709]],[3784798568712268630,[2.9629629629629335,5.135802469135797]],[198953627342130434,[0.19753086419757435,13.82716049382715]],[11663644781855838202,[-74.27160493827171,29.03703703703698]],[16847383896493391194,[0.0,0.0]]],\"stroke\":[[16846650637386857857,0],[8528911024810728168,0],[16847383896493391194,0],[11663644781855838202,0],[13015157349146777770,0],[198953627342130434,0],[3784798568712268630,0],[12873317883441611394,0],[8327087349631976772,0],[3235570761188242773,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[[{\"ty\":\"End\",\"segment\":198953627342130434},{\"ty\":\"Primary\",\"segment\":8327087349631976772}],[{\"ty\":\"End\",\"segment\":13015157349146777770},{\"ty\":\"Primary\",\"segment\":8528911024810728168}],[{\"ty\":\"End\",\"segment\":8528911024810728168},{\"ty\":\"Primary\",\"segment\":3235570761188242773}],[{\"ty\":\"End\",\"segment\":16847383896493391194},{\"ty\":\"Primary\",\"segment\":16846650637386857857}],[{\"ty\":\"End\",\"segment\":3235570761188242773},{\"ty\":\"Primary\",\"segment\":16847383896493391194}],[{\"ty\":\"End\",\"segment\":8327087349631976772},{\"ty\":\"Primary\",\"segment\":13015157349146777770}]],\"remove_g1_continuous\":[[{\"ty\":\"End\",\"segment\":3784798568712268630},{\"ty\":\"Primary\",\"segment\":12873317883441611394}],[{\"ty\":\"End\",\"segment\":16846650637386857857},{\"ty\":\"Primary\",\"segment\":3784798568712268630}]]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7977952035097419157,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.5},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.5},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::generator_nodes::EllipseNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7667878689218439065,{\"inputs\":[{\"Node\":{\"node_id\":18271512507682813443,\"output_index\":0}},{\"Node\":{\"node_id\":5449860184735415958,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14030142873804552388,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[11163144542703672167,7233902871991446034,9403888920678312544,5714042674660607880,9912917483675332510],\"remove\":[],\"delta\":[[11163144542703672167,[763.0,0.0]],[9912917483675332510,[1536.0,254.00000000000009]],[7233902871991446034,[908.0,134.0]],[9403888920678312544,[1271.0,199.0]],[5714042674660607880,[1536.0,0.0]]]},\"segments\":{\"add\":[7099980686025805826,11879054056208937348,7804540624589615499,3118709054343469746,12085471811343146506],\"remove\":[8174738144062904321],\"start_point\":[[7099980686025805826,11163144542703672167],[3118709054343469746,9403888920678312544],[12085471811343146506,9912917483675332510],[7804540624589615499,5714042674660607880],[11879054056208937348,7233902871991446034]],\"end_point\":[[12085471811343146506,5714042674660607880],[7804540624589615499,11163144542703672167],[3118709054343469746,9912917483675332510],[7099980686025805826,7233902871991446034],[11879054056208937348,9403888920678312544]],\"handle_primary\":[[11879054056208937348,[68.41365603453937,54.51084151791355]],[3118709054343469746,[191.0,-106.0]],[7804540624589615499,[0.0,0.0]],[7099980686025805826,[0.0,0.0]],[12085471811343146506,[0.0,0.0]]],\"handle_end\":[[11879054056208937348,[-121.83694366711715,67.61631428646292]],[12085471811343146506,[0.0,0.0]],[7099980686025805826,[-68.41365603453914,-54.51084151791349]],[7804540624589615499,[0.0,0.0]],[3118709054343469746,[0.0,0.0]]],\"stroke\":[[12085471811343146506,0],[3118709054343469746,0],[7804540624589615499,0],[7099980686025805826,0],[11879054056208937348,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[[{\"ty\":\"End\",\"segment\":11879054056208937348},{\"ty\":\"Primary\",\"segment\":8174738144062904321}],[{\"ty\":\"End\",\"segment\":7099980686025805826},{\"ty\":\"Primary\",\"segment\":11879054056208937348}],[{\"ty\":\"End\",\"segment\":11879054056208937348},{\"ty\":\"Primary\",\"segment\":3118709054343469746}]],\"remove_g1_continuous\":[[{\"ty\":\"End\",\"segment\":3118709054343469746},{\"ty\":\"Primary\",\"segment\":12085471811343146506}]]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[17740496701763775226,{\"persistent_metadata\":{\"display_name\":\"Bottom Blue\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11373527190663101881,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12276520439585231336,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\" px\",\"x\":\"X\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"unit\":\"x\",\"is_integer\":false,\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7977952035097419157,{\"persistent_metadata\":{\"display_name\":\"Ellipse\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Radius X\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Radius Y\",\"input_description\":\"\"}}],\"output_names\":[\"Table<Vector>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[17131529656312051452,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[7480253252288032958,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[18188505856445531484,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[14516211820212764316,{\"persistent_metadata\":{\"display_name\":\"Repeat\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Direction\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Angle\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instances\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[5954536408321808728,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7667878689218439065,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16304636129468583592,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-57,135]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[6484183251661832039,{\"persistent_metadata\":{\"display_name\":\"Repeat\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Direction\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Angle\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instances\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-64,93]}}},\"network_metadata\":null}}],[17569892869974995307,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Element\",\"input_description\":\"\"}}],\"output_names\":[\"Table<Graphic>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[7855094781869605606,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Operation\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-50,87]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Boolean Operation\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12234961922142600898,{\"persistent_metadata\":{\"display_name\":\"Top Right Black\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":9}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[6194305264313730032,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Operation\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Boolean Operation\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[18081743490344004315,{\"persistent_metadata\":{\"display_name\":\"Hair Dots Orange\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4301099429811409147,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"X\",\"y\":\"Y\",\"is_integer\":false,\"unit\":\" px\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\"x\",\"x\":\"W\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13203761224559198689,{\"persistent_metadata\":{\"display_name\":\"Front Eye Shadow\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[8511737864852441844,{\"persistent_metadata\":{\"display_name\":\"Circle\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Radius\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[2124231869409556689,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Element\",\"input_description\":\"\"}}],\"output_names\":[\"Table<Graphic>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[13795432594059356320,{\"persistent_metadata\":{\"display_name\":\"Flatten Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}}],\"output_names\":[\"Table<Vector>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[7201841978411396053,{\"persistent_metadata\":{\"display_name\":\"Bottom Black\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":2}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[1809704172129195322,{\"persistent_metadata\":{\"display_name\":\"Circle\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Radius\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-78,106]}}},\"network_metadata\":null}}],[12709602171929957216,{\"persistent_metadata\":{\"display_name\":\"Bottom Left Orange\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11264395591110193456,{\"persistent_metadata\":{\"display_name\":\"Circle\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Radius\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-78,93]}}},\"network_metadata\":null}}],[17735408893002232096,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[11210964267417873667,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"y\":\"Y\",\"unit\":\" px\",\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\"x\",\"is_integer\":false,\"x\":\"W\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15301503532602557206,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Operation\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Boolean Operation\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[6726954210929537972,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12922148192688274227,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"is_integer\":false,\"y\":\"Y\",\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"H\",\"x\":\"W\",\"unit\":\"x\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"Data\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[5488285068107445023,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Operation\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Boolean Operation\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2185437945364824599,{\"persistent_metadata\":{\"display_name\":\"Bottom Left White\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[17036604842139972912,{\"persistent_metadata\":{\"display_name\":\"Hair Dots Blue\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":4}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[5534800796196967885,{\"persistent_metadata\":{\"display_name\":\"Centroid\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Centroid Type\",\"input_description\":\"\"}}],\"output_names\":[\"DVec2\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-50,109]}}},\"network_metadata\":null}}],[1508440849951861669,{\"persistent_metadata\":{\"display_name\":\"Face Red\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14139129879376457893,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[8297015715799006244,{\"persistent_metadata\":{\"display_name\":\"Repeat\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Direction\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Angle\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instances\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[17740294143355019755,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[10063704933309776584,{\"persistent_metadata\":{\"display_name\":\"Multiply\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Multiplier\",\"input_description\":\"The left-hand side of the multiplication operation.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Multiplicand\",\"input_description\":\"The right-hand side of the multiplication operation.\\n\"}}],\"output_names\":[\"f64\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-50,100]}}},\"network_metadata\":null}}],[3050731459444225191,{\"persistent_metadata\":{\"display_name\":\"Repeat\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Direction\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Angle\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instances\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[13616602029989984195,{\"persistent_metadata\":{\"display_name\":\"Flatten Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}}],\"output_names\":[\"Table<Vector>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[13916027199283115943,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[5888633415105234509,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Operation\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Boolean Operation\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[17397123104674848450,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Operation\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Boolean Operation\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3997031659711823213,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"x\":\"X\",\"unit\":\" px\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\"x\",\"x\":\"W\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"Data\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12494428953087324640,{\"persistent_metadata\":{\"display_name\":\"Repeat\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Direction\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Angle\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instances\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[5014806436727666175,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3410481056630111806,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-71,99]}}},\"network_metadata\":null}}],[15692102598187739001,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-55,57]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4102754869474520966,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Operation\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Boolean Operation\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4470272391975492611,{\"persistent_metadata\":{\"display_name\":\"Rose Sliver Silhouette\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9135110142507605216,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Operation\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Boolean Operation\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[1627123781166851142,{\"persistent_metadata\":{\"display_name\":\"Top Right Corner Orange\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15817956847588799375,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[3214181946162459584,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[14139765080256493579,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-57,138]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[8863346544623578893,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-50,90]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[17056531964793634106,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"x\":\"X\",\"unit\":\" px\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"y\":\"H\",\"unit\":\"x\",\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"Data\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10995640810984321903,{\"persistent_metadata\":{\"display_name\":\"Lip Bottom\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2871608309888343463,{\"persistent_metadata\":{\"display_name\":\"Repeat\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Direction\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Angle\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instances\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[7018444885869143173,{\"persistent_metadata\":{\"display_name\":\"Top Right Black - Dots\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7755499790391969923,{\"persistent_metadata\":{\"display_name\":\"Circle\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Radius\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[13838011362258067867,{\"persistent_metadata\":{\"display_name\":\"Centroid\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Centroid Type\",\"input_description\":\"\"}}],\"output_names\":[\"DVec2\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-50,103]}}},\"network_metadata\":null}}],[17299978721726771610,{\"persistent_metadata\":{\"display_name\":\"Repeat\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Direction\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Angle\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instances\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-64,106]}}},\"network_metadata\":null}}],[16177422101884031678,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[8426490990601560741,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[1258994191538244490,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[57390435731316553,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\" px\",\"y\":\"Y\",\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"y\":\"H\",\"unit\":\"x\",\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"Data\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3471746866096043087,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[17562801632450633291,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12145355397916841389,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[11301831865756336526,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[18031616785650843168,{\"persistent_metadata\":{\"display_name\":\"Solidify Stroke\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-36,133]}}},\"network_metadata\":null}}],[9808637865669223270,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[8887924609778270360,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Operation\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Boolean Operation\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2880630606834119505,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"y\":\"Y\",\"x\":\"X\",\"unit\":\" px\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"is_integer\":false,\"unit\":\"x\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"Data\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[990192925663920333,{\"persistent_metadata\":{\"display_name\":\"Repeat\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Direction\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Angle\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instances\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-64,99]}}},\"network_metadata\":null}}],[18351415092709164412,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[6728362629909402903,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13588160462734303101,{\"persistent_metadata\":{\"display_name\":\"Hair Dots Red\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":3}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[6445954214067437701,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Operation\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Boolean Operation\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10080296672372912698,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Operation\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Boolean Operation\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15433707377961038695,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-64,87]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14808063168960305551,{\"persistent_metadata\":{\"display_name\":\"Face Blue\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4560146526699152877,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[8359580532088731394,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[14141479077115894852,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-57,87]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13739729101529293427,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[682567808439406093,{\"persistent_metadata\":{\"display_name\":\"Repeat\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Direction\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Angle\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instances\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[14257963317028524134,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9071802450034150503,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[\"Table<Vector>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[12770183061753030023,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[18003287685830153881,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-43,72]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11095670964487764044,{\"persistent_metadata\":{\"display_name\":\"Hair Strand 2\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3109716240255919254,{\"persistent_metadata\":{\"display_name\":\"Front Eyebrow\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10265035897167064154,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[16322546010403524636,{\"persistent_metadata\":{\"display_name\":\"Centroid\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Centroid Type\",\"input_description\":\"\"}}],\"output_names\":[\"DVec2\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-50,96]}}},\"network_metadata\":null}}],[17223836790030950966,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[5449860184735415958,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-57,124]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16759836951269190891,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Operation\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Boolean Operation\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[5556372312033787775,{\"persistent_metadata\":{\"display_name\":\"Bounding Box\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}}],\"output_names\":[\"Table<Vector>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-57,96]}}},\"network_metadata\":null}}],[3079923906392020295,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Element\",\"input_description\":\"\"}}],\"output_names\":[\"Table<Graphic>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[16306737306999003555,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16609137733952262762,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[3608604157153838227,{\"persistent_metadata\":{\"display_name\":\"Bounding Box\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}}],\"output_names\":[\"Table<Vector>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-57,109]}}},\"network_metadata\":null}}],[6616450276140292763,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12741076678082295759,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-43,118]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14030142873804552388,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-36,66]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4328376070224119511,{\"persistent_metadata\":{\"display_name\":\"Bottom Orange\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15900830679378240619,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Operation\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Boolean Operation\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[17495267820524300686,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"y\":\"Y\",\"is_integer\":false,\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"x\":\"W\",\"unit\":\"x\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15354358358546908017,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[5856350938151339368,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[804622576568168609,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15930698052919171086,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Operation\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Boolean Operation\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4807760870555738383,{\"persistent_metadata\":{\"display_name\":\"Top Right Main Orange\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14684142559936015947,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[16671141883125519098,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[11201759760883367635,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-62,64]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[140396870212231820,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Operation\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Boolean Operation\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10286817149456341619,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12353675714904258944,{\"persistent_metadata\":{\"display_name\":\"Nose Shadow\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13027689870767713939,{\"persistent_metadata\":{\"display_name\":\"Bottom Black - Dots\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14690269209726153565,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[16785043320296790229,{\"persistent_metadata\":{\"display_name\":\"Stroke\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the stroke style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Color\",\"input_description\":\"The stroke color.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Weight\",\"input_description\":\"The stroke weight.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Align\",\"input_description\":\"The alignment of stroke to the path's centerline or (for closed shapes) the inside or outside of the shape.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Cap\",\"input_description\":\"The shape of the stroke at open endpoints.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Join\",\"input_description\":\"The curvature of the bent stroke at sharp corners.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Miter Limit\",\"input_description\":\"The threshold for when a miter-joined stroke is converted to a bevel-joined stroke when a sharp angle becomes pointier than this ratio.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Paint Order\",\"input_description\":\"The order to paint the stroke on top of the fill, or the fill on top of the stroke.\\n<https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty>\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Lengths\",\"input_description\":\"The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Offset\",\"input_description\":\"The phase offset distance from the starting point of the dash pattern.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[9563008199132558110,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[1001728975241745659,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Operation\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Boolean Operation\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[8460565235419043665,{\"persistent_metadata\":{\"display_name\":\"Repeat\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Direction\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Angle\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instances\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[2025899804080897524,{\"persistent_metadata\":{\"display_name\":\"Face White\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9338394475379815879,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"Y\",\"x\":\"X\",\"is_integer\":false,\"unit\":\" px\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"is_integer\":false,\"y\":\"H\",\"unit\":\"x\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"Data\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[18271512507682813443,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15877481873925059044,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"X\",\"y\":\"Y\",\"is_integer\":false,\"unit\":\" px\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\"x\",\"is_integer\":false,\"x\":\"W\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"Data\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2723198387862533596,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[8566844905246185636,{\"persistent_metadata\":{\"display_name\":\"Mouth Orange\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[17952673493105230490,{\"persistent_metadata\":{\"display_name\":\"Stroke\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the stroke style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Color\",\"input_description\":\"The stroke color.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Weight\",\"input_description\":\"The stroke weight.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Align\",\"input_description\":\"The alignment of stroke to the path's centerline or (for closed shapes) the inside or outside of the shape.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Cap\",\"input_description\":\"The shape of the stroke at open endpoints.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Join\",\"input_description\":\"The curvature of the bent stroke at sharp corners.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Miter Limit\",\"input_description\":\"The threshold for when a miter-joined stroke is converted to a bevel-joined stroke when a sharp angle becomes pointier than this ratio.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Paint Order\",\"input_description\":\"The order to paint the stroke on top of the fill, or the fill on top of the stroke.\\n<https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty>\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Lengths\",\"input_description\":\"The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Offset\",\"input_description\":\"The phase offset distance from the starting point of the dash pattern.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-43,133]}}},\"network_metadata\":null}}],[17271572793812678706,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[13353438235848911576,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[876963243827503916,{\"persistent_metadata\":{\"display_name\":\"Top Right Red\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2834866505092039323,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13340751444307201866,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[17753909951719808506,{\"persistent_metadata\":{\"display_name\":\"Bottom Left Red\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[6503655938154160104,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Operation\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-36,47]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Boolean Operation\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[18095952297474762348,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[17696051535511578981,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-41,50]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[1491840484128555837,{\"persistent_metadata\":{\"display_name\":\"Face Shadow\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":3}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[8269257328703012432,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[4809200889774783438,{\"persistent_metadata\":{\"display_name\":\"Front Eye\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[1662641269094032596,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[6787585796949551500,{\"persistent_metadata\":{\"display_name\":\"Hair Strand 1\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-8,15]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7029437790788498388,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-71,106]}}},\"network_metadata\":null}}],[5486211022469996717,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-43,75]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12867379765049504290,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7962101329808960965,{\"persistent_metadata\":{\"display_name\":\"Multiply\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Multiplier\",\"input_description\":\"The left-hand side of the multiplication operation.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Multiplicand\",\"input_description\":\"The right-hand side of the multiplication operation.\\n\"}}],\"output_names\":[\"f64\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-50,94]}}},\"network_metadata\":null}}],[4771789845668099116,{\"persistent_metadata\":{\"display_name\":\"Cheek Shadow\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[294265135510952894,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-71,93]}}},\"network_metadata\":null}}],[18068340617333437755,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"y\":\"Y\",\"x\":\"X\",\"unit\":\" px\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"unit\":\"x\",\"y\":\"H\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13670206802546093234,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12185047359007423618,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[17351444026127625357,{\"persistent_metadata\":{\"display_name\":\"Face Black\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4421418468606442725,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[4261249487994076490,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"X\",\"is_integer\":false,\"unit\":\" px\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"is_integer\":false,\"y\":\"H\",\"unit\":\"x\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"Data\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14698962747138962125,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Operation\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Boolean Operation\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11196821089257149774,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14228923746783465609,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-50,135]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[5877930116725120460,{\"persistent_metadata\":{\"display_name\":\"Rear Eyelash\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2287485748649359627,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10760002922115563021,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Operation\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Boolean Operation\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11429506195623419966,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7252918969430566594,{\"persistent_metadata\":{\"display_name\":\"Multiply\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Multiplier\",\"input_description\":\"The left-hand side of the multiplication operation.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Multiplicand\",\"input_description\":\"The right-hand side of the multiplication operation.\\n\"}}],\"output_names\":[\"f64\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-50,107]}}},\"network_metadata\":null}}],[13446205009526451196,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[1009114585722052052,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Operation\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-48,57]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Boolean Operation\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7104261880154687267,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Operation\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Boolean Operation\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[18053728639616073084,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[1097494158696050491,{\"persistent_metadata\":{\"display_name\":\"Circle\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Radius\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-78,99]}}},\"network_metadata\":null}}],[4631655038168471552,{\"persistent_metadata\":{\"display_name\":\"Bounding Box\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}}],\"output_names\":[\"Table<Vector>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-57,103]}}},\"network_metadata\":null}}],[14950060858756810933,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-62,60]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[17603523494627491590,{\"persistent_metadata\":{\"display_name\":\"Lip Top\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3750439930725791025,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16780039553038473906,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7651693425519490419,{\"persistent_metadata\":{\"display_name\":\"Top Right Blue\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14496934933990319842,{\"persistent_metadata\":{\"display_name\":\"Bottom Left Blue\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[17285637344898461972,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-64,81]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[6852799892628327372,{\"persistent_metadata\":{\"display_name\":\"Head Orange\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[392274448837115448,{\"persistent_metadata\":{\"display_name\":\"Artboard\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Artboards\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Contents\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":true,\"x\":\"X\",\"unit\":\" px\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Location\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":true,\"unit\":\" px\",\"x\":\"W\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Dimensions\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"artboard_background\",\"input_name\":\"Background\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Clip\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-4,12]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Artboard\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-4]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Create Artboard\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13646498613066619660,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Operation\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-36,118]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Boolean Operation\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Boolean Operation\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7104088139635280554,{\"persistent_metadata\":{\"display_name\":\"Solidify Stroke\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[7376049709233607419,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[656.5700198973645,-785.4579096366515],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,1647.0,-205.0],\"node_graph_width\":1981.0},\"selection_undo_history\":[[294265135510952894],[],[],[],[13340751444307201866],[3765280235392282097],[3765280235392282097],[],[13795432594059356320],[],[2871608309888343463],[2871608309888343463,12494428953087324640],[3765280235392282097,12494428953087324640,2871608309888343463],[8511737864852441844,2871608309888343463,12494428953087324640,3765280235392282097],[],[13795432594059356320],[],[12494428953087324640,2871608309888343463],[2871608309888343463,3765280235392282097,12494428953087324640],[12494428953087324640,2871608309888343463,3765280235392282097,8511737864852441844],[],[3765280235392282097],[],[],[13616602029989984195],[],[],[],[16780039553038473906],[7104088139635280554],[],[],[7104088139635280554],[],[7104088139635280554],[],[],[16785043320296790229],[6503655938154160104],[17696051535511578981],[6503655938154160104],[17696051535511578981],[9071802450034150503],[],[17696051535511578981],[6503655938154160104],[],[18031616785650843168],[17952673493105230490],[],[4328376070224119511],[18031616785650843168,3750439930725791025,13446205009526451196,7201841978411396053,17735408893002232096,11196821089257149774,13027689870767713939,17952673493105230490,14228923746783465609,4328376070224119511],[13027689870767713939,16609137733952262762,12276520439585231336,13795432594059356320,7201841978411396053,17740496701763775226,13340751444307201866,5888633415105234509,17735408893002232096,9135110142507605216,3750439930725791025,11196821089257149774,17397123104674848450,13446205009526451196,4328376070224119511,14228923746783465609,1662641269094032596,18031616785650843168,17952673493105230490],[5888633415105234509,17740496701763775226,14228923746783465609,12276520439585231336,18031616785650843168,16306737306999003555,6852799892628327372,17952673493105230490,3750439930725791025,16671141883125519098,13446205009526451196,11196821089257149774,1662641269094032596,9135110142507605216,17397123104674848450,3214181946162459584,13027689870767713939,16609137733952262762,7201841978411396053,13795432594059356320,17735408893002232096,2871608309888343463,4328376070224119511,2025899804080897524,13340751444307201866],[17952673493105230490,13027689870767713939,13646498613066619660,14139765080256493579,3214181946162459584,13795432594059356320,1662641269094032596,16671141883125519098,17740496701763775226,16306737306999003555,17735408893002232096,14228923746783465609,4328376070224119511,12276520439585231336,9135110142507605216,11196821089257149774,2025899804080897524,17397123104674848450,5888633415105234509,7201841978411396053,16609137733952262762,2871608309888343463,16304636129468583592,13446205009526451196,17131529656312051452,8566844905246185636,5449860184735415958,3750439930725791025,18031616785650843168,12494428953087324640,12741076678082295759,6852799892628327372,13340751444307201866],[3750439930725791025,5888633415105234509,18031616785650843168,12494428953087324640,13340751444307201866,16306737306999003555,17952673493105230490,17397123104674848450,13795432594059356320,12741076678082295759,7201841978411396053,9135110142507605216,3214181946162459584,2871608309888343463,6852799892628327372,13646498613066619660,17740496701763775226,17131529656312051452,16671141883125519098,13446205009526451196,12276520439585231336,14139765080256493579,14228923746783465609,5449860184735415958,4328376070224119511,16609137733952262762,17735408893002232096,16304636129468583592,8566844905246185636,1662641269094032596,2025899804080897524,11196821089257149774,13027689870767713939,8511737864852441844],[13340751444307201866,5888633415105234509,14139765080256493579,8566844905246185636,13795432594059356320,16609137733952262762,5449860184735415958,13027689870767713939,6852799892628327372,3750439930725791025,8511737864852441844,11196821089257149774,12494428953087324640,17131529656312051452,16306737306999003555,2834866505092039323,17735408893002232096,13646498613066619660,18031616785650843168,12276520439585231336,17740496701763775226,3214181946162459584,12741076678082295759,16671141883125519098,9135110142507605216,14228923746783465609,2871608309888343463,16304636129468583592,2025899804080897524,17397123104674848450,17952673493105230490,1662641269094032596,7201841978411396053,13446205009526451196,4328376070224119511],[9135110142507605216,12276520439585231336,2871608309888343463,1508440849951861669,2025899804080897524,7201841978411396053,11196821089257149774,17740496701763775226,13446205009526451196,6852799892628327372,3214181946162459584,1662641269094032596,4328376070224119511,12741076678082295759,13340751444307201866,13795432594059356320,16671141883125519098,14139765080256493579,5888633415105234509,17735408893002232096,18031616785650843168,17131529656312051452,13027689870767713939,8566844905246185636,17952673493105230490,2834866505092039323,5449860184735415958,16304636129468583592,16609137733952262762,16306737306999003555,17397123104674848450,13646498613066619660,14228923746783465609,3750439930725791025,12494428953087324640,8511737864852441844],[17397123104674848450,13340751444307201866,12494428953087324640,13446205009526451196,3214181946162459584,17131529656312051452,16609137733952262762,16306737306999003555,12276520439585231336,2871608309888343463,4328376070224119511,16304636129468583592,17740496701763775226,7201841978411396053,2834866505092039323,9135110142507605216,8511737864852441844,5888633415105234509,13795432594059356320,17952673493105230490,7480253252288032958,11196821089257149774,5449860184735415958,14228923746783465609,3750439930725791025,13027689870767713939,17735408893002232096,1508440849951861669,8566844905246185636,18031616785650843168,13646498613066619660,14139765080256493579,7104261880154687267,2025899804080897524,12741076678082295759,1662641269094032596,6852799892628327372,16671141883125519098],[17952673493105230490,7104261880154687267,18031616785650843168,8566844905246185636,17735408893002232096,7201841978411396053,12741076678082295759,5449860184735415958,16609137733952262762,1662641269094032596,3214181946162459584,11196821089257149774,1508440849951861669,13340751444307201866,13446205009526451196,8511737864852441844,9135110142507605216,6852799892628327372,3750439930725791025,13795432594059356320,4328376070224119511,17131529656312051452,16304636129468583592,16306737306999003555,17740496701763775226,12494428953087324640,17397123104674848450,16671141883125519098,5888633415105234509,12276520439585231336,2834866505092039323,7480253252288032958,18095952297474762348,13646498613066619660,2871608309888343463,2025899804080897524,14228923746783465609,14139765080256493579,13027689870767713939],[1491840484128555837,7104261880154687267,17397123104674848450,8511737864852441844,13446205009526451196,13795432594059356320,14228923746783465609,7480253252288032958,2025899804080897524,17735408893002232096,16306737306999003555,17740496701763775226,12741076678082295759,13646498613066619660,4328376070224119511,12276520439585231336,18031616785650843168,3750439930725791025,3214181946162459584,2871608309888343463,5888633415105234509,16304636129468583592,13340751444307201866,1662641269094032596,11196821089257149774,5449860184735415958,16671141883125519098,13027689870767713939,17952673493105230490,6852799892628327372,17131529656312051452,14139765080256493579,1508440849951861669,18095952297474762348,8566844905246185636,12494428953087324640,16609137733952262762,2834866505092039323,9135110142507605216,7201841978411396053],[13027689870767713939,8511737864852441844,2871608309888343463,3214181946162459584,14228923746783465609,9808637865669223270,1508440849951861669,12494428953087324640,7201841978411396053,16671141883125519098,13340751444307201866,140396870212231820,1491840484128555837,13446205009526451196,14139765080256493579,5888633415105234509,2834866505092039323,7480253252288032958,17397123104674848450,17740496701763775226,1662641269094032596,18095952297474762348,16306737306999003555,16304636129468583592,17735408893002232096,12276520439585231336,16609137733952262762,4328376070224119511,13795432594059356320,7104261880154687267,5449860184735415958,17131529656312051452,13646498613066619660,6852799892628327372,3750439930725791025,2025899804080897524,9135110142507605216,5954536408321808728,11196821089257149774,12741076678082295759,8566844905246185636,17952673493105230490,18031616785650843168],[7252918969430566594,15877481873925059044,4261249487994076490,5534800796196967885,17036604842139972912],[5534800796196967885,11210964267417873667,4261249487994076490,17036604842139972912,2124231869409556689,7252918969430566594,14516211820212764316,15877481873925059044],[17036604842139972912,4261249487994076490,11210964267417873667,3608604157153838227,15877481873925059044,5534800796196967885,2124231869409556689,14516211820212764316,7252918969430566594],[14516211820212764316,4261249487994076490,11210964267417873667,7252918969430566594,15877481873925059044,17036604842139972912,5534800796196967885,3608604157153838227,2124231869409556689,17299978721726771610],[7252918969430566594,11210964267417873667,15877481873925059044,3608604157153838227,4261249487994076490,2124231869409556689,17299978721726771610,7029437790788498388,5534800796196967885,14516211820212764316,17036604842139972912],[4261249487994076490,17036604842139972912,5534800796196967885,17299978721726771610,7252918969430566594,3608604157153838227,14516211820212764316,11210964267417873667,1809704172129195322,2124231869409556689,15877481873925059044,7029437790788498388],[57390435731316553,13838011362258067867,17056531964793634106,4631655038168471552,13588160462734303101,10063704933309776584],[3410481056630111806,13838011362258067867,990192925663920333,57390435731316553,4631655038168471552,8460565235419043665,10063704933309776584,3079923906392020295,13588160462734303101,4301099429811409147,17056531964793634106],[57390435731316553,1097494158696050491,3410481056630111806,990192925663920333,8460565235419043665,13588160462734303101,13838011362258067867,17056531964793634106,4631655038168471552,3079923906392020295,4301099429811409147,10063704933309776584],[13588160462734303101],[4301099429811409147,8460565235419043665,13588160462734303101,57390435731316553],[3079923906392020295,17056531964793634106,4301099429811409147,8460565235419043665,57390435731316553,13588160462734303101],[57390435731316553,13588160462734303101,4301099429811409147,10063704933309776584,3079923906392020295,8460565235419043665,17056531964793634106],[4301099429811409147,990192925663920333,10063704933309776584,57390435731316553,17056531964793634106,3079923906392020295,8460565235419043665,13588160462734303101],[3410481056630111806,57390435731316553,990192925663920333,13588160462734303101,10063704933309776584,8460565235419043665,3079923906392020295,4301099429811409147,17056531964793634106],[3410481056630111806,13588160462734303101,57390435731316553,3079923906392020295,17056531964793634106,10063704933309776584,4301099429811409147,1097494158696050491,990192925663920333,8460565235419043665],[17056531964793634106,4631655038168471552,10063704933309776584,3410481056630111806,8460565235419043665,3079923906392020295,990192925663920333,1097494158696050491,13838011362258067867,13588160462734303101,4301099429811409147,57390435731316553],[11210964267417873667,4261249487994076490,14516211820212764316,17036604842139972912],[14516211820212764316,7252918969430566594,4261249487994076490,15877481873925059044,17036604842139972912,2124231869409556689,11210964267417873667],[7252918969430566594,17299978721726771610,15877481873925059044,4261249487994076490,17036604842139972912,11210964267417873667,14516211820212764316,2124231869409556689],[4261249487994076490,15877481873925059044,14516211820212764316,7252918969430566594,17299978721726771610,11210964267417873667,17036604842139972912,7029437790788498388,2124231869409556689],[15877481873925059044,7252918969430566594,4261249487994076490,17036604842139972912,17299978721726771610,1809704172129195322,7029437790788498388,2124231869409556689,11210964267417873667,14516211820212764316],[2124231869409556689,5534800796196967885,7029437790788498388,7252918969430566594,14516211820212764316,4261249487994076490,11210964267417873667,15877481873925059044,17299978721726771610,3608604157153838227,1809704172129195322,17036604842139972912],[17299978721726771610,4261249487994076490,1809704172129195322,7029437790788498388,14516211820212764316,11210964267417873667,15877481873925059044,17036604842139972912,7252918969430566594,2124231869409556689],[14516211820212764316,15877481873925059044,17299978721726771610,7252918969430566594,17036604842139972912,1809704172129195322,5534800796196967885,7029437790788498388,4261249487994076490,11210964267417873667,2124231869409556689,3608604157153838227],[4328376070224119511],[13340751444307201866,6852799892628327372,14228923746783465609,2871608309888343463,11196821089257149774,9135110142507605216,17740496701763775226,12276520439585231336,2025899804080897524,3750439930725791025,1662641269094032596,5888633415105234509,4328376070224119511,17735408893002232096,7201841978411396053,17397123104674848450,13795432594059356320,18031616785650843168,16609137733952262762,13027689870767713939,17952673493105230490,3214181946162459584,16671141883125519098,16306737306999003555,13446205009526451196],[3750439930725791025,12741076678082295759,7201841978411396053,7480253252288032958,13446205009526451196,3214181946162459584,13340751444307201866,13646498613066619660,14139765080256493579,6852799892628327372,16306737306999003555,4328376070224119511,8566844905246185636,7104261880154687267,12494428953087324640,5449860184735415958,1662641269094032596,17740496701763775226,17952673493105230490,16304636129468583592,2025899804080897524,9135110142507605216,2871608309888343463,18031616785650843168,2834866505092039323,13027689870767713939,13795432594059356320,12276520439585231336,11196821089257149774,17397123104674848450,16609137733952262762,17131529656312051452,14228923746783465609,16671141883125519098,5888633415105234509,8511737864852441844,17735408893002232096,1508440849951861669],[17131529656312051452,16306737306999003555,12276520439585231336,17735408893002232096,18095952297474762348,7201841978411396053,17740496701763775226,14139765080256493579,13795432594059356320,8511737864852441844,4328376070224119511,6852799892628327372,5449860184735415958,140396870212231820,14228923746783465609,13446205009526451196,11196821089257149774,9808637865669223270,1508440849951861669,13027689870767713939,1491840484128555837,5954536408321808728,17397123104674848450,9135110142507605216,2871608309888343463,18031616785650843168,2834866505092039323,3214181946162459584,3750439930725791025,1662641269094032596,13646498613066619660,16304636129468583592,8566844905246185636,7480253252288032958,5888633415105234509,2025899804080897524,12741076678082295759,16671141883125519098,12494428953087324640,17952673493105230490,16609137733952262762,7104261880154687267,13340751444307201866],[17036604842139972912],[13588160462734303101],[18081743490344004315],[12353675714904258944,17562801632450633291,6728362629909402903,17603523494627491590,5856350938151339368,10286817149456341619,10995640810984321903,804622576568168609,2287485748649359627],[15900830679378240619,13353438235848911576,6726954210929537972,11373527190663101881,10286817149456341619,17562801632450633291,18271512507682813443,12770183061753030023,392274448837115448,7667878689218439065,16177422101884031678,14690269209726153565,11429506195623419966,16759836951269190891,17271572793812678706,6503655938154160104,11095670964487764044,5856350938151339368,17603523494627491590,6787585796949551500,8426490990601560741,5877930116725120460,7018444885869143173,13203761224559198689,7376049709233607419,804622576568168609,12145355397916841389,682567808439406093,7755499790391969923,14139129879376457893,2287485748649359627,3050731459444225191,1258994191538244490,3109716240255919254,6616450276140292763,14257963317028524134,14698962747138962125,10760002922115563021,17495267820524300686,11301831865756336526,4470272391975492611,8359580532088731394,5488285068107445023,10265035897167064154,18053728639616073084,10995640810984321903,6728362629909402903,1001728975241745659,17740294143355019755,15301503532602557206,4809200889774783438,13616602029989984195,12353675714904258944],[17271572793812678706,13203761224559198689,14690269209726153565,15301503532602557206,5856350938151339368,18053728639616073084,5877930116725120460,7755499790391969923,10995640810984321903,12770183061753030023,16759836951269190891,4809200889774783438,16177422101884031678,2287485748649359627,3050731459444225191,804622576568168609,13616602029989984195,5488285068107445023,7376049709233607419,11301831865756336526,17562801632450633291,17495267820524300686,6787585796949551500,14257963317028524134,6503655938154160104,10760002922115563021,15900830679378240619,18271512507682813443,8426490990601560741,12353675714904258944,11373527190663101881,12145355397916841389,6616450276140292763,9338394475379815879,682567808439406093,7018444885869143173,7667878689218439065,9071802450034150503,13353438235848911576,14698962747138962125,3109716240255919254,17603523494627491590,6726954210929537972,1001728975241745659,17696051535511578981,6728362629909402903,11429506195623419966,10286817149456341619,11095670964487764044,10265035897167064154,7977952035097419157,1258994191538244490,8359580532088731394,14139129879376457893,4470272391975492611,17740294143355019755,392274448837115448],[18271512507682813443,4809200889774783438,10995640810984321903,11373527190663101881,17495267820524300686,11429506195623419966,1001728975241745659,13203761224559198689,6616450276140292763,17740294143355019755,6726954210929537972,10760002922115563021,13353438235848911576,13616602029989984195,3109716240255919254,14257963317028524134,8359580532088731394,7018444885869143173,11301831865756336526,17271572793812678706,7755499790391969923,5488285068107445023,17562801632450633291,14139129879376457893,4470272391975492611,15301503532602557206,5877930116725120460,17603523494627491590,6728362629909402903,3050731459444225191,18053728639616073084,8426490990601560741,14690269209726153565,16759836951269190891,15900830679378240619,12353675714904258944,2287485748649359627,11095670964487764044,17696051535511578981,682567808439406093,804622576568168609,16780039553038473906,7376049709233607419,6503655938154160104,12145355397916841389,16177422101884031678,5856350938151339368,14698962747138962125,9338394475379815879,10265035897167064154,7667878689218439065,1258994191538244490,7977952035097419157,6787585796949551500,9071802450034150503,10286817149456341619,392274448837115448,12770183061753030023],[3109716240255919254,4809200889774783438,15301503532602557206,13616602029989984195,8359580532088731394,7977952035097419157,6787585796949551500,7755499790391969923,10286817149456341619,14257963317028524134,10265035897167064154,17271572793812678706,5856350938151339368,17696051535511578981,6726954210929537972,11373527190663101881,11301831865756336526,14698962747138962125,6728362629909402903,14690269209726153565,10760002922115563021,13353438235848911576,7104088139635280554,9338394475379815879,17495267820524300686,7018444885869143173,7667878689218439065,5877930116725120460,1258994191538244490,804622576568168609,18053728639616073084,12353675714904258944,16177422101884031678,10995640810984321903,17562801632450633291,13203761224559198689,7376049709233607419,6616450276140292763,6503655938154160104,4470272391975492611,392274448837115448,11095670964487764044,18271512507682813443,682567808439406093,15900830679378240619,17603523494627491590,16759836951269190891,12770183061753030023,5488285068107445023,3050731459444225191,8426490990601560741,11429506195623419966,16785043320296790229,9071802450034150503,12145355397916841389,17740294143355019755,1001728975241745659,2287485748649359627,14139129879376457893,16780039553038473906],[]],\"selection_redo_history\":[]}}},\"collapsed\":[],\"commit_hash\":\"3a591dac6a53454813c8df6ceed5b44b91d1e816\",\"document_ptz\":{\"pan\":[-768.2748744089959,-384.56142660725646],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"render_mode\":\"Normal\",\"overlays_visibility_settings\":{\"all\":true,\"artboard_name\":true,\"compass_rose\":true,\"quick_measurement\":true,\"transform_measurement\":true,\"transform_cage\":true,\"hover_outline\":true,\"selection_outline\":true,\"layer_origin_cross\":true,\"pivot\":true,\"origin\":true,\"path\":true,\"anchors\":true,\"handles\":true},\"rulers_visible\":true,\"snapping_state\":{\"snapping_enabled\":true,\"grid_snapping\":false,\"artboards\":true,\"tolerance\":8.0,\"bounding_box\":{\"center_point\":true,\"corner_point\":true,\"edge_midpoint\":true,\"align_with_edges\":true,\"distribute_evenly\":true},\"path\":{\"anchor_point\":true,\"line_midpoint\":true,\"along_path\":true,\"normal_to_path\":true,\"tangent_to_path\":true,\"path_intersection_point\":true,\"align_with_anchor_point\":true,\"perpendicular_from_endpoint\":true},\"grid\":{\"origin\":[0.0,0.0],\"grid_type\":{\"Rectangular\":{\"spacing\":[1.0,1.0]}},\"rectangular_spacing\":[1.0,1.0],\"isometric_y_spacing\":1.0,\"isometric_angle_a\":30.0,\"isometric_angle_b\":30.0,\"grid_color\":{\"red\":0.6038274,\"green\":0.6038274,\"blue\":0.6038274,\"alpha\":1.0},\"dot_display\":false}},\"graph_view_overlay_open\":false,\"graph_fade_artwork_percentage\":80.0}"
  },
  {
    "path": "demo-artwork/parametric-dunescape.graphite",
    "content": "{\"network_interface\":{\"network\":{\"exports\":[{\"Node\":{\"node_id\":4445985685181725042,\"output_index\":0}}],\"nodes\":[[17154757625902243313,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.23529412,\"green\":0.23529412,\"blue\":0.24313726,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1670.79570439},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":24.0},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2288754089236120499,\"output_index\":0}}],\"nodes\":[[11731553664207576737,{\"inputs\":[{\"Node\":{\"node_id\":757876048866560520,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":48.900000000000006},\"exposed\":false}},{\"Node\":{\"node_id\":7828034197076821310,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::JitterPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14158287945315818946,{\"inputs\":[{\"Node\":{\"node_id\":16943732999059587742,\"output_index\":0}},{\"Node\":{\"node_id\":14577956936089455769,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.6181095265062186,3.496297826945966]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2288754089236120499,{\"inputs\":[{\"Node\":{\"node_id\":14158287945315818946,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SplineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7828034197076821310,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":3}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::ToU32Node\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[640915213983348287,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4000.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":500.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::generator_nodes::RectangleNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1791308547102433540,{\"inputs\":[{\"Node\":{\"node_id\":640915213983348287,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.99607843,\"green\":0.827451,\"blue\":0.65882355,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14577956936089455769,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1050.10498991},\"exposed\":false}},{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":2}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::Vec2ValueNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16943732999059587742,{\"inputs\":[{\"Node\":{\"node_id\":1357621220363171879,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.399999999999956},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::JitterPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1357621220363171879,{\"inputs\":[{\"Node\":{\"node_id\":11731553664207576737,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":172.9},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[757876048866560520,{\"inputs\":[{\"Node\":{\"node_id\":1791308547102433540,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":800.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3683309254695891012,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.68235296,\"green\":0.5372549,\"blue\":0.4627451,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1485.34393334},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":225.0},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2288754089236120499,\"output_index\":0}}],\"nodes\":[[3916070947050514908,{\"inputs\":[{\"Node\":{\"node_id\":1791308547102433540,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":800.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2288754089236120499,{\"inputs\":[{\"Node\":{\"node_id\":14158287945315818946,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SplineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14158287945315818946,{\"inputs\":[{\"Node\":{\"node_id\":16943732999059587742,\"output_index\":0}},{\"Node\":{\"node_id\":14577956936089455769,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.6181095265062186,3.496297826945966]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11731553664207576737,{\"inputs\":[{\"Node\":{\"node_id\":3916070947050514908,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":48.900000000000006},\"exposed\":false}},{\"Node\":{\"node_id\":7828034197076821310,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::JitterPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7828034197076821310,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":3}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::ToU32Node\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[640915213983348287,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4000.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":500.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::generator_nodes::RectangleNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1791308547102433540,{\"inputs\":[{\"Node\":{\"node_id\":640915213983348287,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.99607843,\"green\":0.827451,\"blue\":0.65882355,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14577956936089455769,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1050.10498991},\"exposed\":false}},{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":2}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::Vec2ValueNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16943732999059587742,{\"inputs\":[{\"Node\":{\"node_id\":10720574559271598132,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.399999999999956},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::JitterPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10720574559271598132,{\"inputs\":[{\"Node\":{\"node_id\":11731553664207576737,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":172.9},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13644002059194136823,{\"inputs\":[{\"Node\":{\"node_id\":17437077810654043142,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":5.599999999999968},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::JitterPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4037968351431607570,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.8666667,\"green\":0.69803923,\"blue\":0.56078434,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1442.36628417},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":318.0},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2288754089236120499,\"output_index\":0}}],\"nodes\":[[11874141024063691837,{\"inputs\":[{\"Node\":{\"node_id\":1791308547102433540,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":800.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2288754089236120499,{\"inputs\":[{\"Node\":{\"node_id\":14158287945315818946,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SplineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14158287945315818946,{\"inputs\":[{\"Node\":{\"node_id\":16943732999059587742,\"output_index\":0}},{\"Node\":{\"node_id\":14577956936089455769,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.6181095265062186,3.496297826945966]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11731553664207576737,{\"inputs\":[{\"Node\":{\"node_id\":11874141024063691837,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":48.900000000000006},\"exposed\":false}},{\"Node\":{\"node_id\":7828034197076821310,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::JitterPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7828034197076821310,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":3}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::ToU32Node\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[640915213983348287,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4000.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":500.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::generator_nodes::RectangleNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1791308547102433540,{\"inputs\":[{\"Node\":{\"node_id\":640915213983348287,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.99607843,\"green\":0.827451,\"blue\":0.65882355,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14577956936089455769,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1050.10498991},\"exposed\":false}},{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":2}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::Vec2ValueNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16943732999059587742,{\"inputs\":[{\"Node\":{\"node_id\":7807438675023750219,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.399999999999956},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::JitterPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7807438675023750219,{\"inputs\":[{\"Node\":{\"node_id\":11731553664207576737,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":172.9},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13323241418027154136,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.18039216,\"green\":0.19215687,\"blue\":0.21960784,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1733.30579952},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":77.0},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2288754089236120499,\"output_index\":0}}],\"nodes\":[[11731553664207576737,{\"inputs\":[{\"Node\":{\"node_id\":16504069171520924548,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":48.900000000000006},\"exposed\":false}},{\"Node\":{\"node_id\":7828034197076821310,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::JitterPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14158287945315818946,{\"inputs\":[{\"Node\":{\"node_id\":16943732999059587742,\"output_index\":0}},{\"Node\":{\"node_id\":14577956936089455769,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.6181095265062186,3.496297826945966]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2288754089236120499,{\"inputs\":[{\"Node\":{\"node_id\":14158287945315818946,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SplineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7828034197076821310,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":3}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::ToU32Node\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3434804841107735149,{\"inputs\":[{\"Node\":{\"node_id\":11731553664207576737,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":172.9},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[640915213983348287,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4000.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":500.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::generator_nodes::RectangleNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1791308547102433540,{\"inputs\":[{\"Node\":{\"node_id\":640915213983348287,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.99607843,\"green\":0.827451,\"blue\":0.65882355,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14577956936089455769,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1050.10498991},\"exposed\":false}},{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":2}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::Vec2ValueNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16943732999059587742,{\"inputs\":[{\"Node\":{\"node_id\":3434804841107735149,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.399999999999956},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::JitterPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16504069171520924548,{\"inputs\":[{\"Node\":{\"node_id\":1791308547102433540,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":800.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12110920487474373676,{\"inputs\":[{\"Node\":{\"node_id\":12639486733043466090,\"output_index\":0}},{\"Node\":{\"node_id\":13323241418027154136,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10806978668166337270,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.21176471,\"green\":0.21568628,\"blue\":0.23137255,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1579.30304164},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":142.0},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2288754089236120499,\"output_index\":0}}],\"nodes\":[[9133354469966676056,{\"inputs\":[{\"Node\":{\"node_id\":11731553664207576737,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":172.9},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2288754089236120499,{\"inputs\":[{\"Node\":{\"node_id\":14158287945315818946,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SplineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14158287945315818946,{\"inputs\":[{\"Node\":{\"node_id\":16943732999059587742,\"output_index\":0}},{\"Node\":{\"node_id\":14577956936089455769,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.6181095265062186,3.496297826945966]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11731553664207576737,{\"inputs\":[{\"Node\":{\"node_id\":3765311973789472189,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":48.900000000000006},\"exposed\":false}},{\"Node\":{\"node_id\":7828034197076821310,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::JitterPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7828034197076821310,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":3}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::ToU32Node\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[640915213983348287,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4000.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":500.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::generator_nodes::RectangleNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1791308547102433540,{\"inputs\":[{\"Node\":{\"node_id\":640915213983348287,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.99607843,\"green\":0.827451,\"blue\":0.65882355,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14577956936089455769,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1050.10498991},\"exposed\":false}},{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":2}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::Vec2ValueNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16943732999059587742,{\"inputs\":[{\"Node\":{\"node_id\":9133354469966676056,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.399999999999956},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::JitterPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3765311973789472189,{\"inputs\":[{\"Node\":{\"node_id\":1791308547102433540,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":800.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4390668436091073484,{\"inputs\":[{\"Node\":{\"node_id\":13676600738025998635,\"output_index\":0}},{\"Node\":{\"node_id\":12122314434656187176,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5883606306991910210,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.4862745,\"green\":0.43137255,\"blue\":0.39607844,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1524.31304726},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":175.0},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2288754089236120499,\"output_index\":0}}],\"nodes\":[[16598825029377599083,{\"inputs\":[{\"Node\":{\"node_id\":1791308547102433540,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":800.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2288754089236120499,{\"inputs\":[{\"Node\":{\"node_id\":14158287945315818946,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SplineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14158287945315818946,{\"inputs\":[{\"Node\":{\"node_id\":16943732999059587742,\"output_index\":0}},{\"Node\":{\"node_id\":14577956936089455769,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.6181095265062186,3.496297826945966]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11731553664207576737,{\"inputs\":[{\"Node\":{\"node_id\":16598825029377599083,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":48.900000000000006},\"exposed\":false}},{\"Node\":{\"node_id\":7828034197076821310,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::JitterPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7828034197076821310,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":3}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::ToU32Node\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6686718746443793247,{\"inputs\":[{\"Node\":{\"node_id\":11731553664207576737,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":172.9},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[640915213983348287,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4000.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":500.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::generator_nodes::RectangleNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1791308547102433540,{\"inputs\":[{\"Node\":{\"node_id\":640915213983348287,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.99607843,\"green\":0.827451,\"blue\":0.65882355,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14577956936089455769,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1050.10498991},\"exposed\":false}},{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":2}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::Vec2ValueNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16943732999059587742,{\"inputs\":[{\"Node\":{\"node_id\":6686718746443793247,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.399999999999956},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::JitterPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13132104524813958174,{\"inputs\":[{\"Node\":{\"node_id\":4390668436091073484,\"output_index\":0}},{\"Node\":{\"node_id\":2163528024003768644,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12416569579970107543,{\"inputs\":[{\"Node\":{\"node_id\":13644002059194136823,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SplineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9069881382900058607,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":4037968351431607570,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12639486733043466090,{\"inputs\":[{\"Node\":{\"node_id\":8804763001225416124,\"output_index\":0}},{\"Node\":{\"node_id\":1801066723091712434,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7128559142392931896,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.15686275,\"green\":0.16862746,\"blue\":0.1882353,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1803.43646796},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":35.0},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2288754089236120499,\"output_index\":0}}],\"nodes\":[[11731553664207576737,{\"inputs\":[{\"Node\":{\"node_id\":10094915787292727725,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":48.900000000000006},\"exposed\":false}},{\"Node\":{\"node_id\":7828034197076821310,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::JitterPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14158287945315818946,{\"inputs\":[{\"Node\":{\"node_id\":16943732999059587742,\"output_index\":0}},{\"Node\":{\"node_id\":14577956936089455769,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.6181095265062186,3.496297826945966]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2288754089236120499,{\"inputs\":[{\"Node\":{\"node_id\":14158287945315818946,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SplineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[18443039976647938912,{\"inputs\":[{\"Node\":{\"node_id\":11731553664207576737,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":172.9},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7828034197076821310,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":3}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::ToU32Node\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10094915787292727725,{\"inputs\":[{\"Node\":{\"node_id\":1791308547102433540,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":800.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[640915213983348287,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4000.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":500.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::generator_nodes::RectangleNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1791308547102433540,{\"inputs\":[{\"Node\":{\"node_id\":640915213983348287,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.99607843,\"green\":0.827451,\"blue\":0.65882355,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14577956936089455769,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1050.10498991},\"exposed\":false}},{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":2}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::Vec2ValueNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16943732999059587742,{\"inputs\":[{\"Node\":{\"node_id\":18443039976647938912,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.399999999999956},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::JitterPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17327221498641745184,{\"inputs\":[{\"Node\":{\"node_id\":12317719117993811200,\"output_index\":0}},{\"Node\":{\"node_id\":3683309254695891012,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7711570794020903773,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.8784314,\"green\":0.49019608,\"blue\":0.36862746,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1513.3360814},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":22.0},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2288754089236120499,\"output_index\":0}}],\"nodes\":[[3025883099767376126,{\"inputs\":[{\"Node\":{\"node_id\":1791308547102433540,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":800.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2288754089236120499,{\"inputs\":[{\"Node\":{\"node_id\":14158287945315818946,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SplineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14158287945315818946,{\"inputs\":[{\"Node\":{\"node_id\":16943732999059587742,\"output_index\":0}},{\"Node\":{\"node_id\":14577956936089455769,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.6181095265062186,3.496297826945966]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11731553664207576737,{\"inputs\":[{\"Node\":{\"node_id\":3025883099767376126,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":48.900000000000006},\"exposed\":false}},{\"Node\":{\"node_id\":7828034197076821310,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::JitterPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1126802566044359983,{\"inputs\":[{\"Node\":{\"node_id\":11731553664207576737,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":172.9},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7828034197076821310,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":3}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::ToU32Node\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[640915213983348287,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4000.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":500.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::generator_nodes::RectangleNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1791308547102433540,{\"inputs\":[{\"Node\":{\"node_id\":640915213983348287,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.99607843,\"green\":0.827451,\"blue\":0.65882355,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14577956936089455769,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1050.10498991},\"exposed\":false}},{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":2}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::Vec2ValueNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16943732999059587742,{\"inputs\":[{\"Node\":{\"node_id\":1126802566044359983,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.399999999999956},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::JitterPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10686861494573327243,{\"inputs\":[{\"Node\":{\"node_id\":11386926595254122633,\"output_index\":0}},{\"Node\":{\"node_id\":10806978668166337270,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17589660903986237301,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.9529412,\"green\":0.6431373,\"blue\":0.42352942,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1614.38741737},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":17.0},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2288754089236120499,\"output_index\":0}}],\"nodes\":[[3289411516263327806,{\"inputs\":[{\"Node\":{\"node_id\":1791308547102433540,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":800.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2288754089236120499,{\"inputs\":[{\"Node\":{\"node_id\":14158287945315818946,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SplineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14158287945315818946,{\"inputs\":[{\"Node\":{\"node_id\":16943732999059587742,\"output_index\":0}},{\"Node\":{\"node_id\":14577956936089455769,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.6181095265062186,3.496297826945966]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11731553664207576737,{\"inputs\":[{\"Node\":{\"node_id\":3289411516263327806,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":48.900000000000006},\"exposed\":false}},{\"Node\":{\"node_id\":7828034197076821310,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::JitterPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7828034197076821310,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":3}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::ToU32Node\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[640915213983348287,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4000.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":500.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::generator_nodes::RectangleNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1791308547102433540,{\"inputs\":[{\"Node\":{\"node_id\":640915213983348287,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.99607843,\"green\":0.827451,\"blue\":0.65882355,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14577956936089455769,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1050.10498991},\"exposed\":false}},{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":2}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::Vec2ValueNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16943732999059587742,{\"inputs\":[{\"Node\":{\"node_id\":12029121808718862100,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.399999999999956},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::JitterPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12029121808718862100,{\"inputs\":[{\"Node\":{\"node_id\":11731553664207576737,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":172.9},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2163528024003768644,{\"inputs\":[{\"Node\":{\"node_id\":12110920487474373676,\"output_index\":0}},{\"Node\":{\"node_id\":7128559142392931896,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6523786079462141312,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.85882354,\"green\":0.57254905,\"blue\":0.43529412,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1491.01593158},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":173.0},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2288754089236120499,\"output_index\":0}}],\"nodes\":[[7783268010546120518,{\"inputs\":[{\"Node\":{\"node_id\":11731553664207576737,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":172.9},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2288754089236120499,{\"inputs\":[{\"Node\":{\"node_id\":14158287945315818946,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SplineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14158287945315818946,{\"inputs\":[{\"Node\":{\"node_id\":16943732999059587742,\"output_index\":0}},{\"Node\":{\"node_id\":14577956936089455769,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.6181095265062186,3.496297826945966]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11731553664207576737,{\"inputs\":[{\"Node\":{\"node_id\":16009834030113172488,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":48.900000000000006},\"exposed\":false}},{\"Node\":{\"node_id\":7828034197076821310,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::JitterPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7828034197076821310,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":3}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::ToU32Node\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[640915213983348287,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4000.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":500.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::generator_nodes::RectangleNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1791308547102433540,{\"inputs\":[{\"Node\":{\"node_id\":640915213983348287,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.99607843,\"green\":0.827451,\"blue\":0.65882355,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14577956936089455769,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1050.10498991},\"exposed\":false}},{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":2}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::Vec2ValueNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16943732999059587742,{\"inputs\":[{\"Node\":{\"node_id\":7783268010546120518,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.399999999999956},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::JitterPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16009834030113172488,{\"inputs\":[{\"Node\":{\"node_id\":1791308547102433540,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":800.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11386926595254122633,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":6523786079462141312,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15002088321732485705,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.99607843,\"green\":0.827451,\"blue\":0.65882355,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1343.63187023},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":349.0},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2288754089236120499,\"output_index\":0}}],\"nodes\":[[11731553664207576737,{\"inputs\":[{\"Node\":{\"node_id\":15212962088799153943,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":48.900000000000006},\"exposed\":false}},{\"Node\":{\"node_id\":7828034197076821310,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::JitterPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14158287945315818946,{\"inputs\":[{\"Node\":{\"node_id\":16943732999059587742,\"output_index\":0}},{\"Node\":{\"node_id\":14577956936089455769,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.6181095265062186,3.496297826945966]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2288754089236120499,{\"inputs\":[{\"Node\":{\"node_id\":14158287945315818946,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SplineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7828034197076821310,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":3}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::ToU32Node\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[640915213983348287,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4000.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":500.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::generator_nodes::RectangleNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1791308547102433540,{\"inputs\":[{\"Node\":{\"node_id\":640915213983348287,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.99607843,\"green\":0.827451,\"blue\":0.65882355,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14577956936089455769,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1050.10498991},\"exposed\":false}},{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":2}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::Vec2ValueNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16943732999059587742,{\"inputs\":[{\"Node\":{\"node_id\":8591396027454826376,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.399999999999956},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::JitterPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15212962088799153943,{\"inputs\":[{\"Node\":{\"node_id\":1791308547102433540,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":800.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8591396027454826376,{\"inputs\":[{\"Node\":{\"node_id\":11731553664207576737,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":172.9},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3934928477288442109,{\"inputs\":[{\"Node\":{\"node_id\":239716716021064150,\"output_index\":0}},{\"Node\":{\"node_id\":17589660903986237301,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16611856724057842399,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.23529412,\"green\":0.23529412,\"blue\":0.24313726,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1554.40785539},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":50.0},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2288754089236120499,\"output_index\":0}}],\"nodes\":[[17232801702996970986,{\"inputs\":[{\"Node\":{\"node_id\":1791308547102433540,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":800.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9065988052616974602,{\"inputs\":[{\"Node\":{\"node_id\":11731553664207576737,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":172.9},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2288754089236120499,{\"inputs\":[{\"Node\":{\"node_id\":14158287945315818946,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SplineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14158287945315818946,{\"inputs\":[{\"Node\":{\"node_id\":16943732999059587742,\"output_index\":0}},{\"Node\":{\"node_id\":14577956936089455769,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.6181095265062186,3.496297826945966]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11731553664207576737,{\"inputs\":[{\"Node\":{\"node_id\":17232801702996970986,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":48.900000000000006},\"exposed\":false}},{\"Node\":{\"node_id\":7828034197076821310,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::JitterPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7828034197076821310,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":3}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::ToU32Node\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[640915213983348287,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4000.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":500.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::generator_nodes::RectangleNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1791308547102433540,{\"inputs\":[{\"Node\":{\"node_id\":640915213983348287,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.99607843,\"green\":0.827451,\"blue\":0.65882355,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14577956936089455769,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1050.10498991},\"exposed\":false}},{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":2}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::Vec2ValueNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16943732999059587742,{\"inputs\":[{\"Node\":{\"node_id\":9065988052616974602,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.399999999999956},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::JitterPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3726756269632080543,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.5254902,\"green\":0.45882353,\"blue\":0.41568628,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1465.6573046},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":230.0},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2288754089236120499,\"output_index\":0}}],\"nodes\":[[5294703684886212416,{\"inputs\":[{\"Node\":{\"node_id\":1791308547102433540,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":800.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7419291594083587754,{\"inputs\":[{\"Node\":{\"node_id\":11731553664207576737,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":172.9},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2288754089236120499,{\"inputs\":[{\"Node\":{\"node_id\":14158287945315818946,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SplineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14158287945315818946,{\"inputs\":[{\"Node\":{\"node_id\":16943732999059587742,\"output_index\":0}},{\"Node\":{\"node_id\":14577956936089455769,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.6181095265062186,3.496297826945966]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11731553664207576737,{\"inputs\":[{\"Node\":{\"node_id\":5294703684886212416,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":48.900000000000006},\"exposed\":false}},{\"Node\":{\"node_id\":7828034197076821310,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::JitterPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7828034197076821310,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":3}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::ToU32Node\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[640915213983348287,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4000.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":500.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::generator_nodes::RectangleNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1791308547102433540,{\"inputs\":[{\"Node\":{\"node_id\":640915213983348287,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.99607843,\"green\":0.827451,\"blue\":0.65882355,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14577956936089455769,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1050.10498991},\"exposed\":false}},{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":2}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::Vec2ValueNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16943732999059587742,{\"inputs\":[{\"Node\":{\"node_id\":7419291594083587754,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.399999999999956},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::JitterPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1801066723091712434,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.21176471,\"green\":0.21568628,\"blue\":0.23137255,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1744.31393592},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":63.0},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2288754089236120499,\"output_index\":0}}],\"nodes\":[[11731553664207576737,{\"inputs\":[{\"Node\":{\"node_id\":14514477720912258595,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":48.900000000000006},\"exposed\":false}},{\"Node\":{\"node_id\":7828034197076821310,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::JitterPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14158287945315818946,{\"inputs\":[{\"Node\":{\"node_id\":16943732999059587742,\"output_index\":0}},{\"Node\":{\"node_id\":14577956936089455769,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.6181095265062186,3.496297826945966]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2288754089236120499,{\"inputs\":[{\"Node\":{\"node_id\":14158287945315818946,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SplineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7828034197076821310,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":3}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::ToU32Node\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[640915213983348287,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4000.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":500.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::generator_nodes::RectangleNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1791308547102433540,{\"inputs\":[{\"Node\":{\"node_id\":640915213983348287,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.99607843,\"green\":0.827451,\"blue\":0.65882355,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14577956936089455769,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1050.10498991},\"exposed\":false}},{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":2}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::Vec2ValueNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16943732999059587742,{\"inputs\":[{\"Node\":{\"node_id\":18341697272814101120,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.399999999999956},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::JitterPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[18341697272814101120,{\"inputs\":[{\"Node\":{\"node_id\":11731553664207576737,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":172.9},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14514477720912258595,{\"inputs\":[{\"Node\":{\"node_id\":1791308547102433540,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":800.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12317719117993811200,{\"inputs\":[{\"Node\":{\"node_id\":9069881382900058607,\"output_index\":0}},{\"Node\":{\"node_id\":17239043462037837834,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8804763001225416124,{\"inputs\":[{\"Node\":{\"node_id\":3934928477288442109,\"output_index\":0}},{\"Node\":{\"node_id\":17154757625902243313,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17239043462037837834,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.8039216,\"green\":0.6627451,\"blue\":0.52156866,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1447.11050605},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":309.0},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2288754089236120499,\"output_index\":0}}],\"nodes\":[[2288754089236120499,{\"inputs\":[{\"Node\":{\"node_id\":14158287945315818946,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SplineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14158287945315818946,{\"inputs\":[{\"Node\":{\"node_id\":16943732999059587742,\"output_index\":0}},{\"Node\":{\"node_id\":14577956936089455769,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.6181095265062186,3.496297826945966]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15937218612227302315,{\"inputs\":[{\"Node\":{\"node_id\":11731553664207576737,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":172.9},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7260397085903590160,{\"inputs\":[{\"Node\":{\"node_id\":1791308547102433540,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":800.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7828034197076821310,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":3}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::ToU32Node\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11731553664207576737,{\"inputs\":[{\"Node\":{\"node_id\":7260397085903590160,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":48.900000000000006},\"exposed\":false}},{\"Node\":{\"node_id\":7828034197076821310,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::JitterPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[640915213983348287,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4000.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":500.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::generator_nodes::RectangleNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1791308547102433540,{\"inputs\":[{\"Node\":{\"node_id\":640915213983348287,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.99607843,\"green\":0.827451,\"blue\":0.65882355,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14577956936089455769,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1050.10498991},\"exposed\":false}},{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":2}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"math_nodes::Vec2ValueNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16943732999059587742,{\"inputs\":[{\"Node\":{\"node_id\":15937218612227302315,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.399999999999956},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::JitterPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2128810469968776913,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":12416569579970107543,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[239716716021064150,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":7711570794020903773,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15385259560644295534,{\"inputs\":[{\"Node\":{\"node_id\":17327221498641745184,\"output_index\":0}},{\"Node\":{\"node_id\":3726756269632080543,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4445985685181725042,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Artboard\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":13132104524813958174,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[2000.0,1000.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.99607843,\"green\":0.8745098,\"blue\":0.7019608,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":1}},{\"Value\":{\"tagged_value\":{\"String\":\"Artboard\"},\"exposed\":false}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":5}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::artboard::CreateArtboardNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Import\":{\"import_type\":{\"Fn\":[{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::artboard::Artboard>\",\"alias\":null}}]},\"import_index\":0}},{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12122314434656187176,{\"inputs\":[{\"Node\":{\"node_id\":10686861494573327243,\"output_index\":0}},{\"Node\":{\"node_id\":16611856724057842399,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13676600738025998635,{\"inputs\":[{\"Node\":{\"node_id\":2128810469968776913,\"output_index\":0}},{\"Node\":{\"node_id\":6097807941755042226,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6097807941755042226,{\"inputs\":[{\"Node\":{\"node_id\":15385259560644295534,\"output_index\":0}},{\"Node\":{\"node_id\":5883606306991910210,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17437077810654043142,{\"inputs\":[{\"Node\":{\"node_id\":15002088321732485705,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":40.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":40},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[7128559142392931896,{\"persistent_metadata\":{\"display_name\":\"Dune\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1791308547102433540,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-20,-3]}}},\"network_metadata\":null}}],[2288754089236120499,{\"persistent_metadata\":{\"display_name\":\"Spline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[22,-3]}}},\"network_metadata\":null}}],[10094915787292727725,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"is_integer\":false,\"mode\":\"Increment\",\"min\":0.0,\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":true,\"min\":2.0,\"mode\":\"Increment\",\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"mode\":\"Increment\",\"is_integer\":false,\"min\":0.0,\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"is_integer\":false,\"unit\":\" px\",\"min\":0.0,\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-13,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14158287945315818946,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"Y\",\"is_integer\":false,\"unit\":\" px\",\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\"x\",\"is_integer\":false,\"y\":\"H\",\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"Data\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[15,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[640915213983348287,{\"persistent_metadata\":{\"display_name\":\"Rectangle\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Width\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Height\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Individual Corner Radii\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Corner Radius\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Clamped\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-27,-3]}}},\"network_metadata\":null}}],[11731553664207576737,{\"persistent_metadata\":{\"display_name\":\"Jitter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Amount\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Seed\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-6,-3]}}},\"network_metadata\":null}}],[14577956936089455769,{\"persistent_metadata\":{\"display_name\":\"Vec2 Value\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"X\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Y\",\"input_description\":\"\"}}],\"output_names\":[\"Future<DVec2>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[8,-1]}}},\"network_metadata\":null}}],[7828034197076821310,{\"persistent_metadata\":{\"display_name\":\"To u32\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"\"}}],\"output_names\":[\"Future<u32>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-13,1]}}},\"network_metadata\":null}}],[18443039976647938912,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"mode\":\"Increment\",\"blank_assist\":true,\"min\":0.0,\"is_integer\":false},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"is_integer\":true,\"blank_assist\":true,\"min\":2.0},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"blank_assist\":true,\"is_integer\":false,\"mode\":\"Increment\",\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"blank_assist\":true,\"unit\":\" px\",\"is_integer\":false,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[1,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16943732999059587742,{\"persistent_metadata\":{\"display_name\":\"Jitter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Amount\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Seed\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[8,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.33333333333328596,-0.3333333333333428],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,991.0,580.0],\"node_graph_width\":0.0},\"selection_undo_history\":[[10094915787292727725]],\"selection_redo_history\":[]}}}}],[17437077810654043142,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"min\":0.0,\"mode\":\"Increment\",\"unit\":\" px\",\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":true,\"min\":2.0,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"mode\":\"Increment\",\"blank_assist\":true,\"unit\":\" px\",\"min\":0.0},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\" px\",\"blank_assist\":true,\"mode\":\"Increment\",\"min\":0.0},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13644002059194136823,{\"persistent_metadata\":{\"display_name\":\"Jitter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Amount\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Seed\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[239716716021064150,{\"persistent_metadata\":{\"display_name\":\"Dune 2\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12639486733043466090,{\"persistent_metadata\":{\"display_name\":\"Dune in Shadow 3\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[5883606306991910210,{\"persistent_metadata\":{\"display_name\":\"Dune\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[11731553664207576737,{\"persistent_metadata\":{\"display_name\":\"Jitter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Amount\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Seed\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-6,-3]}}},\"network_metadata\":null}}],[14158287945315818946,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"X\",\"unit\":\" px\",\"is_integer\":false,\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\"x\",\"y\":\"H\",\"is_integer\":false,\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"Data\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[15,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[6686718746443793247,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"blank_assist\":true,\"is_integer\":false,\"min\":0.0,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"min\":2.0,\"blank_assist\":true,\"is_integer\":true},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"min\":0.0,\"blank_assist\":true,\"unit\":\" px\",\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"blank_assist\":true,\"mode\":\"Increment\",\"unit\":\" px\",\"min\":0.0},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[1,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16943732999059587742,{\"persistent_metadata\":{\"display_name\":\"Jitter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Amount\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Seed\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[8,-3]}}},\"network_metadata\":null}}],[14577956936089455769,{\"persistent_metadata\":{\"display_name\":\"Vec2 Value\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"X\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Y\",\"input_description\":\"\"}}],\"output_names\":[\"Future<DVec2>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[8,-1]}}},\"network_metadata\":null}}],[16598825029377599083,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"unit\":\" px\",\"blank_assist\":true,\"is_integer\":false,\"min\":0.0},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":true,\"min\":2.0,\"blank_assist\":true,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"min\":0.0,\"is_integer\":false,\"mode\":\"Increment\",\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\" px\",\"mode\":\"Increment\",\"blank_assist\":true,\"min\":0.0},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-13,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2288754089236120499,{\"persistent_metadata\":{\"display_name\":\"Spline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[22,-3]}}},\"network_metadata\":null}}],[7828034197076821310,{\"persistent_metadata\":{\"display_name\":\"To u32\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"\"}}],\"output_names\":[\"Future<u32>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-13,1]}}},\"network_metadata\":null}}],[640915213983348287,{\"persistent_metadata\":{\"display_name\":\"Rectangle\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Width\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Height\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Individual Corner Radii\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Corner Radius\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Clamped\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-27,-3]}}},\"network_metadata\":null}}],[1791308547102433540,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-20,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,991.0,581.0],\"node_graph_width\":0.0},\"selection_undo_history\":[[]],\"selection_redo_history\":[]}}}}],[6523786079462141312,{\"persistent_metadata\":{\"display_name\":\"Dune\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[16943732999059587742,{\"persistent_metadata\":{\"display_name\":\"Jitter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Amount\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Seed\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[8,-3]}}},\"network_metadata\":null}}],[2288754089236120499,{\"persistent_metadata\":{\"display_name\":\"Spline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[22,-3]}}},\"network_metadata\":null}}],[7783268010546120518,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":false,\"min\":0.0,\"mode\":\"Increment\",\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"min\":2.0,\"is_integer\":true,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"blank_assist\":true,\"is_integer\":false,\"mode\":\"Increment\",\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"blank_assist\":true,\"mode\":\"Increment\",\"is_integer\":false,\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[1,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16009834030113172488,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"is_integer\":false,\"blank_assist\":true,\"unit\":\" px\",\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"blank_assist\":true,\"min\":2.0,\"is_integer\":true},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"mode\":\"Increment\",\"unit\":\" px\",\"blank_assist\":true,\"min\":0.0},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":false,\"min\":0.0,\"mode\":\"Increment\",\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-13,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7828034197076821310,{\"persistent_metadata\":{\"display_name\":\"To u32\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"\"}}],\"output_names\":[\"Future<u32>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-13,1]}}},\"network_metadata\":null}}],[14158287945315818946,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"X\",\"is_integer\":false,\"unit\":\" px\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"x\":\"W\",\"unit\":\"x\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"Data\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[15,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11731553664207576737,{\"persistent_metadata\":{\"display_name\":\"Jitter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Amount\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Seed\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-6,-3]}}},\"network_metadata\":null}}],[1791308547102433540,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-20,-3]}}},\"network_metadata\":null}}],[640915213983348287,{\"persistent_metadata\":{\"display_name\":\"Rectangle\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Width\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Height\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Individual Corner Radii\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Corner Radius\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Clamped\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-27,-3]}}},\"network_metadata\":null}}],[14577956936089455769,{\"persistent_metadata\":{\"display_name\":\"Vec2 Value\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"X\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Y\",\"input_description\":\"\"}}],\"output_names\":[\"Future<DVec2>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[8,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,991.0,581.0],\"node_graph_width\":0.0},\"selection_undo_history\":[[]],\"selection_redo_history\":[]}}}}],[4445985685181725042,{\"persistent_metadata\":{\"display_name\":\"Artboard\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Artboards\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Contents\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"X\",\"y\":\"Y\",\"is_integer\":true,\"unit\":\" px\"},\"widget_override\":\"vec2\",\"input_name\":\"Location\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"x\":\"W\",\"y\":\"H\",\"is_integer\":true},\"widget_override\":\"vec2\",\"input_name\":\"Dimensions\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"artboard_background\",\"input_name\":\"Background\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Clip\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[3,0]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Artboard\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Create Artboard\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-4]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11386926595254122633,{\"persistent_metadata\":{\"display_name\":\"Dune\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13676600738025998635,{\"persistent_metadata\":{\"display_name\":\"Background\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":10}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[8804763001225416124,{\"persistent_metadata\":{\"display_name\":\"Dune in Shadow 4\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[17154757625902243313,{\"persistent_metadata\":{\"display_name\":\"Dune\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[2288754089236120499,{\"persistent_metadata\":{\"display_name\":\"Spline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[22,-3]}}},\"network_metadata\":null}}],[16943732999059587742,{\"persistent_metadata\":{\"display_name\":\"Jitter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Amount\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Seed\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[8,-3]}}},\"network_metadata\":null}}],[14158287945315818946,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"Y\",\"unit\":\" px\",\"is_integer\":false,\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"H\",\"x\":\"W\",\"is_integer\":false,\"unit\":\"x\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"Data\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[15,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14577956936089455769,{\"persistent_metadata\":{\"display_name\":\"Vec2 Value\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"X\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Y\",\"input_description\":\"\"}}],\"output_names\":[\"Future<DVec2>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[8,-1]}}},\"network_metadata\":null}}],[11731553664207576737,{\"persistent_metadata\":{\"display_name\":\"Jitter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Amount\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Seed\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-6,-3]}}},\"network_metadata\":null}}],[7828034197076821310,{\"persistent_metadata\":{\"display_name\":\"To u32\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"\"}}],\"output_names\":[\"Future<u32>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-13,1]}}},\"network_metadata\":null}}],[640915213983348287,{\"persistent_metadata\":{\"display_name\":\"Rectangle\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Width\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Height\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Individual Corner Radii\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Corner Radius\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Clamped\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-27,-3]}}},\"network_metadata\":null}}],[1791308547102433540,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-20,-3]}}},\"network_metadata\":null}}],[757876048866560520,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"blank_assist\":true,\"is_integer\":false,\"min\":0.0,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":2.0,\"mode\":\"Increment\",\"is_integer\":true,\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"blank_assist\":true,\"min\":0.0,\"is_integer\":false,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\" px\",\"mode\":\"Increment\",\"blank_assist\":true,\"min\":0.0},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-13,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[1357621220363171879,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"min\":0.0,\"blank_assist\":true,\"unit\":\" px\",\"is_integer\":false},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":true,\"min\":2.0,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"blank_assist\":true,\"is_integer\":false,\"mode\":\"Increment\",\"min\":0.0},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"unit\":\" px\",\"is_integer\":false,\"blank_assist\":true,\"min\":0.0},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[1,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,991.0,581.0],\"node_graph_width\":0.0},\"selection_undo_history\":[[]],\"selection_redo_history\":[]}}}}],[9069881382900058607,{\"persistent_metadata\":{\"display_name\":\"Dune 5\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10806978668166337270,{\"persistent_metadata\":{\"display_name\":\"Dune\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[16943732999059587742,{\"persistent_metadata\":{\"display_name\":\"Jitter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Amount\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Seed\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[8,-3]}}},\"network_metadata\":null}}],[14158287945315818946,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"X\",\"y\":\"Y\",\"is_integer\":false,\"unit\":\" px\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"x\":\"W\",\"y\":\"H\",\"unit\":\"x\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"Data\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[15,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11731553664207576737,{\"persistent_metadata\":{\"display_name\":\"Jitter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Amount\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Seed\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-6,-3]}}},\"network_metadata\":null}}],[2288754089236120499,{\"persistent_metadata\":{\"display_name\":\"Spline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[22,-3]}}},\"network_metadata\":null}}],[7828034197076821310,{\"persistent_metadata\":{\"display_name\":\"To u32\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"\"}}],\"output_names\":[\"Future<u32>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-13,1]}}},\"network_metadata\":null}}],[640915213983348287,{\"persistent_metadata\":{\"display_name\":\"Rectangle\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Width\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Height\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Individual Corner Radii\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Corner Radius\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Clamped\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-27,-3]}}},\"network_metadata\":null}}],[1791308547102433540,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-20,-3]}}},\"network_metadata\":null}}],[14577956936089455769,{\"persistent_metadata\":{\"display_name\":\"Vec2 Value\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"X\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Y\",\"input_description\":\"\"}}],\"output_names\":[\"Future<DVec2>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[8,-1]}}},\"network_metadata\":null}}],[9133354469966676056,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"min\":0.0,\"unit\":\" px\",\"is_integer\":false,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":2.0,\"blank_assist\":true,\"is_integer\":true,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"blank_assist\":true,\"mode\":\"Increment\",\"min\":0.0,\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"mode\":\"Increment\",\"is_integer\":false,\"blank_assist\":true,\"min\":0.0},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[1,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3765311973789472189,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"mode\":\"Increment\",\"blank_assist\":true,\"is_integer\":false,\"min\":0.0},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":2.0,\"is_integer\":true,\"mode\":\"Increment\",\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"is_integer\":false,\"min\":0.0,\"mode\":\"Increment\",\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"mode\":\"Increment\",\"min\":0.0,\"is_integer\":false,\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-13,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,991.0,581.0],\"node_graph_width\":0.0},\"selection_undo_history\":[[]],\"selection_redo_history\":[]}}}}],[12122314434656187176,{\"persistent_metadata\":{\"display_name\":\"Dune in Shadow 1\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-14,27]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10686861494573327243,{\"persistent_metadata\":{\"display_name\":\"Dune in Shadow 2\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3934928477288442109,{\"persistent_metadata\":{\"display_name\":\"Dune 1\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15002088321732485705,{\"persistent_metadata\":{\"display_name\":\"Dune\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1791308547102433540,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-20,-3]}}},\"network_metadata\":null}}],[14577956936089455769,{\"persistent_metadata\":{\"display_name\":\"Vec2 Value\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"X\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Y\",\"input_description\":\"\"}}],\"output_names\":[\"Future<DVec2>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[8,-1]}}},\"network_metadata\":null}}],[15212962088799153943,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"min\":0.0,\"blank_assist\":true,\"mode\":\"Increment\",\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"min\":2.0,\"blank_assist\":true,\"is_integer\":true},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"min\":0.0,\"blank_assist\":true,\"is_integer\":false,\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":false,\"unit\":\" px\",\"min\":0.0,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-13,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[640915213983348287,{\"persistent_metadata\":{\"display_name\":\"Rectangle\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Width\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Height\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Individual Corner Radii\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Corner Radius\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Clamped\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-27,-3]}}},\"network_metadata\":null}}],[11731553664207576737,{\"persistent_metadata\":{\"display_name\":\"Jitter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Amount\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Seed\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-6,-3]}}},\"network_metadata\":null}}],[7828034197076821310,{\"persistent_metadata\":{\"display_name\":\"To u32\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"\"}}],\"output_names\":[\"Future<u32>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-13,1]}}},\"network_metadata\":null}}],[8591396027454826376,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":false,\"unit\":\" px\",\"mode\":\"Increment\",\"min\":0.0},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":true,\"min\":2.0,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"blank_assist\":true,\"is_integer\":false,\"mode\":\"Increment\",\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"unit\":\" px\",\"min\":0.0,\"blank_assist\":true,\"is_integer\":false},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[1,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16943732999059587742,{\"persistent_metadata\":{\"display_name\":\"Jitter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Amount\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Seed\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[8,-3]}}},\"network_metadata\":null}}],[14158287945315818946,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"X\",\"y\":\"Y\",\"unit\":\" px\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"y\":\"H\",\"unit\":\"x\",\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"Data\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[15,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2288754089236120499,{\"persistent_metadata\":{\"display_name\":\"Spline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[22,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,991.0,581.0],\"node_graph_width\":0.0},\"selection_undo_history\":[[]],\"selection_redo_history\":[]}}}}],[6097807941755042226,{\"persistent_metadata\":{\"display_name\":\"Dune 1\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-14,40]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[1801066723091712434,{\"persistent_metadata\":{\"display_name\":\"Dune\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[14158287945315818946,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"X\",\"is_integer\":false,\"unit\":\" px\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"x\":\"W\",\"unit\":\"x\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"Data\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[15,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16943732999059587742,{\"persistent_metadata\":{\"display_name\":\"Jitter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Amount\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Seed\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[8,-3]}}},\"network_metadata\":null}}],[7828034197076821310,{\"persistent_metadata\":{\"display_name\":\"To u32\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"\"}}],\"output_names\":[\"Future<u32>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-13,1]}}},\"network_metadata\":null}}],[2288754089236120499,{\"persistent_metadata\":{\"display_name\":\"Spline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[22,-3]}}},\"network_metadata\":null}}],[11731553664207576737,{\"persistent_metadata\":{\"display_name\":\"Jitter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Amount\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Seed\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-6,-3]}}},\"network_metadata\":null}}],[14514477720912258595,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"unit\":\" px\",\"mode\":\"Increment\",\"blank_assist\":true,\"is_integer\":false},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":true,\"min\":2.0,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"min\":0.0,\"unit\":\" px\",\"is_integer\":false,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":false,\"mode\":\"Increment\",\"unit\":\" px\",\"min\":0.0},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-13,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[18341697272814101120,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"min\":0.0,\"mode\":\"Increment\",\"unit\":\" px\",\"is_integer\":false},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"min\":2.0,\"blank_assist\":true,\"is_integer\":true},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"unit\":\" px\",\"min\":0.0,\"is_integer\":false,\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"mode\":\"Increment\",\"blank_assist\":true,\"min\":0.0,\"is_integer\":false},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[1,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[640915213983348287,{\"persistent_metadata\":{\"display_name\":\"Rectangle\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Width\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Height\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Individual Corner Radii\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Corner Radius\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Clamped\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-27,-3]}}},\"network_metadata\":null}}],[14577956936089455769,{\"persistent_metadata\":{\"display_name\":\"Vec2 Value\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"X\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Y\",\"input_description\":\"\"}}],\"output_names\":[\"Future<DVec2>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[8,-1]}}},\"network_metadata\":null}}],[1791308547102433540,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-20,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[17.0,-79.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,1008.0,502.0],\"node_graph_width\":0.0},\"selection_undo_history\":[[]],\"selection_redo_history\":[]}}}}],[4037968351431607570,{\"persistent_metadata\":{\"display_name\":\"Dune\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[7807438675023750219,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"blank_assist\":true,\"min\":0.0,\"unit\":\" px\",\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":true,\"min\":2.0,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"min\":0.0,\"is_integer\":false,\"unit\":\" px\",\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"mode\":\"Increment\",\"unit\":\" px\",\"min\":0.0,\"is_integer\":false},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[1,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11731553664207576737,{\"persistent_metadata\":{\"display_name\":\"Jitter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Amount\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Seed\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-6,-3]}}},\"network_metadata\":null}}],[11874141024063691837,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"blank_assist\":true,\"is_integer\":false,\"min\":0.0,\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":true,\"mode\":\"Increment\",\"min\":2.0},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":false,\"min\":0.0,\"mode\":\"Increment\",\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"min\":0.0,\"unit\":\" px\",\"blank_assist\":true,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-13,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7828034197076821310,{\"persistent_metadata\":{\"display_name\":\"To u32\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"\"}}],\"output_names\":[\"Future<u32>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-13,1]}}},\"network_metadata\":null}}],[14577956936089455769,{\"persistent_metadata\":{\"display_name\":\"Vec2 Value\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"X\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Y\",\"input_description\":\"\"}}],\"output_names\":[\"Future<DVec2>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[8,-1]}}},\"network_metadata\":null}}],[2288754089236120499,{\"persistent_metadata\":{\"display_name\":\"Spline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[22,-3]}}},\"network_metadata\":null}}],[1791308547102433540,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-20,-3]}}},\"network_metadata\":null}}],[640915213983348287,{\"persistent_metadata\":{\"display_name\":\"Rectangle\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Width\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Height\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Individual Corner Radii\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Corner Radius\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Clamped\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-27,-3]}}},\"network_metadata\":null}}],[16943732999059587742,{\"persistent_metadata\":{\"display_name\":\"Jitter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Amount\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Seed\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[8,-3]}}},\"network_metadata\":null}}],[14158287945315818946,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"x\":\"X\",\"unit\":\" px\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"unit\":\"x\",\"y\":\"H\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"Data\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[15,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,991.0,581.0],\"node_graph_width\":0.0},\"selection_undo_history\":[[]],\"selection_redo_history\":[]}}}}],[4390668436091073484,{\"persistent_metadata\":{\"display_name\":\"Midground in Shadow\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":18}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13323241418027154136,{\"persistent_metadata\":{\"display_name\":\"Dune\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[2288754089236120499,{\"persistent_metadata\":{\"display_name\":\"Spline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[22,-3]}}},\"network_metadata\":null}}],[1791308547102433540,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-20,-3]}}},\"network_metadata\":null}}],[14577956936089455769,{\"persistent_metadata\":{\"display_name\":\"Vec2 Value\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"X\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Y\",\"input_description\":\"\"}}],\"output_names\":[\"Future<DVec2>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[8,-1]}}},\"network_metadata\":null}}],[16943732999059587742,{\"persistent_metadata\":{\"display_name\":\"Jitter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Amount\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Seed\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[8,-3]}}},\"network_metadata\":null}}],[7828034197076821310,{\"persistent_metadata\":{\"display_name\":\"To u32\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"\"}}],\"output_names\":[\"Future<u32>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-13,1]}}},\"network_metadata\":null}}],[14158287945315818946,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\" px\",\"y\":\"Y\",\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\"x\",\"y\":\"H\",\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"Data\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[15,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[640915213983348287,{\"persistent_metadata\":{\"display_name\":\"Rectangle\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Width\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Height\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Individual Corner Radii\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Corner Radius\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Clamped\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-27,-3]}}},\"network_metadata\":null}}],[11731553664207576737,{\"persistent_metadata\":{\"display_name\":\"Jitter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Amount\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Seed\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-6,-3]}}},\"network_metadata\":null}}],[3434804841107735149,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"min\":0.0,\"mode\":\"Increment\",\"blank_assist\":true,\"is_integer\":false},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"mode\":\"Increment\",\"is_integer\":true,\"min\":2.0},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"min\":0.0,\"unit\":\" px\",\"blank_assist\":true,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":false,\"unit\":\" px\",\"min\":0.0,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[1,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16504069171520924548,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"blank_assist\":true,\"unit\":\" px\",\"min\":0.0,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":2.0,\"mode\":\"Increment\",\"blank_assist\":true,\"is_integer\":true},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"unit\":\" px\",\"min\":0.0,\"is_integer\":false,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"min\":0.0,\"is_integer\":false,\"mode\":\"Increment\",\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-13,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[26.0,-82.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,1017.0,499.0],\"node_graph_width\":0.0},\"selection_undo_history\":[[]],\"selection_redo_history\":[]}}}}],[17239043462037837834,{\"persistent_metadata\":{\"display_name\":\"Dune\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[11731553664207576737,{\"persistent_metadata\":{\"display_name\":\"Jitter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Amount\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Seed\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-6,-3]}}},\"network_metadata\":null}}],[7828034197076821310,{\"persistent_metadata\":{\"display_name\":\"To u32\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"\"}}],\"output_names\":[\"Future<u32>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-13,1]}}},\"network_metadata\":null}}],[640915213983348287,{\"persistent_metadata\":{\"display_name\":\"Rectangle\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Width\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Height\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Individual Corner Radii\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Corner Radius\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Clamped\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-27,-3]}}},\"network_metadata\":null}}],[14158287945315818946,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\" px\",\"y\":\"Y\",\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\"x\",\"x\":\"W\",\"is_integer\":false,\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"Data\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[15,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15937218612227302315,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\" px\",\"min\":0.0,\"blank_assist\":true,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":2.0,\"is_integer\":true,\"mode\":\"Increment\",\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"min\":0.0,\"unit\":\" px\",\"is_integer\":false,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"blank_assist\":true,\"is_integer\":false,\"mode\":\"Increment\",\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[1,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2288754089236120499,{\"persistent_metadata\":{\"display_name\":\"Spline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[22,-3]}}},\"network_metadata\":null}}],[7260397085903590160,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"blank_assist\":true,\"is_integer\":false,\"min\":0.0,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"is_integer\":true,\"min\":2.0,\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"blank_assist\":true,\"unit\":\" px\",\"is_integer\":false,\"min\":0.0},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"blank_assist\":true,\"min\":0.0,\"is_integer\":false,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-13,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14577956936089455769,{\"persistent_metadata\":{\"display_name\":\"Vec2 Value\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"X\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Y\",\"input_description\":\"\"}}],\"output_names\":[\"Future<DVec2>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[8,-1]}}},\"network_metadata\":null}}],[16943732999059587742,{\"persistent_metadata\":{\"display_name\":\"Jitter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Amount\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Seed\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[8,-3]}}},\"network_metadata\":null}}],[1791308547102433540,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-20,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,991.0,581.0],\"node_graph_width\":0.0},\"selection_undo_history\":[[]],\"selection_redo_history\":[]}}}}],[15385259560644295534,{\"persistent_metadata\":{\"display_name\":\"Dune 2\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3683309254695891012,{\"persistent_metadata\":{\"display_name\":\"Dune\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[11731553664207576737,{\"persistent_metadata\":{\"display_name\":\"Jitter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Amount\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Seed\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-6,-3]}}},\"network_metadata\":null}}],[10720574559271598132,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":false,\"mode\":\"Increment\",\"unit\":\" px\",\"min\":0.0},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":true,\"min\":2.0,\"mode\":\"Increment\",\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"mode\":\"Increment\",\"blank_assist\":true,\"is_integer\":false,\"min\":0.0},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"min\":0.0,\"unit\":\" px\",\"mode\":\"Increment\",\"is_integer\":false},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[1,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7828034197076821310,{\"persistent_metadata\":{\"display_name\":\"To u32\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"\"}}],\"output_names\":[\"Future<u32>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-13,1]}}},\"network_metadata\":null}}],[16943732999059587742,{\"persistent_metadata\":{\"display_name\":\"Jitter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Amount\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Seed\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[8,-3]}}},\"network_metadata\":null}}],[3916070947050514908,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"mode\":\"Increment\",\"blank_assist\":true,\"min\":0.0,\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":true,\"mode\":\"Increment\",\"blank_assist\":true,\"min\":2.0},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"mode\":\"Increment\",\"blank_assist\":true,\"is_integer\":false,\"min\":0.0},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"min\":0.0,\"blank_assist\":true,\"mode\":\"Increment\",\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-13,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[1791308547102433540,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-20,-3]}}},\"network_metadata\":null}}],[14577956936089455769,{\"persistent_metadata\":{\"display_name\":\"Vec2 Value\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"X\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Y\",\"input_description\":\"\"}}],\"output_names\":[\"Future<DVec2>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[8,-1]}}},\"network_metadata\":null}}],[640915213983348287,{\"persistent_metadata\":{\"display_name\":\"Rectangle\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Width\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Height\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Individual Corner Radii\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Corner Radius\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Clamped\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-27,-3]}}},\"network_metadata\":null}}],[14158287945315818946,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"Y\",\"is_integer\":false,\"unit\":\" px\",\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"y\":\"H\",\"x\":\"W\",\"unit\":\"x\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"Data\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[15,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2288754089236120499,{\"persistent_metadata\":{\"display_name\":\"Spline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[22,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,991.0,581.0],\"node_graph_width\":0.0},\"selection_undo_history\":[[]],\"selection_redo_history\":[]}}}}],[17327221498641745184,{\"persistent_metadata\":{\"display_name\":\"Dune 3\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12416569579970107543,{\"persistent_metadata\":{\"display_name\":\"Spline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[2128810469968776913,{\"persistent_metadata\":{\"display_name\":\"Far Mountains\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":16}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16611856724057842399,{\"persistent_metadata\":{\"display_name\":\"Dune\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[14577956936089455769,{\"persistent_metadata\":{\"display_name\":\"Vec2 Value\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"X\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Y\",\"input_description\":\"\"}}],\"output_names\":[\"Future<DVec2>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[8,-1]}}},\"network_metadata\":null}}],[17232801702996970986,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"min\":0.0,\"unit\":\" px\",\"mode\":\"Increment\",\"is_integer\":false},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":true,\"mode\":\"Increment\",\"min\":2.0},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"blank_assist\":true,\"mode\":\"Increment\",\"is_integer\":false,\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"min\":0.0,\"mode\":\"Increment\",\"unit\":\" px\",\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-13,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9065988052616974602,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"unit\":\" px\",\"is_integer\":false,\"blank_assist\":true,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":true,\"blank_assist\":true,\"min\":2.0,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"blank_assist\":true,\"min\":0.0,\"is_integer\":false,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":false,\"min\":0.0,\"unit\":\" px\",\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[1,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11731553664207576737,{\"persistent_metadata\":{\"display_name\":\"Jitter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Amount\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Seed\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-6,-3]}}},\"network_metadata\":null}}],[1791308547102433540,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-20,-3]}}},\"network_metadata\":null}}],[2288754089236120499,{\"persistent_metadata\":{\"display_name\":\"Spline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[22,-3]}}},\"network_metadata\":null}}],[16943732999059587742,{\"persistent_metadata\":{\"display_name\":\"Jitter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Amount\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Seed\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[8,-3]}}},\"network_metadata\":null}}],[14158287945315818946,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"is_integer\":false,\"y\":\"Y\",\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"H\",\"unit\":\"x\",\"x\":\"W\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"Data\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[15,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7828034197076821310,{\"persistent_metadata\":{\"display_name\":\"To u32\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"\"}}],\"output_names\":[\"Future<u32>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-13,1]}}},\"network_metadata\":null}}],[640915213983348287,{\"persistent_metadata\":{\"display_name\":\"Rectangle\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Width\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Height\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Individual Corner Radii\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Corner Radius\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Clamped\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-27,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,991.0,581.0],\"node_graph_width\":0.0},\"selection_undo_history\":[[]],\"selection_redo_history\":[]}}}}],[3726756269632080543,{\"persistent_metadata\":{\"display_name\":\"Dune\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[16943732999059587742,{\"persistent_metadata\":{\"display_name\":\"Jitter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Amount\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Seed\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[8,-3]}}},\"network_metadata\":null}}],[2288754089236120499,{\"persistent_metadata\":{\"display_name\":\"Spline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[22,-3]}}},\"network_metadata\":null}}],[640915213983348287,{\"persistent_metadata\":{\"display_name\":\"Rectangle\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Width\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Height\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Individual Corner Radii\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Corner Radius\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Clamped\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-27,-3]}}},\"network_metadata\":null}}],[5294703684886212416,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"unit\":\" px\",\"mode\":\"Increment\",\"blank_assist\":true,\"is_integer\":false},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":true,\"mode\":\"Increment\",\"blank_assist\":true,\"min\":2.0},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"is_integer\":false,\"unit\":\" px\",\"mode\":\"Increment\",\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"blank_assist\":true,\"is_integer\":false,\"min\":0.0,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-13,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7419291594083587754,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\" px\",\"blank_assist\":true,\"min\":0.0,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":2.0,\"blank_assist\":true,\"mode\":\"Increment\",\"is_integer\":true},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":false,\"min\":0.0,\"unit\":\" px\",\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":false,\"min\":0.0,\"mode\":\"Increment\",\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[1,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7828034197076821310,{\"persistent_metadata\":{\"display_name\":\"To u32\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"\"}}],\"output_names\":[\"Future<u32>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-13,1]}}},\"network_metadata\":null}}],[11731553664207576737,{\"persistent_metadata\":{\"display_name\":\"Jitter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Amount\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Seed\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-6,-3]}}},\"network_metadata\":null}}],[14158287945315818946,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"X\",\"y\":\"Y\",\"unit\":\" px\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\"x\",\"is_integer\":false,\"x\":\"W\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"Data\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[15,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[1791308547102433540,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-20,-3]}}},\"network_metadata\":null}}],[14577956936089455769,{\"persistent_metadata\":{\"display_name\":\"Vec2 Value\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"X\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Y\",\"input_description\":\"\"}}],\"output_names\":[\"Future<DVec2>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[8,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,991.0,581.0],\"node_graph_width\":0.0},\"selection_undo_history\":[[]],\"selection_redo_history\":[]}}}}],[17589660903986237301,{\"persistent_metadata\":{\"display_name\":\"Dune\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[12029121808718862100,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"unit\":\" px\",\"min\":0.0,\"mode\":\"Increment\",\"is_integer\":false},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":2.0,\"mode\":\"Increment\",\"blank_assist\":true,\"is_integer\":true},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"min\":0.0,\"unit\":\" px\",\"mode\":\"Increment\",\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\" px\",\"mode\":\"Increment\",\"blank_assist\":true,\"min\":0.0},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[1,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14158287945315818946,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"Y\",\"x\":\"X\",\"is_integer\":false,\"unit\":\" px\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"H\",\"x\":\"W\",\"is_integer\":false,\"unit\":\"x\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"Data\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[15,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7828034197076821310,{\"persistent_metadata\":{\"display_name\":\"To u32\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"\"}}],\"output_names\":[\"Future<u32>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-13,1]}}},\"network_metadata\":null}}],[1791308547102433540,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-20,-3]}}},\"network_metadata\":null}}],[16943732999059587742,{\"persistent_metadata\":{\"display_name\":\"Jitter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Amount\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Seed\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[8,-3]}}},\"network_metadata\":null}}],[640915213983348287,{\"persistent_metadata\":{\"display_name\":\"Rectangle\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Width\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Height\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Individual Corner Radii\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Corner Radius\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Clamped\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-27,-3]}}},\"network_metadata\":null}}],[2288754089236120499,{\"persistent_metadata\":{\"display_name\":\"Spline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[22,-3]}}},\"network_metadata\":null}}],[3289411516263327806,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"unit\":\" px\",\"is_integer\":false,\"min\":0.0,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":true,\"blank_assist\":true,\"min\":2.0,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":false,\"min\":0.0,\"mode\":\"Increment\",\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"unit\":\" px\",\"blank_assist\":true,\"mode\":\"Increment\",\"is_integer\":false},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-13,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11731553664207576737,{\"persistent_metadata\":{\"display_name\":\"Jitter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Amount\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Seed\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-6,-3]}}},\"network_metadata\":null}}],[14577956936089455769,{\"persistent_metadata\":{\"display_name\":\"Vec2 Value\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"X\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Y\",\"input_description\":\"\"}}],\"output_names\":[\"Future<DVec2>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[8,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,991.0,581.0],\"node_graph_width\":0.0},\"selection_undo_history\":[[]],\"selection_redo_history\":[]}}}}],[12317719117993811200,{\"persistent_metadata\":{\"display_name\":\"Dune 4\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2163528024003768644,{\"persistent_metadata\":{\"display_name\":\"Dune in Shadow 1\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-14,6]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7711570794020903773,{\"persistent_metadata\":{\"display_name\":\"Dune\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[3025883099767376126,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"blank_assist\":true,\"is_integer\":false,\"unit\":\" px\",\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"min\":2.0,\"blank_assist\":true,\"is_integer\":true},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"blank_assist\":true,\"min\":0.0,\"unit\":\" px\",\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"blank_assist\":true,\"is_integer\":false,\"min\":0.0,\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-13,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[640915213983348287,{\"persistent_metadata\":{\"display_name\":\"Rectangle\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Width\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Height\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Individual Corner Radii\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Corner Radius\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Clamped\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-27,-3]}}},\"network_metadata\":null}}],[11731553664207576737,{\"persistent_metadata\":{\"display_name\":\"Jitter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Amount\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Seed\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-6,-3]}}},\"network_metadata\":null}}],[7828034197076821310,{\"persistent_metadata\":{\"display_name\":\"To u32\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"\"}}],\"output_names\":[\"Future<u32>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-13,1]}}},\"network_metadata\":null}}],[1126802566044359983,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"mode\":\"Increment\",\"min\":0.0,\"unit\":\" px\",\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":true,\"min\":2.0,\"blank_assist\":true,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"mode\":\"Increment\",\"min\":0.0,\"unit\":\" px\",\"is_integer\":false},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"mode\":\"Increment\",\"blank_assist\":true,\"unit\":\" px\",\"is_integer\":false},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[1,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14577956936089455769,{\"persistent_metadata\":{\"display_name\":\"Vec2 Value\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"X\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Y\",\"input_description\":\"\"}}],\"output_names\":[\"Future<DVec2>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[8,-1]}}},\"network_metadata\":null}}],[16943732999059587742,{\"persistent_metadata\":{\"display_name\":\"Jitter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Amount\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Seed\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[8,-3]}}},\"network_metadata\":null}}],[2288754089236120499,{\"persistent_metadata\":{\"display_name\":\"Spline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[22,-3]}}},\"network_metadata\":null}}],[1791308547102433540,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[\"Future<Table<Vector>>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-20,-3]}}},\"network_metadata\":null}}],[14158287945315818946,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"x\":\"X\",\"is_integer\":false,\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\"x\",\"x\":\"W\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"Data\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[15,-3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,991.0,581.0],\"node_graph_width\":0.0},\"selection_undo_history\":[[]],\"selection_redo_history\":[]}}}}],[13132104524813958174,{\"persistent_metadata\":{\"display_name\":\"Foreground\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-4,3]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12110920487474373676,{\"persistent_metadata\":{\"display_name\":\"Dune in Shadow 2\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Out\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[393.8840949272409,-703.048313613491],\"tilt\":0.0,\"zoom\":0.7533811475409836,\"flip\":false},\"node_graph_to_viewport\":[0.7533811475409836,0.0,0.0,0.7533811475409836,1287.0,51.0],\"node_graph_width\":1981.0},\"selection_undo_history\":[[6097807941755042226,15385259560644295534,17327221498641745184,12317719117993811200,9069881382900058607],[17327221498641745184],[6097807941755042226,15385259560644295534,17327221498641745184,12317719117993811200,9069881382900058607],[6097807941755042226,5883606306991910210,2128810469968776913,12416569579970107543,13644002059194136823,17437077810654043142,15002088321732485705],[6097807941755042226,15385259560644295534,17327221498641745184,12317719117993811200,9069881382900058607],[2128810469968776913],[6097807941755042226,15385259560644295534,17327221498641745184,12317719117993811200,9069881382900058607],[15385259560644295534],[6097807941755042226,15385259560644295534,17327221498641745184,12317719117993811200,9069881382900058607],[2128810469968776913,12416569579970107543,13644002059194136823,17437077810654043142,15002088321732485705],[6097807941755042226,15385259560644295534,17327221498641745184,12317719117993811200,9069881382900058607],[13676600738025998635],[2128810469968776913],[11435147660766496741],[15726496377243608372],[4390668436091073484],[11386926595254122633],[11435147660766496741],[15726496377243608372],[15726496377243608372,11435147660766496741,2163528024003768644,12110920487474373676,12639486733043466090,8804763001225416124],[11435147660766496741,2163528024003768644,12110920487474373676,12639486733043466090,8804763001225416124,15726496377243608372],[11435147660766496741],[11435147660766496741,2163528024003768644,12110920487474373676,12639486733043466090,8804763001225416124,15726496377243608372],[11435147660766496741,15726496377243608372,4390668436091073484,11386926595254122633,13676600738025998635,2128810469968776913],[11435147660766496741,2163528024003768644,12110920487474373676,12639486733043466090,8804763001225416124,15726496377243608372],[11435147660766496741],[11435147660766496741,2163528024003768644,12110920487474373676,12639486733043466090,8804763001225416124,15726496377243608372],[4390668436091073484,11386926595254122633,13676600738025998635,2128810469968776913],[11435147660766496741,2163528024003768644,12110920487474373676,12639486733043466090,8804763001225416124,15726496377243608372],[11435147660766496741,2163528024003768644,7128559142392931896,12110920487474373676,13323241418027154136,12639486733043466090,1801066723091712434,8804763001225416124,17154757625902243313,4390668436091073484,12122314434656187176,16611856724057842399,10686861494573327243,10806978668166337270,11386926595254122633,6523786079462141312,13676600738025998635,6097807941755042226,5883606306991910210,15385259560644295534,3726756269632080543,17327221498641745184,3683309254695891012,12317719117993811200,17239043462037837834,9069881382900058607,4037968351431607570,2128810469968776913,12416569579970107543,13644002059194136823,17437077810654043142,15002088321732485705],[11435147660766496741,2163528024003768644,12110920487474373676,12639486733043466090,8804763001225416124,15726496377243608372],[4390668436091073484,11386926595254122633,13676600738025998635,2128810469968776913],[11435147660766496741,2163528024003768644,12110920487474373676,12639486733043466090,8804763001225416124,15726496377243608372],[15726496377243608372],[11435147660766496741,2163528024003768644,12110920487474373676,12639486733043466090,8804763001225416124,15726496377243608372],[4390668436091073484,12122314434656187176,16611856724057842399,10686861494573327243,10806978668166337270,11386926595254122633,6523786079462141312,13676600738025998635,6097807941755042226,5883606306991910210,15385259560644295534,3726756269632080543,17327221498641745184,3683309254695891012,12317719117993811200,17239043462037837834,9069881382900058607,4037968351431607570,2128810469968776913,12416569579970107543,13644002059194136823,17437077810654043142,15002088321732485705],[11435147660766496741,2163528024003768644,12110920487474373676,12639486733043466090,8804763001225416124,15726496377243608372],[13132104524813958174],[11435147660766496741],[2163528024003768644],[12110920487474373676],[12639486733043466090],[8804763001225416124],[11435147660766496741],[15726496377243608372],[3934928477288442109],[239716716021064150],[15726496377243608372],[15726496377243608372,11435147660766496741],[239716716021064150],[239716716021064150,7711570794020903773],[239716716021064150,3934928477288442109],[4390668436091073484],[12122314434656187176],[10686861494573327243],[11386926595254122633],[4390668436091073484],[13676600738025998635,2128810469968776913],[13676600738025998635,6097807941755042226,5883606306991910210,15385259560644295534,3726756269632080543,17327221498641745184,3683309254695891012,12317719117993811200,17239043462037837834,9069881382900058607,4037968351431607570,2128810469968776913,12416569579970107543,13644002059194136823,17437077810654043142,15002088321732485705],[11386926595254122633],[13676600738025998635],[6097807941755042226],[15385259560644295534],[17327221498641745184],[12317719117993811200],[9069881382900058607],[2128810469968776913],[13132104524813958174],[2128810469968776913],[13132104524813958174],[2163528024003768644],[239716716021064150],[3934928477288442109],[8804763001225416124],[12639486733043466090],[12110920487474373676],[2163528024003768644],[4390668436091073484],[13676600738025998635],[2128810469968776913],[2163528024003768644],[9069881382900058607],[12317719117993811200,9069881382900058607],[17327221498641745184,12317719117993811200,9069881382900058607],[15385259560644295534,17327221498641745184,12317719117993811200,9069881382900058607],[12317719117993811200,15385259560644295534,9069881382900058607,17327221498641745184,6097807941755042226],[17327221498641745184,12317719117993811200,11386926595254122633,6097807941755042226,9069881382900058607,15385259560644295534],[9069881382900058607,6097807941755042226,10686861494573327243,15385259560644295534,11386926595254122633,12317719117993811200,17327221498641745184],[10686861494573327243,15385259560644295534,12122314434656187176,11386926595254122633,9069881382900058607,6097807941755042226,12317719117993811200,17327221498641745184],[12122314434656187176,6097807941755042226,11386926595254122633,12317719117993811200,17327221498641745184,10686861494573327243,239716716021064150,15385259560644295534,9069881382900058607],[9069881382900058607,239716716021064150,6097807941755042226,12122314434656187176,12317719117993811200,10686861494573327243,15385259560644295534,17327221498641745184,11386926595254122633,3934928477288442109],[9069881382900058607,13676600738025998635,3934928477288442109,12122314434656187176,6097807941755042226,15385259560644295534,4390668436091073484,10686861494573327243,17327221498641745184,11386926595254122633,12317719117993811200,239716716021064150],[6097807941755042226,3934928477288442109,4390668436091073484,9069881382900058607,13676600738025998635,11386926595254122633,15385259560644295534,12122314434656187176,8804763001225416124,10686861494573327243,239716716021064150,12317719117993811200,17327221498641745184],[9069881382900058607,12122314434656187176,13676600738025998635,10686861494573327243,12317719117993811200,6097807941755042226,15385259560644295534,239716716021064150,17327221498641745184,11386926595254122633,3934928477288442109,8804763001225416124,12639486733043466090,4390668436091073484],[10686861494573327243,15385259560644295534,17327221498641745184,239716716021064150,8804763001225416124,6097807941755042226,11386926595254122633,12317719117993811200,12122314434656187176,9069881382900058607,3934928477288442109,12639486733043466090],[10686861494573327243,17327221498641745184,12317719117993811200,11386926595254122633,12639486733043466090,12122314434656187176,9069881382900058607,6097807941755042226,12110920487474373676,3934928477288442109,15385259560644295534,8804763001225416124,239716716021064150],[12110920487474373676,10686861494573327243,11386926595254122633,12317719117993811200,8804763001225416124,3934928477288442109,9069881382900058607,15385259560644295534,2163528024003768644,17327221498641745184,239716716021064150,12639486733043466090,12122314434656187176,6097807941755042226],[2128810469968776913],[17327221498641745184],[]],\"selection_redo_history\":[]}}},\"collapsed\":[4390668436091073485,13676600738025998636,13132104524813958175],\"commit_hash\":\"c4e16e1aac642bbcde72f41f86deed79e6e38006\",\"document_ptz\":{\"pan\":[-999.515765085624,-500.18277881031514],\"tilt\":0.0,\"zoom\":0.940975,\"flip\":false},\"render_mode\":\"Normal\",\"overlays_visibility_settings\":{\"all\":true,\"artboard_name\":true,\"compass_rose\":true,\"quick_measurement\":true,\"transform_measurement\":true,\"transform_cage\":true,\"hover_outline\":true,\"selection_outline\":true,\"layer_origin_cross\":true,\"pivot\":true,\"origin\":true,\"path\":true,\"anchors\":true,\"handles\":true},\"rulers_visible\":true,\"snapping_state\":{\"snapping_enabled\":false,\"grid_snapping\":false,\"artboards\":true,\"tolerance\":8.0,\"bounding_box\":{\"center_point\":true,\"corner_point\":true,\"edge_midpoint\":true,\"align_with_edges\":true,\"distribute_evenly\":true},\"path\":{\"anchor_point\":true,\"line_midpoint\":true,\"along_path\":true,\"normal_to_path\":true,\"tangent_to_path\":true,\"path_intersection_point\":true,\"align_with_anchor_point\":true,\"perpendicular_from_endpoint\":true},\"grid\":{\"origin\":[0.0,0.0],\"grid_type\":{\"Rectangular\":{\"spacing\":[1.0,1.0]}},\"rectangular_spacing\":[1.0,1.0],\"isometric_y_spacing\":1.0,\"isometric_angle_a\":30.0,\"isometric_angle_b\":30.0,\"grid_color\":{\"red\":0.6038274,\"green\":0.6038274,\"blue\":0.6038274,\"alpha\":1.0},\"dot_display\":false}},\"graph_view_overlay_open\":false,\"graph_fade_artwork_percentage\":80.0}"
  },
  {
    "path": "demo-artwork/procedural-string-lights.graphite",
    "content": "{\"network_interface\":{\"network\":{\"exports\":[{\"Node\":{\"node_id\":14972365039974885000,\"output_index\":0}}],\"nodes\":[[183562335973647870,{\"inputs\":[{\"Node\":{\"node_id\":4248875763694880300,\"output_index\":0}},{\"Node\":{\"node_id\":2181148486404191200,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[665049002420596400,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34],\"remove\":[],\"delta\":[[3,[45.77777777777777,145.33333333333331]],[18,[145.77777777777777,-97.7777777777778]],[9,[156.4444444444444,97.33333333333331]],[21,[76.0,-202.22222222222223]],[28,[39.111111111111086,-58.22222222222226]],[23,[19.555555555555543,-139.55555555555557]],[6,[123.99999999999994,148.4444444444444]],[30,[-9.333333333333371,0.0]],[16,[159.5555555555555,-75.55555555555557]],[29,[-32.888888888888914,-14.666666666666686]],[20,[128.4444444444444,-142.22222222222223]],[13,[153.77777777777777,-1.3333333333333712]],[27,[-11.111111111111144,-72.00000000000003]],[4,[18.66666666666663,157.77777777777771]],[25,[0.8888888888888573,-94.66666666666669]],[11,[136.88888888888886,39.111111111111086]],[8,[252.4444444444444,124.88888888888886]],[32,[9.333333333333314,38.66666666666663]],[17,[123.99999999999994,-84.00000000000003]],[15,[111.11111111111114,-60.888888888888914]],[5,[66.22222222222223,152.88888888888886]],[33,[-78.22222222222223,75.55555555555554]],[24,[57.77777777777777,-129.33333333333334]],[19,[90.66666666666664,-129.33333333333334]],[12,[199.5555555555555,10.666666666666629]],[31,[-53.33333333333337,13.777777777777771]],[34,[-5.333333333333371,99.11111111111109]],[10,[216.4444444444444,72.4444444444444]],[14,[183.11111111111103,-19.111111111111143]],[26,[22.66666666666663,-81.33333333333337]],[7,[229.33333333333331,152.4444444444444]],[2,[-67.55555555555554,158.22222222222217]],[22,[71.11111111111109,-201.7777777777778]],[1,[-103.55555555555554,126.66666666666664]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34],\"remove\":[],\"start_point\":[[1,1],[14,14],[8,8],[33,33],[30,30],[9,9],[13,13],[15,15],[3,3],[7,7],[26,26],[18,18],[27,27],[6,6],[34,34],[21,21],[4,4],[20,20],[16,16],[11,11],[32,32],[19,19],[17,17],[2,2],[24,24],[22,22],[23,23],[29,29],[10,10],[28,28],[5,5],[25,25],[31,31],[12,12]],\"end_point\":[[7,8],[31,32],[3,4],[14,15],[1,2],[16,17],[32,33],[11,12],[33,34],[30,31],[34,1],[27,28],[13,14],[12,13],[18,19],[2,3],[21,22],[6,7],[22,23],[10,11],[26,27],[4,5],[28,29],[8,9],[5,6],[20,21],[23,24],[17,18],[25,26],[19,20],[29,30],[9,10],[24,25],[15,16]],\"handle_primary\":[[13,[0.0,0.0]],[10,[0.0,0.0]],[1,[0.0,0.0]],[8,[0.0,0.0]],[5,[0.0,0.0]],[30,[0.0,0.0]],[6,[0.0,0.0]],[7,[24.88888888888891,-15.111111111111086]],[34,[0.0,0.0]],[22,[0.0,0.0]],[28,[0.0,0.0]],[9,[0.0,0.0]],[31,[0.0,0.0]],[26,[0.0,0.0]],[33,[0.0,0.0]],[23,[0.0,0.0]],[20,[0.0,0.0]],[27,[0.0,0.0]],[17,[0.0,0.0]],[12,[0.0,0.0]],[14,[0.0,0.0]],[19,[0.0,0.0]],[4,[0.0,0.0]],[2,[30.66666666666663,2.6666666666666856]],[11,[0.0,0.0]],[3,[0.0,0.0]],[16,[0.0,0.0]],[29,[0.0,0.0]],[24,[0.0,0.0]],[21,[0.0,0.0]],[25,[0.0,0.0]],[18,[0.0,0.0]],[15,[0.0,0.0]],[32,[0.0,0.0]]],\"handle_end\":[[9,[-34.222222222222285,36.0]],[2,[-51.111111111111086,14.666666666666686]],[25,[-11.111111111111144,-1.7777777777778]],[4,[-31.555555555555543,18.22222222222223]],[30,[35.111111111111086,1.333333333333373]],[24,[47.55555555555554,4.888888888888886]],[14,[29.33333333333337,57.77777777777777]],[23,[-35.55555555555554,11.1111111111111]],[31,[-42.22222222222223,5.333333333333314]],[22,[38.22222222222223,4.888888888888886]],[3,[8.4444444444444,-0.8888888888889142]],[7,[0.0,0.0]],[10,[26.66666666666663,44.44444444444446]],[16,[6.666666666666629,13.333333333333314]],[5,[-13.333333333333384,14.222222222222229]],[6,[-24.88888888888891,15.111111111111086]],[8,[36.0,41.77777777777777]],[19,[-9.777777777777844,23.55555555555557]],[28,[54.66666666666663,6.666666666666686]],[33,[-64.44444444444446,3.111111111111157]],[1,[-30.666666666666615,-2.6666666666666856]],[29,[-17.77777777777777,-3.555555555555543]],[21,[0.0,0.0]],[12,[18.66666666666663,16.0]],[15,[-12.444444444444455,28.444444444444457]],[34,[65.7777777777778,12.444444444444445]],[11,[-24.0,31.555555555555543]],[17,[-10.222222222222172,12.444444444444455]],[26,[26.22222222222223,2.666666666666657]],[20,[13.333333333333371,70.22222222222223]],[27,[-39.55555555555554,12.444444444444429]],[13,[-13.333333333333371,14.222222222222229]],[32,[55.111111111111086,9.777777777777771]],[18,[13.3333333333333,41.33333333333334]]],\"stroke\":[[5,0],[13,0],[27,0],[32,0],[24,0],[1,0],[20,0],[28,0],[31,0],[34,0],[29,0],[33,0],[19,0],[17,0],[18,0],[8,0],[14,0],[9,0],[3,0],[16,0],[30,0],[26,0],[2,0],[6,0],[25,0],[12,0],[10,0],[22,0],[23,0],[7,0],[15,0],[11,0],[4,0],[21,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":34}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14972365039974885000,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Artboard\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":3471929742275053000,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1000.0,1000.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":1}},{\"Value\":{\"tagged_value\":{\"String\":\"Artboard\"},\"exposed\":false}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":5}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::artboard::CreateArtboardNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Import\":{\"import_type\":{\"Fn\":[{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::artboard::Artboard>\",\"alias\":null}}]},\"import_index\":0}},{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13394621587544123000,{\"inputs\":[{\"Node\":{\"node_id\":13907401402762847509,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.6484375,\"green\":0.6484375,\"blue\":0.6484375,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.6484375,\"green\":0.6484375,\"blue\":0.6484375,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.91796875,\"green\":0.68489075,\"blue\":0.68489075,\"alpha\":1.0},{\"red\":0.8862745,\"green\":0.16470589,\"blue\":0.16470589,\"alpha\":1.0}]},\"gradient_type\":\"Radial\",\"start\":[0.4915209831246563,0.36613756613756576],\"end\":[0.49551110871305326,0.9947089947089944]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11411423299989983000,{\"inputs\":[{\"Node\":{\"node_id\":16877573495957869000,\"output_index\":0}},{\"Node\":{\"node_id\":3958246774416220000,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14631609508767818000,{\"inputs\":[{\"Node\":{\"node_id\":15889416971203222000,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[487.1243076693745,127.7443401649906]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[25.393705016577044,25.003032631706716]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2908374490615384600,{\"inputs\":[{\"Node\":{\"node_id\":17339085479159577000,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.078431375,\"green\":0.14901961,\"blue\":0.20784314,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.078431375,\"green\":0.14901961,\"blue\":0.20784314,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16765094648901306000,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":8147814087664910471,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[655907162126315400,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11],\"remove\":[],\"delta\":[[6,[495.0,533.9999999999999]],[8,[475.0,671.9999999999999]],[3,[373.0,372.9999999999999]],[11,[186.0,824.9999999999999]],[5,[639.0,438.9999999999999]],[1,[416.0,270.9999999999999]],[4,[484.0,446.9999999999999]],[7,[304.0,611.9999999999999]],[9,[750.0,703.9999999999999]],[2,[587.0,291.9999999999999]],[10,[455.0,824.9999999999999]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10],\"remove\":[],\"start_point\":[[6,6],[3,3],[9,9],[4,4],[7,7],[2,2],[5,5],[1,1],[8,8],[10,10]],\"end_point\":[[5,6],[6,7],[10,11],[1,2],[9,10],[7,8],[8,9],[4,5],[2,3],[3,4]],\"handle_primary\":[[1,[0.0,0.0]],[3,[0.0,0.0]],[5,[0.0,0.0]],[8,[0.0,0.0]],[4,[0.0,0.0]],[9,[0.0,0.0]],[7,[0.0,0.0]],[6,[0.0,0.0]],[2,[0.0,0.0]],[10,[0.0,0.0]]],\"handle_end\":[[9,[0.0,0.0]],[7,[0.0,0.0]],[2,[0.0,0.0]],[3,[0.0,0.0]],[1,[0.0,0.0]],[8,[0.0,0.0]],[4,[0.0,0.0]],[5,[0.0,0.0]],[10,[0.0,0.0]],[6,[0.0,0.0]]],\"stroke\":[[1,0],[7,0],[6,0],[3,0],[8,0],[10,0],[9,0],[4,0],[5,0],[2,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16877573495957869000,{\"inputs\":[{\"Node\":{\"node_id\":4534782777857480700,\"output_index\":0}},{\"Node\":{\"node_id\":5737014828407011000,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5737014828407011000,{\"inputs\":[{\"Node\":{\"node_id\":10504222558938851000,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[499.21344163872624,106.32837674079803]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":180.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[55.110312549931045,55.110312549931045]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-1.1009529959239839e-14,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10504222558938851000,{\"inputs\":[{\"Node\":{\"node_id\":13571989088655643000,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":1.0,\"green\":0.94460994,\"blue\":0.79296875,\"alpha\":1.0},{\"red\":0.89411765,\"green\":0.654902,\"blue\":0.0,\"alpha\":1.0}]},\"gradient_type\":\"Radial\",\"start\":[0.4703098217208352,0.4995258072961386],\"end\":[0.9924395932459462,0.5005395053456176]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":1.0,\"green\":0.94460994,\"blue\":0.79296875,\"alpha\":1.0},{\"red\":0.89411765,\"green\":0.654902,\"blue\":0.0,\"alpha\":1.0}]},\"gradient_type\":\"Radial\",\"start\":[0.4703098217208352,0.4995258072961386],\"end\":[0.9924395932459462,0.5005395053456176]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4248875763694880300,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":7297408968096180000,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3471929742275053000,{\"inputs\":[{\"Node\":{\"node_id\":11411423299989983000,\"output_index\":0}},{\"Node\":{\"node_id\":4217566479741824000,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[169695252050218443,{\"inputs\":[{\"Node\":{\"node_id\":6559102076450693000,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":0.75},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":0.01}]},\"gradient_type\":\"Radial\",\"start\":[0.4915209831246563,0.36613756613756576],\"end\":[0.49551110871305326,0.9947089947089944]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":0.7734375},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":0.01}]},\"gradient_type\":\"Radial\",\"start\":[0.4915209831246563,0.36613756613756576],\"end\":[0.49551110871305326,0.9947089947089944]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5250960114142560178,{\"inputs\":[{\"Node\":{\"node_id\":655907162126315400,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.18629456,\"green\":0.18054199,\"blue\":0.2265625,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":8.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeAlign\":\"Center\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeCap\":\"Round\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeJoin\":\"Miter\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"PaintOrder\":\"StrokeAbove\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"VecF64\":[]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::StrokeNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17339085479159577000,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7],\"remove\":[],\"delta\":[[3,[123.55555555555554,199.1111111111111]],[4,[28.296296296296305,198.96296296296293]],[6,[58.962962962962976,152.74074074074073]],[7,[66.51851851851853,147.1111111111111]],[1,[88.4444444444444,151.55555555555554]],[5,[28.296296296296305,197.33333333333331]],[2,[122.22222222222224,196.4444444444444]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7],\"remove\":[],\"start_point\":[[5,5],[3,3],[4,4],[2,2],[7,7],[1,1],[6,6]],\"end_point\":[[2,3],[6,7],[5,6],[1,2],[7,1],[4,5],[3,4]],\"handle_primary\":[[5,[0.0,0.0]],[3,[0.0,0.0]],[4,[0.0,0.0]],[7,[0.0,0.0]],[2,[0.0,0.0]],[1,[0.0,0.0]],[6,[0.0,0.0]]],\"handle_end\":[[6,[0.0,0.0]],[5,[5.92592592592591,45.77777777777786]],[1,[-32.0,-3.5555555555555145]],[7,[0.0,0.0]],[2,[0.0,0.0]],[3,[0.0,0.0]],[4,[0.0,0.0]]],\"stroke\":[[7,0],[4,0],[2,0],[1,0],[5,0],[6,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":7}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13571989088655643000,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":5},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.5},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::generator_nodes::StarNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8147814087664910471,{\"inputs\":[{\"Node\":{\"node_id\":2866788868013687300,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.3372549,\"green\":0.33333334,\"blue\":0.40784314,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.3372549,\"green\":0.33333334,\"blue\":0.40784314,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13907401402762847509,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[10666000720855117911,12291367210538906710,11079673538758176715,9852417284720842144,13528392218319754720],\"position\":[[0.0,16.600000381469727],[13.314000129699709,30.4060001373291],[0.0,66.8030014038086],[-13.314000129699709,30.4060001373291],[0.0,16.600000381469727]]},\"segment_domain\":{\"id\":[1,2,3,4,5],\"start_point\":[0,1,2,3,4],\"end_point\":[1,2,3,4,0],\"handles\":[{\"Cubic\":{\"handle_start\":[7.347000122070312,16.600000381469727],\"handle_end\":[13.314000129699709,22.788000106811523]}},{\"Cubic\":{\"handle_start\":[13.314000129699709,38.02399826049805],\"handle_end\":[4.480999946594238,66.8030014038086]}},{\"Cubic\":{\"handle_start\":[-4.480999946594238,66.8030014038086],\"handle_end\":[-13.314000129699709,38.02299880981445]}},{\"Cubic\":{\"handle_start\":[-13.314000129699709,22.788000106811523],\"handle_end\":[-7.347000122070312,16.600000381469727]}},{\"Cubic\":{\"handle_start\":[0.0,16.600000381469727],\"handle_end\":[0.0,16.600000381469727]}}],\"stroke\":[0,0,0,0,0]},\"region_domain\":{\"id\":[0],\"segment_range\":[{\"start\":1,\"end\":5}],\"fill\":[0]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[],\"remove\":[13528392218319754720],\"delta\":[]},\"segments\":{\"add\":[4],\"remove\":[5],\"start_point\":[[4,9852417284720842144]],\"end_point\":[[4,10666000720855117911]],\"handle_primary\":[[4,[0.0,-7.618000030517578]]],\"handle_end\":[[4,[-7.34700012207,-3.552713678800501e-15]]],\"stroke\":[[4,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[[{\"ty\":\"End\",\"segment\":4},{\"ty\":\"Primary\",\"segment\":1}],[{\"ty\":\"End\",\"segment\":4},{\"ty\":\"Primary\",\"segment\":5}]]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6559102076450693000,{\"inputs\":[{\"Node\":{\"node_id\":13907401402762847509,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2866788868013687300,{\"inputs\":[{\"Node\":{\"node_id\":8496292024993914696,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4331062027851128000,{\"inputs\":[{\"Node\":{\"node_id\":665049002420596400,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.1764706,\"green\":0.25882354,\"blue\":0.32156864,\"alpha\":1.0},{\"red\":0.16577148,\"green\":0.37890625,\"blue\":0.36788198,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.3769992978075994,0.1888891278143931],\"end\":[0.9861902161192166,0.9200728483862376]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.1764706,\"green\":0.25882354,\"blue\":0.32156864,\"alpha\":1.0},{\"red\":0.16577148,\"green\":0.37890625,\"blue\":0.36788198,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.3769992978075994,0.1888891278143931],\"end\":[0.9861902161192166,0.9200728483862376]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11815560782623298000,{\"inputs\":[{\"Node\":{\"node_id\":16765094648901306000,\"output_index\":0}},{\"Node\":{\"node_id\":13394621587544123000,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13371003476981866000,{\"inputs\":[{\"Node\":{\"node_id\":11815560782623298000,\"output_index\":0}},{\"Node\":{\"node_id\":169695252050218443,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4534782777857480700,{\"inputs\":[{\"Node\":{\"node_id\":183562335973647870,\"output_index\":0}},{\"Node\":{\"node_id\":14631609508767818000,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17790961655412892000,{\"inputs\":[{\"Node\":{\"node_id\":12728151724950013388,\"output_index\":0}},{\"Node\":{\"node_id\":13371003476981866000,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":10.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2181148486404191200,{\"inputs\":[{\"Node\":{\"node_id\":4331062027851128000,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[353.5143520436918,551.6238777493922]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.9777789484064812,1.9777789484064812]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4217566479741824000,{\"inputs\":[{\"Node\":{\"node_id\":17790961655412892000,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"GradientTable\":{\"element\":[{\"position\":[0.0,0.5,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.16470589,\"green\":0.8862745,\"blue\":0.4117647,\"alpha\":1.0},{\"red\":0.8862745,\"green\":0.16470589,\"blue\":0.16470589,\"alpha\":1.0},{\"red\":0.16470589,\"green\":0.54901963,\"blue\":0.8862745,\"alpha\":1.0}]}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":3},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::AssignColorsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3958246774416220000,{\"inputs\":[{\"Node\":{\"node_id\":5250960114142560178,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SplineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7297408968096180000,{\"inputs\":[{\"Node\":{\"node_id\":2908374490615384600,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[353.5143520436944,551.6238777493922]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.9777789484064812,1.9777789484064812]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8309013977031955000,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[4,[0.0,1.0]],[3,[1.0,1.0]],[1,[0.0,0.0]],[2,[1.0,0.0]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[4,4],[3,3],[2,2],[1,1]],\"end_point\":[[2,3],[4,1],[1,2],[3,4]],\"handle_primary\":[[2,[0.0,0.0]],[1,[0.0,0.0]],[4,[0.0,0.0]],[3,[0.0,0.0]]],\"handle_end\":[[1,[0.0,0.0]],[3,[0.0,0.0]],[4,[0.0,0.0]],[2,[0.0,0.0]]],\"stroke\":[[4,0],[3,0],[2,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8496292024993914696,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[8579252446942557840,5702904670777106146,6914816536199142761,17771770146045579307,4317718082554655671],\"position\":[[-5.75,-0.4000000059604645],[5.75,-0.4000000059604645],[10.0,21.600000381469727],[-10.0,21.600000381469727],[-5.75,-0.4000000059604645]]},\"segment_domain\":{\"id\":[1,2,3,4,5],\"start_point\":[0,1,2,3,4],\"end_point\":[1,2,3,4,0],\"handles\":[{\"Cubic\":{\"handle_start\":[-5.75,-0.4000000059604645],\"handle_end\":[5.75,-0.4000000059604645]}},{\"Cubic\":{\"handle_start\":[5.75,-0.4000000059604645],\"handle_end\":[10.0,21.600000381469727]}},{\"Cubic\":{\"handle_start\":[10.0,21.600000381469727],\"handle_end\":[-10.0,21.600000381469727]}},{\"Cubic\":{\"handle_start\":[-10.0,21.600000381469727],\"handle_end\":[-5.75,-0.4000000059604645]}},{\"Cubic\":{\"handle_start\":[-5.75,-0.4000000059604645],\"handle_end\":[-5.75,-0.4000000059604645]}}],\"stroke\":[0,0,0,0,0]},\"region_domain\":{\"id\":[0],\"segment_range\":[{\"start\":1,\"end\":5}],\"fill\":[0]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[],\"remove\":[4317718082554655671],\"delta\":[]},\"segments\":{\"add\":[4],\"remove\":[5],\"start_point\":[[4,17771770146045579307]],\"end_point\":[[4,8579252446942557840]],\"handle_primary\":[[4,[0.0,0.0]]],\"handle_end\":[[4,[0.0,0.0]]],\"stroke\":[[4,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15889416971203222000,{\"inputs\":[{\"Node\":{\"node_id\":8309013977031955000,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.89411765,\"green\":0.654902,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.89411765,\"green\":0.654902,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12728151724950013388,{\"inputs\":[{\"Node\":{\"node_id\":3958246774416220000,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":105.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[13907401402762847509,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Modification\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-50,21]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3958246774416220000,{\"persistent_metadata\":{\"display_name\":\"Spline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-36,15]}}},\"network_metadata\":null}}],[3471929742275053000,{\"persistent_metadata\":{\"display_name\":\"Lights\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-8,12]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11411423299989983000,{\"persistent_metadata\":{\"display_name\":\"Wire (Use Path Tool to Reshape This!)\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4331062027851128000,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[8147814087664910471,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[8309013977031955000,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16765094648901306000,{\"persistent_metadata\":{\"display_name\":\"Bulb Housing\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13371003476981866000,{\"persistent_metadata\":{\"display_name\":\"Bulb Glow Gradient\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-29,18]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[6559102076450693000,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Element\",\"input_description\":\"\"}}],\"output_names\":[\"Table<Graphic>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[2908374490615384600,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[5250960114142560178,{\"persistent_metadata\":{\"display_name\":\"Stroke\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the stroke style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Color\",\"input_description\":\"The stroke color.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Weight\",\"input_description\":\"The stroke weight.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Align\",\"input_description\":\"The alignment of stroke to the path's centerline or (for closed shapes) the inside or outside of the shape.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Cap\",\"input_description\":\"The shape of the stroke at open endpoints.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Join\",\"input_description\":\"The curvature of the bent stroke at sharp corners.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Miter Limit\",\"input_description\":\"The threshold for when a miter-joined stroke is converted to a bevel-joined stroke when a sharp angle becomes pointier than this ratio.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Paint Order\",\"input_description\":\"The order to paint the stroke on top of the fill, or the fill on top of the stroke.\\n<https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty>\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Lengths\",\"input_description\":\"The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Offset\",\"input_description\":\"The phase offset distance from the starting point of the dash pattern.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-43,15]}}},\"network_metadata\":null}}],[2181148486404191200,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"x\":\"X\",\"unit\":\" px\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"y\":\"H\",\"unit\":\"x\",\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[8496292024993914696,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"Modification\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4534782777857480700,{\"persistent_metadata\":{\"display_name\":\"Star Base\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[5737014828407011000,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"Y\",\"unit\":\" px\",\"x\":\"X\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\"x\",\"x\":\"W\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[17790961655412892000,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[12728151724950013388,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"blank_assist\":true,\"mode\":\"Increment\",\"unit\":\" px\",\"is_integer\":false},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"is_integer\":true,\"blank_assist\":true,\"min\":2.0},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":false,\"unit\":\" px\",\"mode\":\"Increment\",\"min\":0.0},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"mode\":\"Increment\",\"min\":0.0,\"is_integer\":false,\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14972365039974885000,{\"persistent_metadata\":{\"display_name\":\"Artboard\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Artboards\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Contents\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"Y\",\"is_integer\":true,\"unit\":\" px\",\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Location\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":true,\"y\":\"H\",\"x\":\"W\",\"unit\":\" px\"},\"widget_override\":\"vec2\",\"input_name\":\"Dimensions\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"artboard_background\",\"input_name\":\"Background\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Clip\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-4,9]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Artboard\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-4]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Create Artboard\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16877573495957869000,{\"persistent_metadata\":{\"display_name\":\"Star\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":10}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[169695252050218443,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[665049002420596400,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[655907162126315400,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-50,15]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7297408968096180000,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"is_integer\":false,\"y\":\"Y\",\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"y\":\"H\",\"is_integer\":false,\"unit\":\"x\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10504222558938851000,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[17339085479159577000,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15889416971203222000,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[13571989088655643000,{\"persistent_metadata\":{\"display_name\":\"Star\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Sides\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Radius 1\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Radius 2\",\"input_description\":\"\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[183562335973647870,{\"persistent_metadata\":{\"display_name\":\"Tree\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11815560782623298000,{\"persistent_metadata\":{\"display_name\":\"Bulb Shape\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4248875763694880300,{\"persistent_metadata\":{\"display_name\":\"Tree Stump\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14631609508767818000,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\" px\",\"x\":\"X\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\"x\",\"x\":\"W\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4217566479741824000,{\"persistent_metadata\":{\"display_name\":\"Assign Colors\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill and/or stroke style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"Whether to style the fill.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Stroke\",\"input_description\":\"Whether to style the stroke.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"assign_colors_gradient\",\"input_name\":\"Gradient\",\"input_description\":\"The range of colors to select from.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Reverse\",\"input_description\":\"Whether to reverse the gradient.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Randomize\",\"input_description\":\"Whether to randomize the color selection for each element from throughout the gradient.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"assign_colors_seed\",\"input_name\":\"Seed\",\"input_description\":\"The seed used for randomization.\\nSeed to determine unique variations on the randomized color selection.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"assign_colors_repeat_every\",\"input_name\":\"Repeat Every\",\"input_description\":\"The number of elements to span across the gradient before repeating. A 0 value will span the entire gradient once.\\n\"}}],\"output_names\":[\"Content\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[13394621587544123000,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[2866788868013687300,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Element\",\"input_description\":\"\"}}],\"output_names\":[\"Table<Graphic>\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[527.9166666666665,-576.1833333333332],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,1518.0,4.0],\"node_graph_width\":1981.0},\"selection_undo_history\":[[],[2866788868013687300],[],[4352028121261571600],[],[2866788868013687300],[6559102076450693000],[6559102076450693000],[10775791528628074000],[],[6559102076450693000],[],[],[],[],[],[15084833709935380000],[],[16765094648901306000],[16765094648901306000,11815560782623298000],[16765094648901306000,13371003476981866000,11815560782623298000],[6559102076450693000,16765094648901306000,13394621587544123000,11815560782623298000,2866788868013687300,13371003476981866000],[3958246774416220000],[],[],[3958246774416220000],[3958246774416220000,655907162126315400],[],[3958246774416220000],[],[3958246774416220000],[655907162126315400,3958246774416220000],[],[],[3958246774416220000],[655907162126315400,3958246774416220000],[],[14631609508767818000],[],[5737014828407011000],[],[10118219203151733000],[],[],[],[],[],[],[655907162126315400],[],[],[655907162126315400,3958246774416220000],[],[],[16877573495957869000],[11411423299989983000],[16877573495957869000],[3471929742275053000],[11411423299989983000],[11411423299989983000,15209576944107258000],[15209576944107258000,3287844738046380000,11411423299989983000],[15209576944107258000,3287844738046380000,11411423299989983000,3958246774416220000],[3958246774416220000,655907162126315400,15209576944107258000,11411423299989983000,3287844738046380000],[4248875763694880300],[11411423299989983000],[3471929742275053000],[11411423299989983000],[15223368326030279287],[11815560782623298000],[11815560782623298000,16765094648901306000],[11815560782623298000],[13371003476981866000],[16765094648901306000],[15223368326030279287],[13371003476981866000],[13371003476981866000,16765094648901306000,11815560782623298000],[13371003476981866000],[11815560782623298000],[16765094648901306000],[13371003476981866000],[16765094648901306000],[11815560782623298000],[13371003476981866000],[11815560782623298000],[13371003476981866000],[16765094648901306000],[13371003476981866000],[16877573495957869000],[16765094648901306000],[13371003476981866000],[16765094648901306000],[11411423299989983000],[]],\"selection_redo_history\":[]}}},\"collapsed\":[],\"commit_hash\":\"95bbc95606ba40ed7441fdf4e1b954d80b72e3dc\",\"document_ptz\":{\"pan\":[-500.157717622685,-499.8045823704831],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"render_mode\":\"Normal\",\"overlays_visibility_settings\":{\"all\":true,\"artboard_name\":true,\"compass_rose\":true,\"quick_measurement\":true,\"transform_measurement\":true,\"transform_cage\":true,\"hover_outline\":true,\"selection_outline\":true,\"layer_origin_cross\":true,\"pivot\":true,\"origin\":true,\"path\":true,\"anchors\":true,\"handles\":true},\"rulers_visible\":true,\"snapping_state\":{\"snapping_enabled\":true,\"grid_snapping\":false,\"artboards\":true,\"tolerance\":8.0,\"bounding_box\":{\"center_point\":true,\"corner_point\":true,\"edge_midpoint\":true,\"align_with_edges\":true,\"distribute_evenly\":true},\"path\":{\"anchor_point\":true,\"line_midpoint\":true,\"along_path\":true,\"normal_to_path\":true,\"tangent_to_path\":true,\"path_intersection_point\":true,\"align_with_anchor_point\":true,\"perpendicular_from_endpoint\":true},\"grid\":{\"origin\":[0.0,0.0],\"grid_type\":{\"Rectangular\":{\"spacing\":[1.0,1.0]}},\"rectangular_spacing\":[1.0,1.0],\"isometric_y_spacing\":1.0,\"isometric_angle_a\":30.0,\"isometric_angle_b\":30.0,\"grid_color\":{\"red\":0.6038274,\"green\":0.6038274,\"blue\":0.6038274,\"alpha\":1.0},\"dot_display\":false}},\"graph_view_overlay_open\":false,\"graph_fade_artwork_percentage\":80.0}"
  },
  {
    "path": "demo-artwork/red-dress.graphite",
    "content": "{\"network_interface\":{\"network\":{\"exports\":[{\"Node\":{\"node_id\":2394762731964337494,\"output_index\":0}}],\"nodes\":[[10127467043900015225,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2],\"remove\":[],\"delta\":[[1,[258.00000000000006,994.6666666666664]],[2,[644.0,726.6666666666666]]]},\"segments\":{\"add\":[1],\"remove\":[],\"start_point\":[[1,1]],\"end_point\":[[1,2]],\"handle_primary\":[[1,[0.0,0.0]]],\"handle_end\":[[1,[-328.66666666666674,129.33333333333337]]],\"stroke\":[[1,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15961046538654083626,{\"inputs\":[{\"Node\":{\"node_id\":1889157037801767612,\"output_index\":0}},{\"Node\":{\"node_id\":4332145463108161926,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":0.5},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":10.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17378885078543074499,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8],\"remove\":[],\"delta\":[[5,[740.4444444444443,857.1111111111111]],[3,[707.5,1026.5]],[7,[727.5555555555554,1026.6666666666663]],[2,[745.5,826.0]],[1,[823.2222222222221,660.4444444444445]],[6,[725.7777777777779,1026.370370370371]],[4,[709.5555555555554,1026.6666666666667]],[8,[746.0000000000001,842.9999999999999]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8],\"remove\":[],\"start_point\":[[5,5],[2,2],[6,6],[8,8],[3,3],[4,4],[1,1],[7,7]],\"end_point\":[[6,7],[8,1],[2,3],[5,6],[1,2],[3,4],[4,5],[7,8]],\"handle_primary\":[[6,[0.0,0.0]],[8,[17.33333333333337,-84.99999999999989]],[3,[0.0,0.0]],[7,[0.0,0.0]],[5,[0.0,0.0]],[1,[0.0,0.0]],[4,[0.0,0.0]],[2,[-24.5,85.0]]],\"handle_end\":[[7,[-26.8034437596026,131.4399645903585]],[2,[0.0,0.0]],[3,[0.0,0.0]],[4,[-23.11111111111109,90.44444444444456]],[5,[-7.555555555555884,-59.25925925925992]],[8,[1.772016460905547,0.9591220850479658]],[6,[0.0,0.0]],[1,[24.5,-85.0]]],\"stroke\":[[6,0],[4,0],[5,0],[2,0],[3,0],[7,0],[8,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":8}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14537754528543289381,{\"inputs\":[{\"Node\":{\"node_id\":1689789805659535712,\"output_index\":0}},{\"Node\":{\"node_id\":17364155187784942740,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11656581020969095354,{\"inputs\":[{\"Node\":{\"node_id\":8413863870096329943,\"output_index\":0}},{\"Node\":{\"node_id\":9698363115186534174,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4350324834849900949,{\"inputs\":[{\"Node\":{\"node_id\":6672826052605647592,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.8980392,\"green\":0.8,\"blue\":0.6117647,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.8980392,\"green\":0.8,\"blue\":0.6117647,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15857077552290328068,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[2,[820.8888888888889,395.55555555555554]],[1,[848.8888888888889,330.66666666666663]],[3,[740.0,516.0]],[4,[702.2222222222222,621.3333333333333]]]},\"segments\":{\"add\":[1,2,3],\"remove\":[],\"start_point\":[[1,1],[3,3],[2,2]],\"end_point\":[[1,2],[2,3],[3,4]],\"handle_primary\":[[1,[0.0,0.0]],[2,[-15.111111111111086,22.66666666666663]],[3,[-17.77777777777783,35.55555555555554]]],\"handle_end\":[[1,[15.111111111111086,-22.66666666666663]],[2,[17.77777777777783,-35.55555555555554]],[3,[6.222222222222172,-38.66666666666674]]],\"stroke\":[[3,0],[1,0],[2,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15735375935164094402,{\"inputs\":[{\"Node\":{\"node_id\":3414873131936208778,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15239301303367148581,{\"inputs\":[{\"Node\":{\"node_id\":11268046366284173800,\"output_index\":0}},{\"Node\":{\"node_id\":5269304445610080925,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[615144098061106242,{\"inputs\":[{\"Node\":{\"node_id\":14675232891471617236,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.84705883,\"green\":0.5372549,\"blue\":0.38431373,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.84705883,\"green\":0.5372549,\"blue\":0.38431373,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11058365317860779469,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7],\"remove\":[],\"delta\":[[6,[368.0,902.6666666666669]],[1,[181.0,1023.0]],[4,[397.3333333333333,770.6666666666666]],[5,[479.00000000000006,817.0]],[3,[352.0,833.0]],[2,[242.0,917.0]],[7,[311.3333333333333,1018.6666666666666]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7],\"remove\":[],\"start_point\":[[3,3],[7,7],[2,2],[6,6],[4,4],[5,5],[1,1]],\"end_point\":[[5,6],[3,4],[7,1],[2,3],[6,7],[1,2],[4,5]],\"handle_primary\":[[1,[0.0,0.0]],[5,[-35.31654570364651,23.463149348287175]],[4,[0.0,0.0]],[2,[40.0718943376238,-39.16116946631416]],[3,[37.4110841377784,-16.935189837826556]],[6,[0.0,0.0]],[7,[0.0,0.0]]],\"handle_end\":[[4,[35.31654570364611,-23.463149348286947]],[7,[0.0,1.3333333333337123]],[2,[-37.4110841377784,16.935189837826556]],[3,[0.0,0.0]],[5,[45.99999999999994,-52.00000000000023]],[1,[-40.071894337623746,39.16116946631416]],[6,[18.0,-55.33333333333326]]],\"stroke\":[[5,0],[1,0],[4,0],[3,0],[6,0],[2,0],[7,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":7}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14946189826912398678,{\"inputs\":[{\"Node\":{\"node_id\":10086073308516686449,\"output_index\":0}},{\"Node\":{\"node_id\":12030171742672119253,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11553850607251055696,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8],\"remove\":[],\"delta\":[[7,[591.5390946502059,128.7023319615912]],[2,[596.631001371742,112.37311385459536]],[8,[594.962962962963,111.93415637860085]],[6,[609.9753086419754,133.5308641975309]],[4,[608.570644718793,131.2482853223594]],[3,[594.085048010974,128.61454046639233]],[1,[597.5967078189302,96.04389574759946]],[5,[619.2812071330591,124.6639231824417]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8],\"remove\":[],\"start_point\":[[5,5],[1,1],[3,3],[7,7],[6,6],[2,2],[8,8],[4,4]],\"end_point\":[[7,8],[2,3],[8,1],[6,7],[3,4],[5,6],[4,5],[1,2]],\"handle_primary\":[[1,[0.0,0.0]],[8,[4.1262002743484345,-6.935528120713329]],[6,[-7.286694101508829,3.0727023319615796]],[5,[0.0,0.0]],[7,[-2.3703703703704377,-6.057613168724245]],[2,[-4.126200274348321,7.1111111111111]],[3,[3.5534615822588194,4.6302681223374975]],[4,[4.038408779149563,-1.492455418381354]]],\"handle_end\":[[4,[0.0,0.0]],[2,[-2.8971193415636662,-3.7750342935528063]],[6,[2.3703703703704377,6.057613168724259]],[8,[-0.0877914951989851,0.08779149519889984]],[7,[-4.1262002743484345,6.935528120713272]],[3,[-4.038408779149563,1.492455418381354]],[1,[4.126200274348321,-7.111111111111114]],[5,[7.286694101508829,-3.0727023319615796]]],\"stroke\":[[2,0],[4,0],[1,0],[8,0],[5,0],[7,0],[3,0],[6,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":8}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4453139144069993994,{\"inputs\":[{\"Node\":{\"node_id\":11804065810513502701,\"output_index\":0}},{\"Node\":{\"node_id\":3955326429435439190,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5040278174920511484,{\"inputs\":[{\"Node\":{\"node_id\":14345191642063772510,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4577174813962563383,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[4,[683.6813083078299,66.11925839089211]],[2,[681.8346756482305,95.35045043533154]],[3,[682.7287205627164,97.04177207029592]],[1,[681.3689965686843,65.32157692417977]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[1,1],[3,3],[4,4],[2,2]],\"end_point\":[[3,4],[4,1],[2,3],[1,2]],\"handle_primary\":[[3,[-1.544754703853414,-12.605862910106907]],[4,[-1.0406539360374154,-1.1405457962673182]],[1,[0.0,0.0]],[2,[0.0,0.0]]],\"handle_end\":[[2,[-0.5302752037773644,-0.69223185792994]],[4,[-1.1368683772161605e-13,-1.4210854715202004e-14]],[1,[-2.6406503472093164,-12.592334294499352]],[3,null]],\"stroke\":[[4,0],[1,0],[3,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14225285635863713990,{\"inputs\":[{\"Node\":{\"node_id\":8410534738018320047,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[25.333333333333485,12.641975308641918]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11899713172487274471,{\"inputs\":[{\"Node\":{\"node_id\":9954843247420111867,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.7529412,\"green\":0.15686275,\"blue\":0.13333334,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.7529412,\"green\":0.15686275,\"blue\":0.13333334,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6580280438672662494,{\"inputs\":[{\"Node\":{\"node_id\":15395954548128560685,\"output_index\":0}},{\"Node\":{\"node_id\":14598755603287563819,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17967471489196302183,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3],\"remove\":[],\"delta\":[[3,[334.8148148148148,1025.4814814814813]],[2,[568.8888888888889,785.4814814814813]],[1,[339.25925925925924,1025.185185185185]]]},\"segments\":{\"add\":[1,2,3],\"remove\":[],\"start_point\":[[1,1],[3,3],[2,2]],\"end_point\":[[3,1],[2,3],[1,2]],\"handle_primary\":[[3,[0.0,0.0]],[2,[0.0,0.0]],[1,[0.0,0.0]]],\"handle_end\":[[2,[42.07407407407419,-157.6296296296293]],[1,[-209.77777777777777,108.44444444444468]],[3,null]],\"stroke\":[[2,0],[1,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":3}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10086073308516686449,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":3971837674569123876,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4222034829755771252,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[4,[161.6241426611797,258.3703703703704]],[1,[165.5308641975309,250.07407407407408]],[2,[155.25925925925927,256.7901234567901]],[3,[152.49382716049382,262.71604938271605]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[2,2],[4,4],[1,1],[3,3]],\"end_point\":[[3,4],[4,1],[1,2],[2,3]],\"handle_primary\":[[1,[0.0,0.0]],[2,[-1.9753086419753176,0.790123456790127]],[4,[2.7654320987654444,-3.950617283950635]],[3,[0.0,0.0]]],\"handle_end\":[[4,[0.0,0.0]],[2,[-0.3950617283950919,-1.7777777777777717]],[3,[-2.7654320987654444,3.950617283950635]],[1,[1.9753086419753176,-0.790123456790127]]],\"stroke\":[[4,0],[1,0],[2,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4307303572241320716,{\"inputs\":[{\"Node\":{\"node_id\":4265165189651403984,\"output_index\":0}},{\"Node\":{\"node_id\":4572557574846980832,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2878992817082507910,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3],\"remove\":[],\"delta\":[[3,[741.6296296296296,1027.5555555555557]],[2,[738.074074074074,1027.2592592592591]],[1,[777.4814814814814,867.5555555555555]]]},\"segments\":{\"add\":[1,2,3],\"remove\":[],\"start_point\":[[3,3],[2,2],[1,1]],\"end_point\":[[2,3],[1,2],[3,1]],\"handle_primary\":[[3,[0.0,0.0]],[1,[0.0,0.0]],[2,[0.0,0.0]]],\"handle_end\":[[1,[0.0,0.0]],[3,[-25.185185185185105,132.14814814814804]],[2,[0.0,0.0]]],\"stroke\":[[3,0],[1,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":3}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9863310024364795214,{\"inputs\":[{\"Node\":{\"node_id\":5278509881589546420,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11025165626998987360,{\"inputs\":[{\"Node\":{\"node_id\":5326536612985524219,\"output_index\":0}},{\"Node\":{\"node_id\":4332145463108161926,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":2.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":40.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":-15.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15982852655074258238,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2],\"remove\":[],\"delta\":[[1,[837.9999999999998,535.8024691358025]],[2,[798.222222222222,639.8024691358025]]]},\"segments\":{\"add\":[1,2],\"remove\":[],\"start_point\":[[2,2],[1,1]],\"end_point\":[[2,1],[1,2]],\"handle_primary\":[[1,[0.0,0.0]],[2,[31.037037037037067,-61.11111111111131]]],\"handle_end\":[[2,null],[1,[34.000000000000114,-59.77777777777783]]],\"stroke\":[[2,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":2}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17699121037850769131,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[3,[282.2222222222222,277.3333333333333]],[1,[531.1111111111111,364.0]],[4,[158.22222222222223,332.0]],[2,[429.33333333333326,295.1111111111111]]]},\"segments\":{\"add\":[1,2,3],\"remove\":[],\"start_point\":[[2,2],[1,1],[3,3]],\"end_point\":[[2,3],[1,2],[3,4]],\"handle_primary\":[[2,[-56.8888888888888,-24.0]],[1,[0.0,0.0]],[3,[-33.333333333333286,6.666666666666686]]],\"handle_end\":[[1,[56.8888888888888,24.0]],[2,[33.333333333333314,-6.666666666666686]],[3,[60.0,-41.77777777777777]]],\"stroke\":[[2,0],[1,0],[3,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5174744389209053970,{\"inputs\":[{\"Node\":{\"node_id\":12385950900718181935,\"output_index\":0}},{\"Node\":{\"node_id\":5040278174920511484,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1689789805659535712,{\"inputs\":[{\"Node\":{\"node_id\":15637103575662751567,\"output_index\":0}},{\"Node\":{\"node_id\":11590691579869262546,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9778375740427894463,{\"inputs\":[{\"Node\":{\"node_id\":16137033772363318157,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5002654561220917457,{\"inputs\":[{\"Node\":{\"node_id\":11632506522064533635,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":35.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":206},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":2}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::PoissonDiskPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8699675339613677057,{\"inputs\":[{\"Node\":{\"node_id\":15982852655074258238,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[40.2222222222224,-10.469135802469168]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[18207065424980079673,{\"inputs\":[{\"Node\":{\"node_id\":13035777574951374461,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":5.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2660652185019504730,{\"inputs\":[{\"Node\":{\"node_id\":1806828617441445250,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":8.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2959546142916532439,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[4,[0.0,0.5]],[1,[0.5,0.0]],[3,[0.5,1.0]],[2,[1.0,0.5]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[3,3],[1,1],[2,2],[4,4]],\"end_point\":[[2,3],[4,1],[1,2],[3,4]],\"handle_primary\":[[3,[-0.275892388889507,0.0]],[2,[0.0,0.27589238888950707]],[4,[0.0,-0.275892388889507]],[1,[0.27589238888950707,0.0]]],\"handle_end\":[[4,[-0.275892388889507,0.0]],[2,[0.27589238888950707,0.0]],[3,[0.0,0.27589238888950707]],[1,[0.0,-0.275892388889507]]],\"stroke\":[[1,0],[3,0],[2,0],[4,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11479098559726891734,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12],\"remove\":[],\"delta\":[[12,[683.3104709647919,64.0877914951989]],[4,[704.5560128029263,81.29492455418381]],[3,[686.5294924554183,63.38545953360767]],[6,[717.9588477366254,99.32144490169182]],[8,[709.979576284103,102.93065081542449]],[9,[717.5491540923639,95.80978509373573]],[7,[710.5648529187624,103.39887212315196]],[1,[660.660265203475,61.39551897576588]],[10,[712.047553726566,86.56241426611797]],[11,[694.9574759945133,76.78829446730683]],[2,[669.2053040695015,63.20987654320987]],[5,[718.310013717421,90.01554641060812]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12],\"remove\":[],\"start_point\":[[11,11],[12,12],[1,1],[7,7],[5,5],[4,4],[2,2],[6,6],[3,3],[9,9],[10,10],[8,8]],\"end_point\":[[11,12],[2,3],[9,10],[10,11],[3,4],[8,9],[1,2],[4,5],[5,6],[7,8],[12,1],[6,7]],\"handle_primary\":[[6,[-1.9184335509834227,1.995170893022717]],[11,[-5.618655692729931,-7.257430269775952]],[9,[0.17558299039785652,-3.5116598079561214]],[4,[8.369455875628773,2.867855509830818]],[10,[-4.096936442615174,-1.872885230909901]],[12,[-12.8760859625055,-0.4682213077274682]],[7,[-0.585276634659408,-0.4682213077274753]],[8,[0.0,0.0]],[5,[1.1120256058526363,1.9314128943758817]],[2,[5.4430727023319605,-0.6438042981252892]],[3,[3.4531321444902687,2.1655235482396193]],[1,[0.0,0.0]]],\"handle_end\":[[7,null],[1,[-5.4430727023319605,0.6438042981252892]],[6,[1.706505264591101,-0.4025776799149554]],[3,[-8.369455875628773,-2.867855509830818]],[11,[4.036165212980222,0.14676964410837456]],[10,[5.464020763447934,7.057693486120044]],[12,[4.9748513946045705,5.91129401005945]],[9,[4.0931309699032,1.8711455862415676]],[2,[-3.4853769593560173,-2.1857448728164144]],[8,[-0.17558299039785652,3.511659807956093]],[5,[2.926383173296813,-3.043438500228561]],[4,[-1.8416562954789697,-3.1986661974113133]]],\"stroke\":[[8,0],[9,0],[5,0],[11,0],[4,0],[6,0],[3,0],[1,0],[2,0],[10,0],[12,0],[7,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":12}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6282972142629473139,{\"inputs\":[{\"Node\":{\"node_id\":15815816861435910950,\"output_index\":0}},{\"Node\":{\"node_id\":15578929303912288394,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3406722917122601552,{\"inputs\":[{\"Value\":{\"tagged_value\":\"None\",\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":1.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::generator_nodes::CircleNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1167210731467447244,{\"inputs\":[{\"Node\":{\"node_id\":16551385471328831128,\"output_index\":0}},{\"Node\":{\"node_id\":10432831427187785843,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16530658574540156160,{\"inputs\":[{\"Node\":{\"node_id\":11666664915283969027,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[23.70370370370381,12.641975308641918]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4248321400839848160,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":13231685386999438557,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[907841922684377912,{\"inputs\":[{\"Node\":{\"node_id\":17336535036064625290,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.7529412,\"green\":0.15686275,\"blue\":0.13333334,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.7529412,\"green\":0.15686275,\"blue\":0.13333334,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12219771677493189964,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":5140869461760168364,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9954843247420111867,{\"inputs\":[{\"Node\":{\"node_id\":6988349135757634271,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.14046639231827385,0.1473642955124319]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10619788176782820865,{\"inputs\":[{\"Node\":{\"node_id\":2397243911096708995,\"output_index\":0}},{\"Node\":{\"node_id\":1157261387411722141,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14285767317419627814,{\"inputs\":[{\"Node\":{\"node_id\":6749771744300551215,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12049041947382267086,{\"inputs\":[{\"Node\":{\"node_id\":2959546142916532439,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[25.77777777777777,508.44444444444446]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[8.0,8.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11573595155909211511,{\"inputs\":[{\"Node\":{\"node_id\":10127467043900015225,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":5.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17891208858820401648,{\"inputs\":[{\"Node\":{\"node_id\":1204243038352113866,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-7.407407407407391,4.740740740740762]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15949658764632267703,{\"inputs\":[{\"Node\":{\"node_id\":14012583111791538162,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":5.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13352561089252322209,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[4,[615.5555555555555,1025.7777777777778]],[2,[749.7777777777777,741.7777777777778]],[1,[612.0,1025.3333333333333]],[3,[752.4444444444443,739.1111111111111]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[4,4],[3,3],[2,2],[1,1]],\"end_point\":[[3,4],[2,3],[1,2],[4,1]],\"handle_primary\":[[2,[0.0,0.0]],[1,[0.0,0.0]],[3,[0.0,0.0]],[4,[0.0,0.0]]],\"handle_end\":[[2,[0.0,0.0]],[1,[-112.0,179.55555555555577]],[3,[28.000000000000114,-107.55555555555544]],[4,[-0.4444444444444571,0.0]]],\"stroke\":[[1,0],[3,0],[4,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10689298484366290551,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3],\"remove\":[],\"delta\":[[2,[151.22962962962964,252.1283950617284]],[3,[155.85185185185185,253.03703703703707]],[1,[156.93571992954355,246.07901729349]]]},\"segments\":{\"add\":[1,2,3],\"remove\":[],\"start_point\":[[1,1],[3,3],[2,2]],\"end_point\":[[2,3],[3,1],[1,2]],\"handle_primary\":[[3,[1.0949818244169762,-0.3546380887060252]],[2,[-0.4744436253241133,1.2651830008642833]],[1,[-2.071522398679349,1.8617234472507391]]],\"handle_end\":[[2,[-1.0949818244169762,0.3546380887060252]],[1,[0.9481481481481068,-2.5283950617284177]],[3,[1.9215307714004553,1.0902372408288272]]],\"stroke\":[[3,0],[2,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":3}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1785173043494067496,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[4,[770.2222222222222,459.1111111111111]],[3,[712.0,519.1111111111111]],[2,[636.4444444444443,579.5555555555555]],[1,[566.6666666666666,576.0]]]},\"segments\":{\"add\":[1,2,3],\"remove\":[],\"start_point\":[[3,3],[1,1],[2,2]],\"end_point\":[[3,4],[1,2],[2,3]],\"handle_primary\":[[1,[0.0,0.0]],[2,[31.1111111111112,-17.33333333333337]],[3,[18.22222222222217,-21.777777777777715]]],\"handle_end\":[[2,[-18.22222222222217,21.777777777777715]],[1,[-31.1111111111112,17.33333333333337]],[3,[0.0,0.0]]],\"stroke\":[[3,0],[1,0],[2,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12554368619682347699,{\"inputs\":[{\"Node\":{\"node_id\":2594533001540454577,\"output_index\":0}},{\"Node\":{\"node_id\":17078740291337047697,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":2.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":3.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13261814586176172586,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16],\"remove\":[],\"delta\":[[12,[715.8518518518517,452.14814814814815]],[5,[829.8271604938273,345.08641975308643]],[1,[799.1111111111112,188.14814814814815]],[15,[817.4814814814815,272.2962962962963]],[8,[686.5302034429451,490.6109861193811]],[9,[758.716049382716,441.8765432098765]],[7,[680.4331323644109,506.0568995183343]],[2,[827.2592592592594,206.41975308641975]],[14,[782.8148148148148,378.96296296296293]],[13,[736.8888888888889,432.2962962962963]],[3,[851.1111111111111,237.7777777777778]],[16,[809.1851851851852,207.1111111111111]],[4,[858.6666666666666,268.88888888888886]],[11,[719.9999999999999,460.8395061728396]],[6,[775.5061728395061,429.23456790123464]],[10,[755.3580246913581,432.5925925925926]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16],\"remove\":[],\"start_point\":[[12,12],[8,8],[11,11],[2,2],[10,10],[3,3],[6,6],[14,14],[9,9],[4,4],[1,1],[15,15],[5,5],[7,7],[16,16],[13,13]],\"end_point\":[[3,4],[5,6],[14,15],[9,10],[15,16],[2,3],[16,1],[8,9],[4,5],[10,11],[11,12],[13,14],[12,13],[6,7],[1,2],[7,8]],\"handle_primary\":[[16,[0.0,0.0]],[5,[-31.06481223802939,48.93526760703577]],[2,[0.0,0.0]],[15,[5.925925925925867,-34.96296296296299]],[6,[-36.541158121167314,42.67873357730855]],[9,[0.0,0.0]],[1,[0.0,0.0]],[13,[0.0,0.0]],[7,null],[12,[0.0,0.0]],[3,[9.086419753086489,9.086419753086432]],[4,[-2.5679012345678984,17.77777777777777]],[11,[0.0,0.0]],[14,[20.148148148148152,-33.481481481481524]],[8,[37.39536928167615,-11.787670751832536]],[10,[0.0,0.0]]],\"handle_end\":[[4,[31.06481223802939,-48.93526760703571]],[9,[0.0,0.0]],[14,[-5.925925925925867,34.96296296296299]],[15,[6.51851851851859,11.851851851851848]],[1,[0.0,0.0]],[10,[0.0,0.0]],[2,[-9.086419753086489,-9.086419753086432]],[8,[-11.555555555555657,17.18518518518522]],[6,[22.320987654321016,-14.222222222222342]],[3,[2.5679012345678984,-17.77777777777777]],[7,null],[12,[0.0,0.0]],[16,[0.0,0.0]],[5,[18.3855550289378,-21.473654506216747]],[13,[-20.148148148148152,33.481481481481524]],[11,[0.0,0.0]]],\"stroke\":[[4,0],[15,0],[1,0],[11,0],[16,0],[5,0],[7,0],[12,0],[8,0],[9,0],[10,0],[2,0],[3,0],[14,0],[6,0],[13,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":16}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11616089678400336955,{\"inputs\":[{\"Node\":{\"node_id\":2660652185019504730,\"output_index\":0}},{\"Node\":{\"node_id\":17078740291337047697,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.8},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":3.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17336535036064625290,{\"inputs\":[{\"Node\":{\"node_id\":10421722418968896452,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[49.47996245659249,5.913900401382151]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.9277405532359332,0.9277405532359332]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1621196991038859321,{\"inputs\":[{\"Node\":{\"node_id\":15857077552290328068,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":5.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3150436463719911922,{\"inputs\":[{\"Node\":{\"node_id\":18214377096178867498,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5455777299776842371,{\"inputs\":[{\"Node\":{\"node_id\":9470742171134780193,\"output_index\":0}},{\"Node\":{\"node_id\":7385465194555106679,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13045580349734858212,{\"inputs\":[{\"Node\":{\"node_id\":10795820039540504703,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":5.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":2}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::PoissonDiskPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10432831427187785843,{\"inputs\":[{\"Node\":{\"node_id\":2087303479944421366,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14209241002058525241,{\"inputs\":[{\"Node\":{\"node_id\":16290933138334939444,\"output_index\":0}},{\"Node\":{\"node_id\":10918055532782314571,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9374264173303233490,{\"inputs\":[{\"Node\":{\"node_id\":1713644030979611623,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":5.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8934999452649011837,{\"inputs\":[{\"Node\":{\"node_id\":16796171662855500935,\"output_index\":0}},{\"Node\":{\"node_id\":16756940771483104467,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4787732047489141819,{\"inputs\":[{\"Node\":{\"node_id\":12062649793560663566,\"output_index\":0}},{\"Node\":{\"node_id\":4248321400839848160,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16614450796751955858,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7],\"remove\":[],\"delta\":[[4,[327.3333333333333,1024.6666666666663]],[7,[918.6666666666664,604.6666666666666]],[1,[902.6666666666666,446.6666666666667]],[6,[845.3333333333333,842.0000000000001]],[2,[757.1358024691358,661.5308641975308]],[5,[807.3333333333334,1026.0]],[3,[481.33333333333337,826.6666666666666]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7],\"remove\":[],\"start_point\":[[6,6],[1,1],[2,2],[7,7],[3,3],[4,4],[5,5]],\"end_point\":[[5,6],[7,1],[6,7],[3,4],[1,2],[4,5],[2,3]],\"handle_primary\":[[5,[0.0,0.0]],[3,[-131.33333333333337,78.66666666666652]],[6,[49.4943341398548,-116.98660796692934]],[1,[0.0,0.0]],[4,[0.0,0.0]],[7,[16.000000000000227,-89.99999999999989]],[2,[-138.41983388553547,108.26897897977506]]],\"handle_end\":[[2,[143.2366194125077,-85.79655375977609]],[5,[-36.66666666666663,86.66666666666652]],[3,[0.0,0.0]],[7,[3.3333333333333712,32.00000000000006]],[4,[0.0,0.0]],[6,[-16.71260304301461,94.00839211695676]],[1,[134.66666666666686,-105.33333333333326]]],\"stroke\":[[3,0],[2,0],[1,0],[7,0],[4,0],[5,0],[6,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":7}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6666260895482068061,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[1,[643.0921942512911,135.30336230847865]],[4,[645.3845450388659,135.9012345679012]],[3,[646.4033730994855,154.80329633678198]],[2,[645.6821893629258,155.3850506865855]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[1,1],[3,3],[4,4],[2,2]],\"end_point\":[[4,1],[1,2],[3,4],[2,3]],\"handle_primary\":[[4,[0.0,0.0]],[2,[0.0,0.0]],[1,[0.0,0.0]],[3,[-0.2558402858584259,-10.083527774255913]]],\"handle_end\":[[1,[-0.05703059647760256,-13.444628325495556]],[4,[1.68322954928135,-0.022087435068414152]],[2,[-0.3446760851414865,0.611494768909921]],[3,[0.0,0.0]]],\"stroke\":[[2,0],[3,0],[1,0],[4,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7893851488963635918,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2],\"remove\":[],\"delta\":[[1,[125.0,420.0]],[2,[24.0,486.0]]]},\"segments\":{\"add\":[1],\"remove\":[],\"start_point\":[[1,1]],\"end_point\":[[1,2]],\"handle_primary\":[[1,[0.0,0.0]]],\"handle_end\":[[1,[23.0,-70.0]]],\"stroke\":[[1,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3802858053991775169,{\"inputs\":[{\"Node\":{\"node_id\":11058365317860779469,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":25.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":2}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::PoissonDiskPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11199691961479466803,{\"inputs\":[{\"Node\":{\"node_id\":7141088190930752823,\"output_index\":0}},{\"Node\":{\"node_id\":17078740291337047697,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.2},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7141088190930752823,{\"inputs\":[{\"Node\":{\"node_id\":541002100261582638,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":5.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[18319784717194273926,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12],\"remove\":[],\"delta\":[[11,[595.0,690.0]],[5,[418.2222222222222,754.6666666666666]],[9,[828.0,570.2222222222221]],[10,[610.6666666666666,712.4444444444443]],[12,[437.99999999999994,690.0]],[4,[574.6666666666666,712.4444444444443]],[2,[150.22222222222223,568.4444444444445]],[6,[443.1111111111111,783.1111111111111]],[7,[558.6666666666666,749.7777777777778]],[1,[172.22222222222217,564.7777777777779]],[8,[706.6666666666666,687.5555555555554]],[3,[167.0,691.0]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12],\"remove\":[],\"start_point\":[[8,8],[3,3],[11,11],[1,1],[9,9],[7,7],[5,5],[10,10],[2,2],[4,4],[12,12],[6,6]],\"end_point\":[[5,6],[6,7],[12,1],[11,12],[10,11],[9,10],[3,4],[4,5],[7,8],[2,3],[8,9],[1,2]],\"handle_primary\":[[3,[0.0,0.0]],[11,[0.0,0.0]],[4,[0.0,0.0]],[10,[0.0,0.0]],[1,[0.0,0.0]],[6,[0.0,0.0]],[9,[0.0,0.0]],[2,[18.111111111111057,85.22222222222229]],[5,[0.0,0.0]],[7,[32.888888888888914,-13.333333333333483]],[8,[60.44444444444446,-39.55555555555566]],[12,[-98.22222222222224,-28.0]]],\"handle_end\":[[3,[-128.44444444444446,26.22222222222217]],[11,[98.22222222222224,28.0]],[4,[54.22222222222223,-9.333333333333371]],[6,[-32.888888888888914,13.333333333333483]],[9,[145.77777777777771,-61.777777777777715]],[2,null],[12,[161.33333333333337,34.66666666666674]],[1,null],[5,[-18.66666666666663,-4.888888888888914]],[8,[0.0,0.0]],[7,[-60.44444444444446,39.55555555555566]],[10,[-1.3333333333333712,21.77777777777783]]],\"stroke\":[[11,0],[1,0],[2,0],[9,0],[8,0],[6,0],[7,0],[3,0],[10,0],[12,0],[4,0],[5,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":12}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4479074488343511985,{\"inputs\":[{\"Node\":{\"node_id\":11479098559726891734,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.8980392,\"green\":0.8,\"blue\":0.6156863,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.8980392,\"green\":0.8,\"blue\":0.6156863,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16137033772363318157,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3],\"remove\":[],\"delta\":[[2,[720.417009602195,178.00603566529497]],[3,[720.1360768175583,181.58792866941016]],[1,[688.566255144033,175.3371742112483]]]},\"segments\":{\"add\":[1,2,3],\"remove\":[],\"start_point\":[[1,1],[2,2],[3,3]],\"end_point\":[[3,1],[1,2],[2,3]],\"handle_primary\":[[2,[1.8041152263373303,2.00164609053499]],[3,[-1.1237311385458495,-3.125377229080982]],[1,[29.423007364946784,-0.024697364703285984]]],\"handle_end\":[[3,[12.04499314128941,0.8076817558298615]],[1,null],[2,null]],\"stroke\":[[3,0],[1,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":3}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3170924135668664007,{\"inputs\":[{\"Node\":{\"node_id\":4787732047489141819,\"output_index\":0}},{\"Node\":{\"node_id\":13444661581815146533,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16051539163551573193,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5],\"remove\":[],\"delta\":[[2,[609.7777777777777,896.0]],[3,[514.6666666666666,1025.7777777777778]],[4,[519.1111111111111,1026.2222222222222]],[1,[708.0000000000001,769.3333333333333]],[5,[588.0,930.6666666666666]]]},\"segments\":{\"add\":[1,2,3,4,5],\"remove\":[],\"start_point\":[[3,3],[4,4],[2,2],[5,5],[1,1]],\"end_point\":[[2,3],[5,1],[3,4],[1,2],[4,5]],\"handle_primary\":[[1,[0.0,0.0]],[5,[24.0,-29.77777777777783]],[4,[0.0,0.0]],[2,[-54.66666666666663,69.33333333333314]],[3,[0.0,0.0]]],\"handle_end\":[[5,[-28.44444444444457,47.111111111111086]],[2,[0.0,0.0]],[3,[0.0,0.0]],[1,[54.66666666666663,-69.33333333333326]],[4,[-24.0,29.777777777777715]]],\"stroke\":[[4,0],[2,0],[1,0],[5,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":5}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6292009934909381201,{\"inputs\":[{\"Node\":{\"node_id\":10424806499648491677,\"output_index\":0}},{\"Node\":{\"node_id\":9778375740427894463,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1204243038352113866,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2],\"remove\":[],\"delta\":[[2,[837.3333333333334,653.6296296296296]],[1,[808.8888888888889,832.2962962962965]]]},\"segments\":{\"add\":[1,2],\"remove\":[],\"start_point\":[[2,2],[1,1]],\"end_point\":[[1,2],[2,1]],\"handle_primary\":[[2,[-26.22222222222217,80.29629629629608]],[1,[0.0,0.0]]],\"handle_end\":[[1,[-22.22222222222217,79.40740740740739]],[2,[0.0,-0.4444444444443434]]],\"stroke\":[[1,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":2}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4572557574846980832,{\"inputs\":[{\"Node\":{\"node_id\":13014916927589286309,\"output_index\":0}},{\"Node\":{\"node_id\":4332145463108161926,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":0.5},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":30.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":-60.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12062649793560663566,{\"inputs\":[{\"Node\":{\"node_id\":5455777299776842371,\"output_index\":0}},{\"Node\":{\"node_id\":8934999452649011837,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3992858139802231032,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[3,[648.9547325102881,151.8792866941015]],[2,[635.8445358939186,153.225422953818]],[1,[634.615454961134,135.08184727937814]],[4,[645.384545038866,135.90123456790124]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[3,3],[1,1],[4,4],[2,2]],\"end_point\":[[2,3],[1,2],[3,4],[4,1]],\"handle_primary\":[[1,[0.0,0.0]],[3,[1.8143575674440624,-2.575217192501128]],[4,[0.0,0.0]],[2,[6.203932327389111,7.491540923639718]]],\"handle_end\":[[4,[9.247370827617717,-0.3511659807956278]],[1,[-6.203932327389111,-7.491540923639718]],[2,[-1.8143575674440624,2.575217192501128]],[3,[6.730681298582454,7.257430269775966]]],\"stroke\":[[4,0],[2,0],[3,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17494926338451345058,{\"inputs\":[{\"Node\":{\"node_id\":1167210731467447244,\"output_index\":0}},{\"Node\":{\"node_id\":9529195152569434392,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14098374807212007572,{\"inputs\":[{\"Node\":{\"node_id\":17494926338451345058,\"output_index\":0}},{\"Node\":{\"node_id\":10336592647221792772,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8091904580702893317,{\"inputs\":[{\"Node\":{\"node_id\":15446793500614592278,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.8980392,\"green\":0.8,\"blue\":0.6117647,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.8980392,\"green\":0.8,\"blue\":0.6117647,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15492651270767932214,{\"inputs\":[{\"Node\":{\"node_id\":6580280438672662494,\"output_index\":0}},{\"Node\":{\"node_id\":2698266912167150713,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10918055532782314571,{\"inputs\":[{\"Node\":{\"node_id\":7447657690776686262,\"output_index\":0}},{\"Node\":{\"node_id\":17078740291337047697,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.2},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[18067513817508158001,{\"inputs\":[{\"Node\":{\"node_id\":16649851742084147477,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":5.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1661691009086487874,{\"inputs\":[{\"Node\":{\"node_id\":14797986717815207528,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":5.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14029368390543839187,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3],\"remove\":[],\"delta\":[[3,[808.0,413.33333333333337]],[1,[604.8888888888889,523.5555555555557]],[2,[668.4444444444445,516.4444444444443]]]},\"segments\":{\"add\":[1,2],\"remove\":[],\"start_point\":[[1,1],[2,2]],\"end_point\":[[2,3],[1,2]],\"handle_primary\":[[2,[23.1111111111112,-9.7777777777776]],[1,[0.0,0.0]]],\"handle_end\":[[2,[-59.111111111111086,58.22222222222223]],[1,[-23.1111111111112,9.7777777777776]]],\"stroke\":[[1,0],[2,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15896921950407486754,{\"inputs\":[{\"Node\":{\"node_id\":13163272246010991228,\"output_index\":0}},{\"Node\":{\"node_id\":11199691961479466803,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15466714490303763249,{\"inputs\":[{\"Node\":{\"node_id\":10514847656270897393,\"output_index\":0}},{\"Node\":{\"node_id\":11659756061767599421,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2659768650911099730,{\"inputs\":[{\"Node\":{\"node_id\":13045087323693407920,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":6.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12224498203743157414,{\"inputs\":[{\"Node\":{\"node_id\":2878992817082507910,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13045087323693407920,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2],\"remove\":[],\"delta\":[[2,[227.0,496.0]],[1,[19.0,494.0]]]},\"segments\":{\"add\":[1],\"remove\":[],\"start_point\":[[1,1]],\"end_point\":[[1,2]],\"handle_primary\":[[1,[0.0,0.0]]],\"handle_end\":[[1,[-98.0,-55.0]]],\"stroke\":[[1,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8240895922641772563,{\"inputs\":[{\"Node\":{\"node_id\":16530658574540156160,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13644138583806412631,{\"inputs\":[{\"Node\":{\"node_id\":2641530639940889619,\"output_index\":0}},{\"Node\":{\"node_id\":12473080738469616517,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11021243031011826737,{\"inputs\":[{\"Node\":{\"node_id\":16446146761452576438,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.8980392,\"green\":0.8,\"blue\":0.6117647,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.8980392,\"green\":0.8,\"blue\":0.6117647,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17064046832210629373,{\"inputs\":[{\"Node\":{\"node_id\":12219771677493189964,\"output_index\":0}},{\"Node\":{\"node_id\":11677503666435782605,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10420981328998103391,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3],\"remove\":[],\"delta\":[[1,[859.5555555555554,375.1111111111111]],[2,[844.4444444444443,460.44444444444434]],[3,[694.2222222222222,623.5555555555554]]]},\"segments\":{\"add\":[1,2],\"remove\":[],\"start_point\":[[2,2],[1,1]],\"end_point\":[[1,2],[2,3]],\"handle_primary\":[[1,[0.0,0.0]],[2,[-11.999999999999886,30.6666666666668]]],\"handle_end\":[[2,[76.88888888888891,-30.666666666666742]],[1,[11.055745483535702,-28.253571791258253]]],\"stroke\":[[2,0],[1,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15460109068588328521,{\"inputs\":[{\"Node\":{\"node_id\":5185036609290210853,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.9019608,\"green\":0.8,\"blue\":0.6,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.9019608,\"green\":0.8,\"blue\":0.6,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17815494794630739611,{\"inputs\":[{\"Node\":{\"node_id\":14079496619264986678,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4131094614457622424,{\"inputs\":[{\"Node\":{\"node_id\":11356586238302409958,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":5.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10860592954464951000,{\"inputs\":[{\"Node\":{\"node_id\":4236845268521674740,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12387541320114693418,{\"inputs\":[{\"Node\":{\"node_id\":5471152581000334146,\"output_index\":0}},{\"Node\":{\"node_id\":15460109068588328521,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9182448229950585507,{\"inputs\":[{\"Node\":{\"node_id\":12496143061817048445,\"output_index\":0}},{\"Node\":{\"node_id\":7320676248579211727,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[431994205232245356,{\"inputs\":[{\"Node\":{\"node_id\":12387541320114693418,\"output_index\":0}},{\"Node\":{\"node_id\":14894569344576297448,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12594527670567285670,{\"inputs\":[{\"Node\":{\"node_id\":4663768795652429571,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.8980392,\"green\":0.8,\"blue\":0.6117647,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.8980392,\"green\":0.8,\"blue\":0.6117647,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14345191642063772510,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[4,[838.2222222222222,766.6666666666669]],[3,[813.3557395833334,961.1454375]],[2,[833.7777777777777,780.4444444444443]],[1,[880.8888888888888,556.4444444444443]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[2,2],[4,4],[1,1],[3,3]],\"end_point\":[[1,2],[4,1],[2,3],[3,4]],\"handle_primary\":[[3,[0.0,0.0]],[1,[0.0,0.0]],[4,[10.643023589139377,-58.51588472312813]],[2,[-9.333333333333371,54.66666666666674]]],\"handle_end\":[[1,[9.333333333333371,-54.66666666666674]],[3,[-10.643023589139377,58.51588472312813]],[4,[-0.4444444444444571,-0.8888888888888005]],[2,[0.0,0.0]]],\"stroke\":[[1,0],[2,0],[4,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12531351117929704587,{\"inputs\":[{\"Node\":{\"node_id\":11194653561109699287,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.7529412,\"green\":0.15686275,\"blue\":0.13333334,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.7529412,\"green\":0.15686275,\"blue\":0.13333334,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12801133692316734622,{\"inputs\":[{\"Node\":{\"node_id\":17699121037850769131,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":5.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12385950900718181935,{\"inputs\":[{\"Node\":{\"node_id\":4372998635946271235,\"output_index\":0}},{\"Node\":{\"node_id\":615144098061106242,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1713644030979611623,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2],\"remove\":[],\"delta\":[[1,[404.0,882.6666666666666]],[2,[321.33333333333326,1022.0]]]},\"segments\":{\"add\":[1],\"remove\":[],\"start_point\":[[1,1]],\"end_point\":[[1,2]],\"handle_primary\":[[1,[0.0,0.0]]],\"handle_end\":[[1,[30.96296296296316,-100.2222222222224]]],\"stroke\":[[1,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3649809135741361946,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2],\"remove\":[],\"delta\":[[1,[787.1111111111111,414.66666666666663]],[2,[841.3333333333333,336.8888888888889]]]},\"segments\":{\"add\":[1],\"remove\":[],\"start_point\":[[1,1]],\"end_point\":[[1,2]],\"handle_primary\":[[1,[0.0,0.0]]],\"handle_end\":[[1,[-21.333333333333258,48.888888888888914]]],\"stroke\":[[1,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5213978458941436169,{\"inputs\":[{\"Node\":{\"node_id\":13261814586176172586,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":7.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":10},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":2}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::PoissonDiskPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4663768795652429571,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"delta\":[[4,[713.0620332266423,107.3007163542143]],[2,[696.6255144032922,96.92181069958846]],[1,[691.7530864197531,86.91358024691357]],[6,[706.633744855967,98.10699588477364]],[3,[708.8285322359397,101.48696844993144]],[5,[710.8379820149368,102.96966925773508]]]},\"segments\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"start_point\":[[1,1],[6,6],[3,3],[2,2],[5,5],[4,4]],\"end_point\":[[2,3],[4,5],[1,2],[3,4],[5,6],[6,1]],\"handle_primary\":[[3,[1.7753391251332005,4.7992684042066]],[4,[0.0,0.0]],[6,[-6.847736625514244,-2.3703703703703525]],[2,[6.057613168724288,1.9753086419753032]],[1,[0.0,0.0]],[5,[-0.8974241731443726,-2.770309404054231]]],\"handle_end\":[[4,[0.786301337230384,2.4272780410153985]],[3,[-0.9218106995884908,-0.3950617283950635]],[1,[-6.057613168724288,-1.9753086419753032]],[2,[-0.7886938944185431,-2.1320736046921525]],[5,[2.9051419934493197,1.005626074655538]],[6,[1.1851851851849915,8.823045267489718]]],\"stroke\":[[3,0],[1,0],[2,0],[4,0],[5,0],[6,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":6}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10599660455959346550,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[4,[745.3333333333333,471.55555555555554]],[1,[478.2222222222222,515.1111111111111]],[3,[654.6666666666666,546.6666666666666]],[2,[572.0,570.2222222222222]]]},\"segments\":{\"add\":[1,2,3],\"remove\":[],\"start_point\":[[3,3],[1,1],[2,2]],\"end_point\":[[2,3],[1,2],[3,4]],\"handle_primary\":[[1,[0.0,0.0]],[2,[31.555555555555543,7.555555555555543]],[3,[26.222222222222285,-16.0]]],\"handle_end\":[[1,[-31.555555555555543,-7.555555555555543]],[2,[-26.222222222222285,16.0]],[3,[0.0,0.0]]],\"stroke\":[[1,0],[2,0],[3,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16756940771483104467,{\"inputs\":[{\"Node\":{\"node_id\":13975451746581400000,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.84705883,\"green\":0.5372549,\"blue\":0.38431373,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.84705883,\"green\":0.5372549,\"blue\":0.38431373,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17207895962122263432,{\"inputs\":[{\"Node\":{\"node_id\":11573595155909211511,\"output_index\":0}},{\"Node\":{\"node_id\":17078740291337047697,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.2},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2175432926627256613,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":14982414026754548178,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17945736750161448391,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[1,[689.4327280262556,73.68042956754955]],[2,[688.3433248095167,92.67923984990472]],[3,[687.4660700953133,94.52064202140812]],[4,[687.6968543916372,70.6398816184091]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[2,2],[4,4],[1,1],[3,3]],\"end_point\":[[4,1],[3,4],[2,3],[1,2]],\"handle_primary\":[[4,[0.0,0.0]],[1,[-1.460603632035259,12.298077567102167]],[2,[0.0,0.0]],[3,[-0.975596082205584,-10.16276974584038]]],\"handle_end\":[[3,[0.0,0.0]],[4,[-0.3670368206467174,-1.6914035044494111]],[2,[0.6631784948407358,-0.4471776104951459]],[1,null]],\"stroke\":[[3,0],[4,0],[1,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16324258033206362312,{\"inputs\":[{\"Node\":{\"node_id\":16732130236852371275,\"output_index\":0}},{\"Node\":{\"node_id\":17078740291337047697,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14079496619264986678,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3],\"remove\":[],\"delta\":[[1,[597.2345679012346,77.92592592592592]],[2,[608.9876543209878,75.25925925925925]],[3,[609.2839506172841,76.74074074074073]]]},\"segments\":{\"add\":[1,2,3],\"remove\":[],\"start_point\":[[1,1],[2,2],[3,3]],\"end_point\":[[1,2],[3,1],[2,3]],\"handle_primary\":[[1,[0.0,0.0]],[3,[0.0,0.0]],[2,[0.0,0.0]]],\"handle_end\":[[2,[0.0,0.0]],[3,[8.09876543209873,0.2962962962962763]],[1,[-6.222222222222172,-0.9876543209876588]]],\"stroke\":[[1,0],[3,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":3}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10253927692147706615,{\"inputs\":[{\"Node\":{\"node_id\":7262199696924786895,\"output_index\":0}},{\"Node\":{\"node_id\":17078740291337047697,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.2},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9529195152569434392,{\"inputs\":[{\"Node\":{\"node_id\":3121275823460307102,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12876462860151722087,{\"inputs\":[{\"Node\":{\"node_id\":10619788176782820865,\"output_index\":0}},{\"Node\":{\"node_id\":10415872992231003638,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11268046366284173800,{\"inputs\":[{\"Node\":{\"node_id\":4453139144069993994,\"output_index\":0}},{\"Node\":{\"node_id\":11616089678400336955,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15303587427289959766,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2],\"remove\":[],\"delta\":[[2,[818.2222222222222,566.2222222222222]],[1,[610.6666666666666,706.6666666666666]]]},\"segments\":{\"add\":[1],\"remove\":[],\"start_point\":[[1,1]],\"end_point\":[[1,2]],\"handle_primary\":[[1,[0.0,0.0]]],\"handle_end\":[[1,[-82.66666666666674,97.77777777777771]]],\"stroke\":[[1,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12761901161949743155,{\"inputs\":[{\"Node\":{\"node_id\":7659717355245331967,\"output_index\":0}},{\"Node\":{\"node_id\":8091904580702893317,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14887821801874852671,{\"inputs\":[{\"Node\":{\"node_id\":8230694129617719636,\"output_index\":0}},{\"Node\":{\"node_id\":18279507457571359732,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6416452251137958677,{\"inputs\":[{\"Node\":{\"node_id\":9374264173303233490,\"output_index\":0}},{\"Node\":{\"node_id\":17078740291337047697,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.2},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11194653561109699287,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"delta\":[[4,[605.761316872428,82.28989483310471]],[1,[604.3716161316235,95.4260819221956]],[2,[607.4000914494741,86.85505258344766]],[3,[623.4951989026065,81.23639689071788]],[5,[602.0316509633005,90.5516059992284]],[6,[603.4567901234567,92.83950617283948]]]},\"segments\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"start_point\":[[1,1],[6,6],[4,4],[5,5],[3,3],[2,2]],\"end_point\":[[1,2],[4,5],[2,3],[6,1],[5,6],[3,4]],\"handle_primary\":[[2,[3.687242798354191,-3.4531321444901835]],[4,[-3.2460274482192517,2.85650415443304]],[6,[0.3965701826469967,0.8240731861035471]],[5,[0.0,0.0]],[3,[-0.7803688462123546,-2.7117817405883216]],[1,[0.0,0.0]]],\"handle_end\":[[2,[-1.0144795000761633,1.6192653558908745]],[5,[-0.3896135191956773,-0.8096172333722365]],[1,[-3.3249738510837687,3.113864400221118]],[6,[-2.273736754432321e-13,-4.263256414560601e-14]],[3,[4.389574759945049,-3.862825788751721]],[4,[-0.10095077423932251,-1.27829797882373]]],\"stroke\":[[4,0],[1,0],[3,0],[2,0],[5,0],[6,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":6}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7747398671834040298,{\"inputs\":[{\"Node\":{\"node_id\":18319784717194273926,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":30.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":47},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":2}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::PoissonDiskPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1869448627329502330,{\"inputs\":[{\"Node\":{\"node_id\":3827449344952693766,\"output_index\":0}},{\"Node\":{\"node_id\":17078740291337047697,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.2},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16796171662855500935,{\"inputs\":[{\"Node\":{\"node_id\":14993053984267866751,\"output_index\":0}},{\"Node\":{\"node_id\":9371909264427723282,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14012648643507848353,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[3,[615.7168724279835,33.05349794238683]],[4,[613.6098765432099,30.393415637860084]],[1,[610.080658436214,28.760493827160495]],[2,[613.7152263374486,29.076543209876547]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[2,2],[4,4],[1,1],[3,3]],\"end_point\":[[2,3],[1,2],[4,1],[3,4]],\"handle_primary\":[[2,[1.5990193552247547,1.4582133097558128]],[3,[0.0,0.0]],[4,[-1.3168724279836397,-1.343209876543213]],[1,[0.0,0.0]]],\"handle_end\":[[3,[0.7962610294339356,0.812186250022549]],[2,[-0.05267489711934559,-0.9218106995884768]],[4,[0.05267489711934559,0.02633744855966924]],[1,[-1.447323438899616,-1.3198753943965968]]],\"stroke\":[[1,0],[3,0],[2,0],[4,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7525593029671097583,{\"inputs\":[{\"Node\":{\"node_id\":16175421708184657649,\"output_index\":0}},{\"Node\":{\"node_id\":15735375935164094402,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14894569344576297448,{\"inputs\":[{\"Node\":{\"node_id\":5555007473125503522,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.8980392,\"green\":0.8,\"blue\":0.6117647,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.8980392,\"green\":0.8,\"blue\":0.6117647,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15578929303912288394,{\"inputs\":[{\"Node\":{\"node_id\":10770443343193024138,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.8980392,\"green\":0.8,\"blue\":0.6117647,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.8980392,\"green\":0.8,\"blue\":0.6117647,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10133176481349663495,{\"inputs\":[{\"Node\":{\"node_id\":12876462860151722087,\"output_index\":0}},{\"Node\":{\"node_id\":11021243031011826737,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4332145463108161926,{\"inputs\":[{\"Node\":{\"node_id\":3406722917122601552,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.7529412,\"green\":0.15686275,\"blue\":0.13333334,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.7529412,\"green\":0.15686275,\"blue\":0.13333334,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9740500978584792725,{\"inputs\":[{\"Node\":{\"node_id\":14946189826912398678,\"output_index\":0}},{\"Node\":{\"node_id\":10586744777717861556,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10421722418968896452,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3],\"remove\":[],\"delta\":[[3,[691.7384545038866,78.07590306355738]],[2,[683.3689986282578,99.49702789208962]],[1,[677.8673982624599,66.19478737997257]]]},\"segments\":{\"add\":[1,2,3],\"remove\":[],\"start_point\":[[2,2],[1,1],[3,3]],\"end_point\":[[1,2],[3,1],[2,3]],\"handle_primary\":[[3,[0.12630166293718048,-1.7247569518707309]],[2,[0.34676700844204333,0.512429191350904]],[1,[-0.49434169374126213,0.7369041683249975]]],\"handle_end\":[[2,[-1.4537474229852023,19.852161212683583]],[1,[-7.636184307015128,-11.284244620129414]],[3,[5.6142033131263815,-8.368968014727507]]],\"stroke\":[[3,0],[1,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":3}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16551385471328831128,{\"inputs\":[{\"Node\":{\"node_id\":14991324592500870173,\"output_index\":0}},{\"Node\":{\"node_id\":17207349373429328029,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6532401937876437300,{\"inputs\":[{\"Node\":{\"node_id\":3992858139802231032,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3971837674569123876,{\"inputs\":[{\"Node\":{\"node_id\":4131094614457622424,\"output_index\":0}},{\"Node\":{\"node_id\":17078740291337047697,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.2},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":3.3528},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6988349135757634271,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[1,[189.14614932392712,263.9984322947286]],[4,[184.5962505715592,263.55006858710567]],[3,[183.0891632373113,268.771154223006]],[2,[188.0493827160494,269.116049382716]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[1,1],[2,2],[3,3],[4,4]],\"end_point\":[[3,4],[1,2],[4,1],[2,3]],\"handle_primary\":[[2,[-1.9972565157751203,0.9588085439937686]],[4,[1.697261300324044,-1.9917992948792855]],[3,[-0.9800640224909783,-1.3570273385153655]],[1,[1.552958476004363,1.9059035841873424]]],\"handle_end\":[[2,[1.0091841400482906,1.3973479652491392]],[4,[-1.464617942413156,-1.7974856565980986]],[3,[-1.5959762231368018,1.872937487752267]],[1,[3.459421910557637,-1.6607397492127802]]],\"stroke\":[[4,0],[2,0],[1,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1384427686127078856,{\"inputs\":[{\"Node\":{\"node_id\":17064046832210629373,\"output_index\":0}},{\"Node\":{\"node_id\":17529660518597229229,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14057307926677215422,{\"inputs\":[{\"Node\":{\"node_id\":5861306074868809692,\"output_index\":0}},{\"Node\":{\"node_id\":7450965328305122110,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4832236468224231783,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2],\"remove\":[],\"delta\":[[2,[369.77777777777777,381.7777777777778]],[1,[232.44444444444443,332.8888888888889]]]},\"segments\":{\"add\":[1],\"remove\":[],\"start_point\":[[1,1]],\"end_point\":[[1,2]],\"handle_primary\":[[1,[0.0,0.0]]],\"handle_end\":[[1,[-73.77777777777777,-53.77777777777777]]],\"stroke\":[[1,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14991324592500870173,{\"inputs\":[{\"Node\":{\"node_id\":542361600097372754,\"output_index\":0}},{\"Node\":{\"node_id\":10860592954464951000,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11630078441485655672,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2],\"remove\":[],\"delta\":[[1,[760.6913580246915,657.5802469135803]],[2,[751.4074074074075,685.0370370370371]]]},\"segments\":{\"add\":[1,2],\"remove\":[],\"start_point\":[[2,2],[1,1]],\"end_point\":[[1,2],[2,1]],\"handle_primary\":[[1,[0.0,0.0]],[2,[4.543209876543187,-10.271604938271594]]],\"handle_end\":[[1,[7.111111111110972,-8.44444444444457]],[2,null]],\"stroke\":[[1,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":2}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3970516859959908758,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3],\"remove\":[],\"delta\":[[3,[170.96296296296293,544.5925925925925]],[2,[78.22222222222219,579.2592592592591]],[1,[49.77777777777773,636.148148148148]]]},\"segments\":{\"add\":[1,2],\"remove\":[],\"start_point\":[[1,1],[2,2]],\"end_point\":[[1,2],[2,3]],\"handle_primary\":[[1,[0.0,0.0]],[2,[27.259259259259252,-24.88888888888891]]],\"handle_end\":[[2,[-21.62962962962962,-2.0740740740740193]],[1,[-27.259259259259267,24.88888888888891]]],\"stroke\":[[2,0],[1,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13302269488061286120,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2],\"remove\":[],\"delta\":[[1,[262.66666666666674,903.3333333333331]],[2,[565.3333333333335,756.6666666666666]]]},\"segments\":{\"add\":[1],\"remove\":[],\"start_point\":[[1,1]],\"end_point\":[[1,2]],\"handle_primary\":[[1,[0.0,0.0]]],\"handle_end\":[[1,[-200.66666666666652,64.66666666666663]]],\"stroke\":[[1,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1889157037801767612,{\"inputs\":[{\"Node\":{\"node_id\":17324767436949538365,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":30.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":18},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":2}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::PoissonDiskPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10270446074640675342,{\"inputs\":[{\"Node\":{\"node_id\":14883504161508594099,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4078100635676202528,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26],\"remove\":[],\"delta\":[[10,[653.1555555555556,49.89629629629629]],[18,[621.2740740740741,37.21481481481481]],[4,[627.9111111111112,43.73333333333333]],[7,[684.4444444444445,46.1037037037037]],[19,[613.4518518518519,26.429629629629623]],[5,[653.9851851851852,38.99259259259259]],[1,[606.3407407407408,47.76296296296296]],[2,[603.4962962962964,36.977777777777774]],[11,[665.4814814814815,56.05925925925925]],[24,[605.998353909465,33.79094650205761]],[9,[642.4888888888889,44.44444444444444]],[6,[676.2666666666667,48.47407407407407]],[23,[608.5794238683127,44.82633744855967]],[8,[667.2592592592594,52.029629629629625]],[14,[698.3111111111111,43.61481481481481]],[15,[677.925925925926,46.222222222222214]],[17,[639.762962962963,37.45185185185185]],[22,[607.6312757201646,47.64444444444445]],[3,[613.4518518518519,35.43703703703703]],[26,[607.3152263374486,46.38024691358025]],[13,[707.0814814814814,53.33333333333333]],[21,[605.3925925925926,49.42222222222222]],[25,[604.6288065843622,39.321810699588475]],[16,[661.0962962962963,35.792592592592584]],[12,[683.3777777777777,50.48888888888889]],[20,[601.2444444444444,36.859259259259254]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26],\"remove\":[],\"start_point\":[[1,1],[7,7],[4,4],[19,19],[8,8],[2,2],[25,25],[14,14],[17,17],[6,6],[3,3],[23,23],[24,24],[9,9],[12,12],[15,15],[20,20],[26,26],[10,10],[18,18],[13,13],[16,16],[11,11],[5,5],[21,21],[22,22]],\"end_point\":[[15,16],[8,9],[13,14],[9,10],[18,19],[20,21],[26,1],[14,15],[11,12],[21,22],[3,4],[25,26],[19,20],[16,17],[22,23],[10,11],[6,7],[2,3],[4,5],[24,25],[12,13],[1,2],[23,24],[7,8],[17,18],[5,6]],\"handle_primary\":[[18,[-3.437037037037044,-4.148148148148145]],[6,[4.5037037037037635,1.1851851851851831]],[11,[3.318518518518431,-0.11851851851851336]],[9,[0.0,0.0]],[23,[-2.2123456790121736,-2.3967078189300537]],[5,[10.311111111111131,0.5925925925925952]],[3,[2.9629629629629335,4.385185185185186]],[14,[-10.90370370370374,-2.6074074074074076]],[7,[0.0,0.0]],[4,[6.992592592592587,-0.23703703703703383]],[25,[0.18814026836287212,2.510418336797158]],[19,[-3.3185185185185446,-1.1851851851851831]],[24,[0.0,0.0]],[20,[-1.5407407407407163,8.651851851851852]],[15,[-2.844444444444548,-0.829629629629629]],[8,[-10.666666666666629,-0.7111111111111157]],[1,[0.0,0.0]],[2,[2.1333333333333258,-5.68888888888889]],[21,[0.0,0.0]],[16,[-5.214814814814758,-1.3037037037037038]],[22,[0.0,0.0]],[26,[0.0,0.0]],[13,[0.0,0.0]],[17,[-6.992592592592587,2.962962962962962]],[12,[6.9925925925927,-4.740740740740748]],[10,[2.251851851851825,2.2518518518518533]]],\"handle_end\":[[8,[9.36296296296291,-2.4888888888888943]],[6,[0.0,0.0]],[24,[-0.13898543393838736,-1.8545289902200464]],[1,[-2.1333333333333258,5.68888888888889]],[13,[10.90370370370374,2.6074074074074076]],[25,[0.0,0.0]],[4,[-10.311111111111131,-0.5925925925925952]],[2,[-2.9629629629629335,-4.385185185185186]],[18,[3.3185185185185446,1.1851851851851831]],[3,[-6.992592592592587,0.23703703703703383]],[12,[-3.0814814814815463,-9.48148148148148]],[5,[-4.5037037037037635,-1.1851851851851904]],[14,[2.844444444444548,0.829629629629629]],[9,[-2.251851851851825,-2.2518518518518533]],[7,[10.666666666666629,0.7111111111111086]],[22,null],[15,[5.214814814814758,1.3037037037037038]],[20,[0.0,0.0]],[16,[6.992592592592587,-2.962962962962962]],[21,[0.0,0.0]],[10,[-3.318518518518431,0.11851851851851336]],[11,[-6.9925925925927,4.740740740740748]],[26,[0.0,0.0]],[19,[1.5407407407407163,-8.651851851851855]],[17,[3.437037037037044,4.148148148148145]],[23,[0.0,0.0]]],\"stroke\":[[24,0],[25,0],[13,0],[10,0],[26,0],[20,0],[6,0],[7,0],[19,0],[11,0],[16,0],[23,0],[22,0],[15,0],[2,0],[18,0],[5,0],[21,0],[14,0],[9,0],[4,0],[17,0],[1,0],[8,0],[12,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":26}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2422139482859833437,{\"inputs\":[{\"Node\":{\"node_id\":14537754528543289381,\"output_index\":0}},{\"Node\":{\"node_id\":172538270105470471,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4372998635946271235,{\"inputs\":[{\"Node\":{\"node_id\":13481022631108980683,\"output_index\":0}},{\"Node\":{\"node_id\":2126710823743005151,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[542361600097372754,{\"inputs\":[{\"Node\":{\"node_id\":17971411534648521628,\"output_index\":0}},{\"Node\":{\"node_id\":6867142265138950838,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14888395629683671889,{\"inputs\":[{\"Node\":{\"node_id\":4341772758799935306,\"output_index\":0}},{\"Node\":{\"node_id\":17078740291337047697,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.2},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2698266912167150713,{\"inputs\":[{\"Node\":{\"node_id\":3165571685352930240,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17324767436949538365,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"delta\":[[4,[150.22222222222223,429.3333333333333]],[6,[289.7777777777778,503.1111111111111]],[5,[119.1111111111111,456.44444444444446]],[3,[158.66666666666669,419.55555555555554]],[1,[503.11111111111114,614.6666666666666]],[2,[362.22222222222223,512.0]]]},\"segments\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"start_point\":[[6,6],[3,3],[4,4],[1,1],[5,5],[2,2]],\"end_point\":[[6,1],[5,6],[1,2],[2,3],[3,4],[4,5]],\"handle_primary\":[[2,[-52.888888888888914,-48.44444444444446]],[4,[0.0,0.0]],[6,[89.85096850895411,61.20347364650138]],[1,[0.0,0.0]],[3,[0.0,0.0]],[5,[0.0,0.0]]],\"handle_end\":[[5,[-89.85096850895414,-61.20347364650138]],[4,[7.1111111111111,-35.55555555555554]],[2,[76.0,-1.7777777777777717]],[6,[0.8888888888888005,-0.4444444444444571]],[1,[52.888888888888914,48.44444444444446]],[3,[0.0,0.0]]],\"stroke\":[[2,0],[6,0],[5,0],[1,0],[4,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":6}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1659518581611333812,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17],\"remove\":[],\"delta\":[[2,[913.2510288065844,355.55555555555554]],[12,[858.1618655692731,310.6063100137174]],[16,[854.5185185185187,346.46913580246917]],[13,[872.1207133058986,331.2373113854595]],[15,[872.9108367626887,333.08093278463645]],[4,[869.1358024691356,247.17695473251027]],[8,[809.349794238683,192.0]],[11,[865.1851851851853,248.6255144032922]],[6,[812.2469135802468,182.5185185185185]],[10,[856.2304526748969,234.40329218106996]],[1,[889.8106995884773,368.4609053497942]],[17,[866.172839506173,375.11111111111114]],[3,[917.991769547325,333.4320987654321]],[14,[885.9039780521264,331.0617283950617]],[7,[773.7942386831274,177.119341563786]],[5,[838.3209876543208,205.6954732510288]],[9,[838.4526748971191,213.46502057613168]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17],\"remove\":[],\"start_point\":[[13,13],[9,9],[5,5],[1,1],[17,17],[14,14],[3,3],[2,2],[4,4],[15,15],[7,7],[12,12],[10,10],[16,16],[6,6],[8,8],[11,11]],\"end_point\":[[3,4],[4,5],[7,8],[10,11],[16,17],[11,12],[6,7],[9,10],[12,13],[5,6],[2,3],[15,16],[1,2],[17,1],[13,14],[8,9],[14,15]],\"handle_primary\":[[12,[6.935528120713343,13.080932784636502]],[11,[1.9314128943758533,32.395061728395035]],[15,[-5.267489711934104,1.4924554183813257]],[13,[7.111111111111086,-2.72153635116598]],[2,[7.637860082304428,-3.423868312757179]],[9,[6.189300411522595,6.97942386831275]],[1,[9.349794238683105,-4.213991769547306]],[14,[-5.267489711934104,0.0]],[6,[-10.930041152263357,-4.213991769547334]],[17,[4.279835390946232,0.32921810699582466]],[10,[7.506172839506121,6.452674897119351]],[8,[14.748971193415628,7.242798353909478]],[3,[-10.72985850116538,-29.20905925317203]],[7,[6.584362139917744,1.0534979423868265]],[16,[0.0,0.0]],[4,[-8.03292181069969,-10.008230452674894]],[5,[-6.320987654321016,-8.164609053497912]]],\"handle_end\":[[15,[0.0,0.0]],[17,[-9.349794238683105,4.213991769547306]],[14,[5.267489711934104,-1.4924554183813257]],[7,[-14.748971193415628,-7.242798353909478]],[1,[-7.637860082304542,3.423868312757179]],[5,[10.930041152263357,4.213991769547334]],[11,[0.0,0.0]],[9,[-7.506172839506121,-6.452674897119351]],[16,[-8.098765432098958,-2.1728395061728634]],[12,[0.0,0.0]],[3,[8.03292181069969,10.008230452674894]],[8,[-6.189300411522595,-6.97942386831275]],[10,[0.0,0.0]],[13,[0.0,0.0]],[4,[6.320987654321016,8.164609053497912]],[2,[7.1111111111111995,19.35802469135808]],[6,[0.0,0.0]]],\"stroke\":[[10,0],[11,0],[5,0],[7,0],[2,0],[12,0],[1,0],[13,0],[17,0],[3,0],[15,0],[14,0],[9,0],[4,0],[16,0],[6,0],[8,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":17}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7466034304713056391,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[1,[635.2478000597847,135.0597939750059]],[3,[636.4372010299622,153.9035515500083]],[2,[635.5072483424783,152.80078149291265]],[4,[637.4252384335797,135.01742888696126]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[1,1],[4,4],[3,3],[2,2]],\"end_point\":[[3,4],[1,2],[4,1],[2,3]],\"handle_primary\":[[1,[0.0,0.0]],[2,[0.0,0.0]],[4,[0.0,0.0]],[3,[1.020097146128478,-12.422679731687992]]],\"handle_end\":[[1,[0.4153244360613826,-11.397946559213551]],[3,null],[4,[1.1169817316086892,-0.21205734949143107]],[2,[-0.5579820762119425,-0.504231587867622]]],\"stroke\":[[4,0],[3,0],[2,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3636653585682494814,{\"inputs\":[{\"Node\":{\"node_id\":11565160497886435388,\"output_index\":0}},{\"Node\":{\"node_id\":17078740291337047697,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.2},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2.5},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10587073897090054035,{\"inputs\":[{\"Node\":{\"node_id\":8814059393325469059,\"output_index\":0}},{\"Node\":{\"node_id\":907841922684377912,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14817659161913199655,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3],\"remove\":[],\"delta\":[[2,[725.3333333333333,985.7777777777776]],[3,[726.8148148148148,1023.9999999999998]],[1,[796.148148148148,723.8518518518517]]]},\"segments\":{\"add\":[1,2,3],\"remove\":[],\"start_point\":[[1,1],[3,3],[2,2]],\"end_point\":[[1,2],[3,1],[2,3]],\"handle_primary\":[[1,[0.0,0.0]],[2,[0.0,0.0]],[3,[0.0,0.0]]],\"handle_end\":[[2,[0.0,0.0]],[1,[9.777777777777828,-73.18518518518522]],[3,[-75.55555555555566,240.59259259259304]]],\"stroke\":[[1,0],[2,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":3}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4422453582814483232,{\"inputs\":[{\"Node\":{\"node_id\":4577638792388493935,\"output_index\":0}},{\"Node\":{\"node_id\":431994205232245356,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15498700602024283966,{\"inputs\":[{\"Node\":{\"node_id\":15466714490303763249,\"output_index\":0}},{\"Node\":{\"node_id\":9847383247226990698,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[172538270105470471,{\"inputs\":[{\"Node\":{\"node_id\":9276497172451351253,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.8980392,\"green\":0.8,\"blue\":0.6117647,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.8980392,\"green\":0.8,\"blue\":0.6117647,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11481949351661484921,{\"inputs\":[{\"Node\":{\"node_id\":15303587427289959766,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":5.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11632506522064533635,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7],\"remove\":[],\"delta\":[[1,[40.66666666666663,1022.6666666666666]],[2,[113.33333333333331,859.3333333333333]],[3,[299.33333333333326,775.3333333333333]],[4,[397.3333333333333,770.6666666666666]],[6,[208.0,903.0]],[7,[145.33333333333331,1022.6666666666666]],[5,[336.66666666666663,799.3333333333334]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7],\"remove\":[],\"start_point\":[[6,6],[7,7],[1,1],[2,2],[4,4],[3,3],[5,5]],\"end_point\":[[1,2],[6,7],[3,4],[5,6],[4,5],[2,3],[7,1]],\"handle_primary\":[[7,[0.0,0.0]],[2,[58.666666666666686,-57.33333333333326]],[3,[65.33333333333337,-6.0]],[1,[0.0,0.0]],[5,[-40.66666666666663,12.0]],[4,[0.0,0.0]],[6,[0.0,0.0]]],\"handle_end\":[[3,[0.0,0.0]],[1,[-58.666666666666686,57.333333333333144]],[2,[-65.33333333333337,6.0]],[4,[40.66666666666663,-12.0]],[7,[0.0,1.3333333333333712]],[6,[18.00000000000003,-55.33333333333326]],[5,[35.666666666666686,-46.66666666666674]]],\"stroke\":[[1,0],[3,0],[7,0],[2,0],[4,0],[5,0],[6,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":7}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10928540355449103287,{\"inputs\":[{\"Node\":{\"node_id\":18190631752493248867,\"output_index\":0}},{\"Node\":{\"node_id\":12554368619682347699,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10415872992231003638,{\"inputs\":[{\"Node\":{\"node_id\":8375495949882478840,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8766106989344197438,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"delta\":[[1,[197.33333333333337,212.5432098765432]],[6,[184.49382716049385,218.2716049382716]],[4,[146.5679012345679,204.44444444444449]],[5,[155.85185185185185,211.55555555555557]],[2,[172.64197530864195,208.98765432098767]],[3,[153.58712172411558,204.8434307274338]]]},\"segments\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"start_point\":[[5,5],[1,1],[2,2],[4,4],[3,3],[6,6]],\"end_point\":[[6,1],[2,3],[5,6],[4,5],[1,2],[3,4]],\"handle_primary\":[[3,[-5.834035304362487,0.7861989021958493]],[4,[0.0,0.0]],[5,[8.493827160493822,2.3703703703703525]],[2,[-7.703703703703667,-1.1851851851851904]],[1,[0.0,0.0]],[6,[3.160493827160479,-0.9876543209876444]]],\"handle_end\":[[5,[-3.1604938271605363,0.9876543209877012]],[3,[0.0,0.0]],[6,[-3.160493827160479,1.9753086419753456]],[2,[5.834035304362487,-0.7861989021958493]],[1,[7.703703703703724,1.1851851851852189]],[4,[-8.493827160493794,-2.370370370370381]]],\"stroke\":[[1,0],[6,0],[3,0],[2,0],[4,0],[5,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":6}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9470742171134780193,{\"inputs\":[{\"Node\":{\"node_id\":15126865253122550765,\"output_index\":0}},{\"Node\":{\"node_id\":10270446074640675342,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10795820039540504703,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19],\"remove\":[],\"delta\":[[13,[376.7623479921926,0.4130988647245317]],[8,[447.7037037037037,131.25925925925924]],[10,[396.14814814814815,88.29629629629629]],[5,[574.5185185185185,169.18518518518516]],[19,[540.4444444444445,70.22222222222221]],[3,[590.2222222222222,113.18518518518518]],[6,[518.2222222222222,175.7037037037037]],[9,[418.074074074074,116.14814814814814]],[16,[451.9827338808689,43.25925925925927]],[15,[432.1308820290171,20.740740740740748]],[4,[604.4444444444445,138.96296296296293]],[14,[418.0913936876638,12.121582398270874]],[7,[485.6296296296296,153.48148148148147]],[1,[569.1851851851852,61.629629629629605]],[12,[332.131357712622,16.970215357579164]],[17,[478.3481748953775,55.407407407407405]],[2,[591.1111111111111,92.44444444444444]],[18,[498.66666666666663,63.70370370370368]],[11,[350.8679463145693,59.25925925925925]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19],\"remove\":[],\"start_point\":[[6,6],[1,1],[2,2],[9,9],[14,14],[15,15],[16,16],[5,5],[4,4],[12,12],[17,17],[3,3],[19,19],[8,8],[10,10],[7,7],[11,11],[13,13],[18,18]],\"end_point\":[[17,18],[6,7],[11,12],[12,13],[8,9],[18,19],[15,16],[5,6],[7,8],[4,5],[13,14],[9,10],[19,1],[10,11],[1,2],[16,17],[3,4],[2,3],[14,15]],\"handle_primary\":[[15,[20.148148148148152,6.8148148148148096]],[18,[13.333333333333371,4.148148148148152]],[7,[-15.407407407407447,4.444444444444457]],[1,[12.848891737595522,5.11227609582204]],[4,[18.370370370370324,0.2962962962963047]],[6,[-14.222222222222172,-11.851851851851848]],[8,[-9.481481481481524,-12.740740740740762]],[12,[4.019883543587866,-14.744727738229416]],[13,[41.32904569547122,11.708483533546342]],[19,[11.555555555555545,-3.851851851851848]],[10,[-13.333333333333371,-8.59259259259261]],[5,[-13.629629629629562,5.925925925925924]],[2,[0.0,0.0]],[3,[-6.518518518518476,13.3333333333333]],[9,[-13.629629629629562,-7.407407407407419]],[17,[10.962962962963047,12.148148148148124]],[14,[0.0,0.0]],[11,[-11.259259259259125,-10.962962962962962]],[16,[7.703703703703695,6.814814814814817]]],\"handle_end\":[[5,[14.222222222222172,11.85185185185182]],[2,[6.518518518518476,-13.333333333333314]],[1,[-24.88888888888891,-10.666666666666655]],[14,[-20.14814814814821,-6.814814814814827]],[7,[9.481481481481524,12.740740740740762]],[4,[13.629629629629562,-5.925925925925924]],[19,[-12.030418259761518,-4.786624476892754]],[13,null],[18,[-11.555555555555545,3.851851851851848]],[8,[13.629629629629562,7.407407407407419]],[10,[11.259259259259238,10.962962962963076]],[9,[13.333333333333371,8.59259259259261]],[17,[-13.333333333333371,-4.148148148148152]],[3,[-18.370370370370324,-0.2962962962963047]],[11,[-2.7704748413796665,10.16196036497552]],[16,[-10.96296296296299,-12.148148148148188]],[15,[-7.703703703703695,-6.8148148148148096]],[6,[15.407407407407334,-4.444444444444457]],[12,null]],\"stroke\":[[3,0],[19,0],[6,0],[7,0],[15,0],[14,0],[4,0],[11,0],[10,0],[17,0],[2,0],[16,0],[18,0],[5,0],[8,0],[1,0],[12,0],[9,0],[13,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":19}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7505360855062237520,{\"inputs\":[{\"Node\":{\"node_id\":17945736750161448391,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12313564802550122052,{\"inputs\":[{\"Node\":{\"node_id\":13557369662261607646,\"output_index\":0}},{\"Node\":{\"node_id\":9684857454501250999,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8413863870096329943,{\"inputs\":[{\"Node\":{\"node_id\":16195626650123806176,\"output_index\":0}},{\"Node\":{\"node_id\":5302437193964714993,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9847383247226990698,{\"inputs\":[{\"Node\":{\"node_id\":3627710206997006419,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.7529412,\"green\":0.15686275,\"blue\":0.13333334,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.7529412,\"green\":0.15686275,\"blue\":0.13333334,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14831840560430171946,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"delta\":[[1,[791.5555555555555,1026.6666666666663]],[6,[793.7777777777777,1027.2592592592591]],[4,[893.7613168724276,509.6296296296296]],[5,[833.4814814814815,746.3703703703703]],[3,[889.7777777777777,516.8888888888889]],[2,[831.5555555555555,737.3333333333334]]]},\"segments\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"start_point\":[[5,5],[6,6],[4,4],[1,1],[2,2],[3,3]],\"end_point\":[[3,4],[1,2],[4,5],[6,1],[2,3],[5,6]],\"handle_primary\":[[2,[19.555555555555543,-74.66666666666674]],[3,[0.0,0.0]],[4,[0.0,0.0]],[5,[-24.0,92.44444444444446]],[1,[0.0,0.0]],[6,[0.0,0.0]]],\"handle_end\":[[1,[-26.4188207246807,100.8718609487812]],[3,[0.0,0.0]],[5,[0.0,0.0]],[6,[-0.14814814814815236,0.29629629629675946]],[2,[-22.22222222222217,89.77777777777783]],[4,[24.0,-92.44444444444446]]],\"stroke\":[[6,0],[4,0],[2,0],[3,0],[5,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":6}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12473080738469616517,{\"inputs\":[{\"Node\":{\"node_id\":17891208858820401648,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17133591775058457007,{\"inputs\":[{\"Node\":{\"node_id\":18067513817508158001,\"output_index\":0}},{\"Node\":{\"node_id\":17078740291337047697,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.2},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13163272246010991228,{\"inputs\":[{\"Node\":{\"node_id\":9740500978584792725,\"output_index\":0}},{\"Node\":{\"node_id\":10253927692147706615,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15286091228862934481,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7],\"remove\":[],\"delta\":[[1,[623.1001371742112,22.694101508916333]],[2,[627.0946502057614,23.00137174211249]],[5,[684.554183813443,27.654320987654327]],[3,[638.5953360768175,27.56652949245542]],[7,[637.0589849108368,29.980795610425247]],[4,[665.7229080932784,23.79149519890261]],[6,[661.2894375857338,26.381344307270236]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7],\"remove\":[],\"start_point\":[[3,3],[7,7],[4,4],[5,5],[1,1],[2,2],[6,6]],\"end_point\":[[4,5],[1,2],[7,1],[5,6],[6,7],[3,4],[2,3]],\"handle_primary\":[[3,[5.3991769547326385,0.13168724279836042]],[2,[2.150891632373032,1.5802469135802468]],[5,[0.0,0.0]],[4,[6.672153635116501,-0.35116598079560646]],[7,[-6.089851956901498,-0.48236451143773706]],[6,[-11.456790123456813,0.9657064471879302]],[1,[0.0,0.0]]],\"handle_end\":[[3,[-6.672153635116501,0.35116598079560646]],[4,[-0.9218106995884908,-1.0534979423868336]],[1,[-2.150891632373032,-1.5802469135802468]],[2,[-5.486565700800156,-0.13381867562927496]],[6,[4.433470507544598,0.35116598079561]],[5,[11.456790123456813,-0.9657064471879336]],[7,[2.194787379972581,3.906721536351163]]],\"stroke\":[[6,0],[1,0],[3,0],[7,0],[2,0],[5,0],[4,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":7}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3165571685352930240,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[4,[641.1783537148203,135.11412115589184]],[1,[639.0431812985823,135.02706332876082]],[2,[641.3926773385256,156.76403071818197]],[3,[642.0732703685807,156.78028060137643]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[2,2],[4,4],[3,3],[1,1]],\"end_point\":[[4,1],[3,4],[2,3],[1,2]],\"handle_primary\":[[3,[-0.4020858660272779,-10.361367902183218]],[2,[0.0,0.0]],[4,[0.0,0.0]],[1,[0.0,0.0]]],\"handle_end\":[[3,null],[2,[-0.29793124344723765,0.12588207707705124]],[4,[1.0850699611588652,-0.2123114466060372]],[1,[-0.08991158554488266,-10.211393405397416]]],\"stroke\":[[1,0],[3,0],[2,0],[4,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4105711298139980122,{\"inputs\":[{\"Node\":{\"node_id\":1162381870526064378,\"output_index\":0}},{\"Node\":{\"node_id\":1272070255512697108,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13014916927589286309,{\"inputs\":[{\"Node\":{\"node_id\":2044103368441997753,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":5.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":2}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::PoissonDiskPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15815816861435910950,{\"inputs\":[{\"Node\":{\"node_id\":4105711298139980122,\"output_index\":0}},{\"Node\":{\"node_id\":17815494794630739611,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15798070933198867970,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12],\"remove\":[],\"delta\":[[12,[660.0,500.66666666666663]],[11,[588.6666666666665,453.3333333333333]],[6,[438.18064449587104,508.2403828865154]],[10,[508.88888888888886,389.33333333333337]],[1,[595.1111111111111,513.3333333333333]],[5,[449.99999999999994,445.3333333333333]],[2,[642.2222222222222,536.8888888888889]],[9,[424.44444444444446,340.0]],[7,[384.7140020398532,440.0243218219409]],[8,[350.6666666666667,340.0]],[3,[598.6666666666666,547.1111111111111]],[4,[536.0,522.2222222222221]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12],\"remove\":[],\"start_point\":[[6,6],[11,11],[5,5],[1,1],[2,2],[12,12],[8,8],[4,4],[9,9],[3,3],[7,7],[10,10]],\"end_point\":[[7,8],[6,7],[12,1],[1,2],[9,10],[11,12],[2,3],[3,4],[8,9],[5,6],[10,11],[4,5]],\"handle_primary\":[[11,[22.666666666666856,18.0]],[9,[24.0,8.0]],[10,[25.481481481481467,20.4444444444444]],[8,[-46.969945387028645,-26.215783471829923]],[5,[0.0,0.0]],[1,[0.0,0.0]],[7,[5.531031978208716,-44.24825582567013]],[6,[0.0,0.0]],[12,[-31.999999999999886,10.666666666666686]],[3,[-18.370370370370324,2.1728395061728634]],[2,[0.0,0.0]],[4,[-25.28395061728401,-13.827160493827025]]],\"handle_end\":[[6,[-4.764895727801786,38.11916582241446]],[1,[-58.22222222222217,-1.3333333333332575]],[11,[-17.185185185185105,5.925925925925867]],[9,[-25.481481481481467,-20.4444444444444]],[5,[0.0,0.0]],[2,[18.370370370370324,-2.1728395061728634]],[7,[28.6666666666668,16.000000000000057]],[8,[-24.0,-8.0]],[3,[25.283950617284063,13.827160493827025]],[4,[0.0,0.0]],[12,null],[10,[-23.199803616588156,-18.42337346023163]]],\"stroke\":[[6,0],[2,0],[7,0],[9,0],[12,0],[4,0],[5,0],[11,0],[1,0],[10,0],[8,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":12}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6672826052605647592,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12],\"remove\":[],\"delta\":[[7,[591.0123456790125,60.40054869684499]],[11,[599.381801554641,127.00502972107913]],[2,[614.2716726786227,123.52371759047573]],[9,[593.3827160493829,85.77229080932784]],[12,[613.7991159884164,125.99055022100288]],[1,[617.5253772290811,122.03017832647464]],[10,[599.4403292181071,108.68587105624144]],[8,[585.8326474622772,72.60356652949247]],[5,[603.127572016461,99.64334705075449]],[6,[594.172839506173,82.87517146776406]],[4,[600.0548696844994,115.53360768175584]],[3,[605.4979423868314,125.19067215363512]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12],\"remove\":[],\"start_point\":[[4,4],[9,9],[1,1],[8,8],[5,5],[6,6],[10,10],[3,3],[12,12],[11,11],[2,2],[7,7]],\"end_point\":[[5,6],[7,8],[6,7],[2,3],[10,11],[3,4],[8,9],[9,10],[11,12],[12,1],[1,2],[4,5]],\"handle_primary\":[[2,[-1.2820759681926577,0.7008681959453185]],[7,[0.0,0.0]],[6,[-6.145404663923159,-8.076817558299041]],[10,[-2.575217192501441,8.77914951989024]],[3,[-4.594421582076166,-1.1705532693187024]],[4,[0.8779149519890552,-4.477366255144034]],[12,[2.3801249809480396,-1.7168114616673904]],[11,[3.960543177125487,4.094798878044912]],[9,[3.599451303154978,4.477366255144034]],[5,[-0.8779149519890552,-4.477366255144048]],[1,[0.0,0.0]],[8,[0.6346981736430735,5.019885555177211]]],\"handle_end\":[[4,[0.8779149519890552,4.47736625514402]],[6,[-6.057613168724288,9.305898491083669]],[5,[6.145404663923159,8.076817558299041]],[1,[1.3006147436874471,-0.7110027265491681]],[10,[-4.118548609866821,-4.258160427150372]],[3,[-0.8779149519890552,4.477366255144034]],[2,[3.396564570446685,0.8653667695405147]],[12,[0.0,0.0]],[9,[3.3684073442221916,-11.48320685530176]],[11,[-2.036412917146322,1.4688880058104417]],[8,[-3.599451303154978,-4.477366255144034]],[7,[-0.9657064471879266,-7.637860082304528]]],\"stroke\":[[2,0],[1,0],[5,0],[4,0],[12,0],[8,0],[6,0],[9,0],[10,0],[11,0],[3,0],[7,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":12}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7385465194555106679,{\"inputs\":[{\"Node\":{\"node_id\":5009664118231399060,\"output_index\":0}},{\"Node\":{\"node_id\":6416452251137958677,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9425359632144678256,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5],\"remove\":[],\"delta\":[[3,[622.6042778031804,84.90413046791649]],[2,[615.0086877000457,93.76131687242795]],[5,[606.9918024691358,101.68414814814815]],[4,[612.3749428440786,88.61088248742568]],[1,[610.2608816540582,107.73159867034722]]]},\"segments\":{\"add\":[1,2,3,4,5],\"remove\":[],\"start_point\":[[1,1],[5,5],[3,3],[2,2],[4,4]],\"end_point\":[[2,3],[1,2],[3,4],[5,1],[4,5]],\"handle_primary\":[[4,[-3.0889600162577153,2.952395468170536]],[1,[0.0,0.0]],[2,[3.21902149062646,-3.511659807956093]],[3,[-0.6242950769699291,-0.9364426154549648]],[5,[0.0,0.0]]],\"handle_end\":[[3,[2.992367941940074,-2.86007378029646]],[4,[-0.5623782142248501,-6.258066225952547]],[5,[-2.1454520955559246,-3.1101486881290583]],[2,[0.15607376924265282,0.7543565513387165]],[1,[-3.21902149062646,3.5116598079561214]]],\"stroke\":[[3,0],[5,0],[2,0],[1,0],[4,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":5}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15177845878727456758,{\"inputs\":[{\"Node\":{\"node_id\":14225285635863713990,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5555007473125503522,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7],\"remove\":[],\"delta\":[[2,[724.7285474775185,66.64349946654472]],[3,[723.7140679774425,72.106081390032]],[6,[724.0262155159273,62.468526139308025]],[4,[725.0797134583142,72.96448712086573]],[7,[722.6215515927449,59.58116140832189]],[1,[721.7241274196006,63.248894985520494]],[5,[726.0551745160798,67.30681298582532]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7],\"remove\":[],\"start_point\":[[3,3],[1,1],[6,6],[5,5],[4,4],[2,2],[7,7]],\"end_point\":[[5,6],[3,4],[7,1],[4,5],[1,2],[6,7],[2,3]],\"handle_primary\":[[6,[-0.03901844231063478,-1.2485901539399509]],[7,[-1.014479500076277,0.3901844231062413]],[2,[0.546258192348887,2.106995884773667]],[5,[-0.585276634659408,-3.8628257887517066]],[4,[0.6633135192806776,-0.8584057308337094]],[3,[-1.287608596250493,1.7558299039780536]],[1,[0.0,0.0]]],\"handle_end\":[[6,[1.014479500076277,-0.39018442310623414]],[4,[0.585276634659408,3.8628257887517066]],[7,[0.07803688462126956,-0.03901844231062768]],[1,[-0.501794076076294,-1.9354914362939013]],[5,[0.03901844231063478,1.2485901539399509]],[2,[1.852405339488314,-2.526007281120613]],[3,[-0.6633135192806776,0.8584057308337094]]],\"stroke\":[[2,0],[3,0],[6,0],[1,0],[7,0],[4,0],[5,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":7}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17529660518597229229,{\"inputs\":[{\"Node\":{\"node_id\":3802858053991775169,\"output_index\":0}},{\"Node\":{\"node_id\":4332145463108161926,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":4.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":40.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":-30.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16446146761452576438,{\"inputs\":[{\"Node\":{\"node_id\":12131058586835568367,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[619.1444612416404,54.266956717585614]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":-35.05804989521759},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[5.617278800347149,2.5068847538738956]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[6.794509856837352e-15,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[454416440369338250,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2],\"remove\":[],\"delta\":[[1,[696.0,593.3333333333333]],[2,[733.7777777777778,518.6666666666666]]]},\"segments\":{\"add\":[1],\"remove\":[],\"start_point\":[[1,1]],\"end_point\":[[1,2]],\"handle_primary\":[[1,[0.0,0.0]]],\"handle_end\":[[1,[-18.666666666666515,26.66666666666663]]],\"stroke\":[[1,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15038739378867834454,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7],\"remove\":[],\"delta\":[[3,[691.5336076817558,52.14814814814816]],[5,[704.6145404663924,55.39643347050756]],[1,[654.3978052126201,51.621399176954746]],[4,[704.965706447188,54.25514403292181]],[6,[687.4951989026064,54.518518518518526]],[7,[662.3868312757203,59.61042524005489]],[2,[667.4787379972565,58.20576131687244]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7],\"remove\":[],\"start_point\":[[6,6],[4,4],[1,1],[3,3],[7,7],[5,5],[2,2]],\"end_point\":[[7,1],[6,7],[2,3],[1,2],[3,4],[4,5],[5,6]],\"handle_primary\":[[7,[-4.546573253919632,-2.2347563451469625]],[1,[0.0,0.0]],[2,[6.145404663923159,-1.0534979423868336]],[5,[0.0,0.0]],[3,[7.723134415788309,-0.2640387834457485]],[6,[-10.156808190486911,1.6663513437517778]],[4,[0.0,0.0]]],\"handle_end\":[[7,[0.0,0.08779149519890694]],[2,[-10.271604938271594,0.35116598079560646]],[4,[0.0,0.0]],[5,[11.237311385459408,-1.8436213991769537]],[6,[5.179698216735233,2.5459533607681664]],[1,[-5.875074923313605,1.0071557011394745]],[3,null]],\"stroke\":[[6,0],[1,0],[4,0],[3,0],[7,0],[2,0],[5,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":7}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14982414026754548178,{\"inputs\":[{\"Node\":{\"node_id\":13045580349734858212,\"output_index\":0}},{\"Node\":{\"node_id\":4332145463108161926,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":10.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":-25.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4493274523708782092,{\"inputs\":[{\"Node\":{\"node_id\":15239301303367148581,\"output_index\":0}},{\"Node\":{\"node_id\":12880230498984021417,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6973438081601736688,{\"inputs\":[{\"Node\":{\"node_id\":11630078441485655672,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4577638792388493935,{\"inputs\":[{\"Node\":{\"node_id\":3170924135668664007,\"output_index\":0}},{\"Node\":{\"node_id\":6292009934909381201,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12131058586835568367,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[4,[0.022131022857413415,0.4190687668825941]],[2,[1.0389965338526328,0.5311836299154763]],[1,[0.6081211287919952,-0.2081641356766983]],[3,[0.443655685420585,0.8388279058567918]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[1,1],[3,3],[4,4],[2,2]],\"end_point\":[[4,1],[2,3],[1,2],[3,4]],\"handle_primary\":[[3,[-0.29501938196342326,-0.04735956037402645]],[2,[-0.06483434986356718,0.26682960488486684]],[4,[0.021131345375600137,-0.27179882337964756]],[1,[0.297407817404018,0.05174926937677715]]],\"handle_end\":[[1,[0.07365905854782184,-0.30314821587423946]],[2,[0.2950193819634199,0.04735956037402589]],[3,[-0.02113134537560013,0.27179882337964756]],[4,[-0.38805268271111915,-0.0675215701634326]]],\"stroke\":[[4,0],[1,0],[3,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1157261387411722141,{\"inputs\":[{\"Node\":{\"node_id\":4577174813962563383,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11659756061767599421,{\"inputs\":[{\"Node\":{\"node_id\":8766106989344197438,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.8980392,\"green\":0.8,\"blue\":0.6117647,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.8980392,\"green\":0.8,\"blue\":0.6117647,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5140869461760168364,{\"inputs\":[{\"Node\":{\"node_id\":17118107476414252025,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.7529412,\"green\":0.15686275,\"blue\":0.13333334,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.7529412,\"green\":0.15686275,\"blue\":0.13333334,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8543051864256131356,{\"inputs\":[{\"Node\":{\"node_id\":5002654561220917457,\"output_index\":0}},{\"Node\":{\"node_id\":4332145463108161926,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":5.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":300.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":-100.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16732130236852371275,{\"inputs\":[{\"Node\":{\"node_id\":4832236468224231783,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":6.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5185036609290210853,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29],\"remove\":[],\"delta\":[[6,[695.152568206066,67.34583142813594]],[16,[713.7448559670781,60.37860082304528]],[2,[697.9423868312758,64.79012345679013]],[1,[687.846364883402,65.58024691358025]],[13,[706.9556470050298,39.272062185642426]],[9,[702.2222222222223,38.38683127572017]],[25,[719.8512421886905,54.19661636945587]],[11,[697.7283950617284,31.30864197530864]],[8,[709.8600823045268,50.83127572016461]],[12,[698.2935528120714,34.35573845450388]],[21,[716.1444901691814,49.82655083066605]],[19,[705.9094650205762,70.25514403292182]],[26,[722.5825331504344,57.31809175430575]],[20,[716.6907483615302,62.66361835086114]],[17,[713.3351623228167,47.46593507087334]],[10,[696.5925925925927,33.728395061728385]],[3,[703.7366255144034,54.694101508916326]],[29,[694.0600518213687,74.40816948635879]],[5,[705.102270995275,59.269013869836904]],[23,[711.5061728395062,69.99176954732509]],[7,[701.785703398872,67.26779454351473]],[24,[719.0123456790122,66.6337448559671]],[4,[704.9657064471878,54.25514403292179]],[22,[719.6171315348269,59.659198292943145]],[27,[718.680688919372,68.71147690900777]],[14,[711.9695168419447,48.57796067672611]],[18,[716.7736625514402,54.9135802469136]],[15,[703.604938271605,68.21399176954733]],[28,[702.2539247065995,73.00350556317633]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29],\"remove\":[],\"start_point\":[[1,1],[28,28],[11,11],[20,20],[27,27],[26,26],[7,7],[6,6],[2,2],[5,5],[19,19],[4,4],[17,17],[18,18],[14,14],[16,16],[8,8],[25,25],[10,10],[21,21],[23,23],[3,3],[9,9],[29,29],[12,12],[13,13],[15,15],[22,22],[24,24]],\"end_point\":[[19,20],[26,27],[28,29],[15,16],[6,7],[11,12],[5,6],[14,15],[1,2],[7,8],[16,17],[13,14],[4,5],[10,11],[23,24],[27,28],[20,21],[9,10],[29,1],[3,4],[18,19],[25,26],[24,25],[17,18],[8,9],[22,23],[12,13],[2,3],[21,22]],\"handle_primary\":[[2,[1.5646505637769224,-1.2302214356413188]],[18,[-0.7242798353908029,5.860082304526735]],[26,[0.8664211612748431,3.3573819999397045]],[24,[2.106995884773596,-3.0044200579180256]],[12,[1.5089163237310004,2.3850022862368547]],[5,[-1.1705532693187024,3.0044200579180043]],[10,[-0.04481007342064913,-1.8372595419893116]],[17,[0.0,0.0]],[21,[0.0,0.0]],[13,[2.1939750755697105,1.3128956217623369]],[25,[0.0,0.0]],[6,[0.0,0.0]],[4,[0.0,0.0]],[9,[-3.823807346440958,-1.6387745770461848]],[11,[0.0,0.0]],[8,[1.1368683772161605e-13,-7.4135040390184415]],[28,[-7.920532726374063,-0.13055823175342596]],[29,[0.0,0.0]],[20,[2.536198750190465,-6.516079865874104]],[14,[0.9193720469440904,5.347965249199831]],[16,[2.9207548934437,-5.774876595401999]],[27,[-4.128769232802142,3.294230770852593]],[15,[0.0,0.0]],[7,[3.3946044810244302,-1.8728852309099435]],[1,[0.0,0.0]],[23,[0.0,0.0]],[19,[0.0,0.0]],[22,[-1.7948483462886315,6.047858558146615]],[3,[0.0,0.0]]],\"handle_end\":[[18,[9.169333942996444,-4.409083981100437]],[14,[7.30864197530866,-5.333333333333336]],[28,[0.0,0.0]],[27,[7.101356500533598,0.11705532693187592]],[29,[0.009754610577488164,0.048773052888265056]],[1,[-2.555707971345896,2.0094497789970944]],[12,[-3.679926840420876,-2.202103337905797]],[21,[1.7948483462886315,-6.047858558146615]],[20,[1.638774577046206,1.5607376924249363]],[8,[5.150019007352512,2.2071510031511608]],[23,[-2.1028230213785264,2.9984698638176326]],[22,[2.731290961743639,-1.326627038561199]],[16,[1.1705532693187024,1.8728852309099224]],[6,[-3.208476806721251,1.7701941002599142]],[17,[0.667740598959881,-5.402628482494777]],[7,[-1.1368683772161605e-13,8.389895136005812]],[2,[0.5267489711934559,6.320987654320987]],[9,[0.04481007342064913,1.837259541989333]],[25,[-0.6242950769700428,-2.419143423258646]],[24,[2.980033531474078,4.338363054412447]],[26,[3.66773357719876,-2.926383173296756]],[4,[1.1705532693187024,-3.0044200579180043]],[15,[-3.84819387288519,7.608596250571544]],[19,[-2.536198750190465,6.516079865874104]],[3,[0.0,0.0]],[13,[-0.7923207898123792,-4.608911119518574]],[5,[6.516079865874076,-2.4581618655692807]],[10,[0.0,0.0]],[11,[-0.8654907204776237,-1.3679998781853442]]],\"stroke\":[[2,0],[15,0],[17,0],[22,0],[1,0],[10,0],[9,0],[6,0],[25,0],[28,0],[29,0],[19,0],[5,0],[24,0],[13,0],[20,0],[11,0],[26,0],[14,0],[4,0],[8,0],[18,0],[3,0],[21,0],[16,0],[23,0],[7,0],[12,0],[27,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":29}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[18190631752493248867,{\"inputs\":[{\"Node\":{\"node_id\":12428327489525325219,\"output_index\":0}},{\"Node\":{\"node_id\":10375238420217738812,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8644924780109919177,{\"inputs\":[{\"Node\":{\"node_id\":10599660455959346550,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":5.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5020096817747898028,{\"inputs\":[{\"Node\":{\"node_id\":15286091228862934481,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.8980392,\"green\":0.8,\"blue\":0.6117647,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.8980392,\"green\":0.8,\"blue\":0.6117647,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2641530639940889619,{\"inputs\":[{\"Node\":{\"node_id\":5174744389209053970,\"output_index\":0}},{\"Node\":{\"node_id\":14539627480594383748,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5326536612985524219,{\"inputs\":[{\"Node\":{\"node_id\":15798070933198867970,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":27.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":9},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":2}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::PoissonDiskPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11666664915283969027,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2],\"remove\":[],\"delta\":[[1,[802.3703703703703,580.9382716049382]],[2,[751.4074074074075,685.0370370370371]]]},\"segments\":{\"add\":[1,2],\"remove\":[],\"start_point\":[[1,1],[2,2]],\"end_point\":[[1,2],[2,1]],\"handle_primary\":[[2,[20.740740740740534,-42.469135802469054]],[1,[0.0,0.0]]],\"handle_end\":[[1,[27.259259259259125,-48.79012345678995]],[2,null]],\"stroke\":[[1,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":2}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3414873131936208778,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2],\"remove\":[],\"delta\":[[2,[697.7777777777777,954.2222222222222]],[1,[761.7777777777777,737.3333333333333]]]},\"segments\":{\"add\":[1,2],\"remove\":[],\"start_point\":[[1,1],[2,2]],\"end_point\":[[1,2],[2,1]],\"handle_primary\":[[2,[0.0,0.0]],[1,[0.0,0.0]]],\"handle_end\":[[1,[26.22222222222217,-158.66666666666652]],[2,[-40.0,81.33333333333326]]],\"stroke\":[[1,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":2}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3686761601672683183,{\"inputs\":[{\"Node\":{\"node_id\":4859656512650360562,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":5.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16805628435335819723,{\"inputs\":[{\"Node\":{\"node_id\":10689298484366290551,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.7529412,\"green\":0.15686275,\"blue\":0.13333334,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.7529412,\"green\":0.15686275,\"blue\":0.13333334,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13557369662261607646,{\"inputs\":[{\"Node\":{\"node_id\":3535178979443201645,\"output_index\":0}},{\"Node\":{\"node_id\":15177845878727456758,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16339345235172368839,{\"inputs\":[{\"Node\":{\"node_id\":14778750092903591172,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.7529412,\"green\":0.15686275,\"blue\":0.13333334,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.7529412,\"green\":0.15686275,\"blue\":0.13333334,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[18214377096178867498,{\"inputs\":[{\"Node\":{\"node_id\":7747398671834040298,\"output_index\":0}},{\"Node\":{\"node_id\":4332145463108161926,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":2.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":300.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":-100.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3955326429435439190,{\"inputs\":[{\"Node\":{\"node_id\":18207065424980079673,\"output_index\":0}},{\"Node\":{\"node_id\":17078740291337047697,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.2},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2126710823743005151,{\"inputs\":[{\"Node\":{\"node_id\":14831840560430171946,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11804065810513502701,{\"inputs\":[{\"Node\":{\"node_id\":9782123335421401489,\"output_index\":0}},{\"Node\":{\"node_id\":17133591775058457007,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7320676248579211727,{\"inputs\":[{\"Node\":{\"node_id\":14817659161913199655,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7654665057468818389,{\"inputs\":[{\"Node\":{\"node_id\":17378885078543074499,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10514847656270897393,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":16339345235172368839,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16434255153991868080,{\"inputs\":[{\"Node\":{\"node_id\":6124821161363551058,\"output_index\":0}},{\"Node\":{\"node_id\":17078740291337047697,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.2},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17207349373429328029,{\"inputs\":[{\"Node\":{\"node_id\":17967471489196302183,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6867142265138950838,{\"inputs\":[{\"Node\":{\"node_id\":4784708315242877950,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14862049226133442027,{\"inputs\":[{\"Node\":{\"node_id\":16614450796751955858,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.7529412,\"green\":0.15686275,\"blue\":0.13333334,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.7529412,\"green\":0.15686275,\"blue\":0.13333334,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8814059393325469059,{\"inputs\":[{\"Node\":{\"node_id\":15492651270767932214,\"output_index\":0}},{\"Node\":{\"node_id\":14035980686649077716,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17364155187784942740,{\"inputs\":[{\"Node\":{\"node_id\":1378578509112405,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.9019608,\"green\":0.8,\"blue\":0.6117647,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.9019608,\"green\":0.8,\"blue\":0.6117647,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10586744777717861556,{\"inputs\":[{\"Node\":{\"node_id\":11417901103965737900,\"output_index\":0}},{\"Node\":{\"node_id\":17078740291337047697,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.2},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15656854169166220905,{\"inputs\":[{\"Node\":{\"node_id\":7821977654068146599,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-5.0,22.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4909350123806022131,{\"inputs\":[{\"Node\":{\"node_id\":15498700602024283966,\"output_index\":0}},{\"Node\":{\"node_id\":16536768589601337644,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10190227675276560561,{\"inputs\":[{\"Node\":{\"node_id\":2682920349304670808,\"output_index\":0}},{\"Node\":{\"node_id\":16434255153991868080,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10424806499648491677,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":17147975601187022720,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14598755603287563819,{\"inputs\":[{\"Node\":{\"node_id\":7466034304713056391,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8230694129617719636,{\"inputs\":[{\"Node\":{\"node_id\":4909350123806022131,\"output_index\":0}},{\"Node\":{\"node_id\":16805628435335819723,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16175421708184657649,{\"inputs\":[{\"Node\":{\"node_id\":8698602280607307123,\"output_index\":0}},{\"Node\":{\"node_id\":514796034658094296,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[18422317423856403288,{\"inputs\":[{\"Node\":{\"node_id\":10133176481349663495,\"output_index\":0}},{\"Node\":{\"node_id\":12594527670567285670,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15395954548128560685,{\"inputs\":[{\"Node\":{\"node_id\":13475705179546695973,\"output_index\":0}},{\"Node\":{\"node_id\":16767482995096345179,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11464423670065789907,{\"inputs\":[{\"Node\":{\"node_id\":8644924780109919177,\"output_index\":0}},{\"Node\":{\"node_id\":17078740291337047697,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.2},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5269304445610080925,{\"inputs\":[{\"Node\":{\"node_id\":3686761601672683183,\"output_index\":0}},{\"Node\":{\"node_id\":17078740291337047697,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.2},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14012583111791538162,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3],\"remove\":[],\"delta\":[[3,[603.3333333333335,744.6666666666666]],[1,[248.00000000000009,884.0]],[2,[380.00000000000006,806.0]]]},\"segments\":{\"add\":[1,2],\"remove\":[],\"start_point\":[[1,1],[2,2]],\"end_point\":[[2,3],[1,2]],\"handle_primary\":[[1,[0.0,0.0]],[2,[70.80694036316174,-19.49756328840681]]],\"handle_end\":[[2,[-71.33333333333326,24.0]],[1,[-92.00000000000006,25.333333333333258]]],\"stroke\":[[2,0],[1,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4784708315242877950,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7],\"remove\":[],\"delta\":[[4,[468.14814814814815,1025.382716049383]],[6,[592.0,845.3333333333333]],[2,[808.5333333333333,683.1555555555556]],[7,[807.0666666666666,680.1333333333332]],[5,[464.5925925925926,1025.382716049383]],[3,[592.4000000000001,852.3999999999999]],[1,[902.6666666666669,446.66666666666674]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7],\"remove\":[],\"start_point\":[[6,6],[5,5],[1,1],[7,7],[4,4],[3,3],[2,2]],\"end_point\":[[1,2],[3,4],[5,6],[6,7],[7,1],[4,5],[2,3]],\"handle_primary\":[[4,[0.0,0.0]],[5,[0.0,0.0]],[7,[62.39957279246403,-72.271586423759]],[2,[-81.33333333333337,100.88888888888891]],[1,[0.0,0.0]],[6,[93.94069526511169,-85.35957406301043]],[3,[-91.33798434535026,86.27035509501377]]],\"handle_end\":[[6,[-87.55555555555577,101.4074074074075]],[3,[0.0,0.0]],[2,[85.857044886376,-81.09351003138556]],[4,[0.0,0.0]],[7,[-3.466666666666697,51.33333333333326]],[5,[-102.71604938271612,93.3333333333336]],[1,[97.81027061870486,-121.32749415544254]]],\"stroke\":[[7,0],[2,0],[6,0],[4,0],[5,0],[1,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":7}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8375495949882478840,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11],\"remove\":[],\"delta\":[[10,[650.9056546258192,56.62876594015139]],[9,[662.1234567901236,66.9849108367627]],[7,[673.0096021947875,65.84362139917695]],[6,[662.1234567901236,64.96570644718793]],[2,[617.4278311233043,51.042625616013815]],[4,[642.6077325610934,47.29035208047554]],[8,[675.3580246913581,68.74074074074075]],[5,[650.6227709190673,52.93827160493828]],[1,[616.0751917898693,61.31097901742621]],[3,[628.5871056241429,46.71808159325306]],[11,[641.3168724279836,48.02194787379973]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11],\"remove\":[],\"start_point\":[[7,7],[6,6],[8,8],[9,9],[1,1],[11,11],[2,2],[4,4],[3,3],[5,5],[10,10]],\"end_point\":[[7,8],[2,3],[6,7],[9,10],[5,6],[1,2],[3,4],[4,5],[10,11],[11,1],[8,9]],\"handle_primary\":[[8,[0.0,0.0]],[9,[-4.389574759945162,-2.106995884773667]],[6,[5.4430727023319605,2.545953360768181]],[4,[2.784561671457709,0.5265657466421629]],[7,[0.0,0.0]],[2,[2.1833209230284183,-3.0691100638496778]],[1,[-0.7315957933242316,0.01300614743686168]],[11,[-8.252400548696869,-0.8779149519890339]],[10,[-2.0025980353477735,-3.5689865976493422]],[5,[3.160493827160508,4.6529492455418335]],[3,[4.873654303444027,-0.3091793289098348]]],\"handle_end\":[[6,[0.0,0.0]],[4,[-3.160493827160508,-4.6529492455418335]],[10,[6.086554705109506,0.6475058196925048]],[5,[-5.4430727023319605,-2.545953360768181]],[1,[-2.4595700304455477,3.4574354386312436]],[7,[0.0,0.0]],[9,[2.6272417822486887,4.682213077274824]],[3,[-2.7845616714574817,-0.5265657466421203]],[2,[-5.145487535686698,0.32642413354457744]],[8,[4.389574759945162,2.106995884773667]],[11,[2.0257074632933154,-0.6893258141543512]]],\"stroke\":[[9,0],[6,0],[7,0],[8,0],[1,0],[3,0],[11,0],[2,0],[5,0],[10,0],[4,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":11}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11677503666435782605,{\"inputs\":[{\"Node\":{\"node_id\":12049041947382267086,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.7529412,\"green\":0.15686275,\"blue\":0.13333334,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.7529412,\"green\":0.15686275,\"blue\":0.13333334,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16536768589601337644,{\"inputs\":[{\"Node\":{\"node_id\":4222034829755771252,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.8980392,\"green\":0.8,\"blue\":0.6117647,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.8980392,\"green\":0.8,\"blue\":0.6117647,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2087303479944421366,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3],\"remove\":[],\"delta\":[[2,[443.2592592592592,882.0740740740739]],[1,[365.6296296296296,1025.4814814814813]],[3,[368.59259259259255,1025.1851851851852]]]},\"segments\":{\"add\":[1,2],\"remove\":[],\"start_point\":[[1,1],[2,2]],\"end_point\":[[1,2],[2,3]],\"handle_primary\":[[1,[0.0,0.0]],[2,[0.0,0.0]]],\"handle_end\":[[1,[-48.59259259259255,50.07407407407413]],[2,[31.111111111111143,-96.29629629629642]]],\"stroke\":[[1,0],[2,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17426609415699324395,{\"inputs\":[{\"Node\":{\"node_id\":15896921950407486754,\"output_index\":0}},{\"Node\":{\"node_id\":11464423670065789907,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16450742929146919960,{\"inputs\":[{\"Node\":{\"node_id\":14887821801874852671,\"output_index\":0}},{\"Node\":{\"node_id\":11899713172487274471,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8595304668947966919,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2],\"remove\":[],\"delta\":[[1,[810.1333333333333,731.4666666666668]],[2,[798.4000000000001,879.4666666666668]]]},\"segments\":{\"add\":[1,2],\"remove\":[],\"start_point\":[[1,1],[2,2]],\"end_point\":[[1,2],[2,1]],\"handle_primary\":[[1,[0.0,0.0]],[2,[0.0,0.0]]],\"handle_end\":[[2,[-18.13333333333333,82.66666666666674]],[1,[-1.8666666666665608,-67.46666666666658]]],\"stroke\":[[2,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":2}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11451028343967836482,{\"inputs\":[{\"Node\":{\"node_id\":2422139482859833437,\"output_index\":0}},{\"Node\":{\"node_id\":12531351117929704587,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[314278016428495768,{\"inputs\":[{\"Node\":{\"node_id\":6282972142629473139,\"output_index\":0}},{\"Node\":{\"node_id\":5020096817747898028,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17971411534648521628,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":14862049226133442027,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13014628586360765651,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":2175432926627256613,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14778750092903591172,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3],\"remove\":[],\"delta\":[[3,[146.5679012345679,204.44444444444449]],[1,[155.98219408731924,204.757705978404]],[2,[149.0793650793651,202.5537918871252]]]},\"segments\":{\"add\":[1,2,3],\"remove\":[],\"start_point\":[[2,2],[1,1],[3,3]],\"end_point\":[[2,3],[1,2],[3,1]],\"handle_primary\":[[2,[-2.2695727277346123,-0.643804298125275]],[1,[-2.6135873853791907,-0.8494167367814498]],[3,[0.9278738161427498,1.2447087777524644]]],\"handle_end\":[[2,[-1.156966490299823,-1.5520282186949146]],[1,[1.7022700834823468,0.48287890620272833]],[3,[-0.32172621516085087,3.1233079488176827]]],\"stroke\":[[3,0],[1,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":3}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[18015048324114736039,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29],\"remove\":[],\"delta\":[[28,[730.2716049382716,169.4814814814815]],[14,[630.716049382716,132.3456790123457]],[11,[596.9382716049383,85.13580246913581]],[12,[603.4567901234568,92.8395061728395]],[16,[672.5925925925926,176.5925925925926]],[21,[805.925925925926,230.9135802469136]],[20,[805.1358024691358,260.34567901234567]],[29,[705.1851851851852,106.27160493827162]],[7,[607.2098765432099,55.308641975308646]],[24,[746.8641975308642,175.40740740740742]],[27,[722.1728395061729,147.1604938271605]],[23,[760.8888888888889,175.80246913580248]],[5,[639.8024691358025,44.24691358024691]],[8,[613.3333333333334,44.24691358024691]],[6,[617.283950617284,44.641975308641975]],[17,[757.7283950617285,217.48148148148147]],[15,[664.8888888888889,140.64197530864195]],[2,[683.0617283950618,104.2962962962963]],[25,[737.7777777777778,170.07407407407408]],[13,[616.2962962962963,115.16049382716052]],[26,[752.1975308641976,172.83950617283952]],[19,[797.8271604938273,246.1234567901235]],[10,[597.530864197531,73.08641975308642]],[18,[799.2098765432099,253.23456790123456]],[3,[675.3580246913581,68.74074074074075]],[22,[785.9753086419753,197.13580246913585]],[1,[687.4074074074074,99.1604938271605]],[4,[654.4197530864197,54.91358024691358]],[9,[609.3827160493827,43.65432098765433]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29],\"remove\":[],\"start_point\":[[24,24],[25,25],[11,11],[10,10],[28,28],[15,15],[22,22],[2,2],[12,12],[16,16],[6,6],[8,8],[7,7],[14,14],[20,20],[21,21],[18,18],[23,23],[19,19],[5,5],[1,1],[4,4],[26,26],[3,3],[29,29],[13,13],[17,17],[9,9],[27,27]],\"end_point\":[[4,5],[2,3],[18,19],[19,20],[23,24],[28,29],[13,14],[20,21],[1,2],[14,15],[7,8],[3,4],[12,13],[27,28],[11,12],[15,16],[6,7],[8,9],[24,25],[5,6],[25,26],[26,27],[22,23],[17,18],[16,17],[21,22],[10,11],[29,1],[9,10]],\"handle_primary\":[[5,[-9.48148148148141,0.9876543209876516]],[7,[-0.9876543209876444,-4.740740740740733]],[25,[0.0,0.0]],[16,[29.23456790123464,10.666666666666686]],[11,[3.3580246913580822,1.9753086419753176]],[3,[-15.209876543209816,-1.7777777777777717]],[13,[2.370370370370324,7.308641975308632]],[10,[-6.320987654320902,4.740740740740733]],[4,[-2.3703703703704377,-3.555555555555557]],[9,[-14.024691358024713,15.802469135802482]],[28,[0.0,0.0]],[1,[-2.1728395061728634,1.5802469135802255]],[18,[0.0,-2.172839506172835]],[8,[-1.1851851851851052,-1.1851851851851904]],[21,[-2.5679012345678984,-9.481481481481469]],[29,[-13.234567901234527,-3.3580246913580396]],[20,[0.0,0.0]],[14,[5.728395061728293,2.370370370370381]],[2,[-8.120713305898448,-14.573388203017842]],[24,[-5.3333333333332575,-2.5679012345678984]],[19,[4.543209876543187,5.925925925925952]],[15,[6.913580246913625,9.87654320987656]],[17,[20.594048174910657,8.937919170354007]],[22,[-7.506172839506121,-7.901234567901298]],[6,[-8.09876543209873,4.740740740740733]],[23,[-2.5679012345678984,-0.592592592592581]],[26,[0.0,0.0]],[12,[5.728395061728406,16.98765432098766]],[27,[0.0,0.0]]],\"handle_end\":[[11,[0.0,0.0]],[25,[-4.740740740740762,0.790123456790127]],[27,[-3.7530864197531177,-7.506172839506178]],[1,[0.0,0.0]],[28,[17.580246913580254,58.07407407407402]],[16,[-20.59404817491054,-8.937919170353979]],[7,[0.0,0.0]],[29,[0.0,0.0]],[17,[0.0,0.0]],[4,[9.48148148148141,-0.9876543209876444]],[18,[0.0,0.0]],[21,[7.506172839506121,7.901234567901213]],[10,[-3.3580246913580822,-1.9753086419753176]],[2,[0.0,0.0]],[8,[0.0,0.0]],[13,[-5.728395061728293,-2.370370370370381]],[26,[19.35802469135808,26.864197530864203]],[6,[0.0,0.0]],[20,[2.5679012345678984,9.481481481481438]],[12,[0.0,0.0]],[19,[0.0,0.0]],[5,[0.0,0.0]],[24,[0.0,0.0]],[22,[2.5679012345678984,0.592592592592581]],[15,[0.0,0.0]],[23,[5.3333333333332575,2.5679012345678984]],[3,[2.3703703703704377,3.555555555555557]],[14,[-6.913580246913625,-9.876543209876502]],[9,[0.0,0.0]]],\"stroke\":[[4,0],[26,0],[7,0],[13,0],[3,0],[18,0],[12,0],[6,0],[8,0],[28,0],[29,0],[22,0],[24,0],[9,0],[19,0],[17,0],[5,0],[15,0],[2,0],[1,0],[21,0],[14,0],[10,0],[11,0],[20,0],[16,0],[25,0],[27,0],[23,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":29}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13975451746581400000,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2],\"remove\":[],\"delta\":[[1,[835.7333333333332,786.4000000000001]],[2,[901.6,572.8]]]},\"segments\":{\"add\":[1,2],\"remove\":[],\"start_point\":[[1,1],[2,2]],\"end_point\":[[2,1],[1,2]],\"handle_primary\":[[2,[0.0,0.0]],[1,[0.0,0.0]]],\"handle_end\":[[1,[-22.399999999999977,109.06666666666648]],[2,[47.4666666666667,-108.26666666666664]]],\"stroke\":[[1,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":2}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13035777574951374461,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3],\"remove\":[],\"delta\":[[1,[896.0,440.44444444444446]],[2,[833.7777777777778,573.3333333333333]],[3,[696.8888888888889,697.3333333333333]]]},\"segments\":{\"add\":[1,2],\"remove\":[],\"start_point\":[[2,2],[1,1]],\"end_point\":[[1,2],[2,3]],\"handle_primary\":[[2,[-68.0,92.88888888888891]],[1,[0.0,0.0]]],\"handle_end\":[[1,[68.0,-92.88888888888891]],[2,[0.0,0.0]]],\"stroke\":[[2,0],[1,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3827449344952693766,{\"inputs\":[{\"Node\":{\"node_id\":2440895173483452224,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":5.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11565160497886435388,{\"inputs\":[{\"Node\":{\"node_id\":7893851488963635918,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":6.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10336592647221792772,{\"inputs\":[{\"Node\":{\"node_id\":16051539163551573193,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3535178979443201645,{\"inputs\":[{\"Node\":{\"node_id\":12838133055063962839,\"output_index\":0}},{\"Node\":{\"node_id\":8240895922641772563,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14675232891471617236,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3],\"remove\":[],\"delta\":[[1,[652.8888888888889,822.6666666666666]],[2,[480.7407407407408,1026.6666666666663]],[3,[483.9506172839506,1026.7654320987656]]]},\"segments\":{\"add\":[1,2,3],\"remove\":[],\"start_point\":[[3,3],[2,2],[1,1]],\"end_point\":[[2,3],[3,1],[1,2]],\"handle_primary\":[[2,[0.0,0.0]],[3,[0.0,0.0]],[1,[0.0,0.0]]],\"handle_end\":[[3,[-143.85185185185185,150.07407407407413]],[2,[0.0,0.0]],[1,[26.66666666666669,-58.666666666666515]]],\"stroke\":[[2,0],[1,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":3}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14035980686649077716,{\"inputs\":[{\"Node\":{\"node_id\":6666260895482068061,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[421715625023770179,{\"inputs\":[{\"Node\":{\"node_id\":3670529450440935325,\"output_index\":0}},{\"Node\":{\"node_id\":3150436463719911922,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2044103368441997753,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8],\"remove\":[],\"delta\":[[7,[710.7160493827162,200.09876543209884]],[6,[756.7407407407409,223.40740740740748]],[4,[796.4444444444446,278.12345679012356]],[2,[723.3580246913581,337.97530864197535]],[3,[747.6543209876543,394.6666666666667]],[8,[694.5185185185187,204.8395061728396]],[1,[710.5185185185186,302.2222222222223]],[5,[793.6790123456792,258.7654320987655]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8],\"remove\":[],\"start_point\":[[7,7],[3,3],[4,4],[2,2],[1,1],[6,6],[5,5],[8,8]],\"end_point\":[[6,7],[5,6],[3,4],[4,5],[8,1],[7,8],[2,3],[1,2]],\"handle_primary\":[[7,[0.0,0.0]],[1,[0.0,0.0]],[5,[-5.135802469135797,-10.271604938271594]],[2,[0.0,0.0]],[8,[0.0,0.0]],[4,[0.0,0.0]],[6,[-22.518518518518476,-12.641975308641976]],[3,[0.0,0.0]]],\"handle_end\":[[3,[-12.049382716049422,68.54320987654324]],[8,[7.111111111110972,-82.76543209876547]],[1,[1.3827160493826796,-12.641975308641976]],[4,[5.135802469135797,10.271604938271594]],[7,[8.117474523314513,-0.7895812719984008]],[2,[-10.864197530864203,-35.5555555555556]],[6,[0.0,0.0]],[5,[22.518518518518476,12.641975308641976]]],\"stroke\":[[4,0],[2,0],[6,0],[1,0],[7,0],[3,0],[5,0],[8,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":8}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6124821161363551058,{\"inputs\":[{\"Node\":{\"node_id\":10420981328998103391,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":5.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6749771744300551215,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5],\"remove\":[],\"delta\":[[1,[589.3333333333333,1025.3333333333333]],[2,[660.4444444444443,878.2222222222222]],[5,[585.3333333333333,1025.7777777777778]],[3,[726.6666666666666,765.3333333333333]],[4,[620.8888888888888,943.5555555555557]]]},\"segments\":{\"add\":[1,2,3,4,5],\"remove\":[],\"start_point\":[[2,2],[1,1],[3,3],[4,4],[5,5]],\"end_point\":[[2,3],[5,1],[1,2],[4,5],[3,4]],\"handle_primary\":[[2,[31.11111111111109,-50.66666666666663]],[4,[-29.333333333333258,59.11111111111097]],[1,[0.0,0.0]],[5,[0.0,0.0]],[3,[0.0,0.0]]],\"handle_end\":[[2,[0.0,0.0]],[5,[-0.4444444444443434,0.8888888888889142]],[1,[-31.863450886870623,51.89190573004646]],[3,[29.333333333333258,-59.1111111111112]],[4,[0.0,0.0]]],\"stroke\":[[1,0],[5,0],[3,0],[4,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":5}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2440895173483452224,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3],\"remove\":[],\"delta\":[[2,[30.814814814814767,578.0740740740739]],[3,[33.18518518518515,636.148148148148]],[1,[93.037037037037,526.8148148148147]]]},\"segments\":{\"add\":[1,2],\"remove\":[],\"start_point\":[[1,1],[2,2]],\"end_point\":[[1,2],[2,3]],\"handle_primary\":[[1,[0.0,0.0]],[2,[-9.185185185185162,39.703703703703695]]],\"handle_end\":[[2,[0.0,0.0]],[1,[9.185185185185162,-39.703703703703695]]],\"stroke\":[[2,0],[1,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11356586238302409958,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"delta\":[[6,[792.2962962962963,188.74074074074073]],[2,[783.1111111111111,348.74074074074065]],[5,[811.8518518518518,273.18518518518516]],[1,[811.5555555555555,250.96296296296296]],[3,[732.148148148148,432.2962962962963]],[4,[777.7777777777778,375.7037037037037]]]},\"segments\":{\"add\":[1,2,3,4,5],\"remove\":[],\"start_point\":[[1,1],[3,3],[4,4],[5,5],[2,2]],\"end_point\":[[1,2],[3,4],[4,5],[5,6],[2,3]],\"handle_primary\":[[5,[3.7834358363461433,-27.565032521950258]],[1,[0.0,0.0]],[4,[10.310300340717504,-19.181954122264813]],[3,[0.0,0.0]],[2,[-14.6604291210798,34.80924611318039]]],\"handle_end\":[[2,[26.666666666666515,-35.555555555555486]],[4,[-4.148148148148152,30.22222222222223]],[5,[26.074074074074133,33.18518518518516]],[3,[-12.740740740740875,23.703703703703695]],[1,[14.6604291210798,-34.809246113180336]]],\"stroke\":[[4,0],[5,0],[1,0],[3,0],[2,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13231685386999438557,{\"inputs\":[{\"Node\":{\"node_id\":1659518581611333812,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.8980392,\"green\":0.8,\"blue\":0.6117647,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.8980392,\"green\":0.8,\"blue\":0.6117647,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9371909264427723282,{\"inputs\":[{\"Node\":{\"node_id\":8595304668947966919,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.84705883,\"green\":0.5372549,\"blue\":0.38431373,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.84705883,\"green\":0.5372549,\"blue\":0.38431373,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9782123335421401489,{\"inputs\":[{\"Node\":{\"node_id\":11656581020969095354,\"output_index\":0}},{\"Node\":{\"node_id\":1019037285881657884,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2780251074492832077,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[2,[644.0435005900861,214.94345295604788]],[3,[679.189837009989,239.99177480754585]],[1,[620.0443231093315,208.38630063890184]],[4,[654.5349442975197,224.51689533908117]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[1,1],[2,2],[3,3],[4,4]],\"end_point\":[[4,1],[3,4],[2,3],[1,2]],\"handle_primary\":[[2,[17.048596024579638,8.262011919603992]],[3,[0.0,0.0]],[1,[0.0,0.0]],[4,[-14.688021190407198,-10.884872846462486]]],\"handle_end\":[[2,[-15.868308607493532,-9.835728475719122]],[1,[-9.660678057982182,-4.68171321271447]],[4,[0.1311430463429133,0.13114304634288487]],[3,[13.4932102981968,9.999432631699392]]],\"stroke\":[[3,0],[2,0],[4,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17118107476414252025,{\"inputs\":[{\"Node\":{\"node_id\":6645255982686652881,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[74.2222222222222,480.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[40.0,40.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3627710206997006419,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3],\"remove\":[],\"delta\":[[3,[154.2366898148148,257.4780574845679]],[1,[152.49382716049382,262.71604938271605]],[2,[149.94787379972564,262.84773662551436]]]},\"segments\":{\"add\":[1,2,3],\"remove\":[],\"start_point\":[[3,3],[2,2],[1,1]],\"end_point\":[[2,3],[3,1],[1,2]],\"handle_primary\":[[2,[-0.2633744855966995,-0.4389574759944139]],[1,[0.0,0.0]],[3,[4.31137018907981,-2.5386217986682027]]],\"handle_end\":[[3,[2.8421709430404014e-14,0.0]],[1,[0.4650366425890411,0.7750610709815646]],[2,[-3.3775342902714556,1.988760370600971]]],\"stroke\":[[2,0],[1,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":3}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12838133055063962839,{\"inputs\":[{\"Node\":{\"node_id\":13644138583806412631,\"output_index\":0}},{\"Node\":{\"node_id\":6973438081601736688,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7922156219537051964,{\"inputs\":[{\"Node\":{\"node_id\":13594670583065022897,\"output_index\":0}},{\"Node\":{\"node_id\":17078740291337047697,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.2},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15446793500614592278,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8],\"remove\":[],\"delta\":[[6,[724.6310013717421,85.86008230452676]],[7,[718.3100137174213,77.2565157750343]],[1,[694.3429355281208,73.8326474622771]],[8,[697.5034293552812,77.4320987654321]],[2,[702.244170096022,76.11522633744856]],[5,[718.2222222222222,97.09739368998628]],[4,[725.5967078189301,90.3374485596708]],[3,[722.3484224965707,77.2565157750343]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8],\"remove\":[],\"start_point\":[[8,8],[4,4],[1,1],[2,2],[3,3],[6,6],[5,5],[7,7]],\"end_point\":[[7,8],[4,5],[6,7],[5,6],[2,3],[1,2],[8,1],[3,4]],\"handle_primary\":[[1,[0.0,0.0]],[6,[-0.7023319615914261,-5.091906721536347]],[2,[5.267489711934218,-0.7023319615912129]],[8,[-3.5116598079559935,-1.6680384087791396]],[5,[0.0,0.0]],[3,[3.456189131014753,3.3025807251918877]],[4,[-1.9392278971834005,5.143169640356035]],[7,[-6.236870142765838,-1.74052190030676]]],\"handle_end\":[[3,[2.0192043895747247,-5.355281207133089]],[8,[0.08779149519887142,0.08779149519891405]],[7,[3.5116598079559935,1.6680384087791396]],[5,[0.6823799889585871,4.947254919948108]],[6,[3.7750342935527215,1.0534979423868265]],[4,[0.0,0.0]],[2,[-3.950617283950578,-3.775034293552821]],[1,[-5.267489711934218,0.7023319615912129]]],\"stroke\":[[8,0],[7,0],[4,0],[3,0],[1,0],[6,0],[2,0],[5,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":8}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9684857454501250999,{\"inputs\":[{\"Node\":{\"node_id\":8699675339613677057,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13444661581815146533,{\"inputs\":[{\"Node\":{\"node_id\":16450742929146919960,\"output_index\":0}},{\"Node\":{\"node_id\":10792166025753022402,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14993053984267866751,{\"inputs\":[{\"Node\":{\"node_id\":13907578809542898348,\"output_index\":0}},{\"Node\":{\"node_id\":9863310024364795214,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16195626650123806176,{\"inputs\":[{\"Node\":{\"node_id\":14057307926677215422,\"output_index\":0}},{\"Node\":{\"node_id\":1869448627329502330,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[18279507457571359732,{\"inputs\":[{\"Node\":{\"node_id\":8697043784435445845,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.8980392,\"green\":0.8,\"blue\":0.6117647,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.8980392,\"green\":0.8,\"blue\":0.6117647,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3121275823460307102,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5],\"remove\":[],\"delta\":[[5,[595.5555555555555,791.1111111111111]],[3,[452.88888888888886,1025.3333333333333]],[4,[449.77777777777777,1025.7777777777778]],[2,[612.0,780.0]],[1,[705.7777777777778,698.6666666666666]]]},\"segments\":{\"add\":[1,2,3,4,5],\"remove\":[],\"start_point\":[[1,1],[4,4],[3,3],[5,5],[2,2]],\"end_point\":[[5,1],[4,5],[2,3],[1,2],[3,4]],\"handle_primary\":[[5,[54.93054949731868,-52.22097082256312]],[3,[0.0,0.0]],[1,[0.0,0.0]],[2,[-35.111111111111086,34.66666666666663]],[4,[0.0,0.0]]],\"handle_end\":[[3,[0.0,0.0]],[4,[-99.11111111111104,94.22222222222229]],[2,[59.111111111111086,-144.8888888888889]],[1,[35.111111111111086,-34.66666666666663]],[5,[-1.1368683772161605e-13,-0.5925925925926094]]],\"stroke\":[[2,0],[4,0],[5,0],[1,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":5}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13269760558336088742,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5],\"remove\":[],\"delta\":[[2,[447.1111111111111,332.44444444444446]],[1,[265.3333333333333,312.0]],[3,[595.1111111111111,439.55555555555554]],[5,[756.4444444444443,438.22222222222223]],[4,[698.2222222222222,483.1111111111111]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[2,2],[1,1],[4,4],[3,3]],\"end_point\":[[4,5],[3,4],[2,3],[1,2]],\"handle_primary\":[[1,[0.0,0.0]],[2,[83.95959630801838,35.04735442215451]],[4,[22.46059594926794,-9.800987323316916]],[3,[20.8888888888888,20.444444444444457]]],\"handle_end\":[[4,[-22.222222222222285,23.111111111111143]],[1,[-96.88888888888886,-40.44444444444446]],[2,[-20.8888888888888,-20.444444444444457]],[3,[-48.888888888888914,21.33333333333331]]],\"stroke\":[[3,0],[1,0],[4,0],[2,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11590691579869262546,{\"inputs\":[{\"Node\":{\"node_id\":11553850607251055696,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.8980392,\"green\":0.8,\"blue\":0.6117647,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.8980392,\"green\":0.8,\"blue\":0.6117647,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13481022631108980683,{\"inputs\":[{\"Node\":{\"node_id\":9182448229950585507,\"output_index\":0}},{\"Node\":{\"node_id\":12224498203743157414,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14797986717815207528,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5],\"remove\":[],\"delta\":[[5,[868.0,380.44444444444446]],[4,[826.6666666666665,552.4444444444443]],[1,[80.0,557.0]],[3,[665.7777777777778,658.6666666666667]],[2,[425.0,650.0]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[3,3],[1,1],[4,4],[2,2]],\"end_point\":[[1,2],[4,5],[3,4],[2,3]],\"handle_primary\":[[4,[59.11111111111131,-70.66666666666652]],[2,[105.59298295237464,48.735222901095995]],[3,[0.0,0.0]],[1,[134.0,-75.0]]],\"handle_end\":[[1,[-104.0,-48.0]],[2,[0.0,0.0]],[3,[-59.11111111111131,70.66666666666652]],[4,[0.0,0.0]]],\"stroke\":[[3,0],[4,0],[1,0],[2,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5861306074868809692,{\"inputs\":[{\"Node\":{\"node_id\":10190227675276560561,\"output_index\":0}},{\"Node\":{\"node_id\":3636653585682494814,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13907578809542898348,{\"inputs\":[{\"Node\":{\"node_id\":12313564802550122052,\"output_index\":0}},{\"Node\":{\"node_id\":15827578515555598997,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[581013017684525986,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2],\"remove\":[],\"delta\":[[2,[852.0,631.1111111111111]],[1,[803.5555555555554,878.6666666666665]]]},\"segments\":{\"add\":[1,2],\"remove\":[],\"start_point\":[[2,2],[1,1]],\"end_point\":[[1,2],[2,1]],\"handle_primary\":[[1,[0.0,0.0]],[2,[-41.77777777777783,81.77777777777771]]],\"handle_end\":[[2,[0.0,-0.4444444444443434]],[1,[-37.77777777777783,80.88888888888903]]],\"stroke\":[[1,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":2}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3670529450440935325,{\"inputs\":[{\"Node\":{\"node_id\":1384427686127078856,\"output_index\":0}},{\"Node\":{\"node_id\":8543051864256131356,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12030171742672119253,{\"inputs\":[{\"Node\":{\"node_id\":12801133692316734622,\"output_index\":0}},{\"Node\":{\"node_id\":17078740291337047697,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.2},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7450965328305122110,{\"inputs\":[{\"Node\":{\"node_id\":2659768650911099730,\"output_index\":0}},{\"Node\":{\"node_id\":17078740291337047697,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.4},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2.5},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1806828617441445250,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[2,[304.6666666666667,765.3333333333333]],[1,[395.3333333333333,758.0]],[4,[32.0,1025.3333333333333]],[3,[147.33333333333334,814.0000000000001]]]},\"segments\":{\"add\":[1,2,3],\"remove\":[],\"start_point\":[[2,2],[1,1],[3,3]],\"end_point\":[[3,4],[1,2],[2,3]],\"handle_primary\":[[3,[-50.13723402627032,36.301698579412914]],[2,[-32.666666666666686,0.6666666666667425]],[1,[0.0,0.0]]],\"handle_end\":[[1,[32.666666666666686,-0.6666666666667425]],[2,[50.137234026270335,-36.301698579412914]],[3,[-0.6666666666666892,-122.66666666666686]]],\"stroke\":[[2,0],[3,0],[1,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13475705179546695973,{\"inputs\":[{\"Node\":{\"node_id\":11451028343967836482,\"output_index\":0}},{\"Node\":{\"node_id\":501401493219507773,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2594533001540454577,{\"inputs\":[{\"Node\":{\"node_id\":15518174914032911052,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":8.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9276497172451351253,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[4,[605.9173906416706,103.71101966163694]],[3,[605.1760402377686,111.78783721993597]],[1,[606.8928516994359,102.3453741807651]],[2,[607.1269623532997,114.75323883554336]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[2,2],[3,3],[4,4],[1,1]],\"end_point\":[[1,2],[4,1],[3,4],[2,3]],\"handle_primary\":[[4,[0.27360881696279193,-0.7182231445274425]],[1,[0.0,0.0]],[2,[0.0,0.0]],[3,[-0.585276634659408,-2.419143423258646]]],\"handle_end\":[[2,[0.585276634659408,2.4191434232586317]],[3,[-0.6242950769699291,1.6387745770462772]],[4,[0.0,0.03901844231063478]],[1,[-1.3656454808717626,-5.306508154244753]]],\"stroke\":[[4,0],[2,0],[1,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7821977654068146599,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3],\"remove\":[],\"delta\":[[3,[825.9524005971,863.6469292802573]],[1,[917.0,471.6]],[2,[826.5068586621596,856.9308484975209]]]},\"segments\":{\"add\":[1,2,3],\"remove\":[],\"start_point\":[[3,3],[2,2],[1,1]],\"end_point\":[[2,3],[3,1],[1,2]],\"handle_primary\":[[1,[0.0,0.0]],[3,[0.0,0.0]],[2,[0.0,0.0]]],\"handle_end\":[[1,[94.89314133784036,-231.73084849752092]],[3,[-0.39999999999997726,191.19999999999985]],[2,[0.0,0.0]]],\"stroke\":[[3,0],[1,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":3}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15126865253122550765,{\"inputs\":[{\"Node\":{\"node_id\":13014628586360765651,\"output_index\":0}},{\"Node\":{\"node_id\":4307303572241320716,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2682920349304670808,{\"inputs\":[{\"Node\":{\"node_id\":10928540355449103287,\"output_index\":0}},{\"Node\":{\"node_id\":16324258033206362312,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13368990606109678244,{\"inputs\":[{\"Node\":{\"node_id\":421715625023770179,\"output_index\":0}},{\"Node\":{\"node_id\":15961046538654083626,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16290933138334939444,{\"inputs\":[{\"Node\":{\"node_id\":17426609415699324395,\"output_index\":0}},{\"Node\":{\"node_id\":14888395629683671889,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12496143061817048445,{\"inputs\":[{\"Node\":{\"node_id\":7525593029671097583,\"output_index\":0}},{\"Node\":{\"node_id\":7654665057468818389,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12880230498984021417,{\"inputs\":[{\"Node\":{\"node_id\":15949658764632267703,\"output_index\":0}},{\"Node\":{\"node_id\":17078740291337047697,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.2},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14883504161508594099,{\"inputs\":[{\"Node\":{\"node_id\":13368990606109678244,\"output_index\":0}},{\"Node\":{\"node_id\":11025165626998987360,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7262199696924786895,{\"inputs\":[{\"Node\":{\"node_id\":14029368390543839187,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":5.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[952330505278607301,{\"inputs\":[{\"Node\":{\"node_id\":15038739378867834454,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.8980392,\"green\":0.8,\"blue\":0.6117647,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.8980392,\"green\":0.8,\"blue\":0.6117647,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5302437193964714993,{\"inputs\":[{\"Node\":{\"node_id\":1235106489581249820,\"output_index\":0}},{\"Node\":{\"node_id\":17078740291337047697,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.2},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10792166025753022402,{\"inputs\":[{\"Node\":{\"node_id\":2780251074492832077,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16649851742084147477,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2],\"remove\":[],\"delta\":[[1,[870.6666666666665,383.55555555555554]],[2,[838.6666666666666,564.0]]]},\"segments\":{\"add\":[1],\"remove\":[],\"start_point\":[[1,1]],\"end_point\":[[1,2]],\"handle_primary\":[[1,[16.000000000000227,37.33333333333337]]],\"handle_end\":[[1,[48.44444444444446,-91.11111111111109]]],\"stroke\":[[1,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1272070255512697108,{\"inputs\":[{\"Node\":{\"node_id\":14012648643507848353,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17147975601187022720,{\"inputs\":[{\"Node\":{\"node_id\":18015048324114736039,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.8980392,\"green\":0.8,\"blue\":0.6117647,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.8980392,\"green\":0.8,\"blue\":0.6117647,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15518174914032911052,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"delta\":[[3,[639.1111111111112,614.6666666666666]],[2,[814.2222222222223,498.66666666666663]],[4,[429.7777777777778,551.5555555555555]],[6,[129.77777777777777,406.2222222222222]],[5,[262.6666666666667,423.5555555555556]],[1,[851.5555555555557,370.66666666666663]]]},\"segments\":{\"add\":[1,2,3,4,5],\"remove\":[],\"start_point\":[[2,2],[4,4],[5,5],[1,1],[3,3]],\"end_point\":[[1,2],[4,5],[2,3],[3,4],[5,6]],\"handle_primary\":[[3,[-83.11111111111109,3.111111111111086]],[5,[-58.2222222222222,-21.77777777777783]],[1,[0.0,0.0]],[2,[-43.55555555555554,54.66666666666663]],[4,[-74.22222222222217,-57.77777777777783]]],\"handle_end\":[[1,[43.55555555555554,-54.66666666666663]],[2,[83.11111111111109,-3.111111111111086]],[3,[74.22222222222217,57.77777777777783]],[4,[66.87431172777582,25.01405553176352]],[5,[57.333333333333314,-5.777777777777828]]],\"stroke\":[[3,0],[2,0],[4,0],[1,0],[5,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17078740291337047697,{\"inputs\":[{\"Node\":{\"node_id\":3406722917122601552,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.84765625,\"green\":0.5384252,\"blue\":0.3874054,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.84765625,\"green\":0.5384252,\"blue\":0.3874054,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10264089084180279094,{\"inputs\":[{\"Node\":{\"node_id\":5213978458941436169,\"output_index\":0}},{\"Node\":{\"node_id\":4332145463108161926,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":60.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":-100.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4859656512650360562,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3],\"remove\":[],\"delta\":[[1,[163.33333333333343,1025.3333333333333]],[3,[416.66666666666663,803.3333333333333]],[2,[249.3333333333334,898.0]]]},\"segments\":{\"add\":[1,2],\"remove\":[],\"start_point\":[[2,2],[1,1]],\"end_point\":[[2,3],[1,2]],\"handle_primary\":[[1,[0.0,0.0]],[2,[40.66666666666666,-40.666666666666515]]],\"handle_end\":[[2,[-77.99999999999994,22.666666666666742]],[1,[-45.09988913511887,45.099889135118815]]],\"stroke\":[[1,0],[2,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13594670583065022897,{\"inputs\":[{\"Node\":{\"node_id\":13302269488061286120,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":5.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4265165189651403984,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":10264089084180279094,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12428327489525325219,{\"inputs\":[{\"Node\":{\"node_id\":14209241002058525241,\"output_index\":0}},{\"Node\":{\"node_id\":1984475088429379731,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16767482995096345179,{\"inputs\":[{\"Node\":{\"node_id\":6532401937876437300,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.7529412,\"green\":0.15686275,\"blue\":0.13333334,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.7529412,\"green\":0.15686275,\"blue\":0.13333334,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5278509881589546420,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3],\"remove\":[],\"delta\":[[2,[824.4444444444445,850.2222222222223]],[1,[899.1111111111111,600.0000000000001]],[3,[823.7037037037037,861.8666666666667]]]},\"segments\":{\"add\":[1,2,3],\"remove\":[],\"start_point\":[[3,3],[1,1],[2,2]],\"end_point\":[[1,2],[3,1],[2,3]],\"handle_primary\":[[3,[0.0,0.0]],[2,[0.0,0.0]],[1,[0.0,0.0]]],\"handle_end\":[[1,[39.55555555555554,-105.8222222222222]],[3,[-26.31111111111113,120.79999999999984]],[2,[0.0,0.0]]],\"stroke\":[[2,0],[3,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":3}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7659717355245331967,{\"inputs\":[{\"Node\":{\"node_id\":18422317423856403288,\"output_index\":0}},{\"Node\":{\"node_id\":4479074488343511985,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8697043784435445845,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35],\"remove\":[],\"delta\":[[35,[680.888888888889,281.48148148148147]],[26,[227.55555555555557,246.51851851851853]],[7,[452.0,206.22222222222223]],[13,[173.03703703703707,226.962962962963]],[29,[369.3827160493828,240.19753086419755]],[12,[199.11111111111111,214.51851851851853]],[30,[425.4814814814815,246.71604938271605]],[28,[278.9135802469136,223.80246913580248]],[24,[188.90085842299663,268.4351595864769]],[23,[185.37661941777165,263.56957780826093]],[9,[326.22222222222223,208.0]],[25,[201.87654320987656,262.12345679012344]],[3,[679.9012345679013,198.716049382716]],[5,[616.4444444444443,170.22222222222223]],[19,[205.23456790123456,227.55555555555557]],[14,[155.06172839506175,249.08641975308643]],[34,[647.5061728395062,240.79012345679013]],[27,[251.06172839506175,233.283950617284]],[15,[155.85185185185185,253.03703703703707]],[8,[413.77777777777777,199.55555555555551]],[2,[701.7777777777777,252.0]],[17,[183.50617283950615,231.70370370370372]],[6,[553.7777777777777,195.11111111111111]],[20,[217.87654320987656,231.90123456790127]],[1,[699.5555555555554,303.55555555555554]],[21,[216.8888888888889,238.22222222222223]],[16,[172.44444444444446,239.40740740740745]],[32,[526.0246913580247,240.79012345679013]],[22,[195.95061728395063,253.8271604938272]],[11,[208.44444444444443,212.0]],[31,[463.01234567901247,246.71604938271605]],[10,[271.1111111111111,207.55555555555551]],[18,[195.1604938271605,228.54320987654324]],[33,[602.2716049382716,229.5308641975309]],[4,[647.5555555555554,171.55555555555557]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35],\"remove\":[],\"start_point\":[[16,16],[30,30],[15,15],[12,12],[7,7],[10,10],[28,28],[1,1],[26,26],[25,25],[17,17],[5,5],[22,22],[2,2],[6,6],[23,23],[33,33],[34,34],[21,21],[3,3],[29,29],[24,24],[11,11],[9,9],[13,13],[8,8],[31,31],[18,18],[20,20],[35,35],[27,27],[14,14],[4,4],[19,19],[32,32]],\"end_point\":[[28,29],[23,24],[11,12],[22,23],[12,13],[9,10],[10,11],[26,27],[4,5],[17,18],[5,6],[1,2],[14,15],[34,35],[35,1],[2,3],[30,31],[7,8],[32,33],[6,7],[3,4],[25,26],[8,9],[15,16],[31,32],[29,30],[33,34],[18,19],[21,22],[13,14],[16,17],[20,21],[24,25],[19,20],[27,28]],\"handle_primary\":[[5,[-12.0,3.555555555555543]],[16,[5.530864197530889,-7.111111111111143]],[15,[3.7530864197531177,-1.580246913580254]],[23,[-0.9364426154549506,2.4191434232586175]],[26,[8.493827160493822,-2.765432098765416]],[17,[2.7654320987654444,-0.790123456790127]],[8,[-25.33333333333331,1.7777777777778567]],[35,[10.469135802469168,11.851851851851848]],[14,[-1.61975308641982,1.935802469135723]],[21,[-3.753086419753061,3.5555555555555713]],[1,[0.0,0.0]],[30,[11.061728395061778,0.7901234567900985]],[3,[-17.576025737442137,-14.306067460708704]],[27,[6.518518518518505,-6.518518518518562]],[9,[-30.22222222222223,1.3333333333333712]],[10,[-12.888888888888856,1.7777777777778567]],[33,[24.098765432098844,-2.5679012345678984]],[4,[-10.222222222222172,-3.5555555555555713]],[24,[3.3430068414448044,-0.5735531240474074]],[29,[29.4320987654321,3.358024691358054]],[13,[-9.28395061728395,7.703703703703695]],[18,[4.148148148148124,-1.3827160493827364]],[2,[-2.913580246913398,-22.66666666666669]],[11,[0.0,0.0]],[34,[10.074074074074131,11.85185185185182]],[7,[-8.444444444444457,-2.2222222222222285]],[32,[23.90123456790127,-2.765432098765416]],[22,[-8.16460905349794,4.4115226337448235]],[28,[14.81481481481478,1.9753086419753176]],[20,[2.3703703703704093,1.185185185185162]],[12,[-5.53086419753086,3.3580246913580254]],[19,[4.74074074074079,0.9876543209876728]],[6,[-23.11111111111109,9.777777777777771]],[25,[4.938271604938279,-3.160493827160451]],[31,[19.753086419753004,-4.543209876543159]]],\"handle_end\":[[33,[-10.074074074074131,-11.851851851851848]],[4,[12.0,-3.555555555555543]],[22,[0.752878950104872,-1.9449372877709263]],[1,[2.822923929132685,21.961391245287817]],[10,[20.88888888888889,-1.4814814814814952]],[3,[10.222222222222172,3.5555555555555713]],[13,[1.4782632300064904,-1.7667048358613044]],[24,[-4.938271604938279,3.160493827160451]],[8,[30.22222222222223,-1.3333333333333712]],[9,[12.888888888888856,-1.777777777777743]],[18,[-4.740740740740705,-0.9876543209876728]],[32,[-24.098765432098844,2.5679012345678984]],[5,[23.11111111111109,-9.777777777777745]],[16,[-2.765432098765416,0.790123456790127]],[30,[-19.753086419753004,4.543209876543187]],[2,[8.493827160493879,6.913580246913597]],[27,[-14.81481481481478,-1.9753086419753176]],[28,[-29.4320987654321,-3.358024691357997]],[31,[-23.90123456790127,2.7654320987653875]],[7,[25.33333333333331,-1.777777777777743]],[6,[8.444444444444457,2.222222222222257]],[34,[-10.469135802469168,-11.851851851851848]],[17,[-4.148148148148152,1.3827160493827648]],[35,[0.0,5.684341886080804e-14]],[14,[-3.7530864197531177,1.580246913580254]],[12,[9.283950617283978,-7.703703703703695]],[19,[-2.3703703703704093,-1.185185185185162]],[15,[-5.530864197530889,7.111111111111086]],[29,[-11.061728395061778,-0.790123456790127]],[23,[-1.24660051630255,0.213876804468498]],[25,[-8.493827160493822,2.765432098765416]],[20,[3.753086419753089,-3.5555555555556]],[21,[9.952891875905069,-5.377772223271279]],[26,[-6.518518518518505,6.518518518518505]],[11,[5.530864197530917,-3.3580246913580254]]],\"stroke\":[[7,0],[8,0],[21,0],[15,0],[2,0],[4,0],[11,0],[13,0],[27,0],[17,0],[6,0],[26,0],[35,0],[31,0],[19,0],[3,0],[25,0],[1,0],[10,0],[12,0],[5,0],[29,0],[23,0],[18,0],[34,0],[32,0],[14,0],[28,0],[22,0],[30,0],[33,0],[20,0],[16,0],[9,0],[24,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":35}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4236845268521674740,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3],\"remove\":[],\"delta\":[[3,[393.1851851851852,1025.4814814814813]],[2,[568.8888888888889,785.4814814814813]],[1,[397.6296296296296,1025.185185185185]]]},\"segments\":{\"add\":[1,2,3],\"remove\":[],\"start_point\":[[2,2],[1,1],[3,3]],\"end_point\":[[2,3],[3,1],[1,2]],\"handle_primary\":[[2,[0.0,0.0]],[3,[0.0,0.0]],[1,[0.0,0.0]]],\"handle_end\":[[3,null],[2,[33.777777777777885,-150.22222222222194]],[1,[-153.1851851851851,112.88888888888891]]],\"stroke\":[[3,0],[1,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":3}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1984475088429379731,{\"inputs\":[{\"Node\":{\"node_id\":1621196991038859321,\"output_index\":0}},{\"Node\":{\"node_id\":17078740291337047697,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.2},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9271343782272072828,{\"inputs\":[{\"Node\":{\"node_id\":4078100635676202528,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.8980392,\"green\":0.8,\"blue\":0.6117647,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.8980392,\"green\":0.8,\"blue\":0.6117647,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[514796034658094296,{\"inputs\":[{\"Node\":{\"node_id\":13352561089252322209,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8698602280607307123,{\"inputs\":[{\"Node\":{\"node_id\":14098374807212007572,\"output_index\":0}},{\"Node\":{\"node_id\":14285767317419627814,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10770443343193024138,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7],\"remove\":[],\"delta\":[[5,[671.3086419753088,28.641975308641975]],[6,[656.4609053497943,30.375857338820303]],[2,[621.0370370370372,23.01234567901235]],[3,[634.172839506173,30.814814814814817]],[1,[616.0987654320988,23.01234567901235]],[4,[656.6913580246915,28.049382716049383]],[7,[634.1618655692731,33.7997256515775]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7],\"remove\":[],\"start_point\":[[4,4],[5,5],[1,1],[2,2],[3,3],[7,7],[6,6]],\"end_point\":[[2,3],[6,7],[4,5],[1,2],[3,4],[5,6],[7,1]],\"handle_primary\":[[3,[6.024691358024711,0.4938271604938329]],[5,[0.0,0.0]],[2,[1.1851851851851052,1.5802469135802468]],[4,[4.345679012345727,-0.39506172839505993]],[7,[-6.408779149519887,-0.8340192043895769]],[6,[-10.31550068587103,2.4142661179698237]],[1,[0.0,0.0]]],\"handle_end\":[[5,[10.31550068587103,-2.4142661179698237]],[3,[-4.345679012345727,0.39506172839506704]],[6,[6.408779149519887,0.8340192043895769]],[2,[-6.024691358024711,-0.49382716049382935]],[1,[-1.1851851851851052,-1.5802469135802468]],[7,[4.455418381344316,5.4759945130315515]],[4,[-0.39506172839503506,-0.5925925925925917]]],\"stroke\":[[4,0],[1,0],[5,0],[2,0],[3,0],[6,0],[7,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":7}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1019037285881657884,{\"inputs\":[{\"Node\":{\"node_id\":11481949351661484921,\"output_index\":0}},{\"Node\":{\"node_id\":17078740291337047697,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.2},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8410534738018320047,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2],\"remove\":[],\"delta\":[[2,[762.5185185185184,667.5061728395063]],[1,[834.6666666666665,551.8024691358028]]]},\"segments\":{\"add\":[1,2],\"remove\":[],\"start_point\":[[1,1],[2,2]],\"end_point\":[[1,2],[2,1]],\"handle_primary\":[[1,[0.0,0.0]],[2,[44.88888888888857,-64.49382716049388]]],\"handle_end\":[[1,[47.85185185185162,-63.160493827160394]],[2,null]],\"stroke\":[[2,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":2}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2397243911096708995,{\"inputs\":[{\"Node\":{\"node_id\":10587073897090054035,\"output_index\":0}},{\"Node\":{\"node_id\":7505360855062237520,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[541002100261582638,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[1,[456.88888888888886,483.55555555555554]],[3,[609.7777777777777,559.5555555555555]],[2,[536.8888888888889,544.4444444444445]],[4,[648.4444444444443,543.5555555555555]]]},\"segments\":{\"add\":[1,2,3],\"remove\":[],\"start_point\":[[3,3],[1,1],[2,2]],\"end_point\":[[2,3],[3,4],[1,2]],\"handle_primary\":[[2,[25.777777777777715,11.555555555555545]],[1,[0.0,0.0]],[3,[20.0,-7.555555555555543]]],\"handle_end\":[[2,[-20.0,7.555555555555543]],[1,[-25.777777777777715,-11.555555555555545]],[3,[0.0,0.0]]],\"stroke\":[[3,0],[1,0],[2,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5009664118231399060,{\"inputs\":[{\"Node\":{\"node_id\":3226457726231232839,\"output_index\":0}},{\"Node\":{\"node_id\":17207895962122263432,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1378578509112405,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5],\"remove\":[],\"delta\":[[5,[593.3924706599604,60.59564090839812]],[2,[599.5281207133061,50.3923182441701]],[4,[594.6410608139003,75.57872275567746]],[3,[590.4855967078191,66.80932784636488]],[1,[602.6886145404666,50.91906721536352]]]},\"segments\":{\"add\":[1,2,3,4,5],\"remove\":[],\"start_point\":[[3,3],[4,4],[5,5],[2,2],[1,1]],\"end_point\":[[5,1],[3,4],[1,2],[4,5],[2,3]],\"handle_primary\":[[3,[1.9314128943758533,7.3257125438195345]],[1,[0.0,0.0]],[2,[-1.843621399176982,0.5267489711934203]],[5,[1.248590153939972,-3.9798811156835896]],[4,[0.0,0.0]]],\"handle_end\":[[3,[0.0,0.0]],[4,[-1.704168058538812,5.432035686592322]],[5,[-4.379820149367788,0.9754610577655498]],[1,[1.843621399176982,-0.5267489711934203]],[2,[-2.7532629181224593,-10.4429315732828]]],\"stroke\":[[3,0],[4,0],[5,0],[2,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":5}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[501401493219507773,{\"inputs\":[{\"Node\":{\"node_id\":9425359632144678256,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.7529412,\"green\":0.15686275,\"blue\":0.13333334,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.7529412,\"green\":0.15686275,\"blue\":0.13333334,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1235106489581249820,{\"inputs\":[{\"Node\":{\"node_id\":3970516859959908758,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":5.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2394762731964337494,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Artboard\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":4422453582814483232,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1024.0,1024.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":1}},{\"Value\":{\"tagged_value\":{\"String\":\"Artboard\"},\"exposed\":false}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":5}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::artboard::CreateArtboardNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Import\":{\"import_type\":{\"Fn\":[{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::artboard::Artboard>\",\"alias\":null}}]},\"import_index\":0}},{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4341772758799935306,{\"inputs\":[{\"Node\":{\"node_id\":1785173043494067496,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":5.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3226457726231232839,{\"inputs\":[{\"Node\":{\"node_id\":4493274523708782092,\"output_index\":0}},{\"Node\":{\"node_id\":7922156219537051964,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6645255982686652881,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[1,[0.5,0.0]],[2,[1.0,0.5]],[4,[0.0,0.5]],[3,[0.5,1.0]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[3,3],[4,4],[1,1],[2,2]],\"end_point\":[[2,3],[1,2],[3,4],[4,1]],\"handle_primary\":[[2,[0.0,0.27589238888950707]],[1,[0.27589238888950707,0.0]],[3,[-0.275892388889507,0.0]],[4,[0.0,-0.275892388889507]]],\"handle_end\":[[4,[-0.275892388889507,0.0]],[2,[0.27589238888950707,0.0]],[3,[0.0,0.27589238888950707]],[1,[0.0,-0.275892388889507]]],\"stroke\":[[4,0],[3,0],[2,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1162381870526064378,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":9271343782272072828,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7447657690776686262,{\"inputs\":[{\"Node\":{\"node_id\":3649809135741361946,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":5.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9698363115186534174,{\"inputs\":[{\"Node\":{\"node_id\":1661691009086487874,\"output_index\":0}},{\"Node\":{\"node_id\":17078740291337047697,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.2},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5471152581000334146,{\"inputs\":[{\"Node\":{\"node_id\":12761901161949743155,\"output_index\":0}},{\"Node\":{\"node_id\":952330505278607301,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15827578515555598997,{\"inputs\":[{\"Node\":{\"node_id\":15656854169166220905,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14539627480594383748,{\"inputs\":[{\"Node\":{\"node_id\":581013017684525986,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2282726379014798660,{\"inputs\":[{\"Node\":{\"node_id\":454416440369338250,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":5.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11417901103965737900,{\"inputs\":[{\"Node\":{\"node_id\":13269760558336088742,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"PointSpacingType\":\"Separation\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":5.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":100},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":false},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SubpathSegmentLengthsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MemoNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::misc::PointSpacingType\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"u32\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":5}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"bool\",\"alias\":null}},\"import_index\":6}},{\"Node\":{\"node_id\":0,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::SamplePolylineNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15637103575662751567,{\"inputs\":[{\"Node\":{\"node_id\":314278016428495768,\"output_index\":0}},{\"Node\":{\"node_id\":4350324834849900949,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10375238420217738812,{\"inputs\":[{\"Node\":{\"node_id\":2282726379014798660,\"output_index\":0}},{\"Node\":{\"node_id\":17078740291337047697,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":1.2},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"U32\":0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"core_types::vector::CopyToPointsNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[4859656512650360562,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[1869448627329502330,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[5002654561220917457,{\"persistent_metadata\":{\"display_name\":\"Scatter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"range_max\":100.0,\"blank_assist\":true,\"range_min\":1.0,\"mode\":\"Range\",\"min\":0.01,\"is_integer\":false},\"widget_override\":\"number\",\"input_name\":\"Separation Disk Diameter\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"is_integer\":true,\"min\":0.0,\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Seed\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Scatter Points\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Poisson-Disk Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[-312.5,-48.5],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,38.0,473.0],\"node_graph_width\":0.0},\"selection_undo_history\":[[]],\"selection_redo_history\":[]}}}}],[7447657690776686262,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"min\":0.0,\"mode\":\"Increment\",\"is_integer\":false,\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"is_integer\":true,\"min\":2.0,\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"unit\":\" px\",\"blank_assist\":true,\"is_integer\":false,\"min\":0.0},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"min\":0.0,\"blank_assist\":true,\"is_integer\":false,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14079496619264986678,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7821977654068146599,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15518174914032911052,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[1713644030979611623,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4784708315242877950,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15395954548128560685,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13014916927589286309,{\"persistent_metadata\":{\"display_name\":\"Scatter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"range_max\":100.0,\"mode\":\"Range\",\"min\":0.01,\"range_min\":1.0,\"blank_assist\":true,\"is_integer\":false},\"widget_override\":\"number\",\"input_name\":\"Separation Disk Diameter\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":true,\"blank_assist\":true,\"min\":0.0,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Seed\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Scatter Points\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Poisson-Disk Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[-312.5,-48.5],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,38.0,473.0],\"node_graph_width\":0.0},\"selection_undo_history\":[[]],\"selection_redo_history\":[]}}}}],[16434255153991868080,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[16805628435335819723,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[10514847656270897393,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9863310024364795214,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[13045580349734858212,{\"persistent_metadata\":{\"display_name\":\"Scatter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"min\":0.01,\"mode\":\"Range\",\"is_integer\":false,\"range_max\":100.0,\"range_min\":1.0},\"widget_override\":\"number\",\"input_name\":\"Separation Disk Diameter\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"blank_assist\":true,\"is_integer\":true,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Seed\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Scatter Points\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Poisson-Disk Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[-312.5,-48.5],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,38.0,473.0],\"node_graph_width\":0.0},\"selection_undo_history\":[[]],\"selection_redo_history\":[]}}}}],[1659518581611333812,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[6124821161363551058,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"unit\":\" px\",\"mode\":\"Increment\",\"is_integer\":false,\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"min\":2.0,\"mode\":\"Increment\",\"is_integer\":true},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"unit\":\" px\",\"is_integer\":false,\"blank_assist\":true,\"min\":0.0},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":false,\"min\":0.0,\"mode\":\"Increment\",\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11021243031011826737,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[9271343782272072828,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[11565160497886435388,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"unit\":\" px\",\"mode\":\"Increment\",\"is_integer\":false,\"min\":0.0},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":true,\"blank_assist\":true,\"min\":2.0,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"min\":0.0,\"is_integer\":false,\"mode\":\"Increment\",\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"mode\":\"Increment\",\"is_integer\":false,\"unit\":\" px\",\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3121275823460307102,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12761901161949743155,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4493274523708782092,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15038739378867834454,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11417901103965737900,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"mode\":\"Increment\",\"unit\":\" px\",\"min\":0.0,\"is_integer\":false},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":true,\"min\":2.0,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"min\":0.0,\"is_integer\":false,\"mode\":\"Increment\",\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"mode\":\"Increment\",\"unit\":\" px\",\"min\":0.0,\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13163272246010991228,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14993053984267866751,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11630078441485655672,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10264089084180279094,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[10860592954464951000,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[14537754528543289381,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[17945736750161448391,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[5140869461760168364,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[5009664118231399060,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9425359632144678256,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12838133055063962839,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[8934999452649011837,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-17,130]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[454416440369338250,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[18279507457571359732,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[14012583111791538162,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7893851488963635918,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16756940771483104467,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[12313564802550122052,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[8766106989344197438,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10420981328998103391,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13594670583065022897,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"min\":0.0,\"is_integer\":false,\"unit\":\" px\",\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"mode\":\"Increment\",\"is_integer\":true,\"min\":2.0},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"mode\":\"Increment\",\"unit\":\" px\",\"blank_assist\":true,\"min\":0.0},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"unit\":\" px\",\"is_integer\":false,\"min\":0.0,\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15460109068588328521,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[12880230498984021417,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[16649851742084147477,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13035777574951374461,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14888395629683671889,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[1621196991038859321,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":false,\"unit\":\" px\",\"mode\":\"Increment\",\"min\":0.0},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":true,\"min\":2.0,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"blank_assist\":true,\"mode\":\"Increment\",\"is_integer\":false,\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"blank_assist\":true,\"mode\":\"Increment\",\"min\":0.0,\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10421722418968896452,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4832236468224231783,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12030171742672119253,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[2659768650911099730,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"mode\":\"Increment\",\"is_integer\":false,\"unit\":\" px\",\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":true,\"mode\":\"Increment\",\"min\":2.0,\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"min\":0.0,\"blank_assist\":true,\"mode\":\"Increment\",\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"min\":0.0,\"unit\":\" px\",\"is_integer\":false,\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14035980686649077716,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[581013017684525986,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[1157261387411722141,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[11899713172487274471,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[11356586238302409958,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[5278509881589546420,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16732130236852371275,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"blank_assist\":true,\"is_integer\":false,\"min\":0.0,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"blank_assist\":true,\"is_integer\":true,\"min\":2.0},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"blank_assist\":true,\"mode\":\"Increment\",\"is_integer\":false,\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"unit\":\" px\",\"is_integer\":false,\"mode\":\"Increment\",\"min\":0.0},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9371909264427723282,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[2394762731964337494,{\"persistent_metadata\":{\"display_name\":\"Artboard\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Artboards\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Contents\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"Y\",\"x\":\"X\",\"is_integer\":true,\"unit\":\" px\"},\"widget_override\":\"vec2\",\"input_name\":\"Location\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"x\":\"W\",\"y\":\"H\",\"is_integer\":true},\"widget_override\":\"vec2\",\"input_name\":\"Dimensions\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"artboard_background\",\"input_name\":\"Background\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Clip\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-3,1]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Artboard\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Create Artboard\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-4]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13481022631108980683,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[17699121037850769131,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3970516859959908758,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2282726379014798660,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":false,\"min\":0.0,\"unit\":\" px\",\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":2.0,\"blank_assist\":true,\"is_integer\":true,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"blank_assist\":true,\"min\":0.0,\"is_integer\":false,\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"mode\":\"Increment\",\"is_integer\":false,\"unit\":\" px\",\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[1689789805659535712,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11464423670065789907,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[12876462860151722087,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[8375495949882478840,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15949658764632267703,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"mode\":\"Increment\",\"unit\":\" px\",\"blank_assist\":true,\"is_integer\":false},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":2.0,\"is_integer\":true,\"blank_assist\":true,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"mode\":\"Increment\",\"is_integer\":false,\"unit\":\" px\",\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"is_integer\":false,\"blank_assist\":true,\"min\":0.0,\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13261814586176172586,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4787732047489141819,{\"persistent_metadata\":{\"display_name\":\"Tucked Arm\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":24}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3226457726231232839,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3971837674569123876,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[18190631752493248867,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[1378578509112405,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[18067513817508158001,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"blank_assist\":true,\"mode\":\"Increment\",\"is_integer\":false,\"min\":0.0},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"mode\":\"Increment\",\"is_integer\":true,\"min\":2.0},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"mode\":\"Increment\",\"blank_assist\":true,\"unit\":\" px\",\"is_integer\":false},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"is_integer\":false,\"mode\":\"Increment\",\"blank_assist\":true,\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4332145463108161926,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-31,292]}}},\"network_metadata\":null}}],[5302437193964714993,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[10424806499648491677,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3686761601672683183,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"mode\":\"Increment\",\"unit\":\" px\",\"is_integer\":false,\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":true,\"min\":2.0,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"min\":0.0,\"mode\":\"Increment\",\"is_integer\":false,\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"blank_assist\":true,\"mode\":\"Increment\",\"unit\":\" px\",\"is_integer\":false},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[8410534738018320047,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4372998635946271235,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15466714490303763249,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[18214377096178867498,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[15827578515555598997,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[7654665057468818389,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[14894569344576297448,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[12219771677493189964,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4307303572241320716,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-17,319]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12531351117929704587,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[6416452251137958677,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[3827449344952693766,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"min\":0.0,\"mode\":\"Increment\",\"is_integer\":false,\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"min\":2.0,\"is_integer\":true,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"blank_assist\":true,\"is_integer\":false,\"mode\":\"Increment\",\"min\":0.0},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"is_integer\":false,\"unit\":\" px\",\"mode\":\"Increment\",\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14778750092903591172,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15857077552290328068,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10133176481349663495,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15446793500614592278,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15798070933198867970,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[5269304445610080925,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[17336535036064625290,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\" px\",\"x\":\"X\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"y\":\"H\",\"is_integer\":false,\"unit\":\"x\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4422453582814483232,{\"persistent_metadata\":{\"display_name\":\"Hair and Face\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-7,4]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2698266912167150713,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[8698602280607307123,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9954843247420111867,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"X\",\"is_integer\":false,\"unit\":\" px\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\"x\",\"y\":\"H\",\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[1019037285881657884,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[6749771744300551215,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2780251074492832077,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[8240895922641772563,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[17815494794630739611,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[8230694129617719636,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[6645255982686652881,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[5326536612985524219,{\"persistent_metadata\":{\"display_name\":\"Scatter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":false,\"min\":0.01,\"range_max\":100.0,\"mode\":\"Range\",\"range_min\":1.0},\"widget_override\":\"number\",\"input_name\":\"Separation Disk Diameter\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"min\":0.0,\"mode\":\"Increment\",\"is_integer\":true},\"widget_override\":\"number\",\"input_name\":\"Seed\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Scatter Points\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Poisson-Disk Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[-312.5,-48.5],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,38.0,473.0],\"node_graph_width\":0.0},\"selection_undo_history\":[[]],\"selection_redo_history\":[]}}}}],[421715625023770179,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4350324834849900949,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[5020096817747898028,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[10587073897090054035,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[542361600097372754,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[1889157037801767612,{\"persistent_metadata\":{\"display_name\":\"Scatter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"range_min\":1.0,\"mode\":\"Range\",\"min\":0.01,\"is_integer\":false,\"range_max\":100.0,\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Separation Disk Diameter\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"mode\":\"Increment\",\"is_integer\":true,\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Seed\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Scatter Points\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Poisson-Disk Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[-312.5,-48.5],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,38.0,473.0],\"node_graph_width\":0.0},\"selection_undo_history\":[[]],\"selection_redo_history\":[]}}}}],[7747398671834040298,{\"persistent_metadata\":{\"display_name\":\"Scatter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.01,\"mode\":\"Range\",\"range_min\":1.0,\"blank_assist\":true,\"is_integer\":false,\"range_max\":100.0},\"widget_override\":\"number\",\"input_name\":\"Separation Disk Diameter\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":true,\"mode\":\"Increment\",\"min\":0.0},\"widget_override\":\"number\",\"input_name\":\"Seed\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Scatter Points\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Poisson-Disk Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[-312.5,-48.5],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,38.0,473.0],\"node_graph_width\":0.0},\"selection_undo_history\":[[]],\"selection_redo_history\":[]}}}}],[17971411534648521628,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11025165626998987360,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[615144098061106242,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[9847383247226990698,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[15815816861435910950,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[431994205232245356,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-17,7]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14539627480594383748,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[10689298484366290551,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2641530639940889619,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14225285635863713990,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"x\":\"X\",\"y\":\"Y\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\"x\",\"is_integer\":false,\"y\":\"H\",\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3955326429435439190,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[14887821801874852671,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11573595155909211511,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"mode\":\"Increment\",\"unit\":\" px\",\"blank_assist\":true,\"min\":0.0},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":true,\"min\":2.0,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"min\":0.0,\"unit\":\" px\",\"is_integer\":false,\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"unit\":\" px\",\"min\":0.0,\"is_integer\":false,\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2660652185019504730,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"unit\":\" px\",\"mode\":\"Increment\",\"blank_assist\":true,\"is_integer\":false},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":true,\"min\":2.0,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"blank_assist\":true,\"min\":0.0,\"unit\":\" px\",\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"blank_assist\":true,\"is_integer\":false,\"unit\":\" px\",\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4236845268521674740,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[17364155187784942740,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[6666260895482068061,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13444661581815146533,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-17,97]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4105711298139980122,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3802858053991775169,{\"persistent_metadata\":{\"display_name\":\"Scatter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.01,\"mode\":\"Range\",\"blank_assist\":true,\"range_max\":100.0,\"range_min\":1.0,\"is_integer\":false},\"widget_override\":\"number\",\"input_name\":\"Separation Disk Diameter\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"blank_assist\":true,\"is_integer\":true,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Seed\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Scatter Points\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Poisson-Disk Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[-312.5,-48.5],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,38.0,473.0],\"node_graph_width\":0.0},\"selection_undo_history\":[[]],\"selection_redo_history\":[]}}}}],[17378885078543074499,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4479074488343511985,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[10770443343193024138,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7450965328305122110,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[15961046538654083626,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[3670529450440935325,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4577174813962563383,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14029368390543839187,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[6292009934909381201,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-17,88]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14057307926677215422,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14862049226133442027,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[11616089678400336955,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[12428327489525325219,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4078100635676202528,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13368990606109678244,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10599660455959346550,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10127467043900015225,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10415872992231003638,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[8644924780109919177,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"unit\":\" px\",\"blank_assist\":true,\"is_integer\":false,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":2.0,\"mode\":\"Increment\",\"blank_assist\":true,\"is_integer\":true},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"mode\":\"Increment\",\"is_integer\":false,\"min\":0.0,\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"min\":0.0,\"mode\":\"Increment\",\"blank_assist\":true,\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9182448229950585507,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[5040278174920511484,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[8543051864256131356,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[4341772758799935306,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":false,\"min\":0.0,\"unit\":\" px\",\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":true,\"mode\":\"Increment\",\"min\":2.0,\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"blank_assist\":true,\"min\":0.0,\"unit\":\" px\",\"is_integer\":false},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"blank_assist\":true,\"min\":0.0,\"unit\":\" px\",\"is_integer\":false},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[17891208858820401648,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"X\",\"is_integer\":false,\"unit\":\" px\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"H\",\"unit\":\"x\",\"is_integer\":false,\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10795820039540504703,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7922156219537051964,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[2087303479944421366,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15126865253122550765,{\"persistent_metadata\":{\"display_name\":\"Dotted Bodice\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":21}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9782123335421401489,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16339345235172368839,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[8413863870096329943,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15177845878727456758,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[9529195152569434392,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[3406722917122601552,{\"persistent_metadata\":{\"display_name\":\"Circle\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Primary\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Radius\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-38,208]}}},\"network_metadata\":null}}],[13352561089252322209,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7262199696924786895,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"mode\":\"Increment\",\"is_integer\":false,\"unit\":\" px\",\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":true,\"min\":2.0,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":false,\"min\":0.0,\"unit\":\" px\",\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"blank_assist\":true,\"mode\":\"Increment\",\"unit\":\" px\",\"min\":0.0},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16324258033206362312,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[15656854169166220905,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"x\":\"X\",\"is_integer\":false,\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"y\":\"H\",\"x\":\"W\",\"unit\":\"x\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12224498203743157414,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[7525593029671097583,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3992858139802231032,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10253927692147706615,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[11268046366284173800,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[5861306074868809692,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2878992817082507910,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9684857454501250999,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[11659756061767599421,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[1162381870526064378,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4248321400839848160,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-17,124]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14012648643507848353,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4663768795652429571,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9698363115186534174,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[2175432926627256613,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-17,328]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9374264173303233490,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"mode\":\"Increment\",\"is_integer\":false,\"min\":0.0,\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"mode\":\"Increment\",\"min\":2.0,\"is_integer\":true},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"blank_assist\":true,\"min\":0.0,\"unit\":\" px\",\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"min\":0.0,\"is_integer\":false,\"blank_assist\":true,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2594533001540454577,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"unit\":\" px\",\"is_integer\":false,\"min\":0.0,\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":2.0,\"mode\":\"Increment\",\"is_integer\":true,\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"unit\":\" px\",\"min\":0.0,\"mode\":\"Increment\",\"is_integer\":false},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"blank_assist\":true,\"mode\":\"Increment\",\"min\":0.0,\"is_integer\":false},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10375238420217738812,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[6282972142629473139,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10928540355449103287,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13045087323693407920,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2397243911096708995,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13644138583806412631,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[6988349135757634271,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15303587427289959766,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16175421708184657649,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13557369662261607646,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[5455777299776842371,{\"persistent_metadata\":{\"display_name\":\"Beaded Skirt\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":78}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14883504161508594099,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-17,295]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14831840560430171946,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14982414026754548178,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[11553850607251055696,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14598755603287563819,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[3170924135668664007,{\"persistent_metadata\":{\"display_name\":\"Pointing Arm\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":6}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12496143061817048445,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[1235106489581249820,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"unit\":\" px\",\"is_integer\":false,\"blank_assist\":true,\"min\":0.0},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":2.0,\"is_integer\":true,\"blank_assist\":true,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"mode\":\"Increment\",\"is_integer\":false,\"blank_assist\":true,\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"is_integer\":false,\"mode\":\"Increment\",\"blank_assist\":true,\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[18319784717194273926,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[17529660518597229229,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[17118107476414252025,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"y\":\"Y\",\"is_integer\":false,\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\"x\",\"y\":\"H\",\"x\":\"W\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[-144.5,-72.5],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,206.0,449.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16450742929146919960,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2044103368441997753,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15578929303912288394,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[16536768589601337644,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[15896921950407486754,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14991324592500870173,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[501401493219507773,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[16614450796751955858,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[8697043784435445845,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16796171662855500935,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[314278016428495768,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[5185036609290210853,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[1785173043494067496,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4453139144069993994,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12594527670567285670,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[10270446074640675342,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"Y\",\"x\":\"X\",\"unit\":\" px\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"y\":\"H\",\"unit\":\"x\",\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"Data\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[6532401937876437300,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"Y\",\"unit\":\" px\",\"x\":\"X\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"y\":\"H\",\"x\":\"W\",\"unit\":\"x\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3165571685352930240,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4577638792388493935,{\"persistent_metadata\":{\"display_name\":\"Head and Neck\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":78}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15239301303367148581,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[18207065424980079673,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"is_integer\":false,\"blank_assist\":true,\"min\":0.0,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":2.0,\"mode\":\"Increment\",\"blank_assist\":true,\"is_integer\":true},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"min\":0.0,\"unit\":\" px\",\"blank_assist\":true,\"is_integer\":false},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"min\":0.0,\"is_integer\":false,\"blank_assist\":true,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4909350123806022131,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12473080738469616517,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[14098374807212007572,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7466034304713056391,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15982852655074258238,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14946189826912398678,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[6580280438672662494,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[17078740291337047697,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-31,208]}}},\"network_metadata\":null}}],[16195626650123806176,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10586744777717861556,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[11481949351661484921,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":false,\"min\":0.0,\"unit\":\" px\",\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"mode\":\"Increment\",\"is_integer\":true,\"min\":2.0},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"blank_assist\":true,\"is_integer\":false,\"mode\":\"Increment\",\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":false,\"mode\":\"Increment\",\"unit\":\" px\",\"min\":0.0},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14209241002058525241,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15735375935164094402,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[3535178979443201645,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[8595304668947966919,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[1167210731467447244,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12049041947382267086,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"x\":\"X\",\"y\":\"Y\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"H\",\"is_integer\":false,\"unit\":\"x\",\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[1384427686127078856,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14797986717815207528,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[17207895962122263432,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[2422139482859833437,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4265165189651403984,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[17426609415699324395,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16051539163551573193,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[6672826052605647592,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[5471152581000334146,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[17324767436949538365,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12062649793560663566,{\"persistent_metadata\":{\"display_name\":\"Solid Red Skirt\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":3}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12131058586835568367,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[6973438081601736688,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[3636653585682494814,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[10086073308516686449,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4222034829755771252,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4572557574846980832,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[7505360855062237520,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[952330505278607301,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[13907578809542898348,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[18422317423856403288,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16530658574540156160,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\" px\",\"y\":\"Y\",\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"H\",\"unit\":\"x\",\"is_integer\":false,\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[5174744389209053970,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11451028343967836482,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[6867142265138950838,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[14345191642063772510,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11632506522064533635,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16446146761452576438,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"is_integer\":false,\"y\":\"Y\",\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"unit\":\"x\",\"y\":\"H\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10432831427187785843,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[11656581020969095354,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11804065810513502701,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[17064046832210629373,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13014628586360765651,{\"persistent_metadata\":{\"display_name\":\"Dotted Aura\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":6}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9276497172451351253,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13269760558336088742,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10619788176782820865,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10190227675276560561,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3150436463719911922,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"Y\",\"unit\":\" px\",\"is_integer\":false,\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"unit\":\"x\",\"is_integer\":false,\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"Data\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[1204243038352113866,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11479098559726891734,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10336592647221792772,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[11199691961479466803,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[12387541320114693418,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13231685386999438557,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[7385465194555106679,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-17,211]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4131094614457622424,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"unit\":\" px\",\"is_integer\":false,\"min\":0.0,\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"min\":2.0,\"blank_assist\":true,\"is_integer\":true},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"mode\":\"Increment\",\"unit\":\" px\",\"is_integer\":false,\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"unit\":\" px\",\"is_integer\":false,\"blank_assist\":true,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[-228.5,-108.5],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,122.0,413.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3649809135741361946,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7659717355245331967,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13975451746581400000,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15492651270767932214,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[8814059393325469059,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11666664915283969027,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2126710823743005151,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[2440895173483452224,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[8699675339613677057,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"is_integer\":false,\"y\":\"Y\",\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"unit\":\"x\",\"is_integer\":false,\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[5555007473125503522,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[17133591775058457007,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[16551385471328831128,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[8091904580702893317,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[18015048324114736039,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[13475705179546695973,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[541002100261582638,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2682920349304670808,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[5213978458941436169,{\"persistent_metadata\":{\"display_name\":\"Scatter Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"range_min\":1.0,\"mode\":\"Range\",\"blank_assist\":true,\"is_integer\":false,\"min\":0.01,\"range_max\":100.0},\"widget_override\":\"number\",\"input_name\":\"Separation Disk Diameter\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"min\":0.0,\"blank_assist\":true,\"is_integer\":true},\"widget_override\":\"number\",\"input_name\":\"Seed\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Scatter Points\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Poisson-Disk Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[-312.5,-48.5],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,38.0,473.0],\"node_graph_width\":0.0},\"selection_undo_history\":[[]],\"selection_redo_history\":[]}}}}],[1806828617441445250,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16767482995096345179,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[17147975601187022720,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[11677503666435782605,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[16290933138334939444,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11058365317860779469,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[17967471489196302183,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7320676248579211727,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[14285767317419627814,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[12385950900718181935,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9740500978584792725,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[17207349373429328029,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[14675232891471617236,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11590691579869262546,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[17494926338451345058,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3414873131936208778,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[172538270105470471,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[1984475088429379731,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[1661691009086487874,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":false,\"min\":0.0,\"mode\":\"Increment\",\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":true,\"blank_assist\":true,\"min\":2.0,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"is_integer\":false,\"unit\":\" px\",\"blank_assist\":true,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"blank_assist\":true,\"unit\":\" px\",\"is_integer\":false,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12801133692316734622,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"mode\":\"Increment\",\"unit\":\" px\",\"min\":0.0,\"blank_assist\":true,\"is_integer\":false},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"min\":2.0,\"mode\":\"Increment\",\"is_integer\":true},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"mode\":\"Increment\",\"blank_assist\":true,\"is_integer\":false,\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":0.0,\"mode\":\"Increment\",\"is_integer\":false,\"unit\":\" px\",\"blank_assist\":true},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[2959546142916532439,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9470742171134780193,{\"persistent_metadata\":{\"display_name\":\"Dotted Skirt\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":81}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15286091228862934481,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9778375740427894463,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[11194653561109699287,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10792166025753022402,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[3627710206997006419,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15637103575662751567,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10918055532782314571,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[13302269488061286120,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[514796034658094296,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[14817659161913199655,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[907841922684377912,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[7141088190930752823,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The shape to be resampled and converted into a polyline.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Spacing\",\"input_description\":\"Use a point sampling density controlled by a distance between, or specific number of, points.\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"blank_assist\":true,\"is_integer\":false,\"min\":0.0,\"mode\":\"Increment\"},\"widget_override\":\"number\",\"input_name\":\"Separation\",\"input_description\":\"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\"}},{\"persistent_metadata\":{\"input_data\":{\"min\":2.0,\"mode\":\"Increment\",\"blank_assist\":true,\"is_integer\":true},\"widget_override\":\"number\",\"input_name\":\"Quantity\",\"input_description\":\"Number of points to place along the path.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"mode\":\"Increment\",\"min\":0.0,\"is_integer\":false,\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Start Offset\",\"input_description\":\"Exclude some distance from the start of the path before the first instance.\"}},{\"persistent_metadata\":{\"input_data\":{\"blank_assist\":true,\"is_integer\":false,\"min\":0.0,\"mode\":\"Increment\",\"unit\":\" px\"},\"widget_override\":\"number\",\"input_name\":\"Stop Offset\",\"input_description\":\"Exclude some distance from the end of the path after the last instance.\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Adaptive Spacing\",\"input_description\":\"Round 'Separation' to a nearby value that divides into the path length evenly.\"}}],\"output_names\":[\"Vector\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Sample Polyline\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Subpath Segment Lengths\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,7]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Sample Polyline\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Memoize\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[14,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15498700602024283966,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12554368619682347699,{\"persistent_metadata\":{\"display_name\":\"Copy to Points\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Points\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Instance\",\"input_description\":\"Artwork to be copied and placed at each point.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Min\",\"input_description\":\"Minimum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Max\",\"input_description\":\"Maximum range of randomized sizes given to each instance.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Bias\",\"input_description\":\"Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Scale Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance sizes.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation\",\"input_description\":\"Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Random Rotation Seed\",\"input_description\":\"Seed to determine unique variations on all the randomized instance angles.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[16137033772363318157,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[1272070255512697108,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[709.0808337143742,-713.6328227404273],\"tilt\":0.0,\"zoom\":0.6328125,\"flip\":false},\"node_graph_to_viewport\":[0.6328125,0.0,0.0,0.6328125,799.0,70.00000000000006],\"node_graph_width\":1981.0},\"selection_undo_history\":[[17426704671299246894],[2699408592782313690],[4493274523708782092],[14031411536409518176],[13263961817794116841],[835795066714655983],[11477846841203274509],[727544715487174952],[6480666310383891203],[15086626938904467381],[12994398686940961368],[15086626938904467381],[],[12994398686940961368],[],[5140869461760168364],[11677503666435782605],[776454851019809551],[],[10662978266497754900],[],[13201515093260842314],[],[3932608775253338292],[],[8090442493082590595],[],[17545135276965178247],[4332145463108161926],[],[4332145463108161926],[],[4332145463108161926],[],[11356586238302409958,10086073308516686449],[],[4332145463108161926],[],[3406722917122601552],[4332145463108161926],[],[4332145463108161926],[],[10086073308516686449],[17545135276965178247],[],[17078740291337047697],[3457800614598085282],[3457800614598085282,17426704671299246894],[3457800614598085282,17426704671299246894,2699408592782313690],[3457800614598085282,17426704671299246894,2699408592782313690,14031411536409518176],[3457800614598085282,17426704671299246894,2699408592782313690,14031411536409518176,15239301303367148581],[3457800614598085282,17426704671299246894,2699408592782313690,14031411536409518176,15239301303367148581,13263961817794116841],[3457800614598085282,17426704671299246894,2699408592782313690,14031411536409518176,15239301303367148581,13263961817794116841],[3457800614598085282,17426704671299246894,2699408592782313690,14031411536409518176,13263961817794116841],[3457800614598085282,17426704671299246894,2699408592782313690,14031411536409518176,13263961817794116841,835795066714655983],[3457800614598085282,17426704671299246894,2699408592782313690,14031411536409518176,13263961817794116841,835795066714655983,11477846841203274509],[3457800614598085282,17426704671299246894,2699408592782313690,14031411536409518176,13263961817794116841,835795066714655983,11477846841203274509,727544715487174952],[3457800614598085282,17426704671299246894,2699408592782313690,14031411536409518176,13263961817794116841,835795066714655983,11477846841203274509,727544715487174952,6480666310383891203],[3457800614598085282,17426704671299246894,2699408592782313690,14031411536409518176,13263961817794116841,835795066714655983,11477846841203274509,727544715487174952,6480666310383891203,10550662778443296023],[3457800614598085282,17426704671299246894,2699408592782313690,14031411536409518176,13263961817794116841,835795066714655983,11477846841203274509,727544715487174952,6480666310383891203,10550662778443296023,13185163654003144011],[3457800614598085282,17426704671299246894,2699408592782313690,14031411536409518176,13263961817794116841,835795066714655983,11477846841203274509,727544715487174952,6480666310383891203,10550662778443296023,13185163654003144011,3559656994078944914],[3457800614598085282,17426704671299246894,2699408592782313690,14031411536409518176,13263961817794116841,835795066714655983,11477846841203274509,727544715487174952,6480666310383891203,10550662778443296023,13185163654003144011,3559656994078944914,12747708470463124971],[3457800614598085282,17426704671299246894,2699408592782313690,14031411536409518176,13263961817794116841,835795066714655983,11477846841203274509,727544715487174952,6480666310383891203,10550662778443296023,13185163654003144011,3559656994078944914,12747708470463124971,898580338082782932],[3457800614598085282,17426704671299246894,2699408592782313690,14031411536409518176,13263961817794116841,835795066714655983,11477846841203274509,727544715487174952,6480666310383891203,10550662778443296023,13185163654003144011,3559656994078944914,12747708470463124971,898580338082782932,17099718003896986529],[3457800614598085282,17426704671299246894,2699408592782313690,14031411536409518176,13263961817794116841,835795066714655983,11477846841203274509,727544715487174952,6480666310383891203,10550662778443296023,13185163654003144011,3559656994078944914,12747708470463124971,898580338082782932,17099718003896986529,1510915657479425828],[3457800614598085282,17426704671299246894,2699408592782313690,14031411536409518176,13263961817794116841,835795066714655983,11477846841203274509,727544715487174952,6480666310383891203,10550662778443296023,13185163654003144011,3559656994078944914,12747708470463124971,898580338082782932,17099718003896986529,1510915657479425828,770786980744949903],[3457800614598085282,17426704671299246894,2699408592782313690,14031411536409518176,13263961817794116841,835795066714655983,11477846841203274509,727544715487174952,6480666310383891203,10550662778443296023,13185163654003144011,3559656994078944914,12747708470463124971,898580338082782932,17099718003896986529,1510915657479425828,770786980744949903,5742628441455317378],[3457800614598085282,17426704671299246894,2699408592782313690,14031411536409518176,13263961817794116841,835795066714655983,11477846841203274509,727544715487174952,6480666310383891203,10550662778443296023,13185163654003144011,3559656994078944914,12747708470463124971,898580338082782932,17099718003896986529,1510915657479425828,770786980744949903,5742628441455317378,12579808169500774530],[3457800614598085282,17426704671299246894,2699408592782313690,14031411536409518176,13263961817794116841,835795066714655983,11477846841203274509,727544715487174952,6480666310383891203,10550662778443296023,13185163654003144011,3559656994078944914,12747708470463124971,898580338082782932,17099718003896986529,1510915657479425828,770786980744949903,5742628441455317378,12579808169500774530,862014397356527450],[3457800614598085282,17426704671299246894,2699408592782313690,14031411536409518176,13263961817794116841,835795066714655983,11477846841203274509,727544715487174952,6480666310383891203,10550662778443296023,13185163654003144011,3559656994078944914,12747708470463124971,898580338082782932,17099718003896986529,1510915657479425828,770786980744949903,5742628441455317378,12579808169500774530,862014397356527450,13790235862723082076],[3457800614598085282,17426704671299246894,2699408592782313690,14031411536409518176,13263961817794116841,835795066714655983,11477846841203274509,727544715487174952,6480666310383891203,10550662778443296023,13185163654003144011,3559656994078944914,12747708470463124971,898580338082782932,17099718003896986529,1510915657479425828,770786980744949903,5742628441455317378,12579808169500774530,862014397356527450,13790235862723082076,9531720291230511752],[3457800614598085282,17426704671299246894,2699408592782313690,14031411536409518176,13263961817794116841,835795066714655983,11477846841203274509,727544715487174952,6480666310383891203,10550662778443296023,13185163654003144011,3559656994078944914,12747708470463124971,898580338082782932,17099718003896986529,1510915657479425828,770786980744949903,5742628441455317378,12579808169500774530,862014397356527450,13790235862723082076,9531720291230511752,12792838691218304039],[3457800614598085282,17426704671299246894,2699408592782313690,14031411536409518176,13263961817794116841,835795066714655983,11477846841203274509,727544715487174952,6480666310383891203,10550662778443296023,13185163654003144011,3559656994078944914,12747708470463124971,898580338082782932,17099718003896986529,1510915657479425828,770786980744949903,5742628441455317378,12579808169500774530,862014397356527450,13790235862723082076,9531720291230511752,12792838691218304039,5102308641931177440],[3457800614598085282,17426704671299246894,2699408592782313690,14031411536409518176,13263961817794116841,835795066714655983,11477846841203274509,727544715487174952,6480666310383891203,10550662778443296023,13185163654003144011,3559656994078944914,12747708470463124971,898580338082782932,17099718003896986529,1510915657479425828,770786980744949903,5742628441455317378,12579808169500774530,862014397356527450,13790235862723082076,9531720291230511752,12792838691218304039,5102308641931177440,14480644719732559657],[3457800614598085282,17426704671299246894,2699408592782313690,14031411536409518176,13263961817794116841,835795066714655983,11477846841203274509,727544715487174952,6480666310383891203,10550662778443296023,13185163654003144011,3559656994078944914,12747708470463124971,898580338082782932,17099718003896986529,1510915657479425828,770786980744949903,5742628441455317378,12579808169500774530,862014397356527450,13790235862723082076,9531720291230511752,12792838691218304039,5102308641931177440,14480644719732559657,3601587624047636241],[],[17078740291337047697],[3406722917122601552],[],[9470742171134780193],[14883504161508594099],[],[13163272246010991228],[],[4332145463108161926],[17078740291337047697],[],[],[17078740291337047697,3406722917122601552],[],[9470742171134780193],[13368990606109678244],[9470742171134780193],[14883504161508594099],[421715625023770179],[3670529450440935325],[4265165189651403984],[17064046832210629373],[]],\"selection_redo_history\":[]}}},\"collapsed\":[4422453582814483233,4577638792388493936,3170924135668664008,4787732047489141820,12062649793560663567,5455777299776842372,9470742171134780194,15126865253122550766,13014628586360765652],\"commit_hash\":\"8fa46ba63a69bb5fa18a49194cf112d963a2d43b\",\"document_ptz\":{\"pan\":[-512.5,-515.648496025349],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"render_mode\":\"Normal\",\"overlays_visibility_settings\":{\"all\":true,\"artboard_name\":true,\"compass_rose\":true,\"quick_measurement\":true,\"transform_measurement\":true,\"transform_cage\":true,\"hover_outline\":true,\"selection_outline\":true,\"layer_origin_cross\":true,\"pivot\":true,\"origin\":true,\"path\":true,\"anchors\":true,\"handles\":true},\"rulers_visible\":true,\"snapping_state\":{\"snapping_enabled\":false,\"grid_snapping\":false,\"artboards\":true,\"tolerance\":8.0,\"bounding_box\":{\"center_point\":true,\"corner_point\":true,\"edge_midpoint\":true,\"align_with_edges\":true,\"distribute_evenly\":true},\"path\":{\"anchor_point\":true,\"line_midpoint\":true,\"along_path\":true,\"normal_to_path\":true,\"tangent_to_path\":true,\"path_intersection_point\":true,\"align_with_anchor_point\":true,\"perpendicular_from_endpoint\":true},\"grid\":{\"origin\":[0.0,0.0],\"grid_type\":{\"Rectangular\":{\"spacing\":[1.0,1.0]}},\"rectangular_spacing\":[1.0,1.0],\"isometric_y_spacing\":1.0,\"isometric_angle_a\":30.0,\"isometric_angle_b\":30.0,\"grid_color\":{\"red\":0.6038274,\"green\":0.6038274,\"blue\":0.6038274,\"alpha\":1.0},\"dot_display\":false}},\"graph_view_overlay_open\":false,\"graph_fade_artwork_percentage\":80.0}"
  },
  {
    "path": "demo-artwork/valley-of-spires.graphite",
    "content": "{\"network_interface\":{\"network\":{\"exports\":[{\"Node\":{\"node_id\":16815500381887058038,\"output_index\":0}}],\"nodes\":[[202,{\"inputs\":[{\"Node\":{\"node_id\":206,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.5058824,\"green\":0.26666668,\"blue\":0.20392157,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.5058824,\"green\":0.26666668,\"blue\":0.20392157,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[404,{\"inputs\":[{\"Node\":{\"node_id\":402,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeAlign\":\"Center\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeCap\":\"Butt\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeJoin\":\"Miter\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"PaintOrder\":\"StrokeAbove\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"VecF64\":[]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::StrokeNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[93,{\"inputs\":[{\"Node\":{\"node_id\":94,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[851.1666666666667,668.5377104806669]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[39.677869315599935,39.67786931560005]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[155,{\"inputs\":[{\"Node\":{\"node_id\":159,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.7529412,\"green\":0.34117648,\"blue\":0.2,\"alpha\":1.0},{\"red\":0.6431373,\"green\":0.31764707,\"blue\":0.21176471,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.9001916129448388,0.7881778212017586],\"end\":[0.8902470335613286,0.6662432828529555]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.7529412,\"green\":0.34117648,\"blue\":0.2,\"alpha\":1.0},{\"red\":0.6431373,\"green\":0.31764707,\"blue\":0.21176471,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.9001916129448388,0.7881778212017586],\"end\":[0.8902470335613286,0.6662432828529555]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[497,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[2,[1.0,0.0]],[4,[0.0,1.0]],[1,[0.0,0.0]],[3,[1.0,1.0]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[3,3],[4,4],[1,1],[2,2]],\"end_point\":[[2,3],[3,4],[4,1],[1,2]],\"handle_primary\":[[2,[0.0,0.0]],[4,[0.0,0.0]],[3,[0.0,0.0]],[1,[0.0,0.0]]],\"handle_end\":[[1,[0.0,0.0]],[3,[0.0,0.0]],[4,[0.0,0.0]],[2,[0.0,0.0]]],\"stroke\":[[3,0],[1,0],[4,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[8861964493222160710,{\"inputs\":[{\"Node\":{\"node_id\":16894739051789815098,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.5058824,\"green\":0.26666668,\"blue\":0.20392157,\"alpha\":1.0},{\"red\":0.5058824,\"green\":0.26666668,\"blue\":0.20392157,\"alpha\":0.0390625}]},\"gradient_type\":\"Radial\",\"start\":[0.49999999999999994,0.5000000000000009],\"end\":[0.9003149237651733,0.5000000000000009]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.5058824,\"green\":0.26666668,\"blue\":0.20392157,\"alpha\":1.0},{\"red\":0.5058824,\"green\":0.26666668,\"blue\":0.20392157,\"alpha\":0.0390625}]},\"gradient_type\":\"Radial\",\"start\":[0.49999999999999994,0.5000000000000009],\"end\":[0.9003149237651733,0.5000000000000009]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[248,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],\"remove\":[],\"delta\":[[15,[935.0185185185186,608.3024691358025]],[8,[1025.882716049383,576.7015952852717]],[18,[884.845679012346,621.141975308642]],[6,[944.141561350963,527.7098765432099]],[10,[999.8086419753088,626.6728395061729]],[4,[898.8703703703707,571.5617283950618]],[2,[890.3765432098768,558.3271604938273]],[14,[944.3024691358024,613.8333333333334]],[7,[986.8703703703704,552.6481481481483]],[3,[884.6481481481485,571.7592592592594]],[13,[955.9567901234568,612.4506172839507]],[20,[850.4753086419754,600.0720164609053]],[1,[866.5679012345681,572.641975308642]],[19,[867.9970278920896,620.4835390946502]],[12,[965.0432098765434,605.3395061728396]],[17,[915.067901234568,618.7716049382716]],[5,[923.956790123457,545.6851851851852]],[11,[991.9074074074076,607.667262767384]],[9,[1026.277777777778,628.0555555555557]],[16,[922.574074074074,606.9197530864199]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],\"remove\":[],\"start_point\":[[7,7],[19,19],[12,12],[2,2],[4,4],[1,1],[5,5],[11,11],[3,3],[13,13],[15,15],[10,10],[18,18],[20,20],[6,6],[16,16],[9,9],[17,17],[8,8],[14,14]],\"end_point\":[[15,16],[12,13],[19,20],[13,14],[16,17],[10,11],[14,15],[8,9],[1,2],[20,1],[2,3],[5,6],[4,5],[9,10],[17,18],[6,7],[3,4],[11,12],[18,19],[7,8]],\"handle_primary\":[[13,[0.0,0.0]],[14,[0.0,0.0]],[9,[0.0,0.0]],[17,[0.0,0.0]],[5,[0.0,0.5925925925926094]],[15,[0.0,0.0]],[4,[0.0,0.0]],[3,[0.0,0.0]],[8,[0.0,0.0]],[10,[-2.96296296296282,-1.1851851851851052]],[16,[0.0,0.0]],[12,[0.0,0.0]],[19,[-11.881115683584769,-0.11705532693190436]],[20,[0.0,0.0]],[1,[0.0,0.0]],[7,[22.254029366644772,13.337995427526266]],[2,[0.0,0.0]],[18,[0.0,0.0]],[6,[0.0,0.0]],[11,[0.0,0.0]]],\"handle_end\":[[5,[0.0,0.0]],[1,[-10.271604938271707,4.543209876543187]],[4,[-7.703703703703809,15.604938271604851]],[19,[0.0,0.0]],[15,[5.135802469136024,0.9876543209877582]],[6,[-11.851851851851848,-16.036008230452808]],[8,[0.0,0.0]],[12,[2.7654320987655865,-2.7624450928566375]],[10,[0.3950617283951487,3.117880051334623]],[3,[-8.69135802469134,0.39506172839503506]],[17,[20.5432098765433,0.1975308641974607]],[7,[0.0,0.0]],[16,[2.3703703703704377,-1.7777777777778283]],[9,[2.962962962963047,1.1851851851851052]],[20,[0.0,0.0]],[11,[18.567901234567785,4.543209876543301]],[13,[2.1728395061727497,0.39506172839503506]],[14,[4.740740740740762,0.7901234567901838]],[2,[0.5925925925926094,-3.555555555555543]],[18,[11.881115683584769,0.11705532693190436]]],\"stroke\":[[9,0],[3,0],[6,0],[16,0],[8,0],[2,0],[15,0],[12,0],[11,0],[20,0],[14,0],[4,0],[1,0],[5,0],[10,0],[19,0],[17,0],[13,0],[18,0],[7,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":20}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[419,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[3,[1.0,1.0]],[2,[1.0,0.0]],[1,[0.0,0.0]],[4,[0.0,1.0]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[3,3],[2,2],[4,4],[1,1]],\"end_point\":[[2,3],[4,1],[1,2],[3,4]],\"handle_primary\":[[4,[0.0,0.0]],[3,[0.0,0.0]],[1,[0.0,0.0]],[2,[0.0,0.0]]],\"handle_end\":[[1,[0.0,0.0]],[4,[0.0,0.0]],[3,[0.0,0.0]],[2,[0.0,0.0]]],\"stroke\":[[2,0],[4,0],[3,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[77,{\"inputs\":[{\"Node\":{\"node_id\":78,\"output_index\":0}},{\"Node\":{\"node_id\":448,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[108,{\"inputs\":[{\"Node\":{\"node_id\":109,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[858.7905598373798,601.6041802310946]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":7.016709876377979e-15},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.1014123874504275,0.11427520552998474]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[7.90668593248704e-15,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[450,{\"inputs\":[{\"Node\":{\"node_id\":451,\"output_index\":0}},{\"Node\":{\"node_id\":467,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[310,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7],\"remove\":[],\"delta\":[[3,[698.0912208504803,600.3792866941013]],[5,[710.9477975918305,613.6358024691357]],[2,[734.9571457603006,587.5194584158918]],[4,[706.1680384087791,607.8415637860082]],[1,[744.3996087994717,586.9732002235432]],[7,[779.7592592592597,612.6204267490609]],[6,[729.3449931412895,610.3875171467763]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7],\"remove\":[],\"start_point\":[[5,5],[3,3],[1,1],[6,6],[4,4],[2,2],[7,7]],\"end_point\":[[4,5],[5,6],[2,3],[1,2],[7,1],[3,4],[6,7]],\"handle_primary\":[[6,[15.119646395366544,2.853223593964344]],[1,[0.0,0.0]],[3,[0.0,0.0]],[2,[0.0,0.0]],[5,[7.452522481329197,2.721536351166037]],[4,[2.1947873799731497,4.691071467853249]],[7,[0.0,0.0]]],\"handle_end\":[[5,null],[6,[0.0,0.0]],[7,[9.28638926992835,13.56378600823075]],[1,[0.0,0.0]],[3,[-2.494608558449272,-5.331900091455282]],[4,null],[2,[6.496570644718986,-11.149519890260422]]],\"stroke\":[[5,0],[7,0],[4,0],[2,0],[3,0],[6,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":7}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[481,{\"inputs\":[{\"Node\":{\"node_id\":485,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.8980392,\"green\":0.67058825,\"blue\":0.28235295,\"alpha\":1.0},{\"red\":0.73333335,\"green\":0.3764706,\"blue\":0.2,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.5563229660866629,0.005130077289715984],\"end\":[0.5563229660866629,0.9884201225302]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.8980392,\"green\":0.67058825,\"blue\":0.28235295,\"alpha\":1.0},{\"red\":0.73333335,\"green\":0.3764706,\"blue\":0.2,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.5563229660866629,0.005130077289715984],\"end\":[0.5563229660866629,0.9884201225302]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[170,{\"inputs\":[{\"Node\":{\"node_id\":171,\"output_index\":0}},{\"Node\":{\"node_id\":196,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4913361824430066698,{\"inputs\":[{\"Node\":{\"node_id\":11807598261442997948,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.5058824,\"green\":0.26666668,\"blue\":0.20392157,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.5058824,\"green\":0.26666668,\"blue\":0.20392157,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14080831508667499826,{\"inputs\":[{\"Node\":{\"node_id\":11377169273880889832,\"output_index\":0}},{\"Node\":{\"node_id\":14113040319560793790,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[232,{\"inputs\":[{\"Node\":{\"node_id\":236,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[434,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[4,[0.4436233919998075,1.0]],[3,[1.0069833844920426,0.9999999999999988]],[1,[0.5926327057682128,-0.43396226415094336]],[2,[0.8076864692090735,-0.4339622641509434]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[3,3],[4,4],[1,1],[2,2]],\"end_point\":[[3,4],[1,2],[4,1],[2,3]],\"handle_primary\":[[2,[0.0,0.0]],[1,[0.0,0.0]],[4,[0.0,0.0]],[3,[0.0,0.0]]],\"handle_end\":[[4,[0.0,0.0]],[1,[0.0,0.0]],[2,[0.0,0.0]],[3,[0.0,0.0]]],\"stroke\":[[1,0],[4,0],[3,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[465,{\"inputs\":[{\"Node\":{\"node_id\":469,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.8784314,\"green\":0.63529414,\"blue\":0.24313726,\"alpha\":1.0},{\"red\":0.827451,\"green\":0.36862746,\"blue\":0.20784314,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.543973941368078,0.19174161896974656],\"end\":[0.373641330960254,0.5573628465419693]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.8784314,\"green\":0.63529414,\"blue\":0.24313726,\"alpha\":1.0},{\"red\":0.827451,\"green\":0.36862746,\"blue\":0.20784314,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.543973941368078,0.19174161896974656],\"end\":[0.373641330960254,0.5573628465419693]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7148230379224894975,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],\"remove\":[],\"delta\":[[7,[495.90740740740733,567.882982777016]],[8,[502.6234567901235,563.1154930650816]],[5,[500.4506172839507,547.5105547934772]],[11,[504.00617283950623,588.9444444444446]],[16,[478.9197530864198,618.6875095259874]],[4,[500.77983539094663,544.0208428593207]],[18,[438.2283950617284,585.5544307531777]],[17,[448.3683127572017,617.6340115836006]],[2,[497.8388203017833,534.5393613778391]],[13,[516.8017832647463,593.4437585733884]],[19,[468.05555555555594,550.6710486206383]],[9,[510.261316872428,573.2078189300412]],[6,[490.17901234567904,556.0043819539711]],[10,[496.50000000000006,582.6234567901236]],[1,[506.7057613168725,531.9348803536052]],[15,[496.30246913580254,612.3006782502672]],[3,[488.5329218106997,551.6587029416252]],[12,[513.3888888888889,585.5544307531777]],[20,[480.89506172839504,532.4323654930657]],[14,[496.49999999999994,603.4117893613783]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],\"remove\":[],\"start_point\":[[2,2],[12,12],[9,9],[17,17],[13,13],[5,5],[4,4],[15,15],[14,14],[18,18],[8,8],[19,19],[10,10],[6,6],[1,1],[20,20],[16,16],[7,7],[11,11],[3,3]],\"end_point\":[[2,3],[14,15],[11,12],[16,17],[3,4],[6,7],[7,8],[15,16],[18,19],[20,1],[5,6],[12,13],[13,14],[17,18],[8,9],[19,20],[10,11],[4,5],[9,10],[1,2]],\"handle_primary\":[[18,[0.0,0.0]],[11,[0.0,0.0]],[16,[0.0,0.0]],[3,[4.565157750342848,-4.301783264746064]],[7,[0.0,0.0]],[10,[0.0,0.0]],[1,[0.0,0.0]],[6,[0.0,0.0]],[19,[0.0,0.0]],[2,[-7.188100137174104,2.6138545953361927]],[15,[0.0,0.0]],[14,[0.0,0.0]],[9,[0.0,0.0]],[5,[-6.737997256515712,3.1824417009599983]],[17,[0.0,0.0]],[8,[0.0,0.0]],[12,[0.0,0.0]],[4,null],[13,[0.0,0.0]],[20,[7.46503467504715,-4.02781143068205]]],\"handle_end\":[[12,[-0.39506172839503506,-1.975308641975289]],[4,null],[20,[0.0,0.0]],[11,[-3.851851851851848,0.7581344568814075]],[14,[-0.7901234567900701,-4.938271604938336]],[15,[0.0,0.0]],[3,null],[7,[-3.6872427983540206,1.4485596707820605]],[16,[0.0,0.0]],[13,[0.0,0.0]],[18,[0.0,0.0]],[8,[-1.7777777777777717,-5.728395061728406]],[9,[6.716049382716108,-8.49382716049422]],[6,[-2.508333333333439,-2.0902777777778283]],[19,[-3.394604481024089,3.2873037646699004]],[10,[-2.765432098765416,-3.555555555555543]],[2,[0.27087722942241044,-5.120145445603839]],[1,[3.4386245260820374,-1.250408918575317]],[17,[0.0,0.0]],[5,[0.0,0.0]]],\"stroke\":[[11,0],[15,0],[12,0],[17,0],[4,0],[8,0],[3,0],[20,0],[16,0],[5,0],[18,0],[19,0],[7,0],[9,0],[2,0],[13,0],[14,0],[10,0],[6,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":20}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[325,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9],\"remove\":[],\"delta\":[[5,[660.5,187.83333333333331]],[7,[619.8333333333333,577.1666666666666]],[3,[708.5,335.16666666666663]],[6,[619.8333333333333,207.83333333333331]],[2,[733.1666666666666,489.16666666666663]],[4,[695.8333333333333,239.16666666666663]],[8,[631.8333333333333,608.5]],[1,[744.0,592.0]],[9,[677.1666666666666,609.1666666666666]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9],\"remove\":[],\"start_point\":[[6,6],[7,7],[1,1],[4,4],[3,3],[8,8],[9,9],[5,5],[2,2]],\"end_point\":[[7,8],[6,7],[8,9],[9,1],[5,6],[2,3],[1,2],[3,4],[4,5]],\"handle_primary\":[[6,[0.0,16.666666666666686]],[3,[-6.6666666666667425,-57.333333333333314]],[8,[12.666666666666742,4.666666666666629]],[9,[18.66666666666663,-5.3333333333332575]],[4,[-3.3333333333332575,-21.33333333333331]],[7,[0.0,12.666666666666742]],[2,[-4.666666666666629,-50.666666666666686]],[5,[-36.0,-6.666666666666686]],[1,[0.0,0.0]]],\"handle_end\":[[3,[3.3333333333332575,21.33333333333331]],[5,[0.0,-16.666666666666686]],[1,[4.666666666666629,50.66666666666663]],[7,[-12.666666666666742,-4.666666666666629]],[9,[0.0,0.0]],[2,[6.6666666666667425,57.333333333333314]],[4,[36.0,6.666666666666657]],[6,[0.0,-12.666666666666742]],[8,[-18.66666666666663,5.3333333333332575]]],\"stroke\":[[1,0],[2,0],[4,0],[9,0],[7,0],[6,0],[8,0],[3,0],[5,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":9}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[496,{\"inputs\":[{\"Node\":{\"node_id\":497,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1024.0,600.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11377169273880889832,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":14433811491576609500,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11891167879168294182,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"delta\":[[5,[118.93072702331962,598.7990397805213]],[4,[126.900438957476,611.0020576131687]],[2,[104.00617283950618,624.6097393689986]],[6,[119.98422496570645,609.4218106995885]],[3,[148.91152263374485,614.1625514403293]],[1,[112.52194787379976,598.7990397805213]]]},\"segments\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"start_point\":[[4,4],[5,5],[2,2],[6,6],[3,3],[1,1]],\"end_point\":[[1,2],[3,4],[4,5],[2,3],[5,6],[6,1]],\"handle_primary\":[[1,[-2.370370370370395,22.25514403292175]],[5,[0.0,0.0]],[4,[-2.058260034882977,-0.6051267923739942]],[3,null],[6,[-4.477366255144005,-1.843621399176868]],[2,[0.0,0.0]]],\"handle_end\":[[6,[0.0,0.0]],[4,[0.0,0.0]],[5,[0.7023319615912413,-1.9314128943758533]],[1,[0.0,0.0]],[3,null],[2,[-21.11385459533605,1.053497942386798]]],\"stroke\":[[3,0],[2,0],[5,0],[4,0],[6,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":6}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[418,{\"inputs\":[{\"Node\":{\"node_id\":419,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[83.16666666666677,614.179527199694]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[294.3945373546583,138.32047280030588]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[76,{\"inputs\":[{\"Node\":{\"node_id\":77,\"output_index\":0}},{\"Node\":{\"node_id\":16164610528699022118,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[449,{\"inputs\":[{\"Node\":{\"node_id\":450,\"output_index\":0}},{\"Node\":{\"node_id\":6015109908395573189,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14113040319560793790,{\"inputs\":[{\"Node\":{\"node_id\":9603838021022368374,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[138,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[2,[105.84979423868312,685.0102880658435]],[3,[90.17901234567904,708.7139917695472]],[1,[126.55144032921808,714.7983539094649]],[4,[116.38477366255144,723.5946502057614]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[2,2],[1,1],[4,4],[3,3]],\"end_point\":[[3,4],[1,2],[2,3],[4,1]],\"handle_primary\":[[1,[0.0,0.0]],[2,[0.0,0.0]],[3,[-2.4237705319430347,10.543401813951732]],[4,[0.0,0.0]]],\"handle_end\":[[3,[0.0,0.0]],[1,[16.460905349794245,13.695473251028716]],[2,[2.6337448559670804,-11.456790123456472]],[4,[3.6604938271605647,11.166666666666742]]],\"stroke\":[[4,0],[3,0],[1,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[169,{\"inputs\":[{\"Node\":{\"node_id\":170,\"output_index\":0}},{\"Node\":{\"node_id\":190,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[200,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10],\"remove\":[],\"delta\":[[3,[974.574074074074,539.3148148148148]],[10,[1010.492379210486,553.0791800030486]],[8,[994.0802469135804,561.8388203017832]],[6,[988.3518518518518,565.8333333333333]],[7,[988.9444444444443,571.9567901234568]],[2,[993.9814814814814,530.7222222222222]],[9,[1001.2407407407406,568.2037037037037]],[5,[990.3271604938273,558.7222222222222]],[1,[1004.392496062592,536.8475080018289]],[4,[972.0555555555557,547.0185185185185]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10],\"remove\":[],\"start_point\":[[9,9],[6,6],[7,7],[3,3],[5,5],[2,2],[10,10],[8,8],[1,1],[4,4]],\"end_point\":[[8,9],[2,3],[9,10],[5,6],[10,1],[1,2],[4,5],[6,7],[3,4],[7,8]],\"handle_primary\":[[8,[0.0,0.0]],[7,[0.0,0.0]],[1,[0.0,0.0]],[10,[0.0,0.0]],[5,[1.1368683772161605e-13,0.0]],[4,[5.818749999999909,2.0456767733078323]],[3,[0.0,0.0]],[2,[0.0,0.0]],[6,[0.0,0.0]],[9,[0.0,0.0]]],\"handle_end\":[[1,[0.0,0.0]],[6,[0.0,0.0]],[10,[2.600823045267589,7.538372631948732]],[3,[5.171433893884796,-5.185320665887616]],[4,[0.0,0.0]],[2,[0.0,0.0]],[5,[0.0,0.0]],[9,[0.0,0.0]],[7,[0.0,0.0]],[8,[1.1368683772161605e-13,0.0]]],\"stroke\":[[5,0],[6,0],[3,0],[10,0],[2,0],[4,0],[1,0],[9,0],[8,0],[7,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":10}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6015109908395573189,{\"inputs\":[{\"Node\":{\"node_id\":459,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-8.0,2.6666666666000083]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[402,{\"inputs\":[{\"Node\":{\"node_id\":406,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15552693212536925398,{\"inputs\":[{\"Node\":{\"node_id\":1598976462838094167,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0},{\"red\":0.328125,\"green\":0.19633627,\"blue\":0.17047119,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.3458831403130609,0.8154173283216788],\"end\":[0.29832402234636923,0.9999999999999992]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0},{\"red\":0.328125,\"green\":0.19633627,\"blue\":0.17047119,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.3458831403130609,0.8154173283216788],\"end\":[0.29832402234636923,0.9999999999999992]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[262,{\"inputs\":[{\"Node\":{\"node_id\":266,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[433,{\"inputs\":[{\"Node\":{\"node_id\":434,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[628.6154039265571,697.6163522012579]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[206.6666666666667,70.66666666666666]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[122,{\"inputs\":[{\"Node\":{\"node_id\":126,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.5,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0},{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0},{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.8261898755429384,0.6803505808618764],\"end\":[0.9730393010117852,0.6150128281109604]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.5,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0},{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0},{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.8261898755429384,0.6803505808618764],\"end\":[0.9730393010117852,0.6150128281109604]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7135480377162524224,{\"inputs\":[{\"Node\":{\"node_id\":487,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[28.82327697714288,-49.808276940773226]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":3.29474475571},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[184,{\"inputs\":[{\"Node\":{\"node_id\":188,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[75,{\"inputs\":[{\"Node\":{\"node_id\":76,\"output_index\":0}},{\"Node\":{\"node_id\":161,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[277,{\"inputs\":[{\"Node\":{\"node_id\":1453710883947581217,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.92156863,\"green\":0.7294118,\"blue\":0.2784314,\"alpha\":1.0},{\"red\":0.7176471,\"green\":0.28627452,\"blue\":0.16862746,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[1.0246769250512615,0.027819435424882025],\"end\":[0.2667933279762451,0.9665428076110212]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.92156863,\"green\":0.7294118,\"blue\":0.2784314,\"alpha\":1.0},{\"red\":0.7176471,\"green\":0.28627452,\"blue\":0.16862746,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[1.0246769250512615,0.027819435424882025],\"end\":[0.2667933279762451,0.9665428076110212]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[448,{\"inputs\":[{\"Node\":{\"node_id\":449,\"output_index\":0}},{\"Node\":{\"node_id\":455,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11155094820673141470,{\"inputs\":[{\"Node\":{\"node_id\":97478832511923699,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0},{\"red\":0.5058824,\"green\":0.26666668,\"blue\":0.20392157,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.08732233199581252,0.5212000945774993],\"end\":[0.9287098983518448,0.5070261975055625]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0},{\"red\":0.5058824,\"green\":0.26666668,\"blue\":0.20392157,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.08732233199581252,0.5212000945774993],\"end\":[0.9287098983518448,0.5070261975055625]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17257434333682934071,{\"inputs\":[{\"Node\":{\"node_id\":13606781735926093266,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[168,{\"inputs\":[{\"Node\":{\"node_id\":169,\"output_index\":0}},{\"Node\":{\"node_id\":184,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[230,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[3,[1.134036317002156,1.0722882682186752]],[1,[-0.03624142718978522,-0.003682959682299257]],[4,[0.2700473236113544,1.0652669412541609]],[2,[0.935534758874228,0.06746859421299994]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[2,2],[3,3],[1,1],[4,4]],\"end_point\":[[2,3],[4,1],[3,4],[1,2]],\"handle_primary\":[[3,[0.0,0.0]],[4,[0.0,0.0]],[2,[0.0,0.0]],[1,[0.0,0.0]]],\"handle_end\":[[4,[0.0,0.0]],[1,[0.0,0.0]],[3,[0.0,0.0]],[2,[0.0,0.0]]],\"stroke\":[[1,0],[2,0],[3,0],[4,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[90,{\"inputs\":[{\"Node\":{\"node_id\":93,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.5999119243817099,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0},{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0},{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0}]},\"gradient_type\":\"Radial\",\"start\":[0.3201759714645238,0.5902044022091868],\"end\":[1.025282605251574,0.18308301136073535]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.5999119243817099,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0},{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0},{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0}]},\"gradient_type\":\"Radial\",\"start\":[0.3201759714645238,0.5902044022091868],\"end\":[1.025282605251574,0.18308301136073535]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[463,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[4,[129.38888888888889,628.0555555555557]],[1,[-4.833333333333348,600.0555555555555]],[2,[-4.833333333333332,345.83333333333326]],[3,[80.05555555555559,484.94444444444446]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[3,3],[2,2],[4,4],[1,1]],\"end_point\":[[1,2],[4,1],[2,3],[3,4]],\"handle_primary\":[[1,[0.0,0.0]],[4,[0.0,0.0]],[3,[32.038317168599576,69.73045501401077]],[2,[0.0,0.0]]],\"handle_end\":[[4,[0.0,0.0]],[2,[-45.33333333333338,-98.66666666666669]],[3,[0.0,0.0]],[1,[0.0,0.0]]],\"stroke\":[[2,0],[1,0],[3,0],[4,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[292,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":312,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17414691604179185270,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[1,[546.1069958847736,566.7818930041152]],[3,[539.3641975308641,608.7633744855966]],[4,[547.6604938271604,606.7880658436213]],[2,[530.2777777777771,564.9115226337452]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[1,1],[2,2],[3,3],[4,4]],\"end_point\":[[2,3],[3,4],[4,1],[1,2]],\"handle_primary\":[[4,[0.0,0.0]],[3,[0.0,0.0]],[2,[0.0,0.0]],[1,[0.0,0.0]]],\"handle_end\":[[3,[0.0,0.0]],[1,[0.0,0.0]],[2,[0.0,0.0]],[4,[0.0,0.0]]],\"stroke\":[[1,0],[2,0],[4,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[323,{\"inputs\":[{\"Node\":{\"node_id\":321,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeAlign\":\"Center\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeCap\":\"Butt\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeJoin\":\"Miter\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"PaintOrder\":\"StrokeAbove\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"VecF64\":[]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::StrokeNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[214,{\"inputs\":[{\"Node\":{\"node_id\":218,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6272196533192700024,{\"inputs\":[{\"Node\":{\"node_id\":481,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[28.815503095243457,-49.74366671015599]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":3.29474475571},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1.0,1.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[74,{\"inputs\":[{\"Node\":{\"node_id\":75,\"output_index\":0}},{\"Node\":{\"node_id\":81,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[105,{\"inputs\":[{\"Node\":{\"node_id\":108,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.7721675713724807,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0},{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0},{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0}]},\"gradient_type\":\"Radial\",\"start\":[0.401555786122616,0.773953065673463],\"end\":[0.898982212168407,0.4370729973098963]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.7721675713724807,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0},{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0},{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0}]},\"gradient_type\":\"Radial\",\"start\":[0.401555786122616,0.773953065673463],\"end\":[0.898982212168407,0.4370729973098963]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17173383864410319040,{\"inputs\":[{\"Node\":{\"node_id\":15277819403265847073,\"output_index\":0}},{\"Node\":{\"node_id\":15552693212536925398,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[478,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":7135480377162524224,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1635416892097245588,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":11472292186872186521,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6926019345498826421,{\"inputs\":[{\"Node\":{\"node_id\":989999757220954936,\"output_index\":0}},{\"Node\":{\"node_id\":17020523203516467057,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17911294938421300842,{\"inputs\":[{\"Node\":{\"node_id\":17414691604179185270,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[400,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12],\"remove\":[],\"delta\":[[5,[482.47530864197535,316.35185185185185]],[7,[411.7592592592593,261.83333333333337]],[3,[499.1666666666666,380.94444444444446]],[11,[378.8703703703703,545.3888888888889]],[10,[378.8703703703703,472.7962962962963]],[1,[531.0946502057612,568.230452674897]],[12,[399.3148148148148,564.0555555555555]],[4,[487.3148148148147,333.24074074074076]],[8,[381.7345679012346,268.5493827160494]],[2,[513.0925925925925,455.6111111111111]],[6,[457.9814814814815,279.4135802469136]],[9,[378.57407407407413,324.0555555555556]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12],\"remove\":[],\"start_point\":[[4,4],[3,3],[1,1],[2,2],[10,10],[9,9],[5,5],[7,7],[8,8],[6,6],[11,11],[12,12]],\"end_point\":[[12,1],[5,6],[6,7],[1,2],[7,8],[9,10],[11,12],[10,11],[3,4],[8,9],[4,5],[2,3]],\"handle_primary\":[[3,[-1.7777777777777717,-12.444444444444455]],[2,[-6.51851851851859,-35.55555555555554]],[5,[-2.1728395061728065,-7.703703703703695]],[10,[-0.8888888888888573,38.81481481481478]],[7,[-5.925925925925924,-0.9876543209876444]],[6,[-20.345679012345727,-8.691358024691397]],[1,[0.0,0.0]],[9,[0.1975308641974607,16.395061728395035]],[11,[0.0,6.518518518518476]],[12,[22.22222222222223,-1.1851851851852189]],[8,[-6.716049382716051,13.62962962962962]],[4,[0.0,0.0]]],\"handle_end\":[[4,[2.1728395061728065,7.703703703703695]],[3,[8.888888888888971,23.407407407407447]],[10,[0.0,-6.518518518518476]],[5,[20.345679012345784,8.691358024691397]],[11,[-22.22222222222223,1.1851851851852189]],[6,[5.925925925925924,0.9876543209876444]],[9,[0.8888888888888573,-38.81481481481478]],[7,[6.716049382716051,-13.62962962962962]],[12,[-37.99794238683137,-21.306584362139915]],[2,[1.7777777777777717,12.444444444444455]],[1,[6.51851851851859,35.55555555555554]],[8,[-0.1975308641974607,-16.395061728395035]]],\"stroke\":[[4,0],[5,0],[11,0],[3,0],[12,0],[8,0],[10,0],[6,0],[7,0],[1,0],[2,0],[9,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":12}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[229,{\"inputs\":[{\"Node\":{\"node_id\":227,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[829.8099807176391,565.8945401302792]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":-112.21561764131614},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[3.920156284886552,12.362329004080864]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-7.194955915925541,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4454263454059119441,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[1,[0.5,0.0]],[2,[1.0,0.5]],[4,[0.0,0.5]],[3,[0.5,1.0]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[2,2],[4,4],[1,1],[3,3]],\"end_point\":[[4,1],[2,3],[1,2],[3,4]],\"handle_primary\":[[2,[0.0,0.27589238888950707]],[1,[0.27589238888950707,0.0]],[3,[-0.275892388889507,0.0]],[4,[0.0,-0.275892388889507]]],\"handle_end\":[[2,[0.27589238888950707,0.0]],[4,[-0.275892388889507,0.0]],[3,[0.0,0.27589238888950707]],[1,[0.0,-0.275892388889507]]],\"stroke\":[[4,0],[1,0],[3,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[260,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5],\"remove\":[],\"delta\":[[4,[0.4707515606101555,1.0016674771193048]],[1,[-0.06772020100134477,-0.27125764892979654]],[2,[0.8103689541744266,-0.2611110184526325]],[3,[1.1461889241405476,1.0977967891967286]],[5,[0.05417500861004592,0.8211321210533473]]]},\"segments\":{\"add\":[1,2,3,4,5],\"remove\":[],\"start_point\":[[2,2],[4,4],[3,3],[5,5],[1,1]],\"end_point\":[[4,5],[2,3],[1,2],[5,1],[3,4]],\"handle_primary\":[[5,[0.0,0.0]],[2,[0.0,0.0]],[4,[-0.4165765520001096,-0.1805353560659575]],[1,[0.0,0.0]],[3,[-2.220446049250313e-16,-2.220446049250313e-16]]],\"handle_end\":[[5,[0.0,0.0]],[2,[0.0,0.0]],[4,null],[1,[0.0,0.0]],[3,[0.2875939062231115,0.06333186265853907]]],\"stroke\":[[2,0],[5,0],[3,0],[1,0],[4,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":5}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[120,{\"inputs\":[{\"Node\":{\"node_id\":1635416892097245588,\"output_index\":0}},{\"Node\":{\"node_id\":140,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10486443711686704000,{\"inputs\":[{\"Node\":{\"node_id\":5714505144727602368,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0},{\"red\":0.5058824,\"green\":0.26666668,\"blue\":0.20392157,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.15803798551484238,0.5113172122965266],\"end\":[0.8867475285247997,0.49951059174236745]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0},{\"red\":0.5058824,\"green\":0.26666668,\"blue\":0.20392157,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.15803798551484238,0.5113172122965266],\"end\":[0.8867475285247997,0.49951059174236745]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[291,{\"inputs\":[{\"Node\":{\"node_id\":292,\"output_index\":0}},{\"Node\":{\"node_id\":306,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[493,{\"inputs\":[{\"Node\":{\"node_id\":496,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.33108336235841895,0.6158954287284122,1.0],\"midpoint\":[0.5,0.5,0.5,0.5],\"color\":[{\"red\":0.16152954,\"green\":0.5000886,\"blue\":0.5234375,\"alpha\":1.0},{\"red\":0.2509804,\"green\":0.54509807,\"blue\":0.5176471,\"alpha\":1.0},{\"red\":0.48235294,\"green\":0.5568628,\"blue\":0.4,\"alpha\":1.0},{\"red\":0.73333335,\"green\":0.49411765,\"blue\":0.23137255,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.5224609375,-0.000170829498767594],\"end\":[0.5224609375,0.9116247106204544]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.33108336235841895,0.6158954287284122,1.0],\"midpoint\":[0.5,0.5,0.5,0.5],\"color\":[{\"red\":0.16152954,\"green\":0.5000886,\"blue\":0.5234375,\"alpha\":1.0},{\"red\":0.2509804,\"green\":0.54509807,\"blue\":0.5176471,\"alpha\":1.0},{\"red\":0.48235294,\"green\":0.5568628,\"blue\":0.4,\"alpha\":1.0},{\"red\":0.73333335,\"green\":0.49411765,\"blue\":0.23137255,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.5224609375,-0.000170829498767594],\"end\":[0.5224609375,0.9116247106204544]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11807598261442997948,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"delta\":[[1,[527.0733882030179,547.0898491083676]],[4,[495.38065843621376,614.5137174211251]],[6,[530.2777777777774,564.9115226337451]],[3,[481.882716049383,533.8333333333337]],[5,[541.9979423868313,611.7921810699589]],[2,[500.121399176955,531.6385459533608]]]},\"segments\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"start_point\":[[3,3],[6,6],[1,1],[5,5],[2,2],[4,4]],\"end_point\":[[2,3],[1,2],[6,1],[5,6],[3,4],[4,5]],\"handle_primary\":[[2,[5.684341886080804e-14,0.0]],[6,[0.0,0.0]],[5,[0.0,0.0]],[4,[0.0,0.0]],[3,[0.0,0.0]],[1,[0.0,0.0]]],\"handle_end\":[[1,[16.644617182340255,-0.4357059391355733]],[4,[0.0,0.0]],[3,[0.0,0.0]],[5,[0.0,0.0]],[6,[0.0,0.0]],[2,[0.0,0.0]]],\"stroke\":[[6,0],[5,0],[3,0],[1,0],[2,0],[4,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":6}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[182,{\"inputs\":[{\"Node\":{\"node_id\":12768614558324028960,\"output_index\":0}},{\"Node\":{\"node_id\":268,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3930114406985796561,{\"inputs\":[{\"Node\":{\"node_id\":4454263454059119441,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":50.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"blending_nodes::OpacityNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12875520257830460085,{\"inputs\":[{\"Node\":{\"node_id\":11891167879168294182,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9778003574990260202,{\"inputs\":[{\"Node\":{\"node_id\":6926019345498826421,\"output_index\":0}},{\"Node\":{\"node_id\":5364427239360309137,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[244,{\"inputs\":[{\"Node\":{\"node_id\":248,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[415,{\"inputs\":[{\"Node\":{\"node_id\":418,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.5,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.827451,\"green\":0.36862746,\"blue\":0.20784314,\"alpha\":1.0},{\"red\":0.827451,\"green\":0.36862746,\"blue\":0.20784314,\"alpha\":0.80859375},{\"red\":0.827451,\"green\":0.36862746,\"blue\":0.20784314,\"alpha\":0.0}]},\"gradient_type\":\"Radial\",\"start\":[0.30473369160482133,-0.27613184386946],\"end\":[0.66690549049424,0.16136781742013184]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.5,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.827451,\"green\":0.36862746,\"blue\":0.20784314,\"alpha\":1.0},{\"red\":0.827451,\"green\":0.36862746,\"blue\":0.20784314,\"alpha\":0.80859375},{\"red\":0.827451,\"green\":0.36862746,\"blue\":0.20784314,\"alpha\":0.0}]},\"gradient_type\":\"Radial\",\"start\":[0.30473369160482133,-0.27613184386946],\"end\":[0.66690549049424,0.16136781742013184]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[275,{\"inputs\":[{\"Node\":{\"node_id\":11427960919145580782,\"output_index\":0}},{\"Node\":{\"node_id\":283,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[446,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[4,[0.0,1.0]],[3,[1.0,1.0]],[1,[0.0,0.0]],[2,[1.0,0.0]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[4,4],[2,2],[3,3],[1,1]],\"end_point\":[[4,1],[1,2],[2,3],[3,4]],\"handle_primary\":[[2,[0.0,0.0]],[3,[0.0,0.0]],[4,[0.0,0.0]],[1,[0.0,0.0]]],\"handle_end\":[[3,[0.0,0.0]],[4,[0.0,0.0]],[1,[0.0,0.0]],[2,[0.0,0.0]]],\"stroke\":[[3,0],[1,0],[2,0],[4,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[477,{\"inputs\":[{\"Node\":{\"node_id\":478,\"output_index\":0}},{\"Node\":{\"node_id\":6272196533192700024,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[306,{\"inputs\":[{\"Node\":{\"node_id\":310,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[166,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":393,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11472292186872186521,{\"inputs\":[{\"Node\":{\"node_id\":4452902364641883403,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-1367.319046874664,107.29818643577867]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[4.24444444444444,0.8618453375356869]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[88,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":96,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[259,{\"inputs\":[{\"Node\":{\"node_id\":257,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[894.1788584769913,562.0196920444174]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":-133.24181654222085},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[15.813534861768243,49.86845076365074]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-7.194955915925463,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[430,{\"inputs\":[{\"Node\":{\"node_id\":433,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.09210526315789476,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.71875,\"green\":0.37165833,\"blue\":0.20214844,\"alpha\":1.0},{\"red\":0.7254902,\"green\":0.41960785,\"blue\":0.2,\"alpha\":1.0},{\"red\":0.79607844,\"green\":0.49411765,\"blue\":0.22745098,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.5000000000000004,0.0],\"end\":[0.5000000000000004,1.0]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.09210526315789476,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.71875,\"green\":0.37165833,\"blue\":0.20214844,\"alpha\":1.0},{\"red\":0.7254902,\"green\":0.41960785,\"blue\":0.2,\"alpha\":1.0},{\"red\":0.79607844,\"green\":0.49411765,\"blue\":0.22745098,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.5000000000000004,0.0],\"end\":[0.5000000000000004,1.0]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[119,{\"inputs\":[{\"Node\":{\"node_id\":120,\"output_index\":0}},{\"Node\":{\"node_id\":134,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[290,{\"inputs\":[{\"Node\":{\"node_id\":291,\"output_index\":0}},{\"Node\":{\"node_id\":300,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[321,{\"inputs\":[{\"Node\":{\"node_id\":325,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.9254902,\"green\":0.7176471,\"blue\":0.28627452,\"alpha\":1.0},{\"red\":0.7176471,\"green\":0.28627452,\"blue\":0.16862746,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.6317852697063873,0.02415325870852081],\"end\":[0.05910622380623476,0.9861828395407052]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.9254902,\"green\":0.7176471,\"blue\":0.28627452,\"alpha\":1.0},{\"red\":0.7176471,\"green\":0.28627452,\"blue\":0.16862746,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.6317852697063873,0.02415325870852081],\"end\":[0.05910622380623476,0.9861828395407052]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5364427239360309137,{\"inputs\":[{\"Node\":{\"node_id\":12325841371509826180,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":75.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"blending_nodes::OpacityNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[181,{\"inputs\":[{\"Node\":{\"node_id\":182,\"output_index\":0}},{\"Node\":{\"node_id\":262,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14335659566300901430,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":14579754335592291854,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[212,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8],\"remove\":[],\"delta\":[[1,[957.8271604938273,462.1234567901235]],[7,[984.9112747301664,495.90740740740745]],[2,[948.746913580247,472.10493827160496]],[5,[977.4876543209878,545.4876543209878]],[4,[922.2777777777778,549.4629629629628]],[3,[925.6111111111112,527.7098765432099]],[8,[965.6358024691358,465.1913580246914]],[6,[990.3271604938273,526.5246913580247]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8],\"remove\":[],\"start_point\":[[1,1],[8,8],[5,5],[7,7],[2,2],[3,3],[4,4],[6,6]],\"end_point\":[[8,1],[7,8],[3,4],[6,7],[2,3],[5,6],[1,2],[4,5]],\"handle_primary\":[[7,[0.0,0.0]],[5,[0.0,0.0]],[2,[-6.123456790123441,10.962962962962932]],[4,[0.0,0.0]],[3,[-2.469135802469168,9.975308641975287]],[1,[0.0,0.0]],[8,[0.0,0.0]],[6,[0.0,0.0]]],\"handle_end\":[[3,[0.0,0.0]],[4,[0.0,0.0]],[8,[0.0,0.0]],[1,[6.123456790123441,-10.96296296296299]],[5,[0.0,0.0]],[6,[0.0,0.0]],[2,[2.469135802468827,-9.975308641975287]],[7,[7.407407407407391,6.814814814814838]]],\"stroke\":[[8,0],[2,0],[1,0],[4,0],[7,0],[5,0],[6,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":8}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14433811491576609500,{\"inputs\":[{\"Node\":{\"node_id\":9570557034533539493,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.5058824,\"green\":0.26666668,\"blue\":0.20392157,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.5058824,\"green\":0.26666668,\"blue\":0.20392157,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[274,{\"inputs\":[{\"Node\":{\"node_id\":275,\"output_index\":0}},{\"Node\":{\"node_id\":277,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[445,{\"inputs\":[{\"Node\":{\"node_id\":446,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[83.1666666666668,614.1795271996941]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[941.3333333333331,154.32047280030588]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[103,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":111,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[134,{\"inputs\":[{\"Node\":{\"node_id\":138,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[165,{\"inputs\":[{\"Node\":{\"node_id\":166,\"output_index\":0}},{\"Node\":{\"node_id\":16821952675128396603,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10278740841813346388,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7],\"remove\":[],\"delta\":[[1,[744.3996087994717,586.9732002235432]],[5,[710.9477975918305,613.6358024691357]],[6,[729.3449931412895,610.3875171467763]],[7,[779.7592592592597,612.6204267490609]],[4,[706.1680384087791,607.8415637860082]],[3,[698.0912208504803,600.3792866941013]],[2,[734.9571457603006,587.5194584158918]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7],\"remove\":[],\"start_point\":[[1,1],[5,5],[7,7],[4,4],[3,3],[6,6],[2,2]],\"end_point\":[[7,1],[5,6],[1,2],[3,4],[4,5],[2,3],[6,7]],\"handle_primary\":[[3,[0.0,0.0]],[4,[2.1947873799731497,4.691071467853249]],[6,[15.119646395366544,2.853223593964344]],[2,[0.0,0.0]],[5,[7.452522481329197,2.721536351166037]],[1,[0.0,0.0]],[7,[0.0,0.0]]],\"handle_end\":[[1,[0.0,0.0]],[7,[9.28638926992835,13.56378600823075]],[6,[0.0,0.0]],[4,null],[5,null],[2,[6.496570644718986,-11.149519890260422]],[3,[-2.494608558449272,-5.331900091455282]]],\"stroke\":[[7,0],[2,0],[6,0],[4,0],[1,0],[3,0],[5,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":7}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[196,{\"inputs\":[{\"Node\":{\"node_id\":200,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[398,{\"inputs\":[{\"Node\":{\"node_id\":396,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeAlign\":\"Center\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeCap\":\"Butt\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeJoin\":\"Miter\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"PaintOrder\":\"StrokeAbove\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"VecF64\":[]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::StrokeNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[227,{\"inputs\":[{\"Node\":{\"node_id\":230,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":75.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"blending_nodes::OpacityNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[87,{\"inputs\":[{\"Node\":{\"node_id\":88,\"output_index\":0}},{\"Node\":{\"node_id\":90,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[118,{\"inputs\":[{\"Node\":{\"node_id\":119,\"output_index\":0}},{\"Node\":{\"node_id\":128,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[491,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"delta\":[[2,[867.3888888888889,344.05555555555554]],[1,[903.3333333333331,336.44444444444446]],[3,[858.2777777777777,377.8333333333333]],[4,[851.3888888888889,542.2777777777777]],[5,[876.9444444444443,582.1008216600221]],[6,[908.5000000000005,581.0432098765432]]]},\"segments\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"start_point\":[[6,6],[4,4],[2,2],[3,3],[5,5],[1,1]],\"end_point\":[[3,4],[6,1],[4,5],[2,3],[1,2],[5,6]],\"handle_primary\":[[4,[-2.888888888888914,28.66666666666663]],[5,[0.0,0.0]],[6,[0.0,0.0]],[2,[-9.555555555555657,12.444444444444455]],[1,[0.0,0.0]],[3,[-0.4444444444444571,14.444444444444455]]],\"handle_end\":[[2,[0.4444444444444571,-14.444444444444455]],[6,[0.16666666666685614,-0.2777777777777146]],[1,[9.555555555555657,-12.444444444444455]],[3,[2.888888888888914,-28.66666666666663]],[5,[0.0,0.0]],[4,[0.0,0.0]]],\"stroke\":[[6,0],[5,0],[3,0],[2,0],[4,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":6}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[180,{\"inputs\":[{\"Node\":{\"node_id\":181,\"output_index\":0}},{\"Node\":{\"node_id\":256,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11345069121502219134,{\"inputs\":[{\"Node\":{\"node_id\":12068777759187203228,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[413,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":436,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[242,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5],\"remove\":[],\"delta\":[[3,[819.0679012345677,520.2695473251028]],[4,[812.5713305898489,549.7821216278006]],[1,[842.2716049382715,561.8070416095107]],[5,[837.913808870599,583.4355281207133]],[2,[821.8187014174667,518.4112940100595]]]},\"segments\":{\"add\":[1,2,3,4,5],\"remove\":[],\"start_point\":[[3,3],[2,2],[4,4],[5,5],[1,1]],\"end_point\":[[1,2],[3,4],[5,1],[2,3],[4,5]],\"handle_primary\":[[2,[0.0,0.0]],[4,[0.0,0.0]],[3,[-1.4046639231823974,2.1801554641060648]],[1,[0.0,0.0]],[5,[0.0,0.0]]],\"handle_end\":[[4,[0.0,0.0]],[2,[1.4046639231823974,-2.1801554641060648]],[5,[0.0,0.0]],[3,[0.0,0.0]],[1,[10.88614540466392,16.621856424325642]]],\"stroke\":[[4,0],[1,0],[3,0],[5,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":5}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[102,{\"inputs\":[{\"Node\":{\"node_id\":103,\"output_index\":0}},{\"Node\":{\"node_id\":105,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[475,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"delta\":[[6,[102.27777777777776,528.0555555555553]],[4,[85.15294924554185,45.49314128943759]],[5,[172.5,512.0555555555555]],[3,[81.20964791952444,29.23708276177412]],[2,[46.49999999999994,177.5]],[1,[22.827133919383556,312.5]]]},\"segments\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"start_point\":[[4,4],[6,6],[2,2],[3,3],[5,5],[1,1]],\"end_point\":[[5,6],[4,5],[3,4],[2,3],[1,2],[6,1]],\"handle_primary\":[[1,[0.0,0.0]],[3,[0.0,0.0]],[2,[15.777777777777828,-79.0]],[4,[0.0,0.0]],[5,[0.0,0.0]],[6,[0.0,0.0]]],\"handle_end\":[[5,[0.0,0.0]],[6,[0.0,0.0]],[2,[0.0,0.0]],[1,[-14.820678206547353,74.20776200602205]],[4,[0.0,0.0]],[3,[0.0,0.0]]],\"stroke\":[[2,0],[4,0],[5,0],[3,0],[1,0],[6,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":6}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[304,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10],\"remove\":[],\"delta\":[[5,[744.3996087994716,586.9732002235431]],[4,[699.4958847736627,594.497256515775]],[2,[658.574074074074,609.6851851851851]],[8,[708.7139917695473,601.783950617284]],[10,[712.0500685871053,614.4420508944315]],[6,[717.5809327846364,593.2681755829904]],[3,[680.2695473251027,600.730452674897]],[9,[713.2108672458469,610.5533455265964]],[7,[702.5246913580245,600.8931773149878]],[1,[645.3333333333333,614.013717421125]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10],\"remove\":[],\"start_point\":[[6,6],[1,1],[5,5],[8,8],[2,2],[3,3],[9,9],[10,10],[4,4],[7,7]],\"end_point\":[[9,10],[1,2],[8,9],[2,3],[3,4],[10,1],[7,8],[4,5],[5,6],[6,7]],\"handle_primary\":[[9,[0.0,0.0]],[7,[0.0,0.0]],[5,[0.0,0.0]],[1,[0.0,0.0]],[6,[-10.886145404663694,2.8971193415636662]],[3,[7.374485596707928,-0.92181069958815]],[8,[0.0,0.0]],[4,[4.444444444444002,-5.662551440328798]],[10,[-58.35223289132739,3.851425709744945]],[2,[4.740740740740762,-4.148148148148152]]],\"handle_end\":[[1,[-4.740740740740762,4.148148148148152]],[8,[-2.5361987501905787,-3.706752019509281]],[9,[-0.08779149519853036,-2.2109123484780184]],[7,[-2.89711934156378,-1.975308641975289]],[3,[-1.6866098186769705,2.1488658430550913]],[4,[-5.794238683127446,-2.0192043895747247]],[6,[0.0,0.0]],[2,[-6.174173455107166,0.7717716818881399]],[10,null],[5,[10.516302710276136,-2.79869346321857]]],\"stroke\":[[4,0],[9,0],[6,0],[10,0],[2,0],[1,0],[5,0],[3,0],[8,0],[7,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":10}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[164,{\"inputs\":[{\"Node\":{\"node_id\":165,\"output_index\":0}},{\"Node\":{\"node_id\":318,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[226,{\"inputs\":[{\"Node\":{\"node_id\":229,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0},{\"red\":0.5058824,\"green\":0.26666668,\"blue\":0.20392157,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.24641848672125644,0.5269240260497682],\"end\":[0.8641268166855021,0.5169975680471844]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0},{\"red\":0.5058824,\"green\":0.26666668,\"blue\":0.20392157,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.24641848672125644,0.5269240260497682],\"end\":[0.8641268166855021,0.5169975680471844]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[428,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[4,[0.1596715565350542,1.0]],[3,[0.7349403737393546,1.0000000000000002]],[1,[0.17362079214327678,-0.41509433962264153]],[2,[0.8076864692090735,-0.4339622641509434]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[4,4],[2,2],[1,1],[3,3]],\"end_point\":[[2,3],[1,2],[3,4],[4,1]],\"handle_primary\":[[3,[0.0,0.0]],[2,[0.0,0.0]],[4,[0.0,0.0]],[1,[0.0,0.0]]],\"handle_end\":[[3,[0.0,0.0]],[1,[0.0,0.0]],[2,[0.0,0.0]],[4,[0.0,0.0]]],\"stroke\":[[4,0],[2,0],[3,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[257,{\"inputs\":[{\"Node\":{\"node_id\":260,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":75.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"blending_nodes::OpacityNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[10544930474333783117,{\"inputs\":[{\"Node\":{\"node_id\":17173383864410319040,\"output_index\":0}},{\"Node\":{\"node_id\":4633399390154487467,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[117,{\"inputs\":[{\"Node\":{\"node_id\":118,\"output_index\":0}},{\"Node\":{\"node_id\":122,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[459,{\"inputs\":[{\"Node\":{\"node_id\":463,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[319,{\"inputs\":[{\"Node\":{\"node_id\":290,\"output_index\":0}},{\"Node\":{\"node_id\":329,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12068777759187203228,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10],\"remove\":[],\"delta\":[[3,[680.2695473251027,600.730452674897]],[9,[713.2108672458469,610.5533455265964]],[6,[717.5809327846364,593.2681755829904]],[8,[708.7139917695473,601.783950617284]],[4,[699.4958847736627,594.497256515775]],[2,[658.574074074074,609.6851851851851]],[1,[645.3333333333333,614.013717421125]],[10,[712.0500685871053,614.4420508944315]],[7,[702.5246913580245,600.8931773149878]],[5,[744.3996087994716,586.9732002235431]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10],\"remove\":[],\"start_point\":[[8,8],[6,6],[9,9],[10,10],[3,3],[1,1],[2,2],[5,5],[4,4],[7,7]],\"end_point\":[[3,4],[9,10],[4,5],[10,1],[7,8],[6,7],[5,6],[8,9],[2,3],[1,2]],\"handle_primary\":[[3,[7.374485596707928,-0.92181069958815]],[8,[0.0,0.0]],[7,[0.0,0.0]],[10,[-58.35223289132739,3.851425709744945]],[9,[0.0,0.0]],[5,[0.0,0.0]],[4,[4.444444444444002,-5.662551440328798]],[1,[0.0,0.0]],[2,[4.740740740740762,-4.148148148148152]],[6,[-10.886145404663694,2.8971193415636662]]],\"handle_end\":[[3,[-1.6866098186769705,2.1488658430550913]],[4,[-5.794238683127446,-2.0192043895747247]],[6,[0.0,0.0]],[7,[-2.89711934156378,-1.975308641975289]],[1,[-4.740740740740762,4.148148148148152]],[9,[-0.08779149519853036,-2.2109123484780184]],[8,[-2.5361987501905787,-3.706752019509281]],[2,[-6.174173455107166,0.7717716818881399]],[10,null],[5,[10.516302710276136,-2.79869346321857]]],\"stroke\":[[6,0],[4,0],[7,0],[9,0],[2,0],[5,0],[1,0],[10,0],[3,0],[8,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":10}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[179,{\"inputs\":[{\"Node\":{\"node_id\":180,\"output_index\":0}},{\"Node\":{\"node_id\":250,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16815500381887058038,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Artboard\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":74,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[1024.0,768.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":1}},{\"Value\":{\"tagged_value\":{\"String\":\"Artboard\"},\"exposed\":false}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":4}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graph_craft::document::value::TaggedValue\",\"alias\":null}},\"import_index\":5}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::artboard::CreateArtboardNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Import\":{\"import_type\":{\"Fn\":[{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::artboard::Artboard>\",\"alias\":null}}]},\"import_index\":0}},{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[412,{\"inputs\":[{\"Node\":{\"node_id\":413,\"output_index\":0}},{\"Node\":{\"node_id\":421,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[272,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"delta\":[[6,[873.7839506172841,571.9567901234568]],[3,[896.2037037037037,507.6111111111111]],[4,[919.7592592592592,507.7592592592593]],[1,[872.6913580246915,564.7407407407408]],[2,[885.701646090535,534.2283950617283]],[5,[927.7592592592592,537.8333333333333]]]},\"segments\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"start_point\":[[6,6],[3,3],[5,5],[2,2],[1,1],[4,4]],\"end_point\":[[1,2],[6,1],[3,4],[4,5],[2,3],[5,6]],\"handle_primary\":[[4,[9.641681333516315,12.166883587532825]],[6,[0.0,0.0]],[2,[2.6337448559671657,-9.349794238682987]],[3,[5.629629629629449,-5.185185185185162]],[5,[0.0,0.0]],[1,[8.404909667028619,-14.163252363220296]]],\"handle_end\":[[1,null],[4,[0.0,0.0]],[3,[-6.222222222222285,-7.851851851851904]],[6,[0.0,0.0]],[5,[0.0,0.0]],[2,[-6.29752559155645,5.8003525185389435]]],\"stroke\":[[3,0],[6,0],[2,0],[1,0],[4,0],[5,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":6}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[132,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5],\"remove\":[],\"delta\":[[3,[153.6522633744856,666.8374485596709]],[5,[170.5082304526749,714.508230452675]],[2,[158.95389422344155,655.6901143957208]],[1,[172.61522633744855,706.3436213991771]],[4,[158.12962962962962,718.0637860082305]]]},\"segments\":{\"add\":[1,2,3,4,5],\"remove\":[],\"start_point\":[[2,2],[4,4],[1,1],[5,5],[3,3]],\"end_point\":[[2,3],[1,2],[3,4],[5,1],[4,5]],\"handle_primary\":[[2,[0.0,0.0]],[3,[-1.8436213991769537,9.744855967078138]],[1,[0.0,0.0]],[5,[3.160493827160508,-3.5555555555554292]],[4,[0.0,0.0]]],\"handle_end\":[[5,null],[4,[-3.403056460676396,3.828438518260782]],[2,[1.8436213991768968,-9.744855967078138]],[3,[0.0,0.0]],[1,[10.930041152263357,25.448559670781947]]],\"stroke\":[[3,0],[5,0],[4,0],[2,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":5}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[18142347460553706128,{\"inputs\":[{\"Node\":{\"node_id\":3719764965605527929,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":75.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"blending_nodes::OpacityNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5714505144727602368,{\"inputs\":[{\"Node\":{\"node_id\":18142347460553706128,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[742.4503588311712,593.3522045638366]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":-54.607358405925375},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[11.868580002725764,37.42791872115287]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-7.194955915925435,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3719764965605527929,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"delta\":[[1,[0.024789182815927936,-0.19742232174172225]],[5,[0.5092009949861728,0.9569233045341342]],[6,[-0.007635827307500006,0.8135210708932508]],[4,[1.0925954941660798,1.0006513038165834]],[3,[0.9294778693529006,0.07804966382593222]],[2,[0.5284291926980893,-0.05749241759918103]]]},\"segments\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"start_point\":[[3,3],[5,5],[6,6],[2,2],[4,4],[1,1]],\"end_point\":[[3,4],[2,3],[1,2],[6,1],[4,5],[5,6]],\"handle_primary\":[[3,[0.0,0.0]],[1,[0.0,0.0]],[6,[0.0,0.0]],[4,[0.0,0.0]],[5,[-0.28124758738050376,-0.047835328360902984]],[2,[0.21441988872806772,0.05895880273641681]]],\"handle_end\":[[6,[0.0,0.0]],[4,[0.26857587477611267,0.04568009019878494]],[1,[-0.16942059711236046,-0.046585396643413435]],[3,[0.0,0.0]],[2,[0.0,0.0]],[5,[0.0,0.0]]],\"stroke\":[[6,0],[4,0],[3,0],[5,0],[2,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":6}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[194,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5],\"remove\":[],\"delta\":[[1,[-0.15531318767467384,0.11366419216517]],[3,[0.7287108039915611,0.06963660702488284]],[4,[0.9388148027481048,0.674134940686276]],[5,[0.038029134760865314,0.7285470752399478]],[2,[0.2647731761418837,0.17920265855050785]]]},\"segments\":{\"add\":[1,2,3,4,5],\"remove\":[],\"start_point\":[[4,4],[2,2],[3,3],[5,5],[1,1]],\"end_point\":[[3,4],[2,3],[4,5],[5,1],[1,2]],\"handle_primary\":[[2,[0.12249986382303002,-0.04615791866776875]],[3,[-0.06001521816071698,0.06545334966568245]],[1,[0.0,0.0]],[4,[0.0,0.0]],[5,[0.0,0.0]]],\"handle_end\":[[2,[-0.2265909018579063,0.03511994027079236]],[4,[0.0,0.0]],[1,[-0.1993257782423989,0.03454533724430142]],[5,[0.0,0.0]],[3,[0.0,0.0]]],\"stroke\":[[3,0],[4,0],[1,0],[5,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":5}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17020523203516467057,{\"inputs\":[{\"Node\":{\"node_id\":7148230379224894975,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0},{\"red\":0.328125,\"green\":0.19633627,\"blue\":0.17047119,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.3458831403130609,0.8154173283216788],\"end\":[0.29832402234636923,0.9999999999999992]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0},{\"red\":0.328125,\"green\":0.19633627,\"blue\":0.17047119,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.3458831403130609,0.8154173283216788],\"end\":[0.29832402234636923,0.9999999999999992]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[396,{\"inputs\":[{\"Node\":{\"node_id\":400,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.7176471,\"green\":0.28627452,\"blue\":0.16862746,\"alpha\":1.0},{\"red\":0.9098039,\"green\":0.6862745,\"blue\":0.27058825,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.03910157250347135,0.9695090157857073],\"end\":[0.36170698072938423,0.042039957474503786]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.7176471,\"green\":0.28627452,\"blue\":0.16862746,\"alpha\":1.0},{\"red\":0.9098039,\"green\":0.6862745,\"blue\":0.27058825,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.03910157250347135,0.9695090157857073],\"end\":[0.36170698072938423,0.042039957474503786]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[85,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":155,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[427,{\"inputs\":[{\"Node\":{\"node_id\":428,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[583.9293351067386,697.6163522012579]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[206.6666666666667,70.66666666666666]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[256,{\"inputs\":[{\"Node\":{\"node_id\":259,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0},{\"red\":0.5058824,\"green\":0.26666668,\"blue\":0.20392157,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.19879202692409503,0.5201781689919184],\"end\":[0.8398128222157668,0.5168778892557565]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0},{\"red\":0.5058824,\"green\":0.26666668,\"blue\":0.20392157,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.19879202692409503,0.5201781689919184],\"end\":[0.8398128222157668,0.5168778892557565]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[287,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7],\"remove\":[],\"delta\":[[1,[954.864197530864,116.14814814814812]],[5,[904.5,283.46296296296293]],[3,[913.619341563786,134.1625514403292]],[7,[944.7962962962962,568.2037037037037]],[4,[910.06378600823,174.98559670781898]],[6,[898.2777777777778,520.5]],[2,[934.9526748971192,114.67283950617282]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7],\"remove\":[],\"start_point\":[[1,1],[3,3],[5,5],[4,4],[2,2],[7,7],[6,6]],\"end_point\":[[6,7],[4,5],[3,4],[2,3],[5,6],[1,2],[7,1]],\"handle_primary\":[[1,[0.0,0.0]],[6,[0.0,0.0]],[3,[-2.2386831275719032,10.008230452674894]],[7,[0.0,0.0]],[2,[-9.481481481481635,1.8436213991769392]],[5,[-3.5555555555556566,34.37037037037038]],[4,[-0.7901234567891606,29.102880658436305]]],\"handle_end\":[[1,[9.481481481481635,-1.8436213991769392]],[3,[0.370701337431683,-13.654165928739786]],[5,[0.0,0.0]],[6,[0.0,0.0]],[7,[0.0,0.0]],[2,[2.677546335770103,-11.970207148147836]],[4,[3.5555555555554292,-34.37037037037038]]],\"stroke\":[[3,0],[1,0],[6,0],[7,0],[5,0],[2,0],[4,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":7}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[318,{\"inputs\":[{\"Node\":{\"node_id\":319,\"output_index\":0}},{\"Node\":{\"node_id\":323,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[178,{\"inputs\":[{\"Node\":{\"node_id\":179,\"output_index\":0}},{\"Node\":{\"node_id\":244,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16821952675128396603,{\"inputs\":[{\"Node\":{\"node_id\":3885641499621884510,\"output_index\":0}},{\"Node\":{\"node_id\":36935169817407978,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[36935169817407978,{\"inputs\":[{\"Node\":{\"node_id\":15848750910363784662,\"output_index\":0}},{\"Node\":{\"node_id\":11279424538712841875,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[411,{\"inputs\":[{\"Node\":{\"node_id\":412,\"output_index\":0}},{\"Node\":{\"node_id\":415,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[442,{\"inputs\":[{\"Node\":{\"node_id\":445,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.14354235440709684,0.5,1.0],\"midpoint\":[0.5,0.5,0.5,0.5],\"color\":[{\"red\":0.50980395,\"green\":0.26666668,\"blue\":0.2,\"alpha\":1.0},{\"red\":0.5882353,\"green\":0.30588236,\"blue\":0.20392157,\"alpha\":1.0},{\"red\":0.6784314,\"green\":0.33333334,\"blue\":0.20784314,\"alpha\":1.0},{\"red\":0.76953125,\"green\":0.4772935,\"blue\":0.21943665,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.5063739376770537,0.01352247091271197],\"end\":[0.5049575070821529,1.015819012091678]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.14354235440709684,0.5,1.0],\"midpoint\":[0.5,0.5,0.5,0.5],\"color\":[{\"red\":0.50980395,\"green\":0.26666668,\"blue\":0.2,\"alpha\":1.0},{\"red\":0.5882353,\"green\":0.30588236,\"blue\":0.20392157,\"alpha\":1.0},{\"red\":0.6784314,\"green\":0.33333334,\"blue\":0.20784314,\"alpha\":1.0},{\"red\":0.76953125,\"green\":0.4772935,\"blue\":0.21943665,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.5063739376770537,0.01352247091271197],\"end\":[0.5049575070821529,1.015819012091678]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14205611254835578455,{\"inputs\":[{\"Node\":{\"node_id\":14335659566300901430,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-1339.7031164295145,65.50112655997924]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":-2.429481024944041},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[4.244995417859058,0.8619572141015625]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[2.80968031327194e-16,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[100,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[2,[0.9501947601024644,0.0]],[4,[0.0,1.0]],[3,[1.0,1.0]],[1,[-0.03917736275965821,1.5785983631388945e-15]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[4,4],[1,1],[2,2],[3,3]],\"end_point\":[[2,3],[1,2],[4,1],[3,4]],\"handle_primary\":[[3,[0.0,0.0]],[2,[0.0,0.0]],[4,[0.0,0.0]],[1,[0.0,0.0]]],\"handle_end\":[[1,[0.0,0.0]],[3,[0.0,0.0]],[4,[0.0,0.0]],[2,[0.0,0.0]]],\"stroke\":[[4,0],[1,0],[3,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[162,{\"inputs\":[{\"Node\":{\"node_id\":164,\"output_index\":0}},{\"Node\":{\"node_id\":274,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9422094883894860610,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"delta\":[[1,[527.0733882030179,547.0898491083676]],[4,[495.38065843621376,614.5137174211251]],[2,[500.121399176955,531.6385459533608]],[6,[530.2777777777774,564.9115226337451]],[5,[541.9979423868313,611.7921810699589]],[3,[481.882716049383,533.8333333333337]]]},\"segments\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"start_point\":[[1,1],[3,3],[4,4],[6,6],[2,2],[5,5]],\"end_point\":[[1,2],[2,3],[6,1],[4,5],[5,6],[3,4]],\"handle_primary\":[[3,[0.0,0.0]],[1,[0.0,0.0]],[6,[0.0,0.0]],[4,[0.0,0.0]],[2,[5.684341886080804e-14,0.0]],[5,[0.0,0.0]]],\"handle_end\":[[3,[0.0,0.0]],[2,[0.0,0.0]],[6,[0.0,0.0]],[5,[0.0,0.0]],[1,[16.644617182340255,-0.4357059391355733]],[4,[0.0,0.0]]],\"stroke\":[[1,0],[4,0],[5,0],[2,0],[3,0],[6,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":6}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[193,{\"inputs\":[{\"Node\":{\"node_id\":191,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[965.2687196297846,544.9034434174798]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":-98.54705053700596},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[17.59013219658168,55.471003102038694]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-7.194955915925513,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[224,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19],\"remove\":[],\"delta\":[[12,[837.1090534979422,574.2613168724279]],[4,[790.0967078189299,592.170781893004]],[11,[830.3930041152262,568.5987654320987]],[17,[877.4053497942385,607.1831275720164]],[15,[874.7716049382715,566.491769547325]],[18,[876.3518518518517,620.2201646090534]],[19,[742.2503429355281,620.0445816186556]],[3,[779.9567901234567,598.5451457288699]],[1,[741.4602194787379,611.9677640603566]],[6,[819.3312757201645,537.1255144032921]],[16,[858.4423868312756,599.2818930041151]],[7,[817.6193415637858,545.6851851851851]],[2,[773.1090534979423,610.0802469135801]],[10,[827.364197530864,555.9567901234567]],[14,[864.6316872427983,543.3148148148147]],[5,[819.0679012345677,520.2695473251028]],[13,[852.648148148148,535.9403292181069]],[8,[825.5205761316871,544.7633744855966]],[9,[825.2572016460904,550.6893004115226]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19],\"remove\":[],\"start_point\":[[11,11],[1,1],[13,13],[3,3],[18,18],[7,7],[5,5],[8,8],[19,19],[2,2],[10,10],[17,17],[9,9],[12,12],[6,6],[4,4],[15,15],[14,14],[16,16]],\"end_point\":[[14,15],[11,12],[9,10],[17,18],[3,4],[4,5],[10,11],[19,1],[18,19],[12,13],[15,16],[1,2],[6,7],[13,14],[16,17],[5,6],[2,3],[7,8],[8,9]],\"handle_primary\":[[12,[0.0,0.0]],[11,[0.658436213991763,2.1698525002639144]],[3,[5.530864197530832,-1.8969975807218589]],[7,[1.4485596707819468,2.633744855967052]],[2,[4.609053497942341,-0.9218106995883772]],[8,[0.0,0.0]],[10,[1.7119341563786747,1.316872427983526]],[15,[0.0,0.0]],[19,[-21.66213092273972,1.775584501863932]],[16,[0.0,0.0]],[18,[-1.843621399176982,1.975308641975289]],[5,[0.0,0.0]],[17,[0.0,0.0]],[1,[0.0,0.0]],[6,[0.0,0.0]],[9,[0.0,0.0]],[14,[3.68724279835385,7.90123456790127]],[4,[1.0534979423869115,-4.345679012345613]],[13,[0.0,0.0]]],\"handle_end\":[[9,[-1.7119341563786747,-1.316872427983526]],[18,[21.421124828532356,-1.7558299039781105]],[7,[-5.135802469135797,4.609053497942341]],[5,[-0.39506172839503506,-3.160493827160508]],[1,[-4.609053497942341,0.9218106995883772]],[17,[1.843621399176982,-1.975308641975289]],[6,[-1.4485596707819468,-2.633744855967052]],[12,[-7.637860082304542,12.641975308641918]],[3,[-1.0534979423869115,4.345679012345613]],[14,[0.0,0.0]],[4,[-16.987654320987644,28.049382716049426]],[13,[-3.68724279835385,-7.90123456790127]],[15,[2.765432098765359,-11.851851851851848]],[8,[1.1851851851852189,-2.502057613168745]],[11,[-2.10699588477371,-0.3950617283951487]],[16,[-5.1358024691359105,-5.00411522633749]],[19,[0.0,0.0]],[2,[-5.530864197530832,1.8969975807218589]],[10,[-0.658436213991763,-2.1698525002639144]]],\"stroke\":[[19,0],[1,0],[2,0],[13,0],[18,0],[12,0],[7,0],[8,0],[14,0],[3,0],[6,0],[16,0],[4,0],[11,0],[10,0],[9,0],[17,0],[5,0],[15,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":19}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[14579754335592291854,{\"inputs\":[{\"Node\":{\"node_id\":1644624352314732667,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0},{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":0.0}]},\"gradient_type\":\"Radial\",\"start\":[0.49999999999999994,0.5000000000000009],\"end\":[0.9003149237651733,0.5000000000000009]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0},{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":0.0}]},\"gradient_type\":\"Radial\",\"start\":[0.49999999999999994,0.5000000000000009],\"end\":[0.9003149237651733,0.5000000000000009]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[7134154821675013808,{\"inputs\":[{\"Node\":{\"node_id\":408,\"output_index\":0}},{\"Node\":{\"node_id\":14205611254835578455,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[457,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3],\"remove\":[],\"delta\":[[3,[129.38888888888889,628.0555555555557]],[1,[-4.833333333333332,345.83333333333326]],[2,[80.05555555555559,484.94444444444446]]]},\"segments\":{\"add\":[1,2],\"remove\":[],\"start_point\":[[1,1],[2,2]],\"end_point\":[[2,3],[1,2]],\"handle_primary\":[[2,[32.038317168599576,69.73045501401077]],[1,[0.0,0.0]]],\"handle_end\":[[1,[-45.33333333333338,-98.66666666666669]],[2,[0.0,0.0]]],\"stroke\":[[2,0],[1,0]]},\"regions\":{\"add\":[],\"remove\":[],\"segment_range\":[],\"fill\":[]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[115,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[4,[0.0,1.0]],[2,[1.0,0.0]],[3,[1.0,1.0]],[1,[-0.04384002017081715,1.8188575645616826e-15]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[3,3],[4,4],[2,2],[1,1]],\"end_point\":[[1,2],[2,3],[4,1],[3,4]],\"handle_primary\":[[1,[0.0,0.0]],[4,[0.0,0.0]],[2,[0.0,0.0]],[3,[0.0,0.0]]],\"handle_end\":[[2,[0.0,0.0]],[3,[0.0,0.0]],[1,[0.0,0.0]],[4,[0.0,0.0]]],\"stroke\":[[1,0],[4,0],[2,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6710503329407068595,{\"inputs\":[{\"Node\":{\"node_id\":16831252454255560063,\"output_index\":0}},{\"Node\":{\"node_id\":10486443711686704000,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9570557034533539493,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"delta\":[[2,[755.3312757201647,586.2448559670783]],[4,[789.2187928669412,601.4967933823075]],[1,[740.3456790123458,588.2030178326476]],[3,[794.0473251028808,582.3820301783265]],[5,[767.1831275720166,614.1625514403293]],[6,[751.1172839506169,611.1776406035664]]]},\"segments\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"start_point\":[[1,1],[2,2],[6,6],[4,4],[5,5],[3,3]],\"end_point\":[[3,4],[2,3],[6,1],[4,5],[1,2],[5,6]],\"handle_primary\":[[2,[9.481481481481524,0.4824094931645959]],[1,[0.0,0.0]],[5,[-11.149519890260535,0.2794994541025062]],[6,[0.0,0.0]],[4,[-8.427983539094612,8.539557783673331]],[3,[0.0,0.0]]],\"handle_end\":[[6,[0.0,0.0]],[5,[0.0,0.0]],[3,[8.427983539094612,-8.539557783673331]],[2,[-9.305898491083669,-1.1412894375856697]],[1,[-9.481481481481524,-0.4824094931645959]],[4,[11.149519890260535,-0.2794994541025062]]],\"stroke\":[[6,0],[5,0],[1,0],[3,0],[4,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":6}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5175066652268973319,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[1,[-0.09890842105846484,-0.06578040790199424]],[3,[1.0362898771040632,0.9994054840058096]],[4,[0.0,1.0]],[2,[0.8379395417513005,-0.05940639119491883]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[1,1],[4,4],[3,3],[2,2]],\"end_point\":[[4,1],[2,3],[1,2],[3,4]],\"handle_primary\":[[2,[0.0,0.0]],[4,[0.0,0.0]],[1,[0.0,0.0]],[3,[0.0,0.0]]],\"handle_end\":[[1,[0.0,0.0]],[2,[0.0,0.0]],[3,[0.0,0.0]],[4,[0.0,0.0]]],\"stroke\":[[2,0],[3,0],[1,0],[4,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[177,{\"inputs\":[{\"Node\":{\"node_id\":178,\"output_index\":0}},{\"Node\":{\"node_id\":238,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[208,{\"inputs\":[{\"Node\":{\"node_id\":212,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15277819403265847073,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":4913361824430066698,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[99,{\"inputs\":[{\"Node\":{\"node_id\":100,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[827.4018790826805,704.5]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[74.57030178326477,63.99999999999989]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[161,{\"inputs\":[{\"Node\":{\"node_id\":162,\"output_index\":0}},{\"Node\":{\"node_id\":168,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[17245613731534563958,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[2,[1.0,0.5]],[4,[0.0,0.5]],[1,[0.5,0.0]],[3,[0.5,1.0]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[4,4],[3,3],[1,1],[2,2]],\"end_point\":[[1,2],[2,3],[3,4],[4,1]],\"handle_primary\":[[1,[0.27589238888950707,0.0]],[2,[0.0,0.27589238888950707]],[4,[0.0,-0.275892388889507]],[3,[-0.275892388889507,0.0]]],\"handle_end\":[[2,[0.27589238888950707,0.0]],[1,[0.0,-0.275892388889507]],[4,[-0.275892388889507,0.0]],[3,[0.0,0.27589238888950707]]],\"stroke\":[[4,0],[2,0],[1,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[394,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":404,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[254,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5],\"remove\":[],\"delta\":[[1,[887.506172839506,620.2933732713938]],[3,[1012.648148148148,620.7933732713938]],[2,[999.8086419753088,626.6728395061729]],[4,[1004.3518518518516,600.7306004720272]],[5,[910.5246913580244,598.202467627757]]]},\"segments\":{\"add\":[1,2,3,4,5],\"remove\":[],\"start_point\":[[1,1],[2,2],[5,5],[3,3],[4,4]],\"end_point\":[[1,2],[3,4],[2,3],[5,1],[4,5]],\"handle_primary\":[[5,[0.0,0.0]],[2,[0.0,0.0]],[3,[0.0,0.0]],[1,[0.0,0.0]],[4,[0.0,0.0]]],\"handle_end\":[[1,[-43.25925925925878,-1.3827160493827932]],[2,[0.0,0.0]],[5,[0.0,0.0]],[3,[0.0,0.0]],[4,[0.0,0.0]]],\"stroke\":[[2,0],[4,0],[3,0],[1,0],[5,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":5}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[83,{\"inputs\":[{\"Node\":{\"node_id\":85,\"output_index\":0}},{\"Node\":{\"node_id\":117,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[114,{\"inputs\":[{\"Node\":{\"node_id\":115,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[926.5490676442352,657.3888888888888]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[97.92901234567933,111.111111111111]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[487,{\"inputs\":[{\"Node\":{\"node_id\":491,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.5546875,\"green\":0.30678105,\"blue\":0.24050903,\"alpha\":1.0},{\"red\":0.41015625,\"green\":0.26908994,\"blue\":0.22590637,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.6510643291021605,0.0026199374677413345],\"end\":[0.6510643291021605,0.8853991419751945]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.5546875,\"green\":0.30678105,\"blue\":0.24050903,\"alpha\":1.0},{\"red\":0.41015625,\"green\":0.26908994,\"blue\":0.22590637,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.6510643291021605,0.0026199374677413345],\"end\":[0.6510643291021605,0.8853991419751945]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[316,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"delta\":[[6,[751.1172839506169,611.1776406035664]],[1,[740.3456790123458,588.2030178326476]],[5,[767.1831275720166,614.1625514403293]],[3,[794.0473251028808,582.3820301783265]],[4,[789.2187928669412,601.4967933823075]],[2,[755.3312757201647,586.2448559670783]]]},\"segments\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"start_point\":[[6,6],[4,4],[3,3],[5,5],[2,2],[1,1]],\"end_point\":[[4,5],[5,6],[6,1],[3,4],[2,3],[1,2]],\"handle_primary\":[[2,[9.481481481481524,0.4824094931645959]],[4,[-8.427983539094612,8.539557783673331]],[1,[0.0,0.0]],[3,[0.0,0.0]],[5,[-11.149519890260535,0.2794994541025062]],[6,[0.0,0.0]]],\"handle_end\":[[1,[-9.481481481481524,-0.4824094931645959]],[5,[0.0,0.0]],[3,[8.427983539094612,-8.539557783673331]],[6,[0.0,0.0]],[2,[-9.305898491083669,-1.1412894375856697]],[4,[11.149519890260535,-0.2794994541025062]]],\"stroke\":[[1,0],[2,0],[3,0],[6,0],[5,0],[4,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":6}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[97478832511923699,{\"inputs\":[{\"Node\":{\"node_id\":2999157202967297847,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[486.78967826851385,539.8989473007496]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":-8.547050926324854},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[24.603566707484493,77.5880766500041]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-7.194955915925423,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[176,{\"inputs\":[{\"Node\":{\"node_id\":177,\"output_index\":0}},{\"Node\":{\"node_id\":232,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12717405604755313921,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":15483449862348058100,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1644624352314732667,{\"inputs\":[{\"Node\":{\"node_id\":3930114406985796561,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[337.1982047610469,692.7466487935636]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[50.0,50.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[409,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":442,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[238,{\"inputs\":[{\"Node\":{\"node_id\":242,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.5058824,\"green\":0.26666668,\"blue\":0.20392157,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.5058824,\"green\":0.26666668,\"blue\":0.20392157,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3707802522175443254,{\"inputs\":[{\"Node\":{\"node_id\":10278740841813346388,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[440,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[4,[0.0387096774193552,1.0]],[3,[1.035483870967742,1.0]],[1,[0.0,0.0]],[2,[1.0,0.0]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[4,4],[3,3],[1,1],[2,2]],\"end_point\":[[2,3],[3,4],[1,2],[4,1]],\"handle_primary\":[[2,[0.0,0.0]],[3,[0.0,0.0]],[4,[0.0,0.0]],[1,[0.0,0.0]]],\"handle_end\":[[4,[0.0,0.0]],[3,[0.0,0.0]],[2,[0.0,0.0]],[1,[0.0,0.0]]],\"stroke\":[[1,0],[3,0],[2,0],[4,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9603838021022368374,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7],\"remove\":[],\"delta\":[[7,[779.7592592592597,612.6204267490609]],[4,[706.1680384087791,607.8415637860082]],[3,[698.0912208504803,600.3792866941013]],[1,[744.3996087994717,586.9732002235432]],[2,[734.9571457603006,587.5194584158918]],[6,[729.3449931412895,610.3875171467763]],[5,[710.9477975918305,613.6358024691357]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7],\"remove\":[],\"start_point\":[[1,1],[4,4],[6,6],[7,7],[5,5],[2,2],[3,3]],\"end_point\":[[5,6],[7,1],[4,5],[1,2],[6,7],[2,3],[3,4]],\"handle_primary\":[[1,[0.0,0.0]],[7,[0.0,0.0]],[5,[7.452522481329197,2.721536351166037]],[6,[15.119646395366544,2.853223593964344]],[4,[2.1947873799731497,4.691071467853249]],[3,[0.0,0.0]],[2,[0.0,0.0]]],\"handle_end\":[[4,null],[5,null],[2,[6.496570644718986,-11.149519890260422]],[7,[9.28638926992835,13.56378600823075]],[6,[0.0,0.0]],[3,[-2.494608558449272,-5.331900091455282]],[1,[0.0,0.0]]],\"stroke\":[[6,0],[5,0],[3,0],[2,0],[1,0],[7,0],[4,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":7}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[471,{\"inputs\":[{\"Node\":{\"node_id\":475,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[300,{\"inputs\":[{\"Node\":{\"node_id\":304,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[331,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11],\"remove\":[],\"delta\":[[4,[563.1666666666666,433.38888888888886]],[3,[588.0884773662551,227.52880658436212]],[6,[523.3861454046643,611.9385002286241]],[1,[659.4434537418081,187.67146776406028]],[5,[540.9444444444443,605.8710283878144]],[2,[622.9855967078189,184.4670781893004]],[10,[750.8539094650207,617.9375857338821]],[9,[661.5370370370372,619.7592592592594]],[7,[486.7770919067218,613.3724279835391]],[8,[446.6563786008233,618.9032921810701]],[11,[675.4629629629632,591.0185185185186]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11],\"remove\":[],\"start_point\":[[1,1],[10,10],[4,4],[7,7],[8,8],[5,5],[9,9],[11,11],[6,6],[3,3],[2,2]],\"end_point\":[[2,3],[6,7],[7,8],[4,5],[9,10],[3,4],[11,1],[8,9],[1,2],[10,11],[5,6]],\"handle_primary\":[[5,[0.0,0.0]],[1,[-3.9798811156837246,-1.1315348270080108]],[2,[-6.716049382716051,0.9218106995884908]],[7,[-3.511659807956221,0.819387288523103]],[4,[-10.222222222222172,79.55555555555549]],[3,[-3.950617283950692,42.13991769547326]],[9,[43.09465020576113,-0.9876543209876444]],[11,[0.0,0.0]],[6,[0.0,0.0]],[10,[1.1851851851849915,-37.53086419753106]],[8,[0.0,0.0]]],\"handle_end\":[[9,[-22.386831275720624,5.3991769547326385]],[8,[-14.51772944216873,0.33272107201298695]],[1,[6.716049382716051,-0.9218106995884624]],[5,[5.5601280292639785,-9.422953818016254]],[4,[0.0,0.0]],[11,null],[6,[5.110425979711636,-1.1924327285993286]],[7,[1.5363511659809888,-3.599451303154865]],[10,[0.0,0.0]],[2,[3.950617283950692,-42.13991769547323]],[3,[10.222222222222172,-79.55555555555549]]],\"stroke\":[[11,0],[2,0],[4,0],[9,0],[3,0],[6,0],[8,0],[7,0],[1,0],[10,0],[5,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":11}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[191,{\"inputs\":[{\"Node\":{\"node_id\":194,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":75.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"blending_nodes::OpacityNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6980979116665635870,{\"inputs\":[{\"Node\":{\"node_id\":5175066652268973319,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[486.78967826851385,539.8989473007496]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":-8.547050926324854},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[24.603566707484493,77.5880766500041]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-7.194955915925423,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[393,{\"inputs\":[{\"Node\":{\"node_id\":394,\"output_index\":0}},{\"Node\":{\"node_id\":398,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[424,{\"inputs\":[{\"Node\":{\"node_id\":427,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.07456140350877193,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.72265625,\"green\":0.3808298,\"blue\":0.2117157,\"alpha\":1.0},{\"red\":0.827451,\"green\":0.42352942,\"blue\":0.19215687,\"alpha\":1.0},{\"red\":0.84705883,\"green\":0.48235294,\"blue\":0.20784314,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.5000000000000004,0.0],\"end\":[0.5000000000000004,1.0]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.07456140350877193,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.72265625,\"green\":0.3808298,\"blue\":0.2117157,\"alpha\":1.0},{\"red\":0.827451,\"green\":0.42352942,\"blue\":0.19215687,\"alpha\":1.0},{\"red\":0.84705883,\"green\":0.48235294,\"blue\":0.20784314,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.5000000000000004,0.0],\"end\":[0.5000000000000004,1.0]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[82,{\"inputs\":[{\"Node\":{\"node_id\":83,\"output_index\":0}},{\"Node\":{\"node_id\":102,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[455,{\"inputs\":[{\"Node\":{\"node_id\":453,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":6.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeAlign\":\"Center\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeCap\":\"Butt\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeJoin\":\"Miter\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"PaintOrder\":\"StrokeAbove\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"VecF64\":[]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::StrokeNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[144,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[2,[1.0,0.5]],[3,[0.5,1.0]],[4,[0.0,0.5]],[1,[0.5,0.0]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[1,1],[2,2],[3,3],[4,4]],\"end_point\":[[4,1],[1,2],[2,3],[3,4]],\"handle_primary\":[[1,[0.27589238888950707,0.0]],[3,[-0.275892388889507,0.0]],[2,[0.0,0.27589238888950707]],[4,[0.0,-0.275892388889507]]],\"handle_end\":[[1,[0.0,-0.275892388889507]],[4,[-0.275892388889507,0.0]],[3,[0.0,0.27589238888950707]],[2,[0.27589238888950707,0.0]]],\"stroke\":[[4,0],[2,0],[1,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[175,{\"inputs\":[{\"Node\":{\"node_id\":176,\"output_index\":0}},{\"Node\":{\"node_id\":226,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[206,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15],\"remove\":[],\"delta\":[[5,[963.3148148148148,466.8703703703703]],[15,[1001.8333333333334,567.0185185185184]],[14,[974.574074074074,539.3148148148148]],[8,[972.5274348422496,495.6732967535437]],[3,[971.3148148148148,466.5740740740741]],[7,[965.9814814814814,501.38888888888886]],[10,[968.2037037037036,504.05555555555554]],[9,[973.0925925925924,496.5]],[12,[981.6851851851852,523.1666666666666]],[6,[967.9074074074072,482.8703703703703]],[11,[975.1666666666664,511.3148148148148]],[1,[1009.6296296296296,552.8888888888889]],[4,[956.3024691358024,462.55639384240214]],[13,[967.3148148148148,531.9074074074074]],[2,[988.351851851852,494.72222222222223]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15],\"remove\":[],\"start_point\":[[5,5],[15,15],[11,11],[9,9],[10,10],[13,13],[3,3],[7,7],[2,2],[4,4],[12,12],[8,8],[6,6],[14,14],[1,1]],\"end_point\":[[3,4],[5,6],[14,15],[2,3],[8,9],[11,12],[4,5],[1,2],[12,13],[7,8],[13,14],[9,10],[15,1],[10,11],[6,7]],\"handle_primary\":[[1,[0.0,0.0]],[14,[0.0,0.0]],[9,[0.0,0.0]],[3,[-4.395061728394808,-4.740740740740705]],[6,[-5.333333333333144,8.296296296296305]],[2,[-6.51851851851859,-12.148148148148152]],[10,[0.0,0.0]],[5,[2.814814814814781,3.7037037037036953]],[11,[3.703703703703809,-0.7407407407407618]],[15,[0.0,0.0]],[13,[0.0,0.0]],[4,[0.0,0.0]],[8,[0.0,0.0]],[12,[-4.888888888889028,8.59259259259261]],[7,[0.0,0.0]]],\"handle_end\":[[9,[0.7407407407407618,-4.0]],[4,[-2.814814814814781,-3.7037037037036953]],[15,[-3.796296296296191,11.166666666666517]],[10,[-3.7037037037035816,0.7407407407407618]],[12,[0.0,0.0]],[3,[3.265062349348341,-2.124337414689535]],[1,[6.51851851851859,12.148148148148152]],[5,[5.333333333333485,-8.296296296296305]],[8,[-0.009144947416189098,-0.38774577046183367]],[6,[-1.7777777777778283,-0.8888888888889142]],[14,[-3.4074074074073906,-0.7407407407407618]],[7,[-2.6666666666667425,0.740740740740705]],[13,[-4.296296296296418,-2.6666666666666288]],[2,[3.786081133230596,4.083862795394623]],[11,[4.8888888888888005,-8.59259259259261]]],\"stroke\":[[6,0],[4,0],[7,0],[5,0],[12,0],[13,0],[14,0],[3,0],[15,0],[1,0],[10,0],[9,0],[8,0],[11,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":15}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[408,{\"inputs\":[{\"Node\":{\"node_id\":409,\"output_index\":0}},{\"Node\":{\"node_id\":411,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1453710883947581217,{\"inputs\":[{\"Node\":{\"node_id\":281,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeAlign\":\"Center\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeCap\":\"Butt\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeJoin\":\"Miter\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"PaintOrder\":\"StrokeAbove\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"VecF64\":[]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::StrokeNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[268,{\"inputs\":[{\"Node\":{\"node_id\":272,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.5058824,\"green\":0.26666668,\"blue\":0.20392157,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.5058824,\"green\":0.26666668,\"blue\":0.20392157,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[439,{\"inputs\":[{\"Node\":{\"node_id\":440,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[344.5177419354837,697.8333333333333]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[206.6666666666667,70.66666666666666]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12768614558324028960,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":6710503329407068595,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[128,{\"inputs\":[{\"Node\":{\"node_id\":132,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[159,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5],\"remove\":[],\"delta\":[[5,[-2.6666666666666856,770.8703703703704]],[2,[153.0925925925926,632.7962962962963]],[3,[266.2777777777779,704.4077331232156]],[1,[-2.6666666666667,581.3333333333333]],[4,[249.09259259259255,770.8703703703704]]]},\"segments\":{\"add\":[1,2,3,4,5],\"remove\":[],\"start_point\":[[5,5],[2,2],[3,3],[1,1],[4,4]],\"end_point\":[[5,1],[3,4],[1,2],[2,3],[4,5]],\"handle_primary\":[[3,[8.273042653236644,16.012340619167617]],[2,[69.92592592592595,31.40740740740773]],[4,[0.0,0.0]],[1,[0.0,0.0]],[5,[0.0,0.0]]],\"handle_end\":[[4,[0.0,0.0]],[2,[-9.18518518518522,-17.777777777777715]],[3,[19.407407407407447,-32.148148148148266]],[1,[-105.44980253803315,-47.36304690267639]],[5,[0.0,0.0]]],\"stroke\":[[1,0],[2,0],[3,0],[4,0],[5,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":5}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2489761779922717592,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10],\"remove\":[],\"delta\":[[6,[468.0555555555556,551.0185185185187]],[3,[205.93209876543213,600.5987654320988]],[8,[481.88271604938296,600.2037037037037]],[10,[288.6975308641976,620.1543209876544]],[5,[338.4753086419753,574.1296296296297]],[1,[156.18106995884773,623.2098765432096]],[9,[447.46059205066985,619.9047655337092]],[2,[161.81687242798355,618.7716049382714]],[4,[282.37654320987656,585.9814814814815]],[7,[455.2160493827161,586.8374485596709]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10],\"remove\":[],\"start_point\":[[3,3],[2,2],[6,6],[10,10],[1,1],[7,7],[9,9],[4,4],[8,8],[5,5]],\"end_point\":[[6,7],[4,5],[2,3],[1,2],[9,10],[10,1],[3,4],[5,6],[7,8],[8,9]],\"handle_primary\":[[1,[0.0,0.0]],[4,[22.71604938271605,-4.9382716049382225]],[8,[0.0,0.0]],[6,[-0.19753086419751753,-0.19753086419757435]],[7,[0.0,0.0]],[9,[0.0,0.0]],[2,[5.925925925925924,-2.370370370370324]],[10,[-70.32098765432102,1.975308641975289]],[5,[0.0,0.0]],[3,[21.135802469135797,-7.703703703703695]]],\"handle_end\":[[5,[-40.09876543209879,-8.09876543209873]],[6,[4.345679012345613,-25.481481481481524]],[1,[-5.925925925925924,2.370370370370324]],[4,[0.0,0.0]],[8,[11.358024691357969,-11.390946502057773]],[10,[41.77160493827162,-2.8703703703704377]],[9,[70.32098765432102,-1.975308641975289]],[3,[-22.71604938271605,4.9382716049382225]],[2,[-21.135802469135797,7.703703703703695]],[7,[-8.691358024691567,-7.308641975308547]]],\"stroke\":[[3,0],[1,0],[8,0],[4,0],[2,0],[9,0],[7,0],[6,0],[10,0],[5,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":10}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[190,{\"inputs\":[{\"Node\":{\"node_id\":193,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0},{\"red\":0.5058824,\"green\":0.26666668,\"blue\":0.20392157,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.1331892468887066,0.5346273584022614],\"end\":[0.8687039445257865,0.5091081472114145]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0},{\"red\":0.5058824,\"green\":0.26666668,\"blue\":0.20392157,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.1331892468887066,0.5346273584022614],\"end\":[0.8687039445257865,0.5091081472114145]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11427960919145580782,{\"inputs\":[{\"Node\":{\"node_id\":6873123446543957690,\"output_index\":0}},{\"Node\":{\"node_id\":11345069121502219134,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15848750910363784662,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":17911294938421300842,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[81,{\"inputs\":[{\"Node\":{\"node_id\":82,\"output_index\":0}},{\"Node\":{\"node_id\":87,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[15483449862348058100,{\"inputs\":[{\"Node\":{\"node_id\":5382879283978921947,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.5058824,\"green\":0.26666668,\"blue\":0.20392157,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.5058824,\"green\":0.26666668,\"blue\":0.20392157,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[283,{\"inputs\":[{\"Node\":{\"node_id\":16360261423333265502,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[143,{\"inputs\":[{\"Node\":{\"node_id\":144,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[22.81427346112025,718.7256085656885]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":-1.6569586493181607},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[114.60967448512612,10.883703174332329]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[-0.14452841816522286,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[6873123446543957690,{\"inputs\":[{\"Node\":{\"node_id\":12717405604755313921,\"output_index\":0}},{\"Node\":{\"node_id\":3707802522175443254,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[485,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7],\"remove\":[],\"delta\":[[1,[901.7869989330896,337.4632677945435]],[6,[879.873428946497,552.0307817039356]],[5,[874.2777777777776,540.5]],[4,[873.611111111111,465.6111111111111]],[7,[907.1666666666664,501.16666666666663]],[3,[873.1913580246915,359.61111111111114]],[2,[881.6925011431184,340.0384849870446]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7],\"remove\":[],\"start_point\":[[7,7],[5,5],[2,2],[6,6],[4,4],[1,1],[3,3]],\"end_point\":[[3,4],[6,7],[7,1],[5,6],[1,2],[4,5],[2,3]],\"handle_primary\":[[7,[2.888888888889028,-35.111111111111086]],[2,[-5.885745135394927,5.678500588373993]],[6,[4.0,1.7777777777777146]],[1,[0.0,0.0]],[5,[1.086419753086716,4.000000000000114]],[4,[-0.22222222222228535,37.77777777777777]],[3,[0.09876543209873034,9.87654320987656]]],\"handle_end\":[[5,[-4.0,-1.7777777777777146]],[7,[0.0,0.0]],[1,[5.5406188081085475,-5.34552659655543]],[3,[0.22222222222228535,-37.77777777777777]],[6,[-2.8888888888888005,35.111111111111086]],[2,[-0.09876543209873034,-9.87654320987656]],[4,[-0.9901901223357754,-3.645699995871837]]],\"stroke\":[[6,0],[4,0],[2,0],[3,0],[5,0],[7,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":7}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[174,{\"inputs\":[{\"Node\":{\"node_id\":175,\"output_index\":0}},{\"Node\":{\"node_id\":220,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4633399390154487467,{\"inputs\":[{\"Node\":{\"node_id\":11155094820673141470,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"F64\":75.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"blending_nodes::OpacityNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[236,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"delta\":[[2,[837.9320987654322,560.7962962962963]],[1,[842.2716049382717,560.2962962962963]],[3,[832.8950617283951,560.829218106996]],[6,[838.425925925926,581.0432098765433]],[4,[828.8127572016463,566.4259259259259]],[5,[826.9691358024693,580.1543209876544]]]},\"segments\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"start_point\":[[1,1],[3,3],[5,5],[6,6],[4,4],[2,2]],\"end_point\":[[3,4],[4,5],[5,6],[2,3],[1,2],[6,1]],\"handle_primary\":[[3,[-2.1728395061728634,1.4814814814815236]],[4,[-3.649513397469832,2.8283728830390373]],[2,[0.0,0.0]],[6,[0.0,0.0]],[1,[-4.339506172839492,0.5]],[5,[0.0,0.0]]],\"handle_end\":[[4,[0.0,0.0]],[6,[0.0,0.0]],[1,null],[2,[2.1728395061728634,-1.4814814814815236]],[5,[0.0,0.0]],[3,[2.633744855967052,-2.0411522633744426]]],\"stroke\":[[5,0],[3,0],[1,0],[4,0],[2,0],[6,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":6}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1598976462838094167,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],\"remove\":[],\"delta\":[[15,[496.30246913580254,612.3006782502672]],[16,[478.9197530864198,618.6875095259874]],[2,[497.8388203017833,534.5393613778391]],[8,[502.6234567901235,563.1154930650816]],[4,[500.77983539094663,544.0208428593207]],[17,[448.3683127572017,617.6340115836006]],[12,[513.3888888888889,585.5544307531777]],[6,[490.17901234567904,556.0043819539711]],[10,[496.50000000000006,582.6234567901236]],[19,[468.05555555555594,550.6710486206383]],[14,[496.49999999999994,603.4117893613783]],[11,[504.00617283950623,588.9444444444446]],[1,[506.7057613168725,531.9348803536052]],[3,[488.5329218106997,551.6587029416252]],[20,[480.89506172839504,532.4323654930657]],[7,[495.90740740740733,567.882982777016]],[13,[516.8017832647463,593.4437585733884]],[9,[510.261316872428,573.2078189300412]],[18,[438.2283950617284,585.5544307531777]],[5,[500.4506172839507,547.5105547934772]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],\"remove\":[],\"start_point\":[[7,7],[3,3],[16,16],[6,6],[9,9],[17,17],[15,15],[4,4],[8,8],[19,19],[2,2],[12,12],[20,20],[11,11],[1,1],[18,18],[5,5],[10,10],[14,14],[13,13]],\"end_point\":[[2,3],[18,19],[11,12],[12,13],[16,17],[10,11],[13,14],[7,8],[1,2],[15,16],[17,18],[14,15],[4,5],[3,4],[6,7],[19,20],[8,9],[9,10],[20,1],[5,6]],\"handle_primary\":[[13,[0.0,0.0]],[20,[7.46503467504715,-4.02781143068205]],[8,[0.0,0.0]],[17,[0.0,0.0]],[1,[0.0,0.0]],[14,[0.0,0.0]],[3,[4.565157750342848,-4.301783264746064]],[18,[0.0,0.0]],[5,[-6.737997256515712,3.1824417009599983]],[6,[0.0,0.0]],[2,[-7.188100137174104,2.6138545953361927]],[7,[0.0,0.0]],[11,[0.0,0.0]],[4,null],[16,[0.0,0.0]],[10,[0.0,0.0]],[12,[0.0,0.0]],[9,[0.0,0.0]],[19,[0.0,0.0]],[15,[0.0,0.0]]],\"handle_end\":[[14,[-0.7901234567900701,-4.938271604938336]],[1,[3.4386245260820374,-1.250408918575317]],[7,[-3.6872427983540206,1.4485596707820605]],[19,[-3.394604481024089,3.2873037646699004]],[15,[0.0,0.0]],[8,[-1.7777777777777717,-5.728395061728406]],[13,[0.0,0.0]],[20,[0.0,0.0]],[12,[-0.39506172839503506,-1.975308641975289]],[18,[0.0,0.0]],[6,[-2.508333333333439,-2.0902777777778283]],[11,[-3.851851851851848,0.7581344568814075]],[10,[-2.765432098765416,-3.555555555555543]],[17,[0.0,0.0]],[9,[6.716049382716108,-8.49382716049422]],[5,[0.0,0.0]],[3,null],[2,[0.27087722942241044,-5.120145445603839]],[16,[0.0,0.0]],[4,null]],\"stroke\":[[18,0],[13,0],[5,0],[19,0],[3,0],[2,0],[17,0],[12,0],[10,0],[8,0],[9,0],[20,0],[14,0],[7,0],[16,0],[15,0],[4,0],[6,0],[11,0],[1,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":20}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12435496696188763850,{\"inputs\":[{\"Node\":{\"node_id\":9286544882258200464,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[96,{\"inputs\":[{\"Node\":{\"node_id\":99,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.7594724231651877,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0},{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0},{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0}]},\"gradient_type\":\"Radial\",\"start\":[0.0,0.5],\"end\":[1.0,0.0]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.7594724231651877,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0},{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0},{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0}]},\"gradient_type\":\"Radial\",\"start\":[0.0,0.5],\"end\":[1.0,0.0]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[469,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13],\"remove\":[],\"delta\":[[12,[199.1666666666667,473.1666666666667]],[4,[50.72222222222222,278.78532235939633]],[3,[-7.030559365950182,200.5]],[13,[223.25,608.7633744855967]],[5,[81.68518518518522,338.8703703703703]],[11,[177.83333333333343,384.49999999999994]],[10,[137.68518518518513,236.64814814814815]],[6,[110.91975308641976,406.62345679012344]],[8,[82.0,29.5]],[2,[-7.000000000000025,626.675562328647]],[1,[137.75,641.0]],[9,[111.83431058292848,134.6107990062408]],[7,[129.38888888888886,443.61235349483104]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13],\"remove\":[],\"start_point\":[[8,8],[1,1],[10,10],[9,9],[3,3],[11,11],[13,13],[5,5],[2,2],[6,6],[12,12],[7,7],[4,4]],\"end_point\":[[12,13],[2,3],[4,5],[7,8],[10,11],[8,9],[11,12],[5,6],[3,4],[13,1],[1,2],[6,7],[9,10]],\"handle_primary\":[[4,[7.654320987654309,14.850480109739408]],[10,[12.334360893667936,45.715346431469186]],[11,[6.410520201070284,29.915760938327992]],[13,[0.0,0.0]],[12,[11.42335240155694,63.4630688975397]],[3,[31.078939476013172,37.97283968100555]],[6,[8.537957281505157,20.688127259031944]],[1,[0.0,0.0]],[7,[-20.788075479416264,-70.61235349483104]],[8,[0.0,0.0]],[5,[6.73334689148848,12.881185357630102]],[9,[5.9990227504048335,27.666978771536947]],[2,[0.0,0.0]]],\"handle_end\":[[11,[-5.999999999999915,-33.33333333333343]],[2,[0.0,0.0]],[10,[-12.0,-55.99999999999994]],[8,[-13.354098963944438,-61.58795989288773]],[3,[-14.856706650648782,-28.82414089604376]],[7,[14.499999999999943,172.0]],[9,[-13.510174692931455,-48.54050114169854]],[5,[-10.271604938271594,-24.88888888888897]],[12,[0.0,0.0]],[6,[0.0,0.0]],[13,[0.0,0.0]],[1,[0.0,0.0]],[4,[-9.08641975308646,-17.382716049382736]]],\"stroke\":[[5,0],[12,0],[9,0],[8,0],[6,0],[4,0],[7,0],[2,0],[10,0],[11,0],[3,0],[1,0],[13,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":13}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[11279424538712841875,{\"inputs\":[{\"Node\":{\"node_id\":2489761779922717592,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[329,{\"inputs\":[{\"Node\":{\"node_id\":327,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeAlign\":\"Center\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeCap\":\"Butt\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeJoin\":\"Miter\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"PaintOrder\":\"StrokeAbove\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"VecF64\":[]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::StrokeNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4452902364641883403,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":8861964493222160710,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16894739051789815098,{\"inputs\":[{\"Node\":{\"node_id\":17245613731534563958,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"DVec2\":[337.1982047610469,692.7466487935636]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[50.0,50.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"DVec2\":[0.0,0.0]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Bool\":true},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":1}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"f64\",\"alias\":null}},\"import_index\":2}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":3}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"glam::f64::dvec2::DVec2\",\"alias\":null}},\"import_index\":4}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"transform_nodes::transform_nodes::TransformNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[220,{\"inputs\":[{\"Node\":{\"node_id\":224,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[422,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":430,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[453,{\"inputs\":[{\"Node\":{\"node_id\":457,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":\"None\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[111,{\"inputs\":[{\"Node\":{\"node_id\":114,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[13606781735926093266,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10],\"remove\":[],\"delta\":[[5,[744.3996087994716,586.9732002235431]],[9,[713.2108672458469,610.5533455265964]],[8,[708.7139917695473,601.783950617284]],[10,[712.0500685871053,614.4420508944315]],[3,[680.2695473251027,600.730452674897]],[1,[645.3333333333333,614.013717421125]],[2,[658.574074074074,609.6851851851851]],[7,[702.5246913580245,600.8931773149878]],[6,[717.5809327846364,593.2681755829904]],[4,[699.4958847736627,594.497256515775]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10],\"remove\":[],\"start_point\":[[5,5],[3,3],[1,1],[8,8],[2,2],[7,7],[9,9],[10,10],[4,4],[6,6]],\"end_point\":[[9,10],[8,9],[3,4],[1,2],[5,6],[4,5],[10,1],[2,3],[6,7],[7,8]],\"handle_primary\":[[3,[7.374485596707928,-0.92181069958815]],[5,[0.0,0.0]],[6,[-10.886145404663694,2.8971193415636662]],[9,[0.0,0.0]],[7,[0.0,0.0]],[2,[4.740740740740762,-4.148148148148152]],[4,[4.444444444444002,-5.662551440328798]],[8,[0.0,0.0]],[1,[0.0,0.0]],[10,[-58.35223289132739,3.851425709744945]]],\"handle_end\":[[6,[0.0,0.0]],[8,[-2.5361987501905787,-3.706752019509281]],[4,[-5.794238683127446,-2.0192043895747247]],[7,[-2.89711934156378,-1.975308641975289]],[10,null],[2,[-6.174173455107166,0.7717716818881399]],[1,[-4.740740740740762,4.148148148148152]],[5,[10.516302710276136,-2.79869346321857]],[3,[-1.6866098186769705,2.1488658430550913]],[9,[-0.08779149519853036,-2.2109123484780184]]],\"stroke\":[[1,0],[7,0],[8,0],[4,0],[3,0],[6,0],[10,0],[2,0],[9,0],[5,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":10}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2999157202967297847,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[3,[1.0362898771040632,0.9994054840058096]],[1,[-0.09890842105846484,-0.06578040790199424]],[2,[0.8379395417513005,-0.05940639119491883]],[4,[0.0,1.0]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[4,4],[1,1],[2,2],[3,3]],\"end_point\":[[1,2],[4,1],[3,4],[2,3]],\"handle_primary\":[[3,[0.0,0.0]],[2,[0.0,0.0]],[1,[0.0,0.0]],[4,[0.0,0.0]]],\"handle_end\":[[1,[0.0,0.0]],[3,[0.0,0.0]],[4,[0.0,0.0]],[2,[0.0,0.0]]],\"stroke\":[[1,0],[3,0],[2,0],[4,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[173,{\"inputs\":[{\"Node\":{\"node_id\":174,\"output_index\":0}},{\"Node\":{\"node_id\":214,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16360261423333265502,{\"inputs\":[{\"Node\":{\"node_id\":287,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeAlign\":\"Center\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeCap\":\"Butt\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeJoin\":\"Miter\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"PaintOrder\":\"StrokeAbove\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"VecF64\":[]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::StrokeNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16831252454255560063,{\"inputs\":[{\"Node\":{\"node_id\":14080831508667499826,\"output_index\":0}},{\"Node\":{\"node_id\":17257434333682934071,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[406,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"delta\":[[4,[353.38888888888886,444.5]],[1,[401.70713305898494,260.36282578875165]],[6,[428.9444444444444,568.9444444444443]],[3,[369.7510288065844,308.3847736625514]],[5,[332.5,581.8333333333333]],[2,[380.6371742112481,265.1035665294926]]]},\"segments\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"start_point\":[[6,6],[4,4],[2,2],[5,5],[1,1],[3,3]],\"end_point\":[[5,6],[1,2],[4,5],[3,4],[6,1],[2,3]],\"handle_primary\":[[6,[0.0,0.0]],[1,[-10.359396433470463,-3.160493827160451]],[2,[-10.643715697978225,7.851921416541302]],[5,[0.0,0.0]],[3,[-1.3105663299890011,14.89279920442118]],[4,[-5.333333333333314,40.0]]],\"handle_end\":[[2,[0.9657064471879266,-10.97393689986285]],[1,null],[4,[4.0,-30.666666666666742]],[6,[0.0,0.0]],[3,[5.333333333333314,-40.0]],[5,[0.0,0.0]]],\"stroke\":[[3,0],[2,0],[4,0],[1,0],[6,0],[5,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":6}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[9286544882258200464,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[4,[464.82921810699577,552.1378600823044]],[2,[472.818244170096,545.5973936899862]],[1,[480.8950617283949,532.7798353909467]],[3,[455.2160493827161,586.837448559671]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[2,2],[1,1],[3,3],[4,4]],\"end_point\":[[4,1],[1,2],[2,3],[3,4]],\"handle_primary\":[[4,[4.236143848022095,-8.765075372687306]],[1,[0.0,0.0]],[3,[0.0,0.0]],[2,[-5.249967385837806,9.166609721304098]]],\"handle_end\":[[1,[5.530864197530832,-9.657064471879266]],[2,[0.0,0.0]],[4,[-3.58969669257732,1.843621399176868]],[3,[-10.501290993452583,21.72839506172852]]],\"stroke\":[[1,0],[4,0],[2,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[266,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8],\"remove\":[],\"delta\":[[7,[902.8209876543212,578.672839506173]],[2,[903.0185185185188,539.9074074074074]],[4,[906.574074074074,531.3148148148149]],[1,[890.376543209877,558.3271604938273]],[3,[907.9074074074074,539.3148148148149]],[5,[916.9444444444443,525.3888888888889]],[8,[877.5736601163951,577.8827160493829]],[6,[931.067901234568,549.8333333333334]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8],\"remove\":[],\"start_point\":[[8,8],[4,4],[2,2],[7,7],[5,5],[6,6],[3,3],[1,1]],\"end_point\":[[6,7],[4,5],[3,4],[2,3],[5,6],[1,2],[8,1],[7,8]],\"handle_primary\":[[6,[0.0,0.0]],[2,[0.0,0.0]],[4,[0.05385802469163536,0.0]],[7,[0.0,0.0]],[3,[0.0,0.0]],[1,[0.0,0.0]],[5,[12.296296296296418,0.14814814814803867]],[8,[0.0,0.0]]],\"handle_end\":[[4,[-3.0120068298679143,-0.036289238914037014]],[7,[0.0,0.0]],[8,[0.0,0.0]],[1,[-16.592592592592723,23.703703703703923]],[5,[0.0,0.0]],[3,[0.2962962962964184,4.888888888889028]],[6,[0.0,0.0]],[2,[0.0,0.0]]],\"stroke\":[[4,0],[2,0],[7,0],[1,0],[6,0],[8,0],[3,0],[5,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":8}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[126,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17],\"remove\":[],\"delta\":[[17,[209.09259259259255,727.8086419753085]],[5,[115.66049382716052,719.5123456790124]],[1,[83.98971193415636,727.8086419753087]],[6,[123.75925925925928,720.3024691358024]],[2,[79.11728395061729,724.0555555555555]],[8,[148.8456790123457,665.3888888888888]],[7,[131.06790123456793,702.1296296296296]],[12,[169.3888888888889,709.0432098765432]],[15,[196.05555555555557,678.8209876543209]],[16,[211.0679012345679,719.5123456790124]],[14,[188.5493827160494,680.4012345679012]],[4,[102.03086419753087,696.7962962962963]],[10,[158.3271604938272,661.4382716049382]],[9,[158.40763603109284,655.1124066453283]],[3,[94.12962962962963,696.9938271604938]],[11,[162.67283950617286,705.2901234567901]],[13,[177.0925925925926,696.4012345679012]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17],\"remove\":[],\"start_point\":[[9,9],[12,12],[17,17],[6,6],[1,1],[5,5],[3,3],[15,15],[13,13],[8,8],[11,11],[2,2],[16,16],[4,4],[14,14],[10,10],[7,7]],\"end_point\":[[11,12],[14,15],[4,5],[6,7],[2,3],[15,16],[7,8],[16,17],[8,9],[3,4],[13,14],[9,10],[12,13],[5,6],[10,11],[17,1],[1,2]],\"handle_primary\":[[10,[-0.24572721430195088,3.082066920469856]],[3,[2.3703703703704093,-2.5679012345678984]],[4,[3.753086419753103,4.9382716049382225]],[11,[0.0,0.0]],[2,[3.2490948717098007,-6.29142916067417]],[15,[2.370370370370381,3.950617283950692]],[1,[-3.7139917695473343,-0.22427983539080287]],[14,[2.3703703703703525,-2.5679012345678984]],[9,[1.416857186404485,0.2219173906416927]],[17,[-3.028806584362002,2.897119341564121]],[16,[0.0,0.0]],[5,[0.9523778763475974,1.643318688599834]],[8,[2.172839506172835,-5.3333333333332575]],[7,[2.962962962962962,-6.320987654321016]],[6,[0.0,0.0]],[12,[0.0,0.0]],[13,[3.555555555555543,-7.308641975308547]]],\"handle_end\":[[7,[-2.172839506172835,5.3333333333332575]],[15,[-3.753086419753089,-24.493827160493765]],[2,[-2.3703703703703525,2.5679012345678984]],[14,[-2.370370370370381,-3.950617283950692]],[12,[-3.5555555555555713,7.308641975308547]],[5,[-4.148148148148167,0.5925925925926094]],[17,[4.846281557722946,0.29265633783461453]],[6,[-2.962962962962962,6.320987654321016]],[1,[-2.4142661179698734,4.6748971193414945]],[13,[-2.370370370370381,2.5679012345678984]],[8,[-2.3218581751052625,-0.3636645334502191]],[11,[-4.148148148148152,-0.39506172839503506]],[10,[1.1851851851851904,-8.296296296296305]],[4,[-1.119341563786023,-1.9314128943758533]],[16,[3.77785186523289,-3.613597436310215]],[3,[-3.753086419753074,-4.9382716049382225]],[9,[0.31458619112936503,-3.9457399786616634]]],\"stroke\":[[5,0],[7,0],[15,0],[2,0],[11,0],[17,0],[4,0],[3,0],[14,0],[8,0],[6,0],[1,0],[13,0],[9,0],[16,0],[10,0],[12,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":17}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[16164610528699022118,{\"inputs\":[{\"Node\":{\"node_id\":7134154821675013808,\"output_index\":0}},{\"Node\":{\"node_id\":12875520257830460085,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[600590258445096812,{\"inputs\":[{\"Node\":{\"node_id\":9778003574990260202,\"output_index\":0}},{\"Node\":{\"node_id\":12435496696188763850,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[188,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3],\"remove\":[],\"delta\":[[2,[987.6111111111112,593.3888888888889]],[1,[1027.3333333333333,523.5555555555555]],[3,[1026.2777777777778,610.4999999999999]]]},\"segments\":{\"add\":[1,2,3],\"remove\":[],\"start_point\":[[1,1],[3,3],[2,2]],\"end_point\":[[2,3],[3,1],[1,2]],\"handle_primary\":[[3,[0.0,0.0]],[2,[0.0,0.0]],[1,[0.0,0.0]]],\"handle_end\":[[3,[0.0,0.0]],[2,[-16.66666666666663,0.22222222222228535]],[1,[28.22222222222217,-65.11111111111109]]],\"stroke\":[[1,0],[3,0],[2,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":3}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3885641499621884510,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":600590258445096812,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[421,{\"inputs\":[{\"Node\":{\"node_id\":422,\"output_index\":0}},{\"Node\":{\"node_id\":424,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[79,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":493,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[250,{\"inputs\":[{\"Node\":{\"node_id\":254,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0},{\"red\":0.50980395,\"green\":0.26666668,\"blue\":0.2,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.6377392483726765,0.3630996922378644],\"end\":[0.6306417402456479,0.9306187973093224]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0},{\"red\":0.50980395,\"green\":0.26666668,\"blue\":0.2,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.6377392483726765,0.3630996922378644],\"end\":[0.6306417402456479,0.9306187973093224]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[281,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8],\"remove\":[],\"delta\":[[1,[1026.168038408779,250.70576131687224]],[5,[923.3641975308644,158.72222222222226]],[2,[998.8209876543212,150.42592592592595]],[3,[972.746913580247,118.22839506172843]],[4,[929.6851851851852,121.58641975308646]],[7,[926.7222222222222,528.7962962962963]],[6,[923.4629629629628,289.38888888888886]],[8,[1026.2777777777776,592.5]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8],\"remove\":[],\"start_point\":[[3,3],[2,2],[7,7],[4,4],[1,1],[8,8],[5,5],[6,6]],\"end_point\":[[6,7],[8,1],[5,6],[1,2],[2,3],[4,5],[7,8],[3,4]],\"handle_primary\":[[2,[-4.543209876543415,-11.06172839506172]],[3,[-17.77777777777783,-3.753086419753074]],[7,[0.0,0.0]],[5,[0.0,11.061728395061747]],[4,[-5.135802469135797,10.864197530864176]],[1,[-10.22770919067159,-29.761316872427727]],[8,[0.0,0.0]],[6,[0.2962962962964184,19.259259259259295]]],\"handle_end\":[[5,[-0.2962962962964184,-19.259259259259295]],[4,[0.0,-11.061728395061747]],[8,null],[3,[5.135802469135797,-10.864197530864176]],[6,[-2.370370370370324,-53.03703703703695]],[1,[4.543209876543187,11.06172839506172]],[2,[17.77777777777783,3.753086419753089]],[7,[0.0,0.0]]],\"stroke\":[[4,0],[2,0],[5,0],[1,0],[7,0],[3,0],[6,0],[8,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":8}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5382879283978921947,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"delta\":[[3,[794.0473251028808,582.3820301783265]],[4,[789.2187928669412,601.4967933823075]],[5,[767.1831275720166,614.1625514403293]],[6,[751.1172839506169,611.1776406035664]],[1,[740.3456790123458,588.2030178326476]],[2,[755.3312757201647,586.2448559670783]]]},\"segments\":{\"add\":[1,2,3,4,5,6],\"remove\":[],\"start_point\":[[6,6],[5,5],[2,2],[4,4],[3,3],[1,1]],\"end_point\":[[6,1],[4,5],[2,3],[1,2],[3,4],[5,6]],\"handle_primary\":[[4,[-8.427983539094612,8.539557783673331]],[3,[0.0,0.0]],[5,[-11.149519890260535,0.2794994541025062]],[6,[0.0,0.0]],[1,[0.0,0.0]],[2,[9.481481481481524,0.4824094931645959]]],\"handle_end\":[[3,[8.427983539094612,-8.539557783673331]],[1,[-9.481481481481524,-0.4824094931645959]],[6,[0.0,0.0]],[5,[0.0,0.0]],[2,[-9.305898491083669,-1.1412894375856697]],[4,[11.149519890260535,-0.2794994541025062]]],\"stroke\":[[1,0],[3,0],[2,0],[4,0],[5,0],[6,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":6}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[312,{\"inputs\":[{\"Node\":{\"node_id\":316,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.5058824,\"green\":0.26666668,\"blue\":0.20392157,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.5058824,\"green\":0.26666668,\"blue\":0.20392157,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[5105625446268484763,{\"inputs\":[{\"Node\":{\"node_id\":9422094883894860610,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.5058824,\"green\":0.26666668,\"blue\":0.20392157,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.5058824,\"green\":0.26666668,\"blue\":0.20392157,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[989999757220954936,{\"inputs\":[{\"Node\":{\"node_id\":10544930474333783117,\"output_index\":0}},{\"Node\":{\"node_id\":5105625446268484763,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[172,{\"inputs\":[{\"Node\":{\"node_id\":173,\"output_index\":0}},{\"Node\":{\"node_id\":208,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[436,{\"inputs\":[{\"Node\":{\"node_id\":439,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.08176100628930963,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.7490196,\"green\":0.3764706,\"blue\":0.19215687,\"alpha\":1.0},{\"red\":0.827451,\"green\":0.42352942,\"blue\":0.19215687,\"alpha\":1.0},{\"red\":0.84705883,\"green\":0.48235294,\"blue\":0.20784314,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.5000000000000004,0.0],\"end\":[0.5000000000000004,1.0]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,0.08176100628930963,1.0],\"midpoint\":[0.5,0.5,0.5],\"color\":[{\"red\":0.7490196,\"green\":0.3764706,\"blue\":0.19215687,\"alpha\":1.0},{\"red\":0.827451,\"green\":0.42352942,\"blue\":0.19215687,\"alpha\":1.0},{\"red\":0.84705883,\"green\":0.48235294,\"blue\":0.20784314,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.5000000000000004,0.0],\"end\":[0.5000000000000004,1.0]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[94,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4],\"remove\":[],\"delta\":[[2,[1.0,0.5]],[4,[0.0,0.5]],[1,[0.5,0.0]],[3,[0.5,1.0]]]},\"segments\":{\"add\":[1,2,3,4],\"remove\":[],\"start_point\":[[4,4],[3,3],[1,1],[2,2]],\"end_point\":[[4,1],[2,3],[1,2],[3,4]],\"handle_primary\":[[1,[0.27589238888950707,0.0]],[2,[0.0,0.27589238888950707]],[4,[0.0,-0.275892388889507]],[3,[-0.275892388889507,0.0]]],\"handle_end\":[[4,[-0.275892388889507,0.0]],[1,[0.0,-0.275892388889507]],[2,[0.27589238888950707,0.0]],[3,[0.0,0.27589238888950707]]],\"stroke\":[[2,0],[4,0],[1,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":4}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[467,{\"inputs\":[{\"Node\":{\"node_id\":465,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":2.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeAlign\":\"Center\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeCap\":\"Butt\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"StrokeJoin\":\"Round\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":4.0},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"PaintOrder\":\"StrokeAbove\"},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"VecF64\":[]},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"F64\":0.0},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::StrokeNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[327,{\"inputs\":[{\"Node\":{\"node_id\":331,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[218,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8],\"remove\":[],\"delta\":[[3,[872.8621399176955,563.858024691358]],[1,[901.9820911446426,504.0199918711579]],[4,[874.0473251028807,569.7181069958847]],[7,[886.574074074074,540.0555555555557]],[2,[890.7057613168722,514.4753086419753]],[5,[880.5,566.7222222222222]],[6,[888.3518518518518,541.9814814814815]],[8,[891.0185185185185,531.9074074074074]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8],\"remove\":[],\"start_point\":[[4,4],[3,3],[7,7],[8,8],[5,5],[2,2],[6,6],[1,1]],\"end_point\":[[2,3],[8,1],[6,7],[7,8],[4,5],[5,6],[1,2],[3,4]],\"handle_primary\":[[2,[-3.0946502057612406,7.835390946502002]],[4,[0.0,0.0]],[5,[0.0,0.0]],[1,[0.0,0.0]],[6,[0.0,0.0]],[3,[0.0,0.0]],[7,[0.0,0.0]],[8,[0.0,0.0]]],\"handle_end\":[[4,[0.0,0.0]],[7,[0.0,0.0]],[1,[3.0946502057612406,-7.835390946502002]],[5,[0.0,0.0]],[6,[0.0,0.0]],[3,[0.0,0.0]],[8,[-7.693415637860312,3.5987654320987303]],[2,[7.111111111111086,-11.522633744855966]]],\"stroke\":[[1,0],[6,0],[7,0],[2,0],[4,0],[8,0],[5,0],[3,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":8}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[78,{\"inputs\":[{\"Node\":{\"node_id\":79,\"output_index\":0}},{\"Node\":{\"node_id\":477,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[451,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Graphic\":{\"element\":[],\"transform\":[],\"alpha_blending\":[],\"source_node_id\":[]}},\"exposed\":true}},{\"Node\":{\"node_id\":471,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[109,{\"inputs\":[{\"Value\":{\"tagged_value\":{\"Vector\":{\"element\":[{\"style\":{\"stroke\":{\"color\":{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},\"weight\":0.0,\"dash_lengths\":[],\"dash_offset\":0.0,\"cap\":\"Butt\",\"join\":\"Miter\",\"join_miter_limit\":4.0,\"align\":\"Center\",\"transform\":[1.0,0.0,0.0,1.0,0.0,0.0],\"non_scaling\":false,\"paint_order\":\"StrokeAbove\"},\"fill\":\"None\"},\"colinear_manipulators\":[],\"point_domain\":{\"id\":[],\"position\":[]},\"segment_domain\":{\"id\":[],\"start_point\":[],\"end_point\":[],\"handles\":[],\"stroke\":[]},\"region_domain\":{\"id\":[],\"segment_range\":[],\"fill\":[]},\"upstream_data\":null}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":true}},{\"Value\":{\"tagged_value\":{\"VectorModification\":{\"points\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52],\"remove\":[],\"delta\":[[25,[1110.71354181481,529.509684713913]],[31,[1336.888288825075,539.8397813321702]],[14,[783.956275100461,458.8300762732051]],[26,[1143.3348995566753,518.6358987999579]],[7,[758.9465674983644,277.2378515101559]],[49,[1038.402865487009,182.635914058747]],[9,[770.907732003715,320.7329951659761]],[42,[1287.4125629165796,203.2961072952616]],[39,[1145.5096567394662,398.4805644507546]],[12,[783.956275100461,383.2572641712176]],[37,[1302.6358631961166,363.1407602304008]],[41,[1216.7329544758718,262.5582405263166]],[19,[988.3834502828158,483.83978387530175]],[18,[909.5485024066418,487.10191964948825]],[48,[1078.092184072945,124.46115941908752]],[21,[990.5582074656068,498.5193948591411]],[34,[1256.9659623575055,413.8640589204806]],[1,[865.1205730638582,195.5865278374037]],[10,[670.8689015953286,344.1116348809794]],[4,[956.3057818366484,357.1601779777255]],[43,[1210.2086829274988,227.76212560166044]],[8,[810.5970505896508,321.8203737573716]],[51,[994.3640325354912,266.3640655962009]],[27,[1093.315484352482,509.93687006879384]],[24,[1014.480536476308,543.1019171063567]],[36,[1387.451393324966,404.4611467034299]],[50,[1016.655293659099,296.810666155275]],[20,[969.8980142290924,494.7135697892568]],[40,[1180.3057716641222,344.11163488097947]],[46,[1098.7523773094597,263.6456191177121]],[2,[910.288570576692,217.7758226144784]],[23,[972.616460707581,519.7232773913535]],[35,[1281.975669959602,393.04367149377714]],[32,[1323.839745728329,479.4902695097197]],[15,[728.4999669392903,519.7232773913535]],[16,[763.8397711596442,482.7524052839062]],[5,[914.9853953636192,333.78153826272217]],[28,[1134.0921815298134,465.8980371172759]],[52,[939.3251404063708,207.7548507796705]],[3,[943.8009280356002,277.78154080585364]],[6,[838.3252046702362,291.37377319829744]],[38,[1223.8009153199428,370.7524103701693]],[22,[1000.3446147881664,518.0922095042602]],[17,[813.8591863638374,482.2087159882085]],[13,[885.0824841002429,442.5193974022726]],[45,[1105.8203381535304,315.83979150469634]],[33,[1284.1504271423933,435.99512585389954]],[11,[730.1310348263835,333.2378489670244]],[47,[1073.742669707363,200.0339715210751]],[30,[1277.62615559402,478.946580214022]],[44,[1156.927131949119,259.8397940478278]],[29,[1205.8591685619167,441.9757081065748]]]},\"segments\":{\"add\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52],\"remove\":[],\"start_point\":[[3,3],[10,10],[17,17],[19,19],[51,51],[33,33],[48,48],[38,38],[28,28],[52,52],[44,44],[4,4],[14,14],[49,49],[16,16],[2,2],[36,36],[42,42],[29,29],[20,20],[6,6],[25,25],[37,37],[7,7],[46,46],[5,5],[18,18],[26,26],[30,30],[13,13],[11,11],[22,22],[9,9],[15,15],[27,27],[45,45],[24,24],[1,1],[40,40],[23,23],[31,31],[43,43],[41,41],[47,47],[50,50],[12,12],[34,34],[35,35],[39,39],[32,32],[8,8],[21,21]],\"end_point\":[[12,13],[28,29],[27,28],[6,7],[47,48],[7,8],[38,39],[34,35],[9,10],[26,27],[42,43],[25,26],[50,51],[51,52],[48,49],[44,45],[37,38],[39,40],[13,14],[52,1],[19,20],[5,6],[41,42],[43,44],[45,46],[30,31],[22,23],[21,22],[33,34],[10,11],[20,21],[18,19],[17,18],[2,3],[16,17],[23,24],[3,4],[49,50],[8,9],[4,5],[24,25],[46,47],[35,36],[36,37],[1,2],[40,41],[32,33],[29,30],[11,12],[15,16],[31,32],[14,15]],\"handle_primary\":[[27,[0.0,0.0]],[52,[-26.097086193492142,-12.504853801048256]],[3,[7.06796084407074,48.3883473171]],[5,[-19.02912534942118,-15.766989575234843]],[7,[0.0,0.0]],[1,[0.0,0.0]],[22,[0.0,0.0]],[37,[-48.38834731709994,-2.174757182791041]],[42,[0.0,0.0]],[40,[12.504853801048512,-14.135921688141591]],[11,[26.097086193492142,5.980582252675276]],[9,[-26.64077548918988,2.7184464784887723]],[49,[-10.873785913954862,28.81553267198086]],[21,[0.0,0.0]],[10,[0.0,0.0]],[15,[0.0,0.0]],[25,[14.679610983839666,-8.155339435466317]],[26,[0.0,0.0]],[32,[-24.466018306398837,-28.81553267198086]],[17,[31.53397915046969,11.961164505350553]],[35,[8.699028731163935,-10.3300966182573]],[24,[39.145629290237935,9.786407322559626]],[50,[0.0,0.0]],[48,[0.0,0.0]],[51,[-18.48543605372367,-25.009707602096626]],[45,[0.0,0.0]],[39,[0.0,0.0]],[33,[-13.048543096746243,-19.57281464511908]],[30,[26.097086193492032,5.436892956977488]],[31,[0.0,0.0]],[12,[41.864075768726934,30.606794749262978]],[4,[0.0,0.0]],[29,[20.116503940816983,1.631067887093252]],[14,[-44.03883295151786,20.11650394081687]],[2,[16.937999067312603,14.3156740497256]],[44,[-21.74757182791018,14.135921688141591]],[16,[14.13592168814148,-1.631067887093252]],[28,[0.0,0.0]],[38,[-17.941746758026056,-1.0873785913955205]],[47,[-1.6310678870931952,-7.611650139768528]],[46,[-6.524271548372781,-16.310678870932577]],[18,[18.485436053723447,-2.7184464784887723]],[41,[15.427586663144211,-29.493915679540574]],[8,[0.0,0.0]],[36,[0.0,0.0]],[43,[-16.310678870932634,13.048543096746071]],[6,[-25.00970760209657,-1.0873785913954634]],[19,[0.0,0.0]],[34,[0.0,0.0]],[23,[0.0,0.0]],[20,[0.0,0.0]],[13,[0.0,0.0]]],\"handle_end\":[[7,[-5.436892956977545,-19.029125349421292]],[39,[-9.034630967226803,10.21306109338667]],[42,[16.310678870932634,-13.048543096746071]],[9,[27.18446478488761,-22.291261123607853]],[3,[5.436892956977545,-25.553396897794357]],[23,[-39.14562929023816,-9.786407322559626]],[1,[-14.628148339931158,-12.363432230293256]],[40,[-18.48543605372356,35.33980422035398]],[14,[0.0,0.0]],[49,[-5.98058225267539,-75.02912280628982]],[26,[30.446600559074568,-5.980582252675276]],[24,[-14.67961098383944,8.155339435466317]],[17,[-18.48543605372356,2.7184464784887723]],[28,[-20.11650394081721,-1.6310678870931952]],[31,[24.466018306399064,28.81553267198086]],[51,[26.097086193492032,12.50485380104834]],[5,[25.00970760209657,1.0873785913954634]],[25,[-12.504853801048284,-3.262135774186504]],[18,[-11.41747520965282,-9.242718026861724]],[8,[26.64077548918988,-2.7184464784887723]],[11,[-41.864075768726934,-30.606794749263088]],[48,[10.87378591395509,-28.81553267198086]],[43,[21.74757182791018,-14.135921688141565]],[33,[0.0,0.0]],[15,[-14.13592168814148,1.631067887093252]],[12,[-31.533979150469577,-8.15533943546626]],[6,[0.0,0.0]],[50,[18.48543605372356,25.009707602096626]],[38,[0.0,0.0]],[30,[-0.5436892956979591,-24.46601830639895]],[35,[-71.76698703210332,-41.864075768726934]],[52,[21.791261123607796,0.5873785913955203]],[44,[0.0,0.0]],[2,[-7.067960844070626,-48.3883473171]],[47,[-22.83495041930587,36.42718281174942]],[22,[3.805825069884122,-4.3495143655819675]],[19,[3.805825069884008,-5.436892956977601]],[20,[-4.8932036612795855,-5.980582252675276]],[45,[6.524271548372553,16.310678870932577]],[46,[1.6310678870931952,7.611650139768528]],[32,[13.048543096746243,19.57281464511908]],[27,[-8.15533943546643,25.00970760209657]],[10,[-26.097086193492142,-5.980582252675276]],[37,[17.941746758026056,1.0873785913955205]],[13,[44.03883295151786,-20.11650394081687]],[34,[-8.699028731163935,10.330096618257244]],[29,[-26.097086193492032,-5.436892956977488]],[41,[-32.07766844616731,15.223300279537028]],[4,[19.02912534942141,15.766989575234843]],[36,[48.388347317099715,2.174757182790927]],[16,[-31.53397915046969,-11.961164505350553]],[21,[-4.3495143655819675,-3.262135774186504]]],\"stroke\":[[9,0],[41,0],[16,0],[29,0],[26,0],[3,0],[7,0],[28,0],[47,0],[39,0],[32,0],[48,0],[17,0],[21,0],[2,0],[12,0],[37,0],[10,0],[15,0],[11,0],[25,0],[35,0],[27,0],[4,0],[46,0],[1,0],[38,0],[33,0],[30,0],[40,0],[49,0],[13,0],[36,0],[23,0],[42,0],[31,0],[24,0],[14,0],[34,0],[6,0],[50,0],[43,0],[20,0],[19,0],[44,0],[22,0],[8,0],[52,0],[18,0],[5,0],[51,0],[45,0]]},\"regions\":{\"add\":[0],\"remove\":[],\"segment_range\":[[0,{\"start\":1,\"end\":52}]],\"fill\":[[0,0]]},\"add_g1_continuous\":[],\"remove_g1_continuous\":[]}},\"exposed\":false}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":1,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::table::Table<graphene_core::vector::vector_types::Vector>\",\"alias\":null}},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Import\":{\"import_type\":{\"Concrete\":{\"name\":\"graphene_core::vector::vector_modification::VectorModification\",\"alias\":null}},\"import_index\":1}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"vector_nodes::vector_modification_nodes::PathModifyNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[12325841371509826180,{\"inputs\":[{\"Node\":{\"node_id\":6980979116665635870,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0},{\"red\":0.5058824,\"green\":0.26666668,\"blue\":0.20392157,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.08732233199581252,0.5212000945774993],\"end\":[0.9287098983518448,0.5070261975055625]}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.3764706,\"green\":0.23529412,\"blue\":0.20784314,\"alpha\":1.0},{\"red\":0.5058824,\"green\":0.26666668,\"blue\":0.20392157,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.08732233199581252,0.5212000945774993],\"end\":[0.9287098983518448,0.5070261975055625]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[140,{\"inputs\":[{\"Node\":{\"node_id\":143,\"output_index\":0}},{\"Value\":{\"tagged_value\":{\"Fill\":{\"Solid\":{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Color\":{\"element\":[{\"red\":0.24313726,\"green\":0.18431373,\"blue\":0.19215687,\"alpha\":1.0}],\"transform\":[[1.0,0.0,0.0,1.0,0.0,0.0]],\"alpha_blending\":[{\"blend_mode\":\"Normal\",\"opacity\":1.0,\"fill\":1.0,\"clip\":false}],\"source_node_id\":[null]}},\"exposed\":false}},{\"Value\":{\"tagged_value\":{\"Gradient\":{\"stops\":{\"position\":[0.0,1.0],\"midpoint\":[0.5,0.5],\"color\":[{\"red\":0.0,\"green\":0.0,\"blue\":0.0,\"alpha\":1.0},{\"red\":1.0,\"green\":1.0,\"blue\":1.0,\"alpha\":1.0}]},\"gradient_type\":\"Linear\",\"start\":[0.0,0.5],\"end\":[1.0,0.5]}},\"exposed\":false}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::vector::FillNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[171,{\"inputs\":[{\"Node\":{\"node_id\":172,\"output_index\":0}},{\"Node\":{\"node_id\":202,\"output_index\":0}}],\"call_argument\":{\"Generic\":\"T\"},\"implementation\":{\"Network\":{\"exports\":[{\"Node\":{\"node_id\":4,\"output_index\":0}}],\"nodes\":[[0,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ToGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[4,{\"inputs\":[{\"Node\":{\"node_id\":0,\"output_index\":0}},{\"Node\":{\"node_id\":3,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::ExtendNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[3,{\"inputs\":[{\"Node\":{\"node_id\":2,\"output_index\":0}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphene_core::memo::MonitorNode\"}},\"visible\":true,\"skip_deduplication\":true,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[2,{\"inputs\":[{\"Node\":{\"node_id\":1,\"output_index\":0}},{\"Reflection\":\"DocumentNodePath\"}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::SourceNodeIdNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}],[1,{\"inputs\":[{\"Import\":{\"import_type\":{\"Generic\":\"T\"},\"import_index\":1}}],\"call_argument\":{\"Concrete\":{\"name\":\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\"alias\":null}},\"implementation\":{\"ProtoNode\":{\"name\":\"graphic_nodes::graphic::WrapGraphicNode\"}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]}},\"visible\":true,\"skip_deduplication\":false,\"context_features\":{\"extract\":\"\",\"inject\":\"\"}}]],\"scope_injections\":[]},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[181,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[404,{\"persistent_metadata\":{\"display_name\":\"Stroke\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the stroke style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Color\",\"input_description\":\"The stroke color.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Weight\",\"input_description\":\"The stroke weight.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Align\",\"input_description\":\"The alignment of stroke to the path's centerline or (for closed shapes) the inside or outside of the shape.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Cap\",\"input_description\":\"The shape of the stroke at open endpoints.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Join\",\"input_description\":\"The curvature of the bent stroke at sharp corners.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Miter Limit\",\"input_description\":\"The threshold for when a miter-joined stroke is converted to a bevel-joined stroke when a sharp angle becomes pointier than this ratio.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Paint Order\",\"input_description\":\"The order to paint the stroke on top of the fill, or the fill on top of the stroke.\\n<https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty>\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Lengths\",\"input_description\":\"The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Offset\",\"input_description\":\"The phase offset distance from the starting point of the dash pattern.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[463,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16360261423333265502,{\"persistent_metadata\":{\"display_name\":\"Stroke\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the stroke style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Color\",\"input_description\":\"The stroke color.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Weight\",\"input_description\":\"The stroke weight.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Align\",\"input_description\":\"The alignment of stroke to the path's centerline or (for closed shapes) the inside or outside of the shape.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Cap\",\"input_description\":\"The shape of the stroke at open endpoints.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Join\",\"input_description\":\"The curvature of the bent stroke at sharp corners.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Miter Limit\",\"input_description\":\"The threshold for when a miter-joined stroke is converted to a bevel-joined stroke when a sharp angle becomes pointier than this ratio.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Paint Order\",\"input_description\":\"The order to paint the stroke on top of the fill, or the fill on top of the stroke.\\n<https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty>\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Lengths\",\"input_description\":\"The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Offset\",\"input_description\":\"The phase offset distance from the starting point of the dash pattern.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[17245613731534563958,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[87,{\"persistent_metadata\":{\"display_name\":\"Ball\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-25,10]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[4454263454059119441,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[5105625446268484763,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[445,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"x\":\"X\",\"unit\":\" px\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"H\",\"x\":\"W\",\"is_integer\":false,\"unit\":\"x\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[419,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15552693212536925398,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[1453710883947581217,{\"persistent_metadata\":{\"display_name\":\"Stroke\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the stroke style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Color\",\"input_description\":\"The stroke color.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Weight\",\"input_description\":\"The stroke weight.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Align\",\"input_description\":\"The alignment of stroke to the path's centerline or (for closed shapes) the inside or outside of the shape.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Cap\",\"input_description\":\"The shape of the stroke at open endpoints.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Join\",\"input_description\":\"The curvature of the bent stroke at sharp corners.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Miter Limit\",\"input_description\":\"The threshold for when a miter-joined stroke is converted to a bevel-joined stroke when a sharp angle becomes pointier than this ratio.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Paint Order\",\"input_description\":\"The order to paint the stroke on top of the fill, or the fill on top of the stroke.\\n<https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty>\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Lengths\",\"input_description\":\"The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Offset\",\"input_description\":\"The phase offset distance from the starting point of the dash pattern.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[94,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[6980979116665635870,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"is_integer\":false,\"x\":\"X\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"H\",\"unit\":\"x\",\"is_integer\":false,\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[126,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[166,{\"persistent_metadata\":{\"display_name\":\"Left Slab Spire\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":33}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[175,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14433811491576609500,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[171,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[178,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[103,{\"persistent_metadata\":{\"display_name\":\"Right Plinth\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[487,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[256,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[11891167879168294182,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[244,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[430,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[475,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[290,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[184,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[83,{\"persistent_metadata\":{\"display_name\":\"Stone Cluster\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":6}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[331,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[312,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[18142347460553706128,{\"persistent_metadata\":{\"display_name\":\"Opacity\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Opacity\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[111,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[134,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[457,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-36,238]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[220,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[6272196533192700024,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"Y\",\"unit\":\" px\",\"is_integer\":false,\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\"x\",\"y\":\"H\",\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[446,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9778003574990260202,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[477,{\"persistent_metadata\":{\"display_name\":\"Face\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-15,253]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[200,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14579754335592291854,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[99,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"X\",\"y\":\"Y\",\"is_integer\":false,\"unit\":\" px\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"H\",\"is_integer\":false,\"unit\":\"x\",\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[493,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[434,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[76,{\"persistent_metadata\":{\"display_name\":\"Ground\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":144}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[450,{\"persistent_metadata\":{\"display_name\":\"Face\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3885641499621884510,{\"persistent_metadata\":{\"display_name\":\"Rock Outcropping\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":6}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[194,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[1598976462838094167,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[172,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[398,{\"persistent_metadata\":{\"display_name\":\"Stroke\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the stroke style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Color\",\"input_description\":\"The stroke color.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Weight\",\"input_description\":\"The stroke weight.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Align\",\"input_description\":\"The alignment of stroke to the path's centerline or (for closed shapes) the inside or outside of the shape.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Cap\",\"input_description\":\"The shape of the stroke at open endpoints.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Join\",\"input_description\":\"The curvature of the bent stroke at sharp corners.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Miter Limit\",\"input_description\":\"The threshold for when a miter-joined stroke is converted to a bevel-joined stroke when a sharp angle becomes pointier than this ratio.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Paint Order\",\"input_description\":\"The order to paint the stroke on top of the fill, or the fill on top of the stroke.\\n<https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty>\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Lengths\",\"input_description\":\"The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Offset\",\"input_description\":\"The phase offset distance from the starting point of the dash pattern.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[82,{\"persistent_metadata\":{\"display_name\":\"Right Plinth\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":6}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[408,{\"persistent_metadata\":{\"display_name\":\"Reflections\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":6}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[6710503329407068595,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-35,106]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[6873123446543957690,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[191,{\"persistent_metadata\":{\"display_name\":\"Opacity\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Opacity\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[90,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[229,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"X\",\"unit\":\" px\",\"y\":\"Y\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"H\",\"is_integer\":false,\"unit\":\"x\",\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[108,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"x\":\"X\",\"unit\":\" px\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\"x\",\"y\":\"H\",\"is_integer\":false,\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[170,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15277819403265847073,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[74,{\"persistent_metadata\":{\"display_name\":\"Foreground\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-5,4]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[190,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[214,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[14335659566300901430,{\"persistent_metadata\":{\"display_name\":\"Soft Shadow\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[459,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[449,{\"persistent_metadata\":{\"display_name\":\"Shadow Lower\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[238,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[13606781735926093266,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16815500381887058038,{\"persistent_metadata\":{\"display_name\":\"Artboard\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Artboards\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Contents\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"x\":\"X\",\"y\":\"Y\",\"is_integer\":true},\"widget_override\":\"vec2\",\"input_name\":\"Location\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"x\":\"W\",\"is_integer\":true,\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Dimensions\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"artboard_background\",\"input_name\":\"Background\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Clip\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-1,1]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Artboard\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-4]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Create Artboard\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[465,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[448,{\"persistent_metadata\":{\"display_name\":\"Shadow Fissure\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-15,238]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[496,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"y\":\"Y\",\"x\":\"X\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\"x\",\"x\":\"W\",\"is_integer\":false,\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[206,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[81,{\"persistent_metadata\":{\"display_name\":\"Left Plinth\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-15,7]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[323,{\"persistent_metadata\":{\"display_name\":\"Stroke\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the stroke style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Color\",\"input_description\":\"The stroke color.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Weight\",\"input_description\":\"The stroke weight.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Align\",\"input_description\":\"The alignment of stroke to the path's centerline or (for closed shapes) the inside or outside of the shape.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Cap\",\"input_description\":\"The shape of the stroke at open endpoints.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Join\",\"input_description\":\"The curvature of the bent stroke at sharp corners.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Miter Limit\",\"input_description\":\"The threshold for when a miter-joined stroke is converted to a bevel-joined stroke when a sharp angle becomes pointier than this ratio.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Paint Order\",\"input_description\":\"The order to paint the stroke on top of the fill, or the fill on top of the stroke.\\n<https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty>\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Lengths\",\"input_description\":\"The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Offset\",\"input_description\":\"The phase offset distance from the starting point of the dash pattern.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[161,{\"persistent_metadata\":{\"display_name\":\"Rocky Outcropping\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-15,55]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[176,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11472292186872186521,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\" px\",\"y\":\"Y\",\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"y\":\"H\",\"is_integer\":false,\"unit\":\"x\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-35,43]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[1644624352314732667,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\" px\",\"y\":\"Y\",\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\"x\",\"x\":\"W\",\"is_integer\":false,\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16821952675128396603,{\"persistent_metadata\":{\"display_name\":\"Main Slope\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-25,157]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[97478832511923699,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"x\":\"X\",\"unit\":\" px\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"unit\":\"x\",\"is_integer\":false,\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[422,{\"persistent_metadata\":{\"display_name\":\"Right Half\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[478,{\"persistent_metadata\":{\"display_name\":\"Shadow\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[224,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15483449862348058100,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[2489761779922717592,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[128,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[155,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[5382879283978921947,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[6015109908395573189,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\" px\",\"y\":\"Y\",\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"H\",\"unit\":\"x\",\"x\":\"W\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[327,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[5714505144727602368,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"x\":\"X\",\"y\":\"Y\",\"unit\":\" px\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"H\",\"unit\":\"x\",\"is_integer\":false,\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[418,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"Y\",\"is_integer\":false,\"unit\":\" px\",\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"H\",\"is_integer\":false,\"x\":\"W\",\"unit\":\"x\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[318,{\"persistent_metadata\":{\"display_name\":\"Face\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-25,139]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[481,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[11345069121502219134,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[105,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[4452902364641883403,{\"persistent_metadata\":{\"display_name\":\"Soft Shadow\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[277,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[165,{\"persistent_metadata\":{\"display_name\":\"Rocky Slope\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":15}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[120,{\"persistent_metadata\":{\"display_name\":\"Ground Shadow\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[230,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[78,{\"persistent_metadata\":{\"display_name\":\"Distant Spire\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":12}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[193,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"y\":\"Y\",\"x\":\"X\",\"unit\":\" px\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"x\":\"W\",\"y\":\"H\",\"unit\":\"x\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[442,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[12875520257830460085,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[5175066652268973319,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[485,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[455,{\"persistent_metadata\":{\"display_name\":\"Stroke\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the stroke style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Color\",\"input_description\":\"The stroke color.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Weight\",\"input_description\":\"The stroke weight.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Align\",\"input_description\":\"The alignment of stroke to the path's centerline or (for closed shapes) the inside or outside of the shape.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Cap\",\"input_description\":\"The shape of the stroke at open endpoints.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Join\",\"input_description\":\"The curvature of the bent stroke at sharp corners.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Miter Limit\",\"input_description\":\"The threshold for when a miter-joined stroke is converted to a bevel-joined stroke when a sharp angle becomes pointier than this ratio.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Paint Order\",\"input_description\":\"The order to paint the stroke on top of the fill, or the fill on top of the stroke.\\n<https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty>\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Lengths\",\"input_description\":\"The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Offset\",\"input_description\":\"The phase offset distance from the starting point of the dash pattern.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[79,{\"persistent_metadata\":{\"display_name\":\"Sky\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":6}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[6926019345498826421,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[36935169817407978,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-35,160]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[394,{\"persistent_metadata\":{\"display_name\":\"Shadow\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[132,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[143,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"y\":\"Y\",\"x\":\"X\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"y\":\"H\",\"is_integer\":false,\"unit\":\"x\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[393,{\"persistent_metadata\":{\"display_name\":\"Face\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-25,193]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10278740841813346388,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[292,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[433,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"X\",\"y\":\"Y\",\"unit\":\" px\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"is_integer\":false,\"y\":\"H\",\"unit\":\"x\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[179,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[117,{\"persistent_metadata\":{\"display_name\":\"Stones\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-25,28]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[497,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[254,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[406,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[306,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[272,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[164,{\"persistent_metadata\":{\"display_name\":\"Center Slab Spire\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":15}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[212,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[226,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[4633399390154487467,{\"persistent_metadata\":{\"display_name\":\"Opacity\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Opacity\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[2999157202967297847,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[250,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[257,{\"persistent_metadata\":{\"display_name\":\"Opacity\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Opacity\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[177,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7148230379224894975,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3719764965605527929,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[196,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[12717405604755313921,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9570557034533539493,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[118,{\"persistent_metadata\":{\"display_name\":\"Highlight\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3707802522175443254,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[310,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[325,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-46,139]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[453,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-43,238]}}},\"network_metadata\":null}}],[304,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12068777759187203228,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12768614558324028960,{\"persistent_metadata\":{\"display_name\":\"Slope\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[138,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[109,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[300,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[11807598261442997948,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[291,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[93,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"y\":\"Y\",\"unit\":\" px\",\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"H\",\"unit\":\"x\",\"is_integer\":false,\"x\":\"W\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[262,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[17173383864410319040,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[119,{\"persistent_metadata\":{\"display_name\":\"Highlight\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[115,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16831252454255560063,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9286544882258200464,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14080831508667499826,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[7135480377162524224,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\" px\",\"y\":\"Y\",\"x\":\"X\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"y\":\"H\",\"x\":\"W\",\"unit\":\"x\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[88,{\"persistent_metadata\":{\"display_name\":\"Left Plinth\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[415,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[424,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[9603838021022368374,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[413,{\"persistent_metadata\":{\"display_name\":\"From Left Slab Spire\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":6}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[173,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[3930114406985796561,{\"persistent_metadata\":{\"display_name\":\"Opacity\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Opacity\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[427,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"y\":\"Y\",\"unit\":\" px\",\"x\":\"X\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"is_integer\":false,\"y\":\"H\",\"unit\":\"x\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16164610528699022118,{\"persistent_metadata\":{\"display_name\":\"Spike Spire Corner Slope\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-15,202]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[436,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[232,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[17911294938421300842,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[10486443711686704000,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[4913361824430066698,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[169,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[96,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[218,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[242,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[168,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-25,58]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[402,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[100,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[259,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"X\",\"unit\":\" px\",\"y\":\"Y\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\"x\",\"x\":\"W\",\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[440,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[287,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11427960919145580782,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[1635416892097245588,{\"persistent_metadata\":{\"display_name\":\"Shading\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[140,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[319,{\"persistent_metadata\":{\"display_name\":\"Shadow\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[491,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[989999757220954936,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[266,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[85,{\"persistent_metadata\":{\"display_name\":\"Left Slope\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":21}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[281,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-53,121]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[268,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[11279424538712841875,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[227,{\"persistent_metadata\":{\"display_name\":\"Opacity\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Opacity\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[159,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14113040319560793790,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[409,{\"persistent_metadata\":{\"display_name\":\"Ground\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":15}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[439,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"x\":\"X\",\"unit\":\" px\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"H\",\"is_integer\":false,\"x\":\"W\",\"unit\":\"x\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[162,{\"persistent_metadata\":{\"display_name\":\"Right Slab Spire\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":60}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[471,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[321,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-53,139]}}},\"network_metadata\":null}}],[400,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[396,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[467,{\"persistent_metadata\":{\"display_name\":\"Stroke\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the stroke style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Color\",\"input_description\":\"The stroke color.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Weight\",\"input_description\":\"The stroke weight.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Align\",\"input_description\":\"The alignment of stroke to the path's centerline or (for closed shapes) the inside or outside of the shape.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Cap\",\"input_description\":\"The shape of the stroke at open endpoints.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Join\",\"input_description\":\"The curvature of the bent stroke at sharp corners.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Miter Limit\",\"input_description\":\"The threshold for when a miter-joined stroke is converted to a bevel-joined stroke when a sharp angle becomes pointier than this ratio.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Paint Order\",\"input_description\":\"The order to paint the stroke on top of the fill, or the fill on top of the stroke.\\n<https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty>\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Lengths\",\"input_description\":\"The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Offset\",\"input_description\":\"The phase offset distance from the starting point of the dash pattern.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[275,{\"persistent_metadata\":{\"display_name\":\"Shadow\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[17414691604179185270,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[122,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[75,{\"persistent_metadata\":{\"display_name\":\"Slab Spires\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":45}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[77,{\"persistent_metadata\":{\"display_name\":\"Spike Spire\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":33}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[15848750910363784662,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[412,{\"persistent_metadata\":{\"display_name\":\"From Center Slab Spire\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[208,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[236,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[421,{\"persistent_metadata\":{\"display_name\":\"Left Half\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-35,223]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[283,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[260,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11377169273880889832,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12325841371509826180,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[451,{\"persistent_metadata\":{\"display_name\":\"Shadow Upper\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[114,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"unit\":\" px\",\"x\":\"X\",\"y\":\"Y\"},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"unit\":\"x\",\"x\":\"W\",\"is_integer\":false,\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[10544930474333783117,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[316,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[188,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[5364427239360309137,{\"persistent_metadata\":{\"display_name\":\"Opacity\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Opacity\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[274,{\"persistent_metadata\":{\"display_name\":\"Face\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-25,121]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[102,{\"persistent_metadata\":{\"display_name\":\"Agave Plant\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-25,19]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[11155094820673141470,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[600590258445096812,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-35,169]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[180,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[248,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[14205611254835578455,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"y\":\"Y\",\"unit\":\" px\",\"x\":\"X\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"is_integer\":false,\"x\":\"W\",\"y\":\"H\",\"unit\":\"x\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-25,208]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[16894739051789815098,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Value\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"X\",\"y\":\"Y\",\"unit\":\" px\",\"is_integer\":false},\"widget_override\":\"vec2\",\"input_name\":\"Translation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_rotation\",\"input_name\":\"Rotation\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{\"x\":\"W\",\"unit\":\"x\",\"is_integer\":false,\"y\":\"H\"},\"widget_override\":\"vec2\",\"input_name\":\"Scale\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"transform_skew\",\"input_name\":\"Skew\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Origin Offset\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":\"hidden\",\"input_name\":\"Scale Appearance\",\"input_description\":\"\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":null,\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Transform\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[469,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[17020523203516467057,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[202,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[17257434333682934071,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[144,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[9422094883894860610,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[8861964493222160710,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[7134154821675013808,{\"persistent_metadata\":{\"display_name\":\"Left Slope Shading\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[428,{\"persistent_metadata\":{\"display_name\":\"Path\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Modification\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Path\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Path Modify\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[7,0]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,0]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[411,{\"persistent_metadata\":{\"display_name\":\"From Spike Spire\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Absolute\":[-25,217]}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[174,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[329,{\"persistent_metadata\":{\"display_name\":\"Stroke\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the stroke style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Color\",\"input_description\":\"The stroke color.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Weight\",\"input_description\":\"The stroke weight.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Align\",\"input_description\":\"The alignment of stroke to the path's centerline or (for closed shapes) the inside or outside of the shape.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Cap\",\"input_description\":\"The shape of the stroke at open endpoints.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Join\",\"input_description\":\"The curvature of the bent stroke at sharp corners.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Miter Limit\",\"input_description\":\"The threshold for when a miter-joined stroke is converted to a bevel-joined stroke when a sharp angle becomes pointier than this ratio.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Paint Order\",\"input_description\":\"The order to paint the stroke on top of the fill, or the fill on top of the stroke.\\n<https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty>\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Lengths\",\"input_description\":\"The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Dash Offset\",\"input_description\":\"The phase offset distance from the starting point of the dash pattern.\\n\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}],[182,{\"persistent_metadata\":{\"display_name\":\"\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Graphical Data\",\"input_description\":\"TODO\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Over\",\"input_description\":\"TODO\"}}],\"output_names\":[\"\"],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Layer\":{\"position\":{\"Stack\":0}}},\"network_metadata\":{\"persistent_metadata\":{\"reference\":\"Merge\",\"node_metadata\":[[3,{\"persistent_metadata\":{\"display_name\":\"Monitor\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-7,-1]}}},\"network_metadata\":null}}],[0,{\"persistent_metadata\":{\"display_name\":\"To Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-3]}}},\"network_metadata\":null}}],[1,{\"persistent_metadata\":{\"display_name\":\"Wrap Graphic\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-21,-1]}}},\"network_metadata\":null}}],[2,{\"persistent_metadata\":{\"display_name\":\"Source Node ID\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[-14,-1]}}},\"network_metadata\":null}}],[4,{\"persistent_metadata\":{\"display_name\":\"Extend\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":{\"Absolute\":[0,-3]}}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[0.0,0.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,0.0,0.0],\"node_graph_width\":0.0},\"selection_undo_history\":[],\"selection_redo_history\":[]}}}}],[12435496696188763850,{\"persistent_metadata\":{\"display_name\":\"Fill\",\"input_metadata\":[{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Content\",\"input_description\":\"The content with vector paths to apply the fill style to.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Fill\",\"input_description\":\"The fill to paint the path with.\\n\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Color\",\"input_description\":\"\"}},{\"persistent_metadata\":{\"input_data\":{},\"widget_override\":null,\"input_name\":\"Backup Gradient\",\"input_description\":\"\"}}],\"output_names\":[],\"locked\":false,\"pinned\":false,\"node_type_metadata\":{\"Node\":{\"position\":\"Chain\"}},\"network_metadata\":null}}]],\"previewing\":\"No\",\"navigation_metadata\":{\"node_graph_ptz\":{\"pan\":[459.0,-501.0],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"node_graph_to_viewport\":[1.0,0.0,0.0,1.0,1450.0,80.0],\"node_graph_width\":1981.0},\"selection_undo_history\":[[],[],[]],\"selection_redo_history\":[]}}},\"collapsed\":[75,82,88,89,83,103,104,84,118,119,120,121,1635416892097245589,11472292186872186522,4452902364641883404,86,76,162,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,12768614558324028961,6710503329407068596,16831252454255560064,14080831508667499827,11377169273880889833,163,275,276,11427960919145580783,6873123446543957691,12717405604755313922,165,319,320,291,292,293,166,16821952675128396604,36935169817407979,15848750910363784663,3885641499621884511,600590258445096813,9778003574990260203,6926019345498826422,989999757220954937,10544930474333783118,17173383864410319041,15277819403265847074,167,394,395,77,16164610528699022119,7134154821675013809,14205611254835578456,14335659566300901431,409,412,413,422,423,414,410,78,449,450,451,452,79,478,479,80],\"commit_hash\":\"\",\"document_ptz\":{\"pan\":[-507.74999999999994,-385.9351851851852],\"tilt\":0.0,\"zoom\":1.0,\"flip\":false},\"render_mode\":\"Normal\",\"overlays_visibility_settings\":{\"all\":true,\"artboard_name\":true,\"compass_rose\":true,\"quick_measurement\":true,\"transform_measurement\":true,\"transform_cage\":true,\"hover_outline\":true,\"selection_outline\":true,\"layer_origin_cross\":true,\"pivot\":true,\"origin\":true,\"path\":true,\"anchors\":true,\"handles\":true},\"rulers_visible\":true,\"snapping_state\":{\"snapping_enabled\":true,\"grid_snapping\":false,\"artboards\":true,\"tolerance\":8.0,\"bounding_box\":{\"center_point\":true,\"corner_point\":true,\"edge_midpoint\":true,\"align_with_edges\":true,\"distribute_evenly\":true},\"path\":{\"anchor_point\":true,\"line_midpoint\":true,\"along_path\":true,\"normal_to_path\":true,\"tangent_to_path\":true,\"path_intersection_point\":true,\"align_with_anchor_point\":true,\"perpendicular_from_endpoint\":true},\"grid\":{\"origin\":[0.0,0.0],\"grid_type\":{\"Rectangular\":{\"spacing\":[1.0,1.0]}},\"rectangular_spacing\":[1.0,1.0],\"isometric_y_spacing\":1.0,\"isometric_angle_a\":30.0,\"isometric_angle_b\":30.0,\"grid_color\":{\"red\":0.6038274,\"green\":0.6038274,\"blue\":0.6038274,\"alpha\":1.0},\"dot_display\":false}},\"graph_view_overlay_open\":false,\"graph_fade_artwork_percentage\":80.0}"
  },
  {
    "path": "deny.toml",
    "content": "# This template contains all of the possible sections and their default values\n\n# Note that all fields that take a lint level have these possible values:\n# * deny - An error will be produced and the check will fail\n# * warn - A warning will be produced, but the check will not fail\n# * allow - No warning or error will be produced, though in some cases a note\n# will be\n\n# The values provided in this template are the default values that will be used\n# when any section or field is not specified in your own configuration\n\n[graph]\n# If 1 or more target triples (and optionally, target_features) are specified,\n# only the specified targets will be checked when running `cargo deny check`.\n# This means, if a particular package is only ever used as a target specific\n# dependency, such as, for example, the `nix` crate only being used via the\n# `target_family = \"unix\"` configuration, that only having windows targets in\n# this list would mean the nix crate, as well as any of its exclusive\n# dependencies not shared by any other crates, would be ignored, as the target\n# list here is effectively saying which targets you are building for.\ntargets = [\n\t# The triple can be any string, but only the target triples built in to\n\t# rustc (as of 1.40) can be checked against actual config expressions\n\t#{ triple = \"x86_64-unknown-linux-musl\" },\n\t# You can also specify which target_features you promise are enabled for a\n\t# particular target. target_features are currently not validated against\n\t# the actual valid features supported by the target architecture.\n\t#{ triple = \"wasm32-unknown-unknown\", features = [\"atomics\"] },\n]\n\n# This section is considered when running `cargo deny check advisories`\n# More documentation for the advisories section can be found here:\n# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html\n[advisories]\n# The path where the advisory database is cloned/fetched into\ndb-path = \"~/.cargo/advisory-db\"\n# The url(s) of the advisory databases to use\ndb-urls = [\"https://github.com/rustsec/advisory-db\"]\n# A list of advisory IDs to ignore. Note that ignored advisories will still\n# output a note when they are encountered.\nignore = [\n\t\"RUSTSEC-2024-0436\", # Unmaintained but still fully functional crate `paste`\n]\n# Threshold for security vulnerabilities, any vulnerability with a CVSS score\n# lower than the range specified will be ignored. Note that ignored advisories\n# will still output a note when they are encountered.\n# * None - CVSS Score 0.0\n# * Low - CVSS Score 0.1 - 3.9\n# * Medium - CVSS Score 4.0 - 6.9\n# * High - CVSS Score 7.0 - 8.9\n# * Critical - CVSS Score 9.0 - 10.0\n#severity-threshold =\n\n# This section is considered when running `cargo deny check licenses`\n# More documentation for the licenses section can be found here:\n# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html\n[licenses]\n# List of explicitly allowed licenses\n# See https://spdx.org/licenses/ for list of possible licenses\n# [possible values: any SPDX 3.11 short identifier (+ optional exception)].\n#\nallow = [\n\t\"Apache-2.0 WITH LLVM-exception\", # Keep this list in sync with those in `/about.toml`\n\t\"Apache-2.0\", # Keep this list in sync with those in `/about.toml`\n\t\"BSD-2-Clause\", # Keep this list in sync with those in `/about.toml`\n\t\"BSD-3-Clause\", # Keep this list in sync with those in `/about.toml`\n\t\"BSL-1.0\", # Keep this list in sync with those in `/about.toml`\n\t\"CC0-1.0\", # Keep this list in sync with those in `/about.toml`\n\t\"CDLA-Permissive-2.0\", # Keep this list in sync with those in `/about.toml`\n\t\"ISC\", # Keep this list in sync with those in `/about.toml`\n\t\"MIT-0\", # Keep this list in sync with those in `/about.toml`\n\t\"MIT\", # Keep this list in sync with those in `/about.toml`\n\t\"MPL-2.0\", # Keep this list in sync with those in `/about.toml`\n\t\"OpenSSL\", # Keep this list in sync with those in `/about.toml`\n\t\"Unicode-3.0\", # Keep this list in sync with those in `/about.toml`\n\t\"Unicode-DFS-2016\", # Keep this list in sync with those in `/about.toml`\n\t\"Zlib\", # Keep this list in sync with those in `/about.toml`\n\t\"NCSA\", # Keep this list in sync with those in `/about.toml`\n\t\"bzip2-1.0.6\", # Keep this list in sync with those in `/about.toml`\n\t\"OFL-1.1\", # Keep this list in sync with those in `/about.toml`\n]\n# The confidence threshold for detecting a license from license text.\n# The higher the value, the more closely the license text must be to the\n# canonical license text of a valid SPDX license file.\n# [possible values: any between 0.0 and 1.0].\nconfidence-threshold = 0.8\n# Allow 1 or more licenses on a per-crate basis, so that particular licenses\n# aren't accepted for every possible crate as with the normal allow list\nexceptions = [\n\t# Each entry is the crate and version constraint, and its specific allow\n\t# list\n\t#{ allow = [\"Zlib\"], name = \"adler32\", version = \"*\" },\n]\n\n# Some crates don't have (easily) machine readable licensing information,\n# adding a clarification entry for it allows you to manually specify the\n# licensing information\n[[licenses.clarify]]\n# The name of the crate the clarification applies to\nname = \"ring\"\n# The optional version constraint for the crate\n#version = \"*\"\n# The SPDX expression for the license requirements of the crate\nexpression = \"MIT AND ISC AND OpenSSL\"\n# One or more files in the crate's source used as the \"source of truth\" for\n# the license expression. If the contents match, the clarification will be used\n# when running the license check, otherwise the clarification will be ignored\n# and the crate will be checked normally, which may produce warnings or errors\n# depending on the rest of your configuration\nlicense-files = [\n\t# Each entry is a crate relative path, and the (opaque) hash of its contents\n\t{ path = \"LICENSE\", hash = 0xbd0eed23 },\n]\n\n[licenses.private]\n# If true, ignores workspace crates that aren't published, or are only\n# published to private registries\nignore = false\n# One or more private registries that you might publish crates to, if a crate\n# is only published to private registries, and ignore is true, the crate will\n# not have its license(s) checked\nregistries = [\n\t#\"https://sekretz.com/registry\n]\n\n# This section is considered when running `cargo deny check bans`.\n# More documentation about the 'bans' section can be found here:\n# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html\n[bans]\n# Lint level for when multiple versions of the same crate are detected\nmultiple-versions = \"allow\"\n\n# Lint level for when a crate version requirement is `*`\nwildcards = \"allow\"\n# The graph highlighting used when creating dotgraphs for crates\n# with multiple versions\n# * lowest-version - The path to the lowest versioned duplicate is highlighted\n# * simplest-path - The path to the version with the fewest edges is highlighted\n# * all - Both lowest-version and simplest-path are used\nhighlight = \"all\"\n# List of crates that are allowed. Use with care!\nallow = [\n\t#{ name = \"ansi_term\", version = \"=0.11.0\" },\n]\n# List of crates to deny\ndeny = [\n\t# Each entry the name of a crate and a version range. If version is\n\t# not specified, all versions will be matched.\n\t#{ name = \"ansi_term\", version = \"=0.11.0\" },\n\t#\n\t# Wrapper crates can optionally be specified to allow the crate when it\n\t# is a direct dependency of the otherwise banned crate\n\t#{ name = \"ansi_term\", version = \"=0.11.0\", wrappers = [] },\n]\n# Certain crates/versions that will be skipped when doing duplicate detection.\nskip = [\n\t#{ name = \"ansi_term\", version = \"=0.11.0\" },\n\t#{ name = \"cfg-if\", version = \"=0.1.10\" },\n]\n# Similarly to `skip` allows you to skip certain crates during duplicate\n# detection. Unlike skip, it also includes the entire tree of transitive\n# dependencies starting at the specified crate, up to a certain depth, which is\n# by default infinite\nskip-tree = [\n\t#{ name = \"ansi_term\", version = \"=0.11.0\", depth = 20 },\n]\n\n# This section is considered when running `cargo deny check sources`.\n# More documentation about the 'sources' section can be found here:\n# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html\n[sources]\n# Lint level for what to happen when a crate from a crate registry that is not\n# in the allow list is encountered\nunknown-registry = \"warn\"\n# Lint level for what to happen when a crate from a git repository that is not\n# in the allow list is encountered\nunknown-git = \"warn\"\n# List of URLs for allowed crate registries. Defaults to the crates.io index\n# if not specified. If it is specified but empty, no registries are allowed.\nallow-registry = [\"https://github.com/rust-lang/crates.io-index\"]\n# List of URLs for allowed Git repositories\nallow-git = []\n\n[sources.allow-org]\n# 1 or more github.com organizations to allow git sources for\ngithub = [\"linebender\", \"Rust-GPU\"]\n# 1 or more gitlab.com organizations to allow git sources for\n#gitlab = [\"\"]\n# 1 or more bitbucket.org organizations to allow git sources for\n#bitbucket = [\"\"]\n"
  },
  {
    "path": "desktop/Cargo.toml",
    "content": "[package]\nname = \"graphite-desktop\"\nversion = \"0.1.0\"\ndescription = \"Graphite Desktop\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\nlicense = \"Apache-2.0\"\nrepository = \"\"\nedition = \"2024\"\nrust-version = \"1.87\"\n\n[[bin]]\nname = \"graphite\"\npath = \"src/main.rs\"\n\n[features]\ndefault = [\"recommended\", \"embedded_resources\"]\nrecommended = [\"gpu\", \"accelerated_paint\"]\nembedded_resources = [\"dep:graphite-desktop-embedded-resources\"]\ngpu = [\"graphite-desktop-wrapper/gpu\"]\naccelerated_paint = [\"cef/accelerated_osr\"]\n\n[dependencies]\n# Local dependencies\ngraphite-desktop-wrapper = { path = \"wrapper\" }\ngraphite-desktop-embedded-resources = { path = \"embedded-resources\", optional = true }\n\nwgpu = { workspace = true }\nwinit = { workspace = true, features = [\n\t\"wayland-csd-adwaita-notitlebar\",\n\t\"serde\",\n] }\nthiserror = { workspace = true }\nfutures = { workspace = true }\ntokio = { workspace = true }\ncef = { workspace = true }\ncef-dll-sys = { workspace = true }\ntracing-subscriber = { workspace = true }\ntracing = { workspace = true }\ndirs = { workspace = true }\nron = { workspace = true }\nbytemuck = { workspace = true }\nglam = { workspace = true }\nvello = { workspace = true }\nderivative = { workspace = true }\nrfd = { workspace = true }\nopen = { workspace = true }\nlzma-rust2 = { workspace = true }\nserde = { workspace = true }\nrand = { workspace = true, features = [\"thread_rng\"] }\nclap = { workspace = true, features = [\"derive\"] }\nfd-lock = \"4.0.4\"\nctrlc = \"3.5.1\"\nwindow_clipboard = \"0.5\"\n\n# Windows-specific dependencies\n[target.'cfg(target_os = \"windows\")'.dependencies]\nwindows = { version = \"0.58.0\", features = [\n\t\"Win32_Foundation\",\n\t\"Win32_Graphics_Dwm\",\n\t\"Win32_Graphics_Gdi\",\n\t\"Win32_System_LibraryLoader\",\n\t\"Win32_System_Com\",\n\t\"Win32_System_Console\",\n\t\"Win32_UI_Controls\",\n\t\"Win32_UI_WindowsAndMessaging\",\n\t\"Win32_UI_HiDpi\",\n\t\"Win32_UI_Shell\",\n] }\n\n# macOS-specific dependencies\n[target.'cfg(target_os = \"macos\")'.dependencies]\nobjc2 = { version = \"0.6.1\", default-features = false }\nobjc2-foundation = { version = \"0.3.2\", default-features = false }\nobjc2-app-kit = { version = \"0.3.2\", default-features = false }\nmuda = { git = \"https://github.com/timon-schelling/muda.git\", rev = \"e5bc28bbd6781b18afbfc237981f9ef47eddf863\", default-features = false }\n"
  },
  {
    "path": "desktop/assets/art.graphite.Graphite.desktop",
    "content": "[Desktop Entry]\nName=Graphite\nGenericName=Vector & Raster Graphics Editor\nComment=Open-source vector & raster graphics editor. Featuring node based procedural nondestructive editing workflow.\nExec=graphite\nTerminal=false\nType=Application\nIcon=art.graphite.Graphite\nCategories=Graphics;VectorGraphics;RasterGraphics;\nKeywords=graphite;editor;vector;raster;procedural;design;\nStartupWMClass=art.graphite.Graphite\n"
  },
  {
    "path": "desktop/bundle/Cargo.toml",
    "content": "[package]\nname = \"graphite-desktop-bundle\"\nversion = \"0.0.0\"\ndescription = \"Graphite Desktop Bundle\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\nlicense = \"Apache-2.0\"\nrepository = \"\"\nedition = \"2024\"\nrust-version = \"1.87\"\n\n[dependencies]\ncef-dll-sys = { workspace = true }\n\n[target.'cfg(target_os = \"macos\")'.dependencies]\nserde = { workspace = true }\nplist = { version = \"*\" }\n"
  },
  {
    "path": "desktop/bundle/build.rs",
    "content": "fn main() {\n\tprintln!(\"cargo:rerun-if-env-changed=CARGO_PROFILE\");\n\tprintln!(\"cargo:rerun-if-env-changed=PROFILE\");\n\tlet profile = std::env::var(\"CARGO_PROFILE\").or_else(|_| std::env::var(\"PROFILE\")).unwrap();\n\tprintln!(\"cargo:rustc-env=CARGO_PROFILE={profile}\");\n\n\tprintln!(\"cargo:rerun-if-env-changed=DEP_CEF_DLL_WRAPPER_CEF_DIR\");\n\tlet cef_dir = std::env::var(\"DEP_CEF_DLL_WRAPPER_CEF_DIR\").unwrap();\n\tprintln!(\"cargo:rustc-env=CEF_PATH={cef_dir}\");\n}\n"
  },
  {
    "path": "desktop/bundle/src/common.rs",
    "content": "#![cfg_attr(target_os = \"linux\", allow(unused))] // TODO: Remove this when bundling for linux is implemented\n\nuse std::error::Error;\nuse std::fs;\nuse std::path::{Path, PathBuf};\nuse std::process::{Command, Stdio};\n\npub(crate) const APP_NAME: &str = \"Graphite\";\npub(crate) const APP_BIN: &str = \"graphite\";\n\npub(crate) fn workspace_path() -> PathBuf {\n\tPathBuf::from(env!(\"CARGO_WORKSPACE_DIR\"))\n}\n\nfn profile_name() -> &'static str {\n\tlet mut profile = env!(\"CARGO_PROFILE\");\n\tif profile == \"debug\" {\n\t\tprofile = \"dev\";\n\t}\n\tprofile\n}\n\npub(crate) fn profile_path() -> PathBuf {\n\tworkspace_path().join(format!(\"target/{}\", env!(\"CARGO_PROFILE\")))\n}\n\npub(crate) fn cef_path() -> PathBuf {\n\tPathBuf::from(env!(\"CEF_PATH\"))\n}\n\npub(crate) fn build_bin(package: &str, bin: Option<&str>) -> Result<PathBuf, Box<dyn Error>> {\n\tlet mut args = vec![\"build\", \"--package\", package, \"--profile\", profile_name()];\n\tif let Some(bin) = bin {\n\t\targs.push(\"--bin\");\n\t\targs.push(bin);\n\t}\n\trun_command(\"cargo\", &args)?;\n\tlet profile_path = profile_path();\n\tlet mut bin_path = if let Some(bin) = bin { profile_path.join(bin) } else { profile_path.join(APP_BIN) };\n\tif cfg!(target_os = \"windows\") {\n\t\tbin_path.set_extension(\"exe\");\n\t}\n\tOk(bin_path)\n}\n\npub(crate) fn run_command(program: &str, args: &[&str]) -> Result<(), Box<dyn std::error::Error>> {\n\tlet status = Command::new(program).args(args).stdout(Stdio::inherit()).stderr(Stdio::inherit()).status()?;\n\tif !status.success() {\n\t\treturn Err(format!(\"Command '{}' with args {:?} failed with status: {}\", program, args, status).into());\n\t}\n\tOk(())\n}\n\npub(crate) fn clean_dir(dir: &Path) {\n\tif dir.exists() {\n\t\tfs::remove_dir_all(dir).unwrap();\n\t}\n\tfs::create_dir_all(dir).unwrap();\n}\n\npub(crate) fn copy_dir(src: &Path, dst: &Path) {\n\tfs::create_dir_all(dst).unwrap();\n\tfor entry in fs::read_dir(src).unwrap() {\n\t\tlet entry = entry.unwrap();\n\t\tlet dst_path = dst.join(entry.file_name());\n\t\tif entry.file_type().unwrap().is_dir() {\n\t\t\tcopy_dir(&entry.path(), &dst_path);\n\t\t} else {\n\t\t\tfs::copy(entry.path(), &dst_path).unwrap();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "desktop/bundle/src/linux.rs",
    "content": "use crate::common::*;\n\npub fn main() -> Result<(), Box<dyn std::error::Error>> {\n\tlet app_bin = build_bin(\"graphite-desktop-platform-linux\", None)?;\n\n\t// TODO: Implement bundling for linux\n\n\t// TODO: Consider adding more useful cli\n\tlet args: Vec<String> = std::env::args().collect();\n\tif let Some(pos) = args.iter().position(|a| a == \"open\") {\n\t\tlet extra_args: Vec<&str> = args[pos + 1..].iter().map(|s| s.as_str()).collect();\n\t\trun_command(&app_bin.to_string_lossy(), &extra_args).expect(\"failed to open app\");\n\t} else {\n\t\teprintln!(\"Binary built and placed at {}\", app_bin.to_string_lossy());\n\t\teprintln!(\"Bundling for Linux is not yet implemented.\");\n\t\teprintln!(\"You can still start the app with the `open` subcommand. `cargo run -p graphite-desktop-bundle -- open`\");\n\t}\n\n\tOk(())\n}\n"
  },
  {
    "path": "desktop/bundle/src/mac.rs",
    "content": "use std::collections::HashMap;\nuse std::error::Error;\nuse std::fs;\nuse std::path::{Path, PathBuf};\n\nuse crate::common::*;\n\nconst APP_ID: &str = \"art.graphite.Graphite\";\n\nconst ICONS_FILE_NAME: &str = \"graphite.icns\";\n\nconst EXEC_PATH: &str = \"Contents/MacOS\";\nconst FRAMEWORKS_PATH: &str = \"Contents/Frameworks\";\nconst RESOURCES_PATH: &str = \"Contents/Resources\";\nconst CEF_FRAMEWORK: &str = \"Chromium Embedded Framework.framework\";\n\npub fn main() -> Result<(), Box<dyn Error>> {\n\tlet app_bin = build_bin(\"graphite-desktop-platform-mac\", None)?;\n\tlet helper_bin = build_bin(\"graphite-desktop-platform-mac\", Some(\"helper\"))?;\n\n\tlet profile_path = profile_path();\n\tlet app_dir = bundle(&profile_path, &app_bin, &helper_bin);\n\n\t// TODO: Consider adding more useful cli\n\tlet args: Vec<String> = std::env::args().collect();\n\tif let Some(pos) = args.iter().position(|a| a == \"open\") {\n\t\tlet executable = app_dir.join(EXEC_PATH).join(APP_NAME);\n\t\tlet extra_args: Vec<&str> = args[pos + 1..].iter().map(|s| s.as_str()).collect();\n\t\trun_command(&executable.to_string_lossy(), &extra_args).expect(\"failed to open app\");\n\t}\n\n\tOk(())\n}\n\nfn bundle(out_dir: &Path, app_bin: &Path, helper_bin: &Path) -> PathBuf {\n\tlet app_dir = out_dir.join(APP_NAME).with_extension(\"app\");\n\n\tclean_dir(&app_dir);\n\n\tcreate_app(&app_dir, APP_ID, APP_NAME, app_bin, false);\n\n\tfor helper_type in [None, Some(\"GPU\"), Some(\"Renderer\")] {\n\t\tlet helper_id_suffix = helper_type.map(|t| format!(\".{t}\")).unwrap_or_default();\n\t\tlet helper_id = format!(\"{APP_ID}.helper{helper_id_suffix}\");\n\t\tlet helper_name_suffix = helper_type.map(|t| format!(\" ({t})\")).unwrap_or_default();\n\t\tlet helper_name = format!(\"{APP_NAME} Helper{helper_name_suffix}\");\n\t\tlet helper_app_dir = app_dir.join(FRAMEWORKS_PATH).join(&helper_name).with_extension(\"app\");\n\t\tcreate_app(&helper_app_dir, &helper_id, &helper_name, helper_bin, true);\n\t}\n\n\tcopy_dir(&cef_path().join(CEF_FRAMEWORK), &app_dir.join(FRAMEWORKS_PATH).join(CEF_FRAMEWORK));\n\n\tlet resource_dir = app_dir.join(RESOURCES_PATH);\n\tfs::create_dir_all(&resource_dir).expect(\"failed to create app resource dir\");\n\n\tlet icon_file = workspace_path().join(\"branding/app-icons\").join(ICONS_FILE_NAME);\n\tfs::copy(icon_file, resource_dir.join(ICONS_FILE_NAME)).expect(\"failed to copy icon file\");\n\n\tapp_dir\n}\n\nfn create_app(app_dir: &Path, id: &str, name: &str, bin: &Path, is_helper: bool) {\n\tfs::create_dir_all(app_dir.join(EXEC_PATH)).unwrap();\n\n\tlet app_contents_dir: &Path = &app_dir.join(\"Contents\");\n\tcreate_info_plist(app_contents_dir, id, name, is_helper).unwrap();\n\tfs::copy(bin, app_dir.join(EXEC_PATH).join(name)).unwrap();\n}\n\nfn create_info_plist(dir: &Path, id: &str, exec_name: &str, is_helper: bool) -> Result<(), Box<dyn std::error::Error>> {\n\tlet info = InfoPlist {\n\t\tcf_bundle_name: exec_name.to_string(),\n\t\tcf_bundle_identifier: id.to_string(),\n\t\tcf_bundle_display_name: exec_name.to_string(),\n\t\tcf_bundle_executable: exec_name.to_string(),\n\t\tcf_bundle_icon_file: ICONS_FILE_NAME.to_string(),\n\t\tcf_bundle_info_dictionary_version: \"6.0\".to_string(),\n\t\tcf_bundle_package_type: \"APPL\".to_string(),\n\t\tcf_bundle_signature: \"????\".to_string(),\n\t\tcf_bundle_version: \"0.0.0\".to_string(),\n\t\tcf_bundle_short_version_string: \"0.0\".to_string(),\n\t\tcf_bundle_development_region: \"en\".to_string(),\n\t\tls_environment: [(\"MallocNanoZone\".to_string(), \"0\".to_string())].iter().cloned().collect(),\n\t\tls_file_quarantine_enabled: true,\n\t\tls_minimum_system_version: \"11.0\".to_string(),\n\t\tls_ui_element: if is_helper { Some(\"1\".to_string()) } else { None },\n\t\tns_supports_automatic_graphics_switching: true,\n\t};\n\n\tlet plist_file = dir.join(\"Info.plist\");\n\tplist::to_file_xml(plist_file, &info)?;\n\tOk(())\n}\n\n#[derive(serde::Serialize)]\nstruct InfoPlist {\n\t#[serde(rename = \"CFBundleName\")]\n\tcf_bundle_name: String,\n\t#[serde(rename = \"CFBundleIdentifier\")]\n\tcf_bundle_identifier: String,\n\t#[serde(rename = \"CFBundleDisplayName\")]\n\tcf_bundle_display_name: String,\n\t#[serde(rename = \"CFBundleExecutable\")]\n\tcf_bundle_executable: String,\n\t#[serde(rename = \"CFBundleIconFile\")]\n\tcf_bundle_icon_file: String,\n\t#[serde(rename = \"CFBundleInfoDictionaryVersion\")]\n\tcf_bundle_info_dictionary_version: String,\n\t#[serde(rename = \"CFBundlePackageType\")]\n\tcf_bundle_package_type: String,\n\t#[serde(rename = \"CFBundleSignature\")]\n\tcf_bundle_signature: String,\n\t#[serde(rename = \"CFBundleVersion\")]\n\tcf_bundle_version: String,\n\t#[serde(rename = \"CFBundleShortVersionString\")]\n\tcf_bundle_short_version_string: String,\n\t#[serde(rename = \"CFBundleDevelopmentRegion\")]\n\tcf_bundle_development_region: String,\n\t#[serde(rename = \"LSEnvironment\")]\n\tls_environment: HashMap<String, String>,\n\t#[serde(rename = \"LSFileQuarantineEnabled\")]\n\tls_file_quarantine_enabled: bool,\n\t#[serde(rename = \"LSMinimumSystemVersion\")]\n\tls_minimum_system_version: String,\n\t#[serde(rename = \"LSUIElement\")]\n\tls_ui_element: Option<String>,\n\t#[serde(rename = \"NSSupportsAutomaticGraphicsSwitching\")]\n\tns_supports_automatic_graphics_switching: bool,\n}\n"
  },
  {
    "path": "desktop/bundle/src/main.rs",
    "content": "mod common;\n\n#[cfg(target_os = \"linux\")]\nmod linux;\n#[cfg(target_os = \"macos\")]\nmod mac;\n#[cfg(target_os = \"windows\")]\nmod win;\n\nfn main() {\n\t#[cfg(target_os = \"linux\")]\n\tlinux::main().unwrap();\n\t#[cfg(target_os = \"macos\")]\n\tmac::main().unwrap();\n\t#[cfg(target_os = \"windows\")]\n\twin::main().unwrap();\n}\n"
  },
  {
    "path": "desktop/bundle/src/win.rs",
    "content": "use std::error::Error;\nuse std::fs;\nuse std::path::{Path, PathBuf};\n\nuse crate::common::*;\n\nconst EXECUTABLE: &str = \"Graphite.exe\";\n\npub fn main() -> Result<(), Box<dyn Error>> {\n\tlet app_bin = build_bin(\"graphite-desktop-platform-win\", None)?;\n\n\tlet executable = bundle(&profile_path(), &app_bin);\n\n\t// TODO: Consider adding more useful cli\n\tlet args: Vec<String> = std::env::args().collect();\n\tif let Some(pos) = args.iter().position(|a| a == \"open\") {\n\t\tlet extra_args: Vec<&str> = args[pos + 1..].iter().map(|s| s.as_str()).collect();\n\t\trun_command(&executable.to_string_lossy(), &extra_args).expect(\"failed to open app\")\n\t}\n\n\tOk(())\n}\n\nfn bundle(out_dir: &Path, app_bin: &Path) -> PathBuf {\n\tlet app_dir = out_dir.join(APP_NAME);\n\n\tclean_dir(&app_dir);\n\n\tcopy_dir(&cef_path(), &app_dir);\n\n\tif let Err(e) = remove_unnecessary_cef_files(&app_dir) {\n\t\teprintln!(\"Failed to remove unnecessary CEF files: {}\", e);\n\t}\n\n\tlet bin_path = app_dir.join(EXECUTABLE);\n\tfs::copy(app_bin, &bin_path).unwrap();\n\n\tbin_path\n}\n\nfn remove_unnecessary_cef_files(app_dir: &Path) -> Result<(), Box<dyn Error>> {\n\tfs::remove_dir_all(app_dir.join(\"cmake\"))?;\n\tfs::remove_dir_all(app_dir.join(\"include\"))?;\n\tfs::remove_dir_all(app_dir.join(\"libcef_dll\"))?;\n\n\tfor entry in fs::read_dir(app_dir.join(\"locales\"))? {\n\t\tlet path = entry?.path();\n\t\tif path.is_file() && path.file_name() != Some(\"en-US.pak\".as_ref()) {\n\t\t\tfs::remove_file(path)?;\n\t\t}\n\t}\n\n\tfs::remove_file(app_dir.join(\"archive.json\"))?;\n\tfs::remove_file(app_dir.join(\"CMakeLists.txt\"))?;\n\tfs::remove_file(app_dir.join(\"bootstrapc.exe\"))?;\n\tfs::remove_file(app_dir.join(\"bootstrap.exe\"))?;\n\tfs::remove_file(app_dir.join(\"libcef.lib\"))?;\n\tfs::remove_file(app_dir.join(\"CREDITS.html\"))?;\n\n\tOk(())\n}\n"
  },
  {
    "path": "desktop/embedded-resources/Cargo.toml",
    "content": "[package]\nname = \"graphite-desktop-embedded-resources\"\nversion = \"0.1.0\"\ndescription = \"Graphite Desktop Embedded Resources\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\nlicense = \"Apache-2.0\"\nrepository = \"\"\nedition = \"2024\"\nrust-version = \"1.87\"\n\n[dependencies]\ninclude_dir = { workspace = true }\n\n[lints.rust]\nunexpected_cfgs = { level = \"warn\", check-cfg = ['cfg(embedded_resources)'] }\n"
  },
  {
    "path": "desktop/embedded-resources/build.rs",
    "content": "const EMBEDDED_RESOURCES_ENV: &str = \"EMBEDDED_RESOURCES\";\nconst DEFAULT_RESOURCES_DIR: &str = \"../../frontend/dist\";\n\nfn main() {\n\tlet mut embedded_resources: Option<String> = None;\n\n\tprintln!(\"cargo:rerun-if-env-changed={EMBEDDED_RESOURCES_ENV}\");\n\tif let Ok(embedded_resources_env) = std::env::var(EMBEDDED_RESOURCES_ENV)\n\t\t&& std::path::PathBuf::from(&embedded_resources_env).exists()\n\t{\n\t\tembedded_resources = Some(embedded_resources_env);\n\t}\n\n\tif embedded_resources.is_none() {\n\t\t// Check if the directory `DEFAULT_RESOURCES_DIR` exists and sets the embedded_resources cfg accordingly\n\t\t// Absolute path of `DEFAULT_RESOURCES_DIR` available via the `EMBEDDED_RESOURCES` environment variable\n\t\tlet crate_dir = std::path::PathBuf::from(std::env::var(\"CARGO_MANIFEST_DIR\").unwrap());\n\t\tprintln!(\"cargo:rerun-if-changed={DEFAULT_RESOURCES_DIR}\");\n\t\tif let Ok(resources) = crate_dir.join(DEFAULT_RESOURCES_DIR).canonicalize()\n\t\t\t&& resources.exists()\n\t\t{\n\t\t\tembedded_resources = Some(resources.to_string_lossy().to_string());\n\t\t}\n\t}\n\n\tif let Some(embedded_resources) = embedded_resources {\n\t\tprintln!(\"cargo:rustc-cfg=embedded_resources\");\n\t\tprintln!(\"cargo:rustc-env={EMBEDDED_RESOURCES_ENV}={embedded_resources}\");\n\t} else {\n\t\tprintln!(\"cargo:warning=Resource directory does not exist. Resources will not be embedded. Did you forget to build the frontend?\");\n\t}\n}\n"
  },
  {
    "path": "desktop/embedded-resources/src/lib.rs",
    "content": "//! This crate provides `EMBEDDED_RESOURCES` that can be included in the desktop application binary.\n//! It is intended to be used by the `embedded_resources` feature of the `graphite-desktop` crate.\n//! The build script checks if the specified resources directory exists and sets the `embedded_resources` cfg flag accordingly.\n//! If the resources directory does not exist, resources will not be embedded and a warning will be reported during compilation.\n\n#[cfg(embedded_resources)]\npub static EMBEDDED_RESOURCES: Option<include_dir::Dir> = Some(include_dir::include_dir!(\"$EMBEDDED_RESOURCES\"));\n\n#[cfg(not(embedded_resources))]\npub static EMBEDDED_RESOURCES: Option<include_dir::Dir> = None;\n"
  },
  {
    "path": "desktop/platform/linux/Cargo.toml",
    "content": "[package]\nname = \"graphite-desktop-platform-linux\"\nversion = \"0.0.0\"\ndescription = \"Graphite Desktop Platform Linux\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\nlicense = \"Apache-2.0\"\nrepository = \"\"\nedition = \"2024\"\nrust-version = \"1.87\"\n\n[[bin]]\nname = \"graphite\"\npath = \"src/main.rs\"\n\n[dependencies]\ngraphite-desktop = { path = \"../..\" }\n"
  },
  {
    "path": "desktop/platform/linux/src/main.rs",
    "content": "fn main() {\n\tgraphite_desktop::start();\n}\n"
  },
  {
    "path": "desktop/platform/mac/Cargo.toml",
    "content": "[package]\nname = \"graphite-desktop-platform-mac\"\nversion = \"0.0.0\"\ndescription = \"Graphite Desktop Platform Mac\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\nlicense = \"Apache-2.0\"\nrepository = \"\"\nedition = \"2024\"\nrust-version = \"1.87\"\n\n[[bin]]\nname = \"graphite\"\npath = \"src/main.rs\"\n\n[[bin]]\nname = \"helper\"\npath = \"src/helper.rs\"\n\n[dependencies]\ngraphite-desktop = { path = \"../..\" }\n"
  },
  {
    "path": "desktop/platform/mac/src/helper.rs",
    "content": "fn main() {\n\tgraphite_desktop::start_helper();\n}\n"
  },
  {
    "path": "desktop/platform/mac/src/main.rs",
    "content": "fn main() {\n\tgraphite_desktop::start();\n}\n"
  },
  {
    "path": "desktop/platform/win/Cargo.toml",
    "content": "[package]\nname = \"graphite-desktop-platform-win\"\nversion = \"0.0.0\"\ndescription = \"Graphite Desktop Platform Windows\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\nlicense = \"Apache-2.0\"\nrepository = \"\"\nedition = \"2024\"\nrust-version = \"1.87\"\n\n[[bin]]\nname = \"graphite\"\npath = \"src/main.rs\"\n\n[dependencies]\ngraphite-desktop = { path = \"../..\" }\n\n[target.'cfg(target_os = \"windows\")'.build-dependencies]\nwinres = \"0.1\"\n"
  },
  {
    "path": "desktop/platform/win/build.rs",
    "content": "fn main() {\n\t#[cfg(target_os = \"windows\")]\n\t{\n\t\tlet mut res = winres::WindowsResource::new();\n\n\t\tres.set_icon(\"../../../branding/app-icons/graphite.ico\");\n\n\t\tres.set_language(0x0409); // English (US)\n\n\t\t// TODO: Replace with actual version\n\t\tres.set_version_info(winres::VersionInfo::FILEVERSION, {\n\t\t\tconst MAJOR: u64 = 0;\n\t\t\tconst MINOR: u64 = 0;\n\t\t\tconst PATCH: u64 = 0;\n\t\t\tconst RELEASE: u64 = 0;\n\t\t\t(MAJOR << 48) | (MINOR << 32) | (PATCH << 16) | RELEASE\n\t\t});\n\t\tres.set(\"FileVersion\", \"0.0.0.0\");\n\t\tres.set(\"ProductVersion\", \"0.0.0.0\");\n\n\t\tres.set(\"OriginalFilename\", \"Graphite.exe\");\n\n\t\tres.set(\"FileDescription\", \"Graphite\");\n\t\tres.set(\"ProductName\", \"Graphite\");\n\n\t\t// TODO: Pull this year from the Git commit date\n\t\tres.set(\"LegalCopyright\", \"Copyright © 2026 Graphite Labs, LLC\");\n\t\tres.set(\"CompanyName\", \"Graphite Labs, LLC\");\n\n\t\tres.compile().expect(\"Failed to compile Windows resources\");\n\t}\n}\n"
  },
  {
    "path": "desktop/platform/win/src/main.rs",
    "content": "#![windows_subsystem = \"windows\"]\nfn main() {\n\tgraphite_desktop::start();\n}\n"
  },
  {
    "path": "desktop/src/app.rs",
    "content": "use rand::Rng;\nuse rfd::AsyncFileDialog;\nuse std::fs;\nuse std::io::Read;\nuse std::sync::Arc;\nuse std::sync::atomic::{AtomicBool, Ordering};\nuse std::sync::mpsc::{Receiver, Sender, SyncSender};\nuse std::thread;\nuse std::time::{Duration, Instant};\nuse winit::application::ApplicationHandler;\nuse winit::dpi::{PhysicalPosition, PhysicalSize};\nuse winit::event::{ButtonSource, ElementState, MouseButton, StartCause, WindowEvent};\nuse winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};\nuse winit::window::WindowId;\n\nuse crate::cef;\nuse crate::cli::Cli;\nuse crate::consts::CEF_MESSAGE_LOOP_MAX_ITERATIONS;\nuse crate::event::{AppEvent, AppEventScheduler};\nuse crate::persist::PersistentData;\nuse crate::preferences;\nuse crate::render::{RenderError, RenderState};\nuse crate::window::Window;\nuse crate::wrapper::messages::{DesktopFrontendMessage, DesktopWrapperMessage, InputMessage, MouseKeys, MouseState, Preferences};\nuse crate::wrapper::{DesktopWrapper, NodeGraphExecutionResult, WgpuContext, serialize_frontend_messages};\n\npub(crate) struct App {\n\trender_state: Option<RenderState>,\n\twgpu_context: WgpuContext,\n\twindow: Option<Window>,\n\twindow_scale: f64,\n\twindow_size: PhysicalSize<u32>,\n\twindow_maximized: bool,\n\twindow_fullscreen: bool,\n\tpointer_position: PhysicalPosition<f64>,\n\tpointer_lock_position: Option<PhysicalPosition<f64>>,\n\tui_scale: f64,\n\tapp_event_receiver: Receiver<AppEvent>,\n\tapp_event_scheduler: AppEventScheduler,\n\tdesktop_wrapper: DesktopWrapper,\n\tcef_context: Box<dyn cef::CefContext>,\n\tcef_schedule: Option<Instant>,\n\tcef_view_info_sender: Sender<cef::ViewInfoUpdate>,\n\tcef_init_successful: bool,\n\tstart_render_sender: SyncSender<()>,\n\tweb_communication_initialized: bool,\n\tweb_communication_startup_buffer: Vec<Vec<u8>>,\n\tpersistent_data: PersistentData,\n\t#[cfg_attr(not(target_os = \"macos\"), expect(unused))]\n\tpreferences: Preferences,\n\tcli: Cli,\n\tstartup_time: Option<Instant>,\n\texiting: Arc<AtomicBool>,\n\texit_reason: ExitReason,\n}\n\nimpl App {\n\tpub(crate) fn init() {\n\t\tWindow::init();\n\t}\n\n\tpub(crate) fn new(\n\t\tcef_context: Box<dyn cef::CefContext>,\n\t\tcef_view_info_sender: Sender<cef::ViewInfoUpdate>,\n\t\twgpu_context: WgpuContext,\n\t\tapp_event_receiver: Receiver<AppEvent>,\n\t\tapp_event_scheduler: AppEventScheduler,\n\t\tpreferences: Preferences,\n\t\tcli: Cli,\n\t) -> Self {\n\t\tlet ctrlc_app_event_scheduler = app_event_scheduler.clone();\n\t\tctrlc::set_handler(move || {\n\t\t\ttracing::info!(\"Termination signal received, exiting...\");\n\t\t\tctrlc_app_event_scheduler.schedule(AppEvent::Exit);\n\t\t})\n\t\t.expect(\"Error setting Ctrl-C handler\");\n\n\t\tlet exiting = Arc::new(AtomicBool::new(false));\n\n\t\tlet rendering_app_event_scheduler = app_event_scheduler.clone();\n\t\tlet (start_render_sender, start_render_receiver) = std::sync::mpsc::sync_channel(1);\n\t\tlet exiting_clone = exiting.clone();\n\t\tstd::thread::spawn(move || {\n\t\t\tlet runtime = tokio::runtime::Runtime::new().unwrap();\n\t\t\tloop {\n\t\t\t\tlet result = runtime.block_on(DesktopWrapper::execute_node_graph());\n\t\t\t\trendering_app_event_scheduler.schedule(AppEvent::NodeGraphExecutionResult(result));\n\t\t\t\tlet _ = start_render_receiver.recv_timeout(Duration::from_millis(10));\n\t\t\t\tif exiting_clone.load(Ordering::Relaxed) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\tlet mut persistent_data = PersistentData::default();\n\t\tpersistent_data.load_from_disk();\n\n\t\tlet desktop_wrapper = DesktopWrapper::new(rand::rng().random());\n\n\t\tSelf {\n\t\t\trender_state: None,\n\t\t\twgpu_context,\n\t\t\twindow: None,\n\t\t\twindow_scale: 1.,\n\t\t\twindow_size: PhysicalSize { width: 0, height: 0 },\n\t\t\twindow_maximized: false,\n\t\t\twindow_fullscreen: false,\n\t\t\tpointer_position: Default::default(),\n\t\t\tpointer_lock_position: Default::default(),\n\t\t\tui_scale: 1.,\n\t\t\tapp_event_receiver,\n\t\t\tapp_event_scheduler,\n\t\t\tdesktop_wrapper,\n\t\t\tcef_context,\n\t\t\tcef_schedule: Some(Instant::now()),\n\t\t\tcef_view_info_sender,\n\t\t\tcef_init_successful: false,\n\t\t\tstart_render_sender,\n\t\t\tweb_communication_initialized: false,\n\t\t\tweb_communication_startup_buffer: Vec::new(),\n\t\t\tpersistent_data,\n\t\t\tpreferences,\n\t\t\tcli,\n\t\t\tstartup_time: None,\n\t\t\texiting,\n\t\t\texit_reason: ExitReason::Shutdown,\n\t\t}\n\t}\n\n\tpub(crate) fn run(mut self, event_loop: EventLoop) -> ExitReason {\n\t\tevent_loop.run_app(&mut self).unwrap();\n\t\tself.exit_reason\n\t}\n\n\tfn exit(&mut self, reason: Option<ExitReason>) {\n\t\tif self.exiting.swap(true, Ordering::Relaxed) {\n\t\t\treturn;\n\t\t}\n\t\tlet _ = self.start_render_sender.send(());\n\t\tif let Some(reason) = reason {\n\t\t\tself.exit_reason = reason;\n\t\t}\n\t\tself.app_event_scheduler.schedule(AppEvent::Exit);\n\t}\n\n\tfn resize(&mut self) {\n\t\tlet Some(window) = &self.window else {\n\t\t\ttracing::error!(\"Resize failed due to missing window\");\n\t\t\treturn;\n\t\t};\n\n\t\tlet maximized = window.is_maximized();\n\t\tif maximized != self.window_maximized {\n\t\t\tself.window_maximized = maximized;\n\t\t\tself.app_event_scheduler.schedule(AppEvent::DesktopWrapperMessage(DesktopWrapperMessage::UpdateMaximized { maximized }));\n\t\t}\n\n\t\tlet fullscreen = window.is_fullscreen();\n\t\tif fullscreen != self.window_fullscreen {\n\t\t\tself.window_fullscreen = fullscreen;\n\t\t\tself.app_event_scheduler\n\t\t\t\t.schedule(AppEvent::DesktopWrapperMessage(DesktopWrapperMessage::UpdateFullscreen { fullscreen }));\n\t\t}\n\n\t\tlet size = window.surface_size();\n\t\tlet scale = window.scale_factor() * self.ui_scale;\n\t\tlet is_new_size = size != self.window_size;\n\t\tlet is_new_scale = scale != self.window_scale;\n\n\t\tif !is_new_size && !is_new_scale {\n\t\t\treturn;\n\t\t}\n\n\t\tif is_new_size {\n\t\t\tlet _ = self.cef_view_info_sender.send(cef::ViewInfoUpdate::Size {\n\t\t\t\twidth: size.width,\n\t\t\t\theight: size.height,\n\t\t\t});\n\t\t}\n\t\tif is_new_scale {\n\t\t\tlet _ = self.cef_view_info_sender.send(cef::ViewInfoUpdate::Scale(scale));\n\t\t}\n\n\t\tself.cef_context.notify_view_info_changed();\n\n\t\tif let Some(render_state) = &mut self.render_state {\n\t\t\trender_state.resize(size.width, size.height);\n\t\t}\n\n\t\twindow.request_redraw();\n\n\t\tself.window_size = size;\n\t\tself.window_scale = scale;\n\t}\n\n\tfn handle_desktop_frontend_message(&mut self, message: DesktopFrontendMessage, responses: &mut Vec<DesktopWrapperMessage>) {\n\t\tmatch message {\n\t\t\tDesktopFrontendMessage::ToWeb(messages) => {\n\t\t\t\tlet Some(bytes) = serialize_frontend_messages(messages) else {\n\t\t\t\t\ttracing::error!(\"Failed to serialize frontend messages\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tself.send_or_queue_web_message(bytes);\n\t\t\t}\n\t\t\tDesktopFrontendMessage::OpenFileDialog { title, filters, context } => {\n\t\t\t\tlet app_event_scheduler = self.app_event_scheduler.clone();\n\t\t\t\tlet _ = thread::spawn(move || {\n\t\t\t\t\tlet mut dialog = AsyncFileDialog::new().set_title(title);\n\t\t\t\t\tfor filter in filters {\n\t\t\t\t\t\tdialog = dialog.add_filter(filter.name, &filter.extensions);\n\t\t\t\t\t}\n\n\t\t\t\t\tlet show_dialog = async move { dialog.pick_file().await.map(|f| f.path().to_path_buf()) };\n\n\t\t\t\t\tif let Some(path) = futures::executor::block_on(show_dialog)\n\t\t\t\t\t\t&& let Ok(content) = fs::read(&path)\n\t\t\t\t\t{\n\t\t\t\t\t\tlet message = DesktopWrapperMessage::FileDialogResult { path, content, context };\n\t\t\t\t\t\tapp_event_scheduler.schedule(AppEvent::DesktopWrapperMessage(message));\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t\tDesktopFrontendMessage::SaveFileDialog {\n\t\t\t\ttitle,\n\t\t\t\tdefault_filename,\n\t\t\t\tdefault_folder,\n\t\t\t\tfilters,\n\t\t\t\tcontext,\n\t\t\t} => {\n\t\t\t\tlet app_event_scheduler = self.app_event_scheduler.clone();\n\t\t\t\tlet _ = thread::spawn(move || {\n\t\t\t\t\tlet mut dialog = AsyncFileDialog::new().set_title(title).set_file_name(default_filename);\n\t\t\t\t\tif let Some(folder) = default_folder {\n\t\t\t\t\t\tdialog = dialog.set_directory(folder);\n\t\t\t\t\t}\n\t\t\t\t\tfor filter in filters {\n\t\t\t\t\t\tdialog = dialog.add_filter(filter.name, &filter.extensions);\n\t\t\t\t\t}\n\n\t\t\t\t\tlet show_dialog = async move { dialog.save_file().await.map(|f| f.path().to_path_buf()) };\n\n\t\t\t\t\tif let Some(path) = futures::executor::block_on(show_dialog) {\n\t\t\t\t\t\tlet message = DesktopWrapperMessage::SaveFileDialogResult { path, context };\n\t\t\t\t\t\tapp_event_scheduler.schedule(AppEvent::DesktopWrapperMessage(message));\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t\tDesktopFrontendMessage::WriteFile { path, content } => {\n\t\t\t\tif let Err(e) = fs::write(&path, content) {\n\t\t\t\t\ttracing::error!(\"Failed to write file {}: {}\", path.display(), e);\n\t\t\t\t}\n\t\t\t}\n\t\t\tDesktopFrontendMessage::OpenUrl(url) => {\n\t\t\t\tlet _ = thread::spawn(move || {\n\t\t\t\t\tif let Err(e) = open::that(&url) {\n\t\t\t\t\t\ttracing::error!(\"Failed to open URL: {}: {}\", url, e);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t\tDesktopFrontendMessage::UpdateViewportPhysicalBounds { x, y, width, height } => {\n\t\t\t\tif let Some(render_state) = &mut self.render_state\n\t\t\t\t\t&& let Some(window) = &self.window\n\t\t\t\t{\n\t\t\t\t\tlet window_size = window.surface_size();\n\n\t\t\t\t\tlet viewport_offset_x = x / window_size.width as f64;\n\t\t\t\t\tlet viewport_offset_y = y / window_size.height as f64;\n\t\t\t\t\trender_state.set_viewport_offset([viewport_offset_x as f32, viewport_offset_y as f32]);\n\n\t\t\t\t\tlet viewport_scale_x = if width != 0.0 { window_size.width as f64 / width } else { 1.0 };\n\t\t\t\t\tlet viewport_scale_y = if height != 0.0 { window_size.height as f64 / height } else { 1.0 };\n\t\t\t\t\trender_state.set_viewport_scale([viewport_scale_x as f32, viewport_scale_y as f32]);\n\t\t\t\t}\n\t\t\t}\n\t\t\tDesktopFrontendMessage::UpdateUIScale { scale } => {\n\t\t\t\tself.ui_scale = scale;\n\t\t\t\tself.resize();\n\t\t\t}\n\t\t\tDesktopFrontendMessage::UpdateOverlays(scene) => {\n\t\t\t\tif let Some(render_state) = &mut self.render_state {\n\t\t\t\t\trender_state.set_overlays_scene(scene);\n\t\t\t\t}\n\t\t\t\tif let Some(window) = &self.window {\n\t\t\t\t\twindow.request_redraw();\n\t\t\t\t}\n\t\t\t}\n\t\t\tDesktopFrontendMessage::PersistenceWriteDocument { id, document } => {\n\t\t\t\tself.persistent_data.write_document(id, document);\n\t\t\t}\n\t\t\tDesktopFrontendMessage::PersistenceDeleteDocument { id } => {\n\t\t\t\tself.persistent_data.delete_document(&id);\n\t\t\t}\n\t\t\tDesktopFrontendMessage::PersistenceUpdateCurrentDocument { id } => {\n\t\t\t\tself.persistent_data.set_current_document(id);\n\t\t\t}\n\t\t\tDesktopFrontendMessage::PersistenceUpdateDocumentsList { ids } => {\n\t\t\t\tself.persistent_data.set_document_order(ids);\n\t\t\t}\n\t\t\tDesktopFrontendMessage::PersistenceWritePreferences { preferences } => {\n\t\t\t\tpreferences::write(preferences);\n\t\t\t}\n\t\t\tDesktopFrontendMessage::PersistenceLoadPreferences => {\n\t\t\t\tlet preferences = preferences::read();\n\t\t\t\tlet message = DesktopWrapperMessage::LoadPreferences { preferences };\n\t\t\t\tresponses.push(message);\n\t\t\t}\n\t\t\tDesktopFrontendMessage::PersistenceLoadCurrentDocument => {\n\t\t\t\tif let Some((id, document)) = self.persistent_data.current_document() {\n\t\t\t\t\tlet message = DesktopWrapperMessage::LoadDocument {\n\t\t\t\t\t\tid,\n\t\t\t\t\t\tdocument,\n\t\t\t\t\t\tto_front: false,\n\t\t\t\t\t\tselect_after_open: true,\n\t\t\t\t\t};\n\t\t\t\t\tresponses.push(message);\n\t\t\t\t}\n\t\t\t}\n\t\t\tDesktopFrontendMessage::PersistenceLoadRemainingDocuments => {\n\t\t\t\tfor (id, document) in self.persistent_data.documents_before_current().into_iter().rev() {\n\t\t\t\t\tlet message = DesktopWrapperMessage::LoadDocument {\n\t\t\t\t\t\tid,\n\t\t\t\t\t\tdocument,\n\t\t\t\t\t\tto_front: true,\n\t\t\t\t\t\tselect_after_open: false,\n\t\t\t\t\t};\n\t\t\t\t\tresponses.push(message);\n\t\t\t\t}\n\t\t\t\tfor (id, document) in self.persistent_data.documents_after_current() {\n\t\t\t\t\tlet message = DesktopWrapperMessage::LoadDocument {\n\t\t\t\t\t\tid,\n\t\t\t\t\t\tdocument,\n\t\t\t\t\t\tto_front: false,\n\t\t\t\t\t\tselect_after_open: false,\n\t\t\t\t\t};\n\t\t\t\t\tresponses.push(message);\n\t\t\t\t}\n\t\t\t\tif let Some(id) = self.persistent_data.current_document_id() {\n\t\t\t\t\tlet message = DesktopWrapperMessage::SelectDocument { id };\n\t\t\t\t\tresponses.push(message);\n\t\t\t\t}\n\t\t\t}\n\t\t\tDesktopFrontendMessage::OpenLaunchDocuments => {\n\t\t\t\tif self.cli.files.is_empty() {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet app_event_scheduler = self.app_event_scheduler.clone();\n\t\t\t\tlet launch_documents = std::mem::take(&mut self.cli.files);\n\t\t\t\tlet _ = thread::spawn(move || {\n\t\t\t\t\tfor path in launch_documents {\n\t\t\t\t\t\ttracing::info!(\"Opening file from command line: {}\", path.display());\n\t\t\t\t\t\tif let Ok(content) = fs::read(&path) {\n\t\t\t\t\t\t\tlet message = DesktopWrapperMessage::OpenFile { path, content };\n\t\t\t\t\t\t\tapp_event_scheduler.schedule(AppEvent::DesktopWrapperMessage(message));\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ttracing::error!(\"Failed to read file: {}\", path.display());\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t\tDesktopFrontendMessage::UpdateMenu { entries } => {\n\t\t\t\tif let Some(window) = &self.window {\n\t\t\t\t\twindow.update_menu(entries);\n\t\t\t\t}\n\t\t\t}\n\t\t\tDesktopFrontendMessage::ClipboardRead => {\n\t\t\t\tif let Some(window) = &self.window {\n\t\t\t\t\tlet content = window.clipboard_read();\n\t\t\t\t\tlet message = DesktopWrapperMessage::ClipboardReadResult { content };\n\t\t\t\t\tself.app_event_scheduler.schedule(AppEvent::DesktopWrapperMessage(message));\n\t\t\t\t}\n\t\t\t}\n\t\t\tDesktopFrontendMessage::ClipboardWrite { content } => {\n\t\t\t\tif let Some(window) = &mut self.window {\n\t\t\t\t\twindow.clipboard_write(content);\n\t\t\t\t}\n\t\t\t}\n\t\t\tDesktopFrontendMessage::PointerLock => {\n\t\t\t\tself.pointer_lock_position = Some(self.pointer_position);\n\t\t\t\tif let Some(window) = &self.window {\n\t\t\t\t\twindow.start_pointer_lock();\n\t\t\t\t}\n\t\t\t}\n\t\t\tDesktopFrontendMessage::WindowClose => {\n\t\t\t\tself.app_event_scheduler.schedule(AppEvent::Exit);\n\t\t\t}\n\t\t\tDesktopFrontendMessage::WindowMinimize => {\n\t\t\t\tif let Some(window) = &self.window {\n\t\t\t\t\twindow.minimize();\n\t\t\t\t}\n\t\t\t}\n\t\t\tDesktopFrontendMessage::WindowMaximize => {\n\t\t\t\tif let Some(window) = &self.window {\n\t\t\t\t\twindow.toggle_maximize();\n\t\t\t\t}\n\t\t\t}\n\t\t\tDesktopFrontendMessage::WindowFullscreen => {\n\t\t\t\tif let Some(window) = &mut self.window {\n\t\t\t\t\twindow.toggle_fullscreen();\n\t\t\t\t}\n\t\t\t}\n\t\t\tDesktopFrontendMessage::WindowDrag => {\n\t\t\t\tif let Some(window) = &self.window {\n\t\t\t\t\twindow.start_drag();\n\t\t\t\t}\n\t\t\t}\n\t\t\tDesktopFrontendMessage::WindowHide => {\n\t\t\t\tif let Some(window) = &self.window {\n\t\t\t\t\twindow.hide();\n\t\t\t\t}\n\t\t\t}\n\t\t\tDesktopFrontendMessage::WindowHideOthers => {\n\t\t\t\tif let Some(window) = &self.window {\n\t\t\t\t\twindow.hide_others();\n\t\t\t\t}\n\t\t\t}\n\t\t\tDesktopFrontendMessage::WindowShowAll => {\n\t\t\t\tif let Some(window) = &self.window {\n\t\t\t\t\twindow.show_all();\n\t\t\t\t}\n\t\t\t}\n\t\t\tDesktopFrontendMessage::Restart => {\n\t\t\t\tself.exit(Some(ExitReason::Restart));\n\t\t\t}\n\t\t\tDesktopFrontendMessage::LoadThirdPartyLicenses => {\n\t\t\t\tlet compressed = include_bytes!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/third-party-licenses.txt.xz\"));\n\t\t\t\tlet mut reader = lzma_rust2::XzReader::new(compressed.as_slice(), false);\n\t\t\t\tlet mut text = String::new();\n\t\t\t\tif let Err(e) = reader.read_to_string(&mut text) {\n\t\t\t\t\ttracing::error!(\"Failed to decompress third-party licenses: {e}\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet message = DesktopWrapperMessage::LoadThirdPartyLicenses { text };\n\t\t\t\tresponses.push(message);\n\t\t\t}\n\t\t}\n\t}\n\n\tfn handle_desktop_frontend_messages(&mut self, messages: Vec<DesktopFrontendMessage>) {\n\t\tlet mut responses = Vec::new();\n\t\tfor message in messages {\n\t\t\tself.handle_desktop_frontend_message(message, &mut responses);\n\t\t}\n\t\tfor message in responses {\n\t\t\tself.dispatch_desktop_wrapper_message(message);\n\t\t}\n\t}\n\n\tfn dispatch_desktop_wrapper_message(&mut self, message: DesktopWrapperMessage) {\n\t\tlet responses = self.desktop_wrapper.dispatch(message);\n\t\tself.handle_desktop_frontend_messages(responses);\n\t}\n\n\tfn send_or_queue_web_message(&mut self, message: Vec<u8>) {\n\t\tif self.web_communication_initialized {\n\t\t\tself.cef_context.send_web_message(message);\n\t\t} else {\n\t\t\tself.web_communication_startup_buffer.push(message);\n\t\t}\n\t}\n\n\tfn user_event(&mut self, event_loop: &dyn ActiveEventLoop, event: AppEvent) {\n\t\tmatch event {\n\t\t\tAppEvent::WebCommunicationInitialized => {\n\t\t\t\tself.web_communication_initialized = true;\n\t\t\t\tfor message in self.web_communication_startup_buffer.drain(..) {\n\t\t\t\t\tself.cef_context.send_web_message(message);\n\t\t\t\t}\n\t\t\t}\n\t\t\tAppEvent::DesktopWrapperMessage(message) => self.dispatch_desktop_wrapper_message(message),\n\t\t\tAppEvent::NodeGraphExecutionResult(result) => match result {\n\t\t\t\tNodeGraphExecutionResult::HasRun(texture) => {\n\t\t\t\t\tself.dispatch_desktop_wrapper_message(DesktopWrapperMessage::PollNodeGraphEvaluation);\n\t\t\t\t\tif let Some(texture) = texture\n\t\t\t\t\t\t&& let Some(render_state) = self.render_state.as_mut()\n\t\t\t\t\t\t&& let Some(window) = self.window.as_ref()\n\t\t\t\t\t{\n\t\t\t\t\t\trender_state.bind_viewport_texture(texture);\n\t\t\t\t\t\twindow.request_redraw();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tNodeGraphExecutionResult::NotRun => {}\n\t\t\t},\n\t\t\tAppEvent::UiUpdate(texture) => {\n\t\t\t\tif let Some(render_state) = self.render_state.as_mut() {\n\t\t\t\t\trender_state.bind_ui_texture(texture);\n\t\t\t\t}\n\t\t\t\tif let Some(window) = &self.window {\n\t\t\t\t\twindow.request_redraw();\n\t\t\t\t}\n\t\t\t\tif !self.cef_init_successful {\n\t\t\t\t\tself.cef_init_successful = true;\n\t\t\t\t}\n\t\t\t}\n\t\t\tAppEvent::ScheduleBrowserWork(instant) => {\n\t\t\t\tif instant <= Instant::now() {\n\t\t\t\t\tself.cef_context.work();\n\t\t\t\t} else {\n\t\t\t\t\tself.cef_schedule = Some(instant);\n\t\t\t\t}\n\t\t\t}\n\t\t\tAppEvent::CursorChange(cursor) => {\n\t\t\t\tif let Some(window) = &mut self.window {\n\t\t\t\t\twindow.set_cursor(event_loop, cursor);\n\t\t\t\t}\n\t\t\t}\n\t\t\tAppEvent::Exit => {\n\t\t\t\ttracing::info!(\"Exiting main event loop\");\n\t\t\t\tevent_loop.exit();\n\t\t\t}\n\t\t\t#[cfg(target_os = \"macos\")]\n\t\t\tAppEvent::MenuEvent { id } => {\n\t\t\t\tself.dispatch_desktop_wrapper_message(DesktopWrapperMessage::MenuEvent { id });\n\t\t\t}\n\t\t}\n\t}\n}\nimpl ApplicationHandler for App {\n\tfn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {\n\t\tlet window = Window::new(event_loop, self.app_event_scheduler.clone());\n\t\tself.window = Some(window);\n\n\t\t#[cfg(not(target_os = \"macos\"))]\n\t\tlet present_mode = None;\n\t\t#[cfg(target_os = \"macos\")]\n\t\tlet present_mode = if !self.preferences.vsync { Some(wgpu::PresentMode::Immediate) } else { None };\n\n\t\tlet render_state = RenderState::new(self.window.as_ref().unwrap(), self.wgpu_context.clone(), present_mode);\n\t\tself.render_state = Some(render_state);\n\n\t\tif let Some(window) = &self.window.as_ref() {\n\t\t\twindow.show();\n\t\t}\n\n\t\tself.resize();\n\n\t\tself.desktop_wrapper.init(self.wgpu_context.clone());\n\n\t\tself.startup_time = Some(Instant::now());\n\t}\n\n\tfn proxy_wake_up(&mut self, event_loop: &dyn ActiveEventLoop) {\n\t\twhile let Ok(event) = self.app_event_receiver.try_recv() {\n\t\t\tself.user_event(event_loop, event);\n\t\t}\n\t}\n\n\tfn window_event(&mut self, _event_loop: &dyn ActiveEventLoop, _window_id: WindowId, event: WindowEvent) {\n\t\t// Handle pointer lock release\n\t\tif let Some(pointer_lock_position) = self.pointer_lock_position\n\t\t\t&& let WindowEvent::PointerButton {\n\t\t\t\tstate: ElementState::Released,\n\t\t\t\tbutton: ButtonSource::Mouse(MouseButton::Left),\n\t\t\t\t..\n\t\t\t} = event\n\t\t{\n\t\t\tself.pointer_lock_position = None;\n\t\t\tif let Some(window) = &self.window {\n\t\t\t\twindow.end_pointer_lock();\n\t\t\t}\n\t\t\tself.cef_context.handle_window_event(&WindowEvent::PointerMoved {\n\t\t\t\tdevice_id: None,\n\t\t\t\tposition: pointer_lock_position,\n\t\t\t\tprimary: true,\n\t\t\t\tsource: winit::event::PointerSource::Mouse,\n\t\t\t});\n\t\t}\n\n\t\tself.cef_context.handle_window_event(&event);\n\n\t\tmatch event {\n\t\t\tWindowEvent::CloseRequested => {\n\t\t\t\tself.app_event_scheduler.schedule(AppEvent::Exit);\n\t\t\t}\n\t\t\tWindowEvent::SurfaceResized(_) | WindowEvent::ScaleFactorChanged { .. } => {\n\t\t\t\tself.resize();\n\t\t\t}\n\t\t\tWindowEvent::RedrawRequested => {\n\t\t\t\t#[cfg(target_os = \"macos\")]\n\t\t\t\tself.resize();\n\n\t\t\t\tlet Some(render_state) = &mut self.render_state else { return };\n\t\t\t\tif let Some(window) = &self.window {\n\t\t\t\t\tif !window.can_render() {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tmatch render_state.render(window) {\n\t\t\t\t\t\tOk(_) => {}\n\t\t\t\t\t\tErr(RenderError::OutdatedUITextureError) => {\n\t\t\t\t\t\t\tself.cef_context.notify_view_info_changed();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tErr(RenderError::SurfaceError(wgpu::SurfaceError::Lost)) => {\n\t\t\t\t\t\t\ttracing::warn!(\"lost surface\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\tErr(RenderError::SurfaceError(wgpu::SurfaceError::OutOfMemory)) => {\n\t\t\t\t\t\t\ttracing::error!(\"GPU out of memory\");\n\t\t\t\t\t\t\tself.exit(None);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tErr(RenderError::SurfaceError(e)) => tracing::error!(\"Render error: {:?}\", e),\n\t\t\t\t\t}\n\t\t\t\t\tlet _ = self.start_render_sender.try_send(());\n\t\t\t\t}\n\n\t\t\t\tif !self.cef_init_successful\n\t\t\t\t\t&& !self.cli.disable_ui_acceleration\n\t\t\t\t\t&& self.web_communication_initialized\n\t\t\t\t\t&& let Some(startup_time) = self.startup_time\n\t\t\t\t\t&& startup_time.elapsed() > Duration::from_secs(3)\n\t\t\t\t{\n\t\t\t\t\ttracing::error!(\"UI acceleration not working, exiting.\");\n\t\t\t\t\tself.exit(Some(ExitReason::UiAccelerationFailure));\n\t\t\t\t}\n\t\t\t}\n\t\t\tWindowEvent::DragDropped { paths, .. } => {\n\t\t\t\tfor path in paths {\n\t\t\t\t\tmatch fs::read(&path) {\n\t\t\t\t\t\tOk(content) => {\n\t\t\t\t\t\t\tlet message = DesktopWrapperMessage::ImportFile { path, content };\n\t\t\t\t\t\t\tself.app_event_scheduler.schedule(AppEvent::DesktopWrapperMessage(message));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tErr(e) => {\n\t\t\t\t\t\t\ttracing::error!(\"Failed to read dropped file {}: {}\", path.display(), e);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Forward and Back buttons are not supported by CEF and thus need to be directly forwarded the editor\n\t\t\tWindowEvent::PointerButton {\n\t\t\t\tbutton: ButtonSource::Mouse(button),\n\t\t\t\tstate: ElementState::Pressed,\n\t\t\t\t..\n\t\t\t} => {\n\t\t\t\tlet mouse_keys = match button {\n\t\t\t\t\tMouseButton::Back => Some(MouseKeys::BACK),\n\t\t\t\t\tMouseButton::Forward => Some(MouseKeys::FORWARD),\n\t\t\t\t\t_ => None,\n\t\t\t\t};\n\t\t\t\tif let Some(mouse_keys) = mouse_keys {\n\t\t\t\t\tlet message = DesktopWrapperMessage::Input(InputMessage::PointerDown {\n\t\t\t\t\t\teditor_mouse_state: MouseState { mouse_keys, ..Default::default() },\n\t\t\t\t\t\tmodifier_keys: Default::default(),\n\t\t\t\t\t});\n\t\t\t\t\tself.app_event_scheduler.schedule(AppEvent::DesktopWrapperMessage(message));\n\n\t\t\t\t\tlet message = DesktopWrapperMessage::Input(InputMessage::PointerUp {\n\t\t\t\t\t\teditor_mouse_state: Default::default(),\n\t\t\t\t\t\tmodifier_keys: Default::default(),\n\t\t\t\t\t});\n\t\t\t\t\tself.app_event_scheduler.schedule(AppEvent::DesktopWrapperMessage(message));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tWindowEvent::PointerMoved { position, .. } | WindowEvent::PointerLeft { position: Some(position), .. } | WindowEvent::PointerEntered { position, .. }\n\t\t\t\tif self.pointer_lock_position.is_none() =>\n\t\t\t{\n\t\t\t\tself.pointer_position = position;\n\t\t\t}\n\n\t\t\t_ => {}\n\t\t}\n\n\t\t// Notify cef of possible input events\n\t\tself.cef_context.work();\n\t}\n\n\tfn device_event(&mut self, _event_loop: &dyn ActiveEventLoop, _device_id: Option<winit::event::DeviceId>, event: winit::event::DeviceEvent) {\n\t\tif self.pointer_lock_position.is_some()\n\t\t\t&& let winit::event::DeviceEvent::PointerMotion { delta: (x, y) } = event\n\t\t{\n\t\t\tlet message = DesktopWrapperMessage::PointerLockMove { x, y };\n\t\t\tself.app_event_scheduler.schedule(AppEvent::DesktopWrapperMessage(message));\n\t\t}\n\t}\n\n\tfn new_events(&mut self, _event_loop: &dyn ActiveEventLoop, cause: winit::event::StartCause) {\n\t\tif let StartCause::ResumeTimeReached { .. } = cause\n\t\t\t&& let Some(window) = &self.window\n\t\t{\n\t\t\twindow.request_redraw();\n\t\t}\n\t}\n\n\tfn about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) {\n\t\t// Set a timeout in case we miss any cef schedule requests\n\t\tlet mut wait_until = Instant::now() + Duration::from_millis(10);\n\t\tif let Some(schedule) = self.cef_schedule\n\t\t\t&& schedule < Instant::now()\n\t\t{\n\t\t\tself.cef_schedule = None;\n\t\t\t// Poll cef message loop multiple times to avoid message loop starvation\n\t\t\tfor _ in 0..CEF_MESSAGE_LOOP_MAX_ITERATIONS {\n\t\t\t\tself.cef_context.work();\n\t\t\t}\n\t\t} else if let Some(cef_schedule) = self.cef_schedule {\n\t\t\twait_until = wait_until.min(cef_schedule);\n\t\t}\n\t\tevent_loop.set_control_flow(ControlFlow::WaitUntil(wait_until));\n\t}\n}\n\npub(crate) enum ExitReason {\n\tShutdown,\n\tRestart,\n\tUiAccelerationFailure,\n}\n"
  },
  {
    "path": "desktop/src/cef/consts.rs",
    "content": "use std::time::Duration;\n\npub(crate) const RESOURCE_SCHEME: &str = \"resources\";\npub(crate) const RESOURCE_DOMAIN: &str = \"resources\";\n\npub(crate) const SCROLL_LINE_HEIGHT: usize = 40;\npub(crate) const SCROLL_LINE_WIDTH: usize = 40;\n\n#[cfg(target_os = \"linux\")]\npub(crate) const SCROLL_SPEED_X: f32 = 3.0;\n#[cfg(target_os = \"linux\")]\npub(crate) const SCROLL_SPEED_Y: f32 = 3.0;\n\n#[cfg(not(target_os = \"linux\"))]\npub(crate) const SCROLL_SPEED_X: f32 = 1.0;\n#[cfg(not(target_os = \"linux\"))]\npub(crate) const SCROLL_SPEED_Y: f32 = 1.0;\n\npub(crate) const PINCH_ZOOM_SPEED: f64 = 300.0;\n\npub(crate) const MULTICLICK_TIMEOUT: Duration = Duration::from_millis(500);\npub(crate) const MULTICLICK_ALLOWED_TRAVEL: usize = 4;\n"
  },
  {
    "path": "desktop/src/cef/context/builder.rs",
    "content": "use cef::args::Args;\nuse cef::sys::{CEF_API_VERSION_LAST, cef_log_severity_t};\nuse cef::{\n\tApp, BrowserSettings, CefString, Client, DictionaryValue, ImplCommandLine, ImplRequestContext, LogSeverity, RequestContextSettings, SchemeHandlerFactory, Settings, WindowInfo, api_hash,\n\tbrowser_host_create_browser_sync, execute_process,\n};\nuse std::path::{Path, PathBuf};\n\nuse super::CefContext;\nuse super::singlethreaded::SingleThreadedCefContext;\nuse crate::cef::CefEventHandler;\nuse crate::cef::consts::{RESOURCE_DOMAIN, RESOURCE_SCHEME};\nuse crate::cef::dirs::{create_instance_dir, delete_instance_dirs};\nuse crate::cef::input::InputState;\nuse crate::cef::internal::{BrowserProcessAppImpl, BrowserProcessClientImpl, RenderProcessAppImpl, SchemeHandlerFactoryImpl};\n\npub(crate) struct CefContextBuilder<H: CefEventHandler> {\n\tpub(crate) args: Args,\n\tpub(crate) is_sub_process: bool,\n\t_marker: std::marker::PhantomData<H>,\n}\n\nunsafe impl<H: CefEventHandler> Send for CefContextBuilder<H> {}\n\nimpl<H: CefEventHandler> CefContextBuilder<H> {\n\tpub(crate) fn new() -> Self {\n\t\tSelf::new_inner(false)\n\t}\n\n\tpub(crate) fn new_helper() -> Self {\n\t\tSelf::new_inner(true)\n\t}\n\n\tfn new_inner(helper: bool) -> Self {\n\t\t#[cfg(target_os = \"macos\")]\n\t\tlet _loader = {\n\t\t\tlet loader = cef::library_loader::LibraryLoader::new(&std::env::current_exe().unwrap(), helper);\n\t\t\tassert!(loader.load());\n\t\t\tloader\n\t\t};\n\t\t#[cfg(not(target_os = \"macos\"))]\n\t\tlet _ = helper;\n\n\t\tlet _ = api_hash(CEF_API_VERSION_LAST, 0);\n\n\t\tlet args = Args::new();\n\t\tlet cmd = args.as_cmd_line().unwrap();\n\t\tlet switch = CefString::from(\"type\");\n\t\tlet is_sub_process = cmd.has_switch(Some(&switch)) == 1;\n\n\t\tSelf {\n\t\t\targs,\n\t\t\tis_sub_process,\n\t\t\t_marker: std::marker::PhantomData,\n\t\t}\n\t}\n\n\tpub(crate) fn is_sub_process(&self) -> bool {\n\t\tself.is_sub_process\n\t}\n\n\tpub(crate) fn execute_sub_process(&self) -> SetupError {\n\t\tlet cmd = self.args.as_cmd_line().unwrap();\n\t\tlet switch = CefString::from(\"type\");\n\t\tlet process_type = CefString::from(&cmd.switch_value(Some(&switch)));\n\t\tlet mut app = RenderProcessAppImpl::<H>::app();\n\t\tlet ret = execute_process(Some(self.args.as_main_args()), Some(&mut app), std::ptr::null_mut());\n\t\tif ret >= 0 {\n\t\t\tSetupError::SubprocessFailed(process_type.to_string())\n\t\t} else {\n\t\t\tSetupError::Subprocess\n\t\t}\n\t}\n\n\tfn common_settings(instance_dir: &Path) -> Settings {\n\t\tlet log_severity = match std::env::var(\"GRAPHITE_BROWSER_LOG\") {\n\t\t\tOk(level) => match level.to_lowercase().as_str() {\n\t\t\t\t\"debug\" => LogSeverity::from(cef_log_severity_t::LOGSEVERITY_VERBOSE),\n\t\t\t\t\"info\" => LogSeverity::from(cef_log_severity_t::LOGSEVERITY_INFO),\n\t\t\t\t\"warn\" => LogSeverity::from(cef_log_severity_t::LOGSEVERITY_WARNING),\n\t\t\t\t\"error\" => LogSeverity::from(cef_log_severity_t::LOGSEVERITY_ERROR),\n\t\t\t\t\"none\" => LogSeverity::from(cef_log_severity_t::LOGSEVERITY_DISABLE),\n\t\t\t\t_ => LogSeverity::from(cef_log_severity_t::LOGSEVERITY_FATAL),\n\t\t\t},\n\t\t\tErr(_) => LogSeverity::from(cef_log_severity_t::LOGSEVERITY_FATAL),\n\t\t};\n\n\t\tSettings {\n\t\t\twindowless_rendering_enabled: 1,\n\t\t\troot_cache_path: instance_dir.to_str().map(CefString::from).unwrap(),\n\t\t\tcache_path: CefString::from(\"\"),\n\t\t\tdisable_signal_handlers: 1,\n\t\t\tlog_severity,\n\t\t\t..Default::default()\n\t\t}\n\t}\n\n\t#[cfg(target_os = \"macos\")]\n\tpub(crate) fn initialize(self, event_handler: H, disable_gpu_acceleration: bool) -> Result<impl CefContext, InitError> {\n\t\tdelete_instance_dirs();\n\t\tlet instance_dir = create_instance_dir();\n\n\t\tlet exe = std::env::current_exe().expect(\"cannot get current exe path\");\n\t\tlet app_root = exe.parent().and_then(|p| p.parent()).expect(\"bad path structure\").parent().expect(\"bad path structure\");\n\n\t\tlet settings = Settings {\n\t\t\tmain_bundle_path: CefString::from(app_root.to_str().unwrap()),\n\t\t\tmulti_threaded_message_loop: 0,\n\t\t\texternal_message_pump: 1,\n\t\t\tno_sandbox: 1, // GPU helper crashes when running with sandbox\n\t\t\t..Self::common_settings(&instance_dir)\n\t\t};\n\n\t\tself.initialize_inner(&event_handler, settings)?;\n\n\t\tcreate_browser(event_handler, instance_dir, disable_gpu_acceleration)\n\t}\n\n\t#[cfg(not(target_os = \"macos\"))]\n\tpub(crate) fn initialize(self, event_handler: H, disable_gpu_acceleration: bool) -> Result<impl CefContext, InitError> {\n\t\tdelete_instance_dirs();\n\t\tlet instance_dir = create_instance_dir();\n\n\t\tlet settings = Settings {\n\t\t\tmulti_threaded_message_loop: 1,\n\t\t\t#[cfg(target_os = \"linux\")]\n\t\t\tno_sandbox: 1,\n\t\t\t..Self::common_settings(&instance_dir)\n\t\t};\n\n\t\tself.initialize_inner(&event_handler, settings)?;\n\n\t\tsuper::multithreaded::run_on_ui_thread(move || match create_browser(event_handler, instance_dir, disable_gpu_acceleration) {\n\t\t\tOk(context) => {\n\t\t\t\tsuper::multithreaded::CONTEXT.with(|b| {\n\t\t\t\t\t*b.borrow_mut() = Some(context);\n\t\t\t\t});\n\t\t\t}\n\t\t\tErr(e) => {\n\t\t\t\tpanic!(\"Failed to initialize CEF context: {:?}\", e);\n\t\t\t}\n\t\t});\n\n\t\tOk(super::multithreaded::MultiThreadedCefContextProxy)\n\t}\n\n\tfn initialize_inner(self, event_handler: &H, settings: Settings) -> Result<(), InitError> {\n\t\t// Attention! Wrapping this in an extra App is necessary, otherwise the program still compiles but segfaults\n\t\tlet mut cef_app = App::new(BrowserProcessAppImpl::new(event_handler.duplicate()));\n\n\t\tlet result = cef::initialize(Some(self.args.as_main_args()), Some(&settings), Some(&mut cef_app), std::ptr::null_mut());\n\t\tif result != 1 {\n\t\t\tlet cef_exit_code = cef::get_exit_code() as u32;\n\t\t\treturn Err(InitError::InitializationFailureCode(cef_exit_code));\n\t\t}\n\t\tOk(())\n\t}\n}\n\nfn create_browser<H: CefEventHandler>(event_handler: H, instance_dir: PathBuf, disable_gpu_acceleration: bool) -> Result<SingleThreadedCefContext, InitError> {\n\tlet mut client = Client::new(BrowserProcessClientImpl::new(&event_handler));\n\n\t#[cfg(feature = \"accelerated_paint\")]\n\tlet use_accelerated_paint = if disable_gpu_acceleration {\n\t\tfalse\n\t} else {\n\t\tcrate::cef::platform::should_enable_hardware_acceleration()\n\t};\n\n\tlet window_info = WindowInfo {\n\t\twindowless_rendering_enabled: 1,\n\t\t#[cfg(feature = \"accelerated_paint\")]\n\t\tshared_texture_enabled: use_accelerated_paint as i32,\n\t\t..Default::default()\n\t};\n\n\tlet settings = BrowserSettings {\n\t\twindowless_frame_rate: crate::consts::CEF_WINDOWLESS_FRAME_RATE,\n\t\tbackground_color: 0x0,\n\t\t..Default::default()\n\t};\n\n\tlet Some(mut incognito_request_context) = cef::request_context_create_context(\n\t\tSome(&RequestContextSettings {\n\t\t\tpersist_session_cookies: 0,\n\t\t\tcache_path: CefString::from(\"\"),\n\t\t\t..Default::default()\n\t\t}),\n\t\tOption::<&mut cef::RequestContextHandler>::None,\n\t) else {\n\t\treturn Err(InitError::RequestContextCreationFailed);\n\t};\n\n\tlet mut scheme_handler_factory = SchemeHandlerFactory::new(SchemeHandlerFactoryImpl::new(event_handler.duplicate()));\n\tincognito_request_context.clear_scheme_handler_factories();\n\tincognito_request_context.register_scheme_handler_factory(Some(&CefString::from(RESOURCE_SCHEME)), Some(&CefString::from(RESOURCE_DOMAIN)), Some(&mut scheme_handler_factory));\n\n\tlet url = CefString::from(format!(\"{RESOURCE_SCHEME}://{RESOURCE_DOMAIN}/\").as_str());\n\n\tlet browser = browser_host_create_browser_sync(\n\t\tSome(&window_info),\n\t\tSome(&mut client),\n\t\tSome(&url),\n\t\tSome(&settings),\n\t\tOption::<&mut DictionaryValue>::None,\n\t\tSome(&mut incognito_request_context),\n\t);\n\n\tif let Some(browser) = browser {\n\t\tOk(SingleThreadedCefContext {\n\t\t\tevent_handler: Box::new(event_handler),\n\t\t\tbrowser,\n\t\t\tinput_state: InputState::default(),\n\t\t\tinstance_dir,\n\t\t})\n\t} else {\n\t\ttracing::error!(\"Failed to create browser\");\n\t\tErr(InitError::BrowserCreationFailed)\n\t}\n}\n\n#[derive(thiserror::Error, Debug)]\npub(crate) enum SetupError {\n\t#[error(\"This is the sub process should exit immediately\")]\n\tSubprocess,\n\t#[error(\"Subprocess returned non zero exit code: {0}\")]\n\tSubprocessFailed(String),\n}\n\n#[derive(thiserror::Error, Debug)]\npub(crate) enum InitError {\n\t#[error(\"Initialization failed with code: {0}\")]\n\tInitializationFailureCode(u32),\n\t#[error(\"Browser creation failed\")]\n\tBrowserCreationFailed,\n\t#[error(\"Request context creation failed\")]\n\tRequestContextCreationFailed,\n}\n"
  },
  {
    "path": "desktop/src/cef/context/multithreaded.rs",
    "content": "use cef::sys::cef_thread_id_t;\nuse cef::{Task, ThreadId, post_task};\nuse std::cell::RefCell;\nuse winit::event::WindowEvent;\n\nuse crate::cef::internal::task::ClosureTask;\n\nuse super::CefContext;\nuse super::singlethreaded::SingleThreadedCefContext;\n\nthread_local! {\n\tpub(super) static CONTEXT: RefCell<Option<SingleThreadedCefContext>> = const { RefCell::new(None) };\n}\n\npub(super) struct MultiThreadedCefContextProxy;\n\nimpl CefContext for MultiThreadedCefContextProxy {\n\tfn work(&mut self) {\n\t\t// CEF handles its own message loop in multi-threaded mode\n\t}\n\n\tfn handle_window_event(&mut self, event: &WindowEvent) {\n\t\tlet event_clone = event.clone();\n\t\trun_on_ui_thread(move || {\n\t\t\tCONTEXT.with(|b| {\n\t\t\t\tif let Some(context) = b.borrow_mut().as_mut() {\n\t\t\t\t\tcontext.handle_window_event(&event_clone);\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t}\n\n\tfn notify_view_info_changed(&self) {\n\t\trun_on_ui_thread(move || {\n\t\t\tCONTEXT.with(|b| {\n\t\t\t\tif let Some(context) = b.borrow_mut().as_mut() {\n\t\t\t\t\tcontext.notify_view_info_changed();\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t}\n\n\tfn send_web_message(&self, message: Vec<u8>) {\n\t\trun_on_ui_thread(move || {\n\t\t\tCONTEXT.with(|b| {\n\t\t\t\tif let Some(context) = b.borrow_mut().as_mut() {\n\t\t\t\t\tcontext.send_web_message(message);\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t}\n}\n\nimpl Drop for MultiThreadedCefContextProxy {\n\tfn drop(&mut self) {\n\t\t// Force dropping underlying context on the UI thread\n\t\trun_on_ui_thread(move || drop(CONTEXT.take()));\n\t}\n}\n\npub(super) fn run_on_ui_thread<F>(closure: F)\nwhere\n\tF: FnOnce() + Send + 'static,\n{\n\tlet closure_task = ClosureTask::new(closure);\n\tlet mut task = Task::new(closure_task);\n\tpost_task(ThreadId::from(cef_thread_id_t::TID_UI), Some(&mut task));\n}\n"
  },
  {
    "path": "desktop/src/cef/context/singlethreaded.rs",
    "content": "use cef::{Browser, ImplBrowser, ImplBrowserHost};\nuse winit::event::WindowEvent;\n\nuse crate::cef::input::InputState;\nuse crate::cef::ipc::{MessageType, SendMessage};\nuse crate::cef::{CefEventHandler, input};\n\nuse super::CefContext;\n\npub(super) struct SingleThreadedCefContext {\n\tpub(super) event_handler: Box<dyn CefEventHandler>,\n\tpub(super) browser: Browser,\n\tpub(super) input_state: InputState,\n\tpub(super) instance_dir: std::path::PathBuf,\n}\n\nimpl CefContext for SingleThreadedCefContext {\n\tfn work(&mut self) {\n\t\tcef::do_message_loop_work();\n\t}\n\n\tfn handle_window_event(&mut self, event: &WindowEvent) {\n\t\tinput::handle_window_event(&self.browser, &mut self.input_state, event);\n\t}\n\n\tfn notify_view_info_changed(&self) {\n\t\tlet view_info = self.event_handler.view_info();\n\t\tlet host = self.browser.host().unwrap();\n\t\thost.set_zoom_level(view_info.zoom());\n\t\thost.was_resized();\n\n\t\t// Fix for CEF not updating the view after resize\n\t\t// TODO: remove once https://github.com/chromiumembedded/cef/issues/3822 is fixed\n\t\thost.invalidate(cef::PaintElementType::default());\n\t}\n\n\tfn send_web_message(&self, message: Vec<u8>) {\n\t\tself.send_message(MessageType::SendToJS, &message);\n\t}\n}\n\nimpl Drop for SingleThreadedCefContext {\n\tfn drop(&mut self) {\n\t\ttracing::debug!(\"Shutting down CEF\");\n\n\t\t// CEF wants us to close the browser before shutting down, otherwise it may run longer that necessary.\n\t\tself.browser.host().unwrap().close_browser(1);\n\t\tcef::shutdown();\n\n\t\t// Sometimes some CEF processes still linger at this point and hold file handles to the cache directory.\n\t\t// To mitigate this, we try to remove the directory multiple times with some delay.\n\t\t// TODO: find a better solution if possible.\n\t\tfor _ in 0..30 {\n\t\t\tmatch std::fs::remove_dir_all(&self.instance_dir) {\n\t\t\t\tOk(_) => break,\n\t\t\t\tErr(e) => {\n\t\t\t\t\ttracing::warn!(\"Failed to remove CEF cache directory, retrying...: {e}\");\n\t\t\t\t\tstd::thread::sleep(std::time::Duration::from_millis(100));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nimpl SendMessage for SingleThreadedCefContext {\n\tfn send_message(&self, message_type: MessageType, message: &[u8]) {\n\t\tlet Some(frame) = self.browser.main_frame() else {\n\t\t\ttracing::error!(\"Main frame is not available, cannot send message\");\n\t\t\treturn;\n\t\t};\n\n\t\tframe.send_message(message_type, message);\n\t}\n}\n"
  },
  {
    "path": "desktop/src/cef/context.rs",
    "content": "#[cfg(not(target_os = \"macos\"))]\nmod multithreaded;\nmod singlethreaded;\n\nmod builder;\npub(crate) use builder::{CefContextBuilder, InitError};\n\npub(crate) trait CefContext {\n\tfn work(&mut self);\n\n\tfn handle_window_event(&mut self, event: &winit::event::WindowEvent);\n\n\tfn notify_view_info_changed(&self);\n\n\tfn send_web_message(&self, message: Vec<u8>);\n}\n"
  },
  {
    "path": "desktop/src/cef/dirs.rs",
    "content": "use std::path::PathBuf;\n\nuse crate::dirs::{app_data_dir, ensure_dir_exists};\n\nstatic CEF_DIR_NAME: &str = \"browser\";\n\npub(crate) fn delete_instance_dirs() {\n\tlet cef_dir = app_data_dir().join(CEF_DIR_NAME);\n\tif let Ok(entries) = std::fs::read_dir(&cef_dir) {\n\t\tfor entry in entries.flatten() {\n\t\t\tlet path = entry.path();\n\t\t\tif path.is_dir() {\n\t\t\t\tlet _ = std::fs::remove_dir_all(&path);\n\t\t\t}\n\t\t}\n\t}\n}\n\npub(crate) fn create_instance_dir() -> PathBuf {\n\tlet instance_id: String = (0..32).map(|_| format!(\"{:x}\", rand::random::<u8>() % 16)).collect();\n\tlet path = app_data_dir().join(CEF_DIR_NAME).join(instance_id);\n\tensure_dir_exists(&path);\n\tpath\n}\n"
  },
  {
    "path": "desktop/src/cef/input/keymap.rs",
    "content": "use winit::keyboard::{Key, NamedKey, PhysicalKey};\n\npub(crate) trait ToCharRepresentation {\n\tfn to_char_representation(&self) -> char;\n}\n\nimpl ToCharRepresentation for Key {\n\tfn to_char_representation(&self) -> char {\n\t\tmatch self {\n\t\t\tKey::Named(named) => match named {\n\t\t\t\tNamedKey::Tab => '\\t',\n\t\t\t\tNamedKey::Enter => '\\r',\n\t\t\t\tNamedKey::Backspace => '\\x08',\n\t\t\t\tNamedKey::Escape => '\\x1b',\n\t\t\t\t_ => '\\0',\n\t\t\t},\n\t\t\tKey::Character(char) => char.chars().next().unwrap_or_default(),\n\t\t\t_ => '\\0',\n\t\t}\n\t}\n}\n\npub(crate) trait ToNativeKeycode {\n\tfn to_native_keycode(&self) -> i32;\n}\n\nimpl ToNativeKeycode for PhysicalKey {\n\tfn to_native_keycode(&self) -> i32 {\n\t\tuse winit::platform::scancode::PhysicalKeyExtScancode;\n\n\t\t#[cfg(target_os = \"linux\")]\n\t\t{\n\t\t\tself.to_scancode().map(|evdev| (evdev + 8) as i32).unwrap_or_default()\n\t\t}\n\t\t#[cfg(any(target_os = \"macos\", target_os = \"windows\"))]\n\t\t{\n\t\t\tself.to_scancode().map(|c| c as i32).unwrap_or_default()\n\t\t}\n\t}\n}\n\npub(crate) trait ToVKBits {\n\tfn to_vk_bits(&self) -> i32;\n}\n\nmacro_rules! map_enum {\n\t($target:expr, $enum:ident, $( ($code:expr, $variant:ident), )+ ) => {\n\t\tmatch $target {\n\t\t\t$(\n\t\t\t\t$enum::$variant => $code,\n\t\t\t)+\n\t\t\t_ => 0,\n\t\t}\n\t};\n}\nimpl ToVKBits for winit::keyboard::NamedKey {\n\tfn to_vk_bits(&self) -> i32 {\n\t\tmap_enum!(\n\t\t\tself,\n\t\t\tNamedKey,\n\t\t\t(0x12, Alt),\n\t\t\t(0xA5, AltGraph),\n\t\t\t(0x14, CapsLock),\n\t\t\t(0x11, Control),\n\t\t\t(0x90, NumLock),\n\t\t\t(0x91, ScrollLock),\n\t\t\t(0x10, Shift),\n\t\t\t(0x5B, Meta),\n\t\t\t(0x0D, Enter),\n\t\t\t(0x09, Tab),\n\t\t\t(0x25, ArrowLeft),\n\t\t\t(0x26, ArrowUp),\n\t\t\t(0x27, ArrowRight),\n\t\t\t(0x28, ArrowDown),\n\t\t\t(0x23, End),\n\t\t\t(0x24, Home),\n\t\t\t(0x22, PageDown),\n\t\t\t(0x21, PageUp),\n\t\t\t(0x08, Backspace),\n\t\t\t(0x0C, Clear),\n\t\t\t(0xF7, CrSel),\n\t\t\t(0x2E, Delete),\n\t\t\t(0xF9, EraseEof),\n\t\t\t(0xF8, ExSel),\n\t\t\t(0x2D, Insert),\n\t\t\t(0x1E, Accept),\n\t\t\t(0xF6, Attn),\n\t\t\t(0x03, Cancel),\n\t\t\t(0x5D, ContextMenu),\n\t\t\t(0x1B, Escape),\n\t\t\t(0x2B, Execute),\n\t\t\t(0x2F, Help),\n\t\t\t(0x13, Pause),\n\t\t\t(0xFA, Play),\n\t\t\t(0x5D, Props),\n\t\t\t(0x29, Select),\n\t\t\t(0xFB, ZoomIn),\n\t\t\t(0xFB, ZoomOut),\n\t\t\t(0x2C, PrintScreen),\n\t\t\t(0x5F, Standby),\n\t\t\t(0x1C, Convert),\n\t\t\t(0x18, FinalMode),\n\t\t\t(0x1F, ModeChange),\n\t\t\t(0x1D, NonConvert),\n\t\t\t(0xE5, Process),\n\t\t\t(0x15, HangulMode),\n\t\t\t(0x19, HanjaMode),\n\t\t\t(0x17, JunjaMode),\n\t\t\t(0x15, KanaMode),\n\t\t\t(0x19, KanjiMode),\n\t\t\t(0xB0, MediaFastForward),\n\t\t\t(0xB3, MediaPause),\n\t\t\t(0xB3, MediaPlay),\n\t\t\t(0xB3, MediaPlayPause),\n\t\t\t(0xB1, MediaRewind),\n\t\t\t(0xB2, MediaStop),\n\t\t\t(0xB0, MediaTrackNext),\n\t\t\t(0xB1, MediaTrackPrevious),\n\t\t\t(0x2A, Print),\n\t\t\t(0xAE, AudioVolumeDown),\n\t\t\t(0xAF, AudioVolumeUp),\n\t\t\t(0xAD, AudioVolumeMute),\n\t\t\t(0xB6, LaunchApplication1),\n\t\t\t(0xB7, LaunchApplication2),\n\t\t\t(0xB4, LaunchMail),\n\t\t\t(0xB5, LaunchMediaPlayer),\n\t\t\t(0xB5, LaunchMusicPlayer),\n\t\t\t(0xA6, BrowserBack),\n\t\t\t(0xAB, BrowserFavorites),\n\t\t\t(0xA7, BrowserForward),\n\t\t\t(0xAC, BrowserHome),\n\t\t\t(0xA8, BrowserRefresh),\n\t\t\t(0xAA, BrowserSearch),\n\t\t\t(0xA9, BrowserStop),\n\t\t\t(0xFB, ZoomToggle),\n\t\t\t(0x70, F1),\n\t\t\t(0x71, F2),\n\t\t\t(0x72, F3),\n\t\t\t(0x73, F4),\n\t\t\t(0x74, F5),\n\t\t\t(0x75, F6),\n\t\t\t(0x76, F7),\n\t\t\t(0x77, F8),\n\t\t\t(0x78, F9),\n\t\t\t(0x79, F10),\n\t\t\t(0x7A, F11),\n\t\t\t(0x7B, F12),\n\t\t\t(0x7C, F13),\n\t\t\t(0x7D, F14),\n\t\t\t(0x7E, F15),\n\t\t\t(0x7F, F16),\n\t\t\t(0x80, F17),\n\t\t\t(0x81, F18),\n\t\t\t(0x82, F19),\n\t\t\t(0x83, F20),\n\t\t\t(0x84, F21),\n\t\t\t(0x85, F22),\n\t\t\t(0x86, F23),\n\t\t\t(0x87, F24),\n\t\t)\n\t}\n}\n\nmacro_rules! map {\n\t($target:expr, $( ($code:expr, $variant:literal), )+ ) => {\n\t\tmatch $target {\n\t\t\t$(\n\t\t\t\t$variant => $code,\n\t\t\t)+\n\t\t\t_ => 0,\n\t\t}\n\t};\n}\nimpl ToVKBits for char {\n\tfn to_vk_bits(&self) -> i32 {\n\t\tmap!(\n\t\t\tself,\n\t\t\t(0x41, 'a'),\n\t\t\t(0x42, 'b'),\n\t\t\t(0x43, 'c'),\n\t\t\t(0x44, 'd'),\n\t\t\t(0x45, 'e'),\n\t\t\t(0x46, 'f'),\n\t\t\t(0x47, 'g'),\n\t\t\t(0x48, 'h'),\n\t\t\t(0x49, 'i'),\n\t\t\t(0x4a, 'j'),\n\t\t\t(0x4b, 'k'),\n\t\t\t(0x4c, 'l'),\n\t\t\t(0x4d, 'm'),\n\t\t\t(0x4e, 'n'),\n\t\t\t(0x4f, 'o'),\n\t\t\t(0x50, 'p'),\n\t\t\t(0x51, 'q'),\n\t\t\t(0x52, 'r'),\n\t\t\t(0x53, 's'),\n\t\t\t(0x54, 't'),\n\t\t\t(0x55, 'u'),\n\t\t\t(0x56, 'v'),\n\t\t\t(0x57, 'w'),\n\t\t\t(0x58, 'x'),\n\t\t\t(0x59, 'y'),\n\t\t\t(0x5a, 'z'),\n\t\t\t(0x41, 'A'),\n\t\t\t(0x42, 'B'),\n\t\t\t(0x43, 'C'),\n\t\t\t(0x44, 'D'),\n\t\t\t(0x45, 'E'),\n\t\t\t(0x46, 'F'),\n\t\t\t(0x47, 'G'),\n\t\t\t(0x48, 'H'),\n\t\t\t(0x49, 'I'),\n\t\t\t(0x4a, 'J'),\n\t\t\t(0x4b, 'K'),\n\t\t\t(0x4c, 'L'),\n\t\t\t(0x4d, 'M'),\n\t\t\t(0x4e, 'N'),\n\t\t\t(0x4f, 'O'),\n\t\t\t(0x50, 'P'),\n\t\t\t(0x51, 'Q'),\n\t\t\t(0x52, 'R'),\n\t\t\t(0x53, 'S'),\n\t\t\t(0x54, 'T'),\n\t\t\t(0x55, 'U'),\n\t\t\t(0x56, 'V'),\n\t\t\t(0x57, 'W'),\n\t\t\t(0x58, 'X'),\n\t\t\t(0x59, 'Y'),\n\t\t\t(0x5a, 'Z'),\n\t\t\t(0x31, '1'),\n\t\t\t(0x32, '2'),\n\t\t\t(0x33, '3'),\n\t\t\t(0x34, '4'),\n\t\t\t(0x35, '5'),\n\t\t\t(0x36, '6'),\n\t\t\t(0x37, '7'),\n\t\t\t(0x38, '8'),\n\t\t\t(0x39, '9'),\n\t\t\t(0x30, '0'),\n\t\t\t(0x31, '!'),\n\t\t\t(0x32, '@'),\n\t\t\t(0x33, '#'),\n\t\t\t(0x34, '$'),\n\t\t\t(0x35, '%'),\n\t\t\t(0x36, '^'),\n\t\t\t(0x37, '&'),\n\t\t\t(0x38, '*'),\n\t\t\t(0x39, '('),\n\t\t\t(0x30, ')'),\n\t\t\t(0xC0, '`'),\n\t\t\t(0xC0, '~'),\n\t\t\t(0xBD, '-'),\n\t\t\t(0xBD, '_'),\n\t\t\t(0xBB, '='),\n\t\t\t(0xBB, '+'),\n\t\t\t(0xDB, '['),\n\t\t\t(0xDB, '{'),\n\t\t\t(0xDD, ']'),\n\t\t\t(0xDD, '}'),\n\t\t\t(0xDC, '\\\\'),\n\t\t\t(0xDC, '|'),\n\t\t\t(0xBA, ';'),\n\t\t\t(0xBA, ':'),\n\t\t\t(0xBC, ','),\n\t\t\t(0xBC, '<'),\n\t\t\t(0xBE, '.'),\n\t\t\t(0xBE, '>'),\n\t\t\t(0xDE, '\\''),\n\t\t\t(0xDE, '\"'),\n\t\t\t(0xBF, '/'),\n\t\t\t(0xBF, '?'),\n\t\t\t(0x20, ' '),\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "desktop/src/cef/input/state.rs",
    "content": "use cef::MouseEvent;\nuse cef::sys::cef_event_flags_t;\nuse std::time::Instant;\nuse winit::dpi::PhysicalPosition;\nuse winit::event::{ElementState, MouseButton};\nuse winit::keyboard::{Key, KeyLocation, ModifiersState, NamedKey};\n\nuse crate::cef::consts::{MULTICLICK_ALLOWED_TRAVEL, MULTICLICK_TIMEOUT};\n\n#[derive(Default)]\npub(crate) struct InputState {\n\tmodifiers: ModifiersState,\n\tmouse_position: MousePosition,\n\tmouse_state: MouseState,\n\tmouse_click_tracker: ClickTracker,\n}\nimpl InputState {\n\tpub(crate) fn modifiers_changed(&mut self, modifiers: &ModifiersState) {\n\t\tself.modifiers = *modifiers;\n\t}\n\n\tpub(crate) fn modifiers_apply_key_event(&mut self, key: &Key, state: &ElementState) {\n\t\tlet bits = match key {\n\t\t\tKey::Named(NamedKey::Shift) => ModifiersState::SHIFT,\n\t\t\tKey::Named(NamedKey::Control) => ModifiersState::CONTROL,\n\t\t\tKey::Named(NamedKey::Alt) => ModifiersState::ALT,\n\t\t\tKey::Named(NamedKey::Meta) => ModifiersState::META,\n\t\t\t_ => return,\n\t\t};\n\t\tlet is_pressed = matches!(state, ElementState::Pressed);\n\t\tself.modifiers.set(bits, is_pressed);\n\t}\n\n\tpub(crate) fn cursor_move(&mut self, position: &PhysicalPosition<f64>) -> bool {\n\t\tlet new = position.into();\n\t\tif self.mouse_position == new {\n\t\t\treturn false;\n\t\t}\n\t\tself.mouse_position = new;\n\t\ttrue\n\t}\n\n\tpub(crate) fn mouse_input(&mut self, button: &MouseButton, state: &ElementState) -> ClickCount {\n\t\tself.mouse_state.update(button, state);\n\t\tself.mouse_click_tracker.input(button, state, self.mouse_position)\n\t}\n\n\tpub(crate) fn cef_modifiers(&self, location: &KeyLocation, is_repeat: bool) -> CefModifiers {\n\t\tCefModifiers::new(self, location, is_repeat)\n\t}\n\n\tpub(crate) fn cef_mouse_modifiers(&self) -> CefModifiers {\n\t\tself.cef_modifiers(&KeyLocation::Standard, false)\n\t}\n}\n\nimpl From<InputState> for CefModifiers {\n\tfn from(val: InputState) -> Self {\n\t\tCefModifiers::new(&val, &KeyLocation::Standard, false)\n\t}\n}\nimpl From<&InputState> for MouseEvent {\n\tfn from(val: &InputState) -> Self {\n\t\tMouseEvent {\n\t\t\tx: val.mouse_position.x as i32,\n\t\t\ty: val.mouse_position.y as i32,\n\t\t\tmodifiers: val.cef_mouse_modifiers().into(),\n\t\t}\n\t}\n}\nimpl From<&mut InputState> for MouseEvent {\n\tfn from(val: &mut InputState) -> Self {\n\t\tMouseEvent {\n\t\t\tx: val.mouse_position.x as i32,\n\t\t\ty: val.mouse_position.y as i32,\n\t\t\tmodifiers: val.cef_mouse_modifiers().into(),\n\t\t}\n\t}\n}\n\n#[derive(Default, Clone, Copy, Eq, PartialEq)]\npub(crate) struct MousePosition {\n\tx: usize,\n\ty: usize,\n}\nimpl From<&PhysicalPosition<f64>> for MousePosition {\n\tfn from(position: &PhysicalPosition<f64>) -> Self {\n\t\tSelf {\n\t\t\tx: position.x as usize,\n\t\t\ty: position.y as usize,\n\t\t}\n\t}\n}\n\n#[derive(Default, Clone)]\npub(crate) struct MouseState {\n\tleft: bool,\n\tright: bool,\n\tmiddle: bool,\n}\nimpl MouseState {\n\tpub(crate) fn update(&mut self, button: &MouseButton, state: &ElementState) {\n\t\tmatch state {\n\t\t\tElementState::Pressed => match button {\n\t\t\t\tMouseButton::Left => self.left = true,\n\t\t\t\tMouseButton::Right => self.right = true,\n\t\t\t\tMouseButton::Middle => self.middle = true,\n\t\t\t\t_ => {}\n\t\t\t},\n\t\t\tElementState::Released => match button {\n\t\t\t\tMouseButton::Left => self.left = false,\n\t\t\t\tMouseButton::Right => self.right = false,\n\t\t\t\tMouseButton::Middle => self.middle = false,\n\t\t\t\t_ => {}\n\t\t\t},\n\t\t}\n\t}\n}\n\n#[derive(Default)]\nstruct ClickTracker {\n\tleft: Option<ClickRecord>,\n\tmiddle: Option<ClickRecord>,\n\tright: Option<ClickRecord>,\n}\nimpl ClickTracker {\n\tfn input(&mut self, button: &MouseButton, state: &ElementState, position: MousePosition) -> ClickCount {\n\t\tlet record = match button {\n\t\t\tMouseButton::Left => &mut self.left,\n\t\t\tMouseButton::Right => &mut self.right,\n\t\t\tMouseButton::Middle => &mut self.middle,\n\t\t\t_ => return ClickCount::Single,\n\t\t};\n\n\t\tlet Some(record) = record else {\n\t\t\t*record = Some(ClickRecord { position, ..Default::default() });\n\t\t\treturn ClickCount::Single;\n\t\t};\n\n\t\tlet prev_time = record.time;\n\t\tlet prev_position = record.position;\n\t\tlet prev_count: ClickCount = record.down_count;\n\n\t\tlet now = Instant::now();\n\t\trecord.time = now;\n\t\trecord.position = position;\n\n\t\tmatch state {\n\t\t\tElementState::Pressed if record.down_count == ClickCount::Triple => {\n\t\t\t\t*record = ClickRecord {\n\t\t\t\t\tdown_count: ClickCount::Double,\n\t\t\t\t\t..*record\n\t\t\t\t};\n\t\t\t\treturn ClickCount::Double;\n\t\t\t}\n\t\t\tElementState::Released if record.up_count == ClickCount::Triple => {\n\t\t\t\t*record = ClickRecord {\n\t\t\t\t\tup_count: ClickCount::Double,\n\t\t\t\t\t..*record\n\t\t\t\t};\n\t\t\t\treturn ClickCount::Double;\n\t\t\t}\n\t\t\t_ => {}\n\t\t}\n\n\t\tlet dx = position.x.abs_diff(prev_position.x);\n\t\tlet dy = position.y.abs_diff(prev_position.y);\n\t\tlet within_dist = dx <= MULTICLICK_ALLOWED_TRAVEL && dy <= MULTICLICK_ALLOWED_TRAVEL;\n\t\tlet within_time = now.saturating_duration_since(prev_time) <= MULTICLICK_TIMEOUT;\n\n\t\tlet count = match (prev_count, within_time, within_dist) {\n\t\t\t(ClickCount::Double, true, true) => ClickCount::Triple,\n\t\t\t(_, true, true) => ClickCount::Double,\n\t\t\t_ => ClickCount::Single,\n\t\t};\n\n\t\t*record = match state {\n\t\t\tElementState::Pressed => ClickRecord { down_count: count, ..*record },\n\t\t\tElementState::Released => ClickRecord { up_count: count, ..*record },\n\t\t};\n\t\tcount\n\t}\n}\n\n#[derive(Clone, Copy, PartialEq, Default)]\npub(crate) enum ClickCount {\n\t#[default]\n\tSingle,\n\tDouble,\n\tTriple,\n}\nimpl From<ClickCount> for i32 {\n\tfn from(count: ClickCount) -> i32 {\n\t\tmatch count {\n\t\t\tClickCount::Single => 1,\n\t\t\tClickCount::Double => 2,\n\t\t\tClickCount::Triple => 3,\n\t\t}\n\t}\n}\n\n#[derive(Clone, Copy)]\nstruct ClickRecord {\n\ttime: Instant,\n\tposition: MousePosition,\n\tdown_count: ClickCount,\n\tup_count: ClickCount,\n}\n\nimpl Default for ClickRecord {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\ttime: Instant::now(),\n\t\t\tposition: Default::default(),\n\t\t\tdown_count: Default::default(),\n\t\t\tup_count: Default::default(),\n\t\t}\n\t}\n}\n\npub(crate) struct CefModifiers(cef_event_flags_t);\nimpl CefModifiers {\n\tfn new(input_state: &InputState, location: &KeyLocation, is_repeat: bool) -> Self {\n\t\tlet mut inner = cef_event_flags_t::EVENTFLAG_NONE;\n\n\t\tif input_state.modifiers.shift_key() {\n\t\t\tinner |= cef_event_flags_t::EVENTFLAG_SHIFT_DOWN;\n\t\t}\n\t\tif input_state.modifiers.control_key() {\n\t\t\tinner |= cef_event_flags_t::EVENTFLAG_CONTROL_DOWN;\n\t\t}\n\t\tif input_state.modifiers.alt_key() {\n\t\t\tinner |= cef_event_flags_t::EVENTFLAG_ALT_DOWN;\n\t\t}\n\t\tif input_state.modifiers.meta_key() {\n\t\t\tinner |= cef_event_flags_t::EVENTFLAG_COMMAND_DOWN;\n\t\t}\n\n\t\tif input_state.mouse_state.left {\n\t\t\tinner |= cef_event_flags_t::EVENTFLAG_LEFT_MOUSE_BUTTON;\n\t\t}\n\t\tif input_state.mouse_state.right {\n\t\t\tinner |= cef_event_flags_t::EVENTFLAG_RIGHT_MOUSE_BUTTON;\n\t\t}\n\t\tif input_state.mouse_state.middle {\n\t\t\tinner |= cef_event_flags_t::EVENTFLAG_MIDDLE_MOUSE_BUTTON;\n\t\t}\n\n\t\tif is_repeat {\n\t\t\tinner |= cef_event_flags_t::EVENTFLAG_IS_REPEAT;\n\t\t}\n\n\t\tinner |= match location {\n\t\t\tKeyLocation::Left => cef_event_flags_t::EVENTFLAG_IS_LEFT,\n\t\t\tKeyLocation::Right => cef_event_flags_t::EVENTFLAG_IS_RIGHT,\n\t\t\tKeyLocation::Numpad => cef_event_flags_t::EVENTFLAG_IS_KEY_PAD,\n\t\t\tKeyLocation::Standard => cef_event_flags_t::EVENTFLAG_NONE,\n\t\t};\n\n\t\tSelf(inner)\n\t}\n\n\tpub(super) const PINCH_MODIFIERS: Self = Self(cef_event_flags_t(\n\t\tcef_event_flags_t::EVENTFLAG_CONTROL_DOWN.0 | cef_event_flags_t::EVENTFLAG_PRECISION_SCROLLING_DELTA.0,\n\t));\n}\n\nimpl From<CefModifiers> for u32 {\n\tfn from(val: CefModifiers) -> Self {\n\t\t#[cfg(not(target_os = \"windows\"))]\n\t\treturn val.0.0;\n\t\t#[cfg(target_os = \"windows\")]\n\t\treturn val.0.0 as u32;\n\t}\n}\n"
  },
  {
    "path": "desktop/src/cef/input.rs",
    "content": "use cef::sys::{cef_key_event_type_t, cef_mouse_button_type_t};\nuse cef::{Browser, ImplBrowser, ImplBrowserHost, KeyEvent, MouseEvent};\nuse winit::event::{ButtonSource, ElementState, MouseButton, MouseScrollDelta, WindowEvent};\n\nmod keymap;\nuse keymap::{ToCharRepresentation, ToNativeKeycode, ToVKBits};\n\nmod state;\npub(crate) use state::{CefModifiers, InputState};\n\nuse super::consts::{PINCH_ZOOM_SPEED, SCROLL_LINE_HEIGHT, SCROLL_LINE_WIDTH, SCROLL_SPEED_X, SCROLL_SPEED_Y};\n\npub(crate) fn handle_window_event(browser: &Browser, input_state: &mut InputState, event: &WindowEvent) {\n\tmatch event {\n\t\tWindowEvent::PointerMoved { position, .. } | WindowEvent::PointerEntered { position, .. } => {\n\t\t\tif !input_state.cursor_move(position) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet Some(host) = browser.host() else { return };\n\t\t\thost.send_mouse_move_event(Some(&input_state.into()), 0);\n\t\t}\n\t\tWindowEvent::PointerLeft { position, .. } => {\n\t\t\tif let Some(position) = position {\n\t\t\t\tlet _ = input_state.cursor_move(position);\n\t\t\t}\n\n\t\t\tlet Some(host) = browser.host() else { return };\n\t\t\thost.send_mouse_move_event(Some(&(input_state.into())), 1);\n\t\t}\n\t\tWindowEvent::PointerButton { state, button, .. } => {\n\t\t\tlet mouse_button = match button {\n\t\t\t\tButtonSource::Mouse(mouse_button) => mouse_button,\n\t\t\t\t_ => {\n\t\t\t\t\treturn; // TODO: Handle touch input\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tlet cef_click_count = input_state.mouse_input(mouse_button, state).into();\n\t\t\tlet cef_mouse_up = match state {\n\t\t\t\tElementState::Pressed => 0,\n\t\t\t\tElementState::Released => 1,\n\t\t\t};\n\t\t\tlet cef_button = match mouse_button {\n\t\t\t\tMouseButton::Left => cef::MouseButtonType::from(cef_mouse_button_type_t::MBT_LEFT),\n\t\t\t\tMouseButton::Right => cef::MouseButtonType::from(cef_mouse_button_type_t::MBT_RIGHT),\n\t\t\t\tMouseButton::Middle => cef::MouseButtonType::from(cef_mouse_button_type_t::MBT_MIDDLE),\n\t\t\t\t_ => return,\n\t\t\t};\n\n\t\t\tlet Some(host) = browser.host() else { return };\n\t\t\thost.send_mouse_click_event(Some(&input_state.into()), cef_button, cef_mouse_up, cef_click_count);\n\t\t}\n\t\tWindowEvent::MouseWheel { delta, phase: _, device_id: _, .. } => {\n\t\t\tlet mouse_event = input_state.into();\n\t\t\tlet (mut delta_x, mut delta_y) = match delta {\n\t\t\t\tMouseScrollDelta::LineDelta(x, y) => (x * SCROLL_LINE_WIDTH as f32, y * SCROLL_LINE_HEIGHT as f32),\n\t\t\t\tMouseScrollDelta::PixelDelta(physical_position) => (physical_position.x as f32, physical_position.y as f32),\n\t\t\t};\n\t\t\tdelta_x *= SCROLL_SPEED_X;\n\t\t\tdelta_y *= SCROLL_SPEED_Y;\n\n\t\t\tlet Some(host) = browser.host() else { return };\n\t\t\thost.send_mouse_wheel_event(Some(&mouse_event), delta_x as i32, delta_y as i32);\n\t\t}\n\t\tWindowEvent::ModifiersChanged(modifiers) => {\n\t\t\tinput_state.modifiers_changed(&modifiers.state());\n\t\t}\n\t\tWindowEvent::KeyboardInput { device_id: _, event, is_synthetic: _ } => {\n\t\t\tlet Some(host) = browser.host() else { return };\n\n\t\t\tinput_state.modifiers_apply_key_event(&event.logical_key, &event.state);\n\n\t\t\tlet mut key_event = KeyEvent {\n\t\t\t\ttype_: match (event.state, &event.logical_key) {\n\t\t\t\t\t(ElementState::Pressed, winit::keyboard::Key::Character(_)) => cef_key_event_type_t::KEYEVENT_CHAR,\n\t\t\t\t\t(ElementState::Pressed, _) => cef_key_event_type_t::KEYEVENT_RAWKEYDOWN,\n\t\t\t\t\t(ElementState::Released, _) => cef_key_event_type_t::KEYEVENT_KEYUP,\n\t\t\t\t}\n\t\t\t\t.into(),\n\t\t\t\t..Default::default()\n\t\t\t};\n\n\t\t\tkey_event.modifiers = input_state.cef_modifiers(&event.location, event.repeat).into();\n\n\t\t\tkey_event.windows_key_code = match &event.logical_key {\n\t\t\t\twinit::keyboard::Key::Named(named) => named.to_vk_bits(),\n\t\t\t\twinit::keyboard::Key::Character(char) => char.chars().next().unwrap_or_default().to_vk_bits(),\n\t\t\t\t_ => 0,\n\t\t\t};\n\n\t\t\tkey_event.native_key_code = event.physical_key.to_native_keycode();\n\n\t\t\tkey_event.character = event.logical_key.to_char_representation() as u16;\n\n\t\t\tif event.state == ElementState::Pressed && key_event.character != 0 {\n\t\t\t\tkey_event.type_ = cef_key_event_type_t::KEYEVENT_CHAR.into();\n\t\t\t}\n\n\t\t\t// Mitigation for CEF on Mac bug to prevent NSMenu being triggered by this key event.\n\t\t\t//\n\t\t\t// CEF converts the key event into an `NSEvent` internally and passes that to Chromium.\n\t\t\t// In some cases the `NSEvent` gets to the native Cocoa application, is considered \"unhandled\" and can trigger menus.\n\t\t\t//\n\t\t\t// Why mitigation works:\n\t\t\t// Leaving `key_event.unmodified_character = 0` still leads to CEF forwarding a \"unhandled\" event to the native application\n\t\t\t// but that event is discarded because `key_event.unmodified_character = 0` is considered non-printable and not used for shortcut matching.\n\t\t\t//\n\t\t\t// See https://github.com/chromiumembedded/cef/issues/3857\n\t\t\t//\n\t\t\t// TODO: Remove mitigation once bug is fixed or a better solution is found.\n\t\t\t#[cfg(not(target_os = \"macos\"))]\n\t\t\t{\n\t\t\t\tkey_event.unmodified_character = event.key_without_modifiers.to_char_representation() as u16;\n\t\t\t}\n\n\t\t\t#[cfg(target_os = \"macos\")] // See https://www.magpcss.org/ceforum/viewtopic.php?start=10&t=11650\n\t\t\tif key_event.character == 0 && key_event.unmodified_character == 0 && event.text_with_all_modifiers.is_some() {\n\t\t\t\tkey_event.character = 1;\n\t\t\t}\n\n\t\t\tif key_event.type_ == cef_key_event_type_t::KEYEVENT_CHAR.into() {\n\t\t\t\tlet mut key_down_event = key_event.clone();\n\t\t\t\tkey_down_event.type_ = cef_key_event_type_t::KEYEVENT_RAWKEYDOWN.into();\n\t\t\t\thost.send_key_event(Some(&key_down_event));\n\n\t\t\t\tkey_event.windows_key_code = event.logical_key.to_char_representation() as i32;\n\t\t\t}\n\n\t\t\thost.send_key_event(Some(&key_event));\n\t\t}\n\t\tWindowEvent::PinchGesture { delta, .. } => {\n\t\t\tif !delta.is_normal() {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tlet Some(host) = browser.host() else { return };\n\n\t\t\tlet mouse_event = MouseEvent {\n\t\t\t\tmodifiers: CefModifiers::PINCH_MODIFIERS.into(),\n\t\t\t\t..input_state.into()\n\t\t\t};\n\n\t\t\tlet delta = (delta * PINCH_ZOOM_SPEED).round() as i32;\n\n\t\t\thost.send_mouse_wheel_event(Some(&mouse_event), 0, delta);\n\t\t}\n\t\t_ => {}\n\t}\n}\n"
  },
  {
    "path": "desktop/src/cef/internal/browser_process_app.rs",
    "content": "#[cfg(target_os = \"linux\")]\nuse std::env;\n\nuse cef::rc::{Rc, RcImpl};\nuse cef::sys::{_cef_app_t, cef_base_ref_counted_t};\nuse cef::{BrowserProcessHandler, CefString, ImplApp, ImplCommandLine, SchemeRegistrar, WrapApp};\n\nuse super::browser_process_handler::BrowserProcessHandlerImpl;\nuse super::scheme_handler_factory::SchemeHandlerFactoryImpl;\nuse crate::cef::CefEventHandler;\n\npub(crate) struct BrowserProcessAppImpl<H: CefEventHandler> {\n\tobject: *mut RcImpl<_cef_app_t, Self>,\n\tevent_handler: H,\n}\nimpl<H: CefEventHandler> BrowserProcessAppImpl<H> {\n\tpub(crate) fn new(event_handler: H) -> Self {\n\t\tSelf {\n\t\t\tobject: std::ptr::null_mut(),\n\t\t\tevent_handler,\n\t\t}\n\t}\n}\n\nimpl<H: CefEventHandler> ImplApp for BrowserProcessAppImpl<H> {\n\tfn browser_process_handler(&self) -> Option<BrowserProcessHandler> {\n\t\tSome(BrowserProcessHandler::new(BrowserProcessHandlerImpl::new(self.event_handler.duplicate())))\n\t}\n\n\tfn on_register_custom_schemes(&self, registrar: Option<&mut SchemeRegistrar>) {\n\t\tSchemeHandlerFactoryImpl::<H>::register_schemes(registrar);\n\t}\n\n\tfn on_before_command_line_processing(&self, _process_type: Option<&cef::CefString>, command_line: Option<&mut cef::CommandLine>) {\n\t\tif let Some(cmd) = command_line {\n\t\t\tcmd.append_switch_with_value(Some(&CefString::from(\"renderer-process-limit\")), Some(&CefString::from(\"1\")));\n\t\t\tcmd.append_switch_with_value(Some(&CefString::from(\"password-store\")), Some(&CefString::from(\"basic\")));\n\t\t\tcmd.append_switch_with_value(Some(&CefString::from(\"disk-cache-size\")), Some(&CefString::from(\"0\")));\n\t\t\tcmd.append_switch(Some(&CefString::from(\"incognito\")));\n\t\t\tcmd.append_switch(Some(&CefString::from(\"no-first-run\")));\n\t\t\tcmd.append_switch(Some(&CefString::from(\"no-default-browser-check\")));\n\t\t\tcmd.append_switch(Some(&CefString::from(\"disable-component-update\")));\n\t\t\tcmd.append_switch(Some(&CefString::from(\"disable-geolocation\")));\n\t\t\tcmd.append_switch(Some(&CefString::from(\"disable-notifications\")));\n\t\t\tcmd.append_switch(Some(&CefString::from(\"disable-audio-input\")));\n\t\t\tcmd.append_switch(Some(&CefString::from(\"disable-audio-output\")));\n\t\t\tcmd.append_switch(Some(&CefString::from(\"disable-sync\")));\n\t\t\tcmd.append_switch(Some(&CefString::from(\"disable-file-system\")));\n\t\t\tcmd.append_switch(Some(&CefString::from(\"disable-local-storage\")));\n\t\t\tcmd.append_switch(Some(&CefString::from(\"disable-background-networking\")));\n\t\t\tcmd.append_switch(Some(&CefString::from(\"disable-default-apps\")));\n\t\t\tcmd.append_switch(Some(&CefString::from(\"disable-breakpad\")));\n\t\t\tcmd.append_switch_with_value(Some(&CefString::from(\"disable-blink-features\")), Some(&CefString::from(\"WebBluetooth,WebUSB,Serial\")));\n\n\t\t\tlet extra_disabled_features = [\"OptimizationHints\", \"OnDeviceModelService\", \"TranslateUI\"];\n\t\t\tlet disabled_features_switch = Some(&CefString::from(\"disable-features\"));\n\t\t\tlet disabled_features: String = CefString::from(&cmd.switch_value(disabled_features_switch))\n\t\t\t\t.to_string()\n\t\t\t\t.split(',')\n\t\t\t\t.chain(extra_disabled_features)\n\t\t\t\t.collect::<Vec<_>>()\n\t\t\t\t.join(\",\");\n\t\t\tcmd.append_switch_with_value(disabled_features_switch, Some(&CefString::from(disabled_features.as_str())));\n\n\t\t\t#[cfg(not(feature = \"accelerated_paint\"))]\n\t\t\t{\n\t\t\t\t// Disable GPU acceleration when accelerated_paint feature is not enabled\n\t\t\t\tcmd.append_switch(Some(&CefString::from(\"disable-gpu\")));\n\t\t\t\tcmd.append_switch(Some(&CefString::from(\"disable-gpu-compositing\")));\n\t\t\t}\n\n\t\t\t#[cfg(feature = \"accelerated_paint\")]\n\t\t\t{\n\t\t\t\t// Enable GPU acceleration switches for better performance\n\t\t\t\tcmd.append_switch(Some(&CefString::from(\"enable-gpu-rasterization\")));\n\t\t\t\tcmd.append_switch(Some(&CefString::from(\"enable-accelerated-2d-canvas\")));\n\t\t\t}\n\n\t\t\t#[cfg(all(feature = \"accelerated_paint\", target_os = \"linux\"))]\n\t\t\t{\n\t\t\t\t// Use Vulkan for accelerated painting\n\t\t\t\tcmd.append_switch_with_value(Some(&CefString::from(\"use-angle\")), Some(&CefString::from(\"vulkan\")));\n\t\t\t}\n\n\t\t\t// Tell CEF to use Wayland if available\n\t\t\t#[cfg(target_os = \"linux\")]\n\t\t\t{\n\t\t\t\tlet use_wayland = env::var(\"WAYLAND_DISPLAY\")\n\t\t\t\t\t.ok()\n\t\t\t\t\t.filter(|var| !var.is_empty())\n\t\t\t\t\t.or_else(|| env::var(\"WAYLAND_SOCKET\").ok())\n\t\t\t\t\t.filter(|var| !var.is_empty())\n\t\t\t\t\t.is_some();\n\t\t\t\tif use_wayland {\n\t\t\t\t\tcmd.append_switch_with_value(Some(&CefString::from(\"ozone-platform\")), Some(&CefString::from(\"wayland\")));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t#[cfg(target_os = \"macos\")]\n\t\t\t{\n\t\t\t\t// Hide user prompt asking for keychain access\n\t\t\t\tcmd.append_switch(Some(&CefString::from(\"use-mock-keychain\")));\n\t\t\t}\n\n\t\t\t// Enable browser debugging via environment variable\n\t\t\tif let Some(env) = std::env::var(\"GRAPHITE_BROWSER_DEBUG_PORT\").ok()\n\t\t\t\t&& let Some(port) = env.parse::<u16>().ok()\n\t\t\t{\n\t\t\t\tcmd.append_switch_with_value(Some(&CefString::from(\"remote-debugging-port\")), Some(&CefString::from(port.to_string().as_str())));\n\t\t\t\tcmd.append_switch_with_value(Some(&CefString::from(\"remote-allow-origins\")), Some(&CefString::from(\"*\")));\n\t\t\t}\n\t\t}\n\t}\n\n\tfn get_raw(&self) -> *mut _cef_app_t {\n\t\tself.object.cast()\n\t}\n}\n\nimpl<H: CefEventHandler> Clone for BrowserProcessAppImpl<H> {\n\tfn clone(&self) -> Self {\n\t\tunsafe {\n\t\t\tlet rc_impl = &mut *self.object;\n\t\t\trc_impl.interface.add_ref();\n\t\t}\n\t\tSelf {\n\t\t\tobject: self.object,\n\t\t\tevent_handler: self.event_handler.duplicate(),\n\t\t}\n\t}\n}\nimpl<H: CefEventHandler> Rc for BrowserProcessAppImpl<H> {\n\tfn as_base(&self) -> &cef_base_ref_counted_t {\n\t\tunsafe {\n\t\t\tlet base = &*self.object;\n\t\t\tstd::mem::transmute(&base.cef_object)\n\t\t}\n\t}\n}\nimpl<H: CefEventHandler> WrapApp for BrowserProcessAppImpl<H> {\n\tfn wrap_rc(&mut self, object: *mut RcImpl<_cef_app_t, Self>) {\n\t\tself.object = object;\n\t}\n}\n"
  },
  {
    "path": "desktop/src/cef/internal/browser_process_client.rs",
    "content": "use cef::rc::{Rc, RcImpl};\nuse cef::sys::{_cef_client_t, cef_base_ref_counted_t};\nuse cef::{ContextMenuHandler, DisplayHandler, ImplClient, LifeSpanHandler, LoadHandler, RenderHandler, WrapClient};\n\nuse crate::cef::CefEventHandler;\nuse crate::cef::ipc::{MessageType, UnpackMessage, UnpackedMessage};\n\nuse super::context_menu_handler::ContextMenuHandlerImpl;\nuse super::display_handler::DisplayHandlerImpl;\nuse super::life_span_handler::LifeSpanHandlerImpl;\nuse super::load_handler::LoadHandlerImpl;\nuse super::render_handler::RenderHandlerImpl;\n\npub(crate) struct BrowserProcessClientImpl<H: CefEventHandler> {\n\tobject: *mut RcImpl<_cef_client_t, Self>,\n\tevent_handler: H,\n\tload_handler: LoadHandler,\n\trender_handler: RenderHandler,\n\tdisplay_handler: DisplayHandler,\n}\nimpl<H: CefEventHandler> BrowserProcessClientImpl<H> {\n\tpub(crate) fn new(event_handler: &H) -> Self {\n\t\tSelf {\n\t\t\tobject: std::ptr::null_mut(),\n\t\t\tevent_handler: event_handler.duplicate(),\n\t\t\tload_handler: LoadHandler::new(LoadHandlerImpl::new(event_handler.duplicate())),\n\t\t\trender_handler: RenderHandler::new(RenderHandlerImpl::new(event_handler.duplicate())),\n\t\t\tdisplay_handler: DisplayHandler::new(DisplayHandlerImpl::new(event_handler.duplicate())),\n\t\t}\n\t}\n}\n\nimpl<H: CefEventHandler> ImplClient for BrowserProcessClientImpl<H> {\n\tfn on_process_message_received(\n\t\t&self,\n\t\t_browser: Option<&mut cef::Browser>,\n\t\t_frame: Option<&mut cef::Frame>,\n\t\t_source_process: cef::ProcessId,\n\t\tmessage: Option<&mut cef::ProcessMessage>,\n\t) -> std::ffi::c_int {\n\t\tlet unpacked_message = unsafe { message.and_then(|m| m.unpack()) };\n\t\tmatch unpacked_message {\n\t\t\tSome(UnpackedMessage {\n\t\t\t\tmessage_type: MessageType::Initialized,\n\t\t\t\tdata: _,\n\t\t\t}) => self.event_handler.initialized_web_communication(),\n\t\t\tSome(UnpackedMessage {\n\t\t\t\tmessage_type: MessageType::SendToNative,\n\t\t\t\tdata,\n\t\t\t}) => self.event_handler.receive_web_message(data),\n\n\t\t\t_ => {\n\t\t\t\ttracing::error!(\"Unexpected message type received in browser process\");\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\t\t1\n\t}\n\n\tfn load_handler(&self) -> Option<cef::LoadHandler> {\n\t\tSome(self.load_handler.clone())\n\t}\n\n\tfn render_handler(&self) -> Option<RenderHandler> {\n\t\tSome(self.render_handler.clone())\n\t}\n\n\tfn life_span_handler(&self) -> Option<cef::LifeSpanHandler> {\n\t\tSome(LifeSpanHandler::new(LifeSpanHandlerImpl::new()))\n\t}\n\n\tfn display_handler(&self) -> Option<cef::DisplayHandler> {\n\t\tSome(self.display_handler.clone())\n\t}\n\n\tfn context_menu_handler(&self) -> Option<cef::ContextMenuHandler> {\n\t\tSome(ContextMenuHandler::new(ContextMenuHandlerImpl::new()))\n\t}\n\n\tfn get_raw(&self) -> *mut _cef_client_t {\n\t\tself.object.cast()\n\t}\n}\n\nimpl<H: CefEventHandler> Clone for BrowserProcessClientImpl<H> {\n\tfn clone(&self) -> Self {\n\t\tunsafe {\n\t\t\tlet rc_impl = &mut *self.object;\n\t\t\trc_impl.interface.add_ref();\n\t\t}\n\t\tSelf {\n\t\t\tobject: self.object,\n\t\t\tevent_handler: self.event_handler.duplicate(),\n\t\t\tload_handler: self.load_handler.clone(),\n\t\t\trender_handler: self.render_handler.clone(),\n\t\t\tdisplay_handler: self.display_handler.clone(),\n\t\t}\n\t}\n}\nimpl<H: CefEventHandler> Rc for BrowserProcessClientImpl<H> {\n\tfn as_base(&self) -> &cef_base_ref_counted_t {\n\t\tunsafe {\n\t\t\tlet base = &*self.object;\n\t\t\tstd::mem::transmute(&base.cef_object)\n\t\t}\n\t}\n}\nimpl<H: CefEventHandler> WrapClient for BrowserProcessClientImpl<H> {\n\tfn wrap_rc(&mut self, object: *mut RcImpl<_cef_client_t, Self>) {\n\t\tself.object = object;\n\t}\n}\n"
  },
  {
    "path": "desktop/src/cef/internal/browser_process_handler.rs",
    "content": "use std::time::{Duration, Instant};\n\nuse cef::rc::{Rc, RcImpl};\nuse cef::sys::{_cef_browser_process_handler_t, cef_base_ref_counted_t, cef_browser_process_handler_t};\nuse cef::{CefString, ImplBrowserProcessHandler, WrapBrowserProcessHandler};\n\nuse crate::cef::CefEventHandler;\n\npub(crate) struct BrowserProcessHandlerImpl<H: CefEventHandler> {\n\tobject: *mut RcImpl<cef_browser_process_handler_t, Self>,\n\tevent_handler: H,\n}\nimpl<H: CefEventHandler> BrowserProcessHandlerImpl<H> {\n\tpub(crate) fn new(event_handler: H) -> Self {\n\t\tSelf {\n\t\t\tobject: std::ptr::null_mut(),\n\t\t\tevent_handler,\n\t\t}\n\t}\n}\n\nimpl<H: CefEventHandler> ImplBrowserProcessHandler for BrowserProcessHandlerImpl<H> {\n\tfn on_schedule_message_pump_work(&self, delay_ms: i64) {\n\t\tself.event_handler.schedule_cef_message_loop_work(Instant::now() + Duration::from_millis(delay_ms as u64));\n\t}\n\n\tfn on_already_running_app_relaunch(&self, _command_line: Option<&mut cef::CommandLine>, _current_directory: Option<&CefString>) -> std::ffi::c_int {\n\t\t1 // Return 1 to prevent default behavior of opening a empty browser window\n\t}\n\n\tfn get_raw(&self) -> *mut _cef_browser_process_handler_t {\n\t\tself.object.cast()\n\t}\n}\n\nimpl<H: CefEventHandler> Clone for BrowserProcessHandlerImpl<H> {\n\tfn clone(&self) -> Self {\n\t\tunsafe {\n\t\t\tlet rc_impl = &mut *self.object;\n\t\t\trc_impl.interface.add_ref();\n\t\t}\n\t\tSelf {\n\t\t\tobject: self.object,\n\t\t\tevent_handler: self.event_handler.duplicate(),\n\t\t}\n\t}\n}\nimpl<H: CefEventHandler> Rc for BrowserProcessHandlerImpl<H> {\n\tfn as_base(&self) -> &cef_base_ref_counted_t {\n\t\tunsafe {\n\t\t\tlet base = &*self.object;\n\t\t\tstd::mem::transmute(&base.cef_object)\n\t\t}\n\t}\n}\nimpl<H: CefEventHandler> WrapBrowserProcessHandler for BrowserProcessHandlerImpl<H> {\n\tfn wrap_rc(&mut self, object: *mut RcImpl<_cef_browser_process_handler_t, Self>) {\n\t\tself.object = object;\n\t}\n}\n"
  },
  {
    "path": "desktop/src/cef/internal/context_menu_handler.rs",
    "content": "use cef::rc::{Rc, RcImpl};\nuse cef::sys::{_cef_context_menu_handler_t, cef_base_ref_counted_t};\nuse cef::{ImplContextMenuHandler, WrapContextMenuHandler};\n\npub(crate) struct ContextMenuHandlerImpl {\n\tobject: *mut RcImpl<_cef_context_menu_handler_t, Self>,\n}\nimpl ContextMenuHandlerImpl {\n\tpub(crate) fn new() -> Self {\n\t\tSelf { object: std::ptr::null_mut() }\n\t}\n}\n\nimpl ImplContextMenuHandler for ContextMenuHandlerImpl {\n\tfn run_context_menu(\n\t\t&self,\n\t\t_browser: Option<&mut cef::Browser>,\n\t\t_frame: Option<&mut cef::Frame>,\n\t\t_params: Option<&mut cef::ContextMenuParams>,\n\t\t_model: Option<&mut cef::MenuModel>,\n\t\t_callback: Option<&mut cef::RunContextMenuCallback>,\n\t) -> std::ffi::c_int {\n\t\t// Prevent context menu\n\t\t1\n\t}\n\n\tfn run_quick_menu(\n\t\t&self,\n\t\t_browser: Option<&mut cef::Browser>,\n\t\t_frame: Option<&mut cef::Frame>,\n\t\t_location: Option<&cef::Point>,\n\t\t_size: Option<&cef::Size>,\n\t\t_edit_state_flags: cef::QuickMenuEditStateFlags,\n\t\t_callback: Option<&mut cef::RunQuickMenuCallback>,\n\t) -> std::ffi::c_int {\n\t\t// Prevent quick menu\n\t\t1\n\t}\n\n\tfn get_raw(&self) -> *mut _cef_context_menu_handler_t {\n\t\tself.object.cast()\n\t}\n}\n\nimpl Clone for ContextMenuHandlerImpl {\n\tfn clone(&self) -> Self {\n\t\tunsafe {\n\t\t\tlet rc_impl = &mut *self.object;\n\t\t\trc_impl.interface.add_ref();\n\t\t}\n\t\tSelf { object: self.object }\n\t}\n}\nimpl Rc for ContextMenuHandlerImpl {\n\tfn as_base(&self) -> &cef_base_ref_counted_t {\n\t\tunsafe {\n\t\t\tlet base = &*self.object;\n\t\t\tstd::mem::transmute(&base.cef_object)\n\t\t}\n\t}\n}\nimpl WrapContextMenuHandler for ContextMenuHandlerImpl {\n\tfn wrap_rc(&mut self, object: *mut RcImpl<_cef_context_menu_handler_t, Self>) {\n\t\tself.object = object;\n\t}\n}\n"
  },
  {
    "path": "desktop/src/cef/internal/display_handler.rs",
    "content": "use cef::rc::{Rc, RcImpl};\nuse cef::sys::{_cef_display_handler_t, cef_base_ref_counted_t, cef_cursor_type_t::*, cef_log_severity_t::*};\nuse cef::{CefString, ImplDisplayHandler, Point, Size, WrapDisplayHandler};\nuse winit::cursor::CursorIcon;\n\nuse crate::cef::CefEventHandler;\n\npub(crate) struct DisplayHandlerImpl<H: CefEventHandler> {\n\tobject: *mut RcImpl<_cef_display_handler_t, Self>,\n\tevent_handler: H,\n}\n\nimpl<H: CefEventHandler> DisplayHandlerImpl<H> {\n\tpub fn new(event_handler: H) -> Self {\n\t\tSelf {\n\t\t\tobject: std::ptr::null_mut(),\n\t\t\tevent_handler,\n\t\t}\n\t}\n}\n\n#[cfg(not(target_os = \"macos\"))]\ntype CefCursorHandle = cef::CursorHandle;\n#[cfg(target_os = \"macos\")]\ntype CefCursorHandle = *mut u8;\n\nimpl<H: CefEventHandler> ImplDisplayHandler for DisplayHandlerImpl<H> {\n\tfn on_cursor_change(&self, _browser: Option<&mut cef::Browser>, _cursor: CefCursorHandle, cursor_type: cef::CursorType, custom_cursor_info: Option<&cef::CursorInfo>) -> std::ffi::c_int {\n\t\tif let Some(custom_cursor_info) = custom_cursor_info {\n\t\t\tlet Size { width, height } = custom_cursor_info.size;\n\t\t\tlet Point { x: hotspot_x, y: hotspot_y } = custom_cursor_info.hotspot;\n\t\t\tlet buffer_size = (width * height * 4) as usize;\n\t\t\tlet buffer_ptr = custom_cursor_info.buffer as *const u8;\n\n\t\t\tif !buffer_ptr.is_null() && buffer_ptr.align_offset(std::mem::align_of::<u8>()) == 0 {\n\t\t\t\tlet buffer = unsafe { std::slice::from_raw_parts(buffer_ptr, buffer_size) }.to_vec();\n\t\t\t\tlet cursor = winit::cursor::CustomCursorSource::from_rgba(buffer, width as u16, height as u16, hotspot_x as u16, hotspot_y as u16).unwrap();\n\t\t\t\tself.event_handler.cursor_change(cursor.into());\n\t\t\t\treturn 1; // We handled the cursor change.\n\t\t\t}\n\t\t}\n\n\t\tlet cursor = match cursor_type.into() {\n\t\t\tCT_POINTER => CursorIcon::Default,\n\t\t\tCT_CROSS => CursorIcon::Crosshair,\n\t\t\tCT_HAND => CursorIcon::Pointer,\n\t\t\tCT_IBEAM => CursorIcon::Text,\n\t\t\tCT_WAIT => CursorIcon::Wait,\n\t\t\tCT_HELP => CursorIcon::Help,\n\t\t\tCT_EASTRESIZE => CursorIcon::EResize,\n\t\t\tCT_NORTHRESIZE => CursorIcon::NResize,\n\t\t\tCT_NORTHEASTRESIZE => CursorIcon::NeResize,\n\t\t\tCT_NORTHWESTRESIZE => CursorIcon::NwResize,\n\t\t\tCT_SOUTHRESIZE => CursorIcon::SResize,\n\t\t\tCT_SOUTHEASTRESIZE => CursorIcon::SeResize,\n\t\t\tCT_SOUTHWESTRESIZE => CursorIcon::SwResize,\n\t\t\tCT_WESTRESIZE => CursorIcon::WResize,\n\t\t\tCT_NORTHSOUTHRESIZE => CursorIcon::NsResize,\n\t\t\tCT_EASTWESTRESIZE => CursorIcon::EwResize,\n\t\t\tCT_NORTHEASTSOUTHWESTRESIZE => CursorIcon::NeswResize,\n\t\t\tCT_NORTHWESTSOUTHEASTRESIZE => CursorIcon::NwseResize,\n\t\t\tCT_COLUMNRESIZE => CursorIcon::ColResize,\n\t\t\tCT_ROWRESIZE => CursorIcon::RowResize,\n\t\t\tCT_MIDDLEPANNING => CursorIcon::AllScroll,\n\t\t\tCT_EASTPANNING => CursorIcon::AllScroll,\n\t\t\tCT_NORTHPANNING => CursorIcon::AllScroll,\n\t\t\tCT_NORTHEASTPANNING => CursorIcon::AllScroll,\n\t\t\tCT_NORTHWESTPANNING => CursorIcon::AllScroll,\n\t\t\tCT_SOUTHPANNING => CursorIcon::AllScroll,\n\t\t\tCT_SOUTHEASTPANNING => CursorIcon::AllScroll,\n\t\t\tCT_SOUTHWESTPANNING => CursorIcon::AllScroll,\n\t\t\tCT_WESTPANNING => CursorIcon::AllScroll,\n\t\t\tCT_MOVE => CursorIcon::Move,\n\t\t\tCT_VERTICALTEXT => CursorIcon::VerticalText,\n\t\t\tCT_CELL => CursorIcon::Cell,\n\t\t\tCT_CONTEXTMENU => CursorIcon::ContextMenu,\n\t\t\tCT_ALIAS => CursorIcon::Alias,\n\t\t\tCT_PROGRESS => CursorIcon::Progress,\n\t\t\tCT_NODROP => CursorIcon::NoDrop,\n\t\t\tCT_COPY => CursorIcon::Copy,\n\t\t\tCT_NOTALLOWED => CursorIcon::NotAllowed,\n\t\t\tCT_ZOOMIN => CursorIcon::ZoomIn,\n\t\t\tCT_ZOOMOUT => CursorIcon::ZoomOut,\n\t\t\tCT_GRAB => CursorIcon::Grab,\n\t\t\tCT_GRABBING => CursorIcon::Grabbing,\n\t\t\tCT_MIDDLE_PANNING_VERTICAL => CursorIcon::AllScroll,\n\t\t\tCT_MIDDLE_PANNING_HORIZONTAL => CursorIcon::AllScroll,\n\t\t\tCT_DND_NONE => CursorIcon::Default,\n\t\t\tCT_DND_MOVE => CursorIcon::Move,\n\t\t\tCT_DND_COPY => CursorIcon::Copy,\n\t\t\tCT_DND_LINK => CursorIcon::Alias,\n\t\t\tCT_NUM_VALUES => CursorIcon::Default,\n\t\t\tCT_NONE => {\n\t\t\t\tself.event_handler.cursor_change(crate::window::Cursor::None);\n\t\t\t\treturn 1; // We handled the cursor change.\n\t\t\t}\n\t\t\t_ => CursorIcon::Default,\n\t\t};\n\n\t\tself.event_handler.cursor_change(cursor.into());\n\n\t\t1 // We handled the cursor change.\n\t}\n\n\tfn on_console_message(&self, _browser: Option<&mut cef::Browser>, level: cef::LogSeverity, message: Option<&CefString>, source: Option<&CefString>, line: std::ffi::c_int) -> std::ffi::c_int {\n\t\tlet message = message.map(|m| m.to_string()).unwrap_or_default();\n\t\tlet source = source.map(|s| s.to_string()).unwrap_or_default();\n\t\tlet line = line as i64;\n\t\tlet browser_source = format!(\"{source}:{line}\");\n\t\tstatic BROWSER: &str = \"browser\";\n\t\tmatch level.as_ref() {\n\t\t\tLOGSEVERITY_FATAL | LOGSEVERITY_ERROR => tracing::error!(target: BROWSER, \"{browser_source} {message}\"),\n\t\t\tLOGSEVERITY_WARNING => tracing::warn!(target: BROWSER, \"{browser_source} {message}\"),\n\t\t\tLOGSEVERITY_INFO => tracing::info!(target: BROWSER, \"{browser_source} {message}\"),\n\t\t\tLOGSEVERITY_DEFAULT | LOGSEVERITY_VERBOSE => tracing::debug!(target: BROWSER, \"{browser_source} {message}\"),\n\t\t\t_ => tracing::trace!(target: BROWSER, \"{browser_source} {message}\"),\n\t\t}\n\t\t0\n\t}\n\n\tfn get_raw(&self) -> *mut _cef_display_handler_t {\n\t\tself.object.cast()\n\t}\n}\n\nimpl<H: CefEventHandler> Clone for DisplayHandlerImpl<H> {\n\tfn clone(&self) -> Self {\n\t\tunsafe {\n\t\t\tlet rc_impl = &mut *self.object;\n\t\t\trc_impl.interface.add_ref();\n\t\t}\n\t\tSelf {\n\t\t\tobject: self.object,\n\t\t\tevent_handler: self.event_handler.duplicate(),\n\t\t}\n\t}\n}\nimpl<H: CefEventHandler> Rc for DisplayHandlerImpl<H> {\n\tfn as_base(&self) -> &cef_base_ref_counted_t {\n\t\tunsafe {\n\t\t\tlet base = &*self.object;\n\t\t\tstd::mem::transmute(&base.cef_object)\n\t\t}\n\t}\n}\nimpl<H: CefEventHandler> WrapDisplayHandler for DisplayHandlerImpl<H> {\n\tfn wrap_rc(&mut self, object: *mut RcImpl<_cef_display_handler_t, Self>) {\n\t\tself.object = object;\n\t}\n}\n"
  },
  {
    "path": "desktop/src/cef/internal/life_span_handler.rs",
    "content": "use cef::rc::{Rc, RcImpl};\nuse cef::sys::{_cef_life_span_handler_t, cef_base_ref_counted_t};\nuse cef::{ImplLifeSpanHandler, WrapLifeSpanHandler};\n\npub(crate) struct LifeSpanHandlerImpl {\n\tobject: *mut RcImpl<_cef_life_span_handler_t, Self>,\n}\nimpl LifeSpanHandlerImpl {\n\tpub(crate) fn new() -> Self {\n\t\tSelf { object: std::ptr::null_mut() }\n\t}\n}\n\nimpl ImplLifeSpanHandler for LifeSpanHandlerImpl {\n\tfn on_before_popup(\n\t\t&self,\n\t\t_browser: Option<&mut cef::Browser>,\n\t\t_frame: Option<&mut cef::Frame>,\n\t\t_popup_id: std::ffi::c_int,\n\t\ttarget_url: Option<&cef::CefString>,\n\t\t_target_frame_name: Option<&cef::CefString>,\n\t\t_target_disposition: cef::WindowOpenDisposition,\n\t\t_user_gesture: std::ffi::c_int,\n\t\t_popup_features: Option<&cef::PopupFeatures>,\n\t\t_window_info: Option<&mut cef::WindowInfo>,\n\t\t_client: Option<&mut Option<cef::Client>>,\n\t\t_settings: Option<&mut cef::BrowserSettings>,\n\t\t_extra_info: Option<&mut Option<cef::DictionaryValue>>,\n\t\t_no_javascript_access: Option<&mut std::ffi::c_int>,\n\t) -> std::ffi::c_int {\n\t\tlet target = target_url.map(|url| url.to_string()).unwrap_or(\"unknown\".to_string());\n\t\ttracing::error!(\"Browser tried to open a popup at URL: {}\", target);\n\n\t\t// Deny any popup by returning 1\n\t\t1\n\t}\n\n\tfn get_raw(&self) -> *mut _cef_life_span_handler_t {\n\t\tself.object.cast()\n\t}\n}\n\nimpl Clone for LifeSpanHandlerImpl {\n\tfn clone(&self) -> Self {\n\t\tunsafe {\n\t\t\tlet rc_impl = &mut *self.object;\n\t\t\trc_impl.interface.add_ref();\n\t\t}\n\t\tSelf { object: self.object }\n\t}\n}\nimpl Rc for LifeSpanHandlerImpl {\n\tfn as_base(&self) -> &cef_base_ref_counted_t {\n\t\tunsafe {\n\t\t\tlet base = &*self.object;\n\t\t\tstd::mem::transmute(&base.cef_object)\n\t\t}\n\t}\n}\nimpl WrapLifeSpanHandler for LifeSpanHandlerImpl {\n\tfn wrap_rc(&mut self, object: *mut RcImpl<_cef_life_span_handler_t, Self>) {\n\t\tself.object = object;\n\t}\n}\n"
  },
  {
    "path": "desktop/src/cef/internal/load_handler.rs",
    "content": "use cef::rc::{Rc, RcImpl};\nuse cef::sys::{_cef_load_handler_t, cef_base_ref_counted_t, cef_load_handler_t};\nuse cef::{ImplBrowser, ImplBrowserHost, ImplLoadHandler, WrapLoadHandler};\n\nuse crate::cef::CefEventHandler;\n\npub(crate) struct LoadHandlerImpl<H: CefEventHandler> {\n\tobject: *mut RcImpl<cef_load_handler_t, Self>,\n\tevent_handler: H,\n}\nimpl<H: CefEventHandler> LoadHandlerImpl<H> {\n\tpub(crate) fn new(event_handler: H) -> Self {\n\t\tSelf {\n\t\t\tobject: std::ptr::null_mut(),\n\t\t\tevent_handler,\n\t\t}\n\t}\n}\n\nimpl<H: CefEventHandler> ImplLoadHandler for LoadHandlerImpl<H> {\n\tfn on_loading_state_change(&self, browser: Option<&mut cef::Browser>, is_loading: std::ffi::c_int, _can_go_back: std::ffi::c_int, _can_go_forward: std::ffi::c_int) {\n\t\tlet view_info = self.event_handler.view_info();\n\n\t\tif let Some(browser) = browser\n\t\t\t&& is_loading == 0\n\t\t{\n\t\t\tbrowser.host().unwrap().set_zoom_level(view_info.zoom());\n\t\t}\n\t}\n\n\tfn get_raw(&self) -> *mut _cef_load_handler_t {\n\t\tself.object.cast()\n\t}\n}\n\nimpl<H: CefEventHandler> Clone for LoadHandlerImpl<H> {\n\tfn clone(&self) -> Self {\n\t\tunsafe {\n\t\t\tlet rc_impl = &mut *self.object;\n\t\t\trc_impl.interface.add_ref();\n\t\t}\n\t\tSelf {\n\t\t\tobject: self.object,\n\t\t\tevent_handler: self.event_handler.duplicate(),\n\t\t}\n\t}\n}\nimpl<H: CefEventHandler> Rc for LoadHandlerImpl<H> {\n\tfn as_base(&self) -> &cef_base_ref_counted_t {\n\t\tunsafe {\n\t\t\tlet base = &*self.object;\n\t\t\tstd::mem::transmute(&base.cef_object)\n\t\t}\n\t}\n}\nimpl<H: CefEventHandler> WrapLoadHandler for LoadHandlerImpl<H> {\n\tfn wrap_rc(&mut self, object: *mut RcImpl<_cef_load_handler_t, Self>) {\n\t\tself.object = object;\n\t}\n}\n"
  },
  {
    "path": "desktop/src/cef/internal/render_handler.rs",
    "content": "use cef::rc::{Rc, RcImpl};\nuse cef::sys::{_cef_render_handler_t, cef_base_ref_counted_t};\nuse cef::{Browser, ImplRenderHandler, PaintElementType, Rect, WrapRenderHandler};\n\nuse crate::cef::CefEventHandler;\nuse crate::render::FrameBufferRef;\n\npub(crate) struct RenderHandlerImpl<H: CefEventHandler> {\n\tobject: *mut RcImpl<_cef_render_handler_t, Self>,\n\tevent_handler: H,\n}\nimpl<H: CefEventHandler> RenderHandlerImpl<H> {\n\tpub(crate) fn new(event_handler: H) -> Self {\n\t\tSelf {\n\t\t\tobject: std::ptr::null_mut(),\n\t\t\tevent_handler,\n\t\t}\n\t}\n}\n\nimpl<H: CefEventHandler> ImplRenderHandler for RenderHandlerImpl<H> {\n\tfn view_rect(&self, _browser: Option<&mut Browser>, rect: Option<&mut Rect>) {\n\t\tif let Some(rect) = rect {\n\t\t\tlet view_info = self.event_handler.view_info();\n\t\t\t*rect = Rect {\n\t\t\t\tx: 0,\n\t\t\t\ty: 0,\n\t\t\t\twidth: view_info.width() as i32,\n\t\t\t\theight: view_info.height() as i32,\n\t\t\t};\n\t\t}\n\t}\n\n\tfn on_paint(&self, _browser: Option<&mut Browser>, _type_: PaintElementType, _dirty_rects: Option<&[Rect]>, buffer: *const u8, width: std::ffi::c_int, height: std::ffi::c_int) {\n\t\tlet buffer_size = (width * height * 4) as usize;\n\t\tlet buffer_slice = unsafe { std::slice::from_raw_parts(buffer, buffer_size) };\n\t\tlet frame_buffer = FrameBufferRef::new(buffer_slice, width as usize, height as usize).expect(\"Failed to create frame buffer\");\n\n\t\tself.event_handler.draw(frame_buffer)\n\t}\n\n\t#[cfg(feature = \"accelerated_paint\")]\n\tfn on_accelerated_paint(&self, _browser: Option<&mut Browser>, type_: PaintElementType, _dirty_rects: Option<&[Rect]>, info: Option<&cef::AcceleratedPaintInfo>) {\n\t\tuse cef::osr_texture_import::SharedTextureHandle;\n\n\t\tif type_ != PaintElementType::default() {\n\t\t\treturn;\n\t\t}\n\n\t\tlet shared_handle = SharedTextureHandle::new(info.unwrap());\n\t\tif let SharedTextureHandle::Unsupported = shared_handle {\n\t\t\ttracing::error!(\"Platform does not support accelerated painting\");\n\t\t\treturn;\n\t\t}\n\n\t\tself.event_handler.draw_gpu(shared_handle);\n\t}\n\n\tfn get_raw(&self) -> *mut _cef_render_handler_t {\n\t\tself.object.cast()\n\t}\n}\n\nimpl<H: CefEventHandler> Clone for RenderHandlerImpl<H> {\n\tfn clone(&self) -> Self {\n\t\tunsafe {\n\t\t\tlet rc_impl = &mut *self.object;\n\t\t\trc_impl.interface.add_ref();\n\t\t}\n\t\tSelf {\n\t\t\tobject: self.object,\n\t\t\tevent_handler: self.event_handler.duplicate(),\n\t\t}\n\t}\n}\nimpl<H: CefEventHandler> Rc for RenderHandlerImpl<H> {\n\tfn as_base(&self) -> &cef_base_ref_counted_t {\n\t\tunsafe {\n\t\t\tlet base = &*self.object;\n\t\t\tstd::mem::transmute(&base.cef_object)\n\t\t}\n\t}\n}\nimpl<H: CefEventHandler> WrapRenderHandler for RenderHandlerImpl<H> {\n\tfn wrap_rc(&mut self, object: *mut RcImpl<_cef_render_handler_t, Self>) {\n\t\tself.object = object;\n\t}\n}\n"
  },
  {
    "path": "desktop/src/cef/internal/render_process_app.rs",
    "content": "use cef::rc::{Rc, RcImpl};\nuse cef::sys::{_cef_app_t, cef_base_ref_counted_t};\nuse cef::{App, ImplApp, RenderProcessHandler, SchemeRegistrar, WrapApp};\n\nuse super::render_process_handler::RenderProcessHandlerImpl;\nuse super::scheme_handler_factory::SchemeHandlerFactoryImpl;\nuse crate::cef::CefEventHandler;\n\npub(crate) struct RenderProcessAppImpl<H: CefEventHandler> {\n\tobject: *mut RcImpl<_cef_app_t, Self>,\n\trender_process_handler: RenderProcessHandler,\n}\nimpl<H: CefEventHandler> RenderProcessAppImpl<H> {\n\tpub(crate) fn app() -> App {\n\t\tApp::new(Self {\n\t\t\tobject: std::ptr::null_mut(),\n\t\t\trender_process_handler: RenderProcessHandler::new(RenderProcessHandlerImpl::new()),\n\t\t})\n\t}\n}\n\nimpl<H: CefEventHandler> ImplApp for RenderProcessAppImpl<H> {\n\tfn on_register_custom_schemes(&self, registrar: Option<&mut SchemeRegistrar>) {\n\t\tSchemeHandlerFactoryImpl::<H>::register_schemes(registrar);\n\t}\n\n\tfn render_process_handler(&self) -> Option<RenderProcessHandler> {\n\t\tSome(self.render_process_handler.clone())\n\t}\n\n\tfn get_raw(&self) -> *mut _cef_app_t {\n\t\tself.object.cast()\n\t}\n}\n\nimpl<H: CefEventHandler> Clone for RenderProcessAppImpl<H> {\n\tfn clone(&self) -> Self {\n\t\tunsafe {\n\t\t\tlet rc_impl = &mut *self.object;\n\t\t\trc_impl.interface.add_ref();\n\t\t}\n\t\tSelf {\n\t\t\tobject: self.object,\n\t\t\trender_process_handler: self.render_process_handler.clone(),\n\t\t}\n\t}\n}\nimpl<H: CefEventHandler> Rc for RenderProcessAppImpl<H> {\n\tfn as_base(&self) -> &cef_base_ref_counted_t {\n\t\tunsafe {\n\t\t\tlet base = &*self.object;\n\t\t\tstd::mem::transmute(&base.cef_object)\n\t\t}\n\t}\n}\nimpl<H: CefEventHandler> WrapApp for RenderProcessAppImpl<H> {\n\tfn wrap_rc(&mut self, object: *mut RcImpl<_cef_app_t, Self>) {\n\t\tself.object = object;\n\t}\n}\n"
  },
  {
    "path": "desktop/src/cef/internal/render_process_handler.rs",
    "content": "use cef::rc::{ConvertReturnValue, Rc, RcImpl};\nuse cef::sys::{_cef_render_process_handler_t, cef_base_ref_counted_t, cef_render_process_handler_t, cef_v8_propertyattribute_t, cef_v8_value_create_array_buffer_with_copy};\nuse cef::{CefString, ImplFrame, ImplRenderProcessHandler, ImplV8Context, ImplV8Value, V8Handler, V8Propertyattribute, V8Value, WrapRenderProcessHandler, v8_value_create_function};\n\nuse crate::cef::ipc::{MessageType, UnpackMessage, UnpackedMessage};\n\nuse super::render_process_v8_handler::RenderProcessV8HandlerImpl;\n\npub(crate) struct RenderProcessHandlerImpl {\n\tobject: *mut RcImpl<cef_render_process_handler_t, Self>,\n}\nimpl RenderProcessHandlerImpl {\n\tpub(crate) fn new() -> Self {\n\t\tSelf { object: std::ptr::null_mut() }\n\t}\n}\n\nimpl ImplRenderProcessHandler for RenderProcessHandlerImpl {\n\tfn on_process_message_received(\n\t\t&self,\n\t\t_browser: Option<&mut cef::Browser>,\n\t\tframe: Option<&mut cef::Frame>,\n\t\t_source_process: cef::ProcessId,\n\t\tmessage: Option<&mut cef::ProcessMessage>,\n\t) -> std::ffi::c_int {\n\t\tlet unpacked_message = unsafe { message.and_then(|m| m.unpack()) };\n\t\tmatch unpacked_message {\n\t\t\tSome(UnpackedMessage {\n\t\t\t\tmessage_type: MessageType::SendToJS,\n\t\t\t\tdata,\n\t\t\t}) => {\n\t\t\t\tlet Some(frame) = frame else {\n\t\t\t\t\ttracing::error!(\"Frame is not available\");\n\t\t\t\t\treturn 0;\n\t\t\t\t};\n\t\t\t\tlet Some(context) = frame.v8_context() else {\n\t\t\t\t\ttracing::error!(\"V8 context is not available\");\n\t\t\t\t\treturn 0;\n\t\t\t\t};\n\t\t\t\tif context.enter() == 0 {\n\t\t\t\t\ttracing::error!(\"Failed to enter V8 context\");\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\t\t\t\tlet mut value: V8Value = unsafe { cef_v8_value_create_array_buffer_with_copy(data.as_ptr() as *mut std::ffi::c_void, data.len()) }.wrap_result();\n\t\t\t\tlet Some(global) = context.global() else {\n\t\t\t\t\ttracing::error!(\"Global object is not available in V8 context\");\n\t\t\t\t\treturn 0;\n\t\t\t\t};\n\n\t\t\t\tlet function_name = \"receiveNativeMessage\";\n\t\t\t\tlet property_name = \"receiveNativeMessageData\";\n\n\t\t\t\tlet function_call = format!(\"window.{function_name}(window.{property_name})\");\n\n\t\t\t\tglobal.set_value_bykey(\n\t\t\t\t\tSome(&CefString::from(property_name)),\n\t\t\t\t\tSome(&mut value),\n\t\t\t\t\tcef_v8_propertyattribute_t::V8_PROPERTY_ATTRIBUTE_READONLY.wrap_result(),\n\t\t\t\t);\n\n\t\t\t\tif global.value_bykey(Some(&CefString::from(function_name))).is_some() {\n\t\t\t\t\tframe.execute_java_script(Some(&CefString::from(function_call.as_str())), None, 0);\n\t\t\t\t}\n\n\t\t\t\tif context.exit() == 0 {\n\t\t\t\t\ttracing::error!(\"Failed to exit V8 context\");\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\t\t\t}\n\t\t\t_ => {\n\t\t\t\ttracing::error!(\"Unexpected message type received in render process\");\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\t\t1\n\t}\n\n\tfn on_context_created(&self, _browser: Option<&mut cef::Browser>, _frame: Option<&mut cef::Frame>, context: Option<&mut cef::V8Context>) {\n\t\tlet register_js_function = |context: &mut cef::V8Context, name: &'static str| {\n\t\t\tlet mut v8_handler = V8Handler::new(RenderProcessV8HandlerImpl::new());\n\t\t\tlet Some(mut function) = v8_value_create_function(Some(&CefString::from(name)), Some(&mut v8_handler)) else {\n\t\t\t\ttracing::error!(\"Failed to create V8 function {name}\");\n\t\t\t\treturn;\n\t\t\t};\n\n\t\t\tlet Some(global) = context.global() else {\n\t\t\t\ttracing::error!(\"Global object is not available in V8 context\");\n\t\t\t\treturn;\n\t\t\t};\n\t\t\tglobal.set_value_bykey(Some(&CefString::from(name)), Some(&mut function), V8Propertyattribute::default());\n\t\t};\n\n\t\tlet Some(context) = context else {\n\t\t\ttracing::error!(\"V8 context is not available\");\n\t\t\treturn;\n\t\t};\n\n\t\tlet initialized_function_name = \"initializeNativeCommunication\";\n\t\tlet send_function_name = \"sendNativeMessage\";\n\n\t\tregister_js_function(context, initialized_function_name);\n\t\tregister_js_function(context, send_function_name);\n\t}\n\n\tfn get_raw(&self) -> *mut _cef_render_process_handler_t {\n\t\tself.object.cast()\n\t}\n}\n\nimpl Clone for RenderProcessHandlerImpl {\n\tfn clone(&self) -> Self {\n\t\tunsafe {\n\t\t\tlet rc_impl = &mut *self.object;\n\t\t\trc_impl.interface.add_ref();\n\t\t}\n\t\tSelf { object: self.object }\n\t}\n}\nimpl Rc for RenderProcessHandlerImpl {\n\tfn as_base(&self) -> &cef_base_ref_counted_t {\n\t\tunsafe {\n\t\t\tlet base = &*self.object;\n\t\t\tstd::mem::transmute(&base.cef_object)\n\t\t}\n\t}\n}\nimpl WrapRenderProcessHandler for RenderProcessHandlerImpl {\n\tfn wrap_rc(&mut self, object: *mut RcImpl<_cef_render_process_handler_t, Self>) {\n\t\tself.object = object;\n\t}\n}\n"
  },
  {
    "path": "desktop/src/cef/internal/render_process_v8_handler.rs",
    "content": "use cef::{ImplV8Handler, ImplV8Value, V8Value, WrapV8Handler, rc::Rc, v8_context_get_current_context};\n\nuse crate::cef::ipc::{MessageType, SendMessage};\n\npub struct RenderProcessV8HandlerImpl {\n\tobject: *mut cef::rc::RcImpl<cef::sys::_cef_v8_handler_t, Self>,\n}\nimpl RenderProcessV8HandlerImpl {\n\tpub(crate) fn new() -> Self {\n\t\tSelf { object: std::ptr::null_mut() }\n\t}\n}\n\nimpl ImplV8Handler for RenderProcessV8HandlerImpl {\n\tfn execute(\n\t\t&self,\n\t\tname: Option<&cef::CefString>,\n\t\t_object: Option<&mut V8Value>,\n\t\targuments: Option<&[Option<V8Value>]>,\n\t\t_retval: Option<&mut Option<V8Value>>,\n\t\t_exception: Option<&mut cef::CefString>,\n\t) -> std::ffi::c_int {\n\t\tmatch name.map(|s| s.to_string()).unwrap_or_default().as_str() {\n\t\t\t\"initializeNativeCommunication\" => {\n\t\t\t\tv8_context_get_current_context().send_message(MessageType::Initialized, vec![0u8].as_slice());\n\t\t\t}\n\t\t\t\"sendNativeMessage\" => {\n\t\t\t\tlet Some(args) = arguments else {\n\t\t\t\t\ttracing::error!(\"No arguments provided to sendNativeMessage\");\n\t\t\t\t\treturn 0;\n\t\t\t\t};\n\t\t\t\tlet Some(arg1) = args.first() else {\n\t\t\t\t\ttracing::error!(\"No arguments provided to sendNativeMessage\");\n\t\t\t\t\treturn 0;\n\t\t\t\t};\n\t\t\t\tlet Some(arg1) = arg1.as_ref() else {\n\t\t\t\t\ttracing::error!(\"First argument to sendNativeMessage is not an ArrayBuffer\");\n\t\t\t\t\treturn 0;\n\t\t\t\t};\n\t\t\t\tif arg1.is_array_buffer() == 0 {\n\t\t\t\t\ttracing::error!(\"First argument to sendNativeMessage is not an ArrayBuffer\");\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\n\t\t\t\tlet size = arg1.array_buffer_byte_length();\n\t\t\t\tlet ptr = arg1.array_buffer_data();\n\t\t\t\tlet data = unsafe { std::slice::from_raw_parts_mut(ptr as *mut u8, size) };\n\n\t\t\t\tv8_context_get_current_context().send_message(MessageType::SendToNative, data);\n\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\tname => {\n\t\t\t\ttracing::error!(\"Unknown V8 function called: {}\", name);\n\t\t\t}\n\t\t}\n\t\t1\n\t}\n\n\tfn get_raw(&self) -> *mut cef::sys::_cef_v8_handler_t {\n\t\tself.object.cast()\n\t}\n}\n\nimpl Clone for RenderProcessV8HandlerImpl {\n\tfn clone(&self) -> Self {\n\t\tunsafe {\n\t\t\tlet rc_impl = &mut *self.object;\n\t\t\trc_impl.interface.add_ref();\n\t\t}\n\t\tSelf { object: self.object }\n\t}\n}\nimpl Rc for RenderProcessV8HandlerImpl {\n\tfn as_base(&self) -> &cef::sys::cef_base_ref_counted_t {\n\t\tunsafe {\n\t\t\tlet base = &*self.object;\n\t\t\tstd::mem::transmute(&base.cef_object)\n\t\t}\n\t}\n}\nimpl WrapV8Handler for RenderProcessV8HandlerImpl {\n\tfn wrap_rc(&mut self, object: *mut cef::rc::RcImpl<cef::sys::_cef_v8_handler_t, Self>) {\n\t\tself.object = object;\n\t}\n}\n"
  },
  {
    "path": "desktop/src/cef/internal/resource_handler.rs",
    "content": "use cef::rc::{Rc, RcImpl};\nuse cef::sys::{_cef_resource_handler_t, cef_base_ref_counted_t};\nuse cef::{Callback, CefString, ImplResourceHandler, ImplResponse, Request, ResourceReadCallback, Response, WrapResourceHandler};\nuse std::cell::RefCell;\nuse std::ffi::c_int;\nuse std::io::Read;\n\nuse crate::cef::{Resource, ResourceReader};\n\npub(crate) struct ResourceHandlerImpl {\n\tobject: *mut RcImpl<_cef_resource_handler_t, Self>,\n\treader: Option<RefCell<ResourceReader>>,\n\tmimetype: Option<String>,\n}\n\nimpl ResourceHandlerImpl {\n\tpub fn new(resource: Option<Resource>) -> Self {\n\t\tif let Some(resource) = resource {\n\t\t\tSelf {\n\t\t\t\tobject: std::ptr::null_mut(),\n\t\t\t\treader: Some(resource.reader.into()),\n\t\t\t\tmimetype: resource.mimetype,\n\t\t\t}\n\t\t} else {\n\t\t\tSelf {\n\t\t\t\tobject: std::ptr::null_mut(),\n\t\t\t\treader: None,\n\t\t\t\tmimetype: None,\n\t\t\t}\n\t\t}\n\t}\n}\n\nimpl ImplResourceHandler for ResourceHandlerImpl {\n\tfn open(&self, _request: Option<&mut Request>, handle_request: Option<&mut c_int>, _callback: Option<&mut Callback>) -> c_int {\n\t\tif let Some(handle_request) = handle_request {\n\t\t\t*handle_request = 1;\n\t\t}\n\t\t1\n\t}\n\n\tfn response_headers(&self, response: Option<&mut Response>, response_length: Option<&mut i64>, _redirect_url: Option<&mut CefString>) {\n\t\tif let Some(response_length) = response_length {\n\t\t\t*response_length = -1; // Indicating that the length is unknown\n\t\t}\n\t\tif let Some(response) = response {\n\t\t\tif self.reader.is_some() {\n\t\t\t\tif let Some(mimetype) = &self.mimetype {\n\t\t\t\t\tlet cef_mime = CefString::from(mimetype.as_str());\n\t\t\t\t\tresponse.set_mime_type(Some(&cef_mime));\n\t\t\t\t} else {\n\t\t\t\t\tresponse.set_mime_type(None);\n\t\t\t\t}\n\t\t\t\tresponse.set_status(200);\n\t\t\t} else {\n\t\t\t\tresponse.set_status(404);\n\t\t\t\tresponse.set_mime_type(Some(&CefString::from(\"text/plain\")));\n\t\t\t}\n\t\t}\n\t}\n\n\tfn read(&self, data_out: *mut u8, bytes_to_read: c_int, bytes_read: Option<&mut c_int>, _callback: Option<&mut ResourceReadCallback>) -> c_int {\n\t\tlet Some(bytes_read) = bytes_read else { unreachable!() };\n\t\tlet out = unsafe { std::slice::from_raw_parts_mut(data_out, bytes_to_read as usize) };\n\t\tif let Some(reader) = &self.reader {\n\t\t\tif let Ok(read) = reader.borrow_mut().read(out) {\n\t\t\t\t*bytes_read = read as i32;\n\t\t\t\tif read > 0 {\n\t\t\t\t\treturn 1; // Indicating that data was read\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t*bytes_read = -2; // Indicating ERR_FAILED\n\t\t\t}\n\t\t}\n\t\t0 // Indicating no data was read\n\t}\n\n\tfn get_raw(&self) -> *mut _cef_resource_handler_t {\n\t\tself.object.cast()\n\t}\n}\n\nimpl Clone for ResourceHandlerImpl {\n\tfn clone(&self) -> Self {\n\t\tunsafe {\n\t\t\tlet rc_impl = &mut *self.object;\n\t\t\trc_impl.interface.add_ref();\n\t\t}\n\t\tSelf {\n\t\t\tobject: self.object,\n\t\t\treader: self.reader.clone(),\n\t\t\tmimetype: self.mimetype.clone(),\n\t\t}\n\t}\n}\nimpl Rc for ResourceHandlerImpl {\n\tfn as_base(&self) -> &cef_base_ref_counted_t {\n\t\tunsafe {\n\t\t\tlet base = &*self.object;\n\t\t\tstd::mem::transmute(&base.cef_object)\n\t\t}\n\t}\n}\nimpl WrapResourceHandler for ResourceHandlerImpl {\n\tfn wrap_rc(&mut self, object: *mut RcImpl<_cef_resource_handler_t, Self>) {\n\t\tself.object = object;\n\t}\n}\n"
  },
  {
    "path": "desktop/src/cef/internal/scheme_handler_factory.rs",
    "content": "use cef::rc::{Rc, RcImpl};\nuse cef::sys::{_cef_scheme_handler_factory_t, cef_base_ref_counted_t, cef_scheme_options_t};\nuse cef::{Browser, CefString, Frame, ImplRequest, ImplSchemeHandlerFactory, ImplSchemeRegistrar, Request, ResourceHandler, SchemeRegistrar, WrapSchemeHandlerFactory};\n\nuse super::resource_handler::ResourceHandlerImpl;\nuse crate::cef::CefEventHandler;\nuse crate::cef::consts::{RESOURCE_DOMAIN, RESOURCE_SCHEME};\n\npub(crate) struct SchemeHandlerFactoryImpl<H: CefEventHandler> {\n\tobject: *mut RcImpl<_cef_scheme_handler_factory_t, Self>,\n\tevent_handler: H,\n}\nimpl<H: CefEventHandler> SchemeHandlerFactoryImpl<H> {\n\tpub(crate) fn new(event_handler: H) -> Self {\n\t\tSelf {\n\t\t\tobject: std::ptr::null_mut(),\n\t\t\tevent_handler,\n\t\t}\n\t}\n\n\tpub(crate) fn register_schemes(registrar: Option<&mut SchemeRegistrar>) {\n\t\tif let Some(registrar) = registrar {\n\t\t\tlet mut scheme_options = 0;\n\t\t\tscheme_options |= cef_scheme_options_t::CEF_SCHEME_OPTION_STANDARD as i32;\n\t\t\tscheme_options |= cef_scheme_options_t::CEF_SCHEME_OPTION_FETCH_ENABLED as i32;\n\t\t\tscheme_options |= cef_scheme_options_t::CEF_SCHEME_OPTION_SECURE as i32;\n\t\t\tscheme_options |= cef_scheme_options_t::CEF_SCHEME_OPTION_CORS_ENABLED as i32;\n\t\t\tregistrar.add_custom_scheme(Some(&CefString::from(RESOURCE_SCHEME)), scheme_options);\n\t\t}\n\t}\n}\n\nimpl<H: CefEventHandler> ImplSchemeHandlerFactory for SchemeHandlerFactoryImpl<H> {\n\tfn create(&self, _browser: Option<&mut Browser>, _frame: Option<&mut Frame>, _scheme_name: Option<&CefString>, request: Option<&mut Request>) -> Option<ResourceHandler> {\n\t\tif let Some(request) = request {\n\t\t\tlet url = CefString::from(&request.url()).to_string();\n\t\t\tlet path = url\n\t\t\t\t.strip_prefix(&format!(\"{RESOURCE_SCHEME}://{RESOURCE_DOMAIN}/\"))\n\t\t\t\t.expect(\"CEF should only call this for our custom scheme and domain that we registered this factory for\");\n\t\t\tlet resource = self.event_handler.load_resource(path.to_string().into());\n\t\t\treturn Some(ResourceHandler::new(ResourceHandlerImpl::new(resource)));\n\t\t}\n\t\tNone\n\t}\n\tfn get_raw(&self) -> *mut _cef_scheme_handler_factory_t {\n\t\tself.object.cast()\n\t}\n}\n\nimpl<H: CefEventHandler> Clone for SchemeHandlerFactoryImpl<H> {\n\tfn clone(&self) -> Self {\n\t\tunsafe {\n\t\t\tlet rc_impl = &mut *self.object;\n\t\t\trc_impl.interface.add_ref();\n\t\t}\n\t\tSelf {\n\t\t\tobject: self.object,\n\t\t\tevent_handler: self.event_handler.duplicate(),\n\t\t}\n\t}\n}\nimpl<H: CefEventHandler> Rc for SchemeHandlerFactoryImpl<H> {\n\tfn as_base(&self) -> &cef_base_ref_counted_t {\n\t\tunsafe {\n\t\t\tlet base = &*self.object;\n\t\t\tstd::mem::transmute(&base.cef_object)\n\t\t}\n\t}\n}\nimpl<H: CefEventHandler> WrapSchemeHandlerFactory for SchemeHandlerFactoryImpl<H> {\n\tfn wrap_rc(&mut self, object: *mut RcImpl<_cef_scheme_handler_factory_t, Self>) {\n\t\tself.object = object;\n\t}\n}\n"
  },
  {
    "path": "desktop/src/cef/internal/task.rs",
    "content": "use cef::rc::{Rc, RcImpl};\nuse cef::sys::{_cef_task_t, cef_base_ref_counted_t};\nuse cef::{ImplTask, WrapTask};\nuse std::cell::RefCell;\n\n// Closure-based task wrapper following CEF patterns\npub struct ClosureTask<F> {\n\tpub(crate) object: *mut RcImpl<_cef_task_t, Self>,\n\tpub(crate) closure: RefCell<Option<F>>,\n}\n\nimpl<F: FnOnce() + Send + 'static> ClosureTask<F> {\n\tpub fn new(closure: F) -> Self {\n\t\tSelf {\n\t\t\tobject: std::ptr::null_mut(),\n\t\t\tclosure: RefCell::new(Some(closure)),\n\t\t}\n\t}\n}\n\nimpl<F: FnOnce() + Send + 'static> ImplTask for ClosureTask<F> {\n\tfn execute(&self) {\n\t\tif let Some(closure) = self.closure.borrow_mut().take() {\n\t\t\tclosure();\n\t\t}\n\t}\n\n\tfn get_raw(&self) -> *mut _cef_task_t {\n\t\tself.object.cast()\n\t}\n}\n\nimpl<F: FnOnce() + Send + 'static> Clone for ClosureTask<F> {\n\tfn clone(&self) -> Self {\n\t\tunsafe {\n\t\t\tif !self.object.is_null() {\n\t\t\t\tlet rc_impl = &mut *self.object;\n\t\t\t\trc_impl.interface.add_ref();\n\t\t\t}\n\t\t}\n\t\tSelf {\n\t\t\tobject: self.object,\n\t\t\tclosure: RefCell::new(None), // Closure can only be executed once\n\t\t}\n\t}\n}\n\nimpl<F: FnOnce() + Send + 'static> Rc for ClosureTask<F> {\n\tfn as_base(&self) -> &cef_base_ref_counted_t {\n\t\tunsafe {\n\t\t\tlet base = &*self.object;\n\t\t\tstd::mem::transmute(&base.cef_object)\n\t\t}\n\t}\n}\n\nimpl<F: FnOnce() + Send + 'static> WrapTask for ClosureTask<F> {\n\tfn wrap_rc(&mut self, object: *mut RcImpl<_cef_task_t, Self>) {\n\t\tself.object = object;\n\t}\n}\n"
  },
  {
    "path": "desktop/src/cef/internal.rs",
    "content": "mod browser_process_app;\nmod browser_process_client;\nmod browser_process_handler;\n\nmod render_process_app;\nmod render_process_handler;\nmod render_process_v8_handler;\n\nmod context_menu_handler;\nmod display_handler;\nmod life_span_handler;\nmod load_handler;\nmod resource_handler;\nmod scheme_handler_factory;\n\npub(super) mod render_handler;\n\n#[cfg(not(target_os = \"macos\"))]\npub(super) mod task;\n\npub(super) use browser_process_app::BrowserProcessAppImpl;\npub(super) use browser_process_client::BrowserProcessClientImpl;\npub(super) use render_process_app::RenderProcessAppImpl;\npub(super) use scheme_handler_factory::SchemeHandlerFactoryImpl;\n"
  },
  {
    "path": "desktop/src/cef/ipc.rs",
    "content": "use cef::{CefString, Frame, ImplBinaryValue, ImplFrame, ImplListValue, ImplProcessMessage, ImplV8Context, ProcessId, V8Context, sys::cef_process_id_t};\n\npub(crate) enum MessageType {\n\tInitialized,\n\tSendToJS,\n\tSendToNative,\n}\nimpl From<MessageType> for MessageInfo {\n\tfn from(val: MessageType) -> Self {\n\t\tmatch val {\n\t\t\tMessageType::Initialized => MessageInfo {\n\t\t\t\tname: \"initialized\".to_string(),\n\t\t\t\ttarget: cef_process_id_t::PID_BROWSER.into(),\n\t\t\t},\n\t\t\tMessageType::SendToJS => MessageInfo {\n\t\t\t\tname: \"send_to_js\".to_string(),\n\t\t\t\ttarget: cef_process_id_t::PID_RENDERER.into(),\n\t\t\t},\n\t\t\tMessageType::SendToNative => MessageInfo {\n\t\t\t\tname: \"send_to_native\".to_string(),\n\t\t\t\ttarget: cef_process_id_t::PID_BROWSER.into(),\n\t\t\t},\n\t\t}\n\t}\n}\nimpl TryFrom<String> for MessageType {\n\ttype Error = ();\n\tfn try_from(value: String) -> Result<Self, Self::Error> {\n\t\tmatch value.as_str() {\n\t\t\t\"initialized\" => Ok(MessageType::Initialized),\n\t\t\t\"send_to_js\" => Ok(MessageType::SendToJS),\n\t\t\t\"send_to_native\" => Ok(MessageType::SendToNative),\n\t\t\t_ => Err(()),\n\t\t}\n\t}\n}\n\npub(crate) struct MessageInfo {\n\tname: String,\n\ttarget: ProcessId,\n}\n\npub(crate) trait SendMessage {\n\tfn send_message(&self, message_type: MessageType, message: &[u8]);\n}\nimpl SendMessage for Option<V8Context> {\n\tfn send_message(&self, message_type: MessageType, message: &[u8]) {\n\t\tlet Some(context) = self else {\n\t\t\ttracing::error!(\"Current V8 context is not available, cannot send message\");\n\t\t\treturn;\n\t\t};\n\n\t\tcontext.send_message(message_type, message);\n\t}\n}\nimpl SendMessage for V8Context {\n\tfn send_message(&self, message_type: MessageType, message: &[u8]) {\n\t\tlet Some(frame) = self.frame() else {\n\t\t\ttracing::error!(\"Current V8 context does not have a frame, cannot send message\");\n\t\t\treturn;\n\t\t};\n\n\t\tframe.send_message(message_type, message);\n\t}\n}\nimpl SendMessage for Frame {\n\tfn send_message(&self, message_type: MessageType, message: &[u8]) {\n\t\tlet MessageInfo { name, target } = message_type.into();\n\n\t\tlet Some(mut process_message) = cef::process_message_create(Some(&CefString::from(name.as_str()))) else {\n\t\t\ttracing::error!(\"Failed to create process message: {}\", name);\n\t\t\treturn;\n\t\t};\n\t\tlet Some(arg_list) = process_message.argument_list() else { return };\n\t\tlet mut value = ::cef::binary_value_create(Some(message));\n\t\targ_list.set_binary(0, value.as_mut());\n\n\t\tself.send_process_message(target, Some(&mut process_message));\n\t}\n}\n\npub(crate) struct UnpackedMessage<'a> {\n\tpub(crate) message_type: MessageType,\n\tpub(crate) data: &'a [u8],\n}\n\ntrait Sealed {}\nimpl Sealed for cef::ProcessMessage {}\n#[allow(private_bounds)]\npub(crate) trait UnpackMessage: Sealed {\n\t/// # Safety\n\t///\n\t/// The caller must ensure that the message is valid.\n\t/// Message should come from cef.\n\tunsafe fn unpack(&self) -> Option<UnpackedMessage<'_>>;\n}\nimpl UnpackMessage for cef::ProcessMessage {\n\tunsafe fn unpack(&self) -> Option<UnpackedMessage<'_>> {\n\t\tlet pointer: *mut cef::sys::_cef_string_utf16_t = self.name().into();\n\t\tlet message = unsafe { super::utility::pointer_to_string(pointer) };\n\t\tlet Ok(message_type) = message.try_into() else {\n\t\t\ttracing::error!(\"Failed to get message type from process message\");\n\t\t\treturn None;\n\t\t};\n\t\tlet arglist = self.argument_list()?;\n\t\tlet binary = arglist.binary(0)?;\n\t\tlet size = binary.size();\n\t\tlet ptr = binary.raw_data();\n\t\tlet buffer = unsafe { std::slice::from_raw_parts(ptr as *const u8, size) };\n\t\tSome(UnpackedMessage { message_type, data: buffer })\n\t}\n}\n"
  },
  {
    "path": "desktop/src/cef/platform.rs",
    "content": "#[cfg(feature = \"accelerated_paint\")]\npub fn should_enable_hardware_acceleration() -> bool {\n\t#[cfg(target_os = \"linux\")]\n\t{\n\t\t// Check if running on Wayland or X11\n\t\tlet has_wayland = std::env::var(\"WAYLAND_DISPLAY\")\n\t\t\t.ok()\n\t\t\t.filter(|var| !var.is_empty())\n\t\t\t.or_else(|| std::env::var(\"WAYLAND_SOCKET\").ok())\n\t\t\t.filter(|var| !var.is_empty())\n\t\t\t.is_some();\n\n\t\tlet has_x11 = std::env::var(\"DISPLAY\").ok().filter(|var| !var.is_empty()).is_some();\n\n\t\tif !has_wayland && !has_x11 {\n\t\t\ttracing::warn!(\"No display server detected, disabling hardware acceleration\");\n\t\t\treturn false;\n\t\t}\n\n\t\t// Check for NVIDIA proprietary driver (known to have issues)\n\t\tif let Ok(driver_info) = std::fs::read_to_string(\"/proc/driver/nvidia/version\") {\n\t\t\tif driver_info.contains(\"NVIDIA\") {\n\t\t\t\ttracing::warn!(\"NVIDIA proprietary driver detected, hardware acceleration may be unstable\");\n\t\t\t\t// Still return true but with warning\n\t\t\t}\n\t\t}\n\n\t\t// Check for basic GPU capabilities\n\t\tif has_wayland {\n\t\t\ttracing::info!(\"Wayland detected, enabling hardware acceleration\");\n\t\t\ttrue\n\t\t} else if has_x11 {\n\t\t\ttracing::info!(\"X11 detected, enabling hardware acceleration\");\n\t\t\ttrue\n\t\t} else {\n\t\t\tfalse\n\t\t}\n\t}\n\n\t#[cfg(target_os = \"windows\")]\n\t{\n\t\t// Windows generally has good D3D11 support\n\t\ttracing::info!(\"Windows detected, enabling hardware acceleration\");\n\t\ttrue\n\t}\n\n\t#[cfg(target_os = \"macos\")]\n\t{\n\t\t// macOS has good Metal/IOSurface support\n\t\ttracing::info!(\"macOS detected, enabling hardware acceleration\");\n\t\ttrue\n\t}\n\n\t#[cfg(not(any(target_os = \"linux\", target_os = \"windows\", target_os = \"macos\")))]\n\t{\n\t\ttracing::warn!(\"Unsupported platform for hardware acceleration\");\n\t\tfalse\n\t}\n}\n"
  },
  {
    "path": "desktop/src/cef/utility.rs",
    "content": "pub unsafe fn pointer_to_string(pointer: *mut cef::sys::_cef_string_utf16_t) -> String {\n\tlet str = unsafe { (*pointer).str_ };\n\tlet len = unsafe { (*pointer).length };\n\tlet slice = unsafe { std::slice::from_raw_parts(str, len) };\n\tString::from_utf16(slice).unwrap()\n}\n"
  },
  {
    "path": "desktop/src/cef.rs",
    "content": "//! CEF (Chromium Embedded Framework) integration for Graphite Desktop\n//!\n//! This module provides CEF browser integration with hardware-accelerated texture sharing.\n//!\n//! # Hardware Acceleration\n//!\n//! The texture import system supports platform-specific hardware acceleration:\n//!\n//! - **Linux**: DMA-BUF via Vulkan external memory (`accelerated_paint_dmabuf` feature)\n//! - **Windows**: D3D11 shared textures via either Vulkan or D3D12 interop (`accelerated_paint_d3d11` feature)\n//! - **macOS**: IOSurface via Metal/Vulkan interop (`accelerated_paint_iosurface` feature)\n//!\n//! The system gracefully falls back to CPU textures when hardware acceleration is unavailable.\n\nuse std::fs::File;\nuse std::io;\nuse std::io::Read;\nuse std::path::PathBuf;\nuse std::sync::mpsc::Receiver;\nuse std::sync::{Arc, Mutex};\nuse std::time::Instant;\n\nuse crate::event::{AppEvent, AppEventScheduler};\nuse crate::render::FrameBufferRef;\nuse crate::window::Cursor;\nuse crate::wrapper::{WgpuContext, deserialize_editor_message};\n\nmod consts;\nmod context;\nmod dirs;\nmod input;\nmod internal;\nmod ipc;\nmod platform;\nmod utility;\n\n#[cfg(feature = \"accelerated_paint\")]\nuse cef::osr_texture_import::SharedTextureHandle;\n\npub(crate) use context::{CefContext, CefContextBuilder, InitError};\n\npub(crate) trait CefEventHandler: Send + Sync + 'static {\n\tfn view_info(&self) -> ViewInfo;\n\tfn draw<'a>(&self, frame_buffer: FrameBufferRef<'a>);\n\t#[cfg(feature = \"accelerated_paint\")]\n\tfn draw_gpu(&self, shared_texture: SharedTextureHandle);\n\tfn load_resource(&self, path: PathBuf) -> Option<Resource>;\n\tfn cursor_change(&self, cursor: Cursor);\n\t/// Schedule the main event loop to run the CEF event loop after the timeout.\n\t/// See [`_cef_browser_process_handler_t::on_schedule_message_pump_work`] for more documentation.\n\tfn schedule_cef_message_loop_work(&self, scheduled_time: Instant);\n\tfn initialized_web_communication(&self);\n\tfn receive_web_message(&self, message: &[u8]);\n\tfn duplicate(&self) -> Self\n\twhere\n\t\tSelf: Sized;\n}\n\n#[derive(Clone, Copy)]\npub(crate) struct ViewInfo {\n\twidth: u32,\n\theight: u32,\n\tscale: f64,\n}\nimpl ViewInfo {\n\tpub(crate) fn new() -> Self {\n\t\tSelf { width: 1, height: 1, scale: 1. }\n\t}\n\tpub(crate) fn apply_update(&mut self, update: ViewInfoUpdate) {\n\t\tmatch update {\n\t\t\tViewInfoUpdate::Size { width, height } if width > 0 && height > 0 => {\n\t\t\t\tself.width = width;\n\t\t\t\tself.height = height;\n\t\t\t}\n\t\t\tViewInfoUpdate::Scale(scale) if scale > 0. => {\n\t\t\t\tself.scale = scale;\n\t\t\t}\n\t\t\t_ => {}\n\t\t}\n\t}\n\tpub(crate) fn zoom(&self) -> f64 {\n\t\tself.scale.ln() / 1.2_f64.ln()\n\t}\n\tpub(crate) fn width(&self) -> u32 {\n\t\tself.width\n\t}\n\tpub(crate) fn height(&self) -> u32 {\n\t\tself.height\n\t}\n}\nimpl Default for ViewInfo {\n\tfn default() -> Self {\n\t\tSelf::new()\n\t}\n}\n\npub(crate) enum ViewInfoUpdate {\n\tSize { width: u32, height: u32 },\n\tScale(f64),\n}\n\n#[derive(Clone)]\npub(crate) struct Resource {\n\tpub(crate) reader: ResourceReader,\n\tpub(crate) mimetype: Option<String>,\n}\n\n#[expect(dead_code)]\n#[derive(Clone)]\npub(crate) enum ResourceReader {\n\tEmbedded(io::Cursor<&'static [u8]>),\n\tFile(Arc<File>),\n}\nimpl Read for ResourceReader {\n\tfn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {\n\t\tmatch self {\n\t\t\tResourceReader::Embedded(cursor) => cursor.read(buf),\n\t\t\tResourceReader::File(file) => file.as_ref().read(buf),\n\t\t}\n\t}\n}\n\npub(crate) struct CefHandler {\n\twgpu_context: WgpuContext,\n\tapp_event_scheduler: AppEventScheduler,\n\tview_info_receiver: Arc<Mutex<ViewInfoReceiver>>,\n}\n\nimpl CefHandler {\n\tpub(crate) fn new(wgpu_context: WgpuContext, app_event_scheduler: AppEventScheduler, view_info_receiver: Receiver<ViewInfoUpdate>) -> Self {\n\t\tSelf {\n\t\t\twgpu_context,\n\t\t\tapp_event_scheduler,\n\t\t\tview_info_receiver: Arc::new(Mutex::new(ViewInfoReceiver::new(view_info_receiver))),\n\t\t}\n\t}\n}\n\nimpl CefEventHandler for CefHandler {\n\tfn view_info(&self) -> ViewInfo {\n\t\tlet Ok(mut guard) = self.view_info_receiver.lock() else {\n\t\t\ttracing::error!(\"Failed to lock view_info_receiver\");\n\t\t\treturn ViewInfo::new();\n\t\t};\n\t\tlet ViewInfoReceiver { receiver, view_info } = &mut *guard;\n\t\tfor update in receiver.try_iter() {\n\t\t\tview_info.apply_update(update);\n\t\t}\n\t\t*view_info\n\t}\n\tfn draw<'a>(&self, frame_buffer: FrameBufferRef<'a>) {\n\t\tlet width = frame_buffer.width() as u32;\n\t\tlet height = frame_buffer.height() as u32;\n\t\tlet texture = self.wgpu_context.device.create_texture(&wgpu::TextureDescriptor {\n\t\t\tlabel: Some(\"CEF Texture\"),\n\t\t\tsize: wgpu::Extent3d {\n\t\t\t\twidth,\n\t\t\t\theight,\n\t\t\t\tdepth_or_array_layers: 1,\n\t\t\t},\n\t\t\tmip_level_count: 1,\n\t\t\tsample_count: 1,\n\t\t\tdimension: wgpu::TextureDimension::D2,\n\t\t\tformat: wgpu::TextureFormat::Bgra8Unorm,\n\t\t\tusage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,\n\t\t\tview_formats: &[],\n\t\t});\n\t\tself.wgpu_context.queue.write_texture(\n\t\t\twgpu::TexelCopyTextureInfo {\n\t\t\t\ttexture: &texture,\n\t\t\t\tmip_level: 0,\n\t\t\t\torigin: wgpu::Origin3d::ZERO,\n\t\t\t\taspect: wgpu::TextureAspect::All,\n\t\t\t},\n\t\t\tframe_buffer.buffer(),\n\t\t\twgpu::TexelCopyBufferLayout {\n\t\t\t\toffset: 0,\n\t\t\t\tbytes_per_row: Some(4 * width),\n\t\t\t\trows_per_image: Some(height),\n\t\t\t},\n\t\t\twgpu::Extent3d {\n\t\t\t\twidth,\n\t\t\t\theight,\n\t\t\t\tdepth_or_array_layers: 1,\n\t\t\t},\n\t\t);\n\n\t\tself.app_event_scheduler.schedule(AppEvent::UiUpdate(texture));\n\t}\n\n\t#[cfg(feature = \"accelerated_paint\")]\n\tfn draw_gpu(&self, shared_texture: SharedTextureHandle) {\n\t\tmatch shared_texture.import_texture(&self.wgpu_context.device) {\n\t\t\tOk(texture) => {\n\t\t\t\tself.app_event_scheduler.schedule(AppEvent::UiUpdate(texture));\n\t\t\t}\n\t\t\tErr(e) => {\n\t\t\t\ttracing::error!(\"Failed to import shared texture: {}\", e);\n\t\t\t}\n\t\t}\n\t}\n\n\tfn load_resource(&self, path: PathBuf) -> Option<Resource> {\n\t\tlet path = if path.as_os_str().is_empty() { PathBuf::from(\"index.html\") } else { path };\n\n\t\tlet mimetype = match path.extension().and_then(|s| s.to_str()).unwrap_or(\"\") {\n\t\t\t\"html\" => Some(\"text/html\".to_string()),\n\t\t\t\"css\" => Some(\"text/css\".to_string()),\n\t\t\t\"txt\" => Some(\"text/plain\".to_string()),\n\t\t\t\"wasm\" => Some(\"application/wasm\".to_string()),\n\t\t\t\"js\" => Some(\"application/javascript\".to_string()),\n\t\t\t\"png\" => Some(\"image/png\".to_string()),\n\t\t\t\"jpg\" | \"jpeg\" => Some(\"image/jpeg\".to_string()),\n\t\t\t\"svg\" => Some(\"image/svg+xml\".to_string()),\n\t\t\t\"xml\" => Some(\"application/xml\".to_string()),\n\t\t\t\"json\" => Some(\"application/json\".to_string()),\n\t\t\t\"ico\" => Some(\"image/x-icon\".to_string()),\n\t\t\t\"woff\" => Some(\"font/woff\".to_string()),\n\t\t\t\"woff2\" => Some(\"font/woff2\".to_string()),\n\t\t\t\"ttf\" => Some(\"font/ttf\".to_string()),\n\t\t\t\"otf\" => Some(\"font/otf\".to_string()),\n\t\t\t\"webmanifest\" => Some(\"application/manifest+json\".to_string()),\n\t\t\t\"graphite\" => Some(\"application/graphite+json\".to_string()),\n\t\t\t_ => None,\n\t\t};\n\n\t\t#[cfg(feature = \"embedded_resources\")]\n\t\t{\n\t\t\tif let Some(resources) = &graphite_desktop_embedded_resources::EMBEDDED_RESOURCES\n\t\t\t\t&& let Some(file) = resources.get_file(&path)\n\t\t\t{\n\t\t\t\treturn Some(Resource {\n\t\t\t\t\treader: ResourceReader::Embedded(io::Cursor::new(file.contents())),\n\t\t\t\t\tmimetype,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t#[cfg(not(feature = \"embedded_resources\"))]\n\t\t{\n\t\t\tuse std::path::Path;\n\t\t\tlet asset_path_env = std::env::var(\"GRAPHITE_RESOURCES\").ok()?;\n\t\t\tlet asset_path = Path::new(&asset_path_env);\n\t\t\tlet file_path = asset_path.join(path.strip_prefix(\"/\").unwrap_or(&path));\n\t\t\tif file_path.exists() && file_path.is_file() {\n\t\t\t\tif let Ok(file) = std::fs::File::open(file_path) {\n\t\t\t\t\treturn Some(Resource {\n\t\t\t\t\t\treader: ResourceReader::File(file.into()),\n\t\t\t\t\t\tmimetype,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tNone\n\t}\n\n\tfn cursor_change(&self, cursor: Cursor) {\n\t\tself.app_event_scheduler.schedule(AppEvent::CursorChange(cursor));\n\t}\n\n\tfn schedule_cef_message_loop_work(&self, scheduled_time: std::time::Instant) {\n\t\tself.app_event_scheduler.schedule(AppEvent::ScheduleBrowserWork(scheduled_time));\n\t}\n\n\tfn initialized_web_communication(&self) {\n\t\tself.app_event_scheduler.schedule(AppEvent::WebCommunicationInitialized);\n\t}\n\n\tfn receive_web_message(&self, message: &[u8]) {\n\t\tlet Some(desktop_wrapper_message) = deserialize_editor_message(message) else {\n\t\t\ttracing::error!(\"Failed to deserialize web message\");\n\t\t\treturn;\n\t\t};\n\t\tself.app_event_scheduler.schedule(AppEvent::DesktopWrapperMessage(desktop_wrapper_message));\n\t}\n\n\tfn duplicate(&self) -> Self\n\twhere\n\t\tSelf: Sized,\n\t{\n\t\tSelf {\n\t\t\twgpu_context: self.wgpu_context.clone(),\n\t\t\tapp_event_scheduler: self.app_event_scheduler.clone(),\n\t\t\tview_info_receiver: self.view_info_receiver.clone(),\n\t\t}\n\t}\n}\n\nstruct ViewInfoReceiver {\n\tview_info: ViewInfo,\n\treceiver: Receiver<ViewInfoUpdate>,\n}\nimpl ViewInfoReceiver {\n\tfn new(receiver: Receiver<ViewInfoUpdate>) -> Self {\n\t\tSelf { view_info: ViewInfo::new(), receiver }\n\t}\n}\n"
  },
  {
    "path": "desktop/src/cli.rs",
    "content": "#[derive(clap::Parser)]\n#[clap(name = \"graphite\", version)]\npub struct Cli {\n\t#[arg(help = \"Files to open on startup\")]\n\tpub files: Vec<std::path::PathBuf>,\n\n\t#[arg(long, action = clap::ArgAction::SetTrue, help = \"Disable hardware accelerated UI rendering\")]\n\tpub disable_ui_acceleration: bool,\n}\n"
  },
  {
    "path": "desktop/src/consts.rs",
    "content": "pub(crate) const APP_NAME: &str = \"Graphite\";\n#[cfg(any(target_os = \"linux\", target_os = \"windows\"))]\npub(crate) const APP_ID: &str = \"art.graphite.Graphite\";\n\n#[cfg(target_os = \"linux\")]\npub(crate) const APP_DIRECTORY_NAME: &str = \"graphite\";\n#[cfg(not(target_os = \"linux\"))]\npub(crate) const APP_DIRECTORY_NAME: &str = \"Graphite\";\npub(crate) const APP_LOCK_FILE_NAME: &str = \"instance.lock\";\npub(crate) const APP_STATE_FILE_NAME: &str = \"state.ron\";\npub(crate) const APP_PREFERENCES_FILE_NAME: &str = \"preferences.ron\";\npub(crate) const APP_DOCUMENTS_DIRECTORY_NAME: &str = \"documents\";\n\n// CEF configuration constants\npub(crate) const CEF_WINDOWLESS_FRAME_RATE: i32 = 60;\npub(crate) const CEF_MESSAGE_LOOP_MAX_ITERATIONS: usize = 10;\n"
  },
  {
    "path": "desktop/src/dirs.rs",
    "content": "use std::fs::create_dir_all;\nuse std::path::PathBuf;\n\nuse crate::consts::{APP_DIRECTORY_NAME, APP_DOCUMENTS_DIRECTORY_NAME};\n\npub(crate) fn ensure_dir_exists(path: &PathBuf) {\n\tif !path.exists() {\n\t\tcreate_dir_all(path).unwrap_or_else(|_| panic!(\"Failed to create directory at {path:?}\"));\n\t}\n}\n\npub(crate) fn app_data_dir() -> PathBuf {\n\tlet path = dirs::data_dir().expect(\"Failed to get data directory\").join(APP_DIRECTORY_NAME);\n\tensure_dir_exists(&path);\n\tpath\n}\n\npub(crate) fn app_autosave_documents_dir() -> PathBuf {\n\tlet path = app_data_dir().join(APP_DOCUMENTS_DIRECTORY_NAME);\n\tensure_dir_exists(&path);\n\tpath\n}\n"
  },
  {
    "path": "desktop/src/event.rs",
    "content": "use crate::wrapper::NodeGraphExecutionResult;\nuse crate::wrapper::messages::DesktopWrapperMessage;\n\npub(crate) enum AppEvent {\n\tUiUpdate(wgpu::Texture),\n\tCursorChange(crate::window::Cursor),\n\tScheduleBrowserWork(std::time::Instant),\n\tWebCommunicationInitialized,\n\tDesktopWrapperMessage(DesktopWrapperMessage),\n\tNodeGraphExecutionResult(NodeGraphExecutionResult),\n\tExit,\n\t#[cfg(target_os = \"macos\")]\n\tMenuEvent {\n\t\tid: String,\n\t},\n}\n\n#[derive(Clone)]\npub(crate) struct AppEventScheduler {\n\tpub(crate) proxy: winit::event_loop::EventLoopProxy,\n\tpub(crate) sender: std::sync::mpsc::Sender<AppEvent>,\n}\n\nimpl AppEventScheduler {\n\tpub(crate) fn schedule(&self, event: AppEvent) {\n\t\tlet _ = self.sender.send(event);\n\t\tself.proxy.wake_up();\n\t}\n}\n\npub(crate) trait CreateAppEventSchedulerEventLoopExt {\n\tfn create_app_event_scheduler(&self, sender: std::sync::mpsc::Sender<AppEvent>) -> AppEventScheduler;\n}\n\nimpl CreateAppEventSchedulerEventLoopExt for winit::event_loop::EventLoop {\n\tfn create_app_event_scheduler(&self, sender: std::sync::mpsc::Sender<AppEvent>) -> AppEventScheduler {\n\t\tAppEventScheduler { proxy: self.create_proxy(), sender }\n\t}\n}\n"
  },
  {
    "path": "desktop/src/gpu_context.rs",
    "content": "use crate::wrapper::{WgpuContext, WgpuContextBuilder, WgpuFeatures};\n\npub(super) async fn create_wgpu_context() -> WgpuContext {\n\tlet wgpu_context_builder = WgpuContextBuilder::new().with_features(WgpuFeatures::PUSH_CONSTANTS);\n\n\t// TODO: add a cli flag to list adapters and exit instead of always printing\n\tprintln!(\"\\nAvailable WGPU adapters:\\n{}\", wgpu_context_builder.available_adapters_fmt().await);\n\n\t// TODO: make this configurable via cli flags instead\n\tlet wgpu_context = match std::env::var(\"GRAPHITE_WGPU_ADAPTER\").ok().and_then(|s| s.parse().ok()) {\n\t\tNone => wgpu_context_builder.build().await,\n\t\tSome(adapter_index) => {\n\t\t\ttracing::info!(\"Overriding WGPU adapter selection with adapter index {adapter_index}\");\n\t\t\twgpu_context_builder.build_with_adapter_selection(|_| Some(adapter_index)).await\n\t\t}\n\t}\n\t.expect(\"Failed to create WGPU context\");\n\n\t// TODO: add a cli flag to list adapters and exit instead of always printing\n\tprintln!(\"Using WGPU adapter: {:?}\", wgpu_context.adapter.get_info());\n\n\twgpu_context\n}\n"
  },
  {
    "path": "desktop/src/lib.rs",
    "content": "use crate::app::App;\nuse crate::cef::CefHandler;\nuse crate::cli::Cli;\nuse crate::consts::APP_LOCK_FILE_NAME;\nuse crate::event::CreateAppEventSchedulerEventLoopExt;\nuse clap::Parser;\nuse std::io::Write;\nuse tracing_subscriber::EnvFilter;\nuse winit::event_loop::EventLoop;\n\npub(crate) use graphite_desktop_wrapper as wrapper;\n\nmod app;\nmod cef;\nmod cli;\nmod dirs;\nmod event;\nmod gpu_context;\nmod persist;\nmod preferences;\nmod render;\nmod window;\n\npub(crate) mod consts;\n\npub fn start() {\n\ttracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init();\n\n\tlet cef_context_builder = cef::CefContextBuilder::<CefHandler>::new();\n\n\tif cef_context_builder.is_sub_process() {\n\t\t// We are in a CEF subprocess\n\t\t// This will block until the CEF subprocess quits\n\t\tlet error = cef_context_builder.execute_sub_process();\n\t\ttracing::warn!(\"Cef subprocess failed with error: {error}\");\n\t\treturn;\n\t}\n\n\tlet cli = Cli::parse();\n\n\tlet Ok(lock_file) = std::fs::OpenOptions::new()\n\t\t.read(true)\n\t\t.write(true)\n\t\t.create(true)\n\t\t.truncate(true)\n\t\t.open(dirs::app_data_dir().join(APP_LOCK_FILE_NAME))\n\telse {\n\t\tpanic!(\"Failed to open lock file.\")\n\t};\n\tlet mut lock = fd_lock::RwLock::new(lock_file);\n\tlet lock = match lock.try_write() {\n\t\tOk(mut guard) => {\n\t\t\ttracing::info!(\"Acquired application lock\");\n\t\t\tlet _ = guard.set_len(0);\n\t\t\tlet _ = write!(guard, \"{}\", std::process::id());\n\t\t\tlet _ = guard.sync_all();\n\t\t\tguard\n\t\t}\n\t\tErr(_) => {\n\t\t\ttracing::error!(\"Another instance is already running, Exiting.\");\n\t\t\tstd::process::exit(1);\n\t\t}\n\t};\n\n\tlet prefs = preferences::read();\n\n\t// Must be called before event loop initialization or native window integrations will break\n\tApp::init();\n\n\tlet wgpu_context = futures::executor::block_on(gpu_context::create_wgpu_context());\n\n\tlet event_loop = EventLoop::new().unwrap();\n\tlet (app_event_sender, app_event_receiver) = std::sync::mpsc::channel();\n\tlet app_event_scheduler = event_loop.create_app_event_scheduler(app_event_sender);\n\n\tlet (cef_view_info_sender, cef_view_info_receiver) = std::sync::mpsc::channel();\n\n\tlet disable_ui_acceleration = prefs.disable_ui_acceleration || cli.disable_ui_acceleration;\n\tif disable_ui_acceleration {\n\t\tprintln!(\"UI acceleration is disabled\");\n\t}\n\n\tlet cef_handler = cef::CefHandler::new(wgpu_context.clone(), app_event_scheduler.clone(), cef_view_info_receiver);\n\tlet cef_context = match cef_context_builder.initialize(cef_handler, disable_ui_acceleration) {\n\t\tOk(context) => {\n\t\t\ttracing::info!(\"CEF initialized successfully\");\n\t\t\tcontext\n\t\t}\n\t\tErr(cef::InitError::InitializationFailureCode(code)) => {\n\t\t\tpanic!(\"CEF initialization failed with code: {code}\");\n\t\t}\n\t\tErr(cef::InitError::BrowserCreationFailed) => {\n\t\t\tpanic!(\"Failed to create CEF browser\");\n\t\t}\n\t\tErr(cef::InitError::RequestContextCreationFailed) => {\n\t\t\tpanic!(\"Failed to create CEF request context\");\n\t\t}\n\t};\n\n\tlet app = App::new(Box::new(cef_context), cef_view_info_sender, wgpu_context, app_event_receiver, app_event_scheduler, prefs, cli);\n\n\tlet exit_reason = app.run(event_loop);\n\n\t// If exiting due to a UI acceleration failure, update preferences to disable it for next launch\n\tif matches!(exit_reason, app::ExitReason::UiAccelerationFailure) {\n\t\ttracing::error!(\"Disabling UI acceleration\");\n\t\tpreferences::modify(|prefs| {\n\t\t\tprefs.disable_ui_acceleration = true;\n\t\t});\n\t}\n\n\t// Explicitly drop the instance lock\n\tdrop(lock);\n\n\tmatch exit_reason {\n\t\tapp::ExitReason::Restart | app::ExitReason::UiAccelerationFailure => {\n\t\t\ttracing::info!(\"Restarting application\");\n\t\t\tlet mut command = std::process::Command::new(std::env::current_exe().unwrap());\n\t\t\t#[cfg(target_family = \"unix\")]\n\t\t\tlet _ = std::os::unix::process::CommandExt::exec(&mut command);\n\t\t\t#[cfg(target_family = \"unix\")]\n\t\t\ttracing::error!(\"Failed to restart application\");\n\t\t\t#[cfg(not(target_family = \"unix\"))]\n\t\t\tlet _ = command.spawn();\n\t\t}\n\t\t_ => {}\n\t}\n\n\t// Workaround for a Windows-specific exception that occurs when `app` is dropped.\n\t// The issue causes the window to hang for a few seconds before closing.\n\t// Appears to be related to CEF object destruction order.\n\t// Calling `exit` bypasses rust teardown and lets Windows perform process cleanup.\n\t// TODO: Identify and fix the underlying CEF shutdown issue so this workaround can be removed.\n\t#[cfg(target_os = \"windows\")]\n\tstd::process::exit(0);\n}\n\npub fn start_helper() {\n\tlet cef_context_builder = cef::CefContextBuilder::<CefHandler>::new_helper();\n\tassert!(cef_context_builder.is_sub_process());\n\tcef_context_builder.execute_sub_process();\n}\n"
  },
  {
    "path": "desktop/src/main.rs",
    "content": "fn main() {\n\tgraphite_desktop::start();\n}\n"
  },
  {
    "path": "desktop/src/persist.rs",
    "content": "use crate::wrapper::messages::{Document, DocumentId};\n\n#[derive(Default, serde::Serialize, serde::Deserialize)]\npub(crate) struct PersistentData {\n\tdocuments: DocumentStore,\n\tcurrent_document: Option<DocumentId>,\n\t#[serde(skip)]\n\tdocument_order: Option<Vec<DocumentId>>,\n}\n\nimpl PersistentData {\n\tpub(crate) fn write_document(&mut self, id: DocumentId, document: Document) {\n\t\tself.documents.write(id, document);\n\t\tif let Some(order) = &self.document_order {\n\t\t\tself.documents.force_order(order);\n\t\t}\n\t\tself.flush();\n\t}\n\n\tpub(crate) fn delete_document(&mut self, id: &DocumentId) {\n\t\tif Some(*id) == self.current_document {\n\t\t\tself.current_document = None;\n\t\t}\n\t\tself.documents.delete(id);\n\t\tself.flush();\n\t}\n\n\tpub(crate) fn current_document_id(&self) -> Option<DocumentId> {\n\t\tmatch self.current_document {\n\t\t\tSome(id) => Some(id),\n\t\t\tNone => Some(*self.documents.document_ids().first()?),\n\t\t}\n\t}\n\n\tpub(crate) fn current_document(&self) -> Option<(DocumentId, Document)> {\n\t\tlet current_id = self.current_document_id()?;\n\t\tSome((current_id, self.documents.read(&current_id)?))\n\t}\n\n\tpub(crate) fn documents_before_current(&self) -> Vec<(DocumentId, Document)> {\n\t\tlet Some(current_id) = self.current_document_id() else {\n\t\t\treturn Vec::new();\n\t\t};\n\t\tself.documents\n\t\t\t.document_ids()\n\t\t\t.into_iter()\n\t\t\t.take_while(|id| *id != current_id)\n\t\t\t.filter_map(|id| Some((id, self.documents.read(&id)?)))\n\t\t\t.collect()\n\t}\n\n\tpub(crate) fn documents_after_current(&self) -> Vec<(DocumentId, Document)> {\n\t\tlet Some(current_id) = self.current_document_id() else {\n\t\t\treturn Vec::new();\n\t\t};\n\t\tself.documents\n\t\t\t.document_ids()\n\t\t\t.into_iter()\n\t\t\t.skip_while(|id| *id != current_id)\n\t\t\t.skip(1)\n\t\t\t.filter_map(|id| Some((id, self.documents.read(&id)?)))\n\t\t\t.collect()\n\t}\n\n\tpub(crate) fn set_current_document(&mut self, id: DocumentId) {\n\t\tself.current_document = Some(id);\n\t\tself.flush();\n\t}\n\n\tpub(crate) fn set_document_order(&mut self, order: Vec<DocumentId>) {\n\t\tself.document_order = Some(order);\n\t\tself.flush();\n\t}\n\n\tfn flush(&self) {\n\t\tlet data = match ron::ser::to_string_pretty(self, Default::default()) {\n\t\t\tOk(d) => d,\n\t\t\tErr(e) => {\n\t\t\t\ttracing::error!(\"Failed to serialize persistent data: {e}\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t};\n\t\tif let Err(e) = std::fs::write(Self::state_file_path(), data) {\n\t\t\ttracing::error!(\"Failed to write persistent data to disk: {e}\");\n\t\t}\n\t}\n\n\tpub(crate) fn load_from_disk(&mut self) {\n\t\tlet path = Self::state_file_path();\n\t\tlet data = match std::fs::read_to_string(&path) {\n\t\t\tOk(d) => d,\n\t\t\tErr(e) if e.kind() == std::io::ErrorKind::NotFound => {\n\t\t\t\ttracing::info!(\"No persistent data file found at {path:?}, starting fresh\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tErr(e) => {\n\t\t\t\ttracing::error!(\"Failed to read persistent data from disk: {e}\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t};\n\t\tlet loaded = match ron::from_str(&data) {\n\t\t\tOk(d) => d,\n\t\t\tErr(e) => {\n\t\t\t\ttracing::error!(\"Failed to deserialize persistent data: {e}\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t};\n\t\t*self = loaded;\n\t}\n\n\tfn state_file_path() -> std::path::PathBuf {\n\t\tlet mut path = crate::dirs::app_data_dir();\n\t\tpath.push(crate::consts::APP_STATE_FILE_NAME);\n\t\tpath\n\t}\n}\n\n#[derive(Default, serde::Serialize, serde::Deserialize)]\nstruct DocumentStore(Vec<DocumentInfo>);\nimpl DocumentStore {\n\tfn write(&mut self, id: DocumentId, document: Document) {\n\t\tlet meta = DocumentInfo::new(id, &document);\n\t\tif let Some(existing) = self.0.iter_mut().find(|meta| meta.id == id) {\n\t\t\t*existing = meta;\n\t\t} else {\n\t\t\tself.0.push(meta);\n\t\t}\n\t\tif let Err(e) = std::fs::write(Self::document_path(&id), document.content) {\n\t\t\ttracing::error!(\"Failed to write document {id:?} to disk: {e}\");\n\t\t}\n\t}\n\n\tfn delete(&mut self, id: &DocumentId) {\n\t\tself.0.retain(|meta| meta.id != *id);\n\t\tif let Err(e) = std::fs::remove_file(Self::document_path(id)) {\n\t\t\ttracing::error!(\"Failed to delete document {id:?} from disk: {e}\");\n\t\t}\n\t}\n\n\tfn read(&self, id: &DocumentId) -> Option<Document> {\n\t\tlet meta = self.0.iter().find(|meta| meta.id == *id)?;\n\t\tlet content = std::fs::read_to_string(Self::document_path(id)).ok()?;\n\t\tSome(Document {\n\t\t\tcontent,\n\t\t\tname: meta.name.clone(),\n\t\t\tpath: meta.path.clone(),\n\t\t\tis_saved: meta.is_saved,\n\t\t})\n\t}\n\n\tfn force_order(&mut self, desired_order: &Vec<DocumentId>) {\n\t\tlet mut ordered_prefix_len = 0;\n\t\tfor id in desired_order {\n\t\t\tif let Some(offset) = self.0[ordered_prefix_len..].iter().position(|meta| meta.id == *id) {\n\t\t\t\tlet found_index = ordered_prefix_len + offset;\n\t\t\t\tif found_index != ordered_prefix_len {\n\t\t\t\t\tself.0[ordered_prefix_len..=found_index].rotate_right(1);\n\t\t\t\t}\n\t\t\t\tordered_prefix_len += 1;\n\t\t\t}\n\t\t}\n\t\tself.0.truncate(ordered_prefix_len);\n\t}\n\n\tfn document_ids(&self) -> Vec<DocumentId> {\n\t\tself.0.iter().map(|meta| meta.id).collect()\n\t}\n\n\tfn document_path(id: &DocumentId) -> std::path::PathBuf {\n\t\tlet mut path = crate::dirs::app_autosave_documents_dir();\n\t\tpath.push(format!(\"{:x}.{}\", id.0, graphite_desktop_wrapper::FILE_EXTENSION));\n\t\tpath\n\t}\n}\n\n#[derive(serde::Serialize, serde::Deserialize)]\nstruct DocumentInfo {\n\tid: DocumentId,\n\tname: String,\n\tpath: Option<std::path::PathBuf>,\n\tis_saved: bool,\n}\nimpl DocumentInfo {\n\tfn new(id: DocumentId, Document { name, path, is_saved, .. }: &Document) -> Self {\n\t\tSelf {\n\t\t\tid,\n\t\t\tname: name.clone(),\n\t\t\tpath: path.clone(),\n\t\t\tis_saved: *is_saved,\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "desktop/src/preferences.rs",
    "content": "use graphite_desktop_wrapper::messages::Preferences;\n\npub(crate) fn write(preferences: Preferences) {\n\tlet Ok(preferences) = ron::ser::to_string_pretty(&preferences, Default::default()) else {\n\t\ttracing::error!(\"Failed to serialize preferences\");\n\t\treturn;\n\t};\n\tstd::fs::write(file_path(), &preferences).unwrap_or_else(|e| {\n\t\ttracing::error!(\"Failed to write preferences to disk: {e}\");\n\t});\n}\n\npub(crate) fn read() -> Preferences {\n\tlet Ok(data) = std::fs::read_to_string(file_path()) else {\n\t\treturn Preferences::default();\n\t};\n\tlet Ok(preferences) = ron::from_str(&data) else {\n\t\treturn Preferences::default();\n\t};\n\tpreferences\n}\n\npub(crate) fn modify(f: impl FnOnce(&mut Preferences)) {\n\tlet mut preferences = read();\n\tf(&mut preferences);\n\twrite(preferences);\n}\n\nfn file_path() -> std::path::PathBuf {\n\tlet mut path = crate::dirs::app_data_dir();\n\tpath.push(crate::consts::APP_PREFERENCES_FILE_NAME);\n\tpath\n}\n"
  },
  {
    "path": "desktop/src/render/composite_shader.wgsl",
    "content": "// =============\n// VERTEX SHADER\n// =============\n\nstruct VertexOutput {\n\t@builtin(position) clip_position: vec4<f32>,\n\t@location(0) tex_coords: vec2<f32>,\n}\n\n@vertex\nfn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {\n\tvar out: VertexOutput;\n\tlet pos = array(\n\t\tvec2f(-1.0, -1.0),\n\t\tvec2f(3.0, -1.0),\n\t\tvec2f(-1.0, 3.0),\n\t);\n\tlet xy = pos[vertex_index];\n\tout.clip_position = vec4f(xy, 0.0, 1.0);\n\tlet coords = xy / 2. + 0.5;\n\tout.tex_coords = vec2f(coords.x, 1. - coords.y);\n\treturn out;\n}\n\n// ===============\n// FRAGMENT SHADER\n// ===============\n\nstruct Constants {\n\tviewport_scale: vec2<f32>,\n\tviewport_offset: vec2<f32>,\n\tui_scale: vec2<f32>,\n\tbackground_color: vec4<f32>,\n};\n\nvar<push_constant> constants: Constants;\n\n@group(0) @binding(0)\nvar t_viewport: texture_2d<f32>;\n@group(0) @binding(1)\nvar t_overlays: texture_2d<f32>;\n@group(0) @binding(2)\nvar t_ui: texture_2d<f32>;\n@group(0) @binding(3)\nvar s_diffuse: sampler;\n\n@fragment\nfn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {\n\tlet ui_coordinate = in.tex_coords * constants.ui_scale;\n\tif (ui_coordinate.x < 0.0 || ui_coordinate.x > 1.0 ||\n\t\tui_coordinate.y < 0.0 || ui_coordinate.y > 1.0) {\n\t\treturn srgb_to_linear(constants.background_color);\n\t}\n\n\tlet ui_linear = srgb_to_linear(textureSample(t_ui, s_diffuse, ui_coordinate));\n\tif (ui_linear.a >= 0.999) {\n\t\treturn ui_linear;\n\t}\n\n\t// UI texture is premultiplied, we need to unpremultiply before blending\n\tlet ui_srgb = linear_to_srgb(unpremultiply(ui_linear));\n\n\tlet viewport_coordinate = (in.tex_coords - constants.viewport_offset) * constants.viewport_scale;\n\tif (viewport_coordinate.x < 0.0 || viewport_coordinate.x > 1.0 ||\n\t\tviewport_coordinate.y < 0.0 || viewport_coordinate.y > 1.0) {\n\t\treturn srgb_to_linear(constants.background_color);\n\t}\n\n\tlet overlay_srgb = textureSample(t_overlays, s_diffuse, viewport_coordinate);\n\tvar viewport_srgb = textureSample(t_viewport, s_diffuse, viewport_coordinate);\n\n\tif (viewport_srgb.a < 0.001) {\n\t\tviewport_srgb = constants.background_color;\n\t}\n\n\tif (overlay_srgb.a < 0.001) {\n\t\tif (ui_srgb.a < 0.001) {\n\t\t\treturn srgb_to_linear(viewport_srgb);\n\t\t} else {\n\t\t\treturn srgb_to_linear(blend(ui_srgb, viewport_srgb));\n\t\t}\n\t}\n\n\tlet composite_linear = blend(srgb_to_linear(overlay_srgb), srgb_to_linear(viewport_srgb));\n\n\tif (ui_srgb.a < 0.001) {\n\t\treturn composite_linear;\n\t}\n\n\treturn srgb_to_linear(blend(ui_srgb, linear_to_srgb(composite_linear)));\n}\n\nfn blend(fg: vec4<f32>, bg: vec4<f32>) -> vec4<f32> {\n\tlet a = fg.a + bg.a * (1.0 - fg.a);\n\tlet rgb = fg.rgb * fg.a + bg.rgb * bg.a * (1.0 - fg.a);\n\treturn vec4<f32>(rgb, a);\n}\n\nfn linear_to_srgb(in: vec4<f32>) -> vec4<f32> {\n\tlet cutoff = vec3<f32>(0.0031308);\n\tlet lo = in.rgb * 12.92;\n\tlet hi = 1.055 * pow(max(in.rgb, vec3<f32>(0.0)), vec3<f32>(1.0/2.4)) - 0.055;\n\treturn vec4<f32>(select(lo, hi, in.rgb > cutoff), in.a);\n}\n\nfn srgb_to_linear(in: vec4<f32>) -> vec4<f32> {\n\tlet cutoff = vec3<f32>(0.04045);\n\tlet lo = in.rgb / 12.92;\n\tlet hi = pow((in.rgb + 0.055) / 1.055, vec3<f32>(2.4));\n\treturn vec4<f32>(select(lo, hi, in.rgb > cutoff), in.a);\n}\n\nfn unpremultiply(in: vec4<f32>) -> vec4<f32> {\n\tif (in.a > 0.0) {\n\t\treturn vec4<f32>((in.rgb / in.a), in.a);\n\t} else {\n\t\treturn vec4<f32>(0.0);\n\t}\n}\n"
  },
  {
    "path": "desktop/src/render/frame_buffer_ref.rs",
    "content": "use thiserror::Error;\n\npub(crate) struct FrameBufferRef<'a> {\n\tbuffer: &'a [u8],\n\twidth: usize,\n\theight: usize,\n}\nimpl<'a> FrameBufferRef<'a> {\n\tpub(crate) fn new(buffer: &'a [u8], width: usize, height: usize) -> Result<Self, FrameBufferError> {\n\t\tlet fb = Self { buffer, width, height };\n\t\tfb.validate_size()?;\n\t\tOk(fb)\n\t}\n\tpub(crate) fn buffer(&self) -> &[u8] {\n\t\tself.buffer\n\t}\n\n\tpub(crate) fn width(&self) -> usize {\n\t\tself.width\n\t}\n\n\tpub(crate) fn height(&self) -> usize {\n\t\tself.height\n\t}\n\n\tfn validate_size(&self) -> Result<(), FrameBufferError> {\n\t\tif self.buffer.len() != self.width * self.height * 4 {\n\t\t\tErr(FrameBufferError::InvalidSize {\n\t\t\t\tbuffer_size: self.buffer.len(),\n\t\t\t\texpected_size: self.width * self.height * 4,\n\t\t\t\twidth: self.width,\n\t\t\t\theight: self.height,\n\t\t\t})\n\t\t} else {\n\t\t\tOk(())\n\t\t}\n\t}\n}\nimpl<'a> std::fmt::Debug for FrameBufferRef<'a> {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tf.debug_struct(\"FrameBuffer\")\n\t\t\t.field(\"width\", &self.width)\n\t\t\t.field(\"height\", &self.height)\n\t\t\t.field(\"len\", &self.buffer.len())\n\t\t\t.finish()\n\t}\n}\n\n#[derive(Error, Debug)]\npub(crate) enum FrameBufferError {\n\t#[error(\"Invalid buffer size {buffer_size}, expected {expected_size} for width {width} multiplied with height {height} multiplied by 4 channels\")]\n\tInvalidSize { buffer_size: usize, expected_size: usize, width: usize, height: usize },\n}\n"
  },
  {
    "path": "desktop/src/render/state.rs",
    "content": "use std::borrow::Cow;\nuse wgpu::PresentMode;\n\nuse crate::window::Window;\nuse crate::wrapper::{TargetTexture, WgpuContext, WgpuExecutor};\n\n#[derive(derivative::Derivative)]\n#[derivative(Debug)]\npub(crate) struct RenderState {\n\tsurface: wgpu::Surface<'static>,\n\tcontext: WgpuContext,\n\texecutor: WgpuExecutor,\n\tconfig: wgpu::SurfaceConfiguration,\n\trender_pipeline: wgpu::RenderPipeline,\n\ttransparent_texture: std::sync::Arc<wgpu::Texture>,\n\tsampler: wgpu::Sampler,\n\tdesired_width: u32,\n\tdesired_height: u32,\n\tviewport_scale: [f32; 2],\n\tviewport_offset: [f32; 2],\n\tviewport_texture: Option<std::sync::Arc<wgpu::Texture>>,\n\toverlays_texture: Option<TargetTexture>,\n\tui_texture: Option<wgpu::Texture>,\n\tbind_group: Option<wgpu::BindGroup>,\n\t#[derivative(Debug = \"ignore\")]\n\toverlays_scene: Option<vello::Scene>,\n\tsurface_outdated: bool,\n}\n\nimpl RenderState {\n\tpub(crate) fn new(window: &Window, context: WgpuContext, present_mode: Option<PresentMode>) -> Self {\n\t\tlet size = window.surface_size();\n\t\tlet surface = window.create_surface(context.instance.clone());\n\n\t\tlet surface_caps = surface.get_capabilities(&context.adapter);\n\t\tlet surface_format = surface_caps.formats.iter().find(|f| f.is_srgb()).copied().unwrap_or(surface_caps.formats[0]);\n\n\t\tlet config = wgpu::SurfaceConfiguration {\n\t\t\tusage: wgpu::TextureUsages::RENDER_ATTACHMENT,\n\t\t\tformat: surface_format,\n\t\t\twidth: size.width,\n\t\t\theight: size.height,\n\t\t\tpresent_mode: present_mode.unwrap_or(surface_caps.present_modes[0]),\n\t\t\talpha_mode: surface_caps.alpha_modes[0],\n\t\t\tview_formats: vec![],\n\t\t\tdesired_maximum_frame_latency: 1,\n\t\t};\n\n\t\tsurface.configure(&context.device, &config);\n\n\t\tlet transparent_texture = std::sync::Arc::new(context.device.create_texture(&wgpu::TextureDescriptor {\n\t\t\tlabel: Some(\"Transparent Texture\"),\n\t\t\tsize: wgpu::Extent3d {\n\t\t\t\twidth: 1,\n\t\t\t\theight: 1,\n\t\t\t\tdepth_or_array_layers: 1,\n\t\t\t},\n\t\t\tmip_level_count: 1,\n\t\t\tsample_count: 1,\n\t\t\tdimension: wgpu::TextureDimension::D2,\n\t\t\tformat: wgpu::TextureFormat::Bgra8UnormSrgb,\n\t\t\tusage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,\n\t\t\tview_formats: &[],\n\t\t}));\n\n\t\t// Create shader module\n\t\tlet shader = context.device.create_shader_module(wgpu::include_wgsl!(\"composite_shader.wgsl\"));\n\n\t\t// Create sampler\n\t\tlet sampler = context.device.create_sampler(&wgpu::SamplerDescriptor {\n\t\t\taddress_mode_u: wgpu::AddressMode::ClampToEdge,\n\t\t\taddress_mode_v: wgpu::AddressMode::ClampToEdge,\n\t\t\taddress_mode_w: wgpu::AddressMode::ClampToEdge,\n\t\t\tmag_filter: wgpu::FilterMode::Linear,\n\t\t\tmin_filter: wgpu::FilterMode::Nearest,\n\t\t\tmipmap_filter: wgpu::FilterMode::Nearest,\n\t\t\t..Default::default()\n\t\t});\n\n\t\tlet texture_bind_group_layout = context.device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {\n\t\t\tentries: &[\n\t\t\t\twgpu::BindGroupLayoutEntry {\n\t\t\t\t\tbinding: 0,\n\t\t\t\t\tvisibility: wgpu::ShaderStages::FRAGMENT,\n\t\t\t\t\tty: wgpu::BindingType::Texture {\n\t\t\t\t\t\tmultisampled: false,\n\t\t\t\t\t\tview_dimension: wgpu::TextureViewDimension::D2,\n\t\t\t\t\t\tsample_type: wgpu::TextureSampleType::Float { filterable: true },\n\t\t\t\t\t},\n\t\t\t\t\tcount: None,\n\t\t\t\t},\n\t\t\t\twgpu::BindGroupLayoutEntry {\n\t\t\t\t\tbinding: 1,\n\t\t\t\t\tvisibility: wgpu::ShaderStages::FRAGMENT,\n\t\t\t\t\tty: wgpu::BindingType::Texture {\n\t\t\t\t\t\tmultisampled: false,\n\t\t\t\t\t\tview_dimension: wgpu::TextureViewDimension::D2,\n\t\t\t\t\t\tsample_type: wgpu::TextureSampleType::Float { filterable: true },\n\t\t\t\t\t},\n\t\t\t\t\tcount: None,\n\t\t\t\t},\n\t\t\t\twgpu::BindGroupLayoutEntry {\n\t\t\t\t\tbinding: 2,\n\t\t\t\t\tvisibility: wgpu::ShaderStages::FRAGMENT,\n\t\t\t\t\tty: wgpu::BindingType::Texture {\n\t\t\t\t\t\tmultisampled: false,\n\t\t\t\t\t\tview_dimension: wgpu::TextureViewDimension::D2,\n\t\t\t\t\t\tsample_type: wgpu::TextureSampleType::Float { filterable: true },\n\t\t\t\t\t},\n\t\t\t\t\tcount: None,\n\t\t\t\t},\n\t\t\t\twgpu::BindGroupLayoutEntry {\n\t\t\t\t\tbinding: 3,\n\t\t\t\t\tvisibility: wgpu::ShaderStages::FRAGMENT,\n\t\t\t\t\tty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),\n\t\t\t\t\tcount: None,\n\t\t\t\t},\n\t\t\t],\n\t\t\tlabel: Some(\"texture_bind_group_layout\"),\n\t\t});\n\n\t\tlet render_pipeline_layout = context.device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {\n\t\t\tlabel: Some(\"Render Pipeline Layout\"),\n\t\t\tbind_group_layouts: &[&texture_bind_group_layout],\n\t\t\tpush_constant_ranges: &[wgpu::PushConstantRange {\n\t\t\t\tstages: wgpu::ShaderStages::FRAGMENT,\n\t\t\t\trange: 0..size_of::<Constants>() as u32,\n\t\t\t}],\n\t\t});\n\n\t\tlet render_pipeline = context.device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {\n\t\t\tlabel: Some(\"Render Pipeline\"),\n\t\t\tlayout: Some(&render_pipeline_layout),\n\t\t\tvertex: wgpu::VertexState {\n\t\t\t\tmodule: &shader,\n\t\t\t\tentry_point: Some(\"vs_main\"),\n\t\t\t\tbuffers: &[],\n\t\t\t\tcompilation_options: Default::default(),\n\t\t\t},\n\t\t\tfragment: Some(wgpu::FragmentState {\n\t\t\t\tmodule: &shader,\n\t\t\t\tentry_point: Some(\"fs_main\"),\n\t\t\t\ttargets: &[Some(wgpu::ColorTargetState {\n\t\t\t\t\tformat: config.format,\n\t\t\t\t\tblend: Some(wgpu::BlendState::REPLACE),\n\t\t\t\t\twrite_mask: wgpu::ColorWrites::ALL,\n\t\t\t\t})],\n\t\t\t\tcompilation_options: Default::default(),\n\t\t\t}),\n\t\t\tprimitive: wgpu::PrimitiveState {\n\t\t\t\ttopology: wgpu::PrimitiveTopology::TriangleList,\n\t\t\t\tstrip_index_format: None,\n\t\t\t\tfront_face: wgpu::FrontFace::Ccw,\n\t\t\t\tcull_mode: Some(wgpu::Face::Back),\n\t\t\t\tpolygon_mode: wgpu::PolygonMode::Fill,\n\t\t\t\tunclipped_depth: false,\n\t\t\t\tconservative: false,\n\t\t\t},\n\t\t\tdepth_stencil: None,\n\t\t\tmultisample: wgpu::MultisampleState {\n\t\t\t\tcount: 1,\n\t\t\t\tmask: !0,\n\t\t\t\talpha_to_coverage_enabled: false,\n\t\t\t},\n\t\t\tmultiview: None,\n\t\t\tcache: None,\n\t\t});\n\n\t\tlet wgpu_executor = WgpuExecutor::with_context(context.clone()).expect(\"Failed to create WgpuExecutor\");\n\n\t\tSelf {\n\t\t\tsurface,\n\t\t\tcontext,\n\t\t\texecutor: wgpu_executor,\n\t\t\tconfig,\n\t\t\trender_pipeline,\n\t\t\ttransparent_texture,\n\t\t\tsampler,\n\t\t\tdesired_width: size.width,\n\t\t\tdesired_height: size.height,\n\t\t\tviewport_scale: [1.0, 1.0],\n\t\t\tviewport_offset: [0.0, 0.0],\n\t\t\tviewport_texture: None,\n\t\t\toverlays_texture: None,\n\t\t\tui_texture: None,\n\t\t\tbind_group: None,\n\t\t\toverlays_scene: None,\n\t\t\tsurface_outdated: true,\n\t\t}\n\t}\n\n\tpub(crate) fn resize(&mut self, width: u32, height: u32) {\n\t\tif width == self.desired_width && height == self.desired_height {\n\t\t\treturn;\n\t\t}\n\n\t\tself.desired_width = width;\n\t\tself.desired_height = height;\n\t\tself.surface_outdated = true;\n\n\t\tif width > 0 && height > 0 && (self.config.width != width || self.config.height != height) {\n\t\t\tself.config.width = width;\n\t\t\tself.config.height = height;\n\t\t\tself.surface.configure(&self.context.device, &self.config);\n\t\t}\n\t}\n\n\tpub(crate) fn bind_viewport_texture(&mut self, viewport_texture: std::sync::Arc<wgpu::Texture>) {\n\t\tself.viewport_texture = Some(viewport_texture);\n\t\tself.update_bindgroup();\n\t}\n\n\tpub(crate) fn bind_ui_texture(&mut self, bind_ui_texture: wgpu::Texture) {\n\t\tself.ui_texture = Some(bind_ui_texture);\n\t\tself.update_bindgroup();\n\t}\n\n\tpub(crate) fn set_viewport_scale(&mut self, scale: [f32; 2]) {\n\t\tself.surface_outdated = true;\n\t\tself.viewport_scale = scale;\n\t}\n\n\tpub(crate) fn set_viewport_offset(&mut self, offset: [f32; 2]) {\n\t\tself.surface_outdated = true;\n\t\tself.viewport_offset = offset;\n\t}\n\n\tpub(crate) fn set_overlays_scene(&mut self, scene: vello::Scene) {\n\t\tself.surface_outdated = true;\n\t\tself.overlays_scene = Some(scene);\n\t}\n\n\tfn render_overlays(&mut self, scene: vello::Scene) {\n\t\tlet Some(viewport_texture) = self.viewport_texture.as_ref() else {\n\t\t\ttracing::warn!(\"No viewport texture bound, cannot render overlays\");\n\t\t\treturn;\n\t\t};\n\t\tlet size = glam::UVec2::new(viewport_texture.width(), viewport_texture.height());\n\t\tlet result = futures::executor::block_on(self.executor.render_vello_scene_to_target_texture(&scene, size, &Default::default(), None, &mut self.overlays_texture));\n\t\tif let Err(e) = result {\n\t\t\ttracing::error!(\"Error rendering overlays: {:?}\", e);\n\t\t\treturn;\n\t\t}\n\t\tself.update_bindgroup();\n\t}\n\n\tpub(crate) fn render(&mut self, window: &Window) -> Result<(), RenderError> {\n\t\tif !self.surface_outdated {\n\t\t\treturn Ok(());\n\t\t}\n\t\tlet ui_scale = if let Some(ui_texture) = &self.ui_texture\n\t\t\t&& (self.desired_width != ui_texture.width() || self.desired_height != ui_texture.height())\n\t\t{\n\t\t\tSome([self.desired_width as f32 / ui_texture.width() as f32, self.desired_height as f32 / ui_texture.height() as f32])\n\t\t} else {\n\t\t\tNone\n\t\t};\n\n\t\tif let Some(scene) = self.overlays_scene.take() {\n\t\t\tself.render_overlays(scene);\n\t\t}\n\n\t\tlet output = self.surface.get_current_texture().map_err(RenderError::SurfaceError)?;\n\n\t\tlet view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());\n\n\t\tlet mut encoder = self.context.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some(\"Render Encoder\") });\n\n\t\t{\n\t\t\tlet mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {\n\t\t\t\tlabel: Some(\"Graphite Composition Render Pass\"),\n\t\t\t\tcolor_attachments: &[Some(wgpu::RenderPassColorAttachment {\n\t\t\t\t\tview: &view,\n\t\t\t\t\tresolve_target: None,\n\t\t\t\t\tops: wgpu::Operations {\n\t\t\t\t\t\tload: wgpu::LoadOp::Clear(wgpu::Color { r: 0.01, g: 0.01, b: 0.01, a: 1. }),\n\t\t\t\t\t\tstore: wgpu::StoreOp::Store,\n\t\t\t\t\t},\n\t\t\t\t\tdepth_slice: None,\n\t\t\t\t})],\n\t\t\t\tdepth_stencil_attachment: None,\n\t\t\t\tocclusion_query_set: None,\n\t\t\t\ttimestamp_writes: None,\n\t\t\t});\n\n\t\t\trender_pass.set_pipeline(&self.render_pipeline);\n\t\t\trender_pass.set_push_constants(\n\t\t\t\twgpu::ShaderStages::FRAGMENT,\n\t\t\t\t0,\n\t\t\t\tbytemuck::bytes_of(&Constants {\n\t\t\t\t\tviewport_scale: self.viewport_scale,\n\t\t\t\t\tviewport_offset: self.viewport_offset,\n\t\t\t\t\tui_scale: ui_scale.unwrap_or([1., 1.]),\n\t\t\t\t\t_pad: [0., 0.],\n\t\t\t\t\tbackground_color: [0x22 as f32 / 0xff as f32, 0x22 as f32 / 0xff as f32, 0x22 as f32 / 0xff as f32, 1.], // #222222\n\t\t\t\t}),\n\t\t\t);\n\t\t\tif let Some(bind_group) = &self.bind_group {\n\t\t\t\trender_pass.set_bind_group(0, bind_group, &[]);\n\t\t\t\trender_pass.draw(0..3, 0..1); // Draw 3 vertices for fullscreen triangle\n\t\t\t} else {\n\t\t\t\ttracing::warn!(\"No bind group available - showing clear color only\");\n\t\t\t}\n\t\t}\n\t\tself.context.queue.submit(std::iter::once(encoder.finish()));\n\t\twindow.pre_present_notify();\n\t\toutput.present();\n\n\t\tif ui_scale.is_some() {\n\t\t\treturn Err(RenderError::OutdatedUITextureError);\n\t\t}\n\t\tself.surface_outdated = false;\n\n\t\tOk(())\n\t}\n\n\tfn update_bindgroup(&mut self) {\n\t\tself.surface_outdated = true;\n\t\tlet viewport_texture_view = self.viewport_texture.as_ref().unwrap_or(&self.transparent_texture).create_view(&wgpu::TextureViewDescriptor::default());\n\t\tlet overlays_texture_view = self\n\t\t\t.overlays_texture\n\t\t\t.as_ref()\n\t\t\t.map(|target| Cow::Borrowed(target.view()))\n\t\t\t.unwrap_or_else(|| Cow::Owned(self.transparent_texture.create_view(&wgpu::TextureViewDescriptor::default())));\n\t\tlet ui_texture_view = self.ui_texture.as_ref().unwrap_or(&self.transparent_texture).create_view(&wgpu::TextureViewDescriptor::default());\n\n\t\tlet bind_group = self.context.device.create_bind_group(&wgpu::BindGroupDescriptor {\n\t\t\tlayout: &self.render_pipeline.get_bind_group_layout(0),\n\t\t\tentries: &[\n\t\t\t\twgpu::BindGroupEntry {\n\t\t\t\t\tbinding: 0,\n\t\t\t\t\tresource: wgpu::BindingResource::TextureView(&viewport_texture_view),\n\t\t\t\t},\n\t\t\t\twgpu::BindGroupEntry {\n\t\t\t\t\tbinding: 1,\n\t\t\t\t\tresource: wgpu::BindingResource::TextureView(overlays_texture_view.as_ref()),\n\t\t\t\t},\n\t\t\t\twgpu::BindGroupEntry {\n\t\t\t\t\tbinding: 2,\n\t\t\t\t\tresource: wgpu::BindingResource::TextureView(&ui_texture_view),\n\t\t\t\t},\n\t\t\t\twgpu::BindGroupEntry {\n\t\t\t\t\tbinding: 3,\n\t\t\t\t\tresource: wgpu::BindingResource::Sampler(&self.sampler),\n\t\t\t\t},\n\t\t\t],\n\t\t\tlabel: Some(\"texture_bind_group\"),\n\t\t});\n\n\t\tself.bind_group = Some(bind_group);\n\t}\n}\n\npub(crate) enum RenderError {\n\tOutdatedUITextureError,\n\tSurfaceError(wgpu::SurfaceError),\n}\n\n#[repr(C)]\n#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]\nstruct Constants {\n\tviewport_scale: [f32; 2],\n\tviewport_offset: [f32; 2],\n\tui_scale: [f32; 2],\n\t_pad: [f32; 2],\n\tbackground_color: [f32; 4],\n}\n"
  },
  {
    "path": "desktop/src/render.rs",
    "content": "mod frame_buffer_ref;\npub(crate) use frame_buffer_ref::FrameBufferRef;\n\nmod state;\npub(crate) use state::{RenderError, RenderState};\n"
  },
  {
    "path": "desktop/src/window/linux.rs",
    "content": "use winit::event_loop::ActiveEventLoop;\nuse winit::platform::wayland::ActiveEventLoopExtWayland;\nuse winit::platform::wayland::WindowAttributesWayland;\nuse winit::platform::x11::WindowAttributesX11;\nuse winit::window::{Window, WindowAttributes};\n\nuse crate::consts::{APP_ID, APP_NAME};\nuse crate::event::AppEventScheduler;\n\npub(super) struct NativeWindowImpl {}\n\nimpl super::NativeWindow for NativeWindowImpl {\n\tfn configure(attributes: WindowAttributes, event_loop: &dyn ActiveEventLoop) -> WindowAttributes {\n\t\tif event_loop.is_wayland() {\n\t\t\tlet wayland_attributes = WindowAttributesWayland::default().with_name(APP_ID, \"\").with_prefer_csd(true);\n\t\t\tattributes.with_platform_attributes(Box::new(wayland_attributes))\n\t\t} else {\n\t\t\tlet x11_attributes = WindowAttributesX11::default().with_name(APP_ID, APP_NAME);\n\t\t\tattributes.with_platform_attributes(Box::new(x11_attributes))\n\t\t}\n\t}\n\n\tfn new(_window: &dyn Window, _app_event_scheduler: AppEventScheduler) -> Self {\n\t\tNativeWindowImpl {}\n\t}\n}\n"
  },
  {
    "path": "desktop/src/window/mac/app.rs",
    "content": "use objc2::{ClassType, define_class, msg_send};\nuse objc2_app_kit::{NSApplication, NSEvent, NSEventType, NSResponder};\nuse objc2_foundation::NSObject;\n\ndefine_class!(\n\t#[unsafe(super(NSApplication, NSResponder, NSObject))]\n\t#[name = \"GraphiteApplication\"]\n\tpub(super) struct GraphiteApplication;\n\n\timpl GraphiteApplication {\n\t\t#[unsafe(method(sendEvent:))]\n\t\tfn send_event(&self, event: &NSEvent) {\n\t\t\t// Route keyDown events straight to the key window to skip native menu shortcut handling.\n\t\t\tif event.r#type() == NSEventType::KeyDown && let Some(key_window) = self.keyWindow() {\n\t\t\t\tunsafe { msg_send![&key_window, sendEvent: event] }\n\t\t\t} else {\n\t\t\t\tunsafe { msg_send![super(self), sendEvent: event] }\n\t\t\t}\n\t\t}\n\t}\n);\n\nfn instance() -> objc2::rc::Retained<NSApplication> {\n\tunsafe { msg_send![GraphiteApplication::class(), sharedApplication] }\n}\n\npub(super) fn init() {\n\tlet _ = instance();\n}\n\npub(super) fn hide() {\n\tinstance().hide(None);\n}\n\npub(super) fn hide_others() {\n\tinstance().hideOtherApplications(None);\n}\n\npub(super) fn show_all() {\n\tinstance().unhideAllApplications(None);\n}\n"
  },
  {
    "path": "desktop/src/window/mac/menu.rs",
    "content": "use muda::Menu as MudaMenu;\nuse muda::accelerator::Accelerator;\nuse muda::{CheckMenuItem, IsMenuItem, MenuEvent, MenuItem, MenuItemKind, PredefinedMenuItem, Result, Submenu};\n\nuse crate::event::{AppEvent, AppEventScheduler};\nuse crate::wrapper::messages::MenuItem as WrapperMenuItem;\n\npub(super) struct Menu {\n\tinner: MudaMenu,\n}\n\nimpl Menu {\n\tpub(super) fn new(event_scheduler: AppEventScheduler) -> Self {\n\t\t// TODO: Remove as much app submenu special handling as possible\n\t\tlet app_submenu = Submenu::with_items(\"\", true, &[]).unwrap();\n\n\t\tlet menu = MudaMenu::new();\n\t\tmenu.prepend(&app_submenu).unwrap();\n\n\t\tmenu.init_for_nsapp();\n\n\t\tMenuEvent::set_event_handler(Some(move |event: MenuEvent| {\n\t\t\tlet mtm = objc2::MainThreadMarker::new().expect(\"only ever called from main thread\");\n\t\t\tlet is_shortcut_triggered = objc2_app_kit::NSApplication::sharedApplication(mtm)\n\t\t\t\t.mainMenu()\n\t\t\t\t.map(|m| m.highlightedItem().is_some())\n\t\t\t\t.unwrap_or_default();\n\t\t\tif is_shortcut_triggered {\n\t\t\t\ttracing::error!(\"A keyboard input triggered a menu event. This is most likely a bug. Please report!\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet id = event.id().0.clone();\n\t\t\tevent_scheduler.schedule(AppEvent::MenuEvent { id });\n\t\t}));\n\n\t\tMenu { inner: menu }\n\t}\n\n\tpub(super) fn update(&self, entries: Vec<WrapperMenuItem>) {\n\t\tlet new_entries = menu_items_from_wrapper(entries);\n\t\tlet existing_entries = self.inner.items();\n\n\t\tlet mut new_entries_iter = new_entries.iter();\n\t\tlet mut existing_entries_iter = existing_entries.iter();\n\n\t\tlet incremental_update_ok = std::iter::from_fn(move || match (existing_entries_iter.next(), new_entries_iter.next()) {\n\t\t\t(Some(MenuItemKind::Submenu(old)), Some(MenuItemKind::Submenu(new))) if old.text() == new.text() => {\n\t\t\t\treplace_children(old, new.items());\n\t\t\t\tSome(true)\n\t\t\t}\n\t\t\t(None, None) => None,\n\t\t\t_ => Some(false),\n\t\t})\n\t\t.all(|b| b);\n\n\t\tif !incremental_update_ok {\n\t\t\t// Fallback to full replace\n\t\t\treplace_children(&self.inner, new_entries);\n\t\t}\n\t}\n}\n\nfn menu_items_from_wrapper(entries: Vec<WrapperMenuItem>) -> Vec<MenuItemKind> {\n\tlet mut menu_items: Vec<MenuItemKind> = Vec::new();\n\tfor entry in entries {\n\t\tmatch entry {\n\t\t\tWrapperMenuItem::Action { id, text, enabled, shortcut } => {\n\t\t\t\tlet accelerator = shortcut.map(|s| Accelerator::new(Some(s.modifiers), s.key));\n\t\t\t\tlet item = MenuItem::with_id(id, text, enabled, accelerator);\n\t\t\t\tmenu_items.push(MenuItemKind::MenuItem(item));\n\t\t\t}\n\t\t\tWrapperMenuItem::Checkbox { id, text, enabled, shortcut, checked } => {\n\t\t\t\tlet accelerator = shortcut.map(|s| Accelerator::new(Some(s.modifiers), s.key));\n\t\t\t\tlet check = CheckMenuItem::with_id(id, text, enabled, checked, accelerator);\n\t\t\t\tmenu_items.push(MenuItemKind::Check(check));\n\t\t\t}\n\t\t\tWrapperMenuItem::SubMenu { text: name, items, .. } => {\n\t\t\t\tlet items = menu_items_from_wrapper(items);\n\t\t\t\tlet items = items.iter().map(menu_item_kind_to_dyn).collect::<Vec<&dyn IsMenuItem>>();\n\t\t\t\tlet submenu = Submenu::with_items(name, true, &items).unwrap();\n\t\t\t\tmenu_items.push(MenuItemKind::Submenu(submenu));\n\t\t\t}\n\t\t\tWrapperMenuItem::Separator => {\n\t\t\t\tlet separator = PredefinedMenuItem::separator();\n\t\t\t\tmenu_items.push(MenuItemKind::Predefined(separator));\n\t\t\t}\n\t\t}\n\t}\n\tmenu_items\n}\n\nfn menu_item_kind_to_dyn(item: &MenuItemKind) -> &dyn IsMenuItem {\n\tmatch item {\n\t\tMenuItemKind::MenuItem(i) => i,\n\t\tMenuItemKind::Submenu(i) => i,\n\t\tMenuItemKind::Predefined(i) => i,\n\t\tMenuItemKind::Check(i) => i,\n\t\tMenuItemKind::Icon(i) => i,\n\t}\n}\n\nfn replace_children<'a, T: Into<MenuContainer<'a>>>(menu: T, new_items: Vec<MenuItemKind>) {\n\tlet menu: MenuContainer = menu.into();\n\tlet items = menu.items();\n\tfor item in items.iter() {\n\t\tmenu.remove(menu_item_kind_to_dyn(item)).unwrap();\n\t}\n\tlet items = new_items.iter().map(menu_item_kind_to_dyn).collect::<Vec<&dyn IsMenuItem>>();\n\tmenu.append_items(items.as_ref()).unwrap();\n}\n\nenum MenuContainer<'a> {\n\tMenu(&'a MudaMenu),\n\tSubmenu(&'a Submenu),\n}\nimpl<'a> MenuContainer<'a> {\n\tfn items(&self) -> Vec<MenuItemKind> {\n\t\tmatch self {\n\t\t\tMenuContainer::Menu(menu) => menu.items(),\n\t\t\tMenuContainer::Submenu(submenu) => submenu.items(),\n\t\t}\n\t}\n\tfn remove(&self, item: &dyn IsMenuItem) -> Result<()> {\n\t\tmatch self {\n\t\t\tMenuContainer::Menu(menu) => menu.remove(item),\n\t\t\tMenuContainer::Submenu(submenu) => submenu.remove(item),\n\t\t}\n\t}\n\tfn append_items(&self, items: &[&dyn IsMenuItem]) -> Result<()> {\n\t\tmatch self {\n\t\t\tMenuContainer::Menu(menu) => menu.append_items(items),\n\t\t\tMenuContainer::Submenu(submenu) => submenu.append_items(items),\n\t\t}\n\t}\n}\nimpl<'a> From<&'a MudaMenu> for MenuContainer<'a> {\n\tfn from(menu: &'a MudaMenu) -> Self {\n\t\tMenuContainer::Menu(menu)\n\t}\n}\nimpl<'a> From<&'a Submenu> for MenuContainer<'a> {\n\tfn from(submenu: &'a Submenu) -> Self {\n\t\tMenuContainer::Submenu(submenu)\n\t}\n}\n"
  },
  {
    "path": "desktop/src/window/mac.rs",
    "content": "use winit::event_loop::ActiveEventLoop;\nuse winit::platform::macos::WindowAttributesMacOS;\nuse winit::window::{Window, WindowAttributes};\n\nuse crate::event::AppEventScheduler;\nuse crate::wrapper::messages::MenuItem;\n\nmod app;\nmod menu;\n\npub(super) struct NativeWindowImpl {\n\tmenu: menu::Menu,\n}\n\nimpl super::NativeWindow for NativeWindowImpl {\n\tfn init() {\n\t\tapp::init();\n\t}\n\n\tfn configure(attributes: WindowAttributes, _event_loop: &dyn ActiveEventLoop) -> WindowAttributes {\n\t\tlet mac_window = WindowAttributesMacOS::default()\n\t\t\t.with_titlebar_transparent(true)\n\t\t\t.with_fullsize_content_view(true)\n\t\t\t.with_title_hidden(true);\n\t\tattributes.with_platform_attributes(Box::new(mac_window))\n\t}\n\n\tfn new(_window: &dyn Window, app_event_scheduler: AppEventScheduler) -> Self {\n\t\tlet menu = menu::Menu::new(app_event_scheduler);\n\n\t\tNativeWindowImpl { menu }\n\t}\n\n\tfn update_menu(&self, entries: Vec<MenuItem>) {\n\t\tself.menu.update(entries);\n\t}\n\n\tfn hide(&self) {\n\t\tapp::hide();\n\t}\n\n\tfn hide_others(&self) {\n\t\tapp::hide_others();\n\t}\n\n\tfn show_all(&self) {\n\t\tapp::show_all();\n\t}\n}\n"
  },
  {
    "path": "desktop/src/window/win/native_handle.rs",
    "content": "//! Implements a Windows-specific custom window frame (no titlebar, but native boarder, shadows and resize).\n//! Look and feel should be similar to a standard window.\n//!\n//! Implementation notes:\n//! - Windows that don't use standard decorations don't get native resize handles or shadows by default.\n//! - We implement resize handles (outside the main window) by creating an invisible \"helper\" window that\n//!   is a little larger than the main window and positioned on top of it. The helper window does hit-testing\n//!   and triggers native resize operations on the main window when the user clicks and drags a resize area.\n//! - The helper window is a invisible window that never activates, so it doesn't steal focus from the main window.\n//! - The main window needs to update the helper window's position and size whenever it moves or resizes.\n\nuse std::sync::{Arc, Mutex, OnceLock};\nuse std::time::Instant;\nuse wgpu::rwh::{HasWindowHandle, RawWindowHandle};\nuse windows::Win32::Foundation::*;\nuse windows::Win32::Graphics::Dwm::*;\nuse windows::Win32::Graphics::Gdi::*;\nuse windows::Win32::System::LibraryLoader::GetModuleHandleW;\nuse windows::Win32::UI::Controls::MARGINS;\nuse windows::Win32::UI::HiDpi::*;\nuse windows::Win32::UI::WindowsAndMessaging::*;\nuse windows::core::PCWSTR;\nuse winit::window::Window;\n\n#[derive(Default)]\nstruct NativeWindowState {\n\tcan_render: bool,\n\tcan_render_since: Option<Instant>,\n}\n\n#[derive(Clone)]\npub(super) struct NativeWindowHandle {\n\tmain: HWND,\n\thelper: HWND,\n\tprev_window_message_handler: isize,\n\tstate: Arc<Mutex<NativeWindowState>>,\n}\nimpl NativeWindowHandle {\n\tpub(super) fn new(window: &dyn Window) -> NativeWindowHandle {\n\t\t// Extract Win32 HWND from winit.\n\t\tlet main = match window.window_handle().expect(\"No window handle\").as_raw() {\n\t\t\tRawWindowHandle::Win32(h) => HWND(h.hwnd.get() as *mut std::ffi::c_void),\n\t\t\t_ => panic!(\"Not a Win32 window\"),\n\t\t};\n\n\t\t// Register the invisible helper (resize ring) window class.\n\t\tunsafe { ensure_helper_class() };\n\n\t\t// Create the helper as a popup tool window that never activates.\n\t\t// WS_EX_NOACTIVATE keeps focus on the main window; WS_EX_TOOLWINDOW hides it from Alt+Tab.\n\t\t// https://learn.microsoft.com/windows/win32/winmsg/extended-window-styles\n\t\tlet ex = WS_EX_NOACTIVATE | WS_EX_TOOLWINDOW;\n\t\tlet style = WS_POPUP;\n\t\tlet helper = unsafe {\n\t\t\tCreateWindowExW(\n\t\t\t\tex,\n\t\t\t\tPCWSTR(HELPER_CLASS_NAME.encode_utf16().collect::<Vec<_>>().as_ptr()),\n\t\t\t\tPCWSTR::null(),\n\t\t\t\tstyle,\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t\tmain,\n\t\t\t\tNone,\n\t\t\t\tHINSTANCE(std::ptr::null_mut()),\n\t\t\t\t// Pass the main window's HWND to WM_NCCREATE so the helper can store it.\n\t\t\t\tSome(&main as *const _ as _),\n\t\t\t)\n\t\t}\n\t\t.expect(\"CreateWindowExW failed\");\n\n\t\t// Subclass the main window.\n\t\t// https://learn.microsoft.com/windows/win32/api/winuser/nf-winuser-setwindowlongptra\n\t\tlet prev_window_message_handler = unsafe { SetWindowLongPtrW(main, GWLP_WNDPROC, main_window_handle_message as isize) };\n\t\tif prev_window_message_handler == 0 {\n\t\t\tlet _ = unsafe { DestroyWindow(helper) };\n\t\t\tpanic!(\"SetWindowLongPtrW failed\");\n\t\t}\n\n\t\tlet native_handle = NativeWindowHandle {\n\t\t\tmain,\n\t\t\thelper,\n\t\t\tprev_window_message_handler,\n\t\t\tstate: Arc::new(Mutex::new(NativeWindowState::default())),\n\t\t};\n\t\tregistry::insert(&native_handle);\n\n\t\t// Place the helper over the main window and show it without activation.\n\t\tunsafe { position_helper(main, helper) };\n\t\tlet _ = unsafe { ShowWindow(helper, SW_SHOWNOACTIVATE) };\n\n\t\t// DwmExtendFrameIntoClientArea is needed to keep native window frame (but no titlebar).\n\t\t// https://learn.microsoft.com/windows/win32/api/dwmapi/nf-dwmapi-dwmextendframeintoclientarea\n\t\t// https://learn.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute\n\t\tlet mut boarder_size: u32 = 1;\n\t\tlet _ = unsafe { DwmGetWindowAttribute(main, DWMWA_VISIBLE_FRAME_BORDER_THICKNESS, &mut boarder_size as *mut _ as *mut _, size_of::<u32>() as u32) };\n\t\tlet margins = MARGINS {\n\t\t\tcxLeftWidth: 0,\n\t\t\tcxRightWidth: 0,\n\t\t\tcyBottomHeight: 0,\n\t\t\tcyTopHeight: boarder_size as i32,\n\t\t};\n\t\tlet _ = unsafe { DwmExtendFrameIntoClientArea(main, &margins) };\n\n\t\tlet hinst = unsafe { GetModuleHandleW(None) }.unwrap();\n\n\t\t// Set taskbar icon\n\t\tif let Ok(big) = unsafe { LoadImageW(hinst, PCWSTR(1usize as *const u16), IMAGE_ICON, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), LR_SHARED) } {\n\t\t\tunsafe { SetClassLongPtrW(main, GCLP_HICON, big.0 as isize) };\n\t\t\tunsafe { SendMessageW(main, WM_SETICON, WPARAM(ICON_BIG as usize), LPARAM(big.0 as isize)) };\n\t\t}\n\n\t\t// Set window icon\n\t\tif let Ok(small) = unsafe { LoadImageW(hinst, PCWSTR(1usize as *const u16), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), LR_SHARED) } {\n\t\t\tunsafe { SetClassLongPtrW(main, GCLP_HICONSM, small.0 as isize) };\n\t\t\tunsafe { SendMessageW(main, WM_SETICON, WPARAM(ICON_SMALL as usize), LPARAM(small.0 as isize)) };\n\t\t}\n\n\t\t// Force window update\n\t\tlet _ = unsafe { SetWindowPos(main, None, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE) };\n\n\t\tnative_handle\n\t}\n\n\tpub(super) fn destroy(&self) {\n\t\tregistry::remove_by_main(self.main);\n\n\t\t// Undo subclassing and destroy the helper window.\n\t\tlet _ = unsafe { SetWindowLongPtrW(self.main, GWLP_WNDPROC, self.prev_window_message_handler) };\n\t\tif !self.helper.is_invalid() {\n\t\t\tlet _ = unsafe { DestroyWindow(self.helper) };\n\t\t}\n\t}\n\n\t// Rendering should be disabled when window is minimized\n\t// Rendering also needs to be disabled during minimize and restore animations\n\t// Reenabling rendering is done after a small delay to account for restore animation\n\t// TODO: Find a cleaner solution that doesn't depend on a timeout\n\tpub(super) fn can_render(&self) -> bool {\n\t\tlet can_render = !unsafe { IsIconic(self.main).into() } && unsafe { IsWindowVisible(self.main).into() };\n\t\tlet Ok(mut state) = self.state.lock() else {\n\t\t\ttracing::error!(\"Failed to lock NativeWindowState\");\n\t\t\treturn true;\n\t\t};\n\t\tmatch (can_render, state.can_render, state.can_render_since) {\n\t\t\t(true, false, None) => {\n\t\t\t\tstate.can_render_since = Some(Instant::now());\n\t\t\t}\n\t\t\t(true, false, Some(can_render_since)) if can_render_since.elapsed().as_millis() > 50 => {\n\t\t\t\tstate.can_render = true;\n\t\t\t\tstate.can_render_since = None;\n\t\t\t}\n\t\t\t(false, true, _) => {\n\t\t\t\tstate.can_render = false;\n\t\t\t}\n\t\t\t_ => {}\n\t\t}\n\t\tstate.can_render\n\t}\n}\n\nmod registry {\n\tuse std::cell::RefCell;\n\tuse windows::Win32::Foundation::HWND;\n\n\tuse super::NativeWindowHandle;\n\n\tthread_local! {\n\t\tstatic STORE: RefCell<Vec<NativeWindowHandle>> = RefCell::new(Vec::new());\n\t}\n\n\tpub(super) fn find_by_main(main: HWND) -> Option<NativeWindowHandle> {\n\t\tSTORE.with_borrow(|vec| vec.iter().find(|h| h.main == main).cloned())\n\t}\n\tpub(super) fn remove_by_main(main: HWND) {\n\t\tSTORE.with_borrow_mut(|vec| {\n\t\t\tvec.retain(|h| h.main != main);\n\t\t});\n\t}\n\tpub(super) fn insert(handle: &NativeWindowHandle) {\n\t\tSTORE.with_borrow_mut(|vec| {\n\t\t\tvec.push(handle.clone());\n\t\t});\n\t}\n}\n\nconst HELPER_CLASS_NAME: &str = \"Helper\\0\";\n\nstatic HELPER_CLASS_LOCK: OnceLock<u16> = OnceLock::new();\nunsafe fn ensure_helper_class() {\n\t// Register a window class for the invisible resize helper.\n\tlet _ = *HELPER_CLASS_LOCK.get_or_init(|| {\n\t\tlet class_name: Vec<u16> = HELPER_CLASS_NAME.encode_utf16().collect();\n\t\tlet wc = WNDCLASSW {\n\t\t\tstyle: CS_HREDRAW | CS_VREDRAW,\n\t\t\tlpfnWndProc: Some(helper_window_handle_message),\n\t\t\thInstance: unsafe { GetModuleHandleW(None).unwrap().into() },\n\t\t\thIcon: HICON::default(),\n\t\t\thCursor: unsafe { LoadCursorW(HINSTANCE(std::ptr::null_mut()), IDC_ARROW).unwrap() },\n\t\t\t// No painting; the ring is invisible.\n\t\t\thbrBackground: HBRUSH::default(),\n\t\t\tlpszClassName: PCWSTR(class_name.as_ptr()),\n\t\t\t..Default::default()\n\t\t};\n\t\tunsafe { RegisterClassW(&wc) }\n\t});\n}\n\n// Main window message handler, called on the UI thread for every message the main window receives.\nunsafe extern \"system\" fn main_window_handle_message(hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {\n\tif msg == WM_NCCALCSIZE && wparam.0 != 0 {\n\t\tlet params = unsafe { &mut *(lparam.0 as *mut NCCALCSIZE_PARAMS) };\n\n\t\t// When maximized, shrink to visible frame so content doesn't extend beyond it.\n\t\tif unsafe { IsZoomed(hwnd).as_bool() } && !is_effectively_fullscreen(params.rgrc[0]) {\n\t\t\tlet dpi = unsafe { GetDpiForWindow(hwnd) };\n\t\t\tlet size = unsafe { GetSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) };\n\t\t\tlet pad = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };\n\t\t\tlet inset = (size + pad) as i32;\n\n\t\t\tparams.rgrc[0].left += inset;\n\t\t\tparams.rgrc[0].top += inset;\n\t\t\tparams.rgrc[0].right -= inset;\n\t\t\tparams.rgrc[0].bottom -= inset;\n\t\t}\n\n\t\t// Return 0 to to tell Windows to skip the default non-client area calculation and drawing.\n\t\treturn LRESULT(0);\n\t}\n\n\tlet Some(handle) = registry::find_by_main(hwnd) else {\n\t\treturn unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) };\n\t};\n\n\tmatch msg {\n\t\t// Keep the invisible resize helper in sync with moves/resizes/visibility.\n\t\tWM_MOVE | WM_MOVING | WM_SIZE | WM_SIZING | WM_WINDOWPOSCHANGED | WM_SHOWWINDOW => {\n\t\t\tif msg == WM_SHOWWINDOW {\n\t\t\t\tif wparam.0 == 0 {\n\t\t\t\t\tlet _ = unsafe { ShowWindow(handle.helper, SW_HIDE) };\n\t\t\t\t} else {\n\t\t\t\t\tlet _ = unsafe { ShowWindow(handle.helper, SW_SHOWNOACTIVATE) };\n\t\t\t\t}\n\t\t\t}\n\t\t\tunsafe { position_helper(hwnd, handle.helper) };\n\t\t}\n\n\t\t// If the main window is destroyed, destroy the helper too.\n\t\t// Should only be needed if windows forcefully destroys the main window.\n\t\tWM_DESTROY => {\n\t\t\tlet _ = unsafe { DestroyWindow(handle.helper) };\n\t\t}\n\n\t\t_ => {}\n\t}\n\n\t// Ensure the previous window message handler is not null.\n\tassert_ne!(handle.prev_window_message_handler, 0);\n\n\t// Call the previous window message handler, this is a standard subclassing pattern.\n\tlet prev_window_message_handler_fn_ptr: *const () = std::ptr::without_provenance(handle.prev_window_message_handler as usize);\n\tlet prev_window_message_handler_fn = unsafe { std::mem::transmute::<_, _>(prev_window_message_handler_fn_ptr) };\n\tunsafe { CallWindowProcW(Some(prev_window_message_handler_fn), hwnd, msg, wparam, lparam) }\n}\n\n// Helper window message handler, called on the UI thread for every message the helper window receives.\nunsafe extern \"system\" fn helper_window_handle_message(hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {\n\tmatch msg {\n\t\t// Helper window creation, should be the first message that the helper window receives.\n\t\tWM_NCCREATE => {\n\t\t\t// Main window HWND is provided when creating the helper window with `CreateWindowExW`\n\t\t\t// Save main window HWND in GWLP_USERDATA so we can extract it later\n\t\t\tlet crate_struct = lparam.0 as *const CREATESTRUCTW;\n\t\t\tlet create_param = unsafe { (*crate_struct).lpCreateParams as *const HWND };\n\t\t\tunsafe { SetWindowLongPtrW(hwnd, GWLP_USERDATA, (*create_param).0 as isize) };\n\t\t\treturn LRESULT(1);\n\t\t}\n\n\t\t// Invisible; no background erase.\n\t\tWM_ERASEBKGND => return LRESULT(1),\n\n\t\t// Tell windows what resize areas we are hitting, this is used to decide what cursor to show.\n\t\tWM_NCHITTEST => {\n\t\t\tlet ht = unsafe { calculate_hit(hwnd, lparam) };\n\t\t\treturn LRESULT(ht as isize);\n\t\t}\n\n\t\t// This starts the system's resize loop for the main window if a resize area is hit.\n\t\t// Helper window button down translates to SC_SIZE | WMSZ_* on the main window.\n\t\tWM_NCLBUTTONDOWN | WM_NCRBUTTONDOWN | WM_NCMBUTTONDOWN => {\n\t\t\t// Extract the main window's HWND from GWLP_USERDATA that we saved earlier.\n\t\t\tlet main_ptr = unsafe { GetWindowLongPtrW(hwnd, GWLP_USERDATA) } as *mut std::ffi::c_void;\n\t\t\tlet main = HWND(main_ptr);\n\t\t\tif unsafe { IsWindow(main).as_bool() } {\n\t\t\t\tlet Some(wmsz) = (unsafe { calculate_resize_direction(hwnd, lparam) }) else {\n\t\t\t\t\treturn LRESULT(0);\n\t\t\t\t};\n\n\t\t\t\t// Ensure that the main window can receive WM_SYSCOMMAND.\n\t\t\t\tlet _ = unsafe { SetForegroundWindow(main) };\n\n\t\t\t\t// Start sizing on the main window in the calculated direction. (SC_SIZE + WMSZ_*)\n\t\t\t\tlet _ = unsafe { PostMessageW(main, WM_SYSCOMMAND, WPARAM((SC_SIZE + wmsz) as usize), lparam) };\n\t\t\t}\n\t\t\treturn LRESULT(0);\n\t\t}\n\n\t\t// Never activate the helper window, allows all inputs that don't hit the resize areas to pass through.\n\t\tWM_MOUSEACTIVATE => return LRESULT(MA_NOACTIVATE as isize),\n\t\t_ => {}\n\t}\n\tunsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }\n}\n\nconst RESIZE_BAND_THICKNESS: i32 = 8;\n\n// Position the helper window to match the main window's location and size (plus the resize band size).\nunsafe fn position_helper(main: HWND, helper: HWND) {\n\tlet mut r = RECT::default();\n\tlet _ = unsafe { GetWindowRect(main, &mut r) };\n\n\tlet x = r.left - RESIZE_BAND_THICKNESS;\n\tlet y = r.top - RESIZE_BAND_THICKNESS;\n\tlet w = (r.right - r.left) + RESIZE_BAND_THICKNESS * 2;\n\tlet h = (r.bottom - r.top) + RESIZE_BAND_THICKNESS * 2;\n\n\tlet _ = unsafe { SetWindowPos(helper, main, x, y, w, h, SWP_NOACTIVATE | SWP_NOSENDCHANGING) };\n}\n\nunsafe fn calculate_hit(helper: HWND, lparam: LPARAM) -> u32 {\n\tlet x = (lparam.0 & 0xFFFF) as i16 as i32;\n\tlet y = ((lparam.0 >> 16) & 0xFFFF) as i16 as i32;\n\n\tlet mut r = RECT::default();\n\tlet _ = unsafe { GetWindowRect(helper, &mut r) };\n\n\tlet on_top = y < (r.top + RESIZE_BAND_THICKNESS) as i32;\n\tlet on_right = x >= (r.right - RESIZE_BAND_THICKNESS) as i32;\n\tlet on_bottom = y >= (r.bottom - RESIZE_BAND_THICKNESS) as i32;\n\tlet on_left = x < (r.left + RESIZE_BAND_THICKNESS) as i32;\n\n\tmatch (on_top, on_right, on_bottom, on_left) {\n\t\t(true, _, _, true) => HTTOPLEFT,\n\t\t(true, true, _, _) => HTTOPRIGHT,\n\t\t(_, true, true, _) => HTBOTTOMRIGHT,\n\t\t(_, _, true, true) => HTBOTTOMLEFT,\n\t\t(true, _, _, _) => HTTOP,\n\t\t(_, true, _, _) => HTRIGHT,\n\t\t(_, _, true, _) => HTBOTTOM,\n\t\t(_, _, _, true) => HTLEFT,\n\t\t_ => HTTRANSPARENT as u32,\n\t}\n}\n\nunsafe fn calculate_resize_direction(helper: HWND, lparam: LPARAM) -> Option<u32> {\n\tmatch unsafe { calculate_hit(helper, lparam) } {\n\t\tHTLEFT => Some(WMSZ_LEFT),\n\t\tHTRIGHT => Some(WMSZ_RIGHT),\n\t\tHTTOP => Some(WMSZ_TOP),\n\t\tHTBOTTOM => Some(WMSZ_BOTTOM),\n\t\tHTTOPLEFT => Some(WMSZ_TOPLEFT),\n\t\tHTTOPRIGHT => Some(WMSZ_TOPRIGHT),\n\t\tHTBOTTOMLEFT => Some(WMSZ_BOTTOMLEFT),\n\t\tHTBOTTOMRIGHT => Some(WMSZ_BOTTOMRIGHT),\n\t\t_ => None,\n\t}\n}\n\n// Check if the rect is effectively fullscreen, meaning it would cover the entire monitor.\n// We need to use this heuristic because Windows doesn't provide a way to check for fullscreen state.\nfn is_effectively_fullscreen(rect: RECT) -> bool {\n\tlet hmon = unsafe { MonitorFromRect(&rect, MONITOR_DEFAULTTONEAREST) };\n\tif hmon.is_invalid() {\n\t\treturn false;\n\t}\n\n\tlet mut monitor_info = MONITORINFO {\n\t\tcbSize: std::mem::size_of::<MONITORINFO>() as u32,\n\t\t..Default::default()\n\t};\n\tif !unsafe { GetMonitorInfoW(hmon, &mut monitor_info) }.as_bool() {\n\t\treturn false;\n\t}\n\n\t// Allow a tiny tolerance for DPI / rounding issues\n\tconst EPS: i32 = 1;\n\t(rect.left - monitor_info.rcMonitor.left).abs() <= EPS\n\t\t&& (rect.top - monitor_info.rcMonitor.top).abs() <= EPS\n\t\t&& (rect.right - monitor_info.rcMonitor.right).abs() <= EPS\n\t\t&& (rect.bottom - monitor_info.rcMonitor.bottom).abs() <= EPS\n}\n"
  },
  {
    "path": "desktop/src/window/win.rs",
    "content": "use windows::Win32::System::Com::{COINIT_APARTMENTTHREADED, CoInitializeEx};\nuse windows::Win32::System::Console::{ATTACH_PARENT_PROCESS, AttachConsole};\nuse windows::Win32::UI::Shell::SetCurrentProcessExplicitAppUserModelID;\nuse windows::core::HSTRING;\nuse winit::event_loop::ActiveEventLoop;\nuse winit::window::{Window, WindowAttributes};\n\nuse crate::consts::APP_ID;\nuse crate::event::AppEventScheduler;\n\npub(super) struct NativeWindowImpl {\n\tnative_handle: native_handle::NativeWindowHandle,\n}\n\nimpl super::NativeWindow for NativeWindowImpl {\n\tfn init() {\n\t\t// Attach to parent console if launched from a terminal (no-op otherwise)\n\t\tunsafe {\n\t\t\tlet _ = AttachConsole(ATTACH_PARENT_PROCESS);\n\t\t}\n\n\t\t// Set stable app ID\n\t\tlet app_id = HSTRING::from(APP_ID);\n\t\tunsafe {\n\t\t\tlet _ = CoInitializeEx(None, COINIT_APARTMENTTHREADED).ok();\n\t\t\tSetCurrentProcessExplicitAppUserModelID(&app_id).ok();\n\t\t}\n\t}\n\n\tfn configure(attributes: WindowAttributes, _event_loop: &dyn ActiveEventLoop) -> WindowAttributes {\n\t\tattributes\n\t}\n\n\tfn new(window: &dyn Window, _app_event_scheduler: AppEventScheduler) -> Self {\n\t\tlet native_handle = native_handle::NativeWindowHandle::new(window);\n\t\tNativeWindowImpl { native_handle }\n\t}\n\n\tfn can_render(&self) -> bool {\n\t\tself.native_handle.can_render()\n\t}\n}\n\nimpl Drop for NativeWindowImpl {\n\tfn drop(&mut self) {\n\t\tself.native_handle.destroy();\n\t}\n}\n\nmod native_handle;\n"
  },
  {
    "path": "desktop/src/window.rs",
    "content": "use crate::consts::APP_NAME;\nuse crate::event::AppEventScheduler;\nuse crate::wrapper::messages::MenuItem;\nuse std::collections::HashMap;\nuse std::sync::Arc;\nuse winit::cursor::{CursorIcon, CustomCursor, CustomCursorSource};\nuse winit::event_loop::ActiveEventLoop;\nuse winit::monitor::Fullscreen;\nuse winit::window::{Window as WinitWindow, WindowAttributes};\n\npub(crate) trait NativeWindow {\n\tfn init() {}\n\tfn configure(attributes: WindowAttributes, event_loop: &dyn ActiveEventLoop) -> WindowAttributes;\n\tfn new(window: &dyn WinitWindow, app_event_scheduler: AppEventScheduler) -> Self;\n\tfn can_render(&self) -> bool {\n\t\ttrue\n\t}\n\tfn update_menu(&self, _entries: Vec<MenuItem>) {}\n\tfn hide(&self) {}\n\tfn hide_others(&self) {}\n\tfn show_all(&self) {}\n}\n\n#[cfg(target_os = \"linux\")]\nmod linux;\n#[cfg(target_os = \"linux\")]\nuse linux as native;\n\n#[cfg(target_os = \"macos\")]\nmod mac;\n#[cfg(target_os = \"macos\")]\nuse mac as native;\n\n#[cfg(target_os = \"windows\")]\nmod win;\n#[cfg(target_os = \"windows\")]\nuse win as native;\n\npub(crate) struct Window {\n\twinit_window: Arc<dyn winit::window::Window>,\n\t#[allow(dead_code)]\n\tnative_handle: native::NativeWindowImpl,\n\tcustom_cursors: HashMap<CustomCursorSource, CustomCursor>,\n\tclipboard: Option<window_clipboard::Clipboard>,\n}\nimpl Drop for Window {\n\tfn drop(&mut self) {\n\t\t// Clipboard must be dropped before `winit_window`\n\t\tdrop(self.clipboard.take());\n\t}\n}\n\nimpl Window {\n\tpub(crate) fn init() {\n\t\tnative::NativeWindowImpl::init();\n\t}\n\n\tpub(crate) fn new(event_loop: &dyn ActiveEventLoop, app_event_scheduler: AppEventScheduler) -> Self {\n\t\tlet mut attributes = WindowAttributes::default()\n\t\t\t.with_title(APP_NAME)\n\t\t\t.with_min_surface_size(winit::dpi::LogicalSize::new(400, 300))\n\t\t\t.with_surface_size(winit::dpi::LogicalSize::new(1200, 800))\n\t\t\t.with_resizable(true)\n\t\t\t.with_visible(false)\n\t\t\t.with_theme(Some(winit::window::Theme::Dark));\n\n\t\tattributes = native::NativeWindowImpl::configure(attributes, event_loop);\n\n\t\tlet winit_window = event_loop.create_window(attributes).unwrap();\n\t\tlet native_handle = native::NativeWindowImpl::new(winit_window.as_ref(), app_event_scheduler);\n\t\tlet clipboard = unsafe { window_clipboard::Clipboard::connect(&winit_window) }.ok();\n\t\tSelf {\n\t\t\twinit_window: winit_window.into(),\n\t\t\tnative_handle,\n\t\t\tcustom_cursors: HashMap::new(),\n\t\t\tclipboard,\n\t\t}\n\t}\n\n\tpub(crate) fn show(&self) {\n\t\tself.winit_window.set_visible(true);\n\t\tself.winit_window.focus_window();\n\t}\n\n\tpub(crate) fn request_redraw(&self) {\n\t\tself.winit_window.request_redraw();\n\t}\n\n\tpub(crate) fn create_surface(&self, instance: Arc<wgpu::Instance>) -> wgpu::Surface<'static> {\n\t\tinstance.create_surface(self.winit_window.clone()).unwrap()\n\t}\n\n\tpub(crate) fn pre_present_notify(&self) {\n\t\tself.winit_window.pre_present_notify();\n\t}\n\n\tpub(crate) fn can_render(&self) -> bool {\n\t\tself.native_handle.can_render()\n\t}\n\n\tpub(crate) fn surface_size(&self) -> winit::dpi::PhysicalSize<u32> {\n\t\tself.winit_window.surface_size()\n\t}\n\n\tpub(crate) fn scale_factor(&self) -> f64 {\n\t\tself.winit_window.scale_factor()\n\t}\n\n\tpub(crate) fn minimize(&self) {\n\t\tself.winit_window.set_minimized(true);\n\t}\n\n\tpub(crate) fn toggle_maximize(&self) {\n\t\tif self.is_fullscreen() {\n\t\t\treturn;\n\t\t}\n\t\tself.winit_window.set_maximized(!self.winit_window.is_maximized());\n\t}\n\n\tpub(crate) fn is_maximized(&self) -> bool {\n\t\tself.winit_window.is_maximized()\n\t}\n\n\tpub(crate) fn toggle_fullscreen(&mut self) {\n\t\tif self.is_fullscreen() {\n\t\t\tself.winit_window.set_fullscreen(None);\n\t\t} else {\n\t\t\tself.winit_window.set_fullscreen(Some(Fullscreen::Borderless(None)));\n\t\t}\n\t}\n\n\tpub(crate) fn is_fullscreen(&self) -> bool {\n\t\tself.winit_window.fullscreen().is_some()\n\t}\n\n\tpub(crate) fn start_drag(&self) {\n\t\tif self.is_fullscreen() {\n\t\t\treturn;\n\t\t}\n\t\tlet _ = self.winit_window.drag_window();\n\t}\n\n\tpub(crate) fn hide(&self) {\n\t\tself.native_handle.hide();\n\t}\n\n\tpub(crate) fn hide_others(&self) {\n\t\tself.native_handle.hide_others();\n\t}\n\n\tpub(crate) fn show_all(&self) {\n\t\tself.native_handle.show_all();\n\t}\n\n\tpub(crate) fn set_cursor(&mut self, event_loop: &dyn ActiveEventLoop, cursor: Cursor) {\n\t\tlet cursor = match cursor {\n\t\t\tCursor::Icon(cursor_icon) => cursor_icon.into(),\n\t\t\tCursor::Custom(custom_cursor_source) => {\n\t\t\t\tlet custom_cursor = match self.custom_cursors.get(&custom_cursor_source).cloned() {\n\t\t\t\t\tSome(cursor) => cursor,\n\t\t\t\t\tNone => {\n\t\t\t\t\t\tlet Ok(custom_cursor) = event_loop.create_custom_cursor(custom_cursor_source.clone()) else {\n\t\t\t\t\t\t\ttracing::error!(\"Failed to create custom cursor\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t};\n\t\t\t\t\t\tself.custom_cursors.insert(custom_cursor_source, custom_cursor.clone());\n\t\t\t\t\t\tcustom_cursor\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tcustom_cursor.into()\n\t\t\t}\n\t\t\tCursor::None => {\n\t\t\t\tself.winit_window.set_cursor_visible(false);\n\t\t\t\treturn;\n\t\t\t}\n\t\t};\n\t\tself.winit_window.set_cursor_visible(true);\n\t\tself.winit_window.set_cursor(cursor);\n\t}\n\n\tpub(crate) fn start_pointer_lock(&self) {\n\t\tlet _ = self.winit_window.set_cursor_grab(winit::window::CursorGrabMode::Locked);\n\t\tself.winit_window.set_cursor_visible(false);\n\t}\n\n\tpub(crate) fn end_pointer_lock(&self) {\n\t\tlet _ = self.winit_window.set_cursor_grab(winit::window::CursorGrabMode::None);\n\t\tself.winit_window.set_cursor_visible(true);\n\t}\n\n\tpub(crate) fn update_menu(&self, entries: Vec<MenuItem>) {\n\t\tself.native_handle.update_menu(entries);\n\t}\n\n\tpub(crate) fn clipboard_read(&self) -> Option<String> {\n\t\tlet Some(clipboard) = &self.clipboard else {\n\t\t\ttracing::error!(\"Clipboard not available\");\n\t\t\treturn None;\n\t\t};\n\t\tmatch clipboard.read() {\n\t\t\tOk(data) => Some(data),\n\t\t\tErr(e) => {\n\t\t\t\ttracing::error!(\"Failed to read from clipboard: {e}\");\n\t\t\t\tNone\n\t\t\t}\n\t\t}\n\t}\n\n\tpub(crate) fn clipboard_write(&mut self, data: String) {\n\t\tlet Some(clipboard) = &mut self.clipboard else {\n\t\t\ttracing::error!(\"Clipboard not available\");\n\t\t\treturn;\n\t\t};\n\t\tif let Err(e) = clipboard.write(data) {\n\t\t\ttracing::error!(\"Failed to write to clipboard: {e}\")\n\t\t}\n\t}\n}\n\npub(crate) enum Cursor {\n\tIcon(CursorIcon),\n\tCustom(CustomCursorSource),\n\tNone,\n}\nimpl From<CursorIcon> for Cursor {\n\tfn from(icon: CursorIcon) -> Self {\n\t\tCursor::Icon(icon)\n\t}\n}\nimpl From<CustomCursorSource> for Cursor {\n\tfn from(custom: CustomCursorSource) -> Self {\n\t\tCursor::Custom(custom)\n\t}\n}\n"
  },
  {
    "path": "desktop/wrapper/Cargo.toml",
    "content": "[package]\nname = \"graphite-desktop-wrapper\"\nversion = \"0.1.0\"\ndescription = \"Graphite Desktop Wrapper\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\nlicense = \"Apache-2.0\"\nrepository = \"\"\nedition = \"2024\"\nrust-version = \"1.87\"\n\n[features]\ngpu = [\"graphite-editor/gpu\", \"graphene-std/shader-nodes\"]\n\n[dependencies]\n# Local dependencies\ngraphite-editor = { path = \"../../editor\", features = [\n\t\"gpu\",\n] }\ngraphene-std = { workspace = true }\ngraph-craft = { workspace = true }\nwgpu-executor = { workspace = true }\n\nwgpu = { workspace = true }\nthiserror = { workspace = true }\nfutures = { workspace = true }\ntracing = { workspace = true }\ndirs = { workspace = true }\nron = { workspace = true}\nvello = { workspace = true }\nimage = { workspace = true }\nserde = { workspace = true }\nserde_json = { workspace = true }\nkeyboard-types = { workspace = true }\nbase64 = { workspace = true }\n"
  },
  {
    "path": "desktop/wrapper/src/handle_desktop_wrapper_message.rs",
    "content": "use graphite_editor::messages::clipboard::utility_types::ClipboardContentRaw;\nuse graphite_editor::messages::prelude::*;\n\nuse super::DesktopWrapperMessageDispatcher;\nuse super::messages::{DesktopFrontendMessage, DesktopWrapperMessage, EditorMessage, OpenFileDialogContext, SaveFileDialogContext};\n\npub(super) fn handle_desktop_wrapper_message(dispatcher: &mut DesktopWrapperMessageDispatcher, message: DesktopWrapperMessage) {\n\tmatch message {\n\t\tDesktopWrapperMessage::FromWeb(message) => {\n\t\t\tdispatcher.queue_editor_message(*message);\n\t\t}\n\t\tDesktopWrapperMessage::Input(message) => {\n\t\t\tdispatcher.queue_editor_message(EditorMessage::InputPreprocessor(message));\n\t\t}\n\t\tDesktopWrapperMessage::FileDialogResult { path, content, context } => match context {\n\t\t\tOpenFileDialogContext::Open => {\n\t\t\t\tdispatcher.queue_desktop_wrapper_message(DesktopWrapperMessage::OpenFile { path, content });\n\t\t\t}\n\t\t\tOpenFileDialogContext::Import => {\n\t\t\t\tdispatcher.queue_desktop_wrapper_message(DesktopWrapperMessage::ImportFile { path, content });\n\t\t\t}\n\t\t},\n\t\tDesktopWrapperMessage::SaveFileDialogResult { path, context } => match context {\n\t\t\tSaveFileDialogContext::Document { document_id, content } => {\n\t\t\t\tdispatcher.respond(DesktopFrontendMessage::WriteFile { path: path.clone(), content });\n\t\t\t\tdispatcher.queue_editor_message(EditorMessage::Portfolio(PortfolioMessage::DocumentPassMessage {\n\t\t\t\t\tdocument_id,\n\t\t\t\t\tmessage: DocumentMessage::SavedDocument { path: Some(path) },\n\t\t\t\t}));\n\t\t\t}\n\t\t\tSaveFileDialogContext::File { content } => {\n\t\t\t\tdispatcher.respond(DesktopFrontendMessage::WriteFile { path, content });\n\t\t\t}\n\t\t},\n\t\tDesktopWrapperMessage::OpenFile { path, content } => {\n\t\t\tlet message = PortfolioMessage::OpenFile { path, content };\n\t\t\tdispatcher.queue_editor_message(message);\n\t\t}\n\t\tDesktopWrapperMessage::ImportFile { path, content } => {\n\t\t\tlet message = PortfolioMessage::ImportFile { path, content };\n\t\t\tdispatcher.queue_editor_message(message);\n\t\t}\n\t\tDesktopWrapperMessage::PollNodeGraphEvaluation => dispatcher.poll_node_graph_evaluation(),\n\t\tDesktopWrapperMessage::UpdateMaximized { maximized } => {\n\t\t\tlet message = FrontendMessage::UpdateMaximized { maximized };\n\t\t\tdispatcher.queue_editor_message(message);\n\t\t}\n\t\tDesktopWrapperMessage::UpdateFullscreen { fullscreen } => {\n\t\t\tlet message = FrontendMessage::UpdateFullscreen { fullscreen };\n\t\t\tdispatcher.queue_editor_message(message);\n\t\t}\n\t\tDesktopWrapperMessage::LoadDocument {\n\t\t\tid,\n\t\t\tdocument,\n\t\t\tto_front,\n\t\t\tselect_after_open,\n\t\t} => {\n\t\t\tlet message = PortfolioMessage::OpenDocumentFileWithId {\n\t\t\t\tdocument_id: id,\n\t\t\t\tdocument_name: Some(document.name),\n\t\t\t\tdocument_path: document.path,\n\t\t\t\tdocument_serialized_content: document.content,\n\t\t\t\tdocument_is_auto_saved: true,\n\t\t\t\tdocument_is_saved: document.is_saved,\n\t\t\t\tto_front,\n\t\t\t\tselect_after_open,\n\t\t\t};\n\t\t\tdispatcher.queue_editor_message(message);\n\t\t}\n\t\tDesktopWrapperMessage::SelectDocument { id } => {\n\t\t\tlet message = PortfolioMessage::SelectDocument { document_id: id };\n\t\t\tdispatcher.queue_editor_message(message);\n\t\t}\n\t\tDesktopWrapperMessage::LoadPreferences { preferences } => {\n\t\t\tlet message = PreferencesMessage::Load { preferences };\n\t\t\tdispatcher.queue_editor_message(message);\n\t\t}\n\t\t#[cfg(target_os = \"macos\")]\n\t\tDesktopWrapperMessage::MenuEvent { id } => {\n\t\t\tif let Some(message) = crate::utils::menu::parse_item_path(id) {\n\t\t\t\tdispatcher.queue_editor_message(message);\n\t\t\t} else {\n\t\t\t\ttracing::error!(\"Received a malformed MenuEvent id\");\n\t\t\t}\n\t\t}\n\t\t#[cfg(not(target_os = \"macos\"))]\n\t\tDesktopWrapperMessage::MenuEvent { id: _ } => {}\n\t\tDesktopWrapperMessage::ClipboardReadResult { content } => {\n\t\t\tif let Some(content) = content {\n\t\t\t\tlet message = ClipboardMessage::ReadClipboard {\n\t\t\t\t\tcontent: ClipboardContentRaw::Text(content),\n\t\t\t\t};\n\t\t\t\tdispatcher.queue_editor_message(message);\n\t\t\t}\n\t\t}\n\t\tDesktopWrapperMessage::PointerLockMove { x, y } => {\n\t\t\tlet message = AppWindowMessage::PointerLockMove { x, y };\n\t\t\tdispatcher.queue_editor_message(message);\n\t\t}\n\t\tDesktopWrapperMessage::LoadThirdPartyLicenses { text } => {\n\t\t\tlet message = DialogMessage::RequestLicensesThirdPartyDialogWithLicenseText { license_text: text };\n\t\t\tdispatcher.queue_editor_message(message);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "desktop/wrapper/src/intercept_editor_message.rs",
    "content": "use super::DesktopWrapperMessageDispatcher;\nuse super::messages::EditorMessage;\n\npub(super) fn intercept_editor_message(_dispatcher: &mut DesktopWrapperMessageDispatcher, message: EditorMessage) -> Option<EditorMessage> {\n\t// TODO: remove if it turns out to be unnecessary\n\tSome(message)\n}\n"
  },
  {
    "path": "desktop/wrapper/src/intercept_frontend_message.rs",
    "content": "#[cfg(target_os = \"macos\")]\nuse graphite_editor::messages::layout::utility_types::layout_widget::LayoutTarget;\nuse graphite_editor::messages::prelude::FrontendMessage;\nuse std::path::PathBuf;\n\nuse super::DesktopWrapperMessageDispatcher;\nuse super::messages::{DesktopFrontendMessage, Document, FileFilter, OpenFileDialogContext, SaveFileDialogContext};\n\npub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageDispatcher, message: FrontendMessage) -> Option<FrontendMessage> {\n\tmatch message {\n\t\tFrontendMessage::RenderOverlays { context } => {\n\t\t\tdispatcher.respond(DesktopFrontendMessage::UpdateOverlays(context.take_scene()));\n\t\t}\n\t\tFrontendMessage::TriggerOpen => {\n\t\t\tdispatcher.respond(DesktopFrontendMessage::OpenFileDialog {\n\t\t\t\ttitle: \"Open Document\".to_string(),\n\t\t\t\tfilters: vec![],\n\t\t\t\tcontext: OpenFileDialogContext::Open,\n\t\t\t});\n\t\t}\n\t\tFrontendMessage::TriggerImport => {\n\t\t\tdispatcher.respond(DesktopFrontendMessage::OpenFileDialog {\n\t\t\t\ttitle: \"Import File\".to_string(),\n\t\t\t\tfilters: vec![],\n\t\t\t\tcontext: OpenFileDialogContext::Import,\n\t\t\t});\n\t\t}\n\t\tFrontendMessage::TriggerSaveDocument { document_id, name, path, content } => {\n\t\t\tlet content = content.into_vec();\n\t\t\tif let Some(path) = path {\n\t\t\t\tdispatcher.respond(DesktopFrontendMessage::WriteFile { path, content });\n\t\t\t} else {\n\t\t\t\tdispatcher.respond(DesktopFrontendMessage::SaveFileDialog {\n\t\t\t\t\ttitle: \"Save Document\".to_string(),\n\t\t\t\t\tdefault_filename: name,\n\t\t\t\t\tdefault_folder: path.and_then(|p| p.parent().map(PathBuf::from)),\n\t\t\t\t\tfilters: vec![FileFilter {\n\t\t\t\t\t\tname: \"Graphite\".to_string(),\n\t\t\t\t\t\textensions: vec![\"graphite\".to_string()],\n\t\t\t\t\t}],\n\t\t\t\t\tcontext: SaveFileDialogContext::Document { document_id, content },\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tFrontendMessage::TriggerSaveFile { name, content } => {\n\t\t\tlet content = content.into_vec();\n\t\t\tdispatcher.respond(DesktopFrontendMessage::SaveFileDialog {\n\t\t\t\ttitle: \"Save File\".to_string(),\n\t\t\t\tdefault_filename: name,\n\t\t\t\tdefault_folder: None,\n\t\t\t\tfilters: Vec::new(),\n\t\t\t\tcontext: SaveFileDialogContext::File { content },\n\t\t\t});\n\t\t}\n\t\tFrontendMessage::TriggerVisitLink { url } => {\n\t\t\tdispatcher.respond(DesktopFrontendMessage::OpenUrl(url));\n\t\t}\n\t\tFrontendMessage::UpdateViewportPhysicalBounds { x, y, width, height } => {\n\t\t\tdispatcher.respond(DesktopFrontendMessage::UpdateViewportPhysicalBounds { x, y, width, height });\n\t\t}\n\t\tFrontendMessage::UpdateUIScale { scale } => {\n\t\t\tdispatcher.respond(DesktopFrontendMessage::UpdateUIScale { scale });\n\t\t\treturn Some(FrontendMessage::UpdateUIScale { scale });\n\t\t}\n\t\tFrontendMessage::TriggerPersistenceWriteDocument { document_id, document, details } => {\n\t\t\tdispatcher.respond(DesktopFrontendMessage::PersistenceWriteDocument {\n\t\t\t\tid: document_id,\n\t\t\t\tdocument: Document {\n\t\t\t\t\tname: details.name,\n\t\t\t\t\tpath: details.path,\n\t\t\t\t\tcontent: document,\n\t\t\t\t\tis_saved: details.is_saved,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\t\tFrontendMessage::TriggerPersistenceRemoveDocument { document_id } => {\n\t\t\tdispatcher.respond(DesktopFrontendMessage::PersistenceDeleteDocument { id: document_id });\n\t\t}\n\t\tFrontendMessage::UpdateActiveDocument { document_id } => {\n\t\t\tdispatcher.respond(DesktopFrontendMessage::PersistenceUpdateCurrentDocument { id: document_id });\n\n\t\t\t// Forward this to update the UI\n\t\t\treturn Some(FrontendMessage::UpdateActiveDocument { document_id });\n\t\t}\n\t\tFrontendMessage::UpdateOpenDocumentsList { open_documents } => {\n\t\t\tdispatcher.respond(DesktopFrontendMessage::PersistenceUpdateDocumentsList {\n\t\t\t\tids: open_documents.iter().map(|document| document.id).collect(),\n\t\t\t});\n\n\t\t\t// Forward this to update the UI\n\t\t\treturn Some(FrontendMessage::UpdateOpenDocumentsList { open_documents });\n\t\t}\n\t\tFrontendMessage::TriggerLoadFirstAutoSaveDocument => {\n\t\t\tdispatcher.respond(DesktopFrontendMessage::PersistenceLoadCurrentDocument);\n\t\t}\n\t\tFrontendMessage::TriggerLoadRestAutoSaveDocuments => {\n\t\t\tdispatcher.respond(DesktopFrontendMessage::PersistenceLoadRemainingDocuments);\n\t\t}\n\t\tFrontendMessage::TriggerOpenLaunchDocuments => {\n\t\t\tdispatcher.respond(DesktopFrontendMessage::OpenLaunchDocuments);\n\t\t}\n\t\tFrontendMessage::TriggerSavePreferences { preferences } => {\n\t\t\tdispatcher.respond(DesktopFrontendMessage::PersistenceWritePreferences { preferences });\n\t\t}\n\t\tFrontendMessage::TriggerLoadPreferences => {\n\t\t\tdispatcher.respond(DesktopFrontendMessage::PersistenceLoadPreferences);\n\t\t}\n\t\t#[cfg(target_os = \"macos\")]\n\t\tFrontendMessage::UpdateLayout {\n\t\t\tlayout_target: LayoutTarget::MenuBar,\n\t\t\tdiff,\n\t\t} => {\n\t\t\tuse graphite_editor::messages::tool::tool_messages::tool_prelude::{DiffUpdate, WidgetDiff};\n\t\t\tmatch diff.as_slice() {\n\t\t\t\t[\n\t\t\t\t\tWidgetDiff {\n\t\t\t\t\t\twidget_path,\n\t\t\t\t\t\tnew_value: DiffUpdate::Layout(layout),\n\t\t\t\t\t},\n\t\t\t\t] if widget_path.is_empty() => {\n\t\t\t\t\tlet entries = crate::utils::menu::convert_menu_bar_layout_to_menu_items(layout);\n\t\t\t\t\tdispatcher.respond(DesktopFrontendMessage::UpdateMenu { entries });\n\t\t\t\t}\n\t\t\t\t_ => {}\n\t\t\t}\n\t\t}\n\t\tFrontendMessage::TriggerClipboardRead => {\n\t\t\tdispatcher.respond(DesktopFrontendMessage::ClipboardRead);\n\t\t}\n\t\tFrontendMessage::TriggerClipboardWrite { content } => {\n\t\t\tdispatcher.respond(DesktopFrontendMessage::ClipboardWrite { content });\n\t\t}\n\t\tFrontendMessage::WindowPointerLock => {\n\t\t\tdispatcher.respond(DesktopFrontendMessage::PointerLock);\n\t\t}\n\t\tFrontendMessage::WindowClose => {\n\t\t\tdispatcher.respond(DesktopFrontendMessage::WindowClose);\n\t\t}\n\t\tFrontendMessage::WindowMinimize => {\n\t\t\tdispatcher.respond(DesktopFrontendMessage::WindowMinimize);\n\t\t}\n\t\tFrontendMessage::WindowMaximize => {\n\t\t\tdispatcher.respond(DesktopFrontendMessage::WindowMaximize);\n\t\t}\n\t\tFrontendMessage::WindowFullscreen => {\n\t\t\tdispatcher.respond(DesktopFrontendMessage::WindowFullscreen);\n\t\t}\n\t\tFrontendMessage::WindowDrag => {\n\t\t\tdispatcher.respond(DesktopFrontendMessage::WindowDrag);\n\t\t}\n\t\tFrontendMessage::WindowHide => {\n\t\t\tdispatcher.respond(DesktopFrontendMessage::WindowHide);\n\t\t}\n\t\tFrontendMessage::WindowHideOthers => {\n\t\t\tdispatcher.respond(DesktopFrontendMessage::WindowHideOthers);\n\t\t}\n\t\tFrontendMessage::WindowShowAll => {\n\t\t\tdispatcher.respond(DesktopFrontendMessage::WindowShowAll);\n\t\t}\n\t\tFrontendMessage::WindowRestart => {\n\t\t\tdispatcher.respond(DesktopFrontendMessage::Restart);\n\t\t}\n\t\tFrontendMessage::TriggerDisplayThirdPartyLicensesDialog => {\n\t\t\tdispatcher.respond(DesktopFrontendMessage::LoadThirdPartyLicenses);\n\t\t}\n\t\tm => return Some(m),\n\t}\n\tNone\n}\n"
  },
  {
    "path": "desktop/wrapper/src/lib.rs",
    "content": "use graph_craft::wasm_application_io::WasmApplicationIo;\nuse graphite_editor::application::{Editor, Environment, Host, Platform};\nuse graphite_editor::messages::prelude::{FrontendMessage, Message};\nuse message_dispatcher::DesktopWrapperMessageDispatcher;\nuse messages::{DesktopFrontendMessage, DesktopWrapperMessage};\n\npub use graphite_editor::consts::FILE_EXTENSION;\npub use wgpu_executor::TargetTexture;\npub use wgpu_executor::WgpuContext;\npub use wgpu_executor::WgpuContextBuilder;\npub use wgpu_executor::WgpuExecutor;\npub use wgpu_executor::WgpuFeatures;\n\nmod handle_desktop_wrapper_message;\nmod intercept_editor_message;\nmod intercept_frontend_message;\nmod message_dispatcher;\npub mod messages;\npub(crate) mod utils;\n\npub struct DesktopWrapper {\n\teditor: Editor,\n}\n\nimpl DesktopWrapper {\n\tpub fn new(uuid_random_seed: u64) -> Self {\n\t\t#[cfg(target_os = \"windows\")]\n\t\tlet host = Host::Windows;\n\t\t#[cfg(target_os = \"macos\")]\n\t\tlet host = Host::Mac;\n\t\t#[cfg(target_os = \"linux\")]\n\t\tlet host = Host::Linux;\n\t\tlet env = Environment { platform: Platform::Desktop, host };\n\n\t\tSelf {\n\t\t\teditor: Editor::new(env, uuid_random_seed),\n\t\t}\n\t}\n\n\tpub fn init(&self, wgpu_context: WgpuContext) {\n\t\tlet application_io = WasmApplicationIo::new_with_context(wgpu_context);\n\t\tfutures::executor::block_on(graphite_editor::node_graph_executor::replace_application_io(application_io));\n\t}\n\n\tpub fn dispatch(&mut self, message: DesktopWrapperMessage) -> Vec<DesktopFrontendMessage> {\n\t\tlet mut executor = DesktopWrapperMessageDispatcher::new(&mut self.editor);\n\t\texecutor.queue_desktop_wrapper_message(message);\n\t\texecutor.execute()\n\t}\n\n\tpub async fn execute_node_graph() -> NodeGraphExecutionResult {\n\t\tlet result = graphite_editor::node_graph_executor::run_node_graph().await;\n\t\tmatch result {\n\t\t\t(true, texture) => NodeGraphExecutionResult::HasRun(texture.map(|t| t.texture)),\n\t\t\t(false, _) => NodeGraphExecutionResult::NotRun,\n\t\t}\n\t}\n}\n\npub enum NodeGraphExecutionResult {\n\tHasRun(Option<std::sync::Arc<wgpu::Texture>>),\n\tNotRun,\n}\n\npub fn deserialize_editor_message(data: &[u8]) -> Option<DesktopWrapperMessage> {\n\tif let Ok(string) = std::str::from_utf8(data) {\n\t\tif let Ok(message) = ron::de::from_str::<Message>(string) {\n\t\t\tSome(DesktopWrapperMessage::FromWeb(message.into()))\n\t\t} else {\n\t\t\tNone\n\t\t}\n\t} else {\n\t\tNone\n\t}\n}\n\npub fn serialize_frontend_messages(messages: Vec<FrontendMessage>) -> Option<Vec<u8>> {\n\tif let Ok(serialized) = ron::ser::to_string(&messages) {\n\t\tSome(serialized.into_bytes())\n\t} else {\n\t\tNone\n\t}\n}\n"
  },
  {
    "path": "desktop/wrapper/src/message_dispatcher.rs",
    "content": "use graphite_editor::application::Editor;\nuse std::collections::VecDeque;\n\nuse super::handle_desktop_wrapper_message::handle_desktop_wrapper_message;\nuse super::intercept_editor_message::intercept_editor_message;\nuse super::intercept_frontend_message::intercept_frontend_message;\nuse super::messages::{DesktopFrontendMessage, DesktopWrapperMessage, EditorMessage};\n\npub(crate) struct DesktopWrapperMessageDispatcher<'a> {\n\teditor: &'a mut Editor,\n\tdesktop_wrapper_message_queue: VecDeque<DesktopWrapperMessage>,\n\teditor_message_queue: Vec<EditorMessage>,\n\tresponses: Vec<DesktopFrontendMessage>,\n}\n\nimpl<'a> DesktopWrapperMessageDispatcher<'a> {\n\tpub(crate) fn new(editor: &'a mut Editor) -> Self {\n\t\tSelf {\n\t\t\teditor,\n\t\t\tdesktop_wrapper_message_queue: VecDeque::new(),\n\t\t\teditor_message_queue: Vec::new(),\n\t\t\tresponses: Vec::new(),\n\t\t}\n\t}\n\n\tpub(crate) fn execute(mut self) -> Vec<DesktopFrontendMessage> {\n\t\tself.process_queue();\n\t\tself.responses\n\t}\n\n\tpub(crate) fn queue_desktop_wrapper_message(&mut self, message: DesktopWrapperMessage) {\n\t\tself.desktop_wrapper_message_queue.push_back(message);\n\t}\n\n\tpub(super) fn queue_editor_message<T: Into<EditorMessage>>(&mut self, message: T) {\n\t\tif let Some(message) = intercept_editor_message(self, message.into()) {\n\t\t\tself.editor_message_queue.push(message);\n\t\t}\n\t}\n\n\tpub(super) fn respond(&mut self, response: DesktopFrontendMessage) {\n\t\tself.responses.push(response);\n\t}\n\n\tpub(super) fn poll_node_graph_evaluation(&mut self) {\n\t\tlet mut responses = VecDeque::new();\n\t\tif let Err(e) = self.editor.poll_node_graph_evaluation(&mut responses) {\n\t\t\tif e != \"No active document\" {\n\t\t\t\ttracing::error!(\"Error poling node graph: {}\", e);\n\t\t\t}\n\t\t}\n\t\twhile let Some(message) = responses.pop_front() {\n\t\t\tself.queue_editor_message(message);\n\t\t}\n\t}\n\n\tfn process_queue(&mut self) {\n\t\tlet mut frontend_messages = Vec::new();\n\n\t\twhile !self.desktop_wrapper_message_queue.is_empty() || !self.editor_message_queue.is_empty() {\n\t\t\twhile let Some(message) = self.desktop_wrapper_message_queue.pop_front() {\n\t\t\t\thandle_desktop_wrapper_message(self, message);\n\t\t\t}\n\t\t\tlet current_frontend_messages = self\n\t\t\t\t.editor\n\t\t\t\t.handle_message(EditorMessage::Batched {\n\t\t\t\t\tmessages: std::mem::take(&mut self.editor_message_queue).into_boxed_slice(),\n\t\t\t\t})\n\t\t\t\t.into_iter()\n\t\t\t\t.filter_map(|m| intercept_frontend_message(self, m));\n\t\t\tfrontend_messages.extend(current_frontend_messages);\n\t\t}\n\n\t\tif !frontend_messages.is_empty() {\n\t\t\tself.respond(DesktopFrontendMessage::ToWeb(frontend_messages));\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "desktop/wrapper/src/messages.rs",
    "content": "use graphite_editor::messages::prelude::FrontendMessage;\nuse std::path::PathBuf;\n\npub(crate) use graphite_editor::messages::prelude::Message as EditorMessage;\n\npub use graphite_editor::messages::input_mapper::utility_types::input_keyboard::{Key, ModifierKeys};\npub use graphite_editor::messages::input_mapper::utility_types::input_mouse::{EditorMouseState as MouseState, EditorPosition as Position, MouseKeys};\npub use graphite_editor::messages::prelude::InputPreprocessorMessage as InputMessage;\n\npub use graphite_editor::messages::prelude::DocumentId;\npub use graphite_editor::messages::prelude::PreferencesMessageHandler as Preferences;\npub enum DesktopFrontendMessage {\n\tToWeb(Vec<FrontendMessage>),\n\tOpenLaunchDocuments,\n\tOpenFileDialog {\n\t\ttitle: String,\n\t\tfilters: Vec<FileFilter>,\n\t\tcontext: OpenFileDialogContext,\n\t},\n\tSaveFileDialog {\n\t\ttitle: String,\n\t\tdefault_filename: String,\n\t\tdefault_folder: Option<PathBuf>,\n\t\tfilters: Vec<FileFilter>,\n\t\tcontext: SaveFileDialogContext,\n\t},\n\tWriteFile {\n\t\tpath: PathBuf,\n\t\tcontent: Vec<u8>,\n\t},\n\tOpenUrl(String),\n\tUpdateViewportPhysicalBounds {\n\t\tx: f64,\n\t\ty: f64,\n\t\twidth: f64,\n\t\theight: f64,\n\t},\n\tUpdateUIScale {\n\t\tscale: f64,\n\t},\n\tUpdateOverlays(vello::Scene),\n\tPersistenceWriteDocument {\n\t\tid: DocumentId,\n\t\tdocument: Document,\n\t},\n\tPersistenceDeleteDocument {\n\t\tid: DocumentId,\n\t},\n\tPersistenceUpdateCurrentDocument {\n\t\tid: DocumentId,\n\t},\n\tPersistenceLoadCurrentDocument,\n\tPersistenceLoadRemainingDocuments,\n\tPersistenceUpdateDocumentsList {\n\t\tids: Vec<DocumentId>,\n\t},\n\tPersistenceWritePreferences {\n\t\tpreferences: Preferences,\n\t},\n\tPersistenceLoadPreferences,\n\tUpdateMenu {\n\t\tentries: Vec<MenuItem>,\n\t},\n\tClipboardRead,\n\tClipboardWrite {\n\t\tcontent: String,\n\t},\n\tPointerLock,\n\tWindowClose,\n\tWindowMinimize,\n\tWindowMaximize,\n\tWindowFullscreen,\n\tWindowDrag,\n\tWindowHide,\n\tWindowHideOthers,\n\tWindowShowAll,\n\tRestart,\n\tLoadThirdPartyLicenses,\n}\n\npub enum DesktopWrapperMessage {\n\tFromWeb(Box<EditorMessage>),\n\tInput(InputMessage),\n\tFileDialogResult {\n\t\tpath: PathBuf,\n\t\tcontent: Vec<u8>,\n\t\tcontext: OpenFileDialogContext,\n\t},\n\tSaveFileDialogResult {\n\t\tpath: PathBuf,\n\t\tcontext: SaveFileDialogContext,\n\t},\n\tOpenFile {\n\t\tpath: PathBuf,\n\t\tcontent: Vec<u8>,\n\t},\n\tImportFile {\n\t\tpath: PathBuf,\n\t\tcontent: Vec<u8>,\n\t},\n\tPollNodeGraphEvaluation,\n\tUpdateMaximized {\n\t\tmaximized: bool,\n\t},\n\tUpdateFullscreen {\n\t\tfullscreen: bool,\n\t},\n\tLoadDocument {\n\t\tid: DocumentId,\n\t\tdocument: Document,\n\t\tto_front: bool,\n\t\tselect_after_open: bool,\n\t},\n\tSelectDocument {\n\t\tid: DocumentId,\n\t},\n\tLoadPreferences {\n\t\tpreferences: Preferences,\n\t},\n\tMenuEvent {\n\t\tid: String,\n\t},\n\tClipboardReadResult {\n\t\tcontent: Option<String>,\n\t},\n\tPointerLockMove {\n\t\tx: f64,\n\t\ty: f64,\n\t},\n\tLoadThirdPartyLicenses {\n\t\ttext: String,\n\t},\n}\n\n#[derive(Clone, serde::Serialize, serde::Deserialize, Debug)]\npub struct Document {\n\tpub content: String,\n\tpub name: String,\n\tpub path: Option<PathBuf>,\n\tpub is_saved: bool,\n}\n\npub struct FileFilter {\n\tpub name: String,\n\tpub extensions: Vec<String>,\n}\n\npub enum OpenFileDialogContext {\n\tOpen,\n\tImport,\n}\n\npub enum SaveFileDialogContext {\n\tDocument { document_id: DocumentId, content: Vec<u8> },\n\tFile { content: Vec<u8> },\n}\n\npub enum MenuItem {\n\tAction {\n\t\tid: String,\n\t\ttext: String,\n\t\tenabled: bool,\n\t\tshortcut: Option<Shortcut>,\n\t},\n\tCheckbox {\n\t\tid: String,\n\t\ttext: String,\n\t\tenabled: bool,\n\t\tshortcut: Option<Shortcut>,\n\t\tchecked: bool,\n\t},\n\tSubMenu {\n\t\tid: String,\n\t\ttext: String,\n\t\tenabled: bool,\n\t\titems: Vec<MenuItem>,\n\t},\n\tSeparator,\n}\n\npub use keyboard_types::{Code as KeyCode, Modifiers};\npub struct Shortcut {\n\tpub key: KeyCode,\n\tpub modifiers: Modifiers,\n}\n"
  },
  {
    "path": "desktop/wrapper/src/utils.rs",
    "content": "#[cfg(target_os = \"macos\")]\npub(crate) mod menu {\n\tuse base64::engine::Engine;\n\tuse base64::engine::general_purpose::STANDARD as BASE64;\n\n\tuse graphite_editor::messages::input_mapper::utility_types::input_keyboard::{Key, LabeledKeyOrMouseMotion, LabeledShortcut};\n\tuse graphite_editor::messages::input_mapper::utility_types::misc::ActionShortcut;\n\tuse graphite_editor::messages::layout::LayoutMessage;\n\tuse graphite_editor::messages::tool::tool_messages::tool_prelude::{Layout, LayoutGroup, LayoutTarget, MenuListEntry, Widget, WidgetId, WidgetRow};\n\n\tuse crate::messages::{EditorMessage, KeyCode, MenuItem, Modifiers, Shortcut};\n\n\tpub(crate) fn convert_menu_bar_layout_to_menu_items(Layout(layout): &Layout) -> Vec<MenuItem> {\n\t\tlet layout_group = match layout.as_slice() {\n\t\t\t[layout_group] => layout_group,\n\t\t\t_ => panic!(\"Menu bar layout is supposed to have exactly one layout group\"),\n\t\t};\n\t\tlet LayoutGroup::Row(WidgetRow { widgets }) = layout_group else {\n\t\t\tpanic!(\"Menu bar layout group is supposed to be a row\");\n\t\t};\n\t\twidgets\n\t\t\t.iter()\n\t\t\t.map(|widget| {\n\t\t\t\tlet text_button = match widget.widget.as_ref() {\n\t\t\t\t\tWidget::TextButton(text_button) => text_button,\n\t\t\t\t\t_ => panic!(\"Menu bar layout top-level widgets are supposed to be text buttons\"),\n\t\t\t\t};\n\n\t\t\t\tMenuItem::SubMenu {\n\t\t\t\t\tid: widget.widget_id.to_string(),\n\t\t\t\t\ttext: text_button.label.clone(),\n\t\t\t\t\tenabled: !text_button.disabled,\n\t\t\t\t\titems: convert_menu_bar_entry_children_to_menu_items(&text_button.menu_list_children, widget.widget_id.0, Vec::new()),\n\t\t\t\t}\n\t\t\t})\n\t\t\t.collect::<Vec<MenuItem>>()\n\t}\n\n\tpub(crate) fn parse_item_path(id: String) -> Option<EditorMessage> {\n\t\tlet mut id_parts = id.split(':');\n\t\tlet widget_id = id_parts.next()?.parse::<u64>().ok()?;\n\n\t\tlet value = id_parts\n\t\t\t.map(|part| {\n\t\t\t\tlet bytes = BASE64.decode(part).ok()?;\n\t\t\t\tString::from_utf8(bytes).ok()\n\t\t\t})\n\t\t\t.collect::<Option<Vec<String>>>()?;\n\t\tlet value = serde_json::to_value(value).ok()?;\n\n\t\tSome(\n\t\t\tLayoutMessage::WidgetValueUpdate {\n\t\t\t\tlayout_target: LayoutTarget::MenuBar,\n\t\t\t\twidget_id: WidgetId(widget_id),\n\t\t\t\tvalue,\n\t\t\t}\n\t\t\t.into(),\n\t\t)\n\t}\n\n\tfn item_path_to_string(widget_id: u64, path: Vec<String>) -> String {\n\t\tlet path = path.into_iter().map(|element| BASE64.encode(element)).collect::<Vec<_>>().join(\":\");\n\t\tformat!(\"{widget_id}:{path}\")\n\t}\n\n\tfn convert_menu_bar_layout_to_menu_item(entry: &MenuListEntry, root_widget_id: u64, mut path: Vec<String>) -> MenuItem {\n\t\tlet MenuListEntry {\n\t\t\tvalue,\n\t\t\tlabel,\n\t\t\ticon,\n\t\t\tdisabled,\n\t\t\ttooltip_shortcut,\n\t\t\tchildren,\n\t\t\t..\n\t\t}: &MenuListEntry = entry;\n\t\tpath.push(value.clone());\n\t\tlet id = item_path_to_string(root_widget_id, path.clone());\n\t\tlet text = label.clone();\n\t\tlet enabled = !*disabled;\n\n\t\tif !children.is_empty() {\n\t\t\tlet items = convert_menu_bar_entry_children_to_menu_items(children, root_widget_id, path.clone());\n\t\t\treturn MenuItem::SubMenu { id, text, enabled, items };\n\t\t}\n\n\t\tlet shortcut = match tooltip_shortcut {\n\t\t\tSome(ActionShortcut::Shortcut(LabeledShortcut(shortcut))) => convert_labeled_keys_to_shortcut(shortcut),\n\t\t\t_ => None,\n\t\t};\n\n\t\tmatch icon.as_deref() {\n\t\t\tSome(\"CheckboxChecked\") => {\n\t\t\t\treturn MenuItem::Checkbox {\n\t\t\t\t\tid,\n\t\t\t\t\ttext,\n\t\t\t\t\tenabled,\n\t\t\t\t\tshortcut,\n\t\t\t\t\tchecked: true,\n\t\t\t\t};\n\t\t\t}\n\t\t\tSome(\"CheckboxUnchecked\") => {\n\t\t\t\treturn MenuItem::Checkbox {\n\t\t\t\t\tid,\n\t\t\t\t\ttext,\n\t\t\t\t\tenabled,\n\t\t\t\t\tshortcut,\n\t\t\t\t\tchecked: false,\n\t\t\t\t};\n\t\t\t}\n\t\t\t_ => {}\n\t\t}\n\n\t\tMenuItem::Action { id, text, shortcut, enabled }\n\t}\n\n\tfn convert_menu_bar_entry_children_to_menu_items(children: &[Vec<MenuListEntry>], root_widget_id: u64, path: Vec<String>) -> Vec<MenuItem> {\n\t\tlet mut items = Vec::new();\n\t\tfor (i, section) in children.iter().enumerate() {\n\t\t\tfor entry in section.iter() {\n\t\t\t\titems.push(convert_menu_bar_layout_to_menu_item(entry, root_widget_id, path.clone()));\n\t\t\t}\n\t\t\tif i != children.len() - 1 {\n\t\t\t\titems.push(MenuItem::Separator);\n\t\t\t}\n\t\t}\n\t\titems\n\t}\n\n\tfn convert_labeled_keys_to_shortcut(labeled_keys: &Vec<LabeledKeyOrMouseMotion>) -> Option<Shortcut> {\n\t\tlet mut key: Option<KeyCode> = None;\n\t\tlet mut modifiers = Modifiers::default();\n\t\tfor labeled_key in labeled_keys {\n\t\t\tlet LabeledKeyOrMouseMotion::Key(labeled_key) = labeled_key else {\n\t\t\t\t// Return None for shortcuts that include mouse motion because we can't show them in native menu\n\t\t\t\treturn None;\n\t\t\t};\n\t\t\tmatch labeled_key.key() {\n\t\t\t\tKey::Shift => modifiers |= Modifiers::SHIFT,\n\t\t\t\tKey::Control => modifiers |= Modifiers::CONTROL,\n\t\t\t\tKey::Alt => modifiers |= Modifiers::ALT,\n\t\t\t\tKey::Meta => modifiers |= Modifiers::META,\n\t\t\t\tKey::Command => modifiers |= Modifiers::ALT,\n\t\t\t\tKey::Accel => modifiers |= Modifiers::META,\n\t\t\t\tKey::Digit0 => key = Some(KeyCode::Digit0),\n\t\t\t\tKey::Digit1 => key = Some(KeyCode::Digit1),\n\t\t\t\tKey::Digit2 => key = Some(KeyCode::Digit2),\n\t\t\t\tKey::Digit3 => key = Some(KeyCode::Digit3),\n\t\t\t\tKey::Digit4 => key = Some(KeyCode::Digit4),\n\t\t\t\tKey::Digit5 => key = Some(KeyCode::Digit5),\n\t\t\t\tKey::Digit6 => key = Some(KeyCode::Digit6),\n\t\t\t\tKey::Digit7 => key = Some(KeyCode::Digit7),\n\t\t\t\tKey::Digit8 => key = Some(KeyCode::Digit8),\n\t\t\t\tKey::Digit9 => key = Some(KeyCode::Digit9),\n\t\t\t\tKey::KeyA => key = Some(KeyCode::KeyA),\n\t\t\t\tKey::KeyB => key = Some(KeyCode::KeyB),\n\t\t\t\tKey::KeyC => key = Some(KeyCode::KeyC),\n\t\t\t\tKey::KeyD => key = Some(KeyCode::KeyD),\n\t\t\t\tKey::KeyE => key = Some(KeyCode::KeyE),\n\t\t\t\tKey::KeyF => key = Some(KeyCode::KeyF),\n\t\t\t\tKey::KeyG => key = Some(KeyCode::KeyG),\n\t\t\t\tKey::KeyH => key = Some(KeyCode::KeyH),\n\t\t\t\tKey::KeyI => key = Some(KeyCode::KeyI),\n\t\t\t\tKey::KeyJ => key = Some(KeyCode::KeyJ),\n\t\t\t\tKey::KeyK => key = Some(KeyCode::KeyK),\n\t\t\t\tKey::KeyL => key = Some(KeyCode::KeyL),\n\t\t\t\tKey::KeyM => key = Some(KeyCode::KeyM),\n\t\t\t\tKey::KeyN => key = Some(KeyCode::KeyN),\n\t\t\t\tKey::KeyO => key = Some(KeyCode::KeyO),\n\t\t\t\tKey::KeyP => key = Some(KeyCode::KeyP),\n\t\t\t\tKey::KeyQ => key = Some(KeyCode::KeyQ),\n\t\t\t\tKey::KeyR => key = Some(KeyCode::KeyR),\n\t\t\t\tKey::KeyS => key = Some(KeyCode::KeyS),\n\t\t\t\tKey::KeyT => key = Some(KeyCode::KeyT),\n\t\t\t\tKey::KeyU => key = Some(KeyCode::KeyU),\n\t\t\t\tKey::KeyV => key = Some(KeyCode::KeyV),\n\t\t\t\tKey::KeyW => key = Some(KeyCode::KeyW),\n\t\t\t\tKey::KeyX => key = Some(KeyCode::KeyX),\n\t\t\t\tKey::KeyY => key = Some(KeyCode::KeyY),\n\t\t\t\tKey::KeyZ => key = Some(KeyCode::KeyZ),\n\t\t\t\tKey::Backquote => key = Some(KeyCode::Backquote),\n\t\t\t\tKey::Backslash => key = Some(KeyCode::Backslash),\n\t\t\t\tKey::BracketLeft => key = Some(KeyCode::BracketLeft),\n\t\t\t\tKey::BracketRight => key = Some(KeyCode::BracketRight),\n\t\t\t\tKey::Comma => key = Some(KeyCode::Comma),\n\t\t\t\tKey::Equal => key = Some(KeyCode::Equal),\n\t\t\t\tKey::Minus => key = Some(KeyCode::Minus),\n\t\t\t\tKey::Period => key = Some(KeyCode::Period),\n\t\t\t\tKey::Quote => key = Some(KeyCode::Quote),\n\t\t\t\tKey::Semicolon => key = Some(KeyCode::Semicolon),\n\t\t\t\tKey::Slash => key = Some(KeyCode::Slash),\n\t\t\t\tKey::Backspace => key = Some(KeyCode::Backspace),\n\t\t\t\tKey::CapsLock => key = Some(KeyCode::CapsLock),\n\t\t\t\tKey::ContextMenu => key = Some(KeyCode::ContextMenu),\n\t\t\t\tKey::Enter => key = Some(KeyCode::Enter),\n\t\t\t\tKey::Space => key = Some(KeyCode::Space),\n\t\t\t\tKey::Tab => key = Some(KeyCode::Tab),\n\t\t\t\tKey::Delete => key = Some(KeyCode::Delete),\n\t\t\t\tKey::End => key = Some(KeyCode::End),\n\t\t\t\tKey::Help => key = Some(KeyCode::Help),\n\t\t\t\tKey::Home => key = Some(KeyCode::Home),\n\t\t\t\tKey::Insert => key = Some(KeyCode::Insert),\n\t\t\t\tKey::PageDown => key = Some(KeyCode::PageDown),\n\t\t\t\tKey::PageUp => key = Some(KeyCode::PageUp),\n\t\t\t\tKey::ArrowDown => key = Some(KeyCode::ArrowDown),\n\t\t\t\tKey::ArrowLeft => key = Some(KeyCode::ArrowLeft),\n\t\t\t\tKey::ArrowRight => key = Some(KeyCode::ArrowRight),\n\t\t\t\tKey::ArrowUp => key = Some(KeyCode::ArrowUp),\n\t\t\t\tKey::NumLock => key = Some(KeyCode::NumLock),\n\t\t\t\tKey::NumpadAdd => key = Some(KeyCode::NumpadAdd),\n\t\t\t\tKey::NumpadHash => key = Some(KeyCode::NumpadHash),\n\t\t\t\tKey::NumpadMultiply => key = Some(KeyCode::NumpadMultiply),\n\t\t\t\tKey::NumpadParenLeft => key = Some(KeyCode::NumpadParenLeft),\n\t\t\t\tKey::NumpadParenRight => key = Some(KeyCode::NumpadParenRight),\n\t\t\t\tKey::Escape => key = Some(KeyCode::Escape),\n\t\t\t\tKey::F1 => key = Some(KeyCode::F1),\n\t\t\t\tKey::F2 => key = Some(KeyCode::F2),\n\t\t\t\tKey::F3 => key = Some(KeyCode::F3),\n\t\t\t\tKey::F4 => key = Some(KeyCode::F4),\n\t\t\t\tKey::F5 => key = Some(KeyCode::F5),\n\t\t\t\tKey::F6 => key = Some(KeyCode::F6),\n\t\t\t\tKey::F7 => key = Some(KeyCode::F7),\n\t\t\t\tKey::F8 => key = Some(KeyCode::F8),\n\t\t\t\tKey::F9 => key = Some(KeyCode::F9),\n\t\t\t\tKey::F10 => key = Some(KeyCode::F10),\n\t\t\t\tKey::F11 => key = Some(KeyCode::F11),\n\t\t\t\tKey::F12 => key = Some(KeyCode::F12),\n\t\t\t\tKey::F13 => key = Some(KeyCode::F13),\n\t\t\t\tKey::F14 => key = Some(KeyCode::F14),\n\t\t\t\tKey::F15 => key = Some(KeyCode::F15),\n\t\t\t\tKey::F16 => key = Some(KeyCode::F16),\n\t\t\t\tKey::F17 => key = Some(KeyCode::F17),\n\t\t\t\tKey::F18 => key = Some(KeyCode::F18),\n\t\t\t\tKey::F19 => key = Some(KeyCode::F19),\n\t\t\t\tKey::F20 => key = Some(KeyCode::F20),\n\t\t\t\tKey::F21 => key = Some(KeyCode::F21),\n\t\t\t\tKey::F22 => key = Some(KeyCode::F22),\n\t\t\t\tKey::F23 => key = Some(KeyCode::F23),\n\t\t\t\tKey::F24 => key = Some(KeyCode::F24),\n\t\t\t\tKey::Fn => key = Some(KeyCode::Fn),\n\t\t\t\tKey::FnLock => key = Some(KeyCode::FnLock),\n\t\t\t\tKey::PrintScreen => key = Some(KeyCode::PrintScreen),\n\t\t\t\tKey::ScrollLock => key = Some(KeyCode::ScrollLock),\n\t\t\t\tKey::Pause => key = Some(KeyCode::Pause),\n\t\t\t\tKey::Unidentified => key = Some(KeyCode::Unidentified),\n\t\t\t\tKey::FakeKeyPlus => key = Some(KeyCode::Equal),\n\t\t\t\t_ => key = None,\n\t\t\t}\n\t\t}\n\t\tkey.map(|key| Shortcut { key, modifiers })\n\t}\n}\n"
  },
  {
    "path": "editor/Cargo.toml",
    "content": "[package]\nname = \"graphite-editor\"\npublish = false\nversion = \"0.0.0\"\nrust-version = \"1.88\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\nedition = \"2024\"\nreadme = \"../README.md\"\nhomepage = \"https://graphite.art\"\nrepository = \"https://github.com/GraphiteEditor/Graphite\"\nlicense = \"Apache-2.0\"\n\n[features]\ndefault = [\"wasm\", \"gpu\"]\nwasm = [\"wasm-bindgen\", \"graphene-std/wasm\"]\ngpu = [\"interpreted-executor/gpu\", \"wgpu-executor\"]\n\n[dependencies]\n# Local dependencies\ngraphite-proc-macros = { workspace = true }\ngraph-craft = { workspace = true }\ninterpreted-executor = { workspace = true }\ngraphene-std = { workspace = true } # NOTE: `core-types` should not be added here because `graphene-std` re-exports its contents\npreprocessor = { workspace = true }\n\n# Workspace dependencies\njs-sys = { workspace = true }\nlog = { workspace = true }\nbitflags = { workspace = true }\nthiserror = { workspace = true }\nserde = { workspace = true }\nserde_bytes = { workspace = true }\nserde_json = { workspace = true }\nkurbo = { workspace = true }\nfutures = { workspace = true }\nglam = { workspace = true }\nderivative = { workspace = true }\ntsify = { workspace = true }\ndyn-any = { workspace = true }\nnum_enum = { workspace = true }\nusvg = { workspace = true }\nonce_cell = { workspace = true }\nweb-sys = { workspace = true }\nvello = { workspace = true }\nbase64 = { workspace = true }\nspin = { workspace = true }\nimage = { workspace = true }\n\n# Optional local dependencies\nwgpu-executor = { workspace = true, optional = true }\n\n# Optional workspace dependencies\nwasm-bindgen = { workspace = true, optional = true }\n\n[dev-dependencies]\n# Workspace dependencies\nenv_logger = { workspace = true }\nfutures = { workspace = true }\ntokio = { workspace = true }\n"
  },
  {
    "path": "editor/build.rs",
    "content": "use std::env;\nuse std::process::Command;\n\nconst GRAPHITE_RELEASE_SERIES: &str = \"Alpha 4\";\n\nfn main() {\n\t// Instruct Cargo to rerun this build script if any of these environment variables change.\n\tprintln!(\"cargo:rerun-if-env-changed=GRAPHITE_GIT_COMMIT_DATE\");\n\tprintln!(\"cargo:rerun-if-env-changed=GRAPHITE_GIT_COMMIT_HASH\");\n\tprintln!(\"cargo:rerun-if-env-changed=GRAPHITE_GIT_COMMIT_BRANCH\");\n\tprintln!(\"cargo:rerun-if-env-changed=GITHUB_HEAD_REF\");\n\n\t// Try to get the commit information from the environment (e.g. set by CI), otherwise fall back to Git commands.\n\tlet commit_date = env_or_else(\"GRAPHITE_GIT_COMMIT_DATE\", || git_or_unknown(&[\"log\", \"-1\", \"--format=%cI\"]));\n\tlet commit_hash = env_or_else(\"GRAPHITE_GIT_COMMIT_HASH\", || git_or_unknown(&[\"rev-parse\", \"HEAD\"]));\n\tlet commit_branch = env_or_else(\"GRAPHITE_GIT_COMMIT_BRANCH\", || {\n\t\tlet gh = env::var(\"GITHUB_HEAD_REF\").unwrap_or_default();\n\t\tif !gh.trim().is_empty() {\n\t\t\tgh.trim().to_string()\n\t\t} else {\n\t\t\tgit(&[\"rev-parse\", \"--abbrev-ref\", \"HEAD\"]).unwrap_or_default()\n\t\t}\n\t});\n\n\t// Instruct Cargo to set environment variables for compile time.\n\t// They are accessed with the `env!(\"GRAPHITE_*\")` macro in the codebase.\n\tprintln!(\"cargo:rustc-env=GRAPHITE_RELEASE_SERIES={GRAPHITE_RELEASE_SERIES}\");\n\tif !commit_branch.is_empty() {\n\t\tprintln!(\"cargo:rustc-env=GRAPHITE_GIT_COMMIT_BRANCH={commit_branch}\");\n\t}\n\tprintln!(\"cargo:rustc-env=GRAPHITE_GIT_COMMIT_HASH={commit_hash}\");\n\tprintln!(\"cargo:rustc-env=GRAPHITE_GIT_COMMIT_DATE={commit_date}\");\n}\n\n/// Get an environment variable, or if it is not set or empty, use the provided fallback function. Returns a string with trimmed whitespace.\nfn env_or_else(key: &str, fallback: impl FnOnce() -> String) -> String {\n\tmatch env::var(key) {\n\t\tOk(v) if !v.trim().is_empty() => v.trim().to_string(),\n\t\t_ => fallback().trim().to_string(),\n\t}\n}\n\n/// Execute a Git command to obtain its output. Return \"unknown\" if it fails for any of the possible reasons.\nfn git_or_unknown(args: &[&str]) -> String {\n\tgit(args).unwrap_or_else(|| \"unknown\".to_string())\n}\n\n/// Run a git command and capture trimmed stdout.\n/// Returns None if git is missing, exits with error, or stdout is empty/non-UTF8.\nfn git(args: &[&str]) -> Option<String> {\n\tlet output = Command::new(\"git\").args(args).output().ok()?;\n\tif !output.status.success() {\n\t\treturn None;\n\t}\n\tlet s = String::from_utf8(output.stdout).ok()?;\n\tlet t = s.trim();\n\tif t.is_empty() { None } else { Some(t.to_string()) }\n}\n"
  },
  {
    "path": "editor/src/application.rs",
    "content": "use crate::dispatcher::Dispatcher;\nuse crate::messages::prelude::*;\npub use graphene_std::uuid::*;\nuse std::sync::OnceLock;\n\npub struct Editor {\n\tpub dispatcher: Dispatcher,\n}\n\nimpl Editor {\n\tpub fn new(environment: Environment, uuid_random_seed: u64) -> Self {\n\t\tENVIRONMENT.set(environment).expect(\"Editor shoud only be initialized once\");\n\t\tgraphene_std::uuid::set_uuid_seed(uuid_random_seed);\n\n\t\tSelf { dispatcher: Dispatcher::new() }\n\t}\n\n\t#[cfg(test)]\n\tpub(crate) fn new_local_executor() -> (Self, crate::node_graph_executor::NodeRuntime) {\n\t\tlet _ = ENVIRONMENT.set(*Editor::environment());\n\t\tgraphene_std::uuid::set_uuid_seed(0);\n\n\t\tlet (runtime, executor) = crate::node_graph_executor::NodeGraphExecutor::new_with_local_runtime();\n\t\tlet editor = Self {\n\t\t\tdispatcher: Dispatcher::with_executor(executor),\n\t\t};\n\n\t\t(editor, runtime)\n\t}\n\n\tpub fn handle_message<T: Into<Message>>(&mut self, message: T) -> Vec<FrontendMessage> {\n\t\tself.dispatcher.handle_message(message, true);\n\n\t\tstd::mem::take(&mut self.dispatcher.responses)\n\t}\n\n\tpub fn poll_node_graph_evaluation(&mut self, responses: &mut VecDeque<Message>) -> Result<(), String> {\n\t\tself.dispatcher.poll_node_graph_evaluation(responses)\n\t}\n}\n\nstatic ENVIRONMENT: OnceLock<Environment> = OnceLock::new();\nimpl Editor {\n\t#[cfg(not(test))]\n\tpub fn environment() -> &'static Environment {\n\t\tENVIRONMENT.get().expect(\"Editor environment accessed before initialization\")\n\t}\n\n\t#[cfg(test)]\n\tpub fn environment() -> &'static Environment {\n\t\t&Environment {\n\t\t\tplatform: Platform::Desktop,\n\t\t\thost: Host::Linux,\n\t\t}\n\t}\n}\n\n#[derive(Clone, Copy, Debug)]\npub struct Environment {\n\tpub platform: Platform,\n\tpub host: Host,\n}\n#[derive(Clone, Copy, Debug)]\npub enum Platform {\n\tDesktop,\n\tWeb,\n}\n#[derive(Clone, Copy, Debug)]\npub enum Host {\n\tWindows,\n\tMac,\n\tLinux,\n}\nimpl Environment {\n\tpub fn is_desktop(&self) -> bool {\n\t\tmatches!(self.platform, Platform::Desktop)\n\t}\n\tpub fn is_web(&self) -> bool {\n\t\tmatches!(self.platform, Platform::Web)\n\t}\n\tpub fn is_windows(&self) -> bool {\n\t\tmatches!(self.host, Host::Windows)\n\t}\n\tpub fn is_mac(&self) -> bool {\n\t\tmatches!(self.host, Host::Mac)\n\t}\n\tpub fn is_linux(&self) -> bool {\n\t\tmatches!(self.host, Host::Linux)\n\t}\n}\n\npub const GRAPHITE_RELEASE_SERIES: &str = env!(\"GRAPHITE_RELEASE_SERIES\");\npub const GRAPHITE_GIT_COMMIT_BRANCH: Option<&str> = option_env!(\"GRAPHITE_GIT_COMMIT_BRANCH\");\npub const GRAPHITE_GIT_COMMIT_HASH: &str = env!(\"GRAPHITE_GIT_COMMIT_HASH\");\npub const GRAPHITE_GIT_COMMIT_DATE: &str = env!(\"GRAPHITE_GIT_COMMIT_DATE\");\n\npub fn commit_info_localized(localized_commit_date: &str) -> String {\n\tlet mut info = String::new();\n\tinfo.push_str(&format!(\"Release Series: {GRAPHITE_RELEASE_SERIES}\\n\"));\n\tif let Some(branch) = GRAPHITE_GIT_COMMIT_BRANCH {\n\t\tinfo.push_str(&format!(\"Branch: {branch}\\n\"));\n\t}\n\tinfo.push_str(&format!(\"Commit: {}\\n\", GRAPHITE_GIT_COMMIT_HASH.get(..8).unwrap_or(GRAPHITE_GIT_COMMIT_HASH)));\n\tinfo.push_str(localized_commit_date);\n\tinfo\n}\n"
  },
  {
    "path": "editor/src/consts.rs",
    "content": "// GRAPH\npub const GRID_SIZE: u32 = 24;\npub const EXPORTS_TO_TOP_EDGE_PIXEL_GAP: u32 = 72;\npub const EXPORTS_TO_RIGHT_EDGE_PIXEL_GAP: u32 = 120;\npub const IMPORTS_TO_TOP_EDGE_PIXEL_GAP: u32 = 72;\npub const IMPORTS_TO_LEFT_EDGE_PIXEL_GAP: u32 = 120;\n\n// VIEWPORT\npub const VIEWPORT_ZOOM_WHEEL_RATE: f64 = (1. / 600.) * 3.;\npub const VIEWPORT_ZOOM_MOUSE_RATE: f64 = 1. / 400.;\npub const VIEWPORT_ZOOM_SCALE_MIN: f64 = 0.000_000_1;\npub const VIEWPORT_ZOOM_SCALE_MAX: f64 = 10_000.;\npub const VIEWPORT_ZOOM_MIN_FRACTION_COVER: f64 = 0.01;\npub const VIEWPORT_ZOOM_LEVELS: [f64; 74] = [\n\t0.0001, 0.000125, 0.00016, 0.0002, 0.00025, 0.00032, 0.0004, 0.0005, 0.00064, 0.0008, 0.001, 0.0016, 0.002, 0.0025, 0.0032, 0.004, 0.005, 0.0064, 0.008, 0.01, 0.01125, 0.015, 0.02, 0.025, 0.03,\n\t0.04, 0.05, 0.06, 0.08, 0.1, 0.125, 0.15, 0.2, 0.25, 0.33333333, 0.4, 0.5, 0.66666666, 0.8, 1., 1.25, 1.6, 2., 2.5, 3.2, 4., 5., 6.4, 8., 10., 12.5, 16., 20., 25., 32., 40., 50., 64., 80., 100.,\n\t128., 160., 200., 256., 320., 400., 512., 640., 800., 1024., 1280., 1600., 2048., 2560.,\n];\n/// Higher values create a steeper curve (a faster zoom rate change)\npub const VIEWPORT_ZOOM_WHEEL_RATE_CHANGE: f64 = 3.;\n\n/// Helps push values that end in approximately half, plus or minus some floating point imprecision, towards the same side of the round() function.\npub const VIEWPORT_GRID_ROUNDING_BIAS: f64 = 0.002;\n\npub const VIEWPORT_SCROLL_RATE: f64 = 0.6;\n\npub const VIEWPORT_ROTATE_SNAP_INTERVAL: f64 = 15.;\n\npub const VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR: f64 = 0.95;\n\npub const DRAG_BEYOND_VIEWPORT_MAX_OVEREXTENSION_PIXELS: f64 = 50.;\npub const DRAG_BEYOND_VIEWPORT_SPEED_FACTOR: f64 = 20.;\n\n// SNAPPING POINT\npub const SNAP_POINT_TOLERANCE: f64 = 5.;\n/// These are layers whose bounding boxes are used for alignment.\npub const MAX_ALIGNMENT_CANDIDATES: usize = 100;\n/// These are layers that are used for the layer snapper.\npub const MAX_SNAP_CANDIDATES: usize = 10;\n/// These are points (anchors and bounding box corners etc.) in the layer snapper.\npub const MAX_LAYER_SNAP_POINTS: usize = 100;\n\npub const DRAG_THRESHOLD: f64 = 1.;\n\n// TRANSFORMING LAYER\npub const ROTATE_INCREMENT: f64 = 15.;\npub const SCALE_INCREMENT: f64 = 0.1;\npub const SLOWING_DIVISOR: f64 = 10.;\npub const NUDGE_AMOUNT: f64 = 1.;\npub const BIG_NUDGE_AMOUNT: f64 = 10.;\n\n// TOOLS\npub const DEFAULT_STROKE_WIDTH: f64 = 2.;\n\n// SELECT TOOL\npub const SELECTION_TOLERANCE: f64 = 5.;\npub const DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD: f64 = 15.;\npub const SELECTION_DRAG_ANGLE: f64 = 90.;\npub const LAYER_ORIGIN_CROSS_DIAMETER: f64 = 10.;\npub const LAYER_ORIGIN_CROSS_THICKNESS: f64 = 1.;\n\n// PIVOT\npub const PIVOT_CROSSHAIR_THICKNESS: f64 = 1.;\npub const PIVOT_CROSSHAIR_LENGTH: f64 = 9.;\npub const PIVOT_DIAMETER: f64 = 5.;\npub const DOWEL_PIN_RADIUS: f64 = 4.;\n\n// COMPASS ROSE\npub const COMPASS_ROSE_RING_INNER_DIAMETER: f64 = 13.;\npub const COMPASS_ROSE_MAIN_RING_DIAMETER: f64 = 15.;\npub const COMPASS_ROSE_HOVER_RING_DIAMETER: f64 = 23.;\npub const COMPASS_ROSE_ARROW_SIZE: f64 = 5.;\n// Angle to either side of the compass arrows where they are targetted by the cursor (in degrees, must be less than 45°)\npub const COMPASS_ROSE_ARROW_CLICK_TARGET_ANGLE: f64 = 20.;\n\n// TRANSFORM OVERLAY\npub const ANGLE_MEASURE_RADIUS_FACTOR: f64 = 0.04;\npub const ARC_MEASURE_RADIUS_FACTOR_RANGE: (f64, f64) = (0.05, 0.15);\n\n// TRANSFORM CAGE\npub const RESIZE_HANDLE_SIZE: f64 = 6.;\npub const BOUNDS_SELECT_THRESHOLD: f64 = 10.;\npub const BOUNDS_ROTATE_THRESHOLD: f64 = 20.;\npub const MIN_LENGTH_FOR_MIDPOINT_VISIBILITY: f64 = 20.;\npub const MIN_LENGTH_FOR_CORNERS_VISIBILITY: f64 = 12.;\n/// The width or height that the transform cage needs to be (at least) before the corner resize handle click targets take up their full surroundings. Otherwise, when less than this value, the interior edge resize handle takes precedence so the corner handles don't eat into the edge area, making it harder to resize the cage from its edges.\npub const MIN_LENGTH_FOR_EDGE_RESIZE_PRIORITY_OVER_CORNERS: f64 = 10.;\n/// When the width or height of the transform cage is less than this value, only the exterior of the bounding box will act as a click target for resizing.\npub const MIN_LENGTH_FOR_RESIZE_TO_INCLUDE_INTERIOR: f64 = 40.;\n/// When dragging the edge of a cage with Alt, it centers around the pivot.\n/// However if the pivot is on or near the same edge you are dragging, we should avoid scaling by a massive factor caused by the small denominator.\n///\n/// The motion of the user's cursor by an `x` pixel offset results in `x * scale_factor` pixels of offset on the other side.\npub const MAXIMUM_ALT_SCALE_FACTOR: f64 = 25.;\n/// The width or height that the transform cage needs before it is considered to have no width or height.\npub const MAX_LENGTH_FOR_NO_WIDTH_OR_HEIGHT: f64 = 1e-4;\n\n// SKEW TRIANGLES\npub const SKEW_TRIANGLE_SIZE: f64 = 7.;\npub const SKEW_TRIANGLE_OFFSET: f64 = 4.;\npub const MIN_LENGTH_FOR_SKEW_TRIANGLE_VISIBILITY: f64 = 48.;\n\n// PATH TOOL\npub const MANIPULATOR_GROUP_MARKER_SIZE: f64 = 6.;\npub const SELECTION_THRESHOLD: f64 = 10.;\npub const DRILL_THROUGH_THRESHOLD: f64 = 10.;\npub const HIDE_HANDLE_DISTANCE: f64 = 3.;\npub const HANDLE_ROTATE_SNAP_ANGLE: f64 = 15.;\npub const SEGMENT_INSERTION_DISTANCE: f64 = 5.;\npub const SEGMENT_OVERLAY_SIZE: f64 = 10.;\npub const SEGMENT_SELECTED_THICKNESS: f64 = 3.;\npub const HANDLE_LENGTH_FACTOR: f64 = 0.5;\n\n// GRADIENT TOOL\npub const GRADIENT_MIDPOINT_DIAMOND_RADIUS: f64 = 4.;\npub const GRADIENT_MIDPOINT_MIN: f64 = 0.01;\npub const GRADIENT_MIDPOINT_MAX: f64 = 0.99;\npub const GRADIENT_STOP_MIN_VIEWPORT_GAP: f64 = 10.;\n\n// PEN TOOL\npub const CREATE_CURVE_THRESHOLD: f64 = 5.;\n\n// SPLINE TOOL\npub const PATH_JOIN_THRESHOLD: f64 = 5.;\n\n// LINE TOOL\npub const LINE_ROTATE_SNAP_ANGLE: f64 = 15.;\n\n// BRUSH TOOL\npub const BRUSH_SIZE_CHANGE_KEYBOARD: f64 = 5.;\npub const DEFAULT_BRUSH_SIZE: f64 = 20.;\n\n// EYEDROPPER TOOL\npub const EYEDROPPER_PREVIEW_AREA_RESOLUTION: u32 = 11;\n\n// GIZMOS\npub const POINT_RADIUS_HANDLE_SNAP_THRESHOLD: f64 = 8.;\npub const POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD: f64 = 7.9;\npub const NUMBER_OF_POINTS_DIAL_SPOKE_EXTENSION: f64 = 1.2;\npub const NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH: f64 = 10.;\npub const ARC_SNAP_THRESHOLD: f64 = 5.;\npub const ARC_SWEEP_GIZMO_RADIUS: f64 = 14.;\npub const ARC_SWEEP_GIZMO_TEXT_HEIGHT: f64 = 12.;\npub const GIZMO_HIDE_THRESHOLD: f64 = 20.;\npub const GRID_ROW_COLUMN_GIZMO_OFFSET: f64 = 15.;\n\n// SCROLLBARS\npub const SCROLLBAR_SPACING: f64 = 0.1;\npub const ASYMPTOTIC_EFFECT: f64 = 0.5;\npub const SCALE_EFFECT: f64 = 0.5;\n\n// COLORS\npub const COLOR_OVERLAY_BLUE: &str = \"#00a8ff\";\npub const COLOR_OVERLAY_BLUE_50: &str = \"#00a8ff80\";\npub const COLOR_OVERLAY_BLUE_25: &str = \"#00a8ff40\";\npub const COLOR_OVERLAY_BLUE_05: &str = \"#00a8ff0d\";\npub const COLOR_OVERLAY_YELLOW: &str = \"#ffc848\";\npub const COLOR_OVERLAY_YELLOW_DULL: &str = \"#d7ba8b\";\npub const COLOR_OVERLAY_GREEN: &str = \"#63ce63\";\npub const COLOR_OVERLAY_GREEN_25: &str = \"#63ce6340\";\npub const COLOR_OVERLAY_RED: &str = \"#ef5454\";\npub const COLOR_OVERLAY_RED_25: &str = \"#ef545440\";\npub const COLOR_OVERLAY_GRAY: &str = \"#cccccc\";\npub const COLOR_OVERLAY_GRAY_25: &str = \"#cccccc40\";\npub const COLOR_OVERLAY_WHITE: &str = \"#ffffff\";\npub const COLOR_OVERLAY_WHITE_05: &str = \"#ffffff0d\";\npub const COLOR_OVERLAY_BLACK: &str = \"#000000\";\npub const COLOR_OVERLAY_BLACK_75: &str = \"#000000bf\";\n\n// DOCUMENT\npub const FILE_EXTENSION: &str = \"graphite\";\npub const DEFAULT_DOCUMENT_NAME: &str = \"Untitled Document\";\npub const MAX_UNDO_HISTORY_LEN: usize = 100; // TODO: Add this to user preferences\npub const AUTO_SAVE_TIMEOUT_SECONDS: u64 = 1;\n\n// INPUT\npub const DOUBLE_CLICK_MILLISECONDS: u64 = 500;\n\n// UI\npub const UI_SCALE_DEFAULT: f64 = 1.;\npub const UI_SCALE_MIN: f64 = 0.5;\npub const UI_SCALE_MAX: f64 = 3.;\n"
  },
  {
    "path": "editor/src/dispatcher.rs",
    "content": "use crate::messages::debug::utility_types::MessageLoggingVerbosity;\nuse crate::messages::defer::DeferMessageContext;\nuse crate::messages::dialog::DialogMessageContext;\nuse crate::messages::layout::layout_message_handler::LayoutMessageContext;\nuse crate::messages::preferences::preferences_message_handler::PreferencesMessageContext;\nuse crate::messages::prelude::*;\nuse crate::messages::tool::common_functionality::utility_functions::make_path_editable_is_allowed;\n\n#[derive(Debug, Default)]\npub struct Dispatcher {\n\tmessage_queues: Vec<VecDeque<Message>>,\n\tpub responses: Vec<FrontendMessage>,\n\tpub frontend_update_messages: Vec<Message>,\n\tpub message_handlers: DispatcherMessageHandlers,\n}\n\n#[derive(Debug, Default)]\npub struct DispatcherMessageHandlers {\n\tanimation_message_handler: AnimationMessageHandler,\n\tapp_window_message_handler: AppWindowMessageHandler,\n\tbroadcast_message_handler: BroadcastMessageHandler,\n\tclipboard_message_handler: ClipboardMessageHandler,\n\tdebug_message_handler: DebugMessageHandler,\n\tdefer_message_handler: DeferMessageHandler,\n\tdialog_message_handler: DialogMessageHandler,\n\tinput_preprocessor_message_handler: InputPreprocessorMessageHandler,\n\tkey_mapping_message_handler: KeyMappingMessageHandler,\n\tlayout_message_handler: LayoutMessageHandler,\n\tmenu_bar_message_handler: MenuBarMessageHandler,\n\tpub(crate) portfolio_message_handler: PortfolioMessageHandler,\n\tpreferences_message_handler: PreferencesMessageHandler,\n\ttool_message_handler: ToolMessageHandler,\n\tviewport_message_handler: ViewportMessageHandler,\n}\n\nimpl DispatcherMessageHandlers {\n\tpub fn with_executor(executor: crate::node_graph_executor::NodeGraphExecutor) -> Self {\n\t\tSelf {\n\t\t\tportfolio_message_handler: PortfolioMessageHandler::with_executor(executor),\n\t\t\t..Default::default()\n\t\t}\n\t}\n}\n\n/// For optimization, these are messages guaranteed to be redundant when repeated.\n/// The last occurrence of the message in the message queue is sufficient to ensure correct behavior.\n/// In addition, these messages do not change any state in the backend (aside from caches).\nconst SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[\n\tMessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::DocumentStructureChanged)),\n\tMessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::NodeGraph(\n\t\tNodeGraphMessageDiscriminant::RunDocumentGraph,\n\t))),\n\tMessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::SubmitActiveGraphRender),\n\tMessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::SubmitEyedropperPreviewRender),\n\tMessageDiscriminant::Frontend(FrontendMessageDiscriminant::TriggerFontDataLoad),\n\tMessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateUIScale),\n];\n/// Since we don't need to update the frontend multiple times per frame,\n/// we have a set of messages which we will buffer until the next frame is requested.\nconst FRONTEND_UPDATE_MESSAGES: &[MessageDiscriminant] = &[\n\tMessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::PropertiesPanel(\n\t\tPropertiesPanelMessageDiscriminant::Refresh,\n\t))),\n\tMessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::UpdateDocumentWidgets),\n\tMessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::Overlays(OverlaysMessageDiscriminant::Draw))),\n\tMessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::RenderRulers)),\n\tMessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::RenderScrollbars)),\n\tMessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateDocumentLayerStructure),\n];\nconst DEBUG_MESSAGE_BLOCK_LIST: &[MessageDiscriminant] = &[\n\tMessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerEvent(EventMessageDiscriminant::AnimationFrame)),\n\tMessageDiscriminant::Animation(AnimationMessageDiscriminant::IncrementFrameCounter),\n\tMessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::AutoSaveAllDocuments),\n];\n// TODO: Find a way to combine these with the list above. We use strings for now since these are the standard variant names used by multiple messages. But having these also type-checked would be best.\nconst DEBUG_MESSAGE_ENDING_BLOCK_LIST: &[&str] = &[\"PointerMove\", \"PointerOutsideViewport\", \"Overlays\", \"Draw\", \"CurrentTime\", \"Time\"];\n\nimpl Dispatcher {\n\tpub fn new() -> Self {\n\t\tSelf::default()\n\t}\n\n\tpub fn with_executor(executor: crate::node_graph_executor::NodeGraphExecutor) -> Self {\n\t\tSelf {\n\t\t\tmessage_handlers: DispatcherMessageHandlers::with_executor(executor),\n\t\t\t..Default::default()\n\t\t}\n\t}\n\n\t// If the deepest queues (higher index in queues list) are now empty (after being popped from) then remove them\n\tfn cleanup_queues(&mut self, leave_last: bool) {\n\t\twhile self.message_queues.last().filter(|queue| queue.is_empty()).is_some() {\n\t\t\tif leave_last && self.message_queues.len() == 1 {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tself.message_queues.pop();\n\t\t}\n\t}\n\n\t/// Add a message to a queue so that it can be executed.\n\t/// If `process_after_all_current` is set, all currently queued messages (including children) will be processed first.\n\t/// If not set, it (and its children) will be processed as soon as possible.\n\tpub fn schedule_execution(message_queues: &mut Vec<VecDeque<Message>>, process_after_all_current: bool, messages: impl IntoIterator<Item = Message>) {\n\t\tmatch message_queues.first_mut() {\n\t\t\t// If there are currently messages being processed and we are processing after them, add to the end of the first queue\n\t\t\tSome(queue) if process_after_all_current => queue.extend(messages),\n\t\t\t// In all other cases, make a new inner queue and add our message there\n\t\t\t_ => message_queues.push(VecDeque::from_iter(messages)),\n\t\t}\n\t}\n\n\tpub fn handle_message<T: Into<Message>>(&mut self, message: T, process_after_all_current: bool) {\n\t\tlet message = message.into();\n\n\t\t// If we are not maintaining the buffer, simply add to the current queue\n\t\tSelf::schedule_execution(&mut self.message_queues, process_after_all_current, [message]);\n\n\t\twhile let Some(message) = self.message_queues.last_mut().and_then(VecDeque::pop_front) {\n\t\t\t// Skip processing of this message if it will be processed later (at the end of the shallowest level queue)\n\t\t\tif FRONTEND_UPDATE_MESSAGES.contains(&message.to_discriminant()) {\n\t\t\t\tlet already_in_queue = self.message_queues.first().is_some_and(|queue| queue.contains(&message));\n\t\t\t\tif already_in_queue {\n\t\t\t\t\tself.cleanup_queues(false);\n\t\t\t\t\tcontinue;\n\t\t\t\t} else if self.message_queues.len() > 1 {\n\t\t\t\t\tif !self.frontend_update_messages.contains(&message) {\n\t\t\t\t\t\tself.frontend_update_messages.push(message);\n\t\t\t\t\t}\n\t\t\t\t\tself.cleanup_queues(false);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif SIDE_EFFECT_FREE_MESSAGES.contains(&message.to_discriminant()) {\n\t\t\t\tlet already_in_queue = self.message_queues.first().filter(|queue| queue.contains(&message)).is_some();\n\t\t\t\tif already_in_queue {\n\t\t\t\t\tself.log_deferred_message(&message, &self.message_queues, self.message_handlers.debug_message_handler.message_logging_verbosity);\n\t\t\t\t\tself.cleanup_queues(false);\n\t\t\t\t\tcontinue;\n\t\t\t\t} else if self.message_queues.len() > 1 {\n\t\t\t\t\tself.log_deferred_message(&message, &self.message_queues, self.message_handlers.debug_message_handler.message_logging_verbosity);\n\t\t\t\t\tself.cleanup_queues(true);\n\t\t\t\t\tself.message_queues[0].add(message);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Print the message at a verbosity level of `info`\n\t\t\tself.log_message(&message, &self.message_queues, self.message_handlers.debug_message_handler.message_logging_verbosity);\n\n\t\t\t// Create a new queue for the child messages\n\t\t\tlet mut queue = VecDeque::new();\n\n\t\t\t// Process the action by forwarding it to the relevant message handler, or saving the FrontendMessage to be sent to the frontend\n\t\t\tmatch message {\n\t\t\t\tMessage::Animation(message) => {\n\t\t\t\t\tif let AnimationMessage::IncrementFrameCounter = &message {\n\t\t\t\t\t\tself.message_queues[0].extend(self.frontend_update_messages.drain(..));\n\t\t\t\t\t}\n\t\t\t\t\tself.message_handlers.animation_message_handler.process_message(message, &mut queue, ());\n\t\t\t\t}\n\t\t\t\tMessage::AppWindow(message) => {\n\t\t\t\t\tself.message_handlers.app_window_message_handler.process_message(message, &mut queue, ());\n\t\t\t\t}\n\t\t\t\tMessage::Broadcast(message) => self.message_handlers.broadcast_message_handler.process_message(message, &mut queue, ()),\n\t\t\t\tMessage::Clipboard(message) => self.message_handlers.clipboard_message_handler.process_message(message, &mut queue, ()),\n\t\t\t\tMessage::Debug(message) => {\n\t\t\t\t\tself.message_handlers.debug_message_handler.process_message(message, &mut queue, ());\n\t\t\t\t}\n\t\t\t\tMessage::Defer(message) => {\n\t\t\t\t\tlet context = DeferMessageContext {\n\t\t\t\t\t\tportfolio: &self.message_handlers.portfolio_message_handler,\n\t\t\t\t\t};\n\t\t\t\t\tself.message_handlers.defer_message_handler.process_message(message, &mut queue, context);\n\t\t\t\t}\n\t\t\t\tMessage::Dialog(message) => {\n\t\t\t\t\tlet context = DialogMessageContext {\n\t\t\t\t\t\tportfolio: &self.message_handlers.portfolio_message_handler,\n\t\t\t\t\t\tpreferences: &self.message_handlers.preferences_message_handler,\n\t\t\t\t\t};\n\t\t\t\t\tself.message_handlers.dialog_message_handler.process_message(message, &mut queue, context);\n\t\t\t\t}\n\t\t\t\tMessage::Frontend(message) => {\n\t\t\t\t\t// Handle these messages immediately by returning early\n\t\t\t\t\tif let FrontendMessage::TriggerFontDataLoad { .. } | FrontendMessage::TriggerFontCatalogLoad = message {\n\t\t\t\t\t\tself.responses.push(message);\n\t\t\t\t\t\tself.cleanup_queues(false);\n\n\t\t\t\t\t\t// Return early to avoid running the code after the match block\n\t\t\t\t\t\treturn;\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// `FrontendMessage`s are saved and will be sent to the frontend after the message queue is done being processed\n\t\t\t\t\t\tself.responses.push(message);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tMessage::InputPreprocessor(message) => {\n\t\t\t\t\tself.message_handlers.input_preprocessor_message_handler.process_message(\n\t\t\t\t\t\tmessage,\n\t\t\t\t\t\t&mut queue,\n\t\t\t\t\t\tInputPreprocessorMessageContext {\n\t\t\t\t\t\t\tviewport: &self.message_handlers.viewport_message_handler,\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tMessage::KeyMapping(message) => {\n\t\t\t\t\tlet input = &self.message_handlers.input_preprocessor_message_handler;\n\t\t\t\t\tlet actions = self.collect_actions();\n\n\t\t\t\t\tself.message_handlers\n\t\t\t\t\t\t.key_mapping_message_handler\n\t\t\t\t\t\t.process_message(message, &mut queue, KeyMappingMessageContext { input, actions });\n\t\t\t\t}\n\t\t\t\tMessage::Layout(message) => {\n\t\t\t\t\tlet action_input_mapping = &|action_to_find: &MessageDiscriminant| self.message_handlers.key_mapping_message_handler.action_input_mapping(action_to_find);\n\t\t\t\t\tlet context = LayoutMessageContext { action_input_mapping };\n\n\t\t\t\t\tself.message_handlers.layout_message_handler.process_message(message, &mut queue, context);\n\t\t\t\t}\n\t\t\t\tMessage::Portfolio(message) => {\n\t\t\t\t\tself.message_handlers.portfolio_message_handler.process_message(\n\t\t\t\t\t\tmessage,\n\t\t\t\t\t\t&mut queue,\n\t\t\t\t\t\tPortfolioMessageContext {\n\t\t\t\t\t\t\tipp: &self.message_handlers.input_preprocessor_message_handler,\n\t\t\t\t\t\t\tpreferences: &self.message_handlers.preferences_message_handler,\n\t\t\t\t\t\t\tcurrent_tool: &self.message_handlers.tool_message_handler.tool_state.tool_data.active_tool_type,\n\t\t\t\t\t\t\treset_node_definitions_on_open: self.message_handlers.portfolio_message_handler.reset_node_definitions_on_open,\n\t\t\t\t\t\t\ttiming_information: self.message_handlers.animation_message_handler.timing_information(),\n\t\t\t\t\t\t\tanimation: &self.message_handlers.animation_message_handler,\n\t\t\t\t\t\t\tviewport: &self.message_handlers.viewport_message_handler,\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tMessage::MenuBar(message) => {\n\t\t\t\t\tlet menu_bar_message_handler = &mut self.message_handlers.menu_bar_message_handler;\n\n\t\t\t\t\tmenu_bar_message_handler.focus_document = self.message_handlers.portfolio_message_handler.focus_document;\n\t\t\t\t\tmenu_bar_message_handler.data_panel_open = self.message_handlers.portfolio_message_handler.data_panel_open;\n\t\t\t\t\tmenu_bar_message_handler.layers_panel_open = self.message_handlers.portfolio_message_handler.layers_panel_open;\n\t\t\t\t\tmenu_bar_message_handler.properties_panel_open = self.message_handlers.portfolio_message_handler.properties_panel_open;\n\t\t\t\t\tmenu_bar_message_handler.message_logging_verbosity = self.message_handlers.debug_message_handler.message_logging_verbosity;\n\t\t\t\t\tmenu_bar_message_handler.reset_node_definitions_on_open = self.message_handlers.portfolio_message_handler.reset_node_definitions_on_open;\n\n\t\t\t\t\tif let Some(document) = self\n\t\t\t\t\t\t.message_handlers\n\t\t\t\t\t\t.portfolio_message_handler\n\t\t\t\t\t\t.active_document_id\n\t\t\t\t\t\t.and_then(|document_id| self.message_handlers.portfolio_message_handler.documents.get_mut(&document_id))\n\t\t\t\t\t{\n\t\t\t\t\t\tlet selected_nodes = document.network_interface.selected_nodes();\n\t\t\t\t\t\tlet metadata = &document.network_interface.document_network_metadata().persistent_metadata;\n\n\t\t\t\t\t\tmenu_bar_message_handler.has_active_document = true;\n\t\t\t\t\t\tmenu_bar_message_handler.canvas_tilted = document.document_ptz.tilt() != 0.;\n\t\t\t\t\t\tmenu_bar_message_handler.canvas_flipped = document.document_ptz.flip;\n\t\t\t\t\t\tmenu_bar_message_handler.rulers_visible = document.rulers_visible;\n\t\t\t\t\t\tmenu_bar_message_handler.node_graph_open = document.is_graph_overlay_open();\n\t\t\t\t\t\tmenu_bar_message_handler.has_selected_nodes = selected_nodes.selected_nodes().next().is_some();\n\t\t\t\t\t\tmenu_bar_message_handler.has_selected_layers = selected_nodes.selected_visible_layers(&document.network_interface).next().is_some();\n\t\t\t\t\t\tmenu_bar_message_handler.has_selection_history = (!metadata.selection_undo_history.is_empty(), !metadata.selection_redo_history.is_empty());\n\t\t\t\t\t\tmenu_bar_message_handler.make_path_editable_is_allowed = make_path_editable_is_allowed(&mut document.network_interface).is_some();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tmenu_bar_message_handler.has_active_document = false;\n\t\t\t\t\t\tmenu_bar_message_handler.canvas_tilted = false;\n\t\t\t\t\t\tmenu_bar_message_handler.canvas_flipped = false;\n\t\t\t\t\t\tmenu_bar_message_handler.rulers_visible = false;\n\t\t\t\t\t\tmenu_bar_message_handler.node_graph_open = false;\n\t\t\t\t\t\tmenu_bar_message_handler.has_selected_nodes = false;\n\t\t\t\t\t\tmenu_bar_message_handler.has_selected_layers = false;\n\t\t\t\t\t\tmenu_bar_message_handler.has_selection_history = (false, false);\n\t\t\t\t\t\tmenu_bar_message_handler.make_path_editable_is_allowed = false;\n\t\t\t\t\t}\n\n\t\t\t\t\tmenu_bar_message_handler.process_message(message, &mut queue, ());\n\t\t\t\t}\n\t\t\t\tMessage::Preferences(message) => {\n\t\t\t\t\tlet context = PreferencesMessageContext {\n\t\t\t\t\t\ttool_message_handler: &self.message_handlers.tool_message_handler,\n\t\t\t\t\t};\n\n\t\t\t\t\tself.message_handlers.preferences_message_handler.process_message(message, &mut queue, context);\n\t\t\t\t}\n\t\t\t\tMessage::Tool(message) => {\n\t\t\t\t\tlet Some(document_id) = self.message_handlers.portfolio_message_handler.active_document_id() else {\n\t\t\t\t\t\twarn!(\"Called ToolMessage without an active document.\\nGot {message:?}\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t};\n\t\t\t\t\tlet Some(document) = self.message_handlers.portfolio_message_handler.documents.get_mut(&document_id) else {\n\t\t\t\t\t\twarn!(\"Called ToolMessage with an invalid active document.\\nGot {message:?}\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t};\n\n\t\t\t\t\tlet context = ToolMessageContext {\n\t\t\t\t\t\tdocument_id,\n\t\t\t\t\t\tdocument,\n\t\t\t\t\t\tinput: &self.message_handlers.input_preprocessor_message_handler,\n\t\t\t\t\t\tpersistent_data: &self.message_handlers.portfolio_message_handler.persistent_data,\n\t\t\t\t\t\tnode_graph: &self.message_handlers.portfolio_message_handler.executor,\n\t\t\t\t\t\tpreferences: &self.message_handlers.preferences_message_handler,\n\t\t\t\t\t\tviewport: &self.message_handlers.viewport_message_handler,\n\t\t\t\t\t};\n\n\t\t\t\t\tself.message_handlers.tool_message_handler.process_message(message, &mut queue, context);\n\t\t\t\t}\n\t\t\t\tMessage::Viewport(message) => {\n\t\t\t\t\tself.message_handlers.viewport_message_handler.process_message(message, &mut queue, ());\n\t\t\t\t}\n\t\t\t\tMessage::NoOp => {}\n\t\t\t\tMessage::Batched { messages } => {\n\t\t\t\t\tmessages.into_iter().for_each(|message| self.handle_message(message, false));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If there are child messages, append the queue to the list of queues\n\t\t\tif !queue.is_empty() {\n\t\t\t\tself.message_queues.push(queue);\n\t\t\t}\n\n\t\t\tself.cleanup_queues(false);\n\t\t}\n\t}\n\n\tpub fn collect_actions(&self) -> ActionList {\n\t\t// TODO: Reduce the number of heap allocations\n\t\tlet mut list = Vec::new();\n\t\tlist.extend(self.message_handlers.app_window_message_handler.actions());\n\t\tlist.extend(self.message_handlers.clipboard_message_handler.actions());\n\t\tlist.extend(self.message_handlers.dialog_message_handler.actions());\n\t\tlist.extend(self.message_handlers.animation_message_handler.actions());\n\t\tlist.extend(self.message_handlers.input_preprocessor_message_handler.actions());\n\t\tlist.extend(self.message_handlers.key_mapping_message_handler.actions());\n\t\tlist.extend(self.message_handlers.debug_message_handler.actions());\n\t\tif let Some(document) = self.message_handlers.portfolio_message_handler.active_document()\n\t\t\t&& !document.graph_view_overlay_open\n\t\t{\n\t\t\tlist.extend(self.message_handlers.tool_message_handler.actions_with_preferences(&self.message_handlers.preferences_message_handler));\n\t\t}\n\t\tlist.extend(self.message_handlers.portfolio_message_handler.actions());\n\t\tlist\n\t}\n\n\tpub fn poll_node_graph_evaluation(&mut self, responses: &mut VecDeque<Message>) -> Result<(), String> {\n\t\tself.message_handlers.portfolio_message_handler.poll_node_graph_evaluation(responses)\n\t}\n\n\t/// Create the tree structure for logging the messages as a tree\n\tfn create_indents(queues: &[VecDeque<Message>]) -> String {\n\t\tString::from_iter(queues.iter().enumerate().skip(1).map(|(index, queue)| {\n\t\t\tif index == queues.len() - 1 {\n\t\t\t\tif queue.is_empty() { \"└── \" } else { \"├── \" }\n\t\t\t} else if queue.is_empty() {\n\t\t\t\t\"   \"\n\t\t\t} else {\n\t\t\t\t\"│    \"\n\t\t\t}\n\t\t}))\n\t}\n\n\t/// Logs a message that is about to be executed, either as a tree\n\t/// with a discriminant or the entire payload (depending on settings)\n\tfn log_message(&self, message: &Message, queues: &[VecDeque<Message>], message_logging_verbosity: MessageLoggingVerbosity) {\n\t\tlet discriminant = MessageDiscriminant::from(message);\n\t\tlet is_blocked =\n\t\t\t|discriminant| DEBUG_MESSAGE_BLOCK_LIST.contains(&discriminant) || DEBUG_MESSAGE_ENDING_BLOCK_LIST.iter().any(|blocked_name| discriminant.local_name().ends_with(blocked_name));\n\t\tlet is_batch_all_blocked = if let Message::Batched { messages } = message {\n\t\t\tmessages.iter().all(|message| is_blocked(MessageDiscriminant::from(message)))\n\t\t} else {\n\t\t\tfalse\n\t\t};\n\n\t\tif !is_blocked(discriminant) && !is_batch_all_blocked {\n\t\t\tmatch message_logging_verbosity {\n\t\t\t\tMessageLoggingVerbosity::Off => {}\n\t\t\t\tMessageLoggingVerbosity::Names => {\n\t\t\t\t\tinfo!(\"{}{:?}\", Self::create_indents(queues), message.to_discriminant());\n\t\t\t\t}\n\t\t\t\tMessageLoggingVerbosity::Contents => {\n\t\t\t\t\tif !(matches!(message, Message::InputPreprocessor(_))) {\n\t\t\t\t\t\tinfo!(\"Message: {}{:?}\", Self::create_indents(queues), message);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/// Logs into the tree that the message is in the side effect free messages and its execution will be deferred\n\tfn log_deferred_message(&self, message: &Message, queues: &[VecDeque<Message>], message_logging_verbosity: MessageLoggingVerbosity) {\n\t\tif let MessageLoggingVerbosity::Names = message_logging_verbosity {\n\t\t\tinfo!(\"{}Deferred \\\"{:?}\\\" because it's a SIDE_EFFECT_FREE_MESSAGE\", Self::create_indents(queues), message.to_discriminant());\n\t\t}\n\t}\n}\n\n#[cfg(test)]\nmod test {\n\tpub use crate::test_utils::test_prelude::*;\n\n\t/// Create an editor with three layers\n\t/// 1. A red rectangle\n\t/// 2. A blue shape\n\t/// 3. A green ellipse\n\tasync fn create_editor_with_three_layers() -> EditorTestUtils {\n\t\tlet mut editor = EditorTestUtils::create();\n\n\t\teditor.new_document().await;\n\n\t\teditor.select_primary_color(Color::RED).await;\n\t\teditor.draw_rect(100., 200., 300., 400.).await;\n\n\t\teditor.select_primary_color(Color::BLUE).await;\n\t\teditor.draw_polygon(10., 1200., 1300., 400.).await;\n\n\t\teditor.select_primary_color(Color::GREEN).await;\n\t\teditor.draw_ellipse(104., 1200., 1300., 400.).await;\n\n\t\teditor\n\t}\n\n\t/// - create rect, shape and ellipse\n\t/// - copy\n\t/// - paste\n\t/// - assert that ellipse was copied\n\t#[tokio::test]\n\tasync fn copy_paste_single_layer() {\n\t\tlet mut editor = create_editor_with_three_layers().await;\n\n\t\tlet layers_before_copy = editor.active_document().metadata().all_layers().collect::<Vec<_>>();\n\t\teditor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::Internal }).await;\n\t\teditor\n\t\t\t.handle_message(PortfolioMessage::PasteIntoFolder {\n\t\t\t\tclipboard: Clipboard::Internal,\n\t\t\t\tparent: LayerNodeIdentifier::ROOT_PARENT,\n\t\t\t\tinsert_index: 0,\n\t\t\t})\n\t\t\t.await;\n\n\t\tlet layers_after_copy = editor.active_document().metadata().all_layers().collect::<Vec<_>>();\n\n\t\tassert_eq!(layers_before_copy.len(), 3);\n\t\tassert_eq!(layers_after_copy.len(), 4);\n\n\t\t// Existing layers are unaffected\n\t\tfor i in 0..=2 {\n\t\t\tassert_eq!(layers_before_copy[i], layers_after_copy[i + 1]);\n\t\t}\n\t}\n\n\t#[cfg_attr(miri, ignore)]\n\t/// - create rect, shape and ellipse\n\t/// - select shape\n\t/// - copy\n\t/// - paste\n\t/// - assert that shape was copied\n\t#[tokio::test]\n\tasync fn copy_paste_single_layer_from_middle() {\n\t\tlet mut editor = create_editor_with_three_layers().await;\n\n\t\tlet layers_before_copy = editor.active_document().metadata().all_layers().collect::<Vec<_>>();\n\t\tlet shape_id = editor.active_document().metadata().all_layers().nth(1).unwrap();\n\n\t\teditor.handle_message(NodeGraphMessage::SelectedNodesSet { nodes: vec![shape_id.to_node()] }).await;\n\t\teditor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::Internal }).await;\n\t\teditor\n\t\t\t.handle_message(PortfolioMessage::PasteIntoFolder {\n\t\t\t\tclipboard: Clipboard::Internal,\n\t\t\t\tparent: LayerNodeIdentifier::ROOT_PARENT,\n\t\t\t\tinsert_index: 0,\n\t\t\t})\n\t\t\t.await;\n\n\t\tlet layers_after_copy = editor.active_document().metadata().all_layers().collect::<Vec<_>>();\n\n\t\tassert_eq!(layers_before_copy.len(), 3);\n\t\tassert_eq!(layers_after_copy.len(), 4);\n\n\t\t// Existing layers are unaffected\n\t\tfor i in 0..=2 {\n\t\t\tassert_eq!(layers_before_copy[i], layers_after_copy[i + 1]);\n\t\t}\n\t}\n\n\t#[cfg_attr(miri, ignore)]\n\t/// - create rect, shape and ellipse\n\t/// - select ellipse and rect\n\t/// - copy\n\t/// - delete\n\t/// - create another rect\n\t/// - paste\n\t/// - paste\n\t#[tokio::test]\n\tasync fn copy_paste_deleted_layers() {\n\t\tlet mut editor = create_editor_with_three_layers().await;\n\t\tassert_eq!(editor.active_document().metadata().all_layers().count(), 3);\n\n\t\tlet layers_before_copy = editor.active_document().metadata().all_layers().collect::<Vec<_>>();\n\t\tlet rect_id = layers_before_copy[0];\n\t\tlet shape_id = layers_before_copy[1];\n\t\tlet ellipse_id = layers_before_copy[2];\n\n\t\teditor\n\t\t\t.handle_message(NodeGraphMessage::SelectedNodesSet {\n\t\t\t\tnodes: vec![rect_id.to_node(), ellipse_id.to_node()],\n\t\t\t})\n\t\t\t.await;\n\t\teditor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::Internal }).await;\n\t\teditor.handle_message(NodeGraphMessage::DeleteSelectedNodes { delete_children: true }).await;\n\t\teditor.draw_rect(0., 800., 12., 200.).await;\n\t\teditor\n\t\t\t.handle_message(PortfolioMessage::PasteIntoFolder {\n\t\t\t\tclipboard: Clipboard::Internal,\n\t\t\t\tparent: LayerNodeIdentifier::ROOT_PARENT,\n\t\t\t\tinsert_index: 0,\n\t\t\t})\n\t\t\t.await;\n\t\teditor\n\t\t\t.handle_message(PortfolioMessage::PasteIntoFolder {\n\t\t\t\tclipboard: Clipboard::Internal,\n\t\t\t\tparent: LayerNodeIdentifier::ROOT_PARENT,\n\t\t\t\tinsert_index: 0,\n\t\t\t})\n\t\t\t.await;\n\n\t\tlet layers_after_copy = editor.active_document().metadata().all_layers().collect::<Vec<_>>();\n\n\t\tassert_eq!(layers_before_copy.len(), 3);\n\t\tassert_eq!(layers_after_copy.len(), 6);\n\n\t\tprintln!(\"{layers_after_copy:?} {layers_before_copy:?}\");\n\n\t\tassert_eq!(layers_after_copy[5], shape_id);\n\t}\n\n\t#[tokio::test]\n\t/// This test will fail when you make changes to the underlying serialization format for a document.\n\tasync fn check_if_demo_art_opens() {\n\t\tuse crate::messages::layout::utility_types::widget_prelude::*;\n\n\t\tlet print_problem_to_terminal_on_failure = |value: &String| {\n\t\t\tprintln!();\n\t\t\tprintln!(\"-------------------------------------------------\");\n\t\t\tprintln!(\"Failed test due to receiving a DisplayDialogError while loading a Graphite demo file.\");\n\t\t\tprintln!();\n\t\t\tprintln!(\"NOTE:\");\n\t\t\tprintln!(\"Document upgrading isn't performed in tests like when opening in the actual editor.\");\n\t\t\tprintln!(\"You may need to open and re-save a document in the editor to apply its migrations.\");\n\t\t\tprintln!();\n\t\t\tprintln!(\"DisplayDialogError details:\");\n\t\t\tprintln!();\n\t\t\tprintln!(\"Description:\");\n\t\t\tprintln!(\"{value}\");\n\t\t\tprintln!(\"-------------------------------------------------\");\n\t\t\tprintln!();\n\n\t\t\tpanic!()\n\t\t};\n\n\t\tlet mut editor = EditorTestUtils::create();\n\n\t\t// UNCOMMENT THIS FOR RUNNING UNDER MIRI\n\t\t//\n\t\t// let files = [\n\t\t// \tinclude_str!(\"../../demo-artwork/changing-seasons.graphite\"),\n\t\t// \tinclude_str!(\"../../demo-artwork/isometric-fountain.graphite\"),\n\t\t// \tinclude_str!(\"../../demo-artwork/painted-dreams.graphite\"),\n\t\t// \tinclude_str!(\"../../demo-artwork/procedural-string-lights.graphite\"),\n\t\t// \tinclude_str!(\"../../demo-artwork/parametric-dunescape.graphite\"),\n\t\t// \tinclude_str!(\"../../demo-artwork/red-dress.graphite\"),\n\t\t// \tinclude_str!(\"../../demo-artwork/valley-of-spires.graphite\"),\n\t\t// ];\n\t\t// for (id, document_serialized_content) in files.iter().enumerate() {\n\t\t// let document_name = format!(\"document {id}\");\n\n\t\tfor (document_name, _, file_name) in crate::messages::dialog::simple_dialogs::ARTWORK {\n\t\t\tlet document_serialized_content = std::fs::read_to_string(format!(\"../demo-artwork/{file_name}\")).unwrap();\n\n\t\t\tassert_eq!(\n\t\t\t\tdocument_serialized_content.lines().count(),\n\t\t\t\t1,\n\t\t\t\t\"Demo artwork '{document_name}' has more than 1 line (remember to open and re-save it in Graphite)\",\n\t\t\t);\n\n\t\t\tlet responses = editor.editor.handle_message(PortfolioMessage::OpenFile {\n\t\t\t\tpath: file_name.into(),\n\t\t\t\tcontent: document_serialized_content.bytes().collect(),\n\t\t\t});\n\n\t\t\t// Check if the graph renders\n\t\t\tif let Err(e) = editor.eval_graph().await {\n\t\t\t\tprint_problem_to_terminal_on_failure(&format!(\"Failed to evaluate the graph for document '{document_name}':\\n{e}\"));\n\t\t\t}\n\n\t\t\tfor response in responses {\n\t\t\t\t// Check for the existence of the file format incompatibility warning dialog after opening the test file\n\t\t\t\tif let FrontendMessage::UpdateLayout {\n\t\t\t\t\tlayout_target: LayoutTarget::DialogColumn1,\n\t\t\t\t\tdiff,\n\t\t\t\t} = response\n\t\t\t\t{\n\t\t\t\t\tif let DiffUpdate::Layout(sub_layout) = &diff[0].new_value {\n\t\t\t\t\t\tif let LayoutGroup::Row(WidgetRow { widgets }) = &sub_layout.0[0] {\n\t\t\t\t\t\t\tif let Widget::TextLabel(TextLabel { value, .. }) = &*widgets[0].widget {\n\t\t\t\t\t\t\t\tprint_problem_to_terminal_on_failure(value);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "editor/src/lib.rs",
    "content": "extern crate graphite_proc_macros;\n\n// `macro_use` puts these macros into scope for all descendant code files\n#[macro_use]\nmod macros;\n#[macro_use]\nextern crate log;\n\npub mod application;\npub mod consts;\npub mod dispatcher;\npub mod messages;\npub mod node_graph_executor;\n#[cfg(test)]\npub mod test_utils;\npub mod utility_traits;\npub mod utility_types;\n"
  },
  {
    "path": "editor/src/macros.rs",
    "content": "/// Syntax sugar for initializing an `ActionList`\n///\n/// # Example\n///\n/// ```ignore\n/// actions!(DocumentMessage::Undo, DocumentMessage::Redo);\n/// ```\n///\n/// expands to:\n/// ```ignore\n/// vec![vec![DocumentMessage::Undo, DocumentMessage::Redo]];\n/// ```\n///\n/// and\n/// ```ignore\n/// actions!(DocumentMessage;\n///     Undo,\n///     Redo,\n/// );\n/// ```\n///\n/// expands to:\n/// ```ignore\n/// vec![vec![DocumentMessage::Undo, DocumentMessage::Redo]];\n/// ```\n///\nmacro_rules! actions {\n\t($($v:expr_2021),* $(,)?) => {{\n\t\tvec![$(vec![$v.into()]),*]\n\t}};\n\n\t($name:ident; $($v:ident),* $(,)?) => {{\n\t\tvec![vec![$(($name::$v).into()),*]]\n\t}};\n}\n\n/// Does the same thing as the `actions!` macro but wraps everything in:\n///\n/// ```ignore\n/// fn actions(&self) -> ActionList {\n///     actions!(…)\n/// }\n/// ```\nmacro_rules! advertise_actions {\n\t($($v:expr_2021),* $(,)?) => {\n\t\tfn actions(&self) -> $crate::utility_traits::ActionList {\n\t\t\tactions!($($v),*)\n\t\t}\n\t};\n\n\t($name:ident; $($v:ident),* $(,)?) => {\n\t\tfn actions(&self) -> $crate::utility_traits::ActionList {\n\t\t\tactions!($name; $($v),*)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/animation/animation_message.rs",
    "content": "use super::animation_message_handler::AnimationTimeMode;\nuse crate::messages::prelude::*;\n\n#[impl_message(Message, Animation)]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum AnimationMessage {\n\tToggleLivePreview,\n\tEnableLivePreview,\n\tDisableLivePreview,\n\tRestartAnimation,\n\tSetFrameIndex { frame: f64 },\n\tSetTime { time: f64 },\n\tUpdateTime,\n\tIncrementFrameCounter,\n\tSetAnimationTimeMode { animation_time_mode: AnimationTimeMode },\n}\n"
  },
  {
    "path": "editor/src/messages/animation/animation_message_handler.rs",
    "content": "use std::time::Duration;\n\nuse crate::messages::prelude::*;\n\nuse super::TimingInformation;\n\n#[derive(PartialEq, Clone, Default, Debug, serde::Serialize, serde::Deserialize)]\npub enum AnimationTimeMode {\n\t#[default]\n\tTimeBased,\n\tFrameBased,\n}\n\n#[derive(Default, Debug, Clone, PartialEq)]\nenum AnimationState {\n\t#[default]\n\tStopped,\n\tPlaying {\n\t\tstart: f64,\n\t},\n\tPaused {\n\t\tstart: f64,\n\t\tpause_time: f64,\n\t},\n}\n\n#[derive(Default, Debug, Clone, PartialEq, ExtractField)]\npub struct AnimationMessageHandler {\n\t/// Used to re-send the UI on the next frame after playback starts\n\tlive_preview_recently_zero: bool,\n\ttimestamp: f64,\n\tframe_index: f64,\n\tanimation_state: AnimationState,\n\tfps: f64,\n\tanimation_time_mode: AnimationTimeMode,\n}\nimpl AnimationMessageHandler {\n\tpub(crate) fn timing_information(&self) -> TimingInformation {\n\t\tlet animation_time = self.timestamp - self.animation_start();\n\t\tlet animation_time = match self.animation_time_mode {\n\t\t\tAnimationTimeMode::TimeBased => Duration::from_millis(animation_time as u64),\n\t\t\tAnimationTimeMode::FrameBased => Duration::from_secs((self.frame_index / self.fps) as u64),\n\t\t};\n\t\tTimingInformation { time: self.timestamp, animation_time }\n\t}\n\n\tpub(crate) fn animation_start(&self) -> f64 {\n\t\tmatch self.animation_state {\n\t\t\tAnimationState::Stopped => self.timestamp,\n\t\t\tAnimationState::Playing { start } => start,\n\t\t\tAnimationState::Paused { start, pause_time } => start + self.timestamp - pause_time,\n\t\t}\n\t}\n\n\tpub fn is_playing(&self) -> bool {\n\t\tmatches!(self.animation_state, AnimationState::Playing { .. })\n\t}\n}\n\n#[message_handler_data]\nimpl MessageHandler<AnimationMessage, ()> for AnimationMessageHandler {\n\tfn process_message(&mut self, message: AnimationMessage, responses: &mut VecDeque<Message>, _: ()) {\n\t\tmatch message {\n\t\t\tAnimationMessage::ToggleLivePreview => match self.animation_state {\n\t\t\t\tAnimationState::Stopped => responses.add(AnimationMessage::EnableLivePreview),\n\t\t\t\tAnimationState::Playing { .. } => responses.add(AnimationMessage::DisableLivePreview),\n\t\t\t\tAnimationState::Paused { .. } => responses.add(AnimationMessage::EnableLivePreview),\n\t\t\t},\n\t\t\tAnimationMessage::EnableLivePreview => {\n\t\t\t\tself.animation_state = AnimationState::Playing { start: self.animation_start() };\n\n\t\t\t\t// Update the restart and pause/play buttons\n\t\t\t\tresponses.add(PortfolioMessage::UpdateDocumentWidgets);\n\t\t\t}\n\t\t\tAnimationMessage::DisableLivePreview => {\n\t\t\t\tmatch self.animation_state {\n\t\t\t\t\tAnimationState::Stopped => (),\n\t\t\t\t\tAnimationState::Playing { start } => self.animation_state = AnimationState::Paused { start, pause_time: self.timestamp },\n\t\t\t\t\tAnimationState::Paused { .. } => (),\n\t\t\t\t}\n\n\t\t\t\t// Update the restart and pause/play buttons\n\t\t\t\tresponses.add(PortfolioMessage::UpdateDocumentWidgets);\n\t\t\t}\n\t\t\tAnimationMessage::SetFrameIndex { frame } => {\n\t\t\t\tself.frame_index = frame;\n\t\t\t\tresponses.add(PortfolioMessage::SubmitActiveGraphRender);\n\t\t\t\t// Update the restart and pause/play buttons\n\t\t\t\tresponses.add(PortfolioMessage::UpdateDocumentWidgets);\n\t\t\t}\n\t\t\tAnimationMessage::SetTime { time } => {\n\t\t\t\tself.timestamp = time;\n\t\t\t\tresponses.add(AnimationMessage::UpdateTime);\n\t\t\t}\n\t\t\tAnimationMessage::IncrementFrameCounter => {\n\t\t\t\tif self.is_playing() {\n\t\t\t\t\tself.frame_index += 1.;\n\t\t\t\t\tresponses.add(AnimationMessage::UpdateTime);\n\t\t\t\t}\n\t\t\t}\n\t\t\tAnimationMessage::UpdateTime => {\n\t\t\t\tif self.is_playing() {\n\t\t\t\t\tresponses.add(PortfolioMessage::SubmitActiveGraphRender);\n\t\t\t\t\tif self.live_preview_recently_zero {\n\t\t\t\t\t\t// Update the restart and pause/play buttons\n\t\t\t\t\t\tresponses.add(PortfolioMessage::UpdateDocumentWidgets);\n\t\t\t\t\t\tself.live_preview_recently_zero = false;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tAnimationMessage::RestartAnimation => {\n\t\t\t\tself.frame_index = 0.;\n\t\t\t\tself.animation_state = match self.animation_state {\n\t\t\t\t\tAnimationState::Playing { .. } => AnimationState::Playing { start: self.timestamp },\n\t\t\t\t\t_ => AnimationState::Stopped,\n\t\t\t\t};\n\t\t\t\tself.live_preview_recently_zero = true;\n\t\t\t\tresponses.add(PortfolioMessage::SubmitActiveGraphRender);\n\t\t\t\t// Update the restart and pause/play buttons\n\t\t\t\tresponses.add(PortfolioMessage::UpdateDocumentWidgets);\n\t\t\t}\n\t\t\tAnimationMessage::SetAnimationTimeMode { animation_time_mode } => {\n\t\t\t\tself.animation_time_mode = animation_time_mode;\n\t\t\t}\n\t\t}\n\t}\n\n\tadvertise_actions!(AnimationMessageDiscriminant;\n\t\tToggleLivePreview,\n\t\tSetFrameIndex,\n\t\tRestartAnimation,\n\t);\n}\n"
  },
  {
    "path": "editor/src/messages/animation/mod.rs",
    "content": "mod animation_message;\nmod animation_message_handler;\n\n#[doc(inline)]\npub use animation_message::{AnimationMessage, AnimationMessageDiscriminant};\n#[doc(inline)]\npub use animation_message_handler::AnimationMessageHandler;\n\npub use graphene_std::application_io::TimingInformation;\n"
  },
  {
    "path": "editor/src/messages/app_window/app_window_message.rs",
    "content": "use crate::messages::prelude::*;\n\n#[impl_message(Message, AppWindow)]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum AppWindowMessage {\n\tPointerLock,\n\tPointerLockMove { x: f64, y: f64 },\n\tRestart,\n\tClose,\n\tMinimize,\n\tMaximize,\n\tFullscreen,\n\tDrag,\n\tHide,\n\tHideOthers,\n\tShowAll,\n}\n"
  },
  {
    "path": "editor/src/messages/app_window/app_window_message_handler.rs",
    "content": "use crate::application::{Environment, Platform};\nuse crate::messages::prelude::*;\nuse crate::{application::Host, messages::app_window::AppWindowMessage};\nuse graphite_proc_macros::{ExtractField, message_handler_data};\n\n#[derive(Debug, Clone, Default, ExtractField)]\npub struct AppWindowMessageHandler {}\n\n#[message_handler_data]\nimpl MessageHandler<AppWindowMessage, ()> for AppWindowMessageHandler {\n\tfn process_message(&mut self, message: AppWindowMessage, responses: &mut std::collections::VecDeque<Message>, _: ()) {\n\t\tmatch message {\n\t\t\tAppWindowMessage::PointerLock => {\n\t\t\t\t#[cfg(not(target_family = \"wasm\"))]\n\t\t\t\tresponses.add(FrontendMessage::WindowPointerLock);\n\t\t\t}\n\t\t\tAppWindowMessage::PointerLockMove { x, y } => {\n\t\t\t\tresponses.add(FrontendMessage::WindowPointerLockMove { position: (x, y) });\n\t\t\t}\n\t\t\tAppWindowMessage::Close => {\n\t\t\t\t#[cfg(not(target_family = \"wasm\"))]\n\t\t\t\tresponses.add(FrontendMessage::WindowClose);\n\t\t\t}\n\t\t\tAppWindowMessage::Minimize => {\n\t\t\t\t#[cfg(not(target_family = \"wasm\"))]\n\t\t\t\tresponses.add(FrontendMessage::WindowMinimize);\n\t\t\t}\n\t\t\tAppWindowMessage::Maximize => {\n\t\t\t\t#[cfg(not(target_family = \"wasm\"))]\n\t\t\t\tresponses.add(FrontendMessage::WindowMaximize);\n\t\t\t}\n\t\t\tAppWindowMessage::Fullscreen => {\n\t\t\t\tresponses.add(FrontendMessage::WindowFullscreen);\n\t\t\t}\n\t\t\tAppWindowMessage::Drag => {\n\t\t\t\t#[cfg(not(target_family = \"wasm\"))]\n\t\t\t\tresponses.add(FrontendMessage::WindowDrag);\n\t\t\t}\n\t\t\tAppWindowMessage::Hide => {\n\t\t\t\t#[cfg(not(target_family = \"wasm\"))]\n\t\t\t\tresponses.add(FrontendMessage::WindowHide);\n\t\t\t}\n\t\t\tAppWindowMessage::HideOthers => {\n\t\t\t\t#[cfg(not(target_family = \"wasm\"))]\n\t\t\t\tresponses.add(FrontendMessage::WindowHideOthers);\n\t\t\t}\n\t\t\tAppWindowMessage::ShowAll => {\n\t\t\t\t#[cfg(not(target_family = \"wasm\"))]\n\t\t\t\tresponses.add(FrontendMessage::WindowShowAll);\n\t\t\t}\n\t\t\tAppWindowMessage::Restart => {\n\t\t\t\tresponses.add(PortfolioMessage::AutoSaveAllDocuments);\n\t\t\t\t#[cfg(not(target_family = \"wasm\"))]\n\t\t\t\tresponses.add(FrontendMessage::WindowRestart);\n\t\t\t}\n\t\t}\n\t}\n\tadvertise_actions!(AppWindowMessageDiscriminant;\n\t\tClose,\n\t\tMinimize,\n\t\tMaximize,\n\t\tFullscreen,\n\t\tDrag,\n\t\tHide,\n\t\tHideOthers,\n\t);\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, serde::Serialize, serde::Deserialize)]\npub enum AppWindowPlatform {\n\t#[default]\n\tWeb,\n\tWindows,\n\tMac,\n\tLinux,\n}\n\nimpl From<&Environment> for AppWindowPlatform {\n\tfn from(environment: &Environment) -> Self {\n\t\tmatch (environment.platform, environment.host) {\n\t\t\t(Platform::Web, _) => AppWindowPlatform::Web,\n\t\t\t(Platform::Desktop, Host::Linux) => AppWindowPlatform::Linux,\n\t\t\t(Platform::Desktop, Host::Mac) => AppWindowPlatform::Mac,\n\t\t\t(Platform::Desktop, Host::Windows) => AppWindowPlatform::Windows,\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/app_window/mod.rs",
    "content": "mod app_window_message;\npub mod app_window_message_handler;\n\n#[doc(inline)]\npub use app_window_message::{AppWindowMessage, AppWindowMessageDiscriminant};\n#[doc(inline)]\npub use app_window_message_handler::AppWindowMessageHandler;\n"
  },
  {
    "path": "editor/src/messages/broadcast/broadcast_message.rs",
    "content": "use crate::messages::prelude::*;\n\n#[impl_message(Message, Broadcast)]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum BroadcastMessage {\n\t// Sub-messages\n\t#[child]\n\tTriggerEvent(EventMessage),\n\n\t// Messages\n\tSubscribeEvent {\n\t\ton: EventMessage,\n\t\tsend: Box<Message>,\n\t},\n\tUnsubscribeEvent {\n\t\ton: EventMessage,\n\t\tsend: Box<Message>,\n\t},\n}\n"
  },
  {
    "path": "editor/src/messages/broadcast/broadcast_message_handler.rs",
    "content": "use crate::messages::prelude::*;\n\n#[derive(Debug, Clone, Default, ExtractField)]\npub struct BroadcastMessageHandler {\n\tevent: EventMessageHandler,\n\tlisteners: HashMap<EventMessage, Vec<Message>>,\n}\n\n#[message_handler_data]\nimpl MessageHandler<BroadcastMessage, ()> for BroadcastMessageHandler {\n\tfn process_message(&mut self, message: BroadcastMessage, responses: &mut VecDeque<Message>, _: ()) {\n\t\tmatch message {\n\t\t\t// Sub-messages\n\t\t\tBroadcastMessage::TriggerEvent(message) => self.event.process_message(message, responses, EventMessageContext { listeners: &mut self.listeners }),\n\n\t\t\t// Messages\n\t\t\tBroadcastMessage::SubscribeEvent { on, send } => self.listeners.entry(on).or_default().push(*send),\n\t\t\tBroadcastMessage::UnsubscribeEvent { on, send } => self.listeners.entry(on).or_default().retain(|msg| *msg != *send),\n\t\t}\n\t}\n\n\tfn actions(&self) -> ActionList {\n\t\tactions!(EventMessageDiscriminant;)\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/broadcast/event/event_message.rs",
    "content": "use crate::messages::prelude::*;\n\n#[impl_message(Message, BroadcastMessage, TriggerEvent)]\n#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize, Hash)]\npub enum EventMessage {\n\t/// Triggered by requestAnimationFrame in JS\n\tAnimationFrame,\n\tCanvasTransformed,\n\tToolAbort,\n\tSelectionChanged,\n\tWorkingColorChanged,\n}\n"
  },
  {
    "path": "editor/src/messages/broadcast/event/event_message_handler.rs",
    "content": "use crate::messages::prelude::*;\n\n#[derive(ExtractField)]\npub struct EventMessageContext<'a> {\n\tpub listeners: &'a mut HashMap<EventMessage, Vec<Message>>,\n}\n\n#[derive(Debug, Clone, Default, ExtractField)]\npub struct EventMessageHandler {}\n\n#[message_handler_data]\nimpl MessageHandler<EventMessage, EventMessageContext<'_>> for EventMessageHandler {\n\tfn process_message(&mut self, message: EventMessage, responses: &mut VecDeque<Message>, context: EventMessageContext) {\n\t\tfor message in context.listeners.entry(message).or_default() {\n\t\t\tresponses.add_front(message.clone())\n\t\t}\n\t}\n\n\tfn actions(&self) -> ActionList {\n\t\tactions!(EventMessageDiscriminant;)\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/broadcast/event/mod.rs",
    "content": "mod event_message;\nmod event_message_handler;\n\n#[doc(inline)]\npub use event_message::{EventMessage, EventMessageDiscriminant};\n#[doc(inline)]\npub use event_message_handler::{EventMessageContext, EventMessageHandler};\n"
  },
  {
    "path": "editor/src/messages/broadcast/mod.rs",
    "content": "mod broadcast_message;\nmod broadcast_message_handler;\n\npub mod event;\n\n#[doc(inline)]\npub use broadcast_message::{BroadcastMessage, BroadcastMessageDiscriminant};\n#[doc(inline)]\npub use broadcast_message_handler::BroadcastMessageHandler;\n"
  },
  {
    "path": "editor/src/messages/clipboard/clipboard_message.rs",
    "content": "use crate::messages::clipboard::utility_types::{ClipboardContent, ClipboardContentRaw};\nuse crate::messages::prelude::*;\n\n#[impl_message(Message, Clipboard)]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum ClipboardMessage {\n\tCut,\n\tCopy,\n\tPaste,\n\tReadClipboard { content: ClipboardContentRaw },\n\tReadSelection { content: Option<String>, cut: bool },\n\tWrite { content: ClipboardContent },\n}\n"
  },
  {
    "path": "editor/src/messages/clipboard/clipboard_message_handler.rs",
    "content": "use crate::messages::clipboard::utility_types::{ClipboardContent, ClipboardContentRaw};\nuse crate::messages::prelude::*;\nuse graphene_std::raster::Image;\nuse graphite_proc_macros::{ExtractField, message_handler_data};\n\nconst CLIPBOARD_PREFIX_LAYER: &str = \"graphite/layer: \";\nconst CLIPBOARD_PREFIX_NODES: &str = \"graphite/nodes: \";\nconst CLIPBOARD_PREFIX_VECTOR: &str = \"graphite/vector: \";\n\n#[derive(Debug, Clone, Default, ExtractField)]\npub struct ClipboardMessageHandler {}\n\n#[message_handler_data]\nimpl MessageHandler<ClipboardMessage, ()> for ClipboardMessageHandler {\n\tfn process_message(&mut self, message: ClipboardMessage, responses: &mut std::collections::VecDeque<Message>, _: ()) {\n\t\tmatch message {\n\t\t\tClipboardMessage::Cut => responses.add(FrontendMessage::TriggerSelectionRead { cut: true }),\n\t\t\tClipboardMessage::Copy => responses.add(FrontendMessage::TriggerSelectionRead { cut: false }),\n\t\t\tClipboardMessage::Paste => responses.add(FrontendMessage::TriggerClipboardRead),\n\t\t\tClipboardMessage::ReadClipboard { content } => match content {\n\t\t\t\tClipboardContentRaw::Text(text) => {\n\t\t\t\t\tif let Some(layer) = text.strip_prefix(CLIPBOARD_PREFIX_LAYER) {\n\t\t\t\t\t\tresponses.add(PortfolioMessage::PasteSerializedData { data: layer.to_string() });\n\t\t\t\t\t} else if let Some(nodes) = text.strip_prefix(CLIPBOARD_PREFIX_NODES) {\n\t\t\t\t\t\tresponses.add(NodeGraphMessage::PasteNodes { serialized_nodes: nodes.to_string() });\n\t\t\t\t\t} else if let Some(vector) = text.strip_prefix(CLIPBOARD_PREFIX_VECTOR) {\n\t\t\t\t\t\tresponses.add(PortfolioMessage::PasteSerializedVector { data: vector.to_string() });\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresponses.add(FrontendMessage::TriggerSelectionWrite { content: text });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tClipboardContentRaw::Svg(svg) => {\n\t\t\t\t\tresponses.add(PortfolioMessage::PasteSvg {\n\t\t\t\t\t\tsvg,\n\t\t\t\t\t\tname: None,\n\t\t\t\t\t\tmouse: None,\n\t\t\t\t\t\tparent_and_insert_index: None,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tClipboardContentRaw::Image { data, width, height } => {\n\t\t\t\t\tresponses.add(PortfolioMessage::PasteImage {\n\t\t\t\t\t\timage: Image::from_image_data(&data, width, height),\n\t\t\t\t\t\tname: None,\n\t\t\t\t\t\tmouse: None,\n\t\t\t\t\t\tparent_and_insert_index: None,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t},\n\t\t\tClipboardMessage::ReadSelection { content, cut } => {\n\t\t\t\tuse crate::messages::portfolio::document::utility_types::clipboards::Clipboard;\n\t\t\t\tif let Some(text) = content {\n\t\t\t\t\tresponses.add(ClipboardMessage::Write {\n\t\t\t\t\t\tcontent: ClipboardContent::Text(text),\n\t\t\t\t\t});\n\t\t\t\t} else if cut {\n\t\t\t\t\tresponses.add(PortfolioMessage::Cut { clipboard: Clipboard::Device });\n\t\t\t\t} else {\n\t\t\t\t\tresponses.add(PortfolioMessage::Copy { clipboard: Clipboard::Device });\n\t\t\t\t}\n\t\t\t}\n\t\t\tClipboardMessage::Write { content } => {\n\t\t\t\tlet text = match content {\n\t\t\t\t\tClipboardContent::Svg(_) => {\n\t\t\t\t\t\tlog::error!(\"SVG copying is not yet supported\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tClipboardContent::Image { .. } => {\n\t\t\t\t\t\tlog::error!(\"Image copying is not yet supported\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tClipboardContent::Layer(layer) => format!(\"{CLIPBOARD_PREFIX_LAYER}{layer}\"),\n\t\t\t\t\tClipboardContent::Nodes(nodes) => format!(\"{CLIPBOARD_PREFIX_NODES}{nodes}\"),\n\t\t\t\t\tClipboardContent::Vector(vector) => format!(\"{CLIPBOARD_PREFIX_VECTOR}{vector}\"),\n\t\t\t\t\tClipboardContent::Text(text) => text,\n\t\t\t\t};\n\t\t\t\tresponses.add(FrontendMessage::TriggerClipboardWrite { content: text });\n\t\t\t}\n\t\t}\n\t}\n\tadvertise_actions!(ClipboardMessageDiscriminant;\n\t\tCut,\n\t\tCopy,\n\t\tPaste,\n\t);\n}\n"
  },
  {
    "path": "editor/src/messages/clipboard/mod.rs",
    "content": "mod clipboard_message;\npub mod clipboard_message_handler;\npub mod utility_types;\n\n#[doc(inline)]\npub use clipboard_message::{ClipboardMessage, ClipboardMessageDiscriminant};\n#[doc(inline)]\npub use clipboard_message_handler::ClipboardMessageHandler;\n"
  },
  {
    "path": "editor/src/messages/clipboard/utility_types.rs",
    "content": "#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum ClipboardContentRaw {\n\tText(String),\n\tSvg(String),\n\tImage { data: Vec<u8>, width: u32, height: u32 },\n}\n\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum ClipboardContent {\n\tLayer(String),\n\tNodes(String),\n\tVector(String),\n\tText(String),\n\tSvg(String),\n\tImage { data: Vec<u8>, width: u32, height: u32 },\n}\n"
  },
  {
    "path": "editor/src/messages/debug/debug_message.rs",
    "content": "use crate::messages::prelude::*;\n\n#[impl_message(Message, Debug)]\n#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)]\npub enum DebugMessage {\n\tToggleTraceLogs,\n\tMessageOff,\n\tMessageNames,\n\tMessageContents,\n}\n"
  },
  {
    "path": "editor/src/messages/debug/debug_message_handler.rs",
    "content": "use super::utility_types::MessageLoggingVerbosity;\nuse crate::messages::prelude::*;\n\n#[derive(Debug, Default, ExtractField)]\npub struct DebugMessageHandler {\n\tpub message_logging_verbosity: MessageLoggingVerbosity,\n}\n\n#[message_handler_data]\nimpl MessageHandler<DebugMessage, ()> for DebugMessageHandler {\n\tfn process_message(&mut self, message: DebugMessage, responses: &mut VecDeque<Message>, _: ()) {\n\t\tmatch message {\n\t\t\tDebugMessage::ToggleTraceLogs => {\n\t\t\t\tif log::max_level() == log::LevelFilter::Debug {\n\t\t\t\t\tlog::set_max_level(log::LevelFilter::Trace);\n\t\t\t\t} else {\n\t\t\t\t\tlog::set_max_level(log::LevelFilter::Debug);\n\t\t\t\t}\n\n\t\t\t\t// Refresh the checkmark beside the menu entry for this\n\t\t\t\tresponses.add(MenuBarMessage::SendLayout);\n\t\t\t}\n\t\t\tDebugMessage::MessageOff => {\n\t\t\t\tself.message_logging_verbosity = MessageLoggingVerbosity::Off;\n\n\t\t\t\t// Refresh the checkmark beside the menu entry for this\n\t\t\t\tresponses.add(MenuBarMessage::SendLayout);\n\t\t\t}\n\t\t\tDebugMessage::MessageNames => {\n\t\t\t\tself.message_logging_verbosity = MessageLoggingVerbosity::Names;\n\n\t\t\t\t// Refresh the checkmark beside the menu entry for this\n\t\t\t\tresponses.add(MenuBarMessage::SendLayout);\n\t\t\t}\n\t\t\tDebugMessage::MessageContents => {\n\t\t\t\tself.message_logging_verbosity = MessageLoggingVerbosity::Contents;\n\n\t\t\t\t// Refresh the checkmark beside the menu entry for this\n\t\t\t\tresponses.add(MenuBarMessage::SendLayout);\n\t\t\t}\n\t\t}\n\t}\n\n\tadvertise_actions!(DebugMessageDiscriminant;\n\t\tToggleTraceLogs,\n\t\tMessageOff,\n\t\tMessageNames,\n\t\tMessageContents,\n\t);\n}\n"
  },
  {
    "path": "editor/src/messages/debug/mod.rs",
    "content": "mod debug_message;\nmod debug_message_handler;\n\npub mod utility_types;\n\n#[doc(inline)]\npub use debug_message::{DebugMessage, DebugMessageDiscriminant};\n#[doc(inline)]\npub use debug_message_handler::DebugMessageHandler;\n"
  },
  {
    "path": "editor/src/messages/debug/utility_types.rs",
    "content": "#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]\npub enum MessageLoggingVerbosity {\n\t#[default]\n\tOff,\n\tNames,\n\tContents,\n}\n"
  },
  {
    "path": "editor/src/messages/defer/defer_message.rs",
    "content": "use crate::messages::prelude::*;\n\n#[impl_message(Message, Defer)]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum DeferMessage {\n\tSetGraphSubmissionIndex { execution_id: u64 },\n\tTriggerGraphRun { execution_id: u64, document_id: DocumentId },\n\tAfterGraphRun { messages: Vec<Message> },\n\tTriggerNavigationReady,\n\tAfterNavigationReady { messages: Vec<Message> },\n}\n"
  },
  {
    "path": "editor/src/messages/defer/defer_message_handler.rs",
    "content": "use crate::messages::prelude::*;\n\n#[derive(ExtractField)]\npub struct DeferMessageContext<'a> {\n\tpub portfolio: &'a PortfolioMessageHandler,\n}\n\n#[derive(Debug, Default, ExtractField)]\npub struct DeferMessageHandler {\n\tafter_graph_run: HashMap<DocumentId, Vec<(u64, Message)>>,\n\tafter_viewport_resize: Vec<Message>,\n\tcurrent_graph_submission_id: u64,\n}\n\n#[message_handler_data]\nimpl MessageHandler<DeferMessage, DeferMessageContext<'_>> for DeferMessageHandler {\n\tfn process_message(&mut self, message: DeferMessage, responses: &mut VecDeque<Message>, context: DeferMessageContext) {\n\t\tmatch message {\n\t\t\tDeferMessage::AfterGraphRun { mut messages } => {\n\t\t\t\tlet after_graph_run = self.after_graph_run.entry(context.portfolio.active_document_id.unwrap_or(DocumentId(0))).or_default();\n\t\t\t\tafter_graph_run.extend(messages.drain(..).map(|m| (self.current_graph_submission_id, m)));\n\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t}\n\t\t\tDeferMessage::AfterNavigationReady { messages } => {\n\t\t\t\tself.after_viewport_resize.extend_from_slice(&messages);\n\t\t\t}\n\t\t\tDeferMessage::SetGraphSubmissionIndex { execution_id } => {\n\t\t\t\tself.current_graph_submission_id = execution_id + 1;\n\t\t\t}\n\t\t\tDeferMessage::TriggerGraphRun { execution_id, document_id } => {\n\t\t\t\tlet after_graph_run = self.after_graph_run.entry(document_id).or_default();\n\t\t\t\tif after_graph_run.is_empty() {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// Find the index of the last message we can process\n\t\t\t\tlet split = after_graph_run.partition_point(|&(id, _)| id <= execution_id);\n\t\t\t\tlet elements = after_graph_run.drain(..split);\n\t\t\t\tfor (_, message) in elements.rev() {\n\t\t\t\t\tresponses.add_front(message);\n\t\t\t\t}\n\t\t\t\tfor (&document_id, messages) in self.after_graph_run.iter() {\n\t\t\t\t\tif !messages.is_empty() {\n\t\t\t\t\t\tresponses.add(PortfolioMessage::SubmitGraphRender { document_id, ignore_hash: false });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tDeferMessage::TriggerNavigationReady => {\n\t\t\t\tfor message in self.after_viewport_resize.drain(..).rev() {\n\t\t\t\t\tresponses.add_front(message);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tadvertise_actions!(DeferMessageDiscriminant;\n\t);\n}\n"
  },
  {
    "path": "editor/src/messages/defer/mod.rs",
    "content": "mod defer_message;\nmod defer_message_handler;\n\n#[doc(inline)]\npub use defer_message::{DeferMessage, DeferMessageDiscriminant};\n#[doc(inline)]\npub use defer_message_handler::{DeferMessageContext, DeferMessageHandler};\n"
  },
  {
    "path": "editor/src/messages/dialog/dialog_message.rs",
    "content": "use crate::messages::prelude::*;\n\n#[impl_message(Message, Dialog)]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum DialogMessage {\n\t// Sub-messages\n\t#[child]\n\tExportDialog(ExportDialogMessage),\n\t#[child]\n\tNewDocumentDialog(NewDocumentDialogMessage),\n\t#[child]\n\tPreferencesDialog(PreferencesDialogMessage),\n\n\t// Messages\n\tDismiss,\n\tClose,\n\tCloseAndThen {\n\t\tfollowups: Vec<Message>,\n\t},\n\tCloseAllDocumentsWithConfirmation,\n\tDisplayDialogError {\n\t\ttitle: String,\n\t\tdescription: String,\n\t},\n\tRequestAboutGraphiteDialog,\n\tRequestAboutGraphiteDialogWithLocalizedCommitDate {\n\t\tlocalized_commit_date: String,\n\t\tlocalized_commit_year: String,\n\t},\n\tRequestDemoArtworkDialog,\n\tRequestExportDialog,\n\tRequestLicensesDialogWithLocalizedCommitDate {\n\t\tlocalized_commit_year: String,\n\t},\n\tRequestLicensesThirdPartyDialogWithLicenseText {\n\t\tlicense_text: String,\n\t},\n\tRequestNewDocumentDialog,\n\tRequestPreferencesDialog,\n\tRequestConfirmRestartDialog {\n\t\tpreferences_requiring_restart: Vec<String>,\n\t},\n}\n"
  },
  {
    "path": "editor/src/messages/dialog/dialog_message_handler.rs",
    "content": "use super::simple_dialogs::{self, AboutGraphiteDialog, DemoArtworkDialog, LicensesDialog};\nuse crate::application::GRAPHITE_GIT_COMMIT_DATE;\nuse crate::messages::dialog::simple_dialogs::{ConfirmRestartDialog, LicensesThirdPartyDialog};\nuse crate::messages::frontend::utility_types::ExportBounds;\nuse crate::messages::layout::utility_types::widget_prelude::*;\nuse crate::messages::prelude::*;\n\n#[derive(ExtractField)]\npub struct DialogMessageContext<'a> {\n\tpub portfolio: &'a PortfolioMessageHandler,\n\tpub preferences: &'a PreferencesMessageHandler,\n}\n\n/// Stores the dialogs which require state. These are the ones that have their own message handlers, and are not the ones defined in `simple_dialogs`.\n#[derive(Debug, Default, Clone, ExtractField)]\npub struct DialogMessageHandler {\n\ton_dismiss: Option<Message>,\n\texport_dialog: ExportDialogMessageHandler,\n\tnew_document_dialog: NewDocumentDialogMessageHandler,\n\tpreferences_dialog: PreferencesDialogMessageHandler,\n}\n\n#[message_handler_data]\nimpl MessageHandler<DialogMessage, DialogMessageContext<'_>> for DialogMessageHandler {\n\tfn process_message(&mut self, message: DialogMessage, responses: &mut VecDeque<Message>, context: DialogMessageContext) {\n\t\tlet DialogMessageContext { portfolio, preferences } = context;\n\n\t\tmatch message {\n\t\t\tDialogMessage::ExportDialog(message) => self.export_dialog.process_message(message, responses, ExportDialogMessageContext { portfolio }),\n\t\t\tDialogMessage::NewDocumentDialog(message) => self.new_document_dialog.process_message(message, responses, ()),\n\t\t\tDialogMessage::PreferencesDialog(message) => self.preferences_dialog.process_message(message, responses, PreferencesDialogMessageContext { preferences }),\n\n\t\t\tDialogMessage::Dismiss => {\n\t\t\t\tif let Some(message) = self.on_dismiss.take() {\n\t\t\t\t\tresponses.add(message);\n\t\t\t\t}\n\t\t\t}\n\t\t\tDialogMessage::Close => {\n\t\t\t\tself.on_dismiss = None;\n\t\t\t\tresponses.add(FrontendMessage::DialogClose)\n\t\t\t}\n\t\t\tDialogMessage::CloseAndThen { followups } => {\n\t\t\t\tfor message in followups.into_iter() {\n\t\t\t\t\tresponses.add(message);\n\t\t\t\t}\n\n\t\t\t\t// This come after followups, so that the followups (which can cause the dialog to open) happen first, then we close it afterwards.\n\t\t\t\t// If it comes before, the dialog reopens (and appears to not close at all).\n\t\t\t\tresponses.add(DialogMessage::Close);\n\t\t\t}\n\t\t\tDialogMessage::CloseAllDocumentsWithConfirmation => {\n\t\t\t\tself.on_dismiss = Some(DialogMessage::Close.into());\n\t\t\t\tlet dialog = simple_dialogs::CloseAllDocumentsDialog {\n\t\t\t\t\tunsaved_document_names: portfolio.unsaved_document_names(),\n\t\t\t\t};\n\t\t\t\tdialog.send_dialog_to_frontend(responses);\n\t\t\t}\n\t\t\tDialogMessage::DisplayDialogError { title, description } => {\n\t\t\t\tself.on_dismiss = None;\n\t\t\t\tlet dialog = simple_dialogs::ErrorDialog { title, description };\n\t\t\t\tdialog.send_dialog_to_frontend(responses);\n\t\t\t}\n\t\t\tDialogMessage::RequestAboutGraphiteDialog => {\n\t\t\t\tself.on_dismiss = Some(DialogMessage::Close.into());\n\t\t\t\tresponses.add(FrontendMessage::TriggerAboutGraphiteLocalizedCommitDate {\n\t\t\t\t\tcommit_date: GRAPHITE_GIT_COMMIT_DATE.into(),\n\t\t\t\t});\n\t\t\t}\n\t\t\tDialogMessage::RequestAboutGraphiteDialogWithLocalizedCommitDate {\n\t\t\t\tlocalized_commit_date,\n\t\t\t\tlocalized_commit_year,\n\t\t\t} => {\n\t\t\t\tself.on_dismiss = Some(DialogMessage::Close.into());\n\t\t\t\tlet dialog = AboutGraphiteDialog {\n\t\t\t\t\tlocalized_commit_date,\n\t\t\t\t\tlocalized_commit_year,\n\t\t\t\t};\n\n\t\t\t\tdialog.send_dialog_to_frontend(responses);\n\t\t\t}\n\t\t\tDialogMessage::RequestDemoArtworkDialog => {\n\t\t\t\tself.on_dismiss = Some(DialogMessage::Close.into());\n\t\t\t\tlet dialog = DemoArtworkDialog;\n\t\t\t\tdialog.send_dialog_to_frontend(responses);\n\t\t\t}\n\t\t\tDialogMessage::RequestExportDialog => {\n\t\t\t\tself.on_dismiss = Some(DialogMessage::Close.into());\n\t\t\t\tif let Some(document) = portfolio.active_document() {\n\t\t\t\t\tlet artboards = document\n\t\t\t\t\t\t.metadata()\n\t\t\t\t\t\t.all_layers()\n\t\t\t\t\t\t.filter(|&layer| document.network_interface.is_artboard(&layer.to_node(), &[]))\n\t\t\t\t\t\t.map(|layer| {\n\t\t\t\t\t\t\tlet name = document\n\t\t\t\t\t\t\t\t.network_interface\n\t\t\t\t\t\t\t\t.node_metadata(&layer.to_node(), &[])\n\t\t\t\t\t\t\t\t.map(|node| node.persistent_metadata.display_name.clone())\n\t\t\t\t\t\t\t\t.and_then(|name| if name.is_empty() { None } else { Some(name) })\n\t\t\t\t\t\t\t\t.unwrap_or_else(|| \"Artboard\".to_string());\n\t\t\t\t\t\t\t(layer, name)\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.collect();\n\n\t\t\t\t\tself.export_dialog.artboards = artboards;\n\n\t\t\t\t\tif let ExportBounds::Artboard(layer) = self.export_dialog.bounds\n\t\t\t\t\t\t&& !self.export_dialog.artboards.contains_key(&layer)\n\t\t\t\t\t{\n\t\t\t\t\t\tself.export_dialog.bounds = ExportBounds::AllArtwork;\n\t\t\t\t\t}\n\n\t\t\t\t\tself.export_dialog.has_selection = document.network_interface.selected_nodes().selected_layers(document.metadata()).next().is_some();\n\t\t\t\t\tself.export_dialog.send_dialog_to_frontend(responses);\n\t\t\t\t}\n\t\t\t}\n\t\t\tDialogMessage::RequestLicensesDialogWithLocalizedCommitDate { localized_commit_year } => {\n\t\t\t\tself.on_dismiss = Some(DialogMessage::Close.into());\n\t\t\t\tlet dialog = LicensesDialog { localized_commit_year };\n\t\t\t\tdialog.send_dialog_to_frontend(responses);\n\t\t\t}\n\t\t\tDialogMessage::RequestLicensesThirdPartyDialogWithLicenseText { license_text } => {\n\t\t\t\tself.on_dismiss = Some(DialogMessage::Close.into());\n\t\t\t\tlet dialog = LicensesThirdPartyDialog { license_text };\n\t\t\t\tdialog.send_dialog_to_frontend(responses);\n\t\t\t}\n\t\t\tDialogMessage::RequestNewDocumentDialog => {\n\t\t\t\tself.on_dismiss = Some(DialogMessage::Close.into());\n\t\t\t\tself.new_document_dialog = NewDocumentDialogMessageHandler {\n\t\t\t\t\tname: portfolio.generate_new_document_name(),\n\t\t\t\t\tinfinite: false,\n\t\t\t\t\tdimensions: glam::UVec2::new(1920, 1080),\n\t\t\t\t};\n\t\t\t\tself.new_document_dialog.send_dialog_to_frontend(responses);\n\t\t\t}\n\t\t\tDialogMessage::RequestPreferencesDialog => {\n\t\t\t\tself.on_dismiss = Some(PreferencesDialogMessage::Confirm.into());\n\t\t\t\tself.preferences_dialog.send_dialog_to_frontend(responses, preferences);\n\t\t\t}\n\t\t\tDialogMessage::RequestConfirmRestartDialog { preferences_requiring_restart } => {\n\t\t\t\tself.on_dismiss = Some(DialogMessage::Close.into());\n\t\t\t\tlet dialog = ConfirmRestartDialog { preferences_requiring_restart };\n\t\t\t\tdialog.send_dialog_to_frontend(responses);\n\t\t\t}\n\t\t}\n\t}\n\n\tadvertise_actions!(DialogMessageDiscriminant;\n\t\tCloseAllDocumentsWithConfirmation,\n\t\tRequestExportDialog,\n\t\tRequestNewDocumentDialog,\n\t\tRequestPreferencesDialog,\n\t);\n}\n"
  },
  {
    "path": "editor/src/messages/dialog/export_dialog/export_dialog_message.rs",
    "content": "use crate::messages::frontend::utility_types::{ExportBounds, FileType};\nuse crate::messages::prelude::*;\n\n#[impl_message(Message, DialogMessage, ExportDialog)]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum ExportDialogMessage {\n\tFileType { file_type: FileType },\n\tScaleFactor { factor: f64 },\n\tTransparentBackground { transparent: bool },\n\tExportBounds { bounds: ExportBounds },\n\n\tSubmit,\n}\n"
  },
  {
    "path": "editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs",
    "content": "use crate::messages::frontend::utility_types::{ExportBounds, FileType};\nuse crate::messages::layout::utility_types::widget_prelude::*;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::prelude::*;\n\n#[derive(ExtractField)]\npub struct ExportDialogMessageContext<'a> {\n\tpub portfolio: &'a PortfolioMessageHandler,\n}\n\n/// A dialog to allow users to customize their file export.\n#[derive(Debug, Clone, ExtractField)]\npub struct ExportDialogMessageHandler {\n\tpub file_type: FileType,\n\tpub scale_factor: f64,\n\tpub bounds: ExportBounds,\n\tpub transparent_background: bool,\n\tpub artboards: HashMap<LayerNodeIdentifier, String>,\n\tpub has_selection: bool,\n}\n\nimpl Default for ExportDialogMessageHandler {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tfile_type: Default::default(),\n\t\t\tscale_factor: 1.,\n\t\t\tbounds: Default::default(),\n\t\t\ttransparent_background: false,\n\t\t\tartboards: Default::default(),\n\t\t\thas_selection: false,\n\t\t}\n\t}\n}\n\n#[message_handler_data]\nimpl MessageHandler<ExportDialogMessage, ExportDialogMessageContext<'_>> for ExportDialogMessageHandler {\n\tfn process_message(&mut self, message: ExportDialogMessage, responses: &mut VecDeque<Message>, context: ExportDialogMessageContext) {\n\t\tlet ExportDialogMessageContext { portfolio } = context;\n\n\t\tmatch message {\n\t\t\tExportDialogMessage::FileType { file_type } => self.file_type = file_type,\n\t\t\tExportDialogMessage::ScaleFactor { factor } => self.scale_factor = factor,\n\t\t\tExportDialogMessage::TransparentBackground { transparent } => self.transparent_background = transparent,\n\t\t\tExportDialogMessage::ExportBounds { bounds } => self.bounds = bounds,\n\n\t\t\tExportDialogMessage::Submit => {\n\t\t\t\tlet artboard_name = match self.bounds {\n\t\t\t\t\tExportBounds::Artboard(layer) => self.artboards.get(&layer).cloned(),\n\t\t\t\t\t_ => None,\n\t\t\t\t};\n\t\t\t\tresponses.add_front(PortfolioMessage::SubmitDocumentExport {\n\t\t\t\t\tname: portfolio.active_document().map(|document| document.name.clone()).unwrap_or_default(),\n\t\t\t\t\tfile_type: self.file_type,\n\t\t\t\t\tscale_factor: self.scale_factor,\n\t\t\t\t\tbounds: self.bounds,\n\t\t\t\t\ttransparent_background: self.file_type != FileType::Jpg && self.transparent_background,\n\t\t\t\t\tartboard_name,\n\t\t\t\t\tartboard_count: self.artboards.len(),\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\tself.send_dialog_to_frontend(responses);\n\t}\n\n\tadvertise_actions!(ExportDialogUpdate;\n\t);\n}\n\nimpl DialogLayoutHolder for ExportDialogMessageHandler {\n\tconst ICON: &'static str = \"File\";\n\tconst TITLE: &'static str = \"Export\";\n\n\tfn layout_buttons(&self) -> Layout {\n\t\tlet widgets = vec![\n\t\t\tTextButton::new(\"Export\")\n\t\t\t\t.emphasized(true)\n\t\t\t\t.on_update(|_| {\n\t\t\t\t\tDialogMessage::CloseAndThen {\n\t\t\t\t\t\tfollowups: vec![ExportDialogMessage::Submit.into()],\n\t\t\t\t\t}\n\t\t\t\t\t.into()\n\t\t\t\t})\n\t\t\t\t.widget_instance(),\n\t\t\tTextButton::new(\"Cancel\").on_update(|_| FrontendMessage::DialogClose.into()).widget_instance(),\n\t\t];\n\n\t\tLayout(vec![LayoutGroup::row(widgets)])\n\t}\n}\n\nimpl LayoutHolder for ExportDialogMessageHandler {\n\tfn layout(&self) -> Layout {\n\t\tlet entries = [(FileType::Png, \"PNG\"), (FileType::Jpg, \"JPG\"), (FileType::Svg, \"SVG\")]\n\t\t\t.into_iter()\n\t\t\t.map(|(file_type, name)| {\n\t\t\t\tRadioEntryData::new(format!(\"{file_type:?}\"))\n\t\t\t\t\t.label(name)\n\t\t\t\t\t.on_update(move |_| ExportDialogMessage::FileType { file_type }.into())\n\t\t\t})\n\t\t\t.collect();\n\n\t\tlet export_type = vec![\n\t\t\tTextLabel::new(\"File Type\").table_align(true).min_width(100).widget_instance(),\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\tRadioInput::new(entries).selected_index(Some(self.file_type as u32)).widget_instance(),\n\t\t];\n\n\t\tlet resolution = vec![\n\t\t\tTextLabel::new(\"Scale Factor\").table_align(true).min_width(100).widget_instance(),\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\tNumberInput::new(Some(self.scale_factor))\n\t\t\t\t.unit(\"\")\n\t\t\t\t.min(0.)\n\t\t\t\t.max((1_u64 << f64::MANTISSA_DIGITS) as f64)\n\t\t\t\t.increment_step(0.5)\n\t\t\t\t.disabled(self.file_type == FileType::Svg)\n\t\t\t\t.on_update(|number_input: &NumberInput| ExportDialogMessage::ScaleFactor { factor: number_input.value.unwrap() }.into())\n\t\t\t\t.min_width(200)\n\t\t\t\t.widget_instance(),\n\t\t];\n\n\t\tlet standard_bounds = vec![\n\t\t\t(ExportBounds::AllArtwork, \"All Artwork\".to_string(), false),\n\t\t\t(ExportBounds::Selection, \"Selection\".to_string(), !self.has_selection),\n\t\t];\n\t\tlet artboards = self.artboards.iter().map(|(&layer, name)| (ExportBounds::Artboard(layer), name.to_string(), false)).collect();\n\t\tlet choices = [standard_bounds, artboards];\n\n\t\tlet current_bounds = if !self.has_selection && self.bounds == ExportBounds::Selection {\n\t\t\tExportBounds::AllArtwork\n\t\t} else {\n\t\t\tself.bounds\n\t\t};\n\t\tlet index = choices.iter().flatten().position(|(bounds, _, _)| *bounds == current_bounds).unwrap_or(0);\n\n\t\tlet mut entries = choices\n\t\t\t.into_iter()\n\t\t\t.map(|choice| {\n\t\t\t\tchoice\n\t\t\t\t\t.into_iter()\n\t\t\t\t\t.map(|(bounds, name, disabled)| {\n\t\t\t\t\t\tMenuListEntry::new(format!(\"{bounds:?}\"))\n\t\t\t\t\t\t\t.label(name)\n\t\t\t\t\t\t\t.on_commit(move |_| ExportDialogMessage::ExportBounds { bounds }.into())\n\t\t\t\t\t\t\t.disabled(disabled)\n\t\t\t\t\t})\n\t\t\t\t\t.collect::<Vec<_>>()\n\t\t\t})\n\t\t\t.collect::<Vec<_>>();\n\n\t\tif entries[1].is_empty() {\n\t\t\tentries.remove(1);\n\t\t}\n\n\t\tlet export_area = vec![\n\t\t\tTextLabel::new(\"Bounds\").table_align(true).min_width(100).widget_instance(),\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\tDropdownInput::new(entries).selected_index(Some(index as u32)).widget_instance(),\n\t\t];\n\n\t\tlet checkbox_id = CheckboxId::new();\n\t\tlet transparent_background = vec![\n\t\t\tTextLabel::new(\"Transparency\").table_align(true).min_width(100).for_checkbox(checkbox_id).widget_instance(),\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\tCheckboxInput::new(self.transparent_background)\n\t\t\t\t.disabled(self.file_type == FileType::Jpg)\n\t\t\t\t.on_update(move |value: &CheckboxInput| ExportDialogMessage::TransparentBackground { transparent: value.checked }.into())\n\t\t\t\t.for_label(checkbox_id)\n\t\t\t\t.widget_instance(),\n\t\t];\n\n\t\tLayout(vec![\n\t\t\tLayoutGroup::row(export_type),\n\t\t\tLayoutGroup::row(resolution),\n\t\t\tLayoutGroup::row(export_area),\n\t\t\tLayoutGroup::row(transparent_background),\n\t\t])\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/dialog/export_dialog/mod.rs",
    "content": "mod export_dialog_message;\nmod export_dialog_message_handler;\n\n#[doc(inline)]\npub use export_dialog_message::{ExportDialogMessage, ExportDialogMessageDiscriminant};\n#[doc(inline)]\npub use export_dialog_message_handler::{ExportDialogMessageContext, ExportDialogMessageHandler};\n"
  },
  {
    "path": "editor/src/messages/dialog/mod.rs",
    "content": "//! Handles dialogs that appear as floating menus in the center of the editor window.\n//!\n//! Dialogs are represented as structs that implement the `DialogLayoutHolder` trait.\n//!\n//! To open a dialog, call the function `send_dialog_to_frontend()` on the dialog struct.\n//! Then dialog can be opened by sending the `FrontendMessage::DisplayDialog` message;\n\nmod dialog_message;\nmod dialog_message_handler;\n\npub mod export_dialog;\npub mod new_document_dialog;\npub mod preferences_dialog;\npub mod simple_dialogs;\n\n#[doc(inline)]\npub use dialog_message::{DialogMessage, DialogMessageDiscriminant};\n#[doc(inline)]\npub use dialog_message_handler::{DialogMessageContext, DialogMessageHandler};\n"
  },
  {
    "path": "editor/src/messages/dialog/new_document_dialog/mod.rs",
    "content": "mod new_document_dialog_message;\nmod new_document_dialog_message_handler;\n\n#[doc(inline)]\npub use new_document_dialog_message::{NewDocumentDialogMessage, NewDocumentDialogMessageDiscriminant};\n#[doc(inline)]\npub use new_document_dialog_message_handler::NewDocumentDialogMessageHandler;\n"
  },
  {
    "path": "editor/src/messages/dialog/new_document_dialog/new_document_dialog_message.rs",
    "content": "use crate::messages::prelude::*;\n\n#[impl_message(Message, DialogMessage, NewDocumentDialog)]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum NewDocumentDialogMessage {\n\tName { name: String },\n\tInfinite { infinite: bool },\n\tDimensionsX { width: f64 },\n\tDimensionsY { height: f64 },\n\n\tSubmit,\n}\n"
  },
  {
    "path": "editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs",
    "content": "use crate::messages::layout::utility_types::widget_prelude::*;\nuse crate::messages::prelude::*;\nuse glam::{IVec2, UVec2};\nuse graph_craft::document::NodeId;\n\n/// A dialog to allow users to set some initial options about a new document.\n#[derive(Debug, Clone, Default, ExtractField)]\npub struct NewDocumentDialogMessageHandler {\n\tpub name: String,\n\tpub infinite: bool,\n\tpub dimensions: UVec2,\n}\n\n#[message_handler_data]\nimpl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHandler {\n\tfn process_message(&mut self, message: NewDocumentDialogMessage, responses: &mut VecDeque<Message>, _: ()) {\n\t\tmatch message {\n\t\t\tNewDocumentDialogMessage::Name { name } => self.name = name,\n\t\t\tNewDocumentDialogMessage::Infinite { infinite } => self.infinite = infinite,\n\t\t\tNewDocumentDialogMessage::DimensionsX { width } => self.dimensions.x = width as u32,\n\t\t\tNewDocumentDialogMessage::DimensionsY { height } => self.dimensions.y = height as u32,\n\t\t\tNewDocumentDialogMessage::Submit => {\n\t\t\t\tresponses.add(PortfolioMessage::NewDocumentWithName { name: self.name.clone() });\n\n\t\t\t\tlet create_artboard = !self.infinite && self.dimensions.x > 0 && self.dimensions.y > 0;\n\t\t\t\tif create_artboard {\n\t\t\t\t\tresponses.add(GraphOperationMessage::NewArtboard {\n\t\t\t\t\t\tid: NodeId::new(),\n\t\t\t\t\t\tartboard: graphene_std::Artboard::new(IVec2::ZERO, self.dimensions.as_ivec2()),\n\t\t\t\t\t});\n\t\t\t\t\tresponses.add(NavigationMessage::CanvasPan { delta: self.dimensions.as_dvec2() });\n\t\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\n\t\t\t\t\tresponses.add(ViewportMessage::RepropagateUpdate);\n\n\t\t\t\t\tresponses.add(DeferMessage::AfterNavigationReady {\n\t\t\t\t\t\tmessages: vec![\n\t\t\t\t\t\t\tDocumentMessage::ZoomCanvasToFitAll.into(),\n\t\t\t\t\t\t\tDocumentMessage::DeselectAllLayers.into(),\n\t\t\t\t\t\t\tPortfolioMessage::AutoSaveActiveDocument.into(),\n\t\t\t\t\t\t],\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tresponses.add(DocumentMessage::MarkAsSaved);\n\t\t\t}\n\t\t}\n\n\t\tself.send_dialog_to_frontend(responses);\n\t}\n\n\tadvertise_actions!(NewDocumentDialogUpdate;\n\t);\n}\n\nimpl DialogLayoutHolder for NewDocumentDialogMessageHandler {\n\tconst ICON: &'static str = \"File\";\n\tconst TITLE: &'static str = \"New Document\";\n\n\tfn layout_buttons(&self) -> Layout {\n\t\tlet widgets = vec![\n\t\t\tTextButton::new(\"OK\")\n\t\t\t\t.emphasized(true)\n\t\t\t\t.on_update(|_| {\n\t\t\t\t\tDialogMessage::CloseAndThen {\n\t\t\t\t\t\tfollowups: vec![NewDocumentDialogMessage::Submit.into()],\n\t\t\t\t\t}\n\t\t\t\t\t.into()\n\t\t\t\t})\n\t\t\t\t.widget_instance(),\n\t\t\tTextButton::new(\"Cancel\").on_update(|_| FrontendMessage::DialogClose.into()).widget_instance(),\n\t\t];\n\n\t\tLayout(vec![LayoutGroup::row(widgets)])\n\t}\n}\n\nimpl LayoutHolder for NewDocumentDialogMessageHandler {\n\tfn layout(&self) -> Layout {\n\t\tlet name = vec![\n\t\t\tTextLabel::new(\"Name\").table_align(true).min_width(90).widget_instance(),\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\tTextInput::new(&self.name)\n\t\t\t\t.on_update(|text_input: &TextInput| NewDocumentDialogMessage::Name { name: text_input.value.clone() }.into())\n\t\t\t\t.min_width(204) // Matches the 100px of both NumberInputs below + the 4px of the Unrelated-type separator\n\t\t\t\t.widget_instance(),\n\t\t];\n\n\t\tlet checkbox_id = CheckboxId::new();\n\t\tlet infinite = vec![\n\t\t\tTextLabel::new(\"Infinite Canvas\").table_align(true).min_width(90).for_checkbox(checkbox_id).widget_instance(),\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\tCheckboxInput::new(self.infinite)\n\t\t\t\t.on_update(|checkbox_input: &CheckboxInput| NewDocumentDialogMessage::Infinite { infinite: checkbox_input.checked }.into())\n\t\t\t\t.for_label(checkbox_id)\n\t\t\t\t.widget_instance(),\n\t\t];\n\n\t\tlet scale = vec![\n\t\t\tTextLabel::new(\"Dimensions\").table_align(true).min_width(90).widget_instance(),\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\tNumberInput::new(Some(self.dimensions.x as f64))\n\t\t\t\t.label(\"W\")\n\t\t\t\t.unit(\" px\")\n\t\t\t\t.min(0.)\n\t\t\t\t.max((1_u64 << f64::MANTISSA_DIGITS) as f64)\n\t\t\t\t.is_integer(true)\n\t\t\t\t.disabled(self.infinite)\n\t\t\t\t.min_width(100)\n\t\t\t\t.on_update(|number_input: &NumberInput| NewDocumentDialogMessage::DimensionsX { width: number_input.value.unwrap() }.into())\n\t\t\t\t.widget_instance(),\n\t\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\t\tNumberInput::new(Some(self.dimensions.y as f64))\n\t\t\t\t.label(\"H\")\n\t\t\t\t.unit(\" px\")\n\t\t\t\t.min(0.)\n\t\t\t\t.max((1_u64 << f64::MANTISSA_DIGITS) as f64)\n\t\t\t\t.is_integer(true)\n\t\t\t\t.disabled(self.infinite)\n\t\t\t\t.min_width(100)\n\t\t\t\t.on_update(|number_input: &NumberInput| NewDocumentDialogMessage::DimensionsY { height: number_input.value.unwrap() }.into())\n\t\t\t\t.widget_instance(),\n\t\t];\n\n\t\tLayout(vec![LayoutGroup::row(name), LayoutGroup::row(infinite), LayoutGroup::row(scale)])\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/dialog/preferences_dialog/mod.rs",
    "content": "mod preferences_dialog_message;\nmod preferences_dialog_message_handler;\n\n#[doc(inline)]\npub use preferences_dialog_message::{PreferencesDialogMessage, PreferencesDialogMessageDiscriminant};\n#[doc(inline)]\npub use preferences_dialog_message_handler::{PreferencesDialogMessageContext, PreferencesDialogMessageHandler};\n"
  },
  {
    "path": "editor/src/messages/dialog/preferences_dialog/preferences_dialog_message.rs",
    "content": "use crate::messages::prelude::*;\n\n#[impl_message(Message, DialogMessage, PreferencesDialog)]\n#[derive(Eq, PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum PreferencesDialogMessage {\n\tMayRequireRestart,\n\tConfirm,\n\tUpdate,\n}\n"
  },
  {
    "path": "editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs",
    "content": "use crate::consts::{VIEWPORT_ZOOM_WHEEL_RATE, VIEWPORT_ZOOM_WHEEL_RATE_CHANGE};\nuse crate::messages::layout::utility_types::widget_prelude::*;\nuse crate::messages::portfolio::document::utility_types::wires::GraphWireStyle;\nuse crate::messages::preferences::SelectionMode;\nuse crate::messages::prelude::*;\nuse graphene_std::render_node::{EditorPreferences, wgpu_available};\n\n#[derive(ExtractField)]\npub struct PreferencesDialogMessageContext<'a> {\n\tpub preferences: &'a PreferencesMessageHandler,\n}\n\n/// A dialog to allow users to customize Graphite editor options\n#[derive(Debug, Clone, Default, ExtractField)]\npub struct PreferencesDialogMessageHandler {\n\tunmodified_preferences: Option<PreferencesMessageHandler>,\n}\n\n#[message_handler_data]\nimpl MessageHandler<PreferencesDialogMessage, PreferencesDialogMessageContext<'_>> for PreferencesDialogMessageHandler {\n\tfn process_message(&mut self, message: PreferencesDialogMessage, responses: &mut VecDeque<Message>, context: PreferencesDialogMessageContext) {\n\t\tlet PreferencesDialogMessageContext { preferences } = context;\n\t\tmatch message {\n\t\t\tPreferencesDialogMessage::MayRequireRestart => {\n\t\t\t\tif self.unmodified_preferences.is_none() {\n\t\t\t\t\tself.unmodified_preferences = Some(preferences.clone());\n\t\t\t\t}\n\t\t\t}\n\t\t\tPreferencesDialogMessage::Confirm => {\n\t\t\t\tif let Some(unmodified_preferences) = &self.unmodified_preferences\n\t\t\t\t\t&& let preferences_requiring_restart = unmodified_preferences.preferences_requiring_restart(preferences)\n\t\t\t\t\t&& !preferences_requiring_restart.is_empty()\n\t\t\t\t{\n\t\t\t\t\tresponses.add(DialogMessage::RequestConfirmRestartDialog { preferences_requiring_restart });\n\t\t\t\t} else {\n\t\t\t\t\tresponses.add(DialogMessage::Close);\n\t\t\t\t}\n\t\t\t}\n\t\t\tPreferencesDialogMessage::Update => {}\n\t\t}\n\t}\n\n\tadvertise_actions!(PreferencesDialogUpdate;\n\t);\n}\n\n// This doesn't actually implement the `DialogLayoutHolder` trait like the other dialog message handlers.\n// That's because we need to give `send_layout` the `preferences` argument, which is not part of the trait.\n// However, it's important to keep the methods in sync with those from the trait for consistency.\nimpl PreferencesDialogMessageHandler {\n\tconst ICON: &'static str = \"Settings\";\n\tconst TITLE: &'static str = \"Editor Preferences\";\n\n\tfn layout(&self, preferences: &PreferencesMessageHandler) -> Layout {\n\t\tlet mut rows = Vec::new();\n\n\t\t// ==========\n\t\t// NAVIGATION\n\t\t// ==========\n\t\t{\n\t\t\tlet header = vec![TextLabel::new(\"Navigation\").italic(true).widget_instance()];\n\n\t\t\tlet zoom_rate_description = \"\n\t\t\t\tAdjust how fast zooming occurs when using the scroll wheel or pinch gesture.\\n\\\n\t\t\t\t\\n\\\n\t\t\t\t*Default: 50.*\n\t\t\t\t\"\n\t\t\t.trim();\n\t\t\tlet zoom_rate_label = vec![\n\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\tTextLabel::new(\"Zoom Rate\").tooltip_label(\"Zoom Rate\").tooltip_description(zoom_rate_description).widget_instance(),\n\t\t\t];\n\t\t\tlet zoom_rate = vec![\n\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\tNumberInput::new(Some(map_zoom_rate_to_display(preferences.viewport_zoom_wheel_rate)))\n\t\t\t\t\t.tooltip_label(\"Zoom Rate\")\n\t\t\t\t\t.tooltip_description(zoom_rate_description)\n\t\t\t\t\t.mode_range()\n\t\t\t\t\t.int()\n\t\t\t\t\t.min(1.)\n\t\t\t\t\t.max(100.)\n\t\t\t\t\t.on_update(|number_input: &NumberInput| {\n\t\t\t\t\t\tif let Some(display_value) = number_input.value {\n\t\t\t\t\t\t\tlet actual_rate = map_display_to_zoom_rate(display_value);\n\t\t\t\t\t\t\tPreferencesMessage::ViewportZoomWheelRate { rate: actual_rate }.into()\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tPreferencesMessage::ViewportZoomWheelRate { rate: VIEWPORT_ZOOM_WHEEL_RATE }.into()\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\t.widget_instance(),\n\t\t\t];\n\n\t\t\tlet checkbox_id = CheckboxId::new();\n\t\t\tlet zoom_with_scroll_description = \"\n\t\t\t\tUse the scroll wheel for zooming instead of vertically panning (not recommended for trackpads).\\n\\\n\t\t\t\t\\n\\\n\t\t\t\t*Default: Off.*\n\t\t\t\t\"\n\t\t\t.trim();\n\t\t\tlet zoom_with_scroll = vec![\n\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\tCheckboxInput::new(preferences.zoom_with_scroll)\n\t\t\t\t\t.tooltip_label(\"Zoom with Scroll\")\n\t\t\t\t\t.tooltip_description(zoom_with_scroll_description)\n\t\t\t\t\t.on_update(|checkbox_input: &CheckboxInput| {\n\t\t\t\t\t\tPreferencesMessage::ModifyLayout {\n\t\t\t\t\t\t\tzoom_with_scroll: checkbox_input.checked,\n\t\t\t\t\t\t}\n\t\t\t\t\t\t.into()\n\t\t\t\t\t})\n\t\t\t\t\t.for_label(checkbox_id)\n\t\t\t\t\t.widget_instance(),\n\t\t\t\tTextLabel::new(\"Zoom with Scroll\")\n\t\t\t\t\t.tooltip_label(\"Zoom with Scroll\")\n\t\t\t\t\t.tooltip_description(zoom_with_scroll_description)\n\t\t\t\t\t.for_checkbox(checkbox_id)\n\t\t\t\t\t.widget_instance(),\n\t\t\t];\n\n\t\t\trows.extend_from_slice(&[header, zoom_rate_label, zoom_rate, zoom_with_scroll]);\n\t\t}\n\n\t\t// =======\n\t\t// EDITING\n\t\t// =======\n\t\t{\n\t\t\tlet header = vec![TextLabel::new(\"Editing\").italic(true).widget_instance()];\n\n\t\t\tlet selection_label_description = \"\n\t\t\t\tChoose how targets are selected within dragged rectangular and lasso areas.\\n\\\n\t\t\t\t\\n\\\n\t\t\t\t*Default: Touched.*\n\t\t\t\t\"\n\t\t\t.trim();\n\t\t\tlet selection_label = vec![\n\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\tTextLabel::new(\"Selection\")\n\t\t\t\t\t.tooltip_label(\"Selection\")\n\t\t\t\t\t.tooltip_description(selection_label_description)\n\t\t\t\t\t.widget_instance(),\n\t\t\t];\n\n\t\t\tlet selection_mode = RadioInput::new(vec![\n\t\t\t\tRadioEntryData::new(SelectionMode::Touched.to_string())\n\t\t\t\t\t.label(SelectionMode::Touched.to_string())\n\t\t\t\t\t.tooltip_label(SelectionMode::Touched.to_string())\n\t\t\t\t\t.tooltip_description(SelectionMode::Touched.tooltip_description())\n\t\t\t\t\t.on_update(move |_| {\n\t\t\t\t\t\tPreferencesMessage::SelectionMode {\n\t\t\t\t\t\t\tselection_mode: SelectionMode::Touched,\n\t\t\t\t\t\t}\n\t\t\t\t\t\t.into()\n\t\t\t\t\t}),\n\t\t\t\tRadioEntryData::new(SelectionMode::Enclosed.to_string())\n\t\t\t\t\t.label(SelectionMode::Enclosed.to_string())\n\t\t\t\t\t.tooltip_label(SelectionMode::Enclosed.to_string())\n\t\t\t\t\t.tooltip_description(SelectionMode::Enclosed.tooltip_description())\n\t\t\t\t\t.on_update(move |_| {\n\t\t\t\t\t\tPreferencesMessage::SelectionMode {\n\t\t\t\t\t\t\tselection_mode: SelectionMode::Enclosed,\n\t\t\t\t\t\t}\n\t\t\t\t\t\t.into()\n\t\t\t\t\t}),\n\t\t\t\tRadioEntryData::new(SelectionMode::Directional.to_string())\n\t\t\t\t\t.label(SelectionMode::Directional.to_string())\n\t\t\t\t\t.tooltip_label(SelectionMode::Directional.to_string())\n\t\t\t\t\t.tooltip_description(SelectionMode::Directional.tooltip_description())\n\t\t\t\t\t.on_update(move |_| {\n\t\t\t\t\t\tPreferencesMessage::SelectionMode {\n\t\t\t\t\t\t\tselection_mode: SelectionMode::Directional,\n\t\t\t\t\t\t}\n\t\t\t\t\t\t.into()\n\t\t\t\t\t}),\n\t\t\t])\n\t\t\t.selected_index(Some(preferences.selection_mode as u32))\n\t\t\t.widget_instance();\n\t\t\tlet selection_mode = vec![\n\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\tselection_mode,\n\t\t\t];\n\n\t\t\trows.extend_from_slice(&[header, selection_label, selection_mode]);\n\t\t}\n\n\t\t// =========\n\t\t// INTERFACE\n\t\t// =========\n\t\t#[cfg(not(target_family = \"wasm\"))]\n\t\t{\n\t\t\tlet header = vec![TextLabel::new(\"Interface\").italic(true).widget_instance()];\n\n\t\t\tlet scale_description = \"\n\t\t\t\tAdjust the scale of the entire user interface.\\n\\\n\t\t\t\t\\n\\\n\t\t\t\t*Default: 100%.*\n\t\t\t\t\"\n\t\t\t.trim();\n\t\t\tlet scale_label = vec![\n\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\tTextLabel::new(\"Scale\").tooltip_label(\"Scale\").tooltip_description(scale_description).widget_instance(),\n\t\t\t];\n\t\t\tlet scale = vec![\n\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\tNumberInput::new(Some(ui_scale_to_display(preferences.ui_scale)))\n\t\t\t\t\t.tooltip_label(\"Scale\")\n\t\t\t\t\t.tooltip_description(scale_description)\n\t\t\t\t\t.mode_range()\n\t\t\t\t\t.int()\n\t\t\t\t\t.min(ui_scale_to_display(crate::consts::UI_SCALE_MIN))\n\t\t\t\t\t.max(ui_scale_to_display(crate::consts::UI_SCALE_MAX))\n\t\t\t\t\t.unit(\"%\")\n\t\t\t\t\t.on_update(|number_input: &NumberInput| {\n\t\t\t\t\t\tif let Some(display_value) = number_input.value {\n\t\t\t\t\t\t\tlet scale = map_display_to_ui_scale(display_value);\n\t\t\t\t\t\t\tPreferencesMessage::UIScale { scale }.into()\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tPreferencesMessage::UIScale {\n\t\t\t\t\t\t\t\tscale: crate::consts::UI_SCALE_DEFAULT,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t.into()\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\t.widget_instance(),\n\t\t\t];\n\n\t\t\trows.extend_from_slice(&[header, scale_label, scale]);\n\t\t}\n\n\t\t// ============\n\t\t// EXPERIMENTAL\n\t\t// ============\n\t\t{\n\t\t\tlet header = vec![TextLabel::new(\"Experimental\").italic(true).widget_instance()];\n\n\t\t\tlet node_graph_section_description = \"\n\t\t\t\tConfigure the appearance of the wires running between node connections in the graph.\\n\\\n\t\t\t\t\\n\\\n\t\t\t\t*Default: Direct.*\n\t\t\t\t\"\n\t\t\t.trim();\n\t\t\tlet node_graph_wires_label = vec![\n\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\tTextLabel::new(\"Node Graph Wires\")\n\t\t\t\t\t.tooltip_label(\"Node Graph Wires\")\n\t\t\t\t\t.tooltip_description(node_graph_section_description)\n\t\t\t\t\t.widget_instance(),\n\t\t\t];\n\t\t\tlet graph_wire_style = RadioInput::new(vec![\n\t\t\t\tRadioEntryData::new(GraphWireStyle::Direct.to_string())\n\t\t\t\t\t.label(GraphWireStyle::Direct.to_string())\n\t\t\t\t\t.tooltip_label(GraphWireStyle::Direct.to_string())\n\t\t\t\t\t.tooltip_description(GraphWireStyle::Direct.tooltip_description())\n\t\t\t\t\t.on_update(move |_| PreferencesMessage::GraphWireStyle { style: GraphWireStyle::Direct }.into()),\n\t\t\t\tRadioEntryData::new(GraphWireStyle::GridAligned.to_string())\n\t\t\t\t\t.label(GraphWireStyle::GridAligned.to_string())\n\t\t\t\t\t.tooltip_label(GraphWireStyle::GridAligned.to_string())\n\t\t\t\t\t.tooltip_description(GraphWireStyle::GridAligned.tooltip_description())\n\t\t\t\t\t.on_update(move |_| PreferencesMessage::GraphWireStyle { style: GraphWireStyle::GridAligned }.into()),\n\t\t\t])\n\t\t\t.selected_index(Some(preferences.graph_wire_style as u32))\n\t\t\t.widget_instance();\n\t\t\tlet graph_wire_style = vec![\n\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\tgraph_wire_style,\n\t\t\t];\n\n\t\t\tlet checkbox_id = CheckboxId::new();\n\t\t\tlet brush_tool_description = \"\n\t\t\t\tEnable the Brush tool to support basic raster-based layer painting.\\n\\\n\t\t\t\t\\n\\\n\t\t\t\tThis legacy experimental tool has performance and quality limitations and is slated for replacement in future versions of Graphite that will have a renewed focus on raster graphics editing.\\n\\\n\t\t\t\t\\n\\\n\t\t\t\tContent created with the Brush tool may not be compatible with future versions of Graphite.\\n\\\n\t\t\t\t\\n\\\n\t\t\t\t*Default: Off.*\n\t\t\t\t\"\n\t\t\t.trim();\n\t\t\tlet brush_tool = vec![\n\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\tCheckboxInput::new(preferences.brush_tool)\n\t\t\t\t\t.tooltip_label(\"Brush Tool\")\n\t\t\t\t\t.tooltip_description(brush_tool_description)\n\t\t\t\t\t.on_update(|checkbox_input: &CheckboxInput| PreferencesMessage::BrushTool { enabled: checkbox_input.checked }.into())\n\t\t\t\t\t.for_label(checkbox_id)\n\t\t\t\t\t.widget_instance(),\n\t\t\t\tTextLabel::new(\"Brush Tool\")\n\t\t\t\t\t.tooltip_label(\"Brush Tool\")\n\t\t\t\t\t.tooltip_description(brush_tool_description)\n\t\t\t\t\t.for_checkbox(checkbox_id)\n\t\t\t\t\t.widget_instance(),\n\t\t\t];\n\n\t\t\trows.extend_from_slice(&[header, node_graph_wires_label, graph_wire_style, brush_tool]);\n\t\t}\n\n\t\t// =============\n\t\t// COMPATIBILITY\n\t\t// =============\n\t\t{\n\t\t\tlet wgpu_available = wgpu_available().unwrap_or(false);\n\t\t\tlet is_desktop = cfg!(not(target_family = \"wasm\"));\n\t\t\tif wgpu_available || is_desktop {\n\t\t\t\tlet header = vec![TextLabel::new(\"Compatibility\").italic(true).widget_instance()];\n\t\t\t\trows.push(header);\n\t\t\t}\n\n\t\t\tif wgpu_available {\n\t\t\t\tlet render_tile_resolution_description = \"\n\t\t\t\t\tMaximum X or Y resolution per render tile. Larger tiles may improve performance but can cause flickering or missing content in complex artwork if set too high.\\n\\\n\t\t\t\t\t\\n\\\n\t\t\t\t\t*Default: 1280 px.*\n\t\t\t\t\t\"\n\t\t\t\t.trim();\n\t\t\t\tlet render_tile_resolution_label = vec![\n\t\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\t\tTextLabel::new(\"Render Tile Resolution\")\n\t\t\t\t\t\t.tooltip_label(\"Render Tile Resolution\")\n\t\t\t\t\t\t.tooltip_description(render_tile_resolution_description)\n\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t];\n\t\t\t\tlet render_tile_resolution = vec![\n\t\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\t\tNumberInput::new(Some(preferences.max_render_region_size as f64))\n\t\t\t\t\t\t.tooltip_label(\"Render Tile Resolution\")\n\t\t\t\t\t\t.tooltip_description(render_tile_resolution_description)\n\t\t\t\t\t\t.mode_range()\n\t\t\t\t\t\t.int()\n\t\t\t\t\t\t.min(256.)\n\t\t\t\t\t\t.max(4096.)\n\t\t\t\t\t\t.increment_step(256.)\n\t\t\t\t\t\t.unit(\" px\")\n\t\t\t\t\t\t.on_update(|number_input: &NumberInput| {\n\t\t\t\t\t\t\tlet size = number_input.value.unwrap_or(EditorPreferences::default().max_render_region_size as f64) as u32;\n\t\t\t\t\t\t\tPreferencesMessage::MaxRenderRegionSize { size }.into()\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t];\n\n\t\t\t\trows.extend_from_slice(&[render_tile_resolution_label, render_tile_resolution]);\n\t\t\t}\n\n\t\t\tif is_desktop {\n\t\t\t\tlet ui_acceleration_description = \"\n\t\t\t\t\tUse the CPU to draw the Graphite user interface (areas outside of the canvas) instead of the GPU. This does not affect the rendering of artwork in the canvas, which remains hardware accelerated.\\n\\\n\t\t\t\t\t\\n\\\n\t\t\t\t\tDisabling UI acceleration may slightly degrade performance, so this should be used as a workaround only if issues are observed with displaying the UI. This setting may become enabled automatically if Graphite launches, detects that it cannot draw the UI normally, and restarts in compatibility mode.\\n\\\n\t\t\t\t\t\\n\\\n\t\t\t\t\t*Default: Off.*\n\t\t\t\t\t\"\n\t\t\t\t.trim();\n\n\t\t\t\tlet checkbox_id = CheckboxId::new();\n\t\t\t\tlet ui_acceleration = vec![\n\t\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\t\tCheckboxInput::new(preferences.disable_ui_acceleration)\n\t\t\t\t\t\t.tooltip_label(\"Disable UI Acceleration\")\n\t\t\t\t\t\t.tooltip_description(ui_acceleration_description)\n\t\t\t\t\t\t.on_update(|number_input: &CheckboxInput| Message::Batched {\n\t\t\t\t\t\t\tmessages: Box::new([\n\t\t\t\t\t\t\t\tPreferencesDialogMessage::MayRequireRestart.into(),\n\t\t\t\t\t\t\t\tPreferencesMessage::DisableUIAcceleration {\n\t\t\t\t\t\t\t\t\tdisable_ui_acceleration: number_input.checked,\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t.into(),\n\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.for_label(checkbox_id)\n\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t\tTextLabel::new(\"Disable UI Acceleration\")\n\t\t\t\t\t\t.tooltip_label(\"Disable UI Acceleration\")\n\t\t\t\t\t\t.tooltip_description(ui_acceleration_description)\n\t\t\t\t\t\t.for_checkbox(checkbox_id)\n\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t];\n\n\t\t\t\trows.push(ui_acceleration);\n\t\t\t}\n\n\t\t\t#[cfg(target_os = \"macos\")]\n\t\t\t{\n\t\t\t\tlet vsync_description = \"\n\t\t\t\t\tRender frames with vertical synchronization (v-sync) to prevent visual tearing within Graphite and the operating system compositor. This introduces increased input latency which is more noticeable on lower refresh rate displays. Future versions of Graphite will aim to reduce the macOS-specific latency without tearing artifacts.\\n\\\n\t\t\t\t\t\\n\\\n\t\t\t\t\tThe application will restart for this change to take effect.\\n\\\n\t\t\t\t\t\\n\\\n\t\t\t\t\t*Default: Off.*\n\t\t\t\t\t\"\n\t\t\t\t.trim();\n\n\t\t\t\tlet checkbox_id = CheckboxId::new();\n\t\t\t\tlet vsync_checked = preferences.vsync;\n\n\t\t\t\tlet vsync = vec![\n\t\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\t\tCheckboxInput::new(vsync_checked)\n\t\t\t\t\t\t.tooltip_label(\"Enable V-Sync\")\n\t\t\t\t\t\t.tooltip_description(vsync_description)\n\t\t\t\t\t\t.on_update(|checkbox_input: &CheckboxInput| Message::Batched {\n\t\t\t\t\t\t\tmessages: Box::new([PreferencesDialogMessage::MayRequireRestart.into(), PreferencesMessage::VSync { vsync: checkbox_input.checked }.into()]),\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.for_label(checkbox_id)\n\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t\tTextLabel::new(\"Enable V-Sync\")\n\t\t\t\t\t\t.tooltip_label(\"Enable V-Sync\")\n\t\t\t\t\t\t.tooltip_description(vsync_description)\n\t\t\t\t\t\t.for_checkbox(checkbox_id)\n\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t];\n\n\t\t\t\trows.push(vsync);\n\t\t\t}\n\t\t}\n\n\t\tLayout(rows.into_iter().map(LayoutGroup::row).collect())\n\t}\n\n\tpub fn send_layout(&self, responses: &mut VecDeque<Message>, layout_target: LayoutTarget, preferences: &PreferencesMessageHandler) {\n\t\tresponses.add(LayoutMessage::SendLayout {\n\t\t\tlayout: self.layout(preferences),\n\t\t\tlayout_target,\n\t\t})\n\t}\n\n\tfn layout_column_2(&self) -> Layout {\n\t\tLayout::default()\n\t}\n\n\tfn send_layout_column_2(&self, responses: &mut VecDeque<Message>, layout_target: LayoutTarget) {\n\t\tresponses.add(LayoutMessage::SendLayout {\n\t\t\tlayout: self.layout_column_2(),\n\t\t\tlayout_target,\n\t\t});\n\t}\n\n\tfn layout_buttons(&self) -> Layout {\n\t\tlet widgets = vec![\n\t\t\tTextButton::new(\"OK\").emphasized(true).on_update(|_| PreferencesDialogMessage::Confirm.into()).widget_instance(),\n\t\t\tTextButton::new(\"Reset to Defaults\").on_update(|_| PreferencesMessage::ResetToDefaults.into()).widget_instance(),\n\t\t];\n\n\t\tLayout(vec![LayoutGroup::row(widgets)])\n\t}\n\n\tfn send_layout_buttons(&self, responses: &mut VecDeque<Message>, layout_target: LayoutTarget) {\n\t\tresponses.add(LayoutMessage::SendLayout {\n\t\t\tlayout: self.layout_buttons(),\n\t\t\tlayout_target,\n\t\t});\n\t}\n\n\tpub fn send_dialog_to_frontend(&self, responses: &mut VecDeque<Message>, preferences: &PreferencesMessageHandler) {\n\t\tself.send_layout(responses, LayoutTarget::DialogColumn1, preferences);\n\t\tself.send_layout_column_2(responses, LayoutTarget::DialogColumn2);\n\t\tself.send_layout_buttons(responses, LayoutTarget::DialogButtons);\n\t\tresponses.add(FrontendMessage::DisplayDialog {\n\t\t\ticon: Self::ICON.into(),\n\t\t\ttitle: Self::TITLE.into(),\n\t\t});\n\t}\n}\n\n/// Maps display values (1-100) to actual zoom rates.\nfn map_display_to_zoom_rate(display: f64) -> f64 {\n\t// Calculate the relative distance from the reference point (50)\n\tlet distance_from_reference = display - 50.;\n\tlet scaling_factor = (VIEWPORT_ZOOM_WHEEL_RATE_CHANGE * distance_from_reference / 50.).exp();\n\tVIEWPORT_ZOOM_WHEEL_RATE * scaling_factor\n}\n\n/// Maps actual zoom rates back to display values (1-100).\nfn map_zoom_rate_to_display(rate: f64) -> f64 {\n\t// Calculate the scaling factor from the reference rate\n\tlet scaling_factor = rate / VIEWPORT_ZOOM_WHEEL_RATE;\n\tlet distance_from_reference = 50. * scaling_factor.ln() / VIEWPORT_ZOOM_WHEEL_RATE_CHANGE;\n\tlet display = 50. + distance_from_reference;\n\tdisplay.clamp(1., 100.).round()\n}\n\n/// Maps display values in percent to actual ui scale.\n#[cfg(not(target_family = \"wasm\"))]\nfn map_display_to_ui_scale(display: f64) -> f64 {\n\tdisplay / 100.\n}\n\n/// Maps actual ui scale back to display values in percent.\n#[cfg(not(target_family = \"wasm\"))]\nfn ui_scale_to_display(scale: f64) -> f64 {\n\tscale * 100.\n}\n"
  },
  {
    "path": "editor/src/messages/dialog/simple_dialogs/about_graphite_dialog.rs",
    "content": "use crate::application::commit_info_localized;\nuse crate::messages::layout::utility_types::widget_prelude::*;\nuse crate::messages::prelude::*;\n\n/// A dialog for displaying information on [BuildMetadata] viewable via *Help* > *About Graphite* in the menu bar.\npub struct AboutGraphiteDialog {\n\tpub localized_commit_date: String,\n\tpub localized_commit_year: String,\n}\n\nimpl DialogLayoutHolder for AboutGraphiteDialog {\n\tconst ICON: &'static str = \"GraphiteLogo\";\n\tconst TITLE: &'static str = \"About Graphite\";\n\n\tfn layout_buttons(&self) -> Layout {\n\t\tlet widgets = vec![TextButton::new(\"OK\").emphasized(true).on_update(|_| FrontendMessage::DialogClose.into()).widget_instance()];\n\n\t\tLayout(vec![LayoutGroup::row(widgets)])\n\t}\n\n\tfn layout_column_2(&self) -> Layout {\n\t\tlet links = [\n\t\t\t(\"Heart\", \"Donate\", \"https://graphite.art/donate/\"),\n\t\t\t(\"GraphiteLogo\", \"Website\", \"https://graphite.art\"),\n\t\t\t(\"Volunteer\", \"Volunteer\", \"https://graphite.art/volunteer/\"),\n\t\t\t(\"Credits\", \"Credits\", \"https://github.com/GraphiteEditor/Graphite/graphs/contributors\"),\n\t\t];\n\t\tlet mut widgets = links\n\t\t\t.into_iter()\n\t\t\t.map(|(icon, label, url)| {\n\t\t\t\tTextButton::new(label)\n\t\t\t\t\t.icon(icon)\n\t\t\t\t\t.flush(true)\n\t\t\t\t\t.on_update(|_| FrontendMessage::TriggerVisitLink { url: url.into() }.into())\n\t\t\t\t\t.widget_instance()\n\t\t\t})\n\t\t\t.collect::<Vec<_>>();\n\n\t\t// Cloning here and below seems to be necessary to appease the borrow checker, as far as I can tell.\n\t\tlet localized_commit_year = self.localized_commit_year.clone();\n\t\twidgets.push(\n\t\t\tTextButton::new(\"Licenses\")\n\t\t\t\t.icon(\"License\")\n\t\t\t\t.flush(true)\n\t\t\t\t.on_update(move |_| {\n\t\t\t\t\tDialogMessage::RequestLicensesDialogWithLocalizedCommitDate {\n\t\t\t\t\t\tlocalized_commit_year: localized_commit_year.clone(),\n\t\t\t\t\t}\n\t\t\t\t\t.into()\n\t\t\t\t})\n\t\t\t\t.widget_instance(),\n\t\t);\n\n\t\tLayout(vec![LayoutGroup::column(widgets)])\n\t}\n}\n\nimpl LayoutHolder for AboutGraphiteDialog {\n\tfn layout(&self) -> Layout {\n\t\tLayout(vec![\n\t\t\tLayoutGroup::row(vec![TextLabel::new(\"About this release\").bold(true).widget_instance()]),\n\t\t\tLayoutGroup::row(vec![TextLabel::new(commit_info_localized(&self.localized_commit_date)).multiline(true).widget_instance()]),\n\t\t\tLayoutGroup::row(vec![TextLabel::new(format!(\"Copyright © {} Graphite contributors\", self.localized_commit_year)).widget_instance()]),\n\t\t])\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/dialog/simple_dialogs/close_all_documents_dialog.rs",
    "content": "use crate::messages::layout::utility_types::widget_prelude::*;\nuse crate::messages::prelude::*;\n\n/// A dialog for confirming the closing of all documents viewable via `File -> Close All` in the menu bar.\npub struct CloseAllDocumentsDialog {\n\tpub unsaved_document_names: Vec<String>,\n}\n\nimpl DialogLayoutHolder for CloseAllDocumentsDialog {\n\tconst ICON: &'static str = \"Warning\";\n\tconst TITLE: &'static str = \"Closing All Documents\";\n\n\tfn layout_buttons(&self) -> Layout {\n\t\tlet widgets = vec![\n\t\t\tTextButton::new(\"Discard All\")\n\t\t\t\t.emphasized(true)\n\t\t\t\t.on_update(|_| {\n\t\t\t\t\tDialogMessage::CloseAndThen {\n\t\t\t\t\t\tfollowups: vec![PortfolioMessage::CloseAllDocuments.into()],\n\t\t\t\t\t}\n\t\t\t\t\t.into()\n\t\t\t\t})\n\t\t\t\t.widget_instance(),\n\t\t\tTextButton::new(\"Cancel\").on_update(|_| FrontendMessage::DialogClose.into()).widget_instance(),\n\t\t];\n\n\t\tLayout(vec![LayoutGroup::row(widgets)])\n\t}\n}\n\nimpl LayoutHolder for CloseAllDocumentsDialog {\n\tfn layout(&self) -> Layout {\n\t\tlet unsaved_list = \"• \".to_string() + &self.unsaved_document_names.join(\"\\n• \");\n\n\t\tLayout(vec![\n\t\t\tLayoutGroup::row(vec![TextLabel::new(\"Save documents before closing them?\").bold(true).multiline(true).widget_instance()]),\n\t\t\tLayoutGroup::row(vec![TextLabel::new(format!(\"Documents with unsaved changes:\\n{unsaved_list}\")).multiline(true).widget_instance()]),\n\t\t])\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/dialog/simple_dialogs/close_document_dialog.rs",
    "content": "use crate::messages::broadcast::event::EventMessage;\nuse crate::messages::layout::utility_types::widget_prelude::*;\nuse crate::messages::prelude::*;\n\n/// A dialog for confirming the closing a document with unsaved changes.\npub struct CloseDocumentDialog {\n\tpub document_name: String,\n\tpub document_id: DocumentId,\n}\n\nimpl DialogLayoutHolder for CloseDocumentDialog {\n\tconst ICON: &'static str = \"Warning\";\n\tconst TITLE: &'static str = \"Closing Document\";\n\n\tfn layout_buttons(&self) -> Layout {\n\t\tlet document_id = self.document_id;\n\t\tlet widgets = vec![\n\t\t\tTextButton::new(\"Save\")\n\t\t\t\t.emphasized(true)\n\t\t\t\t.on_update(|_| {\n\t\t\t\t\tDialogMessage::CloseAndThen {\n\t\t\t\t\t\tfollowups: vec![DocumentMessage::SaveDocument.into()],\n\t\t\t\t\t}\n\t\t\t\t\t.into()\n\t\t\t\t})\n\t\t\t\t.widget_instance(),\n\t\t\tTextButton::new(\"Discard\")\n\t\t\t\t.on_update(move |_| {\n\t\t\t\t\tDialogMessage::CloseAndThen {\n\t\t\t\t\t\tfollowups: vec![EventMessage::ToolAbort.into(), PortfolioMessage::CloseDocument { document_id }.into()],\n\t\t\t\t\t}\n\t\t\t\t\t.into()\n\t\t\t\t})\n\t\t\t\t.widget_instance(),\n\t\t\tTextButton::new(\"Cancel\").on_update(|_| FrontendMessage::DialogClose.into()).widget_instance(),\n\t\t];\n\n\t\tLayout(vec![LayoutGroup::row(widgets)])\n\t}\n}\n\nimpl LayoutHolder for CloseDocumentDialog {\n\tfn layout(&self) -> Layout {\n\t\tlet max_length = 60;\n\t\tlet max_one_line_length = 40;\n\n\t\tlet mut name = self.document_name.clone();\n\n\t\tname.truncate(max_length);\n\t\tlet ellipsis = if self.document_name.len() > max_length { \"…\" } else { \"\" };\n\n\t\tlet break_lines = if self.document_name.len() > max_one_line_length { '\\n' } else { ' ' };\n\n\t\tLayout(vec![\n\t\t\tLayoutGroup::row(vec![TextLabel::new(\"Save document before closing it?\").bold(true).widget_instance()]),\n\t\t\tLayoutGroup::row(vec![TextLabel::new(format!(\"\\\"{name}{ellipsis}\\\"{break_lines}has unsaved changes\")).multiline(true).widget_instance()]),\n\t\t])\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/dialog/simple_dialogs/confirm_restart_dialog.rs",
    "content": "use crate::messages::layout::utility_types::widget_prelude::*;\nuse crate::messages::prelude::*;\n\n/// A dialog for confirming the restart of the application when changing a preference that requires a restart to take effect.\npub struct ConfirmRestartDialog {\n\tpub preferences_requiring_restart: Vec<String>,\n}\n\nimpl DialogLayoutHolder for ConfirmRestartDialog {\n\tconst ICON: &'static str = \"Warning\";\n\tconst TITLE: &'static str = \"Restart Required\";\n\n\tfn layout_buttons(&self) -> Layout {\n\t\tlet widgets = vec![\n\t\t\tTextButton::new(\"Restart Now\")\n\t\t\t\t.emphasized(true)\n\t\t\t\t.on_update(|_| {\n\t\t\t\t\tDialogMessage::CloseAndThen {\n\t\t\t\t\t\tfollowups: vec![AppWindowMessage::Restart.into()],\n\t\t\t\t\t}\n\t\t\t\t\t.into()\n\t\t\t\t})\n\t\t\t\t.widget_instance(),\n\t\t\tTextButton::new(\"Later\").on_update(|_| FrontendMessage::DialogClose.into()).widget_instance(),\n\t\t];\n\n\t\tLayout(vec![LayoutGroup::row(widgets)])\n\t}\n}\n\nimpl LayoutHolder for ConfirmRestartDialog {\n\tfn layout(&self) -> Layout {\n\t\tlet changed_settings = \"• \".to_string() + &self.preferences_requiring_restart.join(\"\\n• \");\n\n\t\tLayout(vec![\n\t\t\tLayoutGroup::row(vec![TextLabel::new(\"Restart to apply changes?\").bold(true).multiline(true).widget_instance()]),\n\t\t\tLayoutGroup::row(vec![\n\t\t\t\tTextLabel::new(\n\t\t\t\t\tformat!(\n\t\t\t\t\t\t\"\n\t\t\t\t\t\t\tSettings that only take effect on next launch:\\n\\\n\t\t\t\t\t\t\t{changed_settings}\\n\\\n\t\t\t\t\t\t\t\\n\\\n\t\t\t\t\t\t\tThis only takes a few seconds. Open documents,\\n\\\n\t\t\t\t\t\t\teven unsaved ones, will be automatically restored.\n\t\t\t\t\t\t\t\"\n\t\t\t\t\t)\n\t\t\t\t\t.trim(),\n\t\t\t\t)\n\t\t\t\t.multiline(true)\n\t\t\t\t.widget_instance(),\n\t\t\t]),\n\t\t])\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/dialog/simple_dialogs/demo_artwork_dialog.rs",
    "content": "use crate::messages::layout::utility_types::widget_prelude::*;\nuse crate::messages::prelude::*;\n\n/// A dialog to let the user browse a gallery of demo artwork that can be opened.\npub struct DemoArtworkDialog;\n\n/// `(name, thumbnail, filename)`\npub const ARTWORK: [(&str, &str, &str); 7] = [\n\t(\"Isometric Fountain\", \"ThumbnailIsometricFountain\", \"isometric-fountain.graphite\"),\n\t(\"Changing Seasons\", \"ThumbnailChangingSeasons\", \"changing-seasons.graphite\"),\n\t(\"Painted Dreams\", \"ThumbnailPaintedDreams\", \"painted-dreams.graphite\"),\n\t(\"Parametric Dunescape\", \"ThumbnailParametricDunescape\", \"parametric-dunescape.graphite\"),\n\t(\"Red Dress\", \"ThumbnailRedDress\", \"red-dress.graphite\"),\n\t(\"Procedural String Lights\", \"ThumbnailProceduralStringLights\", \"procedural-string-lights.graphite\"),\n\t(\"Valley of Spires\", \"ThumbnailValleyOfSpires\", \"valley-of-spires.graphite\"),\n];\n\nimpl DialogLayoutHolder for DemoArtworkDialog {\n\tconst ICON: &'static str = \"Image\";\n\tconst TITLE: &'static str = \"Demo Artwork\";\n\n\tfn layout_buttons(&self) -> Layout {\n\t\tlet widgets = vec![TextButton::new(\"Close\").emphasized(true).on_update(|_| FrontendMessage::DialogClose.into()).widget_instance()];\n\n\t\tLayout(vec![LayoutGroup::row(widgets)])\n\t}\n}\n\nimpl LayoutHolder for DemoArtworkDialog {\n\tfn layout(&self) -> Layout {\n\t\tlet mut rows_of_images_with_buttons: Vec<_> = ARTWORK\n\t\t\t.chunks(4)\n\t\t\t.flat_map(|chunk| {\n\t\t\t\tfn make_dialog(name: &str, filename: &str) -> Message {\n\t\t\t\t\tDialogMessage::CloseAndThen {\n\t\t\t\t\t\tfollowups: vec![\n\t\t\t\t\t\t\tFrontendMessage::TriggerFetchAndOpenDocument {\n\t\t\t\t\t\t\t\tname: name.to_string(),\n\t\t\t\t\t\t\t\tfilename: filename.to_string(),\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t.into(),\n\t\t\t\t\t\t],\n\t\t\t\t\t}\n\t\t\t\t\t.into()\n\t\t\t\t}\n\n\t\t\t\tlet images = chunk\n\t\t\t\t\t.iter()\n\t\t\t\t\t.map(|(name, thumbnail, filename)| ImageButton::new(*thumbnail).width(\"256px\").on_update(|_| make_dialog(name, filename)).widget_instance())\n\t\t\t\t\t.collect();\n\n\t\t\t\tlet buttons = chunk\n\t\t\t\t\t.iter()\n\t\t\t\t\t.map(|(name, _, filename)| TextButton::new(*name).min_width(256).flush(true).on_update(|_| make_dialog(name, filename)).widget_instance())\n\t\t\t\t\t.collect();\n\n\t\t\t\tvec![LayoutGroup::row(images), LayoutGroup::row(buttons), LayoutGroup::row(vec![])]\n\t\t\t})\n\t\t\t.collect();\n\t\tlet _ = rows_of_images_with_buttons.pop();\n\n\t\tLayout(rows_of_images_with_buttons)\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/dialog/simple_dialogs/error_dialog.rs",
    "content": "use crate::messages::layout::utility_types::widget_prelude::*;\nuse crate::messages::prelude::*;\n\n/// A dialog to notify users of a non-fatal error.\npub struct ErrorDialog {\n\tpub title: String,\n\tpub description: String,\n}\n\nimpl DialogLayoutHolder for ErrorDialog {\n\tconst ICON: &'static str = \"Warning\";\n\tconst TITLE: &'static str = \"Error\";\n\n\tfn layout_buttons(&self) -> Layout {\n\t\tlet widgets = vec![TextButton::new(\"OK\").emphasized(true).on_update(|_| FrontendMessage::DialogClose.into()).widget_instance()];\n\n\t\tLayout(vec![LayoutGroup::row(widgets)])\n\t}\n}\n\nimpl LayoutHolder for ErrorDialog {\n\tfn layout(&self) -> Layout {\n\t\tLayout(vec![\n\t\t\tLayoutGroup::row(vec![TextLabel::new(&self.title).bold(true).widget_instance()]),\n\t\t\tLayoutGroup::row(vec![TextLabel::new(&self.description).multiline(true).widget_instance()]),\n\t\t])\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/dialog/simple_dialogs/licenses_dialog.rs",
    "content": "use crate::messages::layout::utility_types::widget_prelude::*;\nuse crate::messages::prelude::*;\n\npub struct LicensesDialog {\n\tpub localized_commit_year: String,\n}\n\nimpl DialogLayoutHolder for LicensesDialog {\n\tconst ICON: &'static str = \"License12px\";\n\tconst TITLE: &'static str = \"Licenses\";\n\n\tfn layout_buttons(&self) -> Layout {\n\t\tlet widgets = vec![TextButton::new(\"OK\").emphasized(true).on_update(|_| FrontendMessage::DialogClose.into()).widget_instance()];\n\n\t\tLayout(vec![LayoutGroup::row(widgets)])\n\t}\n\n\tfn layout_column_2(&self) -> Layout {\n\t\t#[allow(clippy::type_complexity)]\n\t\tlet button_definitions: &[(&str, &str, fn() -> Message)] = &[\n\t\t\t(\"Code\", \"Source Code License\", || {\n\t\t\t\tFrontendMessage::TriggerVisitLink {\n\t\t\t\t\turl: \"https://graphite.art/license#source-code\".into(),\n\t\t\t\t}\n\t\t\t\t.into()\n\t\t\t}),\n\t\t\t(\"GraphiteLogo\", \"Branding License\", || {\n\t\t\t\tFrontendMessage::TriggerVisitLink {\n\t\t\t\t\turl: \"https://graphite.art/license#branding\".into(),\n\t\t\t\t}\n\t\t\t\t.into()\n\t\t\t}),\n\t\t\t(\"IconsGrid\", \"Dependency Licenses\", || FrontendMessage::TriggerDisplayThirdPartyLicensesDialog.into()),\n\t\t];\n\t\tlet widgets = button_definitions\n\t\t\t.iter()\n\t\t\t.map(|&(icon, label, message_factory)| TextButton::new(label).icon(icon).flush(true).on_update(move |_| message_factory()).widget_instance())\n\t\t\t.collect();\n\n\t\tLayout(vec![LayoutGroup::column(widgets)])\n\t}\n}\n\nimpl LayoutHolder for LicensesDialog {\n\tfn layout(&self) -> Layout {\n\t\tlet year = &self.localized_commit_year;\n\t\tlet description = format!(\n\t\t\t\"\n\t\t\tGraphite source code is copyright © {year} Graphite contrib-\\nutors and is available under the Apache License 2.0. See\\n\\\"Source Code License\\\" for details.\\n\\\n\t\t\t\\n\\\n\t\t\tThe Graphite logo, icons, and visual identity are copyright ©\\n{year} Graphite Labs, LLC. See \\\"Branding License\\\" for details.\\n\\\n\t\t\t\\n\\\n\t\t\tGraphite is distributed with third-party open source code\\ndependencies. See \\\"Dependency Licenses\\\" for details.\n\t\t\t\"\n\t\t);\n\t\tlet description = description.trim();\n\n\t\tLayout(vec![\n\t\t\tLayoutGroup::row(vec![TextLabel::new(\"Graphite is free, open source software\").bold(true).widget_instance()]),\n\t\t\tLayoutGroup::row(vec![TextLabel::new(description).multiline(true).widget_instance()]),\n\t\t])\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/dialog/simple_dialogs/licenses_third_party_dialog.rs",
    "content": "use crate::messages::layout::utility_types::widget_prelude::*;\nuse crate::messages::prelude::*;\n\npub struct LicensesThirdPartyDialog {\n\tpub license_text: String,\n}\n\nimpl DialogLayoutHolder for LicensesThirdPartyDialog {\n\tconst ICON: &'static str = \"License12px\";\n\tconst TITLE: &'static str = \"Third-Party Software License Notices\";\n\n\tfn layout_buttons(&self) -> Layout {\n\t\tlet widgets = vec![TextButton::new(\"OK\").emphasized(true).on_update(|_| FrontendMessage::DialogClose.into()).widget_instance()];\n\n\t\tLayout(vec![LayoutGroup::row(widgets)])\n\t}\n}\n\nimpl LayoutHolder for LicensesThirdPartyDialog {\n\tfn layout(&self) -> Layout {\n\t\t// Remove the header and begin with the line containing the first license section (we otherwise keep the title for standalone viewing of the licenses text file)\n\t\tlet license_text = if let Some(first_underscore_line) = self.license_text.lines().position(|line| line.contains('_')) {\n\t\t\t// Find the byte position where the line with underscore starts\n\t\t\tlet char_position = self.license_text.split('\\n').take(first_underscore_line).map(|line| line.len() + '\\n'.len_utf8()).sum();\n\t\t\tself.license_text[char_position..].to_string()\n\t\t} else {\n\t\t\t// This shouldn't be encountered, but if no underscore line is found, we use the full text as a safety fallback\n\t\t\tself.license_text.clone()\n\t\t};\n\n\t\t// Two characters (one before, one after) the sequence of underscore characters, plus one additional column to provide a space between the text and the scrollbar\n\t\tlet non_wrapping_column_width = license_text.split('\\n').map(|line| line.chars().filter(|&c| c == '_').count() as u32).max().unwrap_or(0) + 2 + 1;\n\n\t\tLayout(vec![LayoutGroup::row(vec![\n\t\t\tTextLabel::new(license_text)\n\t\t\t\t.monospace(true)\n\t\t\t\t.multiline(true)\n\t\t\t\t.min_width_characters(non_wrapping_column_width)\n\t\t\t\t.widget_instance(),\n\t\t])])\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/dialog/simple_dialogs/mod.rs",
    "content": "mod about_graphite_dialog;\nmod close_all_documents_dialog;\nmod close_document_dialog;\nmod confirm_restart_dialog;\nmod demo_artwork_dialog;\nmod error_dialog;\nmod licenses_dialog;\nmod licenses_third_party_dialog;\n\npub use about_graphite_dialog::AboutGraphiteDialog;\npub use close_all_documents_dialog::CloseAllDocumentsDialog;\npub use close_document_dialog::CloseDocumentDialog;\npub use confirm_restart_dialog::ConfirmRestartDialog;\npub use demo_artwork_dialog::ARTWORK;\npub use demo_artwork_dialog::DemoArtworkDialog;\npub use error_dialog::ErrorDialog;\npub use licenses_dialog::LicensesDialog;\npub use licenses_third_party_dialog::LicensesThirdPartyDialog;\n"
  },
  {
    "path": "editor/src/messages/frontend/frontend_message.rs",
    "content": "use super::IconName;\nuse super::utility_types::{DocumentDetails, MouseCursorIcon, OpenDocument};\nuse crate::messages::app_window::app_window_message_handler::AppWindowPlatform;\nuse crate::messages::frontend::utility_types::EyedropperPreviewImage;\nuse crate::messages::input_mapper::utility_types::misc::ActionShortcut;\nuse crate::messages::layout::utility_types::widget_prelude::*;\nuse crate::messages::portfolio::document::node_graph::utility_types::{\n\tBoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, NodeGraphErrorDiagnostic,\n};\nuse crate::messages::portfolio::document::utility_types::nodes::{LayerPanelEntry, LayerStructureEntry};\nuse crate::messages::portfolio::document::utility_types::wires::{WirePath, WirePathUpdate};\nuse crate::messages::prelude::*;\nuse crate::messages::tool::tool_messages::eyedropper_tool::PrimarySecondary;\nuse graph_craft::document::NodeId;\nuse graphene_std::raster::Image;\nuse graphene_std::raster::color::Color;\nuse graphene_std::text::{Font, TextAlign};\nuse std::path::PathBuf;\n\n#[cfg(not(target_family = \"wasm\"))]\nuse crate::messages::portfolio::document::overlays::utility_types::OverlayContext;\n\n#[impl_message(Message, Frontend)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(derivative::Derivative, Clone, serde::Serialize, serde::Deserialize)]\n#[derivative(Debug, PartialEq)]\npub enum FrontendMessage {\n\t// Display prefix: make the frontend show something, like a dialog\n\tDisplayDialog {\n\t\ttitle: String,\n\t\ticon: IconName,\n\t},\n\tDialogClose,\n\tDisplayDialogPanic {\n\t\t#[serde(rename = \"panicInfo\")]\n\t\tpanic_info: String,\n\t},\n\tDisplayEditableTextbox {\n\t\ttext: String,\n\t\t#[serde(rename = \"lineHeightRatio\")]\n\t\tline_height_ratio: f64,\n\t\t#[serde(rename = \"fontSize\")]\n\t\tfont_size: f64,\n\t\tcolor: String,\n\t\t#[serde(rename = \"fontData\")]\n\t\tfont_data: serde_bytes::ByteBuf,\n\t\ttransform: [f64; 6],\n\t\t#[serde(rename = \"maxWidth\")]\n\t\tmax_width: Option<f64>,\n\t\t#[serde(rename = \"maxHeight\")]\n\t\tmax_height: Option<f64>,\n\t\talign: TextAlign,\n\t},\n\tDisplayEditableTextboxUpdateFontData {\n\t\t#[serde(rename = \"fontData\")]\n\t\tfont_data: serde_bytes::ByteBuf,\n\t},\n\tDisplayEditableTextboxTransform {\n\t\ttransform: [f64; 6],\n\t},\n\tDisplayRemoveEditableTextbox,\n\n\t// Send prefix: Send global, static data to the frontend that is never updated\n\tSendUIMetadata {\n\t\t#[serde(rename = \"nodeDescriptions\")]\n\t\tnode_descriptions: Vec<(String, String)>,\n\t\t#[serde(rename = \"nodeTypes\")]\n\t\tnode_types: Vec<FrontendNodeType>,\n\t},\n\tSendShortcutFullscreen {\n\t\tshortcut: Option<ActionShortcut>,\n\t\t#[serde(rename = \"shortcutMac\")]\n\t\tshortcut_mac: Option<ActionShortcut>,\n\t},\n\tSendShortcutAltClick {\n\t\tshortcut: Option<ActionShortcut>,\n\t},\n\tSendShortcutShiftClick {\n\t\tshortcut: Option<ActionShortcut>,\n\t},\n\n\t// Trigger prefix: cause a frontend specific API to do something\n\tTriggerAboutGraphiteLocalizedCommitDate {\n\t\t#[serde(rename = \"commitDate\")]\n\t\tcommit_date: String,\n\t},\n\tTriggerDisplayThirdPartyLicensesDialog,\n\tTriggerSaveDocument {\n\t\tdocument_id: DocumentId,\n\t\tname: String,\n\t\tpath: Option<PathBuf>,\n\t\tcontent: serde_bytes::ByteBuf,\n\t},\n\tTriggerSaveFile {\n\t\tname: String,\n\t\tcontent: serde_bytes::ByteBuf,\n\t},\n\tTriggerExportImage {\n\t\tsvg: String,\n\t\tname: String,\n\t\tmime: String,\n\t\tsize: (f64, f64),\n\t},\n\tTriggerFetchAndOpenDocument {\n\t\tname: String,\n\t\tfilename: String,\n\t},\n\tTriggerFontCatalogLoad,\n\tTriggerFontDataLoad {\n\t\tfont: Font,\n\t\turl: String,\n\t},\n\tTriggerPersistenceRemoveDocument {\n\t\t#[serde(rename = \"documentId\")]\n\t\tdocument_id: DocumentId,\n\t},\n\tTriggerPersistenceWriteDocument {\n\t\t#[serde(rename = \"documentId\")]\n\t\tdocument_id: DocumentId,\n\t\tdocument: String,\n\t\tdetails: DocumentDetails,\n\t},\n\tTriggerLoadFirstAutoSaveDocument,\n\tTriggerLoadRestAutoSaveDocuments,\n\tTriggerOpenLaunchDocuments,\n\tTriggerLoadPreferences,\n\tTriggerOpen,\n\tTriggerImport,\n\tTriggerSavePreferences {\n\t\t#[tsify(type = \"unknown\")]\n\t\tpreferences: PreferencesMessageHandler,\n\t},\n\tTriggerSaveActiveDocument {\n\t\t#[serde(rename = \"documentId\")]\n\t\tdocument_id: DocumentId,\n\t},\n\tTriggerTextCommit,\n\tTriggerVisitLink {\n\t\turl: String,\n\t},\n\tTriggerClipboardRead,\n\tTriggerClipboardWrite {\n\t\tcontent: String,\n\t},\n\tTriggerSelectionRead {\n\t\tcut: bool,\n\t},\n\tTriggerSelectionWrite {\n\t\tcontent: String,\n\t},\n\n\t// Update prefix: give the frontend a new value or state for it to use\n\tUpdateActiveDocument {\n\t\t#[serde(rename = \"documentId\")]\n\t\tdocument_id: DocumentId,\n\t},\n\tUpdateGradientStopColorPickerPosition {\n\t\tcolor: Color, // TODO: Color (without `none`) -> Color (with `none`)\n\t\tposition: (f64, f64),\n\t},\n\tUpdateImportsExports {\n\t\t/// If the primary import is not visible, then it is None.\n\t\timports: Vec<Option<FrontendGraphOutput>>,\n\t\t/// If the primary export is not visible, then it is None.\n\t\texports: Vec<Option<FrontendGraphInput>>,\n\t\t/// The primary import location.\n\t\t#[serde(rename = \"importPosition\")]\n\t\timport_position: (i32, i32),\n\t\t/// The primary export location.\n\t\t#[serde(rename = \"exportPosition\")]\n\t\texport_position: (i32, i32),\n\t\t/// The document network does not have an add import or export button.\n\t\t#[serde(rename = \"addImportExport\")]\n\t\tadd_import_export: bool,\n\t},\n\tUpdateInSelectedNetwork {\n\t\t#[serde(rename = \"inSelectedNetwork\")]\n\t\tin_selected_network: bool,\n\t},\n\tUpdateBox {\n\t\t#[serde(rename = \"box\")]\n\t\tbox_selection: Option<BoxSelection>,\n\t},\n\tUpdateContextMenuInformation {\n\t\t#[serde(rename = \"contextMenuInformation\")]\n\t\tcontext_menu_information: Option<ContextMenuInformation>,\n\t},\n\tUpdateClickTargets {\n\t\t#[serde(rename = \"clickTargets\")]\n\t\tclick_targets: Option<FrontendClickTargets>,\n\t},\n\tUpdateGraphViewOverlay {\n\t\topen: bool,\n\t},\n\tUpdateDataPanelState {\n\t\topen: bool,\n\t},\n\tUpdatePropertiesPanelState {\n\t\topen: bool,\n\t},\n\tUpdateLayersPanelState {\n\t\topen: bool,\n\t},\n\tUpdateLayout {\n\t\t#[serde(rename = \"layoutTarget\")]\n\t\tlayout_target: LayoutTarget,\n\t\tdiff: Vec<WidgetDiff>, // TODO: Align this with what's generated\n\t},\n\tUpdateImportReorderIndex {\n\t\t#[serde(rename = \"importIndex\")]\n\t\tindex: Option<usize>,\n\t},\n\tUpdateExportReorderIndex {\n\t\t#[serde(rename = \"exportIndex\")]\n\t\tindex: Option<usize>,\n\t},\n\tUpdateLayerWidths {\n\t\t#[serde(rename = \"layerWidths\")]\n\t\tlayer_widths: HashMap<NodeId, u32>,\n\t\t#[serde(rename = \"chainWidths\")]\n\t\tchain_widths: HashMap<NodeId, u32>,\n\t\t#[serde(rename = \"hasLeftInputWire\")]\n\t\thas_left_input_wire: HashMap<NodeId, bool>,\n\t},\n\tUpdateDocumentArtwork {\n\t\tsvg: String,\n\t},\n\tUpdateImageData {\n\t\timage_data: Vec<(u64, Image<Color>)>,\n\t},\n\tUpdateDocumentLayerDetails {\n\t\tdata: LayerPanelEntry,\n\t},\n\tUpdateDocumentLayerStructure {\n\t\t#[serde(rename = \"layerStructure\")]\n\t\tlayer_structure: Vec<LayerStructureEntry>,\n\t},\n\tUpdateDocumentRulers {\n\t\torigin: (f64, f64),\n\t\tspacing: f64,\n\t\tinterval: f64,\n\t\tvisible: bool,\n\t},\n\tUpdateDocumentScrollbars {\n\t\tposition: (f64, f64),\n\t\tsize: (f64, f64),\n\t\tmultiplier: (f64, f64),\n\t},\n\tUpdateEyedropperSamplingState {\n\t\timage: Option<EyedropperPreviewImage>,\n\t\t#[serde(rename = \"mousePosition\")]\n\t\tmouse_position: Option<(f64, f64)>,\n\t\t#[serde(rename = \"primaryColor\")]\n\t\tprimary_color: String,\n\t\t#[serde(rename = \"secondaryColor\")]\n\t\tsecondary_color: String,\n\t\t#[serde(rename = \"setColorChoice\")]\n\t\tset_color_choice: Option<PrimarySecondary>,\n\t},\n\tUpdateGraphFadeArtwork {\n\t\tpercentage: f64,\n\t},\n\tUpdateMouseCursor {\n\t\tcursor: MouseCursorIcon,\n\t},\n\tUpdateNodeGraphNodes {\n\t\tnodes: Vec<FrontendNode>,\n\t},\n\tUpdateNodeGraphErrorDiagnostic {\n\t\terror: Option<NodeGraphErrorDiagnostic>,\n\t},\n\tUpdateVisibleNodes {\n\t\tnodes: Vec<NodeId>,\n\t},\n\tUpdateNodeGraphWires {\n\t\twires: Vec<WirePathUpdate>,\n\t},\n\tClearAllNodeGraphWires,\n\tUpdateNodeGraphSelection {\n\t\tselected: Vec<NodeId>,\n\t},\n\tUpdateNodeGraphTransform {\n\t\ttranslation: (f64, f64),\n\t\tscale: f64,\n\t},\n\tUpdateNodeThumbnail {\n\t\tid: NodeId,\n\t\tvalue: String,\n\t},\n\tUpdateOpenDocumentsList {\n\t\t#[serde(rename = \"openDocuments\")]\n\t\topen_documents: Vec<OpenDocument>,\n\t},\n\tUpdateWirePathInProgress {\n\t\t#[serde(rename = \"wirePath\")]\n\t\twire_path: Option<WirePath>,\n\t},\n\tUpdatePlatform {\n\t\tplatform: AppWindowPlatform,\n\t},\n\tUpdateMaximized {\n\t\tmaximized: bool,\n\t},\n\tUpdateFullscreen {\n\t\tfullscreen: bool,\n\t},\n\tUpdateViewportHolePunch {\n\t\tactive: bool,\n\t},\n\t#[cfg(not(target_family = \"wasm\"))]\n\tUpdateViewportPhysicalBounds {\n\t\tx: f64,\n\t\ty: f64,\n\t\twidth: f64,\n\t\theight: f64,\n\t},\n\tUpdateUIScale {\n\t\tscale: f64,\n\t},\n\n\t#[cfg(not(target_family = \"wasm\"))]\n\tRenderOverlays {\n\t\t#[serde(skip, default = \"OverlayContext::default\")]\n\t\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\t\tcontext: OverlayContext,\n\t},\n\n\t// Window prefix: cause the application window to do something\n\t#[cfg(not(target_family = \"wasm\"))]\n\tWindowPointerLock,\n\tWindowPointerLockMove {\n\t\tposition: (f64, f64),\n\t},\n\t#[cfg(not(target_family = \"wasm\"))]\n\tWindowClose,\n\t#[cfg(not(target_family = \"wasm\"))]\n\tWindowMinimize,\n\t#[cfg(not(target_family = \"wasm\"))]\n\tWindowMaximize,\n\tWindowFullscreen,\n\t#[cfg(not(target_family = \"wasm\"))]\n\tWindowDrag,\n\t#[cfg(not(target_family = \"wasm\"))]\n\tWindowHide,\n\t#[cfg(not(target_family = \"wasm\"))]\n\tWindowHideOthers,\n\t#[cfg(not(target_family = \"wasm\"))]\n\tWindowShowAll,\n\t#[cfg(not(target_family = \"wasm\"))]\n\tWindowRestart,\n}\n"
  },
  {
    "path": "editor/src/messages/frontend/mod.rs",
    "content": "mod frontend_message;\n\npub mod utility_types;\n\n#[doc(inline)]\npub use frontend_message::{FrontendMessage, FrontendMessageDiscriminant};\n\n// TODO: Make this an enum with the actual icon names, somehow derived from or tied to the frontend icon set.\n// TODO: Then remove `#[widget_builder(string)]` from all icon fields.\npub type IconName = String;\n"
  },
  {
    "path": "editor/src/messages/frontend/utility_types.rs",
    "content": "use std::path::PathBuf;\n\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::prelude::*;\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub struct OpenDocument {\n\tpub id: DocumentId,\n\tpub details: DocumentDetails,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub struct DocumentDetails {\n\tpub name: String,\n\tpub path: Option<PathBuf>,\n\t#[serde(rename = \"isSaved\")]\n\tpub is_saved: bool,\n\t#[serde(rename = \"isAutoSaved\")]\n\tpub is_auto_saved: bool,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize)]\npub enum MouseCursorIcon {\n\t#[default]\n\tDefault,\n\tNone,\n\tZoomIn,\n\tZoomOut,\n\tGrabbing,\n\tCrosshair,\n\tText,\n\tMove,\n\tNSResize,\n\tEWResize,\n\tNESWResize,\n\tNWSEResize,\n\tRotate,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize)]\npub enum FileType {\n\t#[default]\n\tPng,\n\tJpg,\n\tSvg,\n}\n\nimpl FileType {\n\tpub fn to_mime(self) -> &'static str {\n\t\tmatch self {\n\t\t\tFileType::Png => \"image/png\",\n\t\t\tFileType::Jpg => \"image/jpeg\",\n\t\t\tFileType::Svg => \"image/svg+xml\",\n\t\t}\n\t}\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize)]\npub enum ExportBounds {\n\t#[default]\n\tAllArtwork,\n\tSelection,\n\tArtboard(LayerNodeIdentifier),\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify), tsify(large_number_types_as_bigints))]\n#[derive(Clone, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct EyedropperPreviewImage {\n\tpub data: serde_bytes::ByteBuf,\n\tpub width: u32,\n\tpub height: u32,\n}\n"
  },
  {
    "path": "editor/src/messages/input_mapper/input_mapper_message.rs",
    "content": "use crate::messages::input_mapper::utility_types::input_keyboard::Key;\nuse crate::messages::input_mapper::utility_types::input_mouse::MouseButton;\nuse crate::messages::prelude::*;\n\n#[impl_message(Message, KeyMappingMessage, Lookup)]\n#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)]\npub enum InputMapperMessage {\n\t// Sub-messages\n\t#[child]\n\tKeyDown(Key),\n\t#[child]\n\tKeyUp(Key),\n\t#[child]\n\tKeyDownNoRepeat(Key),\n\t#[child]\n\tKeyUpNoRepeat(Key),\n\t#[child]\n\tDoubleClick(MouseButton),\n\n\t// Messages\n\tPointerMove,\n\tPointerShake,\n\tWheelScroll,\n}\n"
  },
  {
    "path": "editor/src/messages/input_mapper/input_mapper_message_handler.rs",
    "content": "use super::utility_types::input_keyboard::KeysGroup;\nuse super::utility_types::misc::Mapping;\nuse crate::application::Editor;\nuse crate::messages::input_mapper::utility_types::input_keyboard::{self, Key};\nuse crate::messages::input_mapper::utility_types::misc::MappingEntry;\nuse crate::messages::prelude::*;\n\n#[derive(ExtractField)]\npub struct InputMapperMessageContext<'a> {\n\tpub input: &'a InputPreprocessorMessageHandler,\n\tpub actions: ActionList,\n}\n\n#[derive(Debug, Default, ExtractField)]\npub struct InputMapperMessageHandler {\n\tmapping: Mapping,\n}\n\n#[message_handler_data]\nimpl MessageHandler<InputMapperMessage, InputMapperMessageContext<'_>> for InputMapperMessageHandler {\n\tfn process_message(&mut self, message: InputMapperMessage, responses: &mut VecDeque<Message>, context: InputMapperMessageContext) {\n\t\tlet InputMapperMessageContext { input, actions } = context;\n\n\t\tif let Some(message) = self.mapping.match_input_message(message, &input.keyboard, actions) {\n\t\t\tresponses.add(message);\n\t\t}\n\t}\n\tadvertise_actions!();\n}\n\nimpl InputMapperMessageHandler {\n\tpub fn set_mapping(&mut self, mapping: Mapping) {\n\t\tself.mapping = mapping;\n\t}\n\n\tpub fn action_input_mapping(&self, action_to_find: &MessageDiscriminant) -> Option<KeysGroup> {\n\t\tlet all_key_mapping_entries = std::iter::empty()\n\t\t\t.chain(self.mapping.key_up.iter())\n\t\t\t.chain(self.mapping.key_down.iter())\n\t\t\t.chain(self.mapping.key_up_no_repeat.iter())\n\t\t\t.chain(self.mapping.key_down_no_repeat.iter())\n\t\t\t.chain(self.mapping.double_click.iter())\n\t\t\t.chain(std::iter::once(&self.mapping.wheel_scroll))\n\t\t\t.chain(std::iter::once(&self.mapping.pointer_move));\n\t\tlet all_mapping_entries = all_key_mapping_entries.flat_map(|entry| entry.0.iter());\n\n\t\t// Filter for the desired message\n\t\tlet found_actions = all_mapping_entries.filter(|entry| entry.action.to_discriminant() == *action_to_find);\n\n\t\t// Get the `Key` for this platform's accelerator key\n\t\tlet platform_accel_key = if Editor::environment().is_mac() { Key::Command } else { Key::Control };\n\n\t\tlet entry_to_key = |entry: &MappingEntry| {\n\t\t\t// Get the modifier keys for the entry (and convert them to Key)\n\t\t\tlet mut keys = entry\n\t\t\t\t.modifiers\n\t\t\t\t.iter()\n\t\t\t\t.map(|i| {\n\t\t\t\t\t// TODO: Use a safe solution eventually\n\t\t\t\t\tassert!(\n\t\t\t\t\t\ti < input_keyboard::NUMBER_OF_KEYS,\n\t\t\t\t\t\t\"Attempting to convert a Key with enum index {i}, which is larger than the number of Key enums\",\n\t\t\t\t\t);\n\t\t\t\t\t(i as u8).try_into().unwrap()\n\t\t\t\t})\n\t\t\t\t.collect::<Vec<_>>();\n\n\t\t\t// Append the key button for the entry\n\t\t\tuse InputMapperMessage as IMM;\n\t\t\tmatch entry.input {\n\t\t\t\tIMM::KeyDown(key) | IMM::KeyUp(key) | IMM::KeyDownNoRepeat(key) | IMM::KeyUpNoRepeat(key) => keys.push(key),\n\t\t\t\t_ => (),\n\t\t\t}\n\n\t\t\tkeys.sort_by(|&a, &b| {\n\t\t\t\t// Order according to platform guidelines mentioned at https://ux.stackexchange.com/questions/58185/normative-ordering-for-modifier-key-combinations\n\t\t\t\tconst ORDER: [Key; 4] = [Key::Control, Key::Alt, Key::Shift, Key::Command];\n\n\t\t\t\t// Treat the `Accel` virtual key as the platform's accel key for sorting comparison purposes\n\t\t\t\tlet a = if a == Key::Accel { platform_accel_key } else { a };\n\t\t\t\tlet b = if b == Key::Accel { platform_accel_key } else { b };\n\n\t\t\t\t// Find where the keys are in the order, or put them at the end if they're not found\n\t\t\t\tlet a = ORDER.iter().position(|&key| key == a).unwrap_or(ORDER.len());\n\t\t\t\tlet b = ORDER.iter().position(|&key| key == b).unwrap_or(ORDER.len());\n\n\t\t\t\t// Compare the positions of both keys\n\t\t\t\ta.cmp(&b)\n\t\t\t});\n\n\t\t\tKeysGroup(keys)\n\t\t};\n\n\t\t// If a canonical key combination is found, return it\n\t\tif let Some(canonical) = found_actions.clone().find(|entry| entry.canonical).map(entry_to_key) {\n\t\t\treturn Some(canonical);\n\t\t}\n\n\t\t// Find the key combinations for all keymaps matching the desired action\n\t\tassert!(std::mem::size_of::<usize>() >= std::mem::size_of::<Key>());\n\t\tlet mut key_sequences = found_actions.map(entry_to_key).collect::<Vec<_>>();\n\n\t\t// Return the shortest key sequence, if any\n\t\tkey_sequences.sort_by_key(|keys| keys.0.len());\n\t\tkey_sequences.first().cloned()\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/input_mapper/input_mappings.rs",
    "content": "use crate::application::Editor;\nuse crate::consts::{BIG_NUDGE_AMOUNT, BRUSH_SIZE_CHANGE_KEYBOARD, NUDGE_AMOUNT};\nuse crate::messages::input_mapper::key_mapping::MappingVariant;\nuse crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeyStates};\nuse crate::messages::input_mapper::utility_types::input_mouse::MouseButton;\nuse crate::messages::input_mapper::utility_types::macros::*;\nuse crate::messages::input_mapper::utility_types::misc::MappingEntry;\nuse crate::messages::input_mapper::utility_types::misc::{KeyMappingEntries, Mapping};\nuse crate::messages::portfolio::document::node_graph::utility_types::Direction;\nuse crate::messages::portfolio::document::utility_types::clipboards::Clipboard;\nuse crate::messages::portfolio::document::utility_types::misc::GroupFolderType;\nuse crate::messages::prelude::*;\nuse crate::messages::tool::tool_messages::brush_tool::BrushToolMessageOptionsUpdate;\nuse crate::messages::tool::tool_messages::select_tool::SelectToolPointerKeys;\nuse glam::DVec2;\n\nimpl From<MappingVariant> for Mapping {\n\tfn from(value: MappingVariant) -> Self {\n\t\tmatch value {\n\t\t\tMappingVariant::Default => input_mappings(false),\n\t\t\tMappingVariant::ZoomWithScroll => input_mappings(true),\n\t\t}\n\t}\n}\n\npub fn input_mappings(zoom_with_scroll: bool) -> Mapping {\n\tuse InputMapperMessage::*;\n\tuse Key::*;\n\n\tlet is_mac = Editor::environment().is_mac();\n\n\t// NOTICE:\n\t// If a new mapping you added here isn't working (and perhaps another lower-precedence one is instead), make sure to advertise\n\t// it as an available action in the respective message handler file (such as the bottom of `document_message_handler.rs`).\n\n\tlet mappings = mapping![\n\t\t// ===============\n\t\t// HIGHER PRIORITY\n\t\t// ===============\n\t\t//\n\t\t// NavigationMessage\n\t\tentry!(PointerMove; refresh_keys=[Shift], action_dispatch=NavigationMessage::PointerMove { snap: Shift }),\n\t\tentry!(KeyUp(MouseLeft); action_dispatch=NavigationMessage::EndCanvasPTZ { abort_transform: false }),\n\t\tentry!(KeyUp(MouseMiddle); action_dispatch=NavigationMessage::EndCanvasPTZ { abort_transform: false }),\n\t\tentry!(KeyUp(MouseRight); action_dispatch=NavigationMessage::EndCanvasPTZ { abort_transform: false }),\n\t\tentry!(KeyDown(MouseRight); action_dispatch=NavigationMessage::EndCanvasPTZ { abort_transform: true }),\n\t\tentry!(KeyDown(Escape); action_dispatch=NavigationMessage::EndCanvasPTZ { abort_transform: true }),\n\t\tentry!(KeyDown(MouseLeft); action_dispatch=NavigationMessage::EndCanvasPTZWithClick { commit_key: MouseLeft }),\n\t\tentry!(KeyDown(MouseMiddle); action_dispatch=NavigationMessage::EndCanvasPTZWithClick { commit_key: MouseMiddle }),\n\t\tentry!(KeyDown(MouseRight); action_dispatch=NavigationMessage::EndCanvasPTZWithClick { commit_key: MouseRight }),\n\t\t//\n\t\t// ===============\n\t\t// NORMAL PRIORITY\n\t\t// ===============\n\t\t//\n\t\t// Hack to prevent Left Click + Accel + Z combo (this effectively blocks you from making a double undo with AbortTransaction)\n\t\tentry!(KeyDown(KeyZ); modifiers=[Accel, MouseLeft], action_dispatch=DocumentMessage::Noop),\n\t\t//\n\t\t// AppWindowMessage\n\t\tentry!(KeyDown(F11); disabled=is_mac, action_dispatch=AppWindowMessage::Fullscreen),\n\t\tentry!(KeyDown(KeyF); modifiers=[Command, Control], disabled=!is_mac, action_dispatch=AppWindowMessage::Fullscreen),\n\t\tentry!(KeyDown(KeyQ); modifiers=[Command], disabled=cfg!(not(target_os = \"macos\")), action_dispatch=AppWindowMessage::Close),\n\t\t//\n\t\t// ClipboardMessage\n\t\tentry!(KeyDown(KeyX); modifiers=[Accel], action_dispatch=ClipboardMessage::Cut),\n\t\tentry!(KeyDown(KeyC); modifiers=[Accel], action_dispatch=ClipboardMessage::Copy),\n\t\tentry!(KeyDown(KeyV); modifiers=[Accel], action_dispatch=ClipboardMessage::Paste),\n\t\t//\n\t\t// NodeGraphMessage\n\t\tentry!(KeyDown(MouseLeft); action_dispatch=NodeGraphMessage::PointerDown { shift_click: false, control_click: false, alt_click: false, right_click: false }),\n\t\tentry!(KeyDown(MouseLeft); modifiers=[Shift], action_dispatch=NodeGraphMessage::PointerDown { shift_click: true, control_click: false, alt_click: false, right_click: false }),\n\t\tentry!(KeyDown(MouseLeft); modifiers=[Accel], action_dispatch=NodeGraphMessage::PointerDown { shift_click: false, control_click: true, alt_click: false, right_click: false }),\n\t\tentry!(KeyDown(MouseLeft); modifiers=[Shift, Accel], action_dispatch=NodeGraphMessage::PointerDown { shift_click: true, control_click: true, alt_click: false, right_click: false }),\n\t\tentry!(KeyDown(MouseLeft); modifiers=[Alt], action_dispatch=NodeGraphMessage::PointerDown { shift_click: false, control_click: false, alt_click: true, right_click: false }),\n\t\tentry!(KeyDown(MouseRight); action_dispatch=NodeGraphMessage::PointerDown { shift_click: false, control_click: false, alt_click: false, right_click: true }),\n\t\tentry!(DoubleClick(MouseButton::Left); action_dispatch=NodeGraphMessage::EnterNestedNetwork),\n\t\tentry!(PointerMove; refresh_keys=[Control, Alt, Shift], action_dispatch=NodeGraphMessage::PointerMove { shift: Shift }),\n\t\tentry!(PointerShake; action_dispatch=NodeGraphMessage::ShakeNode),\n\t\tentry!(KeyUp(MouseLeft); action_dispatch=NodeGraphMessage::PointerUp),\n\t\tentry!(KeyDown(Delete); modifiers=[Accel], action_dispatch=NodeGraphMessage::DeleteSelectedNodes { delete_children: false }),\n\t\tentry!(KeyDown(Backspace); modifiers=[Accel], action_dispatch=NodeGraphMessage::DeleteSelectedNodes { delete_children: false }),\n\t\tentry!(KeyDown(Delete); action_dispatch=NodeGraphMessage::DeleteSelectedNodes { delete_children: true }),\n\t\tentry!(KeyDown(Backspace); action_dispatch=NodeGraphMessage::DeleteSelectedNodes { delete_children: true }),\n\t\tentry!(KeyDown(KeyX); modifiers=[Accel], action_dispatch=NodeGraphMessage::Cut),\n\t\tentry!(KeyDown(KeyC); modifiers=[Accel], action_dispatch=NodeGraphMessage::Copy),\n\t\tentry!(KeyDown(KeyD); modifiers=[Accel], action_dispatch=NodeGraphMessage::DuplicateSelectedNodes),\n\t\tentry!(KeyDown(KeyH); modifiers=[Accel], action_dispatch=NodeGraphMessage::ToggleSelectedVisibility),\n\t\tentry!(KeyDown(KeyL); modifiers=[Accel], action_dispatch=NodeGraphMessage::ToggleSelectedLocked),\n\t\tentry!(KeyDown(KeyL); modifiers=[Alt], action_dispatch=NodeGraphMessage::ToggleSelectedAsLayersOrNodes),\n\t\tentry!(KeyDown(KeyC); modifiers=[Alt], action_dispatch=NodeGraphMessage::SendClickTargets),\n\t\tentry!(KeyDown(KeyM); modifiers=[Accel], action_dispatch=NodeGraphMessage::MergeSelectedNodes),\n\t\tentry!(KeyUp(KeyC); action_dispatch=NodeGraphMessage::EndSendClickTargets),\n\t\tentry!(KeyDown(ArrowUp); action_dispatch=NodeGraphMessage::ShiftSelectedNodes { direction: Direction::Up, rubber_band: false }),\n\t\tentry!(KeyDown(ArrowRight); action_dispatch=NodeGraphMessage::ShiftSelectedNodes { direction: Direction::Right, rubber_band: false }),\n\t\tentry!(KeyDown(ArrowDown); action_dispatch=NodeGraphMessage::ShiftSelectedNodes { direction: Direction::Down, rubber_band: false }),\n\t\tentry!(KeyDown(ArrowLeft); action_dispatch=NodeGraphMessage::ShiftSelectedNodes { direction: Direction::Left, rubber_band: false }),\n\t\t//\n\t\t// TransformLayerMessage\n\t\tentry!(KeyDown(Enter); action_dispatch=TransformLayerMessage::ApplyTransformOperation { final_transform: true }),\n\t\tentry!(KeyDown(MouseLeft); action_dispatch=TransformLayerMessage::ApplyTransformOperation { final_transform: true }),\n\t\tentry!(KeyDown(MouseRight); action_dispatch=TransformLayerMessage::CancelTransformOperation),\n\t\tentry!(KeyDown(Escape); action_dispatch=TransformLayerMessage::CancelTransformOperation),\n\t\tentry!(KeyDown(KeyX); action_dispatch=TransformLayerMessage::ConstrainX),\n\t\tentry!(KeyDown(KeyY); action_dispatch=TransformLayerMessage::ConstrainY),\n\t\tentry!(KeyDown(Backspace); action_dispatch=TransformLayerMessage::TypeBackspace),\n\t\tentry!(KeyDown(Minus); action_dispatch=TransformLayerMessage::TypeNegate),\n\t\tentry!(KeyDown(Comma); action_dispatch=TransformLayerMessage::TypeDecimalPoint),\n\t\tentry!(KeyDown(Period); action_dispatch=TransformLayerMessage::TypeDecimalPoint),\n\t\tentry!(PointerMove; refresh_keys=[Control, Shift], action_dispatch=TransformLayerMessage::PointerMove { slow_key: Shift, increments_key: Control }),\n\t\t//\n\t\t// SelectToolMessage\n\t\tentry!(PointerMove; refresh_keys=[Control, Alt, Shift], action_dispatch=SelectToolMessage::PointerMove { modifier_keys: SelectToolPointerKeys { axis_align: Shift, snap_angle: Shift, center: Alt, duplicate: Alt } }),\n\t\tentry!(KeyDown(MouseLeft); action_dispatch=SelectToolMessage::DragStart { extend_selection: Shift, remove_from_selection: Alt, select_deepest: Accel, lasso_select: Control, skew: Control }),\n\t\tentry!(KeyUp(MouseLeft); action_dispatch=SelectToolMessage::DragStop { remove_from_selection: Alt }),\n\t\tentry!(KeyDown(Enter); action_dispatch=SelectToolMessage::Enter),\n\t\tentry!(DoubleClick(MouseButton::Left); action_dispatch=SelectToolMessage::EditLayer),\n\t\tentry!(KeyDown(MouseRight); action_dispatch=SelectToolMessage::Abort),\n\t\tentry!(KeyDown(Escape); action_dispatch=SelectToolMessage::Abort),\n\t\t//\n\t\t// ArtboardToolMessage\n\t\tentry!(KeyDown(MouseLeft); action_dispatch=ArtboardToolMessage::PointerDown),\n\t\tentry!(PointerMove; refresh_keys=[Shift, Alt], action_dispatch=ArtboardToolMessage::PointerMove { constrain_axis_or_aspect: Shift, center: Alt }),\n\t\tentry!(KeyUp(MouseLeft); action_dispatch=ArtboardToolMessage::PointerUp),\n\t\tentry!(KeyDown(Delete); action_dispatch=ArtboardToolMessage::DeleteSelected),\n\t\tentry!(KeyDown(Backspace); action_dispatch=ArtboardToolMessage::DeleteSelected),\n\t\tentry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowLeft], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowRight], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowUp); modifiers=[Shift], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: 0., delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowDown); modifiers=[Shift, ArrowLeft], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowDown); modifiers=[Shift, ArrowRight], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowDown); modifiers=[Shift], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: 0., delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowLeft); modifiers=[Shift, ArrowUp], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowLeft); modifiers=[Shift, ArrowDown], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowLeft); modifiers=[Shift], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -BIG_NUDGE_AMOUNT, delta_y: 0., resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowRight); modifiers=[Shift, ArrowUp], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowRight); modifiers=[Shift, ArrowDown], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowRight); modifiers=[Shift], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: BIG_NUDGE_AMOUNT, delta_y: 0., resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowUp); modifiers=[ArrowLeft], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowUp); modifiers=[ArrowRight], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowUp); action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: 0., delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowDown); modifiers=[ArrowLeft], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowDown); modifiers=[ArrowRight], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowDown); action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: 0., delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowLeft); modifiers=[ArrowUp], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowLeft); modifiers=[ArrowDown], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowLeft); action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -NUDGE_AMOUNT, delta_y: 0., resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowRight); modifiers=[ArrowUp], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowRight); modifiers=[ArrowDown], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowRight); action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: NUDGE_AMOUNT, delta_y: 0., resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(MouseRight); action_dispatch=ArtboardToolMessage::Abort),\n\t\tentry!(KeyDown(Escape); action_dispatch=ArtboardToolMessage::Abort),\n\t\t//\n\t\t// NavigateToolMessage\n\t\tentry!(KeyDown(MouseLeft); action_dispatch=NavigateToolMessage::ZoomCanvasBegin),\n\t\tentry!(KeyDown(MouseLeft); modifiers=[Alt], action_dispatch=NavigateToolMessage::TiltCanvasBegin),\n\t\tentry!(PointerMove; refresh_keys=[Shift], action_dispatch=NavigateToolMessage::PointerMove { snap: Shift }),\n\t\tentry!(KeyUp(MouseLeft); action_dispatch=NavigateToolMessage::PointerUp { zoom_in: true }),\n\t\tentry!(KeyUp(MouseLeft); modifiers=[Shift], action_dispatch=NavigateToolMessage::PointerUp { zoom_in: false }),\n\t\t//\n\t\t// EyedropperToolMessage\n\t\tentry!(KeyDown(MouseLeft); action_dispatch=EyedropperToolMessage::SamplePrimaryColorBegin),\n\t\tentry!(KeyDown(MouseLeft); modifiers=[Shift], action_dispatch=EyedropperToolMessage::SampleSecondaryColorBegin),\n\t\tentry!(KeyUp(MouseLeft); action_dispatch=EyedropperToolMessage::SamplePrimaryColorEnd),\n\t\tentry!(KeyUp(MouseLeft); modifiers=[Shift], action_dispatch=EyedropperToolMessage::SampleSecondaryColorEnd),\n\t\tentry!(PointerMove; action_dispatch=EyedropperToolMessage::PointerMove),\n\t\tentry!(KeyDown(MouseRight); action_dispatch=EyedropperToolMessage::Abort),\n\t\tentry!(KeyDown(Escape); action_dispatch=EyedropperToolMessage::Abort),\n\t\t//\n\t\t// TextToolMessage\n\t\tentry!(PointerMove; refresh_keys=[Alt, Shift], action_dispatch=TextToolMessage::PointerMove { center: Alt, lock_ratio: Shift }),\n\t\tentry!(KeyDown(MouseLeft); action_dispatch=TextToolMessage::DragStart),\n\t\tentry!(KeyUp(MouseLeft); action_dispatch=TextToolMessage::DragStop),\n\t\tentry!(KeyDown(MouseRight); action_dispatch=TextToolMessage::Abort),\n\t\tentry!(KeyDown(Escape); action_dispatch=TextToolMessage::Abort),\n\t\tentry!(KeyDown(Enter); modifiers=[Accel], action_dispatch=TextToolMessage::Abort),\n\t\t//\n\t\t// GradientToolMessage\n\t\tentry!(DoubleClick(MouseButton::Left); action_dispatch=GradientToolMessage::DoubleClick),\n\t\tentry!(KeyDown(MouseLeft); action_dispatch=GradientToolMessage::PointerDown),\n\t\tentry!(PointerMove; refresh_keys=[Shift, Control], action_dispatch=GradientToolMessage::PointerMove { constrain_axis: Shift, lock_angle: Control }),\n\t\tentry!(KeyUp(MouseLeft); action_dispatch=GradientToolMessage::PointerUp),\n\t\tentry!(KeyDown(Delete); action_dispatch=GradientToolMessage::DeleteStop),\n\t\tentry!(KeyDown(Backspace); action_dispatch=GradientToolMessage::DeleteStop),\n\t\tentry!(KeyDown(MouseRight); action_dispatch=GradientToolMessage::Abort),\n\t\tentry!(KeyDown(Escape); action_dispatch=GradientToolMessage::Abort),\n\t\t//\n\t\t// ShapeToolMessage\n\t\tentry!(KeyDown(MouseLeft); action_dispatch=ShapeToolMessage::DragStart),\n\t\tentry!(KeyUp(MouseLeft); action_dispatch=ShapeToolMessage::DragStop),\n\t\tentry!(KeyDown(MouseRight); action_dispatch=ShapeToolMessage::Abort),\n\t\tentry!(KeyDown(Escape); action_dispatch=ShapeToolMessage::Abort),\n\t\tentry!(KeyDown(BracketLeft); action_dispatch=ShapeToolMessage::DecreaseSides),\n\t\tentry!(KeyDown(BracketRight); action_dispatch=ShapeToolMessage::IncreaseSides),\n\t\tentry!(PointerMove; refresh_keys=[Alt, Shift, Control], action_dispatch=ShapeToolMessage::PointerMove { modifier: [Alt, Shift, Control] }),\n\t\tentry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowLeft], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowRight], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowUp); modifiers=[Shift], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowDown); modifiers=[Shift, ArrowLeft], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowDown); modifiers=[Shift, ArrowRight], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowDown); modifiers=[Shift], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: 0., delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowLeft); modifiers=[Shift, ArrowUp], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowLeft); modifiers=[Shift, ArrowDown], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowLeft); modifiers=[Shift], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: 0., resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowRight); modifiers=[Shift, ArrowUp], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowRight); modifiers=[Shift, ArrowDown], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowRight); modifiers=[Shift], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: 0., resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowUp); modifiers=[ArrowLeft], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowUp); modifiers=[ArrowRight], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowUp); action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowDown); modifiers=[ArrowLeft], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowDown); modifiers=[ArrowRight], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowDown); action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: 0., delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowLeft); modifiers=[ArrowUp], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowLeft); modifiers=[ArrowDown], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowLeft); action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: 0., resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowRight); modifiers=[ArrowUp], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowRight); modifiers=[ArrowDown], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowRight); action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: 0., resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowUp); action_dispatch=ShapeToolMessage::IncreaseSides),\n\t\tentry!(KeyDown(ArrowDown); action_dispatch=ShapeToolMessage::DecreaseSides),\n\t\t//\n\t\t// PathToolMessage\n\t\tentry!(KeyDown(Delete); modifiers=[Accel], action_dispatch=PathToolMessage::DeleteAndBreakPath),\n\t\tentry!(KeyDown(Backspace); modifiers=[Accel], action_dispatch=PathToolMessage::DeleteAndBreakPath),\n\t\tentry!(KeyDown(Delete); modifiers=[Shift], action_dispatch=PathToolMessage::BreakPath),\n\t\tentry!(KeyDown(Backspace); modifiers=[Shift], action_dispatch=PathToolMessage::BreakPath),\n\t\tentry!(KeyDown(KeyX); modifiers=[Accel], action_dispatch=PathToolMessage::Cut { clipboard: Clipboard::Device }),\n\t\tentry!(KeyDown(KeyC); modifiers=[Accel], action_dispatch=PathToolMessage::Copy { clipboard: Clipboard::Device }),\n\t\tentry!(KeyDown(KeyD); modifiers=[Accel], action_dispatch=PathToolMessage::Duplicate),\n\t\tentry!(KeyDownNoRepeat(Tab); action_dispatch=PathToolMessage::SwapSelectedHandles),\n\t\tentry!(KeyDown(MouseLeft); action_dispatch=PathToolMessage::MouseDown { extend_selection: Shift, lasso_select: Control, handle_drag_from_anchor: Alt, drag_restore_handle: Control, segment_editing_modifier: Control }),\n\t\tentry!(KeyDown(MouseRight); action_dispatch=PathToolMessage::RightClick),\n\t\tentry!(KeyDown(Escape); action_dispatch=PathToolMessage::Escape),\n\t\tentry!(KeyDown(KeyG); action_dispatch=PathToolMessage::GRS { key: KeyG }),\n\t\tentry!(KeyDown(KeyR); action_dispatch=PathToolMessage::GRS { key: KeyR }),\n\t\tentry!(KeyDown(KeyS); action_dispatch=PathToolMessage::GRS { key: KeyS }),\n\t\tentry!(PointerMove; refresh_keys=[KeyC, Space, Control, Shift, Alt], action_dispatch=PathToolMessage::PointerMove { toggle_colinear: KeyC, equidistant: Alt, move_anchor_with_handles: Space, snap_angle: Shift, lock_angle: Control, delete_segment: Alt, break_colinear_molding: Alt, segment_editing_modifier: Control }),\n\t\tentry!(KeyDown(Delete); action_dispatch=PathToolMessage::Delete),\n\t\tentry!(KeyDown(KeyA); modifiers=[Accel], action_dispatch=PathToolMessage::SelectAll),\n\t\tentry!(KeyDown(KeyA); modifiers=[Accel, Shift], canonical, action_dispatch=PathToolMessage::DeselectAllSelected),\n\t\tentry!(KeyDown(KeyA); modifiers=[Alt], action_dispatch=PathToolMessage::DeselectAllSelected),\n\t\tentry!(KeyDown(Backspace); action_dispatch=PathToolMessage::Delete),\n\t\tentry!(KeyUp(MouseLeft); action_dispatch=PathToolMessage::DragStop { extend_selection: Shift, shrink_selection: Alt }),\n\t\tentry!(KeyDown(Enter); action_dispatch=PathToolMessage::Enter { extend_selection: Shift, shrink_selection: Alt }),\n\t\tentry!(DoubleClick(MouseButton::Left); action_dispatch=PathToolMessage::DoubleClick { extend_selection: Shift, shrink_selection: Alt }),\n\t\tentry!(KeyDown(ArrowRight); action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: NUDGE_AMOUNT, delta_y: 0. }),\n\t\tentry!(KeyDown(ArrowRight); modifiers=[Shift], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: BIG_NUDGE_AMOUNT, delta_y: 0. }),\n\t\tentry!(KeyDown(ArrowRight); modifiers=[ArrowUp], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }),\n\t\tentry!(KeyDown(ArrowRight); modifiers=[ArrowDown], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }),\n\t\tentry!(KeyDown(ArrowRight); modifiers=[Shift, ArrowUp], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }),\n\t\tentry!(KeyDown(ArrowRight); modifiers=[Shift, ArrowDown], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }),\n\t\tentry!(KeyDown(ArrowUp); action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: 0., delta_y: -NUDGE_AMOUNT }),\n\t\tentry!(KeyDown(ArrowUp); modifiers=[Shift], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: 0., delta_y: -BIG_NUDGE_AMOUNT }),\n\t\tentry!(KeyDown(ArrowUp); modifiers=[ArrowLeft], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }),\n\t\tentry!(KeyDown(ArrowUp); modifiers=[ArrowRight], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }),\n\t\tentry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowLeft], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }),\n\t\tentry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowRight], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }),\n\t\tentry!(KeyDown(ArrowLeft); action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: -NUDGE_AMOUNT, delta_y: 0. }),\n\t\tentry!(KeyDown(ArrowLeft); modifiers=[Shift], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: -BIG_NUDGE_AMOUNT, delta_y: 0. }),\n\t\tentry!(KeyDown(ArrowLeft); modifiers=[ArrowUp], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }),\n\t\tentry!(KeyDown(ArrowLeft); modifiers=[ArrowDown], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }),\n\t\tentry!(KeyDown(ArrowLeft); modifiers=[Shift, ArrowUp], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }),\n\t\tentry!(KeyDown(ArrowLeft); modifiers=[Shift, ArrowDown], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }),\n\t\tentry!(KeyDown(ArrowDown); action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: 0., delta_y: NUDGE_AMOUNT }),\n\t\tentry!(KeyDown(ArrowDown); modifiers=[Shift], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: 0., delta_y: BIG_NUDGE_AMOUNT }),\n\t\tentry!(KeyDown(ArrowDown); modifiers=[ArrowLeft], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }),\n\t\tentry!(KeyDown(ArrowDown); modifiers=[ArrowRight], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }),\n\t\tentry!(KeyDown(ArrowDown); modifiers=[Shift, ArrowLeft], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }),\n\t\tentry!(KeyDown(ArrowDown); modifiers=[Shift, ArrowRight], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }),\n\t\tentry!(KeyDown(KeyJ); modifiers=[Accel], action_dispatch=ToolMessage::Path(PathToolMessage::ClosePath)),\n\t\t//\n\t\t// PenToolMessage\n\t\tentry!(PointerMove; refresh_keys=[Control, Alt, Shift, KeyC], action_dispatch=PenToolMessage::PointerMove { snap_angle: Shift, break_handle: Alt, lock_angle: Control, colinear: KeyC, move_anchor_with_handles: Space }),\n\t\tentry!(KeyDownNoRepeat(Tab); action_dispatch=PenToolMessage::SwapHandles),\n\t\tentry!(KeyDown(MouseLeft); action_dispatch=PenToolMessage::DragStart { append_to_selected: Shift }),\n\t\tentry!(KeyUp(MouseLeft); action_dispatch=PenToolMessage::DragStop),\n\t\tentry!(KeyDown(MouseRight); action_dispatch=PenToolMessage::Abort),\n\t\tentry!(KeyDown(Escape); action_dispatch=PenToolMessage::Abort),\n\t\tentry!(KeyDown(Enter); action_dispatch=PenToolMessage::Confirm),\n\t\tentry!(KeyDown(Delete); action_dispatch=PenToolMessage::RemovePreviousHandle),\n\t\tentry!(KeyDown(Backspace); action_dispatch=PenToolMessage::RemovePreviousHandle),\n\t\tentry!(KeyDown(KeyG); action_dispatch=PenToolMessage::GRS { grab: KeyG, rotate: KeyR, scale: KeyS }),\n\t\tentry!(KeyDown(KeyR); action_dispatch=PenToolMessage::GRS { grab: KeyG, rotate: KeyR, scale: KeyS }),\n\t\tentry!(KeyDown(KeyS); action_dispatch=PenToolMessage::GRS { grab: KeyG, rotate: KeyR, scale: KeyS }),\n\t\t//\n\t\t// FreehandToolMessage\n\t\tentry!(PointerMove; action_dispatch=FreehandToolMessage::PointerMove),\n\t\tentry!(KeyDown(MouseLeft); action_dispatch=FreehandToolMessage::DragStart { append_to_selected: Shift }),\n\t\tentry!(KeyUp(MouseLeft); action_dispatch=FreehandToolMessage::DragStop),\n\t\tentry!(KeyDown(MouseRight); action_dispatch=FreehandToolMessage::Abort),\n\t\tentry!(KeyDown(Escape); action_dispatch=FreehandToolMessage::Abort),\n\t\t//\n\t\t// SplineToolMessage\n\t\tentry!(PointerMove; action_dispatch=SplineToolMessage::PointerMove),\n\t\tentry!(KeyDown(MouseLeft); action_dispatch=SplineToolMessage::DragStart { append_to_selected: Shift }),\n\t\tentry!(KeyUp(MouseLeft); action_dispatch=SplineToolMessage::DragStop),\n\t\tentry!(KeyDown(MouseRight); action_dispatch=SplineToolMessage::Confirm),\n\t\tentry!(KeyDown(Escape); action_dispatch=SplineToolMessage::Confirm),\n\t\tentry!(KeyDown(Enter); action_dispatch=SplineToolMessage::Confirm),\n\t\t//\n\t\t// FillToolMessage\n\t\tentry!(PointerMove; refresh_keys=[Shift], action_dispatch=FillToolMessage::PointerMove),\n\t\tentry!(KeyDown(MouseLeft); action_dispatch=FillToolMessage::FillPrimaryColor),\n\t\tentry!(KeyDown(MouseLeft); modifiers=[Shift], action_dispatch=FillToolMessage::FillSecondaryColor),\n\t\tentry!(KeyUp(MouseLeft); action_dispatch=FillToolMessage::PointerUp),\n\t\tentry!(KeyDown(MouseRight); action_dispatch=FillToolMessage::Abort),\n\t\tentry!(KeyDown(Escape); action_dispatch=FillToolMessage::Abort),\n\t\t//\n\t\t// BrushToolMessage\n\t\tentry!(PointerMove; action_dispatch=BrushToolMessage::PointerMove),\n\t\tentry!(KeyDown(MouseLeft); action_dispatch=BrushToolMessage::DragStart),\n\t\tentry!(KeyUp(MouseLeft); action_dispatch=BrushToolMessage::DragStop),\n\t\tentry!(KeyDown(BracketLeft); action_dispatch=BrushToolMessage::UpdateOptions { options: BrushToolMessageOptionsUpdate::ChangeDiameter(-BRUSH_SIZE_CHANGE_KEYBOARD) }),\n\t\tentry!(KeyDown(BracketRight); action_dispatch=BrushToolMessage::UpdateOptions { options: BrushToolMessageOptionsUpdate::ChangeDiameter(BRUSH_SIZE_CHANGE_KEYBOARD) }),\n\t\tentry!(KeyDown(MouseRight); action_dispatch=BrushToolMessage::Abort),\n\t\tentry!(KeyDown(Escape); action_dispatch=BrushToolMessage::Abort),\n\t\t//\n\t\t// ToolMessage\n\t\tentry!(KeyDown(KeyV); action_dispatch=ToolMessage::ActivateToolSelect),\n\t\tentry!(KeyDown(KeyZ); action_dispatch=ToolMessage::ActivateToolNavigate),\n\t\tentry!(KeyDown(KeyI); action_dispatch=ToolMessage::ActivateToolEyedropper),\n\t\tentry!(KeyDown(KeyT); action_dispatch=ToolMessage::ActivateToolText),\n\t\tentry!(KeyDown(KeyF); action_dispatch=ToolMessage::ActivateToolFill),\n\t\tentry!(KeyDown(KeyH); action_dispatch=ToolMessage::ActivateToolGradient),\n\t\tentry!(KeyDown(KeyA); action_dispatch=ToolMessage::ActivateToolPath),\n\t\tentry!(KeyDown(KeyP); action_dispatch=ToolMessage::ActivateToolPen),\n\t\tentry!(KeyDown(KeyN); action_dispatch=ToolMessage::ActivateToolFreehand),\n\t\tentry!(KeyDown(KeyL); action_dispatch=ToolMessage::ActivateToolShapeLine),\n\t\tentry!(KeyDown(KeyM); action_dispatch=ToolMessage::ActivateToolShapeRectangle),\n\t\tentry!(KeyDown(KeyE); action_dispatch=ToolMessage::ActivateToolShapeEllipse),\n\t\tentry!(KeyDown(KeyY); action_dispatch=ToolMessage::ActivateToolShape),\n\t\tentry!(KeyDown(KeyB); action_dispatch=ToolMessage::ActivateToolBrush),\n\t\tentry!(KeyDown(KeyD); action_dispatch=ToolMessage::ResetColors),\n\t\tentry!(KeyDown(KeyX); modifiers=[Shift], action_dispatch=ToolMessage::SwapColors),\n\t\tentry!(KeyDown(KeyC); modifiers=[Alt], action_dispatch=ToolMessage::SelectRandomWorkingColor { primary: true }),\n\t\tentry!(KeyDown(KeyC); modifiers=[Alt, Shift], action_dispatch=ToolMessage::SelectRandomWorkingColor { primary: false }),\n\t\t// TODO: Change to KeyDownNoRepeat when https://github.com/GraphiteEditor/Graphite/issues/2266 is resolved\n\t\tentry!(KeyDown(Tab); action_dispatch=ToolMessage::ToggleSelectVsPath),\n\t\t//\n\t\t// DocumentMessage\n\t\tentry!(KeyDown(Space); modifiers=[Control], action_dispatch=DocumentMessage::GraphViewOverlayToggle),\n\t\tentry!(KeyDownNoRepeat(Escape); action_dispatch=DocumentMessage::Escape),\n\t\tentry!(KeyDown(Delete); action_dispatch=DocumentMessage::DeleteSelectedLayers),\n\t\tentry!(KeyDown(Backspace); action_dispatch=DocumentMessage::DeleteSelectedLayers),\n\t\tentry!(KeyDown(KeyO); modifiers=[Alt], action_dispatch=DocumentMessage::ToggleOverlaysVisibility),\n\t\tentry!(KeyDown(KeyS); modifiers=[Alt], action_dispatch=DocumentMessage::ToggleSnapping),\n\t\tentry!(KeyDown(KeyH); modifiers=[Accel], action_dispatch=DocumentMessage::ToggleSelectedVisibility),\n\t\tentry!(KeyDown(KeyL); modifiers=[Accel], action_dispatch=DocumentMessage::ToggleSelectedLocked),\n\t\tentry!(KeyDown(KeyG); modifiers=[Alt], action_dispatch=DocumentMessage::ToggleGridVisibility),\n\t\tentry!(KeyDown(KeyZ); modifiers=[Accel, Shift], canonical, action_dispatch=DocumentMessage::Redo),\n\t\tentry!(KeyDown(KeyY); modifiers=[Accel], action_dispatch=DocumentMessage::Redo),\n\t\tentry!(KeyDown(KeyZ); modifiers=[Accel], action_dispatch=DocumentMessage::Undo),\n\t\tentry!(KeyDown(KeyA); modifiers=[Accel], action_dispatch=DocumentMessage::SelectAllLayers),\n\t\tentry!(KeyDown(KeyA); modifiers=[Accel, Shift], canonical, action_dispatch=DocumentMessage::DeselectAllLayers),\n\t\tentry!(KeyDown(KeyA); modifiers=[Alt], action_dispatch=DocumentMessage::DeselectAllLayers),\n\t\tentry!(KeyDown(KeyS); modifiers=[Accel], action_dispatch=DocumentMessage::SaveDocument),\n\t\tentry!(KeyDown(KeyS); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::SaveDocumentAs),\n\t\tentry!(KeyDown(KeyD); modifiers=[Accel], canonical, action_dispatch=DocumentMessage::DuplicateSelectedLayers),\n\t\tentry!(KeyDown(KeyJ); modifiers=[Accel], action_dispatch=DocumentMessage::DuplicateSelectedLayers),\n\t\tentry!(KeyDown(KeyG); modifiers=[Accel], action_dispatch=DocumentMessage::GroupSelectedLayers { group_folder_type: GroupFolderType::Layer }),\n\t\tentry!(KeyDown(KeyG); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::UngroupSelectedLayers),\n\t\tentry!(KeyDown(KeyN); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::CreateEmptyFolder),\n\t\tentry!(KeyDown(Escape); modifiers=[Shift], action_dispatch=DocumentMessage::SelectParentLayer),\n\t\tentry!(KeyDown(BracketLeft); modifiers=[Alt], canonical, action_dispatch=DocumentMessage::SelectionStepBack),\n\t\tentry!(KeyDown(BracketRight); modifiers=[Alt], canonical, action_dispatch=DocumentMessage::SelectionStepForward),\n\t\tentry!(KeyDown(MouseBack); action_dispatch=DocumentMessage::SelectionStepBack),\n\t\tentry!(KeyDown(MouseForward); action_dispatch=DocumentMessage::SelectionStepForward),\n\t\tentry!(KeyDown(Digit0); modifiers=[Accel], action_dispatch=DocumentMessage::ZoomCanvasToFitAll),\n\t\tentry!(KeyDown(Digit1); modifiers=[Accel], action_dispatch=DocumentMessage::ZoomCanvasTo100Percent),\n\t\tentry!(KeyDown(Digit2); modifiers=[Accel], action_dispatch=DocumentMessage::ZoomCanvasTo200Percent),\n\t\tentry!(KeyDown(BracketLeft); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::SelectedLayersLowerToBack),\n\t\tentry!(KeyDown(BracketRight); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::SelectedLayersRaiseToFront),\n\t\tentry!(KeyDown(BracketLeft); modifiers=[Accel], action_dispatch=DocumentMessage::SelectedLayersLower),\n\t\tentry!(KeyDown(BracketRight); modifiers=[Accel], action_dispatch=DocumentMessage::SelectedLayersRaise),\n\t\tentry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowLeft], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowRight], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowUp); modifiers=[Shift], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowDown); modifiers=[Shift, ArrowLeft], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowDown); modifiers=[Shift, ArrowRight], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowDown); modifiers=[Shift], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowLeft); modifiers=[Shift, ArrowUp], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowLeft); modifiers=[Shift, ArrowDown], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowLeft); modifiers=[Shift], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: 0., resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowRight); modifiers=[Shift, ArrowUp], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowRight); modifiers=[Shift, ArrowDown], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowRight); modifiers=[Shift], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: 0., resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowUp); modifiers=[ArrowLeft], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowUp); modifiers=[ArrowRight], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowUp); action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowDown); modifiers=[ArrowLeft], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowDown); modifiers=[ArrowRight], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowDown); action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowLeft); modifiers=[ArrowUp], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowLeft); modifiers=[ArrowDown], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowLeft); action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: 0., resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowRight); modifiers=[ArrowUp], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowRight); modifiers=[ArrowDown], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),\n\t\tentry!(KeyDown(ArrowRight); action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: 0., resize: Alt, resize_opposite_corner: Control }),\n\t\t//\n\t\t// TransformLayerMessage\n\t\tentry!(KeyDown(KeyG); action_dispatch=TransformLayerMessage::BeginGrab),\n\t\tentry!(KeyDown(KeyR); action_dispatch=TransformLayerMessage::BeginRotate),\n\t\tentry!(KeyDown(KeyS); action_dispatch=TransformLayerMessage::BeginScale),\n\t\tentry!(KeyDown(Digit0); action_dispatch=TransformLayerMessage::TypeDigit { digit: 0 }),\n\t\tentry!(KeyDown(Digit1); action_dispatch=TransformLayerMessage::TypeDigit { digit: 1 }),\n\t\tentry!(KeyDown(Digit2); action_dispatch=TransformLayerMessage::TypeDigit { digit: 2 }),\n\t\tentry!(KeyDown(Digit3); action_dispatch=TransformLayerMessage::TypeDigit { digit: 3 }),\n\t\tentry!(KeyDown(Digit4); action_dispatch=TransformLayerMessage::TypeDigit { digit: 4 }),\n\t\tentry!(KeyDown(Digit5); action_dispatch=TransformLayerMessage::TypeDigit { digit: 5 }),\n\t\tentry!(KeyDown(Digit6); action_dispatch=TransformLayerMessage::TypeDigit { digit: 6 }),\n\t\tentry!(KeyDown(Digit7); action_dispatch=TransformLayerMessage::TypeDigit { digit: 7 }),\n\t\tentry!(KeyDown(Digit8); action_dispatch=TransformLayerMessage::TypeDigit { digit: 8 }),\n\t\tentry!(KeyDown(Digit9); action_dispatch=TransformLayerMessage::TypeDigit { digit: 9 }),\n\t\t//\n\t\t// NavigationMessage\n\t\tentry!(KeyDown(MouseMiddle); modifiers=[Alt], action_dispatch=NavigationMessage::BeginCanvasTilt { was_dispatched_from_menu: false }),\n\t\tentry!(KeyDown(MouseLeft); modifiers=[Alt, Space], action_dispatch=NavigationMessage::BeginCanvasTilt { was_dispatched_from_menu: false }),\n\t\tentry!(KeyDown(MouseMiddle); modifiers=[Control], action_dispatch=NavigationMessage::BeginCanvasZoom),\n\t\tentry!(KeyDown(MouseLeft); modifiers=[Control, Space], action_dispatch=NavigationMessage::BeginCanvasZoom),\n\t\tentry!(KeyDown(MouseMiddle); action_dispatch=NavigationMessage::BeginCanvasPan),\n\t\tentry!(KeyDown(MouseLeft); modifiers=[Space], action_dispatch=NavigationMessage::BeginCanvasPan),\n\t\tentry!(KeyDown(NumpadAdd); modifiers=[Accel], action_dispatch=NavigationMessage::CanvasZoomIncrease { center_on_mouse: false }),\n\t\t// `FakeKeyPlus` is a nonfunctional key mapping that must be accompanied by its real `Equal` key counterpart. This is used only to set the canonical key label so it shows \"+\" instead of \"=\" in the UI.\n\t\tentry!(KeyDown(FakeKeyPlus); modifiers=[Accel], canonical, action_dispatch=NavigationMessage::CanvasZoomIncrease { center_on_mouse: false }),\n\t\tentry!(KeyDown(Equal); modifiers=[Accel], action_dispatch=NavigationMessage::CanvasZoomIncrease { center_on_mouse: false }),\n\t\tentry!(KeyDown(Minus); modifiers=[Accel], action_dispatch=NavigationMessage::CanvasZoomDecrease { center_on_mouse: false }),\n\t\tentry!(WheelScroll; modifiers=[Control], disabled=zoom_with_scroll, action_dispatch=NavigationMessage::CanvasZoomMouseWheel),\n\t\tentry!(WheelScroll; modifiers=[Command], disabled=zoom_with_scroll, action_dispatch=NavigationMessage::CanvasZoomMouseWheel),\n\t\tentry!(WheelScroll; modifiers=[Shift], disabled=zoom_with_scroll, action_dispatch=NavigationMessage::CanvasPanMouseWheel { use_y_as_x: true }),\n\t\tentry!(WheelScroll; disabled=zoom_with_scroll, action_dispatch=NavigationMessage::CanvasPanMouseWheel { use_y_as_x: false }),\n\t\t// On Mac, the OS already converts Shift+scroll into horizontal scrolling so we have to reverse the behavior from normal to produce the same outcome\n\t\tentry!(WheelScroll; modifiers=[Control], disabled=!zoom_with_scroll, action_dispatch=NavigationMessage::CanvasPanMouseWheel { use_y_as_x: is_mac }),\n\t\tentry!(WheelScroll; modifiers=[Shift], disabled=!zoom_with_scroll, action_dispatch=NavigationMessage::CanvasPanMouseWheel { use_y_as_x: !is_mac }),\n\t\tentry!(WheelScroll; disabled=!zoom_with_scroll, action_dispatch=NavigationMessage::CanvasZoomMouseWheel),\n\t\tentry!(KeyDown(PageUp); modifiers=[Shift], action_dispatch=NavigationMessage::CanvasPanByViewportFraction { delta: DVec2::new(1., 0.) }),\n\t\tentry!(KeyDown(PageDown); modifiers=[Shift], action_dispatch=NavigationMessage::CanvasPanByViewportFraction { delta: DVec2::new(-1., 0.) }),\n\t\tentry!(KeyDown(PageUp); action_dispatch=NavigationMessage::CanvasPanByViewportFraction { delta: DVec2::new(0., 1.) }),\n\t\tentry!(KeyDown(PageDown); action_dispatch=NavigationMessage::CanvasPanByViewportFraction { delta: DVec2::new(0., -1.) }),\n\t\tentry!(KeyDown(Period); action_dispatch=NavigationMessage::FitViewportToSelection),\n\t\t//\n\t\t// PortfolioMessage\n\t\tentry!(KeyDown(Tab); modifiers=[Control], action_dispatch=PortfolioMessage::NextDocument),\n\t\tentry!(KeyDown(Tab); modifiers=[Control, Shift], action_dispatch=PortfolioMessage::PrevDocument),\n\t\tentry!(KeyDown(KeyW); modifiers=[Accel], action_dispatch=PortfolioMessage::CloseActiveDocumentWithConfirmation),\n\t\tentry!(KeyDown(KeyW); modifiers=[Accel, Alt], action_dispatch=PortfolioMessage::CloseAllDocumentsWithConfirmation),\n\t\tentry!(KeyDown(KeyO); modifiers=[Accel], action_dispatch=PortfolioMessage::Open),\n\t\tentry!(KeyDown(KeyI); modifiers=[Accel], action_dispatch=PortfolioMessage::Import),\n\t\tentry!(KeyDown(KeyX); modifiers=[Accel], action_dispatch=PortfolioMessage::Cut { clipboard: Clipboard::Device }),\n\t\tentry!(KeyDown(KeyC); modifiers=[Accel], action_dispatch=PortfolioMessage::Copy { clipboard: Clipboard::Device }),\n\t\tentry!(KeyDown(KeyR); modifiers=[Alt], action_dispatch=PortfolioMessage::ToggleRulers),\n\t\tentry!(KeyDown(KeyD); modifiers=[Alt], action_dispatch=PortfolioMessage::ToggleDataPanelOpen),\n\t\tentry!(KeyDown(Enter); modifiers=[Alt], action_dispatch=PortfolioMessage::ToggleFocusDocument),\n\t\t//\n\t\t// DialogMessage\n\t\tentry!(KeyDown(KeyE); modifiers=[Accel], action_dispatch=DialogMessage::RequestExportDialog),\n\t\tentry!(KeyDown(KeyN); modifiers=[Accel], action_dispatch=DialogMessage::RequestNewDocumentDialog),\n\t\tentry!(KeyDown(Comma); modifiers=[Accel], action_dispatch=DialogMessage::RequestPreferencesDialog),\n\t\t//\n\t\t// DebugMessage\n\t\tentry!(KeyDown(Digit0); modifiers=[Alt], action_dispatch=DebugMessage::MessageOff),\n\t\tentry!(KeyDown(Digit1); modifiers=[Alt], action_dispatch=DebugMessage::MessageNames),\n\t\tentry!(KeyDown(Digit2); modifiers=[Alt], action_dispatch=DebugMessage::MessageContents),\n\t\t// AnimationMessage\n\t\tentry!(KeyDown(Space); modifiers=[Shift], action_dispatch=AnimationMessage::ToggleLivePreview),\n\t\tentry!(KeyDown(Home); modifiers=[Shift], action_dispatch=AnimationMessage::RestartAnimation),\n\t];\n\tlet (mut key_up, mut key_down, mut key_up_no_repeat, mut key_down_no_repeat, mut double_click, mut wheel_scroll, mut pointer_move, mut pointer_shake) = mappings;\n\n\tlet sort = |list: &mut KeyMappingEntries| list.0.sort_by(|a, b| b.modifiers.count_ones().cmp(&a.modifiers.count_ones()));\n\t// Sort the sublists of `key_up`, `key_down`, `key_up_no_repeat`, and `key_down_no_repeat`\n\tfor list in [&mut key_up, &mut key_down, &mut key_up_no_repeat, &mut key_down_no_repeat] {\n\t\tfor sublist in list {\n\t\t\tsort(sublist);\n\t\t}\n\t}\n\t// Sort the sublists of `double_click`\n\tfor sublist in &mut double_click {\n\t\tsort(sublist)\n\t}\n\t// Sort `wheel_scroll`\n\tsort(&mut wheel_scroll);\n\t// Sort `pointer_move`\n\tsort(&mut pointer_move);\n\t// Sort `pointer_shake`\n\tsort(&mut pointer_shake);\n\n\tMapping {\n\t\tkey_up,\n\t\tkey_down,\n\t\tkey_up_no_repeat,\n\t\tkey_down_no_repeat,\n\t\tdouble_click,\n\t\twheel_scroll,\n\t\tpointer_move,\n\t\tpointer_shake,\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/input_mapper/key_mapping/key_mapping_message.rs",
    "content": "use crate::messages::prelude::*;\n\n#[impl_message(Message, KeyMapping)]\n#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)]\npub enum KeyMappingMessage {\n\t// Sub-messages\n\t#[child]\n\tLookup(InputMapperMessage),\n\n\t// Messages\n\tModifyMapping {\n\t\tmapping: MappingVariant,\n\t},\n}\n\n#[derive(PartialEq, Eq, Clone, Debug, Default, Hash, serde::Serialize, serde::Deserialize)]\npub enum MappingVariant {\n\t#[default]\n\tDefault,\n\n\tZoomWithScroll,\n}\n"
  },
  {
    "path": "editor/src/messages/input_mapper/key_mapping/key_mapping_message_handler.rs",
    "content": "use crate::messages::input_mapper::input_mapper_message_handler::InputMapperMessageContext;\nuse crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup;\nuse crate::messages::prelude::*;\n\n#[derive(ExtractField)]\npub struct KeyMappingMessageContext<'a> {\n\tpub input: &'a InputPreprocessorMessageHandler,\n\tpub actions: ActionList,\n}\n\n#[derive(Debug, Default, ExtractField)]\npub struct KeyMappingMessageHandler {\n\tmapping_handler: InputMapperMessageHandler,\n}\n\n#[message_handler_data]\nimpl MessageHandler<KeyMappingMessage, KeyMappingMessageContext<'_>> for KeyMappingMessageHandler {\n\tfn process_message(&mut self, message: KeyMappingMessage, responses: &mut VecDeque<Message>, context: KeyMappingMessageContext) {\n\t\tlet KeyMappingMessageContext { input, actions } = context;\n\n\t\tmatch message {\n\t\t\t// Sub-messages\n\t\t\tKeyMappingMessage::Lookup(input_message) => self.mapping_handler.process_message(input_message, responses, InputMapperMessageContext { input, actions }),\n\n\t\t\t// Messages\n\t\t\tKeyMappingMessage::ModifyMapping { mapping } => self.mapping_handler.set_mapping(mapping.into()),\n\t\t}\n\t}\n\tadvertise_actions!();\n}\n\nimpl KeyMappingMessageHandler {\n\tpub fn action_input_mapping(&self, action_to_find: &MessageDiscriminant) -> Option<KeysGroup> {\n\t\tself.mapping_handler.action_input_mapping(action_to_find)\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/input_mapper/key_mapping/mod.rs",
    "content": "mod key_mapping_message;\nmod key_mapping_message_handler;\n\n#[doc(inline)]\npub use key_mapping_message::{KeyMappingMessage, KeyMappingMessageDiscriminant, MappingVariant};\n#[doc(inline)]\npub use key_mapping_message_handler::{KeyMappingMessageContext, KeyMappingMessageHandler};\n"
  },
  {
    "path": "editor/src/messages/input_mapper/mod.rs",
    "content": "mod input_mapper_message;\nmod input_mapper_message_handler;\n\npub mod input_mappings;\npub mod key_mapping;\npub mod utility_types;\n\n#[doc(inline)]\npub use input_mapper_message::{InputMapperMessage, InputMapperMessageDiscriminant};\n#[doc(inline)]\npub use input_mapper_message_handler::{InputMapperMessageContext, InputMapperMessageHandler};\n"
  },
  {
    "path": "editor/src/messages/input_mapper/utility_types/input_keyboard.rs",
    "content": "use crate::application::Editor;\nuse crate::messages::prelude::*;\nuse bitflags::bitflags;\nuse std::fmt::{self, Display, Formatter};\nuse std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign};\n\n// ===========\n// StorageType\n// ===========\n\n// TODO: Increase size of type\n/// Edit this to specify the storage type used.\npub type StorageType = u128;\n\n// =========\n// KeyStates\n// =========\n\n// Base-2 logarithm of the storage type used to represents how many bits you need to fully address every bit in that storage type\nconst STORAGE_SIZE: u32 = (std::mem::size_of::<StorageType>() * 8).trailing_zeros();\nconst STORAGE_SIZE_BITS: usize = 1 << STORAGE_SIZE;\nconst KEY_MASK_STORAGE_LENGTH: usize = (NUMBER_OF_KEYS + STORAGE_SIZE_BITS - 1) >> STORAGE_SIZE;\n\npub type KeyStates = BitVector<KEY_MASK_STORAGE_LENGTH>;\n\npub fn all_required_modifiers_pressed(keyboard_state: &KeyStates, modifiers: &KeyStates) -> bool {\n\t// Find which currently pressed keys are also the modifiers in this hotkey entry, then compare those against the required modifiers to see if there are zero missing\n\tlet pressed_modifiers = *keyboard_state & *modifiers;\n\tlet all_modifiers_without_pressed_modifiers = *modifiers ^ pressed_modifiers;\n\n\tall_modifiers_without_pressed_modifiers.is_empty()\n}\n\n// ===========\n// KeyPosition\n// ===========\n\npub enum KeyPosition {\n\tPressed,\n\tReleased,\n}\n\n// ============\n// ModifierKeys\n// ============\n\nbitflags! {\n\t#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]\n\t#[repr(transparent)]\n\t#[serde(transparent)]\n\tpub struct ModifierKeys: u8 {\n\t\tconst SHIFT           = 0b_0000_0001;\n\t\tconst ALT             = 0b_0000_0010;\n\t\tconst CONTROL         = 0b_0000_0100;\n\t\tconst META_OR_COMMAND = 0b_0000_1000;\n\t}\n}\n\n// ===\n// Key\n// ===\n\n// Currently this is mostly based on the JS `KeyboardEvent.code` list: <https://www.w3.org/TR/uievents-code/>\n// But in the future, especially once users can customize keyboard mappings, we should deviate more from this so we have actual symbols\n// like `+` (which doesn't exist because it's the shifted version of `=` on the US keyboard, after which these scan codes are named).\n// We'd ideally like to bind shortcuts to symbols, not scan codes, so the shortcut for \"zoom in\" is `Ctrl +` which the user can press\n// (although we ignore the shift key, so the user doesn't have to press `Ctrl Shift +` on a US keyboard), even if the keyboard layout\n// is for a different locale where the `+` key is somewhere entirely different, shifted or not. This would then also work for numpad `+`.\n#[impl_message(Message, InputMapperMessage, KeyDown)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, num_enum::TryFromPrimitive)]\n#[repr(u8)]\npub enum Key {\n\t// Writing system keys\n\tDigit0,\n\tDigit1,\n\tDigit2,\n\tDigit3,\n\tDigit4,\n\tDigit5,\n\tDigit6,\n\tDigit7,\n\tDigit8,\n\tDigit9,\n\n\tKeyA,\n\tKeyB,\n\tKeyC,\n\tKeyD,\n\tKeyE,\n\tKeyF,\n\tKeyG,\n\tKeyH,\n\tKeyI,\n\tKeyJ,\n\tKeyK,\n\tKeyL,\n\tKeyM,\n\tKeyN,\n\tKeyO,\n\tKeyP,\n\tKeyQ,\n\tKeyR,\n\tKeyS,\n\tKeyT,\n\tKeyU,\n\tKeyV,\n\tKeyW,\n\tKeyX,\n\tKeyY,\n\tKeyZ,\n\n\tBackquote,\n\tBackslash,\n\tBracketLeft,\n\tBracketRight,\n\tComma,\n\tEqual,\n\tMinus,\n\tPeriod,\n\tQuote,\n\tSemicolon,\n\tSlash,\n\n\t// Functional keys\n\tAlt,\n\tMeta,\n\tShift,\n\tControl,\n\tBackspace,\n\tCapsLock,\n\tContextMenu,\n\tEnter,\n\tSpace,\n\tTab,\n\n\t// Control pad keys\n\tDelete,\n\tEnd,\n\tHelp,\n\tHome,\n\tInsert,\n\tPageDown,\n\tPageUp,\n\n\t// Arrow pad keys\n\tArrowDown,\n\tArrowLeft,\n\tArrowRight,\n\tArrowUp,\n\n\t// Numpad keys\n\t// Numpad0,\n\t// Numpad1,\n\t// Numpad2,\n\t// Numpad3,\n\t// Numpad4,\n\t// Numpad5,\n\t// Numpad6,\n\t// Numpad7,\n\t// Numpad8,\n\t// Numpad9,\n\tNumLock,\n\tNumpadAdd,\n\t// NumpadBackspace,\n\t// NumpadClear,\n\t// NumpadClearEntry,\n\t// NumpadComma,\n\t// NumpadDecimal,\n\t// NumpadDivide,\n\t// NumpadEnter,\n\t// NumpadEqual,\n\tNumpadHash,\n\t// NumpadMemoryAdd,\n\t// NumpadMemoryClear,\n\t// NumpadMemoryRecall,\n\t// NumpadMemoryStore,\n\t// NumpadMemorySubtract,\n\tNumpadMultiply,\n\tNumpadParenLeft,\n\tNumpadParenRight,\n\t// NumpadStar,\n\t// NumpadSubtract,\n\n\t// Function keys\n\tEscape,\n\tF1,\n\tF2,\n\tF3,\n\tF4,\n\tF5,\n\tF6,\n\tF7,\n\tF8,\n\tF9,\n\tF10,\n\tF11,\n\tF12,\n\tF13,\n\tF14,\n\tF15,\n\tF16,\n\tF17,\n\tF18,\n\tF19,\n\tF20,\n\tF21,\n\tF22,\n\tF23,\n\tF24,\n\tFn,\n\tFnLock,\n\tPrintScreen,\n\tScrollLock,\n\tPause,\n\n\t// Unidentified keys\n\tUnidentified,\n\n\t// Other keys that aren't part of the W3C spec\n\t//\n\t/// \"Cmd\" on Mac (not present on other platforms).\n\tCommand,\n\t/// \"Ctrl\" on Windows/Linux, \"Cmd\" on Mac.\n\tAccel,\n\t/// Left mouse button click (LMB).\n\tMouseLeft,\n\t/// Right mouse button click (RMB).\n\tMouseRight,\n\t/// Middle mouse button click (MMB).\n\tMouseMiddle,\n\t/// Mouse backward navigation button (typically on the side of the mouse).\n\tMouseBack,\n\t/// Mouse forward navigation button (typically on the side of the mouse).\n\tMouseForward,\n\n\t// Fake keys for displaying special labels in the UI\n\t//\n\t/// Not a physical key that can be pressed. May be used so that an actual shortcut bound to `Equal` can separately map this fake \"key\" as an additional binding to display the \"+\" shortcut label in the UI.\n\tFakeKeyPlus,\n\t/// Not a physical key that can be pressed. May be used so that an actual shortcut bound to all ten number keys (0, ..., 9) can separately map this fake \"key\" as an additional binding to display the \"0–9\" shortcut label in the UI.\n\tFakeKeyNumbers,\n\n\t_KeysVariantCount, // This has to be the last element in the enum\n}\n\nimpl fmt::Display for Key {\n\t// TODO: Relevant key labels should be localized when we get around to implementing localization/internationalization\n\tfn fmt(&self, f: &mut fmt::Formatter) -> std::fmt::Result {\n\t\tlet key_name = format!(\"{self:?}\");\n\n\t\t// Writing system keys\n\t\tconst DIGIT_PREFIX: &str = \"Digit\";\n\t\tif key_name.len() == DIGIT_PREFIX.len() + 1 && &key_name[0..DIGIT_PREFIX.len()] == DIGIT_PREFIX {\n\t\t\treturn write!(f, \"{}\", key_name.chars().skip(DIGIT_PREFIX.len()).collect::<String>());\n\t\t}\n\t\tconst KEY_PREFIX: &str = \"Key\";\n\t\tif key_name.len() == KEY_PREFIX.len() + 1 && &key_name[0..KEY_PREFIX.len()] == KEY_PREFIX {\n\t\t\treturn write!(f, \"{}\", key_name.chars().skip(KEY_PREFIX.len()).collect::<String>());\n\t\t}\n\n\t\tlet is_mac = Editor::environment().is_mac();\n\n\t\tlet name = match self {\n\t\t\t// Writing system keys\n\t\t\tSelf::Backquote => \"`\",\n\t\t\tSelf::Backslash => \"\\\\\",\n\t\t\tSelf::BracketLeft => \"[\",\n\t\t\tSelf::BracketRight => \"]\",\n\t\t\tSelf::Comma => \",\",\n\t\t\tSelf::Equal => \"=\",\n\t\t\tSelf::Minus => \"-\",\n\t\t\tSelf::Period => \".\",\n\t\t\tSelf::Quote => \"'\",\n\t\t\tSelf::Semicolon => \";\",\n\t\t\tSelf::Slash => \"/\",\n\n\t\t\t// Functional keys\n\t\t\tSelf::Alt => match is_mac {\n\t\t\t\ttrue => \"⌥\",\n\t\t\t\tfalse => \"Alt\",\n\t\t\t},\n\t\t\tSelf::Meta => match is_mac {\n\t\t\t\ttrue => \"⌘\",\n\t\t\t\tfalse => \"⊞\",\n\t\t\t},\n\t\t\tSelf::Shift => match is_mac {\n\t\t\t\ttrue => \"⇧\",\n\t\t\t\tfalse => \"Shift\",\n\t\t\t},\n\t\t\tSelf::Control => match is_mac {\n\t\t\t\ttrue => \"⌃\",\n\t\t\t\tfalse => \"Ctrl\",\n\t\t\t},\n\t\t\tSelf::Backspace => \"⌫\",\n\n\t\t\t// Control pad keys\n\t\t\tSelf::Delete => \"Del\",\n\t\t\tSelf::PageDown => \"PgDn\",\n\t\t\tSelf::PageUp => \"PgUp\",\n\n\t\t\t// Arrow pad keys\n\t\t\tSelf::ArrowDown => \"↓\",\n\t\t\tSelf::ArrowLeft => \"←\",\n\t\t\tSelf::ArrowRight => \"→\",\n\t\t\tSelf::ArrowUp => \"↑\",\n\n\t\t\t// Numpad keys\n\t\t\tSelf::NumpadAdd => \"Numpad +\",\n\t\t\tSelf::NumpadHash => \"Numpad #\",\n\t\t\tSelf::NumpadMultiply => \"Numpad *\",\n\t\t\tSelf::NumpadParenLeft => \"Numpad (\",\n\t\t\tSelf::NumpadParenRight => \"Numpad )\",\n\n\t\t\t// Function keys\n\t\t\tSelf::Escape => \"Esc\",\n\t\t\tSelf::PrintScreen => \"PrtScr\",\n\n\t\t\t// Other keys that aren't part of the W3C spec\n\t\t\tSelf::Command => \"⌘\",\n\t\t\tSelf::Accel => match is_mac {\n\t\t\t\ttrue => \"⌘\",\n\t\t\t\tfalse => \"Ctrl\",\n\t\t\t},\n\t\t\tSelf::MouseLeft => \"Click\",\n\t\t\tSelf::MouseRight => \"R.Click\",\n\t\t\tSelf::MouseMiddle => \"M.Click\",\n\t\t\tSelf::MouseBack => \"Mouse Back\",\n\t\t\tSelf::MouseForward => \"Mouse Fwd\",\n\n\t\t\t// Fake keys for displaying special labels in the UI\n\t\t\tSelf::FakeKeyPlus => \"+\",\n\t\t\tSelf::FakeKeyNumbers => \"0–9\",\n\n\t\t\t_ => key_name.as_str(),\n\t\t};\n\n\t\twrite!(f, \"{name}\")\n\t}\n}\n\npub const NUMBER_OF_KEYS: usize = Key::_KeysVariantCount as usize - 1;\n\n// =========\n// KeysGroup\n// =========\n\n/// Only `Key`s that exist on a physical keyboard should be used.\n#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]\npub struct KeysGroup(pub Vec<Key>);\n\nimpl fmt::Display for KeysGroup {\n\tfn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {\n\t\tconst JOINER_MARK: &str = \" \";\n\n\t\tlet mut joined = self\n\t\t\t.0\n\t\t\t.iter()\n\t\t\t.map(|key| {\n\t\t\t\tlet key_is_modifier = matches!(*key, Key::Control | Key::Command | Key::Alt | Key::Shift | Key::Meta | Key::Accel);\n\n\t\t\t\tif Editor::environment().is_mac() && key_is_modifier {\n\t\t\t\t\tkey.to_string()\n\t\t\t\t} else {\n\t\t\t\t\tkey.to_string() + JOINER_MARK\n\t\t\t\t}\n\t\t\t})\n\t\t\t.collect::<String>();\n\n\t\t// Cut the joining character off the end, if present\n\t\tif joined.ends_with(JOINER_MARK) {\n\t\t\tjoined.truncate(joined.len() - JOINER_MARK.len());\n\t\t}\n\n\t\twrite!(f, \"{joined}\")\n\t}\n}\n\n// ==========\n// LabeledKey\n// ==========\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]\npub struct LabeledKey {\n\tkey: Key,\n\tlabel: String,\n}\n\nimpl LabeledKey {\n\tpub fn key(&self) -> Key {\n\t\tself.key\n\t}\n}\n\n// ===========\n// MouseMotion\n// ===========\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]\npub enum MouseMotion {\n\tNone,\n\tLmb,\n\tRmb,\n\tMmb,\n\tScrollUp,\n\tScrollDown,\n\tDrag,\n\tLmbDouble,\n\tLmbDrag,\n\tRmbDrag,\n\tRmbDouble,\n\tMmbDrag,\n}\n\n// =======================\n// LabeledKeyOrMouseMotion\n// =======================\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]\n#[serde(untagged)]\npub enum LabeledKeyOrMouseMotion {\n\tKey(LabeledKey),\n\tMouseMotion(MouseMotion),\n}\n\nimpl From<Key> for LabeledKeyOrMouseMotion {\n\tfn from(key: Key) -> Self {\n\t\tmatch key {\n\t\t\tKey::MouseLeft => Self::MouseMotion(MouseMotion::Lmb),\n\t\t\tKey::MouseRight => Self::MouseMotion(MouseMotion::Rmb),\n\t\t\tKey::MouseMiddle => Self::MouseMotion(MouseMotion::Mmb),\n\t\t\t_ => Self::Key(LabeledKey { key, label: key.to_string() }),\n\t\t}\n\t}\n}\n\n// ===============\n// LabeledShortcut\n// ===============\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]\npub struct LabeledShortcut(pub Vec<LabeledKeyOrMouseMotion>);\n\nimpl From<KeysGroup> for LabeledShortcut {\n\tfn from(keys_group: KeysGroup) -> Self {\n\t\tSelf(keys_group.0.into_iter().map(|key| key.into()).collect())\n\t}\n}\n\n// =========\n// BitVector\n// =========\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub struct BitVector<const LENGTH: usize>([StorageType; LENGTH]);\n\nimpl<const LENGTH: usize> BitVector<LENGTH> {\n\t#[inline]\n\tfn convert_index(bitvector_index: usize) -> (usize, StorageType) {\n\t\tlet bit = 1 << (bitvector_index & (STORAGE_SIZE_BITS as StorageType - 1) as usize);\n\t\tlet offset = bitvector_index >> STORAGE_SIZE;\n\t\t(offset, bit)\n\t}\n\n\tpub const fn new() -> Self {\n\t\tSelf([0; LENGTH])\n\t}\n\n\tpub fn set(&mut self, bitvector_index: usize) {\n\t\tlet (offset, bit) = Self::convert_index(bitvector_index);\n\t\tself.0[offset] |= bit;\n\t}\n\n\tpub fn unset(&mut self, bitvector_index: usize) {\n\t\tlet (offset, bit) = Self::convert_index(bitvector_index);\n\t\tself.0[offset] &= !bit;\n\t}\n\n\tpub fn toggle(&mut self, bitvector_index: usize) {\n\t\tlet (offset, bit) = Self::convert_index(bitvector_index);\n\t\tself.0[offset] ^= bit;\n\t}\n\n\tpub fn get(&self, bitvector_index: usize) -> bool {\n\t\tlet (offset, bit) = Self::convert_index(bitvector_index);\n\t\t(self.0[offset] & bit) != 0\n\t}\n\n\tpub fn key(&self, key: Key) -> bool {\n\t\tself.get(key as usize)\n\t}\n\n\tpub fn is_empty(&self) -> bool {\n\t\tlet mut result = 0;\n\n\t\tfor storage in self.0.iter() {\n\t\t\tresult |= storage;\n\t\t}\n\n\t\tresult == 0\n\t}\n\n\tpub fn count_ones(&self) -> u32 {\n\t\tlet mut result = 0;\n\n\t\tfor storage in self.0.iter() {\n\t\t\tresult += storage.count_ones();\n\t\t}\n\n\t\tresult\n\t}\n\n\tpub fn iter(&self) -> impl Iterator<Item = usize> + '_ {\n\t\tBitVectorIter::<LENGTH> { bitvector: self, iter_index: 0 }\n\t}\n}\n\nimpl<const LENGTH: usize> Default for BitVector<LENGTH> {\n\tfn default() -> Self {\n\t\tSelf::new()\n\t}\n}\n\nstruct BitVectorIter<'a, const LENGTH: usize> {\n\tbitvector: &'a BitVector<LENGTH>,\n\titer_index: usize,\n}\n\nimpl<const LENGTH: usize> Iterator for BitVectorIter<'_, LENGTH> {\n\ttype Item = usize;\n\n\tfn next(&mut self) -> Option<Self::Item> {\n\t\twhile self.iter_index < STORAGE_SIZE_BITS * LENGTH {\n\t\t\tlet bit_value = self.bitvector.get(self.iter_index);\n\n\t\t\tself.iter_index += 1;\n\n\t\t\tif bit_value {\n\t\t\t\treturn Some(self.iter_index - 1);\n\t\t\t}\n\t\t}\n\n\t\tNone\n\t}\n}\n\nimpl<const LENGTH: usize> Display for BitVector<LENGTH> {\n\tfn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {\n\t\tfor storage in self.0.iter().rev() {\n\t\t\twrite!(f, \"{storage:0STORAGE_SIZE_BITS$b}\")?;\n\t\t}\n\n\t\tOk(())\n\t}\n}\n\nmacro_rules! bit_ops {\n\t($(($op:ident, $func:ident)),* $(,)?) => {\n\t\t$(\n\t\t\timpl<const LENGTH: usize> $op for BitVector<LENGTH> {\n\t\t\t\ttype Output = Self;\n\t\t\t\tfn $func(self, right: Self) -> Self::Output {\n\t\t\t\t\tlet mut result = Self::new();\n\t\t\t\t\tfor ((left, right), new) in self.0.iter().zip(right.0.iter()).zip(result.0.iter_mut()) {\n\t\t\t\t\t\t*new = $op::$func(left, right);\n\t\t\t\t\t}\n\t\t\t\t\tresult\n\t\t\t\t}\n\t\t\t}\n\n\t\t\timpl<const LENGTH: usize> $op for &BitVector<LENGTH> {\n\t\t\t\ttype Output = BitVector<LENGTH>;\n\t\t\t\tfn $func(self, right: Self) -> Self::Output {\n\t\t\t\t\tlet mut result = BitVector::<LENGTH>::new();\n\t\t\t\t\tfor ((left, right), new) in self.0.iter().zip(right.0.iter()).zip(result.0.iter_mut()) {\n\t\t\t\t\t\t*new = $op::$func(left, right);\n\t\t\t\t\t}\n\t\t\t\t\tresult\n\t\t\t\t}\n\t\t\t}\n\t\t)*\n\t};\n}\nmacro_rules! bit_ops_assign {\n\t($(($op:ident, $func:ident)),* $(,)?) => {\n\t\t$(impl<const LENGTH: usize> $op for BitVector<LENGTH> {\n\t\t\tfn $func(&mut self, right: Self) {\n\t\t\t\tfor (left, right) in self.0.iter_mut().zip(right.0.iter()) {\n\t\t\t\t\t$op::$func(left, right);\n\t\t\t\t}\n\t\t\t}\n\t\t})*\n\t};\n}\n\nbit_ops!((BitAnd, bitand), (BitOr, bitor), (BitXor, bitxor));\nbit_ops_assign!((BitAndAssign, bitand_assign), (BitOrAssign, bitor_assign), (BitXorAssign, bitxor_assign));\n"
  },
  {
    "path": "editor/src/messages/input_mapper/utility_types/input_mouse.rs",
    "content": "use crate::consts::DRAG_THRESHOLD;\nuse crate::messages::prelude::*;\nuse bitflags::bitflags;\nuse glam::DVec2;\nuse std::collections::VecDeque;\nuse std::hash::{Hash, Hasher};\n\n// Origin is top left\npub type DocumentPosition = DVec2;\npub type ViewportPosition = DVec2;\npub type EditorPosition = DVec2;\n\n#[derive(Debug, Copy, Clone, Default, serde::Serialize, serde::Deserialize)]\npub struct ScrollDelta {\n\tpub x: f64,\n\tpub y: f64,\n\tpub z: f64,\n}\n\nimpl PartialEq for ScrollDelta {\n\tfn eq(&self, other: &Self) -> bool {\n\t\tself.x == other.x && self.y == other.y && self.z == other.z\n\t}\n}\n\nimpl Eq for ScrollDelta {}\n\nimpl Hash for ScrollDelta {\n\tfn hash<H: Hasher>(&self, state: &mut H) {\n\t\tlet no_negative_zero = |value: f64| if value == 0. { 0. } else { value };\n\n\t\tno_negative_zero(self.x).to_bits().hash(state);\n\t\tno_negative_zero(self.y).to_bits().hash(state);\n\t\tno_negative_zero(self.z).to_bits().hash(state);\n\t}\n}\n\nimpl ScrollDelta {\n\tpub fn new(x: f64, y: f64, z: f64) -> Self {\n\t\tSelf { x, y, z }\n\t}\n\n\tpub fn as_dvec2(&self) -> DVec2 {\n\t\tDVec2::new(self.x, self.y)\n\t}\n\n\tpub fn scroll_delta(&self) -> f64 {\n\t\tlet (dx, dy) = (self.x, self.y);\n\t\tdy.signum() * (dy * dy + f64::min(dy.abs(), dx.abs()).powi(2)).sqrt()\n\t}\n}\n\n// TODO: Document the difference between this and EditorMouseState\n#[derive(Debug, Copy, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct MouseState {\n\tpub position: ViewportPosition,\n\tpub mouse_keys: MouseKeys,\n\tpub scroll_delta: ScrollDelta,\n}\n\nimpl MouseState {\n\tpub fn finish_transaction(&self, drag_start: DVec2, responses: &mut VecDeque<Message>) {\n\t\tlet drag_too_small = drag_start.distance(self.position) <= DRAG_THRESHOLD;\n\t\tlet response = if drag_too_small { DocumentMessage::AbortTransaction } else { DocumentMessage::EndTransaction };\n\t\tresponses.add(response);\n\t}\n}\n\n// TODO: Document the difference between this and MouseState\n#[derive(Debug, Copy, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct EditorMouseState {\n\tpub editor_position: EditorPosition,\n\tpub mouse_keys: MouseKeys,\n\tpub scroll_delta: ScrollDelta,\n}\n\nimpl EditorMouseState {\n\tpub fn from_keys_and_editor_position(keys: u8, editor_position: EditorPosition) -> Self {\n\t\t// TODO: Some graphic tablets send key codes not mentioned in the spec. In the future we would like to support these as well.\n\t\tlet mouse_keys = MouseKeys::from_bits_truncate(keys);\n\n\t\tSelf {\n\t\t\teditor_position,\n\t\t\tmouse_keys,\n\t\t\tscroll_delta: ScrollDelta::default(),\n\t\t}\n\t}\n\n\tpub fn to_mouse_state(&self, viewport: &ViewportMessageHandler) -> MouseState {\n\t\tMouseState {\n\t\t\tposition: (viewport.logical(self.editor_position) - viewport.offset()).into(),\n\t\t\tmouse_keys: self.mouse_keys,\n\t\t\tscroll_delta: self.scroll_delta,\n\t\t}\n\t}\n}\n\nbitflags! {\n\t/// Based on <https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons#value>.\n\t#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]\n\t#[repr(transparent)]\n\tpub struct MouseKeys: u8 {\n\t\tconst NONE    = 0b0000_0000;\n\t\tconst LEFT    = 0b0000_0001;\n\t\tconst RIGHT   = 0b0000_0010;\n\t\tconst MIDDLE  = 0b0000_0100;\n\t\tconst BACK    = 0b0000_1000;\n\t\tconst FORWARD = 0b0001_0000;\n\t}\n}\n\n#[impl_message(Message, InputMapperMessage, DoubleClick)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, num_enum::TryFromPrimitive)]\n#[repr(u8)]\npub enum MouseButton {\n\tLeft,\n\tRight,\n\tMiddle,\n\tBack,\n\tForward,\n}\n\npub const NUMBER_OF_MOUSE_BUTTONS: usize = 5; // Should be the number of variants in MouseButton\n"
  },
  {
    "path": "editor/src/messages/input_mapper/utility_types/macros.rs",
    "content": "/// Constructs a `KeyStates` bit vector and sets the bit flags for all the given modifier `Key`s.\nmacro_rules! modifiers {\n\t($($m:ident),*) => {{\n\t\t#[allow(unused_mut)]\n\t\tlet mut state = KeyStates::new();\n\t\t$(\n\t\tstate.set(Key::$m as usize);\n\t\t)*\n\t\tstate\n\t}};\n}\n\n/// Builds a slice of `MappingEntry` struct(s) that are used to:\n/// - ...dispatch the given `action_dispatch` as an output `Message` if its discriminant is a currently available action\n/// - ...when the `InputMapperMessage` enum variant, as specified at the start and followed by a semicolon, is received\n/// - ...while the optional `modifiers` being pressed.\n///\n/// Syntax:\n/// ```rs\n/// entry_for_layout!(Key; modifiers?: Key[], refresh_keys?: Key[], action_dispatch: Message)\n/// ```\n///\n/// The actions system controls which actions are currently available. Those are provided by the different message handlers based on the current application state and context.\n/// Each handler adds or removes actions in the form of message discriminants. Here, we tie an input condition (such as a hotkey) to an action's full message.\n/// When an action is currently available, and the user enters that input, the action's message is dispatched on the message bus.\nmacro_rules! entry {\n\t// Pattern with canonical parameter\n\t(\n\t\t$input:expr_2021;\n\t\t$(modifiers=[$($modifier:ident),*],)?\n\t\t$(refresh_keys=[$($refresh:ident),* $(,)?],)?\n\t\tcanonical,\n\t\t$(disabled=$disabled:expr,)?\n\t\taction_dispatch=$action_dispatch:expr_2021$(,)?\n\t) => {\n\t\tentry!(\n\t\t\t$input;\n\t\t\t$($($modifier),*)?;\n\t\t\t$($($refresh),*)?;\n\t\t\t$action_dispatch;\n\t\t\ttrue;\n\t\t\tfalse $( || $disabled )?\n\t\t)\n\t};\n\n\t// Pattern without canonical parameter\n\t(\n\t\t$input:expr_2021;\n\t\t$(modifiers=[$($modifier:ident),*],)?\n\t\t$(refresh_keys=[$($refresh:ident),* $(,)?],)?\n\t\t$(disabled=$disabled:expr,)?\n\t\taction_dispatch=$action_dispatch:expr_2021$(,)?\n\t) => {\n\t\tentry!(\n\t\t\t$input;\n\t\t\t$($($modifier),*)?;\n\t\t\t$($($refresh),*)?;\n\t\t\t$action_dispatch;\n\t\t\tfalse;\n\t\t\tfalse $( || $disabled )?\n\t\t)\n\t};\n\n\t// Implementation macro to avoid code duplication\n\t($input:expr; $($modifier:ident),*; $($refresh:ident),*; $action_dispatch:expr; $canonical:expr; $disabled:expr) => {\n\t\t&[&[\n\t\t\t// Cause the `action_dispatch` message to be sent when the specified input occurs.\n\t\t\tMappingEntry {\n\t\t\t\taction: $action_dispatch.into(),\n\t\t\t\tinput: $input,\n\t\t\t\tmodifiers: modifiers!($($modifier),*),\n\t\t\t\tcanonical: $canonical,\n\t\t\t\tdisabled: $disabled,\n\t\t\t},\n\n\t\t\t$(\n\t\t\tMappingEntry {\n\t\t\t\taction: $action_dispatch.into(),\n\t\t\t\tinput: InputMapperMessage::KeyDown(Key::$refresh),\n\t\t\t\tmodifiers: modifiers!(),\n\t\t\t\tcanonical: $canonical,\n\t\t\t\tdisabled: $disabled,\n\t\t\t},\n\t\t\tMappingEntry {\n\t\t\t\taction: $action_dispatch.into(),\n\t\t\t\tinput: InputMapperMessage::KeyUp(Key::$refresh),\n\t\t\t\tmodifiers: modifiers!(),\n\t\t\t\tcanonical: $canonical,\n\t\t\t\tdisabled: $disabled,\n\t\t\t},\n\t\t\tMappingEntry {\n\t\t\t\taction: $action_dispatch.into(),\n\t\t\t\tinput: InputMapperMessage::KeyDownNoRepeat(Key::$refresh),\n\t\t\t\tmodifiers: modifiers!(),\n\t\t\t\tcanonical: $canonical,\n\t\t\t\tdisabled: $disabled,\n\t\t\t},\n\t\t\tMappingEntry {\n\t\t\t\taction: $action_dispatch.into(),\n\t\t\t\tinput: InputMapperMessage::KeyUpNoRepeat(Key::$refresh),\n\t\t\t\tmodifiers: modifiers!(),\n\t\t\t\tcanonical: $canonical,\n\t\t\t\tdisabled: $disabled,\n\t\t\t},\n\t\t\t)*\n\t\t]]\n\t};\n}\n\n/// Constructs a `KeyMappingEntries` list for each input type and inserts every given entry into the list corresponding to its input type.\n/// Returns a tuple of `KeyMappingEntries` in the order:\n/// ```rs\n/// (key_up, key_down, double_click, wheel_scroll, pointer_move)\n/// ```\nmacro_rules! mapping {\n\t[$($entry:expr_2021),* $(,)?] => {{\n\t\tlet mut key_up = KeyMappingEntries::key_array();\n\t\tlet mut key_down = KeyMappingEntries::key_array();\n\t\tlet mut key_up_no_repeat = KeyMappingEntries::key_array();\n\t\tlet mut key_down_no_repeat = KeyMappingEntries::key_array();\n\t\tlet mut double_click = KeyMappingEntries::mouse_buttons_arrays();\n\t\tlet mut wheel_scroll = KeyMappingEntries::new();\n\t\tlet mut pointer_move = KeyMappingEntries::new();\n\t\tlet mut pointer_shake = KeyMappingEntries::new();\n\n\t\t$(\n\t\t// Each of the many entry slices, one specified per action\n\t\tfor entry_slice in $entry {\n\t\t\t// Each entry in the slice (usually just one, except when `refresh_keys` adds additional key entries)\n\t\t\tfor entry in entry_slice.into_iter() {\n\t\t\t\tif entry.disabled {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tlet corresponding_list = match entry.input {\n\t\t\t\t\tInputMapperMessage::KeyDown(key) => &mut key_down[key as usize],\n\t\t\t\t\tInputMapperMessage::KeyUp(key) => &mut key_up[key as usize],\n\t\t\t\t\tInputMapperMessage::KeyDownNoRepeat(key) => &mut key_down_no_repeat[key as usize],\n\t\t\t\t\tInputMapperMessage::KeyUpNoRepeat(key) => &mut key_up_no_repeat[key as usize],\n\t\t\t\t\tInputMapperMessage::DoubleClick(key) => &mut double_click[key as usize],\n\t\t\t\t\tInputMapperMessage::WheelScroll => &mut wheel_scroll,\n\t\t\t\t\tInputMapperMessage::PointerMove => &mut pointer_move,\n\t\t\t\t\tInputMapperMessage::PointerShake => &mut pointer_shake,\n\t\t\t\t};\n\t\t\t\t// Push each entry to the corresponding `KeyMappingEntries` list for its input type\n\t\t\t\tcorresponding_list.push(entry.clone());\n\t\t\t}\n\t\t}\n\t\t)*\n\n\t\t(key_up, key_down, key_up_no_repeat, key_down_no_repeat, double_click, wheel_scroll, pointer_move, pointer_shake)\n\t}};\n}\n\n/// Constructs an `ActionShortcut` macro with a certain `Action` variant, conveniently wrapped in `Some()`.\nmacro_rules! action_shortcut {\n\t($action:expr_2021) => {\n\t\tSome(crate::messages::input_mapper::utility_types::misc::ActionShortcut::Action($action.into()))\n\t};\n}\n\nmacro_rules! action_shortcut_manual {\n\t($($keys:expr),*) => {\n\t\tSome(crate::messages::input_mapper::utility_types::misc::ActionShortcut::Shortcut(\n\t\t\tcrate::messages::input_mapper::utility_types::input_keyboard::LabeledShortcut(vec![$($keys.into()),*]).into(),\n\t\t))\n\t};\n}\n\npub(crate) use action_shortcut;\npub(crate) use action_shortcut_manual;\npub(crate) use entry;\npub(crate) use mapping;\npub(crate) use modifiers;\n"
  },
  {
    "path": "editor/src/messages/input_mapper/utility_types/misc.rs",
    "content": "use super::input_keyboard::{KeysGroup, LabeledShortcut, all_required_modifiers_pressed};\nuse crate::messages::input_mapper::key_mapping::MappingVariant;\nuse crate::messages::input_mapper::utility_types::input_keyboard::{KeyStates, NUMBER_OF_KEYS};\nuse crate::messages::input_mapper::utility_types::input_mouse::NUMBER_OF_MOUSE_BUTTONS;\nuse crate::messages::prelude::*;\nuse core::time::Duration;\n\n#[derive(Debug, Clone)]\npub struct Mapping {\n\tpub key_up: [KeyMappingEntries; NUMBER_OF_KEYS],\n\tpub key_down: [KeyMappingEntries; NUMBER_OF_KEYS],\n\tpub key_up_no_repeat: [KeyMappingEntries; NUMBER_OF_KEYS],\n\tpub key_down_no_repeat: [KeyMappingEntries; NUMBER_OF_KEYS],\n\tpub double_click: [KeyMappingEntries; NUMBER_OF_MOUSE_BUTTONS],\n\tpub wheel_scroll: KeyMappingEntries,\n\tpub pointer_move: KeyMappingEntries,\n\tpub pointer_shake: KeyMappingEntries,\n}\n\nimpl Default for Mapping {\n\tfn default() -> Self {\n\t\tMappingVariant::Default.into()\n\t}\n}\n\nimpl Mapping {\n\tpub fn match_input_message(&self, message: InputMapperMessage, keyboard_state: &KeyStates, actions: ActionList) -> Option<Message> {\n\t\tlet list = self.associated_entries(&message);\n\t\tlist.match_mapping(keyboard_state, actions)\n\t}\n\n\tfn associated_entries(&self, message: &InputMapperMessage) -> &KeyMappingEntries {\n\t\tmatch message {\n\t\t\tInputMapperMessage::KeyDown(key) => &self.key_down[*key as usize],\n\t\t\tInputMapperMessage::KeyUp(key) => &self.key_up[*key as usize],\n\t\t\tInputMapperMessage::KeyDownNoRepeat(key) => &self.key_down_no_repeat[*key as usize],\n\t\t\tInputMapperMessage::KeyUpNoRepeat(key) => &self.key_up_no_repeat[*key as usize],\n\t\t\tInputMapperMessage::DoubleClick(key) => &self.double_click[*key as usize],\n\t\t\tInputMapperMessage::WheelScroll => &self.wheel_scroll,\n\t\t\tInputMapperMessage::PointerMove => &self.pointer_move,\n\t\t\tInputMapperMessage::PointerShake => &self.pointer_shake,\n\t\t}\n\t}\n}\n\n#[derive(Debug, Clone)]\npub struct KeyMappingEntries(pub Vec<MappingEntry>);\n\nimpl KeyMappingEntries {\n\tpub fn match_mapping(&self, keyboard_state: &KeyStates, actions: ActionList) -> Option<Message> {\n\t\tfor mapping in self.0.iter() {\n\t\t\t// Skip this entry if any of the required modifiers are missing\n\t\t\tif all_required_modifiers_pressed(keyboard_state, &mapping.modifiers) {\n\t\t\t\t// Search for the action in the list of available actions to see if it's currently available to activate\n\t\t\t\tlet matching_action_found = actions.iter().flatten().any(|action| mapping.action.to_discriminant() == *action);\n\t\t\t\tif matching_action_found {\n\t\t\t\t\treturn Some(mapping.action.clone());\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tNone\n\t}\n\n\tpub fn push(&mut self, entry: MappingEntry) {\n\t\tself.0.push(entry);\n\t}\n\n\tpub fn remove(&mut self, target_entry: &MappingEntry) {\n\t\tself.0.retain(|entry| entry != target_entry);\n\t}\n\n\tpub const fn new() -> Self {\n\t\tSelf(Vec::new())\n\t}\n\n\tpub fn key_array() -> [Self; NUMBER_OF_KEYS] {\n\t\tconst DEFAULT: KeyMappingEntries = KeyMappingEntries::new();\n\t\t[DEFAULT; NUMBER_OF_KEYS]\n\t}\n\n\tpub fn mouse_buttons_arrays() -> [Self; NUMBER_OF_MOUSE_BUTTONS] {\n\t\tconst DEFAULT: KeyMappingEntries = KeyMappingEntries::new();\n\t\t[DEFAULT; NUMBER_OF_MOUSE_BUTTONS]\n\t}\n}\n\nimpl Default for KeyMappingEntries {\n\tfn default() -> Self {\n\t\tSelf::new()\n\t}\n}\n\n#[derive(PartialEq, Clone, Debug)]\npub struct MappingEntry {\n\t/// Serves two purposes:\n\t/// - This is the message that gets dispatched when the hotkey is matched\n\t/// - This message's discriminant is the action; it must be a currently active action to be considered as a shortcut\n\tpub action: Message,\n\t/// The user input event from an input device which this input mapping matches on\n\tpub input: InputMapperMessage,\n\t/// Any additional keys that must be also pressed for this input mapping to match\n\tpub modifiers: KeyStates,\n\t/// True indicates that this takes priority as the labeled hotkey shown in UI menus and tooltips instead of an alternate binding for the same action\n\tpub canonical: bool,\n\t/// Whether this mapping is disabled\n\tpub disabled: bool,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)]\npub enum ActionShortcut {\n\t#[serde(skip)]\n\tAction(MessageDiscriminant),\n\t#[serde(rename = \"shortcut\")]\n\tShortcut(LabeledShortcut),\n}\n\nimpl ActionShortcut {\n\tpub fn realize_shortcut(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Option<KeysGroup>) {\n\t\tif let Self::Action(action) = self {\n\t\t\tif let Some(keys) = action_input_mapping(action) {\n\t\t\t\t*self = Self::Shortcut(keys.into());\n\t\t\t} else {\n\t\t\t\t*self = Self::Shortcut(KeysGroup::default().into());\n\t\t\t}\n\t\t}\n\t}\n}\n\n#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct FrameTimeInfo {\n\ttimestamp: Duration,\n\tprev_timestamp: Option<Duration>,\n}\n\nimpl FrameTimeInfo {\n\tpub fn frame_duration(&self) -> Option<Duration> {\n\t\tself.prev_timestamp.map(|prev| self.timestamp - prev)\n\t}\n\n\tpub fn advance_timestamp(&mut self, next_timestamp: Duration) {\n\t\tdebug_assert!(next_timestamp >= self.timestamp);\n\n\t\tself.prev_timestamp = Some(self.timestamp);\n\t\tself.timestamp = next_timestamp;\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/input_mapper/utility_types/mod.rs",
    "content": "pub mod input_keyboard;\npub mod input_mouse;\npub mod macros;\npub mod misc;\n"
  },
  {
    "path": "editor/src/messages/input_preprocessor/input_preprocessor_message.rs",
    "content": "use crate::messages::input_mapper::utility_types::input_keyboard::{Key, ModifierKeys};\nuse crate::messages::input_mapper::utility_types::input_mouse::EditorMouseState;\nuse crate::messages::prelude::*;\n\n#[impl_message(Message, InputPreprocessor)]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum InputPreprocessorMessage {\n\tDoubleClick { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },\n\tKeyDown { key: Key, key_repeat: bool, modifier_keys: ModifierKeys },\n\tKeyUp { key: Key, key_repeat: bool, modifier_keys: ModifierKeys },\n\tPointerDown { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },\n\tPointerMove { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },\n\tPointerUp { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },\n\tPointerShake { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },\n\tCurrentTime { timestamp: u64 },\n\tWheelScroll { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },\n}\n"
  },
  {
    "path": "editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs",
    "content": "use crate::application::Editor;\nuse crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeyStates, ModifierKeys};\nuse crate::messages::input_mapper::utility_types::input_mouse::{MouseButton, MouseKeys, MouseState};\nuse crate::messages::input_mapper::utility_types::misc::FrameTimeInfo;\nuse crate::messages::prelude::*;\nuse std::time::Duration;\n\n#[derive(ExtractField)]\npub struct InputPreprocessorMessageContext<'a> {\n\tpub viewport: &'a ViewportMessageHandler,\n}\n\n#[derive(Debug, Default, ExtractField)]\npub struct InputPreprocessorMessageHandler {\n\tpub frame_time: FrameTimeInfo,\n\tpub time: u64,\n\tpub keyboard: KeyStates,\n\tpub mouse: MouseState,\n}\n\n#[message_handler_data]\nimpl<'a> MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageContext<'a>> for InputPreprocessorMessageHandler {\n\tfn process_message(&mut self, message: InputPreprocessorMessage, responses: &mut VecDeque<Message>, context: InputPreprocessorMessageContext<'a>) {\n\t\tlet InputPreprocessorMessageContext { viewport } = context;\n\n\t\tmatch message {\n\t\t\tInputPreprocessorMessage::DoubleClick { editor_mouse_state, modifier_keys } => {\n\t\t\t\tself.update_states_of_modifier_keys(modifier_keys, responses);\n\n\t\t\t\tlet mouse_state = editor_mouse_state.to_mouse_state(viewport);\n\t\t\t\tself.mouse.position = mouse_state.position;\n\n\t\t\t\tfor key in mouse_state.mouse_keys {\n\t\t\t\t\tresponses.add(InputMapperMessage::DoubleClick(match key {\n\t\t\t\t\t\tMouseKeys::LEFT => MouseButton::Left,\n\t\t\t\t\t\tMouseKeys::RIGHT => MouseButton::Right,\n\t\t\t\t\t\tMouseKeys::MIDDLE => MouseButton::Middle,\n\t\t\t\t\t\tMouseKeys::BACK => MouseButton::Back,\n\t\t\t\t\t\tMouseKeys::FORWARD => MouseButton::Forward,\n\t\t\t\t\t\t_ => unimplemented!(),\n\t\t\t\t\t}));\n\t\t\t\t}\n\t\t\t}\n\t\t\tInputPreprocessorMessage::KeyDown { key, key_repeat, modifier_keys } => {\n\t\t\t\tself.update_states_of_modifier_keys(modifier_keys, responses);\n\t\t\t\tself.keyboard.set(key as usize);\n\t\t\t\tif !key_repeat {\n\t\t\t\t\tresponses.add(InputMapperMessage::KeyDownNoRepeat(key));\n\t\t\t\t}\n\t\t\t\tresponses.add(InputMapperMessage::KeyDown(key));\n\t\t\t}\n\t\t\tInputPreprocessorMessage::KeyUp { key, key_repeat, modifier_keys } => {\n\t\t\t\tself.update_states_of_modifier_keys(modifier_keys, responses);\n\t\t\t\tself.keyboard.unset(key as usize);\n\t\t\t\tif !key_repeat {\n\t\t\t\t\tresponses.add(InputMapperMessage::KeyUpNoRepeat(key));\n\t\t\t\t}\n\t\t\t\tresponses.add(InputMapperMessage::KeyUp(key));\n\t\t\t}\n\t\t\tInputPreprocessorMessage::PointerDown { editor_mouse_state, modifier_keys } => {\n\t\t\t\tself.update_states_of_modifier_keys(modifier_keys, responses);\n\n\t\t\t\tlet mouse_state = editor_mouse_state.to_mouse_state(viewport);\n\t\t\t\tself.mouse.position = mouse_state.position;\n\n\t\t\t\tself.translate_mouse_event(mouse_state, true, responses);\n\t\t\t}\n\t\t\tInputPreprocessorMessage::PointerMove { editor_mouse_state, modifier_keys } => {\n\t\t\t\tself.update_states_of_modifier_keys(modifier_keys, responses);\n\n\t\t\t\tlet mouse_state = editor_mouse_state.to_mouse_state(viewport);\n\t\t\t\tself.mouse.position = mouse_state.position;\n\n\t\t\t\tresponses.add(InputMapperMessage::PointerMove);\n\n\t\t\t\t// While any pointer button is already down, additional button down events are not reported, but they are sent as `pointermove` events\n\t\t\t\tself.translate_mouse_event(mouse_state, false, responses);\n\t\t\t}\n\t\t\tInputPreprocessorMessage::PointerUp { editor_mouse_state, modifier_keys } => {\n\t\t\t\tself.update_states_of_modifier_keys(modifier_keys, responses);\n\n\t\t\t\tlet mouse_state = editor_mouse_state.to_mouse_state(viewport);\n\t\t\t\tself.mouse.position = mouse_state.position;\n\n\t\t\t\tself.translate_mouse_event(mouse_state, false, responses);\n\t\t\t}\n\t\t\tInputPreprocessorMessage::PointerShake { editor_mouse_state, modifier_keys } => {\n\t\t\t\tself.update_states_of_modifier_keys(modifier_keys, responses);\n\n\t\t\t\tlet mouse_state = editor_mouse_state.to_mouse_state(viewport);\n\t\t\t\tself.mouse.position = mouse_state.position;\n\n\t\t\t\tresponses.add(InputMapperMessage::PointerShake);\n\t\t\t}\n\t\t\tInputPreprocessorMessage::CurrentTime { timestamp } => {\n\t\t\t\tresponses.add(AnimationMessage::SetTime { time: timestamp as f64 });\n\t\t\t\tself.time = timestamp;\n\t\t\t\tself.frame_time.advance_timestamp(Duration::from_millis(timestamp));\n\t\t\t}\n\t\t\tInputPreprocessorMessage::WheelScroll { editor_mouse_state, modifier_keys } => {\n\t\t\t\tself.update_states_of_modifier_keys(modifier_keys, responses);\n\n\t\t\t\tlet mouse_state = editor_mouse_state.to_mouse_state(viewport);\n\t\t\t\tself.mouse.position = mouse_state.position;\n\t\t\t\tself.mouse.scroll_delta = mouse_state.scroll_delta;\n\n\t\t\t\tresponses.add(InputMapperMessage::WheelScroll);\n\t\t\t}\n\t\t};\n\t}\n\n\t// Clean user input and if possible reconstruct it.\n\t// Store the changes in the keyboard if it is a key event.\n\t// Transform canvas coordinates to document coordinates.\n\tadvertise_actions!();\n}\n\nimpl InputPreprocessorMessageHandler {\n\tfn translate_mouse_event(&mut self, mut new_state: MouseState, allow_first_button_down: bool, responses: &mut VecDeque<Message>) {\n\t\tlet click_mappings = [\n\t\t\t(MouseKeys::LEFT, Key::MouseLeft),\n\t\t\t(MouseKeys::RIGHT, Key::MouseRight),\n\t\t\t(MouseKeys::MIDDLE, Key::MouseMiddle),\n\t\t\t(MouseKeys::BACK, Key::MouseBack),\n\t\t\t(MouseKeys::FORWARD, Key::MouseForward),\n\t\t];\n\n\t\tfor (bit_flag, key) in click_mappings {\n\t\t\t// Calculate the intersection between the two key states\n\t\t\tlet old_down = self.mouse.mouse_keys & bit_flag == bit_flag;\n\t\t\tlet new_down = new_state.mouse_keys & bit_flag == bit_flag;\n\t\t\tif !old_down && new_down {\n\t\t\t\tif allow_first_button_down || self.mouse.mouse_keys != MouseKeys::empty() {\n\t\t\t\t\tself.keyboard.set(key as usize);\n\t\t\t\t\tresponses.add(InputMapperMessage::KeyDown(key));\n\t\t\t\t} else {\n\t\t\t\t\t// Required to stop a keyup being emitted for a keydown outside canvas\n\t\t\t\t\tnew_state.mouse_keys ^= bit_flag;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif old_down && !new_down {\n\t\t\t\tself.keyboard.unset(key as usize);\n\t\t\t\tresponses.add(InputMapperMessage::KeyUp(key));\n\t\t\t}\n\t\t}\n\n\t\tself.mouse = new_state;\n\t}\n\n\tfn update_states_of_modifier_keys(&mut self, pressed_modifier_keys: ModifierKeys, responses: &mut VecDeque<Message>) {\n\t\tlet is_key_pressed = |key_to_check: ModifierKeys| pressed_modifier_keys.contains(key_to_check);\n\n\t\t// Update the state of the concrete modifier keys based on the source state\n\t\tself.update_modifier_key(Key::Shift, is_key_pressed(ModifierKeys::SHIFT), responses);\n\t\tself.update_modifier_key(Key::Alt, is_key_pressed(ModifierKeys::ALT), responses);\n\t\tself.update_modifier_key(Key::Control, is_key_pressed(ModifierKeys::CONTROL), responses);\n\n\t\t// Update the state of either the concrete Meta or the Command keys based on which one is applicable for this platform\n\t\tlet meta_or_command = match Editor::environment().is_mac() {\n\t\t\ttrue => Key::Command,\n\t\t\tfalse => Key::Meta,\n\t\t};\n\t\tself.update_modifier_key(meta_or_command, is_key_pressed(ModifierKeys::META_OR_COMMAND), responses);\n\n\t\t// Update the state of the virtual Accel key (the primary accelerator key) based on the source state of the Control or Command key, whichever is relevant on this platform\n\t\tlet accel_virtual_key_state = match Editor::environment().is_mac() {\n\t\t\ttrue => is_key_pressed(ModifierKeys::META_OR_COMMAND),\n\t\t\tfalse => is_key_pressed(ModifierKeys::CONTROL),\n\t\t};\n\t\tself.update_modifier_key(Key::Accel, accel_virtual_key_state, responses);\n\t}\n\n\tfn update_modifier_key(&mut self, key: Key, key_is_down: bool, responses: &mut VecDeque<Message>) {\n\t\tlet key_was_down = self.keyboard.get(key as usize);\n\n\t\tif key_was_down && !key_is_down {\n\t\t\tself.keyboard.unset(key as usize);\n\t\t\tresponses.add(InputMapperMessage::KeyUp(key));\n\t\t} else if !key_was_down && key_is_down {\n\t\t\tself.keyboard.set(key as usize);\n\t\t\tresponses.add(InputMapperMessage::KeyDown(key));\n\t\t}\n\t}\n}\n\n#[cfg(test)]\nmod test {\n\tuse crate::messages::input_mapper::utility_types::input_keyboard::{Key, ModifierKeys};\n\tuse crate::messages::input_mapper::utility_types::input_mouse::{EditorMouseState, MouseKeys, ScrollDelta};\n\tuse crate::messages::prelude::*;\n\n\t#[test]\n\tfn process_action_mouse_move_handle_modifier_keys() {\n\t\tlet mut input_preprocessor = InputPreprocessorMessageHandler::default();\n\n\t\tlet editor_mouse_state = EditorMouseState {\n\t\t\teditor_position: (4., 809.).into(),\n\t\t\tmouse_keys: MouseKeys::default(),\n\t\t\tscroll_delta: ScrollDelta::default(),\n\t\t};\n\t\tlet modifier_keys = ModifierKeys::ALT;\n\t\tlet message = InputPreprocessorMessage::PointerMove { editor_mouse_state, modifier_keys };\n\n\t\tlet mut responses = VecDeque::new();\n\n\t\tlet context = InputPreprocessorMessageContext {\n\t\t\tviewport: &ViewportMessageHandler::default(),\n\t\t};\n\t\tinput_preprocessor.process_message(message, &mut responses, context);\n\n\t\tassert!(input_preprocessor.keyboard.get(Key::Alt as usize));\n\t\tassert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::Alt).into()));\n\t}\n\n\t#[test]\n\tfn process_action_mouse_down_handle_modifier_keys() {\n\t\tlet mut input_preprocessor = InputPreprocessorMessageHandler::default();\n\n\t\tlet editor_mouse_state = EditorMouseState::default();\n\t\tlet modifier_keys = ModifierKeys::CONTROL;\n\t\tlet message = InputPreprocessorMessage::PointerDown { editor_mouse_state, modifier_keys };\n\n\t\tlet mut responses = VecDeque::new();\n\n\t\tlet context = InputPreprocessorMessageContext {\n\t\t\tviewport: &ViewportMessageHandler::default(),\n\t\t};\n\t\tinput_preprocessor.process_message(message, &mut responses, context);\n\n\t\tassert!(input_preprocessor.keyboard.get(Key::Control as usize));\n\t\tassert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::Control).into()));\n\t}\n\n\t#[test]\n\tfn process_action_mouse_up_handle_modifier_keys() {\n\t\tlet mut input_preprocessor = InputPreprocessorMessageHandler::default();\n\n\t\tlet editor_mouse_state = EditorMouseState::default();\n\t\tlet modifier_keys = ModifierKeys::SHIFT;\n\t\tlet message = InputPreprocessorMessage::PointerUp { editor_mouse_state, modifier_keys };\n\n\t\tlet mut responses = VecDeque::new();\n\n\t\tlet context = InputPreprocessorMessageContext {\n\t\t\tviewport: &ViewportMessageHandler::default(),\n\t\t};\n\t\tinput_preprocessor.process_message(message, &mut responses, context);\n\n\t\tassert!(input_preprocessor.keyboard.get(Key::Shift as usize));\n\t\tassert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::Shift).into()));\n\t}\n\n\t#[test]\n\tfn process_action_key_down_handle_modifier_keys() {\n\t\tlet mut input_preprocessor = InputPreprocessorMessageHandler::default();\n\t\tinput_preprocessor.keyboard.set(Key::Control as usize);\n\n\t\tlet key = Key::KeyA;\n\t\tlet key_repeat = false;\n\t\tlet modifier_keys = ModifierKeys::empty();\n\t\tlet message = InputPreprocessorMessage::KeyDown { key, key_repeat, modifier_keys };\n\n\t\tlet mut responses = VecDeque::new();\n\n\t\tlet context = InputPreprocessorMessageContext {\n\t\t\tviewport: &ViewportMessageHandler::default(),\n\t\t};\n\t\tinput_preprocessor.process_message(message, &mut responses, context);\n\n\t\tassert!(!input_preprocessor.keyboard.get(Key::Control as usize));\n\t\tassert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyUp(Key::Control).into()));\n\t}\n\n\t#[test]\n\tfn process_action_key_up_handle_modifier_keys() {\n\t\tlet mut input_preprocessor = InputPreprocessorMessageHandler::default();\n\n\t\tlet key = Key::KeyS;\n\t\tlet key_repeat = false;\n\t\tlet modifier_keys = ModifierKeys::CONTROL | ModifierKeys::SHIFT;\n\t\tlet message = InputPreprocessorMessage::KeyUp { key, key_repeat, modifier_keys };\n\n\t\tlet mut responses = VecDeque::new();\n\n\t\tlet context = InputPreprocessorMessageContext {\n\t\t\tviewport: &ViewportMessageHandler::default(),\n\t\t};\n\t\tinput_preprocessor.process_message(message, &mut responses, context);\n\n\t\tassert!(input_preprocessor.keyboard.get(Key::Control as usize));\n\t\tassert!(input_preprocessor.keyboard.get(Key::Shift as usize));\n\t\tassert!(responses.contains(&InputMapperMessage::KeyDown(Key::Control).into()));\n\t\tassert!(responses.contains(&InputMapperMessage::KeyDown(Key::Control).into()));\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/input_preprocessor/mod.rs",
    "content": "mod input_preprocessor_message;\nmod input_preprocessor_message_handler;\n\n#[doc(inline)]\npub use input_preprocessor_message::{InputPreprocessorMessage, InputPreprocessorMessageDiscriminant};\n#[doc(inline)]\npub use input_preprocessor_message_handler::{InputPreprocessorMessageContext, InputPreprocessorMessageHandler};\n"
  },
  {
    "path": "editor/src/messages/layout/layout_message.rs",
    "content": "use crate::messages::layout::utility_types::widget_prelude::*;\nuse crate::messages::prelude::*;\n\n#[impl_message(Message, Layout)]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum LayoutMessage {\n\tResendActiveWidget {\n\t\tlayout_target: LayoutTarget,\n\t\twidget_id: WidgetId,\n\t},\n\tResendAllLayouts,\n\tSendLayout {\n\t\tlayout: Layout,\n\t\tlayout_target: LayoutTarget,\n\t},\n\tDestroyLayout {\n\t\tlayout_target: LayoutTarget,\n\t},\n\tWidgetValueCommit {\n\t\tlayout_target: LayoutTarget,\n\t\twidget_id: WidgetId,\n\t\tvalue: serde_json::Value,\n\t},\n\tWidgetValueUpdate {\n\t\tlayout_target: LayoutTarget,\n\t\twidget_id: WidgetId,\n\t\tvalue: serde_json::Value,\n\t},\n}\n"
  },
  {
    "path": "editor/src/messages/layout/layout_message_handler.rs",
    "content": "use crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup;\nuse crate::messages::layout::utility_types::widget_prelude::*;\nuse crate::messages::prelude::*;\nuse graphene_std::vector::style::FillChoice;\nuse serde_json::Value;\nuse std::collections::HashMap;\n\n#[derive(ExtractField)]\npub struct LayoutMessageContext<'a> {\n\tpub action_input_mapping: &'a dyn Fn(&MessageDiscriminant) -> Option<KeysGroup>,\n}\n\n#[derive(Debug, Clone, Default, ExtractField)]\npub struct LayoutMessageHandler {\n\tlayouts: [Layout; LayoutTarget::_LayoutTargetLength as usize],\n}\n\n#[message_handler_data]\nimpl MessageHandler<LayoutMessage, LayoutMessageContext<'_>> for LayoutMessageHandler {\n\tfn process_message(&mut self, message: LayoutMessage, responses: &mut std::collections::VecDeque<Message>, context: LayoutMessageContext) {\n\t\tlet action_input_mapping = &context.action_input_mapping;\n\n\t\tmatch message {\n\t\t\tLayoutMessage::ResendActiveWidget { layout_target, widget_id } => {\n\t\t\t\t// Find the updated diff based on the specified layout target\n\t\t\t\tlet Some(diff) = Self::get_widget_path(&self.layouts[layout_target as usize], widget_id).map(|(widget, widget_path)| {\n\t\t\t\t\t// Create a widget update diff for the relevant id\n\t\t\t\t\tlet new_value = DiffUpdate::Widget(widget.clone());\n\t\t\t\t\tWidgetDiff { widget_path, new_value }\n\t\t\t\t}) else {\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\t// Resend that diff\n\t\t\t\tself.send_diff(vec![diff], layout_target, responses, action_input_mapping);\n\t\t\t}\n\t\t\tLayoutMessage::ResendAllLayouts => {\n\t\t\t\t// Collect non-empty layouts and their indices, then clear the stored copies so diffs compute as full re-sends\n\t\t\t\tlet layouts_to_resend: Vec<_> = self\n\t\t\t\t\t.layouts\n\t\t\t\t\t.iter_mut()\n\t\t\t\t\t.enumerate()\n\t\t\t\t\t.filter(|(_, layout)| !layout.0.is_empty())\n\t\t\t\t\t.map(|(i, layout)| (LayoutTarget::from(i as u8), std::mem::take(layout)))\n\t\t\t\t\t.collect();\n\n\t\t\t\tfor (layout_target, layout) in layouts_to_resend {\n\t\t\t\t\tself.diff_and_send_layout_to_frontend(layout_target, layout, responses, action_input_mapping);\n\t\t\t\t}\n\t\t\t}\n\t\t\tLayoutMessage::SendLayout { layout, layout_target } => {\n\t\t\t\tself.diff_and_send_layout_to_frontend(layout_target, layout, responses, action_input_mapping);\n\t\t\t}\n\t\t\tLayoutMessage::DestroyLayout { layout_target } => {\n\t\t\t\tif let Some(layout) = self.layouts.get_mut(layout_target as usize) {\n\t\t\t\t\t*layout = Layout::default();\n\t\t\t\t}\n\t\t\t}\n\t\t\tLayoutMessage::WidgetValueCommit { layout_target, widget_id, value } => {\n\t\t\t\tself.handle_widget_callback(layout_target, widget_id, value, WidgetValueAction::Commit, responses);\n\t\t\t}\n\t\t\tLayoutMessage::WidgetValueUpdate { layout_target, widget_id, value } => {\n\t\t\t\tself.handle_widget_callback(layout_target, widget_id, value, WidgetValueAction::Update, responses);\n\t\t\t}\n\t\t}\n\t}\n\n\tfn actions(&self) -> ActionList {\n\t\tactions!(LayoutMessageDiscriminant;)\n\t}\n}\n\nimpl LayoutMessageHandler {\n\t/// Get the widget path for the widget with the specified id\n\tfn get_widget_path(widget_layout: &Layout, widget_id: WidgetId) -> Option<(&WidgetInstance, Vec<usize>)> {\n\t\tlet mut stack = widget_layout.0.iter().enumerate().map(|(index, val)| (vec![index], val)).collect::<Vec<_>>();\n\t\twhile let Some((mut widget_path, layout_group)) = stack.pop() {\n\t\t\tmatch layout_group {\n\t\t\t\t// Check if any of the widgets in the current column or row have the correct id\n\t\t\t\tLayoutGroup::Column(WidgetColumn { widgets }) | LayoutGroup::Row(WidgetRow { widgets }) => {\n\t\t\t\t\tfor (index, widget) in widgets.iter().enumerate() {\n\t\t\t\t\t\t// Return if this is the correct ID\n\t\t\t\t\t\tif widget.widget_id == widget_id {\n\t\t\t\t\t\t\twidget_path.push(index);\n\t\t\t\t\t\t\treturn Some((widget, widget_path));\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif let Widget::PopoverButton(popover) = &*widget.widget {\n\t\t\t\t\t\t\tstack.extend(\n\t\t\t\t\t\t\t\tpopover\n\t\t\t\t\t\t\t\t\t.popover_layout\n\t\t\t\t\t\t\t\t\t.0\n\t\t\t\t\t\t\t\t\t.iter()\n\t\t\t\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t\t\t\t.map(|(child, val)| ([widget_path.as_slice(), &[index, child]].concat(), val)),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// A section contains more LayoutGroups which we add to the stack.\n\t\t\t\tLayoutGroup::Section(WidgetSection { layout, .. }) => {\n\t\t\t\t\tstack.extend(layout.0.iter().enumerate().map(|(index, val)| ([widget_path.as_slice(), &[index]].concat(), val)));\n\t\t\t\t}\n\t\t\t\tLayoutGroup::Table(WidgetTable { rows, .. }) => {\n\t\t\t\t\tfor (row_index, row) in rows.iter().enumerate() {\n\t\t\t\t\t\tfor (cell_index, cell) in row.iter().enumerate() {\n\t\t\t\t\t\t\t// Return if this is the correct ID\n\t\t\t\t\t\t\tif cell.widget_id == widget_id {\n\t\t\t\t\t\t\t\twidget_path.push(row_index);\n\t\t\t\t\t\t\t\twidget_path.push(cell_index);\n\t\t\t\t\t\t\t\treturn Some((cell, widget_path));\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif let Widget::PopoverButton(popover) = &*cell.widget {\n\t\t\t\t\t\t\t\tstack.extend(\n\t\t\t\t\t\t\t\t\tpopover\n\t\t\t\t\t\t\t\t\t\t.popover_layout\n\t\t\t\t\t\t\t\t\t\t.0\n\t\t\t\t\t\t\t\t\t\t.iter()\n\t\t\t\t\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t\t\t\t\t.map(|(child, val)| ([widget_path.as_slice(), &[row_index, cell_index, child]].concat(), val)),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tNone\n\t}\n\n\tfn handle_widget_callback(&mut self, layout_target: LayoutTarget, widget_id: WidgetId, value: Value, action: WidgetValueAction, responses: &mut std::collections::VecDeque<Message>) {\n\t\tlet Some(layout) = self.layouts.get_mut(layout_target as usize) else {\n\t\t\twarn!(\"handle_widget_callback was called referencing an invalid layout. `widget_id: {widget_id}`, `layout_target: {layout_target:?}`\",);\n\t\t\treturn;\n\t\t};\n\n\t\tlet Some(widget_instance) = layout.iter_mut().find(|widget| widget.widget_id == widget_id) else {\n\t\t\twarn!(\"handle_widget_callback was called referencing an invalid widget ID, although the layout target was valid. `widget_id: {widget_id}`, `layout_target: {layout_target:?}`\",);\n\t\t\treturn;\n\t\t};\n\n\t\tmatch &mut *widget_instance.widget {\n\t\t\tWidget::BreadcrumbTrailButtons(breadcrumb_trail_buttons) => {\n\t\t\t\tlet callback_message = match action {\n\t\t\t\t\tWidgetValueAction::Commit => (breadcrumb_trail_buttons.on_commit.callback)(&()),\n\t\t\t\t\tWidgetValueAction::Update => {\n\t\t\t\t\t\tlet Some(update_value) = value.as_u64() else {\n\t\t\t\t\t\t\terror!(\"BreadcrumbTrailButtons update was not of type: u64\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t};\n\t\t\t\t\t\t(breadcrumb_trail_buttons.on_update.callback)(&update_value)\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tresponses.add(callback_message);\n\t\t\t}\n\t\t\tWidget::CheckboxInput(checkbox_input) => {\n\t\t\t\tlet callback_message = match action {\n\t\t\t\t\tWidgetValueAction::Commit => (checkbox_input.on_commit.callback)(&()),\n\t\t\t\t\tWidgetValueAction::Update => {\n\t\t\t\t\t\tlet Some(update_value) = value.as_bool() else {\n\t\t\t\t\t\t\terror!(\"CheckboxInput update was not of type: bool\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t};\n\t\t\t\t\t\tcheckbox_input.checked = update_value;\n\t\t\t\t\t\t(checkbox_input.on_update.callback)(checkbox_input)\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tresponses.add(callback_message);\n\t\t\t}\n\t\t\tWidget::ColorInput(color_button) => {\n\t\t\t\tlet callback_message = match action {\n\t\t\t\t\tWidgetValueAction::Commit => (color_button.on_commit.callback)(&()),\n\t\t\t\t\tWidgetValueAction::Update => {\n\t\t\t\t\t\tlet Ok(fill_choice) = serde_json::from_value::<FillChoice>(value) else {\n\t\t\t\t\t\t\twarn!(\"ColorInput update was not able to be parsed as FillChoice: {color_button:?}\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t};\n\t\t\t\t\t\tcolor_button.value = fill_choice;\n\t\t\t\t\t\t(color_button.on_update.callback)(color_button)\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tresponses.add(callback_message);\n\t\t\t}\n\t\t\tWidget::CurveInput(curve_input) => {\n\t\t\t\tlet callback_message = match action {\n\t\t\t\t\tWidgetValueAction::Commit => (curve_input.on_commit.callback)(&()),\n\t\t\t\t\tWidgetValueAction::Update => {\n\t\t\t\t\t\tlet Some(curve) = serde_json::from_value(value).ok() else {\n\t\t\t\t\t\t\terror!(\"CurveInput event data could not be deserialized\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t};\n\t\t\t\t\t\tcurve_input.value = curve;\n\t\t\t\t\t\t(curve_input.on_update.callback)(curve_input)\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tresponses.add(callback_message);\n\t\t\t}\n\t\t\tWidget::DropdownInput(dropdown_input) => {\n\t\t\t\tlet callback_message = match action {\n\t\t\t\t\tWidgetValueAction::Commit => {\n\t\t\t\t\t\tlet Some(update_value) = value.as_u64() else {\n\t\t\t\t\t\t\terror!(\"DropdownInput commit was not of type `u64`, found {value:?}\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t};\n\t\t\t\t\t\tlet Some(entry) = dropdown_input.entries.iter().flatten().nth(update_value as usize) else {\n\t\t\t\t\t\t\terror!(\"DropdownInput commit was not able to find entry for index {update_value}\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t};\n\t\t\t\t\t\t(entry.on_commit.callback)(&())\n\t\t\t\t\t}\n\t\t\t\t\tWidgetValueAction::Update => {\n\t\t\t\t\t\tlet Some(update_value) = value.as_u64() else {\n\t\t\t\t\t\t\terror!(\"DropdownInput update was not of type `u64`, found {value:?}\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t};\n\t\t\t\t\t\tdropdown_input.selected_index = Some(update_value as u32);\n\t\t\t\t\t\tlet Some(entry) = dropdown_input.entries.iter().flatten().nth(update_value as usize) else {\n\t\t\t\t\t\t\terror!(\"DropdownInput update was not able to find entry for index {update_value}\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t};\n\t\t\t\t\t\t(entry.on_update.callback)(&())\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tresponses.add(callback_message);\n\t\t\t}\n\t\t\tWidget::IconButton(icon_button) => {\n\t\t\t\tlet callback_message = match action {\n\t\t\t\t\tWidgetValueAction::Commit => (icon_button.on_commit.callback)(&()),\n\t\t\t\t\tWidgetValueAction::Update => (icon_button.on_update.callback)(icon_button),\n\t\t\t\t};\n\n\t\t\t\tresponses.add(callback_message);\n\t\t\t}\n\t\t\tWidget::ImageButton(image_label) => {\n\t\t\t\tlet callback_message = match action {\n\t\t\t\t\tWidgetValueAction::Commit => (image_label.on_commit.callback)(&()),\n\t\t\t\t\tWidgetValueAction::Update => (image_label.on_update.callback)(&()),\n\t\t\t\t};\n\n\t\t\t\tresponses.add(callback_message);\n\t\t\t}\n\t\t\tWidget::ImageLabel(_) => {}\n\t\t\tWidget::ShortcutLabel(_) => {}\n\t\t\tWidget::IconLabel(_) => {}\n\t\t\tWidget::NodeCatalog(node_type_input) => match action {\n\t\t\t\tWidgetValueAction::Commit => {\n\t\t\t\t\tlet callback_message = (node_type_input.on_commit.callback)(&());\n\t\t\t\t\tresponses.add(callback_message);\n\t\t\t\t}\n\t\t\t\tWidgetValueAction::Update => {\n\t\t\t\t\tlet callback_message = (node_type_input.on_update.callback)(&value.into());\n\t\t\t\t\tresponses.add(callback_message);\n\t\t\t\t}\n\t\t\t},\n\t\t\tWidget::NumberInput(number_input) => match action {\n\t\t\t\tWidgetValueAction::Commit => {\n\t\t\t\t\tlet callback_message = (number_input.on_commit.callback)(&());\n\t\t\t\t\tresponses.add(callback_message);\n\t\t\t\t}\n\t\t\t\tWidgetValueAction::Update => match value {\n\t\t\t\t\tValue::Number(ref num) => {\n\t\t\t\t\t\tlet Some(update_value) = num.as_f64() else {\n\t\t\t\t\t\t\terror!(\"NumberInput update was not of type: f64, found {value:?}\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t};\n\t\t\t\t\t\tnumber_input.value = Some(update_value);\n\t\t\t\t\t\tlet callback_message = (number_input.on_update.callback)(number_input);\n\t\t\t\t\t\tresponses.add(callback_message);\n\t\t\t\t\t}\n\t\t\t\t\t// TODO: This crashes when the cursor is in a text box, such as in the Text node, and the transform node is clicked (https://github.com/GraphiteEditor/Graphite/issues/1761)\n\t\t\t\t\tValue::String(str) => match str.as_str() {\n\t\t\t\t\t\t\"Increment\" => responses.add((number_input.increment_callback_increase.callback)(number_input)),\n\t\t\t\t\t\t\"Decrement\" => responses.add((number_input.increment_callback_decrease.callback)(number_input)),\n\t\t\t\t\t\t_ => panic!(\"Invalid string found when updating `NumberInput`\"),\n\t\t\t\t\t},\n\t\t\t\t\t_ => {}\n\t\t\t\t},\n\t\t\t},\n\t\t\tWidget::ParameterExposeButton(parameter_expose_button) => {\n\t\t\t\tlet callback_message = match action {\n\t\t\t\t\tWidgetValueAction::Commit => (parameter_expose_button.on_commit.callback)(&()),\n\t\t\t\t\tWidgetValueAction::Update => (parameter_expose_button.on_update.callback)(parameter_expose_button),\n\t\t\t\t};\n\n\t\t\t\tresponses.add(callback_message);\n\t\t\t}\n\t\t\tWidget::ReferencePointInput(reference_point_input) => {\n\t\t\t\tlet callback_message = match action {\n\t\t\t\t\tWidgetValueAction::Commit => (reference_point_input.on_commit.callback)(&()),\n\t\t\t\t\tWidgetValueAction::Update => {\n\t\t\t\t\t\tlet Some(update_value) = value.as_str() else {\n\t\t\t\t\t\t\terror!(\"ReferencePointInput update was not of type: u64\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t};\n\t\t\t\t\t\treference_point_input.value = update_value.into();\n\t\t\t\t\t\t(reference_point_input.on_update.callback)(reference_point_input)\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tresponses.add(callback_message);\n\t\t\t}\n\t\t\tWidget::PopoverButton(_) => {}\n\t\t\tWidget::RadioInput(radio_input) => {\n\t\t\t\tlet Some(update_value) = value.as_u64() else {\n\t\t\t\t\terror!(\"RadioInput update was not of type: u64\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tradio_input.selected_index = Some(update_value as u32);\n\t\t\t\tlet callback_message = match action {\n\t\t\t\t\tWidgetValueAction::Commit => (radio_input.entries[update_value as usize].on_commit.callback)(&()),\n\t\t\t\t\tWidgetValueAction::Update => (radio_input.entries[update_value as usize].on_update.callback)(&()),\n\t\t\t\t};\n\n\t\t\t\tresponses.add(callback_message);\n\t\t\t}\n\t\t\tWidget::Separator(_) => {}\n\t\t\tWidget::TextAreaInput(text_area_input) => {\n\t\t\t\tlet callback_message = match action {\n\t\t\t\t\tWidgetValueAction::Commit => (text_area_input.on_commit.callback)(&()),\n\t\t\t\t\tWidgetValueAction::Update => {\n\t\t\t\t\t\tlet Some(update_value) = value.as_str() else {\n\t\t\t\t\t\t\terror!(\"TextAreaInput update was not of type: string\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t};\n\t\t\t\t\t\ttext_area_input.value = update_value.into();\n\t\t\t\t\t\t(text_area_input.on_update.callback)(text_area_input)\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tresponses.add(callback_message);\n\t\t\t}\n\t\t\tWidget::TextButton(text_button) => {\n\t\t\t\tlet callback_message = match action {\n\t\t\t\t\tWidgetValueAction::Commit => (text_button.on_commit.callback)(&()),\n\t\t\t\t\tWidgetValueAction::Update => {\n\t\t\t\t\t\tlet Some(value_path) = value.as_array() else {\n\t\t\t\t\t\t\terror!(\"TextButton update was not of type: array\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\t// Process the text button click, since no menu is involved if we're given an empty array.\n\t\t\t\t\t\tif value_path.is_empty() {\n\t\t\t\t\t\t\t(text_button.on_update.callback)(text_button)\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Process the text button's menu list entry click, since we have a path to the value of the contained menu entry.\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tlet mut current_submenu = &text_button.menu_list_children;\n\t\t\t\t\t\t\tlet mut final_entry: Option<&MenuListEntry> = None;\n\n\t\t\t\t\t\t\t// Loop through all menu entry value strings in the path until we reach the final entry (which we store).\n\t\t\t\t\t\t\t// Otherwise we exit early if we can't traverse the full path.\n\t\t\t\t\t\t\tfor value in value_path.iter().filter_map(|v| v.as_str().map(|s| s.to_string())) {\n\t\t\t\t\t\t\t\tlet Some(next_entry) = current_submenu.iter().flatten().find(|e| e.value == value) else { return };\n\n\t\t\t\t\t\t\t\tcurrent_submenu = &next_entry.children;\n\t\t\t\t\t\t\t\tfinal_entry = Some(next_entry);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// If we've reached here without returning early, we have a final entry in the path and we should now execute its callback.\n\t\t\t\t\t\t\t(final_entry.unwrap().on_commit.callback)(&())\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tresponses.add(callback_message);\n\t\t\t}\n\t\t\tWidget::TextInput(text_input) => {\n\t\t\t\tlet callback_message = match action {\n\t\t\t\t\tWidgetValueAction::Commit => (text_input.on_commit.callback)(&()),\n\t\t\t\t\tWidgetValueAction::Update => {\n\t\t\t\t\t\tlet Some(update_value) = value.as_str() else {\n\t\t\t\t\t\t\terror!(\"TextInput update was not of type: string\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t};\n\t\t\t\t\t\ttext_input.value = update_value.into();\n\t\t\t\t\t\t(text_input.on_update.callback)(text_input)\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tresponses.add(callback_message);\n\t\t\t}\n\t\t\tWidget::TextLabel(_) => {}\n\t\t\tWidget::WorkingColorsInput(_) => {}\n\t\t};\n\t}\n\n\t/// Diff the update and send to the frontend where necessary\n\tfn diff_and_send_layout_to_frontend(\n\t\t&mut self,\n\t\tlayout_target: LayoutTarget,\n\t\tmut new_layout: Layout,\n\t\tresponses: &mut VecDeque<Message>,\n\t\taction_input_mapping: &impl Fn(&MessageDiscriminant) -> Option<KeysGroup>,\n\t) {\n\t\t// Step 1: Collect CheckboxId mappings from new layout\n\t\tlet mut checkbox_map = HashMap::new();\n\t\tnew_layout.collect_checkbox_ids(layout_target, &mut Vec::new(), &mut checkbox_map);\n\n\t\t// Step 2: Replace all IDs in new layout with deterministic ones\n\t\tnew_layout.replace_widget_ids(layout_target, &mut Vec::new(), &checkbox_map);\n\n\t\t// Step 3: Diff with deterministic IDs\n\t\tlet mut widget_diffs = Vec::new();\n\n\t\tself.layouts[layout_target as usize].diff(new_layout, &mut Vec::new(), &mut widget_diffs);\n\n\t\t// Skip sending if no diff\n\t\tif widget_diffs.is_empty() {\n\t\t\treturn;\n\t\t}\n\n\t\t// On Mac we need the full MenuBar layout to construct the native menu\n\t\t#[cfg(target_os = \"macos\")]\n\t\tif layout_target == LayoutTarget::MenuBar {\n\t\t\twidget_diffs = vec![WidgetDiff {\n\t\t\t\twidget_path: Vec::new(),\n\t\t\t\tnew_value: DiffUpdate::Layout(self.layouts[LayoutTarget::MenuBar as usize].clone()),\n\t\t\t}];\n\t\t}\n\n\t\tself.send_diff(widget_diffs, layout_target, responses, action_input_mapping);\n\t}\n\n\t/// Send a diff to the frontend based on the layout target.\n\tfn send_diff(&self, mut diff: Vec<WidgetDiff>, layout_target: LayoutTarget, responses: &mut VecDeque<Message>, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Option<KeysGroup>) {\n\t\tdiff.iter_mut().for_each(|diff| diff.new_value.apply_keyboard_shortcut(action_input_mapping));\n\n\t\tif matches!(layout_target, LayoutTarget::_LayoutTargetLength) {\n\t\t\tpanic!(\"`_LayoutTargetLength` is not a valid `LayoutTarget` and is used for array indexing\");\n\t\t}\n\n\t\tresponses.add(FrontendMessage::UpdateLayout { layout_target, diff });\n\t}\n}\n\nenum WidgetValueAction {\n\tCommit,\n\tUpdate,\n}\n"
  },
  {
    "path": "editor/src/messages/layout/mod.rs",
    "content": "mod layout_message;\npub mod layout_message_handler;\n\npub mod utility_types;\n\n#[doc(inline)]\npub use layout_message::{LayoutMessage, LayoutMessageDiscriminant};\n#[doc(inline)]\npub use layout_message_handler::LayoutMessageHandler;\n"
  },
  {
    "path": "editor/src/messages/layout/utility_types/layout_widget.rs",
    "content": "use super::widgets::button_widgets::*;\nuse super::widgets::input_widgets::*;\nuse super::widgets::label_widgets::*;\nuse crate::application::generate_uuid;\nuse crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup;\nuse crate::messages::prelude::*;\nuse std::collections::HashMap;\nuse std::collections::hash_map::DefaultHasher;\nuse std::hash::{Hash, Hasher};\nuse std::sync::Arc;\n\n#[repr(transparent)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify), tsify(large_number_types_as_bigints))]\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize)]\npub struct WidgetId(pub u64);\n\nimpl core::fmt::Display for WidgetId {\n\tfn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n\t\twrite!(f, \"{}\", self.0)\n\t}\n}\n\nmacro_rules! define_layout_target {\n\t($($(#[$attr:meta])* $variant:ident),* $(,)?) => {\n\t\t#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n\t\t#[derive(PartialEq, Clone, Debug, Hash, Eq, Copy, serde::Serialize, serde::Deserialize)]\n\t\t#[repr(u8)]\n\t\tpub enum LayoutTarget {\n\t\t\t$($(#[$attr])* $variant,)*\n\t\t\t// KEEP THIS ENUM LAST\n\t\t\t// This is a marker that is used to define an array that is used to hold widgets\n\t\t\t#[serde(skip)]\n\t\t\t_LayoutTargetLength,\n\t\t}\n\n\t\timpl From<u8> for LayoutTarget {\n\t\t\tfn from(value: u8) -> Self {\n\t\t\t\tmatch value {\n\t\t\t\t\t$(x if x == Self::$variant as u8 => Self::$variant,)*\n\t\t\t\t\t_ => panic!(\"Invalid LayoutTarget discriminant: {value}\"),\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n}\ndefine_layout_target!(\n\t/// The spreadsheet panel allows for the visualisation of data in the graph.\n\tDataPanel,\n\t/// Contains the action buttons at the bottom of the dialog. Must be shown with the `FrontendMessage::DisplayDialog` message.\n\tDialogButtons,\n\t/// Contains the contents of the dialog's primary column. Must be shown with the `FrontendMessage::DisplayDialog` message.\n\tDialogColumn1,\n\t/// Contains the contents of the dialog's secondary column (often blank). Must be shown with the `FrontendMessage::DisplayDialog` message.\n\tDialogColumn2,\n\t/// Contains the widgets located directly above the canvas to the right, for example the zoom in and out buttons.\n\tDocumentBar,\n\t/// Controls for adding, grouping, and deleting layers at the bottom of the Layers panel.\n\tLayersPanelBottomBar,\n\t/// Blending options at the top of the Layers panel.\n\tLayersPanelControlLeftBar,\n\t/// Selected layer status (locked/hidden) at the top of the Layers panel.\n\tLayersPanelControlRightBar,\n\t/// The dropdown menu at the very top of the application: File, Edit, etc.\n\tMenuBar,\n\t/// Bar at the top of the node graph containing the location and the \"Preview\" and \"Hide\" buttons.\n\tNodeGraphControlBar,\n\t/// The body of the Properties panel containing many collapsable sections.\n\tPropertiesPanel,\n\t/// The contextual input key/mouse combination shortcuts shown in the status bar at the bottom of the window.\n\tStatusBarHints,\n\t/// The version information shown in the status bar at the bottom right of the window.\n\tStatusBarInfo,\n\t/// The left side of the control bar directly above the canvas.\n\tToolOptions,\n\t/// The vertical buttons for all of the tools on the left of the canvas.\n\tToolShelf,\n\t/// The quick access buttons found on the welcome screen, shown when no documents are open.\n\tWelcomeScreenButtons,\n\t/// The color swatch for the working colors and a flip and reset button found at the bottom of the tool shelf.\n\tWorkingColors,\n);\n\n/// For use by structs that define a UI widget layout by implementing the layout() function belonging to this trait.\n/// The send_layout() function can then be called by other code which is a part of the same struct so as to send the layout to the frontend.\npub trait LayoutHolder {\n\tfn layout(&self) -> Layout;\n\n\tfn send_layout(&self, responses: &mut VecDeque<Message>, layout_target: LayoutTarget) {\n\t\tresponses.add(LayoutMessage::SendLayout { layout: self.layout(), layout_target });\n\t}\n}\n\n/// Structs implementing this hold the layout (like [`LayoutHolder`]) for dialog content, but it also requires defining the dialog's title, icon, and action buttons.\npub trait DialogLayoutHolder: LayoutHolder {\n\tconst ICON: &'static str;\n\tconst TITLE: &'static str;\n\n\tfn layout_buttons(&self) -> Layout;\n\tfn send_layout_buttons(&self, responses: &mut VecDeque<Message>, layout_target: LayoutTarget) {\n\t\tresponses.add(LayoutMessage::SendLayout {\n\t\t\tlayout: self.layout_buttons(),\n\t\t\tlayout_target,\n\t\t});\n\t}\n\n\tfn layout_column_2(&self) -> Layout {\n\t\tLayout::default()\n\t}\n\tfn send_layout_column_2(&self, responses: &mut VecDeque<Message>, layout_target: LayoutTarget) {\n\t\tresponses.add(LayoutMessage::SendLayout {\n\t\t\tlayout: self.layout_column_2(),\n\t\t\tlayout_target,\n\t\t});\n\t}\n\n\tfn send_dialog_to_frontend(&self, responses: &mut VecDeque<Message>) {\n\t\tself.send_layout(responses, LayoutTarget::DialogColumn1);\n\t\tself.send_layout_column_2(responses, LayoutTarget::DialogColumn2);\n\t\tself.send_layout_buttons(responses, LayoutTarget::DialogButtons);\n\t\tresponses.add(FrontendMessage::DisplayDialog {\n\t\t\ticon: Self::ICON.into(),\n\t\t\ttitle: Self::TITLE.into(),\n\t\t});\n\t}\n}\n\n/// Trait for types that can compute incremental diffs for UI updates.\n///\n/// This trait unifies the diffing behavior across Layout, LayoutGroup, and WidgetInstance,\n/// allowing each type to specify how it should be represented in a DiffUpdate.\npub trait Diffable: Clone + PartialEq {\n\t/// Converts this value into a DiffUpdate variant.\n\tfn into_diff_update(self) -> DiffUpdate;\n\n\t/// Computes the diff between self (old) and new, updating self and recording changes.\n\tfn diff(&mut self, new: Self, widget_path: &mut Vec<usize>, widget_diffs: &mut Vec<WidgetDiff>);\n\n\t/// Collects all CheckboxIds currently in use in this layout, computing stable replacements.\n\tfn collect_checkbox_ids(&self, layout_target: LayoutTarget, widget_path: &mut Vec<usize>, checkbox_map: &mut HashMap<CheckboxId, CheckboxId>);\n\n\t/// Replaces all widget IDs with deterministic IDs based on position and type.\n\t/// Also replaces CheckboxIds using the provided mapping.\n\tfn replace_widget_ids(&mut self, layout_target: LayoutTarget, widget_path: &mut Vec<usize>, checkbox_map: &HashMap<CheckboxId, CheckboxId>);\n}\n\n/// Computes a deterministic WidgetId based on layout target, path, and widget type.\nfn compute_widget_id(layout_target: LayoutTarget, widget_path: &[usize], widget: &Widget) -> WidgetId {\n\tlet mut hasher = DefaultHasher::new();\n\n\t(layout_target as u8).hash(&mut hasher);\n\twidget_path.hash(&mut hasher);\n\tstd::mem::discriminant(widget).hash(&mut hasher);\n\n\tWidgetId(hasher.finish())\n}\n\n/// Computes a deterministic CheckboxId based on the same WidgetId algorithm.\nfn compute_checkbox_id(layout_target: LayoutTarget, widget_path: &[usize], widget: &Widget) -> CheckboxId {\n\tlet mut hasher = DefaultHasher::new();\n\n\t(layout_target as u8).hash(&mut hasher);\n\twidget_path.hash(&mut hasher);\n\tstd::mem::discriminant(widget).hash(&mut hasher);\n\n\t// Add extra salt for checkbox to differentiate from widget ID\n\t\"checkbox\".hash(&mut hasher);\n\n\tCheckboxId(hasher.finish())\n}\n\n/// Contains an arrangement of widgets mounted somewhere specific in the frontend.\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize, PartialEq)]\npub struct Layout(pub Vec<LayoutGroup>);\n\nimpl Layout {\n\tpub fn iter(&self) -> WidgetIter<'_> {\n\t\tWidgetIter {\n\t\t\tstack: self.0.iter().collect(),\n\t\t\t..Default::default()\n\t\t}\n\t}\n\n\tpub fn iter_mut(&mut self) -> WidgetIterMut<'_> {\n\t\tWidgetIterMut {\n\t\t\tstack: self.0.iter_mut().collect(),\n\t\t\t..Default::default()\n\t\t}\n\t}\n}\n\nimpl Diffable for Layout {\n\tfn into_diff_update(self) -> DiffUpdate {\n\t\tDiffUpdate::Layout(self)\n\t}\n\n\tfn diff(&mut self, new: Self, widget_path: &mut Vec<usize>, widget_diffs: &mut Vec<WidgetDiff>) {\n\t\t// Check if the length of items is different\n\t\t// TODO: Diff insersion and deletion of items\n\t\tif self.0.len() != new.0.len() {\n\t\t\t// Update the layout to the new layout\n\t\t\tself.0.clone_from(&new.0);\n\n\t\t\t// Push an update sublayout to the diff\n\t\t\twidget_diffs.push(WidgetDiff {\n\t\t\t\twidget_path: widget_path.to_vec(),\n\t\t\t\tnew_value: new.into_diff_update(),\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\t// Diff all of the children\n\t\tfor (index, (current_child, new_child)) in self.0.iter_mut().zip(new.0).enumerate() {\n\t\t\twidget_path.push(index);\n\t\t\tcurrent_child.diff(new_child, widget_path, widget_diffs);\n\t\t\twidget_path.pop();\n\t\t}\n\t}\n\n\tfn collect_checkbox_ids(&self, layout_target: LayoutTarget, widget_path: &mut Vec<usize>, checkbox_map: &mut HashMap<CheckboxId, CheckboxId>) {\n\t\tfor (index, child) in self.0.iter().enumerate() {\n\t\t\twidget_path.push(index);\n\t\t\tchild.collect_checkbox_ids(layout_target, widget_path, checkbox_map);\n\t\t\twidget_path.pop();\n\t\t}\n\t}\n\n\tfn replace_widget_ids(&mut self, layout_target: LayoutTarget, widget_path: &mut Vec<usize>, checkbox_map: &HashMap<CheckboxId, CheckboxId>) {\n\t\tfor (index, child) in self.0.iter_mut().enumerate() {\n\t\t\twidget_path.push(index);\n\t\t\tchild.replace_widget_ids(layout_target, widget_path, checkbox_map);\n\t\t\twidget_path.pop();\n\t\t}\n\t}\n}\n\n#[derive(Debug, Default)]\npub struct WidgetIter<'a> {\n\tpub stack: Vec<&'a LayoutGroup>,\n\tpub table: Vec<&'a WidgetInstance>,\n\tpub current_slice: Option<&'a [WidgetInstance]>,\n}\n\nimpl<'a> Iterator for WidgetIter<'a> {\n\ttype Item = &'a WidgetInstance;\n\n\tfn next(&mut self) -> Option<Self::Item> {\n\t\tlet widget = self.table.pop().or_else(|| {\n\t\t\tlet (first, rest) = self.current_slice.take()?.split_first()?;\n\t\t\tself.current_slice = Some(rest);\n\t\t\tSome(first)\n\t\t});\n\n\t\tif let Some(instance) = widget {\n\t\t\tif let Widget::PopoverButton(popover_button) = &*instance.widget {\n\t\t\t\tself.stack.extend(popover_button.popover_layout.0.iter());\n\t\t\t\treturn self.next();\n\t\t\t}\n\n\t\t\treturn Some(instance);\n\t\t}\n\n\t\tmatch self.stack.pop() {\n\t\t\tSome(LayoutGroup::Column(WidgetColumn { widgets })) => {\n\t\t\t\tself.current_slice = Some(widgets);\n\t\t\t\tself.next()\n\t\t\t}\n\t\t\tSome(LayoutGroup::Row(WidgetRow { widgets })) => {\n\t\t\t\tself.current_slice = Some(widgets);\n\t\t\t\tself.next()\n\t\t\t}\n\t\t\tSome(LayoutGroup::Table(WidgetTable { rows, .. })) => {\n\t\t\t\tself.table.extend(rows.iter().flatten().rev());\n\t\t\t\tself.next()\n\t\t\t}\n\t\t\tSome(LayoutGroup::Section(WidgetSection { layout, .. })) => {\n\t\t\t\tfor layout_row in &layout.0 {\n\t\t\t\t\tself.stack.push(layout_row);\n\t\t\t\t}\n\t\t\t\tself.next()\n\t\t\t}\n\t\t\tNone => None,\n\t\t}\n\t}\n}\n\n#[derive(Debug, Default)]\npub struct WidgetIterMut<'a> {\n\tpub stack: Vec<&'a mut LayoutGroup>,\n\tpub table: Vec<&'a mut WidgetInstance>,\n\tpub current_slice: Option<&'a mut [WidgetInstance]>,\n}\n\nimpl<'a> Iterator for WidgetIterMut<'a> {\n\ttype Item = &'a mut WidgetInstance;\n\n\tfn next(&mut self) -> Option<Self::Item> {\n\t\tlet widget = self.table.pop().or_else(|| {\n\t\t\tlet (first, rest) = self.current_slice.take()?.split_first_mut()?;\n\t\t\tself.current_slice = Some(rest);\n\t\t\tSome(first)\n\t\t});\n\n\t\tif let Some(instance) = widget {\n\t\t\t// We have to check that we're not a popover and return first, then extract the popover with an unreachable else condition second, to satisfy the borrow checker.\n\t\t\t// After Rust's Polonius is stable, we can reverse that order of steps to avoid the redundancy and unreachable statement.\n\t\t\tif !matches!(*instance.widget, Widget::PopoverButton(_)) {\n\t\t\t\treturn Some(instance);\n\t\t\t}\n\t\t\tlet Widget::PopoverButton(popover_button) = &mut *instance.widget else { unreachable!() };\n\t\t\tself.stack.extend(popover_button.popover_layout.0.iter_mut());\n\t\t\treturn self.next();\n\t\t}\n\n\t\tmatch self.stack.pop() {\n\t\t\tSome(LayoutGroup::Column(WidgetColumn { widgets })) => {\n\t\t\t\tself.current_slice = Some(widgets);\n\t\t\t\tself.next()\n\t\t\t}\n\t\t\tSome(LayoutGroup::Row(WidgetRow { widgets })) => {\n\t\t\t\tself.current_slice = Some(widgets);\n\t\t\t\tself.next()\n\t\t\t}\n\t\t\tSome(LayoutGroup::Table(WidgetTable { rows, .. })) => {\n\t\t\t\tself.table.extend(rows.iter_mut().flatten().rev());\n\t\t\t\tself.next()\n\t\t\t}\n\t\t\tSome(LayoutGroup::Section(WidgetSection { layout, .. })) => {\n\t\t\t\tfor layout_row in &mut layout.0 {\n\t\t\t\t\tself.stack.push(layout_row);\n\t\t\t\t}\n\t\t\t\tself.next()\n\t\t\t}\n\t\t\tNone => None,\n\t\t}\n\t}\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]\npub enum LayoutGroup {\n\tColumn(WidgetColumn),\n\tRow(WidgetRow),\n\tTable(WidgetTable),\n\tSection(WidgetSection),\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct WidgetColumn {\n\t#[serde(rename = \"columnWidgets\")]\n\tpub widgets: Vec<WidgetInstance>,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct WidgetRow {\n\t#[serde(rename = \"rowWidgets\")]\n\tpub widgets: Vec<WidgetInstance>,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct WidgetTable {\n\t#[serde(rename = \"tableWidgets\")]\n\tpub rows: Vec<Vec<WidgetInstance>>,\n\tpub unstyled: bool,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify), tsify(large_number_types_as_bigints))]\n#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct WidgetSection {\n\tpub name: String,\n\tpub description: String,\n\tpub visible: bool,\n\tpub pinned: bool,\n\tpub id: u64,\n\tpub layout: Layout,\n}\n\nimpl Default for LayoutGroup {\n\tfn default() -> Self {\n\t\tSelf::Row(Default::default())\n\t}\n}\nimpl From<Vec<WidgetInstance>> for LayoutGroup {\n\tfn from(widgets: Vec<WidgetInstance>) -> LayoutGroup {\n\t\tLayoutGroup::Row(WidgetRow { widgets })\n\t}\n}\n\nimpl LayoutGroup {\n\tpub fn row(widgets: Vec<WidgetInstance>) -> Self {\n\t\tSelf::Row(WidgetRow { widgets })\n\t}\n\n\tpub fn column(widgets: Vec<WidgetInstance>) -> Self {\n\t\tSelf::Column(WidgetColumn { widgets })\n\t}\n\n\tpub fn table(rows: Vec<Vec<WidgetInstance>>, unstyled: bool) -> Self {\n\t\tSelf::Table(WidgetTable { rows, unstyled })\n\t}\n\n\tpub fn section(name: impl Into<String>, description: impl Into<String>, visible: bool, pinned: bool, id: u64, layout: Layout) -> Self {\n\t\tSelf::Section(WidgetSection {\n\t\t\tname: name.into(),\n\t\t\tdescription: description.into(),\n\t\t\tvisible,\n\t\t\tpinned,\n\t\t\tid,\n\t\t\tlayout,\n\t\t})\n\t}\n\n\t/// Applies a tooltip description to all widgets without a tooltip in this row or column.\n\tpub fn with_tooltip_description(self, description: impl Into<String>) -> Self {\n\t\tlet (is_col, mut widgets) = match self {\n\t\t\tLayoutGroup::Column(WidgetColumn { widgets }) => (true, widgets),\n\t\t\tLayoutGroup::Row(WidgetRow { widgets }) => (false, widgets),\n\t\t\t_ => unimplemented!(),\n\t\t};\n\t\tlet description = description.into();\n\t\tfor widget in &mut widgets {\n\t\t\tlet val = match &mut *widget.widget {\n\t\t\t\tWidget::CheckboxInput(x) => &mut x.tooltip_description,\n\t\t\t\tWidget::ColorInput(x) => &mut x.tooltip_description,\n\t\t\t\tWidget::CurveInput(x) => &mut x.tooltip_description,\n\t\t\t\tWidget::DropdownInput(x) => &mut x.tooltip_description,\n\t\t\t\tWidget::IconButton(x) => &mut x.tooltip_description,\n\t\t\t\tWidget::IconLabel(x) => &mut x.tooltip_description,\n\t\t\t\tWidget::ImageButton(x) => &mut x.tooltip_description,\n\t\t\t\tWidget::ImageLabel(x) => &mut x.tooltip_description,\n\t\t\t\tWidget::NumberInput(x) => &mut x.tooltip_description,\n\t\t\t\tWidget::PopoverButton(x) => &mut x.tooltip_description,\n\t\t\t\tWidget::TextAreaInput(x) => &mut x.tooltip_description,\n\t\t\t\tWidget::TextButton(x) => &mut x.tooltip_description,\n\t\t\t\tWidget::TextInput(x) => &mut x.tooltip_description,\n\t\t\t\tWidget::TextLabel(x) => &mut x.tooltip_description,\n\t\t\t\tWidget::BreadcrumbTrailButtons(x) => &mut x.tooltip_description,\n\t\t\t\tWidget::ReferencePointInput(_)\n\t\t\t\t| Widget::RadioInput(_)\n\t\t\t\t| Widget::Separator(_)\n\t\t\t\t| Widget::ShortcutLabel(_)\n\t\t\t\t| Widget::WorkingColorsInput(_)\n\t\t\t\t| Widget::NodeCatalog(_)\n\t\t\t\t| Widget::ParameterExposeButton(_) => continue,\n\t\t\t};\n\t\t\tif val.is_empty() {\n\t\t\t\tval.clone_from(&description);\n\t\t\t}\n\t\t}\n\t\tif is_col { Self::Column(WidgetColumn { widgets }) } else { Self::Row(WidgetRow { widgets }) }\n\t}\n\n\tpub fn iter_mut(&mut self) -> WidgetIterMut<'_> {\n\t\tWidgetIterMut {\n\t\t\tstack: vec![self],\n\t\t\t..Default::default()\n\t\t}\n\t}\n}\n\nimpl Diffable for LayoutGroup {\n\tfn into_diff_update(self) -> DiffUpdate {\n\t\tDiffUpdate::LayoutGroup(self)\n\t}\n\n\tfn diff(&mut self, new: Self, widget_path: &mut Vec<usize>, widget_diffs: &mut Vec<WidgetDiff>) {\n\t\tlet is_column = matches!(new, Self::Column { .. });\n\t\tmatch (self, new) {\n\t\t\t(Self::Column(WidgetColumn { widgets: current_widgets }), Self::Column(WidgetColumn { widgets: new_widgets }))\n\t\t\t| (Self::Row(WidgetRow { widgets: current_widgets }), Self::Row(WidgetRow { widgets: new_widgets })) => {\n\t\t\t\t// If the lengths are different then resend the entire panel\n\t\t\t\t// TODO: Diff insersion and deletion of items\n\t\t\t\tif current_widgets.len() != new_widgets.len() {\n\t\t\t\t\t// Update to the new value\n\t\t\t\t\tcurrent_widgets.clone_from(&new_widgets);\n\n\t\t\t\t\t// Push back a LayoutGroup update to the diff\n\t\t\t\t\tlet new_value = (if is_column {\n\t\t\t\t\t\tSelf::Column(WidgetColumn { widgets: new_widgets })\n\t\t\t\t\t} else {\n\t\t\t\t\t\tSelf::Row(WidgetRow { widgets: new_widgets })\n\t\t\t\t\t})\n\t\t\t\t\t.into_diff_update();\n\t\t\t\t\tlet widget_path = widget_path.to_vec();\n\t\t\t\t\twidget_diffs.push(WidgetDiff { widget_path, new_value });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// Diff all of the children\n\t\t\t\tfor (index, (current_child, new_child)) in current_widgets.iter_mut().zip(new_widgets).enumerate() {\n\t\t\t\t\twidget_path.push(index);\n\t\t\t\t\tcurrent_child.diff(new_child, widget_path, widget_diffs);\n\t\t\t\t\twidget_path.pop();\n\t\t\t\t}\n\t\t\t}\n\t\t\t(\n\t\t\t\tSelf::Section(WidgetSection {\n\t\t\t\t\tname: current_name,\n\t\t\t\t\tdescription: current_description,\n\t\t\t\t\tvisible: current_visible,\n\t\t\t\t\tpinned: current_pinned,\n\t\t\t\t\tid: current_id,\n\t\t\t\t\tlayout: current_layout,\n\t\t\t\t}),\n\t\t\t\tSelf::Section(WidgetSection {\n\t\t\t\t\tname: new_name,\n\t\t\t\t\tdescription: new_description,\n\t\t\t\t\tvisible: new_visible,\n\t\t\t\t\tpinned: new_pinned,\n\t\t\t\t\tid: new_id,\n\t\t\t\t\tlayout: new_layout,\n\t\t\t\t}),\n\t\t\t) => {\n\t\t\t\t// Resend the entire panel if the lengths, names, visibility, or node IDs are different\n\t\t\t\t// TODO: Diff insersion and deletion of items\n\t\t\t\tif current_layout.0.len() != new_layout.0.len()\n\t\t\t\t\t|| *current_name != new_name\n\t\t\t\t\t|| *current_description != new_description\n\t\t\t\t\t|| *current_visible != new_visible\n\t\t\t\t\t|| *current_pinned != new_pinned\n\t\t\t\t\t|| *current_id != new_id\n\t\t\t\t{\n\t\t\t\t\t// Update self to reflect new changes\n\t\t\t\t\tcurrent_name.clone_from(&new_name);\n\t\t\t\t\tcurrent_description.clone_from(&new_description);\n\t\t\t\t\t*current_visible = new_visible;\n\t\t\t\t\t*current_pinned = new_pinned;\n\t\t\t\t\t*current_id = new_id;\n\t\t\t\t\tcurrent_layout.clone_from(&new_layout);\n\n\t\t\t\t\t// Push an update layout group to the diff\n\t\t\t\t\tlet new_value = Self::Section(WidgetSection {\n\t\t\t\t\t\tname: new_name,\n\t\t\t\t\t\tdescription: new_description,\n\t\t\t\t\t\tvisible: new_visible,\n\t\t\t\t\t\tpinned: new_pinned,\n\t\t\t\t\t\tid: new_id,\n\t\t\t\t\t\tlayout: new_layout,\n\t\t\t\t\t})\n\t\t\t\t\t.into_diff_update();\n\t\t\t\t\tlet widget_path = widget_path.to_vec();\n\t\t\t\t\twidget_diffs.push(WidgetDiff { widget_path, new_value });\n\t\t\t\t}\n\t\t\t\t// Diff all of the children\n\t\t\t\telse {\n\t\t\t\t\tfor (index, (current_child, new_child)) in current_layout.0.iter_mut().zip(new_layout.0).enumerate() {\n\t\t\t\t\t\twidget_path.push(index);\n\t\t\t\t\t\tcurrent_child.diff(new_child, widget_path, widget_diffs);\n\t\t\t\t\t\twidget_path.pop();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t(current, new) => {\n\t\t\t\t*current = new.clone();\n\t\t\t\tlet new_value = new.into_diff_update();\n\t\t\t\tlet widget_path = widget_path.to_vec();\n\t\t\t\twidget_diffs.push(WidgetDiff { widget_path, new_value });\n\t\t\t}\n\t\t}\n\t}\n\n\tfn collect_checkbox_ids(&self, layout_target: LayoutTarget, widget_path: &mut Vec<usize>, checkbox_map: &mut HashMap<CheckboxId, CheckboxId>) {\n\t\tmatch self {\n\t\t\tSelf::Column(WidgetColumn { widgets }) | Self::Row(WidgetRow { widgets }) => {\n\t\t\t\tfor (index, widget) in widgets.iter().enumerate() {\n\t\t\t\t\twidget_path.push(index);\n\t\t\t\t\twidget.collect_checkbox_ids(layout_target, widget_path, checkbox_map);\n\t\t\t\t\twidget_path.pop();\n\t\t\t\t}\n\t\t\t}\n\t\t\tSelf::Table(WidgetTable { rows, .. }) => {\n\t\t\t\tfor (row_idx, row) in rows.iter().enumerate() {\n\t\t\t\t\tfor (col_idx, widget) in row.iter().enumerate() {\n\t\t\t\t\t\twidget_path.push(row_idx);\n\t\t\t\t\t\twidget_path.push(col_idx);\n\t\t\t\t\t\twidget.collect_checkbox_ids(layout_target, widget_path, checkbox_map);\n\t\t\t\t\t\twidget_path.pop();\n\t\t\t\t\t\twidget_path.pop();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tSelf::Section(WidgetSection { layout, .. }) => {\n\t\t\t\tlayout.collect_checkbox_ids(layout_target, widget_path, checkbox_map);\n\t\t\t}\n\t\t}\n\t}\n\n\tfn replace_widget_ids(&mut self, layout_target: LayoutTarget, widget_path: &mut Vec<usize>, checkbox_map: &HashMap<CheckboxId, CheckboxId>) {\n\t\tmatch self {\n\t\t\tSelf::Column(WidgetColumn { widgets }) | Self::Row(WidgetRow { widgets }) => {\n\t\t\t\tfor (index, widget) in widgets.iter_mut().enumerate() {\n\t\t\t\t\twidget_path.push(index);\n\t\t\t\t\twidget.replace_widget_ids(layout_target, widget_path, checkbox_map);\n\t\t\t\t\twidget_path.pop();\n\t\t\t\t}\n\t\t\t}\n\t\t\tSelf::Table(WidgetTable { rows, .. }) => {\n\t\t\t\tfor (row_idx, row) in rows.iter_mut().enumerate() {\n\t\t\t\t\tfor (col_idx, widget) in row.iter_mut().enumerate() {\n\t\t\t\t\t\twidget_path.push(row_idx);\n\t\t\t\t\t\twidget_path.push(col_idx);\n\t\t\t\t\t\twidget.replace_widget_ids(layout_target, widget_path, checkbox_map);\n\t\t\t\t\t\twidget_path.pop();\n\t\t\t\t\t\twidget_path.pop();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tSelf::Section(WidgetSection { layout, .. }) => {\n\t\t\t\tlayout.replace_widget_ids(layout_target, widget_path, checkbox_map);\n\t\t\t}\n\t\t}\n\t}\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]\npub struct WidgetInstance {\n\t#[serde(rename = \"widgetId\")]\n\tpub widget_id: WidgetId,\n\tpub widget: Box<Widget>,\n}\n\nimpl PartialEq for WidgetInstance {\n\tfn eq(&self, other: &Self) -> bool {\n\t\tself.widget_id == other.widget_id && self.widget == other.widget\n\t}\n}\n\nimpl WidgetInstance {\n\t#[deprecated(since = \"0.0.0\", note = \"Please use the builder pattern, e.g. TextLabel::new(\\\"hello\\\").widget_instance()\")]\n\tpub fn new(widget: Widget) -> Self {\n\t\tSelf {\n\t\t\twidget_id: WidgetId(generate_uuid()),\n\t\t\twidget: Box::new(widget),\n\t\t}\n\t}\n}\n\nimpl Diffable for WidgetInstance {\n\tfn into_diff_update(self) -> DiffUpdate {\n\t\tDiffUpdate::Widget(self)\n\t}\n\n\tfn diff(&mut self, new: Self, widget_path: &mut Vec<usize>, widget_diffs: &mut Vec<WidgetDiff>) {\n\t\tif self == &new {\n\t\t\t// Still need to update callbacks since PartialEq skips them\n\t\t\tself.widget = new.widget;\n\t\t\treturn;\n\t\t}\n\n\t\t// Special handling for PopoverButton: recursively diff nested layout if only the layout changed\n\t\tif let (Widget::PopoverButton(button1), Widget::PopoverButton(button2)) = (&mut *self.widget, &*new.widget) {\n\t\t\t// Check if only the popover layout changed (all other fields are the same)\n\t\t\tif self.widget_id == new.widget_id\n\t\t\t\t&& button1.disabled == button2.disabled\n\t\t\t\t&& button1.style == button2.style\n\t\t\t\t&& button1.menu_direction == button2.menu_direction\n\t\t\t\t&& button1.icon == button2.icon\n\t\t\t\t&& button1.tooltip_label == button2.tooltip_label\n\t\t\t\t&& button1.tooltip_description == button2.tooltip_description\n\t\t\t\t&& button1.tooltip_shortcut == button2.tooltip_shortcut\n\t\t\t\t&& button1.popover_min_width == button2.popover_min_width\n\t\t\t{\n\t\t\t\t// Only the popover layout differs, diff it recursively\n\t\t\t\tfor (i, (a, b)) in button1.popover_layout.0.iter_mut().zip(button2.popover_layout.0.iter()).enumerate() {\n\t\t\t\t\twidget_path.push(i);\n\t\t\t\t\ta.diff(b.clone(), widget_path, widget_diffs);\n\t\t\t\t\twidget_path.pop();\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Widget or ID changed, send full update\n\t\t*self = new.clone();\n\t\tlet new_value = new.into_diff_update();\n\t\tlet widget_path = widget_path.to_vec();\n\t\twidget_diffs.push(WidgetDiff { widget_path, new_value });\n\t}\n\n\tfn collect_checkbox_ids(&self, layout_target: LayoutTarget, widget_path: &mut Vec<usize>, checkbox_map: &mut HashMap<CheckboxId, CheckboxId>) {\n\t\tmatch &*self.widget {\n\t\t\tWidget::CheckboxInput(checkbox) => {\n\t\t\t\t// Compute stable ID based on position and insert mapping\n\t\t\t\tlet checkbox_id = checkbox.for_label;\n\t\t\t\tlet stable_id = compute_checkbox_id(layout_target, widget_path, &self.widget);\n\t\t\t\tcheckbox_map.entry(checkbox_id).or_insert(stable_id);\n\t\t\t}\n\t\t\tWidget::TextLabel(label) => {\n\t\t\t\t// Compute stable ID based on position and insert mapping\n\t\t\t\tlet checkbox_id = label.for_checkbox;\n\t\t\t\tlet stable_id = compute_checkbox_id(layout_target, widget_path, &self.widget);\n\t\t\t\tcheckbox_map.entry(checkbox_id).or_insert(stable_id);\n\t\t\t}\n\t\t\tWidget::PopoverButton(button) => {\n\t\t\t\t// Recursively collect from nested popover layout\n\t\t\t\tfor (index, child) in button.popover_layout.0.iter().enumerate() {\n\t\t\t\t\twidget_path.push(index);\n\t\t\t\t\tchild.collect_checkbox_ids(layout_target, widget_path, checkbox_map);\n\t\t\t\t\twidget_path.pop();\n\t\t\t\t}\n\t\t\t}\n\t\t\t_ => {}\n\t\t}\n\t}\n\n\tfn replace_widget_ids(&mut self, layout_target: LayoutTarget, widget_path: &mut Vec<usize>, checkbox_map: &HashMap<CheckboxId, CheckboxId>) {\n\t\t// 1. Generate deterministic WidgetId\n\t\tself.widget_id = compute_widget_id(layout_target, widget_path, &self.widget);\n\n\t\t// 2. Replace CheckboxIds if present\n\t\tmatch &mut *self.widget {\n\t\t\tWidget::CheckboxInput(checkbox) => {\n\t\t\t\tlet old_id = checkbox.for_label;\n\t\t\t\tif let Some(&new_id) = checkbox_map.get(&old_id) {\n\t\t\t\t\tcheckbox.for_label = new_id;\n\t\t\t\t}\n\t\t\t}\n\t\t\tWidget::TextLabel(label) => {\n\t\t\t\tlet old_id = label.for_checkbox;\n\t\t\t\tif let Some(&new_id) = checkbox_map.get(&old_id) {\n\t\t\t\t\tlabel.for_checkbox = new_id;\n\t\t\t\t}\n\t\t\t}\n\t\t\tWidget::PopoverButton(button) => {\n\t\t\t\t// Recursively replace in nested popover layout\n\t\t\t\tfor (index, child) in button.popover_layout.0.iter_mut().enumerate() {\n\t\t\t\t\twidget_path.push(index);\n\t\t\t\t\tchild.replace_widget_ids(layout_target, widget_path, checkbox_map);\n\t\t\t\t\twidget_path.pop();\n\t\t\t\t}\n\t\t\t}\n\t\t\t_ => {}\n\t\t}\n\t}\n}\n\n#[derive(Clone)]\npub struct WidgetCallback<T> {\n\tpub callback: Arc<dyn Fn(&T) -> Message + 'static + Send + Sync>,\n}\n\nimpl<T> WidgetCallback<T> {\n\tpub fn new(callback: impl Fn(&T) -> Message + 'static + Send + Sync) -> Self {\n\t\tSelf { callback: Arc::new(callback) }\n\t}\n}\n\nimpl<T> Default for WidgetCallback<T> {\n\tfn default() -> Self {\n\t\tSelf::new(|_| Message::NoOp)\n\t}\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]\npub enum Widget {\n\tBreadcrumbTrailButtons(BreadcrumbTrailButtons),\n\tCheckboxInput(CheckboxInput),\n\tColorInput(ColorInput),\n\tCurveInput(CurveInput),\n\tDropdownInput(DropdownInput),\n\tIconButton(IconButton),\n\tIconLabel(IconLabel),\n\tImageButton(ImageButton),\n\tImageLabel(ImageLabel),\n\tShortcutLabel(ShortcutLabel),\n\tNodeCatalog(NodeCatalog),\n\tNumberInput(NumberInput),\n\tParameterExposeButton(ParameterExposeButton),\n\tReferencePointInput(ReferencePointInput),\n\tPopoverButton(PopoverButton),\n\tRadioInput(RadioInput),\n\tSeparator(Separator),\n\tTextAreaInput(TextAreaInput),\n\tTextButton(TextButton),\n\tTextInput(TextInput),\n\tTextLabel(TextLabel),\n\tWorkingColorsInput(WorkingColorsInput),\n}\n\n/// A single change to part of the UI, containing the location of the change and the new value.\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify), tsify(large_number_types_as_bigints))]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub struct WidgetDiff {\n\t/// A path to the change\n\t/// e.g. [0, 1, 2] in the properties panel is the first section, second row and third widget.\n\t/// An empty path [] shows that the entire panel has changed and is sent when the UI is first created.\n\t#[serde(rename = \"widgetPath\")]\n\tpub widget_path: Vec<usize>,\n\t/// What the specified part of the UI has changed to.\n\t#[serde(rename = \"newValue\")]\n\tpub new_value: DiffUpdate,\n}\n\n/// The new value of the UI, sent as part of a diff.\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum DiffUpdate {\n\t#[serde(rename = \"layout\")]\n\tLayout(Layout),\n\t#[serde(rename = \"layoutGroup\")]\n\tLayoutGroup(LayoutGroup),\n\t#[serde(rename = \"widget\")]\n\tWidget(WidgetInstance),\n}\n\nimpl DiffUpdate {\n\t/// Append the keyboard shortcut to the tooltip where applicable\n\tpub fn apply_keyboard_shortcut(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Option<KeysGroup>) {\n\t\t// Go through each widget to convert `ActionShortcut::Action` to `ActionShortcut::Shortcut` and append the key combination to the widget tooltip\n\t\tlet convert_tooltip = |widget_instance: &mut WidgetInstance| {\n\t\t\t// Handle all the widgets that have tooltips\n\t\t\tlet tooltip_shortcut = match &mut *widget_instance.widget {\n\t\t\t\tWidget::BreadcrumbTrailButtons(widget) => widget.tooltip_shortcut.as_mut(),\n\t\t\t\tWidget::CheckboxInput(widget) => widget.tooltip_shortcut.as_mut(),\n\t\t\t\tWidget::ColorInput(widget) => widget.tooltip_shortcut.as_mut(),\n\t\t\t\tWidget::DropdownInput(widget) => widget.tooltip_shortcut.as_mut(),\n\t\t\t\tWidget::IconButton(widget) => widget.tooltip_shortcut.as_mut(),\n\t\t\t\tWidget::NumberInput(widget) => widget.tooltip_shortcut.as_mut(),\n\t\t\t\tWidget::ParameterExposeButton(widget) => widget.tooltip_shortcut.as_mut(),\n\t\t\t\tWidget::PopoverButton(widget) => widget.tooltip_shortcut.as_mut(),\n\t\t\t\tWidget::TextButton(widget) => widget.tooltip_shortcut.as_mut(),\n\t\t\t\tWidget::ImageButton(widget) => widget.tooltip_shortcut.as_mut(),\n\t\t\t\tWidget::ShortcutLabel(widget) => widget.shortcut.as_mut(),\n\t\t\t\tWidget::IconLabel(_)\n\t\t\t\t| Widget::ImageLabel(_)\n\t\t\t\t| Widget::CurveInput(_)\n\t\t\t\t| Widget::NodeCatalog(_)\n\t\t\t\t| Widget::ReferencePointInput(_)\n\t\t\t\t| Widget::RadioInput(_)\n\t\t\t\t| Widget::Separator(_)\n\t\t\t\t| Widget::TextAreaInput(_)\n\t\t\t\t| Widget::TextInput(_)\n\t\t\t\t| Widget::TextLabel(_)\n\t\t\t\t| Widget::WorkingColorsInput(_) => None,\n\t\t\t};\n\n\t\t\t// Convert `ActionShortcut::Action` to `ActionShortcut::Shortcut`\n\t\t\tif let Some(tooltip_shortcut) = tooltip_shortcut {\n\t\t\t\ttooltip_shortcut.realize_shortcut(action_input_mapping);\n\t\t\t}\n\n\t\t\t// Handle RadioInput separately because its tooltips are children of the widget\n\t\t\tif let Widget::RadioInput(radio_input) = &mut *widget_instance.widget {\n\t\t\t\tfor radio_entry_data in &mut radio_input.entries {\n\t\t\t\t\t// Convert `ActionShortcut::Action` to `ActionShortcut::Shortcut`\n\t\t\t\t\tif let Some(tooltip_shortcut) = radio_entry_data.tooltip_shortcut.as_mut() {\n\t\t\t\t\t\ttooltip_shortcut.realize_shortcut(action_input_mapping);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\t// Recursively fill menu list entries with their realized shortcut keys specific to the current bindings and platform\n\t\tfn apply_action_shortcut_to_menu_lists(entry_sections: &mut MenuListEntrySections, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Option<KeysGroup>) {\n\t\t\tfor entries in entry_sections {\n\t\t\t\tfor entry in entries {\n\t\t\t\t\t// Convert `ActionShortcut::Action` to `ActionShortcut::Shortcut`\n\t\t\t\t\tif let Some(tooltip_shortcut) = &mut entry.tooltip_shortcut {\n\t\t\t\t\t\ttooltip_shortcut.realize_shortcut(action_input_mapping);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Recursively call this function on the menu's children\n\t\t\t\t\tapply_action_shortcut_to_menu_lists(&mut entry.children, action_input_mapping);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Hash the menu list entry sections for caching purposes\n\t\tlet hash_menu_list_entry_sections = |entry_sections: &MenuListEntrySections| {\n\t\t\tstruct RecursiveHasher<'a> {\n\t\t\t\thasher: DefaultHasher,\n\t\t\t\thash_fn: &'a dyn Fn(&mut RecursiveHasher, &MenuListEntrySections),\n\t\t\t}\n\t\t\tlet mut recursive_hasher = RecursiveHasher {\n\t\t\t\thasher: DefaultHasher::new(),\n\t\t\t\thash_fn: &|recursive_hasher, entry_sections| {\n\t\t\t\t\tfor (index, entries) in entry_sections.iter().enumerate() {\n\t\t\t\t\t\tindex.hash(&mut recursive_hasher.hasher);\n\t\t\t\t\t\tfor entry in entries {\n\t\t\t\t\t\t\tentry.hash(&mut recursive_hasher.hasher);\n\t\t\t\t\t\t\t(recursive_hasher.hash_fn)(recursive_hasher, &entry.children);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t};\n\t\t\t(recursive_hasher.hash_fn)(&mut recursive_hasher, entry_sections);\n\t\t\trecursive_hasher.hasher.finish()\n\t\t};\n\n\t\t// Apply shortcut conversions to all widgets that have menu lists\n\t\tlet convert_menu_lists = |widget_instance: &mut WidgetInstance| match &mut *widget_instance.widget {\n\t\t\tWidget::DropdownInput(dropdown_input) => {\n\t\t\t\tapply_action_shortcut_to_menu_lists(&mut dropdown_input.entries, action_input_mapping);\n\t\t\t\tdropdown_input.entries_hash = hash_menu_list_entry_sections(&dropdown_input.entries);\n\t\t\t}\n\t\t\tWidget::TextButton(text_button) => {\n\t\t\t\tapply_action_shortcut_to_menu_lists(&mut text_button.menu_list_children, action_input_mapping);\n\t\t\t\ttext_button.menu_list_children_hash = hash_menu_list_entry_sections(&text_button.menu_list_children);\n\t\t\t}\n\t\t\t_ => {}\n\t\t};\n\n\t\tmatch self {\n\t\t\tSelf::Layout(layout) => layout.0.iter_mut().flat_map(|layout_group| layout_group.iter_mut()).for_each(|widget_instance| {\n\t\t\t\tconvert_tooltip(widget_instance);\n\t\t\t\tconvert_menu_lists(widget_instance);\n\t\t\t}),\n\t\t\tSelf::LayoutGroup(layout_group) => layout_group.iter_mut().for_each(|widget_instance| {\n\t\t\t\tconvert_tooltip(widget_instance);\n\t\t\t\tconvert_menu_lists(widget_instance);\n\t\t\t}),\n\t\t\tSelf::Widget(widget_instance) => {\n\t\t\t\tconvert_tooltip(widget_instance);\n\t\t\t\tconvert_menu_lists(widget_instance);\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/layout/utility_types/mod.rs",
    "content": "pub mod layout_widget;\npub mod widgets;\n\npub mod widget_prelude {\n\tpub use super::layout_widget::*;\n\tpub use super::widgets::button_widgets::*;\n\tpub use super::widgets::input_widgets::*;\n\tpub use super::widgets::label_widgets::*;\n}\n"
  },
  {
    "path": "editor/src/messages/layout/utility_types/widgets/button_widgets.rs",
    "content": "use crate::messages::frontend::IconName;\nuse crate::messages::input_mapper::utility_types::misc::ActionShortcut;\nuse crate::messages::layout::utility_types::widget_prelude::*;\nuse crate::messages::portfolio::document::node_graph::utility_types::FrontendGraphDataType;\nuse crate::messages::tool::tool_messages::tool_prelude::WidgetCallback;\nuse derivative::*;\nuse graphene_std::vector::style::FillChoice;\nuse graphite_proc_macros::WidgetBuilder;\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, Default, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder)]\n#[derivative(Debug, PartialEq)]\npub struct IconButton {\n\t// Content\n\t#[widget_builder(constructor)]\n\t#[widget_builder(string)]\n\tpub icon: IconName,\n\t#[serde(rename = \"hoverIcon\")]\n\tpub hover_icon: Option<String>,\n\t#[widget_builder(constructor)]\n\tpub size: u32, // TODO: Convert to an `IconSize` enum\n\tpub disabled: bool,\n\n\t// Styling\n\tpub emphasized: bool,\n\n\t// Tooltips\n\t#[serde(rename = \"tooltipLabel\")]\n\tpub tooltip_label: String,\n\t#[serde(rename = \"tooltipDescription\")]\n\tpub tooltip_description: String,\n\t#[serde(rename = \"tooltipShortcut\")]\n\tpub tooltip_shortcut: Option<ActionShortcut>,\n\n\t// Callbacks\n\t#[serde(skip)]\n\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\tpub on_update: WidgetCallback<IconButton>,\n\t#[serde(skip)]\n\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\tpub on_commit: WidgetCallback<()>,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder)]\n#[derivative(Debug, PartialEq, Default)]\npub struct PopoverButton {\n\t// Content\n\tpub style: Option<String>,\n\t#[widget_builder(string)]\n\tpub icon: Option<IconName>,\n\tpub disabled: bool,\n\n\t// Children\n\t#[serde(rename = \"popoverLayout\")]\n\tpub popover_layout: Layout,\n\t#[serde(rename = \"popoverMinWidth\")]\n\tpub popover_min_width: Option<u32>,\n\t#[serde(rename = \"menuDirection\")]\n\tpub menu_direction: Option<MenuDirection>,\n\n\t// Tooltips\n\t#[serde(rename = \"tooltipLabel\")]\n\tpub tooltip_label: String,\n\t#[serde(rename = \"tooltipDescription\")]\n\tpub tooltip_description: String,\n\t#[serde(rename = \"tooltipShortcut\")]\n\tpub tooltip_shortcut: Option<ActionShortcut>,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize)]\npub enum MenuDirection {\n\tTop,\n\t#[default]\n\tBottom,\n\tLeft,\n\tRight,\n\tTopLeft,\n\tTopRight,\n\tBottomLeft,\n\tBottomRight,\n\tCenter,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder)]\n#[derivative(Debug, PartialEq)]\npub struct ParameterExposeButton {\n\t// Content\n\tpub exposed: bool,\n\t#[serde(rename = \"dataType\")]\n\tpub data_type: FrontendGraphDataType,\n\n\t// Tooltips\n\t#[serde(rename = \"tooltipLabel\")]\n\tpub tooltip_label: String,\n\t#[serde(rename = \"tooltipDescription\")]\n\tpub tooltip_description: String,\n\t#[serde(rename = \"tooltipShortcut\")]\n\tpub tooltip_shortcut: Option<ActionShortcut>,\n\n\t// Callbacks\n\t#[serde(skip)]\n\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\tpub on_update: WidgetCallback<ParameterExposeButton>,\n\t#[serde(skip)]\n\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\tpub on_commit: WidgetCallback<()>,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder)]\n#[derivative(Debug, PartialEq)]\npub struct TextButton {\n\t// Content\n\t#[widget_builder(constructor)]\n\tpub label: String,\n\t#[widget_builder(string)]\n\tpub icon: Option<IconName>,\n\t#[serde(rename = \"hoverIcon\")]\n\tpub hover_icon: Option<String>,\n\tpub disabled: bool,\n\n\t// Children\n\t#[serde(rename = \"menuListChildren\")]\n\tpub menu_list_children: MenuListEntrySections,\n\t#[serde(rename = \"menuListChildrenHash\")]\n\t#[widget_builder(skip)]\n\tpub menu_list_children_hash: u64,\n\n\t// Styling\n\tpub emphasized: bool,\n\tpub flush: bool,\n\tpub narrow: bool,\n\n\t// Sizing\n\t#[serde(rename = \"minWidth\")]\n\tpub min_width: u32,\n\n\t// Tooltips\n\t#[serde(rename = \"tooltipLabel\")]\n\tpub tooltip_label: String,\n\t#[serde(rename = \"tooltipDescription\")]\n\tpub tooltip_description: String,\n\t#[serde(rename = \"tooltipShortcut\")]\n\tpub tooltip_shortcut: Option<ActionShortcut>,\n\n\t// Callbacks\n\t#[serde(skip)]\n\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\tpub on_update: WidgetCallback<TextButton>,\n\t#[serde(skip)]\n\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\tpub on_commit: WidgetCallback<()>,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder)]\n#[derivative(Debug, PartialEq)]\npub struct ImageButton {\n\t// Content\n\t#[widget_builder(constructor)]\n\tpub image: String,\n\tpub width: Option<String>,\n\tpub height: Option<String>,\n\n\t// Tooltips\n\t#[serde(rename = \"tooltipLabel\")]\n\tpub tooltip_label: String,\n\t#[serde(rename = \"tooltipDescription\")]\n\tpub tooltip_description: String,\n\t#[serde(rename = \"tooltipShortcut\")]\n\tpub tooltip_shortcut: Option<ActionShortcut>,\n\n\t// Callbacks\n\t#[serde(skip)]\n\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\tpub on_update: WidgetCallback<()>,\n\t#[serde(skip)]\n\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\tpub on_commit: WidgetCallback<()>,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder)]\n#[derivative(Debug, PartialEq, Default)]\npub struct ColorInput {\n\t// Content\n\t/// WARNING: The colors are gamma, not linear!\n\t#[widget_builder(constructor)]\n\tpub value: FillChoice,\n\t#[serde(rename = \"allowNone\")]\n\t#[derivative(Default(value = \"true\"))]\n\tpub allow_none: bool,\n\t// #[serde(rename = \"allowTransparency\")] pub allow_transparency: bool, // TODO: Implement\n\t#[serde(rename = \"menuDirection\")]\n\tpub menu_direction: Option<MenuDirection>,\n\tpub disabled: bool,\n\n\t// Styling\n\tpub narrow: bool,\n\n\t// Tooltips\n\t#[serde(rename = \"tooltipLabel\")]\n\tpub tooltip_label: String,\n\t#[serde(rename = \"tooltipDescription\")]\n\tpub tooltip_description: String,\n\t#[serde(rename = \"tooltipShortcut\")]\n\tpub tooltip_shortcut: Option<ActionShortcut>,\n\n\t// Callbacks\n\t#[serde(skip)]\n\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\tpub on_update: WidgetCallback<ColorInput>,\n\t#[serde(skip)]\n\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\tpub on_commit: WidgetCallback<()>,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder)]\n#[derivative(Debug, PartialEq)]\npub struct BreadcrumbTrailButtons {\n\t// Content\n\t#[widget_builder(constructor)]\n\tpub labels: Vec<String>,\n\tpub disabled: bool,\n\n\t// Tooltips\n\t#[serde(rename = \"tooltipLabel\")]\n\tpub tooltip_label: String,\n\t#[serde(rename = \"tooltipDescription\")]\n\tpub tooltip_description: String,\n\t#[serde(rename = \"tooltipShortcut\")]\n\tpub tooltip_shortcut: Option<ActionShortcut>,\n\n\t// Callbacks\n\t#[serde(skip)]\n\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\tpub on_update: WidgetCallback<u64>,\n\t#[serde(skip)]\n\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\tpub on_commit: WidgetCallback<()>,\n}\n"
  },
  {
    "path": "editor/src/messages/layout/utility_types/widgets/input_widgets.rs",
    "content": "use crate::messages::frontend::IconName;\nuse crate::messages::input_mapper::utility_types::misc::ActionShortcut;\nuse crate::messages::layout::utility_types::widget_prelude::*;\nuse crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;\nuse derivative::*;\nuse graphene_std::Color;\nuse graphene_std::raster::curve::Curve;\nuse graphene_std::transform::ReferencePoint;\nuse graphite_proc_macros::WidgetBuilder;\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder)]\n#[derivative(Debug, Default, PartialEq)]\npub struct CheckboxInput {\n\t// Content\n\t#[widget_builder(constructor)]\n\tpub checked: bool,\n\t#[widget_builder(string)]\n\tpub icon: Option<IconName>,\n\t#[serde(rename = \"forLabel\")]\n\tpub for_label: CheckboxId,\n\tpub disabled: bool,\n\n\t// Tooltips\n\t#[serde(rename = \"tooltipLabel\")]\n\tpub tooltip_label: String,\n\t#[serde(rename = \"tooltipDescription\")]\n\tpub tooltip_description: String,\n\t#[serde(rename = \"tooltipShortcut\")]\n\tpub tooltip_shortcut: Option<ActionShortcut>,\n\n\t// Callbacks\n\t#[serde(skip)]\n\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\tpub on_update: WidgetCallback<CheckboxInput>,\n\t#[serde(skip)]\n\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\tpub on_commit: WidgetCallback<()>,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]\npub struct CheckboxId(pub u64);\n\nimpl CheckboxId {\n\tpub fn new() -> Self {\n\t\tSelf(graphene_std::uuid::generate_uuid())\n\t}\n}\nimpl Default for CheckboxId {\n\tfn default() -> Self {\n\t\tSelf::new()\n\t}\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder)]\n#[derivative(Debug, PartialEq, Default)]\npub struct DropdownInput {\n\t// Content\n\t// This uses `u32` instead of `usize` since it will be serialized as a normal JS number (we can replace this with `usize` if we switch to a Rust-based GUI)\n\t#[serde(rename = \"selectedIndex\")]\n\tpub selected_index: Option<u32>,\n\t#[serde(rename = \"drawIcon\")]\n\tpub draw_icon: bool,\n\tpub disabled: bool,\n\n\t// Children\n\t#[widget_builder(constructor)]\n\tpub entries: MenuListEntrySections,\n\t#[serde(rename = \"entriesHash\")]\n\t#[widget_builder(skip)]\n\tpub entries_hash: u64,\n\n\t// Styling\n\tpub narrow: bool,\n\n\t// Behavior\n\t#[serde(rename = \"virtualScrolling\")]\n\tpub virtual_scrolling: bool,\n\t#[derivative(Default(value = \"true\"))]\n\tpub interactive: bool,\n\n\t// Sizing\n\t#[serde(rename = \"minWidth\")]\n\tpub min_width: u32,\n\t#[serde(rename = \"maxWidth\")]\n\tpub max_width: u32,\n\n\t// Tooltips\n\t#[serde(rename = \"tooltipLabel\")]\n\tpub tooltip_label: String,\n\t#[serde(rename = \"tooltipDescription\")]\n\tpub tooltip_description: String,\n\t#[serde(rename = \"tooltipShortcut\")]\n\tpub tooltip_shortcut: Option<ActionShortcut>,\n\t//\n\t// Callbacks exists on the `MenuListEntry` children, not this parent `DropdownInput`\n}\n\npub type MenuListEntrySections = Vec<Vec<MenuListEntry>>;\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify), tsify(large_number_types_as_bigints))]\n#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder)]\n#[derivative(Debug, PartialEq)]\n#[widget_builder(not_widget_instance)]\npub struct MenuListEntry {\n\t// Content\n\t#[widget_builder(constructor)]\n\tpub value: String,\n\tpub label: String,\n\t#[widget_builder(string)]\n\tpub icon: Option<IconName>,\n\tpub disabled: bool,\n\n\t// Children\n\tpub children: MenuListEntrySections,\n\t#[serde(rename = \"childrenHash\")]\n\t#[widget_builder(skip)]\n\tpub children_hash: u64,\n\n\t// Styling\n\tpub font: Option<String>,\n\n\t// Tooltips\n\t#[serde(rename = \"tooltipLabel\")]\n\tpub tooltip_label: String,\n\t#[serde(rename = \"tooltipDescription\")]\n\tpub tooltip_description: String,\n\t#[serde(rename = \"tooltipShortcut\")]\n\tpub tooltip_shortcut: Option<ActionShortcut>,\n\n\t// Callbacks\n\t#[serde(skip)]\n\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\tpub on_update: WidgetCallback<()>,\n\t#[serde(skip)]\n\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\tpub on_commit: WidgetCallback<()>,\n}\n\nimpl std::hash::Hash for MenuListEntry {\n\tfn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n\t\tself.value.hash(state);\n\t\tself.label.hash(state);\n\t\tself.icon.hash(state);\n\t\tself.disabled.hash(state);\n\t}\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder)]\n#[derivative(Debug, PartialEq, Default)]\npub struct NumberInput {\n\t// Content\n\t#[widget_builder(constructor)]\n\tpub value: Option<f64>,\n\tpub label: String,\n\tpub disabled: bool,\n\n\t// Styling\n\tpub narrow: bool,\n\n\t// Behavior\n\tpub mode: NumberInputMode,\n\t#[widget_builder(skip)]\n\tpub min: Option<f64>,\n\t#[widget_builder(skip)]\n\tpub max: Option<f64>,\n\t// TODO: Make this (and range_max) apply to both Range and Increment modes when dragging with the mouse\n\t#[serde(rename = \"rangeMin\")]\n\tpub range_min: Option<f64>,\n\t#[serde(rename = \"rangeMax\")]\n\tpub range_max: Option<f64>,\n\t#[derivative(Default(value = \"1.\"))]\n\tpub step: f64,\n\t#[serde(rename = \"isInteger\")]\n\tpub is_integer: bool,\n\t#[serde(rename = \"incrementBehavior\")]\n\tpub increment_behavior: NumberInputIncrementBehavior,\n\t#[serde(rename = \"displayDecimalPlaces\")]\n\t#[derivative(Default(value = \"2\"))]\n\tpub display_decimal_places: u32,\n\tpub unit: String,\n\t#[serde(rename = \"unitIsHiddenWhenEditing\")]\n\t#[derivative(Default(value = \"true\"))]\n\tpub unit_is_hidden_when_editing: bool,\n\n\t// Sizing\n\t#[serde(rename = \"minWidth\")]\n\tpub min_width: u32,\n\t#[serde(rename = \"maxWidth\")]\n\tpub max_width: u32,\n\n\t// Tooltips\n\t#[serde(rename = \"tooltipLabel\")]\n\tpub tooltip_label: String,\n\t#[serde(rename = \"tooltipDescription\")]\n\tpub tooltip_description: String,\n\t#[serde(rename = \"tooltipShortcut\")]\n\tpub tooltip_shortcut: Option<ActionShortcut>,\n\n\t// Callbacks\n\t#[serde(skip)]\n\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\tpub increment_callback_increase: WidgetCallback<NumberInput>,\n\t#[serde(skip)]\n\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\tpub increment_callback_decrease: WidgetCallback<NumberInput>,\n\t#[serde(skip)]\n\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\tpub on_update: WidgetCallback<NumberInput>,\n\t#[serde(skip)]\n\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\tpub on_commit: WidgetCallback<()>,\n}\n\nimpl NumberInput {\n\tpub fn int(mut self) -> Self {\n\t\tself.is_integer = true;\n\t\tself\n\t}\n\tpub fn min(mut self, val: f64) -> Self {\n\t\tself.min = Some(val);\n\t\tself.range_min = Some(val);\n\t\tself\n\t}\n\tpub fn max(mut self, val: f64) -> Self {\n\t\tself.max = Some(val);\n\t\tself.range_max = Some(val);\n\t\tself\n\t}\n\tpub fn mode_range(mut self) -> Self {\n\t\tself.mode = NumberInputMode::Range;\n\t\tself\n\t}\n\tpub fn mode_increment(mut self) -> Self {\n\t\tself.mode = NumberInputMode::Increment;\n\t\tself\n\t}\n\tpub fn increment_step(mut self, step: f64) -> Self {\n\t\tself.step = step;\n\t\tself\n\t}\n\tpub fn percentage(self) -> Self {\n\t\tself.min(0.).max(100.).mode_range().unit(\"%\").display_decimal_places(2)\n\t}\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, serde::Serialize, serde::Deserialize, Debug, Default, PartialEq, Eq)]\npub enum NumberInputIncrementBehavior {\n\t/// The value is added by `step`.\n\t#[default]\n\tAdd,\n\t/// The value is multiplied by `step`.\n\tMultiply,\n\t/// The functions `incrementCallbackIncrease` and `incrementCallbackDecrease` call custom behavior.\n\tCallback,\n\t/// The increment arrows are not shown.\n\tNone,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, serde::Serialize, serde::Deserialize, Debug, Default, PartialEq, Eq)]\npub enum NumberInputMode {\n\t#[default]\n\tIncrement,\n\tRange,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder)]\n#[derivative(Debug, PartialEq, Default)]\npub struct NodeCatalog {\n\t// Content\n\tpub disabled: bool,\n\n\t// Behavior\n\t#[serde(rename = \"initialSearchTerm\")]\n\tpub intial_search: String,\n\n\t// Callbacks\n\t#[serde(skip)]\n\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\tpub on_update: WidgetCallback<DefinitionIdentifier>,\n\t#[serde(skip)]\n\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\tpub on_commit: WidgetCallback<()>,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, Default, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder)]\n#[derivative(Debug, PartialEq)]\npub struct RadioInput {\n\t// Content\n\t// This uses `u32` instead of `usize` since it will be serialized as a normal JS number (replace this with `usize` after switching to a Rust-based GUI)\n\t#[serde(rename = \"selectedIndex\")]\n\tpub selected_index: Option<u32>,\n\tpub disabled: bool,\n\n\t// Children\n\t#[widget_builder(constructor)]\n\tpub entries: Vec<RadioEntryData>,\n\n\t// Styling\n\tpub narrow: bool,\n\n\t// Sizing\n\t#[serde(rename = \"minWidth\")]\n\tpub min_width: u32,\n\t//\n\t// Callbacks exists on the `RadioEntryData` children, not this parent `RadioInput`\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, Default, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder)]\n#[derivative(Debug, PartialEq)]\n#[widget_builder(not_widget_instance)]\npub struct RadioEntryData {\n\t// Content\n\t#[widget_builder(constructor)]\n\tpub value: String,\n\tpub label: String,\n\t#[widget_builder(string)]\n\tpub icon: Option<IconName>,\n\n\t// Tooltips\n\t#[serde(rename = \"tooltipLabel\")]\n\tpub tooltip_label: String,\n\t#[serde(rename = \"tooltipDescription\")]\n\tpub tooltip_description: String,\n\t#[serde(rename = \"tooltipShortcut\")]\n\tpub tooltip_shortcut: Option<ActionShortcut>,\n\n\t// Callbacks\n\t#[serde(skip)]\n\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\tpub on_update: WidgetCallback<()>,\n\t#[serde(skip)]\n\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\tpub on_commit: WidgetCallback<()>,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder)]\n#[derivative(Debug, PartialEq, Default)]\npub struct WorkingColorsInput {\n\t// Content\n\t#[widget_builder(constructor)]\n\tpub primary: Color,\n\t#[widget_builder(constructor)]\n\tpub secondary: Color,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder)]\n#[derivative(Debug, PartialEq, Default)]\npub struct TextAreaInput {\n\t// Content\n\t#[widget_builder(constructor)]\n\tpub value: String,\n\tpub label: Option<String>,\n\tpub disabled: bool,\n\n\t// Tooltips\n\t#[serde(rename = \"tooltipLabel\")]\n\tpub tooltip_label: String,\n\t#[serde(rename = \"tooltipDescription\")]\n\tpub tooltip_description: String,\n\t#[serde(rename = \"tooltipShortcut\")]\n\tpub tooltip_shortcut: Option<ActionShortcut>,\n\n\t// Callbacks\n\t#[serde(skip)]\n\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\tpub on_update: WidgetCallback<TextAreaInput>,\n\t#[serde(skip)]\n\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\tpub on_commit: WidgetCallback<()>,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder)]\n#[derivative(Debug, PartialEq, Default)]\npub struct TextInput {\n\t// Content\n\t#[widget_builder(constructor)]\n\tpub value: String,\n\tpub label: Option<String>,\n\tpub placeholder: Option<String>,\n\tpub disabled: bool,\n\n\t// Styling\n\tpub narrow: bool,\n\tpub centered: bool,\n\n\t// Sizing\n\t#[serde(rename = \"minWidth\")]\n\tpub min_width: u32,\n\t#[serde(rename = \"maxWidth\")]\n\tpub max_width: u32,\n\n\t// Tooltips\n\t#[serde(rename = \"tooltipLabel\")]\n\tpub tooltip_label: String,\n\t#[serde(rename = \"tooltipDescription\")]\n\tpub tooltip_description: String,\n\t#[serde(rename = \"tooltipShortcut\")]\n\tpub tooltip_shortcut: Option<ActionShortcut>,\n\n\t// Callbacks\n\t#[serde(skip)]\n\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\tpub on_update: WidgetCallback<TextInput>,\n\t#[serde(skip)]\n\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\tpub on_commit: WidgetCallback<()>,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder)]\n#[derivative(Debug, PartialEq, Default)]\npub struct CurveInput {\n\t// Content\n\t#[widget_builder(constructor)]\n\tpub value: Curve,\n\n\t// Tooltips\n\t#[serde(rename = \"tooltipLabel\")]\n\tpub tooltip_label: String,\n\t#[serde(rename = \"tooltipDescription\")]\n\tpub tooltip_description: String,\n\t#[serde(rename = \"tooltipShortcut\")]\n\tpub tooltip_shortcut: Option<ActionShortcut>,\n\n\t// Callbacks\n\t#[serde(skip)]\n\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\tpub on_update: WidgetCallback<CurveInput>,\n\t#[serde(skip)]\n\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\tpub on_commit: WidgetCallback<()>,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, Default, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder)]\n#[derivative(Debug, PartialEq)]\npub struct ReferencePointInput {\n\t// Content\n\t#[widget_builder(constructor)]\n\tpub value: ReferencePoint,\n\tpub disabled: bool,\n\n\t// Tooltips\n\t#[serde(rename = \"tooltipLabel\")]\n\tpub tooltip_label: String,\n\t#[serde(rename = \"tooltipDescription\")]\n\tpub tooltip_description: String,\n\t#[serde(rename = \"tooltipShortcut\")]\n\tpub tooltip_shortcut: Option<ActionShortcut>,\n\n\t// Callbacks\n\t#[serde(skip)]\n\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\tpub on_update: WidgetCallback<ReferencePointInput>,\n\t#[serde(skip)]\n\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\tpub on_commit: WidgetCallback<()>,\n}\n"
  },
  {
    "path": "editor/src/messages/layout/utility_types/widgets/label_widgets.rs",
    "content": "use super::input_widgets::CheckboxId;\nuse crate::messages::{frontend::IconName, input_mapper::utility_types::misc::ActionShortcut};\nuse derivative::*;\nuse graphite_proc_macros::WidgetBuilder;\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Debug, Default, PartialEq, Eq, WidgetBuilder)]\npub struct IconLabel {\n\t// Content\n\t#[widget_builder(constructor)]\n\t#[widget_builder(string)]\n\tpub icon: IconName,\n\tpub disabled: bool,\n\n\t// Tooltips\n\t#[serde(rename = \"tooltipLabel\")]\n\tpub tooltip_label: String,\n\t#[serde(rename = \"tooltipDescription\")]\n\tpub tooltip_description: String,\n\t#[serde(rename = \"tooltipShortcut\")]\n\tpub tooltip_shortcut: Option<ActionShortcut>,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, WidgetBuilder)]\npub struct Separator {\n\t// Content\n\tpub direction: SeparatorDirection,\n\t#[widget_builder(constructor)]\n\tpub style: SeparatorStyle,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]\npub enum SeparatorDirection {\n\t#[default]\n\tHorizontal,\n\tVertical,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]\npub enum SeparatorStyle {\n\tRelated,\n\t#[default]\n\tUnrelated,\n\tSection,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Debug, Eq, Default, WidgetBuilder)]\n#[derivative(PartialEq)]\npub struct TextLabel {\n\t// Content\n\t#[widget_builder(constructor)]\n\tpub value: String,\n\tpub disabled: bool,\n\t#[serde(rename = \"forCheckbox\")]\n\tpub for_checkbox: CheckboxId,\n\n\t// Styling\n\tpub narrow: bool,\n\tpub bold: bool,\n\tpub italic: bool,\n\tpub monospace: bool,\n\tpub multiline: bool,\n\t#[serde(rename = \"centerAlign\")]\n\tpub center_align: bool,\n\t#[serde(rename = \"tableAlign\")]\n\tpub table_align: bool,\n\n\t// Sizing\n\t#[serde(rename = \"minWidth\")]\n\tpub min_width: u32,\n\t#[serde(rename = \"minWidthCharacters\")]\n\tpub min_width_characters: u32,\n\n\t// Tooltips\n\t#[serde(rename = \"tooltipLabel\")]\n\tpub tooltip_label: String,\n\t#[serde(rename = \"tooltipDescription\")]\n\tpub tooltip_description: String,\n\t#[serde(rename = \"tooltipShortcut\")]\n\tpub tooltip_shortcut: Option<ActionShortcut>,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder)]\n#[derivative(Debug, PartialEq)]\npub struct ImageLabel {\n\t// Content\n\t#[widget_builder(constructor)]\n\tpub url: String,\n\tpub width: Option<String>,\n\tpub height: Option<String>,\n\n\t// Tooltips\n\t#[serde(rename = \"tooltipLabel\")]\n\tpub tooltip_label: String,\n\t#[serde(rename = \"tooltipDescription\")]\n\tpub tooltip_description: String,\n\t#[serde(rename = \"tooltipShortcut\")]\n\tpub tooltip_shortcut: Option<ActionShortcut>,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder)]\n#[derivative(Debug, PartialEq)]\npub struct ShortcutLabel {\n\t// Content\n\t// This is wrapped in an Option to satisfy the requirement that widgets implement Default\n\t#[widget_builder(constructor)]\n\tpub shortcut: Option<ActionShortcut>,\n}\n"
  },
  {
    "path": "editor/src/messages/layout/utility_types/widgets/mod.rs",
    "content": "pub mod button_widgets;\npub mod input_widgets;\npub mod label_widgets;\n"
  },
  {
    "path": "editor/src/messages/menu_bar/menu_bar_message.rs",
    "content": "use crate::messages::prelude::*;\n\n#[impl_message(Message, MenuBar)]\n#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)]\npub enum MenuBarMessage {\n\t// Messages\n\tSendLayout,\n}\n"
  },
  {
    "path": "editor/src/messages/menu_bar/menu_bar_message_handler.rs",
    "content": "use crate::messages::debug::utility_types::MessageLoggingVerbosity;\nuse crate::messages::input_mapper::utility_types::macros::action_shortcut;\nuse crate::messages::layout::utility_types::widget_prelude::*;\nuse crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis, GroupFolderType};\nuse crate::messages::prelude::*;\nuse graphene_std::vector::misc::BooleanOperation;\n\n#[derive(Debug, Clone, Default, ExtractField)]\npub struct MenuBarMessageHandler {\n\tpub has_active_document: bool,\n\tpub canvas_tilted: bool,\n\tpub canvas_flipped: bool,\n\tpub rulers_visible: bool,\n\tpub node_graph_open: bool,\n\tpub has_selected_nodes: bool,\n\tpub has_selected_layers: bool,\n\tpub has_selection_history: (bool, bool),\n\tpub message_logging_verbosity: MessageLoggingVerbosity,\n\tpub reset_node_definitions_on_open: bool,\n\tpub make_path_editable_is_allowed: bool,\n\tpub data_panel_open: bool,\n\tpub layers_panel_open: bool,\n\tpub properties_panel_open: bool,\n\tpub focus_document: bool,\n}\n\n#[message_handler_data]\nimpl MessageHandler<MenuBarMessage, ()> for MenuBarMessageHandler {\n\tfn process_message(&mut self, message: MenuBarMessage, responses: &mut VecDeque<Message>, _: ()) {\n\t\tmatch message {\n\t\t\tMenuBarMessage::SendLayout => {\n\t\t\t\tself.send_layout(responses, LayoutTarget::MenuBar);\n\t\t\t}\n\t\t}\n\t}\n\n\tfn actions(&self) -> ActionList {\n\t\tactions!(MenuBarMessageDiscriminant;)\n\t}\n}\n\nimpl LayoutHolder for MenuBarMessageHandler {\n\tfn layout(&self) -> Layout {\n\t\tlet no_active_document = !self.has_active_document;\n\t\tlet node_graph_open = self.node_graph_open;\n\t\tlet has_selected_nodes = self.has_selected_nodes;\n\t\tlet has_selected_layers = self.has_selected_layers;\n\t\tlet has_selection_history = self.has_selection_history;\n\t\tlet message_logging_verbosity_off = self.message_logging_verbosity == MessageLoggingVerbosity::Off;\n\t\tlet message_logging_verbosity_names = self.message_logging_verbosity == MessageLoggingVerbosity::Names;\n\t\tlet message_logging_verbosity_contents = self.message_logging_verbosity == MessageLoggingVerbosity::Contents;\n\t\tlet reset_node_definitions_on_open = self.reset_node_definitions_on_open;\n\t\tlet make_path_editable_is_allowed = self.make_path_editable_is_allowed;\n\n\t\tlet about = MenuListEntry::new(\"About Graphite…\")\n\t\t\t.label({\n\t\t\t\t#[cfg(not(target_os = \"macos\"))]\n\t\t\t\t{\n\t\t\t\t\t\"About Graphite…\"\n\t\t\t\t}\n\t\t\t\t#[cfg(target_os = \"macos\")]\n\t\t\t\t{\n\t\t\t\t\t\"About Graphite\"\n\t\t\t\t}\n\t\t\t})\n\t\t\t.icon(\"GraphiteLogo\")\n\t\t\t.on_commit(|_| DialogMessage::RequestAboutGraphiteDialog.into());\n\t\tlet preferences = MenuListEntry::new(\"Preferences…\")\n\t\t\t.label(\"Preferences…\")\n\t\t\t.icon(\"Settings\")\n\t\t\t.tooltip_shortcut(action_shortcut!(DialogMessageDiscriminant::RequestPreferencesDialog))\n\t\t\t.on_commit(|_| DialogMessage::RequestPreferencesDialog.into());\n\n\t\tlet menu_bar_buttons = vec![\n\t\t\t#[cfg(not(target_os = \"macos\"))]\n\t\t\tTextButton::new(\"Graphite\")\n\t\t\t\t.label(\"\")\n\t\t\t\t.flush(true)\n\t\t\t\t.icon(\"GraphiteLogo\")\n\t\t\t\t.on_commit(|_| FrontendMessage::TriggerVisitLink { url: \"https://graphite.art\".into() }.into())\n\t\t\t\t.widget_instance(),\n\t\t\t#[cfg(target_os = \"macos\")]\n\t\t\tTextButton::new(\"Graphite\")\n\t\t\t\t.label(\"\")\n\t\t\t\t.flush(true)\n\t\t\t\t.menu_list_children(vec![\n\t\t\t\t\tvec![about],\n\t\t\t\t\tvec![preferences],\n\t\t\t\t\tvec![\n\t\t\t\t\t\tMenuListEntry::new(\"Hide Graphite\")\n\t\t\t\t\t\t\t.label(\"Hide Graphite\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(AppWindowMessageDiscriminant::Hide))\n\t\t\t\t\t\t\t.on_commit(|_| AppWindowMessage::Hide.into()),\n\t\t\t\t\t\tMenuListEntry::new(\"Hide Others\")\n\t\t\t\t\t\t\t.label(\"Hide Others\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(AppWindowMessageDiscriminant::HideOthers))\n\t\t\t\t\t\t\t.on_commit(|_| AppWindowMessage::HideOthers.into()),\n\t\t\t\t\t\tMenuListEntry::new(\"Show All\")\n\t\t\t\t\t\t\t.label(\"Show All\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(AppWindowMessageDiscriminant::ShowAll))\n\t\t\t\t\t\t\t.on_commit(|_| AppWindowMessage::ShowAll.into()),\n\t\t\t\t\t],\n\t\t\t\t\tvec![\n\t\t\t\t\t\tMenuListEntry::new(\"Quit Graphite\")\n\t\t\t\t\t\t\t.label(\"Quit Graphite\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(AppWindowMessageDiscriminant::Close))\n\t\t\t\t\t\t\t.on_commit(|_| AppWindowMessage::Close.into()),\n\t\t\t\t\t],\n\t\t\t\t])\n\t\t\t\t.widget_instance(),\n\t\t\tTextButton::new(\"File\")\n\t\t\t\t.label(\"File\")\n\t\t\t\t.flush(true)\n\t\t\t\t.menu_list_children(vec![\n\t\t\t\t\tvec![\n\t\t\t\t\t\tMenuListEntry::new(\"New…\")\n\t\t\t\t\t\t\t.label(\"New…\")\n\t\t\t\t\t\t\t.icon(\"File\")\n\t\t\t\t\t\t\t.on_commit(|_| DialogMessage::RequestNewDocumentDialog.into())\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(DialogMessageDiscriminant::RequestNewDocumentDialog)),\n\t\t\t\t\t\tMenuListEntry::new(\"Open…\")\n\t\t\t\t\t\t\t.label(\"Open…\")\n\t\t\t\t\t\t\t.icon(\"Folder\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(PortfolioMessageDiscriminant::Open))\n\t\t\t\t\t\t\t.on_commit(|_| PortfolioMessage::Open.into()),\n\t\t\t\t\t\tMenuListEntry::new(\"Open Demo Artwork…\")\n\t\t\t\t\t\t\t.label(\"Open Demo Artwork…\")\n\t\t\t\t\t\t\t.icon(\"Image\")\n\t\t\t\t\t\t\t.on_commit(|_| DialogMessage::RequestDemoArtworkDialog.into()),\n\t\t\t\t\t],\n\t\t\t\t\tvec![\n\t\t\t\t\t\tMenuListEntry::new(\"Close\")\n\t\t\t\t\t\t\t.label(\"Close\")\n\t\t\t\t\t\t\t.icon(\"Close\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(PortfolioMessageDiscriminant::CloseActiveDocumentWithConfirmation))\n\t\t\t\t\t\t\t.on_commit(|_| PortfolioMessage::CloseActiveDocumentWithConfirmation.into())\n\t\t\t\t\t\t\t.disabled(no_active_document),\n\t\t\t\t\t\tMenuListEntry::new(\"Close All\")\n\t\t\t\t\t\t\t.label(\"Close All\")\n\t\t\t\t\t\t\t.icon(\"CloseAll\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(PortfolioMessageDiscriminant::CloseAllDocumentsWithConfirmation))\n\t\t\t\t\t\t\t.on_commit(|_| PortfolioMessage::CloseAllDocumentsWithConfirmation.into())\n\t\t\t\t\t\t\t.disabled(no_active_document),\n\t\t\t\t\t],\n\t\t\t\t\tvec![\n\t\t\t\t\t\tMenuListEntry::new(\"Save\")\n\t\t\t\t\t\t\t.label(\"Save\")\n\t\t\t\t\t\t\t.icon(\"Save\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::SaveDocument))\n\t\t\t\t\t\t\t.on_commit(|_| DocumentMessage::SaveDocument.into())\n\t\t\t\t\t\t\t.disabled(no_active_document),\n\t\t\t\t\t\t#[cfg(not(target_family = \"wasm\"))]\n\t\t\t\t\t\tMenuListEntry::new(\"Save As…\")\n\t\t\t\t\t\t\t.label(\"Save As…\")\n\t\t\t\t\t\t\t.icon(\"Save\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::SaveDocumentAs))\n\t\t\t\t\t\t\t.on_commit(|_| DocumentMessage::SaveDocumentAs.into())\n\t\t\t\t\t\t\t.disabled(no_active_document),\n\t\t\t\t\t],\n\t\t\t\t\tvec![\n\t\t\t\t\t\tMenuListEntry::new(\"Import…\")\n\t\t\t\t\t\t\t.label(\"Import…\")\n\t\t\t\t\t\t\t.icon(\"FileImport\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(PortfolioMessageDiscriminant::Import))\n\t\t\t\t\t\t\t.on_commit(|_| PortfolioMessage::Import.into())\n\t\t\t\t\t\t\t.disabled(no_active_document),\n\t\t\t\t\t\tMenuListEntry::new(\"Export…\")\n\t\t\t\t\t\t\t.label(\"Export…\")\n\t\t\t\t\t\t\t.icon(\"FileExport\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(DialogMessageDiscriminant::RequestExportDialog))\n\t\t\t\t\t\t\t.on_commit(|_| DialogMessage::RequestExportDialog.into())\n\t\t\t\t\t\t\t.disabled(no_active_document),\n\t\t\t\t\t],\n\t\t\t\t\t#[cfg(not(target_os = \"macos\"))]\n\t\t\t\t\tvec![preferences],\n\t\t\t\t])\n\t\t\t\t.widget_instance(),\n\t\t\tTextButton::new(\"Edit\")\n\t\t\t\t.label(\"Edit\")\n\t\t\t\t.flush(true)\n\t\t\t\t.menu_list_children(vec![\n\t\t\t\t\tvec![\n\t\t\t\t\t\tMenuListEntry::new(\"Undo\")\n\t\t\t\t\t\t\t.label(\"Undo\")\n\t\t\t\t\t\t\t.icon(\"HistoryUndo\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::Undo))\n\t\t\t\t\t\t\t.on_commit(|_| DocumentMessage::Undo.into())\n\t\t\t\t\t\t\t.disabled(no_active_document),\n\t\t\t\t\t\tMenuListEntry::new(\"Redo\")\n\t\t\t\t\t\t\t.label(\"Redo\")\n\t\t\t\t\t\t\t.icon(\"HistoryRedo\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::Redo))\n\t\t\t\t\t\t\t.on_commit(|_| DocumentMessage::Redo.into())\n\t\t\t\t\t\t\t.disabled(no_active_document),\n\t\t\t\t\t],\n\t\t\t\t\tvec![\n\t\t\t\t\t\tMenuListEntry::new(\"Cut\")\n\t\t\t\t\t\t\t.label(\"Cut\")\n\t\t\t\t\t\t\t.icon(\"Cut\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(ClipboardMessageDiscriminant::Cut))\n\t\t\t\t\t\t\t.on_commit(|_| ClipboardMessage::Cut.into())\n\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_layers),\n\t\t\t\t\t\tMenuListEntry::new(\"Copy\")\n\t\t\t\t\t\t\t.label(\"Copy\")\n\t\t\t\t\t\t\t.icon(\"Copy\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(ClipboardMessageDiscriminant::Copy))\n\t\t\t\t\t\t\t.on_commit(|_| ClipboardMessage::Copy.into())\n\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_layers),\n\t\t\t\t\t\tMenuListEntry::new(\"Paste\")\n\t\t\t\t\t\t\t.label(\"Paste\")\n\t\t\t\t\t\t\t.icon(\"Paste\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(ClipboardMessageDiscriminant::Paste))\n\t\t\t\t\t\t\t.on_commit(|_| ClipboardMessage::Paste.into())\n\t\t\t\t\t\t\t.disabled(no_active_document),\n\t\t\t\t\t],\n\t\t\t\t\tvec![\n\t\t\t\t\t\tMenuListEntry::new(\"Duplicate\")\n\t\t\t\t\t\t\t.label(\"Duplicate\")\n\t\t\t\t\t\t\t.icon(\"Copy\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::DuplicateSelectedLayers))\n\t\t\t\t\t\t\t.on_commit(|_| DocumentMessage::DuplicateSelectedLayers.into())\n\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_nodes),\n\t\t\t\t\t\tMenuListEntry::new(\"Delete\")\n\t\t\t\t\t\t\t.label(\"Delete\")\n\t\t\t\t\t\t\t.icon(\"Trash\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::DeleteSelectedLayers))\n\t\t\t\t\t\t\t.on_commit(|_| DocumentMessage::DeleteSelectedLayers.into())\n\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_nodes),\n\t\t\t\t\t],\n\t\t\t\t\tvec![\n\t\t\t\t\t\tMenuListEntry::new(\"Convert to Infinite Canvas\")\n\t\t\t\t\t\t\t.label(\"Convert to Infinite Canvas\")\n\t\t\t\t\t\t\t.icon(\"Artboard\")\n\t\t\t\t\t\t\t.on_commit(|_| DocumentMessage::RemoveArtboards.into())\n\t\t\t\t\t\t\t.disabled(no_active_document),\n\t\t\t\t\t],\n\t\t\t\t])\n\t\t\t\t.widget_instance(),\n\t\t\tTextButton::new(\"Layer\")\n\t\t\t\t.label(\"Layer\")\n\t\t\t\t.flush(true)\n\t\t\t\t.menu_list_children(vec![\n\t\t\t\t\tvec![\n\t\t\t\t\t\tMenuListEntry::new(\"New\")\n\t\t\t\t\t\t\t.label(\"New\")\n\t\t\t\t\t\t\t.icon(\"NewLayer\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::CreateEmptyFolder))\n\t\t\t\t\t\t\t.on_commit(|_| DocumentMessage::CreateEmptyFolder.into())\n\t\t\t\t\t\t\t.disabled(no_active_document),\n\t\t\t\t\t],\n\t\t\t\t\tvec![\n\t\t\t\t\t\tMenuListEntry::new(\"Group\")\n\t\t\t\t\t\t\t.label(\"Group\")\n\t\t\t\t\t\t\t.icon(\"Folder\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::GroupSelectedLayers))\n\t\t\t\t\t\t\t.on_commit(|_| {\n\t\t\t\t\t\t\t\tDocumentMessage::GroupSelectedLayers {\n\t\t\t\t\t\t\t\t\tgroup_folder_type: GroupFolderType::Layer,\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t.into()\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_layers),\n\t\t\t\t\t\tMenuListEntry::new(\"Ungroup\")\n\t\t\t\t\t\t\t.label(\"Ungroup\")\n\t\t\t\t\t\t\t.icon(\"FolderOpen\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::UngroupSelectedLayers))\n\t\t\t\t\t\t\t.on_commit(|_| DocumentMessage::UngroupSelectedLayers.into())\n\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_layers),\n\t\t\t\t\t],\n\t\t\t\t\tvec![\n\t\t\t\t\t\tMenuListEntry::new(\"Hide/Show\")\n\t\t\t\t\t\t\t.label(\"Hide/Show\")\n\t\t\t\t\t\t\t.icon(\"EyeHide\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::ToggleSelectedVisibility))\n\t\t\t\t\t\t\t.on_commit(|_| DocumentMessage::ToggleSelectedVisibility.into())\n\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_layers),\n\t\t\t\t\t\tMenuListEntry::new(\"Lock/Unlock\")\n\t\t\t\t\t\t\t.label(\"Lock/Unlock\")\n\t\t\t\t\t\t\t.icon(\"PadlockLocked\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::ToggleSelectedLocked))\n\t\t\t\t\t\t\t.on_commit(|_| DocumentMessage::ToggleSelectedLocked.into())\n\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_layers),\n\t\t\t\t\t],\n\t\t\t\t\tvec![\n\t\t\t\t\t\tMenuListEntry::new(\"Grab\")\n\t\t\t\t\t\t\t.label(\"Grab\")\n\t\t\t\t\t\t\t.icon(\"TransformationGrab\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(TransformLayerMessageDiscriminant::BeginGrab))\n\t\t\t\t\t\t\t.on_commit(|_| TransformLayerMessage::BeginGrab.into())\n\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_layers),\n\t\t\t\t\t\tMenuListEntry::new(\"Rotate\")\n\t\t\t\t\t\t\t.label(\"Rotate\")\n\t\t\t\t\t\t\t.icon(\"TransformationRotate\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(TransformLayerMessageDiscriminant::BeginRotate))\n\t\t\t\t\t\t\t.on_commit(|_| TransformLayerMessage::BeginRotate.into())\n\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_layers),\n\t\t\t\t\t\tMenuListEntry::new(\"Scale\")\n\t\t\t\t\t\t\t.label(\"Scale\")\n\t\t\t\t\t\t\t.icon(\"TransformationScale\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(TransformLayerMessageDiscriminant::BeginScale))\n\t\t\t\t\t\t\t.on_commit(|_| TransformLayerMessage::BeginScale.into())\n\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_layers),\n\t\t\t\t\t],\n\t\t\t\t\tvec![\n\t\t\t\t\t\tMenuListEntry::new(\"Arrange\")\n\t\t\t\t\t\t\t.label(\"Arrange\")\n\t\t\t\t\t\t\t.icon(\"StackHollow\")\n\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_layers)\n\t\t\t\t\t\t\t.children(vec![\n\t\t\t\t\t\t\t\tvec![\n\t\t\t\t\t\t\t\t\tMenuListEntry::new(\"Raise To Front\")\n\t\t\t\t\t\t\t\t\t\t.label(\"Raise To Front\")\n\t\t\t\t\t\t\t\t\t\t.icon(\"Stack\")\n\t\t\t\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::SelectedLayersRaiseToFront))\n\t\t\t\t\t\t\t\t\t\t.on_commit(|_| DocumentMessage::SelectedLayersRaiseToFront.into())\n\t\t\t\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_layers),\n\t\t\t\t\t\t\t\t\tMenuListEntry::new(\"Raise\")\n\t\t\t\t\t\t\t\t\t\t.label(\"Raise\")\n\t\t\t\t\t\t\t\t\t\t.icon(\"StackRaise\")\n\t\t\t\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::SelectedLayersRaise))\n\t\t\t\t\t\t\t\t\t\t.on_commit(|_| DocumentMessage::SelectedLayersRaise.into())\n\t\t\t\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_layers),\n\t\t\t\t\t\t\t\t\tMenuListEntry::new(\"Lower\")\n\t\t\t\t\t\t\t\t\t\t.label(\"Lower\")\n\t\t\t\t\t\t\t\t\t\t.icon(\"StackLower\")\n\t\t\t\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::SelectedLayersLower))\n\t\t\t\t\t\t\t\t\t\t.on_commit(|_| DocumentMessage::SelectedLayersLower.into())\n\t\t\t\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_layers),\n\t\t\t\t\t\t\t\t\tMenuListEntry::new(\"Lower to Back\")\n\t\t\t\t\t\t\t\t\t\t.label(\"Lower to Back\")\n\t\t\t\t\t\t\t\t\t\t.icon(\"StackBottom\")\n\t\t\t\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::SelectedLayersLowerToBack))\n\t\t\t\t\t\t\t\t\t\t.on_commit(|_| DocumentMessage::SelectedLayersLowerToBack.into())\n\t\t\t\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_layers),\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tvec![\n\t\t\t\t\t\t\t\t\tMenuListEntry::new(\"Reverse\")\n\t\t\t\t\t\t\t\t\t\t.label(\"Reverse\")\n\t\t\t\t\t\t\t\t\t\t.icon(\"StackReverse\")\n\t\t\t\t\t\t\t\t\t\t.on_commit(|_| DocumentMessage::SelectedLayersReverse.into())\n\t\t\t\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_layers),\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t]),\n\t\t\t\t\t\tMenuListEntry::new(\"Align\")\n\t\t\t\t\t\t\t.label(\"Align\")\n\t\t\t\t\t\t\t.icon(\"AlignVerticalCenter\")\n\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_layers)\n\t\t\t\t\t\t\t.children(vec![\n\t\t\t\t\t\t\t\tvec![\n\t\t\t\t\t\t\t\t\tMenuListEntry::new(\"Align Left\")\n\t\t\t\t\t\t\t\t\t\t.label(\"Align Left\")\n\t\t\t\t\t\t\t\t\t\t.icon(\"AlignLeft\")\n\t\t\t\t\t\t\t\t\t\t.on_commit(|_| {\n\t\t\t\t\t\t\t\t\t\t\tDocumentMessage::AlignSelectedLayers {\n\t\t\t\t\t\t\t\t\t\t\t\taxis: AlignAxis::X,\n\t\t\t\t\t\t\t\t\t\t\t\taggregate: AlignAggregate::Min,\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t.into()\n\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_layers),\n\t\t\t\t\t\t\t\t\tMenuListEntry::new(\"Align Horizontal Center\")\n\t\t\t\t\t\t\t\t\t\t.label(\"Align Horizontal Center\")\n\t\t\t\t\t\t\t\t\t\t.icon(\"AlignHorizontalCenter\")\n\t\t\t\t\t\t\t\t\t\t.on_commit(|_| {\n\t\t\t\t\t\t\t\t\t\t\tDocumentMessage::AlignSelectedLayers {\n\t\t\t\t\t\t\t\t\t\t\t\taxis: AlignAxis::X,\n\t\t\t\t\t\t\t\t\t\t\t\taggregate: AlignAggregate::Center,\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t.into()\n\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_layers),\n\t\t\t\t\t\t\t\t\tMenuListEntry::new(\"Align Right\")\n\t\t\t\t\t\t\t\t\t\t.label(\"Align Right\")\n\t\t\t\t\t\t\t\t\t\t.icon(\"AlignRight\")\n\t\t\t\t\t\t\t\t\t\t.on_commit(|_| {\n\t\t\t\t\t\t\t\t\t\t\tDocumentMessage::AlignSelectedLayers {\n\t\t\t\t\t\t\t\t\t\t\t\taxis: AlignAxis::X,\n\t\t\t\t\t\t\t\t\t\t\t\taggregate: AlignAggregate::Max,\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t.into()\n\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_layers),\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tvec![\n\t\t\t\t\t\t\t\t\tMenuListEntry::new(\"Align Top\")\n\t\t\t\t\t\t\t\t\t\t.label(\"Align Top\")\n\t\t\t\t\t\t\t\t\t\t.icon(\"AlignTop\")\n\t\t\t\t\t\t\t\t\t\t.on_commit(|_| {\n\t\t\t\t\t\t\t\t\t\t\tDocumentMessage::AlignSelectedLayers {\n\t\t\t\t\t\t\t\t\t\t\t\taxis: AlignAxis::Y,\n\t\t\t\t\t\t\t\t\t\t\t\taggregate: AlignAggregate::Min,\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t.into()\n\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_layers),\n\t\t\t\t\t\t\t\t\tMenuListEntry::new(\"Align Vertical Center\")\n\t\t\t\t\t\t\t\t\t\t.label(\"Align Vertical Center\")\n\t\t\t\t\t\t\t\t\t\t.icon(\"AlignVerticalCenter\")\n\t\t\t\t\t\t\t\t\t\t.on_commit(|_| {\n\t\t\t\t\t\t\t\t\t\t\tDocumentMessage::AlignSelectedLayers {\n\t\t\t\t\t\t\t\t\t\t\t\taxis: AlignAxis::Y,\n\t\t\t\t\t\t\t\t\t\t\t\taggregate: AlignAggregate::Center,\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t.into()\n\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_layers),\n\t\t\t\t\t\t\t\t\tMenuListEntry::new(\"Align Bottom\")\n\t\t\t\t\t\t\t\t\t\t.label(\"Align Bottom\")\n\t\t\t\t\t\t\t\t\t\t.icon(\"AlignBottom\")\n\t\t\t\t\t\t\t\t\t\t.on_commit(|_| {\n\t\t\t\t\t\t\t\t\t\t\tDocumentMessage::AlignSelectedLayers {\n\t\t\t\t\t\t\t\t\t\t\t\taxis: AlignAxis::Y,\n\t\t\t\t\t\t\t\t\t\t\t\taggregate: AlignAggregate::Max,\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t.into()\n\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_layers),\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t]),\n\t\t\t\t\t\tMenuListEntry::new(\"Flip\")\n\t\t\t\t\t\t\t.label(\"Flip\")\n\t\t\t\t\t\t\t.icon(\"FlipVertical\")\n\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_layers)\n\t\t\t\t\t\t\t.children(vec![vec![\n\t\t\t\t\t\t\t\tMenuListEntry::new(\"Flip Horizontal\")\n\t\t\t\t\t\t\t\t\t.label(\"Flip Horizontal\")\n\t\t\t\t\t\t\t\t\t.icon(\"FlipHorizontal\")\n\t\t\t\t\t\t\t\t\t.on_commit(|_| DocumentMessage::FlipSelectedLayers { flip_axis: FlipAxis::X }.into())\n\t\t\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_layers),\n\t\t\t\t\t\t\t\tMenuListEntry::new(\"Flip Vertical\")\n\t\t\t\t\t\t\t\t\t.label(\"Flip Vertical\")\n\t\t\t\t\t\t\t\t\t.icon(\"FlipVertical\")\n\t\t\t\t\t\t\t\t\t.on_commit(|_| DocumentMessage::FlipSelectedLayers { flip_axis: FlipAxis::Y }.into())\n\t\t\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_layers),\n\t\t\t\t\t\t\t]]),\n\t\t\t\t\t\tMenuListEntry::new(\"Turn\")\n\t\t\t\t\t\t\t.label(\"Turn\")\n\t\t\t\t\t\t\t.icon(\"TurnPositive90\")\n\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_layers)\n\t\t\t\t\t\t\t.children(vec![vec![\n\t\t\t\t\t\t\t\tMenuListEntry::new(\"Turn -90°\")\n\t\t\t\t\t\t\t\t\t.label(\"Turn -90°\")\n\t\t\t\t\t\t\t\t\t.icon(\"TurnNegative90\")\n\t\t\t\t\t\t\t\t\t.on_commit(|_| DocumentMessage::RotateSelectedLayers { degrees: -90. }.into())\n\t\t\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_layers),\n\t\t\t\t\t\t\t\tMenuListEntry::new(\"Turn 90°\")\n\t\t\t\t\t\t\t\t\t.label(\"Turn 90°\")\n\t\t\t\t\t\t\t\t\t.icon(\"TurnPositive90\")\n\t\t\t\t\t\t\t\t\t.on_commit(|_| DocumentMessage::RotateSelectedLayers { degrees: 90. }.into())\n\t\t\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_layers),\n\t\t\t\t\t\t\t]]),\n\t\t\t\t\t\tMenuListEntry::new(\"Boolean\")\n\t\t\t\t\t\t\t.label(\"Boolean\")\n\t\t\t\t\t\t\t.icon(\"BooleanSubtractFront\")\n\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_layers)\n\t\t\t\t\t\t\t.children(vec![vec![\n\t\t\t\t\t\t\t\tMenuListEntry::new(\"Union\")\n\t\t\t\t\t\t\t\t\t.label(\"Union\")\n\t\t\t\t\t\t\t\t\t.icon(\"BooleanUnion\")\n\t\t\t\t\t\t\t\t\t.on_commit(|_| {\n\t\t\t\t\t\t\t\t\t\tlet group_folder_type = GroupFolderType::BooleanOperation(BooleanOperation::Union);\n\t\t\t\t\t\t\t\t\t\tDocumentMessage::GroupSelectedLayers { group_folder_type }.into()\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_layers),\n\t\t\t\t\t\t\t\tMenuListEntry::new(\"Subtract Front\")\n\t\t\t\t\t\t\t\t\t.label(\"Subtract Front\")\n\t\t\t\t\t\t\t\t\t.icon(\"BooleanSubtractFront\")\n\t\t\t\t\t\t\t\t\t.on_commit(|_| {\n\t\t\t\t\t\t\t\t\t\tlet group_folder_type = GroupFolderType::BooleanOperation(BooleanOperation::SubtractFront);\n\t\t\t\t\t\t\t\t\t\tDocumentMessage::GroupSelectedLayers { group_folder_type }.into()\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_layers),\n\t\t\t\t\t\t\t\tMenuListEntry::new(\"Subtract Back\")\n\t\t\t\t\t\t\t\t\t.label(\"Subtract Back\")\n\t\t\t\t\t\t\t\t\t.icon(\"BooleanSubtractBack\")\n\t\t\t\t\t\t\t\t\t.on_commit(|_| {\n\t\t\t\t\t\t\t\t\t\tlet group_folder_type = GroupFolderType::BooleanOperation(BooleanOperation::SubtractBack);\n\t\t\t\t\t\t\t\t\t\tDocumentMessage::GroupSelectedLayers { group_folder_type }.into()\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_layers),\n\t\t\t\t\t\t\t\tMenuListEntry::new(\"Intersect\")\n\t\t\t\t\t\t\t\t\t.label(\"Intersect\")\n\t\t\t\t\t\t\t\t\t.icon(\"BooleanIntersect\")\n\t\t\t\t\t\t\t\t\t.on_commit(|_| {\n\t\t\t\t\t\t\t\t\t\tlet group_folder_type = GroupFolderType::BooleanOperation(BooleanOperation::Intersect);\n\t\t\t\t\t\t\t\t\t\tDocumentMessage::GroupSelectedLayers { group_folder_type }.into()\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_layers),\n\t\t\t\t\t\t\t\tMenuListEntry::new(\"Difference\")\n\t\t\t\t\t\t\t\t\t.label(\"Difference\")\n\t\t\t\t\t\t\t\t\t.icon(\"BooleanDifference\")\n\t\t\t\t\t\t\t\t\t.on_commit(|_| {\n\t\t\t\t\t\t\t\t\t\tlet group_folder_type = GroupFolderType::BooleanOperation(BooleanOperation::Difference);\n\t\t\t\t\t\t\t\t\t\tDocumentMessage::GroupSelectedLayers { group_folder_type }.into()\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_layers),\n\t\t\t\t\t\t\t]]),\n\t\t\t\t\t],\n\t\t\t\t\tvec![\n\t\t\t\t\t\tMenuListEntry::new(\"Make Path Editable\")\n\t\t\t\t\t\t\t.label(\"Make Path Editable\")\n\t\t\t\t\t\t\t.icon(\"NodeShape\")\n\t\t\t\t\t\t\t.on_commit(|_| NodeGraphMessage::AddPathNode.into())\n\t\t\t\t\t\t\t.disabled(!make_path_editable_is_allowed),\n\t\t\t\t\t],\n\t\t\t\t])\n\t\t\t\t.widget_instance(),\n\t\t\tTextButton::new(\"Select\")\n\t\t\t\t.label(\"Select\")\n\t\t\t\t.flush(true)\n\t\t\t\t.menu_list_children(vec![\n\t\t\t\t\tvec![\n\t\t\t\t\t\tMenuListEntry::new(\"Select All\")\n\t\t\t\t\t\t\t.label(\"Select All\")\n\t\t\t\t\t\t\t.icon(\"SelectAll\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::SelectAllLayers))\n\t\t\t\t\t\t\t.on_commit(|_| DocumentMessage::SelectAllLayers.into())\n\t\t\t\t\t\t\t.disabled(no_active_document),\n\t\t\t\t\t\tMenuListEntry::new(\"Deselect All\")\n\t\t\t\t\t\t\t.label(\"Deselect All\")\n\t\t\t\t\t\t\t.icon(\"DeselectAll\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::DeselectAllLayers))\n\t\t\t\t\t\t\t.on_commit(|_| DocumentMessage::DeselectAllLayers.into())\n\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_nodes),\n\t\t\t\t\t\tMenuListEntry::new(\"Select Parent\")\n\t\t\t\t\t\t\t.label(\"Select Parent\")\n\t\t\t\t\t\t\t.icon(\"SelectParent\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::SelectParentLayer))\n\t\t\t\t\t\t\t.on_commit(|_| DocumentMessage::SelectParentLayer.into())\n\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_nodes),\n\t\t\t\t\t],\n\t\t\t\t\tvec![\n\t\t\t\t\t\tMenuListEntry::new(\"Previous Selection\")\n\t\t\t\t\t\t\t.label(\"Previous Selection\")\n\t\t\t\t\t\t\t.icon(\"HistoryUndo\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::SelectionStepBack))\n\t\t\t\t\t\t\t.on_commit(|_| DocumentMessage::SelectionStepBack.into())\n\t\t\t\t\t\t\t.disabled(!has_selection_history.0),\n\t\t\t\t\t\tMenuListEntry::new(\"Next Selection\")\n\t\t\t\t\t\t\t.label(\"Next Selection\")\n\t\t\t\t\t\t\t.icon(\"HistoryRedo\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::SelectionStepForward))\n\t\t\t\t\t\t\t.on_commit(|_| DocumentMessage::SelectionStepForward.into())\n\t\t\t\t\t\t\t.disabled(!has_selection_history.1),\n\t\t\t\t\t],\n\t\t\t\t])\n\t\t\t\t.widget_instance(),\n\t\t\tTextButton::new(\"View\")\n\t\t\t\t.label(\"View\")\n\t\t\t\t.flush(true)\n\t\t\t\t.menu_list_children(vec![\n\t\t\t\t\tvec![\n\t\t\t\t\t\tMenuListEntry::new(\"Tilt\")\n\t\t\t\t\t\t\t.label(\"Tilt\")\n\t\t\t\t\t\t\t.icon(\"Tilt\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(NavigationMessageDiscriminant::BeginCanvasTilt))\n\t\t\t\t\t\t\t.on_commit(|_| NavigationMessage::BeginCanvasTilt { was_dispatched_from_menu: true }.into())\n\t\t\t\t\t\t\t.disabled(no_active_document || node_graph_open),\n\t\t\t\t\t\tMenuListEntry::new(\"Reset Tilt\")\n\t\t\t\t\t\t\t.label(\"Reset Tilt\")\n\t\t\t\t\t\t\t.icon(\"TiltReset\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(NavigationMessageDiscriminant::CanvasTiltSet))\n\t\t\t\t\t\t\t.on_commit(|_| NavigationMessage::CanvasTiltSet { angle_radians: 0.into() }.into())\n\t\t\t\t\t\t\t.disabled(no_active_document || node_graph_open || !self.canvas_tilted),\n\t\t\t\t\t],\n\t\t\t\t\tvec![\n\t\t\t\t\t\tMenuListEntry::new(\"Zoom In\")\n\t\t\t\t\t\t\t.label(\"Zoom In\")\n\t\t\t\t\t\t\t.icon(\"ZoomIn\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(NavigationMessageDiscriminant::CanvasZoomIncrease))\n\t\t\t\t\t\t\t.on_commit(|_| NavigationMessage::CanvasZoomIncrease { center_on_mouse: false }.into())\n\t\t\t\t\t\t\t.disabled(no_active_document),\n\t\t\t\t\t\tMenuListEntry::new(\"Zoom Out\")\n\t\t\t\t\t\t\t.label(\"Zoom Out\")\n\t\t\t\t\t\t\t.icon(\"ZoomOut\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(NavigationMessageDiscriminant::CanvasZoomDecrease))\n\t\t\t\t\t\t\t.on_commit(|_| NavigationMessage::CanvasZoomDecrease { center_on_mouse: false }.into())\n\t\t\t\t\t\t\t.disabled(no_active_document),\n\t\t\t\t\t\tMenuListEntry::new(\"Zoom to Selection\")\n\t\t\t\t\t\t\t.label(\"Zoom to Selection\")\n\t\t\t\t\t\t\t.icon(\"FrameSelected\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(NavigationMessageDiscriminant::FitViewportToSelection))\n\t\t\t\t\t\t\t.on_commit(|_| NavigationMessage::FitViewportToSelection.into())\n\t\t\t\t\t\t\t.disabled(no_active_document || !has_selected_layers),\n\t\t\t\t\t\tMenuListEntry::new(\"Zoom to Fit\")\n\t\t\t\t\t\t\t.label(\"Zoom to Fit\")\n\t\t\t\t\t\t\t.icon(\"FrameAll\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::ZoomCanvasToFitAll))\n\t\t\t\t\t\t\t.on_commit(|_| DocumentMessage::ZoomCanvasToFitAll.into())\n\t\t\t\t\t\t\t.disabled(no_active_document),\n\t\t\t\t\t\tMenuListEntry::new(\"Zoom to 100%\")\n\t\t\t\t\t\t\t.label(\"Zoom to 100%\")\n\t\t\t\t\t\t\t.icon(\"Zoom1x\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::ZoomCanvasTo100Percent))\n\t\t\t\t\t\t\t.on_commit(|_| DocumentMessage::ZoomCanvasTo100Percent.into())\n\t\t\t\t\t\t\t.disabled(no_active_document),\n\t\t\t\t\t\tMenuListEntry::new(\"Zoom to 200%\")\n\t\t\t\t\t\t\t.label(\"Zoom to 200%\")\n\t\t\t\t\t\t\t.icon(\"Zoom2x\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::ZoomCanvasTo200Percent))\n\t\t\t\t\t\t\t.on_commit(|_| DocumentMessage::ZoomCanvasTo200Percent.into())\n\t\t\t\t\t\t\t.disabled(no_active_document),\n\t\t\t\t\t],\n\t\t\t\t\tvec![\n\t\t\t\t\t\tMenuListEntry::new(\"Flip\")\n\t\t\t\t\t\t\t.label(\"Flip\")\n\t\t\t\t\t\t\t.icon(if self.canvas_flipped { \"CheckboxChecked\" } else { \"CheckboxUnchecked\" })\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(NavigationMessageDiscriminant::CanvasFlip))\n\t\t\t\t\t\t\t.on_commit(|_| NavigationMessage::CanvasFlip.into())\n\t\t\t\t\t\t\t.disabled(no_active_document || node_graph_open),\n\t\t\t\t\t],\n\t\t\t\t\tvec![\n\t\t\t\t\t\tMenuListEntry::new(\"Rulers\")\n\t\t\t\t\t\t\t.label(\"Rulers\")\n\t\t\t\t\t\t\t.icon(if self.rulers_visible { \"CheckboxChecked\" } else { \"CheckboxUnchecked\" })\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(PortfolioMessageDiscriminant::ToggleRulers))\n\t\t\t\t\t\t\t.on_commit(|_| PortfolioMessage::ToggleRulers.into())\n\t\t\t\t\t\t\t.disabled(no_active_document),\n\t\t\t\t\t],\n\t\t\t\t])\n\t\t\t\t.widget_instance(),\n\t\t\tTextButton::new(\"Window\")\n\t\t\t\t.label(\"Window\")\n\t\t\t\t.flush(true)\n\t\t\t\t.menu_list_children(vec![\n\t\t\t\t\tvec![\n\t\t\t\t\t\tMenuListEntry::new(\"Fullscreen\")\n\t\t\t\t\t\t\t.label(\"Fullscreen\")\n\t\t\t\t\t\t\t.icon(\"FullscreenEnter\")\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(AppWindowMessageDiscriminant::Fullscreen))\n\t\t\t\t\t\t\t.on_commit(|_| AppWindowMessage::Fullscreen.into()),\n\t\t\t\t\t\tMenuListEntry::new(\"Focus Document\")\n\t\t\t\t\t\t\t.label(\"Focus Document\")\n\t\t\t\t\t\t\t.icon(if self.focus_document { \"CheckboxChecked\" } else { \"CheckboxUnchecked\" })\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(PortfolioMessageDiscriminant::ToggleFocusDocument))\n\t\t\t\t\t\t\t.on_commit(|_| PortfolioMessage::ToggleFocusDocument.into()),\n\t\t\t\t\t],\n\t\t\t\t\tvec![\n\t\t\t\t\t\tMenuListEntry::new(\"Properties\")\n\t\t\t\t\t\t\t.label(\"Properties\")\n\t\t\t\t\t\t\t.icon(if self.properties_panel_open { \"CheckboxChecked\" } else { \"CheckboxUnchecked\" })\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(PortfolioMessageDiscriminant::TogglePropertiesPanelOpen))\n\t\t\t\t\t\t\t.on_commit(|_| PortfolioMessage::TogglePropertiesPanelOpen.into())\n\t\t\t\t\t\t\t.disabled(self.focus_document),\n\t\t\t\t\t\tMenuListEntry::new(\"Layers\")\n\t\t\t\t\t\t\t.label(\"Layers\")\n\t\t\t\t\t\t\t.icon(if self.layers_panel_open { \"CheckboxChecked\" } else { \"CheckboxUnchecked\" })\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(PortfolioMessageDiscriminant::ToggleLayersPanelOpen))\n\t\t\t\t\t\t\t.on_commit(|_| PortfolioMessage::ToggleLayersPanelOpen.into())\n\t\t\t\t\t\t\t.disabled(self.focus_document),\n\t\t\t\t\t\tMenuListEntry::new(\"Data\")\n\t\t\t\t\t\t\t.label(\"Data\")\n\t\t\t\t\t\t\t.icon(if self.data_panel_open { \"CheckboxChecked\" } else { \"CheckboxUnchecked\" })\n\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(PortfolioMessageDiscriminant::ToggleDataPanelOpen))\n\t\t\t\t\t\t\t.on_commit(|_| PortfolioMessage::ToggleDataPanelOpen.into())\n\t\t\t\t\t\t\t.disabled(self.focus_document),\n\t\t\t\t\t],\n\t\t\t\t])\n\t\t\t\t.widget_instance(),\n\t\t\tTextButton::new(\"Help\")\n\t\t\t\t.label(\"Help\")\n\t\t\t\t.flush(true)\n\t\t\t\t.menu_list_children(vec![\n\t\t\t\t\t#[cfg(not(target_os = \"macos\"))]\n\t\t\t\t\tvec![about],\n\t\t\t\t\tvec![\n\t\t\t\t\t\tMenuListEntry::new(\"Donate to Graphite\").label(\"Donate to Graphite\").icon(\"Heart\").on_commit(|_| {\n\t\t\t\t\t\t\tFrontendMessage::TriggerVisitLink {\n\t\t\t\t\t\t\t\turl: \"https://graphite.art/donate/\".into(),\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t.into()\n\t\t\t\t\t\t}),\n\t\t\t\t\t\tMenuListEntry::new(\"User Manual\").label(\"User Manual\").icon(\"UserManual\").on_commit(|_| {\n\t\t\t\t\t\t\tFrontendMessage::TriggerVisitLink {\n\t\t\t\t\t\t\t\turl: \"https://graphite.art/learn/\".into(),\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t.into()\n\t\t\t\t\t\t}),\n\t\t\t\t\t\tMenuListEntry::new(\"Report a Bug\").label(\"Report a Bug\").icon(\"Bug\").on_commit(|_| {\n\t\t\t\t\t\t\tFrontendMessage::TriggerVisitLink {\n\t\t\t\t\t\t\t\turl: \"https://github.com/GraphiteEditor/Graphite/issues/new\".into(),\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t.into()\n\t\t\t\t\t\t}),\n\t\t\t\t\t\tMenuListEntry::new(\"Visit on GitHub\").label(\"Visit on GitHub\").icon(\"Website\").on_commit(|_| {\n\t\t\t\t\t\t\tFrontendMessage::TriggerVisitLink {\n\t\t\t\t\t\t\t\turl: \"https://github.com/GraphiteEditor/Graphite\".into(),\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t.into()\n\t\t\t\t\t\t}),\n\t\t\t\t\t],\n\t\t\t\t\tvec![MenuListEntry::new(\"Developer Debug\").label(\"Developer Debug\").icon(\"Code\").children(vec![\n\t\t\t\t\t\tvec![\n\t\t\t\t\t\t\tMenuListEntry::new(\"Reset Nodes to Definitions on Open\")\n\t\t\t\t\t\t\t\t.label(\"Reset Nodes to Definitions on Open\")\n\t\t\t\t\t\t\t\t.icon(if reset_node_definitions_on_open { \"CheckboxChecked\" } else { \"CheckboxUnchecked\" })\n\t\t\t\t\t\t\t\t.on_commit(|_| PortfolioMessage::ToggleResetNodesToDefinitionsOnOpen.into()),\n\t\t\t\t\t\t],\n\t\t\t\t\t\tvec![\n\t\t\t\t\t\t\tMenuListEntry::new(\"Print Trace Logs\")\n\t\t\t\t\t\t\t\t.label(\"Print Trace Logs\")\n\t\t\t\t\t\t\t\t.icon(if log::max_level() == log::LevelFilter::Trace { \"CheckboxChecked\" } else { \"CheckboxUnchecked\" })\n\t\t\t\t\t\t\t\t.on_commit(|_| DebugMessage::ToggleTraceLogs.into()),\n\t\t\t\t\t\t\tMenuListEntry::new(\"Print Messages: Off\")\n\t\t\t\t\t\t\t\t.label(\"Print Messages: Off\")\n\t\t\t\t\t\t\t\t.icon(if message_logging_verbosity_off {\n\t\t\t\t\t\t\t\t\t#[cfg(not(target_os = \"macos\"))]\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"SmallDot\".to_string()\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t#[cfg(target_os = \"macos\")]\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"CheckboxChecked\".to_string()\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else { Default::default() })\n\t\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(DebugMessageDiscriminant::MessageOff))\n\t\t\t\t\t\t\t\t.on_commit(|_| DebugMessage::MessageOff.into()),\n\t\t\t\t\t\t\tMenuListEntry::new(\"Print Messages: Only Names\")\n\t\t\t\t\t\t\t\t.label(\"Print Messages: Only Names\")\n\t\t\t\t\t\t\t\t.icon(if message_logging_verbosity_names {\n\t\t\t\t\t\t\t\t\t#[cfg(not(target_os = \"macos\"))]\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"SmallDot\".to_string()\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t#[cfg(target_os = \"macos\")]\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"CheckboxChecked\".to_string()\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else { Default::default() })\n\t\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(DebugMessageDiscriminant::MessageNames))\n\t\t\t\t\t\t\t\t.on_commit(|_| DebugMessage::MessageNames.into()),\n\t\t\t\t\t\t\tMenuListEntry::new(\"Print Messages: Full Contents\")\n\t\t\t\t\t\t\t\t.label(\"Print Messages: Full Contents\")\n\t\t\t\t\t\t\t\t.icon(if message_logging_verbosity_contents {\n\t\t\t\t\t\t\t\t\t#[cfg(not(target_os = \"macos\"))]\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"SmallDot\".to_string()\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t#[cfg(target_os = \"macos\")]\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"CheckboxChecked\".to_string()\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else { Default::default() })\n\t\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(DebugMessageDiscriminant::MessageContents))\n\t\t\t\t\t\t\t\t.on_commit(|_| DebugMessage::MessageContents.into()),\n\t\t\t\t\t\t],\n\t\t\t\t\t\tvec![MenuListEntry::new(\"Trigger a Crash\").label(\"Trigger a Crash\").icon(\"Warning\").on_commit(|_| panic!())],\n\t\t\t\t\t])],\n\t\t\t\t])\n\t\t\t\t.widget_instance(),\n\t\t];\n\n\t\tLayout(vec![LayoutGroup::row(menu_bar_buttons)])\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/menu_bar/mod.rs",
    "content": "mod menu_bar_message;\nmod menu_bar_message_handler;\n\n#[doc(inline)]\npub use menu_bar_message::{MenuBarMessage, MenuBarMessageDiscriminant};\n#[doc(inline)]\npub use menu_bar_message_handler::MenuBarMessageHandler;\n"
  },
  {
    "path": "editor/src/messages/message.rs",
    "content": "use crate::messages::prelude::*;\nuse graphite_proc_macros::*;\n\n#[impl_message]\n#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]\npub enum Message {\n\t// Sub-messages\n\t#[child]\n\tAnimation(AnimationMessage),\n\t#[child]\n\tAppWindow(AppWindowMessage),\n\t#[child]\n\tBroadcast(BroadcastMessage),\n\t#[child]\n\tClipboard(ClipboardMessage),\n\t#[child]\n\tDebug(DebugMessage),\n\t#[child]\n\tDefer(DeferMessage),\n\t#[child]\n\tDialog(DialogMessage),\n\t#[child]\n\tFrontend(FrontendMessage),\n\t#[child]\n\tInputPreprocessor(InputPreprocessorMessage),\n\t#[child]\n\tKeyMapping(KeyMappingMessage),\n\t#[child]\n\tLayout(LayoutMessage),\n\t#[child]\n\tMenuBar(MenuBarMessage),\n\t#[child]\n\tPortfolio(PortfolioMessage),\n\t#[child]\n\tPreferences(PreferencesMessage),\n\t#[child]\n\tTool(ToolMessage),\n\t#[child]\n\tViewport(ViewportMessage),\n\n\t// Messages\n\tBatched {\n\t\tmessages: Box<[Message]>,\n\t},\n\tNoOp,\n}\n\nimpl Message {\n\tpub fn message_tree() -> DebugMessageTree {\n\t\tSelf::build_message_tree()\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/mod.rs",
    "content": "//! The root-level messages forming the first layer of the message system architecture.\n\npub mod animation;\npub mod app_window;\npub mod broadcast;\npub mod clipboard;\npub mod debug;\npub mod defer;\npub mod dialog;\npub mod frontend;\npub mod input_mapper;\npub mod input_preprocessor;\npub mod layout;\npub mod menu_bar;\npub mod message;\npub mod portfolio;\npub mod preferences;\npub mod prelude;\npub mod tool;\npub mod viewport;\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/data_panel/data_panel_message.rs",
    "content": "use crate::messages::prelude::*;\nuse crate::node_graph_executor::InspectResult;\n\n/// The Data panel UI allows the user to visualize the output data of the selected node.\n#[impl_message(Message, DocumentMessage, DataPanel)]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum DataPanelMessage {\n\tUpdateLayout {\n\t\t#[serde(skip)]\n\t\tinspect_result: InspectResult,\n\t},\n\tClearLayout,\n\n\tPushToElementPath {\n\t\tindex: usize,\n\t},\n\tTruncateElementPath {\n\t\tlen: usize,\n\t},\n\n\tViewVectorTableTab {\n\t\ttab: VectorTableTab,\n\t},\n}\n\n#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, serde::Serialize, serde::Deserialize)]\npub enum VectorTableTab {\n\t#[default]\n\tProperties,\n\tPoints,\n\tSegments,\n\tRegions,\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs",
    "content": "use super::VectorTableTab;\nuse crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, LayoutTarget};\nuse crate::messages::portfolio::document::data_panel::DataPanelMessage;\nuse crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;\nuse crate::messages::prelude::*;\nuse crate::messages::tool::tool_messages::tool_prelude::*;\nuse glam::{Affine2, Vec2};\nuse graph_craft::document::NodeId;\nuse graphene_std::Color;\nuse graphene_std::Context;\nuse graphene_std::gradient::GradientStops;\nuse graphene_std::memo::IORecord;\nuse graphene_std::raster_types::{CPU, GPU, Raster};\nuse graphene_std::table::Table;\nuse graphene_std::vector::Vector;\nuse graphene_std::vector::style::{Fill, FillChoice};\nuse graphene_std::{Artboard, Graphic};\nuse std::any::Any;\nuse std::sync::Arc;\n\n#[derive(ExtractField)]\npub struct DataPanelMessageContext<'a> {\n\tpub network_interface: &'a mut NodeNetworkInterface,\n\tpub data_panel_open: bool,\n}\n\n/// The data panel allows for graph data to be previewed.\n#[derive(Default, Debug, Clone, ExtractField)]\npub struct DataPanelMessageHandler {\n\tintrospected_node: Option<NodeId>,\n\tintrospected_data: Option<Arc<dyn Any + Send + Sync>>,\n\telement_path: Vec<usize>,\n\tactive_vector_table_tab: VectorTableTab,\n}\n\n#[message_handler_data]\nimpl MessageHandler<DataPanelMessage, DataPanelMessageContext<'_>> for DataPanelMessageHandler {\n\tfn process_message(&mut self, message: DataPanelMessage, responses: &mut VecDeque<Message>, context: DataPanelMessageContext) {\n\t\tmatch message {\n\t\t\tDataPanelMessage::UpdateLayout { mut inspect_result } => {\n\t\t\t\tself.introspected_node = Some(inspect_result.inspect_node);\n\t\t\t\tself.introspected_data = inspect_result.take_data();\n\t\t\t\tself.update_layout(responses, context);\n\t\t\t}\n\t\t\tDataPanelMessage::ClearLayout => {\n\t\t\t\tself.introspected_node = None;\n\t\t\t\tself.introspected_data = None;\n\t\t\t\tself.element_path.clear();\n\t\t\t\tself.active_vector_table_tab = VectorTableTab::default();\n\t\t\t\tself.update_layout(responses, context);\n\t\t\t}\n\n\t\t\tDataPanelMessage::PushToElementPath { index } => {\n\t\t\t\tself.element_path.push(index);\n\t\t\t\tself.update_layout(responses, context);\n\t\t\t}\n\t\t\tDataPanelMessage::TruncateElementPath { len } => {\n\t\t\t\tself.element_path.truncate(len);\n\t\t\t\tself.update_layout(responses, context);\n\t\t\t}\n\n\t\t\tDataPanelMessage::ViewVectorTableTab { tab } => {\n\t\t\t\tself.active_vector_table_tab = tab;\n\t\t\t\tself.update_layout(responses, context);\n\t\t\t}\n\t\t}\n\t}\n\n\tfn actions(&self) -> ActionList {\n\t\tactions!(DataPanelMessage;)\n\t}\n}\n\nimpl DataPanelMessageHandler {\n\tfn update_layout(&mut self, responses: &mut VecDeque<Message>, context: DataPanelMessageContext<'_>) {\n\t\tlet DataPanelMessageContext { network_interface, .. } = context;\n\n\t\tlet mut layout_data = LayoutData {\n\t\t\tcurrent_depth: 0,\n\t\t\tdesired_path: &mut self.element_path,\n\t\t\tbreadcrumbs: Vec::new(),\n\t\t\tvector_table_tab: self.active_vector_table_tab,\n\t\t};\n\n\t\t// Main data visualization\n\t\tlet mut layout = Layout(\n\t\t\tself.introspected_data\n\t\t\t\t.as_ref()\n\t\t\t\t.map(|instrospected_data| generate_layout(instrospected_data, &mut layout_data).unwrap_or_else(|| label(\"Visualization of this data type is not yet supported\")))\n\t\t\t\t.unwrap_or_default(),\n\t\t);\n\n\t\tlet mut widgets = Vec::new();\n\n\t\t// Selected layer/node name\n\t\tif let Some(node_id) = self.introspected_node {\n\t\t\tlet is_layer = network_interface.is_layer(&node_id, &[]);\n\n\t\t\twidgets.extend([\n\t\t\t\tif is_layer {\n\t\t\t\t\tIconLabel::new(\"Layer\").tooltip_description(\"Name of the selected layer.\").widget_instance()\n\t\t\t\t} else {\n\t\t\t\t\tIconLabel::new(\"Node\").tooltip_description(\"Name of the selected node.\").widget_instance()\n\t\t\t\t},\n\t\t\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\t\t\tTextInput::new(network_interface.display_name(&node_id, &[]))\n\t\t\t\t\t.tooltip_description(if is_layer { \"Name of the selected layer.\" } else { \"Name of the selected node.\" })\n\t\t\t\t\t.on_update(move |text_input| {\n\t\t\t\t\t\tNodeGraphMessage::SetDisplayName {\n\t\t\t\t\t\t\tnode_id,\n\t\t\t\t\t\t\talias: text_input.value.clone(),\n\t\t\t\t\t\t\tskip_adding_history_step: false,\n\t\t\t\t\t\t}\n\t\t\t\t\t\t.into()\n\t\t\t\t\t})\n\t\t\t\t\t.max_width(200)\n\t\t\t\t\t.widget_instance(),\n\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t]);\n\t\t}\n\n\t\t// Element path breadcrumbs\n\t\tif !layout_data.breadcrumbs.is_empty() {\n\t\t\tlet breadcrumb = BreadcrumbTrailButtons::new(layout_data.breadcrumbs)\n\t\t\t\t.on_update(|&len| DataPanelMessage::TruncateElementPath { len: len as usize }.into())\n\t\t\t\t.widget_instance();\n\t\t\twidgets.push(breadcrumb);\n\t\t}\n\n\t\tif !widgets.is_empty() {\n\t\t\tlayout.0.insert(0, LayoutGroup::row(widgets));\n\t\t}\n\n\t\tresponses.add(LayoutMessage::SendLayout {\n\t\t\tlayout,\n\t\t\tlayout_target: LayoutTarget::DataPanel,\n\t\t});\n\t}\n}\n\nstruct LayoutData<'a> {\n\tcurrent_depth: usize,\n\tdesired_path: &'a mut Vec<usize>,\n\tbreadcrumbs: Vec<String>,\n\tvector_table_tab: VectorTableTab,\n}\n\nmacro_rules! generate_layout_downcast {\n\t($introspected_data:expr, $data:expr, [ $($ty:ty),* $(,)? ]) => {\n\t\tif false { None }\n\t\t$(\n\t\t\telse if let Some(io) = $introspected_data.downcast_ref::<IORecord<Context, $ty>>() {\n\t\t\t\tSome(io.output.layout_with_breadcrumb($data))\n\t\t\t}\n\t\t)*\n\t\telse { None }\n\t}\n}\n// TODO: We simply try all these types sequentially. Find a better strategy.\nfn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'static>, data: &mut LayoutData) -> Option<Vec<LayoutGroup>> {\n\tgenerate_layout_downcast!(introspected_data, data, [\n\t\tTable<Artboard>,\n\t\tTable<Graphic>,\n\t\tTable<Vector>,\n\t\tTable<Raster<CPU>>,\n\t\tTable<Raster<GPU>>,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t\tGradientStops,\n\t\tf64,\n\t\tu32,\n\t\tu64,\n\t\tbool,\n\t\tVec<String>,\n\t\tString,\n\t\tOption<f64>,\n\t\tDVec2,\n\t\tDAffine2,\n\t])\n}\n\nfn column_headings(value: &[&str]) -> Vec<WidgetInstance> {\n\tvalue.iter().map(|text| TextLabel::new(*text).widget_instance()).collect()\n}\n\nfn label(x: impl Into<String>) -> Vec<LayoutGroup> {\n\tlet error = vec![TextLabel::new(x).widget_instance()];\n\tvec![LayoutGroup::row(error)]\n}\n\ntrait TableRowLayout {\n\tfn type_name() -> &'static str;\n\tfn identifier(&self) -> String;\n\tfn layout_with_breadcrumb(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {\n\t\tdata.breadcrumbs.push(self.identifier());\n\t\tself.element_page(data)\n\t}\n\tfn element_widget(&self, index: usize) -> WidgetInstance {\n\t\tTextButton::new(self.identifier())\n\t\t\t.on_update(move |_| DataPanelMessage::PushToElementPath { index }.into())\n\t\t\t.narrow(true)\n\t\t\t.widget_instance()\n\t}\n\tfn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {\n\t\tvec![]\n\t}\n}\n\nimpl<T: TableRowLayout> TableRowLayout for Vec<T> {\n\tfn type_name() -> &'static str {\n\t\t\"Vec\"\n\t}\n\tfn identifier(&self) -> String {\n\t\tformat!(\"Vec<{}> ({} element{})\", T::type_name(), self.len(), if self.len() == 1 { \"\" } else { \"s\" })\n\t}\n\tfn element_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {\n\t\tif let Some(index) = data.desired_path.get(data.current_depth).copied() {\n\t\t\tif let Some(row) = self.get(index) {\n\t\t\t\tdata.current_depth += 1;\n\t\t\t\tlet result = row.layout_with_breadcrumb(data);\n\t\t\t\tdata.current_depth -= 1;\n\t\t\t\treturn result;\n\t\t\t} else {\n\t\t\t\twarn!(\"Desired path truncated\");\n\t\t\t\tdata.desired_path.truncate(data.current_depth);\n\t\t\t}\n\t\t}\n\n\t\tlet mut rows = self\n\t\t\t.iter()\n\t\t\t.enumerate()\n\t\t\t.map(|(index, row)| vec![TextLabel::new(format!(\"{index}\")).narrow(true).widget_instance(), row.element_widget(index)])\n\t\t\t.collect::<Vec<_>>();\n\n\t\trows.insert(0, column_headings(&[\"\", \"element\"]));\n\n\t\tvec![LayoutGroup::table(rows, false)]\n\t}\n}\n\nimpl<T: TableRowLayout> TableRowLayout for Table<T> {\n\tfn type_name() -> &'static str {\n\t\t\"Table\"\n\t}\n\tfn identifier(&self) -> String {\n\t\tformat!(\"Table<{}> ({} element{})\", T::type_name(), self.len(), if self.len() == 1 { \"\" } else { \"s\" })\n\t}\n\tfn element_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {\n\t\tif let Some(index) = data.desired_path.get(data.current_depth).copied() {\n\t\t\tif let Some(row) = self.get(index) {\n\t\t\t\tdata.current_depth += 1;\n\t\t\t\tlet result = row.element.layout_with_breadcrumb(data);\n\t\t\t\tdata.current_depth -= 1;\n\t\t\t\treturn result;\n\t\t\t} else {\n\t\t\t\twarn!(\"Desired path truncated\");\n\t\t\t\tdata.desired_path.truncate(data.current_depth);\n\t\t\t}\n\t\t}\n\n\t\tlet mut rows = self\n\t\t\t.iter()\n\t\t\t.enumerate()\n\t\t\t.map(|(index, row)| {\n\t\t\t\tvec![\n\t\t\t\t\tTextLabel::new(format!(\"{index}\")).narrow(true).widget_instance(),\n\t\t\t\t\trow.element.element_widget(index),\n\t\t\t\t\tTextLabel::new(format_transform_matrix(row.transform)).narrow(true).widget_instance(),\n\t\t\t\t\tTextLabel::new(format!(\"{}\", row.alpha_blending)).narrow(true).widget_instance(),\n\t\t\t\t\tTextLabel::new(row.source_node_id.map_or_else(|| \"-\".to_string(), |id| format!(\"{}\", id.0)))\n\t\t\t\t\t\t.narrow(true)\n\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t]\n\t\t\t})\n\t\t\t.collect::<Vec<_>>();\n\n\t\trows.insert(0, column_headings(&[\"\", \"element\", \"transform\", \"alpha_blending\", \"source_node_id\"]));\n\n\t\tvec![LayoutGroup::table(rows, false)]\n\t}\n}\n\nimpl TableRowLayout for Artboard {\n\tfn type_name() -> &'static str {\n\t\t\"Artboard\"\n\t}\n\tfn identifier(&self) -> String {\n\t\tself.label.clone()\n\t}\n\tfn element_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {\n\t\tself.content.element_page(data)\n\t}\n}\n\nimpl TableRowLayout for Graphic {\n\tfn type_name() -> &'static str {\n\t\t\"Graphic\"\n\t}\n\tfn identifier(&self) -> String {\n\t\tmatch self {\n\t\t\tSelf::Graphic(table) => table.identifier(),\n\t\t\tSelf::Vector(table) => table.identifier(),\n\t\t\tSelf::RasterCPU(table) => table.identifier(),\n\t\t\tSelf::RasterGPU(table) => table.identifier(),\n\t\t\tSelf::Color(table) => table.identifier(),\n\t\t\tSelf::Gradient(table) => table.identifier(),\n\t\t}\n\t}\n\t// Don't put a breadcrumb for Graphic\n\tfn layout_with_breadcrumb(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {\n\t\tself.element_page(data)\n\t}\n\tfn element_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {\n\t\tmatch self {\n\t\t\tSelf::Graphic(table) => table.layout_with_breadcrumb(data),\n\t\t\tSelf::Vector(table) => table.layout_with_breadcrumb(data),\n\t\t\tSelf::RasterCPU(table) => table.layout_with_breadcrumb(data),\n\t\t\tSelf::RasterGPU(table) => table.layout_with_breadcrumb(data),\n\t\t\tSelf::Color(table) => table.layout_with_breadcrumb(data),\n\t\t\tSelf::Gradient(table) => table.layout_with_breadcrumb(data),\n\t\t}\n\t}\n}\n\nimpl TableRowLayout for Vector {\n\tfn type_name() -> &'static str {\n\t\t\"Vector\"\n\t}\n\tfn identifier(&self) -> String {\n\t\tformat!(\n\t\t\t\"Vector ({} point{}, {} segment{})\",\n\t\t\tself.point_domain.ids().len(),\n\t\t\tif self.point_domain.ids().len() == 1 { \"\" } else { \"s\" },\n\t\t\tself.segment_domain.ids().len(),\n\t\t\tif self.segment_domain.ids().len() == 1 { \"\" } else { \"s\" }\n\t\t)\n\t}\n\tfn element_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {\n\t\tlet table_tab_entries = [VectorTableTab::Properties, VectorTableTab::Points, VectorTableTab::Segments, VectorTableTab::Regions]\n\t\t\t.into_iter()\n\t\t\t.map(|tab| {\n\t\t\t\tRadioEntryData::new(format!(\"{tab:?}\"))\n\t\t\t\t\t.label(format!(\"{tab:?}\"))\n\t\t\t\t\t.on_update(move |_| DataPanelMessage::ViewVectorTableTab { tab }.into())\n\t\t\t})\n\t\t\t.collect();\n\t\tlet table_tabs = vec![RadioInput::new(table_tab_entries).selected_index(Some(data.vector_table_tab as u32)).widget_instance()];\n\n\t\tlet mut table_rows = Vec::new();\n\t\tmatch data.vector_table_tab {\n\t\t\tVectorTableTab::Properties => {\n\t\t\t\ttable_rows.push(column_headings(&[\"property\", \"value\"]));\n\n\t\t\t\tmatch self.style.fill.clone() {\n\t\t\t\t\tFill::None => table_rows.push(vec![\n\t\t\t\t\t\tTextLabel::new(\"Fill\").narrow(true).widget_instance(),\n\t\t\t\t\t\tColorInput::new(FillChoice::None).disabled(true).menu_direction(Some(MenuDirection::Top)).narrow(true).widget_instance(),\n\t\t\t\t\t]),\n\t\t\t\t\tFill::Solid(color) => table_rows.push(vec![\n\t\t\t\t\t\tTextLabel::new(\"Fill\").narrow(true).widget_instance(),\n\t\t\t\t\t\tColorInput::new(FillChoice::Solid(color))\n\t\t\t\t\t\t\t.disabled(true)\n\t\t\t\t\t\t\t.menu_direction(Some(MenuDirection::Top))\n\t\t\t\t\t\t\t.narrow(true)\n\t\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t\t]),\n\t\t\t\t\tFill::Gradient(gradient) => {\n\t\t\t\t\t\ttable_rows.push(vec![\n\t\t\t\t\t\t\tTextLabel::new(\"Fill\").narrow(true).widget_instance(),\n\t\t\t\t\t\t\tColorInput::new(FillChoice::Gradient(gradient.stops))\n\t\t\t\t\t\t\t\t.disabled(true)\n\t\t\t\t\t\t\t\t.menu_direction(Some(MenuDirection::Top))\n\t\t\t\t\t\t\t\t.narrow(true)\n\t\t\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t\t\t]);\n\t\t\t\t\t\ttable_rows.push(vec![\n\t\t\t\t\t\t\tTextLabel::new(\"Fill Gradient Type\").narrow(true).widget_instance(),\n\t\t\t\t\t\t\tTextLabel::new(gradient.gradient_type.to_string()).narrow(true).widget_instance(),\n\t\t\t\t\t\t]);\n\t\t\t\t\t\ttable_rows.push(vec![\n\t\t\t\t\t\t\tTextLabel::new(\"Fill Gradient Start\").narrow(true).widget_instance(),\n\t\t\t\t\t\t\tTextLabel::new(format_dvec2(gradient.start)).narrow(true).widget_instance(),\n\t\t\t\t\t\t]);\n\t\t\t\t\t\ttable_rows.push(vec![\n\t\t\t\t\t\t\tTextLabel::new(\"Fill Gradient End\").narrow(true).widget_instance(),\n\t\t\t\t\t\t\tTextLabel::new(format_dvec2(gradient.end)).narrow(true).widget_instance(),\n\t\t\t\t\t\t]);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif let Some(stroke) = self.style.stroke.clone() {\n\t\t\t\t\tlet color = if let Some(color) = stroke.color { FillChoice::Solid(color) } else { FillChoice::None };\n\t\t\t\t\ttable_rows.push(vec![\n\t\t\t\t\t\tTextLabel::new(\"Stroke\").narrow(true).widget_instance(),\n\t\t\t\t\t\tColorInput::new(color).disabled(true).menu_direction(Some(MenuDirection::Top)).narrow(true).widget_instance(),\n\t\t\t\t\t]);\n\t\t\t\t\ttable_rows.push(vec![\n\t\t\t\t\t\tTextLabel::new(\"Stroke Weight\").narrow(true).widget_instance(),\n\t\t\t\t\t\tTextLabel::new(format!(\"{} px\", stroke.weight)).narrow(true).widget_instance(),\n\t\t\t\t\t]);\n\t\t\t\t\ttable_rows.push(vec![\n\t\t\t\t\t\tTextLabel::new(\"Stroke Dash Lengths\").narrow(true).widget_instance(),\n\t\t\t\t\t\tTextLabel::new(if stroke.dash_lengths.is_empty() {\n\t\t\t\t\t\t\t\"-\".to_string()\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tformat!(\"[{}]\", stroke.dash_lengths.iter().map(|x| format!(\"{x} px\")).collect::<Vec<_>>().join(\", \"))\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.narrow(true)\n\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t\t]);\n\t\t\t\t\ttable_rows.push(vec![\n\t\t\t\t\t\tTextLabel::new(\"Stroke Dash Offset\").narrow(true).widget_instance(),\n\t\t\t\t\t\tTextLabel::new(format!(\"{}\", stroke.dash_offset)).narrow(true).widget_instance(),\n\t\t\t\t\t]);\n\t\t\t\t\ttable_rows.push(vec![\n\t\t\t\t\t\tTextLabel::new(\"Stroke Cap\").narrow(true).widget_instance(),\n\t\t\t\t\t\tTextLabel::new(stroke.cap.to_string()).narrow(true).widget_instance(),\n\t\t\t\t\t]);\n\t\t\t\t\ttable_rows.push(vec![\n\t\t\t\t\t\tTextLabel::new(\"Stroke Join\").narrow(true).widget_instance(),\n\t\t\t\t\t\tTextLabel::new(stroke.join.to_string()).narrow(true).widget_instance(),\n\t\t\t\t\t]);\n\t\t\t\t\ttable_rows.push(vec![\n\t\t\t\t\t\tTextLabel::new(\"Stroke Join Miter Limit\").narrow(true).widget_instance(),\n\t\t\t\t\t\tTextLabel::new(format!(\"{}\", stroke.join_miter_limit)).narrow(true).widget_instance(),\n\t\t\t\t\t]);\n\t\t\t\t\ttable_rows.push(vec![\n\t\t\t\t\t\tTextLabel::new(\"Stroke Align\").narrow(true).widget_instance(),\n\t\t\t\t\t\tTextLabel::new(stroke.align.to_string()).narrow(true).widget_instance(),\n\t\t\t\t\t]);\n\t\t\t\t\ttable_rows.push(vec![\n\t\t\t\t\t\tTextLabel::new(\"Stroke Transform\").narrow(true).widget_instance(),\n\t\t\t\t\t\tTextLabel::new(format_transform_matrix(&stroke.transform)).narrow(true).widget_instance(),\n\t\t\t\t\t]);\n\t\t\t\t\ttable_rows.push(vec![\n\t\t\t\t\t\tTextLabel::new(\"Stroke Paint Order\").narrow(true).widget_instance(),\n\t\t\t\t\t\tTextLabel::new(stroke.paint_order.to_string()).narrow(true).widget_instance(),\n\t\t\t\t\t]);\n\t\t\t\t}\n\n\t\t\t\tlet colinear = self.colinear_manipulators.iter().map(|[a, b]| format!(\"[{a} / {b}]\")).collect::<Vec<_>>().join(\", \");\n\t\t\t\tlet colinear = if colinear.is_empty() { \"-\".to_string() } else { colinear };\n\t\t\t\ttable_rows.push(vec![\n\t\t\t\t\tTextLabel::new(\"Colinear Handle IDs\").narrow(true).widget_instance(),\n\t\t\t\t\tTextLabel::new(colinear).narrow(true).widget_instance(),\n\t\t\t\t]);\n\n\t\t\t\ttable_rows.push(vec![\n\t\t\t\t\tTextLabel::new(\"Upstream Nested Layers\").narrow(true).widget_instance(),\n\t\t\t\t\tTextLabel::new(if self.upstream_data.is_some() {\n\t\t\t\t\t\t\"Yes (this preserves references to its upstream nested layers for editing by tools)\"\n\t\t\t\t\t} else {\n\t\t\t\t\t\t\"No (this doesn't preserve references to its upstream nested layers for editing by tools)\"\n\t\t\t\t\t})\n\t\t\t\t\t.narrow(true)\n\t\t\t\t\t.widget_instance(),\n\t\t\t\t]);\n\t\t\t}\n\t\t\tVectorTableTab::Points => {\n\t\t\t\ttable_rows.push(column_headings(&[\"\", \"position\"]));\n\t\t\t\ttable_rows.extend(self.point_domain.iter().map(|(id, position)| {\n\t\t\t\t\tvec![\n\t\t\t\t\t\tTextLabel::new(format!(\"{}\", id.inner())).narrow(true).widget_instance(),\n\t\t\t\t\t\tTextLabel::new(format!(\"{position}\")).narrow(true).widget_instance(),\n\t\t\t\t\t]\n\t\t\t\t}));\n\t\t\t}\n\t\t\tVectorTableTab::Segments => {\n\t\t\t\ttable_rows.push(column_headings(&[\"\", \"start_index\", \"end_index\", \"handles\"]));\n\t\t\t\ttable_rows.extend(self.segment_domain.iter().map(|(id, start, end, handles)| {\n\t\t\t\t\tvec![\n\t\t\t\t\t\tTextLabel::new(format!(\"{}\", id.inner())).narrow(true).widget_instance(),\n\t\t\t\t\t\tTextLabel::new(format!(\"{start}\")).narrow(true).widget_instance(),\n\t\t\t\t\t\tTextLabel::new(format!(\"{end}\")).narrow(true).widget_instance(),\n\t\t\t\t\t\tTextLabel::new(format!(\"{handles:?}\")).narrow(true).widget_instance(),\n\t\t\t\t\t]\n\t\t\t\t}));\n\t\t\t}\n\t\t\tVectorTableTab::Regions => {\n\t\t\t\ttable_rows.push(column_headings(&[\"\", \"segment_range\", \"fill\"]));\n\t\t\t\ttable_rows.extend(self.region_domain.iter().map(|(id, segment_range, fill)| {\n\t\t\t\t\tvec![\n\t\t\t\t\t\tTextLabel::new(format!(\"{}\", id.inner())).narrow(true).widget_instance(),\n\t\t\t\t\t\tTextLabel::new(format!(\"{segment_range:?}\")).narrow(true).widget_instance(),\n\t\t\t\t\t\tTextLabel::new(format!(\"{}\", fill.inner())).narrow(true).widget_instance(),\n\t\t\t\t\t]\n\t\t\t\t}));\n\t\t\t}\n\t\t}\n\n\t\tvec![LayoutGroup::row(table_tabs), LayoutGroup::table(table_rows, false)]\n\t}\n}\n\nimpl TableRowLayout for Raster<CPU> {\n\tfn type_name() -> &'static str {\n\t\t\"Raster\"\n\t}\n\tfn identifier(&self) -> String {\n\t\tformat!(\"Raster ({}x{})\", self.width, self.height)\n\t}\n\tfn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {\n\t\tlet raster = self.data();\n\n\t\tif raster.width == 0 || raster.height == 0 {\n\t\t\tlet widgets = vec![TextLabel::new(\"Image has no area\").widget_instance()];\n\t\t\treturn vec![LayoutGroup::row(widgets)];\n\t\t}\n\n\t\tlet base64_string = raster.base64_string.clone().unwrap_or_else(|| {\n\t\t\tuse base64::Engine;\n\n\t\t\tlet output = raster.to_png();\n\t\t\tlet preamble = \"data:image/png;base64,\";\n\t\t\tlet mut base64_string = String::with_capacity(preamble.len() + output.len() * 4);\n\t\t\tbase64_string.push_str(preamble);\n\t\t\tbase64::engine::general_purpose::STANDARD.encode_string(output, &mut base64_string);\n\t\t\tbase64_string\n\t\t});\n\n\t\tlet widgets = vec![ImageLabel::new(base64_string).widget_instance()];\n\t\tvec![LayoutGroup::row(widgets)]\n\t}\n}\n\nimpl TableRowLayout for Raster<GPU> {\n\tfn type_name() -> &'static str {\n\t\t\"Raster\"\n\t}\n\tfn identifier(&self) -> String {\n\t\tformat!(\"Raster ({}x{})\", self.data().width(), self.data().height())\n\t}\n\tfn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {\n\t\tlet widgets = vec![TextLabel::new(\"Raster is a texture on the GPU and cannot currently be displayed here\").widget_instance()];\n\t\tvec![LayoutGroup::row(widgets)]\n\t}\n}\n\nimpl TableRowLayout for Color {\n\tfn type_name() -> &'static str {\n\t\t\"Color\"\n\t}\n\tfn identifier(&self) -> String {\n\t\tformat!(\"Color (#{})\", self.to_gamma_srgb().to_rgba_hex_srgb())\n\t}\n\tfn element_widget(&self, _index: usize) -> WidgetInstance {\n\t\tColorInput::new(FillChoice::Solid(*self))\n\t\t\t.disabled(true)\n\t\t\t.menu_direction(Some(MenuDirection::Top))\n\t\t\t.narrow(true)\n\t\t\t.widget_instance()\n\t}\n\tfn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {\n\t\tlet widgets = vec![self.element_widget(0)];\n\t\tvec![LayoutGroup::row(widgets)]\n\t}\n}\n\nimpl TableRowLayout for GradientStops {\n\tfn type_name() -> &'static str {\n\t\t\"Gradient\"\n\t}\n\tfn identifier(&self) -> String {\n\t\tformat!(\"Gradient ({} stops)\", self.len())\n\t}\n\tfn element_widget(&self, _index: usize) -> WidgetInstance {\n\t\tColorInput::new(FillChoice::Gradient(self.clone()))\n\t\t\t.menu_direction(Some(MenuDirection::Top))\n\t\t\t.disabled(true)\n\t\t\t.narrow(true)\n\t\t\t.widget_instance()\n\t}\n\tfn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {\n\t\tlet widgets = vec![self.element_widget(0)];\n\t\tvec![LayoutGroup::row(widgets)]\n\t}\n}\n\nimpl TableRowLayout for f64 {\n\tfn type_name() -> &'static str {\n\t\t\"Number (f64)\"\n\t}\n\tfn identifier(&self) -> String {\n\t\t\"Number (f64)\".to_string()\n\t}\n\tfn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {\n\t\tlet widgets = vec![TextLabel::new(self.to_string()).widget_instance()];\n\t\tvec![LayoutGroup::row(widgets)]\n\t}\n}\n\nimpl TableRowLayout for u32 {\n\tfn type_name() -> &'static str {\n\t\t\"Number (u32)\"\n\t}\n\tfn identifier(&self) -> String {\n\t\t\"Number (u32)\".to_string()\n\t}\n\tfn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {\n\t\tlet widgets = vec![TextLabel::new(self.to_string()).widget_instance()];\n\t\tvec![LayoutGroup::row(widgets)]\n\t}\n}\n\nimpl TableRowLayout for u64 {\n\tfn type_name() -> &'static str {\n\t\t\"Number (u64)\"\n\t}\n\tfn identifier(&self) -> String {\n\t\t\"Number (u64)\".to_string()\n\t}\n\tfn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {\n\t\tlet widgets = vec![TextLabel::new(self.to_string()).widget_instance()];\n\t\tvec![LayoutGroup::row(widgets)]\n\t}\n}\n\nimpl TableRowLayout for bool {\n\tfn type_name() -> &'static str {\n\t\t\"Bool\"\n\t}\n\tfn identifier(&self) -> String {\n\t\t\"Bool\".to_string()\n\t}\n\tfn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {\n\t\tlet widgets = vec![TextLabel::new(self.to_string()).widget_instance()];\n\t\tvec![LayoutGroup::row(widgets)]\n\t}\n}\n\nimpl TableRowLayout for String {\n\tfn type_name() -> &'static str {\n\t\t\"String\"\n\t}\n\tfn identifier(&self) -> String {\n\t\t// Show the first line, and if there are more, indicate that with an ellipsis\n\t\tlet first_line = self.lines().next().unwrap_or(\"\");\n\t\tif self.lines().count() > 1 {\n\t\t\tformat!(\"\\\"{} …\\\"\", first_line)\n\t\t} else {\n\t\t\tformat!(\"\\\"{}\\\"\", first_line)\n\t\t}\n\t}\n\tfn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {\n\t\tlet widgets = vec![TextAreaInput::new(self.to_string()).disabled(true).widget_instance()];\n\t\tvec![LayoutGroup::row(widgets)]\n\t}\n}\n\nimpl TableRowLayout for Option<f64> {\n\tfn type_name() -> &'static str {\n\t\t\"Option<f64>\"\n\t}\n\tfn identifier(&self) -> String {\n\t\t\"Option<f64>\".to_string()\n\t}\n\tfn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {\n\t\tlet widgets = vec![TextLabel::new(format!(\"{self:?}\")).widget_instance()];\n\t\tvec![LayoutGroup::row(widgets)]\n\t}\n}\n\nimpl TableRowLayout for DVec2 {\n\tfn type_name() -> &'static str {\n\t\t\"Vec2\"\n\t}\n\tfn identifier(&self) -> String {\n\t\t\"Vec2\".to_string()\n\t}\n\tfn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {\n\t\tlet widgets = vec![TextLabel::new(format!(\"({}, {})\", self.x, self.y)).widget_instance()];\n\t\tvec![LayoutGroup::row(widgets)]\n\t}\n}\n\nimpl TableRowLayout for Vec2 {\n\tfn type_name() -> &'static str {\n\t\t\"Vec2\"\n\t}\n\tfn identifier(&self) -> String {\n\t\t\"Vec2\".to_string()\n\t}\n\tfn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {\n\t\tlet widgets = vec![TextLabel::new(format!(\"({}, {})\", self.x, self.y)).widget_instance()];\n\t\tvec![LayoutGroup::row(widgets)]\n\t}\n}\n\nimpl TableRowLayout for DAffine2 {\n\tfn type_name() -> &'static str {\n\t\t\"Transform\"\n\t}\n\tfn identifier(&self) -> String {\n\t\t\"Transform\".to_string()\n\t}\n\tfn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {\n\t\tlet widgets = vec![TextLabel::new(format_transform_matrix(self)).widget_instance()];\n\t\tvec![LayoutGroup::row(widgets)]\n\t}\n}\n\nimpl TableRowLayout for Affine2 {\n\tfn type_name() -> &'static str {\n\t\t\"Transform\"\n\t}\n\tfn identifier(&self) -> String {\n\t\t\"Transform\".to_string()\n\t}\n\tfn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {\n\t\tlet matrix = DAffine2::from_cols_array(&self.to_cols_array().map(|x| x as f64));\n\t\tlet widgets = vec![TextLabel::new(format_transform_matrix(&matrix)).widget_instance()];\n\t\tvec![LayoutGroup::row(widgets)]\n\t}\n}\n\nfn format_transform_matrix(transform: &DAffine2) -> String {\n\tlet (scale, angle, translation) = if transform.matrix2.determinant().abs() <= f64::EPSILON {\n\t\tlet [col_0, col_1] = transform.matrix2.to_cols_array_2d().map(|[x, y]| DVec2::new(x, y));\n\n\t\tlet scale = DVec2::new(col_0.length(), col_1.length());\n\n\t\tlet rotation = if scale.x > f64::EPSILON {\n\t\t\tcol_0.y.atan2(col_0.x)\n\t\t} else if scale.y > f64::EPSILON {\n\t\t\tcol_1.y.atan2(col_1.x) - std::f64::consts::FRAC_PI_2\n\t\t} else {\n\t\t\t0.\n\t\t};\n\n\t\t(scale, rotation, transform.translation)\n\t} else {\n\t\ttransform.to_scale_angle_translation()\n\t};\n\tlet rotation = if angle == -0. { 0. } else { angle.to_degrees() };\n\tlet round = |x: f64| (x * 1e3).round() / 1e3;\n\n\tformat!(\n\t\t\"Location: ({} px, {} px) — Rotation: {rotation:2}° — Scale: ({}x, {}x)\",\n\t\tround(translation.x),\n\t\tround(translation.y),\n\t\tround(scale.x),\n\t\tround(scale.y)\n\t)\n}\n\nfn format_dvec2(value: DVec2) -> String {\n\tlet round = |x: f64| (x * 1e3).round() / 1e3;\n\tformat!(\"({} px, {} px)\", round(value.x), round(value.y))\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/data_panel/mod.rs",
    "content": "mod data_panel_message;\nmod data_panel_message_handler;\n\n#[doc(inline)]\npub use data_panel_message::*;\n#[doc(inline)]\npub use data_panel_message_handler::*;\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/document_message.rs",
    "content": "use std::path::PathBuf;\nuse std::sync::Arc;\n\nuse super::utility_types::misc::{GroupFolderType, SnappingState};\nuse crate::messages::input_mapper::utility_types::input_keyboard::Key;\nuse crate::messages::portfolio::document::data_panel::DataPanelMessage;\nuse crate::messages::portfolio::document::overlays::utility_types::{OverlayContext, OverlaysType};\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis, GridSnapping};\nuse crate::messages::portfolio::utility_types::PanelType;\nuse crate::messages::prelude::*;\nuse glam::DAffine2;\nuse graph_craft::document::NodeId;\nuse graphene_std::Color;\nuse graphene_std::raster::BlendMode;\nuse graphene_std::raster::Image;\nuse graphene_std::transform::Footprint;\nuse graphene_std::vector::Vector;\nuse graphene_std::vector::click_target::ClickTarget;\nuse graphene_std::vector::style::RenderMode;\n\n#[impl_message(Message, PortfolioMessage, Document)]\n#[derive(derivative::Derivative, Clone, serde::Serialize, serde::Deserialize)]\n#[derivative(Debug, PartialEq)]\npub enum DocumentMessage {\n\tNoop,\n\t// Sub-messages\n\t#[child]\n\tGraphOperation(GraphOperationMessage),\n\t#[child]\n\tNavigation(NavigationMessage),\n\t#[child]\n\tNodeGraph(NodeGraphMessage),\n\t#[child]\n\tOverlays(OverlaysMessage),\n\t#[child]\n\tPropertiesPanel(PropertiesPanelMessage),\n\t#[child]\n\tDataPanel(DataPanelMessage),\n\n\t// Messages\n\tAlignSelectedLayers {\n\t\taxis: AlignAxis,\n\t\taggregate: AlignAggregate,\n\t},\n\tRemoveArtboards,\n\tClearLayersPanel,\n\tCreateEmptyFolder,\n\tDeleteNode {\n\t\tnode_id: NodeId,\n\t},\n\tDeleteSelectedLayers,\n\tDeselectAllLayers,\n\tDocumentHistoryBackward,\n\tDocumentHistoryForward,\n\tDocumentStructureChanged,\n\tDrawArtboardOverlays {\n\t\tcontext: OverlayContext,\n\t},\n\tDuplicateSelectedLayers,\n\tEnterNestedNetwork {\n\t\tnode_id: NodeId,\n\t},\n\tEscape,\n\tExitNestedNetwork {\n\t\tsteps_back: usize,\n\t},\n\tFlipSelectedLayers {\n\t\tflip_axis: FlipAxis,\n\t},\n\tRotateSelectedLayers {\n\t\tdegrees: f64,\n\t},\n\tGraphViewOverlay {\n\t\topen: bool,\n\t},\n\tGraphViewOverlayToggle,\n\tGridOptions {\n\t\toptions: GridSnapping,\n\t},\n\tGridOverlays {\n\t\tcontext: OverlayContext,\n\t},\n\tGridVisibility {\n\t\tvisible: bool,\n\t},\n\tGroupSelectedLayers {\n\t\tgroup_folder_type: GroupFolderType,\n\t},\n\tMoveSelectedLayersTo {\n\t\tparent: LayerNodeIdentifier,\n\t\tinsert_index: usize,\n\t},\n\tMoveSelectedLayersToGroup {\n\t\tparent: LayerNodeIdentifier,\n\t},\n\tNudgeSelectedLayers {\n\t\tdelta_x: f64,\n\t\tdelta_y: f64,\n\t\tresize: Key,\n\t\tresize_opposite_corner: Key,\n\t},\n\tPasteImage {\n\t\tname: Option<String>,\n\t\timage: Image<Color>,\n\t\tmouse: Option<(f64, f64)>,\n\t\tparent_and_insert_index: Option<(LayerNodeIdentifier, usize)>,\n\t},\n\tPasteSvg {\n\t\tname: Option<String>,\n\t\tsvg: String,\n\t\tmouse: Option<(f64, f64)>,\n\t\tparent_and_insert_index: Option<(LayerNodeIdentifier, usize)>,\n\t},\n\tRedo,\n\tRenameDocument {\n\t\tnew_name: String,\n\t},\n\tRenderRulers,\n\tRenderScrollbars,\n\tSaveDocument,\n\tSaveDocumentAs,\n\tSavedDocument {\n\t\tpath: Option<PathBuf>,\n\t},\n\tMarkAsSaved,\n\tSelectParentLayer,\n\tSelectAllLayers,\n\tSelectedLayersLower,\n\tSelectedLayersLowerToBack,\n\tSelectedLayersRaise,\n\tSelectedLayersRaiseToFront,\n\tSelectedLayersReverse,\n\tSelectedLayersReorder {\n\t\trelative_index_offset: isize,\n\t},\n\tClipLayer {\n\t\tid: NodeId,\n\t},\n\tSelectLayer {\n\t\tid: NodeId,\n\t\tctrl: bool,\n\t\tshift: bool,\n\t},\n\tSetActivePanel {\n\t\tactive_panel: PanelType,\n\t},\n\tSetBlendModeForSelectedLayers {\n\t\tblend_mode: BlendMode,\n\t},\n\tSetGraphFadeArtwork {\n\t\tpercentage: f64,\n\t},\n\tSetNodePinned {\n\t\tnode_id: NodeId,\n\t\tpinned: bool,\n\t},\n\tSetOpacityForSelectedLayers {\n\t\topacity: f64,\n\t},\n\tSetFillForSelectedLayers {\n\t\tfill: f64,\n\t},\n\tSetOverlaysVisibility {\n\t\tvisible: bool,\n\t\toverlays_type: Option<OverlaysType>,\n\t},\n\tSetRangeSelectionLayer {\n\t\tnew_layer: Option<LayerNodeIdentifier>,\n\t},\n\tSetSnapping {\n\t\t#[serde(skip)]\n\t\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\t\tclosure: Option<for<'a> fn(&'a mut SnappingState) -> &'a mut bool>,\n\t\tsnapping_state: bool,\n\t},\n\tSetToNodeOrLayer {\n\t\tnode_id: NodeId,\n\t\tis_layer: bool,\n\t},\n\tSetRenderMode {\n\t\trender_mode: RenderMode,\n\t},\n\tAddTransaction,\n\tStartTransaction,\n\tEndTransaction,\n\tCommitTransaction,\n\tCancelTransaction,\n\tAbortTransaction,\n\tRepeatedAbortTransaction {\n\t\tundo_count: usize,\n\t},\n\tToggleLayerExpansion {\n\t\tid: NodeId,\n\t\trecursive: bool,\n\t},\n\tToggleSelectedVisibility,\n\tToggleSelectedLocked,\n\tToggleGridVisibility,\n\tToggleOverlaysVisibility,\n\tToggleSnapping,\n\tUpdateUpstreamTransforms {\n\t\tupstream_footprints: HashMap<NodeId, Footprint>,\n\t\tlocal_transforms: HashMap<NodeId, DAffine2>,\n\t\tfirst_element_source_id: HashMap<NodeId, Option<NodeId>>,\n\t},\n\tUpdateClickTargets {\n\t\tclick_targets: HashMap<NodeId, Vec<Arc<ClickTarget>>>,\n\t},\n\tUpdateClipTargets {\n\t\tclip_targets: HashSet<NodeId>,\n\t},\n\tUpdateVectorData {\n\t\tvector_data: HashMap<NodeId, Arc<Vector>>,\n\t},\n\tUndo,\n\tUngroupSelectedLayers,\n\tUngroupLayer {\n\t\tlayer: LayerNodeIdentifier,\n\t},\n\tPTZUpdate,\n\tSelectionStepBack,\n\tSelectionStepForward,\n\tWrapContentInArtboard {\n\t\tplace_artboard_at_origin: bool,\n\t},\n\tZoomCanvasTo100Percent,\n\tZoomCanvasTo200Percent,\n\tZoomCanvasToFitAll,\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/document_message_handler.rs",
    "content": "use super::node_graph::document_node_definitions;\nuse super::utility_types::error::EditorError;\nuse super::utility_types::misc::{GroupFolderType, SNAP_FUNCTIONS_FOR_BOUNDING_BOXES, SNAP_FUNCTIONS_FOR_PATHS, SnappingOptions, SnappingState};\nuse super::utility_types::network_interface::{self, NodeNetworkInterface, TransactionStatus};\nuse super::utility_types::nodes::{CollapsedLayers, LayerStructureEntry, SelectedNodes};\nuse crate::application::{GRAPHITE_GIT_COMMIT_HASH, generate_uuid};\nuse crate::consts::{ASYMPTOTIC_EFFECT, COLOR_OVERLAY_GRAY, DEFAULT_DOCUMENT_NAME, FILE_EXTENSION, SCALE_EFFECT, SCROLLBAR_SPACING, VIEWPORT_ROTATE_SNAP_INTERVAL};\nuse crate::messages::input_mapper::utility_types::macros::action_shortcut;\nuse crate::messages::layout::utility_types::widget_prelude::*;\nuse crate::messages::portfolio::document::data_panel::{DataPanelMessageContext, DataPanelMessageHandler};\nuse crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;\nuse crate::messages::portfolio::document::node_graph::NodeGraphMessageContext;\nuse crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;\nuse crate::messages::portfolio::document::node_graph::utility_types::FrontendGraphDataType;\nuse crate::messages::portfolio::document::overlays::grid_overlays::{grid_overlay, overlay_options};\nuse crate::messages::portfolio::document::overlays::utility_types::{OverlaysType, OverlaysVisibilitySettings, Pivot};\nuse crate::messages::portfolio::document::properties_panel::properties_panel_message_handler::PropertiesPanelMessageContext;\nuse crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};\nuse crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis, PTZ};\nuse crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, NodeTemplate};\nuse crate::messages::portfolio::utility_types::{PanelType, PersistentData};\nuse crate::messages::prelude::*;\nuse crate::messages::tool::common_functionality::graph_modification_utils::{self, get_blend_mode, get_fill, get_opacity};\nuse crate::messages::tool::tool_messages::select_tool::SelectToolPointerKeys;\nuse crate::messages::tool::tool_messages::tool_prelude::Key;\nuse crate::messages::tool::utility_types::ToolType;\nuse crate::node_graph_executor::NodeGraphExecutor;\nuse glam::{DAffine2, DVec2, IVec2};\nuse graph_craft::document::value::TaggedValue;\nuse graph_craft::document::{NodeId, NodeInput, NodeNetwork, OldNodeNetwork};\nuse graphene_std::math::quad::Quad;\nuse graphene_std::path_bool_nodes::boolean_intersect;\nuse graphene_std::raster::BlendMode;\nuse graphene_std::raster_types::Raster;\nuse graphene_std::render_node::wgpu_available;\nuse graphene_std::subpath::Subpath;\nuse graphene_std::table::Table;\nuse graphene_std::vector::PointId;\nuse graphene_std::vector::click_target::{ClickTarget, ClickTargetType};\nuse graphene_std::vector::misc::dvec2_to_point;\nuse graphene_std::vector::style::RenderMode;\nuse kurbo::{Affine, BezPath, Line, PathSeg};\nuse std::path::PathBuf;\nuse std::sync::Arc;\nuse std::time::Duration;\n\n#[derive(ExtractField)]\npub struct DocumentMessageContext<'a> {\n\tpub document_id: DocumentId,\n\tpub ipp: &'a InputPreprocessorMessageHandler,\n\tpub persistent_data: &'a PersistentData,\n\tpub executor: &'a mut NodeGraphExecutor,\n\tpub current_tool: &'a ToolType,\n\tpub preferences: &'a PreferencesMessageHandler,\n\tpub data_panel_open: bool,\n\tpub layers_panel_open: bool,\n\tpub properties_panel_open: bool,\n\tpub viewport: &'a ViewportMessageHandler,\n}\n\n#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ExtractField)]\n#[serde(default)]\npub struct DocumentMessageHandler {\n\t// ======================\n\t// Child message handlers\n\t// ======================\n\t//\n\t#[serde(skip)]\n\tpub navigation_handler: NavigationMessageHandler,\n\t#[serde(skip)]\n\tpub node_graph_handler: NodeGraphMessageHandler,\n\t#[serde(skip)]\n\tpub overlays_message_handler: OverlaysMessageHandler,\n\t#[serde(skip)]\n\tpub properties_panel_message_handler: PropertiesPanelMessageHandler,\n\t#[serde(skip)]\n\tpub data_panel_message_handler: DataPanelMessageHandler,\n\n\t// ============================================\n\t// Fields that are saved in the document format\n\t// ============================================\n\t//\n\t// Contains the NodeNetwork and acts an an interface to manipulate the NodeNetwork with custom setters in order to keep NetworkMetadata in sync\n\tpub network_interface: NodeNetworkInterface,\n\t/// List of the [`LayerNodeIdentifier`]s that are currently collapsed by the user in the Layers panel.\n\t/// Collapsed means that the expansion arrow isn't set to show the children of these layers.\n\tpub collapsed: CollapsedLayers,\n\t/// The full Git commit hash of the Graphite repository that was used to build the editor.\n\t/// We save this to provide a hint about which version of the editor was used to create the document.\n\tpub commit_hash: String,\n\t/// The current pan, tilt, and zoom state of the viewport's view of the document canvas.\n\tpub document_ptz: PTZ,\n\t/// The current mode that the user has set for rendering the document within the viewport.\n\t/// This is usually \"Normal\" but can be set to \"Outline\" or \"Pixels\" to see the canvas differently.\n\tpub render_mode: RenderMode,\n\t/// Sets whether or not all the viewport overlays should be drawn on top of the artwork.\n\t/// This includes tool interaction visualizations (like the transform cage and path anchors/handles), the grid, and more.\n\tpub overlays_visibility_settings: OverlaysVisibilitySettings,\n\t/// Sets whether or not the rulers should be drawn along the top and left edges of the viewport area.\n\tpub rulers_visible: bool,\n\t/// The current user choices for snapping behavior, including whether snapping is enabled at all.\n\tpub snapping_state: SnappingState,\n\t/// Sets whether or not the node graph is drawn (as an overlay) on top of the viewport area, or otherwise if it's hidden.\n\tpub graph_view_overlay_open: bool,\n\t/// The current opacity of the faded node graph background that covers up the artwork.\n\tpub graph_fade_artwork_percentage: f64,\n\n\t// =============================================\n\t// Fields omitted from the saved document format\n\t// =============================================\n\t//\n\t/// The name of the document, which is displayed in the tab and title bar of the editor.\n\t#[serde(skip)]\n\tpub name: String,\n\t/// The path of the to the document file.\n\t#[serde(skip)]\n\tpub(crate) path: Option<PathBuf>,\n\t/// Path to network currently viewed in the node graph overlay. This will eventually be stored in each panel, so that multiple panels can refer to different networks\n\t#[serde(skip)]\n\tbreadcrumb_network_path: Vec<NodeId>,\n\t/// Path to network that is currently selected. Updated based on the most recently clicked panel.\n\t#[serde(skip)]\n\tselection_network_path: Vec<NodeId>,\n\t/// Stack of document network snapshots for previous history states.\n\t#[serde(skip)]\n\tdocument_undo_history: VecDeque<NodeNetworkInterface>,\n\t/// Stack of document network snapshots for future history states.\n\t#[serde(skip)]\n\tdocument_redo_history: VecDeque<NodeNetworkInterface>,\n\t/// Hash of the document snapshot that was most recently saved to disk by the user.\n\t#[serde(skip)]\n\tsaved_hash: Option<u64>,\n\t/// Hash of the document snapshot that was most recently auto-saved to the IndexedDB storage that will reopen when the editor is reloaded.\n\t#[serde(skip)]\n\tauto_saved_hash: Option<u64>,\n\t/// The ID of the layer at the start of a range selection in the Layers panel.\n\t/// If the user clicks or Ctrl-clicks one layer, it becomes the start of the range selection and then Shift-clicking another layer selects all layers between the start and end.\n\t#[serde(skip)]\n\tlayer_range_selection_reference: Option<LayerNodeIdentifier>,\n\t/// Whether or not the editor has executed the network to render the document yet. If this is opened as an inactive tab, it won't be loaded initially because the active tab is prioritized.\n\t#[serde(skip)]\n\tpub is_loaded: bool,\n}\n\nimpl Default for DocumentMessageHandler {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\t// ======================\n\t\t\t// Child message handlers\n\t\t\t// ======================\n\t\t\tnavigation_handler: NavigationMessageHandler::default(),\n\t\t\tnode_graph_handler: NodeGraphMessageHandler::default(),\n\t\t\toverlays_message_handler: OverlaysMessageHandler::default(),\n\t\t\tproperties_panel_message_handler: PropertiesPanelMessageHandler::default(),\n\t\t\tdata_panel_message_handler: DataPanelMessageHandler::default(),\n\t\t\t// ============================================\n\t\t\t// Fields that are saved in the document format\n\t\t\t// ============================================\n\t\t\tnetwork_interface: default_document_network_interface(),\n\t\t\tcollapsed: CollapsedLayers::default(),\n\t\t\tcommit_hash: GRAPHITE_GIT_COMMIT_HASH.to_string(),\n\t\t\tdocument_ptz: PTZ::default(),\n\t\t\trender_mode: RenderMode::default(),\n\t\t\toverlays_visibility_settings: OverlaysVisibilitySettings::default(),\n\t\t\trulers_visible: true,\n\t\t\tgraph_view_overlay_open: false,\n\t\t\tsnapping_state: SnappingState::default(),\n\t\t\tgraph_fade_artwork_percentage: 80.,\n\t\t\t// =============================================\n\t\t\t// Fields omitted from the saved document format\n\t\t\t// =============================================\n\t\t\tname: DEFAULT_DOCUMENT_NAME.to_string(),\n\t\t\tpath: None,\n\t\t\tbreadcrumb_network_path: Vec::new(),\n\t\t\tselection_network_path: Vec::new(),\n\t\t\tdocument_undo_history: VecDeque::new(),\n\t\t\tdocument_redo_history: VecDeque::new(),\n\t\t\tsaved_hash: None,\n\t\t\tauto_saved_hash: None,\n\t\t\tlayer_range_selection_reference: None,\n\t\t\tis_loaded: false,\n\t\t}\n\t}\n}\n\n#[message_handler_data]\nimpl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMessageHandler {\n\tfn process_message(&mut self, message: DocumentMessage, responses: &mut VecDeque<Message>, context: DocumentMessageContext) {\n\t\tlet DocumentMessageContext {\n\t\t\tdocument_id,\n\t\t\tipp,\n\t\t\tpersistent_data,\n\t\t\texecutor,\n\t\t\tviewport,\n\t\t\tcurrent_tool,\n\t\t\tpreferences,\n\t\t\tdata_panel_open,\n\t\t\tlayers_panel_open,\n\t\t\tproperties_panel_open,\n\t\t} = context;\n\n\t\tmatch message {\n\t\t\t// Sub-messages\n\t\t\tDocumentMessage::Navigation(message) => {\n\t\t\t\tlet context = NavigationMessageContext {\n\t\t\t\t\tnetwork_interface: &mut self.network_interface,\n\t\t\t\t\tbreadcrumb_network_path: &self.breadcrumb_network_path,\n\t\t\t\t\tipp,\n\t\t\t\t\tdocument_ptz: &mut self.document_ptz,\n\t\t\t\t\tgraph_view_overlay_open: self.graph_view_overlay_open,\n\t\t\t\t\tpreferences,\n\t\t\t\t\tviewport,\n\t\t\t\t};\n\n\t\t\t\tself.navigation_handler.process_message(message, responses, context);\n\t\t\t}\n\t\t\tDocumentMessage::Overlays(message) => {\n\t\t\t\tlet visibility_settings = self.overlays_visibility_settings;\n\n\t\t\t\t// Send the overlays message to the overlays message handler\n\t\t\t\tself.overlays_message_handler\n\t\t\t\t\t.process_message(message, responses, OverlaysMessageContext { visibility_settings, viewport });\n\t\t\t}\n\t\t\tDocumentMessage::PropertiesPanel(message) => {\n\t\t\t\tlet context = PropertiesPanelMessageContext {\n\t\t\t\t\tnetwork_interface: &mut self.network_interface,\n\t\t\t\t\tselection_network_path: &self.selection_network_path,\n\t\t\t\t\tdocument_name: self.name.as_str(),\n\t\t\t\t\texecutor,\n\t\t\t\t\tpersistent_data,\n\t\t\t\t\tproperties_panel_open,\n\t\t\t\t};\n\t\t\t\tself.properties_panel_message_handler.process_message(message, responses, context);\n\t\t\t}\n\t\t\tDocumentMessage::DataPanel(message) => {\n\t\t\t\tself.data_panel_message_handler.process_message(\n\t\t\t\t\tmessage,\n\t\t\t\t\tresponses,\n\t\t\t\t\tDataPanelMessageContext {\n\t\t\t\t\t\tnetwork_interface: &mut self.network_interface,\n\t\t\t\t\t\tdata_panel_open,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\t\t\tDocumentMessage::NodeGraph(message) => {\n\t\t\t\tself.node_graph_handler.process_message(\n\t\t\t\t\tmessage,\n\t\t\t\t\tresponses,\n\t\t\t\t\tNodeGraphMessageContext {\n\t\t\t\t\t\tnetwork_interface: &mut self.network_interface,\n\t\t\t\t\t\tselection_network_path: &self.selection_network_path,\n\t\t\t\t\t\tbreadcrumb_network_path: &self.breadcrumb_network_path,\n\t\t\t\t\t\tdocument_id,\n\t\t\t\t\t\tcollapsed: &mut self.collapsed,\n\t\t\t\t\t\tipp,\n\t\t\t\t\t\tgraph_view_overlay_open: self.graph_view_overlay_open,\n\t\t\t\t\t\tgraph_fade_artwork_percentage: self.graph_fade_artwork_percentage,\n\t\t\t\t\t\tnavigation_handler: &self.navigation_handler,\n\t\t\t\t\t\tpreferences,\n\t\t\t\t\t\tlayers_panel_open,\n\t\t\t\t\t\tviewport,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\t\t\tDocumentMessage::GraphOperation(message) => {\n\t\t\t\tlet context = GraphOperationMessageContext {\n\t\t\t\t\tnetwork_interface: &mut self.network_interface,\n\t\t\t\t\tcollapsed: &mut self.collapsed,\n\t\t\t\t\tnode_graph: &mut self.node_graph_handler,\n\t\t\t\t};\n\t\t\t\tlet mut graph_operation_message_handler = GraphOperationMessageHandler {};\n\t\t\t\tgraph_operation_message_handler.process_message(message, responses, context);\n\t\t\t}\n\t\t\tDocumentMessage::AlignSelectedLayers { axis, aggregate } => {\n\t\t\t\tlet axis = match axis {\n\t\t\t\t\tAlignAxis::X => DVec2::X,\n\t\t\t\t\tAlignAxis::Y => DVec2::Y,\n\t\t\t\t};\n\t\t\t\tlet Some(combined_box) = self.network_interface.selected_layers_artwork_bounding_box_viewport() else {\n\t\t\t\t\treturn;\n\t\t\t\t};\n\n\t\t\t\tlet aggregated = match aggregate {\n\t\t\t\t\tAlignAggregate::Min => combined_box[0],\n\t\t\t\t\tAlignAggregate::Max => combined_box[1],\n\t\t\t\t\tAlignAggregate::Center => (combined_box[0] + combined_box[1]) / 2.,\n\t\t\t\t};\n\n\t\t\t\tlet mut added_transaction = false;\n\t\t\t\tfor layer in self.network_interface.selected_nodes().selected_unlocked_layers(&self.network_interface) {\n\t\t\t\t\tlet Some(bbox) = self.metadata().bounding_box_viewport(layer) else {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t};\n\t\t\t\t\tlet center = match aggregate {\n\t\t\t\t\t\tAlignAggregate::Min => bbox[0],\n\t\t\t\t\t\tAlignAggregate::Max => bbox[1],\n\t\t\t\t\t\t_ => (bbox[0] + bbox[1]) / 2.,\n\t\t\t\t\t};\n\t\t\t\t\tlet translation = (aggregated - center) * axis;\n\t\t\t\t\tif !added_transaction {\n\t\t\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\t\t\t\t\t\tadded_transaction = true;\n\t\t\t\t\t}\n\t\t\t\t\tresponses.add(GraphOperationMessage::TransformChange {\n\t\t\t\t\t\tlayer,\n\t\t\t\t\t\ttransform: DAffine2::from_translation(translation),\n\t\t\t\t\t\ttransform_in: TransformIn::Viewport,\n\t\t\t\t\t\tskip_rerender: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\tDocumentMessage::RemoveArtboards => {\n\t\t\t\tresponses.add(GraphOperationMessage::RemoveArtboards);\n\t\t\t}\n\t\t\tDocumentMessage::ClearLayersPanel => {\n\t\t\t\t// Send an empty layer list\n\t\t\t\tif layers_panel_open {\n\t\t\t\t\tlet layer_structure = Self::default().build_layer_structure(LayerNodeIdentifier::ROOT_PARENT);\n\t\t\t\t\tresponses.add(FrontendMessage::UpdateDocumentLayerStructure { layer_structure });\n\t\t\t\t}\n\n\t\t\t\t// Clear the control bar\n\t\t\t\tresponses.add(LayoutMessage::SendLayout {\n\t\t\t\t\tlayout: Layout::default(),\n\t\t\t\t\tlayout_target: LayoutTarget::LayersPanelControlLeftBar,\n\t\t\t\t});\n\t\t\t\tresponses.add(LayoutMessage::SendLayout {\n\t\t\t\t\tlayout: Layout::default(),\n\t\t\t\t\tlayout_target: LayoutTarget::LayersPanelControlRightBar,\n\t\t\t\t});\n\n\t\t\t\t// Clear the bottom bar\n\t\t\t\tresponses.add(LayoutMessage::SendLayout {\n\t\t\t\t\tlayout: Layout::default(),\n\t\t\t\t\tlayout_target: LayoutTarget::LayersPanelBottomBar,\n\t\t\t\t});\n\t\t\t}\n\t\t\tDocumentMessage::CreateEmptyFolder => {\n\t\t\t\tlet selected_nodes = self.network_interface.selected_nodes();\n\t\t\t\tlet id = NodeId::new();\n\n\t\t\t\tlet parent = self\n\t\t\t\t\t.network_interface\n\t\t\t\t\t.deepest_common_ancestor(&selected_nodes, &self.selection_network_path, true)\n\t\t\t\t\t.unwrap_or(LayerNodeIdentifier::ROOT_PARENT);\n\n\t\t\t\tlet insert_index = DocumentMessageHandler::get_calculated_insert_index(self.metadata(), &self.network_interface.selected_nodes(), parent);\n\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\t\t\t\tresponses.add(GraphOperationMessage::NewCustomLayer {\n\t\t\t\t\tid,\n\t\t\t\t\tnodes: Vec::new(),\n\t\t\t\t\tparent,\n\t\t\t\t\tinsert_index,\n\t\t\t\t});\n\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![id] });\n\t\t\t}\n\t\t\tDocumentMessage::DeleteNode { node_id } => {\n\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\n\t\t\t\tresponses.add(NodeGraphMessage::DeleteNodes {\n\t\t\t\t\tnode_ids: vec![node_id],\n\t\t\t\t\tdelete_children: true,\n\t\t\t\t});\n\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesUpdated);\n\t\t\t\tresponses.add(NodeGraphMessage::SendGraph);\n\t\t\t\tresponses.add(DocumentMessage::EndTransaction);\n\t\t\t}\n\t\t\tDocumentMessage::DeleteSelectedLayers => {\n\t\t\t\tresponses.add(NodeGraphMessage::DeleteSelectedNodes { delete_children: true });\n\t\t\t}\n\t\t\tDocumentMessage::DeselectAllLayers => {\n\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] });\n\t\t\t\tself.layer_range_selection_reference = None;\n\t\t\t}\n\t\t\tDocumentMessage::DocumentHistoryBackward => self.undo_with_history(viewport, responses),\n\t\t\tDocumentMessage::DocumentHistoryForward => self.redo_with_history(viewport, responses),\n\t\t\tDocumentMessage::DocumentStructureChanged => {\n\t\t\t\tif layers_panel_open {\n\t\t\t\t\tself.network_interface.load_structure();\n\t\t\t\t\tlet layer_structure = self.build_layer_structure(LayerNodeIdentifier::ROOT_PARENT);\n\n\t\t\t\t\tself.update_layers_panel_control_bar_widgets(layers_panel_open, responses);\n\t\t\t\t\tself.update_layers_panel_bottom_bar_widgets(layers_panel_open, responses);\n\n\t\t\t\t\tresponses.add(FrontendMessage::UpdateDocumentLayerStructure { layer_structure });\n\t\t\t\t}\n\t\t\t}\n\t\t\tDocumentMessage::DrawArtboardOverlays { context: overlay_context } => {\n\t\t\t\tif !overlay_context.visibility_settings.artboard_name() {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tfor layer in self.metadata().all_layers() {\n\t\t\t\t\tif !self.network_interface.is_artboard(&layer.to_node(), &[]) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tlet Some(bounds) = self.metadata().bounding_box_document(layer) else { continue };\n\t\t\t\t\tlet min = bounds[0].min(bounds[1]);\n\t\t\t\t\tlet max = bounds[0].max(bounds[1]);\n\n\t\t\t\t\tlet name = self.network_interface.display_name(&layer.to_node(), &[]);\n\n\t\t\t\t\t// Calculate position of the text\n\t\t\t\t\tlet corner_pos = if !self.document_ptz.flip {\n\t\t\t\t\t\t// Use the top-left corner\n\t\t\t\t\t\tmin\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Use the top-right corner, which appears to be the top-left due to being flipped\n\t\t\t\t\t\tDVec2::new(max.x, min.y)\n\t\t\t\t\t};\n\n\t\t\t\t\t// When the canvas is flipped, mirror the text so it appears correctly\n\t\t\t\t\tlet scale = if !self.document_ptz.flip { DVec2::ONE } else { DVec2::new(-1., 1.) };\n\n\t\t\t\t\t// Create a transform that puts the text at the true top-left regardless of flip\n\t\t\t\t\tlet transform = self.metadata().document_to_viewport\n\t\t\t\t\t\t* DAffine2::from_translation(corner_pos)\n\t\t\t\t\t\t* DAffine2::from_scale(DVec2::splat(self.document_ptz.zoom().recip()))\n\t\t\t\t\t\t* DAffine2::from_translation(-DVec2::Y * 4.)\n\t\t\t\t\t\t* DAffine2::from_scale(scale);\n\n\t\t\t\t\toverlay_context.text(&name, COLOR_OVERLAY_GRAY, None, transform, 0., [Pivot::Start, Pivot::End]);\n\t\t\t\t}\n\t\t\t}\n\t\t\tDocumentMessage::DuplicateSelectedLayers => {\n\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\n\t\t\t\tlet mut new_dragging = Vec::new();\n\t\t\t\tlet mut layers = self.network_interface.shallowest_unique_layers(&[]).collect::<Vec<_>>();\n\n\t\t\t\tlayers.sort_by_key(|layer| {\n\t\t\t\t\tlet Some(parent) = layer.parent(self.metadata()) else { return usize::MAX };\n\t\t\t\t\tDocumentMessageHandler::get_calculated_insert_index(self.metadata(), &SelectedNodes(vec![layer.to_node()]), parent)\n\t\t\t\t});\n\n\t\t\t\tfor layer in layers.into_iter().rev() {\n\t\t\t\t\tlet Some(parent) = layer.parent(self.metadata()) else { continue };\n\n\t\t\t\t\t// Copy the layer\n\t\t\t\t\tlet mut copy_ids = HashMap::new();\n\t\t\t\t\tlet node_id = layer.to_node();\n\t\t\t\t\tcopy_ids.insert(node_id, NodeId(0));\n\n\t\t\t\t\tself.network_interface\n\t\t\t\t\t\t.upstream_flow_back_from_nodes(vec![layer.to_node()], &[], FlowType::LayerChildrenUpstreamFlow)\n\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t.for_each(|(index, node_id)| {\n\t\t\t\t\t\t\tcopy_ids.insert(node_id, NodeId((index + 1) as u64));\n\t\t\t\t\t\t});\n\n\t\t\t\t\tlet nodes = self.network_interface.copy_nodes(&copy_ids, &[]).collect::<Vec<(NodeId, NodeTemplate)>>();\n\n\t\t\t\t\tlet insert_index = DocumentMessageHandler::get_calculated_insert_index(self.metadata(), &SelectedNodes(vec![layer.to_node()]), parent);\n\n\t\t\t\t\tlet new_ids: HashMap<_, _> = nodes.iter().map(|(id, _)| (*id, NodeId::new())).collect();\n\n\t\t\t\t\tlet layer_id = *new_ids.get(&NodeId(0)).expect(\"Node Id 0 should be a layer\");\n\t\t\t\t\tlet layer = LayerNodeIdentifier::new_unchecked(layer_id);\n\t\t\t\t\tnew_dragging.push(layer);\n\t\t\t\t\tresponses.add(NodeGraphMessage::AddNodes { nodes, new_ids });\n\t\t\t\t\tresponses.add(NodeGraphMessage::MoveLayerToStack { layer, parent, insert_index });\n\t\t\t\t}\n\t\t\t\tlet nodes = new_dragging.iter().map(|layer| layer.to_node()).collect();\n\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes });\n\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t}\n\t\t\tDocumentMessage::EnterNestedNetwork { node_id } => {\n\t\t\t\tself.breadcrumb_network_path.push(node_id);\n\t\t\t\tself.selection_network_path.clone_from(&self.breadcrumb_network_path);\n\t\t\t\tresponses.add(NodeGraphMessage::UnloadWires);\n\t\t\t\tresponses.add(NodeGraphMessage::SendGraph);\n\t\t\t\tresponses.add(DocumentMessage::ZoomCanvasToFitAll);\n\t\t\t\tresponses.add(NodeGraphMessage::UpdateNodeGraphWidth);\n\t\t\t}\n\t\t\tDocumentMessage::Escape => {\n\t\t\t\t// Abort dragging nodes\n\t\t\t\tif self.node_graph_handler.drag_start.is_some() {\n\t\t\t\t\tresponses.add(DocumentMessage::AbortTransaction);\n\t\t\t\t\tself.node_graph_handler.drag_start = None;\n\t\t\t\t\tself.node_graph_handler.select_if_not_dragged = None;\n\t\t\t\t}\n\t\t\t\t// Abort box selection\n\t\t\t\telse if self.node_graph_handler.box_selection_start.is_some() {\n\t\t\t\t\tself.node_graph_handler.box_selection_start = None;\n\t\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesSet {\n\t\t\t\t\t\tnodes: self.node_graph_handler.selection_before_pointer_down.clone(),\n\t\t\t\t\t});\n\t\t\t\t\tresponses.add(FrontendMessage::UpdateBox { box_selection: None });\n\t\t\t\t}\n\t\t\t\t// Abort wire in progress of being connected\n\t\t\t\telse if self.node_graph_handler.wire_in_progress_from_connector.is_some() {\n\t\t\t\t\tself.node_graph_handler.wire_in_progress_from_connector = None;\n\t\t\t\t\tself.node_graph_handler.wire_in_progress_to_connector = None;\n\t\t\t\t\tself.node_graph_handler.wire_in_progress_type = FrontendGraphDataType::General;\n\n\t\t\t\t\tresponses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None });\n\t\t\t\t\tresponses.add(DocumentMessage::AbortTransaction);\n\t\t\t\t}\n\t\t\t\t// Close the context menu if it's open\n\t\t\t\telse if self\n\t\t\t\t\t.node_graph_handler\n\t\t\t\t\t.context_menu\n\t\t\t\t\t.as_ref()\n\t\t\t\t\t.is_some_and(|context_menu| matches!(context_menu.context_menu_data, super::node_graph::utility_types::ContextMenuData::CreateNode { compatible_type: None }))\n\t\t\t\t{\n\t\t\t\t\tself.node_graph_handler.context_menu = None;\n\t\t\t\t\tresponses.add(FrontendMessage::UpdateContextMenuInformation { context_menu_information: None });\n\t\t\t\t}\n\t\t\t\t// Exit one level up if inside a nested network\n\t\t\t\telse if !self.breadcrumb_network_path.is_empty() {\n\t\t\t\t\tresponses.add(DocumentMessage::ExitNestedNetwork { steps_back: 1 });\n\t\t\t\t}\n\t\t\t\t// Close the graph view overlay if it's open\n\t\t\t\telse {\n\t\t\t\t\tresponses.add(DocumentMessage::GraphViewOverlay { open: false });\n\t\t\t\t}\n\t\t\t}\n\t\t\tDocumentMessage::ExitNestedNetwork { steps_back } => {\n\t\t\t\tfor _ in 0..steps_back {\n\t\t\t\t\tself.breadcrumb_network_path.pop();\n\t\t\t\t\tself.selection_network_path.clone_from(&self.breadcrumb_network_path);\n\t\t\t\t}\n\t\t\t\tresponses.add(NodeGraphMessage::UnloadWires);\n\t\t\t\tresponses.add(NodeGraphMessage::SendGraph);\n\t\t\t\tresponses.add(DocumentMessage::PTZUpdate);\n\t\t\t\tresponses.add(NodeGraphMessage::UpdateNodeGraphWidth);\n\t\t\t}\n\t\t\tDocumentMessage::FlipSelectedLayers { flip_axis } => {\n\t\t\t\tlet scale = match flip_axis {\n\t\t\t\t\tFlipAxis::X => DVec2::new(-1., 1.),\n\t\t\t\t\tFlipAxis::Y => DVec2::new(1., -1.),\n\t\t\t\t};\n\t\t\t\tif let Some([min, max]) = self.network_interface.selected_unlocked_layers_bounding_box_viewport() {\n\t\t\t\t\tlet center = (max + min) / 2.;\n\t\t\t\t\tlet bbox_trans = DAffine2::from_translation(-center);\n\t\t\t\t\tlet mut added_transaction = false;\n\t\t\t\t\tfor layer in self.network_interface.selected_nodes().selected_unlocked_layers(&self.network_interface) {\n\t\t\t\t\t\tif !added_transaction {\n\t\t\t\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\t\t\t\t\t\t\tadded_transaction = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tresponses.add(GraphOperationMessage::TransformChange {\n\t\t\t\t\t\t\tlayer,\n\t\t\t\t\t\t\ttransform: DAffine2::from_scale(scale),\n\t\t\t\t\t\t\ttransform_in: TransformIn::Scope { scope: bbox_trans },\n\t\t\t\t\t\t\tskip_rerender: false,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tDocumentMessage::RotateSelectedLayers { degrees } => {\n\t\t\t\t// Get the bounding box of selected layers in viewport space\n\t\t\t\tif let Some([min, max]) = self.network_interface.selected_unlocked_layers_bounding_box_viewport() {\n\t\t\t\t\t// Calculate the center of the bounding box to use as rotation pivot\n\t\t\t\t\tlet center = (max + min) / 2.;\n\t\t\t\t\t// Transform that moves pivot point to origin\n\t\t\t\t\tlet bbox_trans = DAffine2::from_translation(-center);\n\n\t\t\t\t\tlet mut added_transaction = false;\n\t\t\t\t\tfor layer in self.network_interface.selected_nodes().selected_unlocked_layers(&self.network_interface) {\n\t\t\t\t\t\tif !added_transaction {\n\t\t\t\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\t\t\t\t\t\t\tadded_transaction = true;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tresponses.add(GraphOperationMessage::TransformChange {\n\t\t\t\t\t\t\tlayer,\n\t\t\t\t\t\t\ttransform: DAffine2::from_angle(degrees.to_radians()),\n\t\t\t\t\t\t\ttransform_in: TransformIn::Scope { scope: bbox_trans },\n\t\t\t\t\t\t\tskip_rerender: false,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tDocumentMessage::GraphViewOverlay { open } => {\n\t\t\t\tlet opened = !self.graph_view_overlay_open && open;\n\t\t\t\tself.graph_view_overlay_open = open;\n\n\t\t\t\tresponses.add(FrontendMessage::UpdateGraphViewOverlay { open });\n\t\t\t\tresponses.add(FrontendMessage::UpdateGraphFadeArtwork {\n\t\t\t\t\tpercentage: self.graph_fade_artwork_percentage,\n\t\t\t\t});\n\n\t\t\t\t// Update the tilt menu bar buttons to be disabled when the graph is open\n\t\t\t\tresponses.add(MenuBarMessage::SendLayout);\n\n\t\t\t\tresponses.add(DocumentMessage::RenderRulers);\n\t\t\t\tresponses.add(DocumentMessage::RenderScrollbars);\n\t\t\t\tif opened {\n\t\t\t\t\tresponses.add(NodeGraphMessage::UnloadWires);\n\t\t\t\t\tresponses.add(NodeGraphMessage::UpdateNodeGraphWidth);\n\t\t\t\t}\n\t\t\t\tif open {\n\t\t\t\t\tresponses.add(ToolMessage::DeactivateTools);\n\t\t\t\t\tresponses.add(OverlaysMessage::Draw); // Clear the overlays\n\t\t\t\t\tresponses.add(NavigationMessage::CanvasTiltSet { angle_radians: 0. });\n\t\t\t\t\tresponses.add(NodeGraphMessage::UpdateGraphBarRight);\n\t\t\t\t\tresponses.add(NodeGraphMessage::SendGraph);\n\t\t\t\t\tresponses.add(NodeGraphMessage::UpdateHints);\n\t\t\t\t\tresponses.add(NodeGraphMessage::UpdateEdges);\n\t\t\t\t} else {\n\t\t\t\t\tresponses.add(ToolMessage::ActivateTool { tool_type: *current_tool });\n\t\t\t\t\tresponses.add(OverlaysMessage::Draw); // Redraw overlays when graph is closed\n\t\t\t\t}\n\t\t\t}\n\t\t\tDocumentMessage::GraphViewOverlayToggle => {\n\t\t\t\tresponses.add(DocumentMessage::GraphViewOverlay { open: !self.graph_view_overlay_open });\n\t\t\t}\n\t\t\tDocumentMessage::GridOptions { options } => {\n\t\t\t\tself.snapping_state.grid = options;\n\t\t\t\tself.snapping_state.grid_snapping = true;\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\tresponses.add(PortfolioMessage::UpdateDocumentWidgets);\n\t\t\t}\n\t\t\tDocumentMessage::GridOverlays { context: mut overlay_context } => {\n\t\t\t\tif self.snapping_state.grid_snapping {\n\t\t\t\t\tgrid_overlay(self, &mut overlay_context)\n\t\t\t\t}\n\t\t\t}\n\t\t\tDocumentMessage::GridVisibility { visible } => {\n\t\t\t\tself.snapping_state.grid_snapping = visible;\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t}\n\t\t\tDocumentMessage::GroupSelectedLayers { group_folder_type } => {\n\t\t\t\tself.handle_group_selected_layers(group_folder_type, responses);\n\t\t\t}\n\t\t\tDocumentMessage::MoveSelectedLayersTo { parent, insert_index } => {\n\t\t\t\tself.handle_move_selected_layers_to(parent, insert_index, responses);\n\t\t\t}\n\t\t\tDocumentMessage::MoveSelectedLayersToGroup { parent } => {\n\t\t\t\t// Group all shallowest unique selected layers in order\n\t\t\t\tlet all_layers_to_group_sorted = self.network_interface.shallowest_unique_layers_sorted(&self.selection_network_path);\n\n\t\t\t\tfor layer_to_group in all_layers_to_group_sorted.into_iter().rev() {\n\t\t\t\t\tresponses.add(NodeGraphMessage::MoveLayerToStack {\n\t\t\t\t\t\tlayer: layer_to_group,\n\t\t\t\t\t\tparent,\n\t\t\t\t\t\tinsert_index: 0,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![parent.to_node()] });\n\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\tresponses.add(DocumentMessage::DocumentStructureChanged);\n\t\t\t\tresponses.add(NodeGraphMessage::SendGraph);\n\t\t\t}\n\t\t\tDocumentMessage::NudgeSelectedLayers {\n\t\t\t\tdelta_x,\n\t\t\t\tdelta_y,\n\t\t\t\tresize,\n\t\t\t\tresize_opposite_corner,\n\t\t\t} => {\n\t\t\t\tself.handle_nudge_selected_layers(delta_x, delta_y, resize, resize_opposite_corner, ipp, viewport, responses);\n\t\t\t}\n\t\t\tDocumentMessage::PasteImage {\n\t\t\t\tname,\n\t\t\t\timage,\n\t\t\t\tmouse,\n\t\t\t\tparent_and_insert_index,\n\t\t\t} => {\n\t\t\t\t// All the image's pixels have been converted to 0..=1, linear, and premultiplied by `Color::from_rgba8_srgb`\n\n\t\t\t\tlet image_size = DVec2::new(image.width as f64, image.height as f64);\n\n\t\t\t\t// Align the layer with the mouse or center of viewport\n\t\t\t\tlet viewport_location = mouse.map_or(viewport.center_in_viewport_space().into_dvec2() + viewport.offset().into_dvec2(), |pos| pos.into());\n\n\t\t\t\tlet document_to_viewport = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz);\n\t\t\t\tlet center_in_viewport = DAffine2::from_translation(document_to_viewport.inverse().transform_point2(viewport_location - viewport.offset().into_dvec2()));\n\t\t\t\tlet center_in_viewport_layerspace = center_in_viewport;\n\n\t\t\t\t// Make layer the size of the image\n\t\t\t\tlet fit_image_size = DAffine2::from_scale_angle_translation(image_size, 0., image_size / -2.);\n\n\t\t\t\tlet transform = center_in_viewport_layerspace * fit_image_size;\n\n\t\t\t\tlet layer_node_id = NodeId::new();\n\t\t\t\tlet layer_id = LayerNodeIdentifier::new_unchecked(layer_node_id);\n\n\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\n\t\t\t\tlet layer = graph_modification_utils::new_image_layer(Table::new_from_element(Raster::new_cpu(image)), layer_node_id, self.new_layer_parent(true), responses);\n\n\t\t\t\tif let Some(name) = name {\n\t\t\t\t\tresponses.add(NodeGraphMessage::SetDisplayName {\n\t\t\t\t\t\tnode_id: layer.to_node(),\n\t\t\t\t\t\talias: name,\n\t\t\t\t\t\tskip_adding_history_step: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tif let Some((parent, insert_index)) = parent_and_insert_index {\n\t\t\t\t\tresponses.add(NodeGraphMessage::MoveLayerToStack {\n\t\t\t\t\t\tlayer: layer_id,\n\t\t\t\t\t\tparent,\n\t\t\t\t\t\tinsert_index,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\t// `layer` cannot be `ROOT_PARENT` since it is the newly created layer\n\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] });\n\n\t\t\t\tresponses.add(GraphOperationMessage::TransformSet {\n\t\t\t\t\tlayer,\n\t\t\t\t\ttransform,\n\t\t\t\t\ttransform_in: TransformIn::Local,\n\t\t\t\t\tskip_rerender: false,\n\t\t\t\t});\n\n\t\t\t\t// Force chosen tool to be Select Tool after importing image.\n\t\t\t\tresponses.add(ToolMessage::ActivateTool { tool_type: ToolType::Select });\n\t\t\t}\n\t\t\tDocumentMessage::PasteSvg {\n\t\t\t\tname,\n\t\t\t\tsvg,\n\t\t\t\tmouse,\n\t\t\t\tparent_and_insert_index,\n\t\t\t} => {\n\t\t\t\tlet document_to_viewport = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz);\n\t\t\t\tlet viewport_location = mouse.map_or(viewport.center_in_viewport_space().into_dvec2() + viewport.offset().into_dvec2(), |pos| pos.into());\n\t\t\t\tlet center_in_viewport = DAffine2::from_translation(document_to_viewport.inverse().transform_point2(viewport_location - viewport.offset().into_dvec2()));\n\n\t\t\t\tlet layer_node_id = NodeId::new();\n\t\t\t\tlet layer_id = LayerNodeIdentifier::new_unchecked(layer_node_id);\n\n\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\n\t\t\t\tlet layer = graph_modification_utils::new_svg_layer(svg, center_in_viewport, layer_node_id, self.new_layer_parent(true), responses);\n\n\t\t\t\tif let Some(name) = name {\n\t\t\t\t\tresponses.add(NodeGraphMessage::SetDisplayName {\n\t\t\t\t\t\tnode_id: layer.to_node(),\n\t\t\t\t\t\talias: name,\n\t\t\t\t\t\tskip_adding_history_step: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tif let Some((parent, insert_index)) = parent_and_insert_index {\n\t\t\t\t\tresponses.add(NodeGraphMessage::MoveLayerToStack {\n\t\t\t\t\t\tlayer: layer_id,\n\t\t\t\t\t\tparent,\n\t\t\t\t\t\tinsert_index,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] });\n\t\t\t\tresponses.add(ToolMessage::ActivateTool { tool_type: ToolType::Select });\n\t\t\t}\n\t\t\tDocumentMessage::Redo => {\n\t\t\t\tif self.network_interface.transaction_status() != TransactionStatus::Finished {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tresponses.add(SelectToolMessage::Abort);\n\t\t\t\tresponses.add(DocumentMessage::DocumentHistoryForward);\n\t\t\t\tresponses.add(ToolMessage::Redo);\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\tresponses.add(EventMessage::SelectionChanged);\n\t\t\t}\n\t\t\tDocumentMessage::RenameDocument { new_name } => {\n\t\t\t\tself.name = new_name.clone();\n\n\t\t\t\tself.path = None;\n\t\t\t\tself.set_save_state(false);\n\t\t\t\tself.set_auto_save_state(false);\n\n\t\t\t\tresponses.add(PortfolioMessage::UpdateOpenDocumentsList);\n\t\t\t\tresponses.add(NodeGraphMessage::UpdateNewNodeGraph);\n\t\t\t}\n\t\t\tDocumentMessage::RenderRulers => {\n\t\t\t\tlet current_ptz = if self.graph_view_overlay_open {\n\t\t\t\t\tlet Some(network_metadata) = self.network_interface.network_metadata(&self.breadcrumb_network_path) else {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t};\n\t\t\t\t\t&network_metadata.persistent_metadata.navigation_metadata.node_graph_ptz\n\t\t\t\t} else {\n\t\t\t\t\t&self.document_ptz\n\t\t\t\t};\n\t\t\t\tlet document_to_viewport = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), current_ptz);\n\n\t\t\t\tlet ruler_scale = if !self.graph_view_overlay_open {\n\t\t\t\t\tself.navigation_handler.snapped_zoom(current_ptz.zoom())\n\t\t\t\t} else {\n\t\t\t\t\tself.navigation_handler.snapped_zoom(current_ptz.zoom() * (crate::consts::GRID_SIZE as f64))\n\t\t\t\t};\n\n\t\t\t\tlet ruler_origin = document_to_viewport.transform_point2(DVec2::ZERO);\n\t\t\t\tlet log = ruler_scale.log2();\n\t\t\t\tlet mut ruler_interval: f64 = if log < 0. { 100. * 2_f64.powf(-log.ceil()) } else { 100. / 2_f64.powf(log.ceil()) };\n\n\t\t\t\t// When the interval becomes too small, force it to be a whole number, then to powers of 10.\n\t\t\t\t// The progression of intervals is:\n\t\t\t\t// ..., 100, 50, 25, 12.5, 6 (6.25), 4 (3.125), 2 (1.5625), 1, 0.1, 0.01, ...\n\t\t\t\tif ruler_interval < 1. {\n\t\t\t\t\truler_interval = 10_f64.powf(ruler_interval.log10().ceil());\n\t\t\t\t} else if ruler_interval < 12.5 {\n\t\t\t\t\t// Round to nearest even number\n\t\t\t\t\truler_interval = 2. * (ruler_interval / 2.).round();\n\t\t\t\t}\n\n\t\t\t\tif self.graph_view_overlay_open {\n\t\t\t\t\truler_interval = ruler_interval.max(1.);\n\t\t\t\t}\n\n\t\t\t\tlet ruler_spacing = ruler_interval * ruler_scale;\n\n\t\t\t\tresponses.add(FrontendMessage::UpdateDocumentRulers {\n\t\t\t\t\torigin: ruler_origin.into(),\n\t\t\t\t\tspacing: ruler_spacing,\n\t\t\t\t\tinterval: ruler_interval,\n\t\t\t\t\tvisible: self.rulers_visible,\n\t\t\t\t});\n\t\t\t}\n\t\t\tDocumentMessage::RenderScrollbars => {\n\t\t\t\tlet document_transform_scale = self.navigation_handler.snapped_zoom(self.document_ptz.zoom()) / viewport.scale();\n\n\t\t\t\tlet scale = 0.5 + ASYMPTOTIC_EFFECT + document_transform_scale * SCALE_EFFECT;\n\n\t\t\t\tlet viewport_size = viewport.size().into_dvec2();\n\t\t\t\tlet viewport_mid = viewport.center_in_viewport_space().into_dvec2();\n\t\t\t\tlet [bounds1, bounds2] = if !self.graph_view_overlay_open {\n\t\t\t\t\tself.network_interface.document_bounds_viewport_space(true).unwrap_or([viewport_mid; 2])\n\t\t\t\t} else {\n\t\t\t\t\tself.network_interface.graph_bounds_viewport_space(&self.breadcrumb_network_path).unwrap_or([viewport_mid; 2])\n\t\t\t\t};\n\t\t\t\tlet bounds1 = bounds1.min(viewport_mid) - viewport_size * scale;\n\t\t\t\tlet bounds2 = bounds2.max(viewport_mid) + viewport_size * scale;\n\t\t\t\tlet bounds_length = (bounds2 - bounds1) * (1. + SCROLLBAR_SPACING);\n\t\t\t\tlet scrollbar_position = DVec2::splat(0.5) - (bounds1.lerp(bounds2, 0.5) - viewport_mid) / (bounds_length - viewport_size);\n\t\t\t\tlet scrollbar_multiplier = bounds_length - viewport_size;\n\t\t\t\tlet scrollbar_size = viewport_size / bounds_length;\n\n\t\t\t\tresponses.add(FrontendMessage::UpdateDocumentScrollbars {\n\t\t\t\t\tposition: scrollbar_position.into(),\n\t\t\t\t\tsize: scrollbar_size.into(),\n\t\t\t\t\tmultiplier: scrollbar_multiplier.into(),\n\t\t\t\t});\n\t\t\t}\n\t\t\tDocumentMessage::SaveDocument | DocumentMessage::SaveDocumentAs => {\n\t\t\t\tif let DocumentMessage::SaveDocumentAs = message {\n\t\t\t\t\tself.path = None;\n\t\t\t\t}\n\n\t\t\t\tself.set_save_state(true);\n\t\t\t\tresponses.add(PortfolioMessage::AutoSaveActiveDocument);\n\t\t\t\t// Update the save status of the just saved document\n\t\t\t\tresponses.add(PortfolioMessage::UpdateOpenDocumentsList);\n\n\t\t\t\tresponses.add(FrontendMessage::TriggerSaveDocument {\n\t\t\t\t\tdocument_id,\n\t\t\t\t\tname: format!(\"{}.{}\", self.name.clone(), FILE_EXTENSION),\n\t\t\t\t\tpath: self.path.clone(),\n\t\t\t\t\tcontent: self.serialize_document().into_bytes().into(),\n\t\t\t\t})\n\t\t\t}\n\t\t\tDocumentMessage::SavedDocument { path } => {\n\t\t\t\tself.path = path;\n\n\t\t\t\tresponses.add(PortfolioMessage::AutoSaveActiveDocument);\n\n\t\t\t\t// Update the name to match the file stem\n\t\t\t\tlet document_name_from_path = self.path.as_ref().and_then(|path| {\n\t\t\t\t\tif path.extension().is_some_and(|e| e == FILE_EXTENSION) {\n\t\t\t\t\t\tpath.file_stem().map(|n| n.to_string_lossy().to_string())\n\t\t\t\t\t} else {\n\t\t\t\t\t\tNone\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tif let Some(name) = document_name_from_path {\n\t\t\t\t\tself.name = name;\n\n\t\t\t\t\tresponses.add(PortfolioMessage::UpdateOpenDocumentsList);\n\t\t\t\t\tresponses.add(NodeGraphMessage::UpdateNewNodeGraph);\n\t\t\t\t}\n\t\t\t}\n\t\t\tDocumentMessage::MarkAsSaved => {\n\t\t\t\tself.set_save_state(true);\n\t\t\t\tresponses.add(PortfolioMessage::UpdateOpenDocumentsList);\n\t\t\t}\n\t\t\tDocumentMessage::SelectParentLayer => {\n\t\t\t\tlet selected_nodes = self.network_interface.selected_nodes();\n\t\t\t\tlet selected_layers = selected_nodes.selected_layers(self.metadata());\n\n\t\t\t\tlet mut parent_layers = HashSet::new();\n\n\t\t\t\t// Find the parent of each selected layer\n\t\t\t\tfor layer in selected_layers {\n\t\t\t\t\t// Get this layer's parent\n\t\t\t\t\tlet Some(parent) = layer.parent(self.metadata()) else { continue };\n\n\t\t\t\t\t// Either use the parent, or keep the same layer if it's already at the top level\n\t\t\t\t\tlet to_insert = if parent == LayerNodeIdentifier::ROOT_PARENT { layer } else { parent };\n\n\t\t\t\t\t// Add the layer to the set of those which will become selected\n\t\t\t\t\tparent_layers.insert(to_insert.to_node());\n\t\t\t\t}\n\n\t\t\t\t// Select each parent layer\n\t\t\t\tif !parent_layers.is_empty() {\n\t\t\t\t\tlet nodes = parent_layers.into_iter().collect();\n\t\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes });\n\t\t\t\t\tresponses.add(EventMessage::SelectionChanged);\n\t\t\t\t}\n\t\t\t}\n\t\t\tDocumentMessage::SelectAllLayers => {\n\t\t\t\tif !self.overlays_visibility_settings.selection_outline() {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet metadata = self.metadata();\n\t\t\t\tlet all_layers_except_artboards_invisible_and_locked = metadata.all_layers().filter(|&layer| !self.network_interface.is_artboard(&layer.to_node(), &[])).filter(|&layer| {\n\t\t\t\t\tself.network_interface.selected_nodes().layer_visible(layer, &self.network_interface) && !self.network_interface.selected_nodes().layer_locked(layer, &self.network_interface)\n\t\t\t\t});\n\t\t\t\tlet nodes = all_layers_except_artboards_invisible_and_locked.map(|layer| layer.to_node()).collect();\n\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes });\n\t\t\t}\n\t\t\tDocumentMessage::SelectedLayersLower => {\n\t\t\t\tresponses.add(DocumentMessage::SelectedLayersReorder { relative_index_offset: 1 });\n\t\t\t}\n\t\t\tDocumentMessage::SelectedLayersLowerToBack => {\n\t\t\t\tresponses.add(DocumentMessage::SelectedLayersReorder { relative_index_offset: isize::MAX });\n\t\t\t}\n\t\t\tDocumentMessage::SelectedLayersRaise => {\n\t\t\t\tresponses.add(DocumentMessage::SelectedLayersReorder { relative_index_offset: -1 });\n\t\t\t}\n\t\t\tDocumentMessage::SelectedLayersRaiseToFront => {\n\t\t\t\tresponses.add(DocumentMessage::SelectedLayersReorder { relative_index_offset: isize::MIN });\n\t\t\t}\n\t\t\tDocumentMessage::SelectedLayersReverse => {\n\t\t\t\tself.selected_layers_reverse(responses);\n\t\t\t}\n\t\t\tDocumentMessage::SelectedLayersReorder { relative_index_offset } => {\n\t\t\t\tself.selected_layers_reorder(relative_index_offset, responses);\n\t\t\t}\n\t\t\tDocumentMessage::ClipLayer { id } => {\n\t\t\t\tlet layer = LayerNodeIdentifier::new(id, &self.network_interface);\n\n\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\t\t\t\tresponses.add(GraphOperationMessage::ClipModeToggle { layer });\n\t\t\t}\n\t\t\tDocumentMessage::SelectLayer { id, ctrl, shift } => {\n\t\t\t\tlet layer = LayerNodeIdentifier::new(id, &self.network_interface);\n\n\t\t\t\tlet mut nodes = vec![];\n\n\t\t\t\t// If we have shift pressed and a layer already selected then fill the range\n\t\t\t\tif let Some(last_selected) = self.layer_range_selection_reference.filter(|_| shift) {\n\t\t\t\t\tif last_selected == LayerNodeIdentifier::ROOT_PARENT {\n\t\t\t\t\t\tlog::error!(\"ROOT_PARENT cannot be selected in SelectLayer\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tnodes.push(last_selected.to_node());\n\t\t\t\t\tnodes.push(id);\n\n\t\t\t\t\t// Fill the selection range\n\t\t\t\t\tself.metadata()\n\t\t\t\t\t\t.all_layers()\n\t\t\t\t\t\t.skip_while(|&node| node != layer && node != last_selected)\n\t\t\t\t\t\t.skip(1)\n\t\t\t\t\t\t.take_while(|&node| node != layer && node != last_selected)\n\t\t\t\t\t\t.for_each(|node| {\n\t\t\t\t\t\t\tif node == LayerNodeIdentifier::ROOT_PARENT {\n\t\t\t\t\t\t\t\tlog::error!(\"ROOT_PARENT should not exist in all_layers\")\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tnodes.push(node.to_node())\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tif ctrl {\n\t\t\t\t\t\t// Toggle selection when holding ctrl\n\t\t\t\t\t\tif self.network_interface.selected_nodes().selected_layers_contains(layer, self.metadata()) {\n\t\t\t\t\t\t\tresponses.add_front(NodeGraphMessage::SelectedNodesRemove { nodes: vec![id] });\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tresponses.add_front(NodeGraphMessage::SelectedNodesAdd { nodes: vec![id] });\n\t\t\t\t\t\t}\n\t\t\t\t\t\tresponses.add(EventMessage::SelectionChanged);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tnodes.push(id);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Set our last selection reference\n\t\t\t\t\tself.layer_range_selection_reference = Some(layer);\n\t\t\t\t}\n\n\t\t\t\t// Don't create messages for empty operations\n\t\t\t\tif !nodes.is_empty() {\n\t\t\t\t\t// Add or set our selected layers\n\t\t\t\t\tif ctrl {\n\t\t\t\t\t\tresponses.add_front(NodeGraphMessage::SelectedNodesAdd { nodes });\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresponses.add_front(NodeGraphMessage::SelectedNodesSet { nodes });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tDocumentMessage::SetActivePanel { active_panel: panel } => {\n\t\t\t\tmatch panel {\n\t\t\t\t\tPanelType::Document => {\n\t\t\t\t\t\tif self.graph_view_overlay_open {\n\t\t\t\t\t\t\tself.selection_network_path.clone_from(&self.breadcrumb_network_path);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tself.selection_network_path = vec![]\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tPanelType::Layers => self.selection_network_path = vec![],\n\t\t\t\t\t_ => {}\n\t\t\t\t}\n\t\t\t\tresponses.add(PropertiesPanelMessage::Refresh);\n\t\t\t\tresponses.add(NodeGraphMessage::UpdateLayerPanel);\n\t\t\t\tresponses.add(NodeGraphMessage::UpdateInSelectedNetwork);\n\t\t\t}\n\t\t\tDocumentMessage::SetBlendModeForSelectedLayers { blend_mode } => {\n\t\t\t\tfor layer in self.network_interface.selected_nodes().selected_layers_except_artboards(&self.network_interface) {\n\t\t\t\t\tresponses.add(GraphOperationMessage::BlendModeSet { layer, blend_mode });\n\t\t\t\t}\n\t\t\t}\n\t\t\tDocumentMessage::SetGraphFadeArtwork { percentage } => {\n\t\t\t\tself.graph_fade_artwork_percentage = percentage;\n\t\t\t\tresponses.add(FrontendMessage::UpdateGraphFadeArtwork { percentage });\n\t\t\t}\n\t\t\tDocumentMessage::SetNodePinned { node_id, pinned } => {\n\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\t\t\t\tresponses.add(NodeGraphMessage::SetPinned { node_id, pinned });\n\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesUpdated);\n\t\t\t\tresponses.add(NodeGraphMessage::SendGraph);\n\t\t\t}\n\t\t\tDocumentMessage::SetOpacityForSelectedLayers { opacity } => {\n\t\t\t\tlet opacity = opacity.clamp(0., 1.);\n\t\t\t\tfor layer in self.network_interface.selected_nodes().selected_layers_except_artboards(&self.network_interface) {\n\t\t\t\t\tresponses.add(GraphOperationMessage::OpacitySet { layer, opacity });\n\t\t\t\t}\n\t\t\t}\n\t\t\tDocumentMessage::SetFillForSelectedLayers { fill } => {\n\t\t\t\tlet fill = fill.clamp(0., 1.);\n\t\t\t\tfor layer in self.network_interface.selected_nodes().selected_layers_except_artboards(&self.network_interface) {\n\t\t\t\t\tresponses.add(GraphOperationMessage::BlendingFillSet { layer, fill });\n\t\t\t\t}\n\t\t\t}\n\t\t\tDocumentMessage::SetOverlaysVisibility { visible, overlays_type } => {\n\t\t\t\tlet visibility_settings = &mut self.overlays_visibility_settings;\n\t\t\t\tlet overlays_type = match overlays_type {\n\t\t\t\t\tSome(overlays_type) => overlays_type,\n\t\t\t\t\tNone => {\n\t\t\t\t\t\tvisibility_settings.all = visible;\n\t\t\t\t\t\tresponses.add(EventMessage::ToolAbort);\n\t\t\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tmatch overlays_type {\n\t\t\t\t\tOverlaysType::ArtboardName => visibility_settings.artboard_name = visible,\n\t\t\t\t\tOverlaysType::CompassRose => visibility_settings.compass_rose = visible,\n\t\t\t\t\tOverlaysType::QuickMeasurement => visibility_settings.quick_measurement = visible,\n\t\t\t\t\tOverlaysType::TransformMeasurement => visibility_settings.transform_measurement = visible,\n\t\t\t\t\tOverlaysType::TransformCage => visibility_settings.transform_cage = visible,\n\t\t\t\t\tOverlaysType::HoverOutline => visibility_settings.hover_outline = visible,\n\t\t\t\t\tOverlaysType::SelectionOutline => visibility_settings.selection_outline = visible,\n\t\t\t\t\tOverlaysType::LayerOriginCross => visibility_settings.layer_origin_cross = visible,\n\t\t\t\t\tOverlaysType::Pivot => visibility_settings.pivot = visible,\n\t\t\t\t\tOverlaysType::Origin => visibility_settings.origin = visible,\n\t\t\t\t\tOverlaysType::Path => visibility_settings.path = visible,\n\t\t\t\t\tOverlaysType::Anchors => {\n\t\t\t\t\t\tvisibility_settings.anchors = visible;\n\t\t\t\t\t\tresponses.add(PortfolioMessage::UpdateDocumentWidgets);\n\t\t\t\t\t}\n\t\t\t\t\tOverlaysType::Handles => visibility_settings.handles = visible,\n\t\t\t\t}\n\n\t\t\t\tresponses.add(EventMessage::ToolAbort);\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t}\n\t\t\tDocumentMessage::SetRangeSelectionLayer { new_layer } => {\n\t\t\t\tself.layer_range_selection_reference = new_layer;\n\t\t\t}\n\t\t\tDocumentMessage::SetSnapping { closure, snapping_state } => {\n\t\t\t\tif let Some(closure) = closure {\n\t\t\t\t\t*closure(&mut self.snapping_state) = snapping_state;\n\t\t\t\t}\n\t\t\t}\n\t\t\tDocumentMessage::SetToNodeOrLayer { node_id, is_layer } => {\n\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\t\t\t\tresponses.add(NodeGraphMessage::SetToNodeOrLayer { node_id, is_layer });\n\t\t\t\tresponses.add(DocumentMessage::EndTransaction);\n\t\t\t}\n\t\t\tDocumentMessage::SetRenderMode { render_mode } => {\n\t\t\t\tself.render_mode = render_mode;\n\t\t\t\tresponses.add_front(NodeGraphMessage::RunDocumentGraph);\n\t\t\t}\n\t\t\tDocumentMessage::AddTransaction => {\n\t\t\t\t// Reverse order since they are added to the front\n\t\t\t\tresponses.add_front(DocumentMessage::CommitTransaction);\n\t\t\t\tresponses.add_front(DocumentMessage::StartTransaction);\n\t\t\t}\n\t\t\t// Note: A transaction should never be started in a scope that mutates the network interface, since it will only be run after that scope ends.\n\t\t\tDocumentMessage::StartTransaction => {\n\t\t\t\tself.network_interface.start_transaction();\n\t\t\t\tlet network_interface_clone = self.network_interface.clone();\n\t\t\t\tself.document_undo_history.push_back(network_interface_clone);\n\t\t\t\tif self.document_undo_history.len() > crate::consts::MAX_UNDO_HISTORY_LEN {\n\t\t\t\t\tself.document_undo_history.pop_front();\n\t\t\t\t}\n\t\t\t\t// Push the UpdateOpenDocumentsList message to the bus in order to update the save status of the open documents\n\t\t\t\tresponses.add(PortfolioMessage::UpdateOpenDocumentsList);\n\t\t\t}\n\t\t\t// Commits the transaction if the network was mutated since the transaction started, otherwise it cancels the transaction\n\t\t\tDocumentMessage::EndTransaction => match self.network_interface.transaction_status() {\n\t\t\t\tTransactionStatus::Started => {\n\t\t\t\t\tresponses.add_front(DocumentMessage::CancelTransaction);\n\t\t\t\t}\n\t\t\t\tTransactionStatus::Modified => {\n\t\t\t\t\tresponses.add_front(DocumentMessage::CommitTransaction);\n\t\t\t\t}\n\t\t\t\tTransactionStatus::Finished => {}\n\t\t\t},\n\t\t\tDocumentMessage::CancelTransaction => {\n\t\t\t\tself.network_interface.finish_transaction();\n\t\t\t\tself.document_undo_history.pop_back();\n\t\t\t}\n\t\t\tDocumentMessage::CommitTransaction => {\n\t\t\t\tif self.network_interface.transaction_status() == TransactionStatus::Finished {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tself.network_interface.finish_transaction();\n\t\t\t\tself.document_redo_history.clear();\n\t\t\t\tresponses.add(PortfolioMessage::UpdateOpenDocumentsList);\n\t\t\t}\n\t\t\tDocumentMessage::AbortTransaction => match self.network_interface.transaction_status() {\n\t\t\t\tTransactionStatus::Started => {\n\t\t\t\t\tresponses.add_front(DocumentMessage::CancelTransaction);\n\t\t\t\t}\n\t\t\t\tTransactionStatus::Modified => {\n\t\t\t\t\tresponses.add(DocumentMessage::RepeatedAbortTransaction { undo_count: 1 });\n\t\t\t\t}\n\t\t\t\tTransactionStatus::Finished => {}\n\t\t\t},\n\t\t\tDocumentMessage::RepeatedAbortTransaction { undo_count } => {\n\t\t\t\tif self.network_interface.transaction_status() == TransactionStatus::Finished {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tfor _ in 0..undo_count {\n\t\t\t\t\tself.undo(viewport, responses);\n\t\t\t\t}\n\n\t\t\t\tself.network_interface.finish_transaction();\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\tresponses.add(PortfolioMessage::UpdateOpenDocumentsList);\n\t\t\t}\n\t\t\tDocumentMessage::ToggleLayerExpansion { id, recursive } => {\n\t\t\t\tlet layer = LayerNodeIdentifier::new(id, &self.network_interface);\n\t\t\t\tlet metadata = self.metadata();\n\n\t\t\t\tlet is_collapsed = self.collapsed.0.contains(&layer);\n\n\t\t\t\tif is_collapsed {\n\t\t\t\t\tif recursive {\n\t\t\t\t\t\tlet children: HashSet<_> = layer.descendants(metadata).collect();\n\t\t\t\t\t\tself.collapsed.0.retain(|collapsed_layer| !children.contains(collapsed_layer) && collapsed_layer != &layer);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tself.collapsed.0.retain(|collapsed_layer| collapsed_layer != &layer);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif recursive {\n\t\t\t\t\t\tlet children_to_add: Vec<_> = layer.descendants(metadata).filter(|child| !self.collapsed.0.contains(child)).collect();\n\t\t\t\t\t\tself.collapsed.0.extend(children_to_add);\n\t\t\t\t\t}\n\t\t\t\t\tself.collapsed.0.push(layer);\n\t\t\t\t}\n\n\t\t\t\tresponses.add(NodeGraphMessage::SendGraph);\n\t\t\t}\n\t\t\tDocumentMessage::ToggleSelectedLocked => responses.add(NodeGraphMessage::ToggleSelectedLocked),\n\t\t\tDocumentMessage::ToggleSelectedVisibility => {\n\t\t\t\tresponses.add(NodeGraphMessage::ToggleSelectedVisibility);\n\t\t\t}\n\t\t\tDocumentMessage::ToggleGridVisibility => {\n\t\t\t\tself.snapping_state.grid_snapping = !self.snapping_state.grid_snapping;\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\tresponses.add(PortfolioMessage::UpdateDocumentWidgets);\n\t\t\t}\n\t\t\tDocumentMessage::ToggleOverlaysVisibility => {\n\t\t\t\tself.overlays_visibility_settings.all = !self.overlays_visibility_settings.all();\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\tresponses.add(PortfolioMessage::UpdateDocumentWidgets);\n\t\t\t}\n\t\t\tDocumentMessage::ToggleSnapping => {\n\t\t\t\tself.snapping_state.snapping_enabled = !self.snapping_state.snapping_enabled;\n\t\t\t\tresponses.add(PortfolioMessage::UpdateDocumentWidgets);\n\t\t\t}\n\t\t\tDocumentMessage::UpdateUpstreamTransforms {\n\t\t\t\tupstream_footprints,\n\t\t\t\tlocal_transforms,\n\t\t\t\tfirst_element_source_id,\n\t\t\t} => {\n\t\t\t\tself.network_interface.update_transforms(upstream_footprints, local_transforms);\n\t\t\t\tself.network_interface.update_first_element_source_id(first_element_source_id);\n\t\t\t}\n\t\t\tDocumentMessage::UpdateClickTargets { click_targets } => {\n\t\t\t\t// TODO: Allow non layer nodes to have click targets\n\t\t\t\tlet layer_click_targets = click_targets\n\t\t\t\t\t.into_iter()\n\t\t\t\t\t.filter(|(node_id, _)|\n\t\t\t\t\t\t// Ensure that the layer is in the document network to prevent logging an error\n\t\t\t\t\t\tself.network_interface.document_network().nodes.contains_key(node_id))\n\t\t\t\t\t.filter_map(|(node_id, click_targets)| {\n\t\t\t\t\t\tself.network_interface.is_layer(&node_id, &[]).then(|| {\n\t\t\t\t\t\t\tlet layer = LayerNodeIdentifier::new(node_id, &self.network_interface);\n\t\t\t\t\t\t\t(layer, click_targets)\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t\t\t.collect();\n\t\t\t\tself.network_interface.update_click_targets(layer_click_targets);\n\t\t\t}\n\t\t\tDocumentMessage::UpdateClipTargets { clip_targets } => {\n\t\t\t\tself.network_interface.update_clip_targets(clip_targets);\n\t\t\t}\n\t\t\tDocumentMessage::UpdateVectorData { vector_data } => {\n\t\t\t\t// Convert NodeId keys to LayerNodeIdentifier keys, filtering to only layers\n\t\t\t\tlet layer_vector_data = vector_data\n\t\t\t\t\t.into_iter()\n\t\t\t\t\t.filter(|(node_id, _)| self.network_interface.document_network().nodes.contains_key(node_id))\n\t\t\t\t\t.filter_map(|(node_id, vector)| {\n\t\t\t\t\t\tself.network_interface.is_layer(&node_id, &[]).then(|| {\n\t\t\t\t\t\t\tlet layer = LayerNodeIdentifier::new(node_id, &self.network_interface);\n\t\t\t\t\t\t\t(layer, vector)\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t\t\t.collect();\n\t\t\t\tself.network_interface.update_vector_data(layer_vector_data);\n\t\t\t}\n\t\t\tDocumentMessage::Undo => {\n\t\t\t\tif self.network_interface.transaction_status() != TransactionStatus::Finished {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tresponses.add(ToolMessage::PreUndo);\n\t\t\t\tresponses.add(DocumentMessage::DocumentHistoryBackward);\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\tresponses.add(ToolMessage::Undo);\n\t\t\t\tresponses.add(EventMessage::SelectionChanged);\n\t\t\t}\n\t\t\tDocumentMessage::UngroupSelectedLayers => {\n\t\t\t\tif !self.selection_network_path.is_empty() {\n\t\t\t\t\tlog::error!(\"Ungrouping selected layers is only supported for the Document Network\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\n\t\t\t\tlet folder_paths = self.network_interface.folders_sorted_by_most_nested(&self.selection_network_path);\n\t\t\t\tfor folder in folder_paths {\n\t\t\t\t\tif folder == LayerNodeIdentifier::ROOT_PARENT {\n\t\t\t\t\t\tlog::error!(\"ROOT_PARENT cannot be selected when ungrouping selected layers\");\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Cannot ungroup artboard\n\t\t\t\t\tif self.network_interface.is_artboard(&folder.to_node(), &self.selection_network_path) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tresponses.add(DocumentMessage::UngroupLayer { layer: folder });\n\t\t\t\t}\n\n\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\tresponses.add(DocumentMessage::DocumentStructureChanged);\n\t\t\t\tresponses.add(NodeGraphMessage::SendGraph);\n\t\t\t}\n\t\t\tDocumentMessage::UngroupLayer { layer } => {\n\t\t\t\tlet parent = layer.parent(self.metadata()).expect(\"Ungrouped folder must have a parent\");\n\t\t\t\tlet folder_index = parent.children(self.metadata()).position(|child| child == layer).unwrap_or(0);\n\n\t\t\t\t// Move all children of the folder above the folder in reverse order since each children is moved above the previous one\n\t\t\t\tfor child in layer.children(self.metadata()).collect::<Vec<_>>().into_iter().rev() {\n\t\t\t\t\tresponses.add(NodeGraphMessage::MoveLayerToStack {\n\t\t\t\t\t\tlayer: child,\n\t\t\t\t\t\tparent,\n\t\t\t\t\t\tinsert_index: folder_index,\n\t\t\t\t\t});\n\n\t\t\t\t\tlet metadata = self.network_interface.document_metadata();\n\t\t\t\t\tlet layer_local_transform = metadata.transform_to_viewport(child);\n\t\t\t\t\tlet undo_parent_transform = if parent == LayerNodeIdentifier::ROOT_PARENT {\n\t\t\t\t\t\t// This is functionally the same as transform_to_viewport for the root, however to_node cannot run on the root in debug mode.\n\t\t\t\t\t\tmetadata.document_to_viewport.inverse()\n\t\t\t\t\t} else {\n\t\t\t\t\t\tmetadata.transform_to_viewport(parent).inverse()\n\t\t\t\t\t};\n\t\t\t\t\tlet transform = undo_parent_transform * layer_local_transform;\n\t\t\t\t\tresponses.add(GraphOperationMessage::TransformSet {\n\t\t\t\t\t\tlayer: child,\n\t\t\t\t\t\ttransform,\n\t\t\t\t\t\ttransform_in: TransformIn::Local,\n\t\t\t\t\t\tskip_rerender: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\t// Delete empty group folder\n\t\t\t\tresponses.add(NodeGraphMessage::DeleteNodes {\n\t\t\t\t\tnode_ids: vec![layer.to_node()],\n\t\t\t\t\tdelete_children: true,\n\t\t\t\t});\n\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesUpdated);\n\t\t\t\tresponses.add(NodeGraphMessage::SendGraph);\n\t\t\t}\n\t\t\tDocumentMessage::PTZUpdate => {\n\t\t\t\tif !self.graph_view_overlay_open {\n\t\t\t\t\tlet transform = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz);\n\t\t\t\t\tself.network_interface.set_document_to_viewport_transform(transform);\n\t\t\t\t\t// Ensure selection box is kept in sync with the pointer when the PTZ changes\n\t\t\t\t\tresponses.add(SelectToolMessage::PointerMove {\n\t\t\t\t\t\tmodifier_keys: SelectToolPointerKeys {\n\t\t\t\t\t\t\taxis_align: Key::Shift,\n\t\t\t\t\t\t\tsnap_angle: Key::Shift,\n\t\t\t\t\t\t\tcenter: Key::Alt,\n\t\t\t\t\t\t\tduplicate: Key::Alt,\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\t} else {\n\t\t\t\t\tlet Some(network_metadata) = self.network_interface.network_metadata(&self.breadcrumb_network_path) else {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t};\n\n\t\t\t\t\tlet transform = self\n\t\t\t\t\t\t.navigation_handler\n\t\t\t\t\t\t.calculate_offset_transform(viewport.center_in_viewport_space().into(), &network_metadata.persistent_metadata.navigation_metadata.node_graph_ptz);\n\t\t\t\t\tself.network_interface.set_transform(transform, &self.breadcrumb_network_path);\n\n\t\t\t\t\tresponses.add(DocumentMessage::RenderRulers);\n\t\t\t\t\tresponses.add(DocumentMessage::RenderScrollbars);\n\t\t\t\t\tresponses.add(NodeGraphMessage::UpdateEdges);\n\t\t\t\t\tresponses.add(NodeGraphMessage::UpdateBoxSelection);\n\t\t\t\t\tresponses.add(NodeGraphMessage::UpdateImportsExports);\n\n\t\t\t\t\tresponses.add(FrontendMessage::UpdateNodeGraphTransform {\n\t\t\t\t\t\ttranslation: transform.translation.into(),\n\t\t\t\t\t\tscale: transform.matrix2.x_axis.x,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t\tDocumentMessage::SelectionStepBack => {\n\t\t\t\tself.network_interface.selection_step_back(&self.selection_network_path);\n\t\t\t\tresponses.add(EventMessage::SelectionChanged);\n\t\t\t}\n\t\t\tDocumentMessage::SelectionStepForward => {\n\t\t\t\tself.network_interface.selection_step_forward(&self.selection_network_path);\n\t\t\t\tresponses.add(EventMessage::SelectionChanged);\n\t\t\t}\n\t\t\tDocumentMessage::WrapContentInArtboard { place_artboard_at_origin } => {\n\t\t\t\t// Get bounding box of all layers\n\t\t\t\tlet bounds = self.network_interface.document_bounds_document_space(false);\n\t\t\t\tlet Some(bounds) = bounds else { return };\n\t\t\t\tlet bounds_rounded_dimensions = (bounds[1] - bounds[0]).round();\n\n\t\t\t\t// Create an artboard and set its dimensions to the bounding box size and location\n\t\t\t\tlet node_id = NodeId::new();\n\t\t\t\tlet node_layer_id = LayerNodeIdentifier::new_unchecked(node_id);\n\t\t\t\tlet new_artboard_node = document_node_definitions::resolve_network_node_type(\"Artboard\")\n\t\t\t\t\t.expect(\"Failed to create artboard node\")\n\t\t\t\t\t.default_node_template();\n\t\t\t\tresponses.add(NodeGraphMessage::InsertNode {\n\t\t\t\t\tnode_id,\n\t\t\t\t\tnode_template: Box::new(new_artboard_node),\n\t\t\t\t});\n\t\t\t\tresponses.add(NodeGraphMessage::ShiftNodePosition { node_id, x: 15, y: -3 });\n\t\t\t\tresponses.add(GraphOperationMessage::ResizeArtboard {\n\t\t\t\t\tlayer: LayerNodeIdentifier::new_unchecked(node_id),\n\t\t\t\t\tlocation: if place_artboard_at_origin { IVec2::ZERO } else { bounds[0].round().as_ivec2() },\n\t\t\t\t\tdimensions: bounds_rounded_dimensions.as_ivec2(),\n\t\t\t\t});\n\n\t\t\t\t// Connect the current output data to the artboard's input data, and the artboard's output to the document output\n\t\t\t\tresponses.add(NodeGraphMessage::InsertNodeBetween {\n\t\t\t\t\tnode_id,\n\t\t\t\t\tinput_connector: network_interface::InputConnector::Export(0),\n\t\t\t\t\tinsert_node_input_index: 1,\n\t\t\t\t});\n\n\t\t\t\t// Shift the content by half its width and height so it gets centered in the artboard\n\t\t\t\tresponses.add(GraphOperationMessage::TransformChange {\n\t\t\t\t\tlayer: node_layer_id,\n\t\t\t\t\ttransform: DAffine2::from_translation(bounds_rounded_dimensions / 2.),\n\t\t\t\t\ttransform_in: TransformIn::Local,\n\t\t\t\t\tskip_rerender: false,\n\t\t\t\t});\n\t\t\t}\n\t\t\tDocumentMessage::ZoomCanvasTo100Percent => {\n\t\t\t\tresponses.add_front(NavigationMessage::CanvasZoomSet { zoom_factor: 1. });\n\t\t\t}\n\t\t\tDocumentMessage::ZoomCanvasTo200Percent => {\n\t\t\t\tresponses.add_front(NavigationMessage::CanvasZoomSet { zoom_factor: 2. });\n\t\t\t}\n\t\t\tDocumentMessage::ZoomCanvasToFitAll => {\n\t\t\t\tlet bounds = if self.graph_view_overlay_open {\n\t\t\t\t\tself.network_interface.all_nodes_bounding_box(&self.breadcrumb_network_path).cloned()\n\t\t\t\t} else {\n\t\t\t\t\tself.network_interface.document_bounds_document_space(true)\n\t\t\t\t};\n\t\t\t\tif let Some(bounds) = bounds {\n\t\t\t\t\tif self.document_ptz.flip {\n\t\t\t\t\t\tresponses.add(NavigationMessage::CanvasFlip);\n\t\t\t\t\t}\n\t\t\t\t\tresponses.add(NavigationMessage::CanvasTiltSet { angle_radians: 0. });\n\t\t\t\t\tresponses.add(NavigationMessage::FitViewportToBounds { bounds, prevent_zoom_past_100: true });\n\t\t\t\t} else {\n\t\t\t\t\twarn!(\"Cannot zoom due to no bounds\")\n\t\t\t\t}\n\t\t\t}\n\t\t\tDocumentMessage::Noop => (),\n\t\t}\n\t}\n\n\tfn actions(&self) -> ActionList {\n\t\tlet mut common = actions!(DocumentMessageDiscriminant;\n\t\t\tCreateEmptyFolder,\n\t\t\tDeselectAllLayers,\n\t\t\tGraphViewOverlayToggle,\n\t\t\tNoop,\n\t\t\tRedo,\n\t\t\tSaveDocument,\n\t\t\tSelectAllLayers,\n\t\t\tSetSnapping,\n\t\t\tToggleGridVisibility,\n\t\t\tToggleOverlaysVisibility,\n\t\t\tToggleSnapping,\n\t\t\tUndo,\n\t\t\tSelectParentLayer,\n\t\t\tSelectionStepForward,\n\t\t\tSelectionStepBack,\n\t\t\tZoomCanvasTo100Percent,\n\t\t\tZoomCanvasTo200Percent,\n\t\t\tZoomCanvasToFitAll,\n\t\t);\n\n\t\t// Additional actions available on desktop\n\t\t#[cfg(not(target_family = \"wasm\"))]\n\t\tcommon.extend(actions!(DocumentMessageDiscriminant::SaveDocumentAs));\n\n\t\t// Additional actions if there are any selected layers\n\t\tif self.network_interface.selected_nodes().selected_layers(self.metadata()).next().is_some() {\n\t\t\tlet mut select = actions!(DocumentMessageDiscriminant;\n\t\t\t\tDeleteSelectedLayers,\n\t\t\t\tDuplicateSelectedLayers,\n\t\t\t\tGroupSelectedLayers,\n\t\t\t\tSelectedLayersLower,\n\t\t\t\tSelectedLayersLowerToBack,\n\t\t\t\tSelectedLayersRaise,\n\t\t\t\tSelectedLayersRaiseToFront,\n\t\t\t\tUngroupSelectedLayers,\n\t\t\t\tToggleSelectedLocked\n\t\t\t);\n\t\t\tif !self.graph_view_overlay_open {\n\t\t\t\tselect.extend(actions!(DocumentMessageDiscriminant;\n\t\t\t\t\tNudgeSelectedLayers,\n\t\t\t\t\tToggleSelectedVisibility,\n\t\t\t\t));\n\t\t\t}\n\t\t\tcommon.extend(select);\n\t\t}\n\n\t\t// Additional actions if the node graph is open\n\t\tif self.graph_view_overlay_open {\n\t\t\tcommon.extend(actions!(DocumentMessageDiscriminant;\n\t\t\t\tEscape\n\t\t\t));\n\t\t\tcommon.extend(self.node_graph_handler.actions_additional_if_node_graph_is_open());\n\t\t}\n\t\t// More additional actions\n\t\tcommon.extend(self.navigation_handler.actions());\n\t\tcommon.extend(self.node_graph_handler.actions());\n\t\tcommon\n\t}\n}\n\nimpl DocumentMessageHandler {\n\t/// Runs an intersection test with all layers and a viewport space quad\n\tpub fn intersect_quad<'a>(&'a self, viewport_quad: graphene_std::renderer::Quad, viewport: &ViewportMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + use<'a> {\n\t\tlet document_to_viewport = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz);\n\t\tlet document_quad = document_to_viewport.inverse() * viewport_quad;\n\n\t\tClickXRayIter::new(&self.network_interface, XRayTarget::Quad(document_quad))\n\t}\n\n\t/// Runs an intersection test with all layers and a viewport space quad; ignoring artboards\n\tpub fn intersect_quad_no_artboards<'a>(&'a self, viewport_quad: graphene_std::renderer::Quad, viewport: &ViewportMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + use<'a> {\n\t\tself.intersect_quad(viewport_quad, viewport).filter(|layer| !self.network_interface.is_artboard(&layer.to_node(), &[]))\n\t}\n\n\t/// Runs an intersection test with all layers and a viewport space subpath\n\tpub fn intersect_polygon<'a>(&'a self, mut viewport_polygon: Subpath<PointId>, viewport: &ViewportMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + use<'a> {\n\t\tlet document_to_viewport = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz);\n\t\tviewport_polygon.apply_transform(document_to_viewport.inverse());\n\n\t\tClickXRayIter::new(&self.network_interface, XRayTarget::Polygon(viewport_polygon))\n\t}\n\n\t/// Runs an intersection test with all layers and a viewport space subpath; ignoring artboards\n\tpub fn intersect_polygon_no_artboards<'a>(&'a self, viewport_polygon: Subpath<PointId>, viewport: &ViewportMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + use<'a> {\n\t\tself.intersect_polygon(viewport_polygon, viewport)\n\t\t\t.filter(|layer| !self.network_interface.is_artboard(&layer.to_node(), &[]))\n\t}\n\n\tpub fn is_layer_fully_inside(&self, layer: &LayerNodeIdentifier, quad: graphene_std::renderer::Quad) -> bool {\n\t\t// Get the bounding box of the layer in document space\n\t\tlet Some(bounding_box) = self.metadata().bounding_box_viewport(*layer) else { return false };\n\n\t\t// Check if the bounding box is fully within the selection quad\n\t\tlet [top_left, bottom_right] = bounding_box;\n\n\t\tlet quad_bbox = quad.bounding_box();\n\n\t\tlet quad_left = quad_bbox[0].x;\n\t\tlet quad_right = quad_bbox[1].x;\n\t\tlet quad_top = quad_bbox[0].y.max(quad_bbox[1].y); // Correct top\n\t\tlet quad_bottom = quad_bbox[0].y.min(quad_bbox[1].y); // Correct bottom\n\n\t\t// Extract layer's bounding box coordinates\n\t\tlet layer_left = top_left.x;\n\t\tlet layer_right = bottom_right.x;\n\t\tlet layer_top = bottom_right.y;\n\t\tlet layer_bottom = top_left.y;\n\n\t\tlayer_left >= quad_left && layer_right <= quad_right && layer_top <= quad_top && layer_bottom >= quad_bottom\n\t}\n\n\tpub fn is_layer_fully_inside_polygon(&self, layer: &LayerNodeIdentifier, viewport: &ViewportMessageHandler, mut viewport_polygon: Subpath<PointId>) -> bool {\n\t\tlet document_to_viewport = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz);\n\t\tviewport_polygon.apply_transform(document_to_viewport.inverse());\n\n\t\tlet layer_click_targets = self.network_interface.document_metadata().click_targets(*layer);\n\t\tlet layer_transform = self.network_interface.document_metadata().transform_to_document(*layer);\n\n\t\tlayer_click_targets.is_some_and(|targets| {\n\t\t\ttargets.iter().all(|target| match target.target_type() {\n\t\t\t\tClickTargetType::Subpath(subpath) => {\n\t\t\t\t\tlet mut subpath = subpath.clone();\n\t\t\t\t\tsubpath.apply_transform(layer_transform);\n\t\t\t\t\tsubpath.is_inside_subpath(&viewport_polygon, None, None)\n\t\t\t\t}\n\t\t\t\tClickTargetType::FreePoint(point) => {\n\t\t\t\t\tlet mut point = *point;\n\t\t\t\t\tpoint.apply_transform(layer_transform);\n\t\t\t\t\tviewport_polygon.contains_point(point.position)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n\n\t/// Find all of the layers that were clicked on from a viewport space location\n\tpub fn click_xray(&self, ipp: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + use<'_> {\n\t\tlet document_to_viewport = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz);\n\t\tlet point = document_to_viewport.inverse().transform_point2(ipp.mouse.position);\n\t\tClickXRayIter::new(&self.network_interface, XRayTarget::Point(point))\n\t}\n\n\t/// Find the deepest layer given in the sorted array (by returning the one which is not a folder from the list of layers under the click location).\n\tpub fn find_deepest(&self, node_list: &[LayerNodeIdentifier]) -> Option<LayerNodeIdentifier> {\n\t\tnode_list\n\t\t\t.iter()\n\t\t\t.find(|&&layer| {\n\t\t\t\tif layer != LayerNodeIdentifier::ROOT_PARENT {\n\t\t\t\t\t!layer.has_children(self.network_interface.document_metadata())\n\t\t\t\t} else {\n\t\t\t\t\tlog::error!(\"ROOT_PARENT should not exist in find_deepest\");\n\t\t\t\t\tfalse\n\t\t\t\t}\n\t\t\t})\n\t\t\t.copied()\n\t}\n\n\t/// Find layers under the location in viewport space that was clicked, listed by their depth in the layer tree hierarchy.\n\tpub fn click_list<'a>(&'a self, ipp: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + use<'a> {\n\t\tself.click_xray(ipp, viewport)\n\t\t\t.filter(move |&layer| !self.network_interface.is_artboard(&layer.to_node(), &[]))\n\t\t\t.skip_while(|&layer| layer == LayerNodeIdentifier::ROOT_PARENT)\n\t\t\t.scan(true, |last_had_children, layer| {\n\t\t\t\tif *last_had_children {\n\t\t\t\t\t*last_had_children = layer.has_children(self.network_interface.document_metadata());\n\t\t\t\t\tSome(layer)\n\t\t\t\t} else {\n\t\t\t\t\tNone\n\t\t\t\t}\n\t\t\t})\n\t}\n\n\t/// Find layers (including artboards) under the location in viewport space that was clicked, listed by their depth in the layer tree hierarchy.\n\tpub fn click_list_with_artboards<'a>(&'a self, ipp: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + use<'a> {\n\t\tself.click_xray(ipp, viewport)\n\t\t\t.skip_while(|&layer| layer == LayerNodeIdentifier::ROOT_PARENT)\n\t\t\t.scan(true, |last_had_children, layer| {\n\t\t\t\tif *last_had_children {\n\t\t\t\t\t*last_had_children = layer.has_children(self.network_interface.document_metadata());\n\t\t\t\t\tSome(layer)\n\t\t\t\t} else {\n\t\t\t\t\tNone\n\t\t\t\t}\n\t\t\t})\n\t}\n\n\tpub fn click_list_no_parents<'a>(&'a self, ipp: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + use<'a> {\n\t\tself.click_xray(ipp, viewport)\n\t\t\t.filter(move |&layer| !self.network_interface.is_artboard(&layer.to_node(), &[]) && !layer.has_children(self.network_interface.document_metadata()))\n\t}\n\n\t/// Find the deepest layer that has been clicked on from a location in viewport space.\n\tpub fn click(&self, ipp: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler) -> Option<LayerNodeIdentifier> {\n\t\tself.click_list(ipp, viewport).last()\n\t}\n\n\tpub fn click_based_on_position(&self, mouse_snapped_positon: DVec2) -> Option<LayerNodeIdentifier> {\n\t\tClickXRayIter::new(&self.network_interface, XRayTarget::Point(mouse_snapped_positon))\n\t\t\t.filter(move |&layer| !self.network_interface.is_artboard(&layer.to_node(), &[]))\n\t\t\t.skip_while(|&layer| layer == LayerNodeIdentifier::ROOT_PARENT)\n\t\t\t.scan(true, |last_had_children, layer| {\n\t\t\t\tif *last_had_children {\n\t\t\t\t\t*last_had_children = layer.has_children(self.network_interface.document_metadata());\n\t\t\t\t\tSome(layer)\n\t\t\t\t} else {\n\t\t\t\t\tNone\n\t\t\t\t}\n\t\t\t})\n\t\t\t.last()\n\t}\n\n\tpub fn document_network(&self) -> &NodeNetwork {\n\t\tself.network_interface.document_network()\n\t}\n\n\tpub fn metadata(&self) -> &DocumentMetadata {\n\t\tself.network_interface.document_metadata()\n\t}\n\n\tpub fn serialize_document(&self) -> String {\n\t\tlet val = serde_json::to_string(self);\n\t\t// We fully expect the serialization to succeed\n\t\tval.unwrap()\n\t}\n\n\tpub fn deserialize_document(serialized_content: &str) -> Result<Self, EditorError> {\n\t\tlet document_message_handler = serde_json::from_str::<DocumentMessageHandler>(serialized_content)\n\t\t\t.or_else(|e| {\n\t\t\t\tlog::warn!(\"Failed to directly load document with the following error: {e}. Trying old DocumentMessageHandler.\");\n\t\t\t\t// TODO: Eventually remove this document upgrade code\n\t\t\t\t#[derive(Debug, serde::Serialize, serde::Deserialize)]\n\t\t\t\tpub struct OldDocumentMessageHandler {\n\t\t\t\t\t// ============================================\n\t\t\t\t\t// Fields that are saved in the document format\n\t\t\t\t\t// ============================================\n\t\t\t\t\t//\n\t\t\t\t\t/// The node graph that generates this document's artwork.\n\t\t\t\t\t/// It recursively stores its sub-graphs, so this root graph is the whole snapshot of the document content.\n\t\t\t\t\tpub network: OldNodeNetwork,\n\t\t\t\t\t/// List of the [`NodeId`]s that are currently selected by the user.\n\t\t\t\t\tpub selected_nodes: SelectedNodes,\n\t\t\t\t\t/// List of the [`LayerNodeIdentifier`]s that are currently collapsed by the user in the Layers panel.\n\t\t\t\t\t/// Collapsed means that the expansion arrow isn't set to show the children of these layers.\n\t\t\t\t\tpub collapsed: CollapsedLayers,\n\t\t\t\t\t/// The name of the document, which is displayed in the tab and title bar of the editor.\n\t\t\t\t\tpub name: String,\n\t\t\t\t\t/// The full Git commit hash of the Graphite repository that was used to build the editor.\n\t\t\t\t\t/// We save this to provide a hint about which version of the editor was used to create the document.\n\t\t\t\t\tpub commit_hash: String,\n\t\t\t\t\t/// The current pan, tilt, and zoom state of the viewport's view of the document canvas.\n\t\t\t\t\tpub document_ptz: PTZ,\n\t\t\t\t\t/// The current mode that the user has set for rendering the document within the viewport.\n\t\t\t\t\t/// This is usually \"Normal\" but can be set to \"Outline\" or \"Pixels\" to see the canvas differently.\n\t\t\t\t\tpub view_mode: RenderMode,\n\t\t\t\t\t/// Sets whether or not all the viewport overlays should be drawn on top of the artwork.\n\t\t\t\t\t/// This includes tool interaction visualizations (like the transform cage and path anchors/handles), the grid, and more.\n\t\t\t\t\tpub overlays_visibility_settings: OverlaysVisibilitySettings,\n\t\t\t\t\t/// Sets whether or not the rulers should be drawn along the top and left edges of the viewport area.\n\t\t\t\t\tpub rulers_visible: bool,\n\t\t\t\t\t/// Sets whether or not the node graph is drawn (as an overlay) on top of the viewport area, or otherwise if it's hidden.\n\t\t\t\t\tpub graph_view_overlay_open: bool,\n\t\t\t\t\t/// The current user choices for snapping behavior, including whether snapping is enabled at all.\n\t\t\t\t\tpub snapping_state: SnappingState,\n\t\t\t\t}\n\n\t\t\t\tserde_json::from_str::<OldDocumentMessageHandler>(serialized_content).map(|old_message_handler| DocumentMessageHandler {\n\t\t\t\t\tnetwork_interface: NodeNetworkInterface::from_old_network(old_message_handler.network),\n\t\t\t\t\tcollapsed: old_message_handler.collapsed,\n\t\t\t\t\tcommit_hash: old_message_handler.commit_hash,\n\t\t\t\t\tdocument_ptz: old_message_handler.document_ptz,\n\t\t\t\t\trender_mode: old_message_handler.view_mode,\n\t\t\t\t\toverlays_visibility_settings: old_message_handler.overlays_visibility_settings,\n\t\t\t\t\trulers_visible: old_message_handler.rulers_visible,\n\t\t\t\t\tgraph_view_overlay_open: old_message_handler.graph_view_overlay_open,\n\t\t\t\t\tsnapping_state: old_message_handler.snapping_state,\n\t\t\t\t\t..Default::default()\n\t\t\t\t})\n\t\t\t})\n\t\t\t.map_err(|e| EditorError::DocumentDeserialization(e.to_string()))?;\n\t\tOk(document_message_handler)\n\t}\n\n\t/// Recursively builds the layer structure tree for a folder.\n\tfn build_layer_structure(&self, folder: LayerNodeIdentifier) -> Vec<LayerStructureEntry> {\n\t\tfolder\n\t\t\t.children(self.metadata())\n\t\t\t.map(|layer_node| {\n\t\t\t\tlet children = if layer_node.has_children(self.metadata()) && !self.collapsed.0.contains(&layer_node) {\n\t\t\t\t\tself.build_layer_structure(layer_node)\n\t\t\t\t} else {\n\t\t\t\t\tVec::new()\n\t\t\t\t};\n\t\t\t\tLayerStructureEntry {\n\t\t\t\t\tlayer_id: layer_node.to_node(),\n\t\t\t\t\tchildren,\n\t\t\t\t}\n\t\t\t})\n\t\t\t.collect()\n\t}\n\n\tpub fn undo_with_history(&mut self, viewport: &ViewportMessageHandler, responses: &mut VecDeque<Message>) {\n\t\tlet Some(previous_network) = self.undo(viewport, responses) else { return };\n\n\t\tself.document_redo_history.push_back(previous_network);\n\t\tif self.document_redo_history.len() > crate::consts::MAX_UNDO_HISTORY_LEN {\n\t\t\tself.document_redo_history.pop_front();\n\t\t}\n\t}\n\n\tpub fn undo(&mut self, viewport: &ViewportMessageHandler, responses: &mut VecDeque<Message>) -> Option<NodeNetworkInterface> {\n\t\t// If there is no history return and don't broadcast SelectionChanged\n\t\tlet mut network_interface = self.document_undo_history.pop_back()?;\n\n\t\t// Set the previous network navigation metadata to the current navigation metadata\n\t\tnetwork_interface.copy_all_navigation_metadata(&self.network_interface);\n\t\tstd::mem::swap(&mut network_interface.resolved_types, &mut self.network_interface.resolved_types);\n\n\t\t//Update the metadata transform based on document PTZ\n\t\tlet transform = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz);\n\t\tnetwork_interface.set_document_to_viewport_transform(transform);\n\n\t\t// Ensure document structure is loaded so that updating the selected nodes has the correct metadata\n\t\tnetwork_interface.load_structure();\n\n\t\tlet previous_network = std::mem::replace(&mut self.network_interface, network_interface);\n\n\t\t// Push the UpdateOpenDocumentsList message to the bus in order to update the save status of the open documents\n\t\tresponses.add(PortfolioMessage::UpdateOpenDocumentsList);\n\t\tresponses.add(NodeGraphMessage::SelectedNodesUpdated);\n\t\tresponses.add(NodeGraphMessage::ForceRunDocumentGraph);\n\n\t\t// TODO: Remove once the footprint is used to load the imports/export distances from the edge\n\t\tresponses.add(NodeGraphMessage::UnloadWires);\n\n\t\tSome(previous_network)\n\t}\n\tpub fn redo_with_history(&mut self, viewport: &ViewportMessageHandler, responses: &mut VecDeque<Message>) {\n\t\t// Push the UpdateOpenDocumentsList message to the queue in order to update the save status of the open documents\n\t\tlet Some(previous_network) = self.redo(viewport, responses) else { return };\n\n\t\tself.document_undo_history.push_back(previous_network);\n\t\tif self.document_undo_history.len() > crate::consts::MAX_UNDO_HISTORY_LEN {\n\t\t\tself.document_undo_history.pop_front();\n\t\t}\n\t}\n\n\tpub fn redo(&mut self, viewport: &ViewportMessageHandler, responses: &mut VecDeque<Message>) -> Option<NodeNetworkInterface> {\n\t\t// If there is no history return and don't broadcast SelectionChanged\n\t\tlet mut network_interface = self.document_redo_history.pop_back()?;\n\n\t\t// Set the previous network navigation metadata to the current navigation metadata\n\t\tnetwork_interface.copy_all_navigation_metadata(&self.network_interface);\n\t\tstd::mem::swap(&mut network_interface.resolved_types, &mut self.network_interface.resolved_types);\n\n\t\t//Update the metadata transform based on document PTZ\n\t\tlet transform = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz);\n\t\tnetwork_interface.set_document_to_viewport_transform(transform);\n\n\t\tlet previous_network = std::mem::replace(&mut self.network_interface, network_interface);\n\t\t// Push the UpdateOpenDocumentsList message to the bus in order to update the save status of the open documents\n\t\tresponses.add(PortfolioMessage::UpdateOpenDocumentsList);\n\t\tresponses.add(NodeGraphMessage::SelectedNodesUpdated);\n\t\tresponses.add(NodeGraphMessage::ForceRunDocumentGraph);\n\t\tresponses.add(NodeGraphMessage::UnloadWires);\n\t\tresponses.add(NodeGraphMessage::SendWires);\n\t\tSome(previous_network)\n\t}\n\n\tpub fn current_hash(&self) -> u64 {\n\t\tself.network_interface.document_network().current_hash()\n\t}\n\n\tpub fn is_auto_saved(&self) -> bool {\n\t\tSome(self.current_hash()) == self.auto_saved_hash\n\t}\n\n\tpub fn is_saved(&self) -> bool {\n\t\tSome(self.current_hash()) == self.saved_hash\n\t}\n\n\tpub fn is_graph_overlay_open(&self) -> bool {\n\t\tself.graph_view_overlay_open\n\t}\n\n\tpub fn set_auto_save_state(&mut self, is_saved: bool) {\n\t\tif is_saved {\n\t\t\tself.auto_saved_hash = Some(self.current_hash());\n\t\t} else {\n\t\t\tself.auto_saved_hash = None;\n\t\t}\n\t}\n\n\tpub fn set_save_state(&mut self, is_saved: bool) {\n\t\tif is_saved {\n\t\t\tself.saved_hash = Some(self.current_hash());\n\t\t} else {\n\t\t\tself.saved_hash = None;\n\t\t}\n\t}\n\n\t/// Finds the artboard that bounds the point in viewport space and be the container of any newly added layers.\n\tpub fn new_layer_bounding_artboard(&self, ipp: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler) -> LayerNodeIdentifier {\n\t\tlet container_based_on_selection = self.new_layer_parent(true);\n\n\t\tlet container_based_on_clicked_artboard = self\n\t\t\t.click_xray(ipp, viewport)\n\t\t\t.find(|layer| self.network_interface.is_artboard(&layer.to_node(), &[]))\n\t\t\t.unwrap_or(LayerNodeIdentifier::ROOT_PARENT);\n\n\t\tif container_based_on_selection.ancestors(self.metadata()).any(|ancestor| ancestor == container_based_on_clicked_artboard) {\n\t\t\tcontainer_based_on_selection\n\t\t} else {\n\t\t\tcontainer_based_on_clicked_artboard\n\t\t}\n\t}\n\n\t/// Finds the parent folder which, based on the current selections, should be the container of any newly added layers.\n\tpub fn new_layer_parent(&self, include_self: bool) -> LayerNodeIdentifier {\n\t\tlet Some(selected_nodes) = self.network_interface.selected_nodes_in_nested_network(&self.selection_network_path) else {\n\t\t\twarn!(\"No selected nodes found in new_layer_parent. Defaulting to ROOT_PARENT.\");\n\t\t\treturn LayerNodeIdentifier::ROOT_PARENT;\n\t\t};\n\n\t\tself.network_interface\n\t\t\t.deepest_common_ancestor(&selected_nodes, &self.selection_network_path, include_self)\n\t\t\t.unwrap_or_else(|| self.network_interface.all_artboards().iter().next().copied().unwrap_or(LayerNodeIdentifier::ROOT_PARENT))\n\t}\n\n\tpub fn get_calculated_insert_index(metadata: &DocumentMetadata, selected_nodes: &SelectedNodes, parent: LayerNodeIdentifier) -> usize {\n\t\tparent\n\t\t\t.children(metadata)\n\t\t\t.enumerate()\n\t\t\t.find_map(|(index, direct_child)| {\n\t\t\t\tif selected_nodes.selected_layers(metadata).any(|selected| selected == direct_child) {\n\t\t\t\t\treturn Some(index);\n\t\t\t\t}\n\n\t\t\t\tfor descendant in direct_child.descendants(metadata) {\n\t\t\t\t\tif selected_nodes.selected_layers(metadata).any(|selected| selected == descendant) {\n\t\t\t\t\t\treturn Some(index);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tNone\n\t\t\t})\n\t\t\t.unwrap_or(0)\n\t}\n\n\tpub fn group_layers(\n\t\tresponses: &mut VecDeque<Message>,\n\t\tinsert_index: usize,\n\t\tparent: LayerNodeIdentifier,\n\t\tgroup_folder_type: GroupFolderType,\n\t\tnetwork_interface: &mut NodeNetworkInterface,\n\t) -> NodeId {\n\t\tlet folder_id = NodeId(generate_uuid());\n\n\t\tmatch group_folder_type {\n\t\t\tGroupFolderType::Layer => responses.add(GraphOperationMessage::NewCustomLayer {\n\t\t\t\tid: folder_id,\n\t\t\t\tnodes: Vec::new(),\n\t\t\t\tparent,\n\t\t\t\tinsert_index,\n\t\t\t}),\n\t\t\tGroupFolderType::BooleanOperation(operation) => {\n\t\t\t\t// Get the ID of the one selected layer, if exactly one is selected\n\t\t\t\tlet only_selected_layer = {\n\t\t\t\t\tlet selected_nodes = network_interface.selected_nodes();\n\t\t\t\t\tlet mut layers = selected_nodes.selected_layers(network_interface.document_metadata());\n\n\t\t\t\t\tmatch (layers.next(), layers.next()) {\n\t\t\t\t\t\t(Some(id), None) => Some(id),\n\t\t\t\t\t\t_ => None,\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\t// If there is a single selected layer, check if there is a boolean operation upstream from it\n\t\t\t\tlet upstream_boolean_op = only_selected_layer.and_then(|selected_id| {\n\t\t\t\t\tnetwork_interface.upstream_flow_back_from_nodes(vec![selected_id.to_node()], &[], FlowType::HorizontalFlow).find(|id| {\n\t\t\t\t\t\tnetwork_interface\n\t\t\t\t\t\t\t.reference(id, &[])\n\t\t\t\t\t\t\t.is_some_and(|reference| reference == DefinitionIdentifier::Network(\"Boolean Operation\".into()))\n\t\t\t\t\t})\n\t\t\t\t});\n\n\t\t\t\t// If there's already a boolean operation on the selected layer, update it with the new operation\n\t\t\t\tif let (Some(upstream_boolean_op), Some(only_selected_layer)) = (upstream_boolean_op, only_selected_layer) {\n\t\t\t\t\tnetwork_interface.set_input(&InputConnector::node(upstream_boolean_op, 1), NodeInput::value(TaggedValue::BooleanOperation(operation), false), &[]);\n\n\t\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\n\t\t\t\t\treturn only_selected_layer.to_node();\n\t\t\t\t}\n\t\t\t\t// Otherwise, create a new boolean operation node group\n\t\t\t\telse {\n\t\t\t\t\tresponses.add(GraphOperationMessage::NewBooleanOperationLayer {\n\t\t\t\t\t\tid: folder_id,\n\t\t\t\t\t\toperation,\n\t\t\t\t\t\tparent,\n\t\t\t\t\t\tinsert_index,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tlet new_group_folder = LayerNodeIdentifier::new_unchecked(folder_id);\n\t\t// Move the new folder to the correct position\n\t\tresponses.add(NodeGraphMessage::MoveLayerToStack {\n\t\t\tlayer: new_group_folder,\n\t\t\tparent,\n\t\t\tinsert_index,\n\t\t});\n\t\tresponses.add(DocumentMessage::MoveSelectedLayersToGroup { parent: new_group_folder });\n\n\t\tfolder_id\n\t}\n\n\t/// Helper method for GroupSelectedLayers message.\n\t/// Handles grouping layers in both artboard and non-artboard workflows.\n\tfn handle_group_selected_layers(&mut self, group_folder_type: GroupFolderType, responses: &mut VecDeque<Message>) {\n\t\tresponses.add(DocumentMessage::AddTransaction);\n\n\t\tlet mut parent_per_selected_nodes: HashMap<LayerNodeIdentifier, Vec<NodeId>> = HashMap::new();\n\t\tlet artboards = LayerNodeIdentifier::ROOT_PARENT\n\t\t\t.children(self.metadata())\n\t\t\t.filter(|x| self.network_interface.is_artboard(&x.to_node(), &self.selection_network_path))\n\t\t\t.collect::<Vec<_>>();\n\t\tlet selected_nodes = self.network_interface.selected_nodes();\n\n\t\t// Non-artboard (infinite canvas) workflow\n\t\tif artboards.is_empty() {\n\t\t\tlet Some(parent) = self.network_interface.deepest_common_ancestor(&selected_nodes, &self.selection_network_path, false) else {\n\t\t\t\treturn;\n\t\t\t};\n\t\t\tlet Some(selected_nodes) = &self.network_interface.selected_nodes_in_nested_network(&self.selection_network_path) else {\n\t\t\t\treturn;\n\t\t\t};\n\t\t\tlet insert_index = DocumentMessageHandler::get_calculated_insert_index(self.metadata(), selected_nodes, parent);\n\n\t\t\tDocumentMessageHandler::group_layers(responses, insert_index, parent, group_folder_type, &mut self.network_interface);\n\t\t}\n\t\t// Artboard workflow\n\t\telse {\n\t\t\tfor artboard in artboards {\n\t\t\t\tlet selected_descendants = artboard.descendants(self.metadata()).filter(|x| selected_nodes.selected_layers_contains(*x, self.metadata()));\n\t\t\t\tfor selected_descendant in selected_descendants {\n\t\t\t\t\tparent_per_selected_nodes.entry(artboard).or_default().push(selected_descendant.to_node());\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet mut new_folders: Vec<NodeId> = Vec::new();\n\n\t\t\tfor children in parent_per_selected_nodes.into_values() {\n\t\t\t\tlet child_selected_nodes = SelectedNodes(children);\n\t\t\t\tlet Some(parent) = self.network_interface.deepest_common_ancestor(&child_selected_nodes, &self.selection_network_path, false) else {\n\t\t\t\t\tcontinue;\n\t\t\t\t};\n\t\t\t\tlet insert_index = DocumentMessageHandler::get_calculated_insert_index(self.metadata(), &child_selected_nodes, parent);\n\n\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes: child_selected_nodes.0 });\n\n\t\t\t\tnew_folders.push(DocumentMessageHandler::group_layers(responses, insert_index, parent, group_folder_type, &mut self.network_interface));\n\t\t\t}\n\n\t\t\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes: new_folders });\n\t\t}\n\t}\n\n\t/// Helper method for MoveSelectedLayersTo message.\n\t/// Handles moving selected layers to a new parent with proper transform preservation.\n\tfn handle_move_selected_layers_to(&mut self, parent: LayerNodeIdentifier, insert_index: usize, responses: &mut VecDeque<Message>) {\n\t\tif !self.selection_network_path.is_empty() {\n\t\t\tlog::error!(\"Moving selected layers is only supported for the Document Network\");\n\t\t\treturn;\n\t\t}\n\n\t\t// Disallow trying to insert into self.\n\t\tif self\n\t\t\t.network_interface\n\t\t\t.selected_nodes()\n\t\t\t.selected_layers(self.metadata())\n\t\t\t.any(|layer| parent.ancestors(self.metadata()).any(|ancestor| ancestor == layer))\n\t\t{\n\t\t\treturn;\n\t\t}\n\t\t// Artboards can only have `ROOT_PARENT` as the parent.\n\t\tlet any_artboards = self\n\t\t\t.network_interface\n\t\t\t.selected_nodes()\n\t\t\t.selected_layers(self.metadata())\n\t\t\t.any(|layer| self.network_interface.is_artboard(&layer.to_node(), &self.selection_network_path));\n\t\tif any_artboards && parent != LayerNodeIdentifier::ROOT_PARENT {\n\t\t\treturn;\n\t\t}\n\n\t\t// Non-artboards cannot be put at the top level if artboards also exist there\n\t\tlet selected_any_non_artboards = self\n\t\t\t.network_interface\n\t\t\t.selected_nodes()\n\t\t\t.selected_layers(self.metadata())\n\t\t\t.any(|layer| !self.network_interface.is_artboard(&layer.to_node(), &self.selection_network_path));\n\n\t\tlet top_level_artboards = LayerNodeIdentifier::ROOT_PARENT\n\t\t\t.children(self.metadata())\n\t\t\t.any(|layer| self.network_interface.is_artboard(&layer.to_node(), &self.selection_network_path));\n\n\t\tif selected_any_non_artboards && parent == LayerNodeIdentifier::ROOT_PARENT && top_level_artboards {\n\t\t\treturn;\n\t\t}\n\n\t\tlet layers_to_move = self.network_interface.shallowest_unique_layers_sorted(&self.selection_network_path);\n\t\t// Offset the index for layers to move that are below another layer to move. For example when moving 1 and 2 between 3 and 4, 2 should be inserted at the same index as 1 since 1 is moved first.\n\t\tlet layers_to_move_with_insert_offset = layers_to_move\n\t\t\t.iter()\n\t\t\t.map(|layer| {\n\t\t\t\tif layer.parent(self.metadata()) != Some(parent) {\n\t\t\t\t\treturn (*layer, 0);\n\t\t\t\t}\n\n\t\t\t\tlet upstream_selected_siblings = layer\n\t\t\t\t\t.downstream_siblings(self.network_interface.document_metadata())\n\t\t\t\t\t.filter(|sibling| {\n\t\t\t\t\t\tsibling != layer\n\t\t\t\t\t\t\t&& layers_to_move.iter().any(|layer| {\n\t\t\t\t\t\t\t\tlayer == sibling\n\t\t\t\t\t\t\t\t\t&& layer\n\t\t\t\t\t\t\t\t\t\t.parent(self.metadata())\n\t\t\t\t\t\t\t\t\t\t.is_some_and(|parent| parent.children(self.metadata()).position(|child| child == *layer) < Some(insert_index))\n\t\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t\t\t.count();\n\t\t\t\t(*layer, upstream_selected_siblings)\n\t\t\t})\n\t\t\t.collect::<Vec<_>>();\n\n\t\tresponses.add(DocumentMessage::AddTransaction);\n\n\t\tfor (layer_index, (layer_to_move, insert_offset)) in layers_to_move_with_insert_offset.into_iter().enumerate() {\n\t\t\tresponses.add(NodeGraphMessage::MoveLayerToStack {\n\t\t\t\tlayer: layer_to_move,\n\t\t\t\tparent,\n\t\t\t\tinsert_index: insert_index + layer_index - insert_offset,\n\t\t\t});\n\n\t\t\tif layer_to_move.parent(self.metadata()) != Some(parent) {\n\t\t\t\t// TODO: Fix this so it works when dragging a layer into a group parent which has a Transform node, which used to work before #2689 caused this regression by removing the empty vector table row.\n\t\t\t\t// TODO: See #2688 for this issue.\n\t\t\t\tlet layer_local_transform = self.network_interface.document_metadata().transform_to_viewport(layer_to_move);\n\t\t\t\tlet undo_transform = self.network_interface.document_metadata().transform_to_viewport(parent).inverse();\n\t\t\t\tlet transform = undo_transform * layer_local_transform;\n\n\t\t\t\tresponses.add(GraphOperationMessage::TransformSet {\n\t\t\t\t\tlayer: layer_to_move,\n\t\t\t\t\ttransform,\n\t\t\t\t\ttransform_in: TransformIn::Local,\n\t\t\t\t\tskip_rerender: false,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\tresponses.add(NodeGraphMessage::SendGraph);\n\t}\n\n\t/// Helper method for NudgeSelectedLayers message.\n\t/// Handles keyboard nudging of selected layers with optional resize mode.\n\tfn handle_nudge_selected_layers(\n\t\t&mut self,\n\t\tdelta_x: f64,\n\t\tdelta_y: f64,\n\t\tresize: Key,\n\t\tresize_opposite_corner: Key,\n\t\tipp: &InputPreprocessorMessageHandler,\n\t\tviewport: &ViewportMessageHandler,\n\t\tresponses: &mut VecDeque<Message>,\n\t) {\n\t\tresponses.add(DocumentMessage::AddTransaction);\n\n\t\tlet resize = ipp.keyboard.key(resize);\n\t\tlet resize_opposite_corner = ipp.keyboard.key(resize_opposite_corner);\n\n\t\tlet can_move = |layer| {\n\t\t\tlet selected = self.network_interface.selected_nodes();\n\t\t\tselected.layer_visible(layer, &self.network_interface) && !selected.layer_locked(layer, &self.network_interface)\n\t\t};\n\n\t\t// Nudge translation without resizing\n\t\tif !resize {\n\t\t\tlet transform = DAffine2::from_translation(DVec2::from_angle(-self.document_ptz.tilt()).rotate(DVec2::new(delta_x, delta_y)));\n\t\t\tresponses.add(SelectToolMessage::ShiftSelectedNodes { offset: transform.translation });\n\n\t\t\tfor layer in self.network_interface.shallowest_unique_layers(&[]).filter(|layer| can_move(*layer)) {\n\t\t\t\tresponses.add(GraphOperationMessage::TransformChange {\n\t\t\t\t\tlayer,\n\t\t\t\t\ttransform,\n\t\t\t\t\ttransform_in: TransformIn::Local,\n\t\t\t\t\tskip_rerender: false,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tlet selected_bounding_box = self.network_interface.selected_bounds_document_space(false, &[]);\n\t\tlet Some([existing_top_left, existing_bottom_right]) = selected_bounding_box else { return };\n\n\t\t// Swap and negate coordinates as needed to match the resize direction that's closest to the current tilt angle\n\t\tlet tilt = (self.document_ptz.tilt() + std::f64::consts::TAU) % std::f64::consts::TAU;\n\t\tlet (delta_x, delta_y, opposite_x, opposite_y) = match ((tilt + std::f64::consts::FRAC_PI_4) / std::f64::consts::FRAC_PI_2).floor() as i32 % 4 {\n\t\t\t0 => (delta_x, delta_y, false, false),\n\t\t\t1 => (delta_y, -delta_x, false, true),\n\t\t\t2 => (-delta_x, -delta_y, true, true),\n\t\t\t3 => (-delta_y, delta_x, true, false),\n\t\t\t_ => unreachable!(),\n\t\t};\n\n\t\tlet size = existing_bottom_right - existing_top_left;\n\t\t// TODO: This is a hacky band-aid. It still results in the shape becoming zero-sized. Properly fix this using the correct math.\n\t\t// If size is zero we clamp it to minimun value to avoid dividing by zero vector to calculate enlargement.\n\t\tlet size = size.max(DVec2::ONE);\n\t\tlet enlargement = DVec2::new(\n\t\t\tif resize_opposite_corner != opposite_x { -delta_x } else { delta_x },\n\t\t\tif resize_opposite_corner != opposite_y { -delta_y } else { delta_y },\n\t\t);\n\t\tlet enlargement_factor = (enlargement + size) / size;\n\n\t\tlet position = DVec2::new(\n\t\t\texisting_top_left.x + if resize_opposite_corner != opposite_x { delta_x } else { 0. },\n\t\t\texisting_top_left.y + if resize_opposite_corner != opposite_y { delta_y } else { 0. },\n\t\t);\n\t\tlet mut pivot = (existing_top_left * enlargement_factor - position) / (enlargement_factor - DVec2::ONE);\n\t\tif !pivot.x.is_finite() {\n\t\t\tpivot.x = 0.;\n\t\t}\n\t\tif !pivot.y.is_finite() {\n\t\t\tpivot.y = 0.;\n\t\t}\n\t\tlet scale = DAffine2::from_scale(enlargement_factor);\n\t\tlet pivot = DAffine2::from_translation(pivot);\n\t\tlet transformation = pivot * scale * pivot.inverse();\n\t\tlet document_to_viewport = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz);\n\n\t\tfor layer in self.network_interface.shallowest_unique_layers(&[]).filter(|layer| can_move(*layer)) {\n\t\t\tlet to = document_to_viewport.inverse() * self.metadata().downstream_transform_to_viewport(layer);\n\t\t\tlet original_transform = self.metadata().upstream_transform(layer.to_node());\n\t\t\tlet new = to.inverse() * transformation * to * original_transform;\n\t\t\tresponses.add(GraphOperationMessage::TransformSet {\n\t\t\t\tlayer,\n\t\t\t\ttransform: new,\n\t\t\t\ttransform_in: TransformIn::Local,\n\t\t\t\tskip_rerender: false,\n\t\t\t});\n\t\t}\n\t}\n\n\t/// Loads all of the fonts in the document.\n\tpub fn load_layer_resources(&self, responses: &mut VecDeque<Message>) {\n\t\tlet mut fonts_to_load = HashSet::new();\n\n\t\tfor (_, node, _) in self.document_network().recursive_nodes() {\n\t\t\tfor input in &node.inputs {\n\t\t\t\tif let Some(TaggedValue::Font(font)) = input.as_value() {\n\t\t\t\t\tfonts_to_load.insert(font.clone());\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor font in fonts_to_load {\n\t\t\tresponses.add(PortfolioMessage::LoadFontData { font });\n\t\t}\n\t}\n\n\tpub fn update_document_widgets(&self, responses: &mut VecDeque<Message>, animation_is_playing: bool, time: Duration) {\n\t\tlet mut snapping_state = self.snapping_state.clone();\n\t\tlet mut snapping_state2 = self.snapping_state.clone();\n\n\t\tlet mut widgets = vec![\n\t\t\tIconButton::new(\"PlaybackToStart\", 24)\n\t\t\t\t.tooltip_label(\"Restart Animation\")\n\t\t\t\t.tooltip_shortcut(action_shortcut!(AnimationMessageDiscriminant::RestartAnimation))\n\t\t\t\t.on_update(|_| AnimationMessage::RestartAnimation.into())\n\t\t\t\t.disabled(time == Duration::ZERO)\n\t\t\t\t.widget_instance(),\n\t\t\tIconButton::new(if animation_is_playing { \"PlaybackPause\" } else { \"PlaybackPlay\" }, 24)\n\t\t\t\t.tooltip_label(if animation_is_playing { \"Pause Animation\" } else { \"Play Animation\" })\n\t\t\t\t.tooltip_shortcut(action_shortcut!(AnimationMessageDiscriminant::ToggleLivePreview))\n\t\t\t\t.on_update(|_| AnimationMessage::ToggleLivePreview.into())\n\t\t\t\t.widget_instance(),\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\tCheckboxInput::new(self.overlays_visibility_settings.all)\n\t\t\t\t.icon(\"Overlays\")\n\t\t\t\t.tooltip_label(\"Overlays\")\n\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::ToggleOverlaysVisibility))\n\t\t\t\t.on_update(|optional_input: &CheckboxInput| {\n\t\t\t\t\tDocumentMessage::SetOverlaysVisibility {\n\t\t\t\t\t\tvisible: optional_input.checked,\n\t\t\t\t\t\toverlays_type: None,\n\t\t\t\t\t}\n\t\t\t\t\t.into()\n\t\t\t\t})\n\t\t\t\t.widget_instance(),\n\t\t\tPopoverButton::new()\n\t\t\t\t.popover_layout(Layout(vec![\n\t\t\t\t\tLayoutGroup::row(vec![TextLabel::new(\"Overlays\").bold(true).widget_instance()]),\n\t\t\t\t\tLayoutGroup::row(vec![TextLabel::new(\"General\").widget_instance()]),\n\t\t\t\t\tLayoutGroup::row({\n\t\t\t\t\t\tlet checkbox_id = CheckboxId::new();\n\t\t\t\t\t\tvec![\n\t\t\t\t\t\t\tCheckboxInput::new(self.overlays_visibility_settings.artboard_name)\n\t\t\t\t\t\t\t\t.on_update(|optional_input: &CheckboxInput| {\n\t\t\t\t\t\t\t\t\tDocumentMessage::SetOverlaysVisibility {\n\t\t\t\t\t\t\t\t\t\tvisible: optional_input.checked,\n\t\t\t\t\t\t\t\t\t\toverlays_type: Some(OverlaysType::ArtboardName),\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t.into()\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t.for_label(checkbox_id)\n\t\t\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t\t\t\tTextLabel::new(\"Artboard Name\".to_string()).for_checkbox(checkbox_id).widget_instance(),\n\t\t\t\t\t\t]\n\t\t\t\t\t}),\n\t\t\t\t\tLayoutGroup::row({\n\t\t\t\t\t\tlet checkbox_id = CheckboxId::new();\n\t\t\t\t\t\tvec![\n\t\t\t\t\t\t\tCheckboxInput::new(self.overlays_visibility_settings.transform_measurement)\n\t\t\t\t\t\t\t\t.on_update(|optional_input: &CheckboxInput| {\n\t\t\t\t\t\t\t\t\tDocumentMessage::SetOverlaysVisibility {\n\t\t\t\t\t\t\t\t\t\tvisible: optional_input.checked,\n\t\t\t\t\t\t\t\t\t\toverlays_type: Some(OverlaysType::TransformMeasurement),\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t.into()\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t.for_label(checkbox_id)\n\t\t\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t\t\t\tTextLabel::new(\"G/R/S Measurement\".to_string()).for_checkbox(checkbox_id).widget_instance(),\n\t\t\t\t\t\t]\n\t\t\t\t\t}),\n\t\t\t\t\tLayoutGroup::row(vec![TextLabel::new(\"Select Tool\").widget_instance()]),\n\t\t\t\t\tLayoutGroup::row({\n\t\t\t\t\t\tlet checkbox_id = CheckboxId::new();\n\t\t\t\t\t\tvec![\n\t\t\t\t\t\t\tCheckboxInput::new(self.overlays_visibility_settings.quick_measurement)\n\t\t\t\t\t\t\t\t.on_update(|optional_input: &CheckboxInput| {\n\t\t\t\t\t\t\t\t\tDocumentMessage::SetOverlaysVisibility {\n\t\t\t\t\t\t\t\t\t\tvisible: optional_input.checked,\n\t\t\t\t\t\t\t\t\t\toverlays_type: Some(OverlaysType::QuickMeasurement),\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t.into()\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t.for_label(checkbox_id)\n\t\t\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t\t\t\tTextLabel::new(\"Quick Measurement\".to_string()).for_checkbox(checkbox_id).widget_instance(),\n\t\t\t\t\t\t]\n\t\t\t\t\t}),\n\t\t\t\t\tLayoutGroup::row({\n\t\t\t\t\t\tlet checkbox_id = CheckboxId::new();\n\t\t\t\t\t\tvec![\n\t\t\t\t\t\t\tCheckboxInput::new(self.overlays_visibility_settings.transform_cage)\n\t\t\t\t\t\t\t\t.on_update(|optional_input: &CheckboxInput| {\n\t\t\t\t\t\t\t\t\tDocumentMessage::SetOverlaysVisibility {\n\t\t\t\t\t\t\t\t\t\tvisible: optional_input.checked,\n\t\t\t\t\t\t\t\t\t\toverlays_type: Some(OverlaysType::TransformCage),\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t.into()\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t.for_label(checkbox_id)\n\t\t\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t\t\t\tTextLabel::new(\"Transform Cage\".to_string()).for_checkbox(checkbox_id).widget_instance(),\n\t\t\t\t\t\t]\n\t\t\t\t\t}),\n\t\t\t\t\tLayoutGroup::row({\n\t\t\t\t\t\tlet checkbox_id = CheckboxId::new();\n\t\t\t\t\t\tvec![\n\t\t\t\t\t\t\tCheckboxInput::new(self.overlays_visibility_settings.compass_rose)\n\t\t\t\t\t\t\t\t.on_update(|optional_input: &CheckboxInput| {\n\t\t\t\t\t\t\t\t\tDocumentMessage::SetOverlaysVisibility {\n\t\t\t\t\t\t\t\t\t\tvisible: optional_input.checked,\n\t\t\t\t\t\t\t\t\t\toverlays_type: Some(OverlaysType::CompassRose),\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t.into()\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t.for_label(checkbox_id)\n\t\t\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t\t\t\tTextLabel::new(\"Transform Dial\".to_string()).for_checkbox(checkbox_id).widget_instance(),\n\t\t\t\t\t\t]\n\t\t\t\t\t}),\n\t\t\t\t\tLayoutGroup::row({\n\t\t\t\t\t\tlet checkbox_id = CheckboxId::new();\n\t\t\t\t\t\tvec![\n\t\t\t\t\t\t\tCheckboxInput::new(self.overlays_visibility_settings.pivot)\n\t\t\t\t\t\t\t\t.on_update(|optional_input: &CheckboxInput| {\n\t\t\t\t\t\t\t\t\tDocumentMessage::SetOverlaysVisibility {\n\t\t\t\t\t\t\t\t\t\tvisible: optional_input.checked,\n\t\t\t\t\t\t\t\t\t\toverlays_type: Some(OverlaysType::Pivot),\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t.into()\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t.for_label(checkbox_id)\n\t\t\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t\t\t\tTextLabel::new(\"Transform Pivot\".to_string()).for_checkbox(checkbox_id).widget_instance(),\n\t\t\t\t\t\t]\n\t\t\t\t\t}),\n\t\t\t\t\tLayoutGroup::row({\n\t\t\t\t\t\tlet checkbox_id = CheckboxId::new();\n\t\t\t\t\t\tvec![\n\t\t\t\t\t\t\tCheckboxInput::new(self.overlays_visibility_settings.origin)\n\t\t\t\t\t\t\t\t.on_update(|optional_input: &CheckboxInput| {\n\t\t\t\t\t\t\t\t\tDocumentMessage::SetOverlaysVisibility {\n\t\t\t\t\t\t\t\t\t\tvisible: optional_input.checked,\n\t\t\t\t\t\t\t\t\t\toverlays_type: Some(OverlaysType::Origin),\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t.into()\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t.for_label(checkbox_id)\n\t\t\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t\t\t\tTextLabel::new(\"Transform Origin\".to_string()).for_checkbox(checkbox_id).widget_instance(),\n\t\t\t\t\t\t]\n\t\t\t\t\t}),\n\t\t\t\t\tLayoutGroup::row({\n\t\t\t\t\t\tlet checkbox_id = CheckboxId::new();\n\t\t\t\t\t\tvec![\n\t\t\t\t\t\t\tCheckboxInput::new(self.overlays_visibility_settings.hover_outline)\n\t\t\t\t\t\t\t\t.on_update(|optional_input: &CheckboxInput| {\n\t\t\t\t\t\t\t\t\tDocumentMessage::SetOverlaysVisibility {\n\t\t\t\t\t\t\t\t\t\tvisible: optional_input.checked,\n\t\t\t\t\t\t\t\t\t\toverlays_type: Some(OverlaysType::HoverOutline),\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t.into()\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t.for_label(checkbox_id)\n\t\t\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t\t\t\tTextLabel::new(\"Hover Outline\".to_string()).for_checkbox(checkbox_id).widget_instance(),\n\t\t\t\t\t\t]\n\t\t\t\t\t}),\n\t\t\t\t\tLayoutGroup::row({\n\t\t\t\t\t\tlet checkbox_id = CheckboxId::new();\n\t\t\t\t\t\tvec![\n\t\t\t\t\t\t\tCheckboxInput::new(self.overlays_visibility_settings.selection_outline)\n\t\t\t\t\t\t\t\t.on_update(|optional_input: &CheckboxInput| {\n\t\t\t\t\t\t\t\t\tDocumentMessage::SetOverlaysVisibility {\n\t\t\t\t\t\t\t\t\t\tvisible: optional_input.checked,\n\t\t\t\t\t\t\t\t\t\toverlays_type: Some(OverlaysType::SelectionOutline),\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t.into()\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t.for_label(checkbox_id)\n\t\t\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t\t\t\tTextLabel::new(\"Selection Outline\".to_string()).for_checkbox(checkbox_id).widget_instance(),\n\t\t\t\t\t\t]\n\t\t\t\t\t}),\n\t\t\t\t\tLayoutGroup::row({\n\t\t\t\t\t\tlet checkbox_id = CheckboxId::new();\n\t\t\t\t\t\tvec![\n\t\t\t\t\t\t\tCheckboxInput::new(self.overlays_visibility_settings.layer_origin_cross)\n\t\t\t\t\t\t\t\t.on_update(|optional_input: &CheckboxInput| {\n\t\t\t\t\t\t\t\t\tDocumentMessage::SetOverlaysVisibility {\n\t\t\t\t\t\t\t\t\t\tvisible: optional_input.checked,\n\t\t\t\t\t\t\t\t\t\toverlays_type: Some(OverlaysType::LayerOriginCross),\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t.into()\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t.for_label(checkbox_id)\n\t\t\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t\t\t\tTextLabel::new(\"Layer Origin\".to_string()).for_checkbox(checkbox_id).widget_instance(),\n\t\t\t\t\t\t]\n\t\t\t\t\t}),\n\t\t\t\t\tLayoutGroup::row(vec![TextLabel::new(\"Pen & Path Tools\").widget_instance()]),\n\t\t\t\t\tLayoutGroup::row({\n\t\t\t\t\t\tlet checkbox_id = CheckboxId::new();\n\t\t\t\t\t\tvec![\n\t\t\t\t\t\t\tCheckboxInput::new(self.overlays_visibility_settings.path)\n\t\t\t\t\t\t\t\t.on_update(|optional_input: &CheckboxInput| {\n\t\t\t\t\t\t\t\t\tDocumentMessage::SetOverlaysVisibility {\n\t\t\t\t\t\t\t\t\t\tvisible: optional_input.checked,\n\t\t\t\t\t\t\t\t\t\toverlays_type: Some(OverlaysType::Path),\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t.into()\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t.for_label(checkbox_id)\n\t\t\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t\t\t\tTextLabel::new(\"Path\".to_string()).for_checkbox(checkbox_id).widget_instance(),\n\t\t\t\t\t\t]\n\t\t\t\t\t}),\n\t\t\t\t\tLayoutGroup::row({\n\t\t\t\t\t\tlet checkbox_id = CheckboxId::new();\n\t\t\t\t\t\tvec![\n\t\t\t\t\t\t\tCheckboxInput::new(self.overlays_visibility_settings.anchors)\n\t\t\t\t\t\t\t\t.on_update(|optional_input: &CheckboxInput| {\n\t\t\t\t\t\t\t\t\tDocumentMessage::SetOverlaysVisibility {\n\t\t\t\t\t\t\t\t\t\tvisible: optional_input.checked,\n\t\t\t\t\t\t\t\t\t\toverlays_type: Some(OverlaysType::Anchors),\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t.into()\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t.for_label(checkbox_id)\n\t\t\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t\t\t\tTextLabel::new(\"Anchors\".to_string()).for_checkbox(checkbox_id).widget_instance(),\n\t\t\t\t\t\t]\n\t\t\t\t\t}),\n\t\t\t\t\tLayoutGroup::row({\n\t\t\t\t\t\tlet checkbox_id = CheckboxId::new();\n\t\t\t\t\t\tvec![\n\t\t\t\t\t\t\tCheckboxInput::new(self.overlays_visibility_settings.handles)\n\t\t\t\t\t\t\t\t.disabled(!self.overlays_visibility_settings.anchors)\n\t\t\t\t\t\t\t\t.on_update(|optional_input: &CheckboxInput| {\n\t\t\t\t\t\t\t\t\tDocumentMessage::SetOverlaysVisibility {\n\t\t\t\t\t\t\t\t\t\tvisible: optional_input.checked,\n\t\t\t\t\t\t\t\t\t\toverlays_type: Some(OverlaysType::Handles),\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t.into()\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t.for_label(checkbox_id)\n\t\t\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t\t\t\tTextLabel::new(\"Handles\".to_string())\n\t\t\t\t\t\t\t\t.disabled(!self.overlays_visibility_settings.anchors)\n\t\t\t\t\t\t\t\t.for_checkbox(checkbox_id)\n\t\t\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t\t\t]\n\t\t\t\t\t}),\n\t\t\t\t]))\n\t\t\t\t.widget_instance(),\n\t\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\t\tCheckboxInput::new(snapping_state.snapping_enabled)\n\t\t\t\t.icon(\"Snapping\")\n\t\t\t\t.tooltip_label(\"Snapping\")\n\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::ToggleSnapping))\n\t\t\t\t.on_update(move |optional_input: &CheckboxInput| {\n\t\t\t\t\tDocumentMessage::SetSnapping {\n\t\t\t\t\t\tclosure: Some(|snapping_state| &mut snapping_state.snapping_enabled),\n\t\t\t\t\t\tsnapping_state: optional_input.checked,\n\t\t\t\t\t}\n\t\t\t\t\t.into()\n\t\t\t\t})\n\t\t\t\t.widget_instance(),\n\t\t\tPopoverButton::new()\n\t\t\t\t.popover_layout(Layout(\n\t\t\t\t\t[\n\t\t\t\t\t\tLayoutGroup::row(vec![TextLabel::new(\"Snapping\").bold(true).widget_instance()]),\n\t\t\t\t\t\tLayoutGroup::row(vec![TextLabel::new(SnappingOptions::BoundingBoxes.to_string()).widget_instance()]),\n\t\t\t\t\t]\n\t\t\t\t\t.into_iter()\n\t\t\t\t\t.chain(SNAP_FUNCTIONS_FOR_BOUNDING_BOXES.into_iter().map(|(name, closure, description)| {\n\t\t\t\t\t\tLayoutGroup::row({\n\t\t\t\t\t\t\tlet checkbox_id = CheckboxId::new();\n\t\t\t\t\t\t\tvec![\n\t\t\t\t\t\t\t\tCheckboxInput::new(*closure(&mut snapping_state))\n\t\t\t\t\t\t\t\t\t.on_update(move |input: &CheckboxInput| {\n\t\t\t\t\t\t\t\t\t\tDocumentMessage::SetSnapping {\n\t\t\t\t\t\t\t\t\t\t\tclosure: Some(closure),\n\t\t\t\t\t\t\t\t\t\t\tsnapping_state: input.checked,\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t.into()\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t.tooltip_label(name)\n\t\t\t\t\t\t\t\t\t.tooltip_description(description)\n\t\t\t\t\t\t\t\t\t.for_label(checkbox_id)\n\t\t\t\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t\t\t\t\tTextLabel::new(name).tooltip_label(name).tooltip_description(description).for_checkbox(checkbox_id).widget_instance(),\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t})\n\t\t\t\t\t}))\n\t\t\t\t\t.chain([LayoutGroup::row(vec![TextLabel::new(SnappingOptions::Paths.to_string()).widget_instance()])])\n\t\t\t\t\t.chain(SNAP_FUNCTIONS_FOR_PATHS.into_iter().map(|(name, closure, description)| {\n\t\t\t\t\t\tLayoutGroup::row({\n\t\t\t\t\t\t\tlet checkbox_id = CheckboxId::new();\n\t\t\t\t\t\t\tvec![\n\t\t\t\t\t\t\t\tCheckboxInput::new(*closure(&mut snapping_state2))\n\t\t\t\t\t\t\t\t\t.on_update(move |input: &CheckboxInput| {\n\t\t\t\t\t\t\t\t\t\tDocumentMessage::SetSnapping {\n\t\t\t\t\t\t\t\t\t\t\tclosure: Some(closure),\n\t\t\t\t\t\t\t\t\t\t\tsnapping_state: input.checked,\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t.into()\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t.tooltip_label(name)\n\t\t\t\t\t\t\t\t\t.tooltip_description(description)\n\t\t\t\t\t\t\t\t\t.for_label(checkbox_id)\n\t\t\t\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t\t\t\t\tTextLabel::new(name).tooltip_label(name).tooltip_description(description).for_checkbox(checkbox_id).widget_instance(),\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t})\n\t\t\t\t\t}))\n\t\t\t\t\t.collect(),\n\t\t\t\t))\n\t\t\t\t.widget_instance(),\n\t\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\t\tCheckboxInput::new(self.snapping_state.grid_snapping)\n\t\t\t\t.icon(\"Grid\")\n\t\t\t\t.tooltip_label(\"Grid\")\n\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::ToggleGridVisibility))\n\t\t\t\t.on_update(|optional_input: &CheckboxInput| DocumentMessage::GridVisibility { visible: optional_input.checked }.into())\n\t\t\t\t.widget_instance(),\n\t\t\tPopoverButton::new()\n\t\t\t\t.popover_layout(Layout(overlay_options(&self.snapping_state.grid)))\n\t\t\t\t.popover_min_width(Some(320))\n\t\t\t\t.widget_instance(),\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t{\n\t\t\t\tlet disabled = cfg!(target_family = \"wasm\") && wgpu_available() == Some(false);\n\n\t\t\t\tlet mut entries = vec![\n\t\t\t\t\tRadioEntryData::new(\"Normal\")\n\t\t\t\t\t\t.icon(\"RenderModeNormal\")\n\t\t\t\t\t\t.tooltip_label(\"Render Mode: Normal\")\n\t\t\t\t\t\t.on_update(|_| DocumentMessage::SetRenderMode { render_mode: RenderMode::Normal }.into()),\n\t\t\t\t\tRadioEntryData::new(\"Outline\")\n\t\t\t\t\t\t.icon(\"RenderModeOutline\")\n\t\t\t\t\t\t.tooltip_label(\"Render Mode: Outline\")\n\t\t\t\t\t\t.on_update(|_| DocumentMessage::SetRenderMode { render_mode: RenderMode::Outline }.into()),\n\t\t\t\t\tRadioEntryData::new(\"PixelPreview\").icon(\"RenderModePixels\").tooltip_label(\"Render Mode: Pixel Preview\").on_update(|_| {\n\t\t\t\t\t\tDocumentMessage::SetRenderMode {\n\t\t\t\t\t\t\trender_mode: RenderMode::PixelPreview,\n\t\t\t\t\t\t}\n\t\t\t\t\t\t.into()\n\t\t\t\t\t}),\n\t\t\t\t\tRadioEntryData::new(\"SvgPreview\")\n\t\t\t\t\t\t.icon(\"RenderModeSvg\")\n\t\t\t\t\t\t.tooltip_label(\"Render Mode: SVG Preview\")\n\t\t\t\t\t\t.on_update(|_| DocumentMessage::SetRenderMode { render_mode: RenderMode::SvgPreview }.into()),\n\t\t\t\t];\n\t\t\t\tlet mut selected_index = self.render_mode as u32;\n\n\t\t\t\tif disabled {\n\t\t\t\t\tfor entry in &mut entries {\n\t\t\t\t\t\tentry.tooltip_description = \"\n\t\t\t\t\t\t\t*Normal*, *Outline*, and *Pixel Preview* render modes are not available in this browser. For compatibility, *SVG Preview* mode is active as a fallback.\\n\\\n\t\t\t\t\t\t\t\\n\\\n\t\t\t\t\t\t\tThis functionality requires WebGPU support. Check webgpu.org for browser implementation status.\n\t\t\t\t\t\t\t\"\n\t\t\t\t\t\t.trim()\n\t\t\t\t\t\t.into();\n\t\t\t\t\t}\n\n\t\t\t\t\tselected_index = entries.iter().position(|entry| entry.value == \"SvgPreview\").unwrap() as u32;\n\t\t\t\t}\n\n\t\t\t\tRadioInput::new(entries).selected_index(Some(selected_index)).disabled(disabled).narrow(true).widget_instance()\n\t\t\t},\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t];\n\n\t\twidgets.extend(navigation_controls(&self.document_ptz, &self.navigation_handler, false));\n\n\t\tlet tilt_value = self.navigation_handler.snapped_tilt(self.document_ptz.tilt()) / (std::f64::consts::PI / 180.);\n\t\tif tilt_value.abs() > 0.00001 {\n\t\t\twidgets.extend([\n\t\t\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\t\t\tNumberInput::new(Some(tilt_value))\n\t\t\t\t\t.unit(\"°\")\n\t\t\t\t\t.increment_behavior(NumberInputIncrementBehavior::Callback)\n\t\t\t\t\t.increment_callback_increase(|number_input: &NumberInput| {\n\t\t\t\t\t\tlet one = 1. + f64::EPSILON * 100.;\n\t\t\t\t\t\tNavigationMessage::CanvasTiltSet {\n\t\t\t\t\t\t\tangle_radians: ((number_input.value.unwrap() / VIEWPORT_ROTATE_SNAP_INTERVAL + one).floor() * VIEWPORT_ROTATE_SNAP_INTERVAL).to_radians(),\n\t\t\t\t\t\t}\n\t\t\t\t\t\t.into()\n\t\t\t\t\t})\n\t\t\t\t\t.increment_callback_decrease(|number_input: &NumberInput| {\n\t\t\t\t\t\tlet one = 1. + f64::EPSILON * 100.;\n\t\t\t\t\t\tNavigationMessage::CanvasTiltSet {\n\t\t\t\t\t\t\tangle_radians: ((number_input.value.unwrap() / VIEWPORT_ROTATE_SNAP_INTERVAL - one).ceil() * VIEWPORT_ROTATE_SNAP_INTERVAL).to_radians(),\n\t\t\t\t\t\t}\n\t\t\t\t\t\t.into()\n\t\t\t\t\t})\n\t\t\t\t\t.tooltip_label(\"Canvas Tilt\")\n\t\t\t\t\t.on_update(|number_input: &NumberInput| {\n\t\t\t\t\t\tNavigationMessage::CanvasTiltSet {\n\t\t\t\t\t\t\tangle_radians: number_input.value.unwrap().to_radians(),\n\t\t\t\t\t\t}\n\t\t\t\t\t\t.into()\n\t\t\t\t\t})\n\t\t\t\t\t.widget_instance(),\n\t\t\t]);\n\t\t}\n\n\t\twidgets.extend([\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\tTextButton::new(\"Node Graph\")\n\t\t\t\t.icon(if self.graph_view_overlay_open { \"GraphViewOpen\" } else { \"GraphViewClosed\" })\n\t\t\t\t.hover_icon(if self.graph_view_overlay_open { \"GraphViewClosed\" } else { \"GraphViewOpen\" })\n\t\t\t\t.tooltip_label(if self.graph_view_overlay_open { \"Hide Node Graph\" } else { \"Show Node Graph\" })\n\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::GraphViewOverlayToggle))\n\t\t\t\t.on_update(move |_| DocumentMessage::GraphViewOverlayToggle.into())\n\t\t\t\t.widget_instance(),\n\t\t]);\n\n\t\tresponses.add(LayoutMessage::SendLayout {\n\t\t\tlayout: Layout(vec![LayoutGroup::row(widgets)]),\n\t\t\tlayout_target: LayoutTarget::DocumentBar,\n\t\t});\n\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t}\n\n\tpub fn update_layers_panel_control_bar_widgets(&self, layers_panel_open: bool, responses: &mut VecDeque<Message>) {\n\t\tif !layers_panel_open {\n\t\t\treturn;\n\t\t}\n\n\t\t// Get an iterator over the selected layers (excluding artboards which don't have an opacity or blend mode).\n\t\tlet selected_nodes = self.network_interface.selected_nodes();\n\t\tlet selected_layers_except_artboards = selected_nodes.selected_layers_except_artboards(&self.network_interface);\n\n\t\t// Look up the current opacity and blend mode of the selected layers (if any), and split the iterator into the first tuple and the rest.\n\t\tlet mut blending_options = selected_layers_except_artboards.map(|layer| {\n\t\t\t(\n\t\t\t\tget_opacity(layer, &self.network_interface).unwrap_or(100.),\n\t\t\t\tget_fill(layer, &self.network_interface).unwrap_or(100.),\n\t\t\t\tget_blend_mode(layer, &self.network_interface).unwrap_or_default(),\n\t\t\t)\n\t\t});\n\t\tlet first_blending_options = blending_options.next();\n\t\tlet result_blending_options = blending_options;\n\n\t\t// If there are no selected layers, disable the opacity and blend mode widgets.\n\t\tlet disabled = first_blending_options.is_none();\n\n\t\t// Amongst the selected layers, check if the opacities and blend modes are identical across all layers.\n\t\t// The result is setting `option` and `blend_mode` to Some value if all their values are identical, or None if they are not.\n\t\t// If identical, we display the value in the widget. If not, we display a dash indicating dissimilarity.\n\t\tlet (opacity, fill, blend_mode) = first_blending_options\n\t\t\t.map(|(first_opacity, first_fill, first_blend_mode)| {\n\t\t\t\tlet mut opacity_identical = true;\n\t\t\t\tlet mut fill_identical = true;\n\t\t\t\tlet mut blend_mode_identical = true;\n\n\t\t\t\tfor (opacity, fill, blend_mode) in result_blending_options {\n\t\t\t\t\tif (opacity - first_opacity).abs() > (f64::EPSILON * 100.) {\n\t\t\t\t\t\topacity_identical = false;\n\t\t\t\t\t}\n\t\t\t\t\tif (fill - first_fill).abs() > (f64::EPSILON * 100.) {\n\t\t\t\t\t\tfill_identical = false;\n\t\t\t\t\t}\n\t\t\t\t\tif blend_mode != first_blend_mode {\n\t\t\t\t\t\tblend_mode_identical = false;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t(\n\t\t\t\t\topacity_identical.then_some(first_opacity),\n\t\t\t\t\tfill_identical.then_some(first_fill),\n\t\t\t\t\tblend_mode_identical.then_some(first_blend_mode),\n\t\t\t\t)\n\t\t\t})\n\t\t\t.unwrap_or((None, None, None));\n\n\t\tlet blend_mode_menu_entries = BlendMode::list_svg_subset()\n\t\t\t.iter()\n\t\t\t.map(|modes| {\n\t\t\t\tmodes\n\t\t\t\t\t.iter()\n\t\t\t\t\t.map(|&blend_mode| {\n\t\t\t\t\t\tMenuListEntry::new(format!(\"{blend_mode:?}\"))\n\t\t\t\t\t\t\t.label(blend_mode.to_string())\n\t\t\t\t\t\t\t.on_update(move |_| DocumentMessage::SetBlendModeForSelectedLayers { blend_mode }.into())\n\t\t\t\t\t\t\t.on_commit(|_| DocumentMessage::AddTransaction.into())\n\t\t\t\t\t})\n\t\t\t\t\t.collect()\n\t\t\t})\n\t\t\t.collect();\n\n\t\tlet has_selection = self.network_interface.selected_nodes().selected_layers(self.metadata()).next().is_some();\n\t\tlet selection_all_visible = self\n\t\t\t.network_interface\n\t\t\t.selected_nodes()\n\t\t\t.selected_layers(self.metadata())\n\t\t\t.all(|layer| self.network_interface.is_visible(&layer.to_node(), &[]));\n\t\tlet selection_all_locked = self\n\t\t\t.network_interface\n\t\t\t.selected_nodes()\n\t\t\t.selected_layers(self.metadata())\n\t\t\t.all(|layer| self.network_interface.is_locked(&layer.to_node(), &[]));\n\n\t\tlet widgets = vec![\n\t\t\tDropdownInput::new(blend_mode_menu_entries)\n\t\t\t\t.selected_index(blend_mode.and_then(|blend_mode| blend_mode.index_in_list_svg_subset()).map(|index| index as u32))\n\t\t\t\t.disabled(disabled)\n\t\t\t\t.draw_icon(false)\n\t\t\t\t.max_width(100)\n\t\t\t\t.tooltip_label(\"Blend Mode\")\n\t\t\t\t.widget_instance(),\n\t\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\t\tNumberInput::new(opacity)\n\t\t\t\t.label(\"Opacity\")\n\t\t\t\t.unit(\"%\")\n\t\t\t\t.display_decimal_places(0)\n\t\t\t\t.disabled(disabled)\n\t\t\t\t.min(0.)\n\t\t\t\t.max(100.)\n\t\t\t\t.range_min(Some(0.))\n\t\t\t\t.range_max(Some(100.))\n\t\t\t\t.mode_range()\n\t\t\t\t.on_update(|number_input: &NumberInput| {\n\t\t\t\t\tif let Some(value) = number_input.value {\n\t\t\t\t\t\tDocumentMessage::SetOpacityForSelectedLayers { opacity: value / 100. }.into()\n\t\t\t\t\t} else {\n\t\t\t\t\t\tMessage::NoOp\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.on_commit(|_| DocumentMessage::AddTransaction.into())\n\t\t\t\t.max_width(100)\n\t\t\t\t.tooltip_label(\"Opacity\")\n\t\t\t\t.widget_instance(),\n\t\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\t\tNumberInput::new(fill)\n\t\t\t\t.label(\"Fill\")\n\t\t\t\t.unit(\"%\")\n\t\t\t\t.display_decimal_places(0)\n\t\t\t\t.disabled(disabled)\n\t\t\t\t.min(0.)\n\t\t\t\t.max(100.)\n\t\t\t\t.range_min(Some(0.))\n\t\t\t\t.range_max(Some(100.))\n\t\t\t\t.mode_range()\n\t\t\t\t.on_update(|number_input: &NumberInput| {\n\t\t\t\t\tif let Some(value) = number_input.value {\n\t\t\t\t\t\tDocumentMessage::SetFillForSelectedLayers { fill: value / 100. }.into()\n\t\t\t\t\t} else {\n\t\t\t\t\t\tMessage::NoOp\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.on_commit(|_| DocumentMessage::AddTransaction.into())\n\t\t\t\t.max_width(100)\n\t\t\t\t.tooltip_label(\"Fill\")\n\t\t\t\t.widget_instance(),\n\t\t];\n\t\tlet layers_panel_control_bar_left = Layout(vec![LayoutGroup::row(widgets)]);\n\n\t\tlet widgets = vec![\n\t\t\tIconButton::new(if selection_all_locked { \"PadlockLocked\" } else { \"PadlockUnlocked\" }, 24)\n\t\t\t\t.hover_icon(if selection_all_locked { \"PadlockUnlocked\" } else { \"PadlockLocked\" })\n\t\t\t\t.tooltip_label(if selection_all_locked { \"Unlock Selected\" } else { \"Lock Selected\" })\n\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::ToggleSelectedLocked))\n\t\t\t\t.on_update(|_| NodeGraphMessage::ToggleSelectedLocked.into())\n\t\t\t\t.disabled(!has_selection)\n\t\t\t\t.widget_instance(),\n\t\t\tIconButton::new(if selection_all_visible { \"EyeVisible\" } else { \"EyeHidden\" }, 24)\n\t\t\t\t.hover_icon(if selection_all_visible { \"EyeHide\" } else { \"EyeShow\" })\n\t\t\t\t.tooltip_label(if selection_all_visible { \"Hide Selected\" } else { \"Show Selected\" })\n\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::ToggleSelectedVisibility))\n\t\t\t\t.on_update(|_| DocumentMessage::ToggleSelectedVisibility.into())\n\t\t\t\t.disabled(!has_selection)\n\t\t\t\t.widget_instance(),\n\t\t];\n\t\tlet layers_panel_control_bar_right = Layout(vec![LayoutGroup::row(widgets)]);\n\n\t\tresponses.add(LayoutMessage::SendLayout {\n\t\t\tlayout: layers_panel_control_bar_left,\n\t\t\tlayout_target: LayoutTarget::LayersPanelControlLeftBar,\n\t\t});\n\t\tresponses.add(LayoutMessage::SendLayout {\n\t\t\tlayout: layers_panel_control_bar_right,\n\t\t\tlayout_target: LayoutTarget::LayersPanelControlRightBar,\n\t\t});\n\t}\n\n\tpub fn update_layers_panel_bottom_bar_widgets(&mut self, layers_panel_open: bool, responses: &mut VecDeque<Message>) {\n\t\tif !layers_panel_open {\n\t\t\treturn;\n\t\t}\n\n\t\tlet selected_nodes = self.network_interface.selected_nodes();\n\t\tlet mut selected_layers = selected_nodes.selected_layers(self.metadata());\n\t\tlet selected_layer = selected_layers.next();\n\t\tlet has_selection = selected_layer.is_some();\n\t\tlet has_multiple_selection = selected_layers.next().is_some();\n\t\tfor _ in selected_layers {}\n\n\t\tlet widgets = vec![\n\t\t\tPopoverButton::new()\n\t\t\t\t.icon(\"Node\")\n\t\t\t\t.menu_direction(Some(MenuDirection::Top))\n\t\t\t\t.tooltip_description(\"Add an operation to the end of this layer's chain of nodes.\")\n\t\t\t\t.disabled(!has_selection || has_multiple_selection)\n\t\t\t\t.popover_layout({\n\t\t\t\t\t// Showing only compatible types for the layer based on the output type of the node upstream from its horizontal input\n\t\t\t\t\tlet compatible_type = selected_layer.and_then(|layer| {\n\t\t\t\t\t\tself.network_interface\n\t\t\t\t\t\t\t.upstream_output_connector(&InputConnector::node(layer.to_node(), 1), &[])\n\t\t\t\t\t\t\t.and_then(|upstream_output| self.network_interface.output_type(&upstream_output, &[]).add_node_string())\n\t\t\t\t\t});\n\n\t\t\t\t\tlet mut node_chooser = NodeCatalog::new();\n\t\t\t\t\tnode_chooser.intial_search = compatible_type.unwrap_or(\"\".to_string());\n\n\t\t\t\t\tlet node_chooser = node_chooser\n\t\t\t\t\t\t.on_update(move |node_type| {\n\t\t\t\t\t\t\tif let Some(layer) = selected_layer {\n\t\t\t\t\t\t\t\tNodeGraphMessage::CreateNodeInLayerWithTransaction {\n\t\t\t\t\t\t\t\t\tnode_type: node_type.clone(),\n\t\t\t\t\t\t\t\t\tlayer: LayerNodeIdentifier::new_unchecked(layer.to_node()),\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t.into()\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tMessage::NoOp\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.widget_instance();\n\t\t\t\t\tLayout(vec![LayoutGroup::row(vec![node_chooser])])\n\t\t\t\t})\n\t\t\t\t.widget_instance(),\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\tIconButton::new(\"Folder\", 24)\n\t\t\t\t.tooltip_label(\"Group Selected\")\n\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::GroupSelectedLayers))\n\t\t\t\t.on_update(|_| {\n\t\t\t\t\tlet group_folder_type = GroupFolderType::Layer;\n\t\t\t\t\tDocumentMessage::GroupSelectedLayers { group_folder_type }.into()\n\t\t\t\t})\n\t\t\t\t.disabled(!has_selection)\n\t\t\t\t.widget_instance(),\n\t\t\tIconButton::new(\"NewLayer\", 24)\n\t\t\t\t.tooltip_label(\"New Layer\")\n\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::CreateEmptyFolder))\n\t\t\t\t.on_update(|_| DocumentMessage::CreateEmptyFolder.into())\n\t\t\t\t.widget_instance(),\n\t\t\tIconButton::new(\"Trash\", 24)\n\t\t\t\t.tooltip_label(\"Delete Selected\")\n\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::DeleteSelectedLayers))\n\t\t\t\t.on_update(|_| DocumentMessage::DeleteSelectedLayers.into())\n\t\t\t\t.disabled(!has_selection)\n\t\t\t\t.widget_instance(),\n\t\t];\n\t\tresponses.add(LayoutMessage::SendLayout {\n\t\t\tlayout: Layout(vec![LayoutGroup::row(widgets)]),\n\t\t\tlayout_target: LayoutTarget::LayersPanelBottomBar,\n\t\t});\n\t}\n\n\tpub fn selected_layers_reverse(&mut self, responses: &mut VecDeque<Message>) {\n\t\tlet selected_layers = self.network_interface.selected_nodes();\n\t\tlet metadata = self.metadata();\n\t\tlet selected_layer_set = selected_layers.selected_layers(metadata).collect::<HashSet<_>>();\n\n\t\t// Ignore those with selected ancestors\n\t\tlet mut top_level_layers = Vec::new();\n\t\tfor &layer in &selected_layer_set {\n\t\t\tlet mut is_top_level = true;\n\t\t\tlet mut current_layer = layer;\n\n\t\t\twhile let Some(parent) = current_layer.parent(metadata) {\n\t\t\t\tif selected_layer_set.contains(&parent) {\n\t\t\t\t\tis_top_level = false;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcurrent_layer = parent;\n\t\t\t}\n\n\t\t\tif is_top_level {\n\t\t\t\ttop_level_layers.push(layer);\n\t\t\t}\n\t\t}\n\n\t\t// Group selected layers by their parent\n\t\tlet mut grouped_layers: HashMap<LayerNodeIdentifier, Vec<(usize, LayerNodeIdentifier)>> = HashMap::new();\n\t\tfor &layer in &top_level_layers {\n\t\t\tif let Some(parent) = layer.parent(metadata) {\n\t\t\t\tlet index = parent.children(metadata).position(|child| child == layer).unwrap_or(usize::MAX);\n\n\t\t\t\tgrouped_layers.entry(parent).or_default().push((index, layer));\n\t\t\t}\n\t\t}\n\n\t\tlet mut modified = false;\n\n\t\t// Process each group separately\n\t\tfor (parent, mut layers) in grouped_layers {\n\t\t\t// Retrieve all children under the parent\n\t\t\tlet all_children = parent.children(metadata).collect::<Vec<_>>();\n\n\t\t\t// Separate unselected layers with their original indices\n\t\t\tlet unselected_layers = all_children\n\t\t\t\t.iter()\n\t\t\t\t.enumerate()\n\t\t\t\t.filter_map(|(index, &layer)| if !selected_layer_set.contains(&layer) { Some((index, layer)) } else { None })\n\t\t\t\t.collect::<Vec<_>>();\n\n\t\t\tlayers.sort_by_key(|(index, _)| *index);\n\n\t\t\tlet reversed_layers = layers.iter().rev().map(|(_, layer)| *layer).collect::<Vec<_>>();\n\t\t\tlet selected_positions = layers.iter().map(|(index, _)| *index).collect::<Vec<_>>();\n\t\t\tlet selected_iter = reversed_layers.into_iter();\n\t\t\tlet mut merged_layers = vec![None; all_children.len()];\n\n\t\t\tfor (&original_index, new_layer) in selected_positions.iter().zip(selected_iter) {\n\t\t\t\tmerged_layers[original_index] = Some(new_layer);\n\t\t\t}\n\n\t\t\t// Place unselected layers at their original positions\n\t\t\tfor (index, layer) in unselected_layers {\n\t\t\t\tif merged_layers[index].is_none() {\n\t\t\t\t\tmerged_layers[index] = Some(layer);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet final_layers = merged_layers.into_iter().flatten().collect::<Vec<_>>();\n\t\t\tif final_layers.is_empty() {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif !modified {\n\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\t\t\t}\n\n\t\t\tfor (index, layer) in final_layers.iter().enumerate() {\n\t\t\t\tresponses.add(NodeGraphMessage::MoveLayerToStack {\n\t\t\t\t\tlayer: *layer,\n\t\t\t\t\tparent,\n\t\t\t\t\tinsert_index: index,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tmodified = true;\n\t\t}\n\n\t\tif modified {\n\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\tresponses.add(NodeGraphMessage::SendGraph);\n\t\t}\n\t}\n\n\tpub fn selected_layers_reorder(&mut self, relative_index_offset: isize, responses: &mut VecDeque<Message>) {\n\t\tlet selected_nodes = self.network_interface.selected_nodes();\n\t\tlet mut selected_layers = selected_nodes.selected_layers(self.metadata());\n\n\t\tlet first_or_last_selected_layer = match relative_index_offset.signum() {\n\t\t\t-1 => selected_layers.next(),\n\t\t\t1 => selected_layers.last(),\n\t\t\t_ => panic!(\"selected_layers_reorder() must be given a non-zero value\"),\n\t\t};\n\n\t\tlet Some(pivot_layer) = first_or_last_selected_layer else {\n\t\t\treturn;\n\t\t};\n\t\tlet parent = pivot_layer.parent(self.metadata()).unwrap_or(LayerNodeIdentifier::ROOT_PARENT);\n\n\t\tlet sibling_layer_paths: Vec<_> = parent.children(self.metadata()).collect();\n\t\tlet Some(pivot_index) = sibling_layer_paths.iter().position(|path| *path == pivot_layer) else {\n\t\t\treturn;\n\t\t};\n\n\t\tlet max = sibling_layer_paths.len() as i64 - 1;\n\t\tlet insert_index = (pivot_index as i64 + relative_index_offset as i64).clamp(0, max) as usize;\n\n\t\tlet Some(&neighbor) = sibling_layer_paths.get(insert_index) else {\n\t\t\treturn;\n\t\t};\n\t\tlet Some(neighbor_index) = sibling_layer_paths.iter().position(|path| *path == neighbor) else {\n\t\t\treturn;\n\t\t};\n\n\t\t// If moving down, insert below this layer. If moving up, insert above this layer.\n\t\tlet insert_index = if relative_index_offset < 0 { neighbor_index } else { neighbor_index + 1 };\n\t\tresponses.add(DocumentMessage::MoveSelectedLayersTo { parent, insert_index });\n\t}\n\n\tpub fn graph_view_overlay_open(&self) -> bool {\n\t\tself.graph_view_overlay_open\n\t}\n}\n\n/// Create a network interface with a single export\nfn default_document_network_interface() -> NodeNetworkInterface {\n\tlet mut network_interface = NodeNetworkInterface::default();\n\tnetwork_interface.add_export(TaggedValue::Artboard(Default::default()), -1, \"\", &[]);\n\tnetwork_interface\n}\n\n/// Targets for the [`ClickXRayIter`]. In order to reduce computation, we prefer just a point/path test where possible.\n#[derive(Clone)]\nenum XRayTarget {\n\tPoint(DVec2),\n\tQuad(Quad),\n\tPath(BezPath),\n\tPolygon(Subpath<PointId>),\n}\n\n/// The result for the [`ClickXRayIter`] on the layer\nstruct XRayResult {\n\tclicked: bool,\n\tuse_children: bool,\n}\n\n/// An iterator for finding layers within an [`XRayTarget`]. Constructed by [`DocumentMessageHandler::intersect_quad`] and [`DocumentMessageHandler::click_xray`].\n#[derive(Clone)]\npub struct ClickXRayIter<'a> {\n\tnext_layer: Option<LayerNodeIdentifier>,\n\tnetwork_interface: &'a NodeNetworkInterface,\n\tparent_targets: Vec<(LayerNodeIdentifier, XRayTarget)>,\n}\n\nfn quad_to_kurbo(quad: Quad) -> BezPath {\n\tBezPath::from_path_segments(quad.all_edges().into_iter().map(|[start, end]| PathSeg::Line(Line::new(dvec2_to_point(start), dvec2_to_point(end)))))\n}\n\nfn click_targets_to_kurbo<'a>(click_targets: impl Iterator<Item = &'a ClickTarget>, transform: DAffine2) -> BezPath {\n\tlet segments = click_targets\n\t\t.filter_map(|target| {\n\t\t\tif let ClickTargetType::Subpath(subpath) = target.target_type() {\n\t\t\t\tSome(subpath.iter())\n\t\t\t} else {\n\t\t\t\tNone\n\t\t\t}\n\t\t})\n\t\t.flatten()\n\t\t.map(|bezier| Affine::new(transform.to_cols_array()) * bezier);\n\tBezPath::from_path_segments(segments)\n}\n\nimpl<'a> ClickXRayIter<'a> {\n\tfn new(network_interface: &'a NodeNetworkInterface, target: XRayTarget) -> Self {\n\t\tif let Some(first_layer) = LayerNodeIdentifier::ROOT_PARENT.first_child(network_interface.document_metadata()) {\n\t\t\tSelf {\n\t\t\t\tnetwork_interface,\n\t\t\t\tnext_layer: Some(first_layer),\n\t\t\t\tparent_targets: vec![(LayerNodeIdentifier::ROOT_PARENT, target)],\n\t\t\t}\n\t\t} else {\n\t\t\tSelf {\n\t\t\t\tnetwork_interface,\n\t\t\t\tnext_layer: Default::default(),\n\t\t\t\tparent_targets: Default::default(),\n\t\t\t}\n\t\t}\n\t}\n\n\t/// Handles the checking of the layer where the target is a rect or path\n\tfn check_layer_area_target(&mut self, click_targets: Option<&[Arc<ClickTarget>]>, clip: bool, layer: LayerNodeIdentifier, path: BezPath, transform: DAffine2) -> XRayResult {\n\t\tlet get_clip = || path.segments();\n\n\t\tlet intersects = click_targets.is_some_and(|targets| targets.iter().any(|target| target.intersect_path(get_clip, transform)));\n\t\tlet clicked = intersects;\n\t\tlet mut use_children = !clip || intersects;\n\n\t\t// In the case of a clip path where the area partially intersects, it is necessary to do a boolean operation.\n\t\t// We do this on this using the target area to reduce computation (as the target area is usually very simple).\n\t\tif clip && intersects {\n\t\t\tlet clip_path = click_targets_to_kurbo(click_targets.iter().flat_map(|x| x.iter()).map(|x| x.as_ref()), transform);\n\t\t\tlet intersection = boolean_intersect(&path, &clip_path);\n\t\t\tlet subtracted = BezPath::from_path_segments(intersection.iter().flat_map(|p| p.segments()));\n\t\t\tif subtracted.is_empty() {\n\t\t\t\tuse_children = false;\n\t\t\t} else {\n\t\t\t\t// All child layers will use the new clipped target area\n\t\t\t\tself.parent_targets.push((layer, XRayTarget::Path(subtracted)));\n\t\t\t}\n\t\t}\n\t\tXRayResult { clicked, use_children }\n\t}\n\n\t/// Handles the checking of the layer to find if it has been clicked\n\tfn check_layer(&mut self, layer: LayerNodeIdentifier) -> XRayResult {\n\t\tlet selected_layers = self.network_interface.selected_nodes();\n\t\t// Discard invisible and locked layers\n\t\tif !selected_layers.layer_visible(layer, self.network_interface) || selected_layers.layer_locked(layer, self.network_interface) {\n\t\t\treturn XRayResult { clicked: false, use_children: false };\n\t\t}\n\n\t\tlet click_targets = self.network_interface.document_metadata().click_targets(layer);\n\t\tlet transform = self.network_interface.document_metadata().transform_to_document(layer);\n\t\tlet target = &self.parent_targets.last().expect(\"In `check_layer()`: there should be a `target`\").1;\n\t\tlet clip = self.network_interface.document_metadata().is_clip(layer.to_node());\n\n\t\tmatch target {\n\t\t\t// Single points are much cheaper than paths so have their own special case\n\t\t\tXRayTarget::Point(point) => {\n\t\t\t\tlet intersects = click_targets.is_some_and(|targets| targets.iter().any(|target| target.intersect_point(*point, transform)));\n\t\t\t\tXRayResult {\n\t\t\t\t\tclicked: intersects,\n\t\t\t\t\tuse_children: !clip || intersects,\n\t\t\t\t}\n\t\t\t}\n\t\t\tXRayTarget::Quad(quad) => self.check_layer_area_target(click_targets, clip, layer, quad_to_kurbo(*quad), transform),\n\t\t\tXRayTarget::Path(path) => self.check_layer_area_target(click_targets, clip, layer, path.clone(), transform),\n\t\t\tXRayTarget::Polygon(polygon) => {\n\t\t\t\tlet polygon = BezPath::from_path_segments(polygon.iter_closed());\n\t\t\t\tself.check_layer_area_target(click_targets, clip, layer, polygon, transform)\n\t\t\t}\n\t\t}\n\t}\n}\n\npub fn navigation_controls(ptz: &PTZ, navigation_handler: &NavigationMessageHandler, node_graph: bool) -> Vec<WidgetInstance> {\n\tlet mut list = vec![\n\t\tIconButton::new(\"ZoomIn\", 24)\n\t\t\t.tooltip_label(\"Zoom In\")\n\t\t\t.tooltip_shortcut(action_shortcut!(NavigationMessageDiscriminant::CanvasZoomIncrease))\n\t\t\t.on_update(|_| NavigationMessage::CanvasZoomIncrease { center_on_mouse: false }.into())\n\t\t\t.widget_instance(),\n\t\tIconButton::new(\"ZoomOut\", 24)\n\t\t\t.tooltip_label(\"Zoom Out\")\n\t\t\t.tooltip_shortcut(action_shortcut!(NavigationMessageDiscriminant::CanvasZoomDecrease))\n\t\t\t.on_update(|_| NavigationMessage::CanvasZoomDecrease { center_on_mouse: false }.into())\n\t\t\t.widget_instance(),\n\t\tIconButton::new(\"ZoomReset\", 24)\n\t\t\t.tooltip_label(\"Reset Tilt and Zoom to 100%\")\n\t\t\t.tooltip_shortcut(action_shortcut!(NavigationMessageDiscriminant::CanvasTiltResetAndZoomTo100Percent))\n\t\t\t.on_update(|_| NavigationMessage::CanvasTiltResetAndZoomTo100Percent.into())\n\t\t\t.disabled(ptz.tilt().abs() < 1e-4 && (ptz.zoom() - 1.).abs() < 1e-4)\n\t\t\t.widget_instance(),\n\t];\n\tif ptz.flip && !node_graph {\n\t\tlist.push(\n\t\t\tIconButton::new(\"Reverse\", 24)\n\t\t\t\t.tooltip_label(\"Unflip Canvas\")\n\t\t\t\t.tooltip_description(\"Flip the canvas back to its standard orientation.\")\n\t\t\t\t.tooltip_shortcut(action_shortcut!(NavigationMessageDiscriminant::CanvasFlip))\n\t\t\t\t.on_update(|_| NavigationMessage::CanvasFlip.into())\n\t\t\t\t.widget_instance(),\n\t\t);\n\t}\n\tlist.extend([\n\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\tNumberInput::new(Some(navigation_handler.snapped_zoom(ptz.zoom()) * 100.))\n\t\t\t.unit(\"%\")\n\t\t\t.min(0.000001)\n\t\t\t.max(1000000.)\n\t\t\t.tooltip_label(if node_graph { \"Node Graph Zoom\" } else { \"Canvas Zoom\" })\n\t\t\t.on_update(|number_input: &NumberInput| {\n\t\t\t\tNavigationMessage::CanvasZoomSet {\n\t\t\t\t\tzoom_factor: number_input.value.unwrap() / 100.,\n\t\t\t\t}\n\t\t\t\t.into()\n\t\t\t})\n\t\t\t.increment_behavior(NumberInputIncrementBehavior::Callback)\n\t\t\t.increment_callback_decrease(|_| NavigationMessage::CanvasZoomDecrease { center_on_mouse: false }.into())\n\t\t\t.increment_callback_increase(|_| NavigationMessage::CanvasZoomIncrease { center_on_mouse: false }.into())\n\t\t\t.widget_instance(),\n\t]);\n\tlist\n}\n\nimpl Iterator for ClickXRayIter<'_> {\n\ttype Item = LayerNodeIdentifier;\n\n\tfn next(&mut self) -> Option<Self::Item> {\n\t\t// While there are still layers in the layer tree\n\t\twhile let Some(layer) = self.next_layer.take() {\n\t\t\tlet XRayResult { clicked, use_children } = self.check_layer(layer);\n\t\t\tlet metadata = self.network_interface.document_metadata();\n\t\t\t// If we should use the children and also there is a child, that child is the next layer.\n\t\t\tself.next_layer = use_children.then(|| layer.first_child(metadata)).flatten();\n\n\t\t\t// If we aren't using children, iterate up the ancestors until there is a layer with a sibling\n\t\t\tfor ancestor in layer.ancestors(metadata) {\n\t\t\t\tif self.next_layer.is_some() {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\t// If there is a clipped area for this ancestor (that we are now exiting), discard it.\n\t\t\t\tif self.parent_targets.last().is_some_and(|(id, _)| *id == ancestor) {\n\t\t\t\t\tself.parent_targets.pop();\n\t\t\t\t}\n\t\t\t\tself.next_layer = ancestor.next_sibling(metadata)\n\t\t\t}\n\n\t\t\tif clicked {\n\t\t\t\treturn Some(layer);\n\t\t\t}\n\t\t}\n\t\tassert!(self.parent_targets.is_empty(), \"The parent targets should always be empty (since we have left all layers)\");\n\t\tNone\n\t}\n}\n\n#[cfg(test)]\nmod document_message_handler_tests {\n\tuse super::*;\n\tuse crate::test_utils::test_prelude::*;\n\n\t#[tokio::test]\n\tasync fn test_layer_selection_with_shift_and_ctrl() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\t// Three rectangle layers\n\t\teditor.drag_tool(ToolType::Rectangle, 0., 0., 100., 100., ModifierKeys::empty()).await;\n\t\teditor.drag_tool(ToolType::Rectangle, 50., 50., 150., 150., ModifierKeys::empty()).await;\n\t\teditor.drag_tool(ToolType::Rectangle, 100., 100., 200., 200., ModifierKeys::empty()).await;\n\n\t\tlet layers: Vec<_> = editor.active_document().metadata().all_layers().collect();\n\n\t\t// Case 1: Basic selection (no modifier)\n\t\teditor\n\t\t\t.handle_message(DocumentMessage::SelectLayer {\n\t\t\t\tid: layers[0].to_node(),\n\t\t\t\tctrl: false,\n\t\t\t\tshift: false,\n\t\t\t})\n\t\t\t.await;\n\t\t// Fresh document reference for verification\n\t\tlet document = editor.active_document();\n\t\tlet selected_nodes = document.network_interface.selected_nodes();\n\t\tassert_eq!(selected_nodes.selected_nodes_ref().len(), 1);\n\t\tassert!(selected_nodes.selected_layers_contains(layers[0], document.metadata()));\n\n\t\t// Case 2: Ctrl + click to add another layer\n\t\teditor\n\t\t\t.handle_message(DocumentMessage::SelectLayer {\n\t\t\t\tid: layers[2].to_node(),\n\t\t\t\tctrl: true,\n\t\t\t\tshift: false,\n\t\t\t})\n\t\t\t.await;\n\t\tlet document = editor.active_document();\n\t\tlet selected_nodes = document.network_interface.selected_nodes();\n\t\tassert_eq!(selected_nodes.selected_nodes_ref().len(), 2);\n\t\tassert!(selected_nodes.selected_layers_contains(layers[0], document.metadata()));\n\t\tassert!(selected_nodes.selected_layers_contains(layers[2], document.metadata()));\n\n\t\t// Case 3: Shift + click to select a range\n\t\teditor\n\t\t\t.handle_message(DocumentMessage::SelectLayer {\n\t\t\t\tid: layers[1].to_node(),\n\t\t\t\tctrl: false,\n\t\t\t\tshift: true,\n\t\t\t})\n\t\t\t.await;\n\t\tlet document = editor.active_document();\n\t\tlet selected_nodes = document.network_interface.selected_nodes();\n\t\t// We expect 2 layers to be selected (layers 1 and 2) - not 3\n\t\tassert_eq!(selected_nodes.selected_nodes_ref().len(), 2);\n\t\tassert!(!selected_nodes.selected_layers_contains(layers[0], document.metadata()));\n\t\tassert!(selected_nodes.selected_layers_contains(layers[1], document.metadata()));\n\t\tassert!(selected_nodes.selected_layers_contains(layers[2], document.metadata()));\n\n\t\t// Case 4: Ctrl + click to toggle selection (deselect)\n\t\teditor\n\t\t\t.handle_message(DocumentMessage::SelectLayer {\n\t\t\t\tid: layers[1].to_node(),\n\t\t\t\tctrl: true,\n\t\t\t\tshift: false,\n\t\t\t})\n\t\t\t.await;\n\n\t\t// Final fresh document reference\n\t\tlet document = editor.active_document();\n\t\tlet selected_nodes = document.network_interface.selected_nodes();\n\t\tassert_eq!(selected_nodes.selected_nodes_ref().len(), 1);\n\t\tassert!(!selected_nodes.selected_layers_contains(layers[0], document.metadata()));\n\t\tassert!(!selected_nodes.selected_layers_contains(layers[1], document.metadata()));\n\t\tassert!(selected_nodes.selected_layers_contains(layers[2], document.metadata()));\n\t}\n\n\t#[tokio::test]\n\tasync fn test_layer_rearrangement() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\t// Create three rectangle layers\n\t\teditor.drag_tool(ToolType::Rectangle, 0., 0., 100., 100., ModifierKeys::empty()).await;\n\t\teditor.drag_tool(ToolType::Rectangle, 50., 50., 150., 150., ModifierKeys::empty()).await;\n\t\teditor.drag_tool(ToolType::Rectangle, 100., 100., 200., 200., ModifierKeys::empty()).await;\n\n\t\t// Helper function to identify layers by bounds\n\t\tasync fn get_layer_by_bounds(editor: &mut EditorTestUtils, min_x: f64, min_y: f64) -> Option<LayerNodeIdentifier> {\n\t\t\tlet document = editor.active_document();\n\t\t\tfor layer in document.metadata().all_layers() {\n\t\t\t\tif let Some(bbox) = document.metadata().bounding_box_viewport(layer) {\n\t\t\t\t\tif (bbox[0].x - min_x).abs() < 1. && (bbox[0].y - min_y).abs() < 1. {\n\t\t\t\t\t\treturn Some(layer);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tNone\n\t\t}\n\n\t\tasync fn get_layer_index(editor: &mut EditorTestUtils, layer: LayerNodeIdentifier) -> Option<usize> {\n\t\t\tlet document = editor.active_document();\n\t\t\tlet parent = layer.parent(document.metadata())?;\n\t\t\tparent.children(document.metadata()).position(|child| child == layer)\n\t\t}\n\n\t\tlet layer_middle = get_layer_by_bounds(&mut editor, 50., 50.).await.unwrap();\n\t\tlet layer_top = get_layer_by_bounds(&mut editor, 100., 100.).await.unwrap();\n\n\t\tlet initial_index_top = get_layer_index(&mut editor, layer_top).await.unwrap();\n\t\tlet initial_index_middle = get_layer_index(&mut editor, layer_middle).await.unwrap();\n\n\t\t// Test 1: Lower the top layer\n\t\teditor.handle_message(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer_top.to_node()] }).await;\n\t\teditor.handle_message(DocumentMessage::SelectedLayersLower).await;\n\t\tlet new_index_top = get_layer_index(&mut editor, layer_top).await.unwrap();\n\t\tassert!(new_index_top > initial_index_top, \"Top layer should have moved down\");\n\n\t\t// Test 2: Raise the middle layer\n\t\teditor.handle_message(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer_middle.to_node()] }).await;\n\t\teditor.handle_message(DocumentMessage::SelectedLayersRaise).await;\n\t\tlet new_index_middle = get_layer_index(&mut editor, layer_middle).await.unwrap();\n\t\tassert!(new_index_middle < initial_index_middle, \"Middle layer should have moved up\");\n\t}\n\n\t#[tokio::test]\n\tasync fn test_move_folder_into_itself_doesnt_crash() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\n\t\t// Creating a parent folder\n\t\teditor.handle_message(DocumentMessage::CreateEmptyFolder).await;\n\t\tlet parent_folder = editor.active_document().metadata().all_layers().next().unwrap();\n\n\t\t// Creating a child folder inside the parent folder\n\t\teditor.handle_message(NodeGraphMessage::SelectedNodesSet { nodes: vec![parent_folder.to_node()] }).await;\n\t\teditor.handle_message(DocumentMessage::CreateEmptyFolder).await;\n\t\tlet child_folder = editor.active_document().metadata().all_layers().next().unwrap();\n\n\t\t// Attempt to move parent folder into child folder\n\t\teditor.handle_message(NodeGraphMessage::SelectedNodesSet { nodes: vec![parent_folder.to_node()] }).await;\n\t\teditor\n\t\t\t.handle_message(DocumentMessage::MoveSelectedLayersTo {\n\t\t\t\tparent: child_folder,\n\t\t\t\tinsert_index: 0,\n\t\t\t})\n\t\t\t.await;\n\n\t\t// The operation completed without crashing\n\t\t// Verifying application still functions by performing another operation\n\t\teditor.handle_message(DocumentMessage::CreateEmptyFolder).await;\n\t\tassert!(true, \"Application didn't crash after folder move operation\");\n\t}\n\t#[tokio::test]\n\tasync fn test_moving_folder_with_children() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\n\t\t// Creating two folders at root level\n\t\teditor.handle_message(DocumentMessage::CreateEmptyFolder).await;\n\t\teditor.handle_message(DocumentMessage::CreateEmptyFolder).await;\n\n\t\tlet folder1 = editor.active_document().metadata().all_layers().next().unwrap();\n\t\tlet folder2 = editor.active_document().metadata().all_layers().nth(1).unwrap();\n\n\t\teditor.drag_tool(ToolType::Rectangle, 0., 0., 100., 100., ModifierKeys::empty()).await;\n\t\tlet rect_layer = editor.active_document().metadata().all_layers().next().unwrap();\n\n\t\t// First move rectangle into folder1\n\t\teditor.handle_message(NodeGraphMessage::SelectedNodesSet { nodes: vec![rect_layer.to_node()] }).await;\n\t\teditor.handle_message(DocumentMessage::MoveSelectedLayersTo { parent: folder1, insert_index: 0 }).await;\n\n\t\t// Verifying rectagle is now in folder1\n\t\tlet rect_parent = rect_layer.parent(editor.active_document().metadata()).unwrap();\n\t\tassert_eq!(rect_parent, folder1, \"Rectangle should be inside folder1\");\n\n\t\t// Moving folder1 into folder2\n\t\teditor.handle_message(NodeGraphMessage::SelectedNodesSet { nodes: vec![folder1.to_node()] }).await;\n\t\teditor.handle_message(DocumentMessage::MoveSelectedLayersTo { parent: folder2, insert_index: 0 }).await;\n\n\t\t// Verifing hirarchy: folder2 > folder1 > rectangle\n\t\tlet document = editor.active_document();\n\t\tlet folder1_parent = folder1.parent(document.metadata()).unwrap();\n\t\tassert_eq!(folder1_parent, folder2, \"Folder1 should be inside folder2\");\n\n\t\t// Verifing rectangle moved with its parent\n\t\tlet rect_parent = rect_layer.parent(document.metadata()).unwrap();\n\t\tassert_eq!(rect_parent, folder1, \"Rectangle should still be inside folder1\");\n\n\t\tlet rect_grandparent = rect_parent.parent(document.metadata()).unwrap();\n\t\tassert_eq!(rect_grandparent, folder2, \"Rectangle's grandparent should be folder2\");\n\t}\n\n\t// TODO: Fix https://github.com/GraphiteEditor/Graphite/issues/2688 and reenable this as part of that fix.\n\t#[ignore]\n\t#[tokio::test]\n\tasync fn test_moving_layers_retains_transforms() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\n\t\teditor.handle_message(DocumentMessage::CreateEmptyFolder).await;\n\t\teditor.handle_message(DocumentMessage::CreateEmptyFolder).await;\n\n\t\tlet folder2 = editor.active_document().metadata().all_layers().next().unwrap();\n\t\tlet folder1 = editor.active_document().metadata().all_layers().nth(1).unwrap();\n\n\t\t// Applying transform to folder1 (translation)\n\t\teditor.handle_message(NodeGraphMessage::SelectedNodesSet { nodes: vec![folder1.to_node()] }).await;\n\t\teditor.handle_message(TransformLayerMessage::BeginGrab).await;\n\t\teditor.move_mouse(100., 50., ModifierKeys::empty(), MouseKeys::NONE).await;\n\t\teditor\n\t\t\t.handle_message(TransformLayerMessage::PointerMove {\n\t\t\t\tslow_key: Key::Shift,\n\t\t\t\tincrements_key: Key::Control,\n\t\t\t})\n\t\t\t.await;\n\t\teditor.handle_message(TransformLayerMessage::ApplyTransformOperation { final_transform: true }).await;\n\n\t\t// Applying different transform to folder2 (translation)\n\t\teditor.handle_message(NodeGraphMessage::SelectedNodesSet { nodes: vec![folder2.to_node()] }).await;\n\t\teditor.handle_message(TransformLayerMessage::BeginGrab).await;\n\t\teditor.move_mouse(200., 100., ModifierKeys::empty(), MouseKeys::NONE).await;\n\t\teditor\n\t\t\t.handle_message(TransformLayerMessage::PointerMove {\n\t\t\t\tslow_key: Key::Shift,\n\t\t\t\tincrements_key: Key::Control,\n\t\t\t})\n\t\t\t.await;\n\t\teditor.handle_message(TransformLayerMessage::ApplyTransformOperation { final_transform: true }).await;\n\n\t\t// Creating rectangle in folder1\n\t\teditor.handle_message(NodeGraphMessage::SelectedNodesSet { nodes: vec![folder1.to_node()] }).await;\n\t\teditor.drag_tool(ToolType::Rectangle, 0., 0., 100., 100., ModifierKeys::empty()).await;\n\t\tlet rect_layer = editor.active_document().metadata().all_layers().next().unwrap();\n\n\t\t// Moving the rectangle to folder1 to ensure it's inside\n\t\teditor.handle_message(NodeGraphMessage::SelectedNodesSet { nodes: vec![rect_layer.to_node()] }).await;\n\t\teditor.handle_message(DocumentMessage::MoveSelectedLayersTo { parent: folder1, insert_index: 0 }).await;\n\n\t\teditor.handle_message(TransformLayerMessage::BeginGrab).await;\n\t\teditor.move_mouse(50., 25., ModifierKeys::empty(), MouseKeys::NONE).await;\n\t\teditor\n\t\t\t.handle_message(TransformLayerMessage::PointerMove {\n\t\t\t\tslow_key: Key::Shift,\n\t\t\t\tincrements_key: Key::Control,\n\t\t\t})\n\t\t\t.await;\n\t\teditor.handle_message(TransformLayerMessage::ApplyTransformOperation { final_transform: true }).await;\n\n\t\t// Rectangle's viewport position before moving\n\t\tlet document = editor.active_document();\n\t\tlet rect_bbox_before = document.metadata().bounding_box_viewport(rect_layer).unwrap();\n\n\t\t// Moving rectangle from folder1 to folder2\n\t\teditor.handle_message(DocumentMessage::MoveSelectedLayersTo { parent: folder2, insert_index: 0 }).await;\n\n\t\t// Rectangle's viewport position after moving\n\t\tlet document = editor.active_document();\n\t\tlet rect_bbox_after = document.metadata().bounding_box_viewport(rect_layer).unwrap();\n\n\t\t// Verifing the rectangle maintains approximately the same position in viewport space\n\t\tlet before_center = (rect_bbox_before[0] + rect_bbox_before[1]) / 2.; // TODO: Should be: DVec2(0., -25.), regression (#2688) causes it to be: DVec2(100., 25.)\n\t\tlet after_center = (rect_bbox_after[0] + rect_bbox_after[1]) / 2.; // TODO:    Should be: DVec2(0., -25.), regression (#2688) causes it to be: DVec2(200., 75.)\n\t\tlet distance = before_center.distance(after_center); // TODO:                    Should be: 0.,               regression (#2688) causes it to be: 111.80339887498948\n\n\t\tassert!(\n\t\t\tdistance < 1.,\n\t\t\t\"Rectangle should maintain its viewport position after moving between transformed groups.\\n\\\n\t\t\tBefore: {before_center:?}\\n\\\n\t\t\tAfter:  {after_center:?}\\n\\\n\t\t\tDist:   {distance} (should be < 1)\"\n\t\t);\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs",
    "content": "use super::utility_types::TransformIn;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::portfolio::document::utility_types::network_interface::NodeTemplate;\nuse crate::messages::prelude::*;\nuse glam::{DAffine2, IVec2};\nuse graph_craft::document::NodeId;\nuse graphene_std::Artboard;\nuse graphene_std::brush::brush_stroke::BrushStroke;\nuse graphene_std::raster::BlendMode;\nuse graphene_std::raster_types::{CPU, Raster};\nuse graphene_std::subpath::Subpath;\nuse graphene_std::table::Table;\nuse graphene_std::text::{Font, TypesettingConfig};\nuse graphene_std::vector::PointId;\nuse graphene_std::vector::VectorModificationType;\nuse graphene_std::vector::style::{Fill, Stroke};\n\n#[impl_message(Message, DocumentMessage, GraphOperation)]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum GraphOperationMessage {\n\tFillSet {\n\t\tlayer: LayerNodeIdentifier,\n\t\tfill: Fill,\n\t},\n\tBlendingFillSet {\n\t\tlayer: LayerNodeIdentifier,\n\t\tfill: f64,\n\t},\n\tOpacitySet {\n\t\tlayer: LayerNodeIdentifier,\n\t\topacity: f64,\n\t},\n\tBlendModeSet {\n\t\tlayer: LayerNodeIdentifier,\n\t\tblend_mode: BlendMode,\n\t},\n\tClipModeToggle {\n\t\tlayer: LayerNodeIdentifier,\n\t},\n\tStrokeSet {\n\t\tlayer: LayerNodeIdentifier,\n\t\tstroke: Stroke,\n\t},\n\tTransformChange {\n\t\tlayer: LayerNodeIdentifier,\n\t\ttransform: DAffine2,\n\t\ttransform_in: TransformIn,\n\t\tskip_rerender: bool,\n\t},\n\tTransformSet {\n\t\tlayer: LayerNodeIdentifier,\n\t\ttransform: DAffine2,\n\t\ttransform_in: TransformIn,\n\t\tskip_rerender: bool,\n\t},\n\tVector {\n\t\tlayer: LayerNodeIdentifier,\n\t\tmodification_type: VectorModificationType,\n\t},\n\tBrush {\n\t\tlayer: LayerNodeIdentifier,\n\t\tstrokes: Vec<BrushStroke>,\n\t},\n\tSetUpstreamToChain {\n\t\tlayer: LayerNodeIdentifier,\n\t},\n\tNewArtboard {\n\t\tid: NodeId,\n\t\tartboard: Artboard,\n\t},\n\tNewBitmapLayer {\n\t\tid: NodeId,\n\t\timage_frame: Table<Raster<CPU>>,\n\t\tparent: LayerNodeIdentifier,\n\t\tinsert_index: usize,\n\t},\n\tNewBooleanOperationLayer {\n\t\tid: NodeId,\n\t\toperation: graphene_std::vector::misc::BooleanOperation,\n\t\tparent: LayerNodeIdentifier,\n\t\tinsert_index: usize,\n\t},\n\tNewCustomLayer {\n\t\tid: NodeId,\n\t\tnodes: Vec<(NodeId, NodeTemplate)>,\n\t\tparent: LayerNodeIdentifier,\n\t\tinsert_index: usize,\n\t},\n\tNewVectorLayer {\n\t\tid: NodeId,\n\t\tsubpaths: Vec<Subpath<PointId>>,\n\t\tparent: LayerNodeIdentifier,\n\t\tinsert_index: usize,\n\t},\n\tNewTextLayer {\n\t\tid: NodeId,\n\t\ttext: String,\n\t\tfont: Font,\n\t\ttypesetting: TypesettingConfig,\n\t\tparent: LayerNodeIdentifier,\n\t\tinsert_index: usize,\n\t},\n\tResizeArtboard {\n\t\tlayer: LayerNodeIdentifier,\n\t\tlocation: IVec2,\n\t\tdimensions: IVec2,\n\t},\n\tRemoveArtboards,\n\tNewSvg {\n\t\tid: NodeId,\n\t\tsvg: String,\n\t\ttransform: DAffine2,\n\t\tparent: LayerNodeIdentifier,\n\t\tinsert_index: usize,\n\t},\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs",
    "content": "use super::transform_utils;\nuse super::utility_types::ModifyInputsContext;\nuse crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeNetworkInterface, OutputConnector};\nuse crate::messages::portfolio::document::utility_types::nodes::CollapsedLayers;\nuse crate::messages::prelude::*;\nuse crate::messages::tool::common_functionality::graph_modification_utils::get_clip_mode;\nuse glam::{DAffine2, DVec2, IVec2};\nuse graph_craft::document::value::TaggedValue;\nuse graph_craft::document::{NodeId, NodeInput};\nuse graphene_std::Color;\nuse graphene_std::renderer::Quad;\nuse graphene_std::renderer::convert_usvg_path::convert_usvg_path;\nuse graphene_std::table::Table;\nuse graphene_std::text::{Font, TypesettingConfig};\nuse graphene_std::vector::style::{Fill, Gradient, GradientStop, GradientStops, GradientType, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin};\n\n#[derive(ExtractField)]\npub struct GraphOperationMessageContext<'a> {\n\tpub network_interface: &'a mut NodeNetworkInterface,\n\tpub collapsed: &'a mut CollapsedLayers,\n\tpub node_graph: &'a mut NodeGraphMessageHandler,\n}\n\n#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize, ExtractField)]\npub struct GraphOperationMessageHandler {}\n\n// GraphOperationMessageHandler always modified the document network. This is so changes to the layers panel will only affect the document network.\n// For changes to the selected network, use NodeGraphMessageHandler. No NodeGraphMessage's should be added here, since they will affect the selected nested network.\n#[message_handler_data]\nimpl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for GraphOperationMessageHandler {\n\tfn process_message(&mut self, message: GraphOperationMessage, responses: &mut VecDeque<Message>, context: GraphOperationMessageContext) {\n\t\tlet network_interface = context.network_interface;\n\n\t\tmatch message {\n\t\t\tGraphOperationMessage::FillSet { layer, fill } => {\n\t\t\t\tif let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {\n\t\t\t\t\tmodify_inputs.fill_set(fill);\n\t\t\t\t}\n\t\t\t}\n\t\t\tGraphOperationMessage::BlendingFillSet { layer, fill } => {\n\t\t\t\tif let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {\n\t\t\t\t\tmodify_inputs.blending_fill_set(fill);\n\t\t\t\t}\n\t\t\t}\n\t\t\tGraphOperationMessage::OpacitySet { layer, opacity } => {\n\t\t\t\tif let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {\n\t\t\t\t\tmodify_inputs.opacity_set(opacity);\n\t\t\t\t}\n\t\t\t}\n\t\t\tGraphOperationMessage::BlendModeSet { layer, blend_mode } => {\n\t\t\t\tif let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {\n\t\t\t\t\tmodify_inputs.blend_mode_set(blend_mode);\n\t\t\t\t}\n\t\t\t}\n\t\t\tGraphOperationMessage::ClipModeToggle { layer } => {\n\t\t\t\tlet clip_mode = get_clip_mode(layer, network_interface);\n\t\t\t\tif let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {\n\t\t\t\t\tmodify_inputs.clip_mode_toggle(clip_mode);\n\t\t\t\t}\n\t\t\t}\n\t\t\tGraphOperationMessage::StrokeSet { layer, stroke } => {\n\t\t\t\tif let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {\n\t\t\t\t\tmodify_inputs.stroke_set(stroke);\n\t\t\t\t}\n\t\t\t}\n\t\t\tGraphOperationMessage::TransformChange {\n\t\t\t\tlayer,\n\t\t\t\ttransform,\n\t\t\t\ttransform_in,\n\t\t\t\tskip_rerender,\n\t\t\t} => {\n\t\t\t\tlet parent_transform = network_interface.document_metadata().downstream_transform_to_viewport(layer);\n\t\t\t\tif let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {\n\t\t\t\t\tmodify_inputs.transform_change_with_parent(transform, transform_in, parent_transform, skip_rerender);\n\t\t\t\t}\n\t\t\t}\n\t\t\tGraphOperationMessage::TransformSet {\n\t\t\t\tlayer,\n\t\t\t\ttransform,\n\t\t\t\ttransform_in,\n\t\t\t\tskip_rerender,\n\t\t\t} => {\n\t\t\t\tif let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {\n\t\t\t\t\tmodify_inputs.transform_set(transform, transform_in, skip_rerender);\n\t\t\t\t}\n\t\t\t}\n\t\t\tGraphOperationMessage::Vector { layer, modification_type } => {\n\t\t\t\tif layer == LayerNodeIdentifier::ROOT_PARENT {\n\t\t\t\t\tlog::error!(\"Cannot run Vector on ROOT_PARENT\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {\n\t\t\t\t\tmodify_inputs.vector_modify(modification_type);\n\t\t\t\t}\n\t\t\t}\n\t\t\tGraphOperationMessage::Brush { layer, strokes } => {\n\t\t\t\tif let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {\n\t\t\t\t\tmodify_inputs.brush_modify(strokes);\n\t\t\t\t}\n\t\t\t}\n\t\t\tGraphOperationMessage::SetUpstreamToChain { layer } => {\n\t\t\t\tlet Some(OutputConnector::Node { node_id: first_chain_node, .. }) = network_interface.upstream_output_connector(&InputConnector::node(layer.to_node(), 1), &[]) else {\n\t\t\t\t\treturn;\n\t\t\t\t};\n\n\t\t\t\tnetwork_interface.force_set_upstream_to_chain(&first_chain_node, &[]);\n\t\t\t}\n\t\t\tGraphOperationMessage::NewArtboard { id, artboard } => {\n\t\t\t\tlet mut modify_inputs = ModifyInputsContext::new(network_interface, responses);\n\n\t\t\t\tlet artboard_location = artboard.location;\n\t\t\t\tlet artboard_layer = modify_inputs.create_artboard(id, artboard);\n\t\t\t\tnetwork_interface.move_layer_to_stack(artboard_layer, LayerNodeIdentifier::ROOT_PARENT, 0, &[]);\n\n\t\t\t\t// If there is a non artboard feeding into the primary input of the artboard, move it to the secondary input\n\t\t\t\tlet Some(artboard) = network_interface.document_network().nodes.get(&id) else {\n\t\t\t\t\tlog::error!(\"Artboard not created\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tlet document_metadata = network_interface.document_metadata();\n\n\t\t\t\tlet primary_input = artboard.inputs.first().expect(\"Artboard should have a primary input\").clone();\n\t\t\t\tif let NodeInput::Node { node_id, .. } = &primary_input {\n\t\t\t\t\tif network_interface.is_artboard(node_id, &[]) {\n\t\t\t\t\t\t// Nothing to do here: we have a stack full of artboards!\n\t\t\t\t\t} else if network_interface.is_layer(node_id, &[]) {\n\t\t\t\t\t\t// We have a stack of non-layer artboards.\n\t\t\t\t\t\tfor (insert_index, layer) in LayerNodeIdentifier::ROOT_PARENT.children(document_metadata).filter(|&layer| layer != artboard_layer).enumerate() {\n\t\t\t\t\t\t\t// Parent the layer to our new artboard (retaining ordering)\n\t\t\t\t\t\t\tresponses.add(NodeGraphMessage::MoveLayerToStack {\n\t\t\t\t\t\t\t\tlayer,\n\t\t\t\t\t\t\t\tparent: artboard_layer,\n\t\t\t\t\t\t\t\tinsert_index,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t// Apply a translation to prevent the content from shifting\n\t\t\t\t\t\t\tresponses.add(GraphOperationMessage::TransformChange {\n\t\t\t\t\t\t\t\tlayer,\n\t\t\t\t\t\t\t\ttransform: DAffine2::from_translation(-artboard_location.as_dvec2()),\n\t\t\t\t\t\t\t\ttransform_in: TransformIn::Local,\n\t\t\t\t\t\t\t\tskip_rerender: true,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Set the bottom input of the artboard back to artboard\n\t\t\t\t\t\tlet bottom_input = NodeInput::value(TaggedValue::Artboard(Table::new()), true);\n\t\t\t\t\t\tnetwork_interface.set_input(&InputConnector::node(artboard_layer.to_node(), 0), bottom_input, &[]);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// We have some non layers (e.g. just a rectangle node). We disconnect the bottom input and connect it to the left input.\n\t\t\t\t\t\tnetwork_interface.disconnect_input(&InputConnector::node(artboard_layer.to_node(), 0), &[]);\n\t\t\t\t\t\tnetwork_interface.set_input(&InputConnector::node(artboard_layer.to_node(), 1), primary_input, &[]);\n\n\t\t\t\t\t\t// Set the bottom input of the artboard back to artboard\n\t\t\t\t\t\tlet bottom_input = NodeInput::value(TaggedValue::Artboard(Table::new()), true);\n\t\t\t\t\t\tnetwork_interface.set_input(&InputConnector::node(artboard_layer.to_node(), 0), bottom_input, &[]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tresponses.add_front(NodeGraphMessage::SelectedNodesSet { nodes: vec![id] });\n\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t}\n\t\t\tGraphOperationMessage::NewBitmapLayer {\n\t\t\t\tid,\n\t\t\t\timage_frame,\n\t\t\t\tparent,\n\t\t\t\tinsert_index,\n\t\t\t} => {\n\t\t\t\tlet mut modify_inputs = ModifyInputsContext::new(network_interface, responses);\n\t\t\t\tlet layer = modify_inputs.create_layer(id);\n\t\t\t\tmodify_inputs.insert_image_data(image_frame, layer);\n\t\t\t\tnetwork_interface.move_layer_to_stack(layer, parent, insert_index, &[]);\n\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t}\n\t\t\tGraphOperationMessage::NewBooleanOperationLayer { id, operation, parent, insert_index } => {\n\t\t\t\tlet mut modify_inputs = ModifyInputsContext::new(network_interface, responses);\n\t\t\t\tlet layer = modify_inputs.create_layer(id);\n\t\t\t\tmodify_inputs.insert_boolean_data(operation, layer);\n\t\t\t\tnetwork_interface.move_layer_to_stack(layer, parent, insert_index, &[]);\n\t\t\t\tresponses.add(NodeGraphMessage::SetDisplayNameImpl {\n\t\t\t\t\tnode_id: id,\n\t\t\t\t\talias: \"Boolean Operation\".to_string(),\n\t\t\t\t});\n\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t}\n\t\t\tGraphOperationMessage::NewCustomLayer { id, nodes, parent, insert_index } => {\n\t\t\t\tlet mut modify_inputs = ModifyInputsContext::new(network_interface, responses);\n\t\t\t\tlet layer = modify_inputs.create_layer(id);\n\n\t\t\t\tif !nodes.is_empty() {\n\t\t\t\t\t// Add the nodes to the network\n\t\t\t\t\tlet new_ids: HashMap<_, _> = nodes.iter().map(|(id, _)| (*id, NodeId::new())).collect();\n\t\t\t\t\t// Since all the new nodes are already connected, just connect the input of the layer to first new node\n\t\t\t\t\tlet first_new_node_id = new_ids[&NodeId(0)];\n\t\t\t\t\tresponses.add(NodeGraphMessage::AddNodes { nodes, new_ids });\n\n\t\t\t\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\t\t\t\tinput_connector: InputConnector::node(layer.to_node(), 1),\n\t\t\t\t\t\tinput: NodeInput::node(first_new_node_id, 0),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\t// Move the layer and all nodes to the correct position in the network\n\t\t\t\tresponses.add(NodeGraphMessage::MoveLayerToStack { layer, parent, insert_index });\n\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t}\n\t\t\tGraphOperationMessage::NewVectorLayer { id, subpaths, parent, insert_index } => {\n\t\t\t\tlet mut modify_inputs = ModifyInputsContext::new(network_interface, responses);\n\t\t\t\tlet layer = modify_inputs.create_layer(id);\n\t\t\t\tmodify_inputs.insert_vector(subpaths, layer, true, true, true);\n\t\t\t\tnetwork_interface.move_layer_to_stack(layer, parent, insert_index, &[]);\n\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t}\n\t\t\tGraphOperationMessage::NewTextLayer {\n\t\t\t\tid,\n\t\t\t\ttext,\n\t\t\t\tfont,\n\t\t\t\ttypesetting,\n\t\t\t\tparent,\n\t\t\t\tinsert_index,\n\t\t\t} => {\n\t\t\t\tlet mut modify_inputs = ModifyInputsContext::new(network_interface, responses);\n\t\t\t\tlet layer = modify_inputs.create_layer(id);\n\t\t\t\tmodify_inputs.insert_text(text, font, typesetting, layer);\n\t\t\t\tnetwork_interface.move_layer_to_stack(layer, parent, insert_index, &[]);\n\t\t\t\tresponses.add(GraphOperationMessage::StrokeSet { layer, stroke: Stroke::default() });\n\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t}\n\t\t\tGraphOperationMessage::ResizeArtboard { layer, location, dimensions } => {\n\t\t\t\tif let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {\n\t\t\t\t\tmodify_inputs.resize_artboard(location, dimensions);\n\t\t\t\t}\n\t\t\t}\n\t\t\tGraphOperationMessage::RemoveArtboards => {\n\t\t\t\tif network_interface.all_artboards().is_empty() {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\t\t\t\tresponses.add(NodeGraphMessage::DeleteNodes {\n\t\t\t\t\tnode_ids: network_interface.all_artboards().iter().map(|layer_node| layer_node.to_node()).collect(),\n\t\t\t\t\tdelete_children: false,\n\t\t\t\t});\n\n\t\t\t\tlet mut artboard_data: HashMap<NodeId, ArtboardInfo> = HashMap::new();\n\n\t\t\t\t// Go through all artboards and create merge nodes\n\t\t\t\tfor artboard in network_interface.all_artboards() {\n\t\t\t\t\tlet node_id = NodeId::new();\n\t\t\t\t\tlet Some(document_node) = network_interface.document_network().nodes.get(&artboard.to_node()) else {\n\t\t\t\t\t\tlog::error!(\"Artboard not created\");\n\t\t\t\t\t\tresponses.add(DocumentMessage::AbortTransaction);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t};\n\n\t\t\t\t\tartboard_data.insert(\n\t\t\t\t\t\tartboard.to_node(),\n\t\t\t\t\t\tArtboardInfo {\n\t\t\t\t\t\t\tinput_node: NodeInput::node(document_node.inputs[1].as_node().unwrap_or_default(), 0),\n\t\t\t\t\t\t\toutput_nodes: network_interface\n\t\t\t\t\t\t\t\t.outward_wires(&[])\n\t\t\t\t\t\t\t\t.and_then(|outward_wires| outward_wires.get(&OutputConnector::node(artboard.to_node(), 0)))\n\t\t\t\t\t\t\t\t.cloned()\n\t\t\t\t\t\t\t\t.unwrap_or_default(),\n\t\t\t\t\t\t\tmerge_node: node_id,\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\n\t\t\t\t\tlet mut modify_inputs = ModifyInputsContext::new(network_interface, responses);\n\t\t\t\t\tmodify_inputs.create_layer(node_id);\n\n\t\t\t\t\tresponses.add(NodeGraphMessage::SetDisplayName {\n\t\t\t\t\t\tnode_id,\n\t\t\t\t\t\talias: network_interface.display_name(&artboard.to_node(), &[]),\n\t\t\t\t\t\tskip_adding_history_step: true,\n\t\t\t\t\t});\n\n\t\t\t\t\t// Shift node positions in the graph\n\t\t\t\t\tlet (x, y) = network_interface.position(&artboard.to_node(), &[]).unwrap_or_default().into();\n\t\t\t\t\tresponses.add(NodeGraphMessage::ShiftNodePosition { node_id, x, y });\n\t\t\t\t}\n\n\t\t\t\t// Go through all artboards and connect them to the merge nodes\n\t\t\t\tfor artboard in &artboard_data {\n\t\t\t\t\t// Modify downstream connections\n\t\t\t\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\t\t\t\tinput_connector: InputConnector::node(artboard.1.merge_node, 1),\n\t\t\t\t\t\tinput: NodeInput::node(artboard.1.input_node.as_node().unwrap_or_default(), 0),\n\t\t\t\t\t});\n\n\t\t\t\t\t// Modify upstream connections\n\t\t\t\t\tfor outward_wire in &artboard.1.output_nodes {\n\t\t\t\t\t\tlet input = NodeInput::node(artboard_data[artboard.0].merge_node, 0);\n\t\t\t\t\t\tlet input_connector = match artboard_data.get(&outward_wire.node_id().unwrap_or_default()) {\n\t\t\t\t\t\t\tSome(artboard_info) => InputConnector::node(artboard_info.merge_node, outward_wire.input_index()),\n\t\t\t\t\t\t\t_ => *outward_wire,\n\t\t\t\t\t\t};\n\t\t\t\t\t\tresponses.add(NodeGraphMessage::SetInput { input_connector, input });\n\t\t\t\t\t}\n\n\t\t\t\t\t// Apply a transformation to the newly created layers to match the original artboard position\n\t\t\t\t\tlet offset = network_interface\n\t\t\t\t\t\t.document_metadata()\n\t\t\t\t\t\t.bounding_box_document(LayerNodeIdentifier::new_unchecked(*artboard.0))\n\t\t\t\t\t\t.map(|p| p[0])\n\t\t\t\t\t\t.unwrap_or_default();\n\t\t\t\t\tresponses.add(GraphOperationMessage::TransformChange {\n\t\t\t\t\t\tlayer: LayerNodeIdentifier::new_unchecked(artboard.1.merge_node),\n\t\t\t\t\t\ttransform: DAffine2::from_translation(offset),\n\t\t\t\t\t\ttransform_in: TransformIn::Local,\n\t\t\t\t\t\tskip_rerender: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesUpdated);\n\t\t\t\tresponses.add(NodeGraphMessage::SendGraph);\n\t\t\t}\n\t\t\tGraphOperationMessage::NewSvg {\n\t\t\t\tid,\n\t\t\t\tsvg,\n\t\t\t\ttransform,\n\t\t\t\tparent,\n\t\t\t\tinsert_index,\n\t\t\t} => {\n\t\t\t\tlet tree = match usvg::Tree::from_str(&svg, &usvg::Options::default()) {\n\t\t\t\t\tOk(t) => t,\n\t\t\t\t\tErr(e) => {\n\t\t\t\t\t\tresponses.add(DialogMessage::DisplayDialogError {\n\t\t\t\t\t\t\ttitle: \"SVG parsing failed\".to_string(),\n\t\t\t\t\t\t\tdescription: e.to_string(),\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tlet mut modify_inputs = ModifyInputsContext::new(network_interface, responses);\n\n\t\t\t\tlet size = tree.size();\n\t\t\t\tlet offset_to_center = DVec2::new(size.width() as f64, size.height() as f64) / -2.;\n\t\t\t\tlet transform = transform * DAffine2::from_translation(offset_to_center);\n\n\t\t\t\tlet graphite_gradient_stops = extract_graphite_gradient_stops(&svg);\n\n\t\t\t\timport_usvg_node(\n\t\t\t\t\t&mut modify_inputs,\n\t\t\t\t\t&usvg::Node::Group(Box::new(tree.root().clone())),\n\t\t\t\t\ttransform,\n\t\t\t\t\tid,\n\t\t\t\t\tparent,\n\t\t\t\t\tinsert_index,\n\t\t\t\t\t&graphite_gradient_stops,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tfn actions(&self) -> ActionList {\n\t\tactions!(GraphOperationMessage;)\n\t}\n}\n\n#[derive(Debug, Clone)]\nstruct ArtboardInfo {\n\tinput_node: NodeInput,\n\toutput_nodes: Vec<InputConnector>,\n\tmerge_node: NodeId,\n}\n\nfn usvg_color(c: usvg::Color, a: f32) -> Color {\n\tColor::from_rgbaf32_unchecked(c.red as f32 / 255., c.green as f32 / 255., c.blue as f32 / 255., a)\n}\n\nfn usvg_transform(c: usvg::Transform) -> DAffine2 {\n\tDAffine2::from_cols_array(&[c.sx as f64, c.ky as f64, c.kx as f64, c.sy as f64, c.tx as f64, c.ty as f64])\n}\n\nconst GRAPHITE_NAMESPACE: &str = \"https://graphite.art\";\n\n/// Pre-parses the raw SVG XML to extract gradient stops that have `graphite:midpoint` attributes.\n/// Graphite exports gradients with midpoint curve data by writing interpolated approximation stops\n/// alongside the real stops. Real stops are tagged with `graphite:midpoint` attributes.\n/// Returns a map from gradient element `id` to `GradientStops` containing only the real stops.\nfn extract_graphite_gradient_stops(svg: &str) -> HashMap<String, GradientStops> {\n\tlet mut result = HashMap::new();\n\n\t// Quick check: if the SVG doesn't reference `graphite:midpoint` at all, skip parsing\n\tif !svg.contains(\"graphite:midpoint\") {\n\t\treturn result;\n\t}\n\n\tlet doc = match usvg::roxmltree::Document::parse(svg) {\n\t\tOk(doc) => doc,\n\t\tErr(_) => return result,\n\t};\n\n\tfor node in doc.descendants() {\n\t\tmatch node.tag_name().name() {\n\t\t\t\"linearGradient\" | \"radialGradient\" => {}\n\t\t\t_ => continue,\n\t\t}\n\n\t\tlet gradient_id = match node.attribute(\"id\") {\n\t\t\tSome(id) => id.to_string(),\n\t\t\tNone => continue,\n\t\t};\n\n\t\tlet mut real_stops = Vec::new();\n\t\tlet mut has_any_midpoint = false;\n\n\t\tfor child in node.children() {\n\t\t\tif child.tag_name().name() != \"stop\" {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet midpoint = child.attribute((GRAPHITE_NAMESPACE, \"midpoint\")).and_then(|v| v.parse::<f64>().ok());\n\n\t\t\tif let Some(midpoint) = midpoint {\n\t\t\t\thas_any_midpoint = true;\n\n\t\t\t\tlet offset = child.attribute(\"offset\").and_then(|v| v.parse::<f64>().ok()).unwrap_or(0.);\n\t\t\t\tlet opacity = child.attribute(\"stop-opacity\").and_then(|v| v.parse::<f32>().ok()).unwrap_or(1.);\n\t\t\t\tlet color = child.attribute(\"stop-color\").and_then(|hex| parse_hex_stop_color(hex, opacity)).unwrap_or(Color::BLACK);\n\n\t\t\t\treal_stops.push(GradientStop { position: offset, midpoint, color });\n\t\t\t}\n\t\t}\n\n\t\tif has_any_midpoint && !real_stops.is_empty() {\n\t\t\tresult.insert(gradient_id, GradientStops::new(real_stops));\n\t\t}\n\t}\n\n\tresult\n}\n\nfn parse_hex_stop_color(hex: &str, opacity: f32) -> Option<Color> {\n\tlet hex = hex.strip_prefix('#')?;\n\tif hex.len() != 6 {\n\t\treturn None;\n\t}\n\tlet r = u8::from_str_radix(&hex[0..2], 16).ok()? as f32 / 255.;\n\tlet g = u8::from_str_radix(&hex[2..4], 16).ok()? as f32 / 255.;\n\tlet b = u8::from_str_radix(&hex[4..6], 16).ok()? as f32 / 255.;\n\tSome(Color::from_rgbaf32_unchecked(r, g, b, opacity))\n}\n\nfn import_usvg_node(\n\tmodify_inputs: &mut ModifyInputsContext,\n\tnode: &usvg::Node,\n\ttransform: DAffine2,\n\tid: NodeId,\n\tparent: LayerNodeIdentifier,\n\tinsert_index: usize,\n\tgraphite_gradient_stops: &HashMap<String, GradientStops>,\n) {\n\tlet layer = modify_inputs.create_layer(id);\n\tmodify_inputs.network_interface.move_layer_to_stack(layer, parent, insert_index, &[]);\n\tmodify_inputs.layer_node = Some(layer);\n\tif let Some(upstream_layer) = layer.next_sibling(modify_inputs.network_interface.document_metadata()) {\n\t\tmodify_inputs.network_interface.shift_node(&upstream_layer.to_node(), IVec2::new(0, 3), &[]);\n\t}\n\tmatch node {\n\t\tusvg::Node::Group(group) => {\n\t\t\tfor child in group.children() {\n\t\t\t\timport_usvg_node(modify_inputs, child, transform, NodeId::new(), layer, 0, graphite_gradient_stops);\n\t\t\t}\n\t\t\tmodify_inputs.layer_node = Some(layer);\n\t\t}\n\t\tusvg::Node::Path(path) => {\n\t\t\tlet subpaths = convert_usvg_path(path);\n\t\t\tlet bounds = subpaths.iter().filter_map(|subpath| subpath.bounding_box()).reduce(Quad::combine_bounds).unwrap_or_default();\n\n\t\t\tmodify_inputs.insert_vector(subpaths, layer, true, path.fill().is_some(), path.stroke().is_some());\n\n\t\t\tif let Some(transform_node_id) = modify_inputs.existing_network_node_id(\"Transform\", true) {\n\t\t\t\ttransform_utils::update_transform(modify_inputs.network_interface, &transform_node_id, transform * usvg_transform(node.abs_transform()));\n\t\t\t}\n\n\t\t\tif let Some(fill) = path.fill() {\n\t\t\t\tlet bounds_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);\n\t\t\t\tapply_usvg_fill(fill, modify_inputs, bounds_transform, graphite_gradient_stops);\n\t\t\t}\n\t\t\tif let Some(stroke) = path.stroke() {\n\t\t\t\tapply_usvg_stroke(stroke, modify_inputs, transform * usvg_transform(node.abs_transform()));\n\t\t\t}\n\t\t}\n\t\tusvg::Node::Image(_image) => {\n\t\t\twarn!(\"Skip image\")\n\t\t}\n\t\tusvg::Node::Text(text) => {\n\t\t\tlet font = Font::new(graphene_std::consts::DEFAULT_FONT_FAMILY.to_string(), graphene_std::consts::DEFAULT_FONT_STYLE.to_string());\n\t\t\tmodify_inputs.insert_text(text.chunks().iter().map(|chunk| chunk.text()).collect(), font, TypesettingConfig::default(), layer);\n\t\t\tmodify_inputs.fill_set(Fill::Solid(Color::BLACK));\n\t\t}\n\t}\n}\n\nfn apply_usvg_stroke(stroke: &usvg::Stroke, modify_inputs: &mut ModifyInputsContext, transform: DAffine2) {\n\tif let usvg::Paint::Color(color) = &stroke.paint() {\n\t\tmodify_inputs.stroke_set(Stroke {\n\t\t\tcolor: Some(usvg_color(*color, stroke.opacity().get())),\n\t\t\tweight: stroke.width().get() as f64,\n\t\t\tdash_lengths: stroke.dasharray().as_ref().map(|lengths| lengths.iter().map(|&length| length as f64).collect()).unwrap_or_default(),\n\t\t\tdash_offset: stroke.dashoffset() as f64,\n\t\t\tcap: match stroke.linecap() {\n\t\t\t\tusvg::LineCap::Butt => StrokeCap::Butt,\n\t\t\t\tusvg::LineCap::Round => StrokeCap::Round,\n\t\t\t\tusvg::LineCap::Square => StrokeCap::Square,\n\t\t\t},\n\t\t\tjoin: match stroke.linejoin() {\n\t\t\t\tusvg::LineJoin::Miter => StrokeJoin::Miter,\n\t\t\t\tusvg::LineJoin::MiterClip => StrokeJoin::Miter,\n\t\t\t\tusvg::LineJoin::Round => StrokeJoin::Round,\n\t\t\t\tusvg::LineJoin::Bevel => StrokeJoin::Bevel,\n\t\t\t},\n\t\t\tjoin_miter_limit: stroke.miterlimit().get() as f64,\n\t\t\talign: StrokeAlign::Center,\n\t\t\tpaint_order: PaintOrder::StrokeAbove,\n\t\t\ttransform,\n\t\t})\n\t}\n}\n\nfn apply_usvg_fill(fill: &usvg::Fill, modify_inputs: &mut ModifyInputsContext, bounds_transform: DAffine2, graphite_gradient_stops: &HashMap<String, GradientStops>) {\n\tmodify_inputs.fill_set(match &fill.paint() {\n\t\tusvg::Paint::Color(color) => Fill::solid(usvg_color(*color, fill.opacity().get())),\n\t\tusvg::Paint::LinearGradient(linear) => {\n\t\t\tlet gradient_transform = usvg_transform(linear.transform());\n\t\t\tlet (start, end) = (DVec2::new(linear.x1() as f64, linear.y1() as f64), DVec2::new(linear.x2() as f64, linear.y2() as f64));\n\t\t\tlet (start, end) = (gradient_transform.transform_point2(start), gradient_transform.transform_point2(end));\n\t\t\tlet (start, end) = (bounds_transform.inverse().transform_point2(start), bounds_transform.inverse().transform_point2(end));\n\n\t\t\tlet gradient_type = GradientType::Linear;\n\n\t\t\tlet stops = match graphite_gradient_stops.get(linear.id()) {\n\t\t\t\tSome(graphite_stops) => graphite_stops.clone(),\n\t\t\t\tNone => {\n\t\t\t\t\tlet stops = linear.stops().iter().map(|stop| GradientStop {\n\t\t\t\t\t\tposition: stop.offset().get() as f64,\n\t\t\t\t\t\tmidpoint: 0.5,\n\t\t\t\t\t\tcolor: usvg_color(stop.color(), stop.opacity().get()),\n\t\t\t\t\t});\n\t\t\t\t\tGradientStops::new(stops)\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tFill::Gradient(Gradient { start, end, gradient_type, stops })\n\t\t}\n\t\tusvg::Paint::RadialGradient(radial) => {\n\t\t\tlet gradient_transform = usvg_transform(radial.transform());\n\t\t\tlet center = DVec2::new(radial.cx() as f64, radial.cy() as f64);\n\t\t\tlet edge = center + DVec2::X * radial.r().get() as f64;\n\t\t\tlet (start, end) = (gradient_transform.transform_point2(center), gradient_transform.transform_point2(edge));\n\t\t\tlet (start, end) = (bounds_transform.inverse().transform_point2(start), bounds_transform.inverse().transform_point2(end));\n\n\t\t\tlet gradient_type = GradientType::Radial;\n\n\t\t\tlet stops = match graphite_gradient_stops.get(radial.id()) {\n\t\t\t\tSome(graphite_stops) => graphite_stops.clone(),\n\t\t\t\tNone => {\n\t\t\t\t\tlet stops = radial.stops().iter().map(|stop| GradientStop {\n\t\t\t\t\t\tposition: stop.offset().get() as f64,\n\t\t\t\t\t\tmidpoint: 0.5,\n\t\t\t\t\t\tcolor: usvg_color(stop.color(), stop.opacity().get()),\n\t\t\t\t\t});\n\t\t\t\t\tGradientStops::new(stops)\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tFill::Gradient(Gradient { start, end, gradient_type, stops })\n\t\t}\n\t\tusvg::Paint::Pattern(_) => {\n\t\t\twarn!(\"SVG patterns are not currently supported\");\n\t\t\treturn;\n\t\t}\n\t});\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/graph_operation/mod.rs",
    "content": "mod graph_operation_message;\npub mod graph_operation_message_handler;\npub mod transform_utils;\npub mod utility_types;\n\n#[doc(inline)]\npub use graph_operation_message::*;\n#[doc(inline)]\npub use graph_operation_message_handler::*;\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/graph_operation/transform_utils.rs",
    "content": "use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeNetworkInterface};\nuse glam::{DAffine2, DVec2};\nuse graph_craft::document::value::TaggedValue;\nuse graph_craft::document::{NodeId, NodeInput};\nuse graphene_std::subpath::Subpath;\nuse graphene_std::vector::PointId;\n\n/// Convert an affine transform into the tuple `(scale, angle, translation, shear)` assuming `shear.y = 0`.\npub fn compute_scale_angle_translation_shear(transform: DAffine2) -> (DVec2, f64, DVec2, DVec2) {\n\tlet x_axis = transform.matrix2.x_axis;\n\tlet y_axis = transform.matrix2.y_axis;\n\n\t// Assuming there is no vertical shear\n\tlet angle = x_axis.y.atan2(x_axis.x);\n\tlet (sin, cos) = angle.sin_cos();\n\tlet scale_x = if cos.abs() > 1e-10 { x_axis.x / cos } else { x_axis.y / sin };\n\n\tlet mut shear_x = (sin * y_axis.y + cos * y_axis.x) / (sin * sin * scale_x + cos * cos * scale_x);\n\tif !shear_x.is_finite() {\n\t\tshear_x = 0.;\n\t}\n\tlet scale_y = if cos.abs() > 1e-10 {\n\t\t(y_axis.y - scale_x * sin * shear_x) / cos\n\t} else {\n\t\t(scale_x * cos * shear_x - y_axis.x) / sin\n\t};\n\tlet translation = transform.translation;\n\tlet scale = DVec2::new(scale_x, scale_y);\n\tlet shear = DVec2::new(shear_x, 0.);\n\t(scale, angle, translation, shear)\n}\n\n/// Update the inputs of the transform node to match a new transform\npub fn update_transform(network_interface: &mut NodeNetworkInterface, node_id: &NodeId, transform: DAffine2) {\n\tlet (scale, rotation, translation, shear) = compute_scale_angle_translation_shear(transform);\n\n\tlet rotation = rotation.to_degrees();\n\tlet shear = DVec2::new(shear.x.atan().to_degrees(), shear.y.atan().to_degrees());\n\n\tnetwork_interface.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::DVec2(translation), false), &[]);\n\tnetwork_interface.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::F64(rotation), false), &[]);\n\tnetwork_interface.set_input(&InputConnector::node(*node_id, 3), NodeInput::value(TaggedValue::DVec2(scale), false), &[]);\n\tnetwork_interface.set_input(&InputConnector::node(*node_id, 4), NodeInput::value(TaggedValue::DVec2(shear), false), &[]);\n}\n\n// TODO: This should be extracted from the graph at the location of the transform node.\npub struct LayerBounds {\n\tpub bounds: [DVec2; 2],\n\tpub bounds_transform: DAffine2,\n\tpub layer_transform: DAffine2,\n}\n\nimpl LayerBounds {\n\t/// Extract the layer bounds and their transform for a layer.\n\tpub fn new(\n\t\tmetadata: &crate::messages::portfolio::document::utility_types::document_metadata::DocumentMetadata,\n\t\tlayer: crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier,\n\t) -> Self {\n\t\tSelf {\n\t\t\tbounds: metadata.nonzero_bounding_box(layer),\n\t\t\tbounds_transform: DAffine2::IDENTITY,\n\t\t\tlayer_transform: metadata.transform_to_document(layer),\n\t\t}\n\t}\n\n\tpub fn layerspace_pivot(&self, normalized_pivot: DVec2) -> DVec2 {\n\t\tself.bounds[0] + (self.bounds[1] - self.bounds[0]) * normalized_pivot\n\t}\n\n\tpub fn local_pivot(&self, normalized_pivot: DVec2) -> DVec2 {\n\t\tself.bounds_transform.transform_point2(self.layerspace_pivot(normalized_pivot))\n\t}\n}\n\n/// Get the current affine transform from the transform node's inputs\npub fn get_current_transform(inputs: &[NodeInput]) -> DAffine2 {\n\tlet translation = if let Some(&TaggedValue::DVec2(translation)) = inputs[1].as_value() {\n\t\ttranslation\n\t} else {\n\t\tDVec2::ZERO\n\t};\n\tlet rotation = if let Some(&TaggedValue::F64(rotation)) = inputs[2].as_value() { rotation } else { 0. };\n\tlet scale = if let Some(&TaggedValue::DVec2(scale)) = inputs[3].as_value() { scale } else { DVec2::ONE };\n\tlet shear = if let Some(&TaggedValue::DVec2(shear)) = inputs[4].as_value() { shear } else { DVec2::ZERO };\n\n\tlet rotation = rotation.to_radians();\n\tlet shear = DVec2::new(shear.x.to_radians().tan(), shear.y.to_radians().tan());\n\n\tDAffine2::from_scale_angle_translation(scale, rotation, translation) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.])\n}\n\n/// Extract the current normalized pivot from the layer\npub fn get_current_normalized_pivot(inputs: &[NodeInput]) -> DVec2 {\n\tif let Some(&TaggedValue::DVec2(pivot)) = inputs[5].as_value() { pivot } else { DVec2::splat(0.5) }\n}\n\n/// Expand a bounds to avoid div zero errors\nfn clamp_bounds(bounds_min: DVec2, mut bounds_max: DVec2) -> [DVec2; 2] {\n\tlet bounds_size = bounds_max - bounds_min;\n\tif bounds_size.x < 1e-10 {\n\t\tbounds_max.x = bounds_min.x + 1.;\n\t}\n\tif bounds_size.y < 1e-10 {\n\t\tbounds_max.y = bounds_min.y + 1.;\n\t}\n\t[bounds_min, bounds_max]\n}\n/// Returns corners of all subpaths\nfn subpath_bounds(subpaths: &[Subpath<PointId>]) -> [DVec2; 2] {\n\tsubpaths\n\t\t.iter()\n\t\t.filter_map(|subpath| subpath.bounding_box())\n\t\t.reduce(|b1, b2| [b1[0].min(b2[0]), b1[1].max(b2[1])])\n\t\t.unwrap_or_default()\n}\n\n/// Returns corners of all subpaths (but expanded to avoid division-by-zero errors)\npub fn nonzero_subpath_bounds(subpaths: &[Subpath<PointId>]) -> [DVec2; 2] {\n\tlet [bounds_min, bounds_max] = subpath_bounds(subpaths);\n\tclamp_bounds(bounds_min, bounds_max)\n}\n\n#[cfg(test)]\nmod tests {\n\tuse super::*;\n\t/// ![](https://files.keavon.com/-/OptimisticSpotlessTinamou/capture.png)\n\t///\n\t/// Source:\n\t/// ```tex\n\t/// \\begin{bmatrix}\n\t/// S_{x}\\cos(\\theta)-S_{y}\\sin(\\theta)H_{y} & S_{x}\\cos(\\theta)H_{x}-S_{y}\\sin(\\theta) & T_{x}\\\\\n\t/// S_{x}\\sin(\\theta)+S_{y}\\cos(\\theta)H_{y} & S_{x}\\sin(\\theta)H_{x}+S_{y}\\cos(\\theta) & T_{y}\\\\\n\t/// 0 & 0 & 1\n\t/// \\end{bmatrix}\n\t/// ```\n\t#[test]\n\tfn derive_transform() {\n\t\tfor shear_x in -10..=10 {\n\t\t\tlet shear_x = (shear_x as f64) / 2.;\n\t\t\tfor angle in (0..=360).step_by(15) {\n\t\t\t\tlet angle = (angle as f64).to_radians();\n\t\t\t\tfor scale_x in 1..10 {\n\t\t\t\t\tlet scale_x = (scale_x as f64) / 5.;\n\t\t\t\t\tfor scale_y in 1..10 {\n\t\t\t\t\t\tlet scale_y = (scale_y as f64) / 5.;\n\n\t\t\t\t\t\tlet shear = DVec2::new(shear_x, 0.);\n\t\t\t\t\t\tlet scale = DVec2::new(scale_x, scale_y);\n\t\t\t\t\t\tlet translate = DVec2::new(5666., 644.);\n\n\t\t\t\t\t\tlet original_transform = DAffine2::from_cols(\n\t\t\t\t\t\t\tDVec2::new(scale.x * angle.cos() - scale.y * angle.sin() * shear.y, scale.x * angle.sin() + scale.y * angle.cos() * shear.y),\n\t\t\t\t\t\t\tDVec2::new(scale.x * angle.cos() * shear.x - scale.y * angle.sin(), scale.x * angle.sin() * shear.x + scale.y * angle.cos()),\n\t\t\t\t\t\t\ttranslate,\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tlet (new_scale, new_angle, new_translation, new_shear) = compute_scale_angle_translation_shear(original_transform);\n\t\t\t\t\t\tlet new_transform = DAffine2::from_scale_angle_translation(new_scale, new_angle, new_translation) * DAffine2::from_cols_array(&[1., new_shear.y, new_shear.x, 1., 0., 0.]);\n\n\t\t\t\t\t\tassert!(\n\t\t\t\t\t\t\tnew_transform.abs_diff_eq(original_transform, 1e-10),\n\t\t\t\t\t\t\t\"original_transform {original_transform} new_transform {new_transform} / scale {scale} new_scale {new_scale} / angle {angle} new_angle {new_angle} / shear {shear} / new_shear {new_shear}\",\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/graph_operation/utility_types.rs",
    "content": "use super::transform_utils;\nuse crate::messages::portfolio::document::node_graph::document_node_definitions::{DefinitionIdentifier, resolve_document_node_type, resolve_network_node_type, resolve_proto_node_type};\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::portfolio::document::utility_types::network_interface::{self, InputConnector, NodeNetworkInterface, OutputConnector};\nuse crate::messages::prelude::*;\nuse glam::{DAffine2, IVec2};\nuse graph_craft::document::value::TaggedValue;\nuse graph_craft::document::{NodeId, NodeInput};\nuse graph_craft::{ProtoNodeIdentifier, concrete};\nuse graphene_std::Artboard;\nuse graphene_std::brush::brush_stroke::BrushStroke;\nuse graphene_std::raster::BlendMode;\nuse graphene_std::raster_types::{CPU, Raster};\nuse graphene_std::subpath::Subpath;\nuse graphene_std::table::Table;\nuse graphene_std::text::{Font, TypesettingConfig};\nuse graphene_std::vector::Vector;\nuse graphene_std::vector::style::{Fill, Stroke};\nuse graphene_std::vector::{PointId, VectorModificationType};\nuse graphene_std::{Graphic, NodeInputDecleration};\n\n#[derive(PartialEq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]\npub enum TransformIn {\n\tLocal,\n\tScope { scope: DAffine2 },\n\tViewport,\n}\n\n// This struct is helpful to prevent passing the same arguments to multiple functions\n// Should only be used by GraphOperationMessage, since it only affects the document network.\npub struct ModifyInputsContext<'a> {\n\tpub network_interface: &'a mut NodeNetworkInterface,\n\tpub responses: &'a mut VecDeque<Message>,\n\t// Cannot be LayerNodeIdentifier::ROOT_PARENT\n\tpub layer_node: Option<LayerNodeIdentifier>,\n}\n\nimpl<'a> ModifyInputsContext<'a> {\n\t/// Get the node network from the document\n\tpub fn new(network_interface: &'a mut NodeNetworkInterface, responses: &'a mut VecDeque<Message>) -> Self {\n\t\tSelf {\n\t\t\tnetwork_interface,\n\t\t\tresponses,\n\t\t\tlayer_node: None,\n\t\t}\n\t}\n\n\tpub fn new_with_layer(layer: LayerNodeIdentifier, network_interface: &'a mut NodeNetworkInterface, responses: &'a mut VecDeque<Message>) -> Option<Self> {\n\t\tif layer == LayerNodeIdentifier::ROOT_PARENT {\n\t\t\tlog::error!(\"LayerNodeIdentifier::ROOT_PARENT should not be used in ModifyInputsContext::new_with_layer\");\n\t\t\treturn None;\n\t\t}\n\t\tlet mut document = Self::new(network_interface, responses);\n\t\tdocument.layer_node = Some(layer);\n\t\tSome(document)\n\t}\n\n\t/// Starts at any folder, or the output, and skips layer nodes based on insert_index. Non layer nodes are always skipped. Returns the post node InputConnector and pre node OutputConnector\n\t/// Non layer nodes directly upstream of a layer are treated as part of that layer. See insert_index == 2 in the diagram\n\t///       -----> Post node\n\t///      |      if insert_index == 0, return (Post node, Some(Layer1))\n\t/// -> Layer1\n\t///      ↑      if insert_index == 1, return (Layer1, Some(Layer2))\n\t/// -> Layer2\n\t///      ↑\n\t/// -> NonLayerNode\n\t///      ↑      if insert_index == 2, return (NonLayerNode, Some(Layer3))\n\t/// -> Layer3\n\t///             if insert_index == 3, return (Layer3, None)\n\tpub fn get_post_node_with_index(network_interface: &NodeNetworkInterface, parent: LayerNodeIdentifier, insert_index: usize) -> InputConnector {\n\t\tlet mut post_node_input_connector = if parent == LayerNodeIdentifier::ROOT_PARENT {\n\t\t\tInputConnector::Export(0)\n\t\t} else {\n\t\t\tInputConnector::node(parent.to_node(), 1)\n\t\t};\n\t\t// Skip layers based on skip_layer_nodes, which inserts the new layer at a certain index of the layer stack.\n\t\tlet mut current_index = 0;\n\n\t\t// Set the post node to the layer node at insert_index\n\t\tloop {\n\t\t\tif current_index == insert_index {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tlet next_node_in_stack_id = network_interface\n\t\t\t\t.input_from_connector(&post_node_input_connector, &[])\n\t\t\t\t.and_then(|input_from_connector| if let NodeInput::Node { node_id, .. } = input_from_connector { Some(node_id) } else { None });\n\n\t\t\tif let Some(next_node_in_stack_id) = next_node_in_stack_id {\n\t\t\t\t// Only increment index for layer nodes\n\t\t\t\tif network_interface.is_layer(next_node_in_stack_id, &[]) {\n\t\t\t\t\tcurrent_index += 1;\n\t\t\t\t}\n\t\t\t\t// Input as a sibling to the Layer node above\n\t\t\t\tpost_node_input_connector = InputConnector::node(*next_node_in_stack_id, 0);\n\t\t\t} else {\n\t\t\t\tlog::error!(\"Error getting post node: insert_index out of bounds\");\n\t\t\t\tbreak;\n\t\t\t};\n\t\t}\n\n\t\tlet layer_input_connector = post_node_input_connector;\n\n\t\t// Sink post_node down to the end of the non layer chain that feeds into post_node, such that pre_node is the layer node at insert_index + 1, or None if insert_index is the last layer\n\t\tloop {\n\t\t\tlet pre_node_output_connector = network_interface.upstream_output_connector(&post_node_input_connector, &[]);\n\n\t\t\tmatch pre_node_output_connector {\n\t\t\t\tSome(OutputConnector::Node { node_id: pre_node_id, .. }) if !network_interface.is_layer(&pre_node_id, &[]) => {\n\t\t\t\t\t// Update post_node_input_connector for the next iteration\n\t\t\t\t\tpost_node_input_connector = InputConnector::node(pre_node_id, 0);\n\t\t\t\t\t// Insert directly under layer if moving to the end of a layer stack that ends with a non layer node that does not have an exposed primary input\n\t\t\t\t\tlet primary_is_exposed = network_interface.input_from_connector(&post_node_input_connector, &[]).is_some_and(|input| input.is_exposed());\n\t\t\t\t\tif !primary_is_exposed {\n\t\t\t\t\t\treturn layer_input_connector;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t_ => break, // Break if pre_node_output_connector is None or if pre_node_id is a layer\n\t\t\t}\n\t\t}\n\n\t\tpost_node_input_connector\n\t}\n\n\t/// Creates a new layer and adds it to the document network. network_interface.move_layer_to_stack should be called after\n\tpub fn create_layer(&mut self, new_id: NodeId) -> LayerNodeIdentifier {\n\t\tlet new_merge_node = resolve_network_node_type(\"Merge\").expect(\"Merge node\").default_node_template();\n\t\tself.network_interface.insert_node(new_id, new_merge_node, &[]);\n\t\tLayerNodeIdentifier::new(new_id, self.network_interface)\n\t}\n\n\t/// Creates an artboard as the primary export for the document network\n\tpub fn create_artboard(&mut self, new_id: NodeId, artboard: Artboard) -> LayerNodeIdentifier {\n\t\tlet artboard_node_template = resolve_network_node_type(\"Artboard\").expect(\"Node\").node_template_input_override([\n\t\t\tSome(NodeInput::value(TaggedValue::Artboard(Default::default()), true)),\n\t\t\tSome(NodeInput::value(TaggedValue::Graphic(Default::default()), true)),\n\t\t\tSome(NodeInput::value(TaggedValue::DVec2(artboard.location.into()), false)),\n\t\t\tSome(NodeInput::value(TaggedValue::DVec2(artboard.dimensions.into()), false)),\n\t\t\tSome(NodeInput::value(TaggedValue::Color(Table::new_from_element(artboard.background)), false)),\n\t\t\tSome(NodeInput::value(TaggedValue::Bool(artboard.clip), false)),\n\t\t]);\n\t\tself.network_interface.insert_node(new_id, artboard_node_template, &[]);\n\t\tLayerNodeIdentifier::new(new_id, self.network_interface)\n\t}\n\n\tpub fn insert_boolean_data(&mut self, operation: graphene_std::vector::misc::BooleanOperation, layer: LayerNodeIdentifier) {\n\t\tlet boolean = resolve_network_node_type(\"Boolean Operation\").expect(\"Boolean node does not exist\").node_template_input_override([\n\t\t\tSome(NodeInput::value(TaggedValue::Graphic(Default::default()), true)),\n\t\t\tSome(NodeInput::value(TaggedValue::BooleanOperation(operation), false)),\n\t\t]);\n\n\t\tlet boolean_id = NodeId::new();\n\t\tself.network_interface.insert_node(boolean_id, boolean, &[]);\n\t\tself.network_interface.move_node_to_chain_start(&boolean_id, layer, &[]);\n\t}\n\n\tpub fn insert_vector(&mut self, subpaths: Vec<Subpath<PointId>>, layer: LayerNodeIdentifier, include_transform: bool, include_fill: bool, include_stroke: bool) {\n\t\tlet vector = Table::new_from_element(Vector::from_subpaths(subpaths, true));\n\n\t\tlet shape = resolve_network_node_type(\"Path\")\n\t\t\t.expect(\"Path node does not exist\")\n\t\t\t.node_template_input_override([Some(NodeInput::value(TaggedValue::Vector(vector), false))]);\n\t\tlet shape_id = NodeId::new();\n\t\tself.network_interface.insert_node(shape_id, shape, &[]);\n\t\tself.network_interface.move_node_to_chain_start(&shape_id, layer, &[]);\n\n\t\tif include_transform {\n\t\t\tlet transform = resolve_network_node_type(\"Transform\").expect(\"Transform node does not exist\").default_node_template();\n\t\t\tlet transform_id = NodeId::new();\n\t\t\tself.network_interface.insert_node(transform_id, transform, &[]);\n\t\t\tself.network_interface.move_node_to_chain_start(&transform_id, layer, &[]);\n\t\t}\n\n\t\tif include_stroke {\n\t\t\tlet stroke = resolve_proto_node_type(graphene_std::vector_nodes::stroke::IDENTIFIER)\n\t\t\t\t.expect(\"Stroke node does not exist\")\n\t\t\t\t.default_node_template();\n\t\t\tlet stroke_id = NodeId::new();\n\t\t\tself.network_interface.insert_node(stroke_id, stroke, &[]);\n\t\t\tself.network_interface.move_node_to_chain_start(&stroke_id, layer, &[]);\n\t\t}\n\n\t\tif include_fill {\n\t\t\tlet fill = resolve_proto_node_type(graphene_std::vector_nodes::fill::IDENTIFIER)\n\t\t\t\t.expect(\"Fill node does not exist\")\n\t\t\t\t.default_node_template();\n\t\t\tlet fill_id = NodeId::new();\n\t\t\tself.network_interface.insert_node(fill_id, fill, &[]);\n\t\t\tself.network_interface.move_node_to_chain_start(&fill_id, layer, &[]);\n\t\t}\n\t}\n\n\tpub fn insert_text(&mut self, text: String, font: Font, typesetting: TypesettingConfig, layer: LayerNodeIdentifier) {\n\t\tlet text = resolve_proto_node_type(graphene_std::text::text::IDENTIFIER)\n\t\t\t.expect(\"Text node does not exist\")\n\t\t\t.node_template_input_override([\n\t\t\t\tSome(NodeInput::scope(\"editor-api\")),\n\t\t\t\tSome(NodeInput::value(TaggedValue::String(text), false)),\n\t\t\t\tSome(NodeInput::value(TaggedValue::Font(font), false)),\n\t\t\t\tSome(NodeInput::value(TaggedValue::F64(typesetting.font_size), false)),\n\t\t\t\tSome(NodeInput::value(TaggedValue::F64(typesetting.line_height_ratio), false)),\n\t\t\t\tSome(NodeInput::value(TaggedValue::F64(typesetting.character_spacing), false)),\n\t\t\t\tSome(NodeInput::value(TaggedValue::Bool(typesetting.max_width.is_some()), false)),\n\t\t\t\tSome(NodeInput::value(TaggedValue::F64(typesetting.max_width.unwrap_or(100.)), false)),\n\t\t\t\tSome(NodeInput::value(TaggedValue::Bool(typesetting.max_height.is_some()), false)),\n\t\t\t\tSome(NodeInput::value(TaggedValue::F64(typesetting.max_height.unwrap_or(100.)), false)),\n\t\t\t\tSome(NodeInput::value(TaggedValue::F64(typesetting.tilt), false)),\n\t\t\t\tSome(NodeInput::value(TaggedValue::TextAlign(typesetting.align), false)),\n\t\t\t]);\n\t\tlet transform = resolve_network_node_type(\"Transform\").expect(\"Transform node does not exist\").default_node_template();\n\t\tlet stroke = resolve_proto_node_type(graphene_std::vector_nodes::stroke::IDENTIFIER)\n\t\t\t.expect(\"Stroke node does not exist\")\n\t\t\t.default_node_template();\n\t\tlet fill = resolve_proto_node_type(graphene_std::vector_nodes::fill::IDENTIFIER)\n\t\t\t.expect(\"Fill node does not exist\")\n\t\t\t.default_node_template();\n\n\t\tlet text_id = NodeId::new();\n\t\tself.network_interface.insert_node(text_id, text, &[]);\n\t\tself.network_interface.move_node_to_chain_start(&text_id, layer, &[]);\n\n\t\tlet transform_id = NodeId::new();\n\t\tself.network_interface.insert_node(transform_id, transform, &[]);\n\t\tself.network_interface.move_node_to_chain_start(&transform_id, layer, &[]);\n\n\t\tlet stroke_id = NodeId::new();\n\t\tself.network_interface.insert_node(stroke_id, stroke, &[]);\n\t\tself.network_interface.move_node_to_chain_start(&stroke_id, layer, &[]);\n\n\t\tlet fill_id = NodeId::new();\n\t\tself.network_interface.insert_node(fill_id, fill, &[]);\n\t\tself.network_interface.move_node_to_chain_start(&fill_id, layer, &[]);\n\t}\n\n\tpub fn insert_image_data(&mut self, image_frame: Table<Raster<CPU>>, layer: LayerNodeIdentifier) {\n\t\tlet transform = resolve_network_node_type(\"Transform\").expect(\"Transform node does not exist\").default_node_template();\n\t\tlet image = resolve_proto_node_type(graphene_std::raster_nodes::std_nodes::image_value::IDENTIFIER)\n\t\t\t.expect(\"ImageValue node does not exist\")\n\t\t\t.node_template_input_override([Some(NodeInput::value(TaggedValue::None, false)), Some(NodeInput::value(TaggedValue::Raster(image_frame), false))]);\n\n\t\tlet image_id = NodeId::new();\n\t\tself.network_interface.insert_node(image_id, image, &[]);\n\t\tself.network_interface.move_node_to_chain_start(&image_id, layer, &[]);\n\n\t\tlet transform_id = NodeId::new();\n\t\tself.network_interface.insert_node(transform_id, transform, &[]);\n\t\tself.network_interface.move_node_to_chain_start(&transform_id, layer, &[]);\n\t}\n\n\tfn get_output_layer(&self) -> Option<LayerNodeIdentifier> {\n\t\tself.layer_node.or_else(|| {\n\t\t\tlet export_node = self.network_interface.document_network().exports.first().and_then(|export| export.as_node())?;\n\t\t\tif self.network_interface.is_layer(&export_node, &[]) {\n\t\t\t\tSome(LayerNodeIdentifier::new(export_node, self.network_interface))\n\t\t\t} else {\n\t\t\t\tNone\n\t\t\t}\n\t\t})\n\t}\n\n\t/// Gets the node id of a network node with a specific reference that is upstream from the layer node, and optionally creates it if it does not exist.\n\tpub fn existing_network_node_id(&mut self, reference: &str, create_if_nonexistent: bool) -> Option<NodeId> {\n\t\tself.existing_node_id(&DefinitionIdentifier::Network(reference.into()), create_if_nonexistent)\n\t}\n\n\t/// Gets the node id of a proto node with a specific reference that is upstream from the layer node, and optionally creates it if it does not exist.\n\tpub fn existing_proto_node_id(&mut self, reference: ProtoNodeIdentifier, create_if_nonexistent: bool) -> Option<NodeId> {\n\t\tself.existing_node_id(&DefinitionIdentifier::ProtoNode(reference), create_if_nonexistent)\n\t}\n\n\t/// Gets the node id of a document node with a specific reference that is upstream from the layer node, and optionally creates it if it does not exist.\n\tfn existing_node_id(&mut self, reference: &DefinitionIdentifier, create_if_nonexistent: bool) -> Option<NodeId> {\n\t\t// Start from the layer node or export\n\t\tlet output_layer = self.get_output_layer()?;\n\n\t\tlet existing_node_id = Self::locate_node_in_layer_chain(reference, output_layer, self.network_interface);\n\n\t\t// Create a new node if the node does not exist and update its inputs\n\t\tif create_if_nonexistent {\n\t\t\treturn existing_node_id.or_else(|| self.create_node(reference));\n\t\t}\n\n\t\texisting_node_id\n\t}\n\n\t/// Gets the node id of a node with a specific reference (name) that is upstream (leftward) from the layer node, but before reaching another upstream layer stack.\n\t/// For example, if given a parent layer, this would find a requested \"Transform\" or \"Boolean Operation\" node in its chain, between the parent layer and its layer stack child contents.\n\t/// It would also travel up an entire layer that's not fed by a stack until reaching the generator node, such as a \"Rectangle\" or \"Path\" layer.\n\tpub fn locate_node_in_layer_chain(reference: &DefinitionIdentifier, left_of_layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {\n\t\tlet upstream = network_interface.upstream_flow_back_from_nodes(vec![left_of_layer.to_node()], &[], network_interface::FlowType::HorizontalFlow);\n\n\t\t// Look at all of the upstream nodes\n\t\tfor upstream_node in upstream {\n\t\t\t// Check if this is the node we have been searching for.\n\t\t\tif network_interface.reference(&upstream_node, &[]).is_some_and(|node_reference| node_reference == *reference) {\n\t\t\t\tif !network_interface.is_visible(&upstream_node, &[]) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\treturn Some(upstream_node);\n\t\t\t}\n\n\t\t\t// Take until another layer node is found (but not the first layer node)\n\t\t\tlet is_traversal_start = |node_id: NodeId| left_of_layer.to_node() == node_id || network_interface.document_network().exports.iter().any(|export| export.as_node() == Some(node_id));\n\t\t\tif !is_traversal_start(upstream_node) && (network_interface.is_layer(&upstream_node, &[])) {\n\t\t\t\treturn None;\n\t\t\t}\n\t\t}\n\n\t\tNone\n\t}\n\n\t/// Create a new node inside the layer\n\tpub fn create_node(&mut self, reference: &DefinitionIdentifier) -> Option<NodeId> {\n\t\tlet output_layer = self.get_output_layer()?;\n\t\tlet Some(node_definition) = resolve_document_node_type(reference) else {\n\t\t\tlog::error!(\"Node {reference:?} does not exist in ModifyInputsContext::existing_node_id\");\n\t\t\treturn None;\n\t\t};\n\n\t\t// If inserting a 'Path' node, insert a 'Flatten Path' node if the type is `Graphic`.\n\t\t// TODO: Allow the 'Path' node to operate on table data by utilizing the reference (index or ID?) for each row.\n\t\tif node_definition.identifier == \"Path\" {\n\t\t\tlet layer_input_type = self.network_interface.input_type(&InputConnector::node(output_layer.to_node(), 1), &[]);\n\t\t\tif layer_input_type.compiled_nested_type() == Some(&concrete!(Table<Graphic>)) {\n\t\t\t\tlet Some(flatten_path_definition) = resolve_proto_node_type(graphene_std::vector_nodes::flatten_path::IDENTIFIER) else {\n\t\t\t\t\tlog::error!(\"Flatten Path does not exist in ModifyInputsContext::existing_node_id\");\n\t\t\t\t\treturn None;\n\t\t\t\t};\n\t\t\t\tlet node_id = NodeId::new();\n\t\t\t\tself.network_interface.insert_node(node_id, flatten_path_definition.default_node_template(), &[]);\n\t\t\t\tself.network_interface.move_node_to_chain_start(&node_id, output_layer, &[]);\n\t\t\t}\n\t\t}\n\t\tlet node_id = NodeId::new();\n\t\tself.network_interface.insert_node(node_id, node_definition.default_node_template(), &[]);\n\t\tself.network_interface.move_node_to_chain_start(&node_id, output_layer, &[]);\n\t\tSome(node_id)\n\t}\n\n\tpub fn fill_set(&mut self, fill: Fill) {\n\t\tlet fill_index = 1;\n\t\tlet backup_color_index = 2;\n\t\tlet backup_gradient_index = 3;\n\n\t\tlet Some(fill_node_id) = self.existing_proto_node_id(graphene_std::vector_nodes::fill::IDENTIFIER, true) else {\n\t\t\treturn;\n\t\t};\n\t\tmatch &fill {\n\t\t\tFill::None => {\n\t\t\t\tlet input_connector = InputConnector::node(fill_node_id, backup_color_index);\n\t\t\t\tself.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(Table::new()), false), true);\n\t\t\t}\n\t\t\tFill::Solid(color) => {\n\t\t\t\tlet input_connector = InputConnector::node(fill_node_id, backup_color_index);\n\t\t\t\tself.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(Table::new_from_element(*color)), false), true);\n\t\t\t}\n\t\t\tFill::Gradient(gradient) => {\n\t\t\t\tlet input_connector = InputConnector::node(fill_node_id, backup_gradient_index);\n\t\t\t\tself.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Gradient(gradient.clone()), false), true);\n\t\t\t}\n\t\t}\n\t\tlet input_connector = InputConnector::node(fill_node_id, fill_index);\n\t\tself.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Fill(fill), false), false);\n\t}\n\n\tpub fn blend_mode_set(&mut self, blend_mode: BlendMode) {\n\t\tlet Some(blend_node_id) = self.existing_proto_node_id(graphene_std::blending_nodes::blending::IDENTIFIER, true) else {\n\t\t\treturn;\n\t\t};\n\t\tlet input_connector = InputConnector::node(blend_node_id, 1);\n\t\tself.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::BlendMode(blend_mode), false), false);\n\t}\n\n\tpub fn opacity_set(&mut self, opacity: f64) {\n\t\tlet Some(blend_node_id) = self.existing_proto_node_id(graphene_std::blending_nodes::blending::IDENTIFIER, true) else {\n\t\t\treturn;\n\t\t};\n\t\tlet input_connector = InputConnector::node(blend_node_id, 2);\n\t\tself.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(opacity * 100.), false), false);\n\t}\n\n\tpub fn blending_fill_set(&mut self, fill: f64) {\n\t\tlet Some(blend_node_id) = self.existing_proto_node_id(graphene_std::blending_nodes::blending::IDENTIFIER, true) else {\n\t\t\treturn;\n\t\t};\n\t\tlet input_connector = InputConnector::node(blend_node_id, 3);\n\t\tself.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(fill * 100.), false), false);\n\t}\n\n\tpub fn clip_mode_toggle(&mut self, clip_mode: Option<bool>) {\n\t\tlet clip = !clip_mode.unwrap_or(false);\n\t\tlet Some(clip_node_id) = self.existing_proto_node_id(graphene_std::blending_nodes::blending::IDENTIFIER, true) else {\n\t\t\treturn;\n\t\t};\n\t\tlet input_connector = InputConnector::node(clip_node_id, 4);\n\t\tself.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Bool(clip), false), false);\n\t}\n\n\tpub fn stroke_set(&mut self, stroke: Stroke) {\n\t\tlet Some(stroke_node_id) = self.existing_proto_node_id(graphene_std::vector::stroke::IDENTIFIER, true) else {\n\t\t\treturn;\n\t\t};\n\n\t\tlet stroke_color = if let Some(color) = stroke.color { Table::new_from_element(color) } else { Table::new() };\n\n\t\tlet input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::ColorInput::INDEX);\n\t\tself.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(stroke_color), false), true);\n\t\tlet input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::WeightInput::INDEX);\n\t\tself.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(stroke.weight), false), true);\n\t\tlet input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::AlignInput::INDEX);\n\t\tself.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::StrokeAlign(stroke.align), false), false);\n\t\tlet input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::CapInput::INDEX);\n\t\tself.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::StrokeCap(stroke.cap), false), true);\n\t\tlet input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::JoinInput::INDEX);\n\t\tself.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::StrokeJoin(stroke.join), false), true);\n\t\tlet input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::MiterLimitInput::INDEX);\n\t\tself.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(stroke.join_miter_limit), false), false);\n\t\tlet input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::PaintOrderInput::INDEX);\n\t\tself.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::PaintOrder(stroke.paint_order), false), false);\n\t\tlet input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::DashLengthsInput::<Vec<f64>>::INDEX);\n\t\tself.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::VecF64(stroke.dash_lengths), false), true);\n\t\tlet input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::DashOffsetInput::INDEX);\n\t\tself.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(stroke.dash_offset), false), true);\n\t}\n\n\t/// Update the transform value of the upstream Transform node based a change to its existing value and the given parent transform.\n\t/// A new Transform node is created if one does not exist, unless it would be given the identity transform.\n\tpub fn transform_change_with_parent(&mut self, transform: DAffine2, transform_in: TransformIn, parent_transform: DAffine2, skip_rerender: bool) {\n\t\t// Get the existing upstream Transform node and its transform, if present, otherwise use the identity transform\n\t\tlet (layer_transform, transform_node_id) = self\n\t\t\t.existing_network_node_id(\"Transform\", false)\n\t\t\t.and_then(|transform_node_id| {\n\t\t\t\tlet document_node = self.network_interface.document_network().nodes.get(&transform_node_id)?;\n\t\t\t\tSome((transform_utils::get_current_transform(&document_node.inputs), transform_node_id))\n\t\t\t})\n\t\t\t.unzip();\n\t\tlet layer_transform = layer_transform.unwrap_or_default();\n\n\t\t// Get a transform appropriate for the requested space\n\t\tlet to_transform = match transform_in {\n\t\t\tTransformIn::Local => DAffine2::IDENTITY,\n\t\t\tTransformIn::Scope { scope } => scope * parent_transform,\n\t\t\tTransformIn::Viewport => parent_transform,\n\t\t};\n\n\t\t// Set the transform value to the Transform node\n\t\tlet final_transform = to_transform.inverse() * transform * to_transform * layer_transform;\n\t\tself.transform_set_direct(final_transform, skip_rerender, transform_node_id);\n\t}\n\n\t/// Set the transform value to the upstream Transform node, replacing the existing value.\n\t/// A new Transform node is created if one does not exist, unless it would be given the identity transform.\n\tpub fn transform_set(&mut self, transform: DAffine2, transform_in: TransformIn, skip_rerender: bool) {\n\t\t// Get the existing upstream Transform node, if present\n\t\tlet transform_node_id = self.existing_network_node_id(\"Transform\", false);\n\n\t\t// Get a transform appropriate for the requested space\n\t\tlet to_transform = match transform_in {\n\t\t\tTransformIn::Local => DAffine2::IDENTITY,\n\t\t\tTransformIn::Scope { scope } => scope,\n\t\t\tTransformIn::Viewport => self.network_interface.document_metadata().downstream_transform_to_viewport(self.layer_node.unwrap()).inverse(),\n\t\t};\n\n\t\t// Set the transform value to the Transform node\n\t\tlet final_transform = to_transform * transform;\n\t\tself.transform_set_direct(final_transform, skip_rerender, transform_node_id);\n\t}\n\n\t/// Write the given transform value to the upstream Transform node, if one is supplied. If one doesn't exist, it will be created unless the given transform is the identity.\n\tpub fn transform_set_direct(&mut self, transform: DAffine2, skip_rerender: bool, transform_node_id: Option<NodeId>) {\n\t\t// If the Transform node didn't exist yet, create it now\n\t\tlet Some(transform_node_id) = transform_node_id.or_else(|| {\n\t\t\t// Check if the transform is the identity transform (within an epsilon) and if so, don't create a new Transform node\n\t\t\tif transform.abs_diff_eq(DAffine2::IDENTITY, 1e-6) {\n\t\t\t\t// We don't want to pollute the graph with an unnecessary Transform node, so we avoid creating and setting it by returning None\n\t\t\t\treturn None;\n\t\t\t}\n\n\t\t\t// Create the Transform node\n\t\t\tself.existing_network_node_id(\"Transform\", true)\n\t\t}) else {\n\t\t\treturn;\n\t\t};\n\n\t\t// Update the transform value of the Transform node\n\t\ttransform_utils::update_transform(self.network_interface, &transform_node_id, transform);\n\n\t\t// Refresh the render and editor UI\n\t\tself.responses.add(PropertiesPanelMessage::Refresh);\n\t\tif !skip_rerender {\n\t\t\tself.responses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t}\n\t}\n\n\tpub fn vector_modify(&mut self, modification_type: VectorModificationType) {\n\t\tlet Some(path_node_id) = self.existing_network_node_id(\"Path\", true) else {\n\t\t\treturn;\n\t\t};\n\t\tself.network_interface.vector_modify(&path_node_id, modification_type);\n\t\tself.responses.add(PropertiesPanelMessage::Refresh);\n\t\tself.responses.add(NodeGraphMessage::RunDocumentGraph);\n\t}\n\n\tpub fn brush_modify(&mut self, strokes: Vec<BrushStroke>) {\n\t\tlet Some(brush_node_id) = self.existing_network_node_id(\"Brush\", true) else {\n\t\t\treturn;\n\t\t};\n\t\tself.set_input_with_refresh(InputConnector::node(brush_node_id, 1), NodeInput::value(TaggedValue::BrushStrokes(strokes), false), false);\n\t}\n\n\tpub fn resize_artboard(&mut self, location: IVec2, dimensions: IVec2) {\n\t\tlet Some(artboard_node_id) = self.existing_network_node_id(\"Artboard\", true) else {\n\t\t\treturn;\n\t\t};\n\n\t\tlet mut dimensions = dimensions;\n\t\tlet mut location = location;\n\n\t\tif dimensions.x < 0 {\n\t\t\tdimensions.x *= -1;\n\t\t\tlocation.x -= dimensions.x;\n\t\t}\n\t\tif dimensions.y < 0 {\n\t\t\tdimensions.y *= -1;\n\t\t\tlocation.y -= dimensions.y;\n\t\t}\n\t\tself.set_input_with_refresh(InputConnector::node(artboard_node_id, 2), NodeInput::value(TaggedValue::DVec2(location.into()), false), false);\n\t\tself.set_input_with_refresh(InputConnector::node(artboard_node_id, 3), NodeInput::value(TaggedValue::DVec2(dimensions.into()), false), false);\n\t}\n\n\t/// Set the input, refresh the properties panel, and run the document graph if skip_rerender is false\n\tpub fn set_input_with_refresh(&mut self, input_connector: InputConnector, input: NodeInput, skip_rerender: bool) {\n\t\tself.network_interface.set_input(&input_connector, input, &[]);\n\t\tself.responses.add(PropertiesPanelMessage::Refresh);\n\t\tif !skip_rerender {\n\t\t\tself.responses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/mod.rs",
    "content": "mod document_message;\nmod document_message_handler;\n\npub mod data_panel;\npub mod graph_operation;\npub mod navigation;\npub mod node_graph;\npub mod overlays;\npub mod properties_panel;\npub mod utility_types;\n\n#[doc(inline)]\npub use document_message::{DocumentMessage, DocumentMessageDiscriminant};\n#[doc(inline)]\npub use document_message_handler::{DocumentMessageContext, DocumentMessageHandler};\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/navigation/mod.rs",
    "content": "mod navigation_message;\nmod navigation_message_handler;\npub mod utility_types;\n\n#[doc(inline)]\npub use navigation_message::{NavigationMessage, NavigationMessageDiscriminant};\n#[doc(inline)]\npub use navigation_message_handler::{NavigationMessageContext, NavigationMessageHandler};\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/navigation/navigation_message.rs",
    "content": "use crate::messages::input_mapper::utility_types::input_keyboard::Key;\nuse crate::messages::prelude::*;\nuse glam::DVec2;\n\n#[impl_message(Message, DocumentMessage, Navigation)]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum NavigationMessage {\n\t// Messages\n\tBeginCanvasPan,\n\tBeginCanvasTilt { was_dispatched_from_menu: bool },\n\tBeginCanvasZoom,\n\tCanvasPan { delta: DVec2 },\n\tCanvasPanAbortPrepare { x_not_y_axis: bool },\n\tCanvasPanAbort { x_not_y_axis: bool },\n\tCanvasPanByViewportFraction { delta: DVec2 },\n\tCanvasPanMouseWheel { use_y_as_x: bool },\n\tCanvasTiltResetAndZoomTo100Percent,\n\tCanvasTiltSet { angle_radians: f64 },\n\tCanvasZoomDecrease { center_on_mouse: bool },\n\tCanvasZoomIncrease { center_on_mouse: bool },\n\tCanvasZoomMouseWheel,\n\tCanvasZoomSet { zoom_factor: f64 },\n\tCanvasFlip,\n\tEndCanvasPTZ { abort_transform: bool },\n\tEndCanvasPTZWithClick { commit_key: Key },\n\tFitViewportToBounds { bounds: [DVec2; 2], prevent_zoom_past_100: bool },\n\tFitViewportToSelection,\n\tPointerMove { snap: Key },\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs",
    "content": "use crate::application::Editor;\nuse crate::consts::{\n\tVIEWPORT_ROTATE_SNAP_INTERVAL, VIEWPORT_SCROLL_RATE, VIEWPORT_ZOOM_LEVELS, VIEWPORT_ZOOM_MIN_FRACTION_COVER, VIEWPORT_ZOOM_MOUSE_RATE, VIEWPORT_ZOOM_SCALE_MAX, VIEWPORT_ZOOM_SCALE_MIN,\n\tVIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR,\n};\nuse crate::messages::frontend::utility_types::MouseCursorIcon;\nuse crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};\nuse crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;\nuse crate::messages::portfolio::document::navigation::utility_types::NavigationOperation;\nuse crate::messages::portfolio::document::utility_types::misc::PTZ;\nuse crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;\nuse crate::messages::prelude::*;\nuse crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};\nuse glam::{DAffine2, DVec2};\nuse graph_craft::document::NodeId;\n\n#[derive(ExtractField)]\npub struct NavigationMessageContext<'a> {\n\tpub network_interface: &'a mut NodeNetworkInterface,\n\tpub breadcrumb_network_path: &'a [NodeId],\n\tpub ipp: &'a InputPreprocessorMessageHandler,\n\tpub document_ptz: &'a mut PTZ,\n\tpub graph_view_overlay_open: bool,\n\tpub preferences: &'a PreferencesMessageHandler,\n\tpub viewport: &'a ViewportMessageHandler,\n}\n\n#[derive(Debug, Clone, PartialEq, Default, ExtractField)]\npub struct NavigationMessageHandler {\n\tnavigation_operation: NavigationOperation,\n\tmouse_position: ViewportPosition,\n\tfinish_operation_with_click: bool,\n\tabortable_pan_start: Option<f64>,\n}\n\n#[message_handler_data]\nimpl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for NavigationMessageHandler {\n\tfn process_message(&mut self, message: NavigationMessage, responses: &mut VecDeque<Message>, context: NavigationMessageContext) {\n\t\tlet NavigationMessageContext {\n\t\t\tnetwork_interface,\n\t\t\tbreadcrumb_network_path,\n\t\t\tipp,\n\t\t\tdocument_ptz,\n\t\t\tgraph_view_overlay_open,\n\t\t\tpreferences,\n\t\t\tviewport,\n\t\t} = context;\n\n\t\tfn get_ptz<'a>(document_ptz: &'a PTZ, network_interface: &'a NodeNetworkInterface, graph_view_overlay_open: bool, breadcrumb_network_path: &[NodeId]) -> Option<&'a PTZ> {\n\t\t\tif !graph_view_overlay_open {\n\t\t\t\tSome(document_ptz)\n\t\t\t} else {\n\t\t\t\tlet network_metadata = network_interface.network_metadata(breadcrumb_network_path)?;\n\t\t\t\tSome(&network_metadata.persistent_metadata.navigation_metadata.node_graph_ptz)\n\t\t\t}\n\t\t}\n\n\t\tfn get_ptz_mut<'a>(document_ptz: &'a mut PTZ, network_interface: &'a mut NodeNetworkInterface, graph_view_overlay_open: bool, breadcrumb_network_path: &[NodeId]) -> Option<&'a mut PTZ> {\n\t\t\tif !graph_view_overlay_open {\n\t\t\t\tSome(document_ptz)\n\t\t\t} else {\n\t\t\t\tlet Some(node_graph_ptz) = network_interface.node_graph_ptz_mut(breadcrumb_network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get node graph PTZ in NavigationMessageHandler process_message\");\n\t\t\t\t\treturn None;\n\t\t\t\t};\n\t\t\t\tSome(node_graph_ptz)\n\t\t\t}\n\t\t}\n\n\t\tlet Some(ptz) = get_ptz(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {\n\t\t\tlog::error!(\"Could not get PTZ in NavigationMessageHandler process_message\");\n\t\t\treturn;\n\t\t};\n\t\tlet old_zoom = ptz.zoom();\n\n\t\tmatch message {\n\t\t\tNavigationMessage::BeginCanvasPan => {\n\t\t\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Grabbing });\n\n\t\t\t\tHintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"\"), HintInfo::keys([Key::Escape], \"Cancel\").prepend_slash()])]).send_layout(responses);\n\n\t\t\t\tself.mouse_position = ipp.mouse.position;\n\t\t\t\tlet Some(ptz) = get_ptz(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tself.navigation_operation = NavigationOperation::Pan { pan_original_for_abort: ptz.pan };\n\t\t\t}\n\t\t\tNavigationMessage::BeginCanvasTilt { was_dispatched_from_menu } => {\n\t\t\t\tlet Some(ptz) = get_ptz(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\t// If the node graph is open, prevent tilt and instead start panning\n\t\t\t\tif graph_view_overlay_open {\n\t\t\t\t\tresponses.add(NavigationMessage::BeginCanvasPan);\n\t\t\t\t} else {\n\t\t\t\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });\n\t\t\t\t\tHintData(vec![\n\t\t\t\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"\"), HintInfo::keys([Key::Escape], \"Cancel\").prepend_slash()]),\n\t\t\t\t\t\tHintGroup(vec![HintInfo::keys([Key::Shift], \"15° Increments\")]),\n\t\t\t\t\t])\n\t\t\t\t\t.send_layout(responses);\n\n\t\t\t\t\tself.navigation_operation = NavigationOperation::Tilt {\n\t\t\t\t\t\ttilt_original_for_abort: ptz.tilt(),\n\t\t\t\t\t\ttilt_raw_not_snapped: ptz.tilt(),\n\t\t\t\t\t\tsnap: false,\n\t\t\t\t\t};\n\n\t\t\t\t\tself.mouse_position = ipp.mouse.position;\n\t\t\t\t\tself.finish_operation_with_click = was_dispatched_from_menu;\n\t\t\t\t}\n\t\t\t}\n\t\t\tNavigationMessage::BeginCanvasZoom => {\n\t\t\t\tlet Some(ptz) = get_ptz(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {\n\t\t\t\t\treturn;\n\t\t\t\t};\n\n\t\t\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::ZoomIn });\n\t\t\t\tHintData(vec![\n\t\t\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"\"), HintInfo::keys([Key::Escape], \"Cancel\").prepend_slash()]),\n\t\t\t\t\tHintGroup(vec![HintInfo::keys([Key::Shift], \"Increments\")]),\n\t\t\t\t])\n\t\t\t\t.send_layout(responses);\n\n\t\t\t\tself.navigation_operation = NavigationOperation::Zoom {\n\t\t\t\t\tzoom_raw_not_snapped: ptz.zoom(),\n\t\t\t\t\tzoom_original_for_abort: ptz.zoom(),\n\t\t\t\t\tsnap: false,\n\t\t\t\t};\n\t\t\t\tself.mouse_position = ipp.mouse.position;\n\t\t\t}\n\t\t\tNavigationMessage::CanvasPan { delta } => {\n\t\t\t\tlet Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get PTZ in CanvasPan\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tlet document_to_viewport = self.calculate_offset_transform(viewport.center_in_viewport_space().into_dvec2(), ptz);\n\t\t\t\tlet transformed_delta = document_to_viewport.inverse().transform_vector2(delta);\n\n\t\t\t\tptz.pan += transformed_delta;\n\t\t\t\tresponses.add(EventMessage::CanvasTransformed);\n\t\t\t\tresponses.add(DocumentMessage::PTZUpdate);\n\t\t\t}\n\t\t\tNavigationMessage::CanvasPanAbortPrepare { x_not_y_axis } => {\n\t\t\t\tlet Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get PTZ in CanvasPanAbortPrepare\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tself.abortable_pan_start = Some(if x_not_y_axis { ptz.pan.x } else { ptz.pan.y });\n\t\t\t}\n\t\t\tNavigationMessage::CanvasPanAbort { x_not_y_axis } => {\n\t\t\t\tlet Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get PTZ in CanvasPanAbort\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tif let Some(abortable_pan_start) = self.abortable_pan_start {\n\t\t\t\t\tif x_not_y_axis {\n\t\t\t\t\t\tptz.pan.x = abortable_pan_start;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tptz.pan.y = abortable_pan_start;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tself.abortable_pan_start = None;\n\t\t\t\tresponses.add(DocumentMessage::PTZUpdate);\n\t\t\t}\n\t\t\tNavigationMessage::CanvasPanByViewportFraction { delta } => {\n\t\t\t\tlet Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get node graph PTZ in CanvasPanByViewportFraction\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tlet document_to_viewport = self.calculate_offset_transform(viewport.center_in_viewport_space().into_dvec2(), ptz);\n\t\t\t\tlet transformed_delta = document_to_viewport.inverse().transform_vector2(delta * viewport.size().into_dvec2());\n\n\t\t\t\tptz.pan += transformed_delta;\n\t\t\t\tresponses.add(DocumentMessage::PTZUpdate);\n\t\t\t}\n\t\t\tNavigationMessage::CanvasPanMouseWheel { use_y_as_x } => {\n\t\t\t\t// On Mac, the OS already converts Shift+scroll into horizontal scrolling\n\t\t\t\tlet delta = if use_y_as_x && !Editor::environment().is_mac() {\n\t\t\t\t\t(ipp.mouse.scroll_delta.y, 0.).into()\n\t\t\t\t} else {\n\t\t\t\t\tipp.mouse.scroll_delta.as_dvec2()\n\t\t\t\t} * -VIEWPORT_SCROLL_RATE;\n\t\t\t\tresponses.add(NavigationMessage::CanvasPan { delta });\n\t\t\t}\n\t\t\tNavigationMessage::CanvasTiltResetAndZoomTo100Percent => {\n\t\t\t\tlet Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get mutable PTZ in CanvasTiltResetAndZoomTo100Percent\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tptz.set_tilt(0.);\n\t\t\t\tptz.set_zoom(1.);\n\t\t\t\tif graph_view_overlay_open {\n\t\t\t\t\tresponses.add(NodeGraphMessage::UpdateGraphBarRight);\n\t\t\t\t} else {\n\t\t\t\t\tresponses.add(PortfolioMessage::UpdateDocumentWidgets);\n\t\t\t\t}\n\t\t\t\tresponses.add(DocumentMessage::PTZUpdate);\n\t\t\t}\n\t\t\tNavigationMessage::CanvasTiltSet { angle_radians } => {\n\t\t\t\tlet Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get mutable PTZ in CanvasTiltSet\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tptz.set_tilt(angle_radians);\n\t\t\t\tresponses.add(DocumentMessage::PTZUpdate);\n\t\t\t\tif !graph_view_overlay_open {\n\t\t\t\t\tresponses.add(PortfolioMessage::UpdateDocumentWidgets);\n\t\t\t\t\tresponses.add(MenuBarMessage::SendLayout);\n\t\t\t\t}\n\t\t\t}\n\t\t\tNavigationMessage::CanvasZoomDecrease { center_on_mouse } => {\n\t\t\t\tlet Some(ptz) = get_ptz(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {\n\t\t\t\t\treturn;\n\t\t\t\t};\n\n\t\t\t\tlet new_scale = *VIEWPORT_ZOOM_LEVELS.iter().rev().find(|scale| **scale < ptz.zoom()).unwrap_or(&ptz.zoom());\n\t\t\t\tif center_on_mouse {\n\t\t\t\t\tresponses.add(self.center_zoom(viewport.size().into(), new_scale / ptz.zoom(), ipp.mouse.position));\n\t\t\t\t}\n\t\t\t\tresponses.add(NavigationMessage::CanvasZoomSet { zoom_factor: new_scale });\n\t\t\t}\n\t\t\tNavigationMessage::CanvasZoomIncrease { center_on_mouse } => {\n\t\t\t\tlet Some(ptz) = get_ptz(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {\n\t\t\t\t\treturn;\n\t\t\t\t};\n\n\t\t\t\tlet new_scale = *VIEWPORT_ZOOM_LEVELS.iter().find(|scale| **scale > ptz.zoom()).unwrap_or(&ptz.zoom());\n\t\t\t\tif center_on_mouse {\n\t\t\t\t\tresponses.add(self.center_zoom(viewport.size().into(), new_scale / ptz.zoom(), ipp.mouse.position));\n\t\t\t\t}\n\t\t\t\tresponses.add(NavigationMessage::CanvasZoomSet { zoom_factor: new_scale });\n\t\t\t}\n\t\t\tNavigationMessage::CanvasZoomMouseWheel => {\n\t\t\t\tlet scroll = ipp.mouse.scroll_delta.scroll_delta();\n\t\t\t\tlet mut zoom_factor = 1. + scroll.abs() * preferences.viewport_zoom_wheel_rate;\n\t\t\t\tif ipp.mouse.scroll_delta.y > 0. {\n\t\t\t\t\tzoom_factor = 1. / zoom_factor\n\t\t\t\t}\n\t\t\t\tlet document_bounds = if !graph_view_overlay_open {\n\t\t\t\t\t// TODO: Cache this in node graph coordinates and apply the transform to the rectangle to get viewport coordinates\n\t\t\t\t\tnetwork_interface.document_bounds_viewport_space(true)\n\t\t\t\t} else {\n\t\t\t\t\tnetwork_interface.graph_bounds_viewport_space(breadcrumb_network_path)\n\t\t\t\t};\n\t\t\t\tlet Some(ptz) = get_ptz(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {\n\t\t\t\t\treturn;\n\t\t\t\t};\n\n\t\t\t\tzoom_factor *= Self::clamp_zoom(ptz.zoom() * zoom_factor, document_bounds, old_zoom, viewport);\n\n\t\t\t\tresponses.add(self.center_zoom(viewport.size().into(), zoom_factor, ipp.mouse.position));\n\t\t\t\tresponses.add(NavigationMessage::CanvasZoomSet {\n\t\t\t\t\tzoom_factor: ptz.zoom() * zoom_factor,\n\t\t\t\t});\n\t\t\t}\n\t\t\tNavigationMessage::CanvasZoomSet { zoom_factor } => {\n\t\t\t\tlet document_bounds = if !graph_view_overlay_open {\n\t\t\t\t\t// TODO: Cache this in node graph coordinates and apply the transform to the rectangle to get viewport coordinates\n\t\t\t\t\tnetwork_interface.document_bounds_viewport_space(true)\n\t\t\t\t} else {\n\t\t\t\t\tnetwork_interface.graph_bounds_viewport_space(breadcrumb_network_path)\n\t\t\t\t};\n\t\t\t\tlet Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get mutable PTZ in CanvasZoomSet\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tlet zoom = zoom_factor.clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX);\n\t\t\t\tlet zoom = zoom * Self::clamp_zoom(zoom, document_bounds, old_zoom, viewport);\n\t\t\t\tptz.set_zoom(zoom);\n\t\t\t\tif graph_view_overlay_open {\n\t\t\t\t\tresponses.add(NodeGraphMessage::UpdateGraphBarRight);\n\t\t\t\t} else {\n\t\t\t\t\tresponses.add(PortfolioMessage::UpdateDocumentWidgets);\n\t\t\t\t}\n\t\t\t\tresponses.add(DocumentMessage::PTZUpdate);\n\t\t\t}\n\t\t\tNavigationMessage::CanvasFlip => {\n\t\t\t\tif graph_view_overlay_open {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get mutable PTZ in CanvasFlip\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\n\t\t\t\tptz.flip = !ptz.flip;\n\n\t\t\t\tresponses.add(DocumentMessage::PTZUpdate);\n\t\t\t\tresponses.add(EventMessage::CanvasTransformed);\n\t\t\t\tresponses.add(MenuBarMessage::SendLayout);\n\t\t\t\tresponses.add(PortfolioMessage::UpdateDocumentWidgets);\n\t\t\t}\n\t\t\tNavigationMessage::EndCanvasPTZ { abort_transform } => {\n\t\t\t\tlet Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get mutable PTZ in EndCanvasPTZ\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\t// If an abort was requested, reset the active PTZ value to its original state\n\t\t\t\tif abort_transform && self.navigation_operation != NavigationOperation::None {\n\t\t\t\t\tmatch self.navigation_operation {\n\t\t\t\t\t\tNavigationOperation::None => {}\n\t\t\t\t\t\tNavigationOperation::Tilt { tilt_original_for_abort, .. } => {\n\t\t\t\t\t\t\tptz.set_tilt(tilt_original_for_abort);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tNavigationOperation::Pan { pan_original_for_abort, .. } => {\n\t\t\t\t\t\t\tptz.pan = pan_original_for_abort;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tNavigationOperation::Zoom { zoom_original_for_abort, .. } => {\n\t\t\t\t\t\t\tptz.set_zoom(zoom_original_for_abort);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Final chance to apply snapping if the key was pressed during this final frame\n\t\t\t\tptz.set_tilt(self.snapped_tilt(ptz.tilt()));\n\t\t\t\tptz.set_zoom(self.snapped_zoom(ptz.zoom()));\n\t\t\t\tresponses.add(DocumentMessage::PTZUpdate);\n\t\t\t\tif graph_view_overlay_open {\n\t\t\t\t\tresponses.add(NodeGraphMessage::UpdateGraphBarRight);\n\t\t\t\t} else {\n\t\t\t\t\tresponses.add(PortfolioMessage::UpdateDocumentWidgets);\n\t\t\t\t}\n\t\t\t\t// Reset the navigation operation now that it's done\n\t\t\t\tself.navigation_operation = NavigationOperation::None;\n\n\t\t\t\t// Send the final messages to close out the operation\n\t\t\t\tresponses.add(EventMessage::CanvasTransformed);\n\t\t\t\tresponses.add(ToolMessage::UpdateCursor);\n\t\t\t\tresponses.add(ToolMessage::UpdateHints);\n\t\t\t\tresponses.add(NavigateToolMessage::End);\n\t\t\t}\n\t\t\tNavigationMessage::EndCanvasPTZWithClick { commit_key } => {\n\t\t\t\tself.finish_operation_with_click = false;\n\n\t\t\t\tlet abort_transform = commit_key == Key::MouseRight;\n\t\t\t\tresponses.add(NavigationMessage::EndCanvasPTZ { abort_transform });\n\t\t\t}\n\t\t\tNavigationMessage::FitViewportToBounds {\n\t\t\t\tbounds: [pos1, pos2],\n\t\t\t\tprevent_zoom_past_100,\n\t\t\t} => {\n\t\t\t\tlet (pos1, pos2) = (pos1.min(pos2), pos1.max(pos2));\n\t\t\t\tlet diagonal = pos2 - pos1;\n\n\t\t\t\tif diagonal.length() < f64::EPSILON * 1000. || viewport.size().into_dvec2() == DVec2::ZERO {\n\t\t\t\t\twarn!(\"Cannot center since the viewport size is 0\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get node graph PTZ in CanvasPanByViewportFraction\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tlet document_to_viewport = self.calculate_offset_transform(viewport.center_in_viewport_space().into_dvec2(), ptz);\n\n\t\t\t\tlet v1 = document_to_viewport.inverse().transform_point2(DVec2::ZERO);\n\t\t\t\tlet v2 = document_to_viewport.inverse().transform_point2(viewport.size().into_dvec2());\n\n\t\t\t\tlet center = ((v2 + v1) - (pos2 + pos1)) / 2.;\n\t\t\t\tlet size = (v2 - v1) / diagonal;\n\t\t\t\tlet new_scale = size.min_element();\n\n\t\t\t\tlet viewport_change = document_to_viewport.transform_vector2(center);\n\n\t\t\t\t// Only change the pan if the change will be visible in the viewport\n\t\t\t\tif viewport_change.x.abs() > 0.5 || viewport_change.y.abs() > 0.5 {\n\t\t\t\t\tptz.pan += center;\n\t\t\t\t}\n\n\t\t\t\tptz.set_zoom(ptz.zoom() * new_scale * VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR);\n\n\t\t\t\t// Keep the canvas filling less than the full available viewport bounds if requested.\n\t\t\t\t// And if the zoom is close to the full viewport bounds, we ignore the padding because 100% is preferrable if it still fits.\n\t\t\t\tif prevent_zoom_past_100 && ptz.zoom() > VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR {\n\t\t\t\t\tptz.set_zoom(1.);\n\t\t\t\t}\n\n\t\t\t\tif graph_view_overlay_open {\n\t\t\t\t\tresponses.add(NodeGraphMessage::UpdateGraphBarRight);\n\t\t\t\t} else {\n\t\t\t\t\tresponses.add(PortfolioMessage::UpdateDocumentWidgets);\n\t\t\t\t}\n\t\t\t\tresponses.add(DocumentMessage::PTZUpdate);\n\t\t\t}\n\t\t\t// Fully zooms in on the selected\n\t\t\tNavigationMessage::FitViewportToSelection => {\n\t\t\t\tlet selection_bounds = if graph_view_overlay_open {\n\t\t\t\t\tnetwork_interface.selected_nodes_bounding_box_viewport(breadcrumb_network_path)\n\t\t\t\t} else {\n\t\t\t\t\tnetwork_interface.selected_layers_artwork_bounding_box_viewport()\n\t\t\t\t};\n\n\t\t\t\tif let Some(bounds) = selection_bounds {\n\t\t\t\t\tlet Some(ptz) = get_ptz(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {\n\t\t\t\t\t\tlog::error!(\"Could not get node graph PTZ in FitViewportToSelection\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t};\n\n\t\t\t\t\tlet document_to_viewport = self.calculate_offset_transform(viewport.center_in_viewport_space().into_dvec2(), ptz);\n\t\t\t\t\tresponses.add(NavigationMessage::FitViewportToBounds {\n\t\t\t\t\t\tbounds: [document_to_viewport.inverse().transform_point2(bounds[0]), document_to_viewport.inverse().transform_point2(bounds[1])],\n\t\t\t\t\t\tprevent_zoom_past_100: false,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t\tNavigationMessage::PointerMove { snap } => {\n\t\t\t\tmatch self.navigation_operation {\n\t\t\t\t\tNavigationOperation::None => {}\n\t\t\t\t\tNavigationOperation::Pan { .. } => {\n\t\t\t\t\t\tlet delta = ipp.mouse.position - self.mouse_position;\n\t\t\t\t\t\tresponses.add(NavigationMessage::CanvasPan { delta });\n\t\t\t\t\t}\n\t\t\t\t\tNavigationOperation::Tilt {\n\t\t\t\t\t\ttilt_raw_not_snapped,\n\t\t\t\t\t\ttilt_original_for_abort,\n\t\t\t\t\t\t..\n\t\t\t\t\t} => {\n\t\t\t\t\t\tlet tilt_raw_not_snapped = {\n\t\t\t\t\t\t\t// Compute the angle in document space to counter for the canvas being flipped\n\t\t\t\t\t\t\tlet viewport_to_document = network_interface.document_metadata().document_to_viewport.inverse();\n\t\t\t\t\t\t\tlet half_viewport = viewport.center_in_viewport_space().into_dvec2();\n\t\t\t\t\t\t\tlet start_offset = viewport_to_document.transform_vector2(self.mouse_position - half_viewport);\n\t\t\t\t\t\t\tlet end_offset = viewport_to_document.transform_vector2(ipp.mouse.position - half_viewport);\n\t\t\t\t\t\t\tlet angle = start_offset.angle_to(end_offset);\n\n\t\t\t\t\t\t\ttilt_raw_not_snapped + angle\n\t\t\t\t\t\t};\n\t\t\t\t\t\tlet Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {\n\t\t\t\t\t\t\tlog::error!(\"Could not get mutable PTZ in Tilt\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t};\n\t\t\t\t\t\tptz.set_tilt(self.snapped_tilt(tilt_raw_not_snapped));\n\n\t\t\t\t\t\tlet snap = ipp.keyboard.get(snap as usize);\n\n\t\t\t\t\t\tself.navigation_operation = NavigationOperation::Tilt {\n\t\t\t\t\t\t\ttilt_original_for_abort,\n\t\t\t\t\t\t\ttilt_raw_not_snapped,\n\t\t\t\t\t\t\tsnap,\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tresponses.add(NavigationMessage::CanvasTiltSet { angle_radians: ptz.tilt() });\n\t\t\t\t\t}\n\t\t\t\t\tNavigationOperation::Zoom {\n\t\t\t\t\t\tzoom_raw_not_snapped,\n\t\t\t\t\t\tzoom_original_for_abort,\n\t\t\t\t\t\t..\n\t\t\t\t\t} => {\n\t\t\t\t\t\tlet zoom_raw_not_snapped = {\n\t\t\t\t\t\t\tlet vertical_delta = self.mouse_position.y - ipp.mouse.position.y;\n\t\t\t\t\t\t\tlet amount = vertical_delta * VIEWPORT_ZOOM_MOUSE_RATE;\n\t\t\t\t\t\t\tlet updated_zoom = zoom_raw_not_snapped * (1. + amount);\n\n\t\t\t\t\t\t\tlet document_bounds = if !graph_view_overlay_open {\n\t\t\t\t\t\t\t\t// TODO: Cache this in node graph coordinates and apply the transform to the rectangle to get viewport coordinates\n\t\t\t\t\t\t\t\tnetwork_interface.document_bounds_viewport_space(true)\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tnetwork_interface.graph_bounds_viewport_space(breadcrumb_network_path)\n\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\tupdated_zoom * Self::clamp_zoom(updated_zoom, document_bounds, old_zoom, viewport)\n\t\t\t\t\t\t};\n\t\t\t\t\t\tlet Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {\n\t\t\t\t\t\t\tlog::error!(\"Could not get mutable PTZ in Zoom\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t};\n\t\t\t\t\t\tptz.set_zoom(self.snapped_zoom(zoom_raw_not_snapped));\n\n\t\t\t\t\t\tlet snap = ipp.keyboard.get(snap as usize);\n\n\t\t\t\t\t\tself.navigation_operation = NavigationOperation::Zoom {\n\t\t\t\t\t\t\tzoom_raw_not_snapped,\n\t\t\t\t\t\t\tzoom_original_for_abort,\n\t\t\t\t\t\t\tsnap,\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tresponses.add(NavigationMessage::CanvasZoomSet { zoom_factor: ptz.zoom() });\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tself.mouse_position = ipp.mouse.position;\n\t\t\t}\n\t\t}\n\t}\n\n\tfn actions(&self) -> ActionList {\n\t\tlet mut common = actions!(NavigationMessageDiscriminant;\n\t\t\tBeginCanvasPan,\n\t\t\tBeginCanvasTilt,\n\t\t\tBeginCanvasZoom,\n\t\t\tCanvasPan,\n\t\t\tCanvasPanByViewportFraction,\n\t\t\tCanvasPanMouseWheel,\n\t\t\tCanvasTiltSet,\n\t\t\tCanvasZoomDecrease,\n\t\t\tCanvasZoomIncrease,\n\t\t\tCanvasZoomMouseWheel,\n\t\t\tCanvasFlip,\n\t\t\tFitViewportToSelection,\n\t\t);\n\n\t\tif self.navigation_operation != NavigationOperation::None {\n\t\t\tlet transforming = actions!(NavigationMessageDiscriminant;\n\t\t\t\tEndCanvasPTZ,\n\t\t\t\tPointerMove,\n\t\t\t);\n\t\t\tcommon.extend(transforming);\n\t\t}\n\n\t\tif self.finish_operation_with_click {\n\t\t\tlet transforming_from_menu = actions!(NavigationMessageDiscriminant;\n\t\t\t\tEndCanvasPTZWithClick,\n\t\t\t);\n\n\t\t\tcommon.extend(transforming_from_menu);\n\t\t}\n\n\t\tcommon\n\t}\n}\n\nimpl NavigationMessageHandler {\n\tpub fn snapped_tilt(&self, tilt: f64) -> f64 {\n\t\tlet increment_radians: f64 = VIEWPORT_ROTATE_SNAP_INTERVAL.to_radians();\n\t\tif matches!(self.navigation_operation, NavigationOperation::Tilt { snap: true, .. }) {\n\t\t\t(tilt / increment_radians).round() * increment_radians\n\t\t} else {\n\t\t\ttilt\n\t\t}\n\t}\n\n\tpub fn snapped_zoom(&self, zoom: f64) -> f64 {\n\t\tsnapped_zoom(&self.navigation_operation, zoom)\n\t}\n\n\tpub fn calculate_offset_transform(&self, viewport_center: DVec2, ptz: &PTZ) -> DAffine2 {\n\t\tlet pan = ptz.pan;\n\t\tlet tilt = ptz.tilt();\n\t\tlet zoom = ptz.zoom();\n\n\t\tlet scale = self.snapped_zoom(zoom);\n\t\tlet scale_vec = if ptz.flip { DVec2::new(-scale, scale) } else { DVec2::splat(scale) };\n\t\tlet scaled_center = viewport_center / scale_vec;\n\n\t\t// Try to avoid fractional coordinates to reduce anti aliasing.\n\t\tlet rounded_pan = ((pan + scaled_center) * scale).round() / scale - scaled_center;\n\n\t\t// TODO: replace with DAffine2::from_scale_angle_translation and fix the errors\n\t\tlet offset_transform = DAffine2::from_translation(scaled_center);\n\t\tlet scale_transform = DAffine2::from_scale(scale_vec);\n\t\tlet angle_transform = DAffine2::from_angle(self.snapped_tilt(tilt));\n\t\tlet translation_transform = DAffine2::from_translation(rounded_pan);\n\t\tscale_transform * offset_transform * angle_transform * translation_transform\n\t}\n\n\tpub fn center_zoom(&self, viewport_bounds: DVec2, zoom_factor: f64, mouse: DVec2) -> Message {\n\t\tlet new_viewport_bounds = viewport_bounds / zoom_factor;\n\t\tlet delta_size = viewport_bounds - new_viewport_bounds;\n\t\tlet mouse_fraction = mouse / viewport_bounds;\n\t\tlet delta = delta_size * (DVec2::splat(0.5) - mouse_fraction);\n\t\tNavigationMessage::CanvasPan { delta }.into()\n\t}\n\n\tpub fn clamp_zoom(zoom: f64, document_bounds: Option<[DVec2; 2]>, old_zoom: f64, viewport: &ViewportMessageHandler) -> f64 {\n\t\tlet document_size = (document_bounds.map(|[min, max]| max - min).unwrap_or_default() / old_zoom) * zoom;\n\t\tlet scale_factor = (document_size / viewport.size().into_dvec2()).max_element();\n\n\t\tif scale_factor <= f64::EPSILON * 100. || !scale_factor.is_finite() || scale_factor >= VIEWPORT_ZOOM_MIN_FRACTION_COVER {\n\t\t\treturn 1.;\n\t\t}\n\n\t\tVIEWPORT_ZOOM_MIN_FRACTION_COVER / scale_factor\n\t}\n}\n\npub fn snapped_zoom(navigation_operation: &NavigationOperation, zoom: f64) -> f64 {\n\tif matches!(navigation_operation, NavigationOperation::Zoom { snap: true, .. }) {\n\t\t*VIEWPORT_ZOOM_LEVELS.iter().min_by(|a, b| (**a - zoom).abs().partial_cmp(&(**b - zoom).abs()).unwrap()).unwrap_or(&zoom)\n\t} else {\n\t\tzoom\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/navigation/utility_types.rs",
    "content": "use glam::DVec2;\n\n#[derive(Debug, Default, Clone, PartialEq, serde::Serialize, serde::Deserialize)]\npub enum NavigationOperation {\n\t#[default]\n\tNone,\n\tPan {\n\t\tpan_original_for_abort: DVec2,\n\t},\n\tTilt {\n\t\ttilt_original_for_abort: f64,\n\t\ttilt_raw_not_snapped: f64,\n\t\tsnap: bool,\n\t},\n\tZoom {\n\t\tzoom_raw_not_snapped: f64,\n\t\tzoom_original_for_abort: f64,\n\t\tsnap: bool,\n\t},\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs",
    "content": "use super::DocumentNodeDefinition;\nuse crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;\nuse crate::messages::portfolio::document::utility_types::network_interface::{DocumentNodePersistentMetadata, InputMetadata, NodeTemplate, WidgetOverride};\nuse graph_craft::document::*;\nuse graphene_std::registry::*;\nuse graphene_std::*;\nuse std::collections::{HashMap, HashSet};\n\npub(super) fn post_process_nodes(custom: Vec<DocumentNodeDefinition>) -> HashMap<DefinitionIdentifier, DocumentNodeDefinition> {\n\t// Create hashmap for the protonodes added by the macro.\n\tlet mut definitions_map = HashMap::new();\n\t// First remove the custom protonodes and add them to the definitions map since they contain different metadata\n\t// from the macro and must be inserted first so that network nodes which reference them use the correct metadata.\n\tlet network_nodes = custom\n\t\t.into_iter()\n\t\t.filter_map(|definition| {\n\t\t\tif let DocumentNodeImplementation::ProtoNode(proto_node_identifier) = &definition.node_template.document_node.implementation {\n\t\t\t\tdefinitions_map.insert(DefinitionIdentifier::ProtoNode(proto_node_identifier.clone()), definition);\n\t\t\t\treturn None;\n\t\t\t};\n\t\t\tSome(definition)\n\t\t})\n\t\t.collect::<Vec<_>>();\n\n\t// Add the rest of the protonodes from the macro\n\tlet node_registry = NODE_REGISTRY.lock().unwrap();\n\tfor (id, metadata) in NODE_METADATA.lock().unwrap().iter() {\n\t\tlet identifier = DefinitionIdentifier::ProtoNode(id.clone());\n\t\tif definitions_map.contains_key(&identifier) {\n\t\t\tcontinue;\n\t\t};\n\t\tlet NodeMetadata {\n\t\t\tdisplay_name,\n\t\t\tcategory,\n\t\t\tfields,\n\t\t\tdescription,\n\t\t\tproperties,\n\t\t\tcontext_features,\n\t\t} = metadata;\n\n\t\tlet Some(implementations) = &node_registry.get(id) else { continue };\n\n\t\tlet first_node_io = implementations.first().map(|(_, node_io)| node_io).unwrap_or(const { &NodeIOTypes::empty() });\n\n\t\tlet valid_inputs: HashSet<_> = implementations.iter().map(|(_, node_io)| node_io.call_argument.clone()).collect();\n\t\tlet input_type = if valid_inputs.len() > 1 { &const { generic!(D) } } else { &first_node_io.call_argument };\n\n\t\tlet inputs = preprocessor::node_inputs(fields, first_node_io);\n\t\tdefinitions_map.insert(\n\t\t\tidentifier,\n\t\t\tDocumentNodeDefinition {\n\t\t\t\tidentifier: display_name,\n\t\t\t\tnode_template: NodeTemplate {\n\t\t\t\t\tdocument_node: DocumentNode {\n\t\t\t\t\t\tinputs,\n\t\t\t\t\t\tcall_argument: input_type.clone(),\n\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(id.clone()),\n\t\t\t\t\t\tvisible: true,\n\t\t\t\t\t\tskip_deduplication: false,\n\t\t\t\t\t\tcontext_features: ContextDependencies::from(context_features.as_slice()),\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t},\n\t\t\t\t\tpersistent_node_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t// TODO: Store information for input overrides in the node macro\n\t\t\t\t\t\tinput_metadata: fields\n\t\t\t\t\t\t\t.iter()\n\t\t\t\t\t\t\t.map(|f| match f.widget_override {\n\t\t\t\t\t\t\t\tRegistryWidgetOverride::None => (f.name, f.description).into(),\n\t\t\t\t\t\t\t\tRegistryWidgetOverride::Hidden => InputMetadata::with_name_description_override(f.name, f.description, WidgetOverride::Hidden),\n\t\t\t\t\t\t\t\tRegistryWidgetOverride::String(str) => InputMetadata::with_name_description_override(f.name, f.description, WidgetOverride::String(str.to_string())),\n\t\t\t\t\t\t\t\tRegistryWidgetOverride::Custom(str) => InputMetadata::with_name_description_override(f.name, f.description, WidgetOverride::Custom(str.to_string())),\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.collect(),\n\t\t\t\t\t\tlocked: false,\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tcategory,\n\t\t\t\tdescription: Cow::Borrowed(description),\n\t\t\t\tproperties: *properties,\n\t\t\t},\n\t\t);\n\t}\n\n\t// If any protonode does not have metadata then set its display name to its identifier string\n\tfor definition in definitions_map.values_mut() {\n\t\tlet metadata = NODE_METADATA.lock().unwrap();\n\t\tif let DocumentNodeImplementation::ProtoNode(id) = &definition.node_template.document_node.implementation\n\t\t\t&& !metadata.contains_key(id)\n\t\t{\n\t\t\tdefinition.node_template.persistent_node_metadata.display_name = definition.identifier.to_string();\n\t\t}\n\t}\n\n\t// Add the rest of the network nodes to the map and add the metadata for their internal protonodes\n\tfor mut network_node in network_nodes {\n\t\ttraverse_node(&network_node.node_template.document_node, &mut network_node.node_template.persistent_node_metadata, &definitions_map);\n\t\t// Set the reference to the node identifier\n\t\tif let Some(nested_metadata) = network_node.node_template.persistent_node_metadata.network_metadata.as_mut() {\n\t\t\tnested_metadata.persistent_metadata.reference = Some(network_node.identifier.to_string());\n\t\t\t// If it is not a merge node, then set the display name to the identifier/reference\n\t\t\tif network_node.identifier != \"Merge\" {\n\t\t\t\tnetwork_node.node_template.persistent_node_metadata.display_name = network_node.identifier.to_string();\n\t\t\t}\n\t\t}\n\t\tdefinitions_map.insert(DefinitionIdentifier::Network(network_node.identifier.to_string()), network_node);\n\t}\n\n\tdefinitions_map\n}\n\n/// Traverses a document node template and metadata in parallel to add metadata to the protonodes\nfn traverse_node(node: &DocumentNode, node_metadata: &mut DocumentNodePersistentMetadata, definitions_map: &HashMap<DefinitionIdentifier, DocumentNodeDefinition>) {\n\tmatch &node.implementation {\n\t\tDocumentNodeImplementation::Network(node_network) => {\n\t\t\tfor (nested_node_id, nested_node) in node_network.nodes.iter() {\n\t\t\t\tlet nested_metadata = node_metadata.network_metadata.as_mut().unwrap().persistent_metadata.node_metadata.get_mut(nested_node_id).unwrap();\n\t\t\t\ttraverse_node(nested_node, &mut nested_metadata.persistent_metadata, definitions_map);\n\t\t\t}\n\t\t}\n\t\tDocumentNodeImplementation::ProtoNode(id) => {\n\t\t\t// Set all the metadata except the position to the proto node information from the macro\n\t\t\t// TODO: Use options in the template to specify what you want to default and what you want to override\n\t\t\t// If this fails then the proto node id in the definition doesn't match what is generated by the macro\n\t\t\tlet Some(definition) = definitions_map.get(&DefinitionIdentifier::ProtoNode(id.clone())) else {\n\t\t\t\t// log::error!(\"Could not get definition for id {} when filling in protonode metadata for a custom node\", id.clone());\n\t\t\t\treturn;\n\t\t\t};\n\t\t\tlet mut new_metadata = definition.node_template.persistent_node_metadata.clone();\n\t\t\tnew_metadata.node_type_metadata = node_metadata.node_type_metadata.clone();\n\t\t\t*node_metadata = new_metadata\n\t\t}\n\t\tDocumentNodeImplementation::Extract => {}\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs",
    "content": "mod document_node_derive;\n\nuse super::node_properties::choice::enum_choice;\nuse super::node_properties::{self, ParameterWidgetsInfo};\nuse super::utility_types::FrontendNodeType;\nuse crate::messages::layout::utility_types::widget_prelude::*;\nuse crate::messages::portfolio::document::utility_types::network_interface::{\n\tDocumentNodeMetadata, DocumentNodePersistentMetadata, InputMetadata, NodeNetworkInterface, NodeNetworkMetadata, NodeNetworkPersistentMetadata, NodeTemplate, NodeTypePersistentMetadata,\n\tNumberInputSettings, Vec2InputSettings, WidgetOverride,\n};\nuse crate::messages::portfolio::utility_types::PersistentData;\nuse crate::messages::prelude::Message;\nuse crate::node_graph_executor::NodeGraphExecutor;\nuse glam::DVec2;\nuse graph_craft::ProtoNodeIdentifier;\nuse graph_craft::concrete;\nuse graph_craft::document::value::*;\nuse graph_craft::document::*;\nuse graphene_std::brush::brush_cache::BrushCache;\nuse graphene_std::extract_xy::XY;\nuse graphene_std::raster::{CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, NoiseType, RedGreenBlueAlpha};\nuse graphene_std::raster_types::{CPU, Raster};\nuse graphene_std::table::Table;\n#[allow(unused_imports)]\nuse graphene_std::transform::Footprint;\nuse graphene_std::vector::Vector;\nuse graphene_std::*;\nuse serde_json::Value;\nuse std::collections::{HashMap, VecDeque};\n\npub struct NodePropertiesContext<'a> {\n\tpub persistent_data: &'a PersistentData,\n\tpub responses: &'a mut VecDeque<Message>,\n\tpub executor: &'a mut NodeGraphExecutor,\n\tpub network_interface: &'a mut NodeNetworkInterface,\n\tpub selection_network_path: &'a [NodeId],\n\tpub document_name: &'a str,\n}\n\nimpl NodePropertiesContext<'_> {\n\tpub fn call_widget_override(&mut self, node_id: &NodeId, index: usize) -> Option<Vec<LayoutGroup>> {\n\t\tlet input_properties_row = self.network_interface.persistent_input_metadata(node_id, index, self.selection_network_path)?;\n\t\tif let Some(widget_override) = &input_properties_row.widget_override {\n\t\t\tlet Some(widget_override_lambda) = INPUT_OVERRIDES.get(widget_override) else {\n\t\t\t\tlog::error!(\"Could not get widget override '{widget_override}' lambda in call_widget_override\");\n\t\t\t\treturn None;\n\t\t\t};\n\t\t\twidget_override_lambda(*node_id, index, self)\n\t\t\t\t.map_err(|error| log::error!(\"Error in widget override lambda: {error}\"))\n\t\t\t\t.ok()\n\t\t} else {\n\t\t\tNone\n\t\t}\n\t}\n}\n\n/// The key used to access definitions for a network node or proto node.\n/// For proto nodes, this is their [`ProtoNodeIdentifier`].\n/// For network nodes, it doesn't necessarily have to be the same as the network's display name, but it often is.\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)]\n#[serde(tag = \"type\", content = \"data\")]\npub enum DefinitionIdentifier {\n\tProtoNode(ProtoNodeIdentifier),\n\tNetwork(String),\n}\n\nimpl DefinitionIdentifier {\n\tpub fn implementation_name_from_identifier(&self) -> String {\n\t\tmatch self {\n\t\t\tDefinitionIdentifier::Network(name) => name.clone(),\n\t\t\tDefinitionIdentifier::ProtoNode(proto_node_identifier) => registry::NODE_METADATA\n\t\t\t\t.lock()\n\t\t\t\t.unwrap()\n\t\t\t\t.get(proto_node_identifier)\n\t\t\t\t.map(|metadata| metadata.display_name.to_string())\n\t\t\t\t.unwrap_or_else(|| {\n\t\t\t\t\tlet mut last_segment = proto_node_identifier.as_str().split(\"::\").last().unwrap_or_default().to_string();\n\t\t\t\t\tlast_segment = last_segment.strip_suffix(\"Node\").unwrap_or(&last_segment).to_string();\n\t\t\t\t\tlast_segment\n\t\t\t\t}),\n\t\t}\n\t}\n\n\tpub fn serialized(&self) -> String {\n\t\tmatch self {\n\t\t\tDefinitionIdentifier::ProtoNode(id) => format!(\"PROTONODE:{}\", id.as_str()),\n\t\t\tDefinitionIdentifier::Network(data) => format!(\"NETWORK:{}\", data),\n\t\t}\n\t}\n}\n\nimpl From<Value> for DefinitionIdentifier {\n\tfn from(value: Value) -> Self {\n\t\tlet s = value.as_str().expect(\"DefinitionIdentifier value must be a string\");\n\n\t\tmatch s.split_once(':') {\n\t\t\tSome((\"PROTONODE\", data)) => DefinitionIdentifier::ProtoNode(ProtoNodeIdentifier::with_owned_string(data.to_string())),\n\t\t\tSome((\"NETWORK\", data)) => DefinitionIdentifier::Network(data.to_string()),\n\t\t\tother => panic!(\"Unknown `DefinitionIdentifier` type. Found `{other:?}`.\"),\n\t\t}\n\t}\n}\n\n/// Acts as a description for a [DocumentNode] before it gets instantiated as one.\n#[derive(Debug, Clone)]\npub struct DocumentNodeDefinition {\n\t/// Used to create the [`DefinitionIdentifier::Network`] identifier.\n\tpub identifier: &'static str,\n\n\t/// All data required to construct a [`DocumentNode`] and [`DocumentNodeMetadata`]\n\tpub node_template: NodeTemplate,\n\n\t/// Definition specific data. In order for the editor to access this data, the reference will be used.\n\tpub category: &'static str,\n\n\t/// User-facing description of the node's functionality.\n\tpub description: Cow<'static, str>,\n\n\t/// Node level overrides are stored based on the reference, not the instance. If the node is modified such that it becomes a local version\n\t/// (for example an input is added), the reference is no longer to the definition, and the overrides are lost.\n\t/// Most nodes should not use node based properties, since they are less flexible than input level properties.\n\tpub properties: Option<&'static str>,\n}\n\n// We use the once_cell to use the document node definitions throughout the editor without passing a reference\n// TODO: If dynamic node library is required, use a Mutex as well\n// TODO: Replace with `core::cell::LazyCell` (<https://doc.rust-lang.org/core/cell/struct.LazyCell.html>) or similar\nstatic DOCUMENT_NODE_TYPES: once_cell::sync::Lazy<HashMap<DefinitionIdentifier, DocumentNodeDefinition>> = once_cell::sync::Lazy::new(document_node_definitions);\n\n/// Defines the \"signature\" or \"header file\"-like metadata for the document nodes, but not the implementation (which is defined in the node registry).\n/// The [`DocumentNode`] is the instance while these [`DocumentNodeDefinition`]s are the \"classes\" or \"blueprints\" from which the instances are built.\n/// Only the position can be set for protonodes within a definition. The rest of the metadata comes from the node macro in NODE_METADATA\nfn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefinition> {\n\tlet custom = vec![\n\t\t// TODO: Auto-generate this from its proto node macro\n\t\tDocumentNodeDefinition {\n\t\t\tidentifier: \"Passthrough\",\n\t\t\tcategory: \"General\",\n\t\t\tnode_template: NodeTemplate {\n\t\t\t\tdocument_node: DocumentNode {\n\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER),\n\t\t\t\t\tinputs: vec![NodeInput::value(TaggedValue::None, true)],\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t\tpersistent_node_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\tinput_metadata: vec![(\"Content\", \"TODO\").into()],\n\t\t\t\t\toutput_names: vec![\"Out\".to_string()],\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t},\n\t\t\tdescription: Cow::Borrowed(\"Passes-through the input value without changing it. This is useful for rerouting wires for organization purposes.\"),\n\t\t\tproperties: None,\n\t\t},\n\t\t// TODO: Auto-generate this from its proto node macro\n\t\tDocumentNodeDefinition {\n\t\t\tidentifier: \"Monitor\",\n\t\t\tcategory: \"Debug\",\n\t\t\tnode_template: NodeTemplate {\n\t\t\t\tdocument_node: DocumentNode {\n\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),\n\t\t\t\t\tinputs: vec![NodeInput::value(TaggedValue::None, true)],\n\t\t\t\t\tcall_argument: generic!(T),\n\t\t\t\t\tskip_deduplication: true,\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t\tpersistent_node_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\tinput_metadata: vec![(\"In\", \"TODO\").into()],\n\t\t\t\t\toutput_names: vec![\"Out\".to_string()],\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t},\n\t\t\tdescription: Cow::Borrowed(\"The Monitor node is used by the editor to access the data flowing through it.\"),\n\t\t\tproperties: Some(\"monitor_properties\"),\n\t\t},\n\t\tDocumentNodeDefinition {\n\t\t\tidentifier: \"Default Network\",\n\t\t\tcategory: \"General\",\n\t\t\tnode_template: NodeTemplate {\n\t\t\t\tdocument_node: DocumentNode {\n\t\t\t\t\timplementation: DocumentNodeImplementation::Network(NodeNetwork::default()),\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t\tpersistent_node_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\tnetwork_metadata: Some(NodeNetworkMetadata::default()),\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t},\n\t\t\tdescription: Cow::Borrowed(\"An empty node network you can use to create your own custom nodes.\"),\n\t\t\tproperties: None,\n\t\t},\n\t\t// TODO: Auto-generate this from its proto node macro\n\t\tDocumentNodeDefinition {\n\t\t\tidentifier: \"Cache\",\n\t\t\tcategory: \"General\",\n\t\t\tnode_template: NodeTemplate {\n\t\t\t\tdocument_node: DocumentNode {\n\t\t\t\t\tinputs: vec![NodeInput::value(TaggedValue::None, true)],\n\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),\n\t\t\t\t\tcall_argument: generic!(T),\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t\tpersistent_node_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\tinput_metadata: vec![(\"Data\", \"TODO\").into()],\n\t\t\t\t\toutput_names: vec![\"Data\".to_string()],\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t},\n\t\t\tdescription: Cow::Borrowed(\n\t\t\t\t\"Improves rendering performance if used in rare circumstances where automatic caching is not yet advanced enough to handle the situation.\\n\\\n\t\t\t\t\\n\\\n\t\t\t\tStores the last evaluated data that flowed through this node, and immediately returns that data on subsequent renders if the context has not changed.\",\n\t\t\t),\n\t\t\tproperties: None,\n\t\t},\n\t\tDocumentNodeDefinition {\n\t\t\tidentifier: \"Merge\",\n\t\t\tcategory: \"General\",\n\t\t\tnode_template: NodeTemplate {\n\t\t\t\tdocument_node: DocumentNode {\n\t\t\t\t\timplementation: DocumentNodeImplementation::Network(NodeNetwork {\n\t\t\t\t\t\texports: vec![NodeInput::node(NodeId(4), 0)],\n\t\t\t\t\t\tnodes: [\n\t\t\t\t\t\t\t// Primary (bottom) input type coercion\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::import(generic!(T), 0)],\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(graphic::to_graphic::IDENTIFIER),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// Secondary (left) input type coercion\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::import(generic!(T), 1)],\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(graphic::wrap_graphic::IDENTIFIER),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// Store the ID of the parent node (which encapsulates this sub-network) in each row we are extending the table with.\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::node(NodeId(1), 0), NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath)],\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(graphic::source_node_id::IDENTIFIER),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// The monitor node is used to display a thumbnail in the UI\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::node(NodeId(2), 0)],\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),\n\t\t\t\t\t\t\t\tskip_deduplication: true,\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\tcall_argument: generic!(T),\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(3), 0)],\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(graphic::extend::IDENTIFIER),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]\n\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t.map(|(id, node)| (NodeId(id as u64), node))\n\t\t\t\t\t\t.collect(),\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t}),\n\t\t\t\t\tinputs: vec![\n\t\t\t\t\t\tNodeInput::value(TaggedValue::Graphic(Default::default()), true),\n\t\t\t\t\t\tNodeInput::value(TaggedValue::Graphic(Default::default()), true),\n\t\t\t\t\t],\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t\tpersistent_node_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\tinput_metadata: vec![(\"Base\", \"TODO\").into(), (\"Content\", \"TODO\").into()],\n\t\t\t\t\toutput_names: vec![\"Out\".to_string()],\n\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::layer(IVec2::new(0, 0)),\n\t\t\t\t\tnetwork_metadata: Some(NodeNetworkMetadata {\n\t\t\t\t\t\tpersistent_metadata: NodeNetworkPersistentMetadata {\n\t\t\t\t\t\t\tnode_metadata: [\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-21, -3)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-21, -1)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-14, -1)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-7, -1)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, -3)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t\t.map(|(id, node)| (NodeId(id as u64), node))\n\t\t\t\t\t\t\t.collect(),\n\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t},\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t}),\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t},\n\t\t\tdescription: Cow::Borrowed(\"Merges the provided content as a new element in the graphic table that represents a layer compositing stack.\"),\n\t\t\tproperties: None,\n\t\t},\n\t\tDocumentNodeDefinition {\n\t\t\tidentifier: \"Artboard\",\n\t\t\tcategory: \"General\",\n\t\t\tnode_template: NodeTemplate {\n\t\t\t\tdocument_node: DocumentNode {\n\t\t\t\t\timplementation: DocumentNodeImplementation::Network(NodeNetwork {\n\t\t\t\t\t\texports: vec![NodeInput::node(NodeId(3), 0)],\n\t\t\t\t\t\tnodes: [\n\t\t\t\t\t\t\t// Ensure this ID is kept in sync with the ID in set_alias so that the name input is kept in sync with the alias\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\tcall_argument: generic!(T),\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(artboard::create_artboard::IDENTIFIER),\n\t\t\t\t\t\t\t\tinputs: vec![\n\t\t\t\t\t\t\t\t\tNodeInput::import(concrete!(TaggedValue), 1),\n\t\t\t\t\t\t\t\t\tNodeInput::value(TaggedValue::String(String::from(\"Artboard\")), false),\n\t\t\t\t\t\t\t\t\tNodeInput::import(concrete!(TaggedValue), 2),\n\t\t\t\t\t\t\t\t\tNodeInput::import(concrete!(TaggedValue), 3),\n\t\t\t\t\t\t\t\t\tNodeInput::import(concrete!(TaggedValue), 4),\n\t\t\t\t\t\t\t\t\tNodeInput::import(concrete!(TaggedValue), 5),\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// Store the ID of the parent node (which encapsulates this sub-network) in each row we are extending the table with.\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath)],\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(graphic::source_node_id::IDENTIFIER),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// The monitor node is used to display a thumbnail in the UI.\n\t\t\t\t\t\t\t// TODO: Check if thumbnail is reversed\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::node(NodeId(1), 0)],\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),\n\t\t\t\t\t\t\t\tcall_argument: generic!(T),\n\t\t\t\t\t\t\t\tskip_deduplication: true,\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\tinputs: vec![\n\t\t\t\t\t\t\t\t\tNodeInput::import(graphene_std::Type::Fn(Box::new(concrete!(Context)), Box::new(concrete!(Table<Artboard>))), 0),\n\t\t\t\t\t\t\t\t\tNodeInput::node(NodeId(2), 0),\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(graphic::extend::IDENTIFIER),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]\n\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t.map(|(id, node)| (NodeId(id as u64), node))\n\t\t\t\t\t\t.collect(),\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t}),\n\t\t\t\t\tinputs: vec![\n\t\t\t\t\t\tNodeInput::value(TaggedValue::Artboard(Default::default()), true),\n\t\t\t\t\t\tNodeInput::value(TaggedValue::Graphic(Default::default()), true),\n\t\t\t\t\t\tNodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),\n\t\t\t\t\t\tNodeInput::value(TaggedValue::DVec2(DVec2::new(1920., 1080.)), false),\n\t\t\t\t\t\tNodeInput::value(TaggedValue::Color(Table::new_from_element(Color::WHITE)), false),\n\t\t\t\t\t\tNodeInput::value(TaggedValue::Bool(false), false),\n\t\t\t\t\t],\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t\tpersistent_node_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\tinput_metadata: vec![\n\t\t\t\t\t\t(\"Base\", \"TODO\").into(),\n\t\t\t\t\t\tInputMetadata::with_name_description_override(\"Content\", \"TODO\", WidgetOverride::Hidden),\n\t\t\t\t\t\tInputMetadata::with_name_description_override(\n\t\t\t\t\t\t\t\"Location\",\n\t\t\t\t\t\t\t\"TODO\",\n\t\t\t\t\t\t\tWidgetOverride::Vec2(Vec2InputSettings {\n\t\t\t\t\t\t\t\tx: \"X\".to_string(),\n\t\t\t\t\t\t\t\ty: \"Y\".to_string(),\n\t\t\t\t\t\t\t\tunit: \" px\".to_string(),\n\t\t\t\t\t\t\t\tis_integer: true,\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t\tInputMetadata::with_name_description_override(\n\t\t\t\t\t\t\t\"Dimensions\",\n\t\t\t\t\t\t\t\"TODO\",\n\t\t\t\t\t\t\tWidgetOverride::Vec2(Vec2InputSettings {\n\t\t\t\t\t\t\t\tx: \"W\".to_string(),\n\t\t\t\t\t\t\t\ty: \"H\".to_string(),\n\t\t\t\t\t\t\t\tunit: \" px\".to_string(),\n\t\t\t\t\t\t\t\tis_integer: true,\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t\tInputMetadata::with_name_description_override(\"Background\", \"TODO\", WidgetOverride::Custom(\"artboard_background\".to_string())),\n\t\t\t\t\t\t(\"Clip\", \"TODO\").into(),\n\t\t\t\t\t],\n\t\t\t\t\toutput_names: vec![\"Out\".to_string()],\n\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::layer(IVec2::new(0, 0)),\n\t\t\t\t\tnetwork_metadata: Some(NodeNetworkMetadata {\n\t\t\t\t\t\tpersistent_metadata: NodeNetworkPersistentMetadata {\n\t\t\t\t\t\t\tnode_metadata: [\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-21, -3)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-14, -3)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-7, -3)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, -4)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t\t.map(|(id, node)| (NodeId(id as u64), node))\n\t\t\t\t\t\t\t.collect(),\n\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t},\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t}),\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t},\n\t\t\tdescription: Cow::Borrowed(\"Creates a new Artboard which can be used as a working surface.\"),\n\t\t\tproperties: None,\n\t\t},\n\t\tDocumentNodeDefinition {\n\t\t\tidentifier: \"Blend Shapes\",\n\t\t\tcategory: \"Vector\",\n\t\t\t// [IMPORTS]2 -> 0[0:Floor]\n\t\t\t// [0:Floor]0 -> 0[1:Subtract]\n\t\t\t// \"1: f64\" -> 1[1:Subtract]\n\t\t\t// \"(): ()\" -> 0[2:Read Index]\n\t\t\t// \"0: u32\" -> 1[2:Read Index]\n\t\t\t// [2:Read Index]0 -> 0[3:Divide]\n\t\t\t// [1:Subtract]0 -> 1[3:Divide]\n\t\t\t// [IMPORTS]1 -> 0[4:Position on Path]\n\t\t\t// [3:Divide]0 -> 1[4:Position on Path]\n\t\t\t// \"false: bool\" -> 2[4:Position on Path]\n\t\t\t// \"false: bool\" -> 3[4:Position on Path]\n\t\t\t// \"(): ()\" -> 0[5:Read Vector]\n\t\t\t// [5:Read Vector]0 -> 0[6:Reset Transform]\n\t\t\t// \"true: bool\" -> 1[6:Reset Transform]\n\t\t\t// \"false: bool\" -> 2[6:Reset Transform]\n\t\t\t// \"false: bool\" -> 3[6:Reset Transform]\n\t\t\t// [12:Flatten Vector]0 -> 0[7:Map]\n\t\t\t// [6:Reset Transform]0 -> 1[7:Map]\n\t\t\t// [7:Map]0 -> 0[8:Morph]\n\t\t\t// [15:Multiply]0 -> 1[8:Morph]\n\t\t\t// [8:Morph]0 -> 0[9:Transform]\n\t\t\t// [4:Position on Path]0 -> 1[9:Transform]\n\t\t\t// \"0: f64\" -> 2[9:Transform]\n\t\t\t// \"(0, 0): DVec2\" -> 3[9:Transform]\n\t\t\t// \"(0, 0): DVec2\" -> 4[9:Transform]\n\t\t\t// [IMPORTS]1 -> 0[10:Count Points]\n\t\t\t// [10:Count Points]0 -> 0[11:Equals]\n\t\t\t// [13:Count Elements]0 -> 1[11:Equals]\n\t\t\t// [IMPORTS]0 -> 0[12:Flatten Vector]\n\t\t\t// [12:Flatten Vector]0 -> 0[13:Count Elements]\n\t\t\t// [13:Count Elements]0 -> 0[14:Subtract]\n\t\t\t// \"1: f64\" -> 1[14:Subtract]\n\t\t\t// [3:Divide]0 -> 0[15:Multiply]\n\t\t\t// [14:Subtract]0 -> 1[15:Multiply]\n\t\t\t// [12:Flatten Vector]0 -> 0[16:Morph]\n\t\t\t// [15:Multiply]0 -> 1[16:Morph]\n\t\t\t// [11:Equals]0 -> 0[17:Switch]\n\t\t\t// [9:Transform]0 -> 1[17:Switch]\n\t\t\t// [16:Morph]0 -> 2[17:Switch]\n\t\t\t// [17:Switch]0 -> 0[18:Repeat]\n\t\t\t// [0:Floor]0 -> 1[18:Repeat]\n\t\t\t// [IMPORTS]3 -> 2[18:Repeat]\n\t\t\t// [18:Repeat]0 -> 0[EXPORTS]\n\t\t\tnode_template: NodeTemplate {\n\t\t\t\tdocument_node: DocumentNode {\n\t\t\t\t\timplementation: DocumentNodeImplementation::Network(NodeNetwork {\n\t\t\t\t\t\texports: vec![NodeInput::node(NodeId(18), 0)],\n\t\t\t\t\t\tnodes: [\n\t\t\t\t\t\t\t// 0: Floor\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(math_nodes::floor::IDENTIFIER),\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::import(concrete!(f64), 2)],\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// 1: Subtract\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(math_nodes::subtract::IDENTIFIER),\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::value(TaggedValue::F64(1.), false)],\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// 2: Read Index\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(context::read_index::IDENTIFIER),\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::value(TaggedValue::U32(0), false)],\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// 3: Divide\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(math_nodes::divide::IDENTIFIER),\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::node(NodeId(2), 0), NodeInput::node(NodeId(1), 0)],\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// 4: Position on Path\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(vector_nodes::position_on_path::IDENTIFIER),\n\t\t\t\t\t\t\t\tinputs: vec![\n\t\t\t\t\t\t\t\t\tNodeInput::import(generic!(T), 1),\n\t\t\t\t\t\t\t\t\tNodeInput::node(NodeId(3), 0),\n\t\t\t\t\t\t\t\t\tNodeInput::value(TaggedValue::Bool(false), false),\n\t\t\t\t\t\t\t\t\tNodeInput::value(TaggedValue::Bool(false), false),\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// 5: Read Vector\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(context::read_vector::IDENTIFIER),\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::value(TaggedValue::None, false)],\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// 6: Reset Transform\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(transform_nodes::reset_transform::IDENTIFIER),\n\t\t\t\t\t\t\t\tinputs: vec![\n\t\t\t\t\t\t\t\t\tNodeInput::node(NodeId(5), 0),\n\t\t\t\t\t\t\t\t\tNodeInput::value(TaggedValue::Bool(true), false),\n\t\t\t\t\t\t\t\t\tNodeInput::value(TaggedValue::Bool(false), false),\n\t\t\t\t\t\t\t\t\tNodeInput::value(TaggedValue::Bool(false), false),\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// 7: Map\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(graphic::map::IDENTIFIER),\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::node(NodeId(12), 0), NodeInput::node(NodeId(6), 0)],\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// 8: Morph\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(vector::morph::IDENTIFIER),\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::node(NodeId(7), 0), NodeInput::node(NodeId(15), 0)],\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// 9: Transform\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(transform_nodes::transform::IDENTIFIER),\n\t\t\t\t\t\t\t\tinputs: vec![\n\t\t\t\t\t\t\t\t\tNodeInput::node(NodeId(8), 0),\n\t\t\t\t\t\t\t\t\tNodeInput::node(NodeId(4), 0),\n\t\t\t\t\t\t\t\t\tNodeInput::value(TaggedValue::F64(0.), false),\n\t\t\t\t\t\t\t\t\tNodeInput::value(TaggedValue::DVec2(DVec2::ONE), false),\n\t\t\t\t\t\t\t\t\tNodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// 10: Count Points\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(vector_nodes::count_points::IDENTIFIER),\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::import(generic!(T), 1)],\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// 11: Equals\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(math_nodes::equals::IDENTIFIER),\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::node(NodeId(10), 0), NodeInput::node(NodeId(13), 0)],\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// 12: Flatten Vector\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(graphic_nodes::graphic::flatten_vector::IDENTIFIER),\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::import(generic!(T), 0)],\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// 13: Count Elements\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(vector::count_elements::IDENTIFIER),\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::node(NodeId(12), 0)],\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// 14: Subtract\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(math_nodes::subtract::IDENTIFIER),\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::node(NodeId(13), 0), NodeInput::value(TaggedValue::F64(1.), false)],\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// 15: Multiply\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(math_nodes::multiply::IDENTIFIER),\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::node(NodeId(3), 0), NodeInput::node(NodeId(14), 0)],\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// 16: Morph\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(vector::morph::IDENTIFIER),\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::node(NodeId(12), 0), NodeInput::node(NodeId(15), 0)],\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// 17: Switch\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(logic::switch::IDENTIFIER),\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::node(NodeId(11), 0), NodeInput::node(NodeId(9), 0), NodeInput::node(NodeId(16), 0)],\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// 18: Repeat\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(repeat_nodes::repeat::IDENTIFIER),\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::node(NodeId(17), 0), NodeInput::node(NodeId(0), 0), NodeInput::import(generic!(T), 3)],\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]\n\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t.map(|(id, node)| (NodeId(id as u64), node))\n\t\t\t\t\t\t.collect(),\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t}),\n\t\t\t\t\tinputs: vec![\n\t\t\t\t\t\tNodeInput::value(TaggedValue::Vector(Default::default()), true),\n\t\t\t\t\t\tNodeInput::value(TaggedValue::Vector(Default::default()), true),\n\t\t\t\t\t\tNodeInput::value(TaggedValue::F64(10.), false),\n\t\t\t\t\t\tNodeInput::value(TaggedValue::Bool(Default::default()), false),\n\t\t\t\t\t],\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t\tpersistent_node_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\tinput_metadata: vec![(\"Content\", \"TODO\").into(), (\"Path\", \"TODO\").into(), (\"Count\", \"TODO\").into(), (\"Reverse\", \"TODO\").into()],\n\t\t\t\t\toutput_names: vec![\"Out\".to_string()],\n\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),\n\t\t\t\t\tnetwork_metadata: Some(NodeNetworkMetadata {\n\t\t\t\t\t\tpersistent_metadata: NodeNetworkPersistentMetadata {\n\t\t\t\t\t\t\tnode_metadata: [\n\t\t\t\t\t\t\t\t// 0: Floor\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t// 1: Subtract\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, -1)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t// 2: Read Index\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, -2)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t// 3: Divide\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, -2)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t// 4: Position on Path\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(28, -3)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t// 5: Read Vector\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 2)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t// 6: Reset Transform\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 2)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t// 7: Map\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(21, 1)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t// 8: Morph\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(28, 1)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t// 9: Transform\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(35, 1)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t// 10: Count Points\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 4)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t// 11: Equals\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 4)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t// 12: Flatten Vector\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 6)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t// 13: Count Elements\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 8)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t// 14: Subtract\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 8)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t// 15: Multiply\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(21, 7)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t// 16: Morph\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(28, 6)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t// 17: Switch\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(42, 4)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t// 18: Repeat\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(49, -1)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t\t.map(|(id, node)| (NodeId(id as u64), node))\n\t\t\t\t\t\t\t.collect(),\n\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t},\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t}),\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t},\n\t\t\tdescription: Cow::Borrowed(\"TODO\"),\n\t\t\tproperties: None,\n\t\t},\n\t\tDocumentNodeDefinition {\n\t\t\tidentifier: \"Origins to Polyline\",\n\t\t\tcategory: \"Vector\",\n\t\t\t// \"(): ()\" -> 0[0:Read Vector]\n\t\t\t// [0:Read Vector]0 -> 0[1:Extract Transform]\n\t\t\t// [1:Extract Transform]0 -> 0[2:Decompose Translation]\n\t\t\t// [2:Decompose Translation]0 -> 0[3:Vec2 to Point]\n\t\t\t// [IMPORTS]0 -> 0[4:Flatten Vector]\n\t\t\t// [4:Flatten Vector]0 -> 0[5:Map]\n\t\t\t// [3:Vec2 to Point]0 -> 1[5:Map]\n\t\t\t// [5:Map]0 -> 0[6: Flatten Path]\n\t\t\t// [6:Flatten Path]0 -> 0[7:Points to Polyline]\n\t\t\t// \"false: bool\" -> 1[7:Points to Polyline]\n\t\t\t// [7:Points to Polyline]0 -> 0[EXPORTS]\n\t\t\tnode_template: NodeTemplate {\n\t\t\t\tdocument_node: DocumentNode {\n\t\t\t\t\timplementation: DocumentNodeImplementation::Network(NodeNetwork {\n\t\t\t\t\t\texports: vec![NodeInput::node(NodeId(7), 0)],\n\t\t\t\t\t\tnodes: [\n\t\t\t\t\t\t\t// 0: Read Vector\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(context::read_vector::IDENTIFIER),\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::value(TaggedValue::None, false)],\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// 1: Extract Transform\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(transform_nodes::extract_transform::IDENTIFIER),\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::node(NodeId(0), 0)],\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// 2: Decompose Translation\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(transform_nodes::decompose_translation::IDENTIFIER),\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::node(NodeId(1), 0)],\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// 3: Vec2 to Point\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(vector_nodes::vec_2_to_point::IDENTIFIER),\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::node(NodeId(2), 0)],\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// 4: Flatten Vector\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(graphic_nodes::graphic::flatten_vector::IDENTIFIER),\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::import(generic!(T), 0)],\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// 5: Map\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(graphic::map::IDENTIFIER),\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::node(NodeId(4), 0), NodeInput::node(NodeId(3), 0)],\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// 6: Flatten Path\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(vector::flatten_path::IDENTIFIER),\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::node(NodeId(5), 0)],\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// 7: Points to Polyline\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(vector::points_to_polyline::IDENTIFIER),\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::node(NodeId(6), 0), NodeInput::value(TaggedValue::Bool(false), false)],\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]\n\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t.map(|(id, node)| (NodeId(id as u64), node))\n\t\t\t\t\t\t.collect(),\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t}),\n\t\t\t\t\tinputs: vec![NodeInput::value(TaggedValue::Vector(Default::default()), true)],\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t\tpersistent_node_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\tinput_metadata: vec![(\"Vector\", \"TODO\").into()],\n\t\t\t\t\toutput_names: vec![\"Vector\".to_string()],\n\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),\n\t\t\t\t\tnetwork_metadata: Some(NodeNetworkMetadata {\n\t\t\t\t\t\tpersistent_metadata: NodeNetworkPersistentMetadata {\n\t\t\t\t\t\t\tnode_metadata: [\n\t\t\t\t\t\t\t\t// 0: Read Vector\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 1)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t// 1: Extract Transform\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 1)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t// 2: Decompose Transform\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 1)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t// 3: Vec2 to Point\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(21, 1)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t// 4: Flatten Vector\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(21, 0)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t// 5: Map\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(28, 0)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t// 6: Flatten Path\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(35, 0)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t// 7: Points to Polyline\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(42, 0)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t\t.map(|(id, node)| (NodeId(id as u64), node))\n\t\t\t\t\t\t\t.collect(),\n\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t},\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t}),\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t},\n\t\t\tdescription: Cow::Borrowed(\"TODO\"),\n\t\t\tproperties: None,\n\t\t},\n\t\tDocumentNodeDefinition {\n\t\t\tidentifier: \"Load Image\",\n\t\t\tcategory: \"Web Request\",\n\t\t\tnode_template: NodeTemplate {\n\t\t\t\tdocument_node: DocumentNode {\n\t\t\t\t\timplementation: DocumentNodeImplementation::Network(NodeNetwork {\n\t\t\t\t\t\texports: vec![NodeInput::node(NodeId(1), 0)],\n\t\t\t\t\t\tnodes: [\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::scope(\"editor-api\"), NodeInput::import(concrete!(String), 1)],\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::load_resource::IDENTIFIER),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::node(NodeId(0), 0)],\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::decode_image::IDENTIFIER),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]\n\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t.map(|(id, node)| (NodeId(id as u64), node))\n\t\t\t\t\t\t.collect(),\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t}),\n\t\t\t\t\tinputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::value(TaggedValue::String(\"graphite:null\".to_string()), false)],\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t\tpersistent_node_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\tinput_metadata: vec![(\"Empty\", \"TODO\").into(), (\"URL\", \"TODO\").into()],\n\t\t\t\t\toutput_names: vec![\"Image\".to_string()],\n\t\t\t\t\tnetwork_metadata: Some(NodeNetworkMetadata {\n\t\t\t\t\t\tpersistent_metadata: NodeNetworkPersistentMetadata {\n\t\t\t\t\t\t\tnode_metadata: [\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t\t.map(|(id, node)| (NodeId(id as u64), node))\n\t\t\t\t\t\t\t.collect(),\n\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t},\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t}),\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t},\n\t\t\tdescription: Cow::Borrowed(\"Loads an image from a given URL.\"),\n\t\t\tproperties: None,\n\t\t},\n\t\t#[cfg(all(feature = \"gpu\", target_family = \"wasm\"))]\n\t\tDocumentNodeDefinition {\n\t\t\tidentifier: \"Rasterize\",\n\t\t\tcategory: \"Raster\",\n\t\t\tnode_template: NodeTemplate {\n\t\t\t\tdocument_node: DocumentNode {\n\t\t\t\t\timplementation: DocumentNodeImplementation::Network(NodeNetwork {\n\t\t\t\t\t\texports: vec![NodeInput::node(NodeId(2), 0)],\n\t\t\t\t\t\tnodes: [\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::scope(\"editor-api\")],\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::create_surface::IDENTIFIER),\n\t\t\t\t\t\t\t\tskip_deduplication: true,\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::node(NodeId(0), 0)],\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::import(generic!(T), 0), NodeInput::import(concrete!(Footprint), 1), NodeInput::node(NodeId(1), 0)],\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::rasterize::IDENTIFIER),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]\n\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t.map(|(id, node)| (NodeId(id as u64), node))\n\t\t\t\t\t\t.collect(),\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t}),\n\t\t\t\t\tinputs: vec![\n\t\t\t\t\t\tNodeInput::value(TaggedValue::Vector(Default::default()), true),\n\t\t\t\t\t\tNodeInput::value(\n\t\t\t\t\t\t\tTaggedValue::Footprint(Footprint {\n\t\t\t\t\t\t\t\ttransform: DAffine2::from_scale_angle_translation(DVec2::new(1000., 1000.), 0., DVec2::new(0., 0.)),\n\t\t\t\t\t\t\t\tresolution: UVec2::new(1000, 1000),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t),\n\t\t\t\t\t],\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t\tpersistent_node_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\tinput_metadata: vec![(\"Artwork\", \"TODO\").into(), (\"Footprint\", \"TODO\").into()],\n\t\t\t\t\toutput_names: vec![\"Canvas\".to_string()],\n\t\t\t\t\tnetwork_metadata: Some(NodeNetworkMetadata {\n\t\t\t\t\t\tpersistent_metadata: NodeNetworkPersistentMetadata {\n\t\t\t\t\t\t\tnode_metadata: [\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 2)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 2)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t\t.map(|(id, node)| (NodeId(id as u64), node))\n\t\t\t\t\t\t\t.collect(),\n\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t},\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t}),\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t},\n\t\t\tdescription: Cow::Borrowed(\"TODO\"),\n\t\t\tproperties: None,\n\t\t},\n\t\t// TODO: Auto-generate this from its proto node macro\n\t\tDocumentNodeDefinition {\n\t\t\tidentifier: \"Noise Pattern\",\n\t\t\tcategory: \"Raster: Pattern\",\n\t\t\tnode_template: NodeTemplate {\n\t\t\t\tdocument_node: DocumentNode {\n\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(graphene_std::raster_nodes::std_nodes::noise_pattern::IDENTIFIER),\n\t\t\t\t\tinputs: vec![\n\t\t\t\t\t\tNodeInput::value(TaggedValue::None, false),\n\t\t\t\t\t\tNodeInput::value(TaggedValue::Bool(true), false),\n\t\t\t\t\t\tNodeInput::value(TaggedValue::U32(0), false),\n\t\t\t\t\t\tNodeInput::value(TaggedValue::F64(10.), false),\n\t\t\t\t\t\tNodeInput::value(TaggedValue::NoiseType(NoiseType::default()), false),\n\t\t\t\t\t\tNodeInput::value(TaggedValue::DomainWarpType(DomainWarpType::default()), false),\n\t\t\t\t\t\tNodeInput::value(TaggedValue::F64(100.), false),\n\t\t\t\t\t\tNodeInput::value(TaggedValue::FractalType(FractalType::default()), false),\n\t\t\t\t\t\tNodeInput::value(TaggedValue::U32(3), false),\n\t\t\t\t\t\tNodeInput::value(TaggedValue::F64(2.), false),\n\t\t\t\t\t\tNodeInput::value(TaggedValue::F64(0.5), false),\n\t\t\t\t\t\tNodeInput::value(TaggedValue::F64(0.), false), // 0-1 range\n\t\t\t\t\t\tNodeInput::value(TaggedValue::F64(2.), false),\n\t\t\t\t\t\tNodeInput::value(TaggedValue::CellularDistanceFunction(CellularDistanceFunction::default()), false),\n\t\t\t\t\t\tNodeInput::value(TaggedValue::CellularReturnType(CellularReturnType::default()), false),\n\t\t\t\t\t\tNodeInput::value(TaggedValue::F64(1.), false),\n\t\t\t\t\t],\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t\tpersistent_node_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\tinput_metadata: vec![\n\t\t\t\t\t\t(\"Spacer\", \"TODO\").into(),\n\t\t\t\t\t\t(\"Clip\", \"TODO\").into(),\n\t\t\t\t\t\t(\"Seed\", \"TODO\").into(),\n\t\t\t\t\t\tInputMetadata::with_name_description_override(\"Scale\", \"TODO\", WidgetOverride::Custom(\"noise_properties_scale\".to_string())),\n\t\t\t\t\t\tInputMetadata::with_name_description_override(\"Noise Type\", \"TODO\", WidgetOverride::Custom(\"noise_properties_noise_type\".to_string())),\n\t\t\t\t\t\tInputMetadata::with_name_description_override(\"Domain Warp Type\", \"TODO\", WidgetOverride::Custom(\"noise_properties_domain_warp_type\".to_string())),\n\t\t\t\t\t\tInputMetadata::with_name_description_override(\"Domain Warp Amplitude\", \"TODO\", WidgetOverride::Custom(\"noise_properties_domain_warp_amplitude\".to_string())),\n\t\t\t\t\t\tInputMetadata::with_name_description_override(\"Fractal Type\", \"TODO\", WidgetOverride::Custom(\"noise_properties_fractal_type\".to_string())),\n\t\t\t\t\t\tInputMetadata::with_name_description_override(\"Fractal Octaves\", \"TODO\", WidgetOverride::Custom(\"noise_properties_fractal_octaves\".to_string())),\n\t\t\t\t\t\tInputMetadata::with_name_description_override(\"Fractal Lacunarity\", \"TODO\", WidgetOverride::Custom(\"noise_properties_fractal_lacunarity\".to_string())),\n\t\t\t\t\t\tInputMetadata::with_name_description_override(\"Fractal Gain\", \"TODO\", WidgetOverride::Custom(\"noise_properties_fractal_gain\".to_string())),\n\t\t\t\t\t\tInputMetadata::with_name_description_override(\"Fractal Weighted Strength\", \"TODO\", WidgetOverride::Custom(\"noise_properties_fractal_weighted_strength\".to_string())),\n\t\t\t\t\t\tInputMetadata::with_name_description_override(\"Fractal Ping Pong Strength\", \"TODO\", WidgetOverride::Custom(\"noise_properties_ping_pong_strength\".to_string())),\n\t\t\t\t\t\tInputMetadata::with_name_description_override(\"Cellular Distance Function\", \"TODO\", WidgetOverride::Custom(\"noise_properties_cellular_distance_function\".to_string())),\n\t\t\t\t\t\tInputMetadata::with_name_description_override(\"Cellular Return Type\", \"TODO\", WidgetOverride::Custom(\"noise_properties_cellular_return_type\".to_string())),\n\t\t\t\t\t\tInputMetadata::with_name_description_override(\"Cellular Jitter\", \"TODO\", WidgetOverride::Custom(\"noise_properties_cellular_jitter\".to_string())),\n\t\t\t\t\t],\n\t\t\t\t\toutput_names: vec![\"Image\".to_string()],\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t},\n\t\t\tdescription: Cow::Borrowed(\"Generates customizable procedural noise patterns.\"),\n\t\t\tproperties: None,\n\t\t},\n\t\tDocumentNodeDefinition {\n\t\t\tidentifier: \"Split Channels\",\n\t\t\tcategory: \"Raster: Channels\",\n\t\t\tnode_template: NodeTemplate {\n\t\t\t\tdocument_node: DocumentNode {\n\t\t\t\t\timplementation: DocumentNodeImplementation::Network(NodeNetwork {\n\t\t\t\t\t\texports: vec![\n\t\t\t\t\t\t\tNodeInput::value(TaggedValue::None, false),\n\t\t\t\t\t\t\tNodeInput::node(NodeId(0), 0),\n\t\t\t\t\t\t\tNodeInput::node(NodeId(1), 0),\n\t\t\t\t\t\t\tNodeInput::node(NodeId(2), 0),\n\t\t\t\t\t\t\tNodeInput::node(NodeId(3), 0),\n\t\t\t\t\t\t],\n\t\t\t\t\t\tnodes: [\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\tinputs: vec![\n\t\t\t\t\t\t\t\t\tNodeInput::import(concrete!(Table<Raster<CPU>>), 0),\n\t\t\t\t\t\t\t\t\tNodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Red), false),\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),\n\t\t\t\t\t\t\t\tcall_argument: generic!(T),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\tinputs: vec![\n\t\t\t\t\t\t\t\t\tNodeInput::import(concrete!(Table<Raster<CPU>>), 0),\n\t\t\t\t\t\t\t\t\tNodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Green), false),\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),\n\t\t\t\t\t\t\t\tcall_argument: generic!(T),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\tinputs: vec![\n\t\t\t\t\t\t\t\t\tNodeInput::import(concrete!(Table<Raster<CPU>>), 0),\n\t\t\t\t\t\t\t\t\tNodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Blue), false),\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),\n\t\t\t\t\t\t\t\tcall_argument: generic!(T),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\tinputs: vec![\n\t\t\t\t\t\t\t\t\tNodeInput::import(concrete!(Table<Raster<CPU>>), 0),\n\t\t\t\t\t\t\t\t\tNodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Alpha), false),\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),\n\t\t\t\t\t\t\t\tcall_argument: generic!(T),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]\n\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t.map(|(id, node)| (NodeId(id as u64), node))\n\t\t\t\t\t\t.collect(),\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t}),\n\t\t\t\t\tinputs: vec![NodeInput::value(TaggedValue::Raster(Default::default()), true)],\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t\tpersistent_node_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\tinput_metadata: vec![(\"Image\", \"TODO\").into()],\n\t\t\t\t\toutput_names: vec![\"\".to_string(), \"Red\".to_string(), \"Green\".to_string(), \"Blue\".to_string(), \"Alpha\".to_string()],\n\t\t\t\t\tnetwork_metadata: Some(NodeNetworkMetadata {\n\t\t\t\t\t\tpersistent_metadata: NodeNetworkPersistentMetadata {\n\t\t\t\t\t\t\tnode_metadata: [\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 2)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 4)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 6)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t\t.map(|(id, node)| (NodeId(id as u64), node))\n\t\t\t\t\t\t\t.collect(),\n\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t},\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t}),\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t},\n\t\t\tdescription: Cow::Borrowed(\"TODO\"),\n\t\t\tproperties: None,\n\t\t},\n\t\tDocumentNodeDefinition {\n\t\t\tidentifier: \"Split Vec2\",\n\t\t\tcategory: \"Math: Vector\",\n\t\t\tnode_template: NodeTemplate {\n\t\t\t\tdocument_node: DocumentNode {\n\t\t\t\t\timplementation: DocumentNodeImplementation::Network(NodeNetwork {\n\t\t\t\t\t\texports: vec![NodeInput::value(TaggedValue::None, false), NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(1), 0)],\n\t\t\t\t\t\tnodes: [\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::import(concrete!(DVec2), 0), NodeInput::value(TaggedValue::XY(XY::X), false)],\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER),\n\t\t\t\t\t\t\t\tcall_argument: generic!(T),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::import(concrete!(DVec2), 0), NodeInput::value(TaggedValue::XY(XY::Y), false)],\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER),\n\t\t\t\t\t\t\t\tcall_argument: generic!(T),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]\n\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t.map(|(id, node)| (NodeId(id as u64), node))\n\t\t\t\t\t\t.collect(),\n\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t}),\n\t\t\t\t\tinputs: vec![NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), true)],\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t\tpersistent_node_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\tinput_metadata: vec![(\"Vec2\", \"TODO\").into()],\n\t\t\t\t\toutput_names: vec![\"\".to_string(), \"X\".to_string(), \"Y\".to_string()],\n\t\t\t\t\tnetwork_metadata: Some(NodeNetworkMetadata {\n\t\t\t\t\t\tpersistent_metadata: NodeNetworkPersistentMetadata {\n\t\t\t\t\t\t\tnode_metadata: [\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 2)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t\t.map(|(id, node)| (NodeId(id as u64), node))\n\t\t\t\t\t\t\t.collect(),\n\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t},\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t}),\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t},\n\t\t\tdescription: Cow::Borrowed(\n\t\t\t\t\"Decomposes the X and Y components of a vec2.\\n\\\n\t\t\t\t\\n\\\n\t\t\t\tThe inverse of this node is \\\"Vec2 Value\\\", which can have either or both its X and Y parameters exposed as graph inputs.\",\n\t\t\t),\n\t\t\tproperties: None,\n\t\t},\n\t\t// TODO: Remove this and just use the proto node definition directly\n\t\tDocumentNodeDefinition {\n\t\t\tidentifier: \"Brush\",\n\t\t\tcategory: \"Raster\",\n\t\t\tnode_template: NodeTemplate {\n\t\t\t\tdocument_node: DocumentNode {\n\t\t\t\t\timplementation: DocumentNodeImplementation::Network(NodeNetwork {\n\t\t\t\t\t\texports: vec![NodeInput::node(NodeId(0), 0)],\n\t\t\t\t\t\tnodes: vec![DocumentNode {\n\t\t\t\t\t\t\tinputs: vec![\n\t\t\t\t\t\t\t\tNodeInput::import(concrete!(Table<Raster<CPU>>), 0),\n\t\t\t\t\t\t\t\tNodeInput::import(concrete!(Vec<brush::brush_stroke::BrushStroke>), 1),\n\t\t\t\t\t\t\t\tNodeInput::import(concrete!(BrushCache), 2),\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(brush::brush::brush::IDENTIFIER),\n\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t}]\n\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t.map(|(id, node)| (NodeId(id as u64), node))\n\t\t\t\t\t\t.collect(),\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t}),\n\t\t\t\t\tinputs: vec![\n\t\t\t\t\t\tNodeInput::value(TaggedValue::Raster(Default::default()), true),\n\t\t\t\t\t\tNodeInput::value(TaggedValue::BrushStrokes(Vec::new()), false),\n\t\t\t\t\t\tNodeInput::value(TaggedValue::BrushCache(BrushCache::default()), false),\n\t\t\t\t\t],\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t\tpersistent_node_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\tinput_metadata: vec![(\"Background\", \"TODO\").into(), (\"Trace\", \"TODO\").into(), (\"Cache\", \"TODO\").into()],\n\t\t\t\t\toutput_names: vec![\"Image\".to_string()],\n\t\t\t\t\tnetwork_metadata: Some(NodeNetworkMetadata {\n\t\t\t\t\t\tpersistent_metadata: NodeNetworkPersistentMetadata {\n\t\t\t\t\t\t\tnode_metadata: [DocumentNodeMetadata {\n\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t}]\n\t\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t\t.map(|(id, node)| (NodeId(id as u64), node))\n\t\t\t\t\t\t\t.collect(),\n\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t},\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t}),\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t},\n\t\t\tdescription: Cow::Borrowed(\"TODO\"),\n\t\t\tproperties: None,\n\t\t},\n\t\t// TODO: Auto-generate this from its proto node macro\n\t\tDocumentNodeDefinition {\n\t\t\tidentifier: \"Memoize\",\n\t\t\tcategory: \"Debug\",\n\t\t\tnode_template: NodeTemplate {\n\t\t\t\tdocument_node: DocumentNode {\n\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),\n\t\t\t\t\tinputs: vec![NodeInput::value(TaggedValue::Raster(Default::default()), true)],\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t\tpersistent_node_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\tinput_metadata: vec![(\"Image\", \"TODO\").into()],\n\t\t\t\t\toutput_names: vec![\"Image\".to_string()],\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t},\n\t\t\tdescription: Cow::Borrowed(\"TODO\"),\n\t\t\tproperties: None,\n\t\t},\n\t\t#[cfg(feature = \"gpu\")]\n\t\tDocumentNodeDefinition {\n\t\t\tidentifier: \"Upload Texture\",\n\t\t\tcategory: \"Debug\",\n\t\t\tnode_template: NodeTemplate {\n\t\t\t\tdocument_node: DocumentNode {\n\t\t\t\t\timplementation: DocumentNodeImplementation::Network(NodeNetwork {\n\t\t\t\t\t\texports: vec![NodeInput::node(NodeId(2), 0)],\n\t\t\t\t\t\tnodes: [\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::scope(\"editor-api\")],\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new(\"graphene_core::ops::IntoNode<&WgpuExecutor>\")),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::import(concrete!(Table<Raster<CPU>>), 0), NodeInput::node(NodeId(0), 0)],\n\t\t\t\t\t\t\t\tcall_argument: generic!(T),\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(wgpu_executor::texture_conversion::upload_texture::IDENTIFIER),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\tcall_argument: generic!(T),\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::node(NodeId(1), 0)],\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]\n\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t.map(|(id, node)| (NodeId(id as u64), node))\n\t\t\t\t\t\t.collect(),\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t}),\n\t\t\t\t\tinputs: vec![NodeInput::value(TaggedValue::Raster(Default::default()), true)],\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t\tpersistent_node_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\toutput_names: vec![\"Texture\".to_string()],\n\t\t\t\t\tnetwork_metadata: Some(NodeNetworkMetadata {\n\t\t\t\t\t\tpersistent_metadata: NodeNetworkPersistentMetadata {\n\t\t\t\t\t\t\tnode_metadata: [\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-7, 0)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t\t.map(|(id, node)| (NodeId(id as u64), node))\n\t\t\t\t\t\t\t.collect(),\n\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t},\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t}),\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t},\n\t\t\tdescription: Cow::Borrowed(\"TODO\"),\n\t\t\tproperties: None,\n\t\t},\n\t\tDocumentNodeDefinition {\n\t\t\tidentifier: \"Extract\",\n\t\t\tcategory: \"Debug\",\n\t\t\tnode_template: NodeTemplate {\n\t\t\t\tdocument_node: DocumentNode {\n\t\t\t\t\timplementation: DocumentNodeImplementation::Extract,\n\t\t\t\t\tinputs: vec![NodeInput::value(TaggedValue::DocumentNode(DocumentNode::default()), true)],\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t\tpersistent_node_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\tinput_metadata: vec![(\"Node\", \"TODO\").into()],\n\t\t\t\t\toutput_names: vec![\"Document Node\".to_string()],\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t},\n\t\t\tdescription: Cow::Borrowed(\"TODO\"),\n\t\t\tproperties: None,\n\t\t},\n\t\t// Aims for interoperable compatibility with:\n\t\t// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=levl%27%20%3D%20Levels-,%27curv%27%20%3D%20Curves,-%27expA%27%20%3D%20Exposure\n\t\t// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Max%20input%20range-,Curves,-Curves%20settings%20files\n\t\t//\n\t\t// Some further analysis available at:\n\t\t// https://geraldbakker.nl/psnumbers/curves.html\n\t\t// TODO: Fix this, it's currently broken\n\t\t// DocumentNodeDefinition {\n\t\t// \tidentifier: \"Curves\",\n\t\t// \tcategory: \"Raster: Adjustment\",\n\t\t// \tnode_template: NodeTemplate {\n\t\t// \t\tdocument_node: DocumentNode {\n\t\t// \t\t\timplementation: DocumentNodeImplementation::proto(\"core_types::raster::CurvesNode\"),\n\t\t// \t\t\tinputs: vec![\n\t\t// \t\t\t\tNodeInput::value(TaggedValue::Raster(Default::default()), true),\n\t\t// \t\t\t\tNodeInput::value(TaggedValue::Curve(Default::default()), false),\n\t\t// \t\t\t],\n\t\t// \t\t\t..Default::default()\n\t\t// \t\t},\n\t\t// \t\tpersistent_node_metadata: DocumentNodePersistentMetadata {\n\t\t// \t\t\tinput_properties: vec![(\"Image\", \"TODO\").into(), (\"Curve\", \"TODO\").into()],\n\t\t// \t\t\toutput_names: vec![\"Image\".to_string()],\n\t\t// \t\t\t..Default::default()\n\t\t// \t\t},\n\t\t// \t},\n\t\t// \tdescription: Cow::Borrowed(\"TODO\"),\n\t\t// \tproperties: None,\n\t\t// },\n\t\tDocumentNodeDefinition {\n\t\t\tidentifier: \"Path\",\n\t\t\tcategory: \"Vector\",\n\t\t\tnode_template: NodeTemplate {\n\t\t\t\tdocument_node: DocumentNode {\n\t\t\t\t\timplementation: DocumentNodeImplementation::Network(NodeNetwork {\n\t\t\t\t\t\texports: vec![NodeInput::node(NodeId(1), 0)],\n\t\t\t\t\t\tnodes: vec![\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::import(concrete!(Table<Vector>), 0)],\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),\n\t\t\t\t\t\t\t\tcall_argument: generic!(T),\n\t\t\t\t\t\t\t\tskip_deduplication: true,\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\tinputs: vec![\n\t\t\t\t\t\t\t\t\tNodeInput::node(NodeId(0), 0),\n\t\t\t\t\t\t\t\t\tNodeInput::import(concrete!(graphene_std::vector::VectorModification), 1),\n\t\t\t\t\t\t\t\t\tNodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath),\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tcall_argument: generic!(T),\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(vector::path_modify::IDENTIFIER),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]\n\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t.map(|(id, node)| (NodeId(id as u64), node))\n\t\t\t\t\t\t.collect(),\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t}),\n\t\t\t\t\tinputs: vec![\n\t\t\t\t\t\tNodeInput::value(TaggedValue::Vector(Default::default()), true),\n\t\t\t\t\t\tNodeInput::value(TaggedValue::VectorModification(Default::default()), false),\n\t\t\t\t\t],\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t\tpersistent_node_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\tinput_metadata: vec![(\"Content\", \"TODO\").into(), (\"Modification\", \"TODO\").into()],\n\t\t\t\t\toutput_names: vec![\"Modified\".to_string()],\n\t\t\t\t\tnetwork_metadata: Some(NodeNetworkMetadata {\n\t\t\t\t\t\tpersistent_metadata: NodeNetworkPersistentMetadata {\n\t\t\t\t\t\t\tnode_metadata: [\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t\t.map(|(id, node)| (NodeId(id as u64), node))\n\t\t\t\t\t\t\t.collect(),\n\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t},\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t}),\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t},\n\t\t\tdescription: Cow::Borrowed(\"TODO\"),\n\t\t\tproperties: None,\n\t\t},\n\t\t// TODO: Auto-generate this from its proto node macro\n\t\tDocumentNodeDefinition {\n\t\t\tidentifier: \"Transform\",\n\t\t\tcategory: \"Math: Transform\",\n\t\t\tnode_template: NodeTemplate {\n\t\t\t\tdocument_node: DocumentNode {\n\t\t\t\t\tinputs: vec![\n\t\t\t\t\t\t// Value\n\t\t\t\t\t\tNodeInput::value(TaggedValue::DAffine2(DAffine2::default()), true),\n\t\t\t\t\t\t// Translation\n\t\t\t\t\t\tNodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),\n\t\t\t\t\t\t// Rotation\n\t\t\t\t\t\tNodeInput::value(TaggedValue::F64(0.), false),\n\t\t\t\t\t\t// Scale\n\t\t\t\t\t\tNodeInput::value(TaggedValue::DVec2(DVec2::ONE), false),\n\t\t\t\t\t\t// Skew\n\t\t\t\t\t\tNodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),\n\t\t\t\t\t\t// Origin Offset\n\t\t\t\t\t\tNodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),\n\t\t\t\t\t\t// Scale Appearance\n\t\t\t\t\t\tNodeInput::value(TaggedValue::Bool(true), false),\n\t\t\t\t\t],\n\t\t\t\t\timplementation: DocumentNodeImplementation::Network(NodeNetwork {\n\t\t\t\t\t\texports: vec![\n\t\t\t\t\t\t\t// From the Transform node\n\t\t\t\t\t\t\tNodeInput::node(NodeId(1), 0),\n\t\t\t\t\t\t],\n\t\t\t\t\t\tnodes: [\n\t\t\t\t\t\t\t// Monitor node\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\tinputs: vec![\n\t\t\t\t\t\t\t\t\t// From the Value import\n\t\t\t\t\t\t\t\t\tNodeInput::import(generic!(T), 0),\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),\n\t\t\t\t\t\t\t\tcall_argument: generic!(T),\n\t\t\t\t\t\t\t\tskip_deduplication: true,\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// Transform node\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\tinputs: vec![\n\t\t\t\t\t\t\t\t\t// From the Monitor node\n\t\t\t\t\t\t\t\t\tNodeInput::node(NodeId(0), 0),\n\t\t\t\t\t\t\t\t\t// From the Translation import\n\t\t\t\t\t\t\t\t\tNodeInput::import(concrete!(DVec2), 1),\n\t\t\t\t\t\t\t\t\t// From the Rotation import\n\t\t\t\t\t\t\t\t\tNodeInput::import(concrete!(f64), 2),\n\t\t\t\t\t\t\t\t\t// From the Scale import\n\t\t\t\t\t\t\t\t\tNodeInput::import(concrete!(DVec2), 3),\n\t\t\t\t\t\t\t\t\t// From the Skew import\n\t\t\t\t\t\t\t\t\tNodeInput::import(concrete!(DVec2), 4),\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(transform_nodes::transform::IDENTIFIER),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]\n\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t.map(|(id, node)| (NodeId(id as u64), node))\n\t\t\t\t\t\t.collect(),\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t}),\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t\tpersistent_node_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\tnetwork_metadata: Some(NodeNetworkMetadata {\n\t\t\t\t\t\tpersistent_metadata: NodeNetworkPersistentMetadata {\n\t\t\t\t\t\t\tnode_metadata: [\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t\t.map(|(id, node)| (NodeId(id as u64), node))\n\t\t\t\t\t\t\t.collect(),\n\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t},\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t}),\n\t\t\t\t\tinput_metadata: vec![\n\t\t\t\t\t\t(\"Value\", \"TODO\").into(),\n\t\t\t\t\t\tInputMetadata::with_name_description_override(\n\t\t\t\t\t\t\t\"Translation\",\n\t\t\t\t\t\t\t\"TODO\",\n\t\t\t\t\t\t\tWidgetOverride::Vec2(Vec2InputSettings {\n\t\t\t\t\t\t\t\tx: \"X\".to_string(),\n\t\t\t\t\t\t\t\ty: \"Y\".to_string(),\n\t\t\t\t\t\t\t\tunit: \" px\".to_string(),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t\tInputMetadata::with_name_description_override(\"Rotation\", \"TODO\", WidgetOverride::Custom(\"transform_rotation\".to_string())),\n\t\t\t\t\t\tInputMetadata::with_name_description_override(\n\t\t\t\t\t\t\t\"Scale\",\n\t\t\t\t\t\t\t\"TODO\",\n\t\t\t\t\t\t\tWidgetOverride::Vec2(Vec2InputSettings {\n\t\t\t\t\t\t\t\tx: \"W\".to_string(),\n\t\t\t\t\t\t\t\ty: \"H\".to_string(),\n\t\t\t\t\t\t\t\tunit: \"x\".to_string(),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t\tInputMetadata::with_name_description_override(\"Skew\", \"TODO\", WidgetOverride::Custom(\"transform_skew\".to_string())),\n\t\t\t\t\t\tInputMetadata::with_name_description_override(\"Origin Offset\", \"TODO\", WidgetOverride::Custom(\"hidden\".to_string())),\n\t\t\t\t\t\tInputMetadata::with_name_description_override(\"Scale Appearance\", \"TODO\", WidgetOverride::Custom(\"hidden\".to_string())),\n\t\t\t\t\t],\n\t\t\t\t\toutput_names: vec![\"Data\".to_string()],\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t},\n\t\t\tdescription: Cow::Borrowed(\"TODO\"),\n\t\t\tproperties: None,\n\t\t},\n\t\tDocumentNodeDefinition {\n\t\t\tidentifier: \"Boolean Operation\",\n\t\t\tcategory: \"Vector: Modifier\",\n\t\t\tnode_template: NodeTemplate {\n\t\t\t\tdocument_node: DocumentNode {\n\t\t\t\t\timplementation: DocumentNodeImplementation::Network(NodeNetwork {\n\t\t\t\t\t\texports: vec![NodeInput::node(NodeId(1), 0)],\n\t\t\t\t\t\tnodes: vec![\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::import(concrete!(Table<Vector>), 0), NodeInput::import(concrete!(vector::style::Fill), 1)],\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(path_bool_nodes::boolean_operation::IDENTIFIER),\n\t\t\t\t\t\t\t\tcall_argument: generic!(T),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::node(NodeId(0), 0)],\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),\n\t\t\t\t\t\t\t\tcall_argument: generic!(T),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]\n\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t.map(|(id, node)| (NodeId(id as u64), node))\n\t\t\t\t\t\t.collect(),\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t}),\n\t\t\t\t\tinputs: vec![\n\t\t\t\t\t\tNodeInput::value(TaggedValue::Graphic(Default::default()), true),\n\t\t\t\t\t\tNodeInput::value(TaggedValue::BooleanOperation(vector::misc::BooleanOperation::Union), false),\n\t\t\t\t\t],\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t\tpersistent_node_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\tnetwork_metadata: Some(NodeNetworkMetadata {\n\t\t\t\t\t\tpersistent_metadata: NodeNetworkPersistentMetadata {\n\t\t\t\t\t\t\tnode_metadata: [\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t\t.map(|(id, node)| (NodeId(id as u64), node))\n\t\t\t\t\t\t\t.collect(),\n\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t},\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t}),\n\t\t\t\t\tinput_metadata: vec![(\"Content\", \"TODO\").into(), (\"Operation\", \"TODO\").into()],\n\t\t\t\t\toutput_names: vec![\"Vector\".to_string()],\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t},\n\t\t\tdescription: Cow::Borrowed(\"TODO\"),\n\t\t\tproperties: None,\n\t\t},\n\t\tDocumentNodeDefinition {\n\t\t\tidentifier: \"Sample Polyline\",\n\t\t\tcategory: \"Vector: Modifier\",\n\t\t\tnode_template: NodeTemplate {\n\t\t\t\tdocument_node: DocumentNode {\n\t\t\t\t\timplementation: DocumentNodeImplementation::Network(NodeNetwork {\n\t\t\t\t\t\texports: vec![NodeInput::node(NodeId(2), 0)],\n\t\t\t\t\t\tnodes: [\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::import(concrete!(Table<Vector>), 0)],\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(vector::subpath_segment_lengths::IDENTIFIER),\n\t\t\t\t\t\t\t\tcall_argument: generic!(T),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\tinputs: vec![\n\t\t\t\t\t\t\t\t\tNodeInput::import(concrete!(Table<Vector>), 0),\n\t\t\t\t\t\t\t\t\tNodeInput::import(concrete!(vector::misc::PointSpacingType), 1),\n\t\t\t\t\t\t\t\t\tNodeInput::import(concrete!(f64), 2),\n\t\t\t\t\t\t\t\t\tNodeInput::import(concrete!(u32), 3),\n\t\t\t\t\t\t\t\t\tNodeInput::import(concrete!(f64), 4),\n\t\t\t\t\t\t\t\t\tNodeInput::import(concrete!(f64), 5),\n\t\t\t\t\t\t\t\t\tNodeInput::import(concrete!(bool), 6),\n\t\t\t\t\t\t\t\t\tNodeInput::node(NodeId(0), 0),\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(vector::sample_polyline::IDENTIFIER),\n\t\t\t\t\t\t\t\tcall_argument: generic!(T),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::node(NodeId(1), 0)],\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),\n\t\t\t\t\t\t\t\tcall_argument: generic!(T),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]\n\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t.map(|(id, node)| (NodeId(id as u64), node))\n\t\t\t\t\t\t.collect(),\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t}),\n\t\t\t\t\tinputs: vec![\n\t\t\t\t\t\tNodeInput::value(TaggedValue::Vector(Default::default()), true),\n\t\t\t\t\t\tNodeInput::value(TaggedValue::PointSpacingType(Default::default()), false),\n\t\t\t\t\t\tNodeInput::value(TaggedValue::F64(100.), false),\n\t\t\t\t\t\tNodeInput::value(TaggedValue::U32(100), false),\n\t\t\t\t\t\tNodeInput::value(TaggedValue::F64(0.), false),\n\t\t\t\t\t\tNodeInput::value(TaggedValue::F64(0.), false),\n\t\t\t\t\t\tNodeInput::value(TaggedValue::Bool(false), false),\n\t\t\t\t\t],\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t\tpersistent_node_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\tnetwork_metadata: Some(NodeNetworkMetadata {\n\t\t\t\t\t\tpersistent_metadata: NodeNetworkPersistentMetadata {\n\t\t\t\t\t\t\tnode_metadata: [\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 7)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t\t.map(|(id, node)| (NodeId(id as u64), node))\n\t\t\t\t\t\t\t.collect(),\n\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t},\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t}),\n\t\t\t\t\tinput_metadata: vec![\n\t\t\t\t\t\t(\"Content\", \"The shape to be resampled and converted into a polyline.\").into(),\n\t\t\t\t\t\t(\"Spacing\", node_properties::SAMPLE_POLYLINE_DESCRIPTION_SPACING).into(),\n\t\t\t\t\t\tInputMetadata::with_name_description_override(\n\t\t\t\t\t\t\t\"Separation\",\n\t\t\t\t\t\t\tnode_properties::SAMPLE_POLYLINE_DESCRIPTION_SEPARATION,\n\t\t\t\t\t\t\tWidgetOverride::Number(NumberInputSettings {\n\t\t\t\t\t\t\t\tmin: Some(0.),\n\t\t\t\t\t\t\t\tunit: Some(\" px\".to_string()),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t\tInputMetadata::with_name_description_override(\n\t\t\t\t\t\t\t\"Quantity\",\n\t\t\t\t\t\t\tnode_properties::SAMPLE_POLYLINE_DESCRIPTION_QUANTITY,\n\t\t\t\t\t\t\tWidgetOverride::Number(NumberInputSettings {\n\t\t\t\t\t\t\t\tmin: Some(2.),\n\t\t\t\t\t\t\t\tis_integer: true,\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t\tInputMetadata::with_name_description_override(\n\t\t\t\t\t\t\t\"Start Offset\",\n\t\t\t\t\t\t\tnode_properties::SAMPLE_POLYLINE_DESCRIPTION_START_OFFSET,\n\t\t\t\t\t\t\tWidgetOverride::Number(NumberInputSettings {\n\t\t\t\t\t\t\t\tmin: Some(0.),\n\t\t\t\t\t\t\t\tunit: Some(\" px\".to_string()),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t\tInputMetadata::with_name_description_override(\n\t\t\t\t\t\t\t\"Stop Offset\",\n\t\t\t\t\t\t\tnode_properties::SAMPLE_POLYLINE_DESCRIPTION_STOP_OFFSET,\n\t\t\t\t\t\t\tWidgetOverride::Number(NumberInputSettings {\n\t\t\t\t\t\t\t\tmin: Some(0.),\n\t\t\t\t\t\t\t\tunit: Some(\" px\".to_string()),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t\t(\"Adaptive Spacing\", node_properties::SAMPLE_POLYLINE_DESCRIPTION_ADAPTIVE_SPACING).into(),\n\t\t\t\t\t],\n\t\t\t\t\toutput_names: vec![\"Vector\".to_string()],\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t},\n\t\t\tdescription: Cow::Borrowed(\"Convert vector geometry into a polyline composed of evenly spaced points.\"),\n\t\t\tproperties: Some(\"sample_polyline_properties\"),\n\t\t},\n\t\tDocumentNodeDefinition {\n\t\t\tidentifier: \"Scatter Points\",\n\t\t\tcategory: \"Vector: Modifier\",\n\t\t\tnode_template: NodeTemplate {\n\t\t\t\tdocument_node: DocumentNode {\n\t\t\t\t\timplementation: DocumentNodeImplementation::Network(NodeNetwork {\n\t\t\t\t\t\texports: vec![NodeInput::node(NodeId(1), 0)],\n\t\t\t\t\t\tnodes: [\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\tinputs: vec![\n\t\t\t\t\t\t\t\t\tNodeInput::import(concrete!(Table<Vector>), 0),\n\t\t\t\t\t\t\t\t\tNodeInput::import(concrete!(f64), 1),\n\t\t\t\t\t\t\t\t\tNodeInput::import(concrete!(u32), 2),\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tcall_argument: generic!(T),\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(vector::poisson_disk_points::IDENTIFIER),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\tinputs: vec![NodeInput::node(NodeId(0), 0)],\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),\n\t\t\t\t\t\t\t\tcall_argument: generic!(T),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]\n\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t.map(|(id, node)| (NodeId(id as u64), node))\n\t\t\t\t\t\t.collect(),\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t}),\n\t\t\t\t\tinputs: vec![\n\t\t\t\t\t\tNodeInput::value(TaggedValue::Vector(Default::default()), true),\n\t\t\t\t\t\tNodeInput::value(TaggedValue::F64(10.), false),\n\t\t\t\t\t\tNodeInput::value(TaggedValue::U32(0), false),\n\t\t\t\t\t],\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t\tpersistent_node_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\tnetwork_metadata: Some(NodeNetworkMetadata {\n\t\t\t\t\t\tpersistent_metadata: NodeNetworkPersistentMetadata {\n\t\t\t\t\t\t\tnode_metadata: [\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tDocumentNodeMetadata {\n\t\t\t\t\t\t\t\t\tpersistent_metadata: DocumentNodePersistentMetadata {\n\t\t\t\t\t\t\t\t\t\tnode_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),\n\t\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t\t.map(|(id, node)| (NodeId(id as u64), node))\n\t\t\t\t\t\t\t.collect(),\n\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t},\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t}),\n\t\t\t\t\tinput_metadata: vec![\n\t\t\t\t\t\t(\"Content\", \"TODO\").into(),\n\t\t\t\t\t\tInputMetadata::with_name_description_override(\n\t\t\t\t\t\t\t\"Separation\",\n\t\t\t\t\t\t\t\"TODO\",\n\t\t\t\t\t\t\tWidgetOverride::Number(NumberInputSettings {\n\t\t\t\t\t\t\t\tmin: Some(0.01),\n\t\t\t\t\t\t\t\tmode: NumberInputMode::Range,\n\t\t\t\t\t\t\t\trange_min: Some(1.),\n\t\t\t\t\t\t\t\trange_max: Some(100.),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t\tInputMetadata::with_name_description_override(\n\t\t\t\t\t\t\t\"Seed\",\n\t\t\t\t\t\t\t\"TODO\",\n\t\t\t\t\t\t\tWidgetOverride::Number(NumberInputSettings {\n\t\t\t\t\t\t\t\tmin: Some(0.),\n\t\t\t\t\t\t\t\tis_integer: true,\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t],\n\t\t\t\t\toutput_names: vec![\"Vector\".to_string()],\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t},\n\t\t\tdescription: Cow::Borrowed(\"TODO\"),\n\t\t\tproperties: None,\n\t\t},\n\t];\n\n\tdocument_node_derive::post_process_nodes(custom)\n}\n\ntype NodeProperties = HashMap<String, Box<dyn Fn(NodeId, &mut NodePropertiesContext) -> Vec<LayoutGroup> + Send + Sync>>;\n\n// TODO: Replace with `core::cell::LazyCell` (<https://doc.rust-lang.org/core/cell/struct.LazyCell.html>) or similar\npub static NODE_OVERRIDES: once_cell::sync::Lazy<NodeProperties> = once_cell::sync::Lazy::new(static_node_properties);\n\n/// Defines the logic for inputs to display a custom properties panel widget.\nfn static_node_properties() -> NodeProperties {\n\tlet mut map: NodeProperties = HashMap::new();\n\tmap.insert(\"brightness_contrast_properties\".to_string(), Box::new(node_properties::brightness_contrast_properties));\n\tmap.insert(\"channel_mixer_properties\".to_string(), Box::new(node_properties::channel_mixer_properties));\n\tmap.insert(\"fill_properties\".to_string(), Box::new(node_properties::fill_properties));\n\tmap.insert(\"stroke_properties\".to_string(), Box::new(node_properties::stroke_properties));\n\tmap.insert(\"offset_path_properties\".to_string(), Box::new(node_properties::offset_path_properties));\n\tmap.insert(\"selective_color_properties\".to_string(), Box::new(node_properties::selective_color_properties));\n\tmap.insert(\"exposure_properties\".to_string(), Box::new(node_properties::exposure_properties));\n\tmap.insert(\"math_properties\".to_string(), Box::new(node_properties::math_properties));\n\tmap.insert(\"rectangle_properties\".to_string(), Box::new(node_properties::rectangle_properties));\n\tmap.insert(\"grid_properties\".to_string(), Box::new(node_properties::grid_properties));\n\tmap.insert(\"spiral_properties\".to_string(), Box::new(node_properties::spiral_properties));\n\tmap.insert(\"sample_polyline_properties\".to_string(), Box::new(node_properties::sample_polyline_properties));\n\tmap.insert(\n\t\t\"monitor_properties\".to_string(),\n\t\tBox::new(|_node_id, _context| node_properties::string_properties(\"Used internally by the editor to obtain a layer thumbnail.\")),\n\t);\n\tmap\n}\n\ntype InputProperties = HashMap<String, Box<dyn Fn(NodeId, usize, &mut NodePropertiesContext) -> Result<Vec<LayoutGroup>, String> + Send + Sync>>;\n\n// TODO: Replace with `core::cell::LazyCell` (<https://doc.rust-lang.org/core/cell/struct.LazyCell.html>) or similar\nstatic INPUT_OVERRIDES: once_cell::sync::Lazy<InputProperties> = once_cell::sync::Lazy::new(static_input_properties);\n\n/// Defines the logic for inputs to display a custom properties panel widget.\nfn static_input_properties() -> InputProperties {\n\tlet mut map: InputProperties = HashMap::new();\n\tmap.insert(\"hidden\".to_string(), Box::new(|_node_id, _index, _context| Ok(Vec::new())));\n\tmap.insert(\n\t\t\"string\".to_string(),\n\t\tBox::new(|node_id, index, context| {\n\t\t\tlet Some(value) = context.network_interface.input_data(&node_id, index, \"string_properties\", context.selection_network_path) else {\n\t\t\t\treturn Err(format!(\"Could not get string properties for node {node_id}\"));\n\t\t\t};\n\t\t\tlet Some(string) = value.as_str() else {\n\t\t\t\treturn Err(format!(\"Could not downcast string properties for node {node_id}\"));\n\t\t\t};\n\t\t\tOk(node_properties::string_properties(string))\n\t\t}),\n\t);\n\tmap.insert(\n\t\t\"number\".to_string(),\n\t\tBox::new(|node_id, index, context| {\n\t\t\tlet mut number_input = NumberInput::default();\n\t\t\tif let Some(unit) = context\n\t\t\t\t.network_interface\n\t\t\t\t.input_data(&node_id, index, \"unit\", context.selection_network_path)\n\t\t\t\t.and_then(|value| value.as_str())\n\t\t\t{\n\t\t\t\tnumber_input = number_input.unit(unit);\n\t\t\t}\n\t\t\tif let Some(min) = context\n\t\t\t\t.network_interface\n\t\t\t\t.input_data(&node_id, index, \"min\", context.selection_network_path)\n\t\t\t\t.and_then(|value| value.as_f64())\n\t\t\t{\n\t\t\t\tnumber_input = number_input.min(min);\n\t\t\t}\n\t\t\tif let Some(max) = context\n\t\t\t\t.network_interface\n\t\t\t\t.input_data(&node_id, index, \"max\", context.selection_network_path)\n\t\t\t\t.and_then(|value| value.as_f64())\n\t\t\t{\n\t\t\t\tnumber_input = number_input.max(max);\n\t\t\t}\n\t\t\tif let Some(step) = context\n\t\t\t\t.network_interface\n\t\t\t\t.input_data(&node_id, index, \"step\", context.selection_network_path)\n\t\t\t\t.and_then(|value| value.as_f64())\n\t\t\t{\n\t\t\t\tnumber_input = number_input.step(step);\n\t\t\t}\n\t\t\tif let Some(mode) = context.network_interface.input_data(&node_id, index, \"mode\", context.selection_network_path).map(|value| {\n\t\t\t\tlet mode: NumberInputMode = serde_json::from_value(value.clone()).unwrap();\n\t\t\t\tmode\n\t\t\t}) {\n\t\t\t\tnumber_input = number_input.mode(mode);\n\t\t\t}\n\t\t\tif let Some(range_min) = context\n\t\t\t\t.network_interface\n\t\t\t\t.input_data(&node_id, index, \"range_min\", context.selection_network_path)\n\t\t\t\t.and_then(|value| value.as_f64())\n\t\t\t{\n\t\t\t\tnumber_input = number_input.range_min(Some(range_min));\n\t\t\t}\n\t\t\tif let Some(range_max) = context\n\t\t\t\t.network_interface\n\t\t\t\t.input_data(&node_id, index, \"range_max\", context.selection_network_path)\n\t\t\t\t.and_then(|value| value.as_f64())\n\t\t\t{\n\t\t\t\tnumber_input = number_input.range_max(Some(range_max));\n\t\t\t}\n\t\t\tif let Some(is_integer) = context\n\t\t\t\t.network_interface\n\t\t\t\t.input_data(&node_id, index, \"is_integer\", context.selection_network_path)\n\t\t\t\t.and_then(|value| value.as_bool())\n\t\t\t{\n\t\t\t\tnumber_input = number_input.is_integer(is_integer);\n\t\t\t}\n\t\t\tlet blank_assist = context\n\t\t\t\t.network_interface\n\t\t\t\t.input_data(&node_id, index, \"blank_assist\", context.selection_network_path)\n\t\t\t\t.and_then(|value| value.as_bool())\n\t\t\t\t.unwrap_or_else(|| {\n\t\t\t\t\tlog::error!(\"Could not get blank assist when displaying number input for node {node_id}, index {index}\");\n\t\t\t\t\ttrue\n\t\t\t\t});\n\n\t\t\tOk(vec![LayoutGroup::row(node_properties::number_widget(\n\t\t\t\tParameterWidgetsInfo::new(node_id, index, blank_assist, context),\n\t\t\t\tnumber_input,\n\t\t\t))])\n\t\t}),\n\t);\n\tmap.insert(\n\t\t// The custom number input settings are only available on proto nodes\n\t\t\"optional_f64\".to_string(),\n\t\tBox::new(|node_id, index, context| {\n\t\t\tlet node_metadata = registry::NODE_METADATA.lock().unwrap();\n\t\t\tlet mut number_input = NumberInput::default();\n\t\t\tif let Some(field) = context\n\t\t\t\t.network_interface\n\t\t\t\t.implementation(&node_id, context.selection_network_path)\n\t\t\t\t.and_then(|implementation| if let DocumentNodeImplementation::ProtoNode(id) = implementation { Some(id) } else { None })\n\t\t\t\t.and_then(|proto_node_identifier| node_metadata.get(proto_node_identifier))\n\t\t\t\t.and_then(|metadata| metadata.fields.get(index))\n\t\t\t{\n\t\t\t\tif let Some(unit) = field.unit {\n\t\t\t\t\tnumber_input = number_input.unit(unit);\n\t\t\t\t}\n\t\t\t\tif let Some(number_min) = field.number_min {\n\t\t\t\t\tnumber_input = number_input.min(number_min);\n\t\t\t\t}\n\t\t\t\tif let Some(number_max) = field.number_max {\n\t\t\t\t\tnumber_input = number_input.max(number_max);\n\t\t\t\t}\n\t\t\t\tif let Some((range_min, range_max)) = field.number_mode_range {\n\t\t\t\t\tnumber_input = number_input.range_min(Some(range_min));\n\t\t\t\t\tnumber_input = number_input.range_max(Some(range_max));\n\t\t\t\t}\n\t\t\t\tnumber_input = number_input.is_integer(false);\n\t\t\t\tif let Some(number_step) = field.number_step {\n\t\t\t\t\tnumber_input = number_input.step(number_step);\n\t\t\t\t}\n\t\t\t};\n\t\t\t// NOTE: The bool input MUST be at the input index directly before the f64 input!\n\t\t\tOk(vec![LayoutGroup::row(node_properties::optional_f64_widget(\n\t\t\t\tParameterWidgetsInfo::new(node_id, index, false, context),\n\t\t\t\tindex - 1,\n\t\t\t\tnumber_input,\n\t\t\t))])\n\t\t}),\n\t);\n\tmap.insert(\n\t\t\"vec2\".to_string(),\n\t\tBox::new(|node_id, index, context| {\n\t\t\tlet x = context\n\t\t\t\t.network_interface\n\t\t\t\t.input_data(&node_id, index, \"x\", context.selection_network_path)\n\t\t\t\t.and_then(|value| value.as_str())\n\t\t\t\t.unwrap_or_else(|| {\n\t\t\t\t\tlog::error!(\"Could not get x for vec2 input\");\n\t\t\t\t\t\"\"\n\t\t\t\t})\n\t\t\t\t.to_string();\n\t\t\tlet y = context\n\t\t\t\t.network_interface\n\t\t\t\t.input_data(&node_id, index, \"y\", context.selection_network_path)\n\t\t\t\t.and_then(|value| value.as_str())\n\t\t\t\t.unwrap_or_else(|| {\n\t\t\t\t\tlog::error!(\"Could not get y for vec2 input\");\n\t\t\t\t\t\"\"\n\t\t\t\t})\n\t\t\t\t.to_string();\n\t\t\tlet unit = context\n\t\t\t\t.network_interface\n\t\t\t\t.input_data(&node_id, index, \"unit\", context.selection_network_path)\n\t\t\t\t.and_then(|value| value.as_str())\n\t\t\t\t.unwrap_or_else(|| {\n\t\t\t\t\tlog::error!(\"Could not get unit for vec2 input\");\n\t\t\t\t\t\"\"\n\t\t\t\t})\n\t\t\t\t.to_string();\n\t\t\tlet min = context\n\t\t\t\t.network_interface\n\t\t\t\t.input_data(&node_id, index, \"min\", context.selection_network_path)\n\t\t\t\t.and_then(|value| value.as_f64());\n\t\t\tlet is_integer = context\n\t\t\t\t.network_interface\n\t\t\t\t.input_data(&node_id, index, \"is_integer\", context.selection_network_path)\n\t\t\t\t.and_then(|value| value.as_bool())\n\t\t\t\t.unwrap_or_default();\n\n\t\t\tOk(vec![node_properties::vec2_widget(\n\t\t\t\tParameterWidgetsInfo::new(node_id, index, true, context),\n\t\t\t\t&x,\n\t\t\t\t&y,\n\t\t\t\t&unit,\n\t\t\t\tmin,\n\t\t\t\tis_integer,\n\t\t\t)])\n\t\t}),\n\t);\n\tmap.insert(\n\t\t\"noise_properties_scale\".to_string(),\n\t\tBox::new(|node_id, index, context| {\n\t\t\tlet (_, coherent_noise_active, _, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?;\n\t\t\tlet scale = node_properties::number_widget(\n\t\t\t\tParameterWidgetsInfo::new(node_id, index, true, context),\n\t\t\t\tNumberInput::default().min(0.).disabled(!coherent_noise_active),\n\t\t\t);\n\t\t\tOk(vec![scale.into()])\n\t\t}),\n\t);\n\tmap.insert(\n\t\t\"noise_properties_noise_type\".to_string(),\n\t\tBox::new(|node_id, index, context| {\n\t\t\tlet noise_type_row = enum_choice::<NoiseType>().for_socket(ParameterWidgetsInfo::new(node_id, index, true, context)).property_row();\n\t\t\tOk(vec![noise_type_row, LayoutGroup::row(Vec::new())])\n\t\t}),\n\t);\n\tmap.insert(\n\t\t\"noise_properties_domain_warp_type\".to_string(),\n\t\tBox::new(|node_id, index, context| {\n\t\t\tlet (_, coherent_noise_active, _, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?;\n\t\t\tlet domain_warp_type = enum_choice::<DomainWarpType>()\n\t\t\t\t.for_socket(ParameterWidgetsInfo::new(node_id, index, true, context))\n\t\t\t\t.disabled(!coherent_noise_active)\n\t\t\t\t.property_row();\n\t\t\tOk(vec![domain_warp_type])\n\t\t}),\n\t);\n\tmap.insert(\n\t\t\"noise_properties_domain_warp_amplitude\".to_string(),\n\t\tBox::new(|node_id, index, context| {\n\t\t\tlet (_, coherent_noise_active, _, _, domain_warp_active, _) = node_properties::query_noise_pattern_state(node_id, context)?;\n\t\t\tlet domain_warp_amplitude = node_properties::number_widget(\n\t\t\t\tParameterWidgetsInfo::new(node_id, index, true, context),\n\t\t\t\tNumberInput::default().min(0.).disabled(!coherent_noise_active || !domain_warp_active),\n\t\t\t);\n\t\t\tOk(vec![domain_warp_amplitude.into(), LayoutGroup::row(Vec::new())])\n\t\t}),\n\t);\n\tmap.insert(\n\t\t\"noise_properties_fractal_type\".to_string(),\n\t\tBox::new(|node_id, index, context| {\n\t\t\tlet (_, coherent_noise_active, _, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?;\n\t\t\tlet fractal_type_row = enum_choice::<FractalType>()\n\t\t\t\t.for_socket(ParameterWidgetsInfo::new(node_id, index, true, context))\n\t\t\t\t.disabled(!coherent_noise_active)\n\t\t\t\t.property_row();\n\t\t\tOk(vec![fractal_type_row])\n\t\t}),\n\t);\n\tmap.insert(\n\t\t\"noise_properties_fractal_octaves\".to_string(),\n\t\tBox::new(|node_id, index, context| {\n\t\t\tlet (fractal_active, coherent_noise_active, _, _, _, domain_warp_only_fractal_type_wrongly_active) = node_properties::query_noise_pattern_state(node_id, context)?;\n\t\t\tlet fractal_octaves = node_properties::number_widget(\n\t\t\t\tParameterWidgetsInfo::new(node_id, index, true, context),\n\t\t\t\tNumberInput::default()\n\t\t\t\t\t.mode_range()\n\t\t\t\t\t.min(1.)\n\t\t\t\t\t.max(10.)\n\t\t\t\t\t.range_max(Some(4.))\n\t\t\t\t\t.is_integer(true)\n\t\t\t\t\t.disabled(!coherent_noise_active || !fractal_active || domain_warp_only_fractal_type_wrongly_active),\n\t\t\t);\n\t\t\tOk(vec![fractal_octaves.into()])\n\t\t}),\n\t);\n\tmap.insert(\n\t\t\"noise_properties_fractal_lacunarity\".to_string(),\n\t\tBox::new(|node_id, index, context| {\n\t\t\tlet (fractal_active, coherent_noise_active, _, _, _, domain_warp_only_fractal_type_wrongly_active) = node_properties::query_noise_pattern_state(node_id, context)?;\n\t\t\tlet fractal_lacunarity = node_properties::number_widget(\n\t\t\t\tParameterWidgetsInfo::new(node_id, index, true, context),\n\t\t\t\tNumberInput::default()\n\t\t\t\t\t.mode_range()\n\t\t\t\t\t.min(0.)\n\t\t\t\t\t.range_max(Some(10.))\n\t\t\t\t\t.disabled(!coherent_noise_active || !fractal_active || domain_warp_only_fractal_type_wrongly_active),\n\t\t\t);\n\t\t\tOk(vec![fractal_lacunarity.into()])\n\t\t}),\n\t);\n\tmap.insert(\n\t\t\"noise_properties_fractal_gain\".to_string(),\n\t\tBox::new(|node_id, index, context| {\n\t\t\tlet (fractal_active, coherent_noise_active, _, _, _, domain_warp_only_fractal_type_wrongly_active) = node_properties::query_noise_pattern_state(node_id, context)?;\n\t\t\tlet fractal_gain = node_properties::number_widget(\n\t\t\t\tParameterWidgetsInfo::new(node_id, index, true, context),\n\t\t\t\tNumberInput::default()\n\t\t\t\t\t.mode_range()\n\t\t\t\t\t.min(0.)\n\t\t\t\t\t.range_max(Some(10.))\n\t\t\t\t\t.disabled(!coherent_noise_active || !fractal_active || domain_warp_only_fractal_type_wrongly_active),\n\t\t\t);\n\t\t\tOk(vec![fractal_gain.into()])\n\t\t}),\n\t);\n\tmap.insert(\n\t\t\"noise_properties_fractal_weighted_strength\".to_string(),\n\t\tBox::new(|node_id, index, context| {\n\t\t\tlet (fractal_active, coherent_noise_active, _, _, _, domain_warp_only_fractal_type_wrongly_active) = node_properties::query_noise_pattern_state(node_id, context)?;\n\t\t\tlet fractal_weighted_strength = node_properties::number_widget(\n\t\t\t\tParameterWidgetsInfo::new(node_id, index, true, context),\n\t\t\t\tNumberInput::default()\n\t\t\t\t\t.mode_range()\n\t\t\t\t\t.min(0.)\n\t\t\t\t\t.max(1.) // Defined for the 0-1 range\n\t\t\t\t\t.disabled(!coherent_noise_active || !fractal_active || domain_warp_only_fractal_type_wrongly_active),\n\t\t\t);\n\t\t\tOk(vec![fractal_weighted_strength.into()])\n\t\t}),\n\t);\n\tmap.insert(\n\t\t\"noise_properties_ping_pong_strength\".to_string(),\n\t\tBox::new(|node_id, index, context| {\n\t\t\tlet (fractal_active, coherent_noise_active, _, ping_pong_active, _, domain_warp_only_fractal_type_wrongly_active) = node_properties::query_noise_pattern_state(node_id, context)?;\n\t\t\tlet fractal_ping_pong_strength = node_properties::number_widget(\n\t\t\t\tParameterWidgetsInfo::new(node_id, index, true, context),\n\t\t\t\tNumberInput::default()\n\t\t\t\t\t.mode_range()\n\t\t\t\t\t.min(0.)\n\t\t\t\t\t.range_max(Some(10.))\n\t\t\t\t\t.disabled(!ping_pong_active || !coherent_noise_active || !fractal_active || domain_warp_only_fractal_type_wrongly_active),\n\t\t\t);\n\t\t\tOk(vec![fractal_ping_pong_strength.into(), LayoutGroup::row(Vec::new())])\n\t\t}),\n\t);\n\tmap.insert(\n\t\t\"noise_properties_cellular_distance_function\".to_string(),\n\t\tBox::new(|node_id, index, context| {\n\t\t\tlet (_, coherent_noise_active, cellular_noise_active, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?;\n\t\t\tlet cellular_distance_function_row = enum_choice::<CellularDistanceFunction>()\n\t\t\t\t.for_socket(ParameterWidgetsInfo::new(node_id, index, true, context))\n\t\t\t\t.disabled(!coherent_noise_active || !cellular_noise_active)\n\t\t\t\t.property_row();\n\t\t\tOk(vec![cellular_distance_function_row])\n\t\t}),\n\t);\n\tmap.insert(\n\t\t\"noise_properties_cellular_return_type\".to_string(),\n\t\tBox::new(|node_id, index, context| {\n\t\t\tlet (_, coherent_noise_active, cellular_noise_active, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?;\n\t\t\tlet cellular_return_type = enum_choice::<CellularReturnType>()\n\t\t\t\t.for_socket(ParameterWidgetsInfo::new(node_id, index, true, context))\n\t\t\t\t.disabled(!coherent_noise_active || !cellular_noise_active)\n\t\t\t\t.property_row();\n\t\t\tOk(vec![cellular_return_type])\n\t\t}),\n\t);\n\tmap.insert(\n\t\t\"noise_properties_cellular_jitter\".to_string(),\n\t\tBox::new(|node_id, index, context| {\n\t\t\tlet (_, coherent_noise_active, cellular_noise_active, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?;\n\t\t\tlet cellular_jitter = node_properties::number_widget(\n\t\t\t\tParameterWidgetsInfo::new(node_id, index, true, context),\n\t\t\t\tNumberInput::default()\n\t\t\t\t\t.mode_range()\n\t\t\t\t\t.range_min(Some(0.))\n\t\t\t\t\t.range_max(Some(1.))\n\t\t\t\t\t.disabled(!coherent_noise_active || !cellular_noise_active),\n\t\t\t);\n\t\t\tOk(vec![cellular_jitter.into()])\n\t\t}),\n\t);\n\tmap.insert(\n\t\t\"assign_colors_gradient\".to_string(),\n\t\tBox::new(|node_id, index, context| {\n\t\t\tlet gradient_row = node_properties::color_widget(ParameterWidgetsInfo::new(node_id, index, true, context), ColorInput::default().allow_none(false));\n\t\t\tOk(vec![gradient_row])\n\t\t}),\n\t);\n\tmap.insert(\n\t\t\"assign_colors_seed\".to_string(),\n\t\tBox::new(|node_id, index, context| {\n\t\t\tlet randomize_enabled = node_properties::query_assign_colors_randomize(node_id, context)?;\n\t\t\tlet seed_row = node_properties::number_widget(\n\t\t\t\tParameterWidgetsInfo::new(node_id, index, true, context),\n\t\t\t\tNumberInput::default().min(0.).int().disabled(!randomize_enabled),\n\t\t\t);\n\t\t\tOk(vec![seed_row.into()])\n\t\t}),\n\t);\n\tmap.insert(\n\t\t\"assign_colors_repeat_every\".to_string(),\n\t\tBox::new(|node_id, index, context| {\n\t\t\tlet randomize_enabled = node_properties::query_assign_colors_randomize(node_id, context)?;\n\t\t\tlet repeat_every_row = node_properties::number_widget(\n\t\t\t\tParameterWidgetsInfo::new(node_id, index, true, context),\n\t\t\t\tNumberInput::default().min(0.).int().disabled(randomize_enabled),\n\t\t\t);\n\t\t\tOk(vec![repeat_every_row.into()])\n\t\t}),\n\t);\n\tmap.insert(\n\t\t\"transform_rotation\".to_string(),\n\t\tBox::new(|node_id, index, context| {\n\t\t\tlet mut widgets = node_properties::start_widgets(ParameterWidgetsInfo::new(node_id, index, true, context));\n\n\t\t\tlet document_node = node_properties::get_document_node(node_id, context)?;\n\t\t\tlet Some(input) = document_node.inputs.get(index) else {\n\t\t\t\treturn Err(\"Input not found in transform rotation input override\".to_string());\n\t\t\t};\n\t\t\tif let Some(&TaggedValue::F64(val)) = input.as_non_exposed_value() {\n\t\t\t\twidgets.extend_from_slice(&[\n\t\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\t\tNumberInput::new(Some(val))\n\t\t\t\t\t\t.unit(\"°\")\n\t\t\t\t\t\t.mode(NumberInputMode::Range)\n\t\t\t\t\t\t.range_min(Some(-180.))\n\t\t\t\t\t\t.range_max(Some(180.))\n\t\t\t\t\t\t.on_update(node_properties::update_value(\n\t\t\t\t\t\t\t|number_input: &NumberInput| TaggedValue::F64(number_input.value.unwrap()),\n\t\t\t\t\t\t\tnode_id,\n\t\t\t\t\t\t\tindex,\n\t\t\t\t\t\t))\n\t\t\t\t\t\t.on_commit(node_properties::commit_value)\n\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t]);\n\t\t\t}\n\n\t\t\tOk(vec![LayoutGroup::row(widgets)])\n\t\t}),\n\t);\n\t// Skew has a custom override that maps to degrees\n\tmap.insert(\n\t\t\"transform_skew\".to_string(),\n\t\tBox::new(|node_id, index, context| {\n\t\t\tlet mut widgets = node_properties::start_widgets(ParameterWidgetsInfo::new(node_id, index, true, context));\n\n\t\t\tlet document_node = node_properties::get_document_node(node_id, context)?;\n\t\t\tlet Some(input) = document_node.inputs.get(index) else {\n\t\t\t\treturn Err(\"Input not found in transform skew input override\".to_string());\n\t\t\t};\n\t\t\tif let Some(&TaggedValue::DVec2(val)) = input.as_non_exposed_value() {\n\t\t\t\twidgets.extend_from_slice(&[\n\t\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\t\tNumberInput::new(Some(val.x))\n\t\t\t\t\t\t.label(\"X\")\n\t\t\t\t\t\t.unit(\"°\")\n\t\t\t\t\t\t.min(-89.9)\n\t\t\t\t\t\t.max(89.9)\n\t\t\t\t\t\t.on_update(node_properties::update_value(\n\t\t\t\t\t\t\tmove |input: &NumberInput| TaggedValue::DVec2(DVec2::new(input.value.unwrap(), val.y)),\n\t\t\t\t\t\t\tnode_id,\n\t\t\t\t\t\t\tindex,\n\t\t\t\t\t\t))\n\t\t\t\t\t\t.on_commit(node_properties::commit_value)\n\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\t\t\t\tNumberInput::new(Some(val.y))\n\t\t\t\t\t\t.label(\"Y\")\n\t\t\t\t\t\t.unit(\"°\")\n\t\t\t\t\t\t.min(-89.9)\n\t\t\t\t\t\t.max(89.9)\n\t\t\t\t\t\t.on_update(node_properties::update_value(\n\t\t\t\t\t\t\tmove |input: &NumberInput| TaggedValue::DVec2(DVec2::new(val.x, input.value.unwrap())),\n\t\t\t\t\t\t\tnode_id,\n\t\t\t\t\t\t\tindex,\n\t\t\t\t\t\t))\n\t\t\t\t\t\t.on_commit(node_properties::commit_value)\n\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t]);\n\t\t\t}\n\n\t\t\tOk(vec![LayoutGroup::row(widgets)])\n\t\t}),\n\t);\n\tmap.insert(\n\t\t\"text_area\".to_string(),\n\t\tBox::new(|node_id, index, context| Ok(vec![LayoutGroup::row(node_properties::text_area_widget(ParameterWidgetsInfo::new(node_id, index, true, context)))])),\n\t);\n\tmap.insert(\n\t\t\"text_font\".to_string(),\n\t\tBox::new(|node_id, index, context| {\n\t\t\tlet (font, style) = node_properties::font_inputs(ParameterWidgetsInfo::new(node_id, index, true, context));\n\t\t\tlet mut result = vec![LayoutGroup::row(font)];\n\t\t\tif let Some(style) = style {\n\t\t\t\tresult.push(LayoutGroup::row(style));\n\t\t\t}\n\t\t\tOk(result)\n\t\t}),\n\t);\n\tmap.insert(\n\t\t\"artboard_background\".to_string(),\n\t\tBox::new(|node_id, index, context| {\n\t\t\tOk(vec![node_properties::color_widget(\n\t\t\t\tParameterWidgetsInfo::new(node_id, index, true, context),\n\t\t\t\tColorInput::default().allow_none(false),\n\t\t\t)])\n\t\t}),\n\t);\n\tmap.insert(\n\t\t\"text_align\".to_string(),\n\t\tBox::new(|node_id, index, context| {\n\t\t\tlet choices = enum_choice::<text::TextAlign>().for_socket(ParameterWidgetsInfo::new(node_id, index, true, context)).property_row();\n\t\t\tOk(vec![choices])\n\t\t}),\n\t);\n\tmap\n}\n\npub fn resolve_network_node_type(identifier: &str) -> Option<&'static DocumentNodeDefinition> {\n\tresolve_document_node_type(&DefinitionIdentifier::Network(identifier.into()))\n}\n\npub fn resolve_proto_node_type(identifier: ProtoNodeIdentifier) -> Option<&'static DocumentNodeDefinition> {\n\tresolve_document_node_type(&DefinitionIdentifier::ProtoNode(identifier))\n}\n\npub fn resolve_document_node_type(identifier: &DefinitionIdentifier) -> Option<&'static DocumentNodeDefinition> {\n\tDOCUMENT_NODE_TYPES.get(identifier)\n}\n\npub fn collect_node_types() -> Vec<FrontendNodeType> {\n\tDOCUMENT_NODE_TYPES\n\t\t.iter()\n\t\t.filter(|(_, definition)| !definition.category.is_empty())\n\t\t.map(|(identifier, definition)| {\n\t\t\tlet input_types = definition\n\t\t\t\t.node_template\n\t\t\t\t.document_node\n\t\t\t\t.inputs\n\t\t\t\t.iter()\n\t\t\t\t.map(|node_input| node_input.as_value().map(|node_value| node_value.ty().nested_type().to_string()).unwrap_or_default())\n\t\t\t\t.collect::<Vec<String>>();\n\t\t\tlet mut name = definition.node_template.persistent_node_metadata.display_name.clone();\n\t\t\tif name.is_empty() {\n\t\t\t\tname = identifier.implementation_name_from_identifier()\n\t\t\t}\n\t\t\tFrontendNodeType {\n\t\t\t\tidentifier: identifier.serialized(),\n\t\t\t\tname,\n\t\t\t\tcategory: definition.category.to_string(),\n\t\t\t\tinput_types,\n\t\t\t}\n\t\t})\n\t\t.collect()\n}\n\npub fn collect_node_descriptions() -> Vec<(String, String)> {\n\tDOCUMENT_NODE_TYPES\n\t\t.iter()\n\t\t.map(|(identifier, definition)| {\n\t\t\t(\n\t\t\t\tidentifier.serialized(),\n\t\t\t\tif definition.description != \"TODO\" { definition.description.to_string() } else { String::new() },\n\t\t\t)\n\t\t})\n\t\t.collect()\n}\n\nimpl DocumentNodeDefinition {\n\t/// Converts the [DocumentNodeDefinition] type to a [NodeTemplate], using the provided `input_override` and falling back to the default inputs.\n\t/// `input_override` does not have to be the correct length.\n\tpub fn node_template_input_override(&self, input_override: impl IntoIterator<Item = Option<NodeInput>>) -> NodeTemplate {\n\t\tlet mut template = self.node_template.clone();\n\t\t// TODO: Replace the .enumerate() with changing the iterator to take a tuple of (index, input) so the user is forced to provide the correct index\n\t\tinput_override.into_iter().enumerate().for_each(|(index, input_override)| {\n\t\t\tif let Some(input_override) = input_override {\n\t\t\t\t// Only value inputs should be overridden, since node inputs change graph structure and must be handled by the network interface.\n\t\t\t\t// However, this would require changing some tooling which creates multiple nodes at once, before they are inserted into the network.\n\t\t\t\t// debug_assert!(input_override.as_node().is_none(), \"Node inputs are not supported in input overrides\");\n\t\t\t\ttemplate.document_node.inputs[index] = input_override;\n\t\t\t}\n\t\t});\n\n\t\t// Ensure that the input properties are initialized for every Document Node input for every node\n\t\tfn populate_input_properties(node_template: &mut NodeTemplate, mut path: Vec<NodeId>) {\n\t\t\tif let Some(current_node) = path.pop() {\n\t\t\t\tlet DocumentNodeImplementation::Network(template_network) = &node_template.document_node.implementation else {\n\t\t\t\t\tlog::error!(\"Template network should always exist\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tlet Some(nested_network) = template_network.nested_network(&path) else {\n\t\t\t\t\tlog::error!(\"Nested network should exist for path\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tlet Some(input_length) = nested_network.nodes.get(&current_node).map(|node| node.inputs.len()) else {\n\t\t\t\t\tlog::error!(\"Could not get current node in nested network\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tlet Some(template_network_metadata) = &mut node_template.persistent_node_metadata.network_metadata else {\n\t\t\t\t\tlog::error!(\"Template should have metadata if it has network implementation\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tlet Some(nested_network_metadata) = template_network_metadata.nested_metadata_mut(&path) else {\n\t\t\t\t\tlog::error!(\"Path is not valid for network\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tlet Some(nested_node_metadata) = nested_network_metadata.persistent_metadata.node_metadata.get_mut(&current_node) else {\n\t\t\t\t\tlog::error!(\"Path is not valid for network\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tnested_node_metadata.persistent_metadata.input_metadata.resize_with(input_length, InputMetadata::default);\n\n\t\t\t\t// Recurse over all sub-nodes if the current node is a network implementation\n\t\t\t\tlet mut current_path = path.clone();\n\t\t\t\tcurrent_path.push(current_node);\n\t\t\t\tlet DocumentNodeImplementation::Network(template_network) = &node_template.document_node.implementation else {\n\t\t\t\t\tlog::error!(\"Template network should always exist\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tif let Some(current_nested_network) = template_network.nested_network(&current_path) {\n\t\t\t\t\tfor sub_node_id in current_nested_network.nodes.keys().cloned().collect::<Vec<_>>() {\n\t\t\t\t\t\tlet mut sub_path = current_path.clone();\n\t\t\t\t\t\tsub_path.push(sub_node_id);\n\t\t\t\t\t\tpopulate_input_properties(node_template, sub_path);\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t} else {\n\t\t\t\t// Base case\n\t\t\t\tlet input_len = node_template.document_node.inputs.len();\n\t\t\t\tnode_template.persistent_node_metadata.input_metadata.resize_with(input_len, InputMetadata::default);\n\t\t\t\tif let DocumentNodeImplementation::Network(node_template_network) = &node_template.document_node.implementation {\n\t\t\t\t\tfor sub_node_id in node_template_network.nodes.keys().cloned().collect::<Vec<_>>() {\n\t\t\t\t\t\tpopulate_input_properties(node_template, vec![sub_node_id]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tpopulate_input_properties(&mut template, Vec::new());\n\n\t\ttemplate\n\t}\n\n\t/// Converts the [DocumentNodeDefinition] type to a [NodeTemplate], completely default.\n\tpub fn default_node_template(&self) -> NodeTemplate {\n\t\tself.node_template_input_override(self.node_template.document_node.inputs.clone().into_iter().map(Some))\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/node_graph/mod.rs",
    "content": "pub mod document_node_definitions;\nmod node_graph_message;\nmod node_graph_message_handler;\npub mod node_properties;\npub mod utility_types;\n\n#[doc(inline)]\npub use node_graph_message::{NodeGraphMessage, NodeGraphMessageDiscriminant};\n#[doc(inline)]\npub use node_graph_message_handler::*;\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/node_graph/node_graph_message.rs",
    "content": "use super::utility_types::Direction;\nuse crate::messages::input_mapper::utility_types::input_keyboard::Key;\nuse crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::portfolio::document::utility_types::network_interface::{ImportOrExport, InputConnector, NodeTemplate, OutputConnector};\nuse crate::messages::prelude::*;\nuse glam::IVec2;\nuse graph_craft::document::value::TaggedValue;\nuse graph_craft::document::{NodeId, NodeInput};\nuse graph_craft::proto::GraphErrors;\nuse interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypesDelta;\n\n#[impl_message(Message, DocumentMessage, NodeGraph)]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum NodeGraphMessage {\n\tAddNodes {\n\t\tnodes: Vec<(NodeId, NodeTemplate)>,\n\t\tnew_ids: HashMap<NodeId, NodeId>,\n\t},\n\tAddPathNode,\n\tAddImport,\n\tAddPrimaryImport,\n\tAddSecondaryImport,\n\tAddExport,\n\tAddPrimaryExport,\n\tAddSecondaryExport,\n\tInit,\n\tSelectedNodesUpdated,\n\tCopy,\n\tCreateNodeInLayerNoTransaction {\n\t\tnode_type: DefinitionIdentifier,\n\t\tlayer: LayerNodeIdentifier,\n\t},\n\tCreateNodeInLayerWithTransaction {\n\t\tnode_type: DefinitionIdentifier,\n\t\tlayer: LayerNodeIdentifier,\n\t},\n\tCreateNodeFromContextMenu {\n\t\tnode_id: Option<NodeId>,\n\t\tnode_type: DefinitionIdentifier,\n\t\txy: Option<(i32, i32)>,\n\t\tadd_transaction: bool,\n\t},\n\tCreateWire {\n\t\toutput_connector: OutputConnector,\n\t\tinput_connector: InputConnector,\n\t},\n\tConnectUpstreamOutputToInput {\n\t\tdownstream_input: InputConnector,\n\t\tinput_connector: InputConnector,\n\t},\n\tCut,\n\tDeleteNodes {\n\t\tnode_ids: Vec<NodeId>,\n\t\tdelete_children: bool,\n\t},\n\tDeleteSelectedNodes {\n\t\tdelete_children: bool,\n\t},\n\tDisconnectInput {\n\t\tinput_connector: InputConnector,\n\t},\n\tDisconnectRootNode,\n\tEnterNestedNetwork,\n\tDuplicateSelectedNodes,\n\tExposeInput {\n\t\tinput_connector: InputConnector,\n\t\tset_to_exposed: bool,\n\t\tstart_transaction: bool,\n\t},\n\tExposeEncapsulatingPrimaryInput {\n\t\texposed: bool,\n\t},\n\tExposePrimaryExport {\n\t\texposed: bool,\n\t},\n\tInsertNode {\n\t\tnode_id: NodeId,\n\t\t// Boxed to reduce size of enum (1120 bytes to 8 bytes)\n\t\tnode_template: Box<NodeTemplate>,\n\t},\n\tInsertNodeBetween {\n\t\tnode_id: NodeId,\n\t\tinput_connector: InputConnector,\n\t\tinsert_node_input_index: usize,\n\t},\n\tMergeSelectedNodes,\n\tMoveLayerToStack {\n\t\tlayer: LayerNodeIdentifier,\n\t\tparent: LayerNodeIdentifier,\n\t\tinsert_index: usize,\n\t},\n\tMoveNodeToChainStart {\n\t\tnode_id: NodeId,\n\t\tparent: LayerNodeIdentifier,\n\t},\n\tSetChainPosition {\n\t\tnode_id: NodeId,\n\t},\n\tPasteNodes {\n\t\tserialized_nodes: String,\n\t},\n\tPointerDown {\n\t\tshift_click: bool,\n\t\tcontrol_click: bool,\n\t\talt_click: bool,\n\t\tright_click: bool,\n\t},\n\tPointerMove {\n\t\tshift: Key,\n\t},\n\tPointerUp,\n\tPointerOutsideViewport {\n\t\tshift: Key,\n\t},\n\tShakeNode,\n\tUpdateNodeGraphWidth,\n\tRemoveImport {\n\t\timport_index: usize,\n\t},\n\tRemoveExport {\n\t\texport_index: usize,\n\t},\n\tReorderImport {\n\t\tstart_index: usize,\n\t\tend_index: usize,\n\t},\n\tReorderExport {\n\t\tstart_index: usize,\n\t\tend_index: usize,\n\t},\n\tRunDocumentGraph,\n\tForceRunDocumentGraph,\n\tSelectedNodesAdd {\n\t\tnodes: Vec<NodeId>,\n\t},\n\tSelectedNodesRemove {\n\t\tnodes: Vec<NodeId>,\n\t},\n\tSelectedNodesSet {\n\t\tnodes: Vec<NodeId>,\n\t},\n\tSendClickTargets,\n\tEndSendClickTargets,\n\tUnloadWires,\n\tSendWires,\n\tUpdateVisibleNodes,\n\tSendGraph,\n\tSetInputValue {\n\t\tnode_id: NodeId,\n\t\tinput_index: usize,\n\t\tvalue: TaggedValue,\n\t},\n\tSetInput {\n\t\tinput_connector: InputConnector,\n\t\tinput: NodeInput,\n\t},\n\tSetDisplayName {\n\t\tnode_id: NodeId,\n\t\talias: String,\n\t\tskip_adding_history_step: bool,\n\t},\n\tSetDisplayNameImpl {\n\t\tnode_id: NodeId,\n\t\talias: String,\n\t},\n\tSetToNodeOrLayer {\n\t\tnode_id: NodeId,\n\t\tis_layer: bool,\n\t},\n\tShiftNodePosition {\n\t\tnode_id: NodeId,\n\t\tx: i32,\n\t\ty: i32,\n\t},\n\tShiftSelectedNodes {\n\t\tdirection: Direction,\n\t\trubber_band: bool,\n\t},\n\tShiftSelectedNodesByAmount {\n\t\tgraph_delta: IVec2,\n\t\trubber_band: bool,\n\t},\n\tTogglePreview {\n\t\tnode_id: NodeId,\n\t},\n\tTogglePreviewImpl {\n\t\tnode_id: NodeId,\n\t},\n\tSetImportExportName {\n\t\tname: String,\n\t\tindex: ImportOrExport,\n\t},\n\tSetImportExportNameImpl {\n\t\tname: String,\n\t\tindex: ImportOrExport,\n\t},\n\tToggleSelectedAsLayersOrNodes,\n\tToggleSelectedLocked,\n\tToggleLocked {\n\t\tnode_id: NodeId,\n\t},\n\tSetLocked {\n\t\tnode_id: NodeId,\n\t\tlocked: bool,\n\t},\n\tToggleSelectedIsPinned,\n\tToggleSelectedVisibility,\n\tToggleVisibility {\n\t\tnode_id: NodeId,\n\t},\n\tSetPinned {\n\t\tnode_id: NodeId,\n\t\tpinned: bool,\n\t},\n\tSetVisibility {\n\t\tnode_id: NodeId,\n\t\tvisible: bool,\n\t},\n\tSetLockedOrVisibilitySideEffects {\n\t\tnode_ids: Vec<NodeId>,\n\t},\n\tUpdateEdges,\n\tUpdateBoxSelection,\n\tUpdateImportsExports,\n\tUpdateLayerPanel,\n\tUpdateNewNodeGraph,\n\tUpdateTypes {\n\t\t#[serde(skip)]\n\t\tresolved_types: ResolvedDocumentNodeTypesDelta,\n\t\t#[serde(skip)]\n\t\tnode_graph_errors: GraphErrors,\n\t},\n\tUpdateActionButtons,\n\tUpdateGraphBarRight,\n\tUpdateInSelectedNetwork,\n\tUpdateHints,\n\tSendSelectedNodes,\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs",
    "content": "use super::node_properties;\nuse super::utility_types::{BoxSelection, ContextMenuInformation, DragStart, FrontendNode};\nuse crate::consts::GRID_SIZE;\nuse crate::messages::clipboard::utility_types::ClipboardContent;\nuse crate::messages::input_mapper::utility_types::macros::{action_shortcut, action_shortcut_manual};\nuse crate::messages::layout::utility_types::widget_prelude::*;\nuse crate::messages::portfolio::document::document_message_handler::navigation_controls;\nuse crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext;\nuse crate::messages::portfolio::document::node_graph::document_node_definitions::{\n\tDefinitionIdentifier, NodePropertiesContext, resolve_document_node_type, resolve_network_node_type, resolve_proto_node_type,\n};\nuse crate::messages::portfolio::document::node_graph::utility_types::{ContextMenuData, Direction, FrontendGraphDataType, NodeGraphErrorDiagnostic};\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::portfolio::document::utility_types::misc::GroupFolderType;\nuse crate::messages::portfolio::document::utility_types::network_interface::{\n\tself, FlowType, InputConnector, NodeNetworkInterface, NodeTemplate, NodeTypePersistentMetadata, OutputConnector, Previewing,\n};\nuse crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerPanelEntry};\nuse crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle, WirePath, WirePathUpdate, build_vector_wire};\nuse crate::messages::prelude::*;\nuse crate::messages::tool::common_functionality::auto_panning::AutoPanning;\nuse crate::messages::tool::common_functionality::graph_modification_utils::get_clip_mode;\nuse crate::messages::tool::common_functionality::utility_functions::make_path_editable_is_allowed;\nuse crate::messages::tool::tool_messages::tool_prelude::{Key, MouseMotion};\nuse crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};\nuse crate::messages::viewport::Position;\nuse glam::{DAffine2, DVec2, IVec2};\nuse graph_craft::document::value::TaggedValue;\nuse graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput};\nuse graphene_std::math::math_ext::QuadExt;\nuse graphene_std::vector::algorithms::bezpath_algorithms::bezpath_is_inside_bezpath;\nuse graphene_std::*;\nuse kurbo::{DEFAULT_ACCURACY, Shape};\nuse renderer::Quad;\nuse std::cmp::Ordering;\n\n#[derive(Debug, ExtractField)]\npub struct NodeGraphMessageContext<'a> {\n\tpub network_interface: &'a mut NodeNetworkInterface,\n\tpub selection_network_path: &'a [NodeId],\n\tpub breadcrumb_network_path: &'a [NodeId],\n\tpub document_id: DocumentId,\n\tpub collapsed: &'a mut CollapsedLayers,\n\tpub ipp: &'a InputPreprocessorMessageHandler,\n\tpub graph_view_overlay_open: bool,\n\tpub graph_fade_artwork_percentage: f64,\n\tpub navigation_handler: &'a NavigationMessageHandler,\n\tpub preferences: &'a PreferencesMessageHandler,\n\tpub layers_panel_open: bool,\n\tpub viewport: &'a ViewportMessageHandler,\n}\n\n#[derive(Debug, Clone, ExtractField)]\npub struct NodeGraphMessageHandler {\n\t// TODO: Remove network and move to NodeNetworkInterface\n\tpub network: Vec<NodeId>,\n\thas_selection: bool,\n\twidgets: [LayoutGroup; 2],\n\t/// Used to add a transaction for the first node move when dragging.\n\tbegin_dragging: bool,\n\t/// Used to prevent entering a nested network if the node is dragged after double clicking\n\tnode_has_moved_in_drag: bool,\n\t/// If dragging the selected nodes, this stores the starting position both in viewport and node graph coordinates,\n\t/// plus a flag indicating if it has been dragged since the mousedown began.\n\tpub drag_start: Option<(DragStart, bool)>,\n\t// Store the selected chain nodes on drag start so they can be reconnected if shaken\n\tpub drag_start_chain_nodes: Vec<NodeId>,\n\t/// If dragging the background to create a box selection, this stores its starting point in node graph coordinates,\n\t/// plus a flag indicating if it has been dragged since the mousedown began.\n\tpub box_selection_start: Option<(DVec2, bool)>,\n\t/// Restore the selection before box selection if it is aborted\n\tpub selection_before_pointer_down: Vec<NodeId>,\n\t/// If the grip icon is held during a drag, then shift without pushing other nodes\n\tshift_without_push: bool,\n\tdisconnecting: Option<InputConnector>,\n\tinitial_disconnecting: bool,\n\t/// Node to select on pointer up if multiple nodes are selected and they were not dragged.\n\tpub select_if_not_dragged: Option<NodeId>,\n\t/// The start of the dragged line (cannot be moved), stored in node graph coordinates.\n\tpub wire_in_progress_from_connector: Option<DVec2>,\n\t/// The end point of the dragged line (cannot be moved), stored in node graph coordinates.\n\tpub wire_in_progress_to_connector: Option<DVec2>,\n\t/// The data type determining the color of the wire being dragged.\n\tpub wire_in_progress_type: FrontendGraphDataType,\n\t/// State for the context menu popups.\n\tpub context_menu: Option<ContextMenuInformation>,\n\t/// Index of selected node to be deselected on pointer up when shift clicking an already selected node\n\tpub deselect_on_pointer_up: Option<usize>,\n\t/// Adds the auto panning functionality to the node graph when dragging a node or selection box to the edge of the viewport.\n\tauto_panning: AutoPanning,\n\t/// The node to preview on mouse up if alt-clicked\n\tpreview_on_mouse_up: Option<NodeId>,\n\t/// The index of the import that is being moved\n\treordering_import: Option<usize>,\n\t/// The index of the export that is being moved\n\treordering_export: Option<usize>,\n\t/// The end index of the moved connector\n\tend_index: Option<usize>,\n\t/// Used to keep track of what nodes are sent to the front end so that only visible ones are sent to the frontend\n\tfrontend_nodes: Vec<NodeId>,\n\t/// Used to keep track of what wires are sent to the front end so the old ones can be removed\n\tfrontend_wires: HashSet<(NodeId, usize)>,\n}\n\n/// NodeGraphMessageHandler always modifies the network which the selected nodes are in. No GraphOperationMessages should be added here, since those messages will always affect the document network.\n#[message_handler_data]\nimpl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeGraphMessageHandler {\n\tfn process_message(&mut self, message: NodeGraphMessage, responses: &mut VecDeque<Message>, context: NodeGraphMessageContext<'a>) {\n\t\tlet NodeGraphMessageContext {\n\t\t\tnetwork_interface,\n\t\t\tselection_network_path,\n\t\t\tbreadcrumb_network_path,\n\t\t\tdocument_id,\n\t\t\tcollapsed,\n\t\t\tipp,\n\t\t\tgraph_view_overlay_open,\n\t\t\tgraph_fade_artwork_percentage,\n\t\t\tnavigation_handler,\n\t\t\tpreferences,\n\t\t\tlayers_panel_open,\n\t\t\tviewport,\n\t\t} = context;\n\n\t\tmatch message {\n\t\t\t// TODO: automatically remove broadcast messages.\n\t\t\tNodeGraphMessage::AddNodes { nodes, new_ids } => {\n\t\t\t\tlet Some(new_layer_id) = new_ids.get(&NodeId(0)).cloned().or_else(|| nodes.first().map(|(node_id, _)| *node_id)) else {\n\t\t\t\t\tlog::error!(\"No nodes to add in AddNodes\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tnetwork_interface.insert_node_group(nodes, new_ids, selection_network_path);\n\n\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![new_layer_id] });\n\t\t\t}\n\t\t\tNodeGraphMessage::AddPathNode => {\n\t\t\t\tif let Some(layer) = make_path_editable_is_allowed(network_interface) {\n\t\t\t\t\tresponses.add(NodeGraphMessage::CreateNodeInLayerWithTransaction {\n\t\t\t\t\t\tnode_type: DefinitionIdentifier::Network(\"Path\".into()),\n\t\t\t\t\t\tlayer,\n\t\t\t\t\t});\n\t\t\t\t\tresponses.add(EventMessage::SelectionChanged);\n\t\t\t\t}\n\t\t\t}\n\t\t\tNodeGraphMessage::AddImport => {\n\t\t\t\tnetwork_interface.add_import(graph_craft::document::value::TaggedValue::None, true, -1, \"\", \"\", breadcrumb_network_path);\n\t\t\t\tresponses.add(NodeGraphMessage::SendGraph);\n\t\t\t}\n\t\t\tNodeGraphMessage::AddPrimaryImport => {\n\t\t\t\tif network_interface.number_of_imports(breadcrumb_network_path) == 0 {\n\t\t\t\t\tresponses.add(NodeGraphMessage::AddImport);\n\t\t\t\t} else {\n\t\t\t\t\tresponses.add(NodeGraphMessage::ExposeEncapsulatingPrimaryInput { exposed: true });\n\t\t\t\t}\n\t\t\t}\n\t\t\tNodeGraphMessage::AddSecondaryImport => {\n\t\t\t\t// If necessary, add a hidden primary import before the secondary import\n\t\t\t\tif network_interface.number_of_imports(breadcrumb_network_path) == 0 {\n\t\t\t\t\tresponses.add(NodeGraphMessage::AddImport);\n\t\t\t\t\tresponses.add(NodeGraphMessage::ExposeEncapsulatingPrimaryInput { exposed: false });\n\t\t\t\t}\n\n\t\t\t\t// Add the secondary import\n\t\t\t\tresponses.add(NodeGraphMessage::AddImport);\n\t\t\t}\n\t\t\tNodeGraphMessage::AddExport => {\n\t\t\t\tnetwork_interface.add_export(graph_craft::document::value::TaggedValue::None, -1, \"\", breadcrumb_network_path);\n\t\t\t\tresponses.add(NodeGraphMessage::SendGraph);\n\t\t\t}\n\t\t\tNodeGraphMessage::AddPrimaryExport => {\n\t\t\t\tif network_interface.number_of_exports(breadcrumb_network_path) == 0 {\n\t\t\t\t\tresponses.add(NodeGraphMessage::AddExport);\n\t\t\t\t} else {\n\t\t\t\t\tresponses.add(NodeGraphMessage::ExposePrimaryExport { exposed: true });\n\t\t\t\t}\n\t\t\t}\n\t\t\tNodeGraphMessage::AddSecondaryExport => {\n\t\t\t\t// If necessary, add a hidden primary import before the secondary import\n\t\t\t\tif network_interface.number_of_exports(breadcrumb_network_path) == 0 {\n\t\t\t\t\tresponses.add(NodeGraphMessage::AddExport);\n\t\t\t\t\tresponses.add(NodeGraphMessage::ExposePrimaryExport { exposed: false });\n\t\t\t\t}\n\n\t\t\t\t// Add the secondary export\n\t\t\t\tresponses.add(NodeGraphMessage::AddExport);\n\t\t\t}\n\t\t\tNodeGraphMessage::Init => {\n\t\t\t\tresponses.add(BroadcastMessage::SubscribeEvent {\n\t\t\t\t\ton: EventMessage::SelectionChanged,\n\t\t\t\t\tsend: Box::new(NodeGraphMessage::SelectedNodesUpdated.into()),\n\t\t\t\t});\n\t\t\t\tnetwork_interface.load_structure();\n\t\t\t\tcollapsed.0.retain(|&layer| network_interface.document_metadata().layer_exists(layer));\n\t\t\t}\n\t\t\tNodeGraphMessage::SelectedNodesUpdated => {\n\t\t\t\tlet selected_layers = network_interface.selected_nodes().selected_layers(network_interface.document_metadata()).collect::<Vec<_>>();\n\t\t\t\tif selected_layers.len() <= 1 {\n\t\t\t\t\tresponses.add(DocumentMessage::SetRangeSelectionLayer {\n\t\t\t\t\t\tnew_layer: selected_layers.first().cloned(),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tresponses.add(MenuBarMessage::SendLayout);\n\t\t\t\tresponses.add(NodeGraphMessage::UpdateLayerPanel);\n\t\t\t\tresponses.add(PropertiesPanelMessage::Refresh);\n\t\t\t\tresponses.add(NodeGraphMessage::SendSelectedNodes);\n\t\t\t\tresponses.add(ArtboardToolMessage::UpdateSelectedArtboard);\n\t\t\t\tresponses.add(DocumentMessage::DocumentStructureChanged);\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\tresponses.add(NodeGraphMessage::SendGraph);\n\t\t\t\tresponses.add(PortfolioMessage::SubmitActiveGraphRender);\n\t\t\t}\n\t\t\tNodeGraphMessage::CreateWire { output_connector, input_connector } => {\n\t\t\t\t// TODO: Add support for flattening NodeInput::Import exports in flatten_with_fns https://github.com/GraphiteEditor/Graphite/issues/1762\n\t\t\t\tif let (InputConnector::Export(_), OutputConnector::Import(_)) = (input_connector, output_connector) {\n\t\t\t\t\tlet mid_point = (network_interface.get_output_center(&output_connector, breadcrumb_network_path).unwrap()\n\t\t\t\t\t\t+ network_interface.get_input_center(&input_connector, breadcrumb_network_path).unwrap())\n\t\t\t\t\t\t/ 2.;\n\t\t\t\t\tlet node_template = Box::new(resolve_proto_node_type(graphene_core::ops::identity::IDENTIFIER).unwrap().default_node_template());\n\n\t\t\t\t\tlet node_id = NodeId::new();\n\t\t\t\t\tresponses.add(NodeGraphMessage::InsertNode { node_id, node_template });\n\t\t\t\t\tresponses.add(NodeGraphMessage::ShiftNodePosition {\n\t\t\t\t\t\tnode_id,\n\t\t\t\t\t\tx: (mid_point.x / 24.) as i32,\n\t\t\t\t\t\ty: (mid_point.y / 24.) as i32,\n\t\t\t\t\t});\n\t\t\t\t\tlet node_input_connector = InputConnector::node(node_id, 0);\n\t\t\t\t\tlet node_output_connector = OutputConnector::node(node_id, 0);\n\t\t\t\t\tresponses.add(NodeGraphMessage::CreateWire {\n\t\t\t\t\t\toutput_connector,\n\t\t\t\t\t\tinput_connector: node_input_connector,\n\t\t\t\t\t});\n\t\t\t\t\tresponses.add(NodeGraphMessage::CreateWire {\n\t\t\t\t\t\toutput_connector: node_output_connector,\n\t\t\t\t\t\tinput_connector,\n\t\t\t\t\t});\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tnetwork_interface.create_wire(&output_connector, &input_connector, selection_network_path);\n\t\t\t}\n\t\t\tNodeGraphMessage::Copy => {\n\t\t\t\tlet all_selected_nodes = network_interface.upstream_chain_nodes(selection_network_path);\n\t\t\t\t// Collect the selected nodes\n\t\t\t\tlet new_ids = &all_selected_nodes.iter().enumerate().map(|(new, old)| (*old, NodeId(new as u64))).collect();\n\t\t\t\tlet copied_nodes = network_interface.copy_nodes(new_ids, selection_network_path).collect::<Vec<_>>();\n\n\t\t\t\tlet Ok(data) = serde_json::to_string(&copied_nodes) else {\n\t\t\t\t\tlog::error!(\"Failed to serialize nodes for clipboard\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tresponses.add(ClipboardMessage::Write {\n\t\t\t\t\tcontent: ClipboardContent::Nodes(data),\n\t\t\t\t});\n\t\t\t}\n\t\t\tNodeGraphMessage::CreateNodeInLayerNoTransaction { node_type, layer } => {\n\t\t\t\tlet Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) else {\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tmodify_inputs.create_node(&node_type);\n\t\t\t}\n\t\t\tNodeGraphMessage::CreateNodeInLayerWithTransaction { node_type, layer } => {\n\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\t\t\t\tresponses.add(NodeGraphMessage::CreateNodeInLayerNoTransaction { node_type, layer });\n\t\t\t\tresponses.add(PropertiesPanelMessage::Refresh);\n\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t}\n\t\t\tNodeGraphMessage::CreateNodeFromContextMenu {\n\t\t\t\tnode_id,\n\t\t\t\tnode_type,\n\t\t\t\txy,\n\t\t\t\tadd_transaction,\n\t\t\t} => {\n\t\t\t\tlet (x, y) = if let Some((x, y)) = xy {\n\t\t\t\t\t(x, y)\n\t\t\t\t} else if let Some(node_graph_ptz) = network_interface.node_graph_ptz(breadcrumb_network_path) {\n\t\t\t\t\t((-node_graph_ptz.pan.x / GRID_SIZE as f64) as i32, (-node_graph_ptz.pan.y / GRID_SIZE as f64) as i32)\n\t\t\t\t} else {\n\t\t\t\t\t(0, 0)\n\t\t\t\t};\n\n\t\t\t\tlet node_id = node_id.unwrap_or_else(NodeId::new);\n\n\t\t\t\tlet Some(document_node_type) = resolve_document_node_type(&node_type) else {\n\t\t\t\t\tresponses.add(DialogMessage::DisplayDialogError {\n\t\t\t\t\t\ttitle: \"Cannot insert node\".to_string(),\n\t\t\t\t\t\tdescription: format!(\"The document node '{node_type:?}' does not exist in the document node list\"),\n\t\t\t\t\t});\n\t\t\t\t\treturn;\n\t\t\t\t};\n\n\t\t\t\tlet node_template = document_node_type.default_node_template();\n\t\t\t\tself.context_menu = None;\n\n\t\t\t\tif add_transaction {\n\t\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\t\t\t\t}\n\n\t\t\t\tresponses.add(NodeGraphMessage::InsertNode {\n\t\t\t\t\tnode_id,\n\t\t\t\t\tnode_template: Box::new(node_template.clone()),\n\t\t\t\t});\n\t\t\t\tresponses.add(NodeGraphMessage::ShiftNodePosition { node_id, x, y });\n\t\t\t\t// Only auto connect to the dragged wire if the node is being added to the currently opened network\n\t\t\t\tif let Some(output_connector_position) = self.wire_in_progress_from_connector {\n\t\t\t\t\tlet Some(network_metadata) = network_interface.network_metadata(selection_network_path) else {\n\t\t\t\t\t\tlog::error!(\"Could not get network metadata in CreateNodeFromContextMenu\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t};\n\t\t\t\t\tlet output_connector_position_viewport = network_metadata\n\t\t\t\t\t\t.persistent_metadata\n\t\t\t\t\t\t.navigation_metadata\n\t\t\t\t\t\t.node_graph_to_viewport\n\t\t\t\t\t\t.transform_point2(output_connector_position);\n\t\t\t\t\tlet Some(output_connector) = &network_interface.output_connector_from_click(output_connector_position_viewport, breadcrumb_network_path) else {\n\t\t\t\t\t\tlog::error!(\"Could not get output from connector start\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t};\n\n\t\t\t\t\t// Ensure connection is to correct input of new node. If it does not have an input then do not connect\n\t\t\t\t\tif let Some((input_index, _)) = node_template.document_node.inputs.iter().enumerate().find(|(_, input)| input.is_exposed()) {\n\t\t\t\t\t\tresponses.add(NodeGraphMessage::CreateWire {\n\t\t\t\t\t\t\toutput_connector: *output_connector,\n\t\t\t\t\t\t\tinput_connector: InputConnector::node(node_id, input_index),\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\t\t}\n\n\t\t\t\t\tself.wire_in_progress_from_connector = None;\n\t\t\t\t\tself.wire_in_progress_to_connector = None;\n\t\t\t\t\tself.wire_in_progress_type = FrontendGraphDataType::General;\n\t\t\t\t}\n\t\t\t\tresponses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None });\n\t\t\t\tresponses.add(FrontendMessage::UpdateContextMenuInformation {\n\t\t\t\t\tcontext_menu_information: self.context_menu.clone(),\n\t\t\t\t});\n\t\t\t\tresponses.add(NodeGraphMessage::SendGraph);\n\t\t\t}\n\t\t\tNodeGraphMessage::ConnectUpstreamOutputToInput { downstream_input, input_connector } => {\n\t\t\t\tlet Some(upstream_node) = network_interface.upstream_output_connector(&downstream_input, selection_network_path) else {\n\t\t\t\t\tlog::error!(\"Failed to find upstream node for downstream_input\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tresponses.add(NodeGraphMessage::CreateWire {\n\t\t\t\t\toutput_connector: upstream_node,\n\t\t\t\t\tinput_connector,\n\t\t\t\t});\n\t\t\t}\n\t\t\tNodeGraphMessage::Cut => {\n\t\t\t\tresponses.add(NodeGraphMessage::Copy);\n\t\t\t\tresponses.add(NodeGraphMessage::DeleteSelectedNodes { delete_children: true });\n\t\t\t}\n\t\t\tNodeGraphMessage::DeleteNodes { node_ids, delete_children } => {\n\t\t\t\tnetwork_interface.delete_nodes(node_ids, delete_children, selection_network_path);\n\t\t\t}\n\t\t\t// Deletes selected_nodes. If `reconnect` is true, then all children nodes (secondary input) of the selected nodes are deleted and the siblings (primary input/output) are reconnected.\n\t\t\t// If `reconnect` is false, then only the selected nodes are deleted and not reconnected.\n\t\t\tNodeGraphMessage::DeleteSelectedNodes { delete_children } => {\n\t\t\t\tlet Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get selected nodes in DeleteSelectedNodes\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\t\t\t\tresponses.add(NodeGraphMessage::DeleteNodes {\n\t\t\t\t\tnode_ids: selected_nodes.selected_nodes().cloned().collect::<Vec<_>>(),\n\t\t\t\t\tdelete_children,\n\t\t\t\t});\n\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesUpdated);\n\t\t\t\tresponses.add(NodeGraphMessage::SendGraph);\n\t\t\t}\n\t\t\tNodeGraphMessage::DisconnectInput { input_connector } => {\n\t\t\t\tnetwork_interface.disconnect_input(&input_connector, selection_network_path);\n\t\t\t}\n\t\t\tNodeGraphMessage::DisconnectRootNode => {\n\t\t\t\tnetwork_interface.start_previewing_without_restore(selection_network_path);\n\t\t\t}\n\t\t\tNodeGraphMessage::DuplicateSelectedNodes => {\n\t\t\t\tlet all_selected_nodes = network_interface.upstream_chain_nodes(selection_network_path);\n\n\t\t\t\tlet copy_ids = all_selected_nodes.iter().enumerate().map(|(new, id)| (*id, NodeId(new as u64))).collect::<HashMap<NodeId, NodeId>>();\n\n\t\t\t\t// Copy the selected nodes\n\t\t\t\tlet nodes = network_interface.copy_nodes(&copy_ids, selection_network_path).collect::<Vec<_>>();\n\n\t\t\t\tlet new_ids = nodes.iter().map(|(id, _)| (*id, NodeId::new())).collect::<HashMap<_, _>>();\n\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\t\t\t\tresponses.add(NodeGraphMessage::AddNodes { nodes, new_ids: new_ids.clone() });\n\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesSet {\n\t\t\t\t\tnodes: new_ids.values().cloned().collect(),\n\t\t\t\t});\n\t\t\t}\n\t\t\tNodeGraphMessage::EnterNestedNetwork => {\n\t\t\t\t// Do not enter the nested network if the node was dragged\n\t\t\t\tif self.node_has_moved_in_drag {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet Some(node_id) = network_interface.node_from_click(ipp.mouse.position, selection_network_path) else {\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tif network_interface\n\t\t\t\t\t.layer_click_target_from_click(ipp.mouse.position, network_interface::LayerClickTargetTypes::Visibility, selection_network_path)\n\t\t\t\t\t.is_some()\n\t\t\t\t{\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tif let Some(DocumentNodeImplementation::Network(_)) = network_interface.implementation(&node_id, selection_network_path) {\n\t\t\t\t\tresponses.add(DocumentMessage::EnterNestedNetwork { node_id });\n\t\t\t\t}\n\t\t\t}\n\t\t\tNodeGraphMessage::ExposeInput {\n\t\t\t\tinput_connector,\n\t\t\t\tset_to_exposed,\n\t\t\t\tstart_transaction,\n\t\t\t} => {\n\t\t\t\tlet InputConnector::Node { node_id, input_index } = input_connector else {\n\t\t\t\t\tlog::error!(\"Cannot expose/hide export\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tlet Some(node) = network_interface.document_node(&node_id, selection_network_path) else {\n\t\t\t\t\tlog::error!(\"Could not find node {node_id} in NodeGraphMessage::ExposeInput\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tlet Some(mut node_input) = node.inputs.get(input_index).cloned() else {\n\t\t\t\t\tlog::error!(\"Could not find input {input_index} in NodeGraphMessage::ExposeInput\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\n\t\t\t\t// If we're un-exposing an input that is not a value, then disconnect it. This will convert it to a value input,\n\t\t\t\t// so we can come back to handle this message again to set the exposed value in the second run-through.\n\t\t\t\tif !set_to_exposed && node_input.as_value().is_none() {\n\t\t\t\t\t// Reversed order because we are pushing front\n\t\t\t\t\tresponses.add_front(NodeGraphMessage::ExposeInput {\n\t\t\t\t\t\tinput_connector,\n\t\t\t\t\t\tset_to_exposed,\n\t\t\t\t\t\tstart_transaction: false,\n\t\t\t\t\t});\n\t\t\t\t\tresponses.add_front(NodeGraphMessage::DisconnectInput { input_connector });\n\t\t\t\t\tresponses.add_front(DocumentMessage::StartTransaction);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Add a history step, but only do so if we didn't already start a transaction in the first run-through of this message in the above code\n\t\t\t\tif start_transaction {\n\t\t\t\t\tresponses.add_front(DocumentMessage::StartTransaction);\n\t\t\t\t}\n\n\t\t\t\t// If this node's input is a value type, we set its chosen exposed state\n\t\t\t\tif let NodeInput::Value { exposed, .. } = &mut node_input {\n\t\t\t\t\t*exposed = set_to_exposed;\n\t\t\t\t}\n\t\t\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\t\t\tinput_connector: InputConnector::node(node_id, input_index),\n\t\t\t\t\tinput: node_input,\n\t\t\t\t});\n\n\t\t\t\t// Finish the history step\n\t\t\t\tresponses.add(DocumentMessage::CommitTransaction);\n\n\t\t\t\t// Update the graph UI and re-render\n\t\t\t\tif graph_view_overlay_open {\n\t\t\t\t\tresponses.add(PropertiesPanelMessage::Refresh);\n\t\t\t\t\tresponses.add(NodeGraphMessage::SendGraph);\n\t\t\t\t} else {\n\t\t\t\t\tresponses.add(DocumentMessage::GraphViewOverlay { open: true });\n\t\t\t\t\tresponses.add(NavigationMessage::FitViewportToSelection);\n\t\t\t\t\tresponses.add(DocumentMessage::ZoomCanvasTo100Percent);\n\t\t\t\t}\n\t\t\t}\n\t\t\tNodeGraphMessage::ExposeEncapsulatingPrimaryInput { exposed } => {\n\t\t\t\tlet Some((node_id, network_path)) = breadcrumb_network_path.split_last() else {\n\t\t\t\t\treturn;\n\t\t\t\t};\n\n\t\t\t\tlet encapsulating_connector = InputConnector::node(*node_id, 0);\n\t\t\t\tif !exposed {\n\t\t\t\t\tnetwork_interface.disconnect_input(&encapsulating_connector, network_path);\n\t\t\t\t}\n\n\t\t\t\tlet Some(mut input) = network_interface.input_from_connector(&encapsulating_connector, network_path).cloned() else {\n\t\t\t\t\treturn;\n\t\t\t\t};\n\n\t\t\t\tif let NodeInput::Value { exposed: old_exposed, .. } = &mut input {\n\t\t\t\t\t*old_exposed = exposed;\n\t\t\t\t}\n\n\t\t\t\tnetwork_interface.set_input(&encapsulating_connector, input, network_path);\n\n\t\t\t\tlet Some(outward_wires) = network_interface.outward_wires(breadcrumb_network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get outward wires in remove_import\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tlet Some(downstream_connections) = outward_wires.get(&OutputConnector::Import(0)).cloned() else {\n\t\t\t\t\tlog::error!(\"Could not get outward wires for import in remove_import\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\n\t\t\t\t// Disconnect all connections in the encapsulating network\n\t\t\t\tfor downstream_connection in &downstream_connections {\n\t\t\t\t\tnetwork_interface.disconnect_input(downstream_connection, breadcrumb_network_path);\n\t\t\t\t}\n\n\t\t\t\tresponses.add(NodeGraphMessage::UpdateImportsExports);\n\t\t\t\tresponses.add(NodeGraphMessage::SendWires);\n\t\t\t}\n\t\t\tNodeGraphMessage::ExposePrimaryExport { exposed } => {\n\t\t\t\tlet export_connector: InputConnector = InputConnector::Export(0);\n\t\t\t\tif !exposed {\n\t\t\t\t\tnetwork_interface.disconnect_input(&export_connector, breadcrumb_network_path);\n\t\t\t\t}\n\n\t\t\t\tlet Some(mut input) = network_interface.input_from_connector(&export_connector, breadcrumb_network_path).cloned() else {\n\t\t\t\t\treturn;\n\t\t\t\t};\n\n\t\t\t\tif let NodeInput::Value { exposed: old_exposed, .. } = &mut input {\n\t\t\t\t\t*old_exposed = exposed;\n\t\t\t\t}\n\n\t\t\t\tnetwork_interface.set_input(&export_connector, input, breadcrumb_network_path);\n\n\t\t\t\t// Disconnect all connections in the encapsulating network\n\t\t\t\tif let Some((encapsulating_node, encapsulating_path)) = breadcrumb_network_path.split_last() {\n\t\t\t\t\tlet Some(outward_wires) = network_interface.outward_wires(encapsulating_path) else {\n\t\t\t\t\t\tlog::error!(\"Could not get outward wires in remove_import\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t};\n\t\t\t\t\tlet Some(downstream_connections) = outward_wires.get(&OutputConnector::node(*encapsulating_node, 0)).cloned() else {\n\t\t\t\t\t\tlog::error!(\"Could not get outward wires for import in remove_import\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t};\n\n\t\t\t\t\tfor downstream_connection in &downstream_connections {\n\t\t\t\t\t\tnetwork_interface.disconnect_input(downstream_connection, encapsulating_path);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tresponses.add(NodeGraphMessage::UpdateImportsExports);\n\t\t\t}\n\t\t\tNodeGraphMessage::InsertNode { node_id, node_template } => {\n\t\t\t\tnetwork_interface.insert_node(node_id, *node_template, selection_network_path);\n\t\t\t}\n\t\t\tNodeGraphMessage::InsertNodeBetween {\n\t\t\t\tnode_id,\n\t\t\t\tinput_connector,\n\t\t\t\tinsert_node_input_index,\n\t\t\t} => {\n\t\t\t\tnetwork_interface.insert_node_between(&node_id, &input_connector, insert_node_input_index, selection_network_path);\n\t\t\t}\n\t\t\tNodeGraphMessage::MergeSelectedNodes => {\n\t\t\t\tlet new_ids = network_interface\n\t\t\t\t\t.selected_nodes_in_nested_network(breadcrumb_network_path)\n\t\t\t\t\t.unwrap()\n\t\t\t\t\t.selected_nodes()\n\t\t\t\t\t.map(|id| (*id, *id))\n\t\t\t\t\t.collect::<HashMap<NodeId, NodeId>>();\n\n\t\t\t\tlet copied_nodes = network_interface.copy_nodes(&new_ids, breadcrumb_network_path).collect::<Vec<_>>();\n\t\t\t\tlet selected_node_ids = copied_nodes.iter().map(|(node_id, _)| *node_id).collect::<HashSet<_>>();\n\t\t\t\tlet selected_node_ids_vec = copied_nodes.iter().map(|(node_id, _)| *node_id).collect::<Vec<_>>();\n\t\t\t\t// Mapping of the encapsulating node inputs/outputs to where it needs to be connected\n\t\t\t\tlet mut input_connections = Vec::new();\n\t\t\t\tlet mut output_connections = Vec::new();\n\t\t\t\t// Mapping of the inner nodes that need to be connected to the imports/exports\n\t\t\t\tlet mut import_connections = Vec::new();\n\t\t\t\tlet mut export_connections = Vec::new();\n\t\t\t\t// Scan current nodes top to bottom and find all inputs/outputs connected to nodes that are not in the copied nodes. These will represent the new imports and exports.\n\t\t\t\tlet Some(nodes_sorted_top_to_bottom) = network_interface.nodes_sorted_top_to_bottom(\n\t\t\t\t\tnetwork_interface.selected_nodes_in_nested_network(breadcrumb_network_path).unwrap().selected_nodes(),\n\t\t\t\t\tbreadcrumb_network_path,\n\t\t\t\t) else {\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\t// Ensure that nodes can be grouped by checking if there is an unselected node between selected nodes\n\t\t\t\tfor selected_node_id in &selected_node_ids {\n\t\t\t\t\tfor input_index in 0..network_interface.number_of_inputs(selected_node_id, breadcrumb_network_path) {\n\t\t\t\t\t\tlet input_connector = InputConnector::node(*selected_node_id, input_index);\n\t\t\t\t\t\tif let Some(upstream_deselected_node_id) = network_interface\n\t\t\t\t\t\t\t.upstream_output_connector(&input_connector, breadcrumb_network_path)\n\t\t\t\t\t\t\t.and_then(|output_connector| output_connector.node_id())\n\t\t\t\t\t\t\t.filter(|node_id| !selected_node_ids.contains(node_id))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tfor upstream_node_id in\n\t\t\t\t\t\t\t\tnetwork_interface.upstream_flow_back_from_nodes(vec![upstream_deselected_node_id], breadcrumb_network_path, network_interface::FlowType::UpstreamFlow)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tif selected_node_ids.contains(&upstream_node_id) {\n\t\t\t\t\t\t\t\t\tresponses.add(DialogMessage::DisplayDialogError {\n\t\t\t\t\t\t\t\t\t\ttitle: \"Error Grouping Nodes\".to_string(),\n\t\t\t\t\t\t\t\t\t\tdescription: \"A discontinuous selection of nodes cannot be grouped.\\nEnsure no deselected nodes are between selected nodes\".to_string(),\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor node_id in nodes_sorted_top_to_bottom {\n\t\t\t\t\tfor input_index in 0..network_interface.number_of_inputs(&node_id, breadcrumb_network_path) {\n\t\t\t\t\t\tlet current_input_connector = InputConnector::node(node_id, input_index);\n\t\t\t\t\t\tlet Some(upstream_connector) = network_interface.upstream_output_connector(&current_input_connector, breadcrumb_network_path) else {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t};\n\t\t\t\t\t\tif upstream_connector\n\t\t\t\t\t\t\t.node_id()\n\t\t\t\t\t\t\t.is_some_and(|upstream_node_id| selected_node_ids.iter().any(|copied_id| *copied_id == upstream_node_id))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// If the upstream connection is not part of the copied nodes, then connect it to the new imports, or add it if it has not already been added.\n\t\t\t\t\t\tlet import_index = input_connections.iter().position(|old_connection| old_connection == &upstream_connector).unwrap_or_else(|| {\n\t\t\t\t\t\t\tinput_connections.push(upstream_connector);\n\t\t\t\t\t\t\tinput_connections.len() - 1\n\t\t\t\t\t\t});\n\t\t\t\t\t\timport_connections.push((current_input_connector, import_index));\n\t\t\t\t\t}\n\t\t\t\t\tfor output_index in 0..network_interface.number_of_outputs(&node_id, breadcrumb_network_path) {\n\t\t\t\t\t\tlet current_output_connector = OutputConnector::node(node_id, output_index);\n\t\t\t\t\t\tlet Some(outward_wires) = network_interface.outward_wires(breadcrumb_network_path) else {\n\t\t\t\t\t\t\tlog::error!(\"Could not get outward wires in upstream_nodes_below_layer\");\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t};\n\t\t\t\t\t\tlet Some(downstream_connections) = outward_wires.get(&current_output_connector).cloned() else {\n\t\t\t\t\t\t\tlog::error!(\"Could not get downstream connections for {current_output_connector:?}\");\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\t// The output gets connected to all the previous inputs the node was connected to\n\t\t\t\t\t\tlet mut connect_output_to = Vec::new();\n\t\t\t\t\t\tfor downstream_connection in downstream_connections {\n\t\t\t\t\t\t\tif downstream_connection.node_id().is_some_and(|downstream_node_id| selected_node_ids.contains(&downstream_node_id)) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconnect_output_to.push(downstream_connection);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif !connect_output_to.is_empty() {\n\t\t\t\t\t\t\t// Every output connected to some non selected node forms a new export\n\t\t\t\t\t\t\texport_connections.push(current_output_connector);\n\t\t\t\t\t\t\toutput_connections.push(connect_output_to);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Use the network interface to add a default node, then set the imports, exports, paste the nodes inside, and connect them to the imports/exports\n\t\t\t\tlet encapsulating_node_id = NodeId::new();\n\t\t\t\tlet mut default_node_template = resolve_network_node_type(\"Default Network\").expect(\"Default Network node should exist\").default_node_template();\n\t\t\t\tlet Some(center_of_selected_nodes) = network_interface.selected_nodes_bounding_box(breadcrumb_network_path).map(|[a, b]| (a + b) / 2.) else {\n\t\t\t\t\tlog::error!(\"Could not get center of selected_nodes\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tlet center_of_selected_nodes_grid_space = IVec2::new((center_of_selected_nodes.x / 24. + 0.5).floor() as i32, (center_of_selected_nodes.y / 24. + 0.5).floor() as i32);\n\t\t\t\tdefault_node_template.persistent_node_metadata.node_type_metadata = NodeTypePersistentMetadata::node(center_of_selected_nodes_grid_space - IVec2::new(3, 1));\n\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\t\t\t\tresponses.add(NodeGraphMessage::InsertNode {\n\t\t\t\t\tnode_id: encapsulating_node_id,\n\t\t\t\t\tnode_template: Box::new(default_node_template),\n\t\t\t\t});\n\t\t\t\tresponses.add(NodeGraphMessage::SetDisplayNameImpl {\n\t\t\t\t\tnode_id: encapsulating_node_id,\n\t\t\t\t\talias: \"Untitled Node\".to_string(),\n\t\t\t\t});\n\n\t\t\t\tresponses.add(DocumentMessage::EnterNestedNetwork { node_id: encapsulating_node_id });\n\t\t\t\tfor _ in 0..input_connections.len() {\n\t\t\t\t\tresponses.add(NodeGraphMessage::AddImport);\n\t\t\t\t}\n\t\t\t\tfor _ in 0..output_connections.len() {\n\t\t\t\t\tresponses.add(NodeGraphMessage::AddExport);\n\t\t\t\t}\n\t\t\t\tresponses.add(NodeGraphMessage::AddNodes { nodes: copied_nodes, new_ids });\n\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes: selected_node_ids_vec.clone() });\n\n\t\t\t\t// Shift the nodes back to the origin\n\t\t\t\tresponses.add(NodeGraphMessage::ShiftSelectedNodesByAmount {\n\t\t\t\t\tgraph_delta: -center_of_selected_nodes_grid_space - IVec2::new(2, 2),\n\t\t\t\t\trubber_band: false,\n\t\t\t\t});\n\n\t\t\t\tfor (input_connector, import_index) in import_connections {\n\t\t\t\t\tresponses.add(NodeGraphMessage::CreateWire {\n\t\t\t\t\t\toutput_connector: OutputConnector::Import(import_index),\n\t\t\t\t\t\tinput_connector,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tfor (export_index, output_connector) in export_connections.into_iter().enumerate() {\n\t\t\t\t\tresponses.add(NodeGraphMessage::CreateWire {\n\t\t\t\t\t\toutput_connector,\n\t\t\t\t\t\tinput_connector: InputConnector::Export(export_index),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tresponses.add(DocumentMessage::ExitNestedNetwork { steps_back: 1 });\n\t\t\t\tfor (input_index, output_connector) in input_connections.into_iter().enumerate() {\n\t\t\t\t\tresponses.add(NodeGraphMessage::CreateWire {\n\t\t\t\t\t\toutput_connector,\n\t\t\t\t\t\tinput_connector: InputConnector::node(encapsulating_node_id, input_index),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tfor (output_index, input_connectors) in output_connections.into_iter().enumerate() {\n\t\t\t\t\tfor input_connector in input_connectors {\n\t\t\t\t\t\tresponses.add(NodeGraphMessage::CreateWire {\n\t\t\t\t\t\t\toutput_connector: OutputConnector::node(encapsulating_node_id, output_index),\n\t\t\t\t\t\t\tinput_connector,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tresponses.add(NodeGraphMessage::DeleteNodes {\n\t\t\t\t\tnode_ids: selected_node_ids_vec,\n\t\t\t\t\tdelete_children: false,\n\t\t\t\t});\n\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![encapsulating_node_id] });\n\t\t\t\tresponses.add(NodeGraphMessage::SendGraph);\n\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t}\n\t\t\tNodeGraphMessage::MoveLayerToStack { layer, parent, insert_index } => {\n\t\t\t\tnetwork_interface.move_layer_to_stack(layer, parent, insert_index, selection_network_path);\n\t\t\t}\n\t\t\tNodeGraphMessage::MoveNodeToChainStart { node_id, parent } => {\n\t\t\t\tnetwork_interface.move_node_to_chain_start(&node_id, parent, selection_network_path);\n\t\t\t}\n\t\t\tNodeGraphMessage::SetChainPosition { node_id } => {\n\t\t\t\tnetwork_interface.set_chain_position(&node_id, selection_network_path);\n\t\t\t}\n\t\t\tNodeGraphMessage::PasteNodes { serialized_nodes } => {\n\t\t\t\tlet data = match serde_json::from_str::<Vec<(NodeId, NodeTemplate)>>(&serialized_nodes) {\n\t\t\t\t\tOk(d) => d,\n\t\t\t\t\tErr(e) => {\n\t\t\t\t\t\twarn!(\"Invalid node data {e:?}\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tif data.is_empty() {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\n\t\t\t\tlet new_ids: HashMap<_, _> = data.iter().map(|(id, _)| (*id, NodeId::new())).collect();\n\t\t\t\tlet nodes: Vec<_> = new_ids.values().copied().collect();\n\t\t\t\tresponses.add(NodeGraphMessage::AddNodes {\n\t\t\t\t\tnodes: data,\n\t\t\t\t\tnew_ids: new_ids.clone(),\n\t\t\t\t});\n\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes })\n\t\t\t}\n\t\t\tNodeGraphMessage::PointerDown {\n\t\t\t\tshift_click,\n\t\t\t\tcontrol_click,\n\t\t\t\talt_click,\n\t\t\t\tright_click,\n\t\t\t} => {\n\t\t\t\tif selection_network_path != breadcrumb_network_path {\n\t\t\t\t\tlog::error!(\"Selection network path does not match breadcrumb network path in PointerDown\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet Some(network_metadata) = network_interface.network_metadata(selection_network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get network metadata in PointerDown\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\n\t\t\t\tlet click = ipp.mouse.position;\n\n\t\t\t\tlet node_graph_point = network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.inverse().transform_point2(click);\n\n\t\t\t\tif network_interface\n\t\t\t\t\t.layer_click_target_from_click(click, network_interface::LayerClickTargetTypes::Grip, selection_network_path)\n\t\t\t\t\t.is_some()\n\t\t\t\t{\n\t\t\t\t\tself.shift_without_push = true;\n\t\t\t\t}\n\n\t\t\t\tlet clicked_id = network_interface.node_from_click(click, selection_network_path);\n\t\t\t\tlet clicked_input = network_interface.input_connector_from_click(click, selection_network_path);\n\t\t\t\tlet clicked_output = network_interface.output_connector_from_click(click, selection_network_path);\n\t\t\t\tlet network_metadata = network_interface.network_metadata(selection_network_path).unwrap();\n\t\t\t\t// Create the add node popup on right click, then exit\n\t\t\t\tif right_click {\n\t\t\t\t\t// Abort dragging a node\n\t\t\t\t\tif self.drag_start.is_some() {\n\t\t\t\t\t\tself.drag_start = None;\n\t\t\t\t\t\tself.select_if_not_dragged = None;\n\t\t\t\t\t\tresponses.add(DocumentMessage::AbortTransaction);\n\t\t\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesSet {\n\t\t\t\t\t\t\tnodes: self.selection_before_pointer_down.clone(),\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\t// Abort a box selection\n\t\t\t\t\tif self.box_selection_start.is_some() {\n\t\t\t\t\t\tself.box_selection_start = None;\n\t\t\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesSet {\n\t\t\t\t\t\t\tnodes: self.selection_before_pointer_down.clone(),\n\t\t\t\t\t\t});\n\t\t\t\t\t\tresponses.add(FrontendMessage::UpdateBox { box_selection: None });\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\t// Abort dragging a wire\n\t\t\t\t\tif self.wire_in_progress_from_connector.is_some() {\n\t\t\t\t\t\tself.wire_in_progress_from_connector = None;\n\t\t\t\t\t\tself.wire_in_progress_to_connector = None;\n\t\t\t\t\t\tself.wire_in_progress_type = FrontendGraphDataType::General;\n\n\t\t\t\t\t\tresponses.add(DocumentMessage::AbortTransaction);\n\t\t\t\t\t\tresponses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None });\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tlet context_menu_data = if let Some(node_id) = clicked_id {\n\t\t\t\t\t\tlet currently_is_node = !network_interface.is_layer(&node_id, breadcrumb_network_path);\n\t\t\t\t\t\tlet can_be_layer = network_interface.is_eligible_to_be_layer(&node_id, breadcrumb_network_path);\n\n\t\t\t\t\t\t// Determine which layers the Lock/Unlock action would affect:\n\t\t\t\t\t\t// - If the right-clicked node is in the selection, it affects all selected layers\n\t\t\t\t\t\t// - If the right-clicked node is not in the selection, it affects just the right-clicked node\n\t\t\t\t\t\tlet selected_nodes = network_interface.selected_nodes_in_nested_network(selection_network_path);\n\t\t\t\t\t\tlet is_clicked_selected = selected_nodes.as_ref().is_some_and(|selected| selected.selected_nodes().any(|id| *id == node_id));\n\t\t\t\t\t\tlet affected_layer_ids = if is_clicked_selected {\n\t\t\t\t\t\t\tselected_nodes.map(|selected| selected.selected_nodes().copied().filter(|id| network_interface.is_layer(id, selection_network_path)).collect())\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tnetwork_interface.is_layer(&node_id, selection_network_path).then(|| vec![node_id])\n\t\t\t\t\t\t}\n\t\t\t\t\t\t.unwrap_or_default();\n\t\t\t\t\t\tlet has_selected_layers = !affected_layer_ids.is_empty();\n\t\t\t\t\t\tlet all_selected_layers_locked = has_selected_layers && affected_layer_ids.iter().all(|id| network_interface.is_locked(id, selection_network_path));\n\n\t\t\t\t\t\tContextMenuData::ModifyNode {\n\t\t\t\t\t\t\tcan_be_layer,\n\t\t\t\t\t\t\tcurrently_is_node,\n\t\t\t\t\t\t\tnode_id,\n\t\t\t\t\t\t\thas_selected_layers,\n\t\t\t\t\t\t\tall_selected_layers_locked,\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tContextMenuData::CreateNode { compatible_type: None }\n\t\t\t\t\t};\n\n\t\t\t\t\t// TODO: Create function\n\t\t\t\t\tlet node_graph_shift = if matches!(context_menu_data, ContextMenuData::CreateNode { compatible_type: None }) {\n\t\t\t\t\t\tlet appear_right_of_mouse = if click.x > viewport.size().x() - 180. { -180. } else { 0. };\n\t\t\t\t\t\tlet appear_above_mouse = if click.y > viewport.size().y() - 200. { -200. } else { 0. };\n\t\t\t\t\t\tDVec2::new(appear_right_of_mouse, appear_above_mouse) / network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.matrix2.x_axis.x\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlet appear_right_of_mouse = if click.x > viewport.size().x() - 173. { -173. } else { 0. };\n\t\t\t\t\t\tlet appear_above_mouse = if click.y > viewport.size().y() - 34. { -34. } else { 0. };\n\t\t\t\t\t\tDVec2::new(appear_right_of_mouse, appear_above_mouse) / network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.matrix2.x_axis.x\n\t\t\t\t\t};\n\n\t\t\t\t\tself.context_menu = Some(ContextMenuInformation {\n\t\t\t\t\t\tcontext_menu_coordinates: (node_graph_point + node_graph_shift).as_ivec2().into(),\n\t\t\t\t\t\tcontext_menu_data,\n\t\t\t\t\t});\n\n\t\t\t\t\tresponses.add(FrontendMessage::UpdateContextMenuInformation {\n\t\t\t\t\t\tcontext_menu_information: self.context_menu.clone(),\n\t\t\t\t\t});\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet Some(modify_import_export) = network_interface.modify_import_export(selection_network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get modify import export in PointerDown\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\n\t\t\t\tif let Some(remove_import_index) = modify_import_export.remove_imports_exports.clicked_output_port_from_point(node_graph_point) {\n\t\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\t\t\t\t\tif remove_import_index == 0 {\n\t\t\t\t\t\tresponses.add(NodeGraphMessage::ExposeEncapsulatingPrimaryInput { exposed: false })\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresponses.add(NodeGraphMessage::RemoveImport { import_index: remove_import_index });\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t} else if let Some(remove_export_index) = modify_import_export.remove_imports_exports.clicked_input_port_from_point(node_graph_point) {\n\t\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\t\t\t\t\tif remove_export_index == 0 {\n\t\t\t\t\t\tresponses.add(NodeGraphMessage::ExposePrimaryExport { exposed: false })\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresponses.add(NodeGraphMessage::RemoveExport { export_index: remove_export_index });\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t} else if let Some(move_import_index) = modify_import_export.reorder_imports_exports.clicked_output_port_from_point(node_graph_point) {\n\t\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\t\t\t\t\tself.reordering_import = Some(move_import_index);\n\t\t\t\t\treturn;\n\t\t\t\t} else if let Some(move_export_index) = modify_import_export.reorder_imports_exports.clicked_input_port_from_point(node_graph_point) {\n\t\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\t\t\t\t\tself.reordering_export = Some(move_export_index);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tself.selection_before_pointer_down = network_interface\n\t\t\t\t\t.selected_nodes_in_nested_network(selection_network_path)\n\t\t\t\t\t.map(|selected_nodes| selected_nodes.selected_nodes().cloned().collect())\n\t\t\t\t\t.unwrap_or_default();\n\n\t\t\t\t// Since the user is clicking elsewhere in the graph, ensure the add nodes list is closed\n\t\t\t\tif self.context_menu.is_some() {\n\t\t\t\t\tself.context_menu = None;\n\t\t\t\t\tself.wire_in_progress_from_connector = None;\n\t\t\t\t\tself.wire_in_progress_to_connector = None;\n\t\t\t\t\tself.wire_in_progress_type = FrontendGraphDataType::General;\n\n\t\t\t\t\tresponses.add(FrontendMessage::UpdateContextMenuInformation {\n\t\t\t\t\t\tcontext_menu_information: self.context_menu.clone(),\n\t\t\t\t\t});\n\t\t\t\t\tresponses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None });\n\t\t\t\t}\n\n\t\t\t\t// Toggle visibility of clicked node and return\n\t\t\t\tif let Some(clicked_visibility) = network_interface.layer_click_target_from_click(click, network_interface::LayerClickTargetTypes::Visibility, selection_network_path) {\n\t\t\t\t\tresponses.add(NodeGraphMessage::ToggleVisibility { node_id: clicked_visibility });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Toggle lock of clicked node and return\n\t\t\t\tif let Some(clicked_lock) = network_interface.layer_click_target_from_click(click, network_interface::LayerClickTargetTypes::Lock, selection_network_path) {\n\t\t\t\t\tresponses.add(NodeGraphMessage::ToggleLocked { node_id: clicked_lock });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Alt-click sets the clicked node as previewed\n\t\t\t\tif alt_click && let Some(clicked_node) = clicked_id {\n\t\t\t\t\tself.preview_on_mouse_up = Some(clicked_node);\n\t\t\t\t}\n\n\t\t\t\t// Begin moving an existing wire\n\t\t\t\tif let Some(clicked_input) = &clicked_input {\n\t\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\t\t\t\t\tself.initial_disconnecting = true;\n\t\t\t\t\tself.disconnecting = Some(*clicked_input);\n\n\t\t\t\t\tlet output_connector = if *clicked_input == InputConnector::Export(0) {\n\t\t\t\t\t\tnetwork_interface.root_node(breadcrumb_network_path).map(|root_node| root_node.to_connector())\n\t\t\t\t\t} else {\n\t\t\t\t\t\tnetwork_interface.upstream_output_connector(clicked_input, breadcrumb_network_path)\n\t\t\t\t\t};\n\t\t\t\t\tlet Some(output_connector) = output_connector else { return };\n\t\t\t\t\tself.wire_in_progress_from_connector = network_interface.output_position(&output_connector, breadcrumb_network_path);\n\n\t\t\t\t\tself.wire_in_progress_type = network_interface.output_type(&output_connector, breadcrumb_network_path).displayed_type();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Begin creating a new wire\n\t\t\t\tif let Some(clicked_output) = clicked_output {\n\t\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\t\t\t\t\tself.initial_disconnecting = false;\n\n\t\t\t\t\tself.wire_in_progress_from_connector = network_interface.output_position(&clicked_output, selection_network_path);\n\t\t\t\t\tlet output_type = network_interface.output_type(&clicked_output, breadcrumb_network_path);\n\t\t\t\t\tself.wire_in_progress_type = output_type.displayed_type();\n\n\t\t\t\t\tself.update_node_graph_hints(responses);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif let Some(clicked_id) = clicked_id {\n\t\t\t\t\tlet Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else {\n\t\t\t\t\t\tlog::error!(\"Could not get selected nodes in PointerDown\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t};\n\t\t\t\t\tlet mut updated_selected = selected_nodes.selected_nodes().cloned().collect::<Vec<_>>();\n\t\t\t\t\tlet mut modified_selected = false;\n\n\t\t\t\t\t// Add to/remove from selection if holding Shift or Ctrl\n\t\t\t\t\tif shift_click || control_click {\n\t\t\t\t\t\tmodified_selected = true;\n\n\t\t\t\t\t\tlet index = updated_selected.iter().enumerate().find_map(|(i, node_id)| if *node_id == clicked_id { Some(i) } else { None });\n\t\t\t\t\t\t// Remove from selection (on PointerUp) if already selected\n\t\t\t\t\t\tself.deselect_on_pointer_up = index;\n\n\t\t\t\t\t\t// Add to selection if not already selected. Necessary in order to drag multiple nodes\n\t\t\t\t\t\tif index.is_none() {\n\t\t\t\t\t\t\tupdated_selected.push(clicked_id);\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\t// Replace selection with a non-selected node\n\t\t\t\t\telse if !updated_selected.contains(&clicked_id) {\n\t\t\t\t\t\tmodified_selected = true;\n\t\t\t\t\t\tupdated_selected = vec![clicked_id];\n\t\t\t\t\t}\n\t\t\t\t\t// Replace selection (of multiple nodes including this one) with just this one, but only upon pointer up if the user didn't drag the selected nodes\n\t\t\t\t\telse {\n\t\t\t\t\t\tself.select_if_not_dragged = Some(clicked_id);\n\t\t\t\t\t}\n\n\t\t\t\t\t// If this node is selected (whether from before or just now), prepare it for dragging\n\t\t\t\t\tif updated_selected.contains(&clicked_id) {\n\t\t\t\t\t\tlet drag_start = DragStart {\n\t\t\t\t\t\t\tstart_x: node_graph_point.x,\n\t\t\t\t\t\t\tstart_y: node_graph_point.y,\n\t\t\t\t\t\t\tround_x: 0,\n\t\t\t\t\t\t\tround_y: 0,\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tself.drag_start = Some((drag_start, false));\n\t\t\t\t\t\tlet selected_chain_nodes = updated_selected\n\t\t\t\t\t\t\t.iter()\n\t\t\t\t\t\t\t.filter(|node_id| network_interface.is_chain(node_id, selection_network_path))\n\t\t\t\t\t\t\t.copied()\n\t\t\t\t\t\t\t.collect::<Vec<_>>();\n\t\t\t\t\t\tself.drag_start_chain_nodes = selected_chain_nodes\n\t\t\t\t\t\t\t.iter()\n\t\t\t\t\t\t\t.flat_map(|selected| {\n\t\t\t\t\t\t\t\tnetwork_interface\n\t\t\t\t\t\t\t\t\t.upstream_flow_back_from_nodes(vec![*selected], selection_network_path, FlowType::PrimaryFlow)\n\t\t\t\t\t\t\t\t\t.skip(1)\n\t\t\t\t\t\t\t\t\t.filter(|node_id| network_interface.is_chain(node_id, selection_network_path))\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.collect::<Vec<_>>();\n\t\t\t\t\t\tself.begin_dragging = true;\n\t\t\t\t\t\tself.node_has_moved_in_drag = false;\n\t\t\t\t\t\tself.update_node_graph_hints(responses);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Update the selection if it was modified\n\t\t\t\t\tif modified_selected {\n\t\t\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes: updated_selected })\n\t\t\t\t\t}\n\t\t\t\t\t// Start the transaction after setting the node, since when the transactions ends it aborts any changes after this\n\t\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Clicked on the graph background so we box select\n\t\t\t\tif !shift_click && !alt_click {\n\t\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes: Vec::new() })\n\t\t\t\t}\n\t\t\t\tself.box_selection_start = Some((node_graph_point, false));\n\t\t\t\tself.update_node_graph_hints(responses);\n\t\t\t}\n\t\t\tNodeGraphMessage::PointerMove { shift } => {\n\t\t\t\tif selection_network_path != breadcrumb_network_path {\n\t\t\t\t\tlog::error!(\"Selection network path does not match breadcrumb network path in PointerMove\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet Some(network_metadata) = network_interface.network_metadata(selection_network_path) else {\n\t\t\t\t\treturn;\n\t\t\t\t};\n\n\t\t\t\t// Auto-panning\n\t\t\t\tlet messages = [NodeGraphMessage::PointerOutsideViewport { shift }.into(), NodeGraphMessage::PointerMove { shift }.into()];\n\t\t\t\tself.auto_panning.setup_by_mouse_position(ipp, viewport, &messages, responses);\n\n\t\t\t\tlet viewport_location = ipp.mouse.position;\n\t\t\t\tlet point = network_metadata\n\t\t\t\t\t.persistent_metadata\n\t\t\t\t\t.navigation_metadata\n\t\t\t\t\t.node_graph_to_viewport\n\t\t\t\t\t.inverse()\n\t\t\t\t\t.transform_point2(viewport_location);\n\n\t\t\t\tif self.wire_in_progress_from_connector.is_some() && self.context_menu.is_none() {\n\t\t\t\t\tlet to_connector = network_interface.input_connector_from_click(ipp.mouse.position, selection_network_path);\n\t\t\t\t\tif let Some(to_connector) = &to_connector {\n\t\t\t\t\t\tlet Some(input_position) = network_interface.input_position(to_connector, selection_network_path) else {\n\t\t\t\t\t\t\tlog::error!(\"Could not get input position for connector: {to_connector:?}\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t};\n\t\t\t\t\t\tself.wire_in_progress_to_connector = Some(input_position);\n\t\t\t\t\t}\n\t\t\t\t\t// Not hovering over a node input or node output, update with the mouse position.\n\t\t\t\t\telse {\n\t\t\t\t\t\tself.wire_in_progress_to_connector = Some(point);\n\t\t\t\t\t\t// Disconnect if the wire was previously connected to an input\n\t\t\t\t\t\tif let Some(disconnecting) = &self.disconnecting {\n\t\t\t\t\t\t\tlet mut disconnect_root_node = false;\n\t\t\t\t\t\t\tif let Previewing::Yes { root_node_to_restore } = network_interface.previewing(selection_network_path)\n\t\t\t\t\t\t\t\t&& root_node_to_restore.is_some()\n\t\t\t\t\t\t\t\t&& *disconnecting == InputConnector::Export(0)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tdisconnect_root_node = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif disconnect_root_node {\n\t\t\t\t\t\t\t\tresponses.add(NodeGraphMessage::DisconnectRootNode);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tresponses.add(NodeGraphMessage::DisconnectInput { input_connector: *disconnecting });\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// Update the frontend that the node is disconnected\n\t\t\t\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\t\t\t\tresponses.add(NodeGraphMessage::SendGraph);\n\t\t\t\t\t\t\tself.disconnecting = None;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif let (Some(wire_in_progress_from_connector), Some(wire_in_progress_to_connector)) = (self.wire_in_progress_from_connector, self.wire_in_progress_to_connector) {\n\t\t\t\t\t\t// If performance is a concern this can be stored as a field in the wire_in_progress_from/to_connector struct, and updated when snapping to an output\n\t\t\t\t\t\tlet Some(network_metadata) = network_interface.network_metadata(selection_network_path) else {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t};\n\t\t\t\t\t\tlet from_connector_viewport = network_metadata\n\t\t\t\t\t\t\t.persistent_metadata\n\t\t\t\t\t\t\t.navigation_metadata\n\t\t\t\t\t\t\t.node_graph_to_viewport\n\t\t\t\t\t\t\t.transform_point2(wire_in_progress_from_connector);\n\t\t\t\t\t\tlet from_connector_is_layer = network_interface\n\t\t\t\t\t\t\t.output_connector_from_click(from_connector_viewport, selection_network_path)\n\t\t\t\t\t\t\t.is_some_and(|output_connector| {\n\t\t\t\t\t\t\t\tif let OutputConnector::Node { node_id, .. } = output_connector {\n\t\t\t\t\t\t\t\t\tnetwork_interface.is_layer(&node_id, selection_network_path)\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tfalse\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\tlet to_connector_is_layer = to_connector.is_some_and(|to_connector| {\n\t\t\t\t\t\t\tif let InputConnector::Node { node_id, input_index } = to_connector {\n\t\t\t\t\t\t\t\tinput_index == 0 && network_interface.is_layer(&node_id, selection_network_path)\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tfalse\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t\tlet vector_wire = build_vector_wire(\n\t\t\t\t\t\t\twire_in_progress_from_connector,\n\t\t\t\t\t\t\twire_in_progress_to_connector,\n\t\t\t\t\t\t\tfrom_connector_is_layer,\n\t\t\t\t\t\t\tto_connector_is_layer,\n\t\t\t\t\t\t\tGraphWireStyle::Direct,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tlet path_string = vector_wire.to_svg();\n\t\t\t\t\t\tlet wire_path = WirePath {\n\t\t\t\t\t\t\tpath_string,\n\t\t\t\t\t\t\tdata_type: self.wire_in_progress_type,\n\t\t\t\t\t\t\tthick: false,\n\t\t\t\t\t\t\tdashed: false,\n\t\t\t\t\t\t};\n\t\t\t\t\t\tresponses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: Some(wire_path) });\n\t\t\t\t\t}\n\t\t\t\t} else if let Some((drag_start, dragged)) = &mut self.drag_start {\n\t\t\t\t\tif drag_start.start_x != point.x || drag_start.start_y != point.y {\n\t\t\t\t\t\t*dragged = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tself.node_has_moved_in_drag = true;\n\t\t\t\t\tif self.begin_dragging {\n\t\t\t\t\t\tself.begin_dragging = false;\n\t\t\t\t\t\tif ipp.keyboard.get(Key::Alt as usize) {\n\t\t\t\t\t\t\tresponses.add(NodeGraphMessage::DuplicateSelectedNodes);\n\t\t\t\t\t\t\t// Duplicating sets a 2x2 offset, so shift the nodes back to the original position\n\t\t\t\t\t\t\tresponses.add(NodeGraphMessage::ShiftSelectedNodesByAmount {\n\t\t\t\t\t\t\t\tgraph_delta: IVec2::new(-2, -2),\n\t\t\t\t\t\t\t\trubber_band: false,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tself.preview_on_mouse_up = None;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tlet mut graph_delta = IVec2::new(((point.x - drag_start.start_x) / 24.).round() as i32, ((point.y - drag_start.start_y) / 24.).round() as i32);\n\t\t\t\t\tlet previous_round_x = drag_start.round_x;\n\t\t\t\t\tlet previous_round_y = drag_start.round_y;\n\n\t\t\t\t\tdrag_start.round_x = graph_delta.x;\n\t\t\t\t\tdrag_start.round_y = graph_delta.y;\n\n\t\t\t\t\tgraph_delta.x -= previous_round_x;\n\t\t\t\t\tgraph_delta.y -= previous_round_y;\n\n\t\t\t\t\tresponses.add(NodeGraphMessage::ShiftSelectedNodesByAmount { graph_delta, rubber_band: true });\n\n\t\t\t\t\tself.update_node_graph_hints(responses);\n\t\t\t\t} else if let Some((_, box_selection_dragged)) = &mut self.box_selection_start {\n\t\t\t\t\t*box_selection_dragged = true;\n\t\t\t\t\tresponses.add(NodeGraphMessage::UpdateBoxSelection);\n\t\t\t\t\tself.update_node_graph_hints(responses);\n\t\t\t\t} else if self.reordering_import.is_some() {\n\t\t\t\t\tlet Some(modify_import_export) = network_interface.modify_import_export(selection_network_path) else {\n\t\t\t\t\t\tlog::error!(\"Could not get modify import export in PointerMove\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t};\n\t\t\t\t\t// Find the first import that is below the mouse position\n\t\t\t\t\tself.end_index = Some(\n\t\t\t\t\t\tmodify_import_export\n\t\t\t\t\t\t\t.reorder_imports_exports\n\t\t\t\t\t\t\t.output_ports()\n\t\t\t\t\t\t\t.find_map(|(index, click_target)| {\n\t\t\t\t\t\t\t\tlet Some(position) = click_target.bounding_box().map(|bbox| (bbox[0].y + bbox[1].y) / 2.) else {\n\t\t\t\t\t\t\t\t\tlog::error!(\"Could not get bounding box for import: {index}\");\n\t\t\t\t\t\t\t\t\treturn None;\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t(position > point.y).then_some(*index)\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.filter(|end_index| *end_index > 0) // An import cannot be reordered to be the primary\n\t\t\t\t\t\t\t.unwrap_or_else(|| modify_import_export.reorder_imports_exports.output_ports().count() + 1),\n\t\t\t\t\t);\n\t\t\t\t\tresponses.add(FrontendMessage::UpdateImportReorderIndex { index: self.end_index });\n\t\t\t\t} else if self.reordering_export.is_some() {\n\t\t\t\t\tlet Some(modify_import_export) = network_interface.modify_import_export(selection_network_path) else {\n\t\t\t\t\t\tlog::error!(\"Could not get modify import export in PointerMove\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t};\n\t\t\t\t\t// Find the first export that is below the mouse position\n\t\t\t\t\tself.end_index = Some(\n\t\t\t\t\t\tmodify_import_export\n\t\t\t\t\t\t\t.reorder_imports_exports\n\t\t\t\t\t\t\t.input_ports()\n\t\t\t\t\t\t\t.find_map(|(index, click_target)| {\n\t\t\t\t\t\t\t\tlet Some(position) = click_target.bounding_box().map(|bbox| (bbox[0].y + bbox[1].y) / 2.) else {\n\t\t\t\t\t\t\t\t\tlog::error!(\"Could not get bounding box for export: {index}\");\n\t\t\t\t\t\t\t\t\treturn None;\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t(position > point.y).then_some(*index)\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.filter(|end_index| *end_index > 0) // An export cannot be reordered to be the primary\n\t\t\t\t\t\t\t.unwrap_or_else(|| modify_import_export.reorder_imports_exports.input_ports().count() + 1),\n\t\t\t\t\t);\n\t\t\t\t\tresponses.add(FrontendMessage::UpdateExportReorderIndex { index: self.end_index });\n\t\t\t\t}\n\t\t\t}\n\t\t\tNodeGraphMessage::PointerUp => {\n\t\t\t\tif selection_network_path != breadcrumb_network_path {\n\t\t\t\t\tlog::error!(\"Selection network path does not match breadcrumb network path in PointerUp\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get selected nodes in PointerUp\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tlet Some(network_metadata) = network_interface.network_metadata(selection_network_path) else {\n\t\t\t\t\twarn!(\"No network_metadata\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\n\t\t\t\tif let Some(preview_node) = self.preview_on_mouse_up {\n\t\t\t\t\tresponses.add(NodeGraphMessage::TogglePreview { node_id: preview_node });\n\t\t\t\t\tself.preview_on_mouse_up = None;\n\t\t\t\t}\n\t\t\t\tif let Some(node_to_deselect) = self.deselect_on_pointer_up.take()\n\t\t\t\t\t&& !self.drag_start.as_ref().is_some_and(|t| t.1)\n\t\t\t\t{\n\t\t\t\t\tlet mut new_selected_nodes = selected_nodes.selected_nodes_ref().clone();\n\t\t\t\t\tnew_selected_nodes.remove(node_to_deselect);\n\t\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes: new_selected_nodes });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet point = network_metadata\n\t\t\t\t\t.persistent_metadata\n\t\t\t\t\t.navigation_metadata\n\t\t\t\t\t.node_graph_to_viewport\n\t\t\t\t\t.inverse()\n\t\t\t\t\t.transform_point2(ipp.mouse.position);\n\t\t\t\t// Disconnect if the wire was previously connected to an input\n\t\t\t\tif let (Some(wire_in_progress_from_connector), Some(wire_in_progress_to_connector)) = (self.wire_in_progress_from_connector, self.wire_in_progress_to_connector) {\n\t\t\t\t\t// Check if dragged connector is reconnected to another input\n\t\t\t\t\tlet node_graph_to_viewport = network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport;\n\t\t\t\t\tlet from_connector_viewport = node_graph_to_viewport.transform_point2(wire_in_progress_from_connector);\n\t\t\t\t\tlet to_connector_viewport = node_graph_to_viewport.transform_point2(wire_in_progress_to_connector);\n\t\t\t\t\tlet output_connector = network_interface.output_connector_from_click(from_connector_viewport, selection_network_path);\n\t\t\t\t\tlet input_connector = network_interface.input_connector_from_click(to_connector_viewport, selection_network_path);\n\n\t\t\t\t\tif let (Some(output_connector), Some(input_connector)) = (&output_connector, &input_connector) {\n\t\t\t\t\t\tresponses.add(NodeGraphMessage::CreateWire {\n\t\t\t\t\t\t\tinput_connector: *input_connector,\n\t\t\t\t\t\t\toutput_connector: *output_connector,\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\n\t\t\t\t\t\tresponses.add(NodeGraphMessage::SendGraph);\n\t\t\t\t\t} else if !self.initial_disconnecting\n\t\t\t\t\t\t&& input_connector.is_none()\n\t\t\t\t\t\t&& let Some(output_connector) = output_connector\n\t\t\t\t\t{\n\t\t\t\t\t\t// If the add node menu is already open, we don't want to open it again\n\t\t\t\t\t\tif self.context_menu.is_some() {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Get the output types from the network interface\n\t\t\t\t\t\tlet Some(network_metadata) = network_interface.network_metadata(selection_network_path) else {\n\t\t\t\t\t\t\twarn!(\"No network_metadata\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tlet appear_right_of_mouse = if ipp.mouse.position.x > viewport.size().y() - 173. { -173. } else { 0. };\n\t\t\t\t\t\tlet appear_above_mouse = if ipp.mouse.position.y > viewport.size().y() - 34. { -34. } else { 0. };\n\t\t\t\t\t\tlet node_graph_shift = DVec2::new(appear_right_of_mouse, appear_above_mouse) / network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.matrix2.x_axis.x;\n\n\t\t\t\t\t\tlet compatible_type = network_interface.output_type(&output_connector, selection_network_path).add_node_string();\n\n\t\t\t\t\t\tself.context_menu = Some(ContextMenuInformation {\n\t\t\t\t\t\t\tcontext_menu_coordinates: (point + node_graph_shift).as_ivec2().into(),\n\t\t\t\t\t\t\tcontext_menu_data: ContextMenuData::CreateNode { compatible_type },\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tresponses.add(FrontendMessage::UpdateContextMenuInformation {\n\t\t\t\t\t\t\tcontext_menu_information: self.context_menu.clone(),\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// End of dragging a node\n\t\t\t\telse if let Some((drag_start, _)) = &self.drag_start {\n\t\t\t\t\tself.shift_without_push = false;\n\n\t\t\t\t\t// Reset all offsets to end the rubber banding while dragging\n\t\t\t\t\tnetwork_interface.unload_stack_dependents_y_offset(selection_network_path);\n\t\t\t\t\tlet Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else {\n\t\t\t\t\t\tlog::error!(\"Could not get selected nodes in PointerUp\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t};\n\n\t\t\t\t\t// Only select clicked node if multiple are selected and they were not dragged\n\t\t\t\t\tif let Some(select_if_not_dragged) = self.select_if_not_dragged {\n\t\t\t\t\t\tlet not_dragged = drag_start.start_x == point.x && drag_start.start_y == point.y;\n\t\t\t\t\t\tif not_dragged\n\t\t\t\t\t\t\t&& (selected_nodes.selected_nodes_ref().len() != 1\n\t\t\t\t\t\t\t\t|| selected_nodes\n\t\t\t\t\t\t\t\t\t.selected_nodes_ref()\n\t\t\t\t\t\t\t\t\t.first()\n\t\t\t\t\t\t\t\t\t.is_some_and(|first_selected_node| *first_selected_node != select_if_not_dragged))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![select_if_not_dragged] })\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Try expand the upstream chain for all layers if there is an eligible node\n\t\t\t\t\tlet Some(network) = network_interface.nested_network(selection_network_path) else { return };\n\t\t\t\t\tfor layer in network\n\t\t\t\t\t\t.nodes\n\t\t\t\t\t\t.keys()\n\t\t\t\t\t\t.filter(|node_id| network_interface.is_layer(node_id, selection_network_path))\n\t\t\t\t\t\t.cloned()\n\t\t\t\t\t\t.collect::<Vec<_>>()\n\t\t\t\t\t{\n\t\t\t\t\t\tnetwork_interface.try_set_upstream_to_chain(&InputConnector::node(layer, 1), selection_network_path);\n\t\t\t\t\t}\n\t\t\t\t\tresponses.add(NodeGraphMessage::SendGraph);\n\n\t\t\t\t\tlet Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else {\n\t\t\t\t\t\tlog::error!(\"Could not get selected nodes in PointerUp\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t};\n\t\t\t\t\t// Check if a single node was dragged onto a wire and that the node was dragged onto the wire\n\t\t\t\t\tif selected_nodes.selected_nodes_ref().len() == 1 && !self.begin_dragging {\n\t\t\t\t\t\tlet selected_node_id = selected_nodes.selected_nodes_ref()[0];\n\t\t\t\t\t\tlet has_primary_output_connection = network_interface\n\t\t\t\t\t\t\t.outward_wires(selection_network_path)\n\t\t\t\t\t\t\t.is_some_and(|outward_wires| outward_wires.get(&OutputConnector::node(selected_node_id, 0)).is_some_and(|outward_wires| !outward_wires.is_empty()));\n\t\t\t\t\t\tif !has_primary_output_connection {\n\t\t\t\t\t\t\tlet Some(network) = network_interface.nested_network(selection_network_path) else {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tlet Some(selected_node) = network.nodes.get(&selected_node_id) else {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t// Check that the first visible input is disconnected\n\t\t\t\t\t\t\tlet selected_node_input_connect_index = selected_node\n\t\t\t\t\t\t\t\t.inputs\n\t\t\t\t\t\t\t\t.iter()\n\t\t\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t\t\t.find(|input| input.1.is_exposed())\n\t\t\t\t\t\t\t\t.filter(|input| input.1.as_value().is_some())\n\t\t\t\t\t\t\t\t.map(|input| input.0);\n\t\t\t\t\t\t\tif let Some(selected_node_input_connect_index) = selected_node_input_connect_index {\n\t\t\t\t\t\t\t\tlet Some(node_bbox) = network_interface.node_bounding_box(&selected_node_id, selection_network_path) else {\n\t\t\t\t\t\t\t\t\tlog::error!(\"Could not get bounding box for node: {selected_node_id}\");\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tlet mut wires_to_check = network_interface.node_graph_input_connectors(selection_network_path).into_iter().collect::<HashSet<_>>();\n\t\t\t\t\t\t\t\t// Prevent inserting on a link that is connected upstream to the selected node\n\t\t\t\t\t\t\t\tfor upstream_node in network_interface.upstream_flow_back_from_nodes(vec![selected_node_id], selection_network_path, network_interface::FlowType::UpstreamFlow) {\n\t\t\t\t\t\t\t\t\tfor input_index in 0..network_interface.number_of_inputs(&upstream_node, selection_network_path) {\n\t\t\t\t\t\t\t\t\t\twires_to_check.remove(&InputConnector::node(upstream_node, input_index));\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tlet overlapping_wires = wires_to_check\n\t\t\t\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t\t\t\t.filter_map(|input| {\n\t\t\t\t\t\t\t\t\t\t// Prevent inserting a layer into a chain\n\t\t\t\t\t\t\t\t\t\tif network_interface.is_layer(&selected_node_id, selection_network_path)\n\t\t\t\t\t\t\t\t\t\t\t&& input.node_id().is_some_and(|input_node_id| network_interface.is_chain(&input_node_id, selection_network_path))\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\treturn None;\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\tlet (wire, is_stack) = network_interface.vector_wire_from_input(&input, preferences.graph_wire_style, selection_network_path)?;\n\n\t\t\t\t\t\t\t\t\t\tlet node_bbox = kurbo::Rect::new(node_bbox[0].x, node_bbox[0].y, node_bbox[1].x, node_bbox[1].y).to_path(DEFAULT_ACCURACY);\n\t\t\t\t\t\t\t\t\t\tlet inside = bezpath_is_inside_bezpath(&wire, &node_bbox, None, None);\n\n\t\t\t\t\t\t\t\t\t\tlet intersect = wire\n\t\t\t\t\t\t\t\t\t\t\t.segments()\n\t\t\t\t\t\t\t\t\t\t\t.any(|segment| node_bbox.segments().filter_map(|segment| segment.as_line()).any(|line| !segment.intersect_line(line).is_empty()));\n\n\t\t\t\t\t\t\t\t\t\t(intersect || inside).then_some((input, is_stack))\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t.collect::<Vec<_>>();\n\n\t\t\t\t\t\t\t\t// Prioritize vertical thick lines and cancel if there are multiple potential wires\n\t\t\t\t\t\t\t\tlet mut node_wires = Vec::new();\n\t\t\t\t\t\t\t\tlet mut stack_wires = Vec::new();\n\t\t\t\t\t\t\t\tfor (overlapping_wire_input, is_stack) in overlapping_wires {\n\t\t\t\t\t\t\t\t\tif is_stack {\n\t\t\t\t\t\t\t\t\t\tstack_wires.push(overlapping_wire_input)\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tnode_wires.push(overlapping_wire_input)\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tlet overlapping_wire = if network_interface.is_layer(&selected_node_id, selection_network_path) {\n\t\t\t\t\t\t\t\t\tif stack_wires.len() == 1 {\n\t\t\t\t\t\t\t\t\t\tstack_wires.first()\n\t\t\t\t\t\t\t\t\t} else if stack_wires.is_empty() && node_wires.len() == 1 {\n\t\t\t\t\t\t\t\t\t\tnode_wires.first()\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tNone\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else if node_wires.len() == 1 {\n\t\t\t\t\t\t\t\t\tnode_wires.first()\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tNone\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tif let Some(overlapping_wire) = overlapping_wire {\n\t\t\t\t\t\t\t\t\tresponses.add(NodeGraphMessage::InsertNodeBetween {\n\t\t\t\t\t\t\t\t\t\tnode_id: selected_node_id,\n\t\t\t\t\t\t\t\t\t\tinput_connector: *overlapping_wire,\n\t\t\t\t\t\t\t\t\t\tinsert_node_input_index: selected_node_input_connect_index,\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\t\t\t\t\t\tresponses.add(NodeGraphMessage::SendGraph);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tself.select_if_not_dragged = None;\n\t\t\t\t}\n\t\t\t\t// End of reordering an import\n\t\t\t\telse if let (Some(moving_import), Some(end_index)) = (self.reordering_import, self.end_index) {\n\t\t\t\t\tresponses.add(NodeGraphMessage::ReorderImport {\n\t\t\t\t\t\tstart_index: moving_import,\n\t\t\t\t\t\tend_index,\n\t\t\t\t\t});\n\t\t\t\t\tresponses.add(DocumentMessage::EndTransaction);\n\t\t\t\t}\n\t\t\t\t// End of reordering an export\n\t\t\t\telse if let (Some(moving_export), Some(end_index)) = (self.reordering_export, self.end_index) {\n\t\t\t\t\tresponses.add(NodeGraphMessage::ReorderExport {\n\t\t\t\t\t\tstart_index: moving_export,\n\t\t\t\t\t\tend_index,\n\t\t\t\t\t});\n\t\t\t\t\tresponses.add(DocumentMessage::EndTransaction);\n\t\t\t\t}\n\n\t\t\t\tself.drag_start = None;\n\t\t\t\tself.begin_dragging = false;\n\t\t\t\tself.box_selection_start = None;\n\n\t\t\t\tself.wire_in_progress_from_connector = None;\n\t\t\t\tself.wire_in_progress_to_connector = None;\n\t\t\t\tself.wire_in_progress_type = FrontendGraphDataType::General;\n\n\t\t\t\tself.reordering_export = None;\n\t\t\t\tself.reordering_import = None;\n\n\t\t\t\tresponses.add(DocumentMessage::EndTransaction);\n\t\t\t\tresponses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None });\n\t\t\t\tresponses.add(FrontendMessage::UpdateBox { box_selection: None });\n\t\t\t\tresponses.add(FrontendMessage::UpdateImportReorderIndex { index: None });\n\t\t\t\tresponses.add(FrontendMessage::UpdateExportReorderIndex { index: None });\n\t\t\t\tself.update_node_graph_hints(responses);\n\t\t\t}\n\t\t\tNodeGraphMessage::PointerOutsideViewport { shift } => {\n\t\t\t\tif self.drag_start.is_some() || self.box_selection_start.is_some() || (self.wire_in_progress_from_connector.is_some() && self.context_menu.is_none()) {\n\t\t\t\t\tlet _ = self.auto_panning.shift_viewport(ipp, viewport, responses);\n\t\t\t\t} else {\n\t\t\t\t\t// Auto-panning\n\t\t\t\t\tlet messages = [NodeGraphMessage::PointerOutsideViewport { shift }.into(), NodeGraphMessage::PointerMove { shift }.into()];\n\t\t\t\t\tself.auto_panning.stop(&messages, responses);\n\t\t\t\t}\n\t\t\t}\n\t\t\tNodeGraphMessage::ShakeNode => {\n\t\t\t\tlet Some(drag_start) = &self.drag_start else {\n\t\t\t\t\tlog::error!(\"Drag start should be initialized when shaking a node\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\n\t\t\t\tlet Some(network_metadata) = network_interface.network_metadata(selection_network_path) else {\n\t\t\t\t\treturn;\n\t\t\t\t};\n\n\t\t\t\tlet viewport_location = ipp.mouse.position;\n\t\t\t\tlet point = network_metadata\n\t\t\t\t\t.persistent_metadata\n\t\t\t\t\t.navigation_metadata\n\t\t\t\t\t.node_graph_to_viewport\n\t\t\t\t\t.inverse()\n\t\t\t\t\t.transform_point2(viewport_location);\n\n\t\t\t\t// Collect the distance to move the shaken nodes after the undo\n\t\t\t\tlet graph_delta = IVec2::new(((point.x - drag_start.0.start_x) / 24.).round() as i32, ((point.y - drag_start.0.start_y) / 24.).round() as i32);\n\n\t\t\t\t// Undo to the state of the graph before shaking\n\t\t\t\tresponses.add(DocumentMessage::AbortTransaction);\n\n\t\t\t\t// Add a history step to abort to the state before shaking if right clicked\n\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\n\t\t\t\tlet Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get selected nodes in ShakeNode\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\n\t\t\t\tlet mut all_selected_nodes = selected_nodes.0.iter().copied().collect::<HashSet<_>>();\n\t\t\t\tfor selected_layer in selected_nodes\n\t\t\t\t\t.0\n\t\t\t\t\t.iter()\n\t\t\t\t\t.filter(|selected_node| network_interface.is_layer(selected_node, selection_network_path))\n\t\t\t\t\t.copied()\n\t\t\t\t\t.collect::<Vec<_>>()\n\t\t\t\t{\n\t\t\t\t\tfor sole_dependent in network_interface.upstream_nodes_below_layer(&selected_layer, selection_network_path) {\n\t\t\t\t\t\tall_selected_nodes.insert(sole_dependent);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfor selected_node in &all_selected_nodes {\n\t\t\t\t\t// Handle inputs of selected node\n\t\t\t\t\tfor input_index in 0..network_interface.number_of_inputs(selected_node, selection_network_path) {\n\t\t\t\t\t\tlet input_connector = InputConnector::node(*selected_node, input_index);\n\t\t\t\t\t\t// Only disconnect inputs to non selected nodes\n\t\t\t\t\t\tif !network_interface\n\t\t\t\t\t\t\t.upstream_output_connector(&input_connector, selection_network_path)\n\t\t\t\t\t\t\t.and_then(|connector| connector.node_id())\n\t\t\t\t\t\t\t.is_some_and(|node_id| all_selected_nodes.contains(&node_id))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tresponses.add(NodeGraphMessage::DisconnectInput { input_connector });\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tlet number_of_outputs = network_interface.number_of_outputs(selected_node, selection_network_path);\n\t\t\t\t\tlet mut first_deselected_upstream_output = network_interface.upstream_output_connector(&InputConnector::node(*selected_node, 0), selection_network_path);\n\t\t\t\t\twhile let Some(OutputConnector::Node { node_id, .. }) = &first_deselected_upstream_output {\n\t\t\t\t\t\tif !all_selected_nodes.contains(node_id) {\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tfirst_deselected_upstream_output = network_interface.upstream_output_connector(&InputConnector::node(*node_id, 0), selection_network_path);\n\t\t\t\t\t}\n\n\t\t\t\t\tlet Some(outward_wires) = network_interface.outward_wires(selection_network_path) else {\n\t\t\t\t\t\tlog::error!(\"Could not get output wires in shake input\");\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t};\n\n\t\t\t\t\t// Disconnect output wires to non selected nodes\n\t\t\t\t\tfor output_index in 0..number_of_outputs {\n\t\t\t\t\t\tlet output_connector = OutputConnector::node(*selected_node, output_index);\n\t\t\t\t\t\tif let Some(downstream_connections) = outward_wires.get(&output_connector) {\n\t\t\t\t\t\t\tfor &input_connector in downstream_connections {\n\t\t\t\t\t\t\t\tif input_connector.node_id().is_some_and(|downstream_node| !all_selected_nodes.contains(&downstream_node)) {\n\t\t\t\t\t\t\t\t\tresponses.add(NodeGraphMessage::DisconnectInput { input_connector });\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Handle reconnection\n\t\t\t\t\t// Find first non selected upstream node by primary flow\n\t\t\t\t\tif let Some(first_deselected_upstream_output) = first_deselected_upstream_output {\n\t\t\t\t\t\tlet Some(downstream_connections_to_first_output) = outward_wires.get(&OutputConnector::node(*selected_node, 0)).cloned() else {\n\t\t\t\t\t\t\tlog::error!(\"Could not get downstream_connections_to_first_output in shake node\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t};\n\t\t\t\t\t\t// Reconnect only if all downstream outputs are not selected\n\t\t\t\t\t\tfor downstream_connection_to_first_output in &downstream_connections_to_first_output {\n\t\t\t\t\t\t\tif !downstream_connection_to_first_output.node_id().is_some_and(|node_id| all_selected_nodes.contains(&node_id)) {\n\t\t\t\t\t\t\t\t// Reconnect the upstream output to all downstream inputs\n\t\t\t\t\t\t\t\tresponses.add(NodeGraphMessage::CreateWire {\n\t\t\t\t\t\t\t\t\toutput_connector: first_deselected_upstream_output,\n\t\t\t\t\t\t\t\t\tinput_connector: *downstream_connection_to_first_output,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Set all chain nodes back to chain position\n\t\t\t\t\t\t\t// TODO: Fix\n\t\t\t\t\t\t\t// for chain_node_to_reset in std::mem::take(&mut self.drag_start_chain_nodes) {\n\t\t\t\t\t\t\t// \tresponses.add(NodeGraphMessage::SetChainPosition { node_id: chain_node_to_reset });\n\t\t\t\t\t\t\t// }\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tresponses.add(NodeGraphMessage::ShiftSelectedNodesByAmount { graph_delta, rubber_band: false });\n\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\tresponses.add(NodeGraphMessage::SendGraph);\n\t\t\t}\n\t\t\tNodeGraphMessage::UpdateNodeGraphWidth => {\n\t\t\t\tnetwork_interface.set_node_graph_width(viewport.size().x(), breadcrumb_network_path);\n\t\t\t\tresponses.add(NodeGraphMessage::UpdateImportsExports);\n\t\t\t}\n\t\t\tNodeGraphMessage::RemoveImport { import_index: usize } => {\n\t\t\t\tnetwork_interface.remove_import(usize, selection_network_path);\n\t\t\t\tresponses.add(NodeGraphMessage::UpdateImportsExports);\n\t\t\t\tresponses.add(NodeGraphMessage::SendGraph);\n\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t}\n\t\t\tNodeGraphMessage::RemoveExport { export_index: usize } => {\n\t\t\t\tnetwork_interface.remove_export(usize, selection_network_path);\n\t\t\t\tresponses.add(NodeGraphMessage::UpdateImportsExports);\n\t\t\t\tresponses.add(NodeGraphMessage::SendGraph);\n\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t}\n\t\t\tNodeGraphMessage::ReorderImport { start_index, end_index } => {\n\t\t\t\tnetwork_interface.reorder_import(start_index, end_index, selection_network_path);\n\t\t\t\tresponses.add(NodeGraphMessage::SendGraph);\n\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t}\n\t\t\tNodeGraphMessage::ReorderExport { start_index, end_index } => {\n\t\t\t\tnetwork_interface.reorder_export(start_index, end_index, selection_network_path);\n\t\t\t\tresponses.add(NodeGraphMessage::SendGraph);\n\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t}\n\t\t\tNodeGraphMessage::RunDocumentGraph => {\n\t\t\t\tresponses.add(PortfolioMessage::SubmitGraphRender { document_id, ignore_hash: false });\n\t\t\t}\n\t\t\tNodeGraphMessage::ForceRunDocumentGraph => {\n\t\t\t\tresponses.add(PortfolioMessage::SubmitGraphRender { document_id, ignore_hash: true });\n\t\t\t}\n\t\t\tNodeGraphMessage::SelectedNodesAdd { nodes } => {\n\t\t\t\tlet Some(selected_nodes) = network_interface.selected_nodes_mut(selection_network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get selected nodes in NodeGraphMessage::SelectedNodesAdd\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tselected_nodes.add_selected_nodes(nodes);\n\t\t\t\tresponses.add(EventMessage::SelectionChanged);\n\t\t\t}\n\t\t\tNodeGraphMessage::SelectedNodesRemove { nodes } => {\n\t\t\t\tlet Some(selected_nodes) = network_interface.selected_nodes_mut(selection_network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get selected nodes in NodeGraphMessage::SelectedNodesRemove\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tselected_nodes.retain_selected_nodes(|node| !nodes.contains(node));\n\t\t\t\tresponses.add(EventMessage::SelectionChanged);\n\t\t\t}\n\t\t\tNodeGraphMessage::SelectedNodesSet { nodes } => {\n\t\t\t\tlet Some(selected_nodes) = network_interface.selected_nodes_mut(selection_network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get selected nodes in NodeGraphMessage::SelectedNodesSet\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tselected_nodes.set_selected_nodes(nodes);\n\t\t\t\tresponses.add(EventMessage::SelectionChanged);\n\t\t\t}\n\t\t\tNodeGraphMessage::SendClickTargets => responses.add(FrontendMessage::UpdateClickTargets {\n\t\t\t\tclick_targets: Some(network_interface.collect_frontend_click_targets(breadcrumb_network_path)),\n\t\t\t}),\n\t\t\tNodeGraphMessage::EndSendClickTargets => responses.add(FrontendMessage::UpdateClickTargets { click_targets: None }),\n\t\t\tNodeGraphMessage::UnloadWires => {\n\t\t\t\tfor input in network_interface.node_graph_input_connectors(breadcrumb_network_path) {\n\t\t\t\t\tnetwork_interface.unload_wire(&input, breadcrumb_network_path);\n\t\t\t\t}\n\n\t\t\t\tresponses.add(FrontendMessage::ClearAllNodeGraphWires);\n\t\t\t}\n\t\t\tNodeGraphMessage::SendWires => {\n\t\t\t\tlet wires = self.collect_wires(network_interface, preferences.graph_wire_style, breadcrumb_network_path);\n\t\t\t\tresponses.add(FrontendMessage::UpdateNodeGraphWires { wires });\n\t\t\t}\n\t\t\tNodeGraphMessage::UpdateVisibleNodes => {\n\t\t\t\tlet Some(network_metadata) = network_interface.network_metadata(breadcrumb_network_path) else {\n\t\t\t\t\treturn;\n\t\t\t\t};\n\n\t\t\t\tlet viewport_bbox = [DVec2::ZERO, viewport.size().into_dvec2()];\n\t\t\t\tlet document_bbox: [DVec2; 2] = viewport_bbox.map(|p| network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.inverse().transform_point2(p));\n\n\t\t\t\tlet mut nodes = Vec::new();\n\t\t\t\tfor node_id in &self.frontend_nodes {\n\t\t\t\t\tlet Some(node_bbox) = network_interface.node_bounding_box(node_id, breadcrumb_network_path) else {\n\t\t\t\t\t\tlog::error!(\"Could not get bbox for node: {node_id:?}\");\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t};\n\n\t\t\t\t\tif node_bbox[1].x >= document_bbox[0].x && node_bbox[0].x <= document_bbox[1].x && node_bbox[1].y >= document_bbox[0].y && node_bbox[0].y <= document_bbox[1].y {\n\t\t\t\t\t\tnodes.push(*node_id);\n\t\t\t\t\t}\n\t\t\t\t\tfor error in &network_interface.resolved_types.node_graph_errors {\n\t\t\t\t\t\tif error.node_path.contains(node_id) {\n\t\t\t\t\t\t\tnodes.push(*node_id);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tresponses.add(FrontendMessage::UpdateVisibleNodes { nodes });\n\t\t\t}\n\t\t\tNodeGraphMessage::SendGraph => {\n\t\t\t\tresponses.add(NodeGraphMessage::UpdateLayerPanel);\n\t\t\t\tresponses.add(DocumentMessage::DocumentStructureChanged);\n\t\t\t\tresponses.add(PropertiesPanelMessage::Refresh);\n\t\t\t\tif breadcrumb_network_path == selection_network_path && graph_view_overlay_open {\n\t\t\t\t\tlet nodes = self.collect_nodes(network_interface, breadcrumb_network_path);\n\t\t\t\t\tself.frontend_nodes = nodes.iter().map(|node| node.id).collect();\n\t\t\t\t\tresponses.add(FrontendMessage::UpdateNodeGraphNodes { nodes });\n\t\t\t\t\tresponses.add(NodeGraphMessage::UpdateVisibleNodes);\n\n\t\t\t\t\tlet error = self.node_graph_error(network_interface, breadcrumb_network_path);\n\t\t\t\t\tresponses.add(FrontendMessage::UpdateNodeGraphErrorDiagnostic { error });\n\t\t\t\t\tlet (layer_widths, chain_widths, has_left_input_wire) = network_interface.collect_layer_widths(breadcrumb_network_path);\n\n\t\t\t\t\tresponses.add(NodeGraphMessage::UpdateImportsExports);\n\t\t\t\t\tresponses.add(FrontendMessage::UpdateLayerWidths {\n\t\t\t\t\t\tlayer_widths,\n\t\t\t\t\t\tchain_widths,\n\t\t\t\t\t\thas_left_input_wire,\n\t\t\t\t\t});\n\t\t\t\t\tresponses.add(NodeGraphMessage::SendSelectedNodes);\n\t\t\t\t\tself.update_node_graph_hints(responses);\n\t\t\t\t}\n\t\t\t}\n\t\t\tNodeGraphMessage::SetInputValue { node_id, input_index, value } => {\n\t\t\t\tlet is_fill = matches!(value, TaggedValue::Fill(_));\n\t\t\t\tlet input = NodeInput::value(value, false);\n\t\t\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\t\t\tinput_connector: InputConnector::node(node_id, input_index),\n\t\t\t\t\tinput,\n\t\t\t\t});\n\t\t\t\tresponses.add(PropertiesPanelMessage::Refresh);\n\t\t\t\tif is_fill {\n\t\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\t}\n\t\t\t\tif network_interface.connected_to_output(&node_id, selection_network_path) {\n\t\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\t}\n\t\t\t}\n\t\t\tNodeGraphMessage::SetInput { input_connector, input } => {\n\t\t\t\tnetwork_interface.set_input(&input_connector, input, selection_network_path);\n\t\t\t}\n\t\t\tNodeGraphMessage::ShiftSelectedNodes { direction, rubber_band } => {\n\t\t\t\tnetwork_interface.shift_selected_nodes(direction, self.shift_without_push, selection_network_path);\n\n\t\t\t\tif !rubber_band {\n\t\t\t\t\tnetwork_interface.unload_stack_dependents_y_offset(selection_network_path);\n\t\t\t\t}\n\n\t\t\t\tif graph_view_overlay_open {\n\t\t\t\t\tresponses.add(NodeGraphMessage::SendGraph);\n\t\t\t\t\tresponses.add(DocumentMessage::RenderRulers);\n\t\t\t\t\tresponses.add(DocumentMessage::RenderScrollbars);\n\t\t\t\t}\n\t\t\t}\n\t\t\tNodeGraphMessage::ShiftSelectedNodesByAmount { mut graph_delta, rubber_band } => {\n\t\t\t\twhile graph_delta != IVec2::ZERO {\n\t\t\t\t\tmatch graph_delta.x.cmp(&0) {\n\t\t\t\t\t\tOrdering::Greater => {\n\t\t\t\t\t\t\tresponses.add(NodeGraphMessage::ShiftSelectedNodes {\n\t\t\t\t\t\t\t\tdirection: Direction::Right,\n\t\t\t\t\t\t\t\trubber_band,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tgraph_delta.x -= 1;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tOrdering::Less => {\n\t\t\t\t\t\t\tresponses.add(NodeGraphMessage::ShiftSelectedNodes {\n\t\t\t\t\t\t\t\tdirection: Direction::Left,\n\t\t\t\t\t\t\t\trubber_band,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tgraph_delta.x += 1;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tOrdering::Equal => {}\n\t\t\t\t\t}\n\n\t\t\t\t\tmatch graph_delta.y.cmp(&0) {\n\t\t\t\t\t\tOrdering::Greater => {\n\t\t\t\t\t\t\tresponses.add(NodeGraphMessage::ShiftSelectedNodes {\n\t\t\t\t\t\t\t\tdirection: Direction::Down,\n\t\t\t\t\t\t\t\trubber_band,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tgraph_delta.y -= 1;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tOrdering::Less => {\n\t\t\t\t\t\t\tresponses.add(NodeGraphMessage::ShiftSelectedNodes {\n\t\t\t\t\t\t\t\tdirection: Direction::Up,\n\t\t\t\t\t\t\t\trubber_band,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tgraph_delta.y += 1;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tOrdering::Equal => {}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tresponses.add(NodeGraphMessage::SendWires);\n\t\t\t}\n\t\t\tNodeGraphMessage::ToggleSelectedAsLayersOrNodes => {\n\t\t\t\tlet Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get selected nodes in NodeGraphMessage::ToggleSelectedAsLayersOrNodes\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\t\t\t\tfor node_id in selected_nodes.selected_nodes() {\n\t\t\t\t\tresponses.add(NodeGraphMessage::SetToNodeOrLayer {\n\t\t\t\t\t\tnode_id: *node_id,\n\t\t\t\t\t\tis_layer: !network_interface.is_layer(node_id, selection_network_path),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tif selected_nodes.selected_nodes().any(|node_id| network_interface.connected_to_output(node_id, selection_network_path)) {\n\t\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\t}\n\t\t\t}\n\t\t\tNodeGraphMessage::ShiftNodePosition { node_id, x, y } => {\n\t\t\t\tnetwork_interface.shift_absolute_node_position(&node_id, IVec2::new(x, y), selection_network_path);\n\n\t\t\t\tresponses.add(NodeGraphMessage::SendWires);\n\t\t\t}\n\t\t\tNodeGraphMessage::SetToNodeOrLayer { node_id, is_layer } => {\n\t\t\t\tif is_layer && !network_interface.is_eligible_to_be_layer(&node_id, selection_network_path) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tnetwork_interface.set_to_node_or_layer(&node_id, selection_network_path, is_layer);\n\n\t\t\t\tself.context_menu = None;\n\t\t\t\tresponses.add(FrontendMessage::UpdateContextMenuInformation {\n\t\t\t\t\tcontext_menu_information: self.context_menu.clone(),\n\t\t\t\t});\n\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\tresponses.add(NodeGraphMessage::SendGraph);\n\t\t\t\tresponses.add(NodeGraphMessage::SendWires);\n\t\t\t}\n\t\t\tNodeGraphMessage::SetDisplayName {\n\t\t\t\tnode_id,\n\t\t\t\talias,\n\t\t\t\tskip_adding_history_step,\n\t\t\t} => {\n\t\t\t\tif !skip_adding_history_step {\n\t\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\t\t\t\t}\n\t\t\t\tresponses.add(NodeGraphMessage::SetDisplayNameImpl { node_id, alias });\n\t\t\t\tif !skip_adding_history_step {\n\t\t\t\t\t// Does not add a history step if the name was not changed\n\t\t\t\t\tresponses.add(DocumentMessage::EndTransaction);\n\t\t\t\t}\n\t\t\t\tresponses.add(DocumentMessage::RenderRulers);\n\t\t\t\tresponses.add(DocumentMessage::RenderScrollbars);\n\t\t\t\tresponses.add(NodeGraphMessage::SendGraph);\n\t\t\t\tresponses.add(OverlaysMessage::Draw); // Redraw overlays to update artboard names\n\t\t\t}\n\t\t\tNodeGraphMessage::SetDisplayNameImpl { node_id, alias } => {\n\t\t\t\tnetwork_interface.set_display_name(&node_id, alias, selection_network_path);\n\t\t\t}\n\t\t\tNodeGraphMessage::SetImportExportName { name, index } => {\n\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\t\t\t\tresponses.add(NodeGraphMessage::SetImportExportNameImpl { name, index });\n\t\t\t\tresponses.add(DocumentMessage::EndTransaction);\n\t\t\t\tresponses.add(NodeGraphMessage::UpdateImportsExports);\n\t\t\t}\n\t\t\tNodeGraphMessage::SetImportExportNameImpl { name, index } => network_interface.set_import_export_name(name, index, breadcrumb_network_path),\n\t\t\tNodeGraphMessage::TogglePreview { node_id } => {\n\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\t\t\t\tresponses.add(NodeGraphMessage::TogglePreviewImpl { node_id });\n\t\t\t\tresponses.add(NodeGraphMessage::UpdateActionButtons);\n\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t}\n\t\t\tNodeGraphMessage::TogglePreviewImpl { node_id } => {\n\t\t\t\tnetwork_interface.toggle_preview(node_id, selection_network_path);\n\t\t\t}\n\t\t\tNodeGraphMessage::ToggleSelectedLocked => {\n\t\t\t\tlet Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get selected nodes in NodeGraphMessage::ToggleSelectedLocked\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tlet node_ids = selected_nodes\n\t\t\t\t\t.selected_nodes()\n\t\t\t\t\t.filter(|node_id| network_interface.is_layer(node_id, selection_network_path))\n\t\t\t\t\t.cloned()\n\t\t\t\t\t.collect::<Vec<_>>();\n\n\t\t\t\tif node_ids.is_empty() {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// If any of the selected layers are unlocked, lock them all. Otherwise, unlock them all.\n\t\t\t\tlet locked = !node_ids.iter().all(|node_id| network_interface.is_locked(node_id, selection_network_path));\n\n\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\n\t\t\t\tfor node_id in &node_ids {\n\t\t\t\t\tresponses.add(NodeGraphMessage::SetLocked { node_id: *node_id, locked });\n\t\t\t\t}\n\n\t\t\t\tresponses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids })\n\t\t\t}\n\t\t\tNodeGraphMessage::ToggleLocked { node_id } => {\n\t\t\t\tlet Some(node_metadata) = network_interface.document_network_metadata().persistent_metadata.node_metadata.get(&node_id) else {\n\t\t\t\t\tlog::error!(\"Cannot get node {node_id:?} in NodeGraphMessage::ToggleLocked\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\n\t\t\t\tlet locked = !node_metadata.persistent_metadata.locked;\n\n\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\t\t\t\tresponses.add(NodeGraphMessage::SetLocked { node_id, locked });\n\t\t\t\tresponses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids: vec![node_id] })\n\t\t\t}\n\t\t\tNodeGraphMessage::SetLocked { node_id, locked } => {\n\t\t\t\tnetwork_interface.set_locked(&node_id, selection_network_path, locked);\n\t\t\t}\n\t\t\tNodeGraphMessage::ToggleSelectedIsPinned => {\n\t\t\t\tlet Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get selected nodes in NodeGraphMessage::ToggleSelectedIsPinned\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tlet node_ids = selected_nodes.selected_nodes().cloned().collect::<Vec<_>>();\n\n\t\t\t\t// If any of the selected nodes are pinned, unpin them all. Otherwise, pin them all.\n\t\t\t\tlet pinned = !node_ids.iter().all(|node_id| network_interface.is_pinned(node_id, breadcrumb_network_path));\n\n\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\t\t\t\tfor node_id in &node_ids {\n\t\t\t\t\tresponses.add(NodeGraphMessage::SetPinned { node_id: *node_id, pinned });\n\t\t\t\t}\n\t\t\t\tresponses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids });\n\t\t\t}\n\t\t\tNodeGraphMessage::ToggleSelectedVisibility => {\n\t\t\t\tlet Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get selected nodes in NodeGraphMessage::ToggleSelectedLocked\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tlet node_ids = selected_nodes.selected_nodes().cloned().collect::<Vec<_>>();\n\n\t\t\t\t// If any of the selected nodes are hidden, show them all. Otherwise, hide them all.\n\t\t\t\tlet visible = !node_ids.iter().all(|node_id| network_interface.is_visible(node_id, selection_network_path));\n\n\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\t\t\t\tfor node_id in &node_ids {\n\t\t\t\t\tresponses.add(NodeGraphMessage::SetVisibility { node_id: *node_id, visible });\n\t\t\t\t}\n\t\t\t\tresponses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids });\n\t\t\t}\n\t\t\tNodeGraphMessage::ToggleVisibility { node_id } => {\n\t\t\t\tlet visible = !network_interface.is_visible(&node_id, selection_network_path);\n\n\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\t\t\t\tresponses.add(NodeGraphMessage::SetVisibility { node_id, visible });\n\t\t\t\tresponses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids: vec![node_id] });\n\t\t\t}\n\t\t\tNodeGraphMessage::SetPinned { node_id, pinned } => {\n\t\t\t\tnetwork_interface.set_pinned(&node_id, selection_network_path, pinned);\n\t\t\t}\n\t\t\tNodeGraphMessage::SetVisibility { node_id, visible } => {\n\t\t\t\tnetwork_interface.set_visibility(&node_id, selection_network_path, visible);\n\t\t\t}\n\t\t\tNodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids } => {\n\t\t\t\tif node_ids.iter().any(|node_id| network_interface.connected_to_output(node_id, selection_network_path)) {\n\t\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\t}\n\t\t\t\tresponses.add(NodeGraphMessage::UpdateActionButtons);\n\t\t\t\tresponses.add(NodeGraphMessage::SendGraph);\n\n\t\t\t\tresponses.add(PropertiesPanelMessage::Refresh);\n\t\t\t}\n\t\t\tNodeGraphMessage::UpdateBoxSelection => {\n\t\t\t\tif let Some((box_selection_start, _)) = self.box_selection_start {\n\t\t\t\t\t// The mouse button was released but we missed the pointer up event\n\t\t\t\t\t// if ((e.buttons & 1) === 0) {\n\t\t\t\t\t// \tcompleteBoxSelection();\n\t\t\t\t\t// \tboxSelection = undefined;\n\t\t\t\t\t// } else if ((e.buttons & 2) !== 0) {\n\t\t\t\t\t// \teditor.handle.selectNodes(new BigUint64Array(previousSelection));\n\t\t\t\t\t// \tboxSelection = undefined;\n\t\t\t\t\t// }\n\n\t\t\t\t\tlet Some(network_metadata) = network_interface.network_metadata(selection_network_path) else {\n\t\t\t\t\t\tlog::error!(\"Could not get network metadata in UpdateBoxSelection\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t};\n\n\t\t\t\t\tlet box_selection_start_viewport = network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.transform_point2(box_selection_start);\n\n\t\t\t\t\tlet box_selection = Some(BoxSelection {\n\t\t\t\t\t\tstart_x: box_selection_start_viewport.x.max(0.) as u32,\n\t\t\t\t\t\tstart_y: box_selection_start_viewport.y.max(0.) as u32,\n\t\t\t\t\t\tend_x: ipp.mouse.position.x.max(0.) as u32,\n\t\t\t\t\t\tend_y: ipp.mouse.position.y.max(0.) as u32,\n\t\t\t\t\t});\n\t\t\t\t\tlet box_selection_end_graph = network_metadata\n\t\t\t\t\t\t.persistent_metadata\n\t\t\t\t\t\t.navigation_metadata\n\t\t\t\t\t\t.node_graph_to_viewport\n\t\t\t\t\t\t.inverse()\n\t\t\t\t\t\t.transform_point2(ipp.mouse.position);\n\n\t\t\t\t\tlet shift = ipp.keyboard.get(Key::Shift as usize);\n\t\t\t\t\tlet alt = ipp.keyboard.get(Key::Alt as usize);\n\t\t\t\t\tlet control = ipp.keyboard.get(Key::Control as usize);\n\t\t\t\t\tlet Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else {\n\t\t\t\t\t\tlog::error!(\"Could not get selected nodes in UpdateBoxSelection\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t};\n\t\t\t\t\tlet previous_selection = selected_nodes.selected_nodes_ref().iter().cloned().collect::<HashSet<_>>();\n\t\t\t\t\tlet mut nodes = if shift || alt {\n\t\t\t\t\t\tselected_nodes.selected_nodes_ref().iter().cloned().collect::<HashSet<_>>()\n\t\t\t\t\t} else {\n\t\t\t\t\t\tHashSet::new()\n\t\t\t\t\t};\n\t\t\t\t\tlet all_nodes = network_metadata.persistent_metadata.node_metadata.keys().cloned().collect::<Vec<_>>();\n\t\t\t\t\tfor node_id in all_nodes {\n\t\t\t\t\t\tlet Some(click_targets) = network_interface.node_click_targets(&node_id, selection_network_path) else {\n\t\t\t\t\t\t\tlog::error!(\"Could not get transient metadata for node {node_id}\");\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t};\n\t\t\t\t\t\tlet quad = Quad::from_box([box_selection_start, box_selection_end_graph]);\n\t\t\t\t\t\tif click_targets.node_click_target.intersect_path(|| quad.to_lines(), DAffine2::IDENTITY) {\n\t\t\t\t\t\t\tif alt {\n\t\t\t\t\t\t\t\tnodes.remove(&node_id);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tnodes.insert(node_id);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif control {\n\t\t\t\t\t\tlet layer_nodes: HashSet<_> = nodes.iter().filter(|node_id| network_interface.is_layer(node_id, selection_network_path)).cloned().collect();\n\t\t\t\t\t\tlet non_layer_nodes: HashSet<_> = nodes.difference(&layer_nodes).cloned().collect();\n\n\t\t\t\t\t\t// Remove non-layer nodes from selection\n\t\t\t\t\t\tif alt {\n\t\t\t\t\t\t\tnodes = previous_selection.difference(&non_layer_nodes).cloned().collect();\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Add non-layer nodes to selection\n\t\t\t\t\t\telse if shift {\n\t\t\t\t\t\t\tnodes = previous_selection.union(&non_layer_nodes).cloned().collect();\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Replace selection with non-layer nodes\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tnodes = non_layer_nodes;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif nodes != previous_selection {\n\t\t\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesSet {\n\t\t\t\t\t\t\tnodes: nodes.into_iter().collect::<Vec<_>>(),\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tresponses.add(FrontendMessage::UpdateBox { box_selection })\n\t\t\t\t}\n\t\t\t}\n\t\t\tNodeGraphMessage::UpdateImportsExports => {\n\t\t\t\tlet imports = network_interface.frontend_imports(breadcrumb_network_path);\n\t\t\t\tlet exports = network_interface.frontend_exports(breadcrumb_network_path);\n\n\t\t\t\tlet Some((import_position, export_position)) = network_interface.import_export_position(breadcrumb_network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get import export positions\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\n\t\t\t\t// Do not show the add import or add export button in the document network;\n\t\t\t\tlet add_import_export = !breadcrumb_network_path.is_empty();\n\n\t\t\t\tresponses.add(NodeGraphMessage::UpdateVisibleNodes);\n\t\t\t\tresponses.add(NodeGraphMessage::SendWires);\n\t\t\t\tresponses.add(FrontendMessage::UpdateImportsExports {\n\t\t\t\t\timports,\n\t\t\t\t\texports,\n\t\t\t\t\timport_position: import_position.into(),\n\t\t\t\t\texport_position: export_position.into(),\n\t\t\t\t\tadd_import_export,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tNodeGraphMessage::UpdateLayerPanel => {\n\t\t\t\tSelf::update_layer_panel(network_interface, selection_network_path, collapsed, layers_panel_open, responses);\n\t\t\t}\n\t\t\tNodeGraphMessage::UpdateEdges => {\n\t\t\t\t// Update the import/export UI edges whenever the PTZ changes or the bounding box of all nodes changes\n\t\t\t}\n\t\t\tNodeGraphMessage::UpdateNewNodeGraph => {\n\t\t\t\tlet Some(selected_nodes) = network_interface.selected_nodes_mut(selection_network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get selected nodes in NodeGraphMessage::UpdateNewNodeGraph\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tselected_nodes.clear_selected_nodes();\n\t\t\t\tresponses.add(EventMessage::SelectionChanged);\n\n\t\t\t\tresponses.add(NodeGraphMessage::SendGraph);\n\t\t\t}\n\t\t\tNodeGraphMessage::UpdateTypes { resolved_types, node_graph_errors } => {\n\t\t\t\tnetwork_interface.resolved_types.update(resolved_types, node_graph_errors);\n\t\t\t}\n\t\t\tNodeGraphMessage::UpdateActionButtons => {\n\t\t\t\tif selection_network_path == breadcrumb_network_path {\n\t\t\t\t\tself.update_graph_bar_left(network_interface, breadcrumb_network_path, responses);\n\t\t\t\t\tself.send_node_bar_layout(responses);\n\t\t\t\t}\n\t\t\t}\n\t\t\tNodeGraphMessage::UpdateGraphBarRight => {\n\t\t\t\tself.update_graph_bar_right(graph_fade_artwork_percentage, network_interface, breadcrumb_network_path, navigation_handler);\n\t\t\t\tself.send_node_bar_layout(responses);\n\t\t\t}\n\t\t\tNodeGraphMessage::UpdateInSelectedNetwork => responses.add(FrontendMessage::UpdateInSelectedNetwork {\n\t\t\t\tin_selected_network: selection_network_path == breadcrumb_network_path,\n\t\t\t}),\n\t\t\tNodeGraphMessage::UpdateHints => {\n\t\t\t\tself.update_node_graph_hints(responses);\n\t\t\t}\n\t\t\tNodeGraphMessage::SendSelectedNodes => {\n\t\t\t\tlet Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(breadcrumb_network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get selected nodes in NodeGraphMessage::SendSelectedNodes\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tresponses.add(NodeGraphMessage::UpdateActionButtons);\n\t\t\t\tresponses.add(FrontendMessage::UpdateNodeGraphSelection {\n\t\t\t\t\tselected: selected_nodes.selected_nodes().cloned().collect::<Vec<_>>(),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tlet Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else {\n\t\t\tlog::error!(\"Could not get selected nodes in NodeGraphMessageHandler\");\n\t\t\treturn;\n\t\t};\n\t\tself.has_selection = selected_nodes.has_selected_nodes();\n\t}\n\n\tfn actions(&self) -> ActionList {\n\t\tlet common = vec![];\n\t\tcommon\n\t}\n}\n\nimpl NodeGraphMessageHandler {\n\t/// Similar to [`NodeGraphMessageHandler::actions`], but this provides additional actions if the node graph is open and should only be called in that circumstance.\n\tpub fn actions_additional_if_node_graph_is_open(&self) -> ActionList {\n\t\tlet mut common = actions!(NodeGraphMessageDiscriminant; EnterNestedNetwork, PointerDown, PointerMove, PointerUp, SendClickTargets, EndSendClickTargets);\n\n\t\tif self.has_selection {\n\t\t\tcommon.extend(actions!(NodeGraphMessageDiscriminant;\n\t\t\t\tCopy,\n\t\t\t\tCut,\n\t\t\t\tDeleteSelectedNodes,\n\t\t\t\tDuplicateSelectedNodes,\n\t\t\t\tMergeSelectedNodes,\n\t\t\t\tToggleSelectedAsLayersOrNodes,\n\t\t\t\tToggleSelectedLocked,\n\t\t\t\tToggleSelectedVisibility,\n\t\t\t\tShiftSelectedNodes,\n\t\t\t));\n\t\t}\n\n\t\tif self.drag_start.is_some() {\n\t\t\tcommon.extend(actions!(NodeGraphMessageDiscriminant;\n\t\t\t\tShakeNode,\n\t\t\t));\n\t\t}\n\n\t\tcommon\n\t}\n\n\t/// Send the cached layout to the frontend for the control bar at the top of the node panel\n\tfn send_node_bar_layout(&self, responses: &mut VecDeque<Message>) {\n\t\tresponses.add(LayoutMessage::SendLayout {\n\t\t\tlayout: Layout(self.widgets.to_vec()),\n\t\t\tlayout_target: LayoutTarget::NodeGraphControlBar,\n\t\t});\n\t}\n\n\t/// Updates the buttons for visibility, locked, and preview\n\tfn update_graph_bar_left(&mut self, network_interface: &mut NodeNetworkInterface, breadcrumb_network_path: &[NodeId], responses: &mut VecDeque<Message>) {\n\t\tlet Some(subgraph_path_names) = Self::collect_subgraph_names(network_interface, breadcrumb_network_path) else {\n\t\t\t// If a node in a nested network could not be found, exit the nested network\n\t\t\tlet breadcrumb_network_path_len = breadcrumb_network_path.len();\n\t\t\tif breadcrumb_network_path_len > 0 {\n\t\t\t\tresponses.add(DocumentMessage::ExitNestedNetwork {\n\t\t\t\t\tsteps_back: breadcrumb_network_path_len,\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn;\n\t\t};\n\n\t\tlet Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(breadcrumb_network_path) else {\n\t\t\twarn!(\"No selected nodes in update_selection_action_buttons\");\n\t\t\treturn;\n\t\t};\n\n\t\tlet has_selection = selected_nodes.has_selected_nodes();\n\t\tlet selection_includes_layers = network_interface.selected_nodes().selected_layers(network_interface.document_metadata()).count() > 0;\n\t\tlet selection_all_locked = network_interface.selected_nodes().selected_unlocked_layers(network_interface).count() == 0;\n\t\tlet selection_all_visible = selected_nodes.selected_nodes().all(|node_id| network_interface.is_visible(node_id, breadcrumb_network_path));\n\n\t\tlet mut selected_layers = selected_nodes.selected_layers(network_interface.document_metadata());\n\t\tlet selected_layer = selected_layers.next();\n\t\tlet has_multiple_selection = selected_layers.next().is_some();\n\t\tfor _ in selected_layers {}\n\n\t\tlet mut widgets = vec![\n\t\t\tPopoverButton::new()\n\t\t\t\t.icon(\"Node\")\n\t\t\t\t.tooltip_label(\"New Node\")\n\t\t\t\t.tooltip_description(\"To add a node at the pointer location, perform the shortcut in an open area of the graph.\")\n\t\t\t\t.tooltip_shortcut(action_shortcut_manual!(Key::MouseRight))\n\t\t\t\t.popover_layout({\n\t\t\t\t\t// Showing only compatible types\n\t\t\t\t\tlet compatible_type = match (selection_includes_layers, has_multiple_selection, selected_layer) {\n\t\t\t\t\t\t(true, false, Some(layer)) => network_interface.output_type(&OutputConnector::node(layer.to_node(), 1), &[]).add_node_string(),\n\t\t\t\t\t\t_ => None,\n\t\t\t\t\t};\n\n\t\t\t\t\tlet single_layer_selected = selection_includes_layers && !has_multiple_selection;\n\n\t\t\t\t\tlet mut node_chooser = NodeCatalog::new();\n\t\t\t\t\tnode_chooser.intial_search = compatible_type.unwrap_or(\"\".to_string());\n\n\t\t\t\t\tlet node_chooser = node_chooser\n\t\t\t\t\t\t.on_update(move |node_type| {\n\t\t\t\t\t\t\tif let (true, Some(layer)) = (single_layer_selected, selected_layer) {\n\t\t\t\t\t\t\t\tNodeGraphMessage::CreateNodeInLayerWithTransaction {\n\t\t\t\t\t\t\t\t\tnode_type: node_type.clone(),\n\t\t\t\t\t\t\t\t\tlayer: LayerNodeIdentifier::new_unchecked(layer.to_node()),\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t.into()\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tlet node_id = NodeId::new();\n\t\t\t\t\t\t\t\tMessage::Batched {\n\t\t\t\t\t\t\t\t\tmessages: Box::new([\n\t\t\t\t\t\t\t\t\t\tNodeGraphMessage::CreateNodeFromContextMenu {\n\t\t\t\t\t\t\t\t\t\t\tnode_id: Some(node_id),\n\t\t\t\t\t\t\t\t\t\t\tnode_type: node_type.clone(),\n\t\t\t\t\t\t\t\t\t\t\txy: None,\n\t\t\t\t\t\t\t\t\t\t\tadd_transaction: true,\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t.into(),\n\t\t\t\t\t\t\t\t\t\tNodeGraphMessage::SelectedNodesSet { nodes: vec![node_id] }.into(),\n\t\t\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.widget_instance();\n\t\t\t\t\tLayout(vec![LayoutGroup::row(vec![node_chooser])])\n\t\t\t\t})\n\t\t\t\t.widget_instance(),\n\t\t\t//\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t//\n\t\t\tIconButton::new(\"Folder\", 24)\n\t\t\t\t.tooltip_label(\"Group Selected\")\n\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::GroupSelectedLayers))\n\t\t\t\t.on_update(|_| {\n\t\t\t\t\tlet group_folder_type = GroupFolderType::Layer;\n\t\t\t\t\tDocumentMessage::GroupSelectedLayers { group_folder_type }.into()\n\t\t\t\t})\n\t\t\t\t.disabled(!has_selection)\n\t\t\t\t.widget_instance(),\n\t\t\tIconButton::new(\"NewLayer\", 24)\n\t\t\t\t.tooltip_label(\"New Layer\")\n\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::CreateEmptyFolder))\n\t\t\t\t.on_update(|_| DocumentMessage::CreateEmptyFolder.into())\n\t\t\t\t.widget_instance(),\n\t\t\tIconButton::new(\"Trash\", 24)\n\t\t\t\t.tooltip_label(\"Delete Selected\")\n\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::DeleteSelectedLayers))\n\t\t\t\t.on_update(|_| DocumentMessage::DeleteSelectedLayers.into())\n\t\t\t\t.disabled(!has_selection)\n\t\t\t\t.widget_instance(),\n\t\t\t//\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t//\n\t\t\tIconButton::new(if selection_all_locked { \"PadlockLocked\" } else { \"PadlockUnlocked\" }, 24)\n\t\t\t\t.hover_icon(if selection_all_locked { \"PadlockUnlocked\" } else { \"PadlockLocked\" })\n\t\t\t\t.tooltip_label(if selection_all_locked { \"Unlock Selected\" } else { \"Lock Selected\" })\n\t\t\t\t.tooltip_shortcut(action_shortcut!(NodeGraphMessageDiscriminant::ToggleSelectedLocked))\n\t\t\t\t.on_update(|_| NodeGraphMessage::ToggleSelectedLocked.into())\n\t\t\t\t.disabled(!has_selection || !selection_includes_layers)\n\t\t\t\t.widget_instance(),\n\t\t\tIconButton::new(if selection_all_visible { \"EyeVisible\" } else { \"EyeHidden\" }, 24)\n\t\t\t\t.hover_icon(if selection_all_visible { \"EyeHide\" } else { \"EyeShow\" })\n\t\t\t\t.tooltip_label(if selection_all_visible { \"Hide Selected\" } else { \"Show Selected\" })\n\t\t\t\t.tooltip_shortcut(action_shortcut!(NodeGraphMessageDiscriminant::ToggleSelectedVisibility))\n\t\t\t\t.on_update(|_| NodeGraphMessage::ToggleSelectedVisibility.into())\n\t\t\t\t.disabled(!has_selection)\n\t\t\t\t.widget_instance(),\n\t\t];\n\n\t\tlet mut selection = selected_nodes.selected_nodes();\n\t\tlet (selection, no_other_selections) = (selection.next(), selection.count() == 0);\n\n\t\tlet Some(network) = network_interface.nested_network(breadcrumb_network_path) else {\n\t\t\twarn!(\"No network in update_selection_action_buttons\");\n\t\t\treturn;\n\t\t};\n\t\tlet previewing = if matches!(network_interface.previewing(breadcrumb_network_path), Previewing::Yes { .. }) {\n\t\t\tnetwork.exports.iter().find_map(|export| {\n\t\t\t\tlet NodeInput::Node { node_id, .. } = export else { return None };\n\t\t\t\tSome(*node_id)\n\t\t\t})\n\t\t} else {\n\t\t\tNone\n\t\t};\n\n\t\t// If only one node is selected then show the preview or stop previewing button\n\t\tif let Some(node_id) = previewing {\n\t\t\tlet button = TextButton::new(\"End Preview\")\n\t\t\t\t.icon(\"FrameAll\")\n\t\t\t\t.tooltip_description(\"Restore preview to the graph output.\")\n\t\t\t\t.on_update(move |_| NodeGraphMessage::TogglePreview { node_id }.into())\n\t\t\t\t.widget_instance();\n\t\t\twidgets.extend([Separator::new(SeparatorStyle::Unrelated).widget_instance(), button]);\n\t\t} else if let Some(&node_id) = selection {\n\t\t\tlet selection_is_not_already_the_output = !network\n\t\t\t\t.exports\n\t\t\t\t.iter()\n\t\t\t\t.any(|export| matches!(export, NodeInput::Node { node_id: export_node_id, .. } if *export_node_id == node_id));\n\t\t\tif selection_is_not_already_the_output && no_other_selections {\n\t\t\t\tlet button = TextButton::new(\"Preview\")\n\t\t\t\t\t.icon(\"FrameAll\")\n\t\t\t\t\t.tooltip_label(\"Preview\")\n\t\t\t\t\t.tooltip_description(\"Temporarily set the graph output to the selected node or layer. Perform the shortcut on a node or layer for quick access.\")\n\t\t\t\t\t.tooltip_shortcut(action_shortcut_manual!(Key::Alt, Key::MouseLeft))\n\t\t\t\t\t.on_update(move |_| NodeGraphMessage::TogglePreview { node_id }.into())\n\t\t\t\t\t.widget_instance();\n\t\t\t\twidgets.extend([Separator::new(SeparatorStyle::Unrelated).widget_instance(), button]);\n\t\t\t}\n\t\t}\n\n\t\tlet subgraph_path_names_length = subgraph_path_names.len();\n\t\tif subgraph_path_names_length >= 2 {\n\t\t\twidgets.extend([\n\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\tBreadcrumbTrailButtons::new(subgraph_path_names)\n\t\t\t\t\t.on_update(move |index| {\n\t\t\t\t\t\tDocumentMessage::ExitNestedNetwork {\n\t\t\t\t\t\t\tsteps_back: subgraph_path_names_length - (*index as usize) - 1,\n\t\t\t\t\t\t}\n\t\t\t\t\t\t.into()\n\t\t\t\t\t})\n\t\t\t\t\t.widget_instance(),\n\t\t\t]);\n\t\t}\n\n\t\tself.widgets[0] = LayoutGroup::row(widgets);\n\t}\n\n\tfn update_graph_bar_right(\n\t\t&mut self,\n\t\tgraph_fade_artwork_percentage: f64,\n\t\tnetwork_interface: &NodeNetworkInterface,\n\t\tbreadcrumb_network_path: &[NodeId],\n\t\tnavigation_handler: &NavigationMessageHandler,\n\t) {\n\t\tlet Some(node_graph_ptz) = network_interface.node_graph_ptz(breadcrumb_network_path) else {\n\t\t\tlog::error!(\"Could not get node graph PTZ\");\n\t\t\treturn;\n\t\t};\n\n\t\tlet mut widgets = vec![\n\t\t\tNumberInput::new(Some(graph_fade_artwork_percentage))\n\t\t\t\t.percentage()\n\t\t\t\t.display_decimal_places(0)\n\t\t\t\t.label(\"Fade Artwork\")\n\t\t\t\t.tooltip_description(\"Opacity of the graph background that covers the artwork.\")\n\t\t\t\t.on_update(move |number_input: &NumberInput| {\n\t\t\t\t\tDocumentMessage::SetGraphFadeArtwork {\n\t\t\t\t\t\tpercentage: number_input.value.unwrap_or(graph_fade_artwork_percentage),\n\t\t\t\t\t}\n\t\t\t\t\t.into()\n\t\t\t\t})\n\t\t\t\t.widget_instance(),\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t];\n\t\twidgets.extend(navigation_controls(node_graph_ptz, navigation_handler, true));\n\t\twidgets.extend([\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\tTextButton::new(\"Node Graph\")\n\t\t\t\t.icon(\"GraphViewOpen\")\n\t\t\t\t.hover_icon(\"GraphViewClosed\")\n\t\t\t\t.tooltip_label(\"Hide Node Graph\")\n\t\t\t\t.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::GraphViewOverlayToggle))\n\t\t\t\t.on_update(move |_| DocumentMessage::GraphViewOverlayToggle.into())\n\t\t\t\t.widget_instance(),\n\t\t]);\n\n\t\tself.widgets[1] = LayoutGroup::row(widgets);\n\t}\n\n\t/// Collate the properties panel sections for a node graph\n\tpub fn collate_properties(context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {\n\t\t// If the selected nodes are in the document network, use the document network. Otherwise, use the nested network\n\t\tlet Some(selected_nodes) = context.network_interface.selected_nodes_in_nested_network(context.selection_network_path) else {\n\t\t\twarn!(\"No selected nodes in collate_properties\");\n\t\t\treturn Vec::new();\n\t\t};\n\n\t\t// We want:\n\t\t// - If only nodes (no layers) are selected: display each node's properties\n\t\t// - If one layer is selected, and zero or more of its (primary flow) upstream nodes: display the properties for the layer and all its upstream nodes\n\t\t// - If multiple layers are selected, or one node plus other non-upstream nodes: display nothing\n\t\t// - If nothing is selected, display any pinned nodes/layers\n\n\t\t// First, we filter all the selections into layers and nodes\n\t\tlet (mut layers, mut nodes) = (Vec::new(), Vec::new());\n\t\tfor node_id in selected_nodes.selected_nodes() {\n\t\t\tif context.network_interface.is_layer(node_id, context.selection_network_path) {\n\t\t\t\tlayers.push(*node_id);\n\t\t\t} else {\n\t\t\t\tnodes.push(*node_id);\n\t\t\t}\n\t\t}\n\n\t\t// The same layer/node may appear several times. Sort and dedup them for a stable ordering.\n\t\tlayers.sort();\n\t\tlayers.dedup();\n\t\tnodes.sort();\n\t\tnodes.dedup();\n\n\t\t// Next, we decide what to display based on the number of layers and nodes selected\n\t\tmatch *layers.as_slice() {\n\t\t\t// If no layers are selected, show properties for all selected nodes\n\t\t\t[] => {\n\t\t\t\tlet selected_nodes = nodes.iter().map(|node_id| node_properties::generate_node_properties(*node_id, context)).collect::<Vec<_>>();\n\t\t\t\tif !selected_nodes.is_empty() {\n\t\t\t\t\tlet mut properties = Vec::new();\n\n\t\t\t\t\tif let [node_id] = *nodes.as_slice() {\n\t\t\t\t\t\tproperties.push(LayoutGroup::row(vec![\n\t\t\t\t\t\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\t\t\t\t\t\tIconLabel::new(\"Node\").tooltip_description(\"Name of the selected node.\").widget_instance(),\n\t\t\t\t\t\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\t\t\t\t\t\tTextInput::new(context.network_interface.display_name(&node_id, context.selection_network_path))\n\t\t\t\t\t\t\t\t.tooltip_description(\"Name of the selected node.\")\n\t\t\t\t\t\t\t\t.on_update(move |text_input| {\n\t\t\t\t\t\t\t\t\tNodeGraphMessage::SetDisplayName {\n\t\t\t\t\t\t\t\t\t\tnode_id,\n\t\t\t\t\t\t\t\t\t\talias: text_input.value.clone(),\n\t\t\t\t\t\t\t\t\t\tskip_adding_history_step: false,\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t.into()\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t\t\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\t\t\t\t\t]));\n\t\t\t\t\t}\n\n\t\t\t\t\tproperties.extend(selected_nodes);\n\n\t\t\t\t\treturn properties;\n\t\t\t\t}\n\n\t\t\t\t// TODO: Display properties for encapsulating node when no nodes are selected in a nested network\n\t\t\t\t// This may require store a separate path for the properties panel\n\t\t\t\tlet mut properties = vec![LayoutGroup::row(vec![\n\t\t\t\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\t\t\t\tIconLabel::new(\"File\").tooltip_description(\"Name of the current document.\").widget_instance(),\n\t\t\t\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\t\t\t\tTextInput::new(context.document_name)\n\t\t\t\t\t\t.tooltip_description(\"Name of the current document.\")\n\t\t\t\t\t\t.on_update(|text_input| DocumentMessage::RenameDocument { new_name: text_input.value.clone() }.into())\n\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\t\t\t])];\n\n\t\t\t\tlet Some(network) = context.network_interface.nested_network(context.selection_network_path) else {\n\t\t\t\t\twarn!(\"No network in collate_properties\");\n\t\t\t\t\treturn Vec::new();\n\t\t\t\t};\n\t\t\t\t// And if no nodes are selected, show properties for all pinned nodes\n\t\t\t\tlet pinned_node_properties = network\n\t\t\t\t\t.nodes\n\t\t\t\t\t.keys()\n\t\t\t\t\t.cloned()\n\t\t\t\t\t.collect::<Vec<_>>()\n\t\t\t\t\t.iter()\n\t\t\t\t\t.filter_map(|node_id| {\n\t\t\t\t\t\tif context.network_interface.is_pinned(node_id, context.selection_network_path) {\n\t\t\t\t\t\t\tSome(node_properties::generate_node_properties(*node_id, context))\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tNone\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\t.collect::<Vec<_>>();\n\n\t\t\t\tproperties.extend(pinned_node_properties);\n\t\t\t\tproperties\n\t\t\t}\n\t\t\t// If one layer is selected, filter out all selected nodes that are not upstream of it. If there are no nodes left, show properties for the layer. Otherwise, show nothing.\n\t\t\t[layer] => {\n\t\t\t\tlet nodes_not_upstream_of_layer = nodes.into_iter().filter(|&selected_node_id| {\n\t\t\t\t\t!context\n\t\t\t\t\t\t.network_interface\n\t\t\t\t\t\t.is_node_upstream_of_another_by_horizontal_flow(layer, context.selection_network_path, selected_node_id)\n\t\t\t\t});\n\t\t\t\tif nodes_not_upstream_of_layer.count() > 0 {\n\t\t\t\t\treturn Vec::new();\n\t\t\t\t}\n\n\t\t\t\tlet mut layer_properties = vec![LayoutGroup::row(vec![\n\t\t\t\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\t\t\t\tIconLabel::new(\"Layer\").tooltip_description(\"Name of the selected layer.\").widget_instance(),\n\t\t\t\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\t\t\t\tTextInput::new(context.network_interface.display_name(&layer, context.selection_network_path))\n\t\t\t\t\t\t.tooltip_description(\"Name of the selected layer.\")\n\t\t\t\t\t\t.on_update(move |text_input| {\n\t\t\t\t\t\t\tNodeGraphMessage::SetDisplayName {\n\t\t\t\t\t\t\t\tnode_id: layer,\n\t\t\t\t\t\t\t\talias: text_input.value.clone(),\n\t\t\t\t\t\t\t\tskip_adding_history_step: false,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t.into()\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\t\t\t\tPopoverButton::new()\n\t\t\t\t\t\t.icon(\"Node\")\n\t\t\t\t\t\t.tooltip_description(\"Add an operation to the end of this layer's chain of nodes.\")\n\t\t\t\t\t\t.popover_layout({\n\t\t\t\t\t\t\tlet compatible_type = context\n\t\t\t\t\t\t\t\t.network_interface\n\t\t\t\t\t\t\t\t.upstream_output_connector(&InputConnector::node(layer, 1), &[])\n\t\t\t\t\t\t\t\t.and_then(|upstream_output| context.network_interface.output_type(&upstream_output, &[]).add_node_string());\n\n\t\t\t\t\t\t\tlet mut node_chooser = NodeCatalog::new();\n\t\t\t\t\t\t\tnode_chooser.intial_search = compatible_type.unwrap_or(\"\".to_string());\n\n\t\t\t\t\t\t\tlet node_chooser = node_chooser\n\t\t\t\t\t\t\t\t.on_update(move |node_type| {\n\t\t\t\t\t\t\t\t\tNodeGraphMessage::CreateNodeInLayerWithTransaction {\n\t\t\t\t\t\t\t\t\t\tnode_type: node_type.clone(),\n\t\t\t\t\t\t\t\t\t\tlayer: LayerNodeIdentifier::new_unchecked(layer),\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t.into()\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t.widget_instance();\n\t\t\t\t\t\t\tLayout(vec![LayoutGroup::row(vec![node_chooser])])\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\t\t\t])];\n\n\t\t\t\t// Iterate through all the upstream nodes, but stop when we reach another layer (since that's a point where we switch from horizontal to vertical flow)\n\t\t\t\tlet node_properties = context\n\t\t\t\t\t.network_interface\n\t\t\t\t\t.upstream_flow_back_from_nodes(vec![layer], context.selection_network_path, network_interface::FlowType::HorizontalFlow)\n\t\t\t\t\t.enumerate()\n\t\t\t\t\t.take_while(|(i, node_id)| {\n\t\t\t\t\t\tif *i == 0 {\n\t\t\t\t\t\t\ttrue\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t!context.network_interface.is_layer(node_id, context.selection_network_path)\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\t.map(|(_, node_id)| node_id)\n\t\t\t\t\t.collect::<Vec<_>>()\n\t\t\t\t\t.into_iter()\n\t\t\t\t\t.map(|node_id| node_properties::generate_node_properties(node_id, context))\n\t\t\t\t\t.collect::<Vec<_>>();\n\n\t\t\t\tlayer_properties.extend(node_properties);\n\t\t\t\tlayer_properties\n\t\t\t}\n\t\t\t// If multiple layers and/or nodes are selected, show nothing\n\t\t\t_ => Vec::new(),\n\t\t}\n\t}\n\n\tfn collect_wires(&mut self, network_interface: &mut NodeNetworkInterface, graph_wire_style: GraphWireStyle, breadcrumb_network_path: &[NodeId]) -> Vec<WirePathUpdate> {\n\t\tlet mut added_wires = network_interface\n\t\t\t.node_graph_input_connectors(breadcrumb_network_path)\n\t\t\t.iter()\n\t\t\t.filter_map(|connector| network_interface.newly_loaded_input_wire(connector, graph_wire_style, breadcrumb_network_path))\n\t\t\t.collect::<Vec<_>>();\n\n\t\tlet changed_wire_inputs = added_wires.iter().map(|update| (update.id, update.input_index)).collect::<Vec<_>>();\n\t\tself.frontend_wires.extend(changed_wire_inputs);\n\n\t\tlet mut orphaned_wire_inputs = self.frontend_wires.clone();\n\t\tself.frontend_wires = network_interface\n\t\t\t.node_graph_wire_inputs(breadcrumb_network_path)\n\t\t\t.iter()\n\t\t\t.filter_map(|visible_wire_input| orphaned_wire_inputs.take(visible_wire_input))\n\t\t\t.collect::<HashSet<_>>();\n\t\tadded_wires.extend(orphaned_wire_inputs.into_iter().map(|(id, input_index)| WirePathUpdate {\n\t\t\tid,\n\t\t\tinput_index,\n\t\t\twire_path_update: None,\n\t\t}));\n\n\t\tif let Some(wire_to_root) = network_interface.wire_to_root(graph_wire_style, breadcrumb_network_path) {\n\t\t\tadded_wires.push(wire_to_root);\n\t\t} else {\n\t\t\tadded_wires.push(WirePathUpdate {\n\t\t\t\tid: NodeId(u64::MAX),\n\t\t\t\tinput_index: u32::MAX as usize,\n\t\t\t\twire_path_update: None,\n\t\t\t})\n\t\t}\n\n\t\tadded_wires\n\t}\n\n\tfn collect_nodes(&self, network_interface: &mut NodeNetworkInterface, breadcrumb_network_path: &[NodeId]) -> Vec<FrontendNode> {\n\t\tlet Some(network) = network_interface.nested_network(breadcrumb_network_path) else {\n\t\t\tlog::error!(\"Could not get nested network when collecting nodes\");\n\t\t\treturn Vec::new();\n\t\t};\n\t\tlet mut nodes = Vec::new();\n\t\tfor (node_id, visible) in network.nodes.iter().map(|(node_id, node)| (*node_id, node.visible)).collect::<Vec<_>>() {\n\t\t\tlet primary_input_connector = InputConnector::node(node_id, 0);\n\n\t\t\tlet primary_input = if network_interface\n\t\t\t\t.input_from_connector(&primary_input_connector, breadcrumb_network_path)\n\t\t\t\t.is_some_and(|input| input.is_exposed())\n\t\t\t{\n\t\t\t\tnetwork_interface.frontend_input_from_connector(&primary_input_connector, breadcrumb_network_path)\n\t\t\t} else {\n\t\t\t\tNone\n\t\t\t};\n\t\t\tlet exposed_inputs = (1..network_interface.number_of_inputs(&node_id, breadcrumb_network_path))\n\t\t\t\t.filter_map(|input_index| network_interface.frontend_input_from_connector(&InputConnector::node(node_id, input_index), breadcrumb_network_path))\n\t\t\t\t.collect();\n\n\t\t\tlet primary_output = network_interface.frontend_output_from_connector(&OutputConnector::node(node_id, 0), breadcrumb_network_path);\n\n\t\t\tlet exposed_outputs = (1..network_interface.number_of_outputs(&node_id, breadcrumb_network_path))\n\t\t\t\t.filter_map(|output_index| network_interface.frontend_output_from_connector(&OutputConnector::node(node_id, output_index), breadcrumb_network_path))\n\t\t\t\t.collect();\n\t\t\tlet (primary_output_connected_to_layer, primary_input_connected_to_layer) = if network_interface.is_layer(&node_id, breadcrumb_network_path) {\n\t\t\t\t(\n\t\t\t\t\tnetwork_interface.primary_output_connected_to_layer(&node_id, breadcrumb_network_path),\n\t\t\t\t\tnetwork_interface.primary_input_connected_to_layer(&node_id, breadcrumb_network_path),\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\t(false, false)\n\t\t\t};\n\n\t\t\tlet is_export = network_interface\n\t\t\t\t.input_from_connector(&InputConnector::Export(0), breadcrumb_network_path)\n\t\t\t\t.is_some_and(|export| export.as_node().is_some_and(|export_node_id| node_id == export_node_id));\n\t\t\tlet is_root_node = network_interface.root_node(breadcrumb_network_path).is_some_and(|root_node| root_node.node_id == node_id);\n\n\t\t\tlet Some(position) = network_interface.position(&node_id, breadcrumb_network_path) else {\n\t\t\t\tlog::error!(\"Could not get position for node: {node_id}\");\n\t\t\t\tcontinue;\n\t\t\t};\n\t\t\tlet previewed = is_export && !is_root_node;\n\n\t\t\tlet locked = network_interface.is_locked(&node_id, breadcrumb_network_path);\n\n\t\t\tnodes.push(FrontendNode {\n\t\t\t\tid: node_id,\n\t\t\t\tis_layer: network_interface\n\t\t\t\t\t.node_metadata(&node_id, breadcrumb_network_path)\n\t\t\t\t\t.is_some_and(|node_metadata| node_metadata.persistent_metadata.is_layer()),\n\t\t\t\tcan_be_layer: network_interface.is_eligible_to_be_layer(&node_id, breadcrumb_network_path),\n\t\t\t\treference: network_interface.reference(&node_id, breadcrumb_network_path).map(|reference| reference.serialized()),\n\t\t\t\tdisplay_name: network_interface.display_name(&node_id, breadcrumb_network_path),\n\t\t\t\timplementation_name: network_interface.implementation_name(&node_id, breadcrumb_network_path),\n\t\t\t\tprimary_input,\n\t\t\t\texposed_inputs,\n\t\t\t\tprimary_output,\n\t\t\t\texposed_outputs,\n\t\t\t\tprimary_output_connected_to_layer,\n\t\t\t\tprimary_input_connected_to_layer,\n\t\t\t\tposition: position.into(),\n\t\t\t\tpreviewed,\n\t\t\t\tvisible,\n\t\t\t\tlocked,\n\t\t\t});\n\t\t}\n\n\t\tnodes\n\t}\n\n\tfn collect_subgraph_names(network_interface: &mut NodeNetworkInterface, breadcrumb_network_path: &[NodeId]) -> Option<Vec<String>> {\n\t\tlet mut current_network_path = vec![];\n\t\tlet mut current_network = network_interface.nested_network(&current_network_path).unwrap();\n\t\tlet mut subgraph_names = vec![\"Document\".to_string()];\n\t\tfor node_id in breadcrumb_network_path {\n\t\t\tlet node = current_network.nodes.get(node_id)?;\n\t\t\tif let Some(network) = node.implementation.get_network() {\n\t\t\t\tcurrent_network = network;\n\t\t\t};\n\t\t\tsubgraph_names.push(network_interface.display_name(node_id, &current_network_path));\n\t\t\tcurrent_network_path.push(*node_id)\n\t\t}\n\t\tSome(subgraph_names)\n\t}\n\n\tfn node_graph_error(&self, network_interface: &mut NodeNetworkInterface, breadcrumb_network_path: &[NodeId]) -> Option<NodeGraphErrorDiagnostic> {\n\t\tlet graph_error = network_interface\n\t\t\t.resolved_types\n\t\t\t.node_graph_errors\n\t\t\t.iter()\n\t\t\t.find(|error| error.node_path.starts_with(breadcrumb_network_path) && error.node_path.len() > breadcrumb_network_path.len())?;\n\t\tlet error = if graph_error.node_path.len() == breadcrumb_network_path.len() + 1 {\n\t\t\tformat!(\"{:?}\", graph_error.error)\n\t\t} else {\n\t\t\t\"Node graph type error within this node\".to_string()\n\t\t};\n\t\tlet error_node = graph_error.node_path[breadcrumb_network_path.len()];\n\n\t\tlet mut position = network_interface.position(&error_node, breadcrumb_network_path)?;\n\t\t// Convert to graph space\n\t\tposition *= 24;\n\t\tif network_interface.is_layer(&error_node, breadcrumb_network_path) {\n\t\t\tposition += IVec2::new(12, -12)\n\t\t}\n\t\tlet position = position.into();\n\n\t\tSome(NodeGraphErrorDiagnostic { position, error })\n\t}\n\n\tfn update_layer_panel(network_interface: &NodeNetworkInterface, selection_network_path: &[NodeId], collapsed: &CollapsedLayers, layers_panel_open: bool, responses: &mut VecDeque<Message>) {\n\t\tif !layers_panel_open {\n\t\t\treturn;\n\t\t}\n\n\t\tlet selected_layers = network_interface\n\t\t\t.selected_nodes()\n\t\t\t.selected_layers(network_interface.document_metadata())\n\t\t\t.map(|layer| layer.to_node())\n\t\t\t.collect::<HashSet<_>>();\n\n\t\tlet mut ancestors_of_selected = HashSet::new();\n\t\tlet mut descendants_of_selected = HashSet::new();\n\t\tfor selected_layer in &selected_layers {\n\t\t\tfor ancestor in LayerNodeIdentifier::new(*selected_layer, network_interface).ancestors(network_interface.document_metadata()) {\n\t\t\t\tif ancestor != LayerNodeIdentifier::ROOT_PARENT && ancestor.to_node() != *selected_layer {\n\t\t\t\t\tancestors_of_selected.insert(ancestor.to_node());\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor descendant in LayerNodeIdentifier::new(*selected_layer, network_interface).descendants(network_interface.document_metadata()) {\n\t\t\t\tdescendants_of_selected.insert(descendant.to_node());\n\t\t\t}\n\t\t}\n\n\t\tfor (&node_id, node_metadata) in &network_interface.document_network_metadata().persistent_metadata.node_metadata {\n\t\t\tif node_metadata.persistent_metadata.is_layer() {\n\t\t\t\tlet layer = LayerNodeIdentifier::new(node_id, network_interface);\n\n\t\t\t\tlet children_allowed =\n\t\t\t\t\t\t// The layer has other layers as children along the secondary input's horizontal flow\n\t\t\t\t\t\tlayer.has_children(network_interface.document_metadata())\n\t\t\t\t\t\t|| (\n\t\t\t\t\t\t\t// Check if the last node in the chain has an exposed left input\n\t\t\t\t\t\t\tnetwork_interface.upstream_flow_back_from_nodes(vec![node_id], &[], network_interface::FlowType::HorizontalFlow).last().is_some_and(|node_id|\n\t\t\t\t\t\t\t\tnetwork_interface.document_node(&node_id, &[]).map_or_else(||{log::error!(\"Could not get node {node_id} in update_layer_panel\"); false}, |node| {\n\t\t\t\t\t\t\t\t\tif network_interface.is_layer(&node_id, &[]) {\n\t\t\t\t\t\t\t\t\t\tnode.inputs.iter().filter(|input| input.is_exposed()).nth(1).is_some_and(|input| input.as_value().is_some())\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tnode.inputs.iter().filter(|input| input.is_exposed()).nth(0).is_some_and(|input| input.as_value().is_some())\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t);\n\n\t\t\t\tlet parents_visible = layer.ancestors(network_interface.document_metadata()).filter(|&ancestor| ancestor != layer).all(|layer| {\n\t\t\t\t\tif layer != LayerNodeIdentifier::ROOT_PARENT {\n\t\t\t\t\t\tnetwork_interface.document_node(&layer.to_node(), &[]).map(|node| node.visible).unwrap_or_default()\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttrue\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tlet parents_unlocked: bool = layer.ancestors(network_interface.document_metadata()).filter(|&ancestor| ancestor != layer).all(|layer| {\n\t\t\t\t\tif layer != LayerNodeIdentifier::ROOT_PARENT {\n\t\t\t\t\t\t!network_interface.is_locked(&layer.to_node(), &[])\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttrue\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tlet clippable = layer.can_be_clipped(network_interface.document_metadata());\n\n\t\t\t\tlet data = LayerPanelEntry {\n\t\t\t\t\tid: node_id,\n\t\t\t\t\timplementation_name: network_interface.implementation_name(&node_id, &[]),\n\t\t\t\t\ticon_name: network_interface.is_artboard(&node_id, &[]).then(|| \"Artboard\".to_string()),\n\t\t\t\t\talias: network_interface.display_name(&node_id, &[]),\n\t\t\t\t\tin_selected_network: selection_network_path.is_empty(),\n\t\t\t\t\tchildren_allowed,\n\t\t\t\t\tchildren_present: layer.has_children(network_interface.document_metadata()),\n\t\t\t\t\texpanded: layer.has_children(network_interface.document_metadata()) && !collapsed.0.contains(&layer),\n\t\t\t\t\tdepth: layer.ancestors(network_interface.document_metadata()).count() as u32 - 1,\n\t\t\t\t\tvisible: network_interface.is_visible(&node_id, &[]),\n\t\t\t\t\tparents_visible,\n\t\t\t\t\tunlocked: !network_interface.is_locked(&node_id, &[]),\n\t\t\t\t\tparents_unlocked,\n\t\t\t\t\tparent_id: layer\n\t\t\t\t\t\t.parent(network_interface.document_metadata())\n\t\t\t\t\t\t.and_then(|parent| if parent != LayerNodeIdentifier::ROOT_PARENT { Some(parent.to_node()) } else { None }),\n\t\t\t\t\tselected: selected_layers.contains(&node_id),\n\t\t\t\t\tancestor_of_selected: ancestors_of_selected.contains(&node_id),\n\t\t\t\t\tdescendant_of_selected: descendants_of_selected.contains(&node_id),\n\t\t\t\t\tclipped: get_clip_mode(layer, network_interface).unwrap_or(false) && clippable,\n\t\t\t\t\tclippable,\n\t\t\t\t};\n\t\t\t\tresponses.add(FrontendMessage::UpdateDocumentLayerDetails { data });\n\t\t\t}\n\t\t}\n\t}\n\n\tfn update_node_graph_hints(&self, responses: &mut VecDeque<Message>) {\n\t\t// A wire is in progress and its start and end connectors are set\n\t\tlet wiring = self.wire_in_progress_from_connector.is_some();\n\n\t\t// Node gragging is in progress (having already moved at least one pixel from the mouse down position)\n\t\tlet dragging_nodes = self.drag_start.as_ref().is_some_and(|(_, dragged)| *dragged);\n\n\t\t// A box selection is in progress\n\t\tlet dragging_box_selection = self.box_selection_start.is_some_and(|(_, box_selection_dragged)| box_selection_dragged);\n\n\t\t// Cancel the ongoing action\n\t\tif wiring || dragging_nodes || dragging_box_selection {\n\t\t\tHintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"\"), HintInfo::keys([Key::Escape], \"Cancel\").prepend_slash()])]).send_layout(responses);\n\t\t\treturn;\n\t\t}\n\n\t\t// Default hints for all other states\n\t\tlet mut hint_data = HintData(vec![\n\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"Add Node\")]),\n\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, \"Select Node\"), HintInfo::keys([Key::Shift], \"Extend\").prepend_plus()]),\n\t\t\tHintGroup(vec![\n\t\t\t\tHintInfo::mouse(MouseMotion::LmbDrag, \"Select Area\"),\n\t\t\t\tHintInfo::keys([Key::Shift], \"Extend\").prepend_plus(),\n\t\t\t\tHintInfo::keys([Key::Alt], \"Subtract\").prepend_plus(),\n\t\t\t\tHintInfo::keys([Key::Control], \"Exclude Layers\").prepend_plus(),\n\t\t\t]),\n\t\t]);\n\t\tif self.has_selection {\n\t\t\thint_data.0.extend([\n\t\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, \"Drag Selected\")]),\n\t\t\t\tHintGroup(vec![\n\t\t\t\t\tHintInfo::keys([Key::Backspace], \"Delete Selected\"),\n\t\t\t\t\tHintInfo::keys([Key::Control], \"Keep Children\").prepend_plus(),\n\t\t\t\t]),\n\t\t\t\tHintGroup(vec![\n\t\t\t\t\tHintInfo::keys_and_mouse([Key::Alt], MouseMotion::LmbDrag, \"Move Duplicate\"),\n\t\t\t\t\tHintInfo::keys([Key::Control, Key::KeyD], \"Duplicate\").add_mac_keys([Key::Command, Key::KeyD]),\n\t\t\t\t]),\n\t\t\t]);\n\t\t}\n\t\thint_data.0.extend([\n\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::LmbDouble, \"Enter Node Subgraph\")]),\n\t\t\tHintGroup(vec![HintInfo::keys_and_mouse([Key::Alt], MouseMotion::Lmb, \"Preview Node Output\")]),\n\t\t]);\n\t\thint_data.send_layout(responses);\n\t}\n}\n\nimpl Default for NodeGraphMessageHandler {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tnetwork: Vec::new(),\n\t\t\thas_selection: false,\n\t\t\twidgets: [LayoutGroup::row(Vec::new()), LayoutGroup::row(Vec::new())],\n\t\t\tdrag_start: None,\n\t\t\tbegin_dragging: false,\n\t\t\tnode_has_moved_in_drag: false,\n\t\t\tshift_without_push: false,\n\t\t\tbox_selection_start: None,\n\t\t\tdrag_start_chain_nodes: Vec::new(),\n\t\t\tselection_before_pointer_down: Vec::new(),\n\t\t\tdisconnecting: None,\n\t\t\tinitial_disconnecting: false,\n\t\t\tselect_if_not_dragged: None,\n\t\t\twire_in_progress_from_connector: None,\n\t\t\twire_in_progress_to_connector: None,\n\t\t\twire_in_progress_type: FrontendGraphDataType::General,\n\t\t\tcontext_menu: None,\n\t\t\tdeselect_on_pointer_up: None,\n\t\t\tauto_panning: Default::default(),\n\t\t\tpreview_on_mouse_up: None,\n\t\t\treordering_export: None,\n\t\t\treordering_import: None,\n\t\t\tend_index: None,\n\t\t\tfrontend_nodes: Vec::new(),\n\t\t\tfrontend_wires: HashSet::new(),\n\t\t}\n\t}\n}\n\nimpl PartialEq for NodeGraphMessageHandler {\n\tfn eq(&self, other: &Self) -> bool {\n\t\tself.network == other.network\n\t\t\t&& self.has_selection == other.has_selection\n\t\t\t&& self.widgets == other.widgets\n\t\t\t&& self.drag_start == other.drag_start\n\t\t\t&& self.begin_dragging == other.begin_dragging\n\t\t\t&& self.node_has_moved_in_drag == other.node_has_moved_in_drag\n\t\t\t&& self.box_selection_start == other.box_selection_start\n\t\t\t&& self.initial_disconnecting == other.initial_disconnecting\n\t\t\t&& self.select_if_not_dragged == other.select_if_not_dragged\n\t\t\t&& self.wire_in_progress_from_connector == other.wire_in_progress_from_connector\n\t\t\t&& self.wire_in_progress_to_connector == other.wire_in_progress_to_connector\n\t\t\t&& self.context_menu == other.context_menu\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/node_graph/node_properties.rs",
    "content": "#![allow(clippy::too_many_arguments)]\n\nuse super::document_node_definitions::{NODE_OVERRIDES, NodePropertiesContext};\nuse super::utility_types::FrontendGraphDataType;\nuse crate::messages::layout::utility_types::widget_prelude::*;\nuse crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;\nuse crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeNetworkInterface};\nuse crate::messages::portfolio::utility_types::{FontCatalogStyle, PersistentData};\nuse crate::messages::prelude::*;\nuse choice::enum_choice;\nuse dyn_any::DynAny;\nuse glam::{DAffine2, DVec2};\nuse graph_craft::document::value::TaggedValue;\nuse graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput};\nuse graph_craft::{Type, concrete};\nuse graphene_std::NodeInputDecleration;\nuse graphene_std::animation::RealTimeMode;\nuse graphene_std::extract_xy::XY;\nuse graphene_std::raster::curve::Curve;\nuse graphene_std::raster::{\n\tBlendMode, CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, LuminanceCalculation, NoiseType, RedGreenBlue, RedGreenBlueAlpha, RelativeAbsolute,\n\tSelectiveColorChoice,\n};\nuse graphene_std::table::{Table, TableRow};\nuse graphene_std::text::{Font, TextAlign};\nuse graphene_std::transform::{Footprint, ReferencePoint, Transform};\nuse graphene_std::vector::QRCodeErrorCorrectionLevel;\nuse graphene_std::vector::misc::BooleanOperation;\nuse graphene_std::vector::misc::{ArcType, CentroidType, ExtrudeJoiningAlgorithm, GridType, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns, SpiralType};\nuse graphene_std::vector::style::{Fill, FillChoice, FillType, GradientStops, GradientType, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};\n\npub(crate) fn string_properties(text: &str) -> Vec<LayoutGroup> {\n\tlet widget = TextLabel::new(text).widget_instance();\n\tvec![LayoutGroup::row(vec![widget])]\n}\n\nfn optionally_update_value<T>(value: impl Fn(&T) -> Option<TaggedValue> + 'static + Send + Sync, node_id: NodeId, input_index: usize) -> impl Fn(&T) -> Message + 'static + Send + Sync {\n\tmove |input_value: &T| match value(input_value) {\n\t\tSome(value) => NodeGraphMessage::SetInputValue { node_id, input_index, value }.into(),\n\t\tNone => Message::NoOp,\n\t}\n}\n\npub fn update_value<T>(value: impl Fn(&T) -> TaggedValue + 'static + Send + Sync, node_id: NodeId, input_index: usize) -> impl Fn(&T) -> Message + 'static + Send + Sync {\n\toptionally_update_value(move |v| Some(value(v)), node_id, input_index)\n}\n\npub fn commit_value<T>(_: &T) -> Message {\n\tDocumentMessage::AddTransaction.into()\n}\n\npub fn expose_widget(node_id: NodeId, index: usize, data_type: FrontendGraphDataType, exposed: bool) -> WidgetInstance {\n\tParameterExposeButton::new()\n\t\t.exposed(exposed)\n\t\t.data_type(data_type)\n\t\t.tooltip_description(if exposed {\n\t\t\t\"Stop exposing this parameter as a node input in the graph.\"\n\t\t} else {\n\t\t\t\"Expose this parameter as a node input in the graph.\"\n\t\t})\n\t\t.on_update(move |_parameter| Message::Batched {\n\t\t\tmessages: Box::new([NodeGraphMessage::ExposeInput {\n\t\t\t\tinput_connector: InputConnector::node(node_id, index),\n\t\t\t\tset_to_exposed: !exposed,\n\t\t\t\tstart_transaction: true,\n\t\t\t}\n\t\t\t.into()]),\n\t\t})\n\t\t.widget_instance()\n}\n\n// TODO: Remove this when we have proper entry row formatting that includes room for Assists.\npub fn add_blank_assist(widgets: &mut Vec<WidgetInstance>) {\n\twidgets.extend_from_slice(&[\n\t\t// Custom CSS specific to the Properties panel converts this Section separator into the width of an assist (24px).\n\t\tSeparator::new(SeparatorStyle::Section).widget_instance(),\n\t\t// This last one is the separator after the 24px assist.\n\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t]);\n}\n\npub fn jump_to_source_widget(input: &NodeInput, network_interface: &NodeNetworkInterface, selection_network_path: &[NodeId]) -> WidgetInstance {\n\tmatch input {\n\t\tNodeInput::Node { node_id: source_id, .. } => {\n\t\t\tlet source_id = *source_id;\n\t\t\tlet node_name = network_interface.implementation_name(&source_id, selection_network_path);\n\t\t\tTextButton::new(format!(\"From Graph ({})\", node_name))\n\t\t\t\t.tooltip_description(\"Click to select the node producing this parameter's data.\")\n\t\t\t\t.on_update(move |_| NodeGraphMessage::SelectedNodesSet { nodes: vec![source_id] }.into())\n\t\t\t\t.widget_instance()\n\t\t}\n\t\t_ => TextLabel::new(\"From Graph (Disconnected)\")\n\t\t\t.tooltip_description(\n\t\t\t\t\"\n\t\t\t\tThis parameter is exposed as an input in the node graph, but not currently receiving data from any node.\\n\\\n\t\t\t\t\\n\\\n\t\t\t\tIn the graph, drag a wire out from a compatible output connector of another node, and feed it into the input connector of this exposed node parameter. Alternatively, un-expose this parameter by clicking the triangle directly to the left of here.\n\t\t\t\t\"\n\t\t\t\t.trim(),\n\t\t\t)\n\t\t\t.widget_instance(),\n\t}\n}\n\npub fn start_widgets(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetInstance> {\n\tlet ParameterWidgetsInfo {\n\t\tdocument_node,\n\t\tnode_id,\n\t\tindex,\n\t\tname,\n\t\tdescription,\n\t\tinput_type,\n\t\tblank_assist,\n\t\texposable,\n\t\tnetwork_interface,\n\t\tselection_network_path,\n\t\t..\n\t} = parameter_widgets_info;\n\n\tlet Some(document_node) = document_node else {\n\t\tlog::warn!(\"A widget failed to be built because its document node is invalid.\");\n\t\treturn vec![];\n\t};\n\n\tlet Some(input) = document_node.inputs.get(index) else {\n\t\tlog::warn!(\"A widget failed to be built because its node's input index is invalid.\");\n\t\treturn vec![];\n\t};\n\tlet mut widgets = Vec::with_capacity(6);\n\tif exposable {\n\t\twidgets.push(expose_widget(node_id, index, input_type, input.is_exposed()));\n\t}\n\twidgets.push(TextLabel::new(name).tooltip_description(description).widget_instance());\n\n\tlet mut blank_assist = blank_assist;\n\tif input.is_exposed() {\n\t\tif blank_assist {\n\t\t\tadd_blank_assist(&mut widgets);\n\t\t\tblank_assist = false;\n\t\t}\n\t\twidgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\t\twidgets.push(jump_to_source_widget(input, network_interface, selection_network_path));\n\t}\n\n\tif blank_assist {\n\t\tadd_blank_assist(&mut widgets);\n\t}\n\n\twidgets\n}\n\npub(crate) fn property_from_type(\n\tnode_id: NodeId,\n\tindex: usize,\n\tty: &Type,\n\tnumber_options: (Option<f64>, Option<f64>, Option<(f64, f64)>),\n\tunit: Option<&str>,\n\tdisplay_decimal_places: Option<u32>,\n\tstep: Option<f64>,\n\tcontext: &mut NodePropertiesContext,\n) -> Result<Vec<LayoutGroup>, Vec<LayoutGroup>> {\n\tlet (mut number_min, mut number_max, range) = number_options;\n\tlet mut number_input = NumberInput::default();\n\tif let Some((range_start, range_end)) = range {\n\t\tnumber_min = Some(range_start);\n\t\tnumber_max = Some(range_end);\n\t\tnumber_input = number_input.mode_range().min(range_start).max(range_end);\n\t}\n\tif let Some(unit) = unit {\n\t\tnumber_input = number_input.unit(unit);\n\t}\n\tif let Some(display_decimal_places) = display_decimal_places {\n\t\tnumber_input = number_input.display_decimal_places(display_decimal_places);\n\t}\n\tif let Some(step) = step {\n\t\tnumber_input = number_input.step(step);\n\t}\n\n\tlet min = |x: f64| number_min.unwrap_or(x);\n\tlet max = |x: f64| number_max.unwrap_or(x);\n\n\tlet default_info = ParameterWidgetsInfo::new(node_id, index, true, context);\n\n\tlet mut extra_widgets = vec![];\n\tlet widgets = match ty {\n\t\tType::Concrete(concrete_type) => {\n\t\t\tmatch concrete_type.alias.as_ref().map(|x| x.as_ref()) {\n\t\t\t\t// Aliased types (ambiguous values)\n\t\t\t\tSome(\"Percentage\") | Some(\"PercentageF32\") => number_widget(default_info, number_input.percentage().min(min(0.)).max(max(100.))).into(),\n\t\t\t\tSome(\"SignedPercentage\") | Some(\"SignedPercentageF32\") => number_widget(default_info, number_input.percentage().min(min(-100.)).max(max(100.))).into(),\n\t\t\t\tSome(\"Angle\") | Some(\"AngleF32\") => number_widget(default_info, number_input.mode_range().min(min(-180.)).max(max(180.)).unit(unit.unwrap_or(\"°\"))).into(),\n\t\t\t\tSome(\"Multiplier\") => number_widget(default_info, number_input.unit(unit.unwrap_or(\"x\"))).into(),\n\t\t\t\tSome(\"PixelLength\") => number_widget(default_info, number_input.min(min(0.)).unit(unit.unwrap_or(\" px\"))).into(),\n\t\t\t\tSome(\"Length\") => number_widget(default_info, number_input.min(min(0.))).into(),\n\t\t\t\tSome(\"Fraction\") => number_widget(default_info, number_input.mode_range().min(min(0.)).max(max(1.))).into(),\n\t\t\t\tSome(\"Progression\") => progression_widget(default_info, number_input.min(min(0.))).into(),\n\t\t\t\tSome(\"SignedInteger\") => number_widget(default_info, number_input.int()).into(),\n\t\t\t\tSome(\"IntegerCount\") => number_widget(default_info, number_input.int().min(min(1.))).into(),\n\t\t\t\tSome(\"SeedValue\") => number_widget(default_info, number_input.int().min(min(0.))).into(),\n\t\t\t\tSome(\"PixelSize\") => vec2_widget(default_info, \"X\", \"Y\", unit.unwrap_or(\" px\"), None, false),\n\t\t\t\tSome(\"TextArea\") => text_area_widget(default_info).into(),\n\n\t\t\t\t// For all other types, use TypeId-based matching\n\t\t\t\t_ => {\n\t\t\t\t\tuse std::any::TypeId;\n\t\t\t\t\tmatch concrete_type.id {\n\t\t\t\t\t\t// ===============\n\t\t\t\t\t\t// PRIMITIVE TYPES\n\t\t\t\t\t\t// ===============\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<f64>() || x == TypeId::of::<f32>() => number_widget(default_info, number_input.min(min(f64::NEG_INFINITY)).max(max(f64::INFINITY))).into(),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<u32>() => number_widget(default_info, number_input.int().min(min(0.)).max(max(f64::from(u32::MAX)))).into(),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<u64>() => number_widget(default_info, number_input.int().min(min(0.))).into(),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<bool>() => bool_widget(default_info, CheckboxInput::default()).into(),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<String>() => text_widget(default_info).into(),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<DVec2>() => vec2_widget(default_info, \"X\", \"Y\", \"\", None, false),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<DAffine2>() => transform_widget(default_info, &mut extra_widgets),\n\t\t\t\t\t\t// ==========================\n\t\t\t\t\t\t// PRIMITIVE COLLECTION TYPES\n\t\t\t\t\t\t// ==========================\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<Vec<f64>>() => array_of_number_widget(default_info, TextInput::default()).into(),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<Vec<DVec2>>() => array_of_vec2_widget(default_info, TextInput::default()).into(),\n\t\t\t\t\t\t// ===========\n\t\t\t\t\t\t// TABLE TYPES\n\t\t\t\t\t\t// ===========\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<Table<Color>>() => color_widget(default_info, ColorInput::default().allow_none(true)),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<Table<GradientStops>>() => color_widget(default_info, ColorInput::default().allow_none(false)),\n\t\t\t\t\t\t// ============\n\t\t\t\t\t\t// STRUCT TYPES\n\t\t\t\t\t\t// ============\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<Font>() => font_widget(default_info),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<Curve>() => curve_widget(default_info),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<Footprint>() => footprint_widget(default_info, &mut extra_widgets),\n\t\t\t\t\t\t// ===============================\n\t\t\t\t\t\t// MANUALLY IMPLEMENTED ENUM TYPES\n\t\t\t\t\t\t// ===============================\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<ReferencePoint>() => reference_point_widget(default_info, false).into(),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<BlendMode>() => blend_mode_widget(default_info),\n\t\t\t\t\t\t// =========================\n\t\t\t\t\t\t// AUTO-GENERATED ENUM TYPES\n\t\t\t\t\t\t// =========================\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<FillType>() => enum_choice::<FillType>().for_socket(default_info).property_row(),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<GradientType>() => enum_choice::<GradientType>().for_socket(default_info).property_row(),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<RealTimeMode>() => enum_choice::<RealTimeMode>().for_socket(default_info).property_row(),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<RedGreenBlue>() => enum_choice::<RedGreenBlue>().for_socket(default_info).property_row(),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<RedGreenBlueAlpha>() => enum_choice::<RedGreenBlueAlpha>().for_socket(default_info).property_row(),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<XY>() => enum_choice::<XY>().for_socket(default_info).property_row(),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<NoiseType>() => enum_choice::<NoiseType>().for_socket(default_info).property_row(),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<FractalType>() => enum_choice::<FractalType>().for_socket(default_info).disabled(false).property_row(),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<CellularDistanceFunction>() => enum_choice::<CellularDistanceFunction>().for_socket(default_info).disabled(false).property_row(),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<CellularReturnType>() => enum_choice::<CellularReturnType>().for_socket(default_info).disabled(false).property_row(),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<DomainWarpType>() => enum_choice::<DomainWarpType>().for_socket(default_info).disabled(false).property_row(),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<RelativeAbsolute>() => enum_choice::<RelativeAbsolute>().for_socket(default_info).disabled(false).property_row(),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<GridType>() => enum_choice::<GridType>().for_socket(default_info).property_row(),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<StrokeCap>() => enum_choice::<StrokeCap>().for_socket(default_info).property_row(),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<StrokeJoin>() => enum_choice::<StrokeJoin>().for_socket(default_info).property_row(),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<StrokeAlign>() => enum_choice::<StrokeAlign>().for_socket(default_info).property_row(),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<PaintOrder>() => enum_choice::<PaintOrder>().for_socket(default_info).property_row(),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<ArcType>() => enum_choice::<ArcType>().for_socket(default_info).property_row(),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<RowsOrColumns>() => enum_choice::<RowsOrColumns>().for_socket(default_info).property_row(),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<TextAlign>() => enum_choice::<TextAlign>().for_socket(default_info).property_row(),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<MergeByDistanceAlgorithm>() => enum_choice::<MergeByDistanceAlgorithm>().for_socket(default_info).property_row(),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<ExtrudeJoiningAlgorithm>() => enum_choice::<ExtrudeJoiningAlgorithm>().for_socket(default_info).property_row(),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<PointSpacingType>() => enum_choice::<PointSpacingType>().for_socket(default_info).property_row(),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<BooleanOperation>() => enum_choice::<BooleanOperation>().for_socket(default_info).property_row(),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<CentroidType>() => enum_choice::<CentroidType>().for_socket(default_info).property_row(),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<LuminanceCalculation>() => enum_choice::<LuminanceCalculation>().for_socket(default_info).property_row(),\n\t\t\t\t\t\tSome(x) if x == TypeId::of::<QRCodeErrorCorrectionLevel>() => enum_choice::<QRCodeErrorCorrectionLevel>().for_socket(default_info).property_row(),\n\t\t\t\t\t\t// =====\n\t\t\t\t\t\t// OTHER\n\t\t\t\t\t\t// =====\n\t\t\t\t\t\t_ => {\n\t\t\t\t\t\t\tlet is_exposed = default_info.is_exposed();\n\n\t\t\t\t\t\t\tlet mut widgets = start_widgets(default_info);\n\n\t\t\t\t\t\t\tif !is_exposed {\n\t\t\t\t\t\t\t\twidgets.extend_from_slice(&[\n\t\t\t\t\t\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\t\t\t\t\t\tTextLabel::new(\"-\")\n\t\t\t\t\t\t\t\t\t\t.tooltip_label(format!(\"Data Type: {concrete_type}\"))\n\t\t\t\t\t\t\t\t\t\t.tooltip_description(\"This data can only be supplied through the node graph because no widget exists for its type.\")\n\t\t\t\t\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t\t\t\t\t]);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn Err(vec![widgets.into()]);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tType::Generic(_) => vec![TextLabel::new(\"Generic Type (Not Supported)\").widget_instance()].into(),\n\t\tType::Fn(_, out) => return property_from_type(node_id, index, out, number_options, unit, display_decimal_places, step, context),\n\t\tType::Future(out) => return property_from_type(node_id, index, out, number_options, unit, display_decimal_places, step, context),\n\t};\n\n\textra_widgets.push(widgets);\n\n\tOk(extra_widgets)\n}\n\npub fn text_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetInstance> {\n\tlet ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;\n\n\tlet mut widgets = start_widgets(parameter_widgets_info);\n\n\tlet Some(document_node) = document_node else { return Vec::new() };\n\tlet Some(input) = document_node.inputs.get(index) else {\n\t\tlog::warn!(\"A widget failed to be built because its node's input index is invalid.\");\n\t\treturn vec![];\n\t};\n\tif let Some(TaggedValue::String(x)) = &input.as_non_exposed_value() {\n\t\twidgets.extend_from_slice(&[\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\tTextInput::new(x.clone())\n\t\t\t\t.on_update(update_value(|x: &TextInput| TaggedValue::String(x.value.clone()), node_id, index))\n\t\t\t\t.on_commit(commit_value)\n\t\t\t\t.widget_instance(),\n\t\t])\n\t}\n\twidgets\n}\n\npub fn text_area_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetInstance> {\n\tlet ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;\n\n\tlet mut widgets = start_widgets(parameter_widgets_info);\n\n\tlet Some(document_node) = document_node else { return Vec::new() };\n\tlet Some(input) = document_node.inputs.get(index) else {\n\t\tlog::warn!(\"A widget failed to be built because its node's input index is invalid.\");\n\t\treturn vec![];\n\t};\n\tif let Some(TaggedValue::String(x)) = &input.as_non_exposed_value() {\n\t\twidgets.extend_from_slice(&[\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\tTextAreaInput::new(x.clone())\n\t\t\t\t.on_update(update_value(|x: &TextAreaInput| TaggedValue::String(x.value.clone()), node_id, index))\n\t\t\t\t.on_commit(commit_value)\n\t\t\t\t.widget_instance(),\n\t\t])\n\t}\n\twidgets\n}\n\npub fn bool_widget(parameter_widgets_info: ParameterWidgetsInfo, checkbox_input: CheckboxInput) -> Vec<WidgetInstance> {\n\tlet ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;\n\n\tlet mut widgets = start_widgets(parameter_widgets_info);\n\n\tlet Some(document_node) = document_node else { return Vec::new() };\n\tlet Some(input) = document_node.inputs.get(index) else {\n\t\tlog::warn!(\"A widget failed to be built because its node's input index is invalid.\");\n\t\treturn vec![];\n\t};\n\tif let Some(&TaggedValue::Bool(x)) = input.as_non_exposed_value() {\n\t\twidgets.extend_from_slice(&[\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\tcheckbox_input\n\t\t\t\t.checked(x)\n\t\t\t\t.on_update(update_value(|x: &CheckboxInput| TaggedValue::Bool(x.checked), node_id, index))\n\t\t\t\t.on_commit(commit_value)\n\t\t\t\t.widget_instance(),\n\t\t])\n\t}\n\twidgets\n}\n\npub fn reference_point_widget(parameter_widgets_info: ParameterWidgetsInfo, disabled: bool) -> Vec<WidgetInstance> {\n\tlet ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;\n\n\tlet mut widgets = start_widgets(parameter_widgets_info);\n\n\tlet Some(document_node) = document_node else { return Vec::new() };\n\tlet Some(input) = document_node.inputs.get(index) else {\n\t\tlog::warn!(\"A widget failed to be built because its node's input index is invalid.\");\n\t\treturn vec![];\n\t};\n\tif let Some(&TaggedValue::ReferencePoint(reference_point)) = input.as_non_exposed_value() {\n\t\twidgets.extend_from_slice(&[\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\tCheckboxInput::new(reference_point != ReferencePoint::None)\n\t\t\t\t.on_update(update_value(\n\t\t\t\t\tmove |x: &CheckboxInput| TaggedValue::ReferencePoint(if x.checked { ReferencePoint::Center } else { ReferencePoint::None }),\n\t\t\t\t\tnode_id,\n\t\t\t\t\tindex,\n\t\t\t\t))\n\t\t\t\t.disabled(disabled)\n\t\t\t\t.widget_instance(),\n\t\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\t\tReferencePointInput::new(reference_point)\n\t\t\t\t.on_update(update_value(move |x: &ReferencePointInput| TaggedValue::ReferencePoint(x.value), node_id, index))\n\t\t\t\t.disabled(disabled)\n\t\t\t\t.widget_instance(),\n\t\t])\n\t}\n\twidgets\n}\n\npub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widgets: &mut Vec<LayoutGroup>) -> LayoutGroup {\n\tlet ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;\n\n\tlet mut location_widgets = start_widgets(parameter_widgets_info);\n\tlocation_widgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\n\tlet mut scale_widgets = vec![TextLabel::new(\"\").widget_instance()];\n\tadd_blank_assist(&mut scale_widgets);\n\tscale_widgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\n\tlet mut resolution_widgets = vec![TextLabel::new(\"\").widget_instance()];\n\tadd_blank_assist(&mut resolution_widgets);\n\tresolution_widgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\n\tlet Some(document_node) = document_node else { return LayoutGroup::default() };\n\tlet Some(input) = document_node.inputs.get(index) else {\n\t\tlog::warn!(\"A widget failed to be built because its node's input index is invalid.\");\n\t\treturn Vec::new().into();\n\t};\n\n\tif let Some(&TaggedValue::Footprint(footprint)) = input.as_non_exposed_value() {\n\t\tlet top_left = footprint.transform.transform_point2(DVec2::ZERO);\n\t\tlet bounds = footprint.scale();\n\t\tlet oversample = footprint.resolution.as_dvec2() / bounds;\n\n\t\tlocation_widgets.extend_from_slice(&[\n\t\t\tNumberInput::new(Some(top_left.x))\n\t\t\t\t.label(\"X\")\n\t\t\t\t.unit(\" px\")\n\t\t\t\t.on_update(update_value(\n\t\t\t\t\tmove |x: &NumberInput| {\n\t\t\t\t\t\tlet (offset, scale) = {\n\t\t\t\t\t\t\tlet diff = DVec2::new(top_left.x - x.value.unwrap_or_default(), 0.);\n\t\t\t\t\t\t\t(top_left - diff, bounds)\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tlet footprint = Footprint {\n\t\t\t\t\t\t\ttransform: DAffine2::from_scale_angle_translation(scale, 0., offset),\n\t\t\t\t\t\t\tresolution: (oversample * scale).as_uvec2(),\n\t\t\t\t\t\t\t..footprint\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tTaggedValue::Footprint(footprint)\n\t\t\t\t\t},\n\t\t\t\t\tnode_id,\n\t\t\t\t\tindex,\n\t\t\t\t))\n\t\t\t\t.on_commit(commit_value)\n\t\t\t\t.widget_instance(),\n\t\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\t\tNumberInput::new(Some(top_left.y))\n\t\t\t\t.label(\"Y\")\n\t\t\t\t.unit(\" px\")\n\t\t\t\t.on_update(update_value(\n\t\t\t\t\tmove |x: &NumberInput| {\n\t\t\t\t\t\tlet (offset, scale) = {\n\t\t\t\t\t\t\tlet diff = DVec2::new(0., top_left.y - x.value.unwrap_or_default());\n\t\t\t\t\t\t\t(top_left - diff, bounds)\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tlet footprint = Footprint {\n\t\t\t\t\t\t\ttransform: DAffine2::from_scale_angle_translation(scale, 0., offset),\n\t\t\t\t\t\t\tresolution: (oversample * scale).as_uvec2(),\n\t\t\t\t\t\t\t..footprint\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tTaggedValue::Footprint(footprint)\n\t\t\t\t\t},\n\t\t\t\t\tnode_id,\n\t\t\t\t\tindex,\n\t\t\t\t))\n\t\t\t\t.on_commit(commit_value)\n\t\t\t\t.widget_instance(),\n\t\t]);\n\n\t\tscale_widgets.extend_from_slice(&[\n\t\t\tNumberInput::new(Some(bounds.x))\n\t\t\t\t.label(\"W\")\n\t\t\t\t.unit(\" px\")\n\t\t\t\t.on_update(update_value(\n\t\t\t\t\tmove |x: &NumberInput| {\n\t\t\t\t\t\tlet (offset, scale) = (top_left, DVec2::new(x.value.unwrap_or_default(), bounds.y));\n\n\t\t\t\t\t\tlet footprint = Footprint {\n\t\t\t\t\t\t\ttransform: DAffine2::from_scale_angle_translation(scale, 0., offset),\n\t\t\t\t\t\t\tresolution: (oversample * scale).as_uvec2(),\n\t\t\t\t\t\t\t..footprint\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tTaggedValue::Footprint(footprint)\n\t\t\t\t\t},\n\t\t\t\t\tnode_id,\n\t\t\t\t\tindex,\n\t\t\t\t))\n\t\t\t\t.on_commit(commit_value)\n\t\t\t\t.widget_instance(),\n\t\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\t\tNumberInput::new(Some(bounds.y))\n\t\t\t\t.label(\"H\")\n\t\t\t\t.unit(\" px\")\n\t\t\t\t.on_update(update_value(\n\t\t\t\t\tmove |x: &NumberInput| {\n\t\t\t\t\t\tlet (offset, scale) = (top_left, DVec2::new(bounds.x, x.value.unwrap_or_default()));\n\n\t\t\t\t\t\tlet footprint = Footprint {\n\t\t\t\t\t\t\ttransform: DAffine2::from_scale_angle_translation(scale, 0., offset),\n\t\t\t\t\t\t\tresolution: (oversample * scale).as_uvec2(),\n\t\t\t\t\t\t\t..footprint\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tTaggedValue::Footprint(footprint)\n\t\t\t\t\t},\n\t\t\t\t\tnode_id,\n\t\t\t\t\tindex,\n\t\t\t\t))\n\t\t\t\t.on_commit(commit_value)\n\t\t\t\t.widget_instance(),\n\t\t]);\n\n\t\tresolution_widgets.push(\n\t\t\tNumberInput::new(Some((footprint.resolution.as_dvec2() / bounds).x * 100.))\n\t\t\t\t.label(\"Resolution\")\n\t\t\t\t.mode_range()\n\t\t\t\t.min(0.)\n\t\t\t\t.range_min(Some(1.))\n\t\t\t\t.range_max(Some(100.))\n\t\t\t\t.unit(\"%\")\n\t\t\t\t.on_update(update_value(\n\t\t\t\t\tmove |x: &NumberInput| {\n\t\t\t\t\t\tlet resolution = (bounds * x.value.unwrap_or(100.) / 100.).as_uvec2().max((1, 1).into()).min((4000, 4000).into());\n\n\t\t\t\t\t\tlet footprint = Footprint { resolution, ..footprint };\n\t\t\t\t\t\tTaggedValue::Footprint(footprint)\n\t\t\t\t\t},\n\t\t\t\t\tnode_id,\n\t\t\t\t\tindex,\n\t\t\t\t))\n\t\t\t\t.on_commit(commit_value)\n\t\t\t\t.widget_instance(),\n\t\t);\n\t}\n\n\tlet widgets = [LayoutGroup::row(location_widgets), LayoutGroup::row(scale_widgets), LayoutGroup::row(resolution_widgets)];\n\tlet (last, rest) = widgets.split_last().expect(\"Footprint widget should return multiple rows\");\n\t*extra_widgets = rest.to_vec();\n\tlast.clone()\n}\n\npub fn transform_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widgets: &mut Vec<LayoutGroup>) -> LayoutGroup {\n\tlet ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;\n\n\tlet mut location_widgets = start_widgets(parameter_widgets_info);\n\tlocation_widgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\n\tlet mut rotation_widgets = vec![TextLabel::new(\"\").widget_instance()];\n\tadd_blank_assist(&mut rotation_widgets);\n\trotation_widgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\n\tlet mut scale_widgets = vec![TextLabel::new(\"\").widget_instance()];\n\tadd_blank_assist(&mut scale_widgets);\n\tscale_widgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\n\tlet Some(document_node) = document_node else { return LayoutGroup::default() };\n\tlet Some(input) = document_node.inputs.get(index) else {\n\t\tlog::warn!(\"A widget failed to be built because its node's input index is invalid.\");\n\t\treturn Vec::new().into();\n\t};\n\n\tlet widgets = if let Some(&TaggedValue::DAffine2(transform)) = input.as_non_exposed_value() {\n\t\tlet translation = transform.translation;\n\t\tlet rotation = transform.decompose_rotation();\n\t\tlet scale = transform.decompose_scale();\n\n\t\tlocation_widgets.extend_from_slice(&[\n\t\t\tNumberInput::new(Some(translation.x))\n\t\t\t\t.label(\"X\")\n\t\t\t\t.unit(\" px\")\n\t\t\t\t.on_update(update_value(\n\t\t\t\t\tmove |x: &NumberInput| {\n\t\t\t\t\t\tlet mut transform = transform;\n\t\t\t\t\t\ttransform.translation.x = x.value.unwrap_or(transform.translation.x);\n\t\t\t\t\t\tTaggedValue::DAffine2(transform)\n\t\t\t\t\t},\n\t\t\t\t\tnode_id,\n\t\t\t\t\tindex,\n\t\t\t\t))\n\t\t\t\t.on_commit(commit_value)\n\t\t\t\t.widget_instance(),\n\t\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\t\tNumberInput::new(Some(translation.y))\n\t\t\t\t.label(\"Y\")\n\t\t\t\t.unit(\" px\")\n\t\t\t\t.on_update(update_value(\n\t\t\t\t\tmove |y: &NumberInput| {\n\t\t\t\t\t\tlet mut transform = transform;\n\t\t\t\t\t\ttransform.translation.y = y.value.unwrap_or(transform.translation.y);\n\t\t\t\t\t\tTaggedValue::DAffine2(transform)\n\t\t\t\t\t},\n\t\t\t\t\tnode_id,\n\t\t\t\t\tindex,\n\t\t\t\t))\n\t\t\t\t.on_commit(commit_value)\n\t\t\t\t.widget_instance(),\n\t\t]);\n\n\t\trotation_widgets.extend_from_slice(&[NumberInput::new(Some(rotation.to_degrees()))\n\t\t\t.unit(\"°\")\n\t\t\t.mode(NumberInputMode::Range)\n\t\t\t.range_min(Some(-180.))\n\t\t\t.range_max(Some(180.))\n\t\t\t.on_update(update_value(\n\t\t\t\tmove |r: &NumberInput| {\n\t\t\t\t\tlet transform = DAffine2::from_scale_angle_translation(scale, r.value.map(|r| r.to_radians()).unwrap_or(rotation), translation);\n\t\t\t\t\tTaggedValue::DAffine2(transform)\n\t\t\t\t},\n\t\t\t\tnode_id,\n\t\t\t\tindex,\n\t\t\t))\n\t\t\t.on_commit(commit_value)\n\t\t\t.widget_instance()]);\n\n\t\tscale_widgets.extend_from_slice(&[\n\t\t\tNumberInput::new(Some(scale.x))\n\t\t\t\t.label(\"W\")\n\t\t\t\t.unit(\"x\")\n\t\t\t\t.on_update(update_value(\n\t\t\t\t\tmove |w: &NumberInput| {\n\t\t\t\t\t\tlet transform = DAffine2::from_scale_angle_translation(DVec2::new(w.value.unwrap_or(scale.x), scale.y), rotation, translation);\n\t\t\t\t\t\tTaggedValue::DAffine2(transform)\n\t\t\t\t\t},\n\t\t\t\t\tnode_id,\n\t\t\t\t\tindex,\n\t\t\t\t))\n\t\t\t\t.on_commit(commit_value)\n\t\t\t\t.widget_instance(),\n\t\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\t\tNumberInput::new(Some(scale.y))\n\t\t\t\t.label(\"H\")\n\t\t\t\t.unit(\"x\")\n\t\t\t\t.on_update(update_value(\n\t\t\t\t\tmove |h: &NumberInput| {\n\t\t\t\t\t\tlet transform = DAffine2::from_scale_angle_translation(DVec2::new(scale.x, h.value.unwrap_or(scale.y)), rotation, translation);\n\t\t\t\t\t\tTaggedValue::DAffine2(transform)\n\t\t\t\t\t},\n\t\t\t\t\tnode_id,\n\t\t\t\t\tindex,\n\t\t\t\t))\n\t\t\t\t.on_commit(commit_value)\n\t\t\t\t.widget_instance(),\n\t\t]);\n\n\t\tvec![LayoutGroup::row(location_widgets), LayoutGroup::row(rotation_widgets), LayoutGroup::row(scale_widgets)]\n\t} else {\n\t\tvec![LayoutGroup::row(location_widgets)]\n\t};\n\n\tif let Some((last, rest)) = widgets.split_last() {\n\t\t*extra_widgets = rest.to_vec();\n\t\tlast.clone()\n\t} else {\n\t\tLayoutGroup::default()\n\t}\n}\n\npub fn vec2_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str, y: &str, unit: &str, min: Option<f64>, is_integer: bool) -> LayoutGroup {\n\tlet ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;\n\n\tlet mut widgets = start_widgets(parameter_widgets_info);\n\n\tlet Some(document_node) = document_node else { return LayoutGroup::default() };\n\tlet Some(input) = document_node.inputs.get(index) else {\n\t\tlog::warn!(\"A widget failed to be built because its node's input index is invalid.\");\n\t\treturn LayoutGroup::row(vec![]);\n\t};\n\tmatch input.as_non_exposed_value() {\n\t\tSome(&TaggedValue::DVec2(dvec2)) => {\n\t\t\twidgets.extend_from_slice(&[\n\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\tNumberInput::new(Some(dvec2.x))\n\t\t\t\t\t.label(x)\n\t\t\t\t\t.unit(unit)\n\t\t\t\t\t.min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64)))\n\t\t\t\t\t.max((1_u64 << f64::MANTISSA_DIGITS) as f64)\n\t\t\t\t\t.is_integer(is_integer)\n\t\t\t\t\t.on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(input.value.unwrap(), dvec2.y)), node_id, index))\n\t\t\t\t\t.on_commit(commit_value)\n\t\t\t\t\t.widget_instance(),\n\t\t\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\t\t\tNumberInput::new(Some(dvec2.y))\n\t\t\t\t\t.label(y)\n\t\t\t\t\t.unit(unit)\n\t\t\t\t\t.min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64)))\n\t\t\t\t\t.max((1_u64 << f64::MANTISSA_DIGITS) as f64)\n\t\t\t\t\t.is_integer(is_integer)\n\t\t\t\t\t.on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(dvec2.x, input.value.unwrap())), node_id, index))\n\t\t\t\t\t.on_commit(commit_value)\n\t\t\t\t\t.widget_instance(),\n\t\t\t]);\n\t\t}\n\t\tSome(&TaggedValue::F64(value)) => {\n\t\t\twidgets.extend_from_slice(&[\n\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\tNumberInput::new(Some(value))\n\t\t\t\t\t.label(x)\n\t\t\t\t\t.unit(unit)\n\t\t\t\t\t.min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64)))\n\t\t\t\t\t.max((1_u64 << f64::MANTISSA_DIGITS) as f64)\n\t\t\t\t\t.is_integer(is_integer)\n\t\t\t\t\t.on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(input.value.unwrap(), value)), node_id, index))\n\t\t\t\t\t.on_commit(commit_value)\n\t\t\t\t\t.widget_instance(),\n\t\t\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\t\t\tNumberInput::new(Some(value))\n\t\t\t\t\t.label(y)\n\t\t\t\t\t.unit(unit)\n\t\t\t\t\t.min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64)))\n\t\t\t\t\t.max((1_u64 << f64::MANTISSA_DIGITS) as f64)\n\t\t\t\t\t.is_integer(is_integer)\n\t\t\t\t\t.on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(value, input.value.unwrap())), node_id, index))\n\t\t\t\t\t.on_commit(commit_value)\n\t\t\t\t\t.widget_instance(),\n\t\t\t]);\n\t\t}\n\t\t_ => {}\n\t}\n\n\tLayoutGroup::row(widgets)\n}\n\npub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text_input: TextInput) -> Vec<WidgetInstance> {\n\tlet ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;\n\n\tlet mut widgets = start_widgets(parameter_widgets_info);\n\n\tlet from_string = |string: &str| {\n\t\tstring\n\t\t\t.split(&[',', ' '])\n\t\t\t.filter(|x| !x.is_empty())\n\t\t\t.map(str::parse::<f64>)\n\t\t\t.collect::<Result<Vec<_>, _>>()\n\t\t\t.ok()\n\t\t\t.map(TaggedValue::VecF64)\n\t};\n\n\tlet Some(document_node) = document_node else { return Vec::new() };\n\tlet Some(input) = document_node.inputs.get(index) else {\n\t\tlog::warn!(\"A widget failed to be built because its node's input index is invalid.\");\n\t\treturn vec![];\n\t};\n\tif let Some(TaggedValue::VecF64(x)) = &input.as_non_exposed_value() {\n\t\twidgets.extend_from_slice(&[\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\ttext_input\n\t\t\t\t.value(x.iter().map(|v| v.to_string()).collect::<Vec<_>>().join(\", \"))\n\t\t\t\t.on_update(optionally_update_value(move |x: &TextInput| from_string(&x.value), node_id, index))\n\t\t\t\t.widget_instance(),\n\t\t])\n\t}\n\twidgets\n}\n\npub fn array_of_vec2_widget(parameter_widgets_info: ParameterWidgetsInfo, text_props: TextInput) -> Vec<WidgetInstance> {\n\tlet ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;\n\n\tlet mut widgets = start_widgets(parameter_widgets_info);\n\n\tlet from_string = |string: &str| {\n\t\tstring\n\t\t\t.split(|c: char| !c.is_alphanumeric() && !matches!(c, '.' | '+' | '-'))\n\t\t\t.filter(|x| !x.is_empty())\n\t\t\t.map(|x| x.parse::<f64>().ok())\n\t\t\t.collect::<Option<Vec<_>>>()\n\t\t\t.map(|numbers| numbers.chunks_exact(2).map(|values| DVec2::new(values[0], values[1])).collect())\n\t\t\t.map(TaggedValue::VecDVec2)\n\t};\n\n\tlet Some(document_node) = document_node else { return Vec::new() };\n\tlet Some(input) = document_node.inputs.get(index) else {\n\t\tlog::warn!(\"A widget failed to be built because its node's input index is invalid.\");\n\t\treturn vec![];\n\t};\n\tif let Some(TaggedValue::VecDVec2(x)) = &input.as_non_exposed_value() {\n\t\twidgets.extend_from_slice(&[\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\ttext_props\n\t\t\t\t.value(x.iter().map(|v| format!(\"({}, {})\", v.x, v.y)).collect::<Vec<_>>().join(\", \"))\n\t\t\t\t.on_update(optionally_update_value(move |x: &TextInput| from_string(&x.value), node_id, index))\n\t\t\t\t.widget_instance(),\n\t\t])\n\t}\n\twidgets\n}\n\npub fn font_inputs(parameter_widgets_info: ParameterWidgetsInfo) -> (Vec<WidgetInstance>, Option<Vec<WidgetInstance>>) {\n\tlet ParameterWidgetsInfo {\n\t\tpersistent_data,\n\t\tdocument_node,\n\t\tnode_id,\n\t\tindex,\n\t\t..\n\t} = parameter_widgets_info;\n\n\tlet mut first_widgets = start_widgets(parameter_widgets_info);\n\tlet mut second_widgets = None;\n\n\tlet Some(document_node) = document_node else { return (Vec::new(), None) };\n\tlet Some(input) = document_node.inputs.get(index) else {\n\t\tlog::warn!(\"A widget failed to be built because its node's input index is invalid.\");\n\t\treturn (vec![], None);\n\t};\n\n\tif let Some(TaggedValue::Font(font)) = &input.as_non_exposed_value() {\n\t\tfirst_widgets.extend_from_slice(&[\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\tDropdownInput::new(vec![\n\t\t\t\tpersistent_data\n\t\t\t\t\t.font_catalog\n\t\t\t\t\t.0\n\t\t\t\t\t.iter()\n\t\t\t\t\t.map(|family| {\n\t\t\t\t\t\tMenuListEntry::new(family.name.clone())\n\t\t\t\t\t\t\t.label(family.name.clone())\n\t\t\t\t\t\t\t.font(family.closest_style(400, false).preview_url(&family.name))\n\t\t\t\t\t\t\t.on_update({\n\t\t\t\t\t\t\t\t// Construct the new font using the new family and the initial or previous style, although this style might not exist in the catalog\n\t\t\t\t\t\t\t\tlet mut new_font = Font::new(family.name.clone(), font.font_style_to_restore.clone().unwrap_or_else(|| font.font_style.clone()));\n\t\t\t\t\t\t\t\tnew_font.font_style_to_restore = font.font_style_to_restore.clone();\n\n\t\t\t\t\t\t\t\t// If not already, store the initial style so it can be restored if the user switches to another family\n\t\t\t\t\t\t\t\tif new_font.font_style_to_restore.is_none() {\n\t\t\t\t\t\t\t\t\tnew_font.font_style_to_restore = Some(new_font.font_style.clone());\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Use the closest style available in the family for the new font to ensure the style exists\n\t\t\t\t\t\t\t\tlet FontCatalogStyle { weight, italic, .. } = FontCatalogStyle::from_named_style(&new_font.font_style, \"\");\n\t\t\t\t\t\t\t\tnew_font.font_style = family.closest_style(weight, italic).to_named_style();\n\n\t\t\t\t\t\t\t\tmove |_| {\n\t\t\t\t\t\t\t\t\tlet new_font = new_font.clone();\n\n\t\t\t\t\t\t\t\t\tMessage::Batched {\n\t\t\t\t\t\t\t\t\t\tmessages: Box::new([\n\t\t\t\t\t\t\t\t\t\t\tPortfolioMessage::LoadFontData { font: new_font.clone() }.into(),\n\t\t\t\t\t\t\t\t\t\t\tupdate_value(move |_| TaggedValue::Font(new_font.clone()), node_id, index)(&()),\n\t\t\t\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.on_commit({\n\t\t\t\t\t\t\t\t// Use the new value from the user selection\n\t\t\t\t\t\t\t\tlet font_family = family.name.clone();\n\n\t\t\t\t\t\t\t\t// Use the previous style selection and extract its weight and italic properties, then find the closest style in the new family\n\t\t\t\t\t\t\t\tlet FontCatalogStyle { weight, italic, .. } = FontCatalogStyle::from_named_style(&font.font_style, \"\");\n\t\t\t\t\t\t\t\tlet font_style = family.closest_style(weight, italic).to_named_style();\n\n\t\t\t\t\t\t\t\tmove |_| {\n\t\t\t\t\t\t\t\t\tlet new_font = Font::new(font_family.clone(), font_style.clone());\n\n\t\t\t\t\t\t\t\t\tDeferMessage::AfterGraphRun {\n\t\t\t\t\t\t\t\t\t\tmessages: vec![update_value(move |_| TaggedValue::Font(new_font.clone()), node_id, index)(&()), commit_value(&())],\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t.into()\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t\t\t.collect::<Vec<_>>(),\n\t\t\t])\n\t\t\t.selected_index(persistent_data.font_catalog.0.iter().position(|family| family.name == font.font_family).map(|i| i as u32))\n\t\t\t.virtual_scrolling(true)\n\t\t\t.widget_instance(),\n\t\t]);\n\n\t\tlet mut second_row = vec![TextLabel::new(\"\").widget_instance()];\n\t\tadd_blank_assist(&mut second_row);\n\t\tsecond_row.extend_from_slice(&[\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\tDropdownInput::new({\n\t\t\t\tpersistent_data\n\t\t\t\t\t.font_catalog\n\t\t\t\t\t.0\n\t\t\t\t\t.iter()\n\t\t\t\t\t.find(|family| family.name == font.font_family)\n\t\t\t\t\t.map(|family| {\n\t\t\t\t\t\tlet build_entry = |style: &FontCatalogStyle| {\n\t\t\t\t\t\t\tlet font_style = style.to_named_style();\n\t\t\t\t\t\t\tMenuListEntry::new(font_style.clone())\n\t\t\t\t\t\t\t\t.label(font_style.clone())\n\t\t\t\t\t\t\t\t.on_update({\n\t\t\t\t\t\t\t\t\tlet font_family = font.font_family.clone();\n\t\t\t\t\t\t\t\t\tlet font_style = font_style.clone();\n\n\t\t\t\t\t\t\t\t\tmove |_| {\n\t\t\t\t\t\t\t\t\t\t// Keep the existing family\n\t\t\t\t\t\t\t\t\t\tlet new_font = Font::new(font_family.clone(), font_style.clone());\n\n\t\t\t\t\t\t\t\t\t\tMessage::Batched {\n\t\t\t\t\t\t\t\t\t\t\tmessages: Box::new([\n\t\t\t\t\t\t\t\t\t\t\t\tPortfolioMessage::LoadFontData { font: new_font.clone() }.into(),\n\t\t\t\t\t\t\t\t\t\t\t\tupdate_value(move |_| TaggedValue::Font(new_font.clone()), node_id, index)(&()),\n\t\t\t\t\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t.on_commit(commit_value)\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tvec![\n\t\t\t\t\t\t\tfamily.styles.iter().filter(|style| !style.italic).map(build_entry).collect::<Vec<_>>(),\n\t\t\t\t\t\t\tfamily.styles.iter().filter(|style| style.italic).map(build_entry).collect::<Vec<_>>(),\n\t\t\t\t\t\t]\n\t\t\t\t\t})\n\t\t\t\t\t.filter(|styles| !styles.is_empty())\n\t\t\t\t\t.unwrap_or_default()\n\t\t\t})\n\t\t\t.selected_index(\n\t\t\t\tpersistent_data\n\t\t\t\t\t.font_catalog\n\t\t\t\t\t.0\n\t\t\t\t\t.iter()\n\t\t\t\t\t.find(|family| family.name == font.font_family)\n\t\t\t\t\t.and_then(|family| {\n\t\t\t\t\t\tlet not_italic = family.styles.iter().filter(|style| !style.italic);\n\t\t\t\t\t\tlet italic = family.styles.iter().filter(|style| style.italic);\n\t\t\t\t\t\tnot_italic.chain(italic).position(|style| style.to_named_style() == font.font_style)\n\t\t\t\t\t})\n\t\t\t\t\t.map(|i| i as u32),\n\t\t\t)\n\t\t\t.widget_instance(),\n\t\t]);\n\t\tsecond_widgets = Some(second_row);\n\t}\n\t(first_widgets, second_widgets)\n}\n\n// Two number fields beside one another, the first for the fractional part (decimals, range mode) and the second for the whole part (integers, increment mode)\npub fn progression_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props: NumberInput) -> Vec<WidgetInstance> {\n\tlet ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;\n\n\tlet mut widgets = start_widgets(parameter_widgets_info);\n\n\tlet Some(document_node) = document_node else { return Vec::new() };\n\tlet Some(input) = document_node.inputs.get(index) else {\n\t\tlog::warn!(\"A widget failed to be built because its node's input index is invalid.\");\n\t\treturn vec![];\n\t};\n\tif let Some(&TaggedValue::F64(x)) = input.as_non_exposed_value() {\n\t\tlet whole_part = x.trunc();\n\t\tlet fractional_part = x.fract();\n\n\t\twidgets.extend_from_slice(&[\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\tnumber_props\n\t\t\t\t.clone()\n\t\t\t\t.label(\"Progress\")\n\t\t\t\t.mode_range()\n\t\t\t\t.min(0.)\n\t\t\t\t.max(0.99999)\n\t\t\t\t.value(Some(fractional_part))\n\t\t\t\t.on_update(update_value(move |input: &NumberInput| TaggedValue::F64(whole_part + input.value.unwrap()), node_id, index))\n\t\t\t\t.on_commit(commit_value)\n\t\t\t\t.widget_instance(),\n\t\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\t\tTextLabel::new(\"+\").widget_instance(),\n\t\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\t\tnumber_props\n\t\t\t\t.label(\"Element #\")\n\t\t\t\t.mode_increment()\n\t\t\t\t.min(0.)\n\t\t\t\t.is_integer(true)\n\t\t\t\t.value(Some(whole_part))\n\t\t\t\t.on_update(update_value(move |input: &NumberInput| TaggedValue::F64(input.value.unwrap() + fractional_part), node_id, index))\n\t\t\t\t.on_commit(commit_value)\n\t\t\t\t.widget_instance(),\n\t\t])\n\t}\n\twidgets\n}\n\n/// `parameter_widgets_info` is for the f64 parameter. `bool_input_index` is the input index of the bool parameter for the checkbox.\npub fn optional_f64_widget(parameter_widgets_info: ParameterWidgetsInfo, bool_input_index: usize, number_props: NumberInput) -> Vec<WidgetInstance> {\n\tlet ParameterWidgetsInfo {\n\t\tdocument_node,\n\t\tnode_id,\n\t\tindex: number_input_index,\n\t\t..\n\t} = parameter_widgets_info;\n\n\tlet mut widgets = start_widgets(parameter_widgets_info);\n\n\tlet Some(document_node) = document_node else { return Vec::new() };\n\tlet Some(number_input) = document_node.inputs.get(number_input_index) else {\n\t\tlog::warn!(\"A widget failed to be built because its node's input index is invalid.\");\n\t\treturn vec![];\n\t};\n\tlet Some(bool_input) = document_node.inputs.get(bool_input_index) else {\n\t\tlog::warn!(\"A widget failed to be built because its node's input index is invalid.\");\n\t\treturn vec![];\n\t};\n\tif let (Some(&TaggedValue::Bool(enabled)), Some(&TaggedValue::F64(number))) = (bool_input.as_non_exposed_value(), number_input.as_non_exposed_value()) {\n\t\twidgets.extend_from_slice(&[\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\t\t// The checkbox toggles if the value is Some or None\n\t\t\tCheckboxInput::new(enabled)\n\t\t\t\t.on_update(update_value(|x: &CheckboxInput| TaggedValue::Bool(x.checked), node_id, bool_input_index))\n\t\t\t\t.on_commit(commit_value)\n\t\t\t\t.widget_instance(),\n\t\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\tnumber_props\n\t\t\t\t.value(Some(number))\n\t\t\t\t.on_update(update_value(move |x: &NumberInput| TaggedValue::F64(x.value.unwrap_or_default()), node_id, number_input_index))\n\t\t\t\t.disabled(!enabled)\n\t\t\t\t.on_commit(commit_value)\n\t\t\t\t.widget_instance(),\n\t\t]);\n\t}\n\n\twidgets\n}\n\npub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props: NumberInput) -> Vec<WidgetInstance> {\n\tlet ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;\n\n\tlet mut widgets = start_widgets(parameter_widgets_info);\n\n\tlet Some(document_node) = document_node else { return Vec::new() };\n\tlet Some(input) = document_node.inputs.get(index) else {\n\t\tlog::warn!(\"A widget failed to be built because its node's input index is invalid.\");\n\t\treturn vec![];\n\t};\n\tmatch input.as_non_exposed_value() {\n\t\tSome(&TaggedValue::F64(x)) => widgets.extend_from_slice(&[\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\tnumber_props\n\t\t\t\t.value(Some(x))\n\t\t\t\t.on_update(update_value(move |x: &NumberInput| TaggedValue::F64(x.value.unwrap()), node_id, index))\n\t\t\t\t.on_commit(commit_value)\n\t\t\t\t.widget_instance(),\n\t\t]),\n\t\tSome(&TaggedValue::F32(x)) => widgets.extend_from_slice(&[\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\tnumber_props\n\t\t\t\t.value(Some(x as f64))\n\t\t\t\t.on_update(update_value(move |x: &NumberInput| TaggedValue::F32(x.value.unwrap() as f32), node_id, index))\n\t\t\t\t.on_commit(commit_value)\n\t\t\t\t.widget_instance(),\n\t\t]),\n\t\tSome(&TaggedValue::U32(x)) => widgets.extend_from_slice(&[\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\tnumber_props\n\t\t\t\t.value(Some(x as f64))\n\t\t\t\t.on_update(update_value(move |x: &NumberInput| TaggedValue::U32((x.value.unwrap()) as u32), node_id, index))\n\t\t\t\t.on_commit(commit_value)\n\t\t\t\t.widget_instance(),\n\t\t]),\n\t\tSome(&TaggedValue::U64(x)) => widgets.extend_from_slice(&[\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\tnumber_props\n\t\t\t\t.value(Some(x as f64))\n\t\t\t\t.on_update(update_value(move |x: &NumberInput| TaggedValue::U64((x.value.unwrap()) as u64), node_id, index))\n\t\t\t\t.on_commit(commit_value)\n\t\t\t\t.widget_instance(),\n\t\t]),\n\t\tSome(&TaggedValue::DVec2(dvec2)) => widgets.extend_from_slice(&[\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\tnumber_props\n\t\t\t// We use an arbitrary `y` instead of an arbitrary `x` here because the \"Grid\" node's \"Spacing\" value's height should be used from rectangular mode when transferred to \"Y Spacing\" in isometric mode\n\t\t\t\t.value(Some(dvec2.y))\n\t\t\t\t.on_update(update_value(move |x: &NumberInput| TaggedValue::F64(x.value.unwrap()), node_id, index))\n\t\t\t\t.on_commit(commit_value)\n\t\t\t\t.widget_instance(),\n\t\t]),\n\t\tSome(&TaggedValue::FVec2(vec2)) => widgets.extend_from_slice(&[\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\tnumber_props\n\t\t\t\t// We use an arbitrary `y` instead of an arbitrary `x` here because the \"Grid\" node's \"Spacing\" value's height should be used from rectangular mode when transferred to \"Y Spacing\" in isometric mode\n\t\t\t\t.value(Some(vec2.y as f64))\n\t\t\t\t.on_update(update_value(move |x: &NumberInput| TaggedValue::F32(x.value.unwrap() as f32), node_id, index))\n\t\t\t\t.on_commit(commit_value)\n\t\t\t\t.widget_instance(),\n\t\t]),\n\t\t_ => {}\n\t}\n\n\twidgets\n}\n\n// TODO: Auto-generate this enum dropdown menu widget\npub fn blend_mode_widget(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup {\n\tlet ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;\n\n\tlet mut widgets = start_widgets(parameter_widgets_info);\n\tlet Some(document_node) = document_node else { return LayoutGroup::default() };\n\tlet Some(input) = document_node.inputs.get(index) else {\n\t\tlog::warn!(\"A widget failed to be built because its node's input index is invalid.\");\n\t\treturn LayoutGroup::row(vec![]);\n\t};\n\tif let Some(&TaggedValue::BlendMode(blend_mode)) = input.as_non_exposed_value() {\n\t\tlet entries = BlendMode::list_svg_subset()\n\t\t\t.iter()\n\t\t\t.map(|category| {\n\t\t\t\tcategory\n\t\t\t\t\t.iter()\n\t\t\t\t\t.map(|blend_mode| {\n\t\t\t\t\t\tMenuListEntry::new(format!(\"{blend_mode:?}\"))\n\t\t\t\t\t\t\t.label(blend_mode.to_string())\n\t\t\t\t\t\t\t.on_update(update_value(move |_| TaggedValue::BlendMode(*blend_mode), node_id, index))\n\t\t\t\t\t\t\t.on_commit(commit_value)\n\t\t\t\t\t})\n\t\t\t\t\t.collect()\n\t\t\t})\n\t\t\t.collect();\n\n\t\twidgets.extend_from_slice(&[\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\tDropdownInput::new(entries)\n\t\t\t\t.selected_index(blend_mode.index_in_list_svg_subset().map(|index| index as u32))\n\t\t\t\t.widget_instance(),\n\t\t]);\n\t}\n\tLayoutGroup::row(widgets).with_tooltip_description(\"Formula used for blending.\")\n}\n\npub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button: ColorInput) -> LayoutGroup {\n\tlet ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;\n\n\tlet mut widgets = start_widgets(parameter_widgets_info);\n\n\tlet Some(document_node) = document_node else { return LayoutGroup::default() };\n\t// Return early with just the label if the input is exposed to the graph, meaning we don't want to show the color picker widget in the Properties panel\n\tlet NodeInput::Value { tagged_value, exposed: false } = &document_node.inputs[index] else {\n\t\treturn LayoutGroup::row(widgets);\n\t};\n\n\t// Add a separator\n\twidgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\n\t// Add the color input\n\tmatch &**tagged_value {\n\t\tTaggedValue::Color(color_table) => widgets.push(\n\t\t\tcolor_button\n\t\t\t\t.value(match color_table.iter().next() {\n\t\t\t\t\tSome(color) => FillChoice::Solid(*color.element),\n\t\t\t\t\tNone => FillChoice::None,\n\t\t\t\t})\n\t\t\t\t.on_update(update_value(\n\t\t\t\t\t|input: &ColorInput| TaggedValue::Color(input.value.as_solid().iter().map(|&color| TableRow::new_from_element(color)).collect()),\n\t\t\t\t\tnode_id,\n\t\t\t\t\tindex,\n\t\t\t\t))\n\t\t\t\t.on_commit(commit_value)\n\t\t\t\t.widget_instance(),\n\t\t),\n\t\tTaggedValue::GradientTable(gradient_table) => widgets.push(\n\t\t\tcolor_button\n\t\t\t\t.value(match gradient_table.iter().next() {\n\t\t\t\t\tSome(row) => FillChoice::Gradient(row.element.clone()),\n\t\t\t\t\tNone => FillChoice::Gradient(GradientStops::default()),\n\t\t\t\t})\n\t\t\t\t.on_update(update_value(\n\t\t\t\t\t|input: &ColorInput| TaggedValue::GradientTable(input.value.as_gradient().iter().map(|&gradient| TableRow::new_from_element(gradient.clone())).collect()),\n\t\t\t\t\tnode_id,\n\t\t\t\t\tindex,\n\t\t\t\t))\n\t\t\t\t.on_commit(commit_value)\n\t\t\t\t.widget_instance(),\n\t\t),\n\t\tx => warn!(\"Color {x:?}\"),\n\t}\n\n\tLayoutGroup::row(widgets)\n}\n\npub fn font_widget(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup {\n\tlet (font_widgets, style_widgets) = font_inputs(parameter_widgets_info);\n\tfont_widgets.into_iter().chain(style_widgets.unwrap_or_default()).collect::<Vec<_>>().into()\n}\n\npub fn curve_widget(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup {\n\tlet ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;\n\n\tlet mut widgets = start_widgets(parameter_widgets_info);\n\n\tlet Some(document_node) = document_node else { return LayoutGroup::default() };\n\tlet Some(input) = document_node.inputs.get(index) else {\n\t\tlog::warn!(\"A widget failed to be built because its node's input index is invalid.\");\n\t\treturn LayoutGroup::row(vec![]);\n\t};\n\tif let Some(TaggedValue::Curve(curve)) = &input.as_non_exposed_value() {\n\t\twidgets.extend_from_slice(&[\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\tCurveInput::new(curve.clone())\n\t\t\t\t.on_update(update_value(|x: &CurveInput| TaggedValue::Curve(x.value.clone()), node_id, index))\n\t\t\t\t.on_commit(commit_value)\n\t\t\t\t.widget_instance(),\n\t\t])\n\t}\n\tLayoutGroup::row(widgets)\n}\n\npub fn get_document_node<'a>(node_id: NodeId, context: &'a NodePropertiesContext<'a>) -> Result<&'a DocumentNode, String> {\n\tlet network = context\n\t\t.network_interface\n\t\t.nested_network(context.selection_network_path)\n\t\t.ok_or(\"network not found in get_document_node\")?;\n\tnetwork.nodes.get(&node_id).ok_or(format!(\"node {node_id} not found in get_document_node\"))\n}\n\npub fn query_node_and_input_info<'a>(node_id: NodeId, input_index: usize, context: &'a mut NodePropertiesContext<'a>) -> Result<(&'a DocumentNode, String, String), String> {\n\tlet (name, description) = context.network_interface.displayed_input_name_and_description(&node_id, input_index, context.selection_network_path);\n\tlet document_node = get_document_node(node_id, context)?;\n\n\tOk((document_node, name, description))\n}\n\npub fn query_noise_pattern_state(node_id: NodeId, context: &NodePropertiesContext) -> Result<(bool, bool, bool, bool, bool, bool), String> {\n\tlet document_node = get_document_node(node_id, context)?;\n\tlet current_noise_type = document_node.inputs.iter().find_map(|input| match input.as_value() {\n\t\tSome(&TaggedValue::NoiseType(noise_type)) => Some(noise_type),\n\t\t_ => None,\n\t});\n\tlet current_fractal_type = document_node.inputs.iter().find_map(|input| match input.as_value() {\n\t\tSome(&TaggedValue::FractalType(fractal_type)) => Some(fractal_type),\n\t\t_ => None,\n\t});\n\tlet current_domain_warp_type = document_node.inputs.iter().find_map(|input| match input.as_value() {\n\t\tSome(&TaggedValue::DomainWarpType(domain_warp_type)) => Some(domain_warp_type),\n\t\t_ => None,\n\t});\n\tlet fractal_active = current_fractal_type != Some(FractalType::None);\n\tlet coherent_noise_active = current_noise_type != Some(NoiseType::WhiteNoise);\n\tlet cellular_noise_active = current_noise_type == Some(NoiseType::Cellular);\n\tlet ping_pong_active = current_fractal_type == Some(FractalType::PingPong);\n\tlet domain_warp_active = current_domain_warp_type != Some(DomainWarpType::None);\n\tlet domain_warp_only_fractal_type_wrongly_active =\n\t\t!domain_warp_active && (current_fractal_type == Some(FractalType::DomainWarpIndependent) || current_fractal_type == Some(FractalType::DomainWarpProgressive));\n\n\tOk((\n\t\tfractal_active,\n\t\tcoherent_noise_active,\n\t\tcellular_noise_active,\n\t\tping_pong_active,\n\t\tdomain_warp_active,\n\t\tdomain_warp_only_fractal_type_wrongly_active,\n\t))\n}\n\npub fn query_assign_colors_randomize(node_id: NodeId, context: &NodePropertiesContext) -> Result<bool, String> {\n\tuse graphene_std::vector::assign_colors::*;\n\n\tlet document_node = get_document_node(node_id, context)?;\n\t// This is safe since the node is a proto node and the implementation cannot be changed.\n\tOk(match document_node.inputs.get(RandomizeInput::INDEX).and_then(|input| input.as_value()) {\n\t\tSome(TaggedValue::Bool(randomize_enabled)) => *randomize_enabled,\n\t\t_ => false,\n\t})\n}\n\npub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {\n\tuse graphene_std::raster::brightness_contrast::*;\n\n\t// Use Classic\n\tlet use_classic = bool_widget(ParameterWidgetsInfo::new(node_id, UseClassicInput::INDEX, true, context), CheckboxInput::default());\n\n\tlet document_node = match get_document_node(node_id, context) {\n\t\tOk(document_node) => document_node,\n\t\tErr(err) => {\n\t\t\tlog::error!(\"Could not get document node in brightness_contrast_properties: {err}\");\n\t\t\treturn Vec::new();\n\t\t}\n\t};\n\tlet use_classic_value = document_node.inputs.get(UseClassicInput::INDEX);\n\tlet includes_use_classic = use_classic_value.is_some();\n\tlet use_classic_value = match use_classic_value.and_then(|input| input.as_value()) {\n\t\tSome(TaggedValue::Bool(use_classic_choice)) => *use_classic_choice,\n\t\t_ => false,\n\t};\n\n\t// Brightness\n\tlet brightness = number_widget(\n\t\tParameterWidgetsInfo::new(node_id, BrightnessInput::INDEX, true, context),\n\t\tNumberInput::default()\n\t\t\t.unit(\"%\")\n\t\t\t.mode_range()\n\t\t\t.display_decimal_places(2)\n\t\t\t.range_min(Some(if use_classic_value { -100. } else { -150. }))\n\t\t\t.range_max(Some(if use_classic_value { 100. } else { 150. })),\n\t);\n\n\t// Contrast\n\tlet contrast = number_widget(\n\t\tParameterWidgetsInfo::new(node_id, ContrastInput::INDEX, true, context),\n\t\tNumberInput::default()\n\t\t\t.unit(\"%\")\n\t\t\t.mode_range()\n\t\t\t.display_decimal_places(2)\n\t\t\t.range_min(Some(if use_classic_value { -100. } else { -50. }))\n\t\t\t.range_max(Some(100.)),\n\t);\n\n\tlet mut layout = vec![LayoutGroup::row(brightness), LayoutGroup::row(contrast)];\n\tif includes_use_classic {\n\t\t// TODO: When we no longer use this function in the temporary \"Brightness/Contrast Classic\" node, remove this conditional pushing and just always include this\n\t\tlayout.push(LayoutGroup::row(use_classic));\n\t}\n\n\tlayout\n}\n\npub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {\n\tuse graphene_std::raster::channel_mixer::*;\n\n\tlet is_monochrome = bool_widget(ParameterWidgetsInfo::new(node_id, MonochromeInput::INDEX, true, context), CheckboxInput::default());\n\tlet mut parameter_info = ParameterWidgetsInfo::new(node_id, OutputChannelInput::INDEX, true, context);\n\tparameter_info.exposable = false;\n\tlet output_channel = enum_choice::<RedGreenBlue>().for_socket(parameter_info).property_row();\n\n\tlet document_node = match get_document_node(node_id, context) {\n\t\tOk(document_node) => document_node,\n\t\tErr(err) => {\n\t\t\tlog::error!(\"Could not get document node in channel_mixer_properties: {err}\");\n\t\t\treturn Vec::new();\n\t\t}\n\t};\n\t// Monochrome\n\tlet is_monochrome_value = match document_node.inputs[MonochromeInput::INDEX].as_value() {\n\t\tSome(TaggedValue::Bool(monochrome_choice)) => *monochrome_choice,\n\t\t_ => false,\n\t};\n\t// Output channel choice\n\tlet output_channel_value = match &document_node.inputs[OutputChannelInput::INDEX].as_value() {\n\t\tSome(TaggedValue::RedGreenBlue(choice)) => choice,\n\t\t_ => {\n\t\t\twarn!(\"Channel Mixer node properties panel could not be displayed.\");\n\t\t\treturn vec![];\n\t\t}\n\t};\n\n\t// Output Channel modes\n\tlet (red_output_index, green_output_index, blue_output_index, constant_output_index) = match (is_monochrome_value, output_channel_value) {\n\t\t(true, _) => (MonochromeRInput::INDEX, MonochromeGInput::INDEX, MonochromeBInput::INDEX, MonochromeCInput::INDEX),\n\t\t(false, RedGreenBlue::Red) => (RedRInput::INDEX, RedGInput::INDEX, RedBInput::INDEX, RedCInput::INDEX),\n\t\t(false, RedGreenBlue::Green) => (GreenRInput::INDEX, GreenGInput::INDEX, GreenBInput::INDEX, GreenCInput::INDEX),\n\t\t(false, RedGreenBlue::Blue) => (BlueRInput::INDEX, BlueGInput::INDEX, BlueBInput::INDEX, BlueCInput::INDEX),\n\t};\n\tlet number_input = NumberInput::default().mode_range().min(-200.).max(200.).unit(\"%\");\n\tlet red = number_widget(ParameterWidgetsInfo::new(node_id, red_output_index, true, context), number_input.clone());\n\tlet green = number_widget(ParameterWidgetsInfo::new(node_id, green_output_index, true, context), number_input.clone());\n\tlet blue = number_widget(ParameterWidgetsInfo::new(node_id, blue_output_index, true, context), number_input.clone());\n\tlet constant = number_widget(ParameterWidgetsInfo::new(node_id, constant_output_index, true, context), number_input);\n\n\t// Monochrome\n\tlet mut layout = vec![LayoutGroup::row(is_monochrome)];\n\t// Output channel choice\n\tif !is_monochrome_value {\n\t\tlayout.push(output_channel);\n\t}\n\t// Channel values\n\tlayout.extend([LayoutGroup::row(red), LayoutGroup::row(green), LayoutGroup::row(blue), LayoutGroup::row(constant)]);\n\tlayout\n}\n\npub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {\n\tuse graphene_std::raster::selective_color::*;\n\n\tlet mut default_info = ParameterWidgetsInfo::new(node_id, ColorsInput::INDEX, true, context);\n\tdefault_info.exposable = false;\n\tlet colors = enum_choice::<SelectiveColorChoice>().for_socket(default_info).property_row();\n\n\tlet document_node = match get_document_node(node_id, context) {\n\t\tOk(document_node) => document_node,\n\t\tErr(err) => {\n\t\t\tlog::error!(\"Could not get document node in selective_color_properties: {err}\");\n\t\t\treturn Vec::new();\n\t\t}\n\t};\n\t// Colors choice\n\tlet colors_choice = match &document_node.inputs[ColorsInput::INDEX].as_value() {\n\t\tSome(TaggedValue::SelectiveColorChoice(choice)) => choice,\n\t\t_ => {\n\t\t\twarn!(\"Selective Color node properties panel could not be displayed.\");\n\t\t\treturn vec![];\n\t\t}\n\t};\n\t// CMYK\n\tlet (c_index, m_index, y_index, k_index) = match colors_choice {\n\t\tSelectiveColorChoice::Reds => (RCInput::INDEX, RMInput::INDEX, RYInput::INDEX, RKInput::INDEX),\n\t\tSelectiveColorChoice::Yellows => (YCInput::INDEX, YMInput::INDEX, YYInput::INDEX, YKInput::INDEX),\n\t\tSelectiveColorChoice::Greens => (GCInput::INDEX, GMInput::INDEX, GYInput::INDEX, GKInput::INDEX),\n\t\tSelectiveColorChoice::Cyans => (CCInput::INDEX, CMInput::INDEX, CYInput::INDEX, CKInput::INDEX),\n\t\tSelectiveColorChoice::Blues => (BCInput::INDEX, BMInput::INDEX, BYInput::INDEX, BKInput::INDEX),\n\t\tSelectiveColorChoice::Magentas => (MCInput::INDEX, MMInput::INDEX, MYInput::INDEX, MKInput::INDEX),\n\t\tSelectiveColorChoice::Whites => (WCInput::INDEX, WMInput::INDEX, WYInput::INDEX, WKInput::INDEX),\n\t\tSelectiveColorChoice::Neutrals => (NCInput::INDEX, NMInput::INDEX, NYInput::INDEX, NKInput::INDEX),\n\t\tSelectiveColorChoice::Blacks => (KCInput::INDEX, KMInput::INDEX, KYInput::INDEX, KKInput::INDEX),\n\t};\n\tlet number_input = NumberInput::default().mode_range().min(-100.).max(100.).unit(\"%\");\n\tlet cyan = number_widget(ParameterWidgetsInfo::new(node_id, c_index, true, context), number_input.clone());\n\tlet magenta = number_widget(ParameterWidgetsInfo::new(node_id, m_index, true, context), number_input.clone());\n\tlet yellow = number_widget(ParameterWidgetsInfo::new(node_id, y_index, true, context), number_input.clone());\n\tlet black = number_widget(ParameterWidgetsInfo::new(node_id, k_index, true, context), number_input);\n\n\t// Mode\n\tlet mode = enum_choice::<RelativeAbsolute>()\n\t\t.for_socket(ParameterWidgetsInfo::new(node_id, ModeInput::INDEX, true, context))\n\t\t.property_row();\n\n\tvec![\n\t\t// Colors choice\n\t\tcolors,\n\t\t// CMYK\n\t\tLayoutGroup::row(cyan),\n\t\tLayoutGroup::row(magenta),\n\t\tLayoutGroup::row(yellow),\n\t\tLayoutGroup::row(black),\n\t\t// Mode\n\t\tmode,\n\t]\n}\n\npub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {\n\tuse graphene_std::vector::generator_nodes::grid::*;\n\n\tlet grid_type = enum_choice::<GridType>()\n\t\t.for_socket(ParameterWidgetsInfo::new(node_id, GridTypeInput::INDEX, true, context))\n\t\t.property_row();\n\n\tlet mut widgets = vec![grid_type];\n\n\tlet document_node = match get_document_node(node_id, context) {\n\t\tOk(document_node) => document_node,\n\t\tErr(err) => {\n\t\t\tlog::error!(\"Could not get document node in grid_properties: {err}\");\n\t\t\treturn Vec::new();\n\t\t}\n\t};\n\tlet Some(grid_type_input) = document_node.inputs.get(GridTypeInput::INDEX) else {\n\t\tlog::warn!(\"A widget failed to be built because its node's input index is invalid.\");\n\t\treturn vec![];\n\t};\n\tif let Some(&TaggedValue::GridType(grid_type)) = grid_type_input.as_non_exposed_value() {\n\t\tmatch grid_type {\n\t\t\tGridType::Rectangular => {\n\t\t\t\tlet spacing = vec2_widget(ParameterWidgetsInfo::new(node_id, SpacingInput::<f64>::INDEX, true, context), \"W\", \"H\", \" px\", Some(0.), false);\n\t\t\t\twidgets.push(spacing);\n\t\t\t}\n\t\t\tGridType::Isometric => {\n\t\t\t\tlet spacing = LayoutGroup::row(number_widget(\n\t\t\t\t\tParameterWidgetsInfo::new(node_id, SpacingInput::<f64>::INDEX, true, context),\n\t\t\t\t\tNumberInput::default().label(\"H\").min(0.).unit(\" px\"),\n\t\t\t\t));\n\t\t\t\tlet angles = vec2_widget(ParameterWidgetsInfo::new(node_id, AnglesInput::INDEX, true, context), \"\", \"\", \"°\", None, false);\n\t\t\t\twidgets.extend([spacing, angles]);\n\t\t\t}\n\t\t}\n\t}\n\n\tlet columns = number_widget(ParameterWidgetsInfo::new(node_id, ColumnsInput::INDEX, true, context), NumberInput::default().min(1.));\n\tlet rows = number_widget(ParameterWidgetsInfo::new(node_id, RowsInput::INDEX, true, context), NumberInput::default().min(1.));\n\n\twidgets.extend([LayoutGroup::row(columns), LayoutGroup::row(rows)]);\n\n\twidgets\n}\n\npub(crate) fn spiral_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {\n\tuse graphene_std::vector::generator_nodes::spiral::*;\n\n\tlet spiral_type = enum_choice::<SpiralType>()\n\t\t.for_socket(ParameterWidgetsInfo::new(node_id, SpiralTypeInput::INDEX, true, context))\n\t\t.property_row();\n\tlet turns = number_widget(ParameterWidgetsInfo::new(node_id, TurnsInput::INDEX, true, context), NumberInput::default().min(0.1));\n\tlet start_angle = number_widget(ParameterWidgetsInfo::new(node_id, StartAngleInput::INDEX, true, context), NumberInput::default().unit(\"°\"));\n\n\tlet mut widgets = vec![spiral_type, LayoutGroup::row(turns), LayoutGroup::row(start_angle)];\n\n\tlet document_node = match get_document_node(node_id, context) {\n\t\tOk(document_node) => document_node,\n\t\tErr(err) => {\n\t\t\tlog::error!(\"Could not get document node in exposure_properties: {err}\");\n\t\t\treturn Vec::new();\n\t\t}\n\t};\n\n\tlet Some(spiral_type_input) = document_node.inputs.get(SpiralTypeInput::INDEX) else {\n\t\tlog::warn!(\"A widget failed to be built because its node's input index is invalid.\");\n\t\treturn vec![];\n\t};\n\tif let Some(&TaggedValue::SpiralType(spiral_type)) = spiral_type_input.as_non_exposed_value() {\n\t\tmatch spiral_type {\n\t\t\tSpiralType::Archimedean => {\n\t\t\t\tlet inner_radius = LayoutGroup::row(number_widget(\n\t\t\t\t\tParameterWidgetsInfo::new(node_id, InnerRadiusInput::INDEX, true, context),\n\t\t\t\t\tNumberInput::default().min(0.).unit(\" px\"),\n\t\t\t\t));\n\n\t\t\t\tlet outer_radius = LayoutGroup::row(number_widget(\n\t\t\t\t\tParameterWidgetsInfo::new(node_id, OuterRadiusInput::INDEX, true, context),\n\t\t\t\t\tNumberInput::default().unit(\" px\"),\n\t\t\t\t));\n\n\t\t\t\twidgets.extend([inner_radius, outer_radius]);\n\t\t\t}\n\t\t\tSpiralType::Logarithmic => {\n\t\t\t\tlet inner_radius = LayoutGroup::row(number_widget(\n\t\t\t\t\tParameterWidgetsInfo::new(node_id, InnerRadiusInput::INDEX, true, context),\n\t\t\t\t\tNumberInput::default().min(0.).unit(\" px\"),\n\t\t\t\t));\n\n\t\t\t\tlet outer_radius = LayoutGroup::row(number_widget(\n\t\t\t\t\tParameterWidgetsInfo::new(node_id, OuterRadiusInput::INDEX, true, context),\n\t\t\t\t\tNumberInput::default().min(0.1).unit(\" px\"),\n\t\t\t\t));\n\n\t\t\t\twidgets.extend([inner_radius, outer_radius]);\n\t\t\t}\n\t\t}\n\t}\n\n\tlet angular_resolution = number_widget(\n\t\tParameterWidgetsInfo::new(node_id, AngularResolutionInput::INDEX, true, context),\n\t\tNumberInput::default().min(1.).max(180.).unit(\"°\"),\n\t);\n\n\twidgets.push(LayoutGroup::row(angular_resolution));\n\n\twidgets\n}\n\npub(crate) const SAMPLE_POLYLINE_DESCRIPTION_SPACING: &str = \"Use a point sampling density controlled by a distance between, or specific number of, points.\";\npub(crate) const SAMPLE_POLYLINE_DESCRIPTION_SEPARATION: &str = \"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).\";\npub(crate) const SAMPLE_POLYLINE_DESCRIPTION_QUANTITY: &str = \"Number of points to place along the path.\";\npub(crate) const SAMPLE_POLYLINE_DESCRIPTION_START_OFFSET: &str = \"Exclude some distance from the start of the path before the first instance.\";\npub(crate) const SAMPLE_POLYLINE_DESCRIPTION_STOP_OFFSET: &str = \"Exclude some distance from the end of the path after the last instance.\";\npub(crate) const SAMPLE_POLYLINE_DESCRIPTION_ADAPTIVE_SPACING: &str = \"Round 'Separation' to a nearby value that divides into the path length evenly.\";\n\npub(crate) fn sample_polyline_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {\n\tuse graphene_std::vector::sample_polyline::*;\n\n\tlet document_node = match get_document_node(node_id, context) {\n\t\tOk(document_node) => document_node,\n\t\tErr(err) => {\n\t\t\tlog::error!(\"Could not get document node in sample_polyline_properties: {err}\");\n\t\t\treturn Vec::new();\n\t\t}\n\t};\n\n\tlet current_spacing = document_node.inputs.get(SpacingInput::INDEX).and_then(|input| input.as_value()).cloned();\n\tlet is_quantity = matches!(current_spacing, Some(TaggedValue::PointSpacingType(PointSpacingType::Quantity)));\n\n\tlet spacing = enum_choice::<PointSpacingType>()\n\t\t.for_socket(ParameterWidgetsInfo::new(node_id, SpacingInput::INDEX, true, context))\n\t\t.property_row();\n\tlet separation = number_widget(ParameterWidgetsInfo::new(node_id, SeparationInput::INDEX, true, context), NumberInput::default().min(0.).unit(\" px\"));\n\tlet quantity = number_widget(ParameterWidgetsInfo::new(node_id, QuantityInput::INDEX, true, context), NumberInput::default().min(2.).int());\n\tlet start_offset = number_widget(ParameterWidgetsInfo::new(node_id, StartOffsetInput::INDEX, true, context), NumberInput::default().min(0.).unit(\" px\"));\n\tlet stop_offset = number_widget(ParameterWidgetsInfo::new(node_id, StopOffsetInput::INDEX, true, context), NumberInput::default().min(0.).unit(\" px\"));\n\tlet adaptive_spacing = bool_widget(\n\t\tParameterWidgetsInfo::new(node_id, AdaptiveSpacingInput::INDEX, true, context),\n\t\tCheckboxInput::default().disabled(is_quantity),\n\t);\n\n\tvec![\n\t\tspacing.with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_SPACING),\n\t\tmatch current_spacing {\n\t\t\tSome(TaggedValue::PointSpacingType(PointSpacingType::Separation)) => LayoutGroup::row(separation).with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_SEPARATION),\n\t\t\tSome(TaggedValue::PointSpacingType(PointSpacingType::Quantity)) => LayoutGroup::row(quantity).with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_QUANTITY),\n\t\t\t_ => LayoutGroup::row(vec![]),\n\t\t},\n\t\tLayoutGroup::row(start_offset).with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_START_OFFSET),\n\t\tLayoutGroup::row(stop_offset).with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_STOP_OFFSET),\n\t\tLayoutGroup::row(adaptive_spacing).with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_ADAPTIVE_SPACING),\n\t]\n}\n\npub(crate) fn exposure_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {\n\tuse graphene_std::raster::exposure::*;\n\n\tlet exposure = number_widget(ParameterWidgetsInfo::new(node_id, ExposureInput::INDEX, true, context), NumberInput::default().min(-20.).max(20.));\n\tlet offset = number_widget(ParameterWidgetsInfo::new(node_id, OffsetInput::INDEX, true, context), NumberInput::default().min(-0.5).max(0.5));\n\tlet gamma_correction = number_widget(\n\t\tParameterWidgetsInfo::new(node_id, GammaCorrectionInput::INDEX, true, context),\n\t\tNumberInput::default().min(0.01).max(9.99).increment_step(0.1),\n\t);\n\n\tvec![LayoutGroup::row(exposure), LayoutGroup::row(offset), LayoutGroup::row(gamma_correction)]\n}\n\npub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {\n\tuse graphene_std::vector::generator_nodes::rectangle::*;\n\n\t// Corner Radius\n\tlet mut corner_radius_row_1 = start_widgets(ParameterWidgetsInfo::new(node_id, CornerRadiusInput::<f64>::INDEX, true, context));\n\tcorner_radius_row_1.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\n\tlet mut corner_radius_row_2 = vec![Separator::new(SeparatorStyle::Unrelated).widget_instance()];\n\tcorner_radius_row_2.push(TextLabel::new(\"\").widget_instance());\n\tadd_blank_assist(&mut corner_radius_row_2);\n\n\tlet document_node = match get_document_node(node_id, context) {\n\t\tOk(document_node) => document_node,\n\t\tErr(err) => {\n\t\t\tlog::error!(\"Could not get document node in rectangle_properties: {err}\");\n\t\t\treturn Vec::new();\n\t\t}\n\t};\n\tlet Some(input) = document_node.inputs.get(IndividualCornerRadiiInput::INDEX) else {\n\t\tlog::warn!(\"A widget failed to be built because its node's input index is invalid.\");\n\t\treturn vec![];\n\t};\n\tif let Some(&TaggedValue::Bool(is_individual)) = input.as_non_exposed_value() {\n\t\t// Values\n\t\tlet Some(input) = document_node.inputs.get(CornerRadiusInput::<f64>::INDEX) else {\n\t\t\tlog::warn!(\"A widget failed to be built because its node's input index is invalid.\");\n\t\t\treturn vec![];\n\t\t};\n\t\tlet uniform_val = match input.as_non_exposed_value() {\n\t\t\tSome(TaggedValue::F64(x)) => *x,\n\t\t\tSome(TaggedValue::F64Array4(x)) => x[0],\n\t\t\t_ => 0.,\n\t\t};\n\t\tlet individual_val = match input.as_non_exposed_value() {\n\t\t\tSome(&TaggedValue::F64Array4(x)) => x,\n\t\t\tSome(&TaggedValue::F64(x)) => [x; 4],\n\t\t\t_ => [0.; 4],\n\t\t};\n\n\t\t// Uniform/individual radio input widget\n\t\tlet uniform = RadioEntryData::new(\"Uniform\")\n\t\t\t.label(\"Uniform\")\n\t\t\t.on_update(move |_| Message::Batched {\n\t\t\t\tmessages: Box::new([\n\t\t\t\t\tNodeGraphMessage::SetInputValue {\n\t\t\t\t\t\tnode_id,\n\t\t\t\t\t\tinput_index: IndividualCornerRadiiInput::INDEX,\n\t\t\t\t\t\tvalue: TaggedValue::Bool(false),\n\t\t\t\t\t}\n\t\t\t\t\t.into(),\n\t\t\t\t\tNodeGraphMessage::SetInputValue {\n\t\t\t\t\t\tnode_id,\n\t\t\t\t\t\tinput_index: CornerRadiusInput::<f64>::INDEX,\n\t\t\t\t\t\tvalue: TaggedValue::F64(uniform_val),\n\t\t\t\t\t}\n\t\t\t\t\t.into(),\n\t\t\t\t]),\n\t\t\t})\n\t\t\t.on_commit(commit_value);\n\t\tlet individual = RadioEntryData::new(\"Individual\")\n\t\t\t.label(\"Individual\")\n\t\t\t.on_update(move |_| Message::Batched {\n\t\t\t\tmessages: Box::new([\n\t\t\t\t\tNodeGraphMessage::SetInputValue {\n\t\t\t\t\t\tnode_id,\n\t\t\t\t\t\tinput_index: IndividualCornerRadiiInput::INDEX,\n\t\t\t\t\t\tvalue: TaggedValue::Bool(true),\n\t\t\t\t\t}\n\t\t\t\t\t.into(),\n\t\t\t\t\tNodeGraphMessage::SetInputValue {\n\t\t\t\t\t\tnode_id,\n\t\t\t\t\t\tinput_index: CornerRadiusInput::<f64>::INDEX,\n\t\t\t\t\t\tvalue: TaggedValue::F64Array4(individual_val),\n\t\t\t\t\t}\n\t\t\t\t\t.into(),\n\t\t\t\t]),\n\t\t\t})\n\t\t\t.on_commit(commit_value);\n\t\tlet radio_input = RadioInput::new(vec![uniform, individual]).selected_index(Some(is_individual as u32)).widget_instance();\n\t\tcorner_radius_row_1.push(radio_input);\n\n\t\t// Radius value input widget\n\t\tlet input_widget = if is_individual {\n\t\t\tlet from_string = |string: &str| {\n\t\t\t\tstring\n\t\t\t\t\t.split(&[',', ' '])\n\t\t\t\t\t.filter(|x| !x.is_empty())\n\t\t\t\t\t.map(str::parse::<f64>)\n\t\t\t\t\t.collect::<Result<Vec<f64>, _>>()\n\t\t\t\t\t.ok()\n\t\t\t\t\t.map(|v| {\n\t\t\t\t\t\tlet arr: Box<[f64; 4]> = v.into_boxed_slice().try_into().unwrap_or_default();\n\t\t\t\t\t\t*arr\n\t\t\t\t\t})\n\t\t\t\t\t.map(TaggedValue::F64Array4)\n\t\t\t};\n\t\t\tTextInput::default()\n\t\t\t\t.value(individual_val.iter().map(|v| v.to_string()).collect::<Vec<_>>().join(\", \"))\n\t\t\t\t.on_update(optionally_update_value(move |x: &TextInput| from_string(&x.value), node_id, CornerRadiusInput::<f64>::INDEX))\n\t\t\t\t.widget_instance()\n\t\t} else {\n\t\t\tNumberInput::default()\n\t\t\t\t.value(Some(uniform_val))\n\t\t\t\t.unit(\" px\")\n\t\t\t\t.on_update(update_value(move |x: &NumberInput| TaggedValue::F64(x.value.unwrap()), node_id, CornerRadiusInput::<f64>::INDEX))\n\t\t\t\t.on_commit(commit_value)\n\t\t\t\t.widget_instance()\n\t\t};\n\t\tcorner_radius_row_2.push(input_widget);\n\t}\n\n\t// Size X\n\tlet size_x = number_widget(ParameterWidgetsInfo::new(node_id, WidthInput::INDEX, true, context), NumberInput::default());\n\n\t// Size Y\n\tlet size_y = number_widget(ParameterWidgetsInfo::new(node_id, HeightInput::INDEX, true, context), NumberInput::default());\n\n\t// Clamped\n\tlet clamped = bool_widget(ParameterWidgetsInfo::new(node_id, ClampedInput::INDEX, true, context), CheckboxInput::default());\n\n\tvec![\n\t\tLayoutGroup::row(size_x),\n\t\tLayoutGroup::row(size_y),\n\t\tLayoutGroup::row(corner_radius_row_1),\n\t\tLayoutGroup::row(corner_radius_row_2),\n\t\tLayoutGroup::row(clamped),\n\t]\n}\n\npub(crate) fn node_no_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {\n\tlet text = if context.network_interface.is_layer(&node_id, context.selection_network_path) {\n\t\t\"Layer has no parameters\"\n\t} else {\n\t\t\"Node has no parameters\"\n\t};\n\tstring_properties(text)\n}\n\npub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> LayoutGroup {\n\tlet mut layout = Vec::new();\n\n\tif let Some(properties_override) = context\n\t\t.network_interface\n\t\t.reference(&node_id, context.selection_network_path)\n\t\t.as_ref()\n\t\t.and_then(|identifier| resolve_document_node_type(identifier))\n\t\t.and_then(|definition| definition.properties)\n\t\t.and_then(|properties| NODE_OVERRIDES.get(properties))\n\t{\n\t\tlayout = properties_override(node_id, context);\n\t} else {\n\t\tlet number_of_inputs = context.network_interface.number_of_inputs(&node_id, context.selection_network_path);\n\t\tfor input_index in 1..number_of_inputs {\n\t\t\tlet row = context.call_widget_override(&node_id, input_index).unwrap_or_else(|| {\n\t\t\t\tlet Some(implementation) = context.network_interface.implementation(&node_id, context.selection_network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get implementation for node {node_id}\");\n\t\t\t\t\treturn Vec::new();\n\t\t\t\t};\n\n\t\t\t\tlet mut number_options = (None, None, None);\n\t\t\t\tlet mut display_decimal_places = None;\n\t\t\t\tlet mut step = None;\n\t\t\t\tlet mut unit_suffix = None;\n\t\t\t\tlet input_type = match implementation {\n\t\t\t\t\tDocumentNodeImplementation::ProtoNode(proto_node_identifier) => 'early_return: {\n\t\t\t\t\t\tif let Some(field) = graphene_std::registry::NODE_METADATA\n\t\t\t\t\t\t\t.lock()\n\t\t\t\t\t\t\t.unwrap()\n\t\t\t\t\t\t\t.get(proto_node_identifier)\n\t\t\t\t\t\t\t.and_then(|metadata| metadata.fields.get(input_index))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnumber_options = (field.number_min, field.number_max, field.number_mode_range);\n\t\t\t\t\t\t\tdisplay_decimal_places = field.number_display_decimal_places;\n\t\t\t\t\t\t\tunit_suffix = field.unit;\n\t\t\t\t\t\t\tstep = field.number_step;\n\t\t\t\t\t\t\tif let Some(ref default) = field.default_type {\n\t\t\t\t\t\t\t\tbreak 'early_return default.clone();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlet Some(implementations) = &interpreted_executor::node_registry::NODE_REGISTRY.get(proto_node_identifier) else {\n\t\t\t\t\t\t\tlog::error!(\"Could not get implementation for protonode {proto_node_identifier:?}\");\n\t\t\t\t\t\t\treturn Vec::new();\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tlet mut input_types = implementations.keys().filter_map(|item| item.inputs.get(input_index)).collect::<Vec<_>>();\n\t\t\t\t\t\tinput_types.sort_by_key(|ty| ty.type_name());\n\t\t\t\t\t\tlet input_type = input_types.first().cloned();\n\n\t\t\t\t\t\tlet Some(input_type) = input_type else { return Vec::new() };\n\t\t\t\t\t\tinput_type.clone()\n\t\t\t\t\t}\n\t\t\t\t\t_ => context\n\t\t\t\t\t\t.network_interface\n\t\t\t\t\t\t.input_type(&InputConnector::node(node_id, input_index), context.selection_network_path)\n\t\t\t\t\t\t.compiled_nested_type()\n\t\t\t\t\t\t.cloned()\n\t\t\t\t\t\t.unwrap_or(concrete!(())),\n\t\t\t\t};\n\n\t\t\t\tproperty_from_type(node_id, input_index, &input_type, number_options, unit_suffix, display_decimal_places, step, context).unwrap_or_else(|value| value)\n\t\t\t});\n\n\t\t\tlayout.extend(row);\n\t\t}\n\t}\n\n\tif layout.is_empty() {\n\t\tlayout = node_no_properties(node_id, context);\n\t}\n\tlet name = context.network_interface.implementation_name(&node_id, context.selection_network_path);\n\n\tlet description = context\n\t\t.network_interface\n\t\t.reference(&node_id, context.selection_network_path)\n\t\t.as_ref()\n\t\t.and_then(|identifier| resolve_document_node_type(identifier))\n\t\t.map(|definition| definition.description.to_string())\n\t\t.filter(|string| string != \"TODO\")\n\t\t.unwrap_or_default();\n\n\tlet visible = context.network_interface.is_visible(&node_id, context.selection_network_path);\n\tlet pinned = context.network_interface.is_pinned(&node_id, context.selection_network_path);\n\n\tLayoutGroup::section(name, description, visible, pinned, node_id.0, Layout(layout))\n}\n\n/// Fill Node Widgets LayoutGroup\npub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {\n\tuse graphene_std::vector::fill::*;\n\n\tlet mut widgets_first_row = start_widgets(ParameterWidgetsInfo::new(node_id, FillInput::<Color>::INDEX, true, context));\n\n\tlet document_node = match get_document_node(node_id, context) {\n\t\tOk(document_node) => document_node,\n\t\tErr(err) => {\n\t\t\tlog::error!(\"Could not get document node in fill_properties: {err}\");\n\t\t\treturn Vec::new();\n\t\t}\n\t};\n\n\tlet (fill, backup_color, backup_gradient) = if let (Some(TaggedValue::Fill(fill)), Some(TaggedValue::Color(backup_color)), Some(TaggedValue::Gradient(backup_gradient))) = (\n\t\t&document_node.inputs[FillInput::<Color>::INDEX].as_value(),\n\t\t&document_node.inputs[BackupColorInput::INDEX].as_value(),\n\t\t&document_node.inputs[BackupGradientInput::INDEX].as_value(),\n\t) {\n\t\t(fill, backup_color, backup_gradient)\n\t} else {\n\t\treturn vec![LayoutGroup::row(widgets_first_row)];\n\t};\n\tlet fill2 = fill.clone();\n\tlet backup_color_fill: Fill = backup_color.clone().into();\n\tlet backup_gradient_fill: Fill = backup_gradient.clone().into();\n\n\twidgets_first_row.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\twidgets_first_row.push(\n\t\tColorInput::default()\n\t\t\t.value(fill.clone().into())\n\t\t\t.on_update(move |x: &ColorInput| Message::Batched {\n\t\t\t\tmessages: Box::new([\n\t\t\t\t\tmatch &fill2 {\n\t\t\t\t\t\tFill::None => NodeGraphMessage::SetInputValue {\n\t\t\t\t\t\t\tnode_id,\n\t\t\t\t\t\t\tinput_index: BackupColorInput::INDEX,\n\t\t\t\t\t\t\tvalue: TaggedValue::Color(Table::new()),\n\t\t\t\t\t\t}\n\t\t\t\t\t\t.into(),\n\t\t\t\t\t\tFill::Solid(color) => NodeGraphMessage::SetInputValue {\n\t\t\t\t\t\t\tnode_id,\n\t\t\t\t\t\t\tinput_index: BackupColorInput::INDEX,\n\t\t\t\t\t\t\tvalue: TaggedValue::Color(Table::new_from_element(*color)),\n\t\t\t\t\t\t}\n\t\t\t\t\t\t.into(),\n\t\t\t\t\t\tFill::Gradient(gradient) => NodeGraphMessage::SetInputValue {\n\t\t\t\t\t\t\tnode_id,\n\t\t\t\t\t\t\tinput_index: BackupGradientInput::INDEX,\n\t\t\t\t\t\t\tvalue: TaggedValue::Gradient(gradient.clone()),\n\t\t\t\t\t\t}\n\t\t\t\t\t\t.into(),\n\t\t\t\t\t},\n\t\t\t\t\tNodeGraphMessage::SetInputValue {\n\t\t\t\t\t\tnode_id,\n\t\t\t\t\t\tinput_index: FillInput::<Color>::INDEX,\n\t\t\t\t\t\tvalue: TaggedValue::Fill(x.value.to_fill(fill2.as_gradient())),\n\t\t\t\t\t}\n\t\t\t\t\t.into(),\n\t\t\t\t]),\n\t\t\t})\n\t\t\t.on_commit(commit_value)\n\t\t\t.widget_instance(),\n\t);\n\tlet mut widgets = vec![LayoutGroup::row(widgets_first_row)];\n\n\tlet fill_type_switch = {\n\t\tlet mut row = vec![TextLabel::new(\"\").widget_instance()];\n\t\tmatch fill {\n\t\t\tFill::Solid(_) | Fill::None => add_blank_assist(&mut row),\n\t\t\tFill::Gradient(gradient) => {\n\t\t\t\tlet reverse_button = IconButton::new(\"Reverse\", 24)\n\t\t\t\t\t.tooltip_description(\"Reverse the gradient color stops.\")\n\t\t\t\t\t.on_update(update_value(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlet gradient = gradient.clone();\n\t\t\t\t\t\t\tmove |_| {\n\t\t\t\t\t\t\t\tlet mut gradient = gradient.clone();\n\t\t\t\t\t\t\t\tgradient.stops = gradient.stops.reversed();\n\t\t\t\t\t\t\t\tTaggedValue::Fill(Fill::Gradient(gradient))\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\tnode_id,\n\t\t\t\t\t\tFillInput::<Color>::INDEX,\n\t\t\t\t\t))\n\t\t\t\t\t.widget_instance();\n\t\t\t\trow.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\t\t\t\trow.push(reverse_button);\n\t\t\t}\n\t\t}\n\n\t\tlet entries = vec![\n\t\t\tRadioEntryData::new(\"solid\")\n\t\t\t\t.label(\"Solid\")\n\t\t\t\t.on_update(update_value(move |_| TaggedValue::Fill(backup_color_fill.clone()), node_id, FillInput::<Color>::INDEX))\n\t\t\t\t.on_commit(commit_value),\n\t\t\tRadioEntryData::new(\"gradient\")\n\t\t\t\t.label(\"Gradient\")\n\t\t\t\t.on_update(update_value(move |_| TaggedValue::Fill(backup_gradient_fill.clone()), node_id, FillInput::<Color>::INDEX))\n\t\t\t\t.on_commit(commit_value),\n\t\t];\n\n\t\trow.extend_from_slice(&[\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\tRadioInput::new(entries).selected_index(Some(if fill.as_gradient().is_some() { 1 } else { 0 })).widget_instance(),\n\t\t]);\n\n\t\tLayoutGroup::row(row)\n\t};\n\twidgets.push(fill_type_switch);\n\n\tif let Fill::Gradient(gradient) = fill.clone() {\n\t\tlet mut row = vec![TextLabel::new(\"\").widget_instance()];\n\t\tmatch gradient.gradient_type {\n\t\t\tGradientType::Linear => add_blank_assist(&mut row),\n\t\t\tGradientType::Radial => {\n\t\t\t\tlet orientation = if (gradient.end.x - gradient.start.x).abs() > f64::EPSILON * 1e6 {\n\t\t\t\t\tgradient.end.x > gradient.start.x\n\t\t\t\t} else {\n\t\t\t\t\t(gradient.start.x + gradient.start.y) < (gradient.end.x + gradient.end.y)\n\t\t\t\t};\n\t\t\t\tlet reverse_radial_gradient_button = IconButton::new(if orientation { \"ReverseRadialGradientToRight\" } else { \"ReverseRadialGradientToLeft\" }, 24)\n\t\t\t\t\t.tooltip_description(\"Reverse which end the gradient radiates from.\")\n\t\t\t\t\t.on_update(update_value(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlet gradient = gradient.clone();\n\t\t\t\t\t\t\tmove |_| {\n\t\t\t\t\t\t\t\tlet mut gradient = gradient.clone();\n\t\t\t\t\t\t\t\tstd::mem::swap(&mut gradient.start, &mut gradient.end);\n\t\t\t\t\t\t\t\tTaggedValue::Fill(Fill::Gradient(gradient))\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\tnode_id,\n\t\t\t\t\t\tFillInput::<Color>::INDEX,\n\t\t\t\t\t))\n\t\t\t\t\t.widget_instance();\n\t\t\t\trow.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\t\t\t\trow.push(reverse_radial_gradient_button);\n\t\t\t}\n\t\t}\n\n\t\tlet gradient_for_closure = gradient.clone();\n\n\t\tlet entries = [GradientType::Linear, GradientType::Radial]\n\t\t\t.iter()\n\t\t\t.map(|&grad_type| {\n\t\t\t\tlet gradient = gradient_for_closure.clone();\n\t\t\t\tlet set_input_value = update_value(\n\t\t\t\t\tmove |_: &()| {\n\t\t\t\t\t\tlet mut new_gradient = gradient.clone();\n\t\t\t\t\t\tnew_gradient.gradient_type = grad_type;\n\t\t\t\t\t\tTaggedValue::Fill(Fill::Gradient(new_gradient))\n\t\t\t\t\t},\n\t\t\t\t\tnode_id,\n\t\t\t\t\tFillInput::<Color>::INDEX,\n\t\t\t\t);\n\t\t\t\tRadioEntryData::new(format!(\"{:?}\", grad_type))\n\t\t\t\t\t.label(format!(\"{:?}\", grad_type))\n\t\t\t\t\t.on_update(move |_| Message::Batched {\n\t\t\t\t\t\tmessages: Box::new([\n\t\t\t\t\t\t\tset_input_value(&()),\n\t\t\t\t\t\t\tGradientToolMessage::UpdateOptions {\n\t\t\t\t\t\t\t\toptions: GradientOptionsUpdate::Type(grad_type),\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t.into(),\n\t\t\t\t\t\t]),\n\t\t\t\t\t})\n\t\t\t\t\t.on_commit(commit_value)\n\t\t\t})\n\t\t\t.collect();\n\n\t\trow.extend_from_slice(&[\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\tRadioInput::new(entries).selected_index(Some(gradient.gradient_type as u32)).widget_instance(),\n\t\t]);\n\n\t\twidgets.push(LayoutGroup::row(row));\n\t}\n\n\twidgets\n}\n\npub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {\n\tuse graphene_std::vector::stroke::*;\n\n\tlet document_node = match get_document_node(node_id, context) {\n\t\tOk(document_node) => document_node,\n\t\tErr(err) => {\n\t\t\tlog::error!(\"Could not get document node in fill_properties: {err}\");\n\t\t\treturn Vec::new();\n\t\t}\n\t};\n\tlet join_value = match &document_node.inputs[JoinInput::INDEX].as_value() {\n\t\tSome(TaggedValue::StrokeJoin(x)) => x,\n\t\t_ => &StrokeJoin::Miter,\n\t};\n\n\tlet dash_lengths_val = match &document_node.inputs[DashLengthsInput::<Vec<f64>>::INDEX].as_value() {\n\t\tSome(TaggedValue::VecF64(x)) => x,\n\t\t_ => &vec![],\n\t};\n\tlet has_dash_lengths = dash_lengths_val.is_empty();\n\tlet miter_limit_disabled = join_value != &StrokeJoin::Miter;\n\n\tlet color = color_widget(\n\t\tParameterWidgetsInfo::new(node_id, ColorInput::INDEX, true, context),\n\t\tcrate::messages::layout::utility_types::widgets::button_widgets::ColorInput::default(),\n\t);\n\tlet weight = number_widget(ParameterWidgetsInfo::new(node_id, WeightInput::INDEX, true, context), NumberInput::default().unit(\" px\").min(0.));\n\tlet align = enum_choice::<StrokeAlign>()\n\t\t.for_socket(ParameterWidgetsInfo::new(node_id, AlignInput::INDEX, true, context))\n\t\t.property_row();\n\tlet cap = enum_choice::<StrokeCap>().for_socket(ParameterWidgetsInfo::new(node_id, CapInput::INDEX, true, context)).property_row();\n\tlet join = enum_choice::<StrokeJoin>()\n\t\t.for_socket(ParameterWidgetsInfo::new(node_id, JoinInput::INDEX, true, context))\n\t\t.property_row();\n\n\tlet miter_limit = number_widget(\n\t\tParameterWidgetsInfo::new(node_id, MiterLimitInput::INDEX, true, context),\n\t\tNumberInput::default().min(0.).disabled(miter_limit_disabled),\n\t);\n\tlet paint_order = enum_choice::<PaintOrder>()\n\t\t.for_socket(ParameterWidgetsInfo::new(node_id, PaintOrderInput::INDEX, true, context))\n\t\t.property_row();\n\tlet disabled_number_input = NumberInput::default().unit(\" px\").disabled(has_dash_lengths);\n\tlet dash_lengths = array_of_number_widget(\n\t\tParameterWidgetsInfo::new(node_id, DashLengthsInput::<Vec<f64>>::INDEX, true, context),\n\t\tTextInput::default().centered(true),\n\t);\n\tlet number_input = disabled_number_input;\n\tlet dash_offset = number_widget(ParameterWidgetsInfo::new(node_id, DashOffsetInput::INDEX, true, context), number_input);\n\n\tvec![\n\t\tcolor,\n\t\tLayoutGroup::row(weight),\n\t\talign,\n\t\tcap,\n\t\tjoin,\n\t\tLayoutGroup::row(miter_limit),\n\t\tpaint_order,\n\t\tLayoutGroup::row(dash_lengths),\n\t\tLayoutGroup::row(dash_offset),\n\t]\n}\n\npub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {\n\tuse graphene_std::vector::offset_path::*;\n\n\tlet number_input = NumberInput::default().unit(\" px\");\n\tlet distance = number_widget(ParameterWidgetsInfo::new(node_id, DistanceInput::INDEX, true, context), number_input);\n\n\tlet join = enum_choice::<StrokeJoin>()\n\t\t.for_socket(ParameterWidgetsInfo::new(node_id, JoinInput::INDEX, true, context))\n\t\t.property_row();\n\n\tlet document_node = match get_document_node(node_id, context) {\n\t\tOk(document_node) => document_node,\n\t\tErr(err) => {\n\t\t\tlog::error!(\"Could not get document node in offset_path_properties: {err}\");\n\t\t\treturn Vec::new();\n\t\t}\n\t};\n\tlet number_input = NumberInput::default().min(0.).disabled({\n\t\tlet join_val = match &document_node.inputs[JoinInput::INDEX].as_value() {\n\t\t\tSome(TaggedValue::StrokeJoin(x)) => x,\n\t\t\t_ => &StrokeJoin::Miter,\n\t\t};\n\t\tjoin_val != &StrokeJoin::Miter\n\t});\n\tlet miter_limit = number_widget(ParameterWidgetsInfo::new(node_id, MiterLimitInput::INDEX, true, context), number_input);\n\n\tvec![LayoutGroup::row(distance), join, LayoutGroup::row(miter_limit)]\n}\n\npub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {\n\tuse graphene_std::math_nodes::math::*;\n\n\tlet expression = (|| {\n\t\tlet mut widgets = start_widgets(ParameterWidgetsInfo::new(node_id, ExpressionInput::INDEX, true, context));\n\n\t\tlet document_node = match get_document_node(node_id, context) {\n\t\t\tOk(document_node) => document_node,\n\t\t\tErr(err) => {\n\t\t\t\tlog::error!(\"Could not get document node in offset_path_properties: {err}\");\n\t\t\t\treturn Vec::new();\n\t\t\t}\n\t\t};\n\t\tlet Some(input) = document_node.inputs.get(ExpressionInput::INDEX) else {\n\t\t\tlog::warn!(\"A widget failed to be built because its node's input index is invalid.\");\n\t\t\treturn vec![];\n\t\t};\n\t\tif let Some(TaggedValue::String(x)) = &input.as_non_exposed_value() {\n\t\t\twidgets.extend_from_slice(&[\n\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\tTextInput::new(x.clone())\n\t\t\t\t\t.centered(true)\n\t\t\t\t\t.on_update(update_value(\n\t\t\t\t\t\t|x: &TextInput| {\n\t\t\t\t\t\t\tTaggedValue::String({\n\t\t\t\t\t\t\t\tlet mut expression = x.value.trim().to_string();\n\n\t\t\t\t\t\t\t\tif [\"+\", \"-\", \"*\", \"/\", \"^\", \"%\"].iter().any(|&infix| infix == expression) {\n\t\t\t\t\t\t\t\t\texpression = format!(\"A {expression} B\");\n\t\t\t\t\t\t\t\t} else if expression == \"^\" {\n\t\t\t\t\t\t\t\t\texpression = String::from(\"A^B\");\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\texpression\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t},\n\t\t\t\t\t\tnode_id,\n\t\t\t\t\t\tExpressionInput::INDEX,\n\t\t\t\t\t))\n\t\t\t\t\t.on_commit(commit_value)\n\t\t\t\t\t.widget_instance(),\n\t\t\t])\n\t\t}\n\t\twidgets\n\t})();\n\tlet operand_b = number_widget(ParameterWidgetsInfo::new(node_id, OperandBInput::<f64>::INDEX, true, context), NumberInput::default());\n\tlet operand_a_hint = vec![TextLabel::new(\"(Operand A is the primary input)\").widget_instance()];\n\n\tvec![\n\t\tLayoutGroup::row(expression).with_tooltip_description(r#\"A math expression that may incorporate \"A\" and/or \"B\", such as \"sqrt(A + B) - B^2\".\"#),\n\t\tLayoutGroup::row(operand_b).with_tooltip_description(r#\"The value of \"B\" when calculating the expression.\"#),\n\t\tLayoutGroup::row(operand_a_hint).with_tooltip_description(r#\"\"A\" is fed by the value from the previous node in the primary data flow, or it is 0 if disconnected.\"#),\n\t]\n}\n\npub struct ParameterWidgetsInfo<'a> {\n\tpersistent_data: &'a PersistentData,\n\tnetwork_interface: &'a NodeNetworkInterface,\n\tselection_network_path: &'a [NodeId],\n\tdocument_node: Option<&'a DocumentNode>,\n\tnode_id: NodeId,\n\tindex: usize,\n\tname: String,\n\tdescription: String,\n\tinput_type: FrontendGraphDataType,\n\tblank_assist: bool,\n\texposable: bool,\n}\n\nimpl<'a> ParameterWidgetsInfo<'a> {\n\tpub fn new(node_id: NodeId, index: usize, blank_assist: bool, context: &'a mut NodePropertiesContext) -> ParameterWidgetsInfo<'a> {\n\t\tlet (name, description) = context.network_interface.displayed_input_name_and_description(&node_id, index, context.selection_network_path);\n\t\tlet input_type = context\n\t\t\t.network_interface\n\t\t\t.input_type_not_invalid(&InputConnector::node(node_id, index), context.selection_network_path)\n\t\t\t.displayed_type();\n\t\tlet document_node = context.network_interface.document_node(&node_id, context.selection_network_path);\n\n\t\tParameterWidgetsInfo {\n\t\t\tpersistent_data: context.persistent_data,\n\t\t\tnetwork_interface: context.network_interface,\n\t\t\tselection_network_path: context.selection_network_path,\n\t\t\tdocument_node,\n\t\t\tnode_id,\n\t\t\tindex,\n\t\t\tname,\n\t\t\tdescription,\n\t\t\tinput_type,\n\t\t\tblank_assist,\n\t\t\texposable: true,\n\t\t}\n\t}\n\n\tpub fn is_exposed(&self) -> bool {\n\t\tself.document_node.and_then(|node| node.inputs.get(self.index)).map(|input| input.is_exposed()).unwrap_or(false)\n\t}\n}\n\npub mod choice {\n\tuse super::ParameterWidgetsInfo;\n\tuse crate::messages::tool::tool_messages::tool_prelude::*;\n\tuse graph_craft::document::value::TaggedValue;\n\tuse graphene_std::choice_type::{ChoiceTypeStatic, ChoiceWidgetHint};\n\tuse std::marker::PhantomData;\n\n\tpub trait WidgetFactory {\n\t\ttype Value: Clone + 'static;\n\n\t\tfn disabled(self, disabled: bool) -> Self;\n\n\t\tfn build<U, C>(&self, current: Self::Value, updater_factory: impl Fn() -> U, committer_factory: impl Fn() -> C) -> WidgetInstance\n\t\twhere\n\t\t\tU: Fn(&Self::Value) -> Message + 'static + Send + Sync,\n\t\t\tC: Fn(&()) -> Message + 'static + Send + Sync;\n\n\t\tfn description(&self) -> Option<&str>;\n\t}\n\n\tpub fn enum_choice<E: ChoiceTypeStatic>() -> EnumChoice<E> {\n\t\tEnumChoice {\n\t\t\tdisabled: false,\n\t\t\tphantom: PhantomData,\n\t\t}\n\t}\n\n\tpub struct EnumChoice<E> {\n\t\tdisabled: bool,\n\t\tphantom: PhantomData<E>,\n\t}\n\n\timpl<E: ChoiceTypeStatic + 'static> EnumChoice<E> {\n\t\tpub fn for_socket(self, parameter_info: ParameterWidgetsInfo) -> ForSocket<Self> {\n\t\t\tForSocket { widget_factory: self, parameter_info }\n\t\t}\n\n\t\t/// Not yet implemented!\n\t\tpub fn for_value(self, _current: E) -> ForValue<Self> {\n\t\t\ttodo!()\n\t\t}\n\n\t\tpub fn disabled(self, disabled: bool) -> Self {\n\t\t\tSelf { disabled, ..self }\n\t\t}\n\n\t\t/// Not yet implemented!\n\t\tpub fn into_menu_entries(self, _action: impl Fn(E) -> Message + 'static + Send + Sync) -> MenuListEntrySections {\n\t\t\ttodo!()\n\t\t}\n\n\t\tfn dropdown_menu<U, C>(&self, current: E, updater_factory: impl Fn() -> U, committer_factory: impl Fn() -> C) -> WidgetInstance\n\t\twhere\n\t\t\tU: Fn(&E) -> Message + 'static + Send + Sync,\n\t\t\tC: Fn(&()) -> Message + 'static + Send + Sync,\n\t\t{\n\t\t\tlet items = E::list()\n\t\t\t\t.iter()\n\t\t\t\t.map(|section| {\n\t\t\t\t\tsection\n\t\t\t\t\t\t.iter()\n\t\t\t\t\t\t.map(|(item, metadata)| {\n\t\t\t\t\t\t\tlet updater = updater_factory();\n\t\t\t\t\t\t\tlet committer = committer_factory();\n\t\t\t\t\t\t\tMenuListEntry::new(metadata.name).label(metadata.label).on_update(move |_| updater(item)).on_commit(committer)\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.collect()\n\t\t\t\t})\n\t\t\t\t.collect();\n\t\t\tDropdownInput::new(items).disabled(self.disabled).selected_index(Some(current.as_u32())).widget_instance()\n\t\t}\n\n\t\tfn radio_buttons<U, C>(&self, current: E, updater_factory: impl Fn() -> U, committer_factory: impl Fn() -> C) -> WidgetInstance\n\t\twhere\n\t\t\tU: Fn(&E) -> Message + 'static + Send + Sync,\n\t\t\tC: Fn(&()) -> Message + 'static + Send + Sync,\n\t\t{\n\t\t\tlet items = E::list()\n\t\t\t\t.iter()\n\t\t\t\t.flat_map(|section| section.iter())\n\t\t\t\t.map(|(item, var_meta)| {\n\t\t\t\t\tlet updater = updater_factory();\n\t\t\t\t\tlet committer = committer_factory();\n\t\t\t\t\tlet entry = RadioEntryData::new(var_meta.name)\n\t\t\t\t\t\t.on_update(move |_| updater(item))\n\t\t\t\t\t\t.on_commit(committer)\n\t\t\t\t\t\t.tooltip_label(var_meta.label)\n\t\t\t\t\t\t.tooltip_description(var_meta.description.unwrap_or_default());\n\t\t\t\t\tif let Some(icon) = var_meta.icon { entry.icon(icon) } else { entry.label(var_meta.label) }\n\t\t\t\t})\n\t\t\t\t.collect();\n\t\t\tRadioInput::new(items).selected_index(Some(current.as_u32())).widget_instance()\n\t\t}\n\t}\n\n\timpl<E: ChoiceTypeStatic + 'static> WidgetFactory for EnumChoice<E> {\n\t\ttype Value = E;\n\n\t\tfn disabled(self, disabled: bool) -> Self {\n\t\t\tSelf { disabled, ..self }\n\t\t}\n\n\t\tfn description(&self) -> Option<&str> {\n\t\t\tE::DESCRIPTION\n\t\t}\n\n\t\tfn build<U, C>(&self, current: Self::Value, updater_factory: impl Fn() -> U, committer_factory: impl Fn() -> C) -> WidgetInstance\n\t\twhere\n\t\t\tU: Fn(&Self::Value) -> Message + 'static + Send + Sync,\n\t\t\tC: Fn(&()) -> Message + 'static + Send + Sync,\n\t\t{\n\t\t\tmatch E::WIDGET_HINT {\n\t\t\t\tChoiceWidgetHint::Dropdown => self.dropdown_menu(current, updater_factory, committer_factory),\n\t\t\t\tChoiceWidgetHint::RadioButtons => self.radio_buttons(current, updater_factory, committer_factory),\n\t\t\t}\n\t\t}\n\t}\n\n\tpub struct ForSocket<'p, W> {\n\t\twidget_factory: W,\n\t\tparameter_info: ParameterWidgetsInfo<'p>,\n\t}\n\n\timpl<'p, W> ForSocket<'p, W>\n\twhere\n\t\tW: WidgetFactory,\n\t\tW::Value: Clone,\n\t\tfor<'a> &'a W::Value: TryFrom<&'a TaggedValue>,\n\t\tTaggedValue: From<W::Value>,\n\t{\n\t\tpub fn disabled(self, disabled: bool) -> Self {\n\t\t\tSelf {\n\t\t\t\twidget_factory: self.widget_factory.disabled(disabled),\n\t\t\t\t..self\n\t\t\t}\n\t\t}\n\n\t\tpub fn property_row(self) -> LayoutGroup {\n\t\t\tlet ParameterWidgetsInfo { document_node, node_id, index, .. } = self.parameter_info;\n\t\t\tlet Some(document_node) = document_node else {\n\t\t\t\tlog::error!(\"Could not get document node when building property row for node {node_id:?}\");\n\t\t\t\treturn LayoutGroup::row(Vec::new());\n\t\t\t};\n\n\t\t\tlet mut widgets = super::start_widgets(self.parameter_info);\n\n\t\t\tlet Some(input) = document_node.inputs.get(index) else {\n\t\t\t\tlog::warn!(\"A widget failed to be built because its node's input index is invalid.\");\n\t\t\t\treturn LayoutGroup::row(vec![]);\n\t\t\t};\n\n\t\t\tlet input: Option<W::Value> = input.as_non_exposed_value().and_then(|v| <&W::Value as TryFrom<&TaggedValue>>::try_from(v).ok()).cloned();\n\n\t\t\tif let Some(current) = input {\n\t\t\t\tlet committer = || super::commit_value;\n\t\t\t\tlet updater = || super::update_value(move |v: &W::Value| TaggedValue::from(v.clone()), node_id, index);\n\t\t\t\tlet widget = self.widget_factory.build(current, updater, committer);\n\t\t\t\twidgets.extend_from_slice(&[Separator::new(SeparatorStyle::Unrelated).widget_instance(), widget]);\n\t\t\t}\n\n\t\t\tlet mut row = LayoutGroup::row(widgets);\n\t\t\tif let Some(desc) = self.widget_factory.description() {\n\t\t\t\trow = row.with_tooltip_description(desc);\n\t\t\t}\n\t\t\trow\n\t\t}\n\t}\n\n\tpub struct ForValue<W>(PhantomData<W>);\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/node_graph/utility_types.rs",
    "content": "use graph_craft::document::NodeId;\nuse graph_craft::document::value::TaggedValue;\nuse graphene_std::Type;\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]\npub enum FrontendGraphDataType {\n\t#[default]\n\tGeneral,\n\tNumber,\n\tArtboard,\n\tGraphic,\n\tRaster,\n\tVector,\n\tColor,\n\tGradient,\n\tTypography,\n\tInvalid,\n}\n\nimpl FrontendGraphDataType {\n\tpub fn from_type(input: &Type) -> Self {\n\t\tmatch TaggedValue::from_type_or_none(input) {\n\t\t\tTaggedValue::U32(_)\n\t\t\t| TaggedValue::U64(_)\n\t\t\t| TaggedValue::F32(_)\n\t\t\t| TaggedValue::F64(_)\n\t\t\t| TaggedValue::DVec2(_)\n\t\t\t| TaggedValue::F64Array4(_)\n\t\t\t| TaggedValue::VecF64(_)\n\t\t\t| TaggedValue::VecDVec2(_)\n\t\t\t| TaggedValue::DAffine2(_) => Self::Number,\n\t\t\tTaggedValue::Artboard(_) => Self::Artboard,\n\t\t\tTaggedValue::Graphic(_) => Self::Graphic,\n\t\t\tTaggedValue::Raster(_) => Self::Raster,\n\t\t\tTaggedValue::Vector(_) => Self::Vector,\n\t\t\tTaggedValue::Color(_) => Self::Color,\n\t\t\tTaggedValue::Gradient(_) | TaggedValue::GradientTable(_) => Self::Gradient,\n\t\t\tTaggedValue::String(_) | TaggedValue::VecString(_) => Self::Typography,\n\t\t\t_ => Self::General,\n\t\t}\n\t}\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct FrontendGraphInput {\n\t#[serde(rename = \"dataType\")]\n\tpub data_type: FrontendGraphDataType,\n\tpub name: String,\n\tpub description: String,\n\t#[serde(rename = \"resolvedType\")]\n\tpub resolved_type: String,\n\t#[serde(rename = \"validTypes\")]\n\tpub valid_types: Vec<String>,\n\t#[serde(rename = \"connectedTo\")]\n\t/// Either \"nothing\", \"import #{index}\", or \"{node name} #{output_index}\".\n\tpub connected_to: String,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct FrontendGraphOutput {\n\t#[serde(rename = \"dataType\")]\n\tpub data_type: FrontendGraphDataType,\n\tpub name: String,\n\tpub description: String,\n\t#[serde(rename = \"resolvedType\")]\n\tpub resolved_type: String,\n\t/// If connected to an export, it is \"export index {index}\".\n\t/// If connected to a node, it is \"{node name} input {input_index}\".\n\t#[serde(rename = \"connectedTo\")]\n\tpub connected_to: Vec<String>,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct FrontendNode {\n\tpub id: graph_craft::document::NodeId,\n\t#[serde(rename = \"isLayer\")]\n\tpub is_layer: bool,\n\t#[serde(rename = \"canBeLayer\")]\n\tpub can_be_layer: bool,\n\tpub reference: Option<String>,\n\t#[serde(rename = \"displayName\")]\n\tpub display_name: String,\n\t#[serde(rename = \"implementationName\")]\n\tpub implementation_name: String,\n\t#[serde(rename = \"primaryInput\")]\n\tpub primary_input: Option<FrontendGraphInput>,\n\t#[serde(rename = \"exposedInputs\")]\n\tpub exposed_inputs: Vec<FrontendGraphInput>,\n\t#[serde(rename = \"primaryOutput\")]\n\tpub primary_output: Option<FrontendGraphOutput>,\n\t#[serde(rename = \"exposedOutputs\")]\n\tpub exposed_outputs: Vec<FrontendGraphOutput>,\n\t#[serde(rename = \"primaryInputConnectedToLayer\")]\n\tpub primary_input_connected_to_layer: bool,\n\t#[serde(rename = \"primaryOutputConnectedToLayer\")]\n\tpub primary_output_connected_to_layer: bool,\n\tpub position: (i32, i32),\n\tpub previewed: bool,\n\tpub visible: bool,\n\tpub locked: bool,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct FrontendNodeType {\n\tpub identifier: String,\n\tpub name: String,\n\tpub category: String,\n\t#[serde(rename = \"inputTypes\")]\n\tpub input_types: Vec<String>,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct DragStart {\n\tpub start_x: f64,\n\tpub start_y: f64,\n\tpub round_x: i32,\n\tpub round_y: i32,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct BoxSelection {\n\t#[serde(rename = \"startX\")]\n\tpub start_x: u32,\n\t#[serde(rename = \"startY\")]\n\tpub start_y: u32,\n\t#[serde(rename = \"endX\")]\n\tpub end_x: u32,\n\t#[serde(rename = \"endY\")]\n\tpub end_y: u32,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]\n#[serde(tag = \"type\", content = \"data\")]\npub enum ContextMenuData {\n\tModifyNode {\n\t\t#[serde(rename = \"nodeId\")]\n\t\tnode_id: NodeId,\n\t\t#[serde(rename = \"canBeLayer\")]\n\t\tcan_be_layer: bool,\n\t\t#[serde(rename = \"currentlyIsNode\")]\n\t\tcurrently_is_node: bool,\n\t\t#[serde(rename = \"hasSelectedLayers\")]\n\t\thas_selected_layers: bool,\n\t\t#[serde(rename = \"allSelectedLayersLocked\")]\n\t\tall_selected_layers_locked: bool,\n\t},\n\tCreateNode {\n\t\t#[serde(rename = \"compatibleType\")]\n\t\tcompatible_type: Option<String>,\n\t},\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct ContextMenuInformation {\n\t// Stores whether the context menu is open and its position in graph coordinates\n\t#[serde(rename = \"contextMenuCoordinates\")]\n\tpub context_menu_coordinates: (i32, i32),\n\t#[serde(rename = \"contextMenuData\")]\n\tpub context_menu_data: ContextMenuData,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize)]\npub struct NodeGraphErrorDiagnostic {\n\tpub position: (i32, i32),\n\tpub error: String,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize)]\npub struct FrontendClickTargets {\n\t#[serde(rename = \"nodeClickTargets\")]\n\tpub node_click_targets: Vec<String>,\n\t#[serde(rename = \"layerClickTargets\")]\n\tpub layer_click_targets: Vec<String>,\n\t#[serde(rename = \"connectorClickTargets\")]\n\tpub connector_click_targets: Vec<String>,\n\t#[serde(rename = \"iconClickTargets\")]\n\tpub icon_click_targets: Vec<String>,\n\t#[serde(rename = \"allNodesBoundingBox\")]\n\tpub all_nodes_bounding_box: String,\n\t#[serde(rename = \"modifyImportExport\")]\n\tpub modify_import_export: Vec<String>,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)]\npub enum Direction {\n\tUp,\n\tDown,\n\tLeft,\n\tRight,\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/overlays/grid_overlays.rs",
    "content": "use crate::messages::layout::utility_types::widget_prelude::*;\nuse crate::messages::portfolio::document::overlays::utility_types::OverlayContext;\nuse crate::messages::portfolio::document::utility_types::misc::{GridSnapping, GridType};\nuse crate::messages::prelude::*;\nuse glam::DVec2;\nuse graphene_std::raster::color::Color;\nuse graphene_std::renderer::Quad;\nuse graphene_std::vector::style::FillChoice;\n\nfn grid_overlay_rectangular(document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, spacing: DVec2) {\n\tlet origin = document.snapping_state.grid.origin;\n\tlet grid_color = document.snapping_state.grid.color.as_str();\n\tlet Some(spacing) = GridSnapping::compute_rectangle_spacing(spacing, &document.document_ptz) else {\n\t\treturn;\n\t};\n\tlet document_to_viewport = document\n\t\t.navigation_handler\n\t\t.calculate_offset_transform(overlay_context.viewport.center_in_viewport_space().into(), &document.document_ptz);\n\n\tlet bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.viewport.size().into()]);\n\n\tfor primary in 0..2 {\n\t\tlet secondary = 1 - primary;\n\t\tlet min = bounds.0.iter().map(|&corner| corner[secondary]).min_by(|a, b| a.partial_cmp(b).unwrap()).unwrap_or_default();\n\t\tlet max = bounds.0.iter().map(|&corner| corner[secondary]).max_by(|a, b| a.partial_cmp(b).unwrap()).unwrap_or_default();\n\t\tlet primary_start = bounds.0.iter().map(|&corner| corner[primary]).min_by(|a, b| a.partial_cmp(b).unwrap()).unwrap_or_default();\n\t\tlet primary_end = bounds.0.iter().map(|&corner| corner[primary]).max_by(|a, b| a.partial_cmp(b).unwrap()).unwrap_or_default();\n\t\tlet spacing = spacing[secondary];\n\t\tfor line_index in 0..=((max - min) / spacing).ceil() as i32 {\n\t\t\tlet secondary_pos = (((min - origin[secondary]) / spacing).ceil() + line_index as f64) * spacing + origin[secondary];\n\t\t\tlet start = if primary == 0 {\n\t\t\t\tDVec2::new(primary_start, secondary_pos)\n\t\t\t} else {\n\t\t\t\tDVec2::new(secondary_pos, primary_start)\n\t\t\t};\n\t\t\tlet end = if primary == 0 {\n\t\t\t\tDVec2::new(primary_end, secondary_pos)\n\t\t\t} else {\n\t\t\t\tDVec2::new(secondary_pos, primary_end)\n\t\t\t};\n\t\t\toverlay_context.line(document_to_viewport.transform_point2(start), document_to_viewport.transform_point2(end), Some(grid_color), None);\n\t\t}\n\t}\n}\n\n// In the best case, where the x distance/total dots is an integer, this will reduce draw requests from the current m(horizontal dots)*n(vertical dots) to m(horizontal lines) * 1(line changes).\n// In the worst case, where the x distance/total dots is an integer+0.5, then each pixel will require a new line, and requests will be m(horizontal lines)*n(line changes = horizontal dots).\n// The draw dashed line method will also be not grid aligned for tilted grids.\n// TODO: Potentially create an image and render the image onto the canvas a single time.\n// TODO: Implement this with a dashed line (`set_line_dash`), with integer spacing which is continuously adjusted to correct the accumulated error.\nfn grid_overlay_rectangular_dot(document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, spacing: DVec2) {\n\tlet origin = document.snapping_state.grid.origin;\n\tlet grid_color = document.snapping_state.grid.color.as_str();\n\tlet Some(spacing) = GridSnapping::compute_rectangle_spacing(spacing, &document.document_ptz) else {\n\t\treturn;\n\t};\n\tlet document_to_viewport = document\n\t\t.navigation_handler\n\t\t.calculate_offset_transform(overlay_context.viewport.center_in_viewport_space().into(), &document.document_ptz);\n\n\tlet bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.viewport.size().into()]);\n\n\tlet min = bounds.0.iter().map(|corner| corner.y).min_by(|a, b| a.partial_cmp(b).unwrap()).unwrap_or_default();\n\tlet max = bounds.0.iter().map(|corner| corner.y).max_by(|a, b| a.partial_cmp(b).unwrap()).unwrap_or_default();\n\n\tlet mut primary_start = bounds.0.iter().map(|corner| corner.x).min_by(|a, b| a.partial_cmp(b).unwrap()).unwrap_or_default();\n\tlet mut primary_end = bounds.0.iter().map(|corner| corner.x).max_by(|a, b| a.partial_cmp(b).unwrap()).unwrap_or_default();\n\n\tprimary_start = (primary_start / spacing.x).floor() * spacing.x + origin.x % spacing.x;\n\tprimary_end = (primary_end / spacing.x).floor() * spacing.x + origin.x % spacing.x;\n\n\t// Round to avoid floating point errors\n\tlet total_dots = ((primary_end - primary_start) / spacing.x).round();\n\n\tfor line_index in 0..=((max - min) / spacing.y).ceil() as i32 {\n\t\tlet secondary_pos = (((min - origin.y) / spacing.y).ceil() + line_index as f64) * spacing.y + origin.y;\n\t\tlet start = DVec2::new(primary_start, secondary_pos);\n\t\tlet end = DVec2::new(primary_end, secondary_pos);\n\n\t\tlet x_per_dot = (end.x - start.x) / total_dots;\n\t\tfor dot_index in 0..=total_dots as usize {\n\t\t\tlet exact_x = x_per_dot * dot_index as f64;\n\t\t\toverlay_context.pixel(document_to_viewport.transform_point2(DVec2::new(start.x + exact_x, start.y)).round(), Some(grid_color))\n\t\t}\n\t}\n}\n\nfn grid_overlay_isometric(document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, y_axis_spacing: f64, angle_a: f64, angle_b: f64) {\n\tlet grid_color = document.snapping_state.grid.color.as_str();\n\tlet cmp = |a: &f64, b: &f64| a.partial_cmp(b).unwrap();\n\tlet origin = document.snapping_state.grid.origin;\n\tlet document_to_viewport = document\n\t\t.navigation_handler\n\t\t.calculate_offset_transform(overlay_context.viewport.center_in_viewport_space().into(), &document.document_ptz);\n\n\tlet bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.viewport.size().into()]);\n\tlet tan_a = angle_a.to_radians().tan();\n\tlet tan_b = angle_b.to_radians().tan();\n\tlet spacing = DVec2::new(y_axis_spacing / (tan_a + tan_b), y_axis_spacing);\n\tlet Some(spacing_multiplier) = GridSnapping::compute_isometric_multiplier(y_axis_spacing, tan_a + tan_b, &document.document_ptz) else {\n\t\treturn;\n\t};\n\tlet isometric_spacing = spacing * spacing_multiplier;\n\n\tlet min_x = bounds.0.iter().map(|&corner| corner.x).min_by(cmp).unwrap_or_default();\n\tlet max_x = bounds.0.iter().map(|&corner| corner.x).max_by(cmp).unwrap_or_default();\n\tlet min_y = bounds.0.iter().map(|&corner| corner.y).min_by(cmp).unwrap_or_default();\n\tlet max_y = bounds.0.iter().map(|&corner| corner.y).max_by(cmp).unwrap_or_default();\n\tlet spacing = isometric_spacing.x;\n\tfor line_index in 0..=((max_x - min_x) / spacing).ceil() as i32 {\n\t\tlet x_pos = (((min_x - origin.x) / spacing).ceil() + line_index as f64) * spacing + origin.x;\n\t\tlet start = DVec2::new(x_pos, min_y);\n\t\tlet end = DVec2::new(x_pos, max_y);\n\t\toverlay_context.line(document_to_viewport.transform_point2(start), document_to_viewport.transform_point2(end), Some(grid_color), None);\n\t}\n\n\tfor (tan, multiply) in [(tan_a, -1.), (tan_b, 1.)] {\n\t\tlet project = |corner: &DVec2| corner.y + multiply * tan * (corner.x - origin.x);\n\t\tlet inverse_project = |corner: &DVec2| corner.y - tan * multiply * (corner.x - origin.x);\n\t\tlet min_y = bounds.0.into_iter().min_by(|a, b| inverse_project(a).partial_cmp(&inverse_project(b)).unwrap()).unwrap_or_default();\n\t\tlet max_y = bounds.0.into_iter().max_by(|a, b| inverse_project(a).partial_cmp(&inverse_project(b)).unwrap()).unwrap_or_default();\n\t\tlet spacing = isometric_spacing.y;\n\t\tlet lines = ((inverse_project(&max_y) - inverse_project(&min_y)) / spacing).ceil() as i32;\n\t\tfor line_index in 0..=lines {\n\t\t\tlet y_pos = (((inverse_project(&min_y) - origin.y) / spacing).ceil() + line_index as f64) * spacing + origin.y;\n\t\t\tlet start = DVec2::new(min_x, project(&DVec2::new(min_x, y_pos)));\n\t\t\tlet end = DVec2::new(max_x, project(&DVec2::new(max_x, y_pos)));\n\t\t\toverlay_context.line(document_to_viewport.transform_point2(start), document_to_viewport.transform_point2(end), Some(grid_color), None);\n\t\t}\n\t}\n}\n\nfn grid_overlay_isometric_dot(document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, y_axis_spacing: f64, angle_a: f64, angle_b: f64) {\n\tlet grid_color = document.snapping_state.grid.color.as_str();\n\tlet cmp = |a: &f64, b: &f64| a.partial_cmp(b).unwrap();\n\tlet origin = document.snapping_state.grid.origin;\n\tlet document_to_viewport = document\n\t\t.navigation_handler\n\t\t.calculate_offset_transform(overlay_context.viewport.center_in_viewport_space().into(), &document.document_ptz);\n\n\tlet bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.viewport.size().into()]);\n\tlet tan_a = angle_a.to_radians().tan();\n\tlet tan_b = angle_b.to_radians().tan();\n\tlet spacing = DVec2::new(y_axis_spacing / (tan_a + tan_b), y_axis_spacing);\n\tlet Some(spacing_multiplier) = GridSnapping::compute_isometric_multiplier(y_axis_spacing, tan_a + tan_b, &document.document_ptz) else {\n\t\treturn;\n\t};\n\tlet isometric_spacing = spacing * spacing_multiplier;\n\n\tlet min_x = bounds.0.iter().map(|&corner| corner.x).min_by(cmp).unwrap_or_default();\n\tlet max_x = bounds.0.iter().map(|&corner| corner.x).max_by(cmp).unwrap_or_default();\n\tlet spacing_x = isometric_spacing.x;\n\tlet tan = tan_a;\n\tlet multiply = -1.;\n\tlet project = |corner: &DVec2| corner.y + multiply * tan * (corner.x - origin.x);\n\tlet inverse_project = |corner: &DVec2| corner.y - tan * multiply * (corner.x - origin.x);\n\tlet min_y = bounds.0.into_iter().min_by(|a, b| inverse_project(a).partial_cmp(&inverse_project(b)).unwrap()).unwrap_or_default();\n\tlet max_y = bounds.0.into_iter().max_by(|a, b| inverse_project(a).partial_cmp(&inverse_project(b)).unwrap()).unwrap_or_default();\n\tlet spacing_y = isometric_spacing.y;\n\tlet lines = ((inverse_project(&max_y) - inverse_project(&min_y)) / spacing_y).ceil() as i32;\n\n\tlet cos_a = angle_a.to_radians().cos();\n\t// If cos_a is 0 then there will be no intersections and thus no dots should be drawn\n\tif cos_a.abs() <= 0.00001 {\n\t\treturn;\n\t}\n\tlet x_offset = (((min_x - origin.x) / spacing_x).ceil()) * spacing_x + origin.x - min_x;\n\tfor line_index in 0..=lines {\n\t\tlet y_pos = (((inverse_project(&min_y) - origin.y) / spacing_y).ceil() + line_index as f64) * spacing_y + origin.y;\n\t\tlet start = DVec2::new(min_x + x_offset, project(&DVec2::new(min_x + x_offset, y_pos)));\n\t\tlet end = DVec2::new(max_x + x_offset, project(&DVec2::new(max_x + x_offset, y_pos)));\n\n\t\toverlay_context.dashed_line(\n\t\t\tdocument_to_viewport.transform_point2(start),\n\t\t\tdocument_to_viewport.transform_point2(end),\n\t\t\tSome(grid_color),\n\t\t\tNone,\n\t\t\tSome(1.),\n\t\t\tSome((spacing_x / cos_a) * document_to_viewport.matrix2.x_axis.length() - 1.),\n\t\t\tNone,\n\t\t);\n\t}\n}\n\npub fn grid_overlay(document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) {\n\tmatch document.snapping_state.grid.grid_type {\n\t\tGridType::Rectangular { spacing } => {\n\t\t\tif document.snapping_state.grid.dot_display {\n\t\t\t\tgrid_overlay_rectangular_dot(document, overlay_context, spacing)\n\t\t\t} else {\n\t\t\t\tgrid_overlay_rectangular(document, overlay_context, spacing)\n\t\t\t}\n\t\t}\n\t\tGridType::Isometric { y_axis_spacing, angle_a, angle_b } => {\n\t\t\tif document.snapping_state.grid.dot_display {\n\t\t\t\tgrid_overlay_isometric_dot(document, overlay_context, y_axis_spacing, angle_a, angle_b)\n\t\t\t} else {\n\t\t\t\tgrid_overlay_isometric(document, overlay_context, y_axis_spacing, angle_a, angle_b)\n\t\t\t}\n\t\t}\n\t}\n}\n\npub fn overlay_options(grid: &GridSnapping) -> Vec<LayoutGroup> {\n\tlet mut widgets = Vec::new();\n\tfn update_val<I, F: Fn(&mut GridSnapping, &I)>(grid: &GridSnapping, update: F) -> impl Fn(&I) -> Message + use<I, F> {\n\t\tlet grid = grid.clone();\n\t\tmove |input: &I| {\n\t\t\tlet mut grid = grid.clone();\n\t\t\tupdate(&mut grid, input);\n\t\t\tDocumentMessage::GridOptions { options: grid }.into()\n\t\t}\n\t}\n\tlet update_origin = |grid, update: fn(&mut GridSnapping) -> Option<&mut f64>| {\n\t\tupdate_val::<NumberInput, _>(grid, move |grid, val| {\n\t\t\tif let Some(val) = val.value\n\t\t\t\t&& let Some(update) = update(grid)\n\t\t\t{\n\t\t\t\t*update = val;\n\t\t\t}\n\t\t})\n\t};\n\tlet update_display = |grid, update: fn(&mut GridSnapping) -> Option<&mut bool>| {\n\t\tupdate_val::<CheckboxInput, _>(grid, move |grid, checkbox| {\n\t\t\tif let Some(update) = update(grid) {\n\t\t\t\t*update = checkbox.checked;\n\t\t\t}\n\t\t})\n\t};\n\n\twidgets.push(LayoutGroup::row(vec![TextLabel::new(\"Grid\").bold(true).widget_instance()]));\n\n\twidgets.push(LayoutGroup::row(vec![\n\t\tTextLabel::new(\"Type\").table_align(true).widget_instance(),\n\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\tRadioInput::new(vec![\n\t\t\tRadioEntryData::new(\"rectangular\").label(\"Rectangular\").on_update(update_val(grid, |grid, _| {\n\t\t\t\tif let GridType::Isometric { y_axis_spacing, angle_a, angle_b } = grid.grid_type {\n\t\t\t\t\tgrid.isometric_y_spacing = y_axis_spacing;\n\t\t\t\t\tgrid.isometric_angle_a = angle_a;\n\t\t\t\t\tgrid.isometric_angle_b = angle_b;\n\t\t\t\t}\n\t\t\t\tgrid.grid_type = GridType::Rectangular { spacing: grid.rectangular_spacing };\n\t\t\t})),\n\t\t\tRadioEntryData::new(\"isometric\").label(\"Isometric\").on_update(update_val(grid, |grid, _| {\n\t\t\t\tif let GridType::Rectangular { spacing } = grid.grid_type {\n\t\t\t\t\tgrid.rectangular_spacing = spacing;\n\t\t\t\t}\n\t\t\t\tgrid.grid_type = GridType::Isometric {\n\t\t\t\t\ty_axis_spacing: grid.isometric_y_spacing,\n\t\t\t\t\tangle_a: grid.isometric_angle_a,\n\t\t\t\t\tangle_b: grid.isometric_angle_b,\n\t\t\t\t};\n\t\t\t})),\n\t\t])\n\t\t.min_width(200)\n\t\t.selected_index(Some(match grid.grid_type {\n\t\t\tGridType::Rectangular { .. } => 0,\n\t\t\tGridType::Isometric { .. } => 1,\n\t\t}))\n\t\t.widget_instance(),\n\t]));\n\n\tlet mut color_widgets = vec![\n\t\tTextLabel::new(\"Display\").table_align(true).widget_instance(),\n\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t];\n\tcolor_widgets.extend([\n\t\tCheckboxInput::new(grid.dot_display)\n\t\t\t.icon(\"GridDotted\")\n\t\t\t.tooltip_label(\"Display as Dotted Grid\")\n\t\t\t.on_update(update_display(grid, |grid| Some(&mut grid.dot_display)))\n\t\t\t.widget_instance(),\n\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t]);\n\tcolor_widgets.push(\n\t\tColorInput::new(FillChoice::Solid(Color::from_hex_str(&grid.color).unwrap_or(Color::BLACK)))\n\t\t\t.tooltip_label(\"Grid Display Color\")\n\t\t\t.allow_none(false)\n\t\t\t.on_update(update_val::<ColorInput, _>(grid, |grid, color| {\n\t\t\t\tif let Some(color) = color.value.as_solid() {\n\t\t\t\t\tgrid.color = format!(\"#{}\", color.to_rgba_hex_srgb_from_gamma());\n\t\t\t\t}\n\t\t\t}))\n\t\t\t.widget_instance(),\n\t);\n\twidgets.push(LayoutGroup::row(color_widgets));\n\n\twidgets.push(LayoutGroup::row(vec![\n\t\tTextLabel::new(\"Origin\").table_align(true).widget_instance(),\n\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\tNumberInput::new(Some(grid.origin.x))\n\t\t\t.label(\"X\")\n\t\t\t.unit(\" px\")\n\t\t\t.min_width(98)\n\t\t\t.on_update(update_origin(grid, |grid| Some(&mut grid.origin.x)))\n\t\t\t.widget_instance(),\n\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\tNumberInput::new(Some(grid.origin.y))\n\t\t\t.label(\"Y\")\n\t\t\t.unit(\" px\")\n\t\t\t.min_width(98)\n\t\t\t.on_update(update_origin(grid, |grid| Some(&mut grid.origin.y)))\n\t\t\t.widget_instance(),\n\t]));\n\n\tmatch grid.grid_type {\n\t\tGridType::Rectangular { spacing } => widgets.push(LayoutGroup::row(vec![\n\t\t\tTextLabel::new(\"Spacing\").table_align(true).widget_instance(),\n\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\tNumberInput::new(Some(spacing.x))\n\t\t\t\t.label(\"X\")\n\t\t\t\t.unit(\" px\")\n\t\t\t\t.min(0.)\n\t\t\t\t.min_width(98)\n\t\t\t\t.on_update(update_origin(grid, |grid| grid.grid_type.rectangular_spacing().map(|spacing| &mut spacing.x)))\n\t\t\t\t.widget_instance(),\n\t\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\t\tNumberInput::new(Some(spacing.y))\n\t\t\t\t.label(\"Y\")\n\t\t\t\t.unit(\" px\")\n\t\t\t\t.min(0.)\n\t\t\t\t.min_width(98)\n\t\t\t\t.on_update(update_origin(grid, |grid| grid.grid_type.rectangular_spacing().map(|spacing| &mut spacing.y)))\n\t\t\t\t.widget_instance(),\n\t\t])),\n\t\tGridType::Isometric { y_axis_spacing, angle_a, angle_b } => {\n\t\t\twidgets.push(LayoutGroup::row(vec![\n\t\t\t\tTextLabel::new(\"Y Spacing\").table_align(true).widget_instance(),\n\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\tNumberInput::new(Some(y_axis_spacing))\n\t\t\t\t\t.unit(\" px\")\n\t\t\t\t\t.min(0.)\n\t\t\t\t\t.min_width(200)\n\t\t\t\t\t.on_update(update_origin(grid, |grid| grid.grid_type.isometric_y_spacing()))\n\t\t\t\t\t.widget_instance(),\n\t\t\t]));\n\t\t\twidgets.push(LayoutGroup::row(vec![\n\t\t\t\tTextLabel::new(\"Angles\").table_align(true).widget_instance(),\n\t\t\t\tSeparator::new(SeparatorStyle::Unrelated).widget_instance(),\n\t\t\t\tNumberInput::new(Some(angle_a))\n\t\t\t\t\t.unit(\"°\")\n\t\t\t\t\t.min_width(98)\n\t\t\t\t\t.on_update(update_origin(grid, |grid| grid.grid_type.angle_a()))\n\t\t\t\t\t.widget_instance(),\n\t\t\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\t\t\tNumberInput::new(Some(angle_b))\n\t\t\t\t\t.unit(\"°\")\n\t\t\t\t\t.min_width(98)\n\t\t\t\t\t.on_update(update_origin(grid, |grid| grid.grid_type.angle_b()))\n\t\t\t\t\t.widget_instance(),\n\t\t\t]));\n\t\t}\n\t}\n\n\twidgets\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/overlays/mod.rs",
    "content": "pub mod grid_overlays;\nmod overlays_message;\nmod overlays_message_handler;\npub mod utility_functions;\n// Native (non‑wasm)\n#[cfg(not(target_family = \"wasm\"))]\npub mod utility_types_native;\n#[cfg(not(target_family = \"wasm\"))]\npub use utility_types_native as utility_types;\n\n// WebAssembly\n#[cfg(target_family = \"wasm\")]\npub mod utility_types_web;\n#[cfg(target_family = \"wasm\")]\npub use utility_types_web as utility_types;\n\n#[doc(inline)]\npub use overlays_message::{OverlaysMessage, OverlaysMessageDiscriminant};\n#[doc(inline)]\npub use overlays_message_handler::{OverlaysMessageContext, OverlaysMessageHandler};\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/overlays/overlays_message.rs",
    "content": "use super::utility_types::{OverlayProvider, empty_provider};\nuse crate::messages::prelude::*;\n\n#[impl_message(Message, DocumentMessage, Overlays)]\n#[derive(derivative::Derivative, Clone, serde::Serialize, serde::Deserialize)]\n#[derivative(Debug, PartialEq)]\npub enum OverlaysMessage {\n\tDraw,\n\t// Serde functionality isn't used but is required by the message system macros\n\tAddProvider {\n\t\t#[serde(skip, default = \"empty_provider\")]\n\t\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\t\tprovider: OverlayProvider,\n\t},\n\tRemoveProvider {\n\t\t#[serde(skip, default = \"empty_provider\")]\n\t\t#[derivative(Debug = \"ignore\", PartialEq = \"ignore\")]\n\t\tprovider: OverlayProvider,\n\t},\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs",
    "content": "use super::utility_types::{OverlayProvider, OverlaysVisibilitySettings};\nuse crate::messages::prelude::*;\n\n#[derive(ExtractField)]\npub struct OverlaysMessageContext<'a> {\n\tpub visibility_settings: OverlaysVisibilitySettings,\n\tpub viewport: &'a ViewportMessageHandler,\n}\n\n#[derive(Debug, Clone, Default, ExtractField)]\npub struct OverlaysMessageHandler {\n\tpub overlay_providers: HashSet<OverlayProvider>,\n\t#[cfg(target_family = \"wasm\")]\n\tcanvas: Option<web_sys::HtmlCanvasElement>,\n\t#[cfg(target_family = \"wasm\")]\n\tcontext: Option<web_sys::CanvasRenderingContext2d>,\n}\n\n#[message_handler_data]\nimpl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMessageHandler {\n\tfn process_message(&mut self, message: OverlaysMessage, responses: &mut VecDeque<Message>, context: OverlaysMessageContext) {\n\t\tlet OverlaysMessageContext { visibility_settings, viewport, .. } = context;\n\n\t\tmatch message {\n\t\t\t#[cfg(target_family = \"wasm\")]\n\t\t\tOverlaysMessage::Draw => {\n\t\t\t\tuse super::utility_functions::overlay_canvas_element;\n\t\t\t\tuse super::utility_types::OverlayContext;\n\t\t\t\tuse crate::messages::viewport::{Position, ToPhysical};\n\t\t\t\tuse wasm_bindgen::JsCast;\n\n\t\t\t\tlet canvas = match &self.canvas {\n\t\t\t\t\tSome(canvas) => canvas,\n\t\t\t\t\tNone => {\n\t\t\t\t\t\tlet Some(new_canvas) = overlay_canvas_element() else { return };\n\t\t\t\t\t\tself.canvas.get_or_insert(new_canvas)\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tlet canvas_context = self.context.get_or_insert_with(|| {\n\t\t\t\t\tlet canvas_context = canvas.get_context(\"2d\").ok().flatten().expect(\"Failed to get canvas context\");\n\t\t\t\t\tcanvas_context.dyn_into().expect(\"Context should be a canvas 2d context\")\n\t\t\t\t});\n\n\t\t\t\tlet size_logical = viewport.size();\n\t\t\t\tlet size_physical = size_logical.to_physical();\n\t\t\t\tlet width = size_logical.x().max(size_physical.x());\n\t\t\t\tlet height = size_logical.y().max(size_physical.y());\n\n\t\t\t\tcanvas_context.clear_rect(0., 0., width, height);\n\n\t\t\t\tif visibility_settings.all() {\n\t\t\t\t\tresponses.add(DocumentMessage::GridOverlays {\n\t\t\t\t\t\tcontext: OverlayContext {\n\t\t\t\t\t\t\trender_context: canvas_context.clone(),\n\t\t\t\t\t\t\tvisibility_settings: visibility_settings.clone(),\n\t\t\t\t\t\t\tviewport: *viewport,\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t\tfor provider in &self.overlay_providers {\n\t\t\t\t\t\tresponses.add(provider(OverlayContext {\n\t\t\t\t\t\t\trender_context: canvas_context.clone(),\n\t\t\t\t\t\t\tvisibility_settings: visibility_settings.clone(),\n\t\t\t\t\t\t\tviewport: *viewport,\n\t\t\t\t\t\t}));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t#[cfg(all(not(target_family = \"wasm\"), not(test)))]\n\t\t\tOverlaysMessage::Draw => {\n\t\t\t\tuse super::utility_types::OverlayContext;\n\n\t\t\t\tlet overlay_context = OverlayContext::new(*viewport, visibility_settings);\n\n\t\t\t\tif visibility_settings.all() {\n\t\t\t\t\tresponses.add(DocumentMessage::GridOverlays { context: overlay_context.clone() });\n\n\t\t\t\t\tfor provider in &self.overlay_providers {\n\t\t\t\t\t\tresponses.add(provider(overlay_context.clone()));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tresponses.add(FrontendMessage::RenderOverlays { context: overlay_context });\n\t\t\t}\n\t\t\t#[cfg(all(not(target_family = \"wasm\"), test))]\n\t\t\tOverlaysMessage::Draw => {\n\t\t\t\tlet _ = (responses, visibility_settings, viewport);\n\t\t\t}\n\t\t\tOverlaysMessage::AddProvider { provider: message } => {\n\t\t\t\tself.overlay_providers.insert(message);\n\t\t\t}\n\t\t\tOverlaysMessage::RemoveProvider { provider: message } => {\n\t\t\t\tself.overlay_providers.remove(&message);\n\t\t\t}\n\t\t}\n\t}\n\n\tadvertise_actions!(OverlaysMessage;);\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/overlays/utility_functions.rs",
    "content": "use super::utility_types::{DrawHandles, OverlayContext};\nuse crate::consts::HIDE_HANDLE_DISTANCE;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;\nuse crate::messages::tool::common_functionality::shape_editor::{SelectedLayerState, ShapeState};\nuse crate::messages::tool::tool_messages::tool_prelude::DocumentMessageHandler;\nuse glam::{DAffine2, DVec2};\nuse graphene_std::subpath::{Bezier, BezierHandles};\nuse graphene_std::text::{Font, FontCache, TextAlign, TextContext, TypesettingConfig};\nuse graphene_std::vector::misc::ManipulatorPointId;\nuse graphene_std::vector::{PointId, SegmentId, Vector};\nuse std::collections::HashMap;\nuse std::sync::{LazyLock, Mutex};\nuse wasm_bindgen::JsCast;\n\npub fn overlay_canvas_element() -> Option<web_sys::HtmlCanvasElement> {\n\tlet window = web_sys::window()?;\n\tlet document = window.document()?;\n\tlet canvas = document.query_selector(\"[data-overlays-canvas]\").ok().flatten()?;\n\tcanvas.dyn_into::<web_sys::HtmlCanvasElement>().ok()\n}\n\npub fn overlay_canvas_context() -> web_sys::CanvasRenderingContext2d {\n\tlet create_context = || {\n\t\tlet context = overlay_canvas_element()?.get_context(\"2d\").ok().flatten()?;\n\t\tcontext.dyn_into().ok()\n\t};\n\tcreate_context().expect(\"Failed to get canvas context\")\n}\n\npub fn selected_segments(network_interface: &NodeNetworkInterface, shape_editor: &ShapeState) -> HashMap<LayerNodeIdentifier, Vec<SegmentId>> {\n\tlet mut map = HashMap::new();\n\n\tfor (layer, state) in &shape_editor.selected_shape_state {\n\t\tlet Some(vector) = network_interface.compute_modified_vector(*layer) else { continue };\n\t\tlet selected_segments = selected_segments_for_layer(&vector, state);\n\n\t\tmap.insert(*layer, selected_segments);\n\t}\n\n\tmap\n}\n\npub fn selected_segments_for_layer(vector: &Vector, state: &SelectedLayerState) -> Vec<SegmentId> {\n\tlet selected_anchors = state\n\t\t.selected_points()\n\t\t.filter_map(|point| if let ManipulatorPointId::Anchor(p) = point { Some(p) } else { None })\n\t\t.collect::<Vec<_>>();\n\n\t// Collect the segments whose handles are selected\n\tlet mut selected_segments = state\n\t\t.selected_points()\n\t\t.filter_map(|point_id| match point_id {\n\t\t\tManipulatorPointId::PrimaryHandle(segment_id) | ManipulatorPointId::EndHandle(segment_id) => Some(segment_id),\n\t\t\tManipulatorPointId::Anchor(_) => None,\n\t\t})\n\t\t.collect::<Vec<_>>();\n\n\t// Adding segments which are are connected to selected anchors\n\tfor (segment_id, _bezier, start, end) in vector.segment_bezier_iter() {\n\t\tif selected_anchors.contains(&start) || selected_anchors.contains(&end) {\n\t\t\tselected_segments.push(segment_id);\n\t\t}\n\t}\n\tselected_segments\n}\n\nfn overlay_bezier_handles(bezier: Bezier, segment_id: SegmentId, transform: DAffine2, is_selected: impl Fn(ManipulatorPointId) -> bool, overlay_context: &mut OverlayContext) {\n\tlet bezier = bezier.apply_transformation(|point| transform.transform_point2(point));\n\tlet not_under_anchor = |position: DVec2, anchor: DVec2| position.distance_squared(anchor) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE;\n\n\tmatch bezier.handles {\n\t\tBezierHandles::Quadratic { handle } if not_under_anchor(handle, bezier.start) && not_under_anchor(handle, bezier.end) => {\n\t\t\toverlay_context.line(handle, bezier.start, None, None);\n\t\t\toverlay_context.line(handle, bezier.end, None, None);\n\t\t\toverlay_context.manipulator_handle(handle, is_selected(ManipulatorPointId::PrimaryHandle(segment_id)), None);\n\t\t}\n\t\tBezierHandles::Cubic { handle_start, handle_end } => {\n\t\t\tif not_under_anchor(handle_start, bezier.start) {\n\t\t\t\toverlay_context.line(handle_start, bezier.start, None, None);\n\t\t\t\toverlay_context.manipulator_handle(handle_start, is_selected(ManipulatorPointId::PrimaryHandle(segment_id)), None);\n\t\t\t}\n\t\t\tif not_under_anchor(handle_end, bezier.end) {\n\t\t\t\toverlay_context.line(handle_end, bezier.end, None, None);\n\t\t\t\toverlay_context.manipulator_handle(handle_end, is_selected(ManipulatorPointId::EndHandle(segment_id)), None);\n\t\t\t}\n\t\t}\n\t\t_ => {}\n\t}\n}\n\nfn overlay_bezier_handle_specific_point(\n\tbezier: Bezier,\n\tsegment_id: SegmentId,\n\t(start, end): (PointId, PointId),\n\tpoint_to_render: PointId,\n\ttransform: DAffine2,\n\tis_selected: impl Fn(ManipulatorPointId) -> bool,\n\toverlay_context: &mut OverlayContext,\n) {\n\tlet bezier = bezier.apply_transformation(|point| transform.transform_point2(point));\n\tlet not_under_anchor = |position: DVec2, anchor: DVec2| position.distance_squared(anchor) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE;\n\n\tmatch bezier.handles {\n\t\tBezierHandles::Quadratic { handle } => {\n\t\t\tif not_under_anchor(handle, bezier.start) && not_under_anchor(handle, bezier.end) {\n\t\t\t\tlet end = if start == point_to_render { bezier.start } else { bezier.end };\n\t\t\t\toverlay_context.line(handle, end, None, None);\n\t\t\t\toverlay_context.manipulator_handle(handle, is_selected(ManipulatorPointId::PrimaryHandle(segment_id)), None);\n\t\t\t}\n\t\t}\n\t\tBezierHandles::Cubic { handle_start, handle_end } => {\n\t\t\tif not_under_anchor(handle_start, bezier.start) && (point_to_render == start) {\n\t\t\t\toverlay_context.line(handle_start, bezier.start, None, None);\n\t\t\t\toverlay_context.manipulator_handle(handle_start, is_selected(ManipulatorPointId::PrimaryHandle(segment_id)), None);\n\t\t\t}\n\t\t\tif not_under_anchor(handle_end, bezier.end) && (point_to_render == end) {\n\t\t\t\toverlay_context.line(handle_end, bezier.end, None, None);\n\t\t\t\toverlay_context.manipulator_handle(handle_end, is_selected(ManipulatorPointId::EndHandle(segment_id)), None);\n\t\t\t}\n\t\t}\n\t\t_ => {}\n\t}\n}\n\npub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandles, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext) {\n\tlet display_path = overlay_context.visibility_settings.path();\n\tlet display_handles = overlay_context.visibility_settings.handles();\n\tlet display_anchors = overlay_context.visibility_settings.anchors();\n\n\tfor layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) {\n\t\tlet Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };\n\t\tlet transform = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);\n\t\tif display_path {\n\t\t\toverlay_context.outline_vector(&vector, transform);\n\t\t}\n\n\t\tlet selected_shape_state = shape_editor.selected_shape_state.entry(layer).or_default();\n\t\t// Get the selected segments and then add a bold line overlay on them\n\t\tfor (segment_id, bezier, _, _) in vector.segment_iter() {\n\t\t\tif selected_shape_state.is_segment_selected(segment_id) {\n\t\t\t\toverlay_context.outline_select_bezier(bezier, transform);\n\t\t\t}\n\t\t}\n\n\t\tlet is_selected = |point: ManipulatorPointId| selected_shape_state.is_point_selected(point);\n\n\t\tif display_handles {\n\t\t\tlet opposite_handles_data = selected_shape_state.selected_points().filter_map(|point_id| vector.adjacent_segment(&point_id)).collect::<Vec<_>>();\n\n\t\t\tmatch draw_handles {\n\t\t\t\tDrawHandles::All => {\n\t\t\t\t\tvector.segment_bezier_iter().for_each(|(segment_id, bezier, _start, _end)| {\n\t\t\t\t\t\toverlay_bezier_handles(bezier, segment_id, transform, is_selected, overlay_context);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tDrawHandles::SelectedAnchors(ref selected_segments) => {\n\t\t\t\t\tlet Some(focused_segments) = selected_segments.get(&layer) else { continue };\n\n\t\t\t\t\tvector\n\t\t\t\t\t\t.segment_bezier_iter()\n\t\t\t\t\t\t.filter(|(segment_id, ..)| focused_segments.contains(segment_id))\n\t\t\t\t\t\t.for_each(|(segment_id, bezier, _start, _end)| {\n\t\t\t\t\t\t\toverlay_bezier_handles(bezier, segment_id, transform, is_selected, overlay_context);\n\t\t\t\t\t\t});\n\n\t\t\t\t\tfor (segment_id, bezier, start, end) in vector.segment_bezier_iter() {\n\t\t\t\t\t\tif let Some((corresponding_anchor, _)) = opposite_handles_data.iter().find(|(_, adj_segment_id)| adj_segment_id == &segment_id) {\n\t\t\t\t\t\t\toverlay_bezier_handle_specific_point(bezier, segment_id, (start, end), *corresponding_anchor, transform, is_selected, overlay_context);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tDrawHandles::FrontierHandles(ref segment_endpoints_by_layer) => {\n\t\t\t\t\tlet Some(segment_endpoints) = segment_endpoints_by_layer.get(&layer) else { continue };\n\n\t\t\t\t\tvector\n\t\t\t\t\t\t.segment_bezier_iter()\n\t\t\t\t\t\t.filter(|(segment_id, ..)| segment_endpoints.contains_key(segment_id))\n\t\t\t\t\t\t.for_each(|(segment_id, bezier, start, end)| {\n\t\t\t\t\t\t\tif segment_endpoints.get(&segment_id).unwrap().len() == 1 {\n\t\t\t\t\t\t\t\tlet point_to_render = segment_endpoints.get(&segment_id).unwrap()[0];\n\t\t\t\t\t\t\t\toverlay_bezier_handle_specific_point(bezier, segment_id, (start, end), point_to_render, transform, is_selected, overlay_context);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\toverlay_bezier_handles(bezier, segment_id, transform, is_selected, overlay_context);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tDrawHandles::None => {}\n\t\t\t}\n\t\t}\n\n\t\tif display_anchors {\n\t\t\tfor (&id, &position) in vector.point_domain.ids().iter().zip(vector.point_domain.positions()) {\n\t\t\t\toverlay_context.manipulator_anchor(transform.transform_point2(position), is_selected(ManipulatorPointId::Anchor(id)), None);\n\t\t\t}\n\t\t}\n\t}\n}\n\npub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext) {\n\tif !overlay_context.visibility_settings.anchors() {\n\t\treturn;\n\t}\n\n\tfor layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) {\n\t\tlet Some(vector) = document.network_interface.compute_modified_vector(layer) else {\n\t\t\tcontinue;\n\t\t};\n\t\tlet transform = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);\n\t\tlet selected = shape_editor.selected_shape_state.get(&layer);\n\t\tlet is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_point_selected(point));\n\n\t\tfor point in vector.anchor_endpoints() {\n\t\t\tlet Some(position) = vector.point_domain.position_from_id(point) else { continue };\n\t\t\tlet position = transform.transform_point2(position);\n\t\t\toverlay_context.manipulator_anchor(position, is_selected(selected, ManipulatorPointId::Anchor(point)), None);\n\t\t}\n\t}\n}\n\n// Global lazy initialized font cache and text context\npub static GLOBAL_FONT_CACHE: LazyLock<FontCache> = LazyLock::new(|| {\n\tlet mut font_cache = FontCache::default();\n\t// Initialize with the hardcoded font used by overlay text\n\tconst FONT_DATA: &[u8] = include_bytes!(\"source-sans-pro-regular.ttf\");\n\tlet font = Font::new(\"Source Sans Pro\".to_string(), \"Regular\".to_string());\n\tfont_cache.insert(font, FONT_DATA.to_vec());\n\tfont_cache\n});\n\npub static GLOBAL_TEXT_CONTEXT: LazyLock<Mutex<TextContext>> = LazyLock::new(|| Mutex::new(TextContext::default()));\n\npub fn text_width(text: &str, font_size: f64) -> f64 {\n\tlet typesetting = TypesettingConfig {\n\t\tfont_size,\n\t\tline_height_ratio: 1.2,\n\t\tcharacter_spacing: 0.0,\n\t\tmax_width: None,\n\t\tmax_height: None,\n\t\ttilt: 0.0,\n\t\talign: TextAlign::Left,\n\t};\n\n\t// Load Source Sans Pro font data\n\t// TODO: Grab this from the node_modules folder (either with `include_bytes!` or ideally at runtime) instead of checking the font file into the repo.\n\t// TODO: And maybe use the WOFF2 version (if it's supported) for its smaller, compressed file size.\n\tlet font = Font::new(\"Source Sans Pro\".to_string(), \"Regular\".to_string());\n\tlet mut text_context = GLOBAL_TEXT_CONTEXT.lock().expect(\"Failed to lock global text context\");\n\tlet bounds = text_context.bounding_box(text, &font, &GLOBAL_FONT_CACHE, typesetting, false);\n\tbounds.x\n}\n\npub fn hex_to_rgba_u8(hex: &str) -> [u8; 4] {\n\tlet hex = hex.trim().trim_start_matches('#');\n\tif hex.len() != 6 && hex.len() != 8 {\n\t\treturn [0, 0, 0, 255];\n\t}\n\tlet r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(0);\n\tlet g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(0);\n\tlet b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(0);\n\tlet a = if hex.len() >= 8 { u8::from_str_radix(&hex[6..8], 16).unwrap_or(255) } else { 255 };\n\t[r, g, b, a]\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/overlays/utility_types_native.rs",
    "content": "use crate::consts::{\n\tARC_SWEEP_GIZMO_RADIUS, COLOR_OVERLAY_BLACK, COLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_50, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_WHITE_05, COLOR_OVERLAY_YELLOW,\n\tCOLOR_OVERLAY_YELLOW_DULL, COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, DOWEL_PIN_RADIUS,\n\tGRADIENT_MIDPOINT_DIAMOND_RADIUS, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, RESIZE_HANDLE_SIZE, SKEW_TRIANGLE_OFFSET, SKEW_TRIANGLE_SIZE,\n};\nuse crate::messages::portfolio::document::overlays::utility_functions::{GLOBAL_FONT_CACHE, GLOBAL_TEXT_CONTEXT, hex_to_rgba_u8};\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::prelude::Message;\nuse crate::messages::prelude::ViewportMessageHandler;\nuse core::borrow::Borrow;\nuse core::f64::consts::{FRAC_PI_2, PI, TAU};\nuse glam::{DAffine2, DVec2};\nuse graphene_std::math::quad::Quad;\nuse graphene_std::subpath::{self, Subpath};\nuse graphene_std::table::Table;\nuse graphene_std::text::{Font, TextAlign, TypesettingConfig};\nuse graphene_std::vector::click_target::ClickTargetType;\nuse graphene_std::vector::misc::point_to_dvec2;\nuse graphene_std::vector::{PointId, SegmentId, Vector};\nuse kurbo::{self, BezPath, ParamCurve};\nuse kurbo::{Affine, PathSeg};\nuse std::collections::HashMap;\nuse std::sync::{Arc, Mutex, MutexGuard};\nuse vello::Scene;\nuse vello::peniko;\n\n// TODO Remove duplicated definition of this in `utility_types_web.rs`\npub type OverlayProvider = fn(OverlayContext) -> Message;\n\n// TODO Remove duplicated definition of this in `utility_types_web.rs`\npub fn empty_provider() -> OverlayProvider {\n\t|_| Message::NoOp\n}\n\n// TODO Remove duplicated definition of this in `utility_types_web.rs`\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub enum GizmoEmphasis {\n\tRegular,\n\tHovered,\n\tActive,\n}\n\n// TODO Remove duplicated definition of this in `utility_types_web.rs`\n/// Types of overlays used by DocumentMessage to enable/disable the selected set of viewport overlays.\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum OverlaysType {\n\tArtboardName,\n\tCompassRose,\n\tQuickMeasurement,\n\tTransformMeasurement,\n\tTransformCage,\n\tHoverOutline,\n\tSelectionOutline,\n\tLayerOriginCross,\n\tPivot,\n\tOrigin,\n\tPath,\n\tAnchors,\n\tHandles,\n}\n\n// TODO Remove duplicated definition of this in `utility_types_web.rs`\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Copy, Clone, Debug, serde::Serialize, serde::Deserialize)]\n#[serde(default)]\npub struct OverlaysVisibilitySettings {\n\tpub all: bool,\n\tpub artboard_name: bool,\n\tpub compass_rose: bool,\n\tpub quick_measurement: bool,\n\tpub transform_measurement: bool,\n\tpub transform_cage: bool,\n\tpub hover_outline: bool,\n\tpub selection_outline: bool,\n\tpub layer_origin_cross: bool,\n\tpub pivot: bool,\n\tpub origin: bool,\n\tpub path: bool,\n\tpub anchors: bool,\n\tpub handles: bool,\n}\n\n// TODO Remove duplicated definition of this in `utility_types_web.rs`\nimpl Default for OverlaysVisibilitySettings {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tall: true,\n\t\t\tartboard_name: true,\n\t\t\tcompass_rose: true,\n\t\t\tquick_measurement: true,\n\t\t\ttransform_measurement: true,\n\t\t\ttransform_cage: true,\n\t\t\thover_outline: true,\n\t\t\tselection_outline: true,\n\t\t\tlayer_origin_cross: true,\n\t\t\tpivot: true,\n\t\t\torigin: true,\n\t\t\tpath: true,\n\t\t\tanchors: true,\n\t\t\thandles: true,\n\t\t}\n\t}\n}\n\n// TODO Remove duplicated definition of this in `utility_types_web.rs`\nimpl OverlaysVisibilitySettings {\n\tpub fn all(&self) -> bool {\n\t\tself.all\n\t}\n\n\tpub fn artboard_name(&self) -> bool {\n\t\tself.all && self.artboard_name\n\t}\n\n\tpub fn compass_rose(&self) -> bool {\n\t\tself.all && self.compass_rose\n\t}\n\n\tpub fn quick_measurement(&self) -> bool {\n\t\tself.all && self.quick_measurement\n\t}\n\n\tpub fn transform_measurement(&self) -> bool {\n\t\tself.all && self.transform_measurement\n\t}\n\n\tpub fn transform_cage(&self) -> bool {\n\t\tself.all && self.transform_cage\n\t}\n\n\tpub fn hover_outline(&self) -> bool {\n\t\tself.all && self.hover_outline\n\t}\n\n\tpub fn selection_outline(&self) -> bool {\n\t\tself.all && self.selection_outline\n\t}\n\n\tpub fn layer_origin_cross(&self) -> bool {\n\t\tself.all && self.layer_origin_cross\n\t}\n\n\tpub fn pivot(&self) -> bool {\n\t\tself.all && self.pivot\n\t}\n\n\tpub fn origin(&self) -> bool {\n\t\tself.all && self.origin\n\t}\n\n\tpub fn path(&self) -> bool {\n\t\tself.all && self.path\n\t}\n\n\tpub fn anchors(&self) -> bool {\n\t\tself.all && self.anchors\n\t}\n\n\tpub fn handles(&self) -> bool {\n\t\tself.all && self.anchors && self.handles\n\t}\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(serde::Serialize, serde::Deserialize)]\npub struct OverlayContext {\n\t// Serde functionality isn't used but is required by the message system macros\n\t#[serde(skip)]\n\tinternal: Arc<Mutex<OverlayContextInternal>>,\n\tpub viewport: ViewportMessageHandler,\n\tpub visibility_settings: OverlaysVisibilitySettings,\n}\n\nimpl Clone for OverlayContext {\n\tfn clone(&self) -> Self {\n\t\tlet internal = self.internal.lock().expect(\"Failed to lock internal overlay context\");\n\t\tlet visibility_settings = internal.visibility_settings;\n\t\tdrop(internal); // Explicitly release the lock before cloning the Arc<Mutex<_>>\n\t\tSelf {\n\t\t\tinternal: self.internal.clone(),\n\t\t\tviewport: self.viewport,\n\t\t\tvisibility_settings,\n\t\t}\n\t}\n}\n\n// Manual implementations since Scene doesn't implement PartialEq or Debug\nimpl PartialEq for OverlayContext {\n\tfn eq(&self, other: &Self) -> bool {\n\t\tself.viewport == other.viewport && self.visibility_settings == other.visibility_settings\n\t}\n}\n\nimpl std::fmt::Debug for OverlayContext {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tf.debug_struct(\"OverlayContext\")\n\t\t\t.field(\"scene\", &\"Scene { ... }\")\n\t\t\t.field(\"viewport\", &self.viewport)\n\t\t\t.field(\"visibility_settings\", &self.visibility_settings)\n\t\t\t.finish()\n\t}\n}\n\n// Default implementation for Scene\nimpl Default for OverlayContext {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tinternal: Mutex::new(OverlayContextInternal::default()).into(),\n\t\t\tviewport: ViewportMessageHandler::default(),\n\t\t\tvisibility_settings: OverlaysVisibilitySettings::default(),\n\t\t}\n\t}\n}\n\n// Message hashing isn't used but is required by the message system macros\nimpl core::hash::Hash for OverlayContext {\n\tfn hash<H: std::hash::Hasher>(&self, _state: &mut H) {}\n}\n\nimpl OverlayContext {\n\t#[allow(dead_code)]\n\tpub(super) fn new(viewport: ViewportMessageHandler, visibility_settings: OverlaysVisibilitySettings) -> Self {\n\t\tSelf {\n\t\t\tinternal: Arc::new(Mutex::new(OverlayContextInternal::new(viewport, visibility_settings))),\n\t\t\tviewport,\n\t\t\tvisibility_settings,\n\t\t}\n\t}\n\n\tpub fn take_scene(self) -> Scene {\n\t\tlet mut internal = self.internal.lock().expect(\"Failed to lock internal overlay context\");\n\t\tstd::mem::take(&mut internal.scene)\n\t}\n\n\tfn internal(&'_ self) -> MutexGuard<'_, OverlayContextInternal> {\n\t\tself.internal.lock().expect(\"Failed to lock internal overlay context\")\n\t}\n\n\tpub fn quad(&mut self, quad: Quad, stroke_color: Option<&str>, color_fill: Option<&str>) {\n\t\tself.internal().quad(quad, stroke_color, color_fill);\n\t}\n\n\tpub fn draw_triangle(&mut self, base: DVec2, direction: DVec2, size: f64, color_fill: Option<&str>, color_stroke: Option<&str>) {\n\t\tself.internal().draw_triangle(base, direction, size, color_fill, color_stroke);\n\t}\n\n\tpub fn dashed_quad(&mut self, quad: Quad, stroke_color: Option<&str>, color_fill: Option<&str>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {\n\t\tself.internal().dashed_quad(quad, stroke_color, color_fill, dash_width, dash_gap_width, dash_offset);\n\t}\n\n\tpub fn polygon(&mut self, polygon: &[DVec2], stroke_color: Option<&str>, color_fill: Option<&str>) {\n\t\tself.internal().polygon(polygon, stroke_color, color_fill);\n\t}\n\n\tpub fn dashed_polygon(&mut self, polygon: &[DVec2], stroke_color: Option<&str>, color_fill: Option<&str>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {\n\t\tself.internal().dashed_polygon(polygon, stroke_color, color_fill, dash_width, dash_gap_width, dash_offset);\n\t}\n\n\tpub fn line(&mut self, start: DVec2, end: DVec2, color: Option<&str>, thickness: Option<f64>) {\n\t\tself.internal().line(start, end, color, thickness);\n\t}\n\n\t#[allow(clippy::too_many_arguments)]\n\tpub fn dashed_line(&mut self, start: DVec2, end: DVec2, color: Option<&str>, thickness: Option<f64>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {\n\t\tself.internal().dashed_line(start, end, color, thickness, dash_width, dash_gap_width, dash_offset);\n\t}\n\n\tpub fn hover_manipulator_handle(&mut self, position: DVec2, selected: bool) {\n\t\tself.internal().hover_manipulator_handle(position, selected);\n\t}\n\n\tpub fn hover_manipulator_anchor(&mut self, position: DVec2, selected: bool) {\n\t\tself.internal().hover_manipulator_anchor(position, selected);\n\t}\n\n\tpub fn manipulator_handle(&mut self, position: DVec2, selected: bool, color: Option<&str>) {\n\t\tself.internal().manipulator_handle(position, selected, color);\n\t}\n\n\tpub fn manipulator_anchor(&mut self, position: DVec2, selected: bool, color: Option<&str>) {\n\t\tself.internal().manipulator_anchor(position, selected, color);\n\t}\n\n\tpub fn gradient_color_stop(&mut self, position: DVec2, emphasis: GizmoEmphasis, color: &str, small: bool) {\n\t\tself.internal().gradient_color_stop(position, emphasis, color, small);\n\t}\n\n\tpub fn gradient_midpoint(&mut self, position: DVec2, emphasis: GizmoEmphasis, angle: f64) {\n\t\tself.internal().gradient_midpoint(position, emphasis, angle);\n\t}\n\n\tpub fn resize_handle(&mut self, position: DVec2, rotation: f64) {\n\t\tself.internal().resize_handle(position, rotation);\n\t}\n\n\tpub fn skew_handles(&mut self, edge_start: DVec2, edge_end: DVec2) {\n\t\tself.internal().skew_handles(edge_start, edge_end);\n\t}\n\n\tpub fn square(&mut self, position: DVec2, size: Option<f64>, color_fill: Option<&str>, color_stroke: Option<&str>) {\n\t\tself.internal().square(position, size, color_fill, color_stroke);\n\t}\n\n\tpub fn pixel(&mut self, position: DVec2, color: Option<&str>) {\n\t\tself.internal().pixel(position, color);\n\t}\n\n\tpub fn circle(&mut self, position: DVec2, radius: f64, color_fill: Option<&str>, color_stroke: Option<&str>) {\n\t\tself.internal().circle(position, radius, color_fill, color_stroke);\n\t}\n\n\t#[allow(clippy::too_many_arguments)]\n\tpub fn dashed_ellipse(\n\t\t&mut self,\n\t\tcenter: DVec2,\n\t\tradius_x: f64,\n\t\tradius_y: f64,\n\t\trotation: Option<f64>,\n\t\tstart_angle: Option<f64>,\n\t\tend_angle: Option<f64>,\n\t\tcounterclockwise: Option<bool>,\n\t\tcolor_fill: Option<&str>,\n\t\tcolor_stroke: Option<&str>,\n\t\tdash_width: Option<f64>,\n\t\tdash_gap_width: Option<f64>,\n\t\tdash_offset: Option<f64>,\n\t) {\n\t\tself.internal().dashed_ellipse(\n\t\t\tcenter,\n\t\t\tradius_x,\n\t\t\tradius_y,\n\t\t\trotation,\n\t\t\tstart_angle,\n\t\t\tend_angle,\n\t\t\tcounterclockwise,\n\t\t\tcolor_fill,\n\t\t\tcolor_stroke,\n\t\t\tdash_width,\n\t\t\tdash_gap_width,\n\t\t\tdash_offset,\n\t\t);\n\t}\n\n\tpub fn draw_arc(&mut self, center: DVec2, radius: f64, start_from: f64, end_at: f64) {\n\t\tself.internal().draw_arc(center, radius, start_from, end_at);\n\t}\n\n\tpub fn draw_arc_gizmo_angle(&mut self, pivot: DVec2, bold_radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) {\n\t\tself.internal().draw_arc_gizmo_angle(pivot, bold_radius, arc_radius, offset_angle, angle);\n\t}\n\n\tpub fn draw_angle(&mut self, pivot: DVec2, radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) {\n\t\tself.internal().draw_angle(pivot, radius, arc_radius, offset_angle, angle);\n\t}\n\n\tpub fn draw_scale(&mut self, start: DVec2, scale: f64, radius: f64, text: &str) {\n\t\tself.internal().draw_scale(start, scale, radius, text);\n\t}\n\n\tpub fn compass_rose(&mut self, compass_center: DVec2, angle: f64, show_compass_with_hover_ring: Option<bool>) {\n\t\tself.internal().compass_rose(compass_center, angle, show_compass_with_hover_ring);\n\t}\n\n\tpub fn pivot(&mut self, position: DVec2, angle: f64) {\n\t\tself.internal().pivot(position, angle);\n\t}\n\n\tpub fn dowel_pin(&mut self, position: DVec2, angle: f64, color: Option<&str>) {\n\t\tself.internal().dowel_pin(position, angle, color);\n\t}\n\n\t#[allow(clippy::too_many_arguments)]\n\tpub fn arc_sweep_angle(&mut self, offset_angle: f64, angle: f64, end_point_position: DVec2, bold_radius: f64, pivot: DVec2, text: &str, transform: DAffine2) {\n\t\tself.internal().arc_sweep_angle(offset_angle, angle, end_point_position, bold_radius, pivot, text, transform);\n\t}\n\n\t/// Used by the Pen and Path tools to outline the path of the shape.\n\tpub fn outline_vector(&mut self, vector: &Vector, transform: DAffine2) {\n\t\tself.internal().outline_vector(vector, transform);\n\t}\n\n\t/// Used by the Pen tool in order to show how the bezier curve would look like.\n\tpub fn outline_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {\n\t\tself.internal().outline_bezier(bezier, transform);\n\t}\n\n\t/// Used by the path tool segment mode in order to show the selected segments.\n\tpub fn outline_select_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {\n\t\tself.internal().outline_select_bezier(bezier, transform);\n\t}\n\n\tpub fn outline_overlay_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {\n\t\tself.internal().outline_overlay_bezier(bezier, transform);\n\t}\n\n\t/// Used by the Select tool to outline a path or a free point when selected or hovered.\n\tpub fn outline(&mut self, target_types: impl Iterator<Item = impl Borrow<ClickTargetType>>, transform: DAffine2, color: Option<&str>) {\n\t\tself.internal().outline(target_types, transform, color);\n\t}\n\n\t/// Fills the area inside the path. Assumes `color` is in gamma space.\n\t/// Used by the Pen tool to show the path being closed.\n\tpub fn fill_path(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &str) {\n\t\tself.internal().fill_path(subpaths, transform, color);\n\t}\n\n\t/// Fills the area inside the path with a pattern. Assumes `color` is an sRGB hex string.\n\t/// Used by the fill tool to show the area to be filled.\n\tpub fn fill_path_pattern(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &str) {\n\t\tself.internal().fill_path_pattern(subpaths, transform, color);\n\t}\n\n\tpub fn text(&self, text: &str, font_color: &str, background_color: Option<&str>, transform: DAffine2, padding: f64, pivot: [Pivot; 2]) {\n\t\tlet mut internal = self.internal();\n\t\tinternal.text(text, font_color, background_color, transform, padding, pivot);\n\t}\n\n\tpub fn translation_box(&mut self, translation: DVec2, quad: Quad, typed_string: Option<String>) {\n\t\tself.internal().translation_box(translation, quad, typed_string);\n\t}\n}\n\n// TODO Remove duplicated definition of this in `utility_types_web.rs`\npub enum Pivot {\n\tStart,\n\tMiddle,\n\tEnd,\n}\n\n// TODO Remove duplicated definition of this in `utility_types_web.rs`\npub enum DrawHandles {\n\tAll,\n\tSelectedAnchors(HashMap<LayerNodeIdentifier, Vec<SegmentId>>),\n\tFrontierHandles(HashMap<LayerNodeIdentifier, HashMap<SegmentId, Vec<PointId>>>),\n\tNone,\n}\n\npub(super) struct OverlayContextInternal {\n\tscene: Scene,\n\tviewport: ViewportMessageHandler,\n\tvisibility_settings: OverlaysVisibilitySettings,\n}\n\nimpl Default for OverlayContextInternal {\n\tfn default() -> Self {\n\t\tSelf::new(ViewportMessageHandler::default(), OverlaysVisibilitySettings::default())\n\t}\n}\n\nimpl OverlayContextInternal {\n\tpub(super) fn new(viewport: ViewportMessageHandler, visibility_settings: OverlaysVisibilitySettings) -> Self {\n\t\tSelf {\n\t\t\tscene: Scene::new(),\n\t\t\tviewport,\n\t\t\tvisibility_settings,\n\t\t}\n\t}\n\n\tfn parse_color(color: &str) -> peniko::Color {\n\t\tlet [r, g, b, a] = hex_to_rgba_u8(color);\n\t\tpeniko::Color::from_rgba8(r, g, b, a)\n\t}\n\n\tfn quad(&mut self, quad: Quad, stroke_color: Option<&str>, color_fill: Option<&str>) {\n\t\tself.dashed_polygon(&quad.0, stroke_color, color_fill, None, None, None);\n\t}\n\n\tfn draw_triangle(&mut self, base: DVec2, direction: DVec2, size: f64, color_fill: Option<&str>, color_stroke: Option<&str>) {\n\t\tlet color_fill = color_fill.unwrap_or(COLOR_OVERLAY_WHITE);\n\t\tlet color_stroke = color_stroke.unwrap_or(COLOR_OVERLAY_BLUE);\n\t\tlet normal = direction.perp();\n\t\tlet top = base + direction * size;\n\t\tlet edge1 = base + normal * size / 2.;\n\t\tlet edge2 = base - normal * size / 2.;\n\n\t\tlet transform = self.get_transform();\n\n\t\tlet mut path = BezPath::new();\n\t\tpath.move_to(kurbo::Point::new(top.x, top.y));\n\t\tpath.line_to(kurbo::Point::new(edge1.x, edge1.y));\n\t\tpath.line_to(kurbo::Point::new(edge2.x, edge2.y));\n\t\tpath.close_path();\n\n\t\tself.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(color_fill), None, &path);\n\n\t\tself.scene.stroke(&kurbo::Stroke::new(1.), transform, Self::parse_color(color_stroke), None, &path);\n\t}\n\n\tfn dashed_quad(&mut self, quad: Quad, stroke_color: Option<&str>, color_fill: Option<&str>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {\n\t\tself.dashed_polygon(&quad.0, stroke_color, color_fill, dash_width, dash_gap_width, dash_offset);\n\t}\n\n\tfn polygon(&mut self, polygon: &[DVec2], stroke_color: Option<&str>, color_fill: Option<&str>) {\n\t\tself.dashed_polygon(polygon, stroke_color, color_fill, None, None, None);\n\t}\n\n\tfn dashed_polygon(&mut self, polygon: &[DVec2], stroke_color: Option<&str>, color_fill: Option<&str>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {\n\t\tif polygon.len() < 2 {\n\t\t\treturn;\n\t\t}\n\n\t\tlet transform = self.get_transform();\n\n\t\tlet mut path = BezPath::new();\n\t\tif let Some(first) = polygon.last() {\n\t\t\tlet p = self.snap_to_physical_pixel_center(*first);\n\t\t\tpath.move_to(kurbo::Point::new(p.x, p.y));\n\t\t}\n\n\t\tfor point in polygon {\n\t\t\tlet p = self.snap_to_physical_pixel_center(*point);\n\t\t\tpath.line_to(kurbo::Point::new(p.x, p.y));\n\t\t}\n\t\tpath.close_path();\n\n\t\tif let Some(color_fill) = color_fill {\n\t\t\tself.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(color_fill), None, &path);\n\t\t}\n\n\t\tlet stroke_color = stroke_color.unwrap_or(COLOR_OVERLAY_BLUE);\n\t\tlet mut stroke = kurbo::Stroke::new(1.);\n\n\t\tif let Some(dash_width) = dash_width {\n\t\t\tlet dash_gap = dash_gap_width.unwrap_or(1.);\n\t\t\tstroke = stroke.with_dashes(dash_offset.unwrap_or(0.), [dash_width, dash_gap]);\n\t\t}\n\n\t\tself.scene.stroke(&stroke, transform, Self::parse_color(stroke_color), None, &path);\n\t}\n\n\tfn line(&mut self, start: DVec2, end: DVec2, color: Option<&str>, thickness: Option<f64>) {\n\t\tself.dashed_line(start, end, color, thickness, None, None, None)\n\t}\n\n\t#[allow(clippy::too_many_arguments)]\n\tfn dashed_line(&mut self, start: DVec2, end: DVec2, color: Option<&str>, thickness: Option<f64>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {\n\t\tlet transform = self.get_transform();\n\n\t\tlet start = self.snap_to_physical_pixel_center(start);\n\t\tlet end = self.snap_to_physical_pixel_center(end);\n\n\t\tlet mut path = BezPath::new();\n\t\tpath.move_to(kurbo::Point::new(start.x, start.y));\n\t\tpath.line_to(kurbo::Point::new(end.x, end.y));\n\n\t\tlet mut stroke = kurbo::Stroke::new(thickness.unwrap_or(1.));\n\n\t\tif let Some(dash_width) = dash_width {\n\t\t\tlet dash_gap = dash_gap_width.unwrap_or(1.);\n\t\t\tstroke = stroke.with_dashes(dash_offset.unwrap_or(0.), [dash_width, dash_gap]);\n\t\t}\n\n\t\tself.scene.stroke(&stroke, transform, Self::parse_color(color.unwrap_or(COLOR_OVERLAY_BLUE)), None, &path);\n\t}\n\n\tfn manipulator_handle(&mut self, position: DVec2, selected: bool, color: Option<&str>) {\n\t\tlet transform = self.get_transform();\n\t\tlet position = self.snap_to_physical_pixel_center(position);\n\n\t\tlet circle = kurbo::Circle::new((position.x, position.y), MANIPULATOR_GROUP_MARKER_SIZE / 2.);\n\n\t\tlet fill = if selected { COLOR_OVERLAY_BLUE } else { COLOR_OVERLAY_WHITE };\n\t\tself.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(fill), None, &circle);\n\n\t\tself.scene\n\t\t\t.stroke(&kurbo::Stroke::new(1.), transform, Self::parse_color(color.unwrap_or(COLOR_OVERLAY_BLUE)), None, &circle);\n\t}\n\n\tfn hover_manipulator_handle(&mut self, position: DVec2, selected: bool) {\n\t\tlet transform = self.get_transform();\n\t\tlet position = self.snap_to_physical_pixel_center(position);\n\n\t\tlet circle = kurbo::Circle::new((position.x, position.y), (MANIPULATOR_GROUP_MARKER_SIZE + 2.) / 2.);\n\n\t\tlet fill = COLOR_OVERLAY_BLUE_50;\n\t\tself.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(fill), None, &circle);\n\t\tself.scene.stroke(&kurbo::Stroke::new(1.), transform, Self::parse_color(COLOR_OVERLAY_BLUE_50), None, &circle);\n\n\t\tlet inner_circle = kurbo::Circle::new((position.x, position.y), MANIPULATOR_GROUP_MARKER_SIZE / 2.);\n\n\t\tlet color_fill = if selected { COLOR_OVERLAY_BLUE } else { COLOR_OVERLAY_WHITE };\n\t\tself.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(color_fill), None, &circle);\n\t\tself.scene.stroke(&kurbo::Stroke::new(1.), transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &inner_circle);\n\t}\n\n\tfn manipulator_anchor(&mut self, position: DVec2, selected: bool, color: Option<&str>) {\n\t\tlet color_stroke = color.unwrap_or(COLOR_OVERLAY_BLUE);\n\t\tlet color_fill = if selected { color_stroke } else { COLOR_OVERLAY_WHITE };\n\t\tself.square(position, None, Some(color_fill), Some(color_stroke));\n\t}\n\n\tfn hover_manipulator_anchor(&mut self, position: DVec2, selected: bool) {\n\t\tself.square(position, Some(MANIPULATOR_GROUP_MARKER_SIZE + 2.), Some(COLOR_OVERLAY_BLUE_50), Some(COLOR_OVERLAY_BLUE_50));\n\t\tlet color_fill = if selected { COLOR_OVERLAY_BLUE } else { COLOR_OVERLAY_WHITE };\n\t\tself.square(position, None, Some(color_fill), Some(COLOR_OVERLAY_BLUE));\n\t}\n\n\tfn gradient_color_stop(&mut self, position: DVec2, emphasis: GizmoEmphasis, color: &str, small: bool) {\n\t\tlet transform = self.get_transform();\n\t\tlet position = position.round() - DVec2::splat(0.5);\n\n\t\tlet radius_offset = match emphasis {\n\t\t\tGizmoEmphasis::Regular => 0.,\n\t\t\tGizmoEmphasis::Hovered => 0.5,\n\t\t\tGizmoEmphasis::Active => 1.,\n\t\t};\n\t\tlet stroke_width = radius_offset * 2. + 1.;\n\t\tlet radius = (MANIPULATOR_GROUP_MARKER_SIZE / 1.5 + 1. + radius_offset) * if small { 0.5 } else { 1. };\n\n\t\tlet mut draw_circle = |radius: f64, width: Option<f64>, color: &str| {\n\t\t\tlet circle = kurbo::Circle::new((position.x, position.y), radius);\n\t\t\tif let Some(width) = width {\n\t\t\t\tself.scene.stroke(&kurbo::Stroke::new(width), transform, Self::parse_color(color), None, &circle);\n\t\t\t} else {\n\t\t\t\tself.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(color), None, &circle);\n\t\t\t}\n\t\t};\n\t\t// Fill\n\t\tdraw_circle(radius, None, color);\n\t\t// Stroke (inner)\n\t\tdraw_circle(radius + stroke_width / 2., Some(1.), COLOR_OVERLAY_BLACK);\n\t\t// Stroke (outer)\n\t\tdraw_circle(radius, Some(stroke_width), COLOR_OVERLAY_WHITE);\n\t}\n\n\tfn gradient_midpoint(&mut self, position: DVec2, emphasis: GizmoEmphasis, angle: f64) {\n\t\tlet transform = self.get_transform();\n\t\tlet position = position.round() - DVec2::splat(0.5);\n\n\t\t// Rotate diamond points by the gradient line angle\n\t\tlet (sin, cos) = angle.sin_cos();\n\t\tlet rotate = |dx: f64, dy: f64| DVec2::new(dx * cos - dy * sin, dx * sin + dy * cos);\n\n\t\tlet top = rotate(0., -GRADIENT_MIDPOINT_DIAMOND_RADIUS);\n\t\tlet right = rotate(GRADIENT_MIDPOINT_DIAMOND_RADIUS, 0.);\n\t\tlet bottom = rotate(0., GRADIENT_MIDPOINT_DIAMOND_RADIUS);\n\t\tlet left = rotate(-GRADIENT_MIDPOINT_DIAMOND_RADIUS, 0.);\n\n\t\tlet mut path = BezPath::new();\n\t\tpath.move_to(kurbo::Point::new(position.x + top.x, position.y + top.y));\n\t\tpath.line_to(kurbo::Point::new(position.x + right.x, position.y + right.y));\n\t\tpath.line_to(kurbo::Point::new(position.x + bottom.x, position.y + bottom.y));\n\t\tpath.line_to(kurbo::Point::new(position.x + left.x, position.y + left.y));\n\t\tpath.close_path();\n\n\t\tlet (fill, stroke_width) = match emphasis {\n\t\t\tGizmoEmphasis::Regular => (COLOR_OVERLAY_WHITE, 1.),\n\t\t\tGizmoEmphasis::Hovered => (COLOR_OVERLAY_WHITE, 2.),\n\t\t\tGizmoEmphasis::Active => (COLOR_OVERLAY_BLUE, 1.),\n\t\t};\n\t\tself.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(fill), None, &path);\n\t\tself.scene.stroke(&kurbo::Stroke::new(stroke_width), transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);\n\t}\n\n\tfn resize_handle(&mut self, position: DVec2, rotation: f64) {\n\t\tlet quad = DAffine2::from_angle_translation(rotation, position) * Quad::from_box([DVec2::splat(-RESIZE_HANDLE_SIZE / 2.), DVec2::splat(RESIZE_HANDLE_SIZE / 2.)]);\n\t\tself.quad(quad, None, Some(COLOR_OVERLAY_WHITE));\n\t}\n\n\tfn skew_handles(&mut self, edge_start: DVec2, edge_end: DVec2) {\n\t\tlet edge_dir = (edge_end - edge_start).normalize();\n\t\tlet mid = edge_end.midpoint(edge_start);\n\n\t\tfor edge in [edge_dir, -edge_dir] {\n\t\t\tself.draw_triangle(mid + edge * (3. + SKEW_TRIANGLE_OFFSET), edge, SKEW_TRIANGLE_SIZE, None, None);\n\t\t}\n\t}\n\n\tfn get_transform(&self) -> kurbo::Affine {\n\t\tkurbo::Affine::scale(self.viewport.scale())\n\t}\n\n\tfn square(&mut self, position: DVec2, size: Option<f64>, color_fill: Option<&str>, color_stroke: Option<&str>) {\n\t\tlet size = size.unwrap_or(MANIPULATOR_GROUP_MARKER_SIZE);\n\t\tlet color_fill = color_fill.unwrap_or(COLOR_OVERLAY_WHITE);\n\t\tlet color_stroke = color_stroke.unwrap_or(COLOR_OVERLAY_BLUE);\n\n\t\tlet position = self.snap_to_physical_pixel_center(position);\n\t\tlet corner = position - DVec2::splat(size) / 2.;\n\n\t\tlet transform = self.get_transform();\n\t\tlet rect = kurbo::Rect::new(corner.x, corner.y, corner.x + size, corner.y + size);\n\n\t\tself.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(color_fill), None, &rect);\n\n\t\tself.scene.stroke(&kurbo::Stroke::new(1.), transform, Self::parse_color(color_stroke), None, &rect);\n\t}\n\n\tfn pixel(&mut self, position: DVec2, color: Option<&str>) {\n\t\tlet size = 1.;\n\t\tlet color_fill = color.unwrap_or(COLOR_OVERLAY_WHITE);\n\n\t\tlet position = self.snap_to_physical_pixel_center(position);\n\t\tlet corner = position - DVec2::splat(size) / 2.;\n\n\t\tlet transform = self.get_transform();\n\t\tlet rect = kurbo::Rect::new(corner.x, corner.y, corner.x + size, corner.y + size);\n\n\t\tself.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(color_fill), None, &rect);\n\t}\n\n\tfn circle(&mut self, position: DVec2, radius: f64, color_fill: Option<&str>, color_stroke: Option<&str>) {\n\t\tlet color_fill = color_fill.unwrap_or(COLOR_OVERLAY_WHITE);\n\t\tlet color_stroke = color_stroke.unwrap_or(COLOR_OVERLAY_BLUE);\n\t\tlet position = self.snap_to_physical_pixel(position);\n\n\t\tlet transform = self.get_transform();\n\t\tlet circle = kurbo::Circle::new((position.x, position.y), radius);\n\n\t\tself.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(color_fill), None, &circle);\n\n\t\tself.scene.stroke(&kurbo::Stroke::new(1.), transform, Self::parse_color(color_stroke), None, &circle);\n\t}\n\n\t#[allow(clippy::too_many_arguments)]\n\tfn dashed_ellipse(\n\t\t&mut self,\n\t\t_center: DVec2,\n\t\t_radius_x: f64,\n\t\t_radius_y: f64,\n\t\t_rotation: Option<f64>,\n\t\t_start_angle: Option<f64>,\n\t\t_end_angle: Option<f64>,\n\t\t_counterclockwise: Option<bool>,\n\t\t_color_fill: Option<&str>,\n\t\t_color_stroke: Option<&str>,\n\t\t_dash_width: Option<f64>,\n\t\t_dash_gap_width: Option<f64>,\n\t\t_dash_offset: Option<f64>,\n\t) {\n\t}\n\n\tfn draw_arc(&mut self, center: DVec2, radius: f64, start_from: f64, end_at: f64) {\n\t\tlet segments = ((end_at - start_from).abs() / (std::f64::consts::PI / 4.)).ceil() as usize;\n\t\tlet step = (end_at - start_from) / segments as f64;\n\t\tlet half_step = step / 2.;\n\t\tlet factor = 4. / 3. * half_step.sin() / (1. + half_step.cos());\n\n\t\tlet mut path = BezPath::new();\n\n\t\tfor i in 0..segments {\n\t\t\tlet start_angle = start_from + step * i as f64;\n\t\t\tlet end_angle = start_angle + step;\n\t\t\tlet start_vec = DVec2::from_angle(start_angle);\n\t\t\tlet end_vec = DVec2::from_angle(end_angle);\n\n\t\t\tlet start = center + radius * start_vec;\n\t\t\tlet end = center + radius * end_vec;\n\n\t\t\tlet handle_start = start + start_vec.perp() * radius * factor;\n\t\t\tlet handle_end = end - end_vec.perp() * radius * factor;\n\n\t\t\tif i == 0 {\n\t\t\t\tpath.move_to(kurbo::Point::new(start.x, start.y));\n\t\t\t}\n\n\t\t\tpath.curve_to(\n\t\t\t\tkurbo::Point::new(handle_start.x, handle_start.y),\n\t\t\t\tkurbo::Point::new(handle_end.x, handle_end.y),\n\t\t\t\tkurbo::Point::new(end.x, end.y),\n\t\t\t);\n\t\t}\n\n\t\tself.scene.stroke(&kurbo::Stroke::new(1.), self.get_transform(), Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);\n\t}\n\n\tfn draw_arc_gizmo_angle(&mut self, pivot: DVec2, bold_radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) {\n\t\tlet end_point1 = pivot + bold_radius * DVec2::from_angle(angle + offset_angle);\n\t\tself.line(pivot, end_point1, None, None);\n\t\tself.draw_arc(pivot, arc_radius, offset_angle, (angle) % TAU + offset_angle);\n\t}\n\n\tfn draw_angle(&mut self, pivot: DVec2, radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) {\n\t\tlet end_point1 = pivot + radius * DVec2::from_angle(angle + offset_angle);\n\t\tlet end_point2 = pivot + radius * DVec2::from_angle(offset_angle);\n\t\tself.line(pivot, end_point1, None, None);\n\t\tself.dashed_line(pivot, end_point2, None, None, Some(2.), Some(2.), Some(0.5));\n\t\tself.draw_arc(pivot, arc_radius, offset_angle, (angle) % TAU + offset_angle);\n\t}\n\n\tpub fn draw_scale(&mut self, start: DVec2, scale: f64, radius: f64, text: &str) {\n\t\tlet sign = scale.signum();\n\t\tlet fill_color = Some(COLOR_OVERLAY_WHITE_05);\n\t\tself.line(start + DVec2::X * radius * sign, start + DVec2::X * radius * scale.abs(), None, None);\n\t\tself.circle(start, radius, fill_color, None);\n\t\tself.circle(start, radius * scale.abs(), fill_color, None);\n\t\tself.text(\n\t\t\ttext,\n\t\t\tCOLOR_OVERLAY_BLUE,\n\t\t\tNone,\n\t\t\tDAffine2::from_translation(start + sign * DVec2::X * radius * (1. + scale.abs()) / 2.),\n\t\t\t2.,\n\t\t\t[Pivot::Middle, Pivot::End],\n\t\t)\n\t}\n\n\tfn compass_rose(&mut self, compass_center: DVec2, angle: f64, show_compass_with_hover_ring: Option<bool>) {\n\t\tlet hover_ring_outer_radius: f64 = COMPASS_ROSE_HOVER_RING_DIAMETER / 2.;\n\t\tlet main_ring_outer_radius: f64 = COMPASS_ROSE_MAIN_RING_DIAMETER / 2.;\n\t\tlet main_ring_inner_radius: f64 = COMPASS_ROSE_RING_INNER_DIAMETER / 2.;\n\t\tlet arrow_radius: f64 = COMPASS_ROSE_ARROW_SIZE / 2.;\n\t\tlet hover_ring_stroke_width: f64 = hover_ring_outer_radius - main_ring_inner_radius;\n\t\tlet hover_ring_centerline_radius: f64 = (hover_ring_outer_radius + main_ring_inner_radius) / 2.;\n\t\tlet main_ring_stroke_width: f64 = main_ring_outer_radius - main_ring_inner_radius;\n\t\tlet main_ring_centerline_radius: f64 = (main_ring_outer_radius + main_ring_inner_radius) / 2.;\n\n\t\tlet Some(show_hover_ring) = show_compass_with_hover_ring else { return };\n\n\t\tlet transform = self.get_transform();\n\t\tlet center = self.snap_to_physical_pixel_center(compass_center);\n\n\t\t// Hover ring\n\t\tif show_hover_ring {\n\t\t\tlet circle = kurbo::Circle::new((center.x, center.y), hover_ring_centerline_radius);\n\t\t\tself.scene\n\t\t\t\t.stroke(&kurbo::Stroke::new(hover_ring_stroke_width), transform, Self::parse_color(COLOR_OVERLAY_BLUE_50), None, &circle);\n\t\t}\n\n\t\t// Arrows\n\t\tfor i in 0..4 {\n\t\t\tlet direction = DVec2::from_angle(i as f64 * FRAC_PI_2 + angle);\n\t\t\tlet color = if i % 2 == 0 { COLOR_OVERLAY_RED } else { COLOR_OVERLAY_GREEN };\n\n\t\t\tlet tip = center + direction * hover_ring_outer_radius;\n\t\t\tlet base = center + direction * (main_ring_inner_radius + main_ring_outer_radius) / 2.;\n\n\t\t\tlet r = (arrow_radius.powi(2) + main_ring_inner_radius.powi(2)).sqrt();\n\t\t\tlet (cos, sin) = (main_ring_inner_radius / r, arrow_radius / r);\n\t\t\tlet side1 = center + r * DVec2::new(cos * direction.x - sin * direction.y, sin * direction.x + direction.y * cos);\n\t\t\tlet side2 = center + r * DVec2::new(cos * direction.x + sin * direction.y, -sin * direction.x + direction.y * cos);\n\n\t\t\tlet mut path = BezPath::new();\n\t\t\tpath.move_to(kurbo::Point::new(tip.x, tip.y));\n\t\t\tpath.line_to(kurbo::Point::new(side1.x, side1.y));\n\t\t\tpath.line_to(kurbo::Point::new(base.x, base.y));\n\t\t\tpath.line_to(kurbo::Point::new(side2.x, side2.y));\n\t\t\tpath.close_path();\n\n\t\t\tlet color_parsed = Self::parse_color(color);\n\t\t\tself.scene.fill(peniko::Fill::NonZero, transform, color_parsed, None, &path);\n\t\t\tself.scene.stroke(&kurbo::Stroke::new(0.01), transform, color_parsed, None, &path);\n\t\t}\n\n\t\t// Main ring\n\t\tlet circle = kurbo::Circle::new((center.x, center.y), main_ring_centerline_radius);\n\t\tself.scene\n\t\t\t.stroke(&kurbo::Stroke::new(main_ring_stroke_width), transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &circle);\n\t}\n\n\tfn pivot(&mut self, position: DVec2, angle: f64) {\n\t\tlet uv = DVec2::from_angle(angle);\n\t\tlet (x, y) = self.snap_to_physical_pixel_center(position).into();\n\n\t\tlet transform = self.get_transform();\n\n\t\t// Circle\n\t\tlet circle = kurbo::Circle::new((x, y), PIVOT_DIAMETER / 2.);\n\t\tself.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(COLOR_OVERLAY_YELLOW), None, &circle);\n\n\t\t// Crosshair\n\t\tlet crosshair_radius: f64 = (PIVOT_CROSSHAIR_LENGTH - PIVOT_CROSSHAIR_THICKNESS) / 2.;\n\n\t\tlet mut stroke = kurbo::Stroke::new(PIVOT_CROSSHAIR_THICKNESS);\n\t\tstroke = stroke.with_caps(kurbo::Cap::Round);\n\n\t\t// Horizontal line\n\t\tlet mut path = BezPath::new();\n\t\tpath.move_to(kurbo::Point::new(x + crosshair_radius * uv.x, y + crosshair_radius * uv.y));\n\t\tpath.line_to(kurbo::Point::new(x - crosshair_radius * uv.x, y - crosshair_radius * uv.y));\n\n\t\tself.scene.stroke(&stroke, transform, Self::parse_color(COLOR_OVERLAY_YELLOW), None, &path);\n\n\t\t// Vertical line\n\t\tlet mut path = BezPath::new();\n\t\tpath.move_to(kurbo::Point::new(x - crosshair_radius * uv.y, y + crosshair_radius * uv.x));\n\t\tpath.line_to(kurbo::Point::new(x + crosshair_radius * uv.y, y - crosshair_radius * uv.x));\n\n\t\tself.scene.stroke(&stroke, transform, Self::parse_color(COLOR_OVERLAY_YELLOW), None, &path);\n\t}\n\n\tfn dowel_pin(&mut self, position: DVec2, angle: f64, color: Option<&str>) {\n\t\tlet (x, y) = self.snap_to_physical_pixel_center(position).into();\n\t\tlet color = color.unwrap_or(COLOR_OVERLAY_YELLOW_DULL);\n\n\t\tlet transform = self.get_transform();\n\n\t\tlet circle = kurbo::Circle::new((x, y), DOWEL_PIN_RADIUS);\n\t\tself.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(COLOR_OVERLAY_WHITE), None, &circle);\n\t\tself.scene.stroke(&kurbo::Stroke::new(1.), transform, Self::parse_color(color), None, &circle);\n\n\t\tlet mut path = BezPath::new();\n\n\t\tlet start1 = FRAC_PI_2 + angle;\n\t\tlet start1_x = x + DOWEL_PIN_RADIUS * start1.cos();\n\t\tlet start1_y = y + DOWEL_PIN_RADIUS * start1.sin();\n\t\tpath.move_to(kurbo::Point::new(x, y));\n\t\tpath.line_to(kurbo::Point::new(start1_x, start1_y));\n\n\t\tlet arc1 = kurbo::Arc::new((x, y), (DOWEL_PIN_RADIUS, DOWEL_PIN_RADIUS), start1, FRAC_PI_2, 0.);\n\t\tarc1.to_cubic_beziers(0.1, |p1, p2, p| {\n\t\t\tpath.curve_to(p1, p2, p);\n\t\t});\n\t\tpath.close_path();\n\n\t\tlet start2 = PI + FRAC_PI_2 + angle;\n\t\tlet start2_x = x + DOWEL_PIN_RADIUS * start2.cos();\n\t\tlet start2_y = y + DOWEL_PIN_RADIUS * start2.sin();\n\t\tpath.move_to(kurbo::Point::new(x, y));\n\t\tpath.line_to(kurbo::Point::new(start2_x, start2_y));\n\n\t\tlet arc2 = kurbo::Arc::new((x, y), (DOWEL_PIN_RADIUS, DOWEL_PIN_RADIUS), start2, FRAC_PI_2, 0.);\n\t\tarc2.to_cubic_beziers(0.1, |p1, p2, p| {\n\t\t\tpath.curve_to(p1, p2, p);\n\t\t});\n\t\tpath.close_path();\n\n\t\tself.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(color), None, &path);\n\t}\n\n\t#[allow(clippy::too_many_arguments)]\n\tfn arc_sweep_angle(&mut self, offset_angle: f64, angle: f64, end_point_position: DVec2, bold_radius: f64, pivot: DVec2, text: &str, transform: DAffine2) {\n\t\tself.manipulator_handle(end_point_position, true, None);\n\t\tself.draw_arc_gizmo_angle(pivot, bold_radius, ARC_SWEEP_GIZMO_RADIUS, offset_angle, angle.to_radians());\n\t\tself.text(text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);\n\t}\n\n\t/// Used by the Pen and Path tools to outline the path of the shape.\n\tfn outline_vector(&mut self, vector: &Vector, transform: DAffine2) {\n\t\tlet vello_transform = self.get_transform();\n\t\tlet mut path = BezPath::new();\n\n\t\tlet mut last_point = None;\n\t\tfor (_, bezier, start_id, end_id) in vector.segment_iter() {\n\t\t\tlet move_to = last_point != Some(start_id);\n\t\t\tlast_point = Some(end_id);\n\n\t\t\tself.bezier_to_path(bezier, transform, move_to, &mut path);\n\t\t}\n\n\t\tself.scene.stroke(&kurbo::Stroke::new(1.), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);\n\t}\n\n\t/// Used by the Pen tool in order to show how the bezier curve would look like.\n\tfn outline_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {\n\t\tlet vello_transform = self.get_transform();\n\t\tlet mut path = BezPath::new();\n\t\tself.bezier_to_path(bezier, transform, true, &mut path);\n\n\t\tself.scene.stroke(&kurbo::Stroke::new(1.), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);\n\t}\n\n\t/// Used by the path tool segment mode in order to show the selected segments.\n\tfn outline_select_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {\n\t\tlet vello_transform = self.get_transform();\n\t\tlet mut path = BezPath::new();\n\t\tself.bezier_to_path(bezier, transform, true, &mut path);\n\n\t\tself.scene.stroke(&kurbo::Stroke::new(4.), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);\n\t}\n\n\tfn outline_overlay_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {\n\t\tlet vello_transform = self.get_transform();\n\t\tlet mut path = BezPath::new();\n\t\tself.bezier_to_path(bezier, transform, true, &mut path);\n\n\t\tself.scene.stroke(&kurbo::Stroke::new(4.), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE_50), None, &path);\n\t}\n\n\tfn bezier_to_path(&self, bezier: PathSeg, transform: DAffine2, move_to: bool, path: &mut BezPath) {\n\t\tlet bezier = Affine::new(transform.to_cols_array()) * bezier;\n\t\tif move_to {\n\t\t\tpath.move_to(bezier.start());\n\t\t}\n\t\tpath.push(bezier.as_path_el());\n\t}\n\n\tfn push_path(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2) -> BezPath {\n\t\tlet mut path = BezPath::new();\n\n\t\tfor subpath in subpaths {\n\t\t\tlet subpath = subpath.borrow();\n\t\t\tlet mut curves = subpath.iter().peekable();\n\n\t\t\tlet Some(first) = curves.peek() else {\n\t\t\t\tcontinue;\n\t\t\t};\n\n\t\t\tlet start_point = transform.transform_point2(point_to_dvec2(first.start()));\n\t\t\tlet start_point = self.snap_to_physical_pixel(start_point);\n\t\t\tpath.move_to(kurbo::Point::new(start_point.x, start_point.y));\n\n\t\t\tfor curve in curves {\n\t\t\t\tmatch curve {\n\t\t\t\t\tPathSeg::Line(line) => {\n\t\t\t\t\t\tlet a = transform.transform_point2(point_to_dvec2(line.p1));\n\t\t\t\t\t\tlet a = self.snap_to_physical_pixel_center(a);\n\t\t\t\t\t\tpath.line_to(kurbo::Point::new(a.x, a.y));\n\t\t\t\t\t}\n\t\t\t\t\tPathSeg::Quad(quad_bez) => {\n\t\t\t\t\t\tlet a = transform.transform_point2(point_to_dvec2(quad_bez.p1));\n\t\t\t\t\t\tlet b = transform.transform_point2(point_to_dvec2(quad_bez.p2));\n\t\t\t\t\t\tlet a = self.snap_to_physical_pixel_center(a);\n\t\t\t\t\t\tlet b = self.snap_to_physical_pixel_center(b);\n\t\t\t\t\t\tpath.quad_to(kurbo::Point::new(a.x, a.y), kurbo::Point::new(b.x, b.y));\n\t\t\t\t\t}\n\t\t\t\t\tPathSeg::Cubic(cubic_bez) => {\n\t\t\t\t\t\tlet a = transform.transform_point2(point_to_dvec2(cubic_bez.p1));\n\t\t\t\t\t\tlet b = transform.transform_point2(point_to_dvec2(cubic_bez.p2));\n\t\t\t\t\t\tlet c = transform.transform_point2(point_to_dvec2(cubic_bez.p3));\n\t\t\t\t\t\tlet a = self.snap_to_physical_pixel_center(a);\n\t\t\t\t\t\tlet b = self.snap_to_physical_pixel_center(b);\n\t\t\t\t\t\tlet c = self.snap_to_physical_pixel_center(c);\n\t\t\t\t\t\tpath.curve_to(kurbo::Point::new(a.x, a.y), kurbo::Point::new(b.x, b.y), kurbo::Point::new(c.x, c.y));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif subpath.closed() {\n\t\t\t\tpath.close_path();\n\t\t\t}\n\t\t}\n\n\t\tpath\n\t}\n\n\t/// Used by the Select tool to outline a path or a free point when selected or hovered.\n\tfn outline(&mut self, target_types: impl Iterator<Item = impl Borrow<ClickTargetType>>, transform: DAffine2, color: Option<&str>) {\n\t\tlet mut subpaths: Vec<subpath::Subpath<PointId>> = vec![];\n\n\t\tfor target_type in target_types {\n\t\t\tmatch target_type.borrow() {\n\t\t\t\tClickTargetType::FreePoint(point) => {\n\t\t\t\t\tself.manipulator_anchor(transform.transform_point2(point.position), false, None);\n\t\t\t\t}\n\t\t\t\tClickTargetType::Subpath(subpath) => subpaths.push(subpath.clone()),\n\t\t\t}\n\t\t}\n\n\t\tif !subpaths.is_empty() {\n\t\t\tlet path = self.push_path(subpaths.iter(), transform);\n\t\t\tlet color = color.unwrap_or(COLOR_OVERLAY_BLUE);\n\n\t\t\tself.scene.stroke(&kurbo::Stroke::new(1.), self.get_transform(), Self::parse_color(color), None, &path);\n\t\t}\n\t}\n\n\t/// Fills the area inside the path. Assumes `color` is in gamma space.\n\t/// Used by the Pen tool to show the path being closed.\n\tfn fill_path(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &str) {\n\t\tlet path = self.push_path(subpaths, transform);\n\n\t\tself.scene.fill(peniko::Fill::NonZero, self.get_transform(), Self::parse_color(color), None, &path);\n\t}\n\n\t/// Fills the area inside the path with a pattern. Assumes `color` is an sRGB hex string.\n\t/// Used by the fill tool to show the area to be filled.\n\tfn fill_path_pattern(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &str) {\n\t\tconst PATTERN_WIDTH: u32 = 4;\n\t\tconst PATTERN_HEIGHT: u32 = 4;\n\n\t\t// Create a 4x4 pixel pattern with colored pixels at (0,0) and (2,2)\n\t\t// This matches the Canvas2D checkerboard pattern\n\t\tlet mut data = vec![0u8; (PATTERN_WIDTH * PATTERN_HEIGHT * 4) as usize];\n\t\tlet rgba = hex_to_rgba_u8(color);\n\n\t\t// ┌▄▄┬──┬──┬──┐\n\t\t// ├▀▀┼──┼──┼──┤\n\t\t// ├──┼──┼▄▄┼──┤\n\t\t// ├──┼──┼▀▀┼──┤\n\t\t// └──┴──┴──┴──┘\n\t\t// Set pixels at (0,0) and (2,2) to the specified color\n\t\tlet pixels = [(0, 0), (2, 2)];\n\t\tfor &(x, y) in &pixels {\n\t\t\tlet index = ((y * PATTERN_WIDTH + x) * 4) as usize;\n\t\t\tdata[index..index + 4].copy_from_slice(&rgba);\n\t\t}\n\n\t\tlet image = peniko::ImageBrush {\n\t\t\timage: peniko::ImageData {\n\t\t\t\tdata: data.into(),\n\t\t\t\tformat: peniko::ImageFormat::Rgba8,\n\t\t\t\twidth: PATTERN_WIDTH,\n\t\t\t\theight: PATTERN_HEIGHT,\n\t\t\t\talpha_type: peniko::ImageAlphaType::Alpha,\n\t\t\t},\n\t\t\tsampler: peniko::ImageSampler {\n\t\t\t\tx_extend: peniko::Extend::Repeat,\n\t\t\t\ty_extend: peniko::Extend::Repeat,\n\t\t\t\tquality: peniko::ImageQuality::default(),\n\t\t\t\talpha: 1.,\n\t\t\t},\n\t\t};\n\n\t\tlet path = self.push_path(subpaths, transform);\n\t\tlet brush = peniko::Brush::Image(image);\n\n\t\tself.scene.fill(peniko::Fill::NonZero, self.get_transform(), &brush, None, &path);\n\t}\n\n\tfn text(&mut self, text: &str, font_color: &str, background_color: Option<&str>, transform: DAffine2, padding: f64, pivot: [Pivot; 2]) {\n\t\t// Use the proper text-to-path system for accurate text rendering\n\t\tconst FONT_SIZE: f64 = 12.;\n\n\t\t// Create typesetting configuration\n\t\tlet typesetting = TypesettingConfig {\n\t\t\tfont_size: FONT_SIZE,\n\t\t\tline_height_ratio: 1.2,\n\t\t\tcharacter_spacing: 0.,\n\t\t\tmax_width: None,\n\t\t\tmax_height: None,\n\t\t\ttilt: 0.,\n\t\t\talign: TextAlign::Left, // We'll handle alignment manually via pivot\n\t\t};\n\n\t\t// Load Source Sans Pro font data\n\t\t// TODO: Grab this from the node_modules folder (either with `include_bytes!` or ideally at runtime) instead of checking the font file into the repo.\n\t\t// TODO: And maybe use the WOFF2 version (if it's supported) for its smaller, compressed file size.\n\t\tlet font = Font::new(\"Source Sans Pro\".to_string(), \"Regular\".to_string());\n\n\t\t// Get text dimensions directly from layout\n\t\tlet mut text_context = GLOBAL_TEXT_CONTEXT.lock().expect(\"Failed to lock global text context\");\n\t\tlet text_size = text_context.bounding_box(text, &font, &GLOBAL_FONT_CACHE, typesetting, false);\n\t\tlet text_width = text_size.x;\n\t\tlet text_height = text_size.y;\n\t\t// Create a rect from the size (assuming text starts at origin)\n\t\tlet text_bounds = kurbo::Rect::new(0., 0., text_width, text_height);\n\n\t\t// Convert text to vector paths for rendering\n\t\tlet text_table = text_context.to_path(text, &font, &GLOBAL_FONT_CACHE, typesetting, false);\n\n\t\t// Calculate position based on pivot\n\t\tlet mut position = DVec2::ZERO;\n\t\tmatch pivot[0] {\n\t\t\tPivot::Start => position.x = padding,\n\t\t\tPivot::Middle => position.x = -text_width / 2.,\n\t\t\tPivot::End => position.x = -padding - text_width,\n\t\t}\n\t\tmatch pivot[1] {\n\t\t\tPivot::Start => position.y = padding,\n\t\t\tPivot::Middle => position.y -= text_height * 0.5,\n\t\t\tPivot::End => position.y = -padding - text_height,\n\t\t}\n\n\t\tlet text_transform = transform * DAffine2::from_translation(position);\n\t\tlet device_transform = self.get_transform();\n\t\tlet combined_transform = kurbo::Affine::new(text_transform.to_cols_array());\n\t\tlet vello_transform = device_transform * combined_transform;\n\n\t\t// Draw background if specified\n\t\tif let Some(bg_color) = background_color {\n\t\t\tlet bg_rect = kurbo::Rect::new(\n\t\t\t\ttext_bounds.min_x() - padding,\n\t\t\t\ttext_bounds.min_y() - padding,\n\t\t\t\ttext_bounds.max_x() + padding,\n\t\t\t\ttext_bounds.max_y() + padding,\n\t\t\t);\n\t\t\tself.scene.fill(peniko::Fill::NonZero, vello_transform, Self::parse_color(bg_color), None, &bg_rect);\n\t\t}\n\n\t\t// Render the actual text paths\n\t\tself.render_text_paths(&text_table, font_color, vello_transform);\n\t}\n\n\t// Render text paths to the vello scene using existing infrastructure\n\tfn render_text_paths(&mut self, text_table: &Table<Vector>, font_color: &str, base_transform: kurbo::Affine) {\n\t\tlet color = Self::parse_color(font_color);\n\n\t\tfor row in text_table.iter() {\n\t\t\t// Use the existing bezier_to_path infrastructure to convert Vector to BezPath\n\t\t\tlet mut path = BezPath::new();\n\t\t\tlet mut last_point = None;\n\n\t\t\tfor (_, bezier, start_id, end_id) in row.element.segment_iter() {\n\t\t\t\tlet move_to = last_point != Some(start_id);\n\t\t\t\tlast_point = Some(end_id);\n\n\t\t\t\tself.bezier_to_path(bezier, *row.transform, move_to, &mut path);\n\t\t\t}\n\n\t\t\t// Render the path\n\t\t\tself.scene.fill(peniko::Fill::NonZero, base_transform, color, None, &path);\n\t\t}\n\t}\n\n\tfn translation_box(&mut self, translation: DVec2, quad: Quad, typed_string: Option<String>) {\n\t\tif translation.x.abs() > 1e-3 {\n\t\t\tself.dashed_line(quad.top_left(), quad.top_right(), None, None, Some(2.), Some(2.), Some(0.5));\n\n\t\t\tlet width = match typed_string {\n\t\t\t\tSome(ref typed_string) => typed_string,\n\t\t\t\tNone => &format!(\"{:.2}\", translation.x).trim_end_matches('0').trim_end_matches('.').to_string(),\n\t\t\t};\n\t\t\tlet x_transform = DAffine2::from_translation((quad.top_left() + quad.top_right()) / 2.);\n\t\t\tself.text(width, COLOR_OVERLAY_BLUE, None, x_transform, 4., [Pivot::Middle, Pivot::End]);\n\t\t}\n\n\t\tif translation.y.abs() > 1e-3 {\n\t\t\tself.dashed_line(quad.top_left(), quad.bottom_left(), None, None, Some(2.), Some(2.), Some(0.5));\n\n\t\t\tlet height = match typed_string {\n\t\t\t\tSome(ref typed_string) => typed_string,\n\t\t\t\tNone => &format!(\"{:.2}\", translation.y).trim_end_matches('0').trim_end_matches('.').to_string(),\n\t\t\t};\n\t\t\tlet y_transform = DAffine2::from_translation((quad.top_left() + quad.bottom_left()) / 2.);\n\t\t\tlet height_pivot = if translation.x > -1e-3 { Pivot::Start } else { Pivot::End };\n\t\t\tself.text(height, COLOR_OVERLAY_BLUE, None, y_transform, 3., [height_pivot, Pivot::Middle]);\n\t\t}\n\n\t\tif translation.x.abs() > 1e-3 && translation.y.abs() > 1e-3 {\n\t\t\tself.line(quad.top_right(), quad.bottom_right(), None, None);\n\t\t\tself.line(quad.bottom_left(), quad.bottom_right(), None, None);\n\t\t}\n\t}\n\n\tfn snap_to_physical_pixel(&self, p: DVec2) -> DVec2 {\n\t\tlet s = self.viewport.scale();\n\t\tif !s.is_finite() || s <= 0.0 {\n\t\t\treturn p.round();\n\t\t}\n\t\t(p * s).round() / s\n\t}\n\n\tfn snap_to_physical_pixel_center(&self, p: DVec2) -> DVec2 {\n\t\tlet s = self.viewport.scale();\n\t\tif !s.is_finite() || s <= 0.0 {\n\t\t\treturn p.round() - DVec2::splat(0.5);\n\t\t}\n\t\tself.snap_to_physical_pixel(p) - DVec2::splat(0.5 / s)\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/overlays/utility_types_web.rs",
    "content": "use super::utility_functions::{hex_to_rgba_u8, overlay_canvas_context};\nuse crate::consts::{\n\tARC_SWEEP_GIZMO_RADIUS, COLOR_OVERLAY_BLACK, COLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_50, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_WHITE_05, COLOR_OVERLAY_YELLOW,\n\tCOLOR_OVERLAY_YELLOW_DULL, COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, DOWEL_PIN_RADIUS,\n\tGRADIENT_MIDPOINT_DIAMOND_RADIUS, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, RESIZE_HANDLE_SIZE, SEGMENT_SELECTED_THICKNESS,\n\tSKEW_TRIANGLE_OFFSET, SKEW_TRIANGLE_SIZE,\n};\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::prelude::Message;\nuse crate::messages::viewport::ViewportMessageHandler;\nuse core::borrow::Borrow;\nuse core::f64::consts::{FRAC_PI_2, PI, TAU};\nuse glam::{DAffine2, DVec2};\nuse graphene_std::math::quad::Quad;\nuse graphene_std::subpath::Subpath;\nuse graphene_std::vector::click_target::ClickTargetType;\nuse graphene_std::vector::misc::{dvec2_to_point, point_to_dvec2};\nuse graphene_std::vector::{PointId, SegmentId, Vector};\nuse kurbo::{self, Affine, CubicBez, ParamCurve, PathSeg};\nuse std::collections::HashMap;\nuse wasm_bindgen::{JsCast, JsValue};\nuse web_sys::{OffscreenCanvas, OffscreenCanvasRenderingContext2d};\n\npub type OverlayProvider = fn(OverlayContext) -> Message;\n\npub fn empty_provider() -> OverlayProvider {\n\t|_| Message::NoOp\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub enum GizmoEmphasis {\n\tRegular,\n\tHovered,\n\tActive,\n}\n\n/// Types of overlays used by DocumentMessage to enable/disable the selected set of viewport overlays.\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum OverlaysType {\n\tArtboardName,\n\tCompassRose,\n\tQuickMeasurement,\n\tTransformMeasurement,\n\tTransformCage,\n\tHoverOutline,\n\tSelectionOutline,\n\tLayerOriginCross,\n\tPivot,\n\tOrigin,\n\tPath,\n\tAnchors,\n\tHandles,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Copy, Clone, Debug, serde::Serialize, serde::Deserialize)]\n#[serde(default)]\npub struct OverlaysVisibilitySettings {\n\tpub all: bool,\n\tpub artboard_name: bool,\n\tpub compass_rose: bool,\n\tpub quick_measurement: bool,\n\tpub transform_measurement: bool,\n\tpub transform_cage: bool,\n\tpub hover_outline: bool,\n\tpub selection_outline: bool,\n\tpub layer_origin_cross: bool,\n\tpub pivot: bool,\n\tpub origin: bool,\n\tpub path: bool,\n\tpub anchors: bool,\n\tpub handles: bool,\n}\n\nimpl Default for OverlaysVisibilitySettings {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tall: true,\n\t\t\tartboard_name: true,\n\t\t\tcompass_rose: true,\n\t\t\tquick_measurement: true,\n\t\t\ttransform_measurement: true,\n\t\t\ttransform_cage: true,\n\t\t\thover_outline: true,\n\t\t\tselection_outline: true,\n\t\t\tlayer_origin_cross: true,\n\t\t\tpivot: true,\n\t\t\torigin: true,\n\t\t\tpath: true,\n\t\t\tanchors: true,\n\t\t\thandles: true,\n\t\t}\n\t}\n}\n\nimpl OverlaysVisibilitySettings {\n\tpub fn all(&self) -> bool {\n\t\tself.all\n\t}\n\n\tpub fn artboard_name(&self) -> bool {\n\t\tself.all && self.artboard_name\n\t}\n\n\tpub fn compass_rose(&self) -> bool {\n\t\tself.all && self.compass_rose\n\t}\n\n\tpub fn quick_measurement(&self) -> bool {\n\t\tself.all && self.quick_measurement\n\t}\n\n\tpub fn transform_measurement(&self) -> bool {\n\t\tself.all && self.transform_measurement\n\t}\n\n\tpub fn transform_cage(&self) -> bool {\n\t\tself.all && self.transform_cage\n\t}\n\n\tpub fn hover_outline(&self) -> bool {\n\t\tself.all && self.hover_outline\n\t}\n\n\tpub fn selection_outline(&self) -> bool {\n\t\tself.all && self.selection_outline\n\t}\n\n\tpub fn layer_origin_cross(&self) -> bool {\n\t\tself.all && self.layer_origin_cross\n\t}\n\n\tpub fn pivot(&self) -> bool {\n\t\tself.all && self.pivot\n\t}\n\n\tpub fn origin(&self) -> bool {\n\t\tself.all && self.origin\n\t}\n\n\tpub fn path(&self) -> bool {\n\t\tself.all && self.path\n\t}\n\n\tpub fn anchors(&self) -> bool {\n\t\tself.all && self.anchors\n\t}\n\n\tpub fn handles(&self) -> bool {\n\t\tself.all && self.anchors && self.handles\n\t}\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub struct OverlayContext {\n\t// Serde functionality isn't used but is required by the message system macros\n\t#[serde(skip, default = \"overlay_canvas_context\")]\n\tpub render_context: web_sys::CanvasRenderingContext2d,\n\tpub viewport: ViewportMessageHandler,\n\tpub visibility_settings: OverlaysVisibilitySettings,\n}\n// Message hashing isn't used but is required by the message system macros\nimpl core::hash::Hash for OverlayContext {\n\tfn hash<H: std::hash::Hasher>(&self, _state: &mut H) {}\n}\n\nimpl OverlayContext {\n\tpub fn quad(&mut self, quad: Quad, stroke_color: Option<&str>, color_fill: Option<&str>) {\n\t\tself.dashed_polygon(&quad.0, stroke_color, color_fill, None, None, None);\n\t}\n\n\tpub fn draw_triangle(&mut self, base: DVec2, direction: DVec2, size: f64, color_fill: Option<&str>, color_stroke: Option<&str>) {\n\t\tlet color_fill = color_fill.unwrap_or(COLOR_OVERLAY_WHITE);\n\t\tlet color_stroke = color_stroke.unwrap_or(COLOR_OVERLAY_BLUE);\n\t\tlet normal = direction.perp();\n\t\tlet top = base + direction * size;\n\t\tlet edge1 = base + normal * size / 2.;\n\t\tlet edge2 = base - normal * size / 2.;\n\n\t\tself.start_dpi_aware_transform();\n\n\t\tself.render_context.begin_path();\n\t\tself.render_context.move_to(top.x, top.y);\n\t\tself.render_context.line_to(edge1.x, edge1.y);\n\t\tself.render_context.line_to(edge2.x, edge2.y);\n\t\tself.render_context.close_path();\n\n\t\tself.render_context.set_fill_style_str(color_fill);\n\t\tself.render_context.set_stroke_style_str(color_stroke);\n\t\tself.render_context.fill();\n\t\tself.render_context.stroke();\n\n\t\tself.end_dpi_aware_transform();\n\t}\n\n\tpub fn dashed_quad(&mut self, quad: Quad, stroke_color: Option<&str>, color_fill: Option<&str>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {\n\t\tself.dashed_polygon(&quad.0, stroke_color, color_fill, dash_width, dash_gap_width, dash_offset);\n\t}\n\n\tpub fn polygon(&mut self, polygon: &[DVec2], stroke_color: Option<&str>, color_fill: Option<&str>) {\n\t\tself.dashed_polygon(polygon, stroke_color, color_fill, None, None, None);\n\t}\n\n\tpub fn dashed_polygon(&mut self, polygon: &[DVec2], stroke_color: Option<&str>, color_fill: Option<&str>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {\n\t\tif polygon.len() < 2 {\n\t\t\treturn;\n\t\t}\n\n\t\tself.start_dpi_aware_transform();\n\n\t\t// Set the dash pattern\n\t\tif let Some(dash_width) = dash_width {\n\t\t\tlet dash_gap_width = dash_gap_width.unwrap_or(1.);\n\t\t\tlet array = js_sys::Array::new();\n\t\t\tarray.push(&JsValue::from(dash_width));\n\t\t\tarray.push(&JsValue::from(dash_gap_width));\n\n\t\t\tif let Some(dash_offset) = dash_offset {\n\t\t\t\tif dash_offset != 0. {\n\t\t\t\t\tself.render_context.set_line_dash_offset(dash_offset);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tself.render_context\n\t\t\t\t.set_line_dash(&JsValue::from(array))\n\t\t\t\t.map_err(|error| log::warn!(\"Error drawing dashed line: {:?}\", error))\n\t\t\t\t.ok();\n\t\t}\n\n\t\tself.render_context.begin_path();\n\t\tself.render_context.move_to(polygon.last().unwrap().x.round() - 0.5, polygon.last().unwrap().y.round() - 0.5);\n\n\t\tfor point in polygon {\n\t\t\tself.render_context.line_to(point.x.round() - 0.5, point.y.round() - 0.5);\n\t\t}\n\n\t\tif let Some(color_fill) = color_fill {\n\t\t\tself.render_context.set_fill_style_str(color_fill);\n\t\t\tself.render_context.fill();\n\t\t}\n\n\t\tlet stroke_color = stroke_color.unwrap_or(COLOR_OVERLAY_BLUE);\n\t\tself.render_context.set_stroke_style_str(stroke_color);\n\t\tself.render_context.stroke();\n\n\t\t// Reset the dash pattern back to solid\n\t\tif dash_width.is_some() {\n\t\t\tself.render_context\n\t\t\t\t.set_line_dash(&JsValue::from(js_sys::Array::new()))\n\t\t\t\t.map_err(|error| log::warn!(\"Error drawing dashed line: {:?}\", error))\n\t\t\t\t.ok();\n\t\t}\n\t\tif dash_offset.is_some() && dash_offset != Some(0.) {\n\t\t\tself.render_context.set_line_dash_offset(0.);\n\t\t}\n\n\t\tself.end_dpi_aware_transform();\n\t}\n\n\tpub fn line(&mut self, start: DVec2, end: DVec2, color: Option<&str>, thickness: Option<f64>) {\n\t\tself.dashed_line(start, end, color, thickness, None, None, None)\n\t}\n\n\t#[allow(clippy::too_many_arguments)]\n\tpub fn dashed_line(&mut self, start: DVec2, end: DVec2, color: Option<&str>, thickness: Option<f64>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {\n\t\tself.start_dpi_aware_transform();\n\n\t\t// Set the dash pattern\n\t\tif let Some(dash_width) = dash_width {\n\t\t\tlet dash_gap_width = dash_gap_width.unwrap_or(1.);\n\t\t\tlet array = js_sys::Array::new();\n\t\t\tarray.push(&JsValue::from(dash_width));\n\t\t\tarray.push(&JsValue::from(dash_gap_width));\n\n\t\t\tif let Some(dash_offset) = dash_offset {\n\t\t\t\tif dash_offset != 0. {\n\t\t\t\t\tself.render_context.set_line_dash_offset(dash_offset);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tself.render_context\n\t\t\t\t.set_line_dash(&JsValue::from(array))\n\t\t\t\t.map_err(|error| log::warn!(\"Error drawing dashed line: {:?}\", error))\n\t\t\t\t.ok();\n\t\t}\n\n\t\tlet start = start.round() - DVec2::splat(0.5);\n\t\tlet end = end.round() - DVec2::splat(0.5);\n\n\t\tself.render_context.begin_path();\n\t\tself.render_context.move_to(start.x, start.y);\n\t\tself.render_context.line_to(end.x, end.y);\n\t\tself.render_context.set_line_width(thickness.unwrap_or(1.));\n\t\tself.render_context.set_stroke_style_str(color.unwrap_or(COLOR_OVERLAY_BLUE));\n\t\tself.render_context.stroke();\n\t\tself.render_context.set_line_width(1.);\n\n\t\t// Reset the dash pattern back to solid\n\t\tif dash_width.is_some() {\n\t\t\tself.render_context\n\t\t\t\t.set_line_dash(&JsValue::from(js_sys::Array::new()))\n\t\t\t\t.map_err(|error| log::warn!(\"Error drawing dashed line: {:?}\", error))\n\t\t\t\t.ok();\n\t\t}\n\t\tif dash_offset.is_some() && dash_offset != Some(0.) {\n\t\t\tself.render_context.set_line_dash_offset(0.);\n\t\t}\n\n\t\tself.end_dpi_aware_transform();\n\t}\n\n\t#[allow(clippy::too_many_arguments)]\n\tpub fn dashed_ellipse(\n\t\t&mut self,\n\t\tcenter: DVec2,\n\t\tradius_x: f64,\n\t\tradius_y: f64,\n\t\trotation: Option<f64>,\n\t\tstart_angle: Option<f64>,\n\t\tend_angle: Option<f64>,\n\t\tcounterclockwise: Option<bool>,\n\t\tcolor_fill: Option<&str>,\n\t\tcolor_stroke: Option<&str>,\n\t\tdash_width: Option<f64>,\n\t\tdash_gap_width: Option<f64>,\n\t\tdash_offset: Option<f64>,\n\t) {\n\t\tlet color_stroke = color_stroke.unwrap_or(COLOR_OVERLAY_BLUE);\n\t\tlet center = center.round();\n\n\t\tself.start_dpi_aware_transform();\n\n\t\tif let Some(dash_width) = dash_width {\n\t\t\tlet dash_gap_width = dash_gap_width.unwrap_or(1.);\n\t\t\tlet array = js_sys::Array::new();\n\t\t\tarray.push(&JsValue::from(dash_width));\n\t\t\tarray.push(&JsValue::from(dash_gap_width));\n\n\t\t\tif let Some(dash_offset) = dash_offset {\n\t\t\t\tif dash_offset != 0. {\n\t\t\t\t\tself.render_context.set_line_dash_offset(dash_offset);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tself.render_context\n\t\t\t\t.set_line_dash(&JsValue::from(array))\n\t\t\t\t.map_err(|error| log::warn!(\"Error drawing dashed line: {:?}\", error))\n\t\t\t\t.ok();\n\t\t}\n\n\t\tself.render_context.begin_path();\n\t\tself.render_context\n\t\t\t.ellipse_with_anticlockwise(\n\t\t\t\tcenter.x,\n\t\t\t\tcenter.y,\n\t\t\t\tradius_x,\n\t\t\t\tradius_y,\n\t\t\t\trotation.unwrap_or_default(),\n\t\t\t\tstart_angle.unwrap_or_default(),\n\t\t\t\tend_angle.unwrap_or(TAU),\n\t\t\t\tcounterclockwise.unwrap_or_default(),\n\t\t\t)\n\t\t\t.expect(\"Failed to draw ellipse\");\n\t\tself.render_context.set_stroke_style_str(color_stroke);\n\n\t\tif let Some(fill_color) = color_fill {\n\t\t\tself.render_context.set_fill_style_str(fill_color);\n\t\t\tself.render_context.fill();\n\t\t}\n\t\tself.render_context.stroke();\n\n\t\t// Reset the dash pattern back to solid\n\t\tif dash_width.is_some() {\n\t\t\tself.render_context\n\t\t\t\t.set_line_dash(&JsValue::from(js_sys::Array::new()))\n\t\t\t\t.map_err(|error| log::warn!(\"Error drawing dashed line: {:?}\", error))\n\t\t\t\t.ok();\n\t\t}\n\t\tif dash_offset.is_some() && dash_offset != Some(0.) {\n\t\t\tself.render_context.set_line_dash_offset(0.);\n\t\t}\n\n\t\tself.end_dpi_aware_transform();\n\t}\n\n\tpub fn dashed_circle(\n\t\t&mut self,\n\t\tposition: DVec2,\n\t\tradius: f64,\n\t\tcolor_fill: Option<&str>,\n\t\tcolor_stroke: Option<&str>,\n\t\tdash_width: Option<f64>,\n\t\tdash_gap_width: Option<f64>,\n\t\tdash_offset: Option<f64>,\n\t\ttransform: Option<DAffine2>,\n\t) {\n\t\tlet color_stroke = color_stroke.unwrap_or(COLOR_OVERLAY_BLUE);\n\t\tlet position = position.round();\n\n\t\tself.start_dpi_aware_transform();\n\n\t\tif let Some(transform) = transform {\n\t\t\tlet [a, b, c, d, e, f] = transform.to_cols_array();\n\t\t\tself.render_context.transform(a, b, c, d, e, f).expect(\"Failed to transform circle\");\n\t\t}\n\n\t\tif let Some(dash_width) = dash_width {\n\t\t\tlet dash_gap_width = dash_gap_width.unwrap_or(1.);\n\t\t\tlet array = js_sys::Array::new();\n\t\t\tarray.push(&JsValue::from(dash_width));\n\t\t\tarray.push(&JsValue::from(dash_gap_width));\n\n\t\t\tif let Some(dash_offset) = dash_offset {\n\t\t\t\tif dash_offset != 0. {\n\t\t\t\t\tself.render_context.set_line_dash_offset(dash_offset);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tself.render_context\n\t\t\t\t.set_line_dash(&JsValue::from(array))\n\t\t\t\t.map_err(|error| log::warn!(\"Error drawing dashed line: {:?}\", error))\n\t\t\t\t.ok();\n\t\t}\n\n\t\tself.render_context.begin_path();\n\t\tself.render_context.arc(position.x, position.y, radius, 0., TAU).expect(\"Failed to draw the circle\");\n\t\tself.render_context.set_stroke_style_str(color_stroke);\n\n\t\tif let Some(fill_color) = color_fill {\n\t\t\tself.render_context.set_fill_style_str(fill_color);\n\t\t\tself.render_context.fill();\n\t\t}\n\t\tself.render_context.stroke();\n\n\t\t// Reset the dash pattern back to solid\n\t\tif dash_width.is_some() {\n\t\t\tself.render_context\n\t\t\t\t.set_line_dash(&JsValue::from(js_sys::Array::new()))\n\t\t\t\t.map_err(|error| log::warn!(\"Error drawing dashed line: {:?}\", error))\n\t\t\t\t.ok();\n\t\t}\n\t\tif dash_offset.is_some() && dash_offset != Some(0.) {\n\t\t\tself.render_context.set_line_dash_offset(0.);\n\t\t}\n\n\t\tself.end_dpi_aware_transform();\n\t}\n\n\tpub fn circle(&mut self, position: DVec2, radius: f64, color_fill: Option<&str>, color_stroke: Option<&str>) {\n\t\tself.dashed_circle(position, radius, color_fill, color_stroke, None, None, None, None);\n\t}\n\n\tpub fn manipulator_handle(&mut self, position: DVec2, selected: bool, color: Option<&str>) {\n\t\tself.start_dpi_aware_transform();\n\n\t\tlet position = position.round() - DVec2::splat(0.5);\n\n\t\tself.render_context.begin_path();\n\t\tself.render_context\n\t\t\t.arc(position.x, position.y, MANIPULATOR_GROUP_MARKER_SIZE / 2., 0., TAU)\n\t\t\t.expect(\"Failed to draw the circle\");\n\n\t\tlet fill = if selected { COLOR_OVERLAY_BLUE } else { COLOR_OVERLAY_WHITE };\n\t\tself.render_context.set_fill_style_str(fill);\n\t\tself.render_context.set_stroke_style_str(color.unwrap_or(COLOR_OVERLAY_BLUE));\n\t\tself.render_context.fill();\n\t\tself.render_context.stroke();\n\n\t\tself.end_dpi_aware_transform();\n\t}\n\n\tpub fn manipulator_anchor(&mut self, position: DVec2, selected: bool, color: Option<&str>) {\n\t\tlet color_stroke = color.unwrap_or(COLOR_OVERLAY_BLUE);\n\t\tlet color_fill = if selected { color_stroke } else { COLOR_OVERLAY_WHITE };\n\t\tself.square(position, None, Some(color_fill), Some(color_stroke));\n\t}\n\n\tpub fn gradient_color_stop(&mut self, position: DVec2, emphasis: GizmoEmphasis, color: &str, small: bool) {\n\t\tself.start_dpi_aware_transform();\n\n\t\tlet position = position.round() - DVec2::splat(0.5);\n\n\t\tlet radius_offset = match emphasis {\n\t\t\tGizmoEmphasis::Regular => 0.,\n\t\t\tGizmoEmphasis::Hovered => 0.5,\n\t\t\tGizmoEmphasis::Active => 1.,\n\t\t};\n\t\tlet stroke_width = radius_offset * 2. + 1.;\n\t\tlet radius = (MANIPULATOR_GROUP_MARKER_SIZE / 1.5 + 1. + radius_offset) * if small { 0.5 } else { 1. };\n\n\t\tlet draw_circle = |radius: f64, width: Option<f64>, color: &str| {\n\t\t\tself.render_context.begin_path();\n\t\t\tself.render_context.arc(position.x, position.y, radius, 0., TAU).expect(\"Failed to draw the circle\");\n\n\t\t\tif let Some(width) = width {\n\t\t\t\tself.render_context.set_line_width(width);\n\t\t\t\tself.render_context.set_stroke_style_str(color);\n\t\t\t\tself.render_context.stroke();\n\t\t\t\tself.render_context.set_line_width(1.);\n\t\t\t} else {\n\t\t\t\tself.render_context.set_fill_style_str(color);\n\t\t\t\tself.render_context.fill();\n\t\t\t}\n\t\t};\n\t\t// Fill\n\t\tdraw_circle(radius, None, color);\n\t\t// Stroke (inner)\n\t\tdraw_circle(radius + stroke_width / 2., Some(1.), COLOR_OVERLAY_BLACK);\n\t\t// Stroke (outer)\n\t\tdraw_circle(radius, Some(stroke_width), COLOR_OVERLAY_WHITE);\n\n\t\tself.end_dpi_aware_transform();\n\t}\n\n\tpub fn gradient_midpoint(&mut self, position: DVec2, emphasis: GizmoEmphasis, angle: f64) {\n\t\tself.start_dpi_aware_transform();\n\n\t\tlet position = position.round() - DVec2::splat(0.5);\n\n\t\t// Rotate diamond points by the gradient line angle\n\t\tlet (sin, cos) = angle.sin_cos();\n\t\tlet rotate = |dx: f64, dy: f64| DVec2::new(dx * cos - dy * sin, dx * sin + dy * cos);\n\n\t\tlet top = rotate(0., -GRADIENT_MIDPOINT_DIAMOND_RADIUS);\n\t\tlet right = rotate(GRADIENT_MIDPOINT_DIAMOND_RADIUS, 0.);\n\t\tlet bottom = rotate(0., GRADIENT_MIDPOINT_DIAMOND_RADIUS);\n\t\tlet left = rotate(-GRADIENT_MIDPOINT_DIAMOND_RADIUS, 0.);\n\n\t\tself.render_context.begin_path();\n\t\tself.render_context.move_to(position.x + top.x, position.y + top.y);\n\t\tself.render_context.line_to(position.x + right.x, position.y + right.y);\n\t\tself.render_context.line_to(position.x + bottom.x, position.y + bottom.y);\n\t\tself.render_context.line_to(position.x + left.x, position.y + left.y);\n\t\tself.render_context.close_path();\n\n\t\tlet (fill, stroke_width) = match emphasis {\n\t\t\tGizmoEmphasis::Regular => (COLOR_OVERLAY_WHITE, 1.),\n\t\t\tGizmoEmphasis::Hovered => (COLOR_OVERLAY_WHITE, 2.),\n\t\t\tGizmoEmphasis::Active => (COLOR_OVERLAY_BLUE, 1.),\n\t\t};\n\t\tself.render_context.set_fill_style_str(fill);\n\t\tself.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE);\n\t\tself.render_context.fill();\n\t\tself.render_context.set_line_width(stroke_width);\n\t\tself.render_context.stroke();\n\t\tself.render_context.set_line_width(1.);\n\n\t\tself.end_dpi_aware_transform();\n\t}\n\n\tpub fn hover_manipulator_handle(&mut self, position: DVec2, selected: bool) {\n\t\tself.start_dpi_aware_transform();\n\n\t\tlet position = position.round() - DVec2::splat(0.5);\n\n\t\tself.render_context.begin_path();\n\t\tself.render_context\n\t\t\t.arc(position.x, position.y, (MANIPULATOR_GROUP_MARKER_SIZE + 2.) / 2., 0., TAU)\n\t\t\t.expect(\"Failed to draw the circle\");\n\n\t\tself.render_context.set_fill_style_str(COLOR_OVERLAY_BLUE_50);\n\t\tself.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE_50);\n\t\tself.render_context.fill();\n\t\tself.render_context.stroke();\n\n\t\tself.render_context.begin_path();\n\t\tself.render_context\n\t\t\t.arc(position.x, position.y, MANIPULATOR_GROUP_MARKER_SIZE / 2., 0., TAU)\n\t\t\t.expect(\"Failed to draw the circle\");\n\n\t\tlet color_fill = if selected { COLOR_OVERLAY_BLUE } else { COLOR_OVERLAY_WHITE };\n\n\t\tself.render_context.set_fill_style_str(color_fill);\n\t\tself.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE);\n\t\tself.render_context.fill();\n\t\tself.render_context.stroke();\n\n\t\tself.end_dpi_aware_transform();\n\t}\n\n\tpub fn hover_manipulator_anchor(&mut self, position: DVec2, selected: bool) {\n\t\tself.square(position, Some(MANIPULATOR_GROUP_MARKER_SIZE + 2.), Some(COLOR_OVERLAY_BLUE_50), Some(COLOR_OVERLAY_BLUE_50));\n\t\tlet color_fill = if selected { COLOR_OVERLAY_BLUE } else { COLOR_OVERLAY_WHITE };\n\t\tself.square(position, None, Some(color_fill), Some(COLOR_OVERLAY_BLUE));\n\t}\n\n\tpub fn resize_handle(&mut self, position: DVec2, rotation: f64) {\n\t\tlet quad = DAffine2::from_angle_translation(rotation, position) * Quad::from_box([DVec2::splat(-RESIZE_HANDLE_SIZE / 2.), DVec2::splat(RESIZE_HANDLE_SIZE / 2.)]);\n\t\tself.quad(quad, None, Some(COLOR_OVERLAY_WHITE));\n\t}\n\n\tpub fn skew_handles(&mut self, edge_start: DVec2, edge_end: DVec2) {\n\t\tlet edge_dir = (edge_end - edge_start).normalize();\n\t\tlet mid = edge_end.midpoint(edge_start);\n\n\t\tfor edge in [edge_dir, -edge_dir] {\n\t\t\tself.draw_triangle(mid + edge * (3. + SKEW_TRIANGLE_OFFSET), edge, SKEW_TRIANGLE_SIZE, None, None);\n\t\t}\n\t}\n\n\t/// Transforms the canvas context to adjust for DPI scaling\n\t///\n\t/// Overwrites all existing tranforms. This operation can be reversed with [`Self::reset_transform`].\n\tfn start_dpi_aware_transform(&self) {\n\t\tlet [a, b, c, d, e, f] = DAffine2::from_scale(DVec2::splat(self.viewport.scale())).to_cols_array();\n\t\tself.render_context\n\t\t\t.set_transform(a, b, c, d, e, f)\n\t\t\t.expect(\"transform should be able to be set to be able to account for DPI\");\n\t}\n\n\t/// Un-transforms the Canvas context to adjust for DPI scaling\n\t///\n\t/// Warning: this function doesn't only reset the DPI scaling adjustment, it resets the entire transform.\n\tfn end_dpi_aware_transform(&self) {\n\t\tself.render_context.reset_transform().expect(\"transform should be able to be reset to be able to account for DPI\");\n\t}\n\n\tpub fn square(&mut self, position: DVec2, size: Option<f64>, color_fill: Option<&str>, color_stroke: Option<&str>) {\n\t\tlet size = size.unwrap_or(MANIPULATOR_GROUP_MARKER_SIZE);\n\t\tlet color_fill = color_fill.unwrap_or(COLOR_OVERLAY_WHITE);\n\t\tlet color_stroke = color_stroke.unwrap_or(COLOR_OVERLAY_BLUE);\n\n\t\tlet position = position.round() - DVec2::splat(0.5);\n\t\tlet corner = position - DVec2::splat(size) / 2.;\n\n\t\tself.start_dpi_aware_transform();\n\n\t\tself.render_context.begin_path();\n\t\tself.render_context.rect(corner.x, corner.y, size, size);\n\t\tself.render_context.set_fill_style_str(color_fill);\n\t\tself.render_context.set_stroke_style_str(color_stroke);\n\t\tself.render_context.set_line_width(1.);\n\t\tself.render_context.fill();\n\t\tself.render_context.stroke();\n\n\t\tself.end_dpi_aware_transform();\n\t}\n\n\tpub fn pixel(&mut self, position: DVec2, color: Option<&str>) {\n\t\tlet size = 1.;\n\t\tlet color_fill = color.unwrap_or(COLOR_OVERLAY_WHITE);\n\n\t\tlet position = position.round() - DVec2::splat(0.5);\n\t\tlet corner = position - DVec2::splat(size) / 2.;\n\n\t\tself.start_dpi_aware_transform();\n\n\t\tself.render_context.begin_path();\n\t\tself.render_context.rect(corner.x, corner.y, size, size);\n\t\tself.render_context.set_fill_style_str(color_fill);\n\t\tself.render_context.fill();\n\n\t\tself.end_dpi_aware_transform();\n\t}\n\n\tpub fn draw_arc(&mut self, center: DVec2, radius: f64, start_from: f64, end_at: f64) {\n\t\tlet segments = ((end_at - start_from).abs() / (std::f64::consts::PI / 4.)).ceil() as usize;\n\t\tlet step = (end_at - start_from) / segments as f64;\n\t\tlet half_step = step / 2.;\n\t\tlet factor = 4. / 3. * half_step.sin() / (1. + half_step.cos());\n\n\t\tself.render_context.begin_path();\n\n\t\tfor i in 0..segments {\n\t\t\tlet start_angle = start_from + step * i as f64;\n\t\t\tlet end_angle = start_angle + step;\n\t\t\tlet start_vec = DVec2::from_angle(start_angle);\n\t\t\tlet end_vec = DVec2::from_angle(end_angle);\n\n\t\t\tlet start = center + radius * start_vec;\n\t\t\tlet end = center + radius * end_vec;\n\n\t\t\tlet handle_start = start + start_vec.perp() * radius * factor;\n\t\t\tlet handle_end = end - end_vec.perp() * radius * factor;\n\n\t\t\tlet bezier = PathSeg::Cubic(CubicBez::new(dvec2_to_point(start), dvec2_to_point(handle_start), dvec2_to_point(handle_end), dvec2_to_point(end)));\n\n\t\t\tself.bezier_command(bezier, DAffine2::IDENTITY, i == 0);\n\t\t}\n\n\t\tself.render_context.stroke();\n\t}\n\n\tpub fn draw_arc_gizmo_angle(&mut self, pivot: DVec2, bold_radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) {\n\t\tlet end_point1 = pivot + bold_radius * DVec2::from_angle(angle + offset_angle);\n\t\tself.line(pivot, end_point1, None, None);\n\t\tself.draw_arc(pivot, arc_radius, offset_angle, (angle) % TAU + offset_angle);\n\t}\n\n\tpub fn draw_angle(&mut self, pivot: DVec2, radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) {\n\t\tlet end_point1 = pivot + radius * DVec2::from_angle(angle + offset_angle);\n\t\tlet end_point2 = pivot + radius * DVec2::from_angle(offset_angle);\n\t\tself.line(pivot, end_point1, None, None);\n\t\tself.dashed_line(pivot, end_point2, None, None, Some(2.), Some(2.), Some(0.5));\n\t\tself.draw_arc(pivot, arc_radius, offset_angle, (angle) % TAU + offset_angle);\n\t}\n\n\tpub fn draw_scale(&mut self, start: DVec2, scale: f64, radius: f64, text: &str) {\n\t\tlet sign = scale.signum();\n\t\tlet fill_color = Some(COLOR_OVERLAY_WHITE_05);\n\t\tself.line(start + DVec2::X * radius * sign, start + DVec2::X * (radius * scale), None, None);\n\t\tself.circle(start, radius, fill_color, None);\n\t\tself.circle(start, radius * scale.abs(), fill_color, None);\n\t\tself.text(\n\t\t\ttext,\n\t\t\tCOLOR_OVERLAY_BLUE,\n\t\t\tNone,\n\t\t\tDAffine2::from_translation(start + sign * DVec2::X * radius * (1. + scale.abs()) / 2.),\n\t\t\t2.,\n\t\t\t[Pivot::Middle, Pivot::End],\n\t\t)\n\t}\n\n\tpub fn compass_rose(&mut self, compass_center: DVec2, angle: f64, show_compass_with_hover_ring: Option<bool>) {\n\t\tconst HOVER_RING_OUTER_RADIUS: f64 = COMPASS_ROSE_HOVER_RING_DIAMETER / 2.;\n\t\tconst MAIN_RING_OUTER_RADIUS: f64 = COMPASS_ROSE_MAIN_RING_DIAMETER / 2.;\n\t\tconst MAIN_RING_INNER_RADIUS: f64 = COMPASS_ROSE_RING_INNER_DIAMETER / 2.;\n\t\tconst ARROW_RADIUS: f64 = COMPASS_ROSE_ARROW_SIZE / 2.;\n\t\tconst HOVER_RING_STROKE_WIDTH: f64 = HOVER_RING_OUTER_RADIUS - MAIN_RING_INNER_RADIUS;\n\t\tconst HOVER_RING_CENTERLINE_RADIUS: f64 = (HOVER_RING_OUTER_RADIUS + MAIN_RING_INNER_RADIUS) / 2.;\n\t\tconst MAIN_RING_STROKE_WIDTH: f64 = MAIN_RING_OUTER_RADIUS - MAIN_RING_INNER_RADIUS;\n\t\tconst MAIN_RING_CENTERLINE_RADIUS: f64 = (MAIN_RING_OUTER_RADIUS + MAIN_RING_INNER_RADIUS) / 2.;\n\n\t\tlet Some(show_hover_ring) = show_compass_with_hover_ring else { return };\n\n\t\tself.start_dpi_aware_transform();\n\n\t\tlet center = compass_center.round() - DVec2::splat(0.5);\n\n\t\t// Save the old line width to restore it later\n\t\tlet old_line_width = self.render_context.line_width();\n\n\t\t// Hover ring\n\t\tif show_hover_ring {\n\t\t\tself.render_context.set_line_width(HOVER_RING_STROKE_WIDTH);\n\t\t\tself.render_context.begin_path();\n\t\t\tself.render_context.arc(center.x, center.y, HOVER_RING_CENTERLINE_RADIUS, 0., TAU).expect(\"Failed to draw hover ring\");\n\t\t\tself.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE_50);\n\t\t\tself.render_context.stroke();\n\t\t}\n\n\t\t// Arrows\n\t\tself.render_context.set_line_width(0.01);\n\t\tfor i in 0..4 {\n\t\t\tlet direction = DVec2::from_angle(i as f64 * FRAC_PI_2 + angle);\n\t\t\tlet color = if i % 2 == 0 { COLOR_OVERLAY_RED } else { COLOR_OVERLAY_GREEN };\n\n\t\t\tlet tip = center + direction * HOVER_RING_OUTER_RADIUS;\n\t\t\tlet base = center + direction * (MAIN_RING_INNER_RADIUS + MAIN_RING_OUTER_RADIUS) / 2.;\n\n\t\t\tlet r = (ARROW_RADIUS.powi(2) + MAIN_RING_INNER_RADIUS.powi(2)).sqrt();\n\t\t\tlet (cos, sin) = (MAIN_RING_INNER_RADIUS / r, ARROW_RADIUS / r);\n\t\t\tlet side1 = center + r * DVec2::new(cos * direction.x - sin * direction.y, sin * direction.x + direction.y * cos);\n\t\t\tlet side2 = center + r * DVec2::new(cos * direction.x + sin * direction.y, -sin * direction.x + direction.y * cos);\n\n\t\t\tself.render_context.begin_path();\n\t\t\tself.render_context.move_to(tip.x, tip.y);\n\t\t\tself.render_context.line_to(side1.x, side1.y);\n\t\t\tself.render_context.line_to(base.x, base.y);\n\t\t\tself.render_context.line_to(side2.x, side2.y);\n\t\t\tself.render_context.close_path();\n\n\t\t\tself.render_context.set_fill_style_str(color);\n\t\t\tself.render_context.fill();\n\t\t\tself.render_context.set_stroke_style_str(color);\n\t\t\tself.render_context.stroke();\n\t\t}\n\n\t\t// Main ring\n\t\tself.render_context.set_line_width(MAIN_RING_STROKE_WIDTH);\n\t\tself.render_context.begin_path();\n\t\tself.render_context.arc(center.x, center.y, MAIN_RING_CENTERLINE_RADIUS, 0., TAU).expect(\"Failed to draw main ring\");\n\t\tself.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE);\n\t\tself.render_context.stroke();\n\n\t\t// Restore the old line width\n\t\tself.render_context.set_line_width(old_line_width);\n\t}\n\n\tpub fn pivot(&mut self, position: DVec2, angle: f64) {\n\t\tlet uv = DVec2::from_angle(angle);\n\t\tlet (x, y) = (position.round() - DVec2::splat(0.5)).into();\n\n\t\tself.start_dpi_aware_transform();\n\n\t\t// Circle\n\n\t\tself.render_context.begin_path();\n\t\tself.render_context.arc(x, y, PIVOT_DIAMETER / 2., 0., TAU).expect(\"Failed to draw the circle\");\n\t\tself.render_context.set_fill_style_str(COLOR_OVERLAY_YELLOW);\n\t\tself.render_context.fill();\n\n\t\t// Crosshair\n\n\t\t// Round line caps add half the stroke width to the length on each end, so we subtract that here before halving to get the radius\n\t\tconst CROSSHAIR_RADIUS: f64 = (PIVOT_CROSSHAIR_LENGTH - PIVOT_CROSSHAIR_THICKNESS) / 2.;\n\n\t\tself.render_context.set_stroke_style_str(COLOR_OVERLAY_YELLOW);\n\t\tself.render_context.set_line_cap(\"round\");\n\n\t\tself.render_context.begin_path();\n\t\tself.render_context.move_to(x + CROSSHAIR_RADIUS * uv.x, y + CROSSHAIR_RADIUS * uv.y);\n\t\tself.render_context.line_to(x - CROSSHAIR_RADIUS * uv.x, y - CROSSHAIR_RADIUS * uv.y);\n\t\tself.render_context.stroke();\n\n\t\tself.render_context.begin_path();\n\t\tself.render_context.move_to(x - CROSSHAIR_RADIUS * uv.y, y + CROSSHAIR_RADIUS * uv.x);\n\t\tself.render_context.line_to(x + CROSSHAIR_RADIUS * uv.y, y - CROSSHAIR_RADIUS * uv.x);\n\t\tself.render_context.stroke();\n\n\t\tself.render_context.set_line_cap(\"butt\");\n\n\t\tself.end_dpi_aware_transform();\n\t}\n\n\tpub fn dowel_pin(&mut self, position: DVec2, angle: f64, color: Option<&str>) {\n\t\tlet (x, y) = (position.round() - DVec2::splat(0.5)).into();\n\t\tlet color = color.unwrap_or(COLOR_OVERLAY_YELLOW_DULL);\n\n\t\tself.start_dpi_aware_transform();\n\n\t\t// Draw the background circle with a white fill and blue outline\n\t\tself.render_context.begin_path();\n\t\tself.render_context.arc(x, y, DOWEL_PIN_RADIUS, 0., TAU).expect(\"Failed to draw the circle\");\n\t\tself.render_context.set_fill_style_str(COLOR_OVERLAY_WHITE);\n\t\tself.render_context.fill();\n\t\tself.render_context.set_stroke_style_str(color);\n\t\tself.render_context.stroke();\n\n\t\t// Draw the two blue filled sectors\n\t\tself.render_context.begin_path();\n\t\t// Top-left sector\n\t\tself.render_context.move_to(x, y);\n\t\tself.render_context.arc(x, y, DOWEL_PIN_RADIUS, FRAC_PI_2 + angle, PI + angle).expect(\"Failed to draw arc\");\n\t\tself.render_context.close_path();\n\t\t// Bottom-right sector\n\t\tself.render_context.move_to(x, y);\n\t\tself.render_context.arc(x, y, DOWEL_PIN_RADIUS, PI + FRAC_PI_2 + angle, TAU + angle).expect(\"Failed to draw arc\");\n\t\tself.render_context.close_path();\n\t\tself.render_context.set_fill_style_str(color);\n\t\tself.render_context.fill();\n\n\t\tself.end_dpi_aware_transform();\n\t}\n\n\tpub fn arc_sweep_angle(&mut self, offset_angle: f64, angle: f64, end_point_position: DVec2, bold_radius: f64, pivot: DVec2, text: &str, transform: DAffine2) {\n\t\tself.manipulator_handle(end_point_position, true, None);\n\t\tself.draw_arc_gizmo_angle(pivot, bold_radius, ARC_SWEEP_GIZMO_RADIUS, offset_angle, angle.to_radians());\n\t\tself.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);\n\t}\n\n\t/// Used by the Pen and Path tools to outline the path of the shape.\n\tpub fn outline_vector(&mut self, vector: &Vector, transform: DAffine2) {\n\t\tself.start_dpi_aware_transform();\n\n\t\tself.render_context.begin_path();\n\t\tlet mut last_point = None;\n\t\tfor (_, bezier, start_id, end_id) in vector.segment_iter() {\n\t\t\tlet move_to = last_point != Some(start_id);\n\t\t\tlast_point = Some(end_id);\n\n\t\t\tself.bezier_command(bezier, transform, move_to);\n\t\t}\n\n\t\tself.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE);\n\t\tself.render_context.stroke();\n\n\t\tself.end_dpi_aware_transform();\n\t}\n\n\t/// Used by the Pen tool in order to show how the bezier curve would look like.\n\tpub fn outline_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {\n\t\tself.start_dpi_aware_transform();\n\n\t\tself.render_context.begin_path();\n\t\tself.bezier_command(bezier, transform, true);\n\t\tself.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE);\n\t\tself.render_context.stroke();\n\n\t\tself.end_dpi_aware_transform();\n\t}\n\n\t/// Used by the path tool segment mode in order to show the selected segments.\n\tpub fn outline_select_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {\n\t\tself.start_dpi_aware_transform();\n\n\t\tself.render_context.begin_path();\n\t\tself.bezier_command(bezier, transform, true);\n\t\tself.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE);\n\t\tself.render_context.set_line_width(SEGMENT_SELECTED_THICKNESS);\n\t\tself.render_context.stroke();\n\n\t\tself.render_context.set_line_width(1.);\n\n\t\tself.end_dpi_aware_transform();\n\t}\n\n\tpub fn outline_overlay_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {\n\t\tself.start_dpi_aware_transform();\n\n\t\tself.render_context.begin_path();\n\t\tself.bezier_command(bezier, transform, true);\n\t\tself.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE_50);\n\t\tself.render_context.set_line_width(SEGMENT_SELECTED_THICKNESS);\n\t\tself.render_context.stroke();\n\n\t\tself.render_context.set_line_width(1.);\n\n\t\tself.end_dpi_aware_transform();\n\t}\n\n\tfn bezier_command(&self, bezier: PathSeg, transform: DAffine2, move_to: bool) {\n\t\tself.start_dpi_aware_transform();\n\n\t\tlet bezier = Affine::new(transform.to_cols_array()) * bezier;\n\t\tif move_to {\n\t\t\tself.render_context.move_to(bezier.start().x, bezier.start().y);\n\t\t}\n\t\tmatch bezier.as_path_el() {\n\t\t\tkurbo::PathEl::LineTo(point) => self.render_context.line_to(point.x, point.y),\n\t\t\tkurbo::PathEl::QuadTo(point, point1) => self.render_context.quadratic_curve_to(point.x, point.y, point1.x, point1.y),\n\t\t\tkurbo::PathEl::CurveTo(point, point1, point2) => self.render_context.bezier_curve_to(point.x, point.y, point1.x, point1.y, point2.x, point2.y),\n\t\t\t_ => unreachable!(),\n\t\t}\n\n\t\tself.end_dpi_aware_transform();\n\t}\n\n\tfn push_path(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2) {\n\t\tself.start_dpi_aware_transform();\n\n\t\tself.render_context.begin_path();\n\t\tfor subpath in subpaths {\n\t\t\tlet subpath = subpath.borrow();\n\t\t\tlet mut curves = subpath.iter().peekable();\n\n\t\t\tlet Some(&first) = curves.peek() else {\n\t\t\t\tcontinue;\n\t\t\t};\n\n\t\t\tlet start_point = transform.transform_point2(point_to_dvec2(first.start()));\n\t\t\tself.render_context.move_to(start_point.x, start_point.y);\n\n\t\t\tfor curve in curves {\n\t\t\t\tmatch curve {\n\t\t\t\t\tPathSeg::Line(line) => {\n\t\t\t\t\t\tlet a = transform.transform_point2(point_to_dvec2(line.p1));\n\t\t\t\t\t\tlet a = a.round() - DVec2::splat(0.5);\n\t\t\t\t\t\tself.render_context.line_to(a.x, a.y);\n\t\t\t\t\t}\n\t\t\t\t\tPathSeg::Quad(quad_bez) => {\n\t\t\t\t\t\tlet a = transform.transform_point2(point_to_dvec2(quad_bez.p1));\n\t\t\t\t\t\tlet b = transform.transform_point2(point_to_dvec2(quad_bez.p2));\n\t\t\t\t\t\tlet a = a.round() - DVec2::splat(0.5);\n\t\t\t\t\t\tlet b = b.round() - DVec2::splat(0.5);\n\t\t\t\t\t\tself.render_context.quadratic_curve_to(a.x, a.y, b.x, b.y);\n\t\t\t\t\t}\n\t\t\t\t\tPathSeg::Cubic(cubic_bez) => {\n\t\t\t\t\t\tlet a = transform.transform_point2(point_to_dvec2(cubic_bez.p1));\n\t\t\t\t\t\tlet b = transform.transform_point2(point_to_dvec2(cubic_bez.p2));\n\t\t\t\t\t\tlet c = transform.transform_point2(point_to_dvec2(cubic_bez.p3));\n\t\t\t\t\t\tlet a = a.round() - DVec2::splat(0.5);\n\t\t\t\t\t\tlet b = b.round() - DVec2::splat(0.5);\n\t\t\t\t\t\tlet c = c.round() - DVec2::splat(0.5);\n\t\t\t\t\t\tself.render_context.bezier_curve_to(a.x, a.y, b.x, b.y, c.x, c.y);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif subpath.closed() {\n\t\t\t\tself.render_context.close_path();\n\t\t\t}\n\t\t}\n\n\t\tself.end_dpi_aware_transform();\n\t}\n\n\t/// Used by the Select tool to outline a path or a free point when selected or hovered.\n\tpub fn outline(&mut self, target_types: impl Iterator<Item = impl Borrow<ClickTargetType>>, transform: DAffine2, color: Option<&str>) {\n\t\tlet mut subpaths: Vec<Subpath<PointId>> = vec![];\n\n\t\ttarget_types.for_each(|target_type| match target_type.borrow() {\n\t\t\tClickTargetType::FreePoint(point) => {\n\t\t\t\tself.manipulator_anchor(transform.transform_point2(point.position), false, None);\n\t\t\t}\n\t\t\tClickTargetType::Subpath(subpath) => subpaths.push(subpath.clone()),\n\t\t});\n\n\t\tif !subpaths.is_empty() {\n\t\t\tself.push_path(subpaths.iter(), transform);\n\n\t\t\tlet color = color.unwrap_or(COLOR_OVERLAY_BLUE);\n\t\t\tself.render_context.set_stroke_style_str(color);\n\t\t\tself.render_context.set_line_width(1.);\n\t\t\tself.render_context.stroke();\n\t\t}\n\t}\n\n\t/// Fills the area inside the path. Assumes `color` is in gamma space.\n\t/// Used by the Pen tool to show the path being closed.\n\tpub fn fill_path(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &str) {\n\t\tself.push_path(subpaths, transform);\n\n\t\tself.render_context.set_fill_style_str(color);\n\t\tself.render_context.fill();\n\t}\n\n\t/// Fills the area inside the path with a pattern. Assumes `color` is an sRGB hex string.\n\t/// Used by the fill tool to show the area to be filled.\n\tpub fn fill_path_pattern(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &str) {\n\t\tconst PATTERN_WIDTH: usize = 4;\n\t\tconst PATTERN_HEIGHT: usize = 4;\n\n\t\tlet pattern_canvas = OffscreenCanvas::new(PATTERN_WIDTH as u32, PATTERN_HEIGHT as u32).unwrap();\n\t\tlet pattern_context: OffscreenCanvasRenderingContext2d = pattern_canvas\n\t\t\t.get_context(\"2d\")\n\t\t\t.ok()\n\t\t\t.flatten()\n\t\t\t.expect(\"Failed to get canvas context\")\n\t\t\t.dyn_into()\n\t\t\t.expect(\"Context should be a canvas 2d context\");\n\n\t\t// 4x4 pixels, 4 components (RGBA) per pixel\n\t\tlet mut data = [0_u8; 4 * PATTERN_WIDTH * PATTERN_HEIGHT];\n\n\t\tlet rgba = hex_to_rgba_u8(color);\n\n\t\t// ┌▄▄┬──┬──┬──┐\n\t\t// ├▀▀┼──┼──┼──┤\n\t\t// ├──┼──┼▄▄┼──┤\n\t\t// ├──┼──┼▀▀┼──┤\n\t\t// └──┴──┴──┴──┘\n\t\tlet pixels = [(0, 0), (2, 2)];\n\t\tfor &(x, y) in &pixels {\n\t\t\tlet index = (x + y * PATTERN_WIDTH) * 4;\n\t\t\tdata[index..index + 4].copy_from_slice(&rgba);\n\t\t}\n\n\t\tlet image_data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(wasm_bindgen::Clamped(&data), PATTERN_WIDTH as u32, PATTERN_HEIGHT as u32).unwrap();\n\t\tpattern_context.put_image_data(&image_data, 0., 0.).unwrap();\n\t\tlet pattern = self.render_context.create_pattern_with_offscreen_canvas(&pattern_canvas, \"repeat\").unwrap().unwrap();\n\n\t\tself.push_path(subpaths, transform);\n\n\t\tself.render_context.set_fill_style_canvas_pattern(&pattern);\n\t\tself.render_context.fill();\n\t}\n\n\tpub fn text(&self, text: &str, font_color: &str, background_color: Option<&str>, transform: DAffine2, padding: f64, pivot: [Pivot; 2]) {\n\t\tlet metrics = self.render_context.measure_text(text).expect(\"Failed to measure the text dimensions\");\n\t\tlet x = match pivot[0] {\n\t\t\tPivot::Start => padding,\n\t\t\tPivot::Middle => -(metrics.actual_bounding_box_right() + metrics.actual_bounding_box_left()) / 2.,\n\t\t\tPivot::End => -padding - metrics.actual_bounding_box_right() + metrics.actual_bounding_box_left(),\n\t\t};\n\t\tlet y = match pivot[1] {\n\t\t\tPivot::Start => padding + metrics.font_bounding_box_ascent() - metrics.font_bounding_box_descent(),\n\t\t\tPivot::Middle => (metrics.font_bounding_box_ascent() + metrics.font_bounding_box_descent()) / 2.,\n\t\t\tPivot::End => -padding,\n\t\t};\n\n\t\tlet [a, b, c, d, e, f] = (DAffine2::from_scale(DVec2::splat(self.viewport.scale())) * transform * DAffine2::from_translation(DVec2::new(x, y))).to_cols_array();\n\t\tself.render_context.set_transform(a, b, c, d, e, f).expect(\"Failed to rotate the render context to the specified angle\");\n\n\t\tif let Some(background) = background_color {\n\t\t\tself.render_context.set_fill_style_str(background);\n\t\t\tself.render_context.fill_rect(\n\t\t\t\t-padding,\n\t\t\t\tpadding,\n\t\t\t\tmetrics.actual_bounding_box_right() - metrics.actual_bounding_box_left() + padding * 2.,\n\t\t\t\tmetrics.font_bounding_box_descent() - metrics.font_bounding_box_ascent() - padding * 2.,\n\t\t\t);\n\t\t}\n\n\t\tself.render_context.set_font(r#\"12px \"Source Sans Pro\", Arial, sans-serif\"#);\n\t\tself.render_context.set_fill_style_str(font_color);\n\t\tself.render_context.fill_text(text, 0., 0.).expect(\"Failed to draw the text at the calculated position\");\n\t\tself.render_context.reset_transform().expect(\"Failed to reset the render context transform\");\n\t}\n\n\tpub fn translation_box(&mut self, translation: DVec2, quad: Quad, typed_string: Option<String>) {\n\t\tif translation.x.abs() > 1e-3 {\n\t\t\tself.dashed_line(quad.top_left(), quad.top_right(), None, None, Some(2.), Some(2.), Some(0.5));\n\n\t\t\tlet width = match typed_string {\n\t\t\t\tSome(ref typed_string) => typed_string,\n\t\t\t\tNone => &format!(\"{:.2}\", translation.x).trim_end_matches('0').trim_end_matches('.').to_string(),\n\t\t\t};\n\t\t\tlet x_transform = DAffine2::from_translation((quad.top_left() + quad.top_right()) / 2.);\n\t\t\tself.text(width, COLOR_OVERLAY_BLUE, None, x_transform, 4., [Pivot::Middle, Pivot::End]);\n\t\t}\n\n\t\tif translation.y.abs() > 1e-3 {\n\t\t\tself.dashed_line(quad.top_left(), quad.bottom_left(), None, None, Some(2.), Some(2.), Some(0.5));\n\n\t\t\tlet height = match typed_string {\n\t\t\t\tSome(ref typed_string) => typed_string,\n\t\t\t\tNone => &format!(\"{:.2}\", translation.y).trim_end_matches('0').trim_end_matches('.').to_string(),\n\t\t\t};\n\t\t\tlet y_transform = DAffine2::from_translation((quad.top_left() + quad.bottom_left()) / 2.);\n\t\t\tlet height_pivot = if translation.x > -1e-3 { Pivot::Start } else { Pivot::End };\n\t\t\tself.text(height, COLOR_OVERLAY_BLUE, None, y_transform, 3., [height_pivot, Pivot::Middle]);\n\t\t}\n\n\t\tif translation.x.abs() > 1e-3 && translation.y.abs() > 1e-3 {\n\t\t\tself.line(quad.top_right(), quad.bottom_right(), None, None);\n\t\t\tself.line(quad.bottom_left(), quad.bottom_right(), None, None);\n\t\t}\n\t}\n}\n\npub enum Pivot {\n\tStart,\n\tMiddle,\n\tEnd,\n}\n\npub enum DrawHandles {\n\tAll,\n\tSelectedAnchors(HashMap<LayerNodeIdentifier, Vec<SegmentId>>),\n\tFrontierHandles(HashMap<LayerNodeIdentifier, HashMap<SegmentId, Vec<PointId>>>),\n\tNone,\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/properties_panel/mod.rs",
    "content": "mod properties_panel_message;\npub mod properties_panel_message_handler;\n\n#[doc(inline)]\npub use properties_panel_message::{PropertiesPanelMessage, PropertiesPanelMessageDiscriminant};\n#[doc(inline)]\npub use properties_panel_message_handler::PropertiesPanelMessageHandler;\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/properties_panel/properties_panel_message.rs",
    "content": "use crate::messages::prelude::*;\n\n#[impl_message(Message, DocumentMessage, PropertiesPanel)]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum PropertiesPanelMessage {\n\t// Messages\n\tClear,\n\tRefresh,\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs",
    "content": "use graphene_std::uuid::NodeId;\n\nuse crate::messages::layout::utility_types::widget_prelude::*;\nuse crate::messages::portfolio::document::node_graph::document_node_definitions::NodePropertiesContext;\nuse crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;\nuse crate::messages::portfolio::utility_types::PersistentData;\nuse crate::messages::prelude::*;\nuse crate::node_graph_executor::NodeGraphExecutor;\n\n#[derive(ExtractField)]\npub struct PropertiesPanelMessageContext<'a> {\n\tpub network_interface: &'a mut NodeNetworkInterface,\n\tpub selection_network_path: &'a [NodeId],\n\tpub document_name: &'a str,\n\tpub executor: &'a mut NodeGraphExecutor,\n\tpub persistent_data: &'a PersistentData,\n\tpub properties_panel_open: bool,\n}\n\n#[derive(Debug, Clone, Default, ExtractField)]\npub struct PropertiesPanelMessageHandler {}\n\n#[message_handler_data]\nimpl MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageContext<'_>> for PropertiesPanelMessageHandler {\n\tfn process_message(&mut self, message: PropertiesPanelMessage, responses: &mut VecDeque<Message>, context: PropertiesPanelMessageContext) {\n\t\tlet PropertiesPanelMessageContext {\n\t\t\tnetwork_interface,\n\t\t\tselection_network_path,\n\t\t\tdocument_name,\n\t\t\texecutor,\n\t\t\tpersistent_data,\n\t\t\tproperties_panel_open,\n\t\t} = context;\n\n\t\tmatch message {\n\t\t\tPropertiesPanelMessage::Clear => {\n\t\t\t\tresponses.add(LayoutMessage::SendLayout {\n\t\t\t\t\tlayout: Layout::default(),\n\t\t\t\t\tlayout_target: LayoutTarget::PropertiesPanel,\n\t\t\t\t});\n\t\t\t}\n\t\t\tPropertiesPanelMessage::Refresh => {\n\t\t\t\tif !properties_panel_open {\n\t\t\t\t\tresponses.add(PropertiesPanelMessage::Clear);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet mut node_properties_context = NodePropertiesContext {\n\t\t\t\t\tpersistent_data,\n\t\t\t\t\tresponses,\n\t\t\t\t\tnetwork_interface,\n\t\t\t\t\tselection_network_path,\n\t\t\t\t\tdocument_name,\n\t\t\t\t\texecutor,\n\t\t\t\t};\n\t\t\t\tlet layout = Layout(NodeGraphMessageHandler::collate_properties(&mut node_properties_context));\n\n\t\t\t\tnode_properties_context.responses.add(LayoutMessage::SendLayout {\n\t\t\t\t\tlayout,\n\t\t\t\t\tlayout_target: LayoutTarget::PropertiesPanel,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\tfn actions(&self) -> ActionList {\n\t\tactions!(PropertiesMessageDiscriminant;)\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/utility_types/clipboards.rs",
    "content": "use super::network_interface::NodeTemplate;\nuse graph_craft::document::NodeId;\n\n#[repr(u8)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, PartialEq, Eq, Debug)]\npub enum Clipboard {\n\tInternal,\n\tDevice,\n\n\t_InternalClipboardCount, // Keep this as the last entry of **internal** clipboards since it is used for counting the number of enum variants\n}\n\npub const INTERNAL_CLIPBOARD_COUNT: u8 = Clipboard::_InternalClipboardCount as u8;\n\n#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct CopyBufferEntry {\n\tpub nodes: Vec<(NodeId, NodeTemplate)>,\n\tpub selected: bool,\n\tpub visible: bool,\n\tpub locked: bool,\n\tpub collapsed: bool,\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/utility_types/document_metadata.rs",
    "content": "use super::network_interface::NodeNetworkInterface;\nuse crate::messages::portfolio::document::graph_operation::transform_utils;\nuse crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext;\nuse crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;\nuse crate::messages::portfolio::document::utility_types::network_interface::FlowType;\nuse crate::messages::tool::common_functionality::graph_modification_utils;\nuse glam::{DAffine2, DVec2};\nuse graph_craft::document::NodeId;\nuse graphene_std::math::quad::Quad;\nuse graphene_std::subpath;\nuse graphene_std::transform::Footprint;\nuse graphene_std::vector::click_target::{ClickTarget, ClickTargetType};\nuse graphene_std::vector::{PointId, Vector};\nuse std::collections::{HashMap, HashSet};\nuse std::num::NonZeroU64;\nuse std::sync::Arc;\n\n// ================\n// DocumentMetadata\n// ================\n\n// TODO: To avoid storing a stateful snapshot of some other system's state (which is easily to accidentally get out of sync),\n// TODO: it might be better to have a system that can query the state of the node network on demand.\n#[derive(Debug, Clone, Default)]\npub struct DocumentMetadata {\n\tpub upstream_footprints: HashMap<NodeId, Footprint>,\n\tpub local_transforms: HashMap<NodeId, DAffine2>,\n\tpub first_element_source_ids: HashMap<NodeId, Option<NodeId>>,\n\tpub structure: HashMap<LayerNodeIdentifier, NodeRelations>,\n\tpub click_targets: HashMap<LayerNodeIdentifier, Vec<Arc<ClickTarget>>>,\n\tpub clip_targets: HashSet<NodeId>,\n\tpub vector_modify: HashMap<NodeId, Vector>,\n\t/// Vector data keyed by layer ID, used as fallback when no Path node exists.\n\t/// This provides accurate SegmentIds for layers without explicit Path nodes.\n\tpub layer_vector_data: HashMap<LayerNodeIdentifier, Arc<Vector>>,\n\t/// Transform from document space to viewport space.\n\tpub document_to_viewport: DAffine2,\n}\n\n// =================================\n// DocumentMetadata: Layer iterators\n// =================================\n\nimpl DocumentMetadata {\n\tpub fn all_layers(&self) -> DescendantsIter<'_> {\n\t\tLayerNodeIdentifier::ROOT_PARENT.descendants(self)\n\t}\n\n\tpub fn layer_exists(&self, layer: LayerNodeIdentifier) -> bool {\n\t\tself.structure.contains_key(&layer)\n\t}\n\n\tpub fn click_targets(&self, layer: LayerNodeIdentifier) -> Option<&[Arc<ClickTarget>]> {\n\t\tself.click_targets.get(&layer).map(|x| x.as_slice())\n\t}\n\n\t/// Access the [`NodeRelations`] of a layer.\n\tfn get_relations(&self, node_identifier: LayerNodeIdentifier) -> Option<&NodeRelations> {\n\t\tself.structure.get(&node_identifier)\n\t}\n\n\t/// Mutably access the [`NodeRelations`] of a layer.\n\tfn get_structure_mut(&mut self, node_identifier: LayerNodeIdentifier) -> &mut NodeRelations {\n\t\tself.structure.entry(node_identifier).or_default()\n\t}\n}\n// ============================\n// DocumentMetadata: Transforms\n// ============================\n\nimpl DocumentMetadata {\n\t/// Access the cached transformation to document space from layer space\n\tpub fn transform_to_document(&self, layer: LayerNodeIdentifier) -> DAffine2 {\n\t\tself.document_to_viewport.inverse() * self.transform_to_viewport(layer)\n\t}\n\n\tpub fn transform_to_viewport(&self, layer: LayerNodeIdentifier) -> DAffine2 {\n\t\t// We're not allowed to convert the root parent to a node id\n\t\tif layer == LayerNodeIdentifier::ROOT_PARENT {\n\t\t\treturn self.document_to_viewport;\n\t\t}\n\n\t\tlet footprint = self.upstream_footprints.get(&layer.to_node()).map(|footprint| footprint.transform).unwrap_or(self.document_to_viewport);\n\t\tlet local_transform = self.local_transforms.get(&layer.to_node()).copied().unwrap_or_default();\n\n\t\tfootprint * local_transform\n\t}\n\n\tpub fn transform_to_viewport_if_feeds(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DAffine2 {\n\t\t// We're not allowed to convert the root parent to a node id\n\t\tif layer == LayerNodeIdentifier::ROOT_PARENT {\n\t\t\treturn self.document_to_viewport;\n\t\t}\n\n\t\tlet footprint = self.upstream_footprints.get(&layer.to_node()).map(|footprint| footprint.transform).unwrap_or(self.document_to_viewport);\n\n\t\tlet mut use_local = true;\n\t\tlet graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, network_interface);\n\t\tlet identifier = DefinitionIdentifier::Network(\"Path\".into());\n\t\tif let Some(path_node) = graph_layer.upstream_visible_node_id_from_name_in_layer(&identifier)\n\t\t\t&& let Some(&source) = self.first_element_source_ids.get(&layer.to_node())\n\t\t\t&& !network_interface\n\t\t\t\t.upstream_flow_back_from_nodes(vec![path_node], &[], FlowType::HorizontalFlow)\n\t\t\t\t.any(|upstream| Some(upstream) == source)\n\t\t{\n\t\t\tuse_local = false;\n\t\t\tinfo!(\"Local transform is invalid — using the identity for the local transform instead\")\n\t\t}\n\t\tlet local_transform = use_local.then(|| self.local_transforms.get(&layer.to_node()).copied()).flatten().unwrap_or_default();\n\n\t\tfootprint * local_transform\n\t}\n\n\tpub fn transform_to_document_if_feeds(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DAffine2 {\n\t\tself.document_to_viewport.inverse() * self.transform_to_viewport_if_feeds(layer, network_interface)\n\t}\n\n\tpub fn transform_to_viewport_with_first_transform_node_if_group(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DAffine2 {\n\t\tlet footprint = self.upstream_footprints.get(&layer.to_node()).map(|footprint| footprint.transform).unwrap_or(self.document_to_viewport);\n\t\tlet local_transform = self.local_transforms.get(&layer.to_node()).copied();\n\n\t\tlet transform = local_transform.unwrap_or_else(|| {\n\t\t\tlet transform_node_id = ModifyInputsContext::locate_node_in_layer_chain(&DefinitionIdentifier::Network(\"Transform\".into()), layer, network_interface);\n\t\t\tlet transform_node = transform_node_id.and_then(|id| network_interface.document_node(&id, &[]));\n\t\t\ttransform_node.map(|node| transform_utils::get_current_transform(node.inputs.as_slice())).unwrap_or_default()\n\t\t});\n\n\t\tfootprint * transform\n\t}\n\n\tpub fn upstream_transform(&self, node_id: NodeId) -> DAffine2 {\n\t\tself.local_transforms.get(&node_id).copied().unwrap_or(DAffine2::IDENTITY)\n\t}\n\n\tpub fn downstream_transform_to_document(&self, layer: LayerNodeIdentifier) -> DAffine2 {\n\t\tself.document_to_viewport.inverse() * self.downstream_transform_to_viewport(layer)\n\t}\n\n\tpub fn downstream_transform_to_viewport(&self, layer: LayerNodeIdentifier) -> DAffine2 {\n\t\tif layer == LayerNodeIdentifier::ROOT_PARENT {\n\t\t\treturn self.transform_to_viewport(layer);\n\t\t}\n\n\t\tself.upstream_footprints\n\t\t\t.get(&layer.to_node())\n\t\t\t.copied()\n\t\t\t.map(|footprint| footprint.transform)\n\t\t\t.unwrap_or_else(|| self.transform_to_viewport(layer))\n\t}\n}\n\n// ===============================\n// DocumentMetadata: Click targets\n// ===============================\n\nimpl DocumentMetadata {\n\t/// Get the bounding box of the click target of the specified layer in the specified transform space\n\tpub fn bounding_box_with_transform(&self, layer: LayerNodeIdentifier, transform: DAffine2) -> Option<[DVec2; 2]> {\n\t\tself.click_targets(layer)?\n\t\t\t.iter()\n\t\t\t.filter_map(|click_target| click_target.bounding_box_with_transform(transform))\n\t\t\t.reduce(Quad::combine_bounds)\n\t}\n\n\t/// Get the loose bounding box of the click target of the specified layer in the specified transform space\n\tpub fn loose_bounding_box_with_transform(&self, layer: LayerNodeIdentifier, transform: DAffine2) -> Option<[DVec2; 2]> {\n\t\tself.click_targets(layer)?\n\t\t\t.iter()\n\t\t\t.filter_map(|click_target| match click_target.target_type() {\n\t\t\t\tClickTargetType::Subpath(subpath) => subpath.loose_bounding_box_with_transform(transform),\n\t\t\t\tClickTargetType::FreePoint(_) => click_target.bounding_box_with_transform(transform),\n\t\t\t})\n\t\t\t.reduce(Quad::combine_bounds)\n\t}\n\n\t/// Calculate the corners of the bounding box but with a nonzero size.\n\t///\n\t/// If the layer bounds are `0` in either axis then they are changed to be `1`.\n\tpub fn nonzero_bounding_box(&self, layer: LayerNodeIdentifier) -> [DVec2; 2] {\n\t\tlet [mut bounds_min, mut bounds_max] = self.bounding_box_with_transform(layer, DAffine2::IDENTITY).unwrap_or_default();\n\n\t\tlet bounds_size = bounds_max - bounds_min;\n\t\tlet bounds_midpoint = bounds_min.midpoint(bounds_max);\n\t\tconst BOX_NUDGE: f64 = 5e-9;\n\t\tif bounds_size.x < 1e-10 {\n\t\t\tbounds_max.x = bounds_midpoint.x + BOX_NUDGE;\n\t\t\tbounds_min.x = bounds_midpoint.x - BOX_NUDGE;\n\t\t}\n\t\tif bounds_size.y < 1e-10 {\n\t\t\tbounds_max.y = bounds_midpoint.y + BOX_NUDGE;\n\t\t\tbounds_min.y = bounds_midpoint.y - BOX_NUDGE;\n\t\t}\n\n\t\t[bounds_min, bounds_max]\n\t}\n\n\t/// Get the bounding box of the click target of the specified layer in document space\n\tpub fn bounding_box_document(&self, layer: LayerNodeIdentifier) -> Option<[DVec2; 2]> {\n\t\tself.bounding_box_with_transform(layer, self.transform_to_document(layer))\n\t}\n\n\t/// Get the bounding box of the click target of the specified layer in viewport space\n\tpub fn bounding_box_viewport(&self, layer: LayerNodeIdentifier) -> Option<[DVec2; 2]> {\n\t\tself.bounding_box_with_transform(layer, self.transform_to_viewport(layer))\n\t}\n\n\t/// Calculates the document bounds in viewport space\n\tpub fn document_bounds_viewport_space(&self) -> Option<[DVec2; 2]> {\n\t\tself.all_layers().filter_map(|layer| self.bounding_box_viewport(layer)).reduce(Quad::combine_bounds)\n\t}\n\n\tpub fn layer_outline(&self, layer: LayerNodeIdentifier) -> impl Iterator<Item = &subpath::Subpath<PointId>> {\n\t\tstatic EMPTY: Vec<Arc<ClickTarget>> = Vec::new();\n\t\tlet click_targets = self.click_targets.get(&layer).unwrap_or(&EMPTY);\n\t\tclick_targets.iter().filter_map(|target| match target.target_type() {\n\t\t\tClickTargetType::Subpath(subpath) => Some(subpath),\n\t\t\t_ => None,\n\t\t})\n\t}\n\n\tpub fn layer_with_free_points_outline(&self, layer: LayerNodeIdentifier) -> impl Iterator<Item = &ClickTargetType> {\n\t\tstatic EMPTY: Vec<Arc<ClickTarget>> = Vec::new();\n\t\tlet click_targets = self.click_targets.get(&layer).unwrap_or(&EMPTY);\n\t\tclick_targets.iter().map(|target| target.target_type())\n\t}\n\n\tpub fn is_clip(&self, node: NodeId) -> bool {\n\t\tself.clip_targets.contains(&node)\n\t}\n}\n\n// ===================\n// LayerNodeIdentifier\n// ===================\n\n/// ID of a layer node\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize)]\npub struct LayerNodeIdentifier(NonZeroU64);\n\nimpl core::fmt::Debug for LayerNodeIdentifier {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tlet node_id = if *self != LayerNodeIdentifier::ROOT_PARENT { self.to_node() } else { NodeId(0) };\n\n\t\tf.debug_tuple(\"LayerNodeIdentifier\").field(&node_id).finish()\n\t}\n}\n\nimpl Default for LayerNodeIdentifier {\n\tfn default() -> Self {\n\t\tSelf::ROOT_PARENT\n\t}\n}\n\nimpl LayerNodeIdentifier {\n\t/// A conceptual layer used to represent the parent of layers that feed into the export\n\tpub const ROOT_PARENT: Self = LayerNodeIdentifier::new_unchecked(NodeId(0));\n\n\t/// Construct a [`LayerNodeIdentifier`] without checking if it is a layer node\n\tpub const fn new_unchecked(node_id: NodeId) -> Self {\n\t\t// # Safety: will always be >=1\n\t\tSelf(unsafe { NonZeroU64::new_unchecked(node_id.0 + 1) })\n\t}\n\n\t/// Construct a [`LayerNodeIdentifier`], debug asserting that it is a layer node. This should only be used in the document network since the structure is not loaded in nested networks.\n\t#[track_caller]\n\tpub fn new(node_id: NodeId, network_interface: &NodeNetworkInterface) -> Self {\n\t\tdebug_assert!(network_interface.is_layer(&node_id, &[]), \"Layer identifier constructed from non-layer node {node_id}\",);\n\t\tSelf::new_unchecked(node_id)\n\t}\n\n\t/// Access the node id of this layer\n\tpub fn to_node(self) -> NodeId {\n\t\tlet id = NodeId(u64::from(self.0) - 1);\n\t\tdebug_assert!(id != NodeId(0), \"LayerNodeIdentifier::ROOT_PARENT cannot be converted to NodeId\");\n\n\t\tid\n\t}\n\n\t/// Access the parent layer if possible\n\tpub fn parent(self, metadata: &DocumentMetadata) -> Option<LayerNodeIdentifier> {\n\t\tmetadata.get_relations(self).and_then(|relations| relations.parent)\n\t}\n\n\t/// Access the previous sibling of this layer (up the Layers panel)\n\tpub fn previous_sibling(self, metadata: &DocumentMetadata) -> Option<LayerNodeIdentifier> {\n\t\tmetadata.get_relations(self).and_then(|relations| relations.previous_sibling)\n\t}\n\n\t/// Access the next sibling of this layer (down the Layers panel)\n\tpub fn next_sibling(self, metadata: &DocumentMetadata) -> Option<LayerNodeIdentifier> {\n\t\tmetadata.get_relations(self).and_then(|relations| relations.next_sibling)\n\t}\n\n\t/// Access the first child of this layer (top most in Layers panel)\n\tpub fn first_child(self, metadata: &DocumentMetadata) -> Option<LayerNodeIdentifier> {\n\t\tmetadata.get_relations(self).and_then(|relations| relations.first_child)\n\t}\n\n\t/// Access the last child of this layer (bottom most in Layers panel)\n\tpub fn last_child(self, metadata: &DocumentMetadata) -> Option<LayerNodeIdentifier> {\n\t\tmetadata.get_relations(self).and_then(|relations| relations.last_child)\n\t}\n\n\t/// Does the layer have children? If so, then it is a folder.\n\tpub fn has_children(self, metadata: &DocumentMetadata) -> bool {\n\t\tself.first_child(metadata).is_some()\n\t}\n\n\t/// Is the layer a child of the given layer?\n\tpub fn is_child_of(self, metadata: &DocumentMetadata, parent: &LayerNodeIdentifier) -> bool {\n\t\tparent.children(metadata).any(|child| child == self)\n\t}\n\n\t/// Is the layer an ancestor of the given layer?\n\tpub fn is_ancestor_of(self, metadata: &DocumentMetadata, child: &LayerNodeIdentifier) -> bool {\n\t\tchild.ancestors(metadata).any(|ancestor| ancestor == self)\n\t}\n\n\t/// Is the layer the last child of its stack? Used for clipping\n\tpub fn can_be_clipped(self, metadata: &DocumentMetadata) -> bool {\n\t\tself.parent(metadata)\n\t\t\t.is_some_and(|layer| layer.last_child(metadata).expect(\"Parent accessed via child should have children\") != self)\n\t}\n\n\t/// Iterator over all direct children (excluding self and recursive children)\n\tpub fn children(self, metadata: &DocumentMetadata) -> AxisIter<'_> {\n\t\tAxisIter {\n\t\t\tlayer_node: self.first_child(metadata),\n\t\t\tnext_node: Self::next_sibling,\n\t\t\tmetadata,\n\t\t}\n\t}\n\n\tpub fn downstream_siblings(self, metadata: &DocumentMetadata) -> AxisIter<'_> {\n\t\tAxisIter {\n\t\t\tlayer_node: Some(self),\n\t\t\tnext_node: Self::previous_sibling,\n\t\t\tmetadata,\n\t\t}\n\t}\n\n\t/// All ancestors of this layer, including self, going to the document root\n\tpub fn ancestors(self, metadata: &DocumentMetadata) -> AxisIter<'_> {\n\t\tAxisIter {\n\t\t\tlayer_node: Some(self),\n\t\t\tnext_node: Self::parent,\n\t\t\tmetadata,\n\t\t}\n\t}\n\n\t/// Iterator through all the last children, starting from self\n\tpub fn last_children(self, metadata: &DocumentMetadata) -> AxisIter<'_> {\n\t\tAxisIter {\n\t\t\tlayer_node: Some(self),\n\t\t\tnext_node: Self::last_child,\n\t\t\tmetadata,\n\t\t}\n\t}\n\n\t/// Iterator through all descendants, including recursive children (not including self)\n\tpub fn descendants(self, metadata: &DocumentMetadata) -> DescendantsIter<'_> {\n\t\tDescendantsIter {\n\t\t\tfront: self.first_child(metadata),\n\t\t\tback: self.last_child(metadata).and_then(|child| child.last_children(metadata).last()),\n\t\t\tmetadata,\n\t\t}\n\t}\n\n\t/// Add a child towards the top of the Layers panel\n\tpub fn push_front_child(self, metadata: &mut DocumentMetadata, new: LayerNodeIdentifier) {\n\t\tassert!(!metadata.structure.contains_key(&new), \"Cannot add already existing layer\");\n\t\tlet parent = metadata.get_structure_mut(self);\n\t\tlet old_first_child = parent.first_child.replace(new);\n\t\tparent.last_child.get_or_insert(new);\n\t\tif let Some(old_first_child) = old_first_child {\n\t\t\tmetadata.get_structure_mut(old_first_child).previous_sibling = Some(new);\n\t\t}\n\t\tmetadata.get_structure_mut(new).next_sibling = old_first_child;\n\t\tmetadata.get_structure_mut(new).parent = Some(self);\n\t}\n\n\t/// Add a child towards the bottom of the Layers panel\n\tpub fn push_child(self, metadata: &mut DocumentMetadata, new: LayerNodeIdentifier) {\n\t\tassert!(!metadata.structure.contains_key(&new), \"Cannot add already existing layer\");\n\t\tlet parent = metadata.get_structure_mut(self);\n\t\tlet old_last_child = parent.last_child.replace(new);\n\t\tparent.first_child.get_or_insert(new);\n\t\tif let Some(old_last_child) = old_last_child {\n\t\t\tmetadata.get_structure_mut(old_last_child).next_sibling = Some(new);\n\t\t}\n\t\tmetadata.get_structure_mut(new).previous_sibling = old_last_child;\n\t\tmetadata.get_structure_mut(new).parent = Some(self);\n\t}\n\n\t/// Add sibling above in the Layers panel\n\tpub fn add_before(self, metadata: &mut DocumentMetadata, new: LayerNodeIdentifier) {\n\t\tassert!(!metadata.structure.contains_key(&new), \"Cannot add already existing layer\");\n\t\tmetadata.get_structure_mut(new).next_sibling = Some(self);\n\t\tmetadata.get_structure_mut(new).parent = self.parent(metadata);\n\t\tlet old_previous_sibling = metadata.get_structure_mut(self).previous_sibling.replace(new);\n\t\tif let Some(old_previous_sibling) = old_previous_sibling {\n\t\t\tmetadata.get_structure_mut(old_previous_sibling).next_sibling = Some(new);\n\t\t\tmetadata.get_structure_mut(new).previous_sibling = Some(old_previous_sibling);\n\t\t} else if let Some(structure) = self\n\t\t\t.parent(metadata)\n\t\t\t.map(|parent| metadata.get_structure_mut(parent))\n\t\t\t.filter(|structure| structure.first_child == Some(self))\n\t\t{\n\t\t\tstructure.first_child = Some(new);\n\t\t}\n\t}\n\n\t/// Add sibling below in the Layers panel\n\tpub fn add_after(self, metadata: &mut DocumentMetadata, new: LayerNodeIdentifier) {\n\t\tassert!(!metadata.structure.contains_key(&new), \"Cannot add already existing layer\");\n\t\tmetadata.get_structure_mut(new).previous_sibling = Some(self);\n\t\tmetadata.get_structure_mut(new).parent = self.parent(metadata);\n\t\tlet old_next_sibling = metadata.get_structure_mut(self).next_sibling.replace(new);\n\t\tif let Some(old_next_sibling) = old_next_sibling {\n\t\t\tmetadata.get_structure_mut(old_next_sibling).previous_sibling = Some(new);\n\t\t\tmetadata.get_structure_mut(new).next_sibling = Some(old_next_sibling);\n\t\t} else if let Some(structure) = self\n\t\t\t.parent(metadata)\n\t\t\t.map(|parent| metadata.get_structure_mut(parent))\n\t\t\t.filter(|structure| structure.last_child == Some(self))\n\t\t{\n\t\t\tstructure.last_child = Some(new);\n\t\t}\n\t}\n\n\t/// Delete layer and all children\n\tpub fn delete(self, metadata: &mut DocumentMetadata) {\n\t\tlet previous_sibling = self.previous_sibling(metadata);\n\t\tlet next_sibling = self.next_sibling(metadata);\n\n\t\tif let Some(previous_sibling) = previous_sibling.map(|node| metadata.get_structure_mut(node)) {\n\t\t\tprevious_sibling.next_sibling = next_sibling;\n\t\t}\n\n\t\tif let Some(next_sibling) = next_sibling.map(|node| metadata.get_structure_mut(node)) {\n\t\t\tnext_sibling.previous_sibling = previous_sibling;\n\t\t}\n\t\tlet mut parent = self.parent(metadata).map(|parent| metadata.get_structure_mut(parent));\n\t\tif let Some(structure) = parent.as_mut().filter(|structure| structure.first_child == Some(self)) {\n\t\t\tstructure.first_child = next_sibling;\n\t\t}\n\t\tif let Some(structure) = parent.as_mut().filter(|structure| structure.last_child == Some(self)) {\n\t\t\tstructure.last_child = previous_sibling;\n\t\t}\n\n\t\tlet mut delete = vec![self];\n\t\tdelete.extend(self.descendants(metadata));\n\t\tfor node in delete {\n\t\t\tmetadata.structure.remove(&node);\n\t\t}\n\t}\n\n\tpub fn exists(&self, metadata: &DocumentMetadata) -> bool {\n\t\tmetadata.get_relations(*self).is_some()\n\t}\n\n\tpub fn starts_with(&self, other: Self, metadata: &DocumentMetadata) -> bool {\n\t\tself.ancestors(metadata).any(|parent| parent == other)\n\t}\n}\n\n// ========\n// AxisIter\n// ========\n\n/// Iterator over specified axis.\n#[derive(Clone)]\npub struct AxisIter<'a> {\n\tpub layer_node: Option<LayerNodeIdentifier>,\n\tpub next_node: fn(LayerNodeIdentifier, &DocumentMetadata) -> Option<LayerNodeIdentifier>,\n\tpub metadata: &'a DocumentMetadata,\n}\n\nimpl Iterator for AxisIter<'_> {\n\ttype Item = LayerNodeIdentifier;\n\n\tfn next(&mut self) -> Option<Self::Item> {\n\t\tlet layer_node = self.layer_node.take();\n\t\tself.layer_node = layer_node.and_then(|node| (self.next_node)(node, self.metadata));\n\t\tlayer_node\n\t}\n}\n\n// ===============\n// DescendantsIter\n// ===============\n\n#[derive(Clone)]\npub struct DescendantsIter<'a> {\n\tfront: Option<LayerNodeIdentifier>,\n\tback: Option<LayerNodeIdentifier>,\n\tmetadata: &'a DocumentMetadata,\n}\n\nimpl Iterator for DescendantsIter<'_> {\n\ttype Item = LayerNodeIdentifier;\n\n\tfn next(&mut self) -> Option<Self::Item> {\n\t\tif self.front == self.back {\n\t\t\tself.back = None;\n\t\t\tself.front.take()\n\t\t} else {\n\t\t\tlet layer_node = self.front.take();\n\t\t\tif let Some(layer_node) = layer_node {\n\t\t\t\tself.front = layer_node\n\t\t\t\t\t.first_child(self.metadata)\n\t\t\t\t\t.or_else(|| layer_node.ancestors(self.metadata).find_map(|ancestor| ancestor.next_sibling(self.metadata)));\n\t\t\t}\n\t\t\tlayer_node\n\t\t}\n\t}\n}\nimpl DoubleEndedIterator for DescendantsIter<'_> {\n\tfn next_back(&mut self) -> Option<Self::Item> {\n\t\tif self.front == self.back {\n\t\t\tself.front = None;\n\t\t\tself.back.take()\n\t\t} else {\n\t\t\tlet layer_node = self.back.take();\n\t\t\tif let Some(layer_node) = layer_node {\n\t\t\t\tself.back = layer_node\n\t\t\t\t\t.previous_sibling(self.metadata)\n\t\t\t\t\t.and_then(|sibling| sibling.last_children(self.metadata).last())\n\t\t\t\t\t.or_else(|| layer_node.parent(self.metadata));\n\t\t\t}\n\n\t\t\tlayer_node\n\t\t}\n\t}\n}\n\n// =============\n// NodeRelations\n// =============\n\n#[derive(Debug, Clone, Copy, Default)]\npub struct NodeRelations {\n\tpub parent: Option<LayerNodeIdentifier>,\n\tprevious_sibling: Option<LayerNodeIdentifier>,\n\tnext_sibling: Option<LayerNodeIdentifier>,\n\tfirst_child: Option<LayerNodeIdentifier>,\n\tlast_child: Option<LayerNodeIdentifier>,\n}\n\n// ================\n// Helper functions\n// ================\n\n#[cfg(test)]\nmod tests {\n\tuse super::*;\n\t#[test]\n\tfn test_tree() {\n\t\tlet mut metadata = DocumentMetadata::default();\n\t\tlet root = LayerNodeIdentifier::ROOT_PARENT;\n\t\tlet metadata = &mut metadata;\n\t\troot.push_child(metadata, LayerNodeIdentifier::new_unchecked(NodeId(3)));\n\t\tassert_eq!(root.children(metadata).collect::<Vec<_>>(), vec![LayerNodeIdentifier::new_unchecked(NodeId(3))]);\n\t\troot.push_child(metadata, LayerNodeIdentifier::new_unchecked(NodeId(6)));\n\t\tassert_eq!(root.children(metadata).map(LayerNodeIdentifier::to_node).collect::<Vec<_>>(), vec![NodeId(3), NodeId(6)]);\n\t\tassert_eq!(root.descendants(metadata).map(LayerNodeIdentifier::to_node).collect::<Vec<_>>(), vec![NodeId(3), NodeId(6)]);\n\t\tLayerNodeIdentifier::new_unchecked(NodeId(3)).add_after(metadata, LayerNodeIdentifier::new_unchecked(NodeId(4)));\n\t\tLayerNodeIdentifier::new_unchecked(NodeId(3)).add_before(metadata, LayerNodeIdentifier::new_unchecked(NodeId(2)));\n\t\tLayerNodeIdentifier::new_unchecked(NodeId(6)).add_before(metadata, LayerNodeIdentifier::new_unchecked(NodeId(5)));\n\t\tLayerNodeIdentifier::new_unchecked(NodeId(6)).add_after(metadata, LayerNodeIdentifier::new_unchecked(NodeId(9)));\n\t\tLayerNodeIdentifier::new_unchecked(NodeId(6)).push_child(metadata, LayerNodeIdentifier::new_unchecked(NodeId(8)));\n\t\tLayerNodeIdentifier::new_unchecked(NodeId(6)).push_front_child(metadata, LayerNodeIdentifier::new_unchecked(NodeId(7)));\n\t\troot.push_front_child(metadata, LayerNodeIdentifier::new_unchecked(NodeId(1)));\n\t\tassert_eq!(\n\t\t\troot.children(metadata).map(LayerNodeIdentifier::to_node).collect::<Vec<_>>(),\n\t\t\tvec![NodeId(1), NodeId(2), NodeId(3), NodeId(4), NodeId(5), NodeId(6), NodeId(9)]\n\t\t);\n\t\tassert_eq!(\n\t\t\troot.descendants(metadata).map(LayerNodeIdentifier::to_node).collect::<Vec<_>>(),\n\t\t\tvec![NodeId(1), NodeId(2), NodeId(3), NodeId(4), NodeId(5), NodeId(6), NodeId(7), NodeId(8), NodeId(9)]\n\t\t);\n\t\tassert_eq!(\n\t\t\troot.descendants(metadata).map(LayerNodeIdentifier::to_node).rev().collect::<Vec<_>>(),\n\t\t\tvec![NodeId(9), NodeId(8), NodeId(7), NodeId(6), NodeId(5), NodeId(4), NodeId(3), NodeId(2), NodeId(1)]\n\t\t);\n\t\tassert!(root.children(metadata).all(|child| child.parent(metadata) == Some(root)));\n\t\tLayerNodeIdentifier::new_unchecked(NodeId(6)).delete(metadata);\n\t\tLayerNodeIdentifier::new_unchecked(NodeId(1)).delete(metadata);\n\t\tLayerNodeIdentifier::new_unchecked(NodeId(9)).push_child(metadata, LayerNodeIdentifier::new_unchecked(NodeId(10)));\n\t\tassert_eq!(\n\t\t\troot.children(metadata).map(LayerNodeIdentifier::to_node).collect::<Vec<_>>(),\n\t\t\tvec![NodeId(2), NodeId(3), NodeId(4), NodeId(5), NodeId(9)]\n\t\t);\n\t\tassert_eq!(\n\t\t\troot.descendants(metadata).map(LayerNodeIdentifier::to_node).collect::<Vec<_>>(),\n\t\t\tvec![NodeId(2), NodeId(3), NodeId(4), NodeId(5), NodeId(9), NodeId(10)]\n\t\t);\n\t\tassert_eq!(\n\t\t\troot.descendants(metadata).map(LayerNodeIdentifier::to_node).rev().collect::<Vec<_>>(),\n\t\t\tvec![NodeId(10), NodeId(9), NodeId(5), NodeId(4), NodeId(3), NodeId(2)]\n\t\t);\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/utility_types/error.rs",
    "content": "use graphene_std::raster::color::Color;\nuse thiserror::Error;\n\n/// The error type used by the Graphite editor.\n#[derive(Clone, Debug, Error)]\npub enum EditorError {\n\t#[error(\"Failed to execute operation:\\n{0}\")]\n\tInvalidOperation(String),\n\n\t#[error(\"Tried to construct an invalid color:\\n{0:?}\")]\n\tColor(String),\n\n\t#[error(\"The requested tool does not exist\")]\n\tUnknownTool,\n\n\t#[error(\"The operation caused a document error:\\n{0:?}\")]\n\tDocument(String),\n\n\t#[error(\n\t\t\"This document was created in an older version of the editor.\\n\\\n\t\t\\n\\\n\t\tFull backwards compatibility is not guaranteed in the current alpha release.\\n\\\n\t\t\\n\\\n\t\tIf this document is critical, ask for support in Graphite's Discord community.\"\n\t)]\n\tDocumentDeserialization(String),\n\n\t#[error(\"{0}\")]\n\tMisc(String),\n}\n\nmacro_rules! derive_from {\n\t($type:ty, $kind:ident) => {\n\t\timpl From<$type> for EditorError {\n\t\t\tfn from(error: $type) -> Self {\n\t\t\t\tEditorError::$kind(format!(\"{error:?}\"))\n\t\t\t}\n\t\t}\n\t};\n}\n\nderive_from!(&str, Misc);\nderive_from!(String, Misc);\nderive_from!(Color, Color);\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/utility_types/misc.rs",
    "content": "use crate::consts::COLOR_OVERLAY_GRAY;\nuse glam::DVec2;\nuse std::fmt;\n\n#[repr(transparent)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify), tsify(large_number_types_as_bigints))]\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize)]\npub struct DocumentId(pub u64);\n\n#[derive(PartialEq, Eq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize, Hash)]\npub enum FlipAxis {\n\tX,\n\tY,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Eq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize, Hash)]\npub enum AlignAxis {\n\tX,\n\tY,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Eq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize, Hash)]\npub enum AlignAggregate {\n\tMin,\n\tMax,\n\tCenter,\n}\n\n// #[derive(Default, PartialEq, Eq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]\n// pub enum DocumentMode {\n// \t#[default]\n// \tDesignMode,\n// \tSelectMode,\n// \tGuideMode,\n// }\n\n// impl fmt::Display for DocumentMode {\n// \tfn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n// \t\tmatch self {\n// \t\t\tDocumentMode::DesignMode => write!(f, \"Design Mode\"),\n// \t\t\tDocumentMode::SelectMode => write!(f, \"Select Mode\"),\n// \t\t\tDocumentMode::GuideMode => write!(f, \"Guide Mode\"),\n// \t\t}\n// \t}\n// }\n\n// impl DocumentMode {\n// \tpub fn icon_name(&self) -> String {\n// \t\tmatch self {\n// \t\t\tDocumentMode::DesignMode => \"ViewportDesignMode\".to_string(),\n// \t\t\tDocumentMode::SelectMode => \"ViewportSelectMode\".to_string(),\n// \t\t\tDocumentMode::GuideMode => \"ViewportGuideMode\".to_string(),\n// \t\t}\n// \t}\n// }\n\n/// SnappingState determines the current individual snapping states\n#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]\n#[serde(default)]\npub struct SnappingState {\n\tpub snapping_enabled: bool,\n\tpub grid_snapping: bool,\n\tpub artboards: bool,\n\tpub tolerance: f64,\n\tpub bounding_box: BoundingBoxSnapping,\n\tpub path: PathSnapping,\n\tpub grid: GridSnapping,\n}\n\nimpl Default for SnappingState {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tsnapping_enabled: true,\n\t\t\tgrid_snapping: false,\n\t\t\tartboards: true,\n\t\t\ttolerance: 8.,\n\t\t\tbounding_box: BoundingBoxSnapping::default(),\n\t\t\tpath: PathSnapping::default(),\n\t\t\tgrid: GridSnapping::default(),\n\t\t}\n\t}\n}\n\nimpl SnappingState {\n\tpub const fn target_enabled(&self, target: SnapTarget) -> bool {\n\t\tif !self.snapping_enabled {\n\t\t\treturn false;\n\t\t}\n\t\tmatch target {\n\t\t\tSnapTarget::BoundingBox(target) => match target {\n\t\t\t\tBoundingBoxSnapTarget::CornerPoint => self.bounding_box.corner_point,\n\t\t\t\tBoundingBoxSnapTarget::EdgeMidpoint => self.bounding_box.edge_midpoint,\n\t\t\t\tBoundingBoxSnapTarget::CenterPoint => self.bounding_box.center_point,\n\t\t\t},\n\t\t\tSnapTarget::Path(target) => match target {\n\t\t\t\tPathSnapTarget::AnchorPointWithColinearHandles | PathSnapTarget::AnchorPointWithFreeHandles => self.path.anchor_point,\n\t\t\t\tPathSnapTarget::LineMidpoint => self.path.line_midpoint,\n\t\t\t\tPathSnapTarget::AlongPath => self.path.along_path,\n\t\t\t\tPathSnapTarget::NormalToPath => self.path.normal_to_path,\n\t\t\t\tPathSnapTarget::TangentToPath => self.path.tangent_to_path,\n\t\t\t\tPathSnapTarget::IntersectionPoint => self.path.path_intersection_point,\n\t\t\t\tPathSnapTarget::PerpendicularToEndpoint => self.path.perpendicular_from_endpoint,\n\t\t\t},\n\t\t\tSnapTarget::Artboard(_) => self.artboards,\n\t\t\tSnapTarget::Grid(_) => self.grid_snapping,\n\t\t\tSnapTarget::Alignment(AlignmentSnapTarget::AlignWithAnchorPoint) => self.path.align_with_anchor_point,\n\t\t\tSnapTarget::Alignment(_) => self.bounding_box.align_with_edges,\n\t\t\tSnapTarget::DistributeEvenly(_) => self.bounding_box.distribute_evenly,\n\t\t\t_ => false,\n\t\t}\n\t}\n}\n\n#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]\n#[serde(default)]\npub struct BoundingBoxSnapping {\n\tpub center_point: bool,\n\tpub corner_point: bool,\n\tpub edge_midpoint: bool,\n\tpub align_with_edges: bool,\n\tpub distribute_evenly: bool,\n}\n\nimpl Default for BoundingBoxSnapping {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tcenter_point: true,\n\t\t\tcorner_point: true,\n\t\t\tedge_midpoint: true,\n\t\t\talign_with_edges: true,\n\t\t\tdistribute_evenly: true,\n\t\t}\n\t}\n}\n\n#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]\n#[serde(default)]\npub struct PathSnapping {\n\tpub anchor_point: bool,\n\tpub line_midpoint: bool,\n\tpub along_path: bool,\n\tpub normal_to_path: bool,\n\tpub tangent_to_path: bool,\n\tpub path_intersection_point: bool,\n\tpub align_with_anchor_point: bool, // TODO: Rename\n\tpub perpendicular_from_endpoint: bool,\n}\n\nimpl Default for PathSnapping {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tanchor_point: true,\n\t\t\tline_midpoint: true,\n\t\t\talong_path: true,\n\t\t\tnormal_to_path: true,\n\t\t\ttangent_to_path: true,\n\t\t\tpath_intersection_point: true,\n\t\t\talign_with_anchor_point: true,\n\t\t\tperpendicular_from_endpoint: true,\n\t\t}\n\t}\n}\n\n#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize, PartialEq)]\npub enum GridType {\n\t#[serde(alias = \"Rectangle\")]\n\tRectangular {\n\t\tspacing: DVec2,\n\t},\n\tIsometric {\n\t\ty_axis_spacing: f64,\n\t\tangle_a: f64,\n\t\tangle_b: f64,\n\t},\n}\n\nimpl Default for GridType {\n\tfn default() -> Self {\n\t\tSelf::Rectangular { spacing: DVec2::ONE }\n\t}\n}\n\nimpl GridType {\n\tpub fn rectangular_spacing(&mut self) -> Option<&mut DVec2> {\n\t\tmatch self {\n\t\t\tSelf::Rectangular { spacing } => Some(spacing),\n\t\t\t_ => None,\n\t\t}\n\t}\n\tpub fn isometric_y_spacing(&mut self) -> Option<&mut f64> {\n\t\tmatch self {\n\t\t\tSelf::Isometric { y_axis_spacing, .. } => Some(y_axis_spacing),\n\t\t\t_ => None,\n\t\t}\n\t}\n\tpub fn angle_a(&mut self) -> Option<&mut f64> {\n\t\tmatch self {\n\t\t\tSelf::Isometric { angle_a, .. } => Some(angle_a),\n\t\t\t_ => None,\n\t\t}\n\t}\n\tpub fn angle_b(&mut self) -> Option<&mut f64> {\n\t\tmatch self {\n\t\t\tSelf::Isometric { angle_b, .. } => Some(angle_b),\n\t\t\t_ => None,\n\t\t}\n\t}\n}\n\n#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq)]\n#[serde(default)]\npub struct GridSnapping {\n\tpub origin: DVec2,\n\tpub grid_type: GridType,\n\tpub rectangular_spacing: DVec2,\n\tpub isometric_y_spacing: f64,\n\tpub isometric_angle_a: f64,\n\tpub isometric_angle_b: f64,\n\tpub color: String,\n\tpub dot_display: bool,\n}\n\nimpl Default for GridSnapping {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\torigin: DVec2::ZERO,\n\t\t\tgrid_type: Default::default(),\n\t\t\trectangular_spacing: DVec2::ONE,\n\t\t\tisometric_y_spacing: 1.,\n\t\t\tisometric_angle_a: 30.,\n\t\t\tisometric_angle_b: 30.,\n\t\t\tcolor: COLOR_OVERLAY_GRAY.to_string(),\n\t\t\tdot_display: false,\n\t\t}\n\t}\n}\n\nimpl GridSnapping {\n\t// Double grid size until it takes up at least 10px.\n\tpub fn compute_rectangle_spacing(mut size: DVec2, navigation: &PTZ) -> Option<DVec2> {\n\t\tlet mut iterations = 0;\n\t\tsize = size.abs();\n\t\twhile (size * navigation.zoom()).cmplt(DVec2::splat(10.)).any() {\n\t\t\tif iterations > 100 {\n\t\t\t\treturn None;\n\t\t\t}\n\t\t\tsize *= 2.;\n\t\t\titerations += 1;\n\t\t}\n\t\tSome(size)\n\t}\n\n\t// Double grid size until it takes up at least 10px.\n\tpub fn compute_isometric_multiplier(length: f64, divisor: f64, navigation: &PTZ) -> Option<f64> {\n\t\tlet length = length.abs();\n\t\tlet mut iterations = 0;\n\t\tlet mut multiplier = 1.;\n\t\twhile (length / divisor.abs().max(1.)) * multiplier * navigation.zoom() < 10. {\n\t\t\tif iterations > 100 {\n\t\t\t\treturn None;\n\t\t\t}\n\t\t\tmultiplier *= 2.;\n\t\t\titerations += 1;\n\t\t}\n\t\tSome(multiplier)\n\t}\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum BoundingBoxSnapSource {\n\tCornerPoint,\n\tCenterPoint,\n\tEdgeMidpoint,\n}\n\nimpl fmt::Display for BoundingBoxSnapSource {\n\tfn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n\t\tmatch self {\n\t\t\tBoundingBoxSnapSource::CornerPoint => write!(f, \"Bounding Box: Corner Point\"),\n\t\t\tBoundingBoxSnapSource::CenterPoint => write!(f, \"Bounding Box: Center Point\"),\n\t\t\tBoundingBoxSnapSource::EdgeMidpoint => write!(f, \"Bounding Box: Edge Midpoint\"),\n\t\t}\n\t}\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum ArtboardSnapSource {\n\tCornerPoint,\n\tCenterPoint,\n}\n\nimpl fmt::Display for ArtboardSnapSource {\n\tfn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n\t\tmatch self {\n\t\t\tArtboardSnapSource::CornerPoint => write!(f, \"Artboard: Corner Point\"),\n\t\t\tArtboardSnapSource::CenterPoint => write!(f, \"Artboard: Center Point\"),\n\t\t}\n\t}\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum PathSnapSource {\n\tAnchorPointWithColinearHandles,\n\tAnchorPointWithFreeHandles,\n\tHandlePoint,\n\tLineMidpoint,\n\tIntersectionPoint,\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum GradientSnapSource {\n\tEndpoint,\n}\n\nimpl fmt::Display for GradientSnapSource {\n\tfn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n\t\tmatch self {\n\t\t\tGradientSnapSource::Endpoint => write!(f, \"Gradient: Endpoint\"),\n\t\t}\n\t}\n}\n\nimpl fmt::Display for PathSnapSource {\n\tfn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n\t\tmatch self {\n\t\t\tPathSnapSource::AnchorPointWithColinearHandles | PathSnapSource::AnchorPointWithFreeHandles => write!(f, \"Path: Anchor Point\"),\n\t\t\tPathSnapSource::HandlePoint => write!(f, \"Path: Handle Point\"),\n\t\t\tPathSnapSource::LineMidpoint => write!(f, \"Path: Line Midpoint\"),\n\t\t\tPathSnapSource::IntersectionPoint => write!(f, \"Path: Intersection Point\"),\n\t\t}\n\t}\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum AlignmentSnapSource {\n\tBoundingBoxCornerPoint,\n\tBoundingBoxCenterPoint,\n\tBoundingBoxEdgeMidpoint,\n\tArtboardCornerPoint,\n\tArtboardCenterPoint,\n}\n\nimpl fmt::Display for AlignmentSnapSource {\n\tfn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n\t\tmatch self {\n\t\t\tAlignmentSnapSource::BoundingBoxCornerPoint => write!(f, \"{}\", BoundingBoxSnapSource::CornerPoint),\n\t\t\tAlignmentSnapSource::BoundingBoxCenterPoint => write!(f, \"{}\", BoundingBoxSnapSource::CenterPoint),\n\t\t\tAlignmentSnapSource::BoundingBoxEdgeMidpoint => write!(f, \"{}\", BoundingBoxSnapSource::EdgeMidpoint),\n\t\t\tAlignmentSnapSource::ArtboardCornerPoint => write!(f, \"{}\", ArtboardSnapSource::CornerPoint),\n\t\t\tAlignmentSnapSource::ArtboardCenterPoint => write!(f, \"{}\", ArtboardSnapSource::CenterPoint),\n\t\t}\n\t}\n}\n\n#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]\npub enum SnapSource {\n\t#[default]\n\tNone,\n\tBoundingBox(BoundingBoxSnapSource),\n\tArtboard(ArtboardSnapSource),\n\tPath(PathSnapSource),\n\tAlignment(AlignmentSnapSource),\n\tGradient(GradientSnapSource),\n}\n\nimpl SnapSource {\n\tpub fn is_some(&self) -> bool {\n\t\tself != &Self::None\n\t}\n\tpub fn bounding_box(&self) -> bool {\n\t\tmatches!(self, Self::BoundingBox(_) | Self::Artboard(_))\n\t}\n\tpub fn align(&self) -> bool {\n\t\tmatches!(self, Self::Alignment(_))\n\t}\n\tpub fn center(&self) -> bool {\n\t\tmatches!(\n\t\t\tself,\n\t\t\tSelf::Alignment(AlignmentSnapSource::ArtboardCenterPoint | AlignmentSnapSource::BoundingBoxCenterPoint)\n\t\t\t\t| Self::Artboard(ArtboardSnapSource::CenterPoint)\n\t\t\t\t| Self::BoundingBox(BoundingBoxSnapSource::CenterPoint)\n\t\t)\n\t}\n}\n\nimpl fmt::Display for SnapSource {\n\tfn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n\t\tmatch self {\n\t\t\tSnapSource::None => write!(f, \"None\"),\n\t\t\tSnapSource::BoundingBox(bounding_box_snap_source) => write!(f, \"{bounding_box_snap_source}\"),\n\t\t\tSnapSource::Artboard(artboard_snap_source) => write!(f, \"{artboard_snap_source}\"),\n\t\t\tSnapSource::Path(path_snap_source) => write!(f, \"{path_snap_source}\"),\n\t\t\tSnapSource::Alignment(alignment_snap_source) => write!(f, \"{alignment_snap_source}\"),\n\t\t\tSnapSource::Gradient(gradient_snap_source) => write!(f, \"{gradient_snap_source}\"),\n\t\t}\n\t}\n}\n\ntype GetSnapState = for<'a> fn(&'a mut SnappingState) -> &'a mut bool;\npub const SNAP_FUNCTIONS_FOR_BOUNDING_BOXES: [(&str, GetSnapState, &str); 5] = [\n\t(\n\t\t\"Align with Edges\",\n\t\t(|snapping_state| &mut snapping_state.bounding_box.align_with_edges) as GetSnapState,\n\t\t\"Snaps to horizontal/vertical alignment with the edges of any layer's bounding box.\",\n\t),\n\t(\n\t\t\"Corner Points\",\n\t\t(|snapping_state| &mut snapping_state.bounding_box.corner_point) as GetSnapState,\n\t\t\"Snaps to the four corners of any layer's bounding box.\",\n\t),\n\t(\n\t\t\"Center Points\",\n\t\t(|snapping_state| &mut snapping_state.bounding_box.center_point) as GetSnapState,\n\t\t\"Snaps to the center point of any layer's bounding box.\",\n\t),\n\t(\n\t\t\"Edge Midpoints\",\n\t\t(|snapping_state| &mut snapping_state.bounding_box.edge_midpoint) as GetSnapState,\n\t\t\"Snaps to any of the four points at the middle of the edges of any layer's bounding box.\",\n\t),\n\t(\n\t\t\"Distribute Evenly\",\n\t\t(|snapping_state| &mut snapping_state.bounding_box.distribute_evenly) as GetSnapState,\n\t\t\"Snaps to a consistent distance offset established by the bounding boxes of nearby layers.\",\n\t),\n];\npub const SNAP_FUNCTIONS_FOR_PATHS: [(&str, GetSnapState, &str); 7] = [\n\t(\n\t\t\"Align with Anchor Points\",\n\t\t(|snapping_state: &mut SnappingState| &mut snapping_state.path.align_with_anchor_point) as GetSnapState,\n\t\t\"Snaps to horizontal/vertical alignment with the anchor points of any vector path.\",\n\t),\n\t(\n\t\t\"Anchor Points\",\n\t\t(|snapping_state: &mut SnappingState| &mut snapping_state.path.anchor_point) as GetSnapState,\n\t\t\"Snaps to the anchor point of any vector path.\",\n\t),\n\t(\n\t\t// TODO: Extend to the midpoints of curved segments and rename to \"Segment Midpoint\"\n\t\t\"Line Midpoints\",\n\t\t(|snapping_state: &mut SnappingState| &mut snapping_state.path.line_midpoint) as GetSnapState,\n\t\t\"Snaps to the point at the middle of any straight line segment of a vector path.\",\n\t),\n\t(\n\t\t\"Path Intersection Points\",\n\t\t(|snapping_state: &mut SnappingState| &mut snapping_state.path.path_intersection_point) as GetSnapState,\n\t\t\"Snaps to any points where vector paths intersect.\",\n\t),\n\t(\n\t\t\"Along Paths\",\n\t\t(|snapping_state: &mut SnappingState| &mut snapping_state.path.along_path) as GetSnapState,\n\t\t\"Snaps along the length of any vector path.\",\n\t),\n\t(\n\t\t// TODO: This works correctly for line segments, but not curved segments.\n\t\t// TODO: Therefore, we should make this use the normal in relation to the incoming curve, not the straight line between the incoming curve's start point and the path.\n\t\t\"Normal to Paths\",\n\t\t(|snapping_state: &mut SnappingState| &mut snapping_state.path.normal_to_path) as GetSnapState,\n\t\t// TODO: Fix the bug/limitation that requires 'Intersections of Paths' to be enabled\n\t\t\"Snaps a line to a point perpendicular to a vector path.\\n(Due to a bug, 'Intersections of Paths' must be enabled.)\",\n\t),\n\t(\n\t\t// TODO: This works correctly for line segments, but not curved segments.\n\t\t// TODO: Therefore, we should make this use the tangent in relation to the incoming curve, not the straight line between the incoming curve's start point and the path.\n\t\t\"Tangent to Paths\",\n\t\t(|snapping_state: &mut SnappingState| &mut snapping_state.path.tangent_to_path) as GetSnapState,\n\t\t// TODO: Fix the bug/limitation that requires 'Intersections of Paths' to be enabled\n\t\t\"Snaps a line to a point tangent to a vector path.\\n(Due to a bug, 'Intersections of Paths' must be enabled.)\",\n\t),\n];\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]\npub enum BoundingBoxSnapTarget {\n\tCornerPoint,\n\tCenterPoint,\n\tEdgeMidpoint,\n}\n\nimpl fmt::Display for BoundingBoxSnapTarget {\n\tfn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n\t\tmatch self {\n\t\t\tBoundingBoxSnapTarget::CornerPoint => write!(f, \"Bounding Box: Corner Point\"),\n\t\t\tBoundingBoxSnapTarget::CenterPoint => write!(f, \"Bounding Box: Center Point\"),\n\t\t\tBoundingBoxSnapTarget::EdgeMidpoint => write!(f, \"Bounding Box: Edge Midpoint\"),\n\t\t}\n\t}\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]\npub enum PathSnapTarget {\n\tAnchorPointWithColinearHandles,\n\tAnchorPointWithFreeHandles,\n\tLineMidpoint,\n\tAlongPath,\n\tNormalToPath,\n\tTangentToPath,\n\tIntersectionPoint,\n\tPerpendicularToEndpoint,\n}\n\nimpl fmt::Display for PathSnapTarget {\n\tfn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n\t\tmatch self {\n\t\t\tPathSnapTarget::AnchorPointWithColinearHandles | PathSnapTarget::AnchorPointWithFreeHandles => write!(f, \"Path: Anchor Point\"),\n\t\t\tPathSnapTarget::LineMidpoint => write!(f, \"Path: Line Midpoint\"),\n\t\t\tPathSnapTarget::AlongPath => write!(f, \"Path: Along Path\"),\n\t\t\tPathSnapTarget::NormalToPath => write!(f, \"Path: Normal to Path\"),\n\t\t\tPathSnapTarget::TangentToPath => write!(f, \"Path: Tangent to Path\"),\n\t\t\tPathSnapTarget::IntersectionPoint => write!(f, \"Path: Intersection Point\"),\n\t\t\tPathSnapTarget::PerpendicularToEndpoint => write!(f, \"Path: Perp. to Endpoint\"),\n\t\t}\n\t}\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum ArtboardSnapTarget {\n\tCornerPoint,\n\tCenterPoint,\n\tAlongEdge,\n}\n\nimpl fmt::Display for ArtboardSnapTarget {\n\tfn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n\t\tmatch self {\n\t\t\tArtboardSnapTarget::CornerPoint => write!(f, \"Artboard: Corner Point\"),\n\t\t\tArtboardSnapTarget::CenterPoint => write!(f, \"Artboard: Center Point\"),\n\t\t\tArtboardSnapTarget::AlongEdge => write!(f, \"Artboard: Along Edge\"),\n\t\t}\n\t}\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum GridSnapTarget {\n\tLine,\n\tLineNormal,\n\tIntersection,\n}\n\nimpl fmt::Display for GridSnapTarget {\n\tfn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n\t\tmatch self {\n\t\t\tGridSnapTarget::Line => write!(f, \"Grid: Along Line\"),\n\t\t\tGridSnapTarget::LineNormal => write!(f, \"Grid: Normal to Line\"),\n\t\t\tGridSnapTarget::Intersection => write!(f, \"Grid: Intersection Point\"),\n\t\t}\n\t}\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum AlignmentSnapTarget {\n\tBoundingBoxCornerPoint,\n\tBoundingBoxCenterPoint,\n\tArtboardCornerPoint,\n\tArtboardCenterPoint,\n\tAlignWithAnchorPoint,\n\tIntersectionPoint,\n\tPerpendicularToEndpoint,\n}\n\nimpl fmt::Display for AlignmentSnapTarget {\n\tfn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n\t\tmatch self {\n\t\t\tAlignmentSnapTarget::BoundingBoxCornerPoint => write!(f, \"{}\", BoundingBoxSnapTarget::CornerPoint),\n\t\t\tAlignmentSnapTarget::BoundingBoxCenterPoint => write!(f, \"{}\", BoundingBoxSnapTarget::CenterPoint),\n\t\t\tAlignmentSnapTarget::ArtboardCornerPoint => write!(f, \"{}\", ArtboardSnapTarget::CornerPoint),\n\t\t\tAlignmentSnapTarget::ArtboardCenterPoint => write!(f, \"{}\", ArtboardSnapTarget::CenterPoint),\n\t\t\tAlignmentSnapTarget::AlignWithAnchorPoint => write!(f, \"{}\", PathSnapTarget::AnchorPointWithColinearHandles),\n\t\t\tAlignmentSnapTarget::IntersectionPoint => write!(f, \"{}\", PathSnapTarget::IntersectionPoint),\n\t\t\tAlignmentSnapTarget::PerpendicularToEndpoint => write!(f, \"{}\", PathSnapTarget::PerpendicularToEndpoint),\n\t\t}\n\t}\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum DistributionSnapTarget {\n\tX,\n\tY,\n\tRight,\n\tLeft,\n\tUp,\n\tDown,\n\tXY,\n}\n\nimpl fmt::Display for DistributionSnapTarget {\n\tfn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n\t\tmatch self {\n\t\t\tDistributionSnapTarget::X => write!(f, \"Distribute: X\"),\n\t\t\tDistributionSnapTarget::Y => write!(f, \"Distribute: Y\"),\n\t\t\tDistributionSnapTarget::Right => write!(f, \"Distribute: Right\"),\n\t\t\tDistributionSnapTarget::Left => write!(f, \"Distribute: Left\"),\n\t\t\tDistributionSnapTarget::Up => write!(f, \"Distribute: Up\"),\n\t\t\tDistributionSnapTarget::Down => write!(f, \"Distribute: Down\"),\n\t\t\tDistributionSnapTarget::XY => write!(f, \"Distribute: XY\"),\n\t\t}\n\t}\n}\n\nimpl DistributionSnapTarget {\n\tpub const fn is_x(&self) -> bool {\n\t\tmatches!(self, Self::Left | Self::Right | Self::X)\n\t}\n\tpub const fn is_y(&self) -> bool {\n\t\tmatches!(self, Self::Up | Self::Down | Self::Y)\n\t}\n}\n\n#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]\npub enum SnapTarget {\n\t#[default]\n\tNone,\n\tBoundingBox(BoundingBoxSnapTarget),\n\tPath(PathSnapTarget),\n\tArtboard(ArtboardSnapTarget),\n\tGrid(GridSnapTarget),\n\tAlignment(AlignmentSnapTarget),\n\tDistributeEvenly(DistributionSnapTarget),\n}\n\nimpl SnapTarget {\n\tpub fn is_some(&self) -> bool {\n\t\tself != &Self::None\n\t}\n\tpub fn bounding_box(&self) -> bool {\n\t\tmatches!(self, Self::BoundingBox(_) | Self::Artboard(_))\n\t}\n}\n\nimpl fmt::Display for SnapTarget {\n\tfn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n\t\tmatch self {\n\t\t\tSnapTarget::None => write!(f, \"None\"),\n\t\t\tSnapTarget::BoundingBox(bounding_box_snap_target) => write!(f, \"{bounding_box_snap_target}\"),\n\t\t\tSnapTarget::Path(path_snap_target) => write!(f, \"{path_snap_target}\"),\n\t\t\tSnapTarget::Artboard(artboard_snap_target) => write!(f, \"{artboard_snap_target}\"),\n\t\t\tSnapTarget::Grid(grid_snap_target) => write!(f, \"{grid_snap_target}\"),\n\t\t\tSnapTarget::Alignment(alignment_snap_target) => write!(f, \"{alignment_snap_target}\"),\n\t\t\tSnapTarget::DistributeEvenly(distribution_snap_target) => write!(f, \"{distribution_snap_target}\"),\n\t\t}\n\t}\n}\n\n// TODO: implement icons for SnappingOptions eventually\npub enum SnappingOptions {\n\tBoundingBoxes,\n\tPaths,\n}\n\nimpl fmt::Display for SnappingOptions {\n\tfn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n\t\tmatch self {\n\t\t\tSnappingOptions::BoundingBoxes => write!(f, \"Bounding Boxes\"),\n\t\t\tSnappingOptions::Paths => write!(f, \"Paths\"),\n\t\t}\n\t}\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)]\n#[serde(default)]\npub struct PTZ {\n\t/// Offset distance.\n\tpub pan: DVec2,\n\t/// Angle in radians.\n\ttilt: f64,\n\t/// Scale factor.\n\tzoom: f64,\n\t/// Flipped status.\n\tpub flip: bool,\n}\n\nimpl Default for PTZ {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tpan: DVec2::ZERO,\n\t\t\ttilt: 0.,\n\t\t\tzoom: 1.,\n\t\t\tflip: false,\n\t\t}\n\t}\n}\n\nimpl PTZ {\n\t/// Get the tilt angle between -180° and 180° in radians.\n\tpub fn tilt(&self) -> f64 {\n\t\t(((self.tilt + std::f64::consts::PI) % std::f64::consts::TAU) + std::f64::consts::TAU) % std::f64::consts::TAU - std::f64::consts::PI\n\t}\n\n\tpub fn unmodified_tilt(&self) -> f64 {\n\t\tself.tilt\n\t}\n\n\t/// Set a new tilt angle in radians.\n\tpub fn set_tilt(&mut self, tilt: f64) {\n\t\tself.tilt = tilt;\n\t}\n\n\t/// Get the scale factor.\n\tpub fn zoom(&self) -> f64 {\n\t\tself.zoom\n\t}\n\n\t/// Set a new scale factor.\n\tpub fn set_zoom(&mut self, zoom: f64) {\n\t\tself.zoom = zoom.clamp(crate::consts::VIEWPORT_ZOOM_SCALE_MIN, crate::consts::VIEWPORT_ZOOM_SCALE_MAX)\n\t}\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)]\npub enum GroupFolderType {\n\tLayer,\n\tBooleanOperation(graphene_std::vector::misc::BooleanOperation),\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/utility_types/mod.rs",
    "content": "pub mod clipboards;\npub mod document_metadata;\npub mod error;\npub mod misc;\npub mod network_interface;\npub mod nodes;\npub mod transformation;\npub mod wires;\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/utility_types/network_interface/deserialization.rs",
    "content": "use crate::messages::portfolio::document::utility_types::network_interface::{DocumentNodePersistentMetadata, InputMetadata, InputPersistentMetadata, NodeNetworkMetadata, NodeTypePersistentMetadata};\nuse serde_json::Value;\nuse std::collections::HashMap;\n\n/// Persistent metadata for each node in the network, which must be included when creating, serializing, and deserializing saving a node.\n#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct DocumentNodePersistentMetadataInputNames {\n\tpub reference: Option<String>,\n\t#[serde(default)]\n\tpub display_name: String,\n\tpub input_names: Vec<String>,\n\tpub output_names: Vec<String>,\n\tpub has_primary_output: bool,\n\t#[serde(default)]\n\tpub locked: bool,\n\t#[serde(default)]\n\tpub pinned: bool,\n\tpub node_type_metadata: NodeTypePersistentMetadata,\n\tpub network_metadata: Option<NodeNetworkMetadata>,\n}\n\nimpl From<DocumentNodePersistentMetadataInputNames> for DocumentNodePersistentMetadataPropertiesRow {\n\tfn from(old: DocumentNodePersistentMetadataInputNames) -> Self {\n\t\tDocumentNodePersistentMetadataPropertiesRow {\n\t\t\treference: old.reference,\n\t\t\tinput_properties: Vec::new(),\n\t\t\tdisplay_name: old.display_name,\n\t\t\toutput_names: old.output_names,\n\t\t\thas_primary_output: old.has_primary_output,\n\t\t\tlocked: old.locked,\n\t\t\tpinned: old.pinned,\n\t\t\tnode_type_metadata: old.node_type_metadata,\n\t\t\tnetwork_metadata: old.network_metadata,\n\t\t}\n\t}\n}\n\n#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct DocumentNodePersistentMetadataPropertiesRow {\n\tpub reference: Option<String>,\n\t#[serde(default)]\n\tpub display_name: String,\n\tpub input_properties: Vec<PropertiesRow>,\n\tpub output_names: Vec<String>,\n\tpub has_primary_output: bool,\n\t#[serde(default)]\n\tpub locked: bool,\n\t#[serde(default)]\n\tpub pinned: bool,\n\tpub node_type_metadata: NodeTypePersistentMetadata,\n\tpub network_metadata: Option<NodeNetworkMetadata>,\n}\n\n#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct PropertiesRow {\n\tpub input_data: HashMap<String, Value>,\n\tpub widget_override: Option<String>,\n\t#[serde(skip)]\n\tpub input_name: String,\n\t#[serde(skip)]\n\tpub input_description: String,\n}\n\nimpl From<DocumentNodePersistentMetadataPropertiesRow> for DocumentNodePersistentMetadataHasPrimaryOutput {\n\tfn from(old: DocumentNodePersistentMetadataPropertiesRow) -> Self {\n\t\tlet mut input_metadata = Vec::new();\n\t\tfor properties_row in old.input_properties {\n\t\t\tinput_metadata.push(InputMetadata {\n\t\t\t\tpersistent_metadata: InputPersistentMetadata {\n\t\t\t\t\tinput_data: properties_row.input_data,\n\t\t\t\t\twidget_override: properties_row.widget_override,\n\t\t\t\t\tinput_name: properties_row.input_name,\n\t\t\t\t\tinput_description: properties_row.input_description,\n\t\t\t\t},\n\t\t\t\t..Default::default()\n\t\t\t})\n\t\t}\n\t\tDocumentNodePersistentMetadataHasPrimaryOutput {\n\t\t\treference: old.reference,\n\t\t\tdisplay_name: old.display_name,\n\t\t\tinput_metadata: Vec::new(),\n\t\t\toutput_names: old.output_names,\n\t\t\thas_primary_output: old.has_primary_output,\n\t\t\tlocked: old.locked,\n\t\t\tpinned: old.pinned,\n\t\t\tnode_type_metadata: old.node_type_metadata,\n\t\t\tnetwork_metadata: old.network_metadata,\n\t\t}\n\t}\n}\n\n/// Persistent metadata for each node in the network, which must be included when creating, serializing, and deserializing saving a node.\n#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct DocumentNodePersistentMetadataHasPrimaryOutput {\n\tpub reference: Option<String>,\n\t#[serde(default)]\n\tpub display_name: String,\n\tpub input_metadata: Vec<InputMetadata>,\n\tpub output_names: Vec<String>,\n\tpub has_primary_output: bool,\n\t#[serde(default)]\n\tpub locked: bool,\n\t#[serde(default)]\n\tpub pinned: bool,\n\tpub node_type_metadata: NodeTypePersistentMetadata,\n\tpub network_metadata: Option<NodeNetworkMetadata>,\n}\n\nimpl From<DocumentNodePersistentMetadataHasPrimaryOutput> for DocumentNodePersistentMetadataStringReference {\n\tfn from(old: DocumentNodePersistentMetadataHasPrimaryOutput) -> Self {\n\t\tDocumentNodePersistentMetadataStringReference {\n\t\t\treference: old.reference,\n\t\t\tdisplay_name: old.display_name,\n\t\t\tinput_metadata: old.input_metadata,\n\t\t\toutput_names: old.output_names,\n\t\t\tlocked: old.locked,\n\t\t\tpinned: old.pinned,\n\t\t\tnode_type_metadata: old.node_type_metadata,\n\t\t\tnetwork_metadata: old.network_metadata,\n\t\t}\n\t}\n}\n\n#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]\nstruct DocumentNodePersistentMetadataStringReference {\n\tpub reference: Option<String>,\n\t#[serde(default)]\n\tpub display_name: String,\n\tpub input_metadata: Vec<InputMetadata>,\n\tpub output_names: Vec<String>,\n\t#[serde(default)]\n\tpub locked: bool,\n\t#[serde(default)]\n\tpub pinned: bool,\n\tpub node_type_metadata: NodeTypePersistentMetadata,\n\tpub network_metadata: Option<NodeNetworkMetadata>,\n}\n\nimpl From<DocumentNodePersistentMetadataStringReference> for DocumentNodePersistentMetadata {\n\tfn from(mut old: DocumentNodePersistentMetadataStringReference) -> Self {\n\t\tif let Some(metadata) = old.network_metadata.as_mut() {\n\t\t\tmetadata.persistent_metadata.reference = old.reference;\n\t\t}\n\t\tDocumentNodePersistentMetadata {\n\t\t\tdisplay_name: old.display_name,\n\t\t\tinput_metadata: old.input_metadata,\n\t\t\toutput_names: old.output_names,\n\t\t\tlocked: old.locked,\n\t\t\tpinned: old.pinned,\n\t\t\tnode_type_metadata: old.node_type_metadata,\n\t\t\tnetwork_metadata: old.network_metadata,\n\t\t}\n\t}\n}\n\n#[derive(serde::Deserialize)]\n#[serde(untagged)]\nenum DocumentNodePersistentMetadataVersioned {\n\t// Newest first\n\tCurrent(DocumentNodePersistentMetadata),\n\tStringReference(DocumentNodePersistentMetadataStringReference),\n\tHasPrimaryOutput(DocumentNodePersistentMetadataHasPrimaryOutput),\n\tPropertiesRow(DocumentNodePersistentMetadataPropertiesRow),\n\tInputNames(DocumentNodePersistentMetadataInputNames),\n}\n\npub fn deserialize_node_persistent_metadata<'de, D>(deserializer: D) -> Result<DocumentNodePersistentMetadata, D::Error>\nwhere\n\tD: serde::Deserializer<'de>,\n{\n\tuse serde::Deserialize;\n\n\tlet value = Value::deserialize(deserializer)?;\n\n\tlet versioned_document = serde_json::from_value::<DocumentNodePersistentMetadataVersioned>(value).map_err(serde::de::Error::custom)?;\n\n\tlet current: DocumentNodePersistentMetadata = match versioned_document {\n\t\tDocumentNodePersistentMetadataVersioned::Current(v) => v,\n\t\tDocumentNodePersistentMetadataVersioned::StringReference(v) => {\n\t\t\tlet v: DocumentNodePersistentMetadataStringReference = v;\n\t\t\tv.into()\n\t\t}\n\t\tDocumentNodePersistentMetadataVersioned::HasPrimaryOutput(v) => {\n\t\t\tlet v: DocumentNodePersistentMetadataStringReference = v.into();\n\t\t\tv.into()\n\t\t}\n\t\tDocumentNodePersistentMetadataVersioned::PropertiesRow(v) => {\n\t\t\tlet v: DocumentNodePersistentMetadataHasPrimaryOutput = v.into();\n\t\t\tlet v: DocumentNodePersistentMetadataStringReference = v.into();\n\t\t\tv.into()\n\t\t}\n\t\tDocumentNodePersistentMetadataVersioned::InputNames(v) => {\n\t\t\tlet v: DocumentNodePersistentMetadataPropertiesRow = v.into();\n\t\t\tlet v: DocumentNodePersistentMetadataHasPrimaryOutput = v.into();\n\t\t\tlet v: DocumentNodePersistentMetadataStringReference = v.into();\n\t\t\tv.into()\n\t\t}\n\t};\n\n\tOk(current)\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/utility_types/network_interface/memo_network.rs",
    "content": "use graph_craft::document::NodeNetwork;\nuse std::cell::Cell;\nuse std::hash::{Hash, Hasher};\n\n#[derive(Debug, Default, Clone, PartialEq)]\npub struct MemoNetwork {\n\tnetwork: NodeNetwork,\n\thash_code: Cell<Option<u64>>,\n}\n\nimpl<'de> serde::Deserialize<'de> for MemoNetwork {\n\tfn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n\twhere\n\t\tD: serde::Deserializer<'de>,\n\t{\n\t\tOk(Self::new(NodeNetwork::deserialize(deserializer)?))\n\t}\n}\n\nimpl serde::Serialize for MemoNetwork {\n\tfn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n\twhere\n\t\tS: serde::Serializer,\n\t{\n\t\tself.network.serialize(serializer)\n\t}\n}\n\nimpl Hash for MemoNetwork {\n\tfn hash<H: Hasher>(&self, state: &mut H) {\n\t\tself.current_hash().hash(state);\n\t}\n}\n\nimpl MemoNetwork {\n\tpub fn network(&self) -> &NodeNetwork {\n\t\t&self.network\n\t}\n\n\tpub fn network_mut(&mut self) -> &mut NodeNetwork {\n\t\tself.hash_code.set(None);\n\t\t&mut self.network\n\t}\n\n\tpub fn new(network: NodeNetwork) -> Self {\n\t\tSelf { network, hash_code: None.into() }\n\t}\n\n\tpub fn current_hash(&self) -> u64 {\n\t\tlet mut hash_code = self.hash_code.get();\n\t\tif hash_code.is_none() {\n\t\t\thash_code = Some(self.network.current_hash());\n\t\t\tself.hash_code.set(hash_code);\n\t\t}\n\t\thash_code.unwrap()\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs",
    "content": "use std::collections::{HashMap, HashSet};\n\nuse graph_craft::document::value::TaggedValue;\nuse graph_craft::document::{DocumentNodeImplementation, InlineRust, NodeInput};\nuse graph_craft::proto::{GraphErrorType, GraphErrors};\nuse graph_craft::{Type, concrete};\nuse graphene_std::uuid::NodeId;\nuse interpreted_executor::dynamic_executor::{NodeTypes, ResolvedDocumentNodeTypesDelta};\nuse interpreted_executor::node_registry::NODE_REGISTRY;\n\nuse crate::messages::portfolio::document::node_graph::utility_types::FrontendGraphDataType;\nuse crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeNetworkInterface, OutputConnector};\n\n// This file contains utility methods for interfacing with the resolved types returned from the compiler\n#[derive(Debug, Default)]\npub struct ResolvedDocumentNodeTypes {\n\tpub types: HashMap<Vec<NodeId>, NodeTypes>,\n\tpub node_graph_errors: GraphErrors,\n}\n\nimpl ResolvedDocumentNodeTypes {\n\tpub fn update(&mut self, delta: ResolvedDocumentNodeTypesDelta, errors: GraphErrors) {\n\t\tfor (path, node_type) in delta.add {\n\t\t\tself.types.insert(path.to_vec(), node_type);\n\t\t}\n\t\tfor path in delta.remove {\n\t\t\tself.types.remove(&path.to_vec());\n\t\t}\n\t\tself.node_graph_errors = errors;\n\t}\n}\n\n/// Represents the result of a type query for an input or output connector.\n#[derive(Debug, Clone, PartialEq)]\npub enum TypeSource {\n\t/// A type that has been compiled based on all upstream types.\n\tCompiled(Type),\n\t/// The type of value inputs.\n\tTaggedValue(Type),\n\t/// When the input/output is not compiled. The Type is from the document node definition, or () if it doesn't exist.\n\tUnknown,\n\t/// When there is a node graph error for the inputs to a node. The Type is from the document node definition, or () if it doesn't exist.\n\tInvalid,\n\t/// When there is an error in the algorithm for determining the input/output type (indicates a bug in the editor).\n\tError(&'static str),\n}\n\nimpl TypeSource {\n\t/// The reduced set of frontend types for displaying color.\n\tpub fn displayed_type(&self) -> FrontendGraphDataType {\n\t\tif matches!(self, TypeSource::Invalid) {\n\t\t\treturn FrontendGraphDataType::Invalid;\n\t\t};\n\t\tmatch self.compiled_nested_type() {\n\t\t\tSome(nested_type) => match TaggedValue::from_type_or_none(nested_type) {\n\t\t\t\tTaggedValue::U32(_)\n\t\t\t\t| TaggedValue::U64(_)\n\t\t\t\t| TaggedValue::F32(_)\n\t\t\t\t| TaggedValue::F64(_)\n\t\t\t\t| TaggedValue::DVec2(_)\n\t\t\t\t| TaggedValue::F64Array4(_)\n\t\t\t\t| TaggedValue::VecF64(_)\n\t\t\t\t| TaggedValue::VecDVec2(_)\n\t\t\t\t| TaggedValue::DAffine2(_) => FrontendGraphDataType::Number,\n\t\t\t\tTaggedValue::Artboard(_) => FrontendGraphDataType::Artboard,\n\t\t\t\tTaggedValue::Graphic(_) => FrontendGraphDataType::Graphic,\n\t\t\t\tTaggedValue::Raster(_) => FrontendGraphDataType::Raster,\n\t\t\t\tTaggedValue::Vector(_) => FrontendGraphDataType::Vector,\n\t\t\t\tTaggedValue::Color(_) => FrontendGraphDataType::Color,\n\t\t\t\tTaggedValue::Gradient(_) | TaggedValue::GradientTable(_) => FrontendGraphDataType::Gradient,\n\t\t\t\tTaggedValue::String(_) => FrontendGraphDataType::Typography,\n\t\t\t\t_ => FrontendGraphDataType::General,\n\t\t\t},\n\t\t\tNone => FrontendGraphDataType::General,\n\t\t}\n\t}\n\n\tpub fn compiled_nested_type(&self) -> Option<&Type> {\n\t\tmatch self {\n\t\t\tTypeSource::Compiled(compiled_type) => Some(compiled_type.nested_type()),\n\t\t\tTypeSource::TaggedValue(value_type) => Some(value_type.nested_type()),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\t/// Used when searching for nodes in the add Node popup.\n\tpub fn add_node_string(self) -> Option<String> {\n\t\tself.compiled_nested_type().map(|ty| format!(\"type:{ty}\"))\n\t}\n\n\t/// The type to display in the tooltip label.\n\tpub fn resolved_type_tooltip_string(&self) -> String {\n\t\tmatch self {\n\t\t\tTypeSource::Compiled(compiled_type) => format!(\"Data Type: {}\", compiled_type.nested_type()),\n\t\t\tTypeSource::TaggedValue(value_type) => format!(\"Data Type: {}\", value_type.nested_type()),\n\t\t\tTypeSource::Unknown => \"Unknown Data Type\".to_string(),\n\t\t\tTypeSource::Invalid => \"Invalid Type Combination\".to_string(),\n\t\t\tTypeSource::Error(_) => \"Error Getting Data Type\".to_string(),\n\t\t}\n\t}\n\n\t/// The type to display in the node row.\n\tpub fn resolved_type_node_string(&self) -> String {\n\t\tmatch self {\n\t\t\tTypeSource::Compiled(compiled_type) => compiled_type.nested_type().to_string(),\n\t\t\tTypeSource::TaggedValue(value_type) => value_type.nested_type().to_string(),\n\t\t\tTypeSource::Unknown => \"Unknown\".to_string(),\n\t\t\tTypeSource::Invalid => \"Invalid\".to_string(),\n\t\t\tTypeSource::Error(_) => \"Error\".to_string(),\n\t\t}\n\t}\n}\n\nimpl NodeNetworkInterface {\n\tfn input_has_error(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> bool {\n\t\tmatch input_connector {\n\t\t\tInputConnector::Node { node_id, input_index } => {\n\t\t\t\tlet Some(implementation) = self.implementation(node_id, network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get implementation in input_has_error\");\n\t\t\t\t\treturn false;\n\t\t\t\t};\n\t\t\t\tlet node_path = [network_path, &[*node_id]].concat();\n\t\t\t\tmatch implementation {\n\t\t\t\t\tDocumentNodeImplementation::Network(_) => {\n\t\t\t\t\t\tlet Some(map) = self.outward_wires(&node_path) else { return false };\n\t\t\t\t\t\tlet Some(outward_wires) = map.get(&OutputConnector::Import(*input_index)) else { return false };\n\t\t\t\t\t\toutward_wires.clone().iter().any(|connector| match connector {\n\t\t\t\t\t\t\tInputConnector::Node { node_id, input_index } => self.input_has_error(&InputConnector::node(*node_id, *input_index), &node_path),\n\t\t\t\t\t\t\tInputConnector::Export(_) => false,\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t\tDocumentNodeImplementation::ProtoNode(_) => self.resolved_types.node_graph_errors.iter().any(|error| {\n\t\t\t\t\t\terror.node_path == node_path\n\t\t\t\t\t\t\t&& match &error.error {\n\t\t\t\t\t\t\t\tGraphErrorType::InvalidImplementations { error_inputs, .. } => error_inputs.iter().any(|solution| solution.iter().any(|(index, _)| index == input_index)),\n\t\t\t\t\t\t\t\t_ => true,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\n\t\t\t\t\tDocumentNodeImplementation::Extract => false,\n\t\t\t\t}\n\t\t\t}\n\t\t\tInputConnector::Export(_) => false,\n\t\t}\n\t}\n\n\tpub fn input_type_not_invalid(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> TypeSource {\n\t\tlet Some(input) = self.input_from_connector(input_connector, network_path) else {\n\t\t\treturn TypeSource::Error(\"Could not get input from connector\");\n\t\t};\n\n\t\tmatch input {\n\t\t\tNodeInput::Node { node_id, output_index } => {\n\t\t\t\tlet output_connector = OutputConnector::node(*node_id, *output_index);\n\n\t\t\t\tself.output_type(&output_connector, network_path)\n\t\t\t}\n\n\t\t\tNodeInput::Value { tagged_value, .. } => TypeSource::TaggedValue(tagged_value.ty()),\n\t\t\tNodeInput::Import { import_index, .. } => {\n\t\t\t\t// Get the input type of the encapsulating node input\n\t\t\t\tlet Some((encapsulating_node, encapsulating_path)) = network_path.split_last() else {\n\t\t\t\t\treturn TypeSource::Error(\"Could not get type of import in document network since it has no imports\");\n\t\t\t\t};\n\t\t\t\tself.input_type(&InputConnector::node(*encapsulating_node, *import_index), encapsulating_path)\n\t\t\t}\n\t\t\tNodeInput::Scope(_) => TypeSource::Compiled(concrete!(())),\n\t\t\tNodeInput::Reflection(document_node_metadata) => TypeSource::Compiled(document_node_metadata.ty()),\n\t\t\tNodeInput::Inline(_) => TypeSource::Compiled(concrete!(InlineRust)),\n\t\t}\n\t}\n\n\t/// Get the [`TypeSource`] for any InputConnector.\n\t/// If the input is not compiled, then an Unknown or default from the definition is returned.\n\tpub fn input_type(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> TypeSource {\n\t\t// First check if there is an error with this node or any protonodes it is connected to\n\t\tif self.input_has_error(input_connector, network_path) {\n\t\t\treturn TypeSource::Invalid;\n\t\t}\n\t\tself.input_type_not_invalid(input_connector, network_path)\n\t}\n\n\t/// Gets the default tagged value for an input. If its not compiled, then it tries to get a valid type. If there are no valid types, then it picks a random implementation.\n\tpub fn tagged_value_from_input(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> TaggedValue {\n\t\tlet guaranteed_type = match self.input_type(input_connector, network_path) {\n\t\t\tTypeSource::Compiled(compiled) => compiled,\n\t\t\tTypeSource::TaggedValue(value) => value,\n\t\t\tTypeSource::Unknown | TypeSource::Invalid => {\n\t\t\t\t// Pick a random type from the complete valid types\n\t\t\t\t// TODO: Add a NodeInput::Indeterminate which can be resolved at compile time to be any type that prevents an error. This may require bidirectional typing.\n\t\t\t\tself.complete_valid_input_types(input_connector, network_path)\n\t\t\t\t\t.into_iter()\n\t\t\t\t\t.min_by_key(|ty| ty.nested_type().identifier_name())\n\t\t\t\t\t// Pick a random type from the potential valid types\n\t\t\t\t\t.or_else(|| {\n\t\t\t\t\t\tself.potential_valid_input_types(input_connector, network_path)\n\t\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t\t.min_by_key(|ty| ty.nested_type().identifier_name())\n\t\t\t\t\t}).unwrap_or(concrete!(()))\n\t\t\t}\n\t\t\tTypeSource::Error(e) => {\n\t\t\t\tlog::error!(\"Error getting tagged_value_from_input for {input_connector:?} {e}\");\n\t\t\t\tconcrete!(())\n\t\t\t}\n\t\t};\n\t\tTaggedValue::from_type_or_none(&guaranteed_type)\n\t}\n\n\t/// A list of all valid input types for this specific node.\n\tpub fn potential_valid_input_types(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Vec<Type> {\n\t\tlet InputConnector::Node { node_id, input_index } = input_connector else {\n\t\t\t// An export can have any type connected to it\n\t\t\treturn vec![graph_craft::generic!(T)];\n\t\t};\n\t\tlet Some(implementation) = self.implementation(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get node implementation in potential_valid_input_types\");\n\t\t\treturn Vec::new();\n\t\t};\n\t\tmatch implementation {\n\t\t\tDocumentNodeImplementation::Network(_) => {\n\t\t\t\tlet nested_path = [network_path, &[*node_id]].concat();\n\t\t\t\tlet Some(outward_wires) = self.outward_wires(&nested_path) else {\n\t\t\t\t\tlog::error!(\"Could not get outward wires in potential_valid_input_types\");\n\t\t\t\t\treturn Vec::new();\n\t\t\t\t};\n\t\t\t\tlet Some(inputs_from_import) = outward_wires.get(&OutputConnector::Import(*input_index)) else {\n\t\t\t\t\tlog::error!(\"Could not get inputs from import in potential_valid_input_types\");\n\t\t\t\t\treturn Vec::new();\n\t\t\t\t};\n\n\t\t\t\tlet intersection: HashSet<Type> = inputs_from_import\n\t\t\t\t\t.clone()\n\t\t\t\t\t.iter()\n\t\t\t\t\t.map(|input_connector| self.potential_valid_input_types(input_connector, &nested_path).into_iter().collect::<HashSet<_>>())\n\t\t\t\t\t.fold(None, |acc: Option<HashSet<Type>>, set| match acc {\n\t\t\t\t\t\tSome(acc_set) => Some(acc_set.intersection(&set).cloned().collect()),\n\t\t\t\t\t\tNone => Some(set),\n\t\t\t\t\t})\n\t\t\t\t\t.unwrap_or_default();\n\n\t\t\t\tintersection.into_iter().collect::<Vec<_>>()\n\t\t\t}\n\t\t\tDocumentNodeImplementation::ProtoNode(proto_node_identifier) => {\n\t\t\t\tlet Some(implementations) = NODE_REGISTRY.get(proto_node_identifier) else {\n\t\t\t\t\tlog::error!(\"Protonode {proto_node_identifier:?} not found in registry in potential_valid_input_types\");\n\t\t\t\t\treturn Vec::new();\n\t\t\t\t};\n\t\t\t\tlet number_of_inputs = self.number_of_inputs(node_id, network_path);\n\t\t\t\timplementations\n\t\t\t\t\t.iter()\n\t\t\t\t\t.filter_map(|(node_io, _)| {\n\t\t\t\t\t\t// Check if this NodeIOTypes implementation is valid for the other inputs\n\t\t\t\t\t\tlet valid_implementation = (0..number_of_inputs).filter(|iterator_index| iterator_index != input_index).all(|iterator_index| {\n\t\t\t\t\t\t\tlet input_type = self.input_type_not_invalid(&InputConnector::node(*node_id, iterator_index), network_path);\n\t\t\t\t\t\t\t// TODO: Fix type checking for different call arguments\n\t\t\t\t\t\t\t// For example a node input of (Footprint) -> Vector would not be compatible with a node that is called with () and returns Vector\n\t\t\t\t\t\t\tnode_io.inputs.get(iterator_index).map(|ty| ty.nested_type()) == input_type.compiled_nested_type()\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// If so, then return the input at the chosen index\n\t\t\t\t\t\tif valid_implementation { node_io.inputs.get(*input_index).cloned() } else { None }\n\t\t\t\t\t})\n\t\t\t\t\t.collect::<Vec<_>>()\n\t\t\t}\n\t\t\tDocumentNodeImplementation::Extract => {\n\t\t\t\tlog::error!(\"Input types for extract node not supported\");\n\t\t\t\tVec::new()\n\t\t\t}\n\t\t}\n\t}\n\n\t/// Performs a downstream traversal to ensure input type will work in the full context of the graph.\n\tpub fn complete_valid_input_types(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Vec<Type> {\n\t\tmatch input_connector {\n\t\t\tInputConnector::Node { node_id, input_index } => {\n\t\t\t\tlet Some(implementation) = self.implementation(node_id, network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get node implementation for {:?} {} in complete_valid_input_types\", network_path, *node_id);\n\t\t\t\t\treturn Vec::new();\n\t\t\t\t};\n\t\t\t\tmatch implementation {\n\t\t\t\t\tDocumentNodeImplementation::Network(_) => self.valid_output_types(&OutputConnector::Import(input_connector.input_index()), &[network_path, &[*node_id]].concat()),\n\t\t\t\t\tDocumentNodeImplementation::ProtoNode(proto_node_identifier) => {\n\t\t\t\t\t\tlet Some(implementations) = NODE_REGISTRY.get(proto_node_identifier) else {\n\t\t\t\t\t\t\tlog::error!(\"Protonode {proto_node_identifier:?} not found in registry in complete_valid_input_types\");\n\t\t\t\t\t\t\treturn Vec::new();\n\t\t\t\t\t\t};\n\t\t\t\t\t\tlet valid_output_types = self.valid_output_types(&OutputConnector::node(*node_id, 0), network_path);\n\n\t\t\t\t\t\timplementations\n\t\t\t\t\t\t\t.iter()\n\t\t\t\t\t\t\t.filter_map(|(node_io, _)| {\n\t\t\t\t\t\t\t\tif !valid_output_types.iter().any(|output_type| output_type.nested_type() == node_io.return_value.nested_type()) {\n\t\t\t\t\t\t\t\t\treturn None;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tlet valid_inputs = (0..node_io.inputs.len()).filter(|iterator_index| iterator_index != input_index).all(|iterator_index| {\n\t\t\t\t\t\t\t\t\tlet input_type = self.input_type_not_invalid(&InputConnector::node(*node_id, iterator_index), network_path);\n\t\t\t\t\t\t\t\t\tmatch input_type.compiled_nested_type() {\n\t\t\t\t\t\t\t\t\t\tSome(input_type) => node_io.inputs.get(iterator_index).is_some_and(|node_io_input_type| node_io_input_type.nested_type() == input_type),\n\t\t\t\t\t\t\t\t\t\tNone => true,\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tif valid_inputs { node_io.inputs.get(*input_index).cloned() } else { None }\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.collect::<Vec<_>>()\n\t\t\t\t\t}\n\t\t\t\t\tDocumentNodeImplementation::Extract => Vec::new(),\n\t\t\t\t}\n\t\t\t}\n\t\t\tInputConnector::Export(export_index) => {\n\t\t\t\tmatch network_path.split_last() {\n\t\t\t\t\tSome((encapsulating_node, encapsulating_path)) => self.valid_output_types(&OutputConnector::node(*encapsulating_node, *export_index), encapsulating_path),\n\t\t\t\t\tNone => {\n\t\t\t\t\t\t// Valid types for the export are all types that can be fed into the render node\n\t\t\t\t\t\tlet render_node = graphene_std::render_node::render::IDENTIFIER;\n\t\t\t\t\t\tlet Some(implementations) = NODE_REGISTRY.get(&render_node) else {\n\t\t\t\t\t\t\tlog::error!(\"Protonode {render_node:?} not found in registry\");\n\t\t\t\t\t\t\treturn Vec::new();\n\t\t\t\t\t\t};\n\t\t\t\t\t\timplementations.keys().map(|types| types.inputs[1].clone()).collect()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn output_type(&mut self, output_connector: &OutputConnector, network_path: &[NodeId]) -> TypeSource {\n\t\tmatch output_connector {\n\t\t\tOutputConnector::Node { node_id, output_index } => {\n\t\t\t\t// First try iterating upstream to the first protonode and try get its compiled type\n\t\t\t\tlet Some(implementation) = self.implementation(node_id, network_path) else {\n\t\t\t\t\treturn TypeSource::Error(\"Could not get implementation\");\n\t\t\t\t};\n\t\t\t\tmatch implementation {\n\t\t\t\t\tDocumentNodeImplementation::Network(_) => self.input_type(&InputConnector::Export(*output_index), &[network_path, &[*node_id]].concat()),\n\t\t\t\t\tDocumentNodeImplementation::ProtoNode(_) => match self.resolved_types.types.get(&[network_path, &[*node_id]].concat()) {\n\t\t\t\t\t\tSome(resolved_type) => TypeSource::Compiled(resolved_type.output.clone()),\n\t\t\t\t\t\tNone => TypeSource::Unknown,\n\t\t\t\t\t},\n\t\t\t\t\tDocumentNodeImplementation::Extract => TypeSource::Compiled(concrete!(())),\n\t\t\t\t}\n\t\t\t}\n\t\t\tOutputConnector::Import(import_index) => {\n\t\t\t\tlet Some((encapsulating_node, encapsulating_path)) = network_path.split_last() else {\n\t\t\t\t\treturn TypeSource::Error(\"Cannot get import type in document network since it has no imports\");\n\t\t\t\t};\n\t\t\t\tlet mut input_type = self.input_type(&InputConnector::node(*encapsulating_node, *import_index), encapsulating_path);\n\t\t\t\tif matches!(input_type, TypeSource::Invalid) {\n\t\t\t\t\tinput_type = TypeSource::Unknown\n\t\t\t\t}\n\t\t\t\tinput_type\n\t\t\t}\n\t\t}\n\t}\n\n\t/// The valid output types are all types that are valid for each downstream connection.\n\tfn valid_output_types(&mut self, output_connector: &OutputConnector, network_path: &[NodeId]) -> Vec<Type> {\n\t\tlet Some(outward_wires) = self.outward_wires(network_path) else {\n\t\t\tlog::error!(\"Could not get outward wires in valid_output_types\");\n\t\t\treturn Vec::new();\n\t\t};\n\t\tlet Some(inputs_from_import) = outward_wires.get(output_connector) else {\n\t\t\tlog::error!(\"Could not get inputs from import in valid_output_types\");\n\t\t\treturn Vec::new();\n\t\t};\n\n\t\tlet intersection = inputs_from_import\n\t\t\t.clone()\n\t\t\t.iter()\n\t\t\t.map(|input_connector| self.potential_valid_input_types(input_connector, network_path).into_iter().collect::<HashSet<_>>())\n\t\t\t.fold(None, |acc: Option<HashSet<Type>>, set| match acc {\n\t\t\t\tSome(acc_set) => Some(acc_set.intersection(&set).cloned().collect()),\n\t\t\t\tNone => Some(set),\n\t\t\t})\n\t\t\t.unwrap_or_default();\n\n\t\tintersection.into_iter().collect::<Vec<_>>()\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/utility_types/network_interface.rs",
    "content": "mod deserialization;\nmod memo_network;\nmod resolved_types;\n\nuse super::document_metadata::{DocumentMetadata, LayerNodeIdentifier, NodeRelations};\nuse super::misc::PTZ;\nuse super::nodes::SelectedNodes;\nuse crate::consts::{EXPORTS_TO_RIGHT_EDGE_PIXEL_GAP, EXPORTS_TO_TOP_EDGE_PIXEL_GAP, GRID_SIZE, IMPORTS_TO_LEFT_EDGE_PIXEL_GAP, IMPORTS_TO_TOP_EDGE_PIXEL_GAP};\nuse crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext;\nuse crate::messages::portfolio::document::node_graph::document_node_definitions::{DefinitionIdentifier, resolve_document_node_type};\nuse crate::messages::portfolio::document::node_graph::utility_types::{Direction, FrontendClickTargets, FrontendGraphDataType, FrontendGraphInput, FrontendGraphOutput};\nuse crate::messages::portfolio::document::overlays::utility_functions::text_width;\nuse crate::messages::portfolio::document::utility_types::network_interface::resolved_types::ResolvedDocumentNodeTypes;\nuse crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle, WirePath, WirePathUpdate, build_vector_wire};\nuse crate::messages::tool::common_functionality::graph_modification_utils;\nuse crate::messages::tool::tool_messages::tool_prelude::NumberInputMode;\nuse deserialization::deserialize_node_persistent_metadata;\nuse glam::{DAffine2, DVec2, IVec2};\nuse graph_craft::Type;\nuse graph_craft::document::value::TaggedValue;\nuse graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, OldDocumentNodeImplementation, OldNodeNetwork};\nuse graphene_std::ContextDependencies;\nuse graphene_std::math::quad::Quad;\nuse graphene_std::subpath::Subpath;\nuse graphene_std::transform::Footprint;\nuse graphene_std::vector::click_target::{ClickTarget, ClickTargetType};\nuse graphene_std::vector::{PointId, Vector, VectorModificationType};\nuse kurbo::BezPath;\nuse memo_network::MemoNetwork;\nuse serde_json::{Value, json};\nuse std::collections::{HashMap, HashSet, VecDeque};\nuse std::hash::Hash;\nuse std::ops::Deref;\nuse std::sync::Arc;\n\n/// All network modifications should be done through this API, so the fields cannot be public. However, all fields within this struct can be public since it it not possible to have a public mutable reference.\n#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]\npub struct NodeNetworkInterface {\n\t/// The node graph that generates this document's artwork. It recursively stores its sub-graphs, so this root graph is the whole snapshot of the document content.\n\t/// A public mutable reference should never be created. It should only be mutated through custom setters which perform the necessary side effects to keep network_metadata in sync\n\tnetwork: MemoNetwork,\n\t/// Stores all editor information for a NodeNetwork. Should automatically kept in sync by the setter methods when changes to the document network are made.\n\tnetwork_metadata: NodeNetworkMetadata,\n\t// TODO: Wrap in TransientMetadata Option\n\t/// Stores the document network's structural topology. Should automatically kept in sync by the setter methods when changes to the document network are made.\n\t#[serde(skip)]\n\tdocument_metadata: DocumentMetadata,\n\t/// All input/output types based on the compiled network.\n\t#[serde(skip)]\n\tpub resolved_types: ResolvedDocumentNodeTypes,\n\t#[serde(skip)]\n\ttransaction_status: TransactionStatus,\n}\n\nimpl Clone for NodeNetworkInterface {\n\tfn clone(&self) -> Self {\n\t\tSelf {\n\t\t\tnetwork: self.network.clone(),\n\t\t\tnetwork_metadata: self.network_metadata.clone(),\n\t\t\tdocument_metadata: Default::default(),\n\t\t\tresolved_types: Default::default(),\n\t\t\ttransaction_status: TransactionStatus::Finished,\n\t\t}\n\t}\n}\n\nimpl PartialEq for NodeNetworkInterface {\n\tfn eq(&self, other: &Self) -> bool {\n\t\tself.network == other.network && self.network_metadata == other.network_metadata\n\t}\n}\n\nimpl NodeNetworkInterface {\n\t/// Add DocumentNodePath input to the PathModifyNode protonode\n\tpub fn migrate_path_modify_node(&mut self) {\n\t\tfix_network(self.document_network_mut());\n\t\tfn fix_network(network: &mut NodeNetwork) {\n\t\t\tfor node in network.nodes.values_mut() {\n\t\t\t\tif let Some(network) = node.implementation.get_network_mut() {\n\t\t\t\t\tfix_network(network);\n\t\t\t\t}\n\t\t\t\tif let DocumentNodeImplementation::ProtoNode(protonode) = &node.implementation\n\t\t\t\t\t&& protonode.as_str().contains(\"PathModifyNode\")\n\t\t\t\t\t&& node.inputs.len() < 3\n\t\t\t\t{\n\t\t\t\t\tnode.inputs.push(NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Public immutable getters for the network interface\nimpl NodeNetworkInterface {\n\t/// Gets the network of the root document\n\tpub fn document_network(&self) -> &NodeNetwork {\n\t\tself.network.network()\n\t}\n\tpub fn document_network_mut(&mut self) -> &mut NodeNetwork {\n\t\tself.network.network_mut()\n\t}\n\n\t/// Gets the nested network based on network_path\n\tpub fn nested_network(&self, network_path: &[NodeId]) -> Option<&NodeNetwork> {\n\t\tlet Some(network) = self.document_network().nested_network(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network with path {network_path:?} in NodeNetworkInterface::network\");\n\t\t\treturn None;\n\t\t};\n\t\tSome(network)\n\t}\n\n\tpub fn network_hash(&self) -> u64 {\n\t\tself.network.current_hash()\n\t}\n\n\t/// Get the specified document node in the nested network based on node_id and network_path\n\tpub fn document_node(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&DocumentNode> {\n\t\tlet network = self.nested_network(network_path)?;\n\t\tlet Some(node_metadata) = network.nodes.get(node_id) else {\n\t\t\tlog::error!(\"Could not get document node with id {node_id} in network {network_path:?}\");\n\t\t\treturn None;\n\t\t};\n\t\tSome(node_metadata)\n\t}\n\n\tpub fn node_metadata(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&DocumentNodeMetadata> {\n\t\tlet network_metadata = self.network_metadata(network_path)?;\n\t\tlet Some(node_metadata) = network_metadata.persistent_metadata.node_metadata.get(node_id) else {\n\t\t\tlog::error!(\"Could not get node metadata for node {node_id} in network {network_path:?}\");\n\t\t\treturn None;\n\t\t};\n\t\tSome(node_metadata)\n\t}\n\n\tpub fn document_network_metadata(&self) -> &NodeNetworkMetadata {\n\t\t&self.network_metadata\n\t}\n\n\t/// The network metadata should always exist for the current network\n\tpub fn network_metadata(&self, network_path: &[NodeId]) -> Option<&NodeNetworkMetadata> {\n\t\tlet Some(network_metadata) = self.network_metadata.nested_metadata(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata with path {network_path:?}\");\n\t\t\treturn None;\n\t\t};\n\t\tSome(network_metadata)\n\t}\n\n\tpub fn document_metadata(&self) -> &DocumentMetadata {\n\t\t&self.document_metadata\n\t}\n\n\tpub fn transaction_status(&self) -> TransactionStatus {\n\t\tself.transaction_status\n\t}\n\n\tpub fn selected_nodes(&self) -> SelectedNodes {\n\t\tself.selected_nodes_in_nested_network(&[]).unwrap_or_default()\n\t}\n\n\t/// Get the selected nodes for the network at the network_path\n\tpub fn selected_nodes_in_nested_network(&self, network_path: &[NodeId]) -> Option<SelectedNodes> {\n\t\tlet Some(network_metadata) = self.network_metadata(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata in selected_nodes\");\n\t\t\treturn None;\n\t\t};\n\n\t\tSome(\n\t\t\tnetwork_metadata\n\t\t\t\t.persistent_metadata\n\t\t\t\t.selection_undo_history\n\t\t\t\t.back()\n\t\t\t\t.cloned()\n\t\t\t\t.unwrap_or_default()\n\t\t\t\t.filtered_selected_nodes(|node_id| network_metadata.persistent_metadata.node_metadata.contains_key(node_id)),\n\t\t)\n\t}\n\n\t/// Get the network which the encapsulating node of the currently viewed network is part of. Will always be None in the document network.\n\tpub fn encapsulating_network_metadata(&self, network_path: &[NodeId]) -> Option<&NodeNetworkMetadata> {\n\t\tlet mut encapsulating_path = network_path.to_vec();\n\t\tencapsulating_path.pop()?;\n\t\tlet Some(parent_metadata) = self.network_metadata(&encapsulating_path) else {\n\t\t\tlog::error!(\"Could not get parent network in encapsulating_node_metadata\");\n\t\t\treturn None;\n\t\t};\n\t\tSome(parent_metadata)\n\t}\n\n\t/// Get the node which encapsulates the currently viewed network. Will always be None in the document network.\n\tpub fn encapsulating_node(&self, network_path: &[NodeId]) -> Option<&DocumentNode> {\n\t\tlet mut encapsulating_path = network_path.to_vec();\n\t\tlet encapsulating_node_id = encapsulating_path.pop()?;\n\t\tlet Some(encapsulating_node) = self.document_node(&encapsulating_node_id, &encapsulating_path) else {\n\t\t\tlog::error!(\"Could not get encapsulating node in encapsulating_node\");\n\t\t\treturn None;\n\t\t};\n\t\tSome(encapsulating_node)\n\t}\n\n\t/// Get the node metadata for the node which encapsulates the currently viewed network. Will always be None in the document network.\n\tpub fn encapsulating_node_metadata(&self, network_path: &[NodeId]) -> Option<&DocumentNodeMetadata> {\n\t\tlet mut encapsulating_path = network_path.to_vec();\n\t\tlet encapsulating_node_id = encapsulating_path.pop()?;\n\t\tlet Some(parent_metadata) = self.network_metadata(&encapsulating_path) else {\n\t\t\tlog::error!(\"Could not get parent network in encapsulating_node_metadata\");\n\t\t\treturn None;\n\t\t};\n\t\tlet Some(encapsulating_node_metadata) = parent_metadata.persistent_metadata.node_metadata.get(&encapsulating_node_id) else {\n\t\t\tlog::error!(\"Could not get encapsulating node metadata in encapsulating_node_metadata\");\n\t\t\treturn None;\n\t\t};\n\t\tSome(encapsulating_node_metadata)\n\t}\n\n\t/// Returns the first downstream layer(inclusive) from a node. If the node is a layer, it will return itself.\n\tpub fn downstream_layer_for_chain_node(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option<NodeId> {\n\t\tlet mut id = *node_id;\n\t\twhile !self.is_layer(&id, network_path) {\n\t\t\tid = self.outward_wires(network_path)?.get(&OutputConnector::node(id, 0))?.first()?.node_id()?;\n\t\t}\n\t\tSome(id)\n\t}\n\n\t/// Returns all downstream layers (inclusive) from a node. If the node is a layer, it will return itself.\n\tpub fn downstream_layers(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Vec<NodeId> {\n\t\tlet mut stack = vec![*node_id];\n\t\tlet mut layers = Vec::new();\n\t\twhile let Some(current_node) = stack.pop() {\n\t\t\tif self.is_layer(&current_node, network_path) {\n\t\t\t\tlayers.push(current_node);\n\t\t\t} else {\n\t\t\t\tlet Some(outward_wires) = self.outward_wires(network_path).and_then(|outward_wires| outward_wires.get(&OutputConnector::node(current_node, 0))) else {\n\t\t\t\t\tlog::error!(\"Could not get outward wires in downstream_layer\");\n\t\t\t\t\treturn Vec::new();\n\t\t\t\t};\n\t\t\t\tstack.extend(outward_wires.iter().filter_map(|input_connector| input_connector.node_id()));\n\t\t\t}\n\t\t}\n\t\tlayers\n\t}\n\n\tpub fn chain_width(&self, node_id: &NodeId, network_path: &[NodeId]) -> u32 {\n\t\tif self.number_of_displayed_inputs(node_id, network_path) > 1 {\n\t\t\tlet mut last_chain_node_distance = 0u32;\n\t\t\t// Iterate upstream from the layer, and get the number of nodes distance to the last node with Position::Chain\n\t\t\tfor (index, node_id) in self\n\t\t\t\t.upstream_flow_back_from_nodes(vec![*node_id], network_path, FlowType::HorizontalPrimaryOutputFlow)\n\t\t\t\t.skip(1)\n\t\t\t\t.enumerate()\n\t\t\t\t.collect::<Vec<_>>()\n\t\t\t{\n\t\t\t\t// Check if the node is positioned as a chain\n\t\t\t\tif self.is_chain(&node_id, network_path) {\n\t\t\t\t\tlast_chain_node_distance = (index as u32) + 1;\n\t\t\t\t} else {\n\t\t\t\t\treturn last_chain_node_distance * 7 + 1;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlast_chain_node_distance * 7 + 1\n\t\t} else {\n\t\t\t// Layer with no inputs has no chain\n\t\t\t0\n\t\t}\n\t}\n\n\t/// Check if the specified node id is connected to the output\n\tpub fn connected_to_output(&self, target_node_id: &NodeId, network_path: &[NodeId]) -> bool {\n\t\tlet Some(network) = self.nested_network(network_path) else {\n\t\t\tlog::error!(\"Could not get network in connected_to_output\");\n\t\t\treturn false;\n\t\t};\n\t\t// If the node is the output then return true\n\t\tif network\n\t\t\t.exports\n\t\t\t.iter()\n\t\t\t.any(|export| if let NodeInput::Node { node_id, .. } = export { node_id == target_node_id } else { false })\n\t\t{\n\t\t\treturn true;\n\t\t}\n\n\t\t// Get the outputs\n\t\tlet mut stack = network\n\t\t\t.exports\n\t\t\t.iter()\n\t\t\t.filter_map(|output| if let NodeInput::Node { node_id, .. } = output { network.nodes.get(node_id) } else { None })\n\t\t\t.collect::<Vec<_>>();\n\t\tlet mut already_visited = HashSet::new();\n\t\talready_visited.extend(\n\t\t\tnetwork\n\t\t\t\t.exports\n\t\t\t\t.iter()\n\t\t\t\t.filter_map(|output| if let NodeInput::Node { node_id, .. } = output { Some(node_id) } else { None }),\n\t\t);\n\n\t\twhile let Some(node) = stack.pop() {\n\t\t\tfor input in &node.inputs {\n\t\t\t\tif let &NodeInput::Node { node_id: ref_id, .. } = input {\n\t\t\t\t\t// Skip if already viewed\n\t\t\t\t\tif already_visited.contains(&ref_id) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\t// If the target node is used as input then return true\n\t\t\t\t\tif ref_id == *target_node_id {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t\t// Add the referenced node to the stack\n\t\t\t\t\tlet Some(ref_node) = network.nodes.get(&ref_id) else {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t};\n\t\t\t\t\talready_visited.insert(ref_id);\n\t\t\t\t\tstack.push(ref_node);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfalse\n\t}\n\n\tpub fn number_of_imports(&self, network_path: &[NodeId]) -> usize {\n\t\t// TODO: Use network.import_types.len()\n\t\tif let Some(encapsulating_node) = self.encapsulating_node(network_path) {\n\t\t\tencapsulating_node.inputs.len()\n\t\t} else {\n\t\t\t0\n\t\t}\n\t}\n\n\tpub fn number_of_exports(&self, network_path: &[NodeId]) -> usize {\n\t\tif let Some(network) = self.nested_network(network_path) { network.exports.len() } else { 0 }\n\t}\n\n\tfn number_of_displayed_inputs(&self, node_id: &NodeId, network_path: &[NodeId]) -> usize {\n\t\tlet Some(node) = self.document_node(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get node {node_id} in number_of_displayed_inputs\");\n\t\t\treturn 0;\n\t\t};\n\t\tnode.inputs.iter().filter(|input| input.is_exposed()).count()\n\t}\n\n\tpub fn number_of_inputs(&self, node_id: &NodeId, network_path: &[NodeId]) -> usize {\n\t\tlet Some(node) = self.document_node(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get node {node_id} in number_of_inputs\");\n\t\t\treturn 0;\n\t\t};\n\t\tnode.inputs.len()\n\t}\n\n\tpub fn number_of_outputs(&self, node_id: &NodeId, network_path: &[NodeId]) -> usize {\n\t\tlet Some(implementation) = self.implementation(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get node {node_id} in number_of_outputs\");\n\t\t\treturn 0;\n\t\t};\n\t\tmatch &implementation {\n\t\t\tDocumentNodeImplementation::ProtoNode(_) => 1,\n\t\t\tDocumentNodeImplementation::Network(nested_network) => nested_network.exports.len(),\n\t\t\tDocumentNodeImplementation::Extract => 1,\n\t\t}\n\t}\n\n\t/// Creates a copy for each node by disconnecting nodes which are not connected to other copied nodes.\n\t/// Returns an iterator of all persistent metadata for a node and their ids\n\tpub fn copy_nodes<'a>(&'a mut self, new_ids: &'a HashMap<NodeId, NodeId>, network_path: &'a [NodeId]) -> impl Iterator<Item = (NodeId, NodeTemplate)> + 'a {\n\t\tlet mut new_nodes = new_ids\n\t\t\t.iter()\n\t\t\t.filter_map(|(node_id, &new)| {\n\t\t\t\tself.create_node_template(node_id, network_path).and_then(|mut node_template| {\n\t\t\t\t\tlet Some(outward_wires) = self.outward_wires(network_path) else {\n\t\t\t\t\t\tlog::error!(\"Could not get outward wires in copy_nodes\");\n\t\t\t\t\t\treturn None;\n\t\t\t\t\t};\n\t\t\t\t\t// TODO: Get downstream connections from all outputs\n\t\t\t\t\tlet mut downstream_connections = outward_wires.get(&OutputConnector::node(*node_id, 0)).map_or([].iter(), |outputs| outputs.iter());\n\t\t\t\t\tlet has_selected_node_downstream = downstream_connections.any(|input_connector| input_connector.node_id().is_some_and(|upstream_id| new_ids.keys().any(|key| *key == upstream_id)));\n\t\t\t\t\t// If the copied node does not have a downstream connection to another copied node, then set the position to absolute\n\t\t\t\t\tif !has_selected_node_downstream {\n\t\t\t\t\t\tlet Some(position) = self.position(node_id, network_path) else {\n\t\t\t\t\t\t\tlog::error!(\"Could not get position in create_node_template\");\n\t\t\t\t\t\t\treturn None;\n\t\t\t\t\t\t};\n\t\t\t\t\t\tmatch &mut node_template.persistent_node_metadata.node_type_metadata {\n\t\t\t\t\t\t\tNodeTypePersistentMetadata::Layer(layer_metadata) => layer_metadata.position = LayerPosition::Absolute(position),\n\t\t\t\t\t\t\tNodeTypePersistentMetadata::Node(node_metadata) => node_metadata.position = NodePosition::Absolute(position),\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\n\t\t\t\t\t// If a chain node does not have a selected downstream layer, then set the position to absolute\n\t\t\t\t\tlet downstream_layer = self.downstream_layer_for_chain_node(node_id, network_path);\n\t\t\t\t\tif downstream_layer.is_none_or(|downstream_layer| new_ids.keys().all(|key| *key != downstream_layer)) {\n\t\t\t\t\t\tlet Some(position) = self.position(node_id, network_path) else {\n\t\t\t\t\t\t\tlog::error!(\"Could not get position in create_node_template\");\n\t\t\t\t\t\t\treturn None;\n\t\t\t\t\t\t};\n\t\t\t\t\t\tnode_template.persistent_node_metadata.node_type_metadata = NodeTypePersistentMetadata::Node(NodePersistentMetadata {\n\t\t\t\t\t\t\tposition: NodePosition::Absolute(position),\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\t// Shift all absolute nodes 2 to the right and 2 down\n\t\t\t\t\t// TODO: Remove 2x2 offset and replace with layout system to find space for new node\n\t\t\t\t\tmatch &mut node_template.persistent_node_metadata.node_type_metadata {\n\t\t\t\t\t\tNodeTypePersistentMetadata::Layer(layer_metadata) => {\n\t\t\t\t\t\t\tif let LayerPosition::Absolute(position) = &mut layer_metadata.position {\n\t\t\t\t\t\t\t\t*position += IVec2::new(2, 2)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tNodeTypePersistentMetadata::Node(node_metadata) => {\n\t\t\t\t\t\t\tif let NodePosition::Absolute(position) = &mut node_metadata.position {\n\t\t\t\t\t\t\t\t*position += IVec2::new(2, 2)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tSome((new, *node_id, node_template))\n\t\t\t\t})\n\t\t\t})\n\t\t\t.collect::<Vec<_>>();\n\n\t\tfor old_id in new_nodes.iter().map(|(_, old_id, _)| *old_id).collect::<Vec<_>>() {\n\t\t\t// Try set all selected nodes upstream of a layer to be chain nodes\n\t\t\tif self.is_layer(&old_id, network_path) {\n\t\t\t\tfor valid_upstream_chain_node in self.valid_upstream_chain_nodes(&InputConnector::node(old_id, 1), network_path) {\n\t\t\t\t\tif let Some(node_template) = new_nodes.iter_mut().find_map(|(_, old_id, template)| (*old_id == valid_upstream_chain_node).then_some(template)) {\n\t\t\t\t\t\tmatch &mut node_template.persistent_node_metadata.node_type_metadata {\n\t\t\t\t\t\t\tNodeTypePersistentMetadata::Node(node_metadata) => node_metadata.position = NodePosition::Chain,\n\t\t\t\t\t\t\tNodeTypePersistentMetadata::Layer(_) => log::error!(\"Node cannot be a layer\"),\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tnew_nodes.into_iter().map(move |(new, node_id, node)| (new, self.map_ids(node, &node_id, new_ids, network_path)))\n\t}\n\n\t/// Create a node template from an existing node.\n\tpub fn create_node_template(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<NodeTemplate> {\n\t\tlet Some(node) = self.document_node(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get node {node_id} in create_node_template\");\n\t\t\treturn None;\n\t\t};\n\t\tlet Some(node_metadata) = self.node_metadata(node_id, network_path).cloned() else {\n\t\t\tlog::error!(\"Could not get node_metadata in create_node_template\");\n\t\t\treturn None;\n\t\t};\n\n\t\tSome(NodeTemplate {\n\t\t\tpersistent_node_metadata: node_metadata.persistent_metadata,\n\t\t\tdocument_node: node.clone(),\n\t\t})\n\t}\n\n\t/// Converts all node id inputs to a new id based on a HashMap.\n\t///\n\t/// If the node is not in the hashmap then a default input is found based on the compiled network, using the node_id passed as a parameter\n\tpub fn map_ids(&mut self, mut node_template: NodeTemplate, node_id: &NodeId, new_ids: &HashMap<NodeId, NodeId>, network_path: &[NodeId]) -> NodeTemplate {\n\t\tfor (input_index, input) in node_template.document_node.inputs.iter_mut().enumerate() {\n\t\t\tif let &mut NodeInput::Node { node_id: id, output_index } = input {\n\t\t\t\tif let Some(&new_id) = new_ids.get(&id) {\n\t\t\t\t\t*input = NodeInput::Node { node_id: new_id, output_index };\n\t\t\t\t} else {\n\t\t\t\t\t// Disconnect node input if it is not connected to another node in new_ids\n\t\t\t\t\tlet tagged_value = self.tagged_value_from_input(&InputConnector::node(*node_id, input_index), network_path);\n\t\t\t\t\t*input = NodeInput::value(tagged_value, true);\n\t\t\t\t}\n\t\t\t} else if let &mut NodeInput::Import { .. } = input {\n\t\t\t\t// Always disconnect network node input\n\t\t\t\tlet tagged_value = self.tagged_value_from_input(&InputConnector::node(*node_id, input_index), network_path);\n\t\t\t\t*input = NodeInput::value(tagged_value, true);\n\t\t\t}\n\t\t}\n\t\tnode_template\n\t}\n\n\tpub fn input_from_connector(&self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option<&NodeInput> {\n\t\tlet Some(network) = self.nested_network(network_path) else {\n\t\t\tlog::error!(\"Could not get network in input_from_connector\");\n\t\t\treturn None;\n\t\t};\n\t\tmatch input_connector {\n\t\t\tInputConnector::Node { node_id, input_index } => {\n\t\t\t\tlet Some(node) = network.nodes.get(node_id) else {\n\t\t\t\t\tlog::error!(\"Could not get node {node_id} in input_from_connector\");\n\t\t\t\t\treturn None;\n\t\t\t\t};\n\t\t\t\tnode.inputs.get(*input_index)\n\t\t\t}\n\t\t\tInputConnector::Export(export_index) => network.exports.get(*export_index),\n\t\t}\n\t}\n\n\tpub fn position(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option<IVec2> {\n\t\tlet top_left_position = self\n\t\t\t.node_click_targets(node_id, network_path)\n\t\t\t.and_then(|click_targets| click_targets.node_click_target.bounding_box())\n\t\t\t.map(|mut bounding_box| {\n\t\t\t\tif !self.is_layer(node_id, network_path) {\n\t\t\t\t\tbounding_box[0] -= DVec2::new(0., 12.);\n\t\t\t\t}\n\t\t\t\t(bounding_box[0] / 24.).as_ivec2()\n\t\t\t});\n\t\ttop_left_position.map(|position| {\n\t\t\tif self.is_layer(node_id, network_path) {\n\t\t\t\tposition + IVec2::new(self.chain_width(node_id, network_path) as i32, 0)\n\t\t\t} else {\n\t\t\t\tposition\n\t\t\t}\n\t\t})\n\t}\n\n\tpub fn frontend_imports(&mut self, network_path: &[NodeId]) -> Vec<Option<FrontendGraphOutput>> {\n\t\tmatch network_path.split_last() {\n\t\t\tSome((node_id, encapsulating_network_path)) => {\n\t\t\t\tlet Some(node) = self.document_node(node_id, encapsulating_network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get node {node_id} in network {encapsulating_network_path:?}\");\n\t\t\t\t\treturn Vec::new();\n\t\t\t\t};\n\t\t\t\tlet mut frontend_imports = (0..node.inputs.len())\n\t\t\t\t\t.map(|import_index| self.frontend_output_from_connector(&OutputConnector::Import(import_index), network_path))\n\t\t\t\t\t.collect::<Vec<_>>();\n\t\t\t\tif frontend_imports.is_empty() {\n\t\t\t\t\tfrontend_imports.push(None);\n\t\t\t\t}\n\t\t\t\tfrontend_imports\n\t\t\t}\n\t\t\t// In the document network display no imports\n\t\t\tNone => Vec::new(),\n\t\t}\n\t}\n\n\tpub fn frontend_exports(&mut self, network_path: &[NodeId]) -> Vec<Option<FrontendGraphInput>> {\n\t\tlet Some(network) = self.nested_network(network_path) else { return Vec::new() };\n\t\tlet mut frontend_exports = ((0..network.exports.len()).map(|export_index| self.frontend_input_from_connector(&InputConnector::Export(export_index), network_path))).collect::<Vec<_>>();\n\t\tif frontend_exports.is_empty() {\n\t\t\tfrontend_exports.push(None);\n\t\t}\n\t\tfrontend_exports\n\t}\n\n\tpub fn import_export_position(&mut self, network_path: &[NodeId]) -> Option<(IVec2, IVec2)> {\n\t\tlet Some(all_nodes_bounding_box) = self.all_nodes_bounding_box(network_path).cloned() else {\n\t\t\tlog::error!(\"Could not get all nodes bounding box in load_export_ports\");\n\t\t\treturn None;\n\t\t};\n\t\tlet Some(network) = self.nested_network(network_path) else {\n\t\t\tlog::error!(\"Could not get current network in load_export_ports\");\n\t\t\treturn None;\n\t\t};\n\n\t\tlet Some(network_metadata) = self.network_metadata(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata in load_export_ports\");\n\t\t\treturn None;\n\t\t};\n\t\tlet node_graph_to_viewport = network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport;\n\t\tlet target_viewport_top_left = DVec2::new(IMPORTS_TO_LEFT_EDGE_PIXEL_GAP as f64, IMPORTS_TO_TOP_EDGE_PIXEL_GAP as f64);\n\n\t\tlet node_graph_pixel_offset_top_left = node_graph_to_viewport.inverse().transform_point2(target_viewport_top_left);\n\n\t\t// A 5x5 grid offset from the top left corner\n\t\tlet node_graph_grid_space_offset_top_left = node_graph_to_viewport.inverse().transform_point2(DVec2::ZERO) + DVec2::new(5. * GRID_SIZE as f64, 4. * GRID_SIZE as f64);\n\n\t\t// The inner bound of the import is the highest/furthest left of the two offsets\n\t\tlet top_left_inner_bound = DVec2::new(\n\t\t\tnode_graph_pixel_offset_top_left.x.min(node_graph_grid_space_offset_top_left.x),\n\t\t\tnode_graph_pixel_offset_top_left.y.min(node_graph_grid_space_offset_top_left.y),\n\t\t);\n\n\t\tlet offset_from_top_left = if network\n\t\t\t.exports\n\t\t\t.first()\n\t\t\t.is_some_and(|export| export.as_node().is_some_and(|export_node| self.is_layer(&export_node, network_path)))\n\t\t{\n\t\t\tDVec2::new(-4. * GRID_SIZE as f64, -2. * GRID_SIZE as f64)\n\t\t} else {\n\t\t\tDVec2::new(-4. * GRID_SIZE as f64, 0.)\n\t\t};\n\n\t\tlet bounding_box_top_left = DVec2::new((all_nodes_bounding_box[0].x / 24. + 0.5).floor() * 24., (all_nodes_bounding_box[0].y / 24. + 0.5).floor() * 24.) + offset_from_top_left;\n\t\tlet import_top_left = DVec2::new(top_left_inner_bound.x.min(bounding_box_top_left.x), top_left_inner_bound.y.min(bounding_box_top_left.y));\n\t\tlet rounded_import_top_left = DVec2::new((import_top_left.x / 24.).round() * 24., (import_top_left.y / 24.).round() * 24.);\n\n\t\tlet viewport_width = network_metadata.persistent_metadata.navigation_metadata.node_graph_width;\n\n\t\tlet target_viewport_top_right = DVec2::new(viewport_width - EXPORTS_TO_RIGHT_EDGE_PIXEL_GAP as f64, EXPORTS_TO_TOP_EDGE_PIXEL_GAP as f64);\n\n\t\t// An offset from the right edge in viewport pixels\n\t\tlet node_graph_pixel_offset_top_right = node_graph_to_viewport.inverse().transform_point2(target_viewport_top_right);\n\n\t\t// A 5x5 grid offset from the right corner\n\t\tlet node_graph_grid_space_offset_top_right = node_graph_to_viewport.inverse().transform_point2(DVec2::new(viewport_width, 0.)) + DVec2::new(-5. * GRID_SIZE as f64, 4. * GRID_SIZE as f64);\n\n\t\t// The inner bound of the export is the highest/furthest right of the two offsets.\n\t\t// When zoomed out this keeps it a constant grid space away from the edge, but when zoomed in it prevents the exports from getting too far in\n\t\tlet top_right_inner_bound = DVec2::new(\n\t\t\tnode_graph_pixel_offset_top_right.x.max(node_graph_grid_space_offset_top_right.x),\n\t\t\tnode_graph_pixel_offset_top_right.y.min(node_graph_grid_space_offset_top_right.y),\n\t\t);\n\n\t\tlet offset_from_top_right = if network\n\t\t\t.exports\n\t\t\t.first()\n\t\t\t.is_some_and(|export| export.as_node().is_some_and(|export_node| self.is_layer(&export_node, network_path)))\n\t\t{\n\t\t\tDVec2::new(2. * GRID_SIZE as f64, -2. * GRID_SIZE as f64)\n\t\t} else {\n\t\t\tDVec2::new(4. * GRID_SIZE as f64, 0.)\n\t\t};\n\n\t\tlet mut bounding_box_top_right = DVec2::new((all_nodes_bounding_box[1].x / 24. + 0.5).floor() * 24., (all_nodes_bounding_box[0].y / 24. + 0.5).floor() * 24.);\n\t\tbounding_box_top_right += offset_from_top_right;\n\t\tlet export_top_right = DVec2::new(top_right_inner_bound.x.max(bounding_box_top_right.x), top_right_inner_bound.y.min(bounding_box_top_right.y));\n\t\tlet rounded_export_top_right = DVec2::new((export_top_right.x / 24.).round() * 24., (export_top_right.y / 24.).round() * 24.);\n\n\t\tSome((rounded_import_top_left.as_ivec2(), rounded_export_top_right.as_ivec2()))\n\t}\n\n\t/// Returns None if there is an error, it is a hidden primary export, or a hidden input\n\tpub fn frontend_input_from_connector(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option<FrontendGraphInput> {\n\t\t// Return None if it is a hidden input\n\t\tif self.input_from_connector(input_connector, network_path).is_some_and(|input| !input.is_exposed()) {\n\t\t\treturn None;\n\t\t}\n\t\tlet input_type = self.input_type(input_connector, network_path);\n\t\tlet data_type = input_type.displayed_type();\n\t\tlet resolved_type = input_type.resolved_type_node_string();\n\n\t\tlet connected_to = self\n\t\t\t.upstream_output_connector(input_connector, network_path)\n\t\t\t.map(|output_connector| match output_connector {\n\t\t\t\tOutputConnector::Node { node_id, output_index } => {\n\t\t\t\t\tlet name = self.display_name(&node_id, network_path);\n\t\t\t\t\tformat!(\"Connected to output #{output_index} of \\\"{name}\\\", ID: {node_id}.\")\n\t\t\t\t}\n\t\t\t\tOutputConnector::Import(import_index) => format!(\"Connected to import #{import_index}.\"),\n\t\t\t})\n\t\t\t.unwrap_or(\"Connected to nothing.\".to_string());\n\n\t\tlet (name, description) = match input_connector {\n\t\t\tInputConnector::Node { node_id, input_index } => self.displayed_input_name_and_description(node_id, *input_index, network_path),\n\t\t\tInputConnector::Export(export_index) => {\n\t\t\t\t// Get export name from parent node metadata input, which must match the number of exports.\n\t\t\t\t// Empty string means to use type, or \"Export + index\" if type is empty determined\n\t\t\t\tlet export_name = if network_path.is_empty() {\n\t\t\t\t\t\"Canvas\".to_string()\n\t\t\t\t} else {\n\t\t\t\t\tself.encapsulating_node_metadata(network_path)\n\t\t\t\t\t\t.and_then(|encapsulating_metadata| encapsulating_metadata.persistent_metadata.output_names.get(*export_index).cloned())\n\t\t\t\t\t\t.unwrap_or_default()\n\t\t\t\t};\n\n\t\t\t\tlet export_name = if !export_name.is_empty() {\n\t\t\t\t\texport_name\n\t\t\t\t} else if let Some(export_type_name) = input_type.compiled_nested_type().map(ToString::to_string) {\n\t\t\t\t\texport_type_name\n\t\t\t\t} else {\n\t\t\t\t\tformat!(\"Export #{}\", export_index)\n\t\t\t\t};\n\n\t\t\t\t(export_name, String::new())\n\t\t\t}\n\t\t};\n\n\t\tlet valid_types = self.potential_valid_input_types(input_connector, network_path).iter().map(ToString::to_string).collect::<Vec<_>>();\n\t\tlet valid_types = {\n\t\t\t// Dedupe while preserving order\n\t\t\tlet mut found = HashSet::new();\n\t\t\tvalid_types.into_iter().filter(|s| found.insert(s.clone())).collect::<Vec<_>>()\n\t\t};\n\n\t\tSome(FrontendGraphInput {\n\t\t\tdata_type,\n\t\t\tresolved_type,\n\t\t\tname,\n\t\t\tdescription,\n\t\t\tvalid_types,\n\t\t\tconnected_to,\n\t\t})\n\t}\n\n\t/// Returns None if there is an error, it is the document network, a hidden primary output or import\n\tpub fn frontend_output_from_connector(&mut self, output_connector: &OutputConnector, network_path: &[NodeId]) -> Option<FrontendGraphOutput> {\n\t\tlet output_type = self.output_type(output_connector, network_path);\n\t\tlet (name, description) = match output_connector {\n\t\t\tOutputConnector::Node { node_id, output_index } => {\n\t\t\t\t// Do not display the primary output port for a node if it is a network node with a hidden primary export\n\t\t\t\tif *output_index == 0 && self.hidden_primary_output(node_id, network_path) {\n\t\t\t\t\treturn None;\n\t\t\t\t};\n\t\t\t\t// Get the output name from the interior network export name\n\t\t\t\tlet node_metadata = self.node_metadata(node_id, network_path)?;\n\t\t\t\tlet output_name = node_metadata.persistent_metadata.output_names.get(*output_index).cloned().unwrap_or_default();\n\n\t\t\t\tlet output_name = if !output_name.is_empty() { output_name } else { output_type.resolved_type_node_string() };\n\t\t\t\t(output_name, String::new())\n\t\t\t}\n\t\t\tOutputConnector::Import(import_index) => {\n\t\t\t\t// Get the import name from the encapsulating node input metadata\n\t\t\t\tlet Some((encapsulating_node_id, encapsulating_path)) = network_path.split_last() else {\n\t\t\t\t\t// Return None if it is an import in the document network\n\t\t\t\t\treturn None;\n\t\t\t\t};\n\t\t\t\t// Return None if the primary input is hidden and this is the primary import\n\t\t\t\tif *import_index == 0 && self.hidden_primary_import(network_path) {\n\t\t\t\t\treturn None;\n\t\t\t\t};\n\t\t\t\tlet (import_name, description) = self.displayed_input_name_and_description(encapsulating_node_id, *import_index, encapsulating_path);\n\n\t\t\t\tlet import_name = if !import_name.is_empty() {\n\t\t\t\t\timport_name\n\t\t\t\t} else if let Some(import_type_name) = output_type.compiled_nested_type().map(ToString::to_string) {\n\t\t\t\t\timport_type_name\n\t\t\t\t} else {\n\t\t\t\t\tformat!(\"Import #{}\", import_index)\n\t\t\t\t};\n\n\t\t\t\t(import_name, description)\n\t\t\t}\n\t\t};\n\t\tlet data_type = output_type.displayed_type();\n\t\tlet resolved_type = output_type.resolved_type_node_string();\n\t\tlet mut connected_to = self\n\t\t\t.outward_wires(network_path)\n\t\t\t.and_then(|outward_wires| outward_wires.get(output_connector))\n\t\t\t.cloned()\n\t\t\t.unwrap_or_default()\n\t\t\t.iter()\n\t\t\t.map(|input| match input {\n\t\t\t\t&InputConnector::Node { node_id, input_index } => {\n\t\t\t\t\tlet name = self.display_name(&node_id, network_path);\n\t\t\t\t\tformat!(\"Connected to input #{input_index} of \\\"{name}\\\", ID: {node_id}.\")\n\t\t\t\t}\n\t\t\t\tInputConnector::Export(export_index) => format!(\"Connected to export #{export_index}.\"),\n\t\t\t})\n\t\t\t.collect::<Vec<_>>();\n\n\t\tif connected_to.is_empty() {\n\t\t\tconnected_to.push(\"Connected to nothing.\".to_string());\n\t\t}\n\n\t\tSome(FrontendGraphOutput {\n\t\t\tdata_type,\n\t\t\tresolved_type,\n\t\t\tname,\n\t\t\tdescription,\n\t\t\tconnected_to,\n\t\t})\n\t}\n\n\tpub fn height_from_click_target(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option<u32> {\n\t\tlet mut node_height: Option<u32> = self\n\t\t\t.node_click_targets(node_id, network_path)\n\t\t\t.and_then(|click_targets: &DocumentNodeClickTargets| click_targets.node_click_target.bounding_box())\n\t\t\t.map(|bounding_box| ((bounding_box[1].y - bounding_box[0].y) / 24.) as u32);\n\t\tif !self.is_layer(node_id, network_path) {\n\t\t\tnode_height = node_height.map(|height| height + 1);\n\t\t}\n\t\tnode_height\n\t}\n\n\t// All chain nodes and branches from the chain which are sole dependents of the layer\n\tpub fn upstream_nodes_below_layer(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> HashSet<NodeId> {\n\t\t// Every upstream node below layer must be a sole dependent\n\t\tlet mut upstream_nodes_below_layer = HashSet::new();\n\n\t\tlet mut potential_upstream_nodes = HashSet::new();\n\t\tfor chain_node in self\n\t\t\t.upstream_flow_back_from_nodes(vec![*node_id], network_path, FlowType::HorizontalFlow)\n\t\t\t.skip(1)\n\t\t\t.take_while(|node_id| self.is_chain(node_id, network_path))\n\t\t\t.collect::<Vec<_>>()\n\t\t{\n\t\t\tupstream_nodes_below_layer.insert(chain_node);\n\t\t\tlet Some(chain_node) = self.document_node(&chain_node, network_path) else {\n\t\t\t\tlog::error!(\"Could not get node {node_id} in upstream_nodes_below_layer\");\n\t\t\t\tcontinue;\n\t\t\t};\n\t\t\tpotential_upstream_nodes.extend(chain_node.inputs.iter().filter(|input| input.is_exposed()).skip(1).filter_map(|node_input| node_input.as_node()))\n\t\t}\n\n\t\t// Get the node feeding into the left input of the chain\n\t\tlet mut current_node_id = *node_id;\n\t\tloop {\n\t\t\tlet Some(current_node) = self.document_node(&current_node_id, network_path) else {\n\t\t\t\tlog::error!(\"Could not get node {node_id} in upstream_nodes_below_layer\");\n\t\t\t\tbreak;\n\t\t\t};\n\t\t\tif let Some(primary_node_id) = current_node\n\t\t\t\t.inputs\n\t\t\t\t.iter()\n\t\t\t\t.filter(|input| input.is_exposed())\n\t\t\t\t.nth(if self.is_layer(&current_node_id, network_path) { 1 } else { 0 })\n\t\t\t\t.and_then(|left_input| left_input.as_node())\n\t\t\t{\n\t\t\t\tif self.is_chain(&primary_node_id, network_path) {\n\t\t\t\t\tcurrent_node_id = primary_node_id;\n\t\t\t\t} else {\n\t\t\t\t\tpotential_upstream_nodes.insert(primary_node_id);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tfor potential_upstream_node in potential_upstream_nodes {\n\t\t\t// The upstream chain cannot be added if there is some node upstream from an input that is not a sole dependent\n\t\t\tlet mut upstream_chain_can_be_added = true;\n\t\t\t// Collect a vec of nodes that are sole dependents while iterating\n\t\t\tlet mut sole_dependents = HashSet::new();\n\n\t\t\tfor upstream_node_from_input in self\n\t\t\t\t.upstream_flow_back_from_nodes(vec![potential_upstream_node], network_path, FlowType::UpstreamFlow)\n\t\t\t\t.collect::<Vec<_>>()\n\t\t\t{\n\t\t\t\tlet number_of_outputs = self.number_of_outputs(&upstream_node_from_input, network_path);\n\n\t\t\t\t// A node is a sole dependent if all outputs are sole dependents, and there are no dead ends\n\t\t\t\tlet mut all_outputs_are_sole_dependents = true;\n\t\t\t\tlet mut dead_ends = 0;\n\n\t\t\t\tfor output_index in 0..number_of_outputs {\n\t\t\t\t\tlet downstream_connections = {\n\t\t\t\t\t\tlet Some(outward_wires) = self.outward_wires(network_path) else {\n\t\t\t\t\t\t\tlog::error!(\"Could not get outward wires in upstream_nodes_below_layer\");\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t};\n\t\t\t\t\t\toutward_wires.get(&OutputConnector::node(upstream_node_from_input, output_index)).cloned()\n\t\t\t\t\t};\n\t\t\t\t\tlet Some(downstream_connections) = downstream_connections else {\n\t\t\t\t\t\tlog::error!(\"Could not get outward wires in upstream_nodes_below_layer\");\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t};\n\t\t\t\t\tlet mut current_output_is_sole_dependent = true;\n\t\t\t\t\tlet mut stack = downstream_connections;\n\t\t\t\t\twhile let Some(current_downstream_connection) = stack.pop() {\n\t\t\t\t\t\t// Iterate downstream. If a sole dependent or chain_node_id is reached, then stop the iteration. If the exports is eventually reached, then it is not a sole dependent\n\t\t\t\t\t\tmatch &current_downstream_connection {\n\t\t\t\t\t\t\tInputConnector::Node {\n\t\t\t\t\t\t\t\tnode_id: downstream_node_id,\n\t\t\t\t\t\t\t\tinput_index,\n\t\t\t\t\t\t\t} => {\n\t\t\t\t\t\t\t\t// Stop iterating once the downstream node is the left input to the chain or a sole dependent\n\t\t\t\t\t\t\t\tif !(sole_dependents.contains(downstream_node_id) || downstream_node_id == node_id && *input_index == 1) {\n\t\t\t\t\t\t\t\t\t// Continue iterating downstream for the downstream node\n\t\t\t\t\t\t\t\t\tlet number_of_outputs = self.number_of_outputs(downstream_node_id, network_path);\n\t\t\t\t\t\t\t\t\tlet Some(outward_wires) = self.outward_wires(network_path) else {\n\t\t\t\t\t\t\t\t\t\tlog::error!(\"Could not get outward wires in upstream_nodes_below_layer\");\n\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\tlet mut has_downstream_connections = false;\n\t\t\t\t\t\t\t\t\tfor output_index in 0..number_of_outputs {\n\t\t\t\t\t\t\t\t\t\tlet Some(downstream_connections) = outward_wires.get(&OutputConnector::node(*downstream_node_id, output_index)) else {\n\t\t\t\t\t\t\t\t\t\t\tlog::error!(\"Could not get outward wires in upstream_nodes_below_layer\");\n\t\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\tif !downstream_connections.is_empty() {\n\t\t\t\t\t\t\t\t\t\t\thas_downstream_connections = true;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tstack.extend(downstream_connections.clone());\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tif !has_downstream_connections {\n\t\t\t\t\t\t\t\t\t\tdead_ends += 1;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tInputConnector::Export(_) => current_output_is_sole_dependent = false,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif !current_output_is_sole_dependent || dead_ends != 0 {\n\t\t\t\t\t\tall_outputs_are_sole_dependents = false;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif all_outputs_are_sole_dependents && dead_ends == 0 {\n\t\t\t\t\tsole_dependents.insert(upstream_node_from_input);\n\t\t\t\t} else {\n\t\t\t\t\tupstream_chain_can_be_added = false;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif upstream_chain_can_be_added {\n\t\t\t\tupstream_nodes_below_layer.extend(sole_dependents)\n\t\t\t}\n\t\t}\n\t\tupstream_nodes_below_layer\n\t}\n\n\tpub fn previewing(&self, network_path: &[NodeId]) -> Previewing {\n\t\tlet Some(network_metadata) = self.network_metadata(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata in previewing\");\n\t\t\treturn Previewing::No;\n\t\t};\n\t\tnetwork_metadata.persistent_metadata.previewing\n\t}\n\n\t/// Returns the root node (the node that the solid line is connect to), or None if no nodes are connected to the output\n\tpub fn root_node(&self, network_path: &[NodeId]) -> Option<RootNode> {\n\t\tlet Some(network) = self.nested_network(network_path) else {\n\t\t\tlog::error!(\"Could not get network in root_node\");\n\t\t\treturn None;\n\t\t};\n\t\tlet Some(network_metadata) = self.network_metadata(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata in root_node\");\n\t\t\treturn None;\n\t\t};\n\t\tmatch &network_metadata.persistent_metadata.previewing {\n\t\t\tPreviewing::Yes { root_node_to_restore } => *root_node_to_restore,\n\t\t\tPreviewing::No => network.exports.first().and_then(|export| {\n\t\t\t\tif let NodeInput::Node { node_id, output_index, .. } = export {\n\t\t\t\t\tSome(RootNode {\n\t\t\t\t\t\tnode_id: *node_id,\n\t\t\t\t\t\toutput_index: *output_index,\n\t\t\t\t\t})\n\t\t\t\t} else {\n\t\t\t\t\tNone\n\t\t\t\t}\n\t\t\t}),\n\t\t}\n\t}\n\n\tpub fn reference(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<DefinitionIdentifier> {\n\t\tlet Some(document_node) = self.document_node(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get document_node for node in reference: {node_id:?}\");\n\t\t\treturn None;\n\t\t};\n\t\tmatch &document_node.implementation {\n\t\t\tDocumentNodeImplementation::Network(_) => {\n\t\t\t\tlet Some(node_metadata) = self.node_metadata(node_id, network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get reference for node in reference: {node_id:?}\");\n\t\t\t\t\treturn None;\n\t\t\t\t};\n\t\t\t\tnode_metadata\n\t\t\t\t\t.persistent_metadata\n\t\t\t\t\t.network_metadata\n\t\t\t\t\t.as_ref()\n\t\t\t\t\t.expect(\"Network metadata must exist for network node in reference\")\n\t\t\t\t\t.persistent_metadata\n\t\t\t\t\t.reference\n\t\t\t\t\t.clone()\n\t\t\t\t\t.map(DefinitionIdentifier::Network)\n\t\t\t}\n\t\t\tDocumentNodeImplementation::ProtoNode(protonode_id) => Some(DefinitionIdentifier::ProtoNode(protonode_id.clone())),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tpub fn implementation(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&DocumentNodeImplementation> {\n\t\tlet Some(node) = self.document_node(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get implementation\");\n\t\t\treturn None;\n\t\t};\n\t\tSome(&node.implementation)\n\t}\n\n\tpub fn input_data(&self, node_id: &NodeId, index: usize, key: &str, network_path: &[NodeId]) -> Option<&Value> {\n\t\tlet metadata = self\n\t\t\t.node_metadata(node_id, network_path)\n\t\t\t.and_then(|node_metadata| node_metadata.persistent_metadata.input_metadata.get(index))?;\n\t\tmetadata.persistent_metadata.input_data.get(key)\n\t}\n\tpub fn persistent_input_metadata(&self, node_id: &NodeId, index: usize, network_path: &[NodeId]) -> Option<&InputPersistentMetadata> {\n\t\tlet metadata = self\n\t\t\t.node_metadata(node_id, network_path)\n\t\t\t.and_then(|node_metadata| node_metadata.persistent_metadata.input_metadata.get(index))?;\n\t\tSome(&metadata.persistent_metadata)\n\t}\n\n\tfn transient_input_metadata(&self, node_id: &NodeId, index: usize, network_path: &[NodeId]) -> Option<&InputTransientMetadata> {\n\t\tlet metadata = self\n\t\t\t.node_metadata(node_id, network_path)\n\t\t\t.and_then(|node_metadata| node_metadata.persistent_metadata.input_metadata.get(index))?;\n\t\tSome(&metadata.transient_metadata)\n\t}\n\n\tpub fn set_input_override(&mut self, node_id: &NodeId, index: usize, widget_override: Option<String>, network_path: &[NodeId]) {\n\t\tlet Some(metadata) = self\n\t\t\t.node_metadata_mut(node_id, network_path)\n\t\t\t.and_then(|node_metadata| node_metadata.persistent_metadata.input_metadata.get_mut(index))\n\t\telse {\n\t\t\tlog::error!(\"Could not get input metadata for {node_id} index {index} in set_input_override\");\n\t\t\treturn;\n\t\t};\n\t\tmetadata.persistent_metadata.widget_override = widget_override;\n\t}\n\n\t/// Returns the input name to display in the properties panel. If the name is empty then the type is used.\n\tpub fn displayed_input_name_and_description(&mut self, node_id: &NodeId, input_index: usize, network_path: &[NodeId]) -> (String, String) {\n\t\tlet Some(input_metadata) = self.persistent_input_metadata(node_id, input_index, network_path) else {\n\t\t\tlog::warn!(\"input metadata not found in displayed_input_name_and_description\");\n\t\t\treturn (String::new(), String::new());\n\t\t};\n\t\tlet description = input_metadata.input_description.to_string();\n\t\tlet name = if input_metadata.input_name.is_empty() {\n\t\t\tself.input_type(&InputConnector::node(*node_id, input_index), network_path).resolved_type_node_string()\n\t\t} else {\n\t\t\tinput_metadata.input_name.to_string()\n\t\t};\n\t\t(name, description)\n\t}\n\n\t/// Returns the display name of the node. If the display name is empty, it will return \"Untitled Node\" or \"Untitled Layer\" depending on the node type.\n\tpub fn display_name(&self, node_id: &NodeId, network_path: &[NodeId]) -> String {\n\t\tlet is_layer = self.is_layer(node_id, network_path);\n\n\t\tlet display_name = if let Some(node_metadata) = self.node_metadata(node_id, network_path) {\n\t\t\tnode_metadata.persistent_metadata.display_name.clone()\n\t\t} else {\n\t\t\tlog::error!(\"Could not get node_metadata in display_name\");\n\t\t\tString::new()\n\t\t};\n\n\t\tif display_name.is_empty() {\n\t\t\tif is_layer {\n\t\t\t\t\"Untitled Layer\".to_string()\n\t\t\t} else {\n\t\t\t\t// TODO: Have this displayed in italics in the UI\n\t\t\t\tself.implementation_name(node_id, network_path)\n\t\t\t}\n\t\t} else {\n\t\t\tdisplay_name\n\t\t}\n\t}\n\n\t/// The uneditable name in the Properties panel which represents the function name of the node implementation.\n\tpub fn implementation_name(&self, node_id: &NodeId, network_path: &[NodeId]) -> String {\n\t\tself.reference(node_id, network_path)\n\t\t\t.map(|identifier| identifier.implementation_name_from_identifier())\n\t\t\t.unwrap_or(\"Custom Node\".to_string())\n\t}\n\n\tpub fn is_locked(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {\n\t\tlet Some(node_metadata) = self.node_metadata(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get persistent node metadata in is_locked for node {node_id}\");\n\t\t\treturn false;\n\t\t};\n\t\tnode_metadata.persistent_metadata.locked\n\t}\n\n\tpub fn is_pinned(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {\n\t\tlet Some(node_metadata) = self.node_metadata(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get persistent node metadata in is_pinned for node {node_id}\");\n\t\t\treturn false;\n\t\t};\n\t\tnode_metadata.persistent_metadata.pinned\n\t}\n\n\tpub fn is_visible(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {\n\t\tlet Some(node) = self.document_node(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get node in is_visible\");\n\t\t\treturn false;\n\t\t};\n\t\tnode.visible\n\t}\n\n\tpub fn is_layer(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {\n\t\tlet Some(node_metadata) = self.node_metadata(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get nested node_metadata in is_layer\");\n\t\t\treturn false;\n\t\t};\n\t\tnode_metadata.persistent_metadata.is_layer()\n\t}\n\n\tpub fn primary_output_connected_to_layer(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> bool {\n\t\tlet Some(outward_wires) = self.outward_wires(network_path) else {\n\t\t\tlog::error!(\"Could not get outward_wires in primary_output_connected_to_layer\");\n\t\t\treturn false;\n\t\t};\n\t\tlet Some(downstream_connectors) = outward_wires.get(&OutputConnector::node(*node_id, 0)) else {\n\t\t\tlog::error!(\"Could not get downstream_connectors in primary_output_connected_to_layer\");\n\t\t\treturn false;\n\t\t};\n\n\t\tlet downstream_nodes = downstream_connectors\n\t\t\t.iter()\n\t\t\t.filter_map(|connector| connector.node_id().filter(|_| connector.input_index() == 0))\n\t\t\t.collect::<Vec<_>>();\n\t\tdownstream_nodes.iter().any(|node_id| self.is_layer(node_id, network_path))\n\t}\n\n\tpub fn primary_input_connected_to_layer(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> bool {\n\t\tself.input_from_connector(&InputConnector::node(*node_id, 0), network_path)\n\t\t\t.and_then(|input| input.as_node())\n\t\t\t.is_some_and(|node_id| self.is_layer(&node_id, network_path))\n\t}\n\n\tpub fn hidden_primary_export(&self, network_path: &[NodeId]) -> bool {\n\t\tlet Some((node_id, network_path)) = network_path.split_last() else {\n\t\t\t// The document network does not have a hidden primary export\n\t\t\treturn false;\n\t\t};\n\t\tself.hidden_primary_output(node_id, network_path)\n\t}\n\n\tpub fn hidden_primary_output(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {\n\t\tmatch self.implementation(node_id, network_path) {\n\t\t\tSome(DocumentNodeImplementation::Network(network)) => network.exports.first().is_none_or(|input| !input.is_exposed()),\n\t\t\t_ => false,\n\t\t}\n\t}\n\n\tpub fn hidden_primary_import(&self, network_path: &[NodeId]) -> bool {\n\t\tlet Some((encapsulating_node_id, encapsulating_path)) = network_path.split_last() else {\n\t\t\treturn false;\n\t\t};\n\t\tself.input_from_connector(&InputConnector::node(*encapsulating_node_id, 0), encapsulating_path)\n\t\t\t.is_some_and(|input| !input.is_exposed())\n\t}\n\n\tpub fn is_absolute(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {\n\t\tlet Some(node_metadata) = self.node_metadata(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get node_metadata in is_absolute\");\n\t\t\treturn false;\n\t\t};\n\t\tmatch &node_metadata.persistent_metadata.node_type_metadata {\n\t\t\tNodeTypePersistentMetadata::Layer(layer_metadata) => matches!(layer_metadata.position, LayerPosition::Absolute(_)),\n\t\t\tNodeTypePersistentMetadata::Node(node_metadata) => matches!(node_metadata.position, NodePosition::Absolute(_)),\n\t\t}\n\t}\n\n\tpub fn is_chain(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {\n\t\tlet Some(node_metadata) = self.node_metadata(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get node_metadata in is_chain\");\n\t\t\treturn false;\n\t\t};\n\t\tmatch &node_metadata.persistent_metadata.node_type_metadata {\n\t\t\tNodeTypePersistentMetadata::Node(node_metadata) => matches!(node_metadata.position, NodePosition::Chain),\n\t\t\t_ => false,\n\t\t}\n\t}\n\n\tpub fn is_stack(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {\n\t\tlet Some(node_metadata) = self.node_metadata(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get node_metadata in is_stack\");\n\t\t\treturn false;\n\t\t};\n\t\tmatch &node_metadata.persistent_metadata.node_type_metadata {\n\t\t\tNodeTypePersistentMetadata::Layer(layer_metadata) => matches!(layer_metadata.position, LayerPosition::Stack(_)),\n\t\t\t_ => false,\n\t\t}\n\t}\n\n\tpub fn is_artboard(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {\n\t\tself.reference(node_id, network_path)\n\t\t\t.is_some_and(|reference| reference == DefinitionIdentifier::Network(\"Artboard\".into()) && self.connected_to_output(node_id, &[]))\n\t}\n\n\tpub fn all_artboards(&self) -> HashSet<LayerNodeIdentifier> {\n\t\tself.document_network_metadata()\n\t\t\t.persistent_metadata\n\t\t\t.node_metadata\n\t\t\t.iter()\n\t\t\t.filter_map(|(node_id, node_metadata)| {\n\t\t\t\tif node_metadata.persistent_metadata.network_metadata.as_ref().is_some_and(|network_metadata| {\n\t\t\t\t\tnetwork_metadata\n\t\t\t\t\t\t.persistent_metadata\n\t\t\t\t\t\t.reference\n\t\t\t\t\t\t.as_ref()\n\t\t\t\t\t\t.is_some_and(|reference| reference == \"Artboard\" && self.connected_to_output(node_id, &[]) && self.is_layer(node_id, &[]))\n\t\t\t\t}) {\n\t\t\t\t\tSome(LayerNodeIdentifier::new(*node_id, self))\n\t\t\t\t} else {\n\t\t\t\t\tNone\n\t\t\t\t}\n\t\t\t})\n\t\t\t.collect()\n\t}\n\n\t/// Folders sorted from most nested to least nested\n\tpub fn folders_sorted_by_most_nested(&self, network_path: &[NodeId]) -> Vec<LayerNodeIdentifier> {\n\t\tif !network_path.is_empty() {\n\t\t\tlog::error!(\"Currently can only get deepest common ancestor in the document network\");\n\t\t\treturn Vec::new();\n\t\t}\n\t\tlet Some(selected_nodes) = self.selected_nodes_in_nested_network(network_path) else {\n\t\t\tlog::error!(\"Could not get selected nodes in deepest_common_ancestor\");\n\t\t\treturn Vec::new();\n\t\t};\n\t\tlet mut folders: Vec<_> = selected_nodes\n\t\t\t.selected_layers(self.document_metadata())\n\t\t\t.filter(|layer| layer.has_children(self.document_metadata()))\n\t\t\t.collect();\n\t\tfolders.sort_by_cached_key(|a| std::cmp::Reverse(a.ancestors(self.document_metadata()).count()));\n\t\tfolders\n\t}\n\n\t/// Calculates the document bounds in document space\n\tpub fn document_bounds_document_space(&self, include_artboards: bool) -> Option<[DVec2; 2]> {\n\t\tself.document_metadata\n\t\t\t.all_layers()\n\t\t\t.filter(|layer| include_artboards || !self.is_artboard(&layer.to_node(), &[]))\n\t\t\t.filter_map(|layer| {\n\t\t\t\tif !self.is_artboard(&layer.to_node(), &[])\n\t\t\t\t\t&& let Some(artboard_node_identifier) = layer\n\t\t\t\t\t\t.ancestors(self.document_metadata())\n\t\t\t\t\t\t.find(|ancestor| *ancestor != LayerNodeIdentifier::ROOT_PARENT && self.is_artboard(&ancestor.to_node(), &[]))\n\t\t\t\t{\n\t\t\t\t\tlet artboard = self.document_node(&artboard_node_identifier.to_node(), &[]);\n\t\t\t\t\tlet clip_input = artboard.unwrap().inputs.get(5).unwrap();\n\t\t\t\t\tif let NodeInput::Value { tagged_value, .. } = clip_input\n\t\t\t\t\t\t&& tagged_value.clone().deref() == &TaggedValue::Bool(true)\n\t\t\t\t\t{\n\t\t\t\t\t\treturn Some(Quad::clip(\n\t\t\t\t\t\t\tself.document_metadata.bounding_box_document(layer).unwrap_or_default(),\n\t\t\t\t\t\t\tself.document_metadata.bounding_box_document(artboard_node_identifier).unwrap_or_default(),\n\t\t\t\t\t\t));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tself.document_metadata.bounding_box_document(layer)\n\t\t\t})\n\t\t\t.reduce(Quad::combine_bounds)\n\t}\n\n\tpub fn document_bounds_viewport_space(&self, include_artboards: bool) -> Option<[DVec2; 2]> {\n\t\tlet [min, max] = self.document_bounds_document_space(include_artboards)?;\n\t\tlet quad = Quad::from_box([min, max]);\n\t\tlet transformed = self.document_metadata.document_to_viewport * quad;\n\t\tSome(transformed.bounding_box())\n\t}\n\n\t/// Calculates the selected layer bounds in document space\n\tpub fn selected_bounds_document_space(&self, include_artboards: bool, network_path: &[NodeId]) -> Option<[DVec2; 2]> {\n\t\tlet Some(selected_nodes) = self.selected_nodes_in_nested_network(network_path) else {\n\t\t\tlog::error!(\"Could not get selected nodes in shallowest_unique_layers\");\n\t\t\treturn None;\n\t\t};\n\t\tselected_nodes\n\t\t\t.selected_layers(&self.document_metadata)\n\t\t\t.filter(|&layer| include_artboards || !self.is_artboard(&layer.to_node(), &[]))\n\t\t\t.filter_map(|layer| self.document_metadata.bounding_box_document(layer))\n\t\t\t.reduce(Quad::combine_bounds)\n\t}\n\n\t/// Layers excluding ones that are children of other layers in the list.\n\t// TODO: Cache this\n\tpub fn shallowest_unique_layers(&self, network_path: &[NodeId]) -> impl Iterator<Item = LayerNodeIdentifier> + use<> {\n\t\tlet mut sorted_layers = if let Some(selected_nodes) = self.selected_nodes_in_nested_network(network_path) {\n\t\t\tselected_nodes\n\t\t\t\t.selected_layers(self.document_metadata())\n\t\t\t\t.map(|layer| {\n\t\t\t\t\tlet mut layer_path = layer.ancestors(&self.document_metadata).collect::<Vec<_>>();\n\t\t\t\t\tlayer_path.reverse();\n\t\t\t\t\tlayer_path\n\t\t\t\t})\n\t\t\t\t.collect::<Vec<_>>()\n\t\t} else {\n\t\t\tlog::error!(\"Could not get selected nodes in shallowest_unique_layers\");\n\t\t\tVec::new()\n\t\t};\n\n\t\t// Sorting here creates groups of similar UUID paths\n\t\tsorted_layers.sort();\n\t\tsorted_layers.dedup_by(|a, b| a.starts_with(b));\n\t\tsorted_layers.into_iter().map(|mut path| {\n\t\t\tlet layer = path.pop().expect(\"Path should not be empty\");\n\t\t\tassert!(\n\t\t\t\tlayer != LayerNodeIdentifier::ROOT_PARENT,\n\t\t\t\t\"The root parent cannot be selected, so it cannot be a shallowest selected layer\"\n\t\t\t);\n\t\t\tlayer\n\t\t})\n\t}\n\n\tpub fn shallowest_unique_layers_sorted(&self, network_path: &[NodeId]) -> Vec<LayerNodeIdentifier> {\n\t\tlet all_layers_to_group = self.shallowest_unique_layers(network_path).collect::<Vec<_>>();\n\t\t// Ensure nodes are grouped in the correct order\n\t\tlet mut all_layers_to_group_sorted = Vec::new();\n\t\tfor descendant in LayerNodeIdentifier::ROOT_PARENT.descendants(self.document_metadata()) {\n\t\t\tif all_layers_to_group.contains(&descendant) {\n\t\t\t\tall_layers_to_group_sorted.push(descendant);\n\t\t\t};\n\t\t}\n\t\tall_layers_to_group_sorted\n\t}\n\n\t/// Ancestor that is shared by all layers and that is deepest (more nested). Default may be the root. Skips selected non-folder, non-artboard layers\n\tpub fn deepest_common_ancestor(&self, selected_nodes: &SelectedNodes, network_path: &[NodeId], include_self: bool) -> Option<LayerNodeIdentifier> {\n\t\tif !network_path.is_empty() {\n\t\t\tlog::error!(\"Currently can only get deepest common ancestor in the document network\");\n\t\t\treturn None;\n\t\t}\n\t\tselected_nodes\n\t\t\t.selected_layers(&self.document_metadata)\n\t\t\t.map(|layer| {\n\t\t\t\tlet mut layer_path = layer.ancestors(&self.document_metadata).collect::<Vec<_>>();\n\t\t\t\tlayer_path.reverse();\n\t\t\t\tif !include_self || !self.is_artboard(&layer.to_node(), network_path) {\n\t\t\t\t\tlayer_path.pop();\n\t\t\t\t}\n\n\t\t\t\tlayer_path\n\t\t\t})\n\t\t\t.reduce(|mut a, b| {\n\t\t\t\ta.truncate(a.iter().zip(b.iter()).position(|(&a, &b)| a != b).unwrap_or_else(|| a.len().min(b.len())));\n\t\t\t\ta\n\t\t\t})\n\t\t\t.and_then(|layer| layer.last().copied())\n\t}\n\n\t/// Gives an iterator to all nodes connected to the given nodes by all inputs (primary or primary + secondary depending on `only_follow_primary` choice), traversing backwards upstream starting from the given node's inputs.\n\tpub fn upstream_flow_back_from_nodes<'a>(&'a self, mut node_ids: Vec<NodeId>, network_path: &'a [NodeId], mut flow_type: FlowType) -> impl Iterator<Item = NodeId> + 'a {\n\t\tlet (Some(network), Some(network_metadata)) = (self.nested_network(network_path), self.network_metadata(network_path)) else {\n\t\t\tlog::error!(\"Could not get network or network_metadata in upstream_flow_back_from_nodes\");\n\t\t\treturn FlowIter {\n\t\t\t\tstack: Vec::new(),\n\t\t\t\tnetwork: self.document_network(),\n\t\t\t\tnetwork_metadata: &self.network_metadata,\n\t\t\t\tflow_type: FlowType::UpstreamFlow,\n\t\t\t};\n\t\t};\n\t\tif matches!(flow_type, FlowType::LayerChildrenUpstreamFlow) {\n\t\t\tnode_ids = node_ids\n\t\t\t\t.iter()\n\t\t\t\t.filter_map(move |node_id| {\n\t\t\t\t\tif self.is_layer(node_id, network_path) {\n\t\t\t\t\t\tnetwork.nodes.get(node_id).and_then(|node| node.inputs.get(1)).and_then(|input| input.as_node())\n\t\t\t\t\t} else {\n\t\t\t\t\t\tSome(*node_id)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.collect::<Vec<_>>();\n\t\t\tflow_type = FlowType::UpstreamFlow;\n\t\t};\n\t\tFlowIter {\n\t\t\tstack: node_ids,\n\t\t\tnetwork,\n\t\t\tnetwork_metadata,\n\t\t\tflow_type,\n\t\t}\n\t}\n\n\tpub fn upstream_output_connector(&self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option<OutputConnector> {\n\t\tlet input = self.input_from_connector(input_connector, network_path);\n\t\tinput.and_then(|input| match input {\n\t\t\tNodeInput::Node { node_id, output_index, .. } => Some(OutputConnector::node(*node_id, *output_index)),\n\t\t\tNodeInput::Import { import_index, .. } => Some(OutputConnector::Import(*import_index)),\n\t\t\t_ => None,\n\t\t})\n\t}\n\n\t/// In the network `X -> Y -> Z`, `is_node_upstream_of_another_by_primary_flow(Z, X)` returns true.\n\tpub fn is_node_upstream_of_another_by_horizontal_flow(&self, node: NodeId, network_path: &[NodeId], potentially_upstream_node: NodeId) -> bool {\n\t\tself.upstream_flow_back_from_nodes(vec![node], network_path, FlowType::HorizontalFlow)\n\t\t\t.any(|id| id == potentially_upstream_node)\n\t}\n\n\tpub fn from_old_network(old_network: OldNodeNetwork) -> Self {\n\t\tlet mut node_network = NodeNetwork::default();\n\t\tlet mut network_metadata = NodeNetworkMetadata::default();\n\t\tlet mut stack = vec![(Vec::new(), old_network)];\n\t\twhile let Some((network_path, old_network)) = stack.pop() {\n\t\t\tlet Some(nested_network) = node_network.nested_network_mut(&network_path) else {\n\t\t\t\tlog::error!(\"Could not get nested network in from_old_network\");\n\t\t\t\tcontinue;\n\t\t\t};\n\t\t\tnested_network.exports = old_network.exports;\n\t\t\tnested_network.scope_injections = old_network.scope_injections.into_iter().collect();\n\t\t\tlet Some(nested_network_metadata) = network_metadata.nested_metadata_mut(&network_path) else {\n\t\t\t\tlog::error!(\"Could not get nested network in from_old_network\");\n\t\t\t\tcontinue;\n\t\t\t};\n\t\t\tnested_network_metadata.persistent_metadata.previewing = Previewing::No;\n\t\t\tfor (node_id, old_node) in old_network.nodes {\n\t\t\t\tlet mut node = DocumentNode::default();\n\t\t\t\tlet mut node_metadata = DocumentNodeMetadata::default();\n\n\t\t\t\tnode.inputs = old_node.inputs;\n\t\t\t\tnode.call_argument = old_node.manual_composition.unwrap();\n\t\t\t\tnode.visible = old_node.visible;\n\t\t\t\tnode.skip_deduplication = old_node.skip_deduplication;\n\t\t\t\tnode.original_location = old_node.original_location;\n\t\t\t\tnode_metadata.persistent_metadata.display_name = old_node.alias;\n\t\t\t\tnode_metadata.persistent_metadata.locked = old_node.locked;\n\t\t\t\tnode_metadata.persistent_metadata.node_type_metadata = if old_node.is_layer {\n\t\t\t\t\tNodeTypePersistentMetadata::Layer(LayerPersistentMetadata {\n\t\t\t\t\t\tposition: LayerPosition::Absolute(old_node.metadata.position),\n\t\t\t\t\t\towned_nodes: TransientMetadata::Unloaded,\n\t\t\t\t\t})\n\t\t\t\t} else {\n\t\t\t\t\tNodeTypePersistentMetadata::Node(NodePersistentMetadata {\n\t\t\t\t\t\tposition: NodePosition::Absolute(old_node.metadata.position),\n\t\t\t\t\t})\n\t\t\t\t};\n\n\t\t\t\tmatch old_node.implementation {\n\t\t\t\t\tOldDocumentNodeImplementation::ProtoNode(protonode) => {\n\t\t\t\t\t\tnode.implementation = DocumentNodeImplementation::ProtoNode(protonode);\n\t\t\t\t\t}\n\t\t\t\t\tOldDocumentNodeImplementation::Network(old_network) => {\n\t\t\t\t\t\tnode.implementation = DocumentNodeImplementation::Network(NodeNetwork::default());\n\t\t\t\t\t\tnode_metadata.persistent_metadata.network_metadata = Some(NodeNetworkMetadata::default());\n\t\t\t\t\t\tlet mut nested_path = network_path.clone();\n\t\t\t\t\t\tnested_path.push(node_id);\n\t\t\t\t\t\tstack.push((nested_path, old_network));\n\t\t\t\t\t}\n\t\t\t\t\tOldDocumentNodeImplementation::Extract => {\n\t\t\t\t\t\tnode.implementation = DocumentNodeImplementation::Extract;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tnested_network.nodes.insert(node_id, node);\n\t\t\t\tnested_network_metadata.persistent_metadata.node_metadata.insert(node_id, node_metadata);\n\t\t\t}\n\t\t}\n\t\tSelf {\n\t\t\tnetwork: MemoNetwork::new(node_network),\n\t\t\tnetwork_metadata,\n\t\t\tdocument_metadata: DocumentMetadata::default(),\n\t\t\tresolved_types: ResolvedDocumentNodeTypes::default(),\n\t\t\ttransaction_status: TransactionStatus::Finished,\n\t\t}\n\t}\n}\n\n// Private mutable getters for use within the network interface\nimpl NodeNetworkInterface {\n\tfn network_mut(&mut self, network_path: &[NodeId]) -> Option<&mut NodeNetwork> {\n\t\tself.document_network_mut().nested_network_mut(network_path)\n\t}\n\n\tfn network_metadata_mut(&mut self, network_path: &[NodeId]) -> Option<&mut NodeNetworkMetadata> {\n\t\tself.network_metadata.nested_metadata_mut(network_path)\n\t}\n\n\tfn node_metadata_mut(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&mut DocumentNodeMetadata> {\n\t\tlet Some(network_metadata) = self.network_metadata_mut(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata\");\n\t\t\treturn None;\n\t\t};\n\t\tlet Some(node_metadata) = network_metadata.persistent_metadata.node_metadata.get_mut(node_id) else {\n\t\t\tlog::error!(\"Could not get nested node_metadata for node {node_id} in network {network_path:?}\");\n\t\t\treturn None;\n\t\t};\n\t\tSome(node_metadata)\n\t}\n\n\t/// Mutably get the network which the encapsulating node of the currently viewed network is part of. Will always be None in the document network.\n\tfn encapsulating_network_metadata_mut(&mut self, network_path: &[NodeId]) -> Option<&mut NodeNetworkMetadata> {\n\t\tlet mut encapsulating_path = network_path.to_vec();\n\t\tencapsulating_path.pop()?;\n\t\tlet Some(parent_metadata) = self.network_metadata_mut(&encapsulating_path) else {\n\t\t\tlog::error!(\"Could not get parent network in encapsulating_node_metadata\");\n\t\t\treturn None;\n\t\t};\n\t\tSome(parent_metadata)\n\t}\n\n\t// /// Mutably get the node which encapsulates the currently viewed network. Will always be None in the document network.\n\t// fn encapsulating_node_mut(&mut self, network_path: &[NodeId]) -> Option<&mut DocumentNode> {\n\t// \tlet mut encapsulating_path = network_path.to_vec();\n\t// \tlet encapsulating_node_id = encapsulating_path.pop()?;\n\t// \tlet Some(parent_network) = self.network_mut(&encapsulating_path) else {\n\t// \t\tlog::error!(\"Could not get parent network in encapsulating_node_mut\");\n\t// \t\treturn None;\n\t// \t};\n\t// \tlet Some(encapsulating_node) = parent_network.nodes.mut(&encapsulating_node_id) else {\n\t// \t\tlog::error!(\"Could not get encapsulating node in encapsulating_node_mut\");\n\t// \t\treturn None;\n\t// \t};\n\t// \tSome(encapsulating_node)\n\t// }\n\n\t/// Get the node metadata for the node which encapsulates the currently viewed network. Will always be None in the document network.\n\tfn encapsulating_node_metadata_mut(&mut self, network_path: &[NodeId]) -> Option<&mut DocumentNodeMetadata> {\n\t\tlet mut encapsulating_path = network_path.to_vec();\n\t\tlet encapsulating_node_id = encapsulating_path.pop()?;\n\t\tlet Some(parent_metadata) = self.network_metadata_mut(&encapsulating_path) else {\n\t\t\tlog::error!(\"Could not get parent network in encapsulating_node_metadata\");\n\t\t\treturn None;\n\t\t};\n\t\tlet Some(encapsulating_node_metadata) = parent_metadata.persistent_metadata.node_metadata.get_mut(&encapsulating_node_id) else {\n\t\t\tlog::error!(\"Could not get encapsulating node metadata in encapsulating_node_metadata\");\n\t\t\treturn None;\n\t\t};\n\t\tSome(encapsulating_node_metadata)\n\t}\n}\n\n// Public mutable getters for data that involves transient network metadata\n// Mutable methods never recalculate the transient metadata, they only unload it. Loading metadata should only be done by the getter.\nimpl NodeNetworkInterface {\n\tpub fn start_transaction(&mut self) {\n\t\tself.transaction_status = TransactionStatus::Started;\n\t}\n\n\tpub fn transaction_modified(&mut self) {\n\t\tif self.transaction_status == TransactionStatus::Started {\n\t\t\tself.transaction_status = TransactionStatus::Modified;\n\t\t}\n\t}\n\n\tpub fn finish_transaction(&mut self) {\n\t\tself.transaction_status = TransactionStatus::Finished;\n\t}\n\n\t/// Mutably get the selected nodes for the network at the network_path. Every time they are mutated, the transient metadata for the top of the stack gets unloaded.\n\tpub fn selected_nodes_mut(&mut self, network_path: &[NodeId]) -> Option<&mut SelectedNodes> {\n\t\tlet (last_selection_state, prev_state, is_selection_empty) = {\n\t\t\tlet network_metadata = self.network_metadata(network_path)?;\n\t\t\tlet history = &network_metadata.persistent_metadata.selection_undo_history;\n\t\t\tlet current = history.back().cloned().unwrap_or_default();\n\t\t\tlet previous = history.iter().rev().nth(1).cloned();\n\t\t\tlet empty = current.selected_layers_except_artboards(self).next().is_none();\n\t\t\t(current, previous, empty)\n\t\t};\n\t\tself.unload_stack_dependents(network_path);\n\n\t\tlet Some(network_metadata) = self.network_metadata_mut(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata in selected_nodes\");\n\t\t\treturn None;\n\t\t};\n\n\t\t// Initialize default value if selection_undo_history is empty\n\t\tif network_metadata.persistent_metadata.selection_undo_history.is_empty() {\n\t\t\tnetwork_metadata.persistent_metadata.selection_undo_history.push_back(SelectedNodes::default());\n\t\t}\n\n\t\t// Update history only if selection is non-empty/does not contain only artboards\n\t\tif !is_selection_empty && prev_state.as_ref() != Some(&last_selection_state) {\n\t\t\tnetwork_metadata.persistent_metadata.selection_undo_history.push_back(last_selection_state);\n\t\t\tnetwork_metadata.persistent_metadata.selection_redo_history.clear();\n\n\t\t\tif network_metadata.persistent_metadata.selection_undo_history.len() > crate::consts::MAX_UNDO_HISTORY_LEN {\n\t\t\t\tnetwork_metadata.persistent_metadata.selection_undo_history.pop_front();\n\t\t\t}\n\t\t}\n\n\t\tnetwork_metadata.persistent_metadata.selection_undo_history.back_mut()\n\t}\n\n\tpub fn selection_step_back(&mut self, network_path: &[NodeId]) {\n\t\tlet Some(network_metadata) = self.network_metadata_mut(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata in selection_step_back\");\n\t\t\treturn;\n\t\t};\n\n\t\tif let Some(selection_state) = network_metadata.persistent_metadata.selection_undo_history.pop_back() {\n\t\t\tnetwork_metadata.persistent_metadata.selection_redo_history.push_front(selection_state);\n\t\t}\n\t}\n\n\tpub fn selection_step_forward(&mut self, network_path: &[NodeId]) {\n\t\tlet Some(network_metadata) = self.network_metadata_mut(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata in selection_step_forward\");\n\t\t\treturn;\n\t\t};\n\n\t\tif let Some(selection_state) = network_metadata.persistent_metadata.selection_redo_history.pop_front() {\n\t\t\tnetwork_metadata.persistent_metadata.selection_undo_history.push_back(selection_state);\n\t\t}\n\t}\n\n\tfn stack_dependents(&mut self, network_path: &[NodeId]) -> Option<&HashMap<NodeId, LayerOwner>> {\n\t\tself.try_load_stack_dependents(network_path);\n\t\tself.try_get_stack_dependents(network_path)\n\t}\n\n\tfn try_load_stack_dependents(&mut self, network_path: &[NodeId]) {\n\t\tlet Some(network_metadata) = self.network_metadata(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata in stack_dependents\");\n\t\t\treturn;\n\t\t};\n\n\t\tif !network_metadata.transient_metadata.stack_dependents.is_loaded() {\n\t\t\tself.load_stack_dependents(network_path);\n\t\t}\n\t}\n\n\tfn try_get_stack_dependents(&self, network_path: &[NodeId]) -> Option<&HashMap<NodeId, LayerOwner>> {\n\t\tlet Some(network_metadata) = self.network_metadata(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata in try_get_stack_dependents\");\n\t\t\treturn None;\n\t\t};\n\t\tlet TransientMetadata::Loaded(stack_dependents) = &network_metadata.transient_metadata.stack_dependents else {\n\t\t\tlog::error!(\"could not load stack_dependents\");\n\t\t\treturn None;\n\t\t};\n\t\tSome(stack_dependents)\n\t}\n\n\t// This function always has to be in sync with the selected nodes.\n\tfn load_stack_dependents(&mut self, network_path: &[NodeId]) {\n\t\tlet Some(selected_nodes) = self.selected_nodes_in_nested_network(network_path) else {\n\t\t\tlog::error!(\"Could not get selected nodes in load_stack_dependents\");\n\t\t\treturn;\n\t\t};\n\n\t\tlet mut selected_layers = selected_nodes.selected_nodes().filter(|node_id| self.is_layer(node_id, network_path)).cloned().collect::<HashSet<_>>();\n\n\t\t// Deselect all layers that are upstream of other selected layers\n\t\tlet mut removed_layers = Vec::new();\n\t\tfor layer in selected_layers.clone() {\n\t\t\tif removed_layers.contains(&layer) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tfor upstream_node in self.upstream_flow_back_from_nodes(vec![layer], network_path, FlowType::UpstreamFlow).skip(1) {\n\t\t\t\tif selected_layers.remove(&upstream_node) {\n\t\t\t\t\tremoved_layers.push(upstream_node)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Get a unique list of the top of each stack for each layer\n\t\tlet mut stack_tops = HashSet::new();\n\n\t\tfor layer in &selected_layers {\n\t\t\tlet mut current_node = *layer;\n\t\t\tloop {\n\t\t\t\tif self.is_layer(&current_node, network_path) && self.is_absolute(&current_node, network_path) {\n\t\t\t\t\tstack_tops.insert(current_node);\n\t\t\t\t\tbreak;\n\t\t\t\t};\n\t\t\t\tlet Some(outward_wires) = self.outward_wires(network_path) else {\n\t\t\t\t\tlog::error!(\"Cannot load outward wires in load_stack_dependents\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tlet Some(layer_outward_wires) = outward_wires.get(&OutputConnector::node(current_node, 0)) else {\n\t\t\t\t\tlog::error!(\"Could not get outward_wires for layer {current_node}\");\n\t\t\t\t\tbreak;\n\t\t\t\t};\n\t\t\t\tmatch layer_outward_wires.first() {\n\t\t\t\t\tSome(downstream_input) => {\n\t\t\t\t\t\tlet Some(downstream_node) = downstream_input.node_id() else {\n\t\t\t\t\t\t\tlog::error!(\"Node connected to export should be absolute\");\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t};\n\t\t\t\t\t\tcurrent_node = downstream_node\n\t\t\t\t\t}\n\t\t\t\t\tNone => break,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tlet mut stack_dependents = HashMap::new();\n\t\tlet mut owned_sole_dependents = HashSet::new();\n\t\t// Loop through all layers below the stack_tops, and set sole dependents upstream from that layer to be owned by that layer. Ensure LayerOwner is kept in sync.\n\t\tfor stack_top in &stack_tops {\n\t\t\tfor upstream_stack_layer in self\n\t\t\t\t.upstream_flow_back_from_nodes(vec![*stack_top], network_path, FlowType::PrimaryFlow)\n\t\t\t\t.take_while(|upstream_node| self.is_layer(upstream_node, network_path))\n\t\t\t\t.collect::<Vec<_>>()\n\t\t\t{\n\t\t\t\tfor upstream_layer in self.upstream_flow_back_from_nodes(vec![upstream_stack_layer], network_path, FlowType::UpstreamFlow).collect::<Vec<_>>() {\n\t\t\t\t\tif !self.is_layer(&upstream_layer, network_path) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tlet mut new_owned_nodes = HashSet::new();\n\t\t\t\t\tfor layer_sole_dependent in &self.upstream_nodes_below_layer(&upstream_layer, network_path) {\n\t\t\t\t\t\tstack_dependents.insert(*layer_sole_dependent, LayerOwner::Layer(upstream_layer));\n\t\t\t\t\t\towned_sole_dependents.insert(*layer_sole_dependent);\n\t\t\t\t\t\tnew_owned_nodes.insert(*layer_sole_dependent);\n\t\t\t\t\t}\n\t\t\t\t\tlet Some(layer_node) = self.node_metadata_mut(&upstream_layer, network_path) else {\n\t\t\t\t\t\tlog::error!(\"Could not get layer node in load_stack_dependents\");\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t};\n\t\t\t\t\tlet NodeTypePersistentMetadata::Layer(LayerPersistentMetadata { owned_nodes, .. }) = &mut layer_node.persistent_metadata.node_type_metadata else {\n\t\t\t\t\t\tlog::error!(\"upstream layer should be a layer\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t};\n\t\t\t\t\t*owned_nodes = TransientMetadata::Loaded(new_owned_nodes);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Set any sole dependents of the stack top that are not dependents of a layer in the stack to LayerOwner::None. These nodes will be pushed as blocks when a layer is shifted.\n\t\tfor stack_top in &stack_tops {\n\t\t\tlet mut sole_dependents = HashSet::new();\n\t\t\tlet mut not_sole_dependents = HashSet::new();\n\t\t\tsole_dependents.insert(*stack_top);\n\t\t\tfor upstream_node in self.upstream_flow_back_from_nodes(vec![*stack_top], network_path, FlowType::UpstreamFlow).collect::<Vec<_>>() {\n\t\t\t\tlet mut stack = vec![upstream_node];\n\t\t\t\tlet mut is_sole_dependent = true;\n\t\t\t\twhile let Some(current_node) = stack.pop() {\n\t\t\t\t\tif not_sole_dependents.contains(&current_node) {\n\t\t\t\t\t\tis_sole_dependent = false;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tif !sole_dependents.contains(&current_node) {\n\t\t\t\t\t\tlet mut has_outward_wire = false;\n\t\t\t\t\t\tfor output_index in 0..self.number_of_outputs(&current_node, network_path) {\n\t\t\t\t\t\t\tlet Some(outward_wires) = self.outward_wires(network_path) else {\n\t\t\t\t\t\t\t\tlog::error!(\"Cannot load outward wires in load_stack_dependents\");\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tlet Some(outward_wires) = outward_wires.get(&OutputConnector::node(current_node, output_index)) else {\n\t\t\t\t\t\t\t\tlog::error!(\"Cannot load outward wires in load_stack_dependents\");\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tfor downstream_input in outward_wires {\n\t\t\t\t\t\t\t\thas_outward_wire = true;\n\t\t\t\t\t\t\t\tmatch downstream_input {\n\t\t\t\t\t\t\t\t\tInputConnector::Node { node_id, .. } => stack.push(*node_id),\n\t\t\t\t\t\t\t\t\tInputConnector::Export(_) => is_sole_dependent = false,\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif !has_outward_wire {\n\t\t\t\t\t\t\tis_sole_dependent = false;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif !is_sole_dependent {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif is_sole_dependent {\n\t\t\t\t\tsole_dependents.insert(upstream_node);\n\t\t\t\t} else {\n\t\t\t\t\tnot_sole_dependents.insert(upstream_node);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor sole_dependent in sole_dependents {\n\t\t\t\tif !owned_sole_dependents.contains(&sole_dependent) {\n\t\t\t\t\tstack_dependents.insert(sole_dependent, LayerOwner::None(0));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tlet Some(network_metadata) = self.network_metadata_mut(network_path) else {\n\t\t\tlog::error!(\"Could not get current network in load_export_ports\");\n\t\t\treturn;\n\t\t};\n\n\t\tnetwork_metadata.transient_metadata.stack_dependents = TransientMetadata::Loaded(stack_dependents);\n\t}\n\n\tpub fn unload_stack_dependents(&mut self, network_path: &[NodeId]) {\n\t\tlet Some(network_metadata) = self.network_metadata_mut(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata in unload_stack_dependents\");\n\t\t\treturn;\n\t\t};\n\t\tnetwork_metadata.transient_metadata.stack_dependents.unload();\n\t}\n\n\t/// Resets all the offsets for nodes with no LayerOwner when the drag ends\n\tpub fn unload_stack_dependents_y_offset(&mut self, network_path: &[NodeId]) {\n\t\tlet Some(network_metadata) = self.network_metadata_mut(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata in unload_stack_dependents_y_offset\");\n\t\t\treturn;\n\t\t};\n\n\t\tif let TransientMetadata::Loaded(stack_dependents) = &mut network_metadata.transient_metadata.stack_dependents {\n\t\t\tfor layer_owner in stack_dependents.values_mut() {\n\t\t\t\tif let LayerOwner::None(offset) = layer_owner {\n\t\t\t\t\t*offset = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn import_export_ports(&mut self, network_path: &[NodeId]) -> Option<&Ports> {\n\t\tlet Some(network_metadata) = self.network_metadata(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata in export_ports\");\n\t\t\treturn None;\n\t\t};\n\t\tif !network_metadata.transient_metadata.import_export_ports.is_loaded() {\n\t\t\tself.load_import_export_ports(network_path);\n\t\t}\n\n\t\tlet Some(network_metadata) = self.network_metadata(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata in export_ports\");\n\t\t\treturn None;\n\t\t};\n\t\tlet TransientMetadata::Loaded(ports) = &network_metadata.transient_metadata.import_export_ports else {\n\t\t\tlog::error!(\"could not load import ports\");\n\t\t\treturn None;\n\t\t};\n\t\tSome(ports)\n\t}\n\n\tpub fn load_import_export_ports(&mut self, network_path: &[NodeId]) {\n\t\tlet Some(import_export_position) = self.import_export_position(network_path) else {\n\t\t\tlog::error!(\"Could not get import_export_position\");\n\t\t\treturn;\n\t\t};\n\t\tlet Some(network) = self.nested_network(network_path) else { return };\n\t\tlet mut import_export_ports = Ports::new();\n\n\t\tif !network_path.is_empty() {\n\t\t\tlet import_start_index = if self.hidden_primary_import(network_path) { 1 } else { 0 };\n\t\t\tfor import_index in import_start_index..self.number_of_imports(network_path) {\n\t\t\t\timport_export_ports.insert_output_port_at_center(import_index, import_export_position.0.as_dvec2() + DVec2::new(0., import_index as f64 * 24.));\n\t\t\t}\n\t\t}\n\n\t\tlet export_start_index = if self.hidden_primary_export(network_path) { 1 } else { 0 };\n\t\tfor export_index in export_start_index..network.exports.len() {\n\t\t\timport_export_ports.insert_input_port_at_center(export_index, import_export_position.1.as_dvec2() + DVec2::new(0., export_index as f64 * 24.));\n\t\t}\n\n\t\tlet Some(network_metadata) = self.network_metadata_mut(network_path) else {\n\t\t\tlog::error!(\"Could not get current network in load_export_ports\");\n\t\t\treturn;\n\t\t};\n\n\t\tnetwork_metadata.transient_metadata.import_export_ports = TransientMetadata::Loaded(import_export_ports);\n\t}\n\n\tfn unload_import_export_ports(&mut self, network_path: &[NodeId]) {\n\t\tlet Some(network_metadata) = self.network_metadata_mut(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata in unload_export_ports\");\n\t\t\treturn;\n\t\t};\n\t\tnetwork_metadata.transient_metadata.import_export_ports.unload();\n\n\t\t// Always unload all wires connected to them as well\n\t\tlet number_of_imports = self.number_of_imports(network_path);\n\t\tlet Some(outward_wires) = self.outward_wires(network_path) else {\n\t\t\tlog::error!(\"Could not get outward wires in remove_import\");\n\t\t\treturn;\n\t\t};\n\t\tlet mut input_connectors = Vec::new();\n\t\tfor import_index in 0..number_of_imports {\n\t\t\tlet Some(outward_wires_for_import) = outward_wires.get(&OutputConnector::Import(import_index)).cloned() else {\n\t\t\t\tlog::error!(\"Could not get outward wires for import in remove_import\");\n\t\t\t\treturn;\n\t\t\t};\n\t\t\tinput_connectors.extend(outward_wires_for_import);\n\t\t}\n\t\tlet Some(network) = self.nested_network(network_path) else {\n\t\t\treturn;\n\t\t};\n\t\tfor export_index in 0..network.exports.len() {\n\t\t\tinput_connectors.push(InputConnector::Export(export_index));\n\t\t}\n\t\tfor input in &input_connectors {\n\t\t\tself.unload_wire(input, network_path);\n\t\t}\n\t}\n\n\tpub fn modify_import_export(&mut self, network_path: &[NodeId]) -> Option<&ModifyImportExportClickTarget> {\n\t\tlet Some(network_metadata) = self.network_metadata(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata in modify_import_export\");\n\t\t\treturn None;\n\t\t};\n\t\tif !network_metadata.transient_metadata.modify_import_export.is_loaded() {\n\t\t\tself.load_modify_import_export(network_path);\n\t\t}\n\t\tlet Some(network_metadata) = self.network_metadata(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata in modify_import_export\");\n\t\t\treturn None;\n\t\t};\n\t\tlet TransientMetadata::Loaded(click_targets) = &network_metadata.transient_metadata.modify_import_export else {\n\t\t\tlog::error!(\"could not load modify import export ports\");\n\t\t\treturn None;\n\t\t};\n\t\tSome(click_targets)\n\t}\n\n\tpub fn load_modify_import_export(&mut self, network_path: &[NodeId]) {\n\t\tlet mut reorder_imports_exports = Ports::new();\n\t\tlet mut remove_imports_exports = Ports::new();\n\n\t\tif !network_path.is_empty() {\n\t\t\tlet Some(import_exports) = self.import_export_ports(network_path) else {\n\t\t\t\tlog::error!(\"Could not get import_export_ports in load_modify_import_export\");\n\t\t\t\treturn;\n\t\t\t};\n\n\t\t\tfor (import_index, import_click_target) in import_exports.output_ports() {\n\t\t\t\tlet Some(import_bounding_box) = import_click_target.bounding_box() else {\n\t\t\t\t\tlog::error!(\"Could not get export bounding box in load_modify_import_export\");\n\t\t\t\t\tcontinue;\n\t\t\t\t};\n\t\t\t\tlet reorder_import_center = (import_bounding_box[0] + import_bounding_box[1]) / 2. + DVec2::new(-12., 0.);\n\n\t\t\t\tif *import_index == 0 {\n\t\t\t\t\tlet remove_import_center = reorder_import_center + DVec2::new(-4., 0.);\n\t\t\t\t\tlet remove_import = ClickTarget::new_with_subpath(Subpath::new_rectangle(remove_import_center - DVec2::new(8., 8.), remove_import_center + DVec2::new(8., 8.)), 0.);\n\t\t\t\t\tremove_imports_exports.insert_custom_output_port(*import_index, remove_import);\n\t\t\t\t} else {\n\t\t\t\t\tlet remove_import_center = reorder_import_center + DVec2::new(-12., 0.);\n\t\t\t\t\tlet reorder_import = ClickTarget::new_with_subpath(Subpath::new_rectangle(reorder_import_center - DVec2::new(3., 4.), reorder_import_center + DVec2::new(3., 4.)), 0.);\n\t\t\t\t\tlet remove_import = ClickTarget::new_with_subpath(Subpath::new_rectangle(remove_import_center - DVec2::new(8., 8.), remove_import_center + DVec2::new(8., 8.)), 0.);\n\t\t\t\t\treorder_imports_exports.insert_custom_output_port(*import_index, reorder_import);\n\t\t\t\t\tremove_imports_exports.insert_custom_output_port(*import_index, remove_import);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (export_index, export_click_target) in import_exports.input_ports() {\n\t\t\t\tlet Some(export_bounding_box) = export_click_target.bounding_box() else {\n\t\t\t\t\tlog::error!(\"Could not get export bounding box in load_modify_import_export\");\n\t\t\t\t\tcontinue;\n\t\t\t\t};\n\t\t\t\tlet reorder_export_center = (export_bounding_box[0] + export_bounding_box[1]) / 2. + DVec2::new(12., 0.);\n\n\t\t\t\tif *export_index == 0 {\n\t\t\t\t\tlet remove_export_center = reorder_export_center + DVec2::new(4., 0.);\n\t\t\t\t\tlet remove_export = ClickTarget::new_with_subpath(Subpath::new_rectangle(remove_export_center - DVec2::new(8., 8.), remove_export_center + DVec2::new(8., 8.)), 0.);\n\t\t\t\t\tremove_imports_exports.insert_custom_input_port(*export_index, remove_export);\n\t\t\t\t} else {\n\t\t\t\t\tlet remove_export_center = reorder_export_center + DVec2::new(12., 0.);\n\t\t\t\t\tlet reorder_export = ClickTarget::new_with_subpath(Subpath::new_rectangle(reorder_export_center - DVec2::new(3., 4.), reorder_export_center + DVec2::new(3., 4.)), 0.);\n\t\t\t\t\tlet remove_export = ClickTarget::new_with_subpath(Subpath::new_rectangle(remove_export_center - DVec2::new(8., 8.), remove_export_center + DVec2::new(8., 8.)), 0.);\n\t\t\t\t\treorder_imports_exports.insert_custom_input_port(*export_index, reorder_export);\n\t\t\t\t\tremove_imports_exports.insert_custom_input_port(*export_index, remove_export);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tlet Some(network_metadata) = self.network_metadata_mut(network_path) else {\n\t\t\tlog::error!(\"Could not get current network in load_modify_import_export\");\n\t\t\treturn;\n\t\t};\n\n\t\tnetwork_metadata.transient_metadata.modify_import_export = TransientMetadata::Loaded(ModifyImportExportClickTarget {\n\t\t\tremove_imports_exports,\n\t\t\treorder_imports_exports,\n\t\t});\n\t}\n\n\tfn unload_modify_import_export(&mut self, network_path: &[NodeId]) {\n\t\tlet Some(network_metadata) = self.network_metadata_mut(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata in unload_export_ports\");\n\t\t\treturn;\n\t\t};\n\t\tnetwork_metadata.transient_metadata.modify_import_export.unload();\n\t}\n\n\tfn owned_nodes(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&HashSet<NodeId>> {\n\t\tlet layer_node = self.node_metadata(node_id, network_path)?;\n\t\tlet NodeTypePersistentMetadata::Layer(LayerPersistentMetadata { owned_nodes, .. }) = &layer_node.persistent_metadata.node_type_metadata else {\n\t\t\treturn None;\n\t\t};\n\t\tlet TransientMetadata::Loaded(owned_nodes) = owned_nodes else {\n\t\t\treturn None;\n\t\t};\n\t\tSome(owned_nodes)\n\t}\n\n\tpub fn all_nodes_bounding_box(&mut self, network_path: &[NodeId]) -> Option<&[DVec2; 2]> {\n\t\tlet Some(network_metadata) = self.network_metadata(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata in all_nodes_bounding_box\");\n\t\t\treturn None;\n\t\t};\n\n\t\tif !network_metadata.transient_metadata.all_nodes_bounding_box.is_loaded() {\n\t\t\tself.load_all_nodes_bounding_box(network_path);\n\t\t}\n\n\t\tlet network_metadata = self.network_metadata(network_path)?;\n\n\t\tlet TransientMetadata::Loaded(bounding_box) = &network_metadata.transient_metadata.all_nodes_bounding_box else {\n\t\t\tlog::error!(\"could not load all nodes bounding box\");\n\t\t\treturn None;\n\t\t};\n\n\t\tSome(bounding_box)\n\t}\n\n\tpub fn load_all_nodes_bounding_box(&mut self, network_path: &[NodeId]) {\n\t\tlet Some(network_metadata) = self.network_metadata(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata in load_all_nodes_bounding_box\");\n\t\t\treturn;\n\t\t};\n\t\tlet nodes = network_metadata.persistent_metadata.node_metadata.keys().copied().collect::<Vec<_>>();\n\n\t\tlet all_nodes_bounding_box = nodes\n\t\t\t.iter()\n\t\t\t.filter_map(|node_id| {\n\t\t\t\tself.node_click_targets(node_id, network_path)\n\t\t\t\t\t.and_then(|transient_node_metadata| transient_node_metadata.node_click_target.bounding_box())\n\t\t\t})\n\t\t\t.reduce(Quad::combine_bounds)\n\t\t\t.unwrap_or([DVec2::new(0., 0.), DVec2::new(0., 0.)]);\n\n\t\tlet Some(network_metadata) = self.network_metadata_mut(network_path) else { return };\n\t\tnetwork_metadata.transient_metadata.all_nodes_bounding_box = TransientMetadata::Loaded(all_nodes_bounding_box);\n\t}\n\n\tpub fn unload_all_nodes_bounding_box(&mut self, network_path: &[NodeId]) {\n\t\tlet Some(network_metadata) = self.network_metadata_mut(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata in unload_all_nodes_bounding_box\");\n\t\t\treturn;\n\t\t};\n\t\tnetwork_metadata.transient_metadata.all_nodes_bounding_box.unload();\n\t\tself.unload_import_export_ports(network_path);\n\t}\n\n\tpub fn outward_wires(&mut self, network_path: &[NodeId]) -> Option<&HashMap<OutputConnector, Vec<InputConnector>>> {\n\t\tlet Some(network_metadata) = self.network_metadata(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata in outward_wires\");\n\t\t\treturn None;\n\t\t};\n\n\t\tif !network_metadata.transient_metadata.outward_wires.is_loaded() {\n\t\t\tself.load_outward_wires(network_path);\n\t\t}\n\n\t\tlet network_metadata = self.network_metadata(network_path)?;\n\n\t\tlet TransientMetadata::Loaded(outward_wires) = &network_metadata.transient_metadata.outward_wires else {\n\t\t\tlog::error!(\"could not load outward wires\");\n\t\t\treturn None;\n\t\t};\n\n\t\tSome(outward_wires)\n\t}\n\n\tfn load_outward_wires(&mut self, network_path: &[NodeId]) {\n\t\tlet mut outward_wires = HashMap::new();\n\t\tlet Some(network) = self.nested_network(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network in load_outward_wires\");\n\t\t\treturn;\n\t\t};\n\t\t// Initialize all output connectors for nodes\n\t\tfor (node_id, _) in network.nodes.iter() {\n\t\t\tlet number_of_outputs = self.number_of_outputs(node_id, network_path);\n\t\t\tfor output_index in 0..number_of_outputs {\n\t\t\t\toutward_wires.insert(OutputConnector::node(*node_id, output_index), Vec::new());\n\t\t\t}\n\t\t}\n\t\t// Initialize output connectors for the import node\n\t\tfor import_index in 0..self.number_of_imports(network_path) {\n\t\t\toutward_wires.insert(OutputConnector::Import(import_index), Vec::new());\n\t\t}\n\t\t// Collect wires between all nodes and the Imports\n\t\tfor (current_node_id, node) in network.nodes.iter() {\n\t\t\tfor (input_index, input) in node.inputs.iter().enumerate() {\n\t\t\t\tif let NodeInput::Node { node_id, output_index, .. } = input {\n\t\t\t\t\t// If this errors then there is an input to a node that does not exist\n\t\t\t\t\tlet outward_wires_entry = outward_wires.get_mut(&OutputConnector::node(*node_id, *output_index)).unwrap_or_else(|| {\n\t\t\t\t\t\tpanic!(\n\t\t\t\t\t\t\t\"Output connector {:?} should be initialized for each node output from a node\",\n\t\t\t\t\t\t\tOutputConnector::node(*node_id, *output_index)\n\t\t\t\t\t\t)\n\t\t\t\t\t});\n\t\t\t\t\toutward_wires_entry.push(InputConnector::node(*current_node_id, input_index));\n\t\t\t\t} else if let NodeInput::Import { import_index, .. } = input {\n\t\t\t\t\tlet outward_wires_entry = outward_wires\n\t\t\t\t\t\t.get_mut(&OutputConnector::Import(*import_index))\n\t\t\t\t\t\t.unwrap_or_else(|| panic!(\"Output connector {:?} should be initialized for each import from a node\", OutputConnector::Import(*import_index)));\n\t\t\t\t\toutward_wires_entry.push(InputConnector::node(*current_node_id, input_index));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfor (export_index, export) in network.exports.iter().enumerate() {\n\t\t\tif let NodeInput::Node { node_id, output_index, .. } = export {\n\t\t\t\tlet outward_wires_entry = outward_wires.get_mut(&OutputConnector::node(*node_id, *output_index)).unwrap_or_else(|| {\n\t\t\t\t\tpanic!(\n\t\t\t\t\t\t\"Output connector {:?} should be initialized for each node input from exports\",\n\t\t\t\t\t\tOutputConnector::node(*node_id, *output_index)\n\t\t\t\t\t)\n\t\t\t\t});\n\t\t\t\toutward_wires_entry.push(InputConnector::Export(export_index));\n\t\t\t} else if let NodeInput::Import { import_index, .. } = export {\n\t\t\t\tlet outward_wires_entry = outward_wires\n\t\t\t\t\t.get_mut(&OutputConnector::Import(*import_index))\n\t\t\t\t\t.unwrap_or_else(|| panic!(\"Output connector {:?} should be initialized between imports and exports\", OutputConnector::Import(*import_index)));\n\t\t\t\toutward_wires_entry.push(InputConnector::Export(export_index));\n\t\t\t}\n\t\t}\n\n\t\tlet Some(network_metadata) = self.network_metadata_mut(network_path) else { return };\n\n\t\tnetwork_metadata.transient_metadata.outward_wires = TransientMetadata::Loaded(outward_wires);\n\t}\n\n\tfn unload_outward_wires(&mut self, network_path: &[NodeId]) {\n\t\tlet Some(network_metadata) = self.network_metadata_mut(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata in unload_outward_wires\");\n\t\t\treturn;\n\t\t};\n\t\tnetwork_metadata.transient_metadata.outward_wires.unload();\n\t}\n\n\tpub fn layer_width(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option<u32> {\n\t\tlet Some(node_metadata) = self.node_metadata(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get nested node_metadata in layer_width\");\n\t\t\treturn None;\n\t\t};\n\t\tif !node_metadata.persistent_metadata.is_layer() {\n\t\t\tlog::error!(\"Cannot get layer width for non layer node {node_id} in network {network_path:?}\");\n\t\t\treturn None;\n\t\t}\n\n\t\tlet layer_width_loaded = if let NodeTypeTransientMetadata::Layer(layer_metadata) = &node_metadata.transient_metadata.node_type_metadata {\n\t\t\tlayer_metadata.layer_width.is_loaded()\n\t\t} else {\n\t\t\tfalse\n\t\t};\n\t\tif !layer_width_loaded {\n\t\t\tself.load_layer_width(node_id, network_path);\n\t\t}\n\n\t\tlet node_metadata = self.node_metadata(node_id, network_path)?;\n\t\tlet NodeTypeTransientMetadata::Layer(layer_metadata) = &node_metadata.transient_metadata.node_type_metadata else {\n\t\t\tlog::error!(\"Transient metadata should be layer metadata when getting layer width\");\n\t\t\treturn None;\n\t\t};\n\t\tlet TransientMetadata::Loaded(layer_width) = layer_metadata.layer_width else {\n\t\t\tlog::error!(\"Transient metadata was not loaded when getting layer width\");\n\t\t\treturn None;\n\t\t};\n\n\t\tSome(layer_width)\n\t}\n\n\tpub fn load_layer_width(&mut self, node_id: &NodeId, network_path: &[NodeId]) {\n\t\tconst GAP_WIDTH: f64 = 8.;\n\t\tconst FONT_SIZE: f64 = 14.;\n\t\tlet left_thumbnail_padding = GRID_SIZE as f64 / 2.;\n\t\tlet thumbnail_width = 3. * GRID_SIZE as f64;\n\t\tlet layer_text = self.display_name(node_id, network_path);\n\n\t\tlet text_width = text_width(&layer_text, FONT_SIZE);\n\n\t\tlet grip_padding = 4.;\n\t\tlet grip_width = 8.;\n\t\tlet lock_icon_width = if self.is_locked(node_id, network_path) { GRID_SIZE as f64 } else { 0. };\n\t\tlet icon_overhang_width = GRID_SIZE as f64 / 2.;\n\n\t\tlet layer_width_pixels = left_thumbnail_padding + thumbnail_width + GAP_WIDTH + text_width + grip_padding + grip_width + lock_icon_width + icon_overhang_width;\n\t\tlet layer_width = ((layer_width_pixels / 24.).ceil() as u32).max(8);\n\n\t\tlet Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get nested node_metadata in load_layer_width\");\n\t\t\treturn;\n\t\t};\n\n\t\t// Ensure layer width is not loaded for a non layer node\n\t\tif node_metadata.persistent_metadata.is_layer() {\n\t\t\tif let NodeTypeTransientMetadata::Layer(layer_metadata) = &mut node_metadata.transient_metadata.node_type_metadata {\n\t\t\t\tlayer_metadata.layer_width = TransientMetadata::Loaded(layer_width);\n\t\t\t} else {\n\t\t\t\t// Set the entire transient node type metadata to be a layer, in case it was previously a node\n\t\t\t\tnode_metadata.transient_metadata.node_type_metadata = NodeTypeTransientMetadata::Layer(LayerTransientMetadata {\n\t\t\t\t\tlayer_width: TransientMetadata::Loaded(layer_width),\n\t\t\t\t});\n\t\t\t}\n\t\t} else {\n\t\t\tlog::warn!(\"Tried loading layer width for non layer node\");\n\t\t}\n\t}\n\n\t/// Unloads layer width if the node is a layer\n\tpub fn try_unload_layer_width(&mut self, node_id: &NodeId, network_path: &[NodeId]) {\n\t\tlet is_layer = self.is_layer(node_id, network_path);\n\n\t\tlet Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else {\n\t\t\treturn;\n\t\t};\n\n\t\t// If the node is a layer, then the width and click targets need to be recalculated\n\t\tif is_layer && let NodeTypeTransientMetadata::Layer(layer_metadata) = &mut node_metadata.transient_metadata.node_type_metadata {\n\t\t\tlayer_metadata.layer_width.unload();\n\t\t}\n\t}\n\n\tpub fn get_input_center(&mut self, input: &InputConnector, network_path: &[NodeId]) -> Option<DVec2> {\n\t\tlet (ports, index) = match input {\n\t\t\tInputConnector::Node { node_id, input_index } => {\n\t\t\t\tlet node_click_target = self.node_click_targets(node_id, network_path)?;\n\t\t\t\t(&node_click_target.port_click_targets, input_index)\n\t\t\t}\n\t\t\tInputConnector::Export(export_index) => {\n\t\t\t\tlet ports = self.import_export_ports(network_path)?;\n\t\t\t\t(ports, export_index)\n\t\t\t}\n\t\t};\n\t\tports\n\t\t\t.input_ports\n\t\t\t.iter()\n\t\t\t.find_map(|(input_index, click_target)| if index == input_index { click_target.bounding_box_center() } else { None })\n\t}\n\n\tpub fn get_output_center(&mut self, output: &OutputConnector, network_path: &[NodeId]) -> Option<DVec2> {\n\t\tlet (ports, index) = match output {\n\t\t\tOutputConnector::Node { node_id, output_index } => {\n\t\t\t\tlet node_click_target = self.node_click_targets(node_id, network_path)?;\n\t\t\t\t(&node_click_target.port_click_targets, output_index)\n\t\t\t}\n\t\t\tOutputConnector::Import(import_index) => {\n\t\t\t\tlet ports = self.import_export_ports(network_path)?;\n\t\t\t\t(ports, import_index)\n\t\t\t}\n\t\t};\n\t\tports\n\t\t\t.output_ports\n\t\t\t.iter()\n\t\t\t.find_map(|(input_index, click_target)| if index == input_index { click_target.bounding_box_center() } else { None })\n\t}\n\n\tpub fn newly_loaded_input_wire(&mut self, input: &InputConnector, graph_wire_style: GraphWireStyle, network_path: &[NodeId]) -> Option<WirePathUpdate> {\n\t\tif !self.wire_is_loaded(input, network_path) {\n\t\t\tself.load_wire(input, graph_wire_style, network_path);\n\t\t} else {\n\t\t\treturn None;\n\t\t}\n\n\t\tlet wire = match input {\n\t\t\tInputConnector::Node { node_id, input_index } => {\n\t\t\t\tlet input_metadata = self.transient_input_metadata(node_id, *input_index, network_path)?;\n\t\t\t\tlet TransientMetadata::Loaded(wire) = &input_metadata.wire else {\n\t\t\t\t\tlog::error!(\"Could not load wire for input: {input:?}\");\n\t\t\t\t\treturn None;\n\t\t\t\t};\n\t\t\t\twire.clone()\n\t\t\t}\n\t\t\tInputConnector::Export(export_index) => {\n\t\t\t\tlet network_metadata = self.network_metadata(network_path)?;\n\t\t\t\tlet Some(TransientMetadata::Loaded(wire)) = network_metadata.transient_metadata.wires.get(*export_index) else {\n\t\t\t\t\tlog::error!(\"Could not load wire for input: {input:?}\");\n\t\t\t\t\treturn None;\n\t\t\t\t};\n\t\t\t\twire.clone()\n\t\t\t}\n\t\t};\n\t\tSome(wire)\n\t}\n\n\tpub fn wire_is_loaded(&mut self, input: &InputConnector, network_path: &[NodeId]) -> bool {\n\t\tmatch input {\n\t\t\tInputConnector::Node { node_id, input_index } => {\n\t\t\t\tlet Some(input_metadata) = self.transient_input_metadata(node_id, *input_index, network_path) else {\n\t\t\t\t\tlog::error!(\"Input metadata should always exist for input\");\n\t\t\t\t\treturn false;\n\t\t\t\t};\n\t\t\t\tinput_metadata.wire.is_loaded()\n\t\t\t}\n\t\t\tInputConnector::Export(export_index) => {\n\t\t\t\tlet Some(network_metadata) = self.network_metadata(network_path) else {\n\t\t\t\t\treturn false;\n\t\t\t\t};\n\t\t\t\tmatch network_metadata.transient_metadata.wires.get(*export_index) {\n\t\t\t\t\tSome(wire) => wire.is_loaded(),\n\t\t\t\t\tNone => false,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfn load_wire(&mut self, input: &InputConnector, graph_wire_style: GraphWireStyle, network_path: &[NodeId]) {\n\t\tlet dashed = match self.previewing(network_path) {\n\t\t\tPreviewing::Yes { .. } => match input {\n\t\t\t\tInputConnector::Node { .. } => false,\n\t\t\t\tInputConnector::Export(export_index) => *export_index == 0,\n\t\t\t},\n\t\t\tPreviewing::No => false,\n\t\t};\n\t\tlet Some(wire) = self.wire_path_from_input(input, graph_wire_style, dashed, network_path) else {\n\t\t\tlog::error!(\"Could not load wire path from input\");\n\t\t\treturn;\n\t\t};\n\t\tmatch input {\n\t\t\tInputConnector::Node { node_id, input_index } => {\n\t\t\t\tlet Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else { return };\n\t\t\t\tlet Some(input_metadata) = node_metadata.persistent_metadata.input_metadata.get_mut(*input_index) else {\n\t\t\t\t\t// log::warn!(\"Node metadata must exist on node: {input:?}\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tlet wire_update = WirePathUpdate {\n\t\t\t\t\tid: *node_id,\n\t\t\t\t\tinput_index: *input_index,\n\t\t\t\t\twire_path_update: Some(wire),\n\t\t\t\t};\n\t\t\t\tinput_metadata.transient_metadata.wire = TransientMetadata::Loaded(wire_update);\n\t\t\t}\n\t\t\tInputConnector::Export(export_index) => {\n\t\t\t\tlet Some(network_metadata) = self.network_metadata_mut(network_path) else { return };\n\t\t\t\tif *export_index >= network_metadata.transient_metadata.wires.len() {\n\t\t\t\t\tnetwork_metadata.transient_metadata.wires.resize(export_index + 1, TransientMetadata::Unloaded);\n\t\t\t\t}\n\t\t\t\tlet Some(input_metadata) = network_metadata.transient_metadata.wires.get_mut(*export_index) else {\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tlet wire_update = WirePathUpdate {\n\t\t\t\t\tid: NodeId(u64::MAX),\n\t\t\t\t\tinput_index: *export_index,\n\t\t\t\t\twire_path_update: Some(wire),\n\t\t\t\t};\n\t\t\t\t*input_metadata = TransientMetadata::Loaded(wire_update);\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn all_input_connectors(&self, network_path: &[NodeId]) -> Vec<InputConnector> {\n\t\tlet mut input_connectors = Vec::new();\n\t\tlet Some(network) = self.nested_network(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network in all_input_connectors\");\n\t\t\treturn Vec::new();\n\t\t};\n\t\tfor export_index in 0..network.exports.len() {\n\t\t\tinput_connectors.push(InputConnector::Export(export_index));\n\t\t}\n\t\tfor (node_id, node) in &network.nodes {\n\t\t\tfor input_index in 0..node.inputs.len() {\n\t\t\t\tinput_connectors.push(InputConnector::node(*node_id, input_index));\n\t\t\t}\n\t\t}\n\t\tinput_connectors\n\t}\n\n\tpub fn node_graph_input_connectors(&self, network_path: &[NodeId]) -> Vec<InputConnector> {\n\t\tself.all_input_connectors(network_path)\n\t\t\t.into_iter()\n\t\t\t.filter(|input| self.input_from_connector(input, network_path).is_some_and(|input| input.is_exposed()))\n\t\t\t.collect()\n\t}\n\n\t/// Maps to the frontend representation of a wire start. Includes disconnected value wire inputs.\n\tpub fn node_graph_wire_inputs(&self, network_path: &[NodeId]) -> Vec<(NodeId, usize)> {\n\t\tself.node_graph_input_connectors(network_path)\n\t\t\t.iter()\n\t\t\t.map(|input| match input {\n\t\t\t\tInputConnector::Node { node_id, input_index } => (*node_id, *input_index),\n\t\t\t\tInputConnector::Export(export_index) => (NodeId(u64::MAX), *export_index),\n\t\t\t})\n\t\t\t.chain(std::iter::once((NodeId(u64::MAX), u32::MAX as usize)))\n\t\t\t.collect()\n\t}\n\n\tfn unload_wires_for_node(&mut self, node_id: &NodeId, network_path: &[NodeId]) {\n\t\tlet number_of_outputs = self.number_of_outputs(node_id, network_path);\n\t\tlet Some(outward_wires) = self.outward_wires(network_path) else {\n\t\t\tlog::error!(\"Could not get outward wires in reorder_export\");\n\t\t\treturn;\n\t\t};\n\t\tlet mut input_connectors = Vec::new();\n\t\tfor output_index in 0..number_of_outputs {\n\t\t\tlet Some(inputs) = outward_wires.get(&OutputConnector::node(*node_id, output_index)) else {\n\t\t\t\tcontinue;\n\t\t\t};\n\t\t\tinput_connectors.extend(inputs.clone())\n\t\t}\n\t\tfor input_index in 0..self.number_of_inputs(node_id, network_path) {\n\t\t\tinput_connectors.push(InputConnector::node(*node_id, input_index));\n\t\t}\n\t\tfor input in input_connectors {\n\t\t\tself.unload_wire(&input, network_path);\n\t\t}\n\t}\n\n\tpub fn unload_wire(&mut self, input: &InputConnector, network_path: &[NodeId]) {\n\t\tmatch input {\n\t\t\tInputConnector::Node { node_id, input_index } => {\n\t\t\t\tlet Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else {\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tlet Some(input_metadata) = node_metadata.persistent_metadata.input_metadata.get_mut(*input_index) else {\n\t\t\t\t\t// log::warn!(\"Node metadata must exist on node: {input:?}\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tinput_metadata.transient_metadata.wire = TransientMetadata::Unloaded;\n\t\t\t}\n\t\t\tInputConnector::Export(export_index) => {\n\t\t\t\tlet Some(network_metadata) = self.network_metadata_mut(network_path) else {\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tif *export_index >= network_metadata.transient_metadata.wires.len() {\n\t\t\t\t\tnetwork_metadata.transient_metadata.wires.resize(export_index + 1, TransientMetadata::Unloaded);\n\t\t\t\t}\n\t\t\t\tlet Some(input_metadata) = network_metadata.transient_metadata.wires.get_mut(*export_index) else {\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\t*input_metadata = TransientMetadata::Unloaded;\n\t\t\t}\n\t\t}\n\t}\n\n\t/// When previewing, there may be a second path to the root node.\n\tpub fn wire_to_root(&mut self, graph_wire_style: GraphWireStyle, network_path: &[NodeId]) -> Option<WirePathUpdate> {\n\t\tlet input = InputConnector::Export(0);\n\t\tlet current_export = self.upstream_output_connector(&input, network_path)?;\n\n\t\tlet root_node = match self.previewing(network_path) {\n\t\t\tPreviewing::Yes { root_node_to_restore } => root_node_to_restore,\n\t\t\tPreviewing::No => None,\n\t\t}?;\n\n\t\tif Some(root_node.node_id) == current_export.node_id() {\n\t\t\treturn None;\n\t\t}\n\t\tlet Some(input_position) = self.get_input_center(&input, network_path) else {\n\t\t\tlog::error!(\"Could not get input position for wire end in root node: {input:?}\");\n\t\t\treturn None;\n\t\t};\n\t\tlet upstream_output = OutputConnector::node(root_node.node_id, root_node.output_index);\n\t\tlet Some(output_position) = self.get_output_center(&upstream_output, network_path) else {\n\t\t\tlog::error!(\"Could not get output position for wire start in root node: {upstream_output:?}\");\n\t\t\treturn None;\n\t\t};\n\t\tlet vertical_end = input.node_id().is_some_and(|node_id| self.is_layer(&node_id, network_path) && input.input_index() == 0);\n\t\tlet vertical_start: bool = upstream_output.node_id().is_some_and(|node_id| self.is_layer(&node_id, network_path));\n\t\tlet thick = vertical_end && vertical_start;\n\t\tlet vector_wire = build_vector_wire(output_position, input_position, vertical_start, vertical_end, graph_wire_style);\n\n\t\tlet path_string = vector_wire.to_svg();\n\t\tlet data_type = self.input_type(&input, network_path).displayed_type();\n\t\tlet wire_path_update = Some(WirePath {\n\t\t\tpath_string,\n\t\t\tdata_type,\n\t\t\tthick,\n\t\t\tdashed: false,\n\t\t});\n\n\t\tSome(WirePathUpdate {\n\t\t\tid: NodeId(u64::MAX),\n\t\t\tinput_index: u32::MAX as usize,\n\t\t\twire_path_update,\n\t\t})\n\t}\n\n\t/// Returns the vector subpath and a boolean of whether the wire should be thick.\n\tpub fn vector_wire_from_input(&mut self, input: &InputConnector, wire_style: GraphWireStyle, network_path: &[NodeId]) -> Option<(BezPath, bool)> {\n\t\tlet Some(input_position) = self.get_input_center(input, network_path) else {\n\t\t\tlog::error!(\"Could not get dom rect for wire end: {input:?}\");\n\t\t\treturn None;\n\t\t};\n\t\t// An upstream output could not be found, so the wire does not exist, but it should still be loaded as as empty vector\n\t\tlet Some(upstream_output) = self.upstream_output_connector(input, network_path) else {\n\t\t\treturn Some((BezPath::new(), false));\n\t\t};\n\t\tlet Some(output_position) = self.get_output_center(&upstream_output, network_path) else {\n\t\t\tlog::error!(\"Could not get output port for wire start: {:?}\", upstream_output);\n\t\t\treturn None;\n\t\t};\n\t\tlet vertical_end = input.node_id().is_some_and(|node_id| self.is_layer(&node_id, network_path) && input.input_index() == 0);\n\t\tlet vertical_start = upstream_output.node_id().is_some_and(|node_id| self.is_layer(&node_id, network_path));\n\t\tlet thick = vertical_end && vertical_start;\n\t\tSome((build_vector_wire(output_position, input_position, vertical_start, vertical_end, wire_style), thick))\n\t}\n\n\tpub fn wire_path_from_input(&mut self, input: &InputConnector, graph_wire_style: GraphWireStyle, dashed: bool, network_path: &[NodeId]) -> Option<WirePath> {\n\t\tlet (vector_wire, thick) = self.vector_wire_from_input(input, graph_wire_style, network_path)?;\n\t\tlet path_string = vector_wire.to_svg();\n\t\tlet data_type = self\n\t\t\t.upstream_output_connector(input, network_path)\n\t\t\t.map(|output| self.output_type(&output, network_path).displayed_type())\n\t\t\t.unwrap_or(FrontendGraphDataType::General);\n\t\tSome(WirePath {\n\t\t\tpath_string,\n\t\t\tdata_type,\n\t\t\tthick,\n\t\t\tdashed,\n\t\t})\n\t}\n\n\tpub fn node_click_targets(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&DocumentNodeClickTargets> {\n\t\tself.try_load_node_click_targets(node_id, network_path);\n\t\tself.try_get_node_click_targets(node_id, network_path)\n\t}\n\n\tfn try_load_node_click_targets(&mut self, node_id: &NodeId, network_path: &[NodeId]) {\n\t\tlet Some(node_metadata) = self.node_metadata(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get nested node_metadata in node_click_targets\");\n\t\t\treturn;\n\t\t};\n\t\tif !node_metadata.transient_metadata.click_targets.is_loaded() {\n\t\t\tself.load_node_click_targets(node_id, network_path)\n\t\t};\n\t}\n\n\tfn try_get_node_click_targets(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&DocumentNodeClickTargets> {\n\t\tlet node_metadata = self.node_metadata(node_id, network_path)?;\n\t\tlet TransientMetadata::Loaded(click_target) = &node_metadata.transient_metadata.click_targets else {\n\t\t\tlog::error!(\"Could not load node type metadata when getting click targets\");\n\t\t\treturn None;\n\t\t};\n\t\tSome(click_target)\n\t}\n\n\tpub fn load_node_click_targets(&mut self, node_id: &NodeId, network_path: &[NodeId]) {\n\t\tlet Some(node_position) = self.position_from_downstream_node(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get node position in load_node_click_targets for node {node_id}\");\n\t\t\treturn;\n\t\t};\n\t\tlet Some(node_metadata) = self.node_metadata(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get nested node_metadata in load_node_click_targets\");\n\t\t\treturn;\n\t\t};\n\t\tlet Some(document_node) = self.document_node(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get document node in load_node_click_targets\");\n\t\t\treturn;\n\t\t};\n\n\t\tlet node_top_left = node_position.as_dvec2() * 24.;\n\t\tlet mut port_click_targets = Ports::new();\n\t\tlet document_node_click_targets = if !node_metadata.persistent_metadata.is_layer() {\n\t\t\t// Create input/output click targets\n\t\t\tlet mut input_row_count = 0;\n\t\t\tfor (input_index, input) in document_node.inputs.iter().enumerate() {\n\t\t\t\tif input.is_exposed() {\n\t\t\t\t\tport_click_targets.insert_node_input(input_index, input_row_count, node_top_left);\n\t\t\t\t}\n\t\t\t\t// Primary input row is always displayed, even if the input is not exposed\n\t\t\t\tif input_index == 0 || input.is_exposed() {\n\t\t\t\t\tinput_row_count += 1;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet number_of_outputs = match &document_node.implementation {\n\t\t\t\tDocumentNodeImplementation::Network(network) => network.exports.len(),\n\t\t\t\t_ => 1,\n\t\t\t};\n\t\t\t// If the node has a hidden primary output, do not display the first output\n\t\t\tlet start_index = if self.hidden_primary_output(node_id, network_path) { 1 } else { 0 };\n\t\t\tfor output_index in start_index..number_of_outputs {\n\t\t\t\tport_click_targets.insert_node_output(output_index, node_top_left);\n\t\t\t}\n\n\t\t\tlet height = input_row_count.max(number_of_outputs).max(1) as u32 * crate::consts::GRID_SIZE;\n\t\t\tlet width = 5 * crate::consts::GRID_SIZE;\n\t\t\tlet node_click_target_top_left = node_top_left + DVec2::new(0., 12.);\n\t\t\tlet node_click_target_bottom_right = node_click_target_top_left + DVec2::new(width as f64, height as f64);\n\n\t\t\tlet radius = 3.;\n\t\t\tlet subpath = Subpath::new_rounded_rectangle(node_click_target_top_left, node_click_target_bottom_right, [radius; 4]);\n\t\t\tlet node_click_target = ClickTarget::new_with_subpath(subpath, 0.);\n\n\t\t\tDocumentNodeClickTargets {\n\t\t\t\tnode_click_target,\n\t\t\t\tport_click_targets,\n\t\t\t\tnode_type_metadata: NodeTypeClickTargets::Node,\n\t\t\t}\n\t\t} else {\n\t\t\t// Layer inputs\n\t\t\tport_click_targets.insert_layer_input(0, node_top_left);\n\t\t\tif document_node.inputs.iter().filter(|input| input.is_exposed()).count() > 1 {\n\t\t\t\tport_click_targets.insert_layer_input(1, node_top_left);\n\t\t\t}\n\t\t\tport_click_targets.insert_layer_output(node_top_left);\n\n\t\t\tlet layer_width_cells = self.layer_width(node_id, network_path).unwrap_or_else(|| {\n\t\t\t\tlog::error!(\"Could not get layer width in load_node_click_targets\");\n\t\t\t\t0\n\t\t\t});\n\t\t\tlet width = layer_width_cells * crate::consts::GRID_SIZE;\n\t\t\tlet height = 2 * crate::consts::GRID_SIZE;\n\t\t\tlet locked = self.is_locked(node_id, network_path);\n\n\t\t\t// Update visibility button click target\n\t\t\tlet visibility_offset = node_top_left + DVec2::new(width as f64, 24.);\n\t\t\tlet subpath = Subpath::new_rounded_rectangle(DVec2::new(-12., -12.) + visibility_offset, DVec2::new(12., 12.) + visibility_offset, [3.; 4]);\n\t\t\tlet visibility_click_target = ClickTarget::new_with_subpath(subpath, 0.);\n\n\t\t\t// Update lock button click target, positioned one grid unit to the left of the visibility button (only when locked)\n\t\t\tlet lock_click_target = if locked {\n\t\t\t\tlet lock_offset = node_top_left + DVec2::new(width as f64 - GRID_SIZE as f64, 24.);\n\t\t\t\tlet subpath = Subpath::new_rounded_rectangle(DVec2::new(-12., -12.) + lock_offset, DVec2::new(12., 12.) + lock_offset, [3.; 4]);\n\t\t\t\tSome(ClickTarget::new_with_subpath(subpath, 0.))\n\t\t\t} else {\n\t\t\t\tNone\n\t\t\t};\n\n\t\t\t// Update grip button click target, which is positioned to the left of the leftmost icon\n\t\t\tlet icons_width = if locked { GRID_SIZE as f64 } else { 0. };\n\t\t\tlet grip_offset_right_edge = node_top_left + DVec2::new(width as f64 - (GRID_SIZE as f64) / 2. - icons_width, 24.);\n\t\t\tlet subpath = Subpath::new_rounded_rectangle(DVec2::new(-8., -12.) + grip_offset_right_edge, DVec2::new(0., 12.) + grip_offset_right_edge, [0.; 4]);\n\t\t\tlet grip_click_target = ClickTarget::new_with_subpath(subpath, 0.);\n\n\t\t\t// Create layer click target, which is contains the layer and the chain background\n\t\t\tlet chain_width_grid_spaces = self.chain_width(node_id, network_path);\n\n\t\t\tlet node_bottom_right = node_top_left + DVec2::new(width as f64, height as f64);\n\t\t\tlet chain_top_left = node_top_left - DVec2::new((chain_width_grid_spaces * crate::consts::GRID_SIZE) as f64, 0.);\n\t\t\tlet radius = 10.;\n\t\t\tlet subpath = Subpath::new_rounded_rectangle(chain_top_left, node_bottom_right, [radius; 4]);\n\t\t\tlet node_click_target = ClickTarget::new_with_subpath(subpath, 0.);\n\n\t\t\tDocumentNodeClickTargets {\n\t\t\t\tnode_click_target,\n\t\t\t\tport_click_targets,\n\t\t\t\tnode_type_metadata: NodeTypeClickTargets::Layer(LayerClickTargets {\n\t\t\t\t\tvisibility_click_target,\n\t\t\t\t\tlock_click_target,\n\t\t\t\t\tgrip_click_target,\n\t\t\t\t}),\n\t\t\t}\n\t\t};\n\n\t\tlet Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get nested node_metadata in load_node_click_targets\");\n\t\t\treturn;\n\t\t};\n\t\tnode_metadata.transient_metadata.click_targets = TransientMetadata::Loaded(document_node_click_targets);\n\t}\n\n\tpub fn node_bounding_box(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option<[DVec2; 2]> {\n\t\tself.node_click_targets(node_id, network_path)\n\t\t\t.and_then(|transient_node_metadata| transient_node_metadata.node_click_target.bounding_box())\n\t}\n\n\tpub fn try_get_node_bounding_box(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<[DVec2; 2]> {\n\t\tself.try_get_node_click_targets(node_id, network_path)\n\t\t\t.and_then(|transient_node_metadata| transient_node_metadata.node_click_target.bounding_box())\n\t}\n\n\tpub fn try_load_all_node_click_targets(&mut self, network_path: &[NodeId]) {\n\t\tlet Some(network) = self.nested_network(network_path) else {\n\t\t\tlog::error!(\"Could not get network in load_all_node_click_targets\");\n\t\t\treturn;\n\t\t};\n\t\tfor node_id in network.nodes.keys().cloned().collect::<Vec<_>>() {\n\t\t\tself.try_load_node_click_targets(&node_id, network_path);\n\t\t}\n\t}\n\n\t/// Get the top left position in node graph coordinates for a node by recursively iterating downstream through cached positions, which means the iteration can be broken once a known position is reached.\n\tpub fn position_from_downstream_node(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option<IVec2> {\n\t\tlet Some(node_metadata) = self.node_metadata(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get nested node_metadata in position_from_downstream_node\");\n\t\t\treturn None;\n\t\t};\n\t\tmatch &node_metadata.persistent_metadata.node_type_metadata {\n\t\t\tNodeTypePersistentMetadata::Layer(layer_metadata) => {\n\t\t\t\tmatch layer_metadata.position {\n\t\t\t\t\tLayerPosition::Absolute(position) => Some(position),\n\t\t\t\t\tLayerPosition::Stack(y_offset) => {\n\t\t\t\t\t\tlet Some(downstream_node_connectors) = self\n\t\t\t\t\t\t\t.outward_wires(network_path)\n\t\t\t\t\t\t\t.and_then(|outward_wires| outward_wires.get(&OutputConnector::node(*node_id, 0)))\n\t\t\t\t\t\t\t.cloned()\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tlog::error!(\"Could not get downstream node in position_from_downstream_node\");\n\t\t\t\t\t\t\treturn None;\n\t\t\t\t\t\t};\n\t\t\t\t\t\tlet downstream_connector = downstream_node_connectors\n\t\t\t\t\t\t\t.iter()\n\t\t\t\t\t\t\t.find_map(|input_connector| input_connector.node_id().map(|node_id| (node_id, input_connector.input_index())));\n\n\t\t\t\t\t\tlet Some((downstream_node_id, _)) = downstream_connector else {\n\t\t\t\t\t\t\tlog::error!(\"Could not get downstream node input connector for node {node_id}\");\n\t\t\t\t\t\t\treturn None;\n\t\t\t\t\t\t};\n\t\t\t\t\t\t// Get the height of the node to ensure nodes do not overlap\n\t\t\t\t\t\tlet Some(downstream_node_height) = self.height_from_click_target(&downstream_node_id, network_path) else {\n\t\t\t\t\t\t\tlog::error!(\"Could not get click target height in position_from_downstream_node\");\n\t\t\t\t\t\t\treturn None;\n\t\t\t\t\t\t};\n\t\t\t\t\t\tself.position(&downstream_node_id, network_path)\n\t\t\t\t\t\t\t.map(|position| position + IVec2::new(0, 1 + downstream_node_height as i32 + y_offset as i32))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tNodeTypePersistentMetadata::Node(node_metadata) => {\n\t\t\t\tmatch node_metadata.position {\n\t\t\t\t\tNodePosition::Absolute(position) => Some(position),\n\t\t\t\t\tNodePosition::Chain => {\n\t\t\t\t\t\t// Iterate through primary flow to find the first Layer\n\t\t\t\t\t\tlet mut current_node_id = *node_id;\n\t\t\t\t\t\tlet mut node_distance_from_layer = 1;\n\t\t\t\t\t\tloop {\n\t\t\t\t\t\t\t// TODO: Use root node to restore if previewing\n\t\t\t\t\t\t\tlet Some(downstream_node_connectors) = self\n\t\t\t\t\t\t\t\t.outward_wires(network_path)\n\t\t\t\t\t\t\t\t.and_then(|outward_wires| outward_wires.get(&OutputConnector::node(current_node_id, 0)))\n\t\t\t\t\t\t\t\t.cloned()\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tlog::error!(\"Could not get downstream node for node {node_id} with Position::Chain\");\n\t\t\t\t\t\t\t\treturn None;\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tlet Some(downstream_node_id) = downstream_node_connectors.iter().find_map(|input_connector| {\n\t\t\t\t\t\t\t\tif let InputConnector::Node { node_id, input_index } = input_connector {\n\t\t\t\t\t\t\t\t\tlet downstream_input_index = if self.is_layer(node_id, network_path) { 1 } else { 0 };\n\t\t\t\t\t\t\t\t\tif *input_index == downstream_input_index { Some(node_id) } else { None }\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tNone\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}) else {\n\t\t\t\t\t\t\t\tlog::error!(\"Could not get downstream node input connector with input index 1 for node with Position::Chain\");\n\t\t\t\t\t\t\t\treturn None;\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tlet Some(downstream_node_metadata) = self.network_metadata(network_path)?.persistent_metadata.node_metadata.get(downstream_node_id) else {\n\t\t\t\t\t\t\t\tlog::error!(\"Downstream node metadata not found in node_metadata for node with Position::Chain\");\n\t\t\t\t\t\t\t\treturn None;\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tif downstream_node_metadata.persistent_metadata.is_layer() {\n\t\t\t\t\t\t\t\t// Get the position of the layer\n\t\t\t\t\t\t\t\tlet layer_position = self.position(downstream_node_id, network_path)?;\n\t\t\t\t\t\t\t\treturn Some(layer_position + IVec2::new(-node_distance_from_layer * 7, 0));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tnode_distance_from_layer += 1;\n\t\t\t\t\t\t\tcurrent_node_id = *downstream_node_id;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn unload_node_click_targets(&mut self, node_id: &NodeId, network_path: &[NodeId]) {\n\t\tlet Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get nested node_metadata in unload_node_click_target\");\n\t\t\treturn;\n\t\t};\n\t\tnode_metadata.transient_metadata.click_targets.unload();\n\t\tself.unload_wires_for_node(node_id, network_path);\n\t}\n\n\tpub fn unload_upstream_node_click_targets(&mut self, node_ids: Vec<NodeId>, network_path: &[NodeId]) {\n\t\tlet upstream_nodes = self.upstream_flow_back_from_nodes(node_ids, network_path, FlowType::UpstreamFlow).collect::<Vec<_>>();\n\n\t\tfor upstream_id in &upstream_nodes {\n\t\t\tself.unload_node_click_targets(upstream_id, network_path);\n\t\t}\n\t}\n\n\tpub fn unload_all_nodes_click_targets(&mut self, network_path: &[NodeId]) {\n\t\tlet Some(network) = self.nested_network(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network in unload_all_nodes_click_targets\");\n\t\t\treturn;\n\t\t};\n\t\tlet upstream_nodes = network.nodes.keys().cloned().collect::<Vec<_>>();\n\n\t\tfor upstream_id in &upstream_nodes {\n\t\t\tself.unload_node_click_targets(upstream_id, network_path);\n\t\t}\n\t}\n}\n\n// Helper functions for mutable getters\nimpl NodeNetworkInterface {\n\tpub fn upstream_chain_nodes(&self, network_path: &[NodeId]) -> Vec<NodeId> {\n\t\tlet Some(selected_nodes) = self.selected_nodes_in_nested_network(network_path) else {\n\t\t\tlog::error!(\"Could not get selected nodes in upstream_chain_nodes\");\n\t\t\treturn Vec::new();\n\t\t};\n\t\tlet mut all_selected_nodes = selected_nodes.selected_nodes().cloned().collect::<Vec<_>>();\n\t\tfor selected_node_id in selected_nodes.selected_nodes() {\n\t\t\tif self.is_layer(selected_node_id, network_path) {\n\t\t\t\tlet unique_upstream_chain = self\n\t\t\t\t\t.upstream_flow_back_from_nodes(vec![*selected_node_id], network_path, FlowType::HorizontalFlow)\n\t\t\t\t\t.skip(1)\n\t\t\t\t\t.take_while(|node_id| self.is_chain(node_id, network_path))\n\t\t\t\t\t.filter(|upstream_node| all_selected_nodes.iter().all(|new_selected_node| new_selected_node != upstream_node))\n\t\t\t\t\t.collect::<Vec<_>>();\n\t\t\t\tall_selected_nodes.extend(unique_upstream_chain);\n\t\t\t}\n\t\t}\n\t\tall_selected_nodes\n\t}\n\n\tpub fn collect_frontend_click_targets(&mut self, network_path: &[NodeId]) -> FrontendClickTargets {\n\t\tlet mut all_node_click_targets = Vec::new();\n\t\tlet mut connector_click_targets = Vec::new();\n\t\tlet mut icon_click_targets = Vec::new();\n\t\tlet Some(network_metadata) = self.network_metadata(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata in collect_frontend_click_targets\");\n\t\t\treturn FrontendClickTargets::default();\n\t\t};\n\t\tlet nodes = network_metadata.persistent_metadata.node_metadata.keys().copied().collect::<Vec<_>>();\n\t\tif let Some(import_export_click_targets) = self.import_export_ports(network_path).cloned() {\n\t\t\tfor port in import_export_click_targets.click_targets() {\n\t\t\t\tif let ClickTargetType::Subpath(subpath) = port.target_type() {\n\t\t\t\t\tconnector_click_targets.push(subpath.to_bezpath().to_svg());\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tnodes.into_iter().for_each(|node_id| {\n\t\t\tif let Some(node_click_targets) = self.node_click_targets(&node_id, network_path) {\n\t\t\t\tlet mut node_path = String::new();\n\n\t\t\t\tif let ClickTargetType::Subpath(subpath) = node_click_targets.node_click_target.target_type() {\n\t\t\t\t\tnode_path.push_str(subpath.to_bezpath().to_svg().as_str())\n\t\t\t\t}\n\t\t\t\tall_node_click_targets.push((node_id, node_path));\n\t\t\t\tfor port in node_click_targets.port_click_targets.click_targets() {\n\t\t\t\t\tif let ClickTargetType::Subpath(subpath) = port.target_type() {\n\t\t\t\t\t\tconnector_click_targets.push(subpath.to_bezpath().to_svg());\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif let NodeTypeClickTargets::Layer(layer_metadata) = &node_click_targets.node_type_metadata {\n\t\t\t\t\t// Visibility button (eye icon)\n\t\t\t\t\tif let ClickTargetType::Subpath(subpath) = layer_metadata.visibility_click_target.target_type() {\n\t\t\t\t\t\ticon_click_targets.push(subpath.to_bezpath().to_svg());\n\t\t\t\t\t}\n\t\t\t\t\t// Lock button (padlock icon), only when the layer is locked\n\t\t\t\t\tif let Some(lock_click_target) = &layer_metadata.lock_click_target\n\t\t\t\t\t\t&& let ClickTargetType::Subpath(subpath) = lock_click_target.target_type()\n\t\t\t\t\t{\n\t\t\t\t\t\ticon_click_targets.push(subpath.to_bezpath().to_svg());\n\t\t\t\t\t}\n\t\t\t\t\t// Drag grip (dotted symbol)\n\t\t\t\t\tif let ClickTargetType::Subpath(subpath) = layer_metadata.grip_click_target.target_type() {\n\t\t\t\t\t\ticon_click_targets.push(subpath.to_bezpath().to_svg());\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\tlet mut layer_click_targets = Vec::new();\n\t\tlet mut node_click_targets = Vec::new();\n\t\tall_node_click_targets.into_iter().for_each(|(node_id, path)| {\n\t\t\tif self.is_layer(&node_id, network_path) {\n\t\t\t\tlayer_click_targets.push(path);\n\t\t\t} else {\n\t\t\t\tnode_click_targets.push(path);\n\t\t\t}\n\t\t});\n\n\t\tlet bounds = self.all_nodes_bounding_box(network_path).cloned().unwrap_or([DVec2::ZERO, DVec2::ZERO]);\n\t\tlet rect = Subpath::<PointId>::new_rectangle(bounds[0], bounds[1]);\n\t\tlet all_nodes_bounding_box = rect.to_bezpath().to_svg();\n\n\t\tlet mut modify_import_export = Vec::new();\n\t\tif let Some(modify_import_export_click_targets) = self.modify_import_export(network_path) {\n\t\t\tfor click_target in modify_import_export_click_targets\n\t\t\t\t.remove_imports_exports\n\t\t\t\t.click_targets()\n\t\t\t\t.chain(modify_import_export_click_targets.reorder_imports_exports.click_targets())\n\t\t\t{\n\t\t\t\tif let ClickTargetType::Subpath(subpath) = click_target.target_type() {\n\t\t\t\t\tmodify_import_export.push(subpath.to_bezpath().to_svg());\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tFrontendClickTargets {\n\t\t\tnode_click_targets,\n\t\t\tlayer_click_targets,\n\t\t\tconnector_click_targets,\n\t\t\ticon_click_targets,\n\t\t\tall_nodes_bounding_box,\n\t\t\tmodify_import_export,\n\t\t}\n\t}\n\n\tpub fn set_document_to_viewport_transform(&mut self, transform: DAffine2) {\n\t\tself.document_metadata.document_to_viewport = transform;\n\t}\n\n\tpub fn is_eligible_to_be_layer(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {\n\t\tlet Some(node) = self.document_node(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get node {node_id} in is_eligible_to_be_layer\");\n\t\t\treturn false;\n\t\t};\n\t\tlet input_count = node.inputs.iter().take(2).filter(|input| input.is_exposed()).count();\n\t\tlet parameters_hidden = node.inputs.iter().skip(2).all(|input| !input.is_exposed());\n\t\tlet output_count = self.number_of_outputs(node_id, network_path);\n\n\t\t!self.hidden_primary_output(node_id, network_path) && output_count == 1 && (input_count <= 2) && parameters_hidden\n\t}\n\n\tpub fn node_graph_ptz(&self, network_path: &[NodeId]) -> Option<&PTZ> {\n\t\tlet Some(network_metadata) = self.network_metadata(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata in node_graph_ptz_mut\");\n\t\t\treturn None;\n\t\t};\n\t\tSome(&network_metadata.persistent_metadata.navigation_metadata.node_graph_ptz)\n\t}\n\n\tpub fn node_graph_ptz_mut(&mut self, network_path: &[NodeId]) -> Option<&mut PTZ> {\n\t\tlet Some(network_metadata) = self.network_metadata_mut(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata in node_graph_ptz_mut\");\n\t\t\treturn None;\n\t\t};\n\t\tSome(&mut network_metadata.persistent_metadata.navigation_metadata.node_graph_ptz)\n\t}\n\n\t// TODO: Optimize getting click target intersections from click by using a spacial data structure like a quadtree instead of linear search\n\t/// Click target getter methods\n\tpub fn node_from_click(&mut self, click: DVec2, network_path: &[NodeId]) -> Option<NodeId> {\n\t\tlet Some(network_metadata) = self.network_metadata(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata in node_from_click\");\n\t\t\treturn None;\n\t\t};\n\t\tlet Some(network) = self.nested_network(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network in node_from_click\");\n\t\t\treturn None;\n\t\t};\n\n\t\tlet point = network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.inverse().transform_point2(click);\n\t\tlet nodes = network.nodes.keys().copied().collect::<Vec<_>>();\n\t\tlet clicked_nodes = nodes\n\t\t\t.iter()\n\t\t\t.filter(|node_id| {\n\t\t\t\tself.node_click_targets(node_id, network_path)\n\t\t\t\t\t.is_some_and(|transient_node_metadata| transient_node_metadata.node_click_target.intersect_point_no_stroke(point))\n\t\t\t})\n\t\t\t.cloned()\n\t\t\t.collect::<Vec<_>>();\n\t\t// Since nodes are placed on top of layer chains, find the first non layer node that was clicked, and if there way no non layer nodes clicked, then find the first layer node that was clicked\n\t\tclicked_nodes\n\t\t\t.iter()\n\t\t\t.find_map(|node_id| {\n\t\t\t\tlet Some(node_metadata) = self.network_metadata(network_path)?.persistent_metadata.node_metadata.get(node_id) else {\n\t\t\t\t\tlog::error!(\"Could not get node_metadata for node {node_id}\");\n\t\t\t\t\treturn None;\n\t\t\t\t};\n\t\t\t\tif !node_metadata.persistent_metadata.is_layer() { Some(*node_id) } else { None }\n\t\t\t})\n\t\t\t.or_else(|| clicked_nodes.into_iter().next())\n\t}\n\n\tpub fn layer_click_target_from_click(&mut self, click: DVec2, click_target_type: LayerClickTargetTypes, network_path: &[NodeId]) -> Option<NodeId> {\n\t\tlet Some(network_metadata) = self.network_metadata(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata in visibility_from_click\");\n\t\t\treturn None;\n\t\t};\n\t\tlet Some(network) = self.nested_network(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network in visibility_from_click\");\n\t\t\treturn None;\n\t\t};\n\n\t\tlet point = network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.inverse().transform_point2(click);\n\t\tlet node_ids: Vec<_> = network.nodes.keys().copied().collect();\n\n\t\tnode_ids\n\t\t\t.iter()\n\t\t\t.filter_map(|node_id| {\n\t\t\t\tself.node_click_targets(node_id, network_path).and_then(|transient_node_metadata| {\n\t\t\t\t\tif let NodeTypeClickTargets::Layer(layer) = &transient_node_metadata.node_type_metadata {\n\t\t\t\t\t\tmatch click_target_type {\n\t\t\t\t\t\t\tLayerClickTargetTypes::Visibility => layer.visibility_click_target.intersect_point_no_stroke(point).then_some(*node_id),\n\t\t\t\t\t\t\tLayerClickTargetTypes::Lock => layer.lock_click_target.as_ref().and_then(|target| target.intersect_point_no_stroke(point).then_some(*node_id)),\n\t\t\t\t\t\t\tLayerClickTargetTypes::Grip => layer.grip_click_target.intersect_point_no_stroke(point).then_some(*node_id),\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tNone\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\t\t\t.next()\n\t}\n\n\tpub fn input_connector_from_click(&mut self, click: DVec2, network_path: &[NodeId]) -> Option<InputConnector> {\n\t\tlet Some(network_metadata) = self.network_metadata(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata in input_connector_from_click\");\n\t\t\treturn None;\n\t\t};\n\t\tlet Some(network) = self.nested_network(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network in input_connector_from_click\");\n\t\t\treturn None;\n\t\t};\n\n\t\tlet point = network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.inverse().transform_point2(click);\n\t\tnetwork\n\t\t\t.nodes\n\t\t\t.keys()\n\t\t\t.copied()\n\t\t\t.collect::<Vec<_>>()\n\t\t\t.iter()\n\t\t\t.filter_map(|node_id| {\n\t\t\t\tself.node_click_targets(node_id, network_path).and_then(|transient_node_metadata| {\n\t\t\t\t\ttransient_node_metadata\n\t\t\t\t\t\t.port_click_targets\n\t\t\t\t\t\t.clicked_input_port_from_point(point)\n\t\t\t\t\t\t.map(|port| InputConnector::node(*node_id, port))\n\t\t\t\t})\n\t\t\t})\n\t\t\t.next()\n\t\t\t.or_else(|| {\n\t\t\t\tself.import_export_ports(network_path)\n\t\t\t\t\t.and_then(|import_export_ports| import_export_ports.clicked_input_port_from_point(point).map(InputConnector::Export))\n\t\t\t})\n\t}\n\n\tpub fn output_connector_from_click(&mut self, click: DVec2, network_path: &[NodeId]) -> Option<OutputConnector> {\n\t\tlet Some(network_metadata) = self.network_metadata(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata in output_connector_from_click\");\n\t\t\treturn None;\n\t\t};\n\t\tlet Some(network) = self.nested_network(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network in output_connector_from_click\");\n\t\t\treturn None;\n\t\t};\n\n\t\tlet point = network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.inverse().transform_point2(click);\n\t\tlet nodes = network.nodes.keys().copied().collect::<Vec<_>>();\n\t\tnodes\n\t\t\t.iter()\n\t\t\t.filter_map(|node_id| {\n\t\t\t\tself.node_click_targets(node_id, network_path).and_then(|transient_node_metadata| {\n\t\t\t\t\ttransient_node_metadata\n\t\t\t\t\t\t.port_click_targets\n\t\t\t\t\t\t.clicked_output_port_from_point(point)\n\t\t\t\t\t\t.map(|output_index| OutputConnector::node(*node_id, output_index))\n\t\t\t\t})\n\t\t\t})\n\t\t\t.next()\n\t\t\t.or_else(|| {\n\t\t\t\tself.import_export_ports(network_path)\n\t\t\t\t\t.and_then(|import_export_ports| import_export_ports.clicked_output_port_from_point(point).map(OutputConnector::Import))\n\t\t\t})\n\t}\n\n\tpub fn input_position(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option<DVec2> {\n\t\tmatch input_connector {\n\t\t\tInputConnector::Node { node_id, input_index } => self\n\t\t\t\t.node_click_targets(node_id, network_path)\n\t\t\t\t.and_then(|transient_node_metadata| transient_node_metadata.port_click_targets.input_port_position(*input_index)),\n\t\t\tInputConnector::Export(export_index) => self\n\t\t\t\t.import_export_ports(network_path)\n\t\t\t\t.and_then(|import_export_ports| import_export_ports.input_port_position(*export_index)),\n\t\t}\n\t}\n\n\tpub fn output_position(&mut self, output_connector: &OutputConnector, network_path: &[NodeId]) -> Option<DVec2> {\n\t\tmatch output_connector {\n\t\t\tOutputConnector::Node { node_id, output_index } => self\n\t\t\t\t.node_click_targets(node_id, network_path)\n\t\t\t\t.and_then(|transient_node_metadata| transient_node_metadata.port_click_targets.output_port_position(*output_index)),\n\t\t\tOutputConnector::Import(import_index) => self\n\t\t\t\t.import_export_ports(network_path)\n\t\t\t\t.and_then(|import_export_ports| import_export_ports.output_port_position(*import_index)),\n\t\t}\n\t}\n\n\t/// Get the combined bounding box of the click targets of the selected nodes in the node graph in viewport space\n\tpub fn selected_nodes_bounding_box_viewport(&mut self, network_path: &[NodeId]) -> Option<[DVec2; 2]> {\n\t\t// Always get the bounding box for nodes in the currently viewed network\n\t\tlet Some(network_metadata) = self.network_metadata(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata in selected_nodes_bounding_box_viewport\");\n\t\t\treturn None;\n\t\t};\n\t\tlet node_graph_to_viewport = network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport;\n\t\tself.selected_nodes_bounding_box(network_path)\n\t\t\t.map(|[a, b]| [node_graph_to_viewport.transform_point2(a), node_graph_to_viewport.transform_point2(b)])\n\t}\n\n\tpub fn selected_layers_artwork_bounding_box_viewport(&self) -> Option<[DVec2; 2]> {\n\t\tself.selected_nodes()\n\t\t\t.0\n\t\t\t.iter()\n\t\t\t.filter(|node| self.is_layer(node, &[]))\n\t\t\t.filter_map(|layer| self.document_metadata.bounding_box_viewport(LayerNodeIdentifier::new(*layer, self)))\n\t\t\t.reduce(Quad::combine_bounds)\n\t}\n\n\tpub fn selected_unlocked_layers_bounding_box_viewport(&self) -> Option<[DVec2; 2]> {\n\t\tself.selected_nodes()\n\t\t\t.0\n\t\t\t.iter()\n\t\t\t.filter(|node| self.is_layer(node, &[]) && !self.is_locked(node, &[]))\n\t\t\t.filter_map(|layer| self.document_metadata.bounding_box_viewport(LayerNodeIdentifier::new(*layer, self)))\n\t\t\t.reduce(Quad::combine_bounds)\n\t}\n\n\t/// Get the combined bounding box of the click targets of the selected nodes in the node graph in layer space\n\tpub fn selected_nodes_bounding_box(&mut self, network_path: &[NodeId]) -> Option<[DVec2; 2]> {\n\t\tlet Some(selected_nodes) = self.selected_nodes_in_nested_network(network_path) else {\n\t\t\tlog::error!(\"Could not get selected nodes in selected_nodes_bounding_box_viewport\");\n\t\t\treturn None;\n\t\t};\n\t\tselected_nodes\n\t\t\t.selected_nodes()\n\t\t\t.cloned()\n\t\t\t.collect::<Vec<_>>()\n\t\t\t.iter()\n\t\t\t.filter_map(|node_id| {\n\t\t\t\tself.node_click_targets(node_id, network_path)\n\t\t\t\t\t.and_then(|transient_node_metadata| transient_node_metadata.node_click_target.bounding_box())\n\t\t\t})\n\t\t\t.reduce(graphene_std::renderer::Quad::combine_bounds)\n\t}\n\n\t/// Gets the bounding box in viewport coordinates for each node in the node graph\n\tpub fn graph_bounds_viewport_space(&mut self, network_path: &[NodeId]) -> Option<[DVec2; 2]> {\n\t\tlet bounds = *self.all_nodes_bounding_box(network_path)?;\n\t\tlet Some(network_metadata) = self.network_metadata(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata in graph_bounds_viewport_space\");\n\t\t\treturn None;\n\t\t};\n\n\t\tlet bounding_box_subpath = Subpath::<PointId>::new_rectangle(bounds[0], bounds[1]);\n\t\tbounding_box_subpath.bounding_box_with_transform(network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport)\n\t}\n\n\tpub fn collect_layer_widths(&mut self, network_path: &[NodeId]) -> (HashMap<NodeId, u32>, HashMap<NodeId, u32>, HashMap<NodeId, bool>) {\n\t\tlet Some(network_metadata) = self.network_metadata(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata in collect_layer_widths\");\n\t\t\treturn (HashMap::new(), HashMap::new(), HashMap::new());\n\t\t};\n\t\tlet nodes = network_metadata\n\t\t\t.persistent_metadata\n\t\t\t.node_metadata\n\t\t\t.iter()\n\t\t\t.filter_map(|(node_id, _)| if self.is_layer(node_id, network_path) { Some(*node_id) } else { None })\n\t\t\t.collect::<Vec<_>>();\n\t\tlet layer_widths = nodes\n\t\t\t.iter()\n\t\t\t.filter_map(|node_id| self.layer_width(node_id, network_path).map(|layer_width| (*node_id, layer_width)))\n\t\t\t.collect::<HashMap<NodeId, u32>>();\n\t\tlet chain_widths = nodes.iter().map(|node_id| (*node_id, self.chain_width(node_id, network_path))).collect::<HashMap<NodeId, u32>>();\n\t\tlet has_left_input_wire = nodes\n\t\t\t.iter()\n\t\t\t.map(|node_id| {\n\t\t\t\t(\n\t\t\t\t\t*node_id,\n\t\t\t\t\t!self\n\t\t\t\t\t\t.upstream_flow_back_from_nodes(vec![*node_id], network_path, FlowType::HorizontalFlow)\n\t\t\t\t\t\t.skip(1)\n\t\t\t\t\t\t.all(|node_id| self.is_chain(&node_id, network_path)),\n\t\t\t\t)\n\t\t\t})\n\t\t\t.collect::<HashMap<NodeId, bool>>();\n\n\t\t(layer_widths, chain_widths, has_left_input_wire)\n\t}\n\n\tpub fn compute_modified_vector(&self, layer: LayerNodeIdentifier) -> Option<Vector> {\n\t\tlet graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, self);\n\n\t\tif let Some(path_node) = graph_layer.upstream_visible_node_id_from_name_in_layer(&DefinitionIdentifier::Network(\"Path\".into()))\n\t\t\t&& let Some(vector) = self.document_metadata.vector_modify.get(&path_node)\n\t\t{\n\t\t\tlet mut modified = vector.clone();\n\n\t\t\tlet path_node = self.document_network().nodes.get(&path_node);\n\t\t\tlet modification_input = path_node.and_then(|node: &DocumentNode| node.inputs.get(1)).and_then(|input| input.as_value());\n\t\t\tif let Some(TaggedValue::VectorModification(modification)) = modification_input {\n\t\t\t\tmodification.apply(&mut modified);\n\t\t\t}\n\t\t\treturn Some(modified);\n\t\t}\n\n\t\tself.document_metadata.layer_vector_data.get(&layer).map(|arc| arc.as_ref().clone())\n\t}\n\n\t/// Loads the structure of layer nodes from a node graph.\n\tpub fn load_structure(&mut self) {\n\t\tself.document_metadata.structure = HashMap::from_iter([(LayerNodeIdentifier::ROOT_PARENT, NodeRelations::default())]);\n\n\t\t// Only load structure if there is a root node\n\t\tlet Some(root_node) = self.root_node(&[]) else { return };\n\n\t\tlet Some(first_root_layer) = self\n\t\t\t.upstream_flow_back_from_nodes(vec![root_node.node_id], &[], FlowType::PrimaryFlow)\n\t\t\t.find_map(|node_id| if self.is_layer(&node_id, &[]) { Some(LayerNodeIdentifier::new(node_id, self)) } else { None })\n\t\telse {\n\t\t\treturn;\n\t\t};\n\t\t// Should refer to output node\n\t\tlet mut awaiting_horizontal_flow = vec![(first_root_layer.to_node(), first_root_layer)];\n\t\tlet mut awaiting_primary_flow = vec![];\n\n\t\twhile let Some((horizontal_root_node_id, mut parent_layer_node)) = awaiting_horizontal_flow.pop() {\n\t\t\tlet horizontal_flow_iter = self.upstream_flow_back_from_nodes(vec![horizontal_root_node_id], &[], FlowType::HorizontalFlow);\n\t\t\tlet mut children = Vec::new();\n\n\t\t\t// Special handling for the root layer, since it should not be skipped\n\t\t\tif horizontal_root_node_id == first_root_layer.to_node() {\n\t\t\t\tfor current_node_id in horizontal_flow_iter {\n\t\t\t\t\tif self.is_layer(&current_node_id, &[]) {\n\t\t\t\t\t\tlet current_layer_node = LayerNodeIdentifier::new(current_node_id, self);\n\t\t\t\t\t\tif !self.document_metadata.structure.contains_key(&current_layer_node) {\n\t\t\t\t\t\t\tif current_node_id == first_root_layer.to_node() {\n\t\t\t\t\t\t\t\tawaiting_primary_flow.push((current_node_id, LayerNodeIdentifier::ROOT_PARENT));\n\t\t\t\t\t\t\t\tchildren.push((LayerNodeIdentifier::ROOT_PARENT, current_layer_node));\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tawaiting_primary_flow.push((current_node_id, parent_layer_node));\n\t\t\t\t\t\t\t\tchildren.push((parent_layer_node, current_layer_node));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tparent_layer_node = current_layer_node;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Skip the horizontal_root_node_id node\n\t\t\t\tfor current_node_id in horizontal_flow_iter.skip(1) {\n\t\t\t\t\tif self.is_layer(&current_node_id, &[]) {\n\t\t\t\t\t\tlet current_layer_node = LayerNodeIdentifier::new(current_node_id, self);\n\t\t\t\t\t\tif !self.document_metadata.structure.contains_key(&current_layer_node) {\n\t\t\t\t\t\t\tawaiting_primary_flow.push((current_node_id, parent_layer_node));\n\t\t\t\t\t\t\tchildren.push((parent_layer_node, current_layer_node));\n\t\t\t\t\t\t\tparent_layer_node = current_layer_node;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (parent, child) in children {\n\t\t\t\tparent.push_child(&mut self.document_metadata, child);\n\t\t\t}\n\n\t\t\twhile let Some((primary_root_node_id, parent_layer_node)) = awaiting_primary_flow.pop() {\n\t\t\t\tlet primary_flow_iter = self.upstream_flow_back_from_nodes(vec![primary_root_node_id], &[], FlowType::PrimaryFlow);\n\t\t\t\t// Skip the primary_root_node_id node\n\t\t\t\tlet mut children = Vec::new();\n\t\t\t\tfor current_node_id in primary_flow_iter.skip(1) {\n\t\t\t\t\tif self.is_layer(&current_node_id, &[]) {\n\t\t\t\t\t\t// Create a new layer for the top of each stack, and add it as a child to the previous parent\n\t\t\t\t\t\tlet current_layer_node = LayerNodeIdentifier::new(current_node_id, self);\n\t\t\t\t\t\tif !self.document_metadata.structure.contains_key(&current_layer_node) {\n\t\t\t\t\t\t\tchildren.push(current_layer_node);\n\n\t\t\t\t\t\t\t// The layer nodes for the horizontal flow is itself\n\t\t\t\t\t\t\tawaiting_horizontal_flow.push((current_node_id, current_layer_node));\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor child in children {\n\t\t\t\t\tparent_layer_node.push_child(&mut self.document_metadata, child);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tlet nodes: HashSet<NodeId> = self.document_network().nodes.keys().cloned().collect::<HashSet<_>>();\n\n\t\tself.document_metadata.upstream_footprints.retain(|node, _| nodes.contains(node));\n\t\tself.document_metadata.local_transforms.retain(|node, _| nodes.contains(node));\n\t\tself.document_metadata.vector_modify.retain(|node, _| nodes.contains(node));\n\t\tself.document_metadata.click_targets.retain(|layer, _| self.document_metadata.structure.contains_key(layer));\n\t}\n\n\t/// Update the cached transforms of the layers\n\tpub fn update_transforms(&mut self, upstream_footprints: HashMap<NodeId, Footprint>, local_transforms: HashMap<NodeId, DAffine2>) {\n\t\tself.document_metadata.upstream_footprints = upstream_footprints;\n\t\tself.document_metadata.local_transforms = local_transforms;\n\t}\n\n\t/// Update the cached first instance source id of the layers\n\tpub fn update_first_element_source_id(&mut self, new: HashMap<NodeId, Option<NodeId>>) {\n\t\tself.document_metadata.first_element_source_ids = new;\n\t}\n\n\t/// Update the cached click targets of the layers\n\tpub fn update_click_targets(&mut self, new_click_targets: HashMap<LayerNodeIdentifier, Vec<Arc<ClickTarget>>>) {\n\t\tself.document_metadata.click_targets = new_click_targets;\n\t}\n\n\t/// Update the cached clip targets of the layers\n\tpub fn update_clip_targets(&mut self, new_clip_targets: HashSet<NodeId>) {\n\t\tself.document_metadata.clip_targets = new_clip_targets;\n\t}\n\n\t/// Update the vector modify of the layers\n\tpub fn update_vector_modify(&mut self, new_vector_modify: HashMap<NodeId, Vector>) {\n\t\tself.document_metadata.vector_modify = new_vector_modify;\n\t}\n\n\t/// Update the layer vector data (for layers without Path nodes)\n\tpub fn update_vector_data(&mut self, new_layer_vector_data: HashMap<LayerNodeIdentifier, Arc<Vector>>) {\n\t\tself.document_metadata.layer_vector_data = new_layer_vector_data;\n\t}\n}\n\n// Public mutable methods\nimpl NodeNetworkInterface {\n\tpub fn copy_all_navigation_metadata(&mut self, other_interface: &NodeNetworkInterface) {\n\t\tlet mut stack = vec![vec![]];\n\t\twhile let Some(path) = stack.pop() {\n\t\t\tlet Some(self_network_metadata) = self.network_metadata_mut(&path) else {\n\t\t\t\tcontinue;\n\t\t\t};\n\t\t\tif let Some(other_network_metadata) = other_interface.network_metadata(&path) {\n\t\t\t\tself_network_metadata.persistent_metadata.navigation_metadata = other_network_metadata.persistent_metadata.navigation_metadata.clone();\n\t\t\t}\n\n\t\t\tstack.extend(self_network_metadata.persistent_metadata.node_metadata.keys().map(|node_id| {\n\t\t\t\tlet mut current_path: Vec<NodeId> = path.clone();\n\t\t\t\tcurrent_path.push(*node_id);\n\t\t\t\tcurrent_path\n\t\t\t}));\n\t\t}\n\t}\n\n\tpub fn set_transform(&mut self, transform: DAffine2, network_path: &[NodeId]) {\n\t\tlet Some(network_metadata) = self.network_metadata_mut(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network in set_transform\");\n\t\t\treturn;\n\t\t};\n\t\tnetwork_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport = transform;\n\t\tself.unload_import_export_ports(network_path);\n\t\tself.unload_modify_import_export(network_path);\n\t}\n\n\t// This should be run whenever the pan ends, a zoom occurs, or the network is opened\n\tpub fn set_node_graph_width(&mut self, node_graph_width: f64, network_path: &[NodeId]) {\n\t\tlet Some(network_metadata) = self.network_metadata_mut(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network in set_transform\");\n\t\t\treturn;\n\t\t};\n\t\tnetwork_metadata.persistent_metadata.navigation_metadata.node_graph_width = node_graph_width;\n\t\tself.unload_import_export_ports(network_path);\n\t\tself.unload_modify_import_export(network_path);\n\t}\n\n\tpub fn vector_modify(&mut self, node_id: &NodeId, modification_type: VectorModificationType) {\n\t\tlet Some(node) = self.network_mut(&[]).unwrap().nodes.get_mut(node_id) else {\n\t\t\tlog::error!(\"Could not get node in vector_modification\");\n\t\t\treturn;\n\t\t};\n\t\t{\n\t\t\tlet mut value = node.inputs.get_mut(1).and_then(|input| input.as_value_mut());\n\t\t\tlet Some(TaggedValue::VectorModification(modification)) = value.as_deref_mut() else {\n\t\t\t\tpanic!(\"Path node does not have modification input\");\n\t\t\t};\n\n\t\t\tmodification.modify(&modification_type);\n\t\t}\n\t\tself.transaction_modified();\n\t}\n\n\t/// Inserts a new export at insert index. If the insert index is -1 it is inserted at the end. The output_name is used by the encapsulating node.\n\tpub fn add_export(&mut self, default_value: TaggedValue, insert_index: isize, output_name: &str, network_path: &[NodeId]) {\n\t\tlet Some(network) = self.network_mut(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network in add_export\");\n\t\t\treturn;\n\t\t};\n\n\t\tlet input = NodeInput::value(default_value, true);\n\t\tif insert_index == -1 {\n\t\t\tnetwork.exports.push(input);\n\t\t} else {\n\t\t\tnetwork.exports.insert(insert_index as usize, input);\n\t\t}\n\n\t\tself.transaction_modified();\n\n\t\tlet mut encapsulating_path = network_path.to_vec();\n\t\t// Set the parent node (if it exists) to be a non layer if it is no longer eligible to be a layer\n\t\tif let Some(parent_id) = encapsulating_path.pop()\n\t\t\t&& !self.is_eligible_to_be_layer(&parent_id, &encapsulating_path)\n\t\t\t&& self.is_layer(&parent_id, &encapsulating_path)\n\t\t{\n\t\t\tself.set_to_node_or_layer(&parent_id, &encapsulating_path, false);\n\t\t};\n\n\t\t// There will not be an encapsulating node if the network is the document network\n\t\tif let Some(encapsulating_node_metadata) = self.encapsulating_node_metadata_mut(network_path) {\n\t\t\tif insert_index == -1 {\n\t\t\t\tencapsulating_node_metadata.persistent_metadata.output_names.push(output_name.to_string());\n\t\t\t} else {\n\t\t\t\tencapsulating_node_metadata.persistent_metadata.output_names.insert(insert_index as usize, output_name.to_string());\n\t\t\t}\n\t\t\t// Clear the reference to the nodes definition\n\t\t\tif let Some(network_metadata) = encapsulating_node_metadata.persistent_metadata.network_metadata.as_mut() {\n\t\t\t\tnetwork_metadata.persistent_metadata.reference = None\n\t\t\t}\n\t\t};\n\n\t\t// Update the export ports and outward wires for the current network\n\t\tself.unload_import_export_ports(network_path);\n\t\tself.unload_modify_import_export(network_path);\n\t\tself.unload_outward_wires(network_path);\n\n\t\t// Update the outward wires and bounding box for all nodes in the encapsulating network\n\t\tif let Some(encapsulating_network_metadata) = self.encapsulating_network_metadata_mut(network_path) {\n\t\t\tencapsulating_network_metadata.transient_metadata.outward_wires.unload();\n\t\t\tencapsulating_network_metadata.transient_metadata.all_nodes_bounding_box.unload();\n\t\t}\n\n\t\t// Update the click targets for the encapsulating node, if it exists. There is no encapsulating node if the network is the document network\n\t\tlet mut path = network_path.to_vec();\n\t\tif let Some(encapsulating_node) = path.pop() {\n\t\t\tself.unload_node_click_targets(&encapsulating_node, &path);\n\t\t}\n\n\t\t// If the export is inserted as the first input or second input, and the parent network is the document_network, then it may have affected the document metadata structure\n\t\tif network_path.len() == 1 && (insert_index == 0 || insert_index == 1) {\n\t\t\tself.load_structure();\n\t\t}\n\t}\n\n\t/// Inserts a new input at insert index. If the insert index is -1 it is inserted at the end. The output_name is used by the encapsulating node.\n\tpub fn add_import(&mut self, default_value: TaggedValue, exposed: bool, insert_index: isize, input_name: &str, input_description: &str, network_path: &[NodeId]) {\n\t\tlet mut encapsulating_network_path = network_path.to_vec();\n\t\tlet Some(node_id) = encapsulating_network_path.pop() else {\n\t\t\tlog::error!(\"Cannot add import for document network\");\n\t\t\treturn;\n\t\t};\n\n\t\tlet Some(network) = self.network_mut(&encapsulating_network_path) else {\n\t\t\tlog::error!(\"Could not get nested network in insert_input\");\n\t\t\treturn;\n\t\t};\n\t\tlet Some(node) = network.nodes.get_mut(&node_id) else {\n\t\t\tlog::error!(\"Could not get node in insert_input\");\n\t\t\treturn;\n\t\t};\n\n\t\tlet input = NodeInput::value(default_value, exposed);\n\t\tif insert_index == -1 {\n\t\t\tnode.inputs.push(input);\n\t\t} else {\n\t\t\tnode.inputs.insert(insert_index as usize, input);\n\t\t}\n\n\t\tself.transaction_modified();\n\n\t\t// Set the node to be a non layer if it is no longer eligible to be a layer\n\t\tif !self.is_eligible_to_be_layer(&node_id, &encapsulating_network_path) && self.is_layer(&node_id, &encapsulating_network_path) {\n\t\t\tself.set_to_node_or_layer(&node_id, &encapsulating_network_path, false);\n\t\t}\n\n\t\tlet Some(node_metadata) = self.node_metadata_mut(&node_id, &encapsulating_network_path) else {\n\t\t\tlog::error!(\"Could not get node_metadata in insert_input\");\n\t\t\treturn;\n\t\t};\n\t\tlet new_input = (input_name, input_description).into();\n\t\tif insert_index == -1 {\n\t\t\tnode_metadata.persistent_metadata.input_metadata.push(new_input);\n\t\t} else {\n\t\t\tnode_metadata.persistent_metadata.input_metadata.insert(insert_index as usize, new_input);\n\t\t}\n\n\t\t// Clear the reference to the nodes definition\n\t\tif let Some(network_metadata) = node_metadata.persistent_metadata.network_metadata.as_mut() {\n\t\t\tnetwork_metadata.persistent_metadata.reference = None\n\t\t}\n\n\t\t// Update the metadata for the encapsulating node\n\t\tself.unload_node_click_targets(&node_id, &encapsulating_network_path);\n\t\tself.unload_all_nodes_bounding_box(&encapsulating_network_path);\n\t\tif encapsulating_network_path.is_empty() && (insert_index == 0 || insert_index == 1) {\n\t\t\tself.load_structure();\n\t\t}\n\n\t\t// Unload the metadata for the nested network\n\t\tself.unload_outward_wires(network_path);\n\t\tself.unload_import_export_ports(network_path);\n\t\tself.unload_modify_import_export(network_path);\n\t}\n\n\t// First disconnects the export, then removes it\n\tpub fn remove_export(&mut self, export_index: usize, network_path: &[NodeId]) {\n\t\tlet mut encapsulating_network_path = network_path.to_vec();\n\t\tlet Some(parent_id) = encapsulating_network_path.pop() else {\n\t\t\tlog::error!(\"Cannot remove export for document network\");\n\t\t\treturn;\n\t\t};\n\n\t\t// Disconnect the removed export, and handle connections to the node which had its output removed\n\t\tself.disconnect_input(&InputConnector::Export(export_index), network_path);\n\t\tlet number_of_outputs = self.number_of_outputs(&parent_id, &encapsulating_network_path);\n\t\tfor shifted_export in export_index..number_of_outputs {\n\t\t\tlet Some(encapsulating_outward_wires) = self.outward_wires(&encapsulating_network_path) else {\n\t\t\t\tlog::error!(\"Could not get outward wires in remove_export\");\n\t\t\t\treturn;\n\t\t\t};\n\t\t\tlet Some(downstream_connections_for_shifted_export) = encapsulating_outward_wires.get(&OutputConnector::node(parent_id, shifted_export)).cloned() else {\n\t\t\t\tlog::error!(\"Could not get downstream connections for shifted export in remove_export\");\n\t\t\t\treturn;\n\t\t\t};\n\t\t\tfor downstream_connection in downstream_connections_for_shifted_export {\n\t\t\t\tself.disconnect_input(&downstream_connection, &encapsulating_network_path);\n\t\t\t\tif shifted_export != export_index {\n\t\t\t\t\tself.create_wire(&OutputConnector::node(parent_id, shifted_export - 1), &downstream_connection, &encapsulating_network_path);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tlet Some(network) = self.network_mut(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network in add_export\");\n\t\t\treturn;\n\t\t};\n\t\tnetwork.exports.remove(export_index);\n\n\t\tself.transaction_modified();\n\n\t\tlet Some(encapsulating_node_metadata) = self.node_metadata_mut(&parent_id, &encapsulating_network_path) else {\n\t\t\tlog::error!(\"Could not get encapsulating node metadata in remove_export\");\n\t\t\treturn;\n\t\t};\n\t\tencapsulating_node_metadata.persistent_metadata.output_names.remove(export_index);\n\t\tif let Some(network_metadata) = encapsulating_node_metadata.persistent_metadata.network_metadata.as_mut() {\n\t\t\tnetwork_metadata.persistent_metadata.reference = None;\n\t\t}\n\n\t\t// Update the metadata for the encapsulating node\n\t\tself.unload_outward_wires(&encapsulating_network_path);\n\t\tself.unload_node_click_targets(&parent_id, &encapsulating_network_path);\n\t\tself.unload_all_nodes_bounding_box(&encapsulating_network_path);\n\t\tif !self.is_eligible_to_be_layer(&parent_id, &encapsulating_network_path) && self.is_layer(&parent_id, &encapsulating_network_path) {\n\t\t\tself.set_to_node_or_layer(&parent_id, &encapsulating_network_path, false);\n\t\t}\n\t\tif encapsulating_network_path.is_empty() {\n\t\t\tself.load_structure();\n\t\t}\n\n\t\t// Unload the metadata for the nested network\n\t\tself.unload_outward_wires(network_path);\n\t\tself.unload_import_export_ports(network_path);\n\t\tself.unload_modify_import_export(network_path);\n\t}\n\n\t// First disconnects the import, then removes it\n\tpub fn remove_import(&mut self, import_index: usize, network_path: &[NodeId]) {\n\t\tlet Some((parent_id, encapsulating_network_path)) = network_path.split_last() else {\n\t\t\tlog::error!(\"Cannot remove export for document network\");\n\t\t\treturn;\n\t\t};\n\n\t\tlet number_of_inputs = self.number_of_inputs(parent_id, encapsulating_network_path);\n\t\tlet Some(outward_wires) = self.outward_wires(network_path) else {\n\t\t\tlog::error!(\"Could not get outward wires in remove_import\");\n\t\t\treturn;\n\t\t};\n\t\tlet Some(outward_wires_for_import) = outward_wires.get(&OutputConnector::Import(import_index)).cloned() else {\n\t\t\tlog::error!(\"Could not get outward wires for import in remove_import\");\n\t\t\treturn;\n\t\t};\n\t\tlet mut new_import_mapping = Vec::new();\n\t\tfor i in (import_index + 1)..number_of_inputs {\n\t\t\tlet Some(outward_wires_for_import) = outward_wires.get(&OutputConnector::Import(i)).cloned() else {\n\t\t\t\tlog::error!(\"Could not get outward wires for import in remove_import\");\n\t\t\t\treturn;\n\t\t\t};\n\t\t\tfor upstream_input_wire in outward_wires_for_import {\n\t\t\t\tnew_import_mapping.push((OutputConnector::Import(i - 1), upstream_input_wire));\n\t\t\t}\n\t\t}\n\n\t\t// Disconnect all upstream connections\n\t\tfor outward_wire in outward_wires_for_import {\n\t\t\tself.disconnect_input(&outward_wire, network_path);\n\t\t}\n\t\t// Shift inputs connected to to imports at a higher index down one\n\t\tfor (output_connector, input_wire) in new_import_mapping {\n\t\t\tself.create_wire(&output_connector, &input_wire, network_path);\n\t\t}\n\n\t\tlet Some(network) = self.network_mut(encapsulating_network_path) else {\n\t\t\tlog::error!(\"Could not get parent node in remove_import\");\n\t\t\treturn;\n\t\t};\n\t\tlet Some(node) = network.nodes.get_mut(parent_id) else {\n\t\t\tlog::error!(\"Could not get node in remove_import\");\n\t\t\treturn;\n\t\t};\n\n\t\tnode.inputs.remove(import_index);\n\n\t\tself.transaction_modified();\n\n\t\t// There will not be an encapsulating node if the network is the document network\n\t\tlet Some(encapsulating_node_metadata) = self.node_metadata_mut(parent_id, encapsulating_network_path) else {\n\t\t\tlog::error!(\"Could not get encapsulating node metadata in remove_export\");\n\t\t\treturn;\n\t\t};\n\t\tencapsulating_node_metadata.persistent_metadata.input_metadata.remove(import_index);\n\t\tif let Some(network_metadata) = encapsulating_node_metadata.persistent_metadata.network_metadata.as_mut() {\n\t\t\tnetwork_metadata.persistent_metadata.reference = None;\n\t\t}\n\n\t\t// Update the metadata for the encapsulating node\n\t\tself.unload_outward_wires(encapsulating_network_path);\n\t\tself.unload_node_click_targets(parent_id, encapsulating_network_path);\n\t\tself.unload_all_nodes_bounding_box(encapsulating_network_path);\n\t\tif !self.is_eligible_to_be_layer(parent_id, encapsulating_network_path) && self.is_layer(parent_id, encapsulating_network_path) {\n\t\t\tself.set_to_node_or_layer(parent_id, encapsulating_network_path, false);\n\t\t}\n\t\tif encapsulating_network_path.is_empty() {\n\t\t\tself.load_structure();\n\t\t}\n\n\t\t// Unload the metadata for the nested network\n\t\tself.unload_outward_wires(network_path);\n\t\tself.unload_import_export_ports(network_path);\n\t\tself.unload_modify_import_export(network_path);\n\t}\n\n\t/// The end index is before the export is removed, so moving to the end is the length of the current exports\n\tpub fn reorder_export(&mut self, start_index: usize, mut end_index: usize, network_path: &[NodeId]) {\n\t\tlet mut encapsulating_network_path = network_path.to_vec();\n\t\tlet Some(parent_id) = encapsulating_network_path.pop() else {\n\t\t\tlog::error!(\"Could not reorder export for document network\");\n\t\t\treturn;\n\t\t};\n\n\t\tlet Some(network) = self.network_mut(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network in reorder_export\");\n\t\t\treturn;\n\t\t};\n\t\tif end_index > start_index {\n\t\t\tend_index -= 1;\n\t\t}\n\t\tlet export = network.exports.remove(start_index);\n\t\tnetwork.exports.insert(end_index, export);\n\n\t\tself.transaction_modified();\n\n\t\tlet Some(encapsulating_node_metadata) = self.node_metadata_mut(&parent_id, &encapsulating_network_path) else {\n\t\t\tlog::error!(\"Could not get encapsulating network_metadata in reorder_export\");\n\t\t\treturn;\n\t\t};\n\n\t\tlet name = encapsulating_node_metadata.persistent_metadata.output_names.remove(start_index);\n\t\tencapsulating_node_metadata.persistent_metadata.output_names.insert(end_index, name);\n\t\tif let Some(network_metadata) = encapsulating_node_metadata.persistent_metadata.network_metadata.as_mut() {\n\t\t\tnetwork_metadata.persistent_metadata.reference = None;\n\t\t}\n\n\t\t// Update the metadata for the encapsulating network\n\t\tself.unload_outward_wires(&encapsulating_network_path);\n\t\tself.unload_stack_dependents(&encapsulating_network_path);\n\n\t\t// Node input at the start index is now at the end index\n\t\tlet Some(move_to_end_index) = self\n\t\t\t.outward_wires(&encapsulating_network_path)\n\t\t\t.and_then(|outward_wires| outward_wires.get(&OutputConnector::node(parent_id, start_index)))\n\t\t\t.cloned()\n\t\telse {\n\t\t\tlog::error!(\"Could not get outward wires in reorder_export\");\n\t\t\treturn;\n\t\t};\n\t\t// Node inputs above the start index should be shifted down one\n\t\tlet last_output_index = self.number_of_outputs(&parent_id, &encapsulating_network_path) - 1;\n\t\tfor shift_output_down in (start_index + 1)..=last_output_index {\n\t\t\tlet Some(outward_wires) = self\n\t\t\t\t.outward_wires(&encapsulating_network_path)\n\t\t\t\t.and_then(|outward_wires| outward_wires.get(&OutputConnector::node(parent_id, shift_output_down)))\n\t\t\t\t.cloned()\n\t\t\telse {\n\t\t\t\tlog::error!(\"Could not get outward wires in reorder_export\");\n\t\t\t\treturn;\n\t\t\t};\n\t\t\tfor downstream_connection in &outward_wires {\n\t\t\t\tself.disconnect_input(downstream_connection, &encapsulating_network_path);\n\t\t\t\tself.create_wire(&OutputConnector::node(parent_id, shift_output_down - 1), downstream_connection, &encapsulating_network_path);\n\t\t\t}\n\t\t}\n\t\t// Node inputs at or above the end index should be shifted up one\n\t\tfor shift_output_up in (end_index..last_output_index).rev() {\n\t\t\tlet Some(outward_wires) = self\n\t\t\t\t.outward_wires(&encapsulating_network_path)\n\t\t\t\t.and_then(|outward_wires| outward_wires.get(&OutputConnector::node(parent_id, shift_output_up)))\n\t\t\t\t.cloned()\n\t\t\telse {\n\t\t\t\tlog::error!(\"Could not get outward wires in reorder_export\");\n\t\t\t\treturn;\n\t\t\t};\n\t\t\tfor downstream_connection in &outward_wires {\n\t\t\t\tself.disconnect_input(downstream_connection, &encapsulating_network_path);\n\t\t\t\tself.create_wire(&OutputConnector::node(parent_id, shift_output_up + 1), downstream_connection, &encapsulating_network_path);\n\t\t\t}\n\t\t}\n\n\t\t// Move the connections to the moved export after all other ones have been shifted\n\t\tfor downstream_connection in &move_to_end_index {\n\t\t\tself.disconnect_input(downstream_connection, &encapsulating_network_path);\n\t\t\tself.create_wire(&OutputConnector::node(parent_id, end_index), downstream_connection, &encapsulating_network_path);\n\t\t}\n\n\t\t// Update the metadata for the current network\n\t\tself.unload_outward_wires(network_path);\n\t\tself.unload_import_export_ports(network_path);\n\t\tself.unload_modify_import_export(network_path);\n\t\tself.unload_stack_dependents(network_path);\n\t}\n\n\t/// The end index is before the import is removed, so moving to the end is the length of the current imports\n\tpub fn reorder_import(&mut self, start_index: usize, mut end_index: usize, network_path: &[NodeId]) {\n\t\tlet mut encapsulating_network_path = network_path.to_vec();\n\t\tlet Some(parent_id) = encapsulating_network_path.pop() else {\n\t\t\tlog::error!(\"Could not reorder import for document network\");\n\t\t\treturn;\n\t\t};\n\n\t\tlet Some(encapsulating_network) = self.network_mut(&encapsulating_network_path) else {\n\t\t\tlog::error!(\"Could not get nested network in reorder_import\");\n\t\t\treturn;\n\t\t};\n\t\tlet Some(encapsulating_node) = encapsulating_network.nodes.get_mut(&parent_id) else {\n\t\t\tlog::error!(\"Could not get encapsulating node in reorder_import\");\n\t\t\treturn;\n\t\t};\n\n\t\tif end_index > start_index {\n\t\t\tend_index -= 1;\n\t\t}\n\t\tlet import = encapsulating_node.inputs.remove(start_index);\n\t\tencapsulating_node.inputs.insert(end_index, import);\n\n\t\tself.transaction_modified();\n\n\t\tlet Some(encapsulating_node_metadata) = self.node_metadata_mut(&parent_id, &encapsulating_network_path) else {\n\t\t\tlog::error!(\"Could not get encapsulating network_metadata in reorder_import\");\n\t\t\treturn;\n\t\t};\n\n\t\tlet properties_row = encapsulating_node_metadata.persistent_metadata.input_metadata.remove(start_index);\n\t\tencapsulating_node_metadata.persistent_metadata.input_metadata.insert(end_index, properties_row);\n\t\tif let Some(network_metadata) = encapsulating_node_metadata.persistent_metadata.network_metadata.as_mut() {\n\t\t\tnetwork_metadata.persistent_metadata.reference = None;\n\t\t}\n\n\t\t// Update the metadata for the outer network\n\t\tself.unload_outward_wires(&encapsulating_network_path);\n\t\tself.unload_stack_dependents(&encapsulating_network_path);\n\n\t\t// Node input at the start index is now at the end index\n\t\tlet Some(move_to_end_index) = self\n\t\t\t.outward_wires(network_path)\n\t\t\t.and_then(|outward_wires| outward_wires.get(&OutputConnector::Import(start_index)))\n\t\t\t.cloned()\n\t\telse {\n\t\t\tlog::error!(\"Could not get outward wires in reorder_import\");\n\t\t\treturn;\n\t\t};\n\t\t// Node inputs above the start index should be shifted down one\n\t\tlet last_import_index = self.number_of_imports(network_path) - 1;\n\t\tfor shift_output_down in (start_index + 1)..=last_import_index {\n\t\t\tlet Some(outward_wires) = self\n\t\t\t\t.outward_wires(network_path)\n\t\t\t\t.and_then(|outward_wires| outward_wires.get(&OutputConnector::Import(shift_output_down)))\n\t\t\t\t.cloned()\n\t\t\telse {\n\t\t\t\tlog::error!(\"Could not get outward wires in reorder_import\");\n\t\t\t\treturn;\n\t\t\t};\n\t\t\tfor downstream_connection in &outward_wires {\n\t\t\t\tself.disconnect_input(downstream_connection, network_path);\n\t\t\t\tself.create_wire(&OutputConnector::Import(shift_output_down - 1), downstream_connection, network_path);\n\t\t\t}\n\t\t}\n\t\t// Node inputs at or above the end index should be shifted up one\n\t\tfor shift_output_up in (end_index..last_import_index).rev() {\n\t\t\tlet Some(outward_wires) = self\n\t\t\t\t.outward_wires(network_path)\n\t\t\t\t.and_then(|outward_wires| outward_wires.get(&OutputConnector::Import(shift_output_up)))\n\t\t\t\t.cloned()\n\t\t\telse {\n\t\t\t\tlog::error!(\"Could not get outward wires in reorder_import\");\n\t\t\t\treturn;\n\t\t\t};\n\t\t\tfor downstream_connection in &outward_wires {\n\t\t\t\tself.disconnect_input(downstream_connection, network_path);\n\t\t\t\tself.create_wire(&OutputConnector::Import(shift_output_up + 1), downstream_connection, network_path);\n\t\t\t}\n\t\t}\n\n\t\t// Move the connections to the moved export after all other ones have been shifted\n\t\tfor downstream_connection in &move_to_end_index {\n\t\t\tself.disconnect_input(downstream_connection, network_path);\n\t\t\tself.create_wire(&OutputConnector::Import(end_index), downstream_connection, network_path);\n\t\t}\n\n\t\t// Update the metadata for the current network\n\t\tself.unload_outward_wires(network_path);\n\t\tself.unload_import_export_ports(network_path);\n\t\tself.unload_modify_import_export(network_path);\n\t\tself.unload_stack_dependents(network_path);\n\t}\n\n\t/// Replaces the implementation and corresponding metadata.\n\tpub fn replace_implementation(&mut self, node_id: &NodeId, network_path: &[NodeId], new_template: &mut NodeTemplate) {\n\t\tlet Some(network) = self.network_mut(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network in set_implementation\");\n\t\t\treturn;\n\t\t};\n\t\tlet Some(node) = network.nodes.get_mut(node_id) else {\n\t\t\tlog::error!(\"Could not get node in set_implementation\");\n\t\t\treturn;\n\t\t};\n\t\tlet new_implementation = std::mem::take(&mut new_template.document_node.implementation);\n\t\tlet _ = std::mem::replace(&mut node.implementation, new_implementation);\n\t\tlet Some(metadata) = self.node_metadata_mut(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get metadata in set_implementation\");\n\t\t\treturn;\n\t\t};\n\t\tlet new_metadata = std::mem::take(&mut new_template.persistent_node_metadata.network_metadata);\n\t\tlet _ = std::mem::replace(&mut metadata.persistent_metadata.network_metadata, new_metadata);\n\t}\n\n\t/// Replaces the inputs and corresponding metadata.\n\tpub fn replace_inputs(&mut self, node_id: &NodeId, network_path: &[NodeId], new_template: &mut NodeTemplate) -> Option<Vec<NodeInput>> {\n\t\tlet Some(network) = self.network_mut(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network in set_implementation\");\n\t\t\treturn None;\n\t\t};\n\t\tlet Some(node) = network.nodes.get_mut(node_id) else {\n\t\t\tlog::error!(\"Could not get node in set_implementation\");\n\t\t\treturn None;\n\t\t};\n\t\tlet new_inputs = std::mem::take(&mut new_template.document_node.inputs);\n\t\tlet old_inputs = std::mem::replace(&mut node.inputs, new_inputs);\n\t\tlet Some(metadata) = self.node_metadata_mut(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get metadata in set_implementation\");\n\t\t\treturn None;\n\t\t};\n\t\tlet new_metadata = std::mem::take(&mut new_template.persistent_node_metadata.input_metadata);\n\t\tlet _ = std::mem::replace(&mut metadata.persistent_metadata.input_metadata, new_metadata);\n\t\tSome(old_inputs)\n\t}\n\n\t/// Used when opening an old document to add the persistent metadata for each input if it doesnt exist, which is where the name/description are saved.\n\tpub fn validate_input_metadata(&mut self, node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId]) {\n\t\tlet number_of_inputs = node.inputs.len();\n\t\tlet Some(metadata) = self.node_metadata_mut(node_id, network_path) else { return };\n\t\tfor added_input_index in metadata.persistent_metadata.input_metadata.len()..number_of_inputs {\n\t\t\tlet input_metadata = self\n\t\t\t\t.reference(node_id, network_path)\n\t\t\t\t.as_ref()\n\t\t\t\t.and_then(resolve_document_node_type)\n\t\t\t\t.and_then(|definition| definition.node_template.persistent_node_metadata.input_metadata.get(added_input_index))\n\t\t\t\t.cloned();\n\t\t\tlet Some(metadata) = self.node_metadata_mut(node_id, network_path) else { return };\n\t\t\tmetadata.persistent_metadata.input_metadata.push(input_metadata.unwrap_or_default());\n\t\t}\n\t}\n\n\t// When opening an old document to ensure the output names match the number of exports\n\tpub fn validate_output_names(&mut self, node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId]) {\n\t\tif let DocumentNodeImplementation::Network(network) = &node.implementation {\n\t\t\tlet number_of_exports = network.exports.len();\n\t\t\tlet Some(metadata) = self.node_metadata_mut(node_id, network_path) else {\n\t\t\t\tlog::error!(\"Could not get metadata for node: {node_id:?}\");\n\t\t\t\treturn;\n\t\t\t};\n\t\t\tmetadata.persistent_metadata.output_names.resize(number_of_exports, \"\".to_string());\n\t\t}\n\t}\n\n\t/// Keep metadata in sync with the new implementation if this is used by anything other than the upgrade scripts.\n\t/// Only works with network nodes. Proto nodes use their ID as the reference.\n\tpub fn set_reference(&mut self, node_id: &NodeId, network_path: &[NodeId], reference_name: Option<String>) {\n\t\tlet Some(node_network_metadata) = self\n\t\t\t.node_metadata_mut(node_id, network_path)\n\t\t\t.and_then(|node_metadata| node_metadata.persistent_metadata.network_metadata.as_mut())\n\t\telse {\n\t\t\tlog::error!(\"Could not get network metadata in replace_reference_name\");\n\t\t\treturn;\n\t\t};\n\t\tnode_network_metadata.persistent_metadata.reference = reference_name;\n\t}\n\n\t/// Keep metadata in sync with the new implementation if this is used by anything other than the upgrade scripts\n\tpub fn set_call_argument(&mut self, node_id: &NodeId, network_path: &[NodeId], call_argument: Type) {\n\t\tlet Some(network) = self.network_mut(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network in set_implementation\");\n\t\t\treturn;\n\t\t};\n\t\tlet Some(node) = network.nodes.get_mut(node_id) else {\n\t\t\tlog::error!(\"Could not get node in set_implementation\");\n\t\t\treturn;\n\t\t};\n\t\tnode.call_argument = call_argument;\n\t}\n\n\tpub fn set_context_features(&mut self, node_id: &NodeId, network_path: &[NodeId], context_features: ContextDependencies) {\n\t\tlet Some(network) = self.network_mut(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network in set_context_features\");\n\t\t\treturn;\n\t\t};\n\t\tlet Some(node) = network.nodes.get_mut(node_id) else {\n\t\t\tlog::error!(\"Could not get node in set_context_features\");\n\t\t\treturn;\n\t\t};\n\t\tnode.context_features = context_features;\n\t}\n\n\tpub fn set_input(&mut self, input_connector: &InputConnector, new_input: NodeInput, network_path: &[NodeId]) {\n\t\tif matches!(input_connector, InputConnector::Export(_)) && matches!(new_input, NodeInput::Import { .. }) {\n\t\t\t// TODO: Add support for flattening NodeInput::Import exports in flatten_with_fns https://github.com/GraphiteEditor/Graphite/issues/1762\n\t\t\tlog::error!(\"Cannot connect a network to an export, see https://github.com/GraphiteEditor/Graphite/issues/1762\");\n\t\t\treturn;\n\t\t}\n\t\tlet Some(previous_input) = self.input_from_connector(input_connector, network_path).cloned() else {\n\t\t\tlog::error!(\"Could not get previous input in set_input\");\n\t\t\treturn;\n\t\t};\n\n\t\t// When changing a NodeInput::Node to a NodeInput::Node, the input should first be disconnected to ensure proper side effects\n\t\tif (matches!(previous_input, NodeInput::Node { .. }) && matches!(new_input, NodeInput::Node { .. })) {\n\t\t\tself.disconnect_input(input_connector, network_path);\n\t\t\tself.set_input(input_connector, new_input, network_path);\n\t\t\treturn;\n\t\t}\n\n\t\t// If the previous input is connected to a chain node, then set all upstream chain nodes to absolute position\n\t\tif let NodeInput::Node { node_id: previous_upstream_id, .. } = &previous_input\n\t\t\t&& self.is_chain(previous_upstream_id, network_path)\n\t\t{\n\t\t\tself.set_upstream_chain_to_absolute(previous_upstream_id, network_path);\n\t\t}\n\t\tif let NodeInput::Node { node_id: new_upstream_id, .. } = &new_input {\n\t\t\t// If the new input is connected to a chain node, then break its chain\n\t\t\tif self.is_chain(new_upstream_id, network_path) {\n\t\t\t\tself.set_upstream_chain_to_absolute(new_upstream_id, network_path);\n\t\t\t}\n\t\t}\n\n\t\tlet Some(network) = self.network_mut(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network in set_input\");\n\t\t\treturn;\n\t\t};\n\n\t\tlet old_input = match input_connector {\n\t\t\tInputConnector::Node { node_id, input_index } => {\n\t\t\t\tlet Some(node) = network.nodes.get_mut(node_id) else {\n\t\t\t\t\tlog::error!(\"Could not get node in set_input\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tlet Some(input) = node.inputs.get_mut(*input_index) else {\n\t\t\t\t\tlog::error!(\"Could not get input in set_input\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tstd::mem::replace(input, new_input.clone())\n\t\t\t}\n\t\t\tInputConnector::Export(export_index) => {\n\t\t\t\tlet Some(export) = network.exports.get_mut(*export_index) else {\n\t\t\t\t\tlog::error!(\"Could not get export in set_input\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tstd::mem::replace(export, new_input.clone())\n\t\t\t}\n\t\t};\n\n\t\tif old_input == new_input {\n\t\t\treturn;\n\t\t};\n\n\t\t// Ensure the network is not cyclic\n\t\tif !network.is_acyclic() {\n\t\t\tself.set_input(input_connector, old_input, network_path);\n\t\t\treturn;\n\t\t}\n\n\t\t// It is necessary to ensure the grpah is acyclic before calling `self.position` as it sometimes crashes with cyclic graphs #3227\n\t\tlet previous_metadata = match &previous_input {\n\t\t\tNodeInput::Node { node_id, .. } => self.position(node_id, network_path).map(|position| (*node_id, position)),\n\t\t\t_ => None,\n\t\t};\n\n\t\tself.transaction_modified();\n\n\t\t// Ensure layer is toggled to non layer if it is no longer eligible to be a layer\n\t\tlet layer_node_path = match input_connector {\n\t\t\tInputConnector::Node { node_id, .. } => Some((node_id, network_path)),\n\t\t\tInputConnector::Export(_) => network_path.split_last(),\n\t\t};\n\n\t\tif let Some((layer_id, layer_path)) = layer_node_path\n\t\t\t&& !self.is_eligible_to_be_layer(layer_id, layer_path)\n\t\t\t&& self.is_layer(layer_id, layer_path)\n\t\t{\n\t\t\tself.set_to_node_or_layer(layer_id, layer_path, false);\n\t\t}\n\n\t\t// Side effects\n\t\tmatch (&old_input, &new_input) {\n\t\t\t// If a node input is exposed or hidden reload the click targets and update the bounding box for all nodes\n\t\t\t(NodeInput::Value { exposed: new_exposed, .. }, NodeInput::Value { exposed: old_exposed, .. }) => {\n\t\t\t\tif let InputConnector::Node { node_id, .. } = input_connector {\n\t\t\t\t\tif new_exposed != old_exposed {\n\t\t\t\t\t\tself.unload_upstream_node_click_targets(vec![*node_id], network_path);\n\t\t\t\t\t\tself.unload_all_nodes_bounding_box(network_path);\n\n\t\t\t\t\t\t// Unload the interior imports ports\n\t\t\t\t\t\tlet nested_path = [network_path, &[*node_id]].concat();\n\t\t\t\t\t\tself.unload_import_export_ports(&nested_path);\n\t\t\t\t\t\tself.unload_modify_import_export(&nested_path);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tself.unload_import_export_ports(network_path);\n\t\t\t\t\tself.unload_modify_import_export(network_path);\n\t\t\t\t}\n\t\t\t}\n\t\t\t(_, NodeInput::Node { node_id: upstream_node_id, .. }) => {\n\t\t\t\t// Load structure if the change is to the document network and to the first or second\n\t\t\t\tif network_path.is_empty() {\n\t\t\t\t\tif matches!(input_connector, InputConnector::Export(0)) {\n\t\t\t\t\t\tself.load_structure();\n\t\t\t\t\t} else if let InputConnector::Node { node_id, input_index } = &input_connector {\n\t\t\t\t\t\t// If the connection is made to the first or second input of a node connected to the output, then load the structure\n\t\t\t\t\t\tif self.connected_to_output(node_id, network_path) && (*input_index == 0 || *input_index == 1) {\n\t\t\t\t\t\t\tself.load_structure();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tself.unload_outward_wires(network_path);\n\t\t\t\t// Layout system\n\t\t\t\tlet Some(current_node_position) = self.position(upstream_node_id, network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get current node position in set_input for node {upstream_node_id}\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tlet Some(node_metadata) = self.node_metadata(upstream_node_id, network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get node_metadata in set_input\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tmatch &node_metadata.persistent_metadata.node_type_metadata {\n\t\t\t\t\tNodeTypePersistentMetadata::Layer(_) => {\n\t\t\t\t\t\tmatch &input_connector {\n\t\t\t\t\t\t\tInputConnector::Export(_) => {\n\t\t\t\t\t\t\t\t// If a layer is connected to the exports, it should be set to absolute position without being moved.\n\t\t\t\t\t\t\t\tself.set_absolute_position(upstream_node_id, current_node_position, network_path)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tInputConnector::Node {\n\t\t\t\t\t\t\t\tnode_id: downstream_node_id,\n\t\t\t\t\t\t\t\tinput_index,\n\t\t\t\t\t\t\t} => {\n\t\t\t\t\t\t\t\t// If a layer has a single connection to the bottom of another layer, it should be set to stack positioning\n\t\t\t\t\t\t\t\tlet Some(downstream_node_metadata) = self.node_metadata(downstream_node_id, network_path) else {\n\t\t\t\t\t\t\t\t\tlog::error!(\"Could not get downstream node_metadata in set_input\");\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tmatch &downstream_node_metadata.persistent_metadata.node_type_metadata {\n\t\t\t\t\t\t\t\t\tNodeTypePersistentMetadata::Layer(_) => {\n\t\t\t\t\t\t\t\t\t\t// If the layer feeds into the bottom input of layer, and has no other outputs, set its position to stack at its previous y position\n\t\t\t\t\t\t\t\t\t\tlet multiple_outward_wires = self\n\t\t\t\t\t\t\t\t\t\t\t.outward_wires(network_path)\n\t\t\t\t\t\t\t\t\t\t\t.and_then(|all_outward_wires| all_outward_wires.get(&OutputConnector::node(*upstream_node_id, 0)))\n\t\t\t\t\t\t\t\t\t\t\t.is_some_and(|outward_wires| outward_wires.len() > 1);\n\t\t\t\t\t\t\t\t\t\tif *input_index == 0 && !multiple_outward_wires {\n\t\t\t\t\t\t\t\t\t\t\tself.set_stack_position_calculated_offset(upstream_node_id, downstream_node_id, network_path);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\tself.set_absolute_position(upstream_node_id, current_node_position, network_path);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tNodeTypePersistentMetadata::Node(_) => {\n\t\t\t\t\t\t\t\t\t\t// If the layer feeds into a node, set its y offset to 0\n\t\t\t\t\t\t\t\t\t\tself.set_absolute_position(upstream_node_id, current_node_position, network_path);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tNodeTypePersistentMetadata::Node(_) => {}\n\t\t\t\t}\n\t\t\t\tself.unload_upstream_node_click_targets(vec![*upstream_node_id], network_path);\n\t\t\t\tself.unload_stack_dependents(network_path);\n\t\t\t\tself.try_set_upstream_to_chain(input_connector, network_path);\n\t\t\t}\n\t\t\t// If a connection is made to the imports\n\t\t\t(NodeInput::Value { .. } | NodeInput::Scope { .. } | NodeInput::Inline { .. }, NodeInput::Import { .. }) => {\n\t\t\t\tself.unload_outward_wires(network_path);\n\t\t\t\tself.unload_wire(input_connector, network_path);\n\t\t\t}\n\t\t\t// If a connection to the imports is disconnected\n\t\t\t(NodeInput::Import { .. }, NodeInput::Value { .. } | NodeInput::Scope { .. } | NodeInput::Inline { .. }) => {\n\t\t\t\tself.unload_outward_wires(network_path);\n\t\t\t\tself.unload_wire(input_connector, network_path);\n\t\t\t}\n\t\t\t// If a node is disconnected.\n\t\t\t(NodeInput::Node { .. }, NodeInput::Value { .. } | NodeInput::Scope { .. } | NodeInput::Inline { .. }) => {\n\t\t\t\tself.unload_outward_wires(network_path);\n\t\t\t\tself.unload_wire(input_connector, network_path);\n\n\t\t\t\tif let Some((old_upstream_node_id, previous_position)) = previous_metadata {\n\t\t\t\t\tlet old_upstream_node_is_layer = self.is_layer(&old_upstream_node_id, network_path);\n\t\t\t\t\tlet Some(outward_wires) = self\n\t\t\t\t\t\t.outward_wires(network_path)\n\t\t\t\t\t\t.and_then(|outward_wires| outward_wires.get(&OutputConnector::node(old_upstream_node_id, 0)))\n\t\t\t\t\telse {\n\t\t\t\t\t\tlog::error!(\"Could not get outward wires in set_input\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t};\n\t\t\t\t\t// If it is a layer and is connected to a single layer, set its position to stack at its previous y position\n\t\t\t\t\tif old_upstream_node_is_layer && outward_wires.len() == 1 && outward_wires[0].input_index() == 0 {\n\t\t\t\t\t\tif let Some(downstream_node_id) = outward_wires[0].node_id()\n\t\t\t\t\t\t\t&& self.is_layer(&downstream_node_id, network_path)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tself.set_stack_position_calculated_offset(&old_upstream_node_id, &downstream_node_id, network_path);\n\t\t\t\t\t\t\tself.unload_upstream_node_click_targets(vec![old_upstream_node_id], network_path);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t// If it is a node and is eligible to be in a chain, then set it to chain positioning\n\t\t\t\t\telse if !old_upstream_node_is_layer {\n\t\t\t\t\t\tself.try_set_node_to_chain(&old_upstream_node_id, network_path);\n\t\t\t\t\t}\n\t\t\t\t\t// If a node was previously connected, and it is no longer connected to any nodes, then set its position to absolute at its previous position\n\t\t\t\t\telse {\n\t\t\t\t\t\tself.set_absolute_position(&old_upstream_node_id, previous_position, network_path);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Load structure if the change is to the document network and to the first or second\n\t\t\t\tif network_path.is_empty() {\n\t\t\t\t\tif matches!(input_connector, InputConnector::Export(0)) {\n\t\t\t\t\t\tself.load_structure();\n\t\t\t\t\t} else if let InputConnector::Node { node_id, input_index } = &input_connector {\n\t\t\t\t\t\t// If the connection is made to the first or second input of a node connected to the output, then load the structure\n\t\t\t\t\t\tif self.connected_to_output(node_id, network_path) && (*input_index == 0 || *input_index == 1) {\n\t\t\t\t\t\t\tself.load_structure();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tself.unload_stack_dependents(network_path);\n\t\t\t}\n\t\t\t_ => {}\n\t\t}\n\t}\n\n\t/// Ensure network metadata, positions, and other metadata is kept in sync\n\tpub fn disconnect_input(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) {\n\t\tlet Some(current_input) = self.input_from_connector(input_connector, network_path).cloned() else {\n\t\t\tlog::error!(\"Could not get current input in disconnect_input\");\n\t\t\treturn;\n\t\t};\n\t\t// Do not disconnect an already disconnected input\n\t\tif matches!(current_input, NodeInput::Value { .. }) {\n\t\t\treturn;\n\t\t}\n\n\t\tif let NodeInput::Node {\n\t\t\tnode_id: upstream_node_id,\n\t\t\toutput_index,\n\t\t\t..\n\t\t} = &current_input\n\t\t{\n\t\t\t// If the node upstream from the disconnected input is a chain, then break the chain by setting it to absolute positioning\n\t\t\tif self.is_chain(upstream_node_id, network_path) {\n\t\t\t\tself.set_upstream_chain_to_absolute(upstream_node_id, network_path);\n\t\t\t}\n\t\t\t// If the node upstream from the disconnected input has an outward wire to the bottom of a layer, set it back to stack positioning\n\t\t\tif self.is_layer(upstream_node_id, network_path) {\n\t\t\t\tlet Some(outward_wires) = self\n\t\t\t\t\t.outward_wires(network_path)\n\t\t\t\t\t.and_then(|outward_wires| outward_wires.get(&OutputConnector::node(*upstream_node_id, *output_index)))\n\t\t\t\telse {\n\t\t\t\t\tlog::error!(\"Could not get outward wires in disconnect_input\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tlet mut other_outward_wires = outward_wires.iter().filter(|outward_wire| *outward_wire != input_connector);\n\t\t\t\tif let Some(other_outward_wire) = other_outward_wires.next().cloned()\n\t\t\t\t\t&& other_outward_wires.next().is_none()\n\t\t\t\t\t&& let InputConnector::Node {\n\t\t\t\t\t\tnode_id: downstream_node_id,\n\t\t\t\t\t\tinput_index,\n\t\t\t\t\t} = other_outward_wire\n\t\t\t\t\t&& self.is_layer(&downstream_node_id, network_path)\n\t\t\t\t\t&& input_index == 0\n\t\t\t\t{\n\t\t\t\t\tself.set_stack_position_calculated_offset(upstream_node_id, &downstream_node_id, network_path);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tlet tagged_value = self.tagged_value_from_input(input_connector, network_path);\n\n\t\tlet value_input = NodeInput::value(tagged_value, true);\n\n\t\tself.set_input(input_connector, value_input, network_path);\n\t}\n\n\tpub fn create_wire(&mut self, output_connector: &OutputConnector, input_connector: &InputConnector, network_path: &[NodeId]) {\n\t\tlet input = match output_connector {\n\t\t\tOutputConnector::Node { node_id, output_index } => NodeInput::node(*node_id, *output_index),\n\t\t\tOutputConnector::Import(import_index) => NodeInput::Import {\n\t\t\t\timport_type: graph_craft::generic!(T),\n\t\t\t\timport_index: *import_index,\n\t\t\t},\n\t\t};\n\n\t\tself.set_input(input_connector, input, network_path);\n\t}\n\n\t/// Used to insert a group of nodes into the network\n\tpub fn insert_node_group(&mut self, nodes: Vec<(NodeId, NodeTemplate)>, new_ids: HashMap<NodeId, NodeId>, network_path: &[NodeId]) {\n\t\tfor (old_node_id, mut node_template) in nodes {\n\t\t\t// Get the new node template\n\t\t\tnode_template = self.map_ids(node_template, &old_node_id, &new_ids, network_path);\n\t\t\t// Insert node into network\n\t\t\tlet node_id = *new_ids.get(&old_node_id).unwrap();\n\t\t\tlet Some(network) = self.network_mut(network_path) else {\n\t\t\t\tlog::error!(\"Network not found in insert_node\");\n\t\t\t\treturn;\n\t\t\t};\n\n\t\t\tnetwork.nodes.insert(node_id, node_template.document_node);\n\t\t\tself.transaction_modified();\n\n\t\t\tlet Some(network_metadata) = self.network_metadata_mut(network_path) else {\n\t\t\t\tlog::error!(\"Network not found in insert_node\");\n\t\t\t\treturn;\n\t\t\t};\n\t\t\tlet node_metadata = DocumentNodeMetadata {\n\t\t\t\tpersistent_metadata: node_template.persistent_node_metadata,\n\t\t\t\ttransient_metadata: DocumentNodeTransientMetadata::default(),\n\t\t\t};\n\t\t\tnetwork_metadata.persistent_metadata.node_metadata.insert(node_id, node_metadata);\n\t\t}\n\t\tfor new_node_id in new_ids.values() {\n\t\t\tself.unload_node_click_targets(new_node_id, network_path);\n\t\t}\n\t\tself.unload_all_nodes_bounding_box(network_path);\n\t\tself.unload_outward_wires(network_path);\n\t}\n\n\t/// Used to insert a node template with no node/network inputs into the network and returns the a NodeTemplate with information from the previous node, if it existed.\n\tpub fn insert_node(&mut self, node_id: NodeId, node_template: NodeTemplate, network_path: &[NodeId]) -> Option<NodeTemplate> {\n\t\tlet has_node_or_network_input = node_template\n\t\t\t.document_node\n\t\t\t.inputs\n\t\t\t.iter()\n\t\t\t.all(|input| !(matches!(input, NodeInput::Node { .. }) || matches!(input, NodeInput::Import { .. })));\n\t\tassert!(has_node_or_network_input, \"Cannot insert node with node or network inputs. Use insert_node_group instead\");\n\t\tlet Some(network) = self.network_mut(network_path) else {\n\t\t\tlog::error!(\"Network not found in insert_node\");\n\t\t\treturn None;\n\t\t};\n\n\t\tlet previous_node = network.nodes.insert(node_id, node_template.document_node);\n\t\tself.transaction_modified();\n\n\t\tlet Some(network_metadata) = self.network_metadata_mut(network_path) else {\n\t\t\tlog::error!(\"Network not found in insert_node\");\n\t\t\treturn None;\n\t\t};\n\t\tlet node_metadata = DocumentNodeMetadata {\n\t\t\tpersistent_metadata: node_template.persistent_node_metadata,\n\t\t\ttransient_metadata: DocumentNodeTransientMetadata::default(),\n\t\t};\n\t\tlet previous_metadata = network_metadata.persistent_metadata.node_metadata.insert(node_id, node_metadata);\n\n\t\tself.unload_all_nodes_bounding_box(network_path);\n\t\tself.unload_node_click_targets(&node_id, network_path);\n\n\t\tprevious_node.zip(previous_metadata).map(|(document_node, node_metadata)| NodeTemplate {\n\t\t\tdocument_node,\n\t\t\tpersistent_node_metadata: node_metadata.persistent_metadata,\n\t\t})\n\t}\n\n\t/// Deletes all nodes in `node_ids` and any sole dependents in the horizontal chain if the node to delete is a layer node.\n\tpub fn delete_nodes(&mut self, nodes_to_delete: Vec<NodeId>, delete_children: bool, network_path: &[NodeId]) {\n\t\tlet Some(outward_wires) = self.outward_wires(network_path).cloned() else {\n\t\t\tlog::error!(\"Could not get outward wires in delete_nodes\");\n\t\t\treturn;\n\t\t};\n\n\t\tlet mut delete_nodes = HashSet::new();\n\t\tfor node_id in &nodes_to_delete {\n\t\t\tdelete_nodes.insert(*node_id);\n\n\t\t\tif !delete_children {\n\t\t\t\tcontinue;\n\t\t\t};\n\n\t\t\t// Perform an upstream traversal to try delete children for secondary inputs\n\t\t\tlet mut upstream_nodes = (1..self.number_of_inputs(node_id, network_path))\n\t\t\t\t.filter_map(|input_index| self.upstream_output_connector(&InputConnector::node(*node_id, input_index), network_path).and_then(|oc| oc.node_id()))\n\t\t\t\t.collect::<Vec<_>>();\n\t\t\twhile let Some(upstream_node) = upstream_nodes.pop() {\n\t\t\t\t// Add the upstream nodes to the traversal\n\t\t\t\tfor input_connector in (0..self.number_of_inputs(&upstream_node, network_path)).map(|input_index| InputConnector::node(upstream_node, input_index)) {\n\t\t\t\t\tif let Some(upstream_node) = self.upstream_output_connector(&input_connector, network_path).and_then(|oc| oc.node_id()) {\n\t\t\t\t\t\tupstream_nodes.push(upstream_node);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// For each potential child perform a complete downstream traversal, ending at either a node in the `delete_nodes` set (excluding layer bottom inputs), the output, or a dead end.\n\t\t\t\t// If the output node is eventually reached, then it is not a sole dependent and will not be deleted\n\t\t\t\tlet mut stack = vec![upstream_node];\n\t\t\t\tlet mut can_delete = true;\n\t\t\t\twhile let Some(current_node) = stack.pop() {\n\t\t\t\t\tlet mut is_dead_end = true;\n\t\t\t\t\tfor output_connector in (0..self.number_of_outputs(&current_node, network_path)).map(|output_index| OutputConnector::node(current_node, output_index)) {\n\t\t\t\t\t\tlet Some(downstream_nodes) = outward_wires.get(&output_connector) else { continue };\n\t\t\t\t\t\tif !downstream_nodes.is_empty() {\n\t\t\t\t\t\t\tis_dead_end = false\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor downstream_node in downstream_nodes {\n\t\t\t\t\t\t\tif let InputConnector::Node { node_id: downstream_id, input_index } = downstream_node {\n\t\t\t\t\t\t\t\t// If the downstream node is not in the delete nodes set, then continue iterating\n\t\t\t\t\t\t\t\t// If the downstream node is the bottom input of a layer then continue iterating\n\t\t\t\t\t\t\t\tif !delete_nodes.contains(downstream_id) || (*input_index == 0 && self.is_layer(downstream_id, network_path)) {\n\t\t\t\t\t\t\t\t\tstack.push(*downstream_id);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t// If the traversal reaches the primary input of the node to delete then do not delete it\n\t\t\t\t\t\t\t\tif node_id == downstream_id && *input_index == 0 {\n\t\t\t\t\t\t\t\t\tcan_delete = false;\n\t\t\t\t\t\t\t\t\tstack = Vec::new();\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// If the traversal reaches the export, then the current node is not a sole dependent and cannot be deleted\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tcan_delete = false;\n\t\t\t\t\t\t\t\tstack = Vec::new();\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t// If there are no outward wires, then we have reached a dead end, and the node cannot be deleted\n\t\t\t\t\tif is_dead_end {\n\t\t\t\t\t\tcan_delete = false;\n\t\t\t\t\t\tstack = Vec::new();\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif can_delete {\n\t\t\t\t\tdelete_nodes.insert(upstream_node);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor delete_node_id in &delete_nodes {\n\t\t\tlet upstream_chain_nodes = self\n\t\t\t\t.upstream_flow_back_from_nodes(vec![*delete_node_id], network_path, FlowType::PrimaryFlow)\n\t\t\t\t.skip(1)\n\t\t\t\t.take_while(|upstream_node| self.is_chain(upstream_node, network_path))\n\t\t\t\t.collect::<Vec<_>>();\n\n\t\t\tif !self.remove_references_from_network(delete_node_id, network_path) {\n\t\t\t\tlog::error!(\"could not remove references from network\");\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfor input_index in 0..self.number_of_displayed_inputs(delete_node_id, network_path) {\n\t\t\t\tself.disconnect_input(&InputConnector::node(*delete_node_id, input_index), network_path);\n\t\t\t}\n\n\t\t\tlet Some(network) = self.network_mut(network_path) else {\n\t\t\t\tlog::error!(\"Could not get nested network in delete_nodes\");\n\t\t\t\tcontinue;\n\t\t\t};\n\n\t\t\tnetwork.nodes.remove(delete_node_id);\n\t\t\tself.transaction_modified();\n\n\t\t\tlet Some(network_metadata) = self.network_metadata_mut(network_path) else {\n\t\t\t\tlog::error!(\"Could not get nested network_metadata in delete_nodes\");\n\t\t\t\tcontinue;\n\t\t\t};\n\t\t\tnetwork_metadata.persistent_metadata.node_metadata.remove(delete_node_id);\n\t\t\tfor previous_chain_node in upstream_chain_nodes {\n\t\t\t\tself.set_chain_position(&previous_chain_node, network_path);\n\t\t\t}\n\t\t}\n\t\tself.unload_all_nodes_bounding_box(network_path);\n\t\t// Instead of unloaded all node click targets, just unload the nodes upstream from the deleted nodes. unload_upstream_node_click_targets will not work since the nodes have been deleted.\n\t\tself.unload_all_nodes_click_targets(network_path);\n\t\tlet Some(selected_nodes) = self.selected_nodes_mut(network_path) else {\n\t\t\tlog::error!(\"Could not get selected nodes in NodeGraphMessage::DeleteNodes\");\n\t\t\treturn;\n\t\t};\n\t\tselected_nodes.retain_selected_nodes(|node_id| !nodes_to_delete.contains(node_id));\n\t}\n\n\t/// Removes all references to the node with the given id from the network, and reconnects the input to the node below.\n\tpub fn remove_references_from_network(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> bool {\n\t\t// TODO: Add more logic to support retaining preview when removing references. Since there are so many edge cases/possible crashes, for now the preview is ended.\n\t\tself.stop_previewing(network_path);\n\n\t\t// Check whether the being-deleted node's first (primary) input is a node\n\t\tlet reconnect_to_input = self.document_node(node_id, network_path).and_then(|node| {\n\t\t\tnode.inputs\n\t\t\t\t.iter()\n\t\t\t\t.find(|input| input.is_exposed())\n\t\t\t\t.filter(|input| matches!(input, NodeInput::Node { .. } | NodeInput::Import { .. }))\n\t\t\t\t.cloned()\n\t\t});\n\t\t// Get all upstream references\n\t\tlet number_of_outputs = self.number_of_outputs(node_id, network_path);\n\t\tlet Some(all_outward_wires) = self.outward_wires(network_path) else {\n\t\t\tlog::error!(\"Could not get outward wires in remove_references_from_network\");\n\t\t\treturn false;\n\t\t};\n\t\tlet mut downstream_inputs_to_disconnect = Vec::new();\n\t\tfor output_index in 0..number_of_outputs {\n\t\t\tif let Some(outward_wires) = all_outward_wires.get(&OutputConnector::node(*node_id, output_index)) {\n\t\t\t\tdownstream_inputs_to_disconnect.extend(outward_wires.clone());\n\t\t\t}\n\t\t}\n\n\t\tlet mut reconnect_node = None;\n\n\t\tfor downstream_input in &downstream_inputs_to_disconnect {\n\t\t\tself.disconnect_input(downstream_input, network_path);\n\t\t\t// Prevent reconnecting export to import until https://github.com/GraphiteEditor/Graphite/issues/1762 is solved\n\t\t\tif !(matches!(reconnect_to_input, Some(NodeInput::Import { .. })) && matches!(downstream_input, InputConnector::Export(_)))\n\t\t\t\t&& let Some(reconnect_input) = &reconnect_to_input\n\t\t\t{\n\t\t\t\treconnect_node = reconnect_input.as_node().and_then(|node_id| if self.is_stack(&node_id, network_path) { Some(node_id) } else { None });\n\t\t\t\tself.disconnect_input(&InputConnector::node(*node_id, 0), network_path);\n\t\t\t\tself.set_input(downstream_input, reconnect_input.clone(), network_path);\n\t\t\t}\n\t\t}\n\n\t\t// Shift the reconnected node up to collapse space\n\t\tif let Some(reconnect_node) = &reconnect_node {\n\t\t\tlet Some(reconnected_node_position) = self.position(reconnect_node, network_path) else {\n\t\t\t\tlog::error!(\"Could not get reconnected node position in remove_references_from_network\");\n\t\t\t\treturn false;\n\t\t\t};\n\t\t\tlet Some(disconnected_node_position) = self.position(node_id, network_path) else {\n\t\t\t\tlog::error!(\"Could not get disconnected node position in remove_references_from_network\");\n\t\t\t\treturn false;\n\t\t\t};\n\t\t\tlet max_shift_distance = reconnected_node_position.y - disconnected_node_position.y;\n\n\t\t\tlet upstream_nodes = self.upstream_flow_back_from_nodes(vec![*reconnect_node], network_path, FlowType::PrimaryFlow).collect::<Vec<_>>();\n\n\t\t\t// Select the reconnect node to move to ensure the shifting works correctly\n\t\t\tlet Some(selected_nodes) = self.selected_nodes_mut(network_path) else {\n\t\t\t\tlog::error!(\"Could not get selected nodes in remove_references_from_network\");\n\t\t\t\treturn false;\n\t\t\t};\n\n\t\t\tlet old_selected_nodes = selected_nodes.replace_with(upstream_nodes);\n\n\t\t\t// Shift up until there is either a collision or the disconnected node position is reached\n\t\t\tlet mut current_shift_distance = 0;\n\t\t\twhile self.check_collision_with_stack_dependents(reconnect_node, -1, network_path).is_empty() && max_shift_distance > current_shift_distance {\n\t\t\t\tself.shift_selected_nodes(Direction::Up, false, network_path);\n\t\t\t\tcurrent_shift_distance += 1;\n\t\t\t}\n\n\t\t\tlet _ = self.selected_nodes_mut(network_path).unwrap().replace_with(old_selected_nodes);\n\t\t}\n\n\t\ttrue\n\t}\n\n\tpub fn start_previewing_without_restore(&mut self, network_path: &[NodeId]) {\n\t\t// Some logic will have to be performed to prevent the graph positions from being completely changed when the export changes to some previewed node\n\t\tlet Some(network_metadata) = self.network_metadata_mut(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata in start_previewing_without_restore\");\n\t\t\treturn;\n\t\t};\n\t\tnetwork_metadata.persistent_metadata.previewing = Previewing::Yes { root_node_to_restore: None };\n\t}\n\n\tfn stop_previewing(&mut self, network_path: &[NodeId]) {\n\t\tif let Previewing::Yes {\n\t\t\troot_node_to_restore: Some(root_node_to_restore),\n\t\t} = self.previewing(network_path)\n\t\t{\n\t\t\tself.set_input(\n\t\t\t\t&InputConnector::Export(0),\n\t\t\t\tNodeInput::node(root_node_to_restore.node_id, root_node_to_restore.output_index),\n\t\t\t\tnetwork_path,\n\t\t\t);\n\t\t}\n\t\tlet Some(network_metadata) = self.network_metadata_mut(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata in stop_previewing\");\n\t\t\treturn;\n\t\t};\n\t\tnetwork_metadata.persistent_metadata.previewing = Previewing::No;\n\t}\n\n\t// /// Sets the root node only if a node is being previewed\n\t// pub fn update_root_node(&mut self, node_id: NodeId, output_index: usize) {\n\t// \tif let Previewing::Yes { root_node_to_restore } = self.previewing {\n\t// \t\t// Only continue previewing if the new root node is not the same as the primary export. If it is the same, end the preview\n\t// \t\tif let Some(root_node_to_restore) = root_node_to_restore {\n\t// \t\t\tif root_node_to_restore.id != node_id {\n\t// \t\t\t\tself.start_previewing(node_id, output_index);\n\t// \t\t\t} else {\n\t// \t\t\t\tself.stop_preview();\n\t// \t\t\t}\n\t// \t\t} else {\n\t// \t\t\tself.stop_preview();\n\t// \t\t}\n\t// \t}\n\t// }\n\n\tpub fn set_display_name(&mut self, node_id: &NodeId, display_name: String, network_path: &[NodeId]) {\n\t\tlet Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get node {node_id} in set_visibility\");\n\t\t\treturn;\n\t\t};\n\n\t\tif node_metadata.persistent_metadata.display_name == display_name {\n\t\t\treturn;\n\t\t}\n\n\t\tnode_metadata.persistent_metadata.display_name.clone_from(&display_name);\n\n\t\t// Keep the alias in sync with the `ToArtboard` name input\n\t\tif self\n\t\t\t.reference(node_id, network_path)\n\t\t\t.is_some_and(|reference| reference == DefinitionIdentifier::Network(\"Artboard\".into()))\n\t\t{\n\t\t\tlet Some(nested_network) = self.network_mut(network_path) else {\n\t\t\t\treturn;\n\t\t\t};\n\t\t\tlet Some(artboard_node) = nested_network.nodes.get_mut(node_id) else {\n\t\t\t\treturn;\n\t\t\t};\n\t\t\tlet DocumentNodeImplementation::Network(network) = &mut artboard_node.implementation else {\n\t\t\t\treturn;\n\t\t\t};\n\t\t\t// Keep this in sync with the definition\n\t\t\tlet Some(to_artboard) = network.nodes.get_mut(&NodeId(0)) else {\n\t\t\t\treturn;\n\t\t\t};\n\n\t\t\tlet label_index = 1;\n\t\t\tlet label = if !display_name.is_empty() { display_name } else { \"Artboard\".to_string() };\n\t\t\tlet label_input = NodeInput::value(TaggedValue::String(label), false);\n\t\t\tto_artboard.inputs[label_index] = label_input;\n\t\t}\n\n\t\tself.transaction_modified();\n\t\tself.try_unload_layer_width(node_id, network_path);\n\t\tself.unload_node_click_targets(node_id, network_path);\n\t}\n\n\tpub fn set_import_export_name(&mut self, mut name: String, index: ImportOrExport, network_path: &[NodeId]) {\n\t\tlet Some(encapsulating_node) = self.encapsulating_node_metadata_mut(network_path) else {\n\t\t\tlog::error!(\"Could not get encapsulating network in set_import_export_name\");\n\t\t\treturn;\n\t\t};\n\n\t\tlet name_changed = match index {\n\t\t\tImportOrExport::Import(import_index) => {\n\t\t\t\tlet Some(input_properties) = encapsulating_node.persistent_metadata.input_metadata.get_mut(import_index) else {\n\t\t\t\t\tlog::error!(\"Could not get input properties in set_import_export_name\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\t// Only return false if the previous value is the same as the current value\n\t\t\t\tstd::mem::swap(&mut input_properties.persistent_metadata.input_name, &mut name);\n\t\t\t\tinput_properties.persistent_metadata.input_name != name\n\t\t\t}\n\t\t\tImportOrExport::Export(export_index) => {\n\t\t\t\tlet Some(export_name) = encapsulating_node.persistent_metadata.output_names.get_mut(export_index) else {\n\t\t\t\t\tlog::error!(\"Could not get export_name in set_import_export_name\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tstd::mem::swap(export_name, &mut name);\n\t\t\t\t*export_name != name\n\t\t\t}\n\t\t};\n\t\tif name_changed {\n\t\t\tself.transaction_modified();\n\t\t}\n\t}\n\n\tpub fn set_pinned(&mut self, node_id: &NodeId, network_path: &[NodeId], pinned: bool) {\n\t\tlet Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get node {node_id} in set_pinned\");\n\t\t\treturn;\n\t\t};\n\n\t\tnode_metadata.persistent_metadata.pinned = pinned;\n\t\tself.transaction_modified();\n\t}\n\n\tpub fn set_visibility(&mut self, node_id: &NodeId, network_path: &[NodeId], is_visible: bool) {\n\t\tlet Some(network) = self.network_mut(network_path) else {\n\t\t\treturn;\n\t\t};\n\t\tlet Some(node) = network.nodes.get_mut(node_id) else {\n\t\t\tlog::error!(\"Could not get node {node_id} in set_visibility\");\n\t\t\treturn;\n\t\t};\n\n\t\tnode.visible = is_visible;\n\t\tself.transaction_modified();\n\t}\n\n\tpub fn set_locked(&mut self, node_id: &NodeId, network_path: &[NodeId], locked: bool) {\n\t\tlet Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get node {node_id} in set_visibility\");\n\t\t\treturn;\n\t\t};\n\n\t\tnode_metadata.persistent_metadata.locked = locked;\n\t\tself.transaction_modified();\n\t\tself.try_unload_layer_width(node_id, network_path);\n\t\tself.unload_node_click_targets(node_id, network_path);\n\t}\n\n\tpub fn set_to_node_or_layer(&mut self, node_id: &NodeId, network_path: &[NodeId], is_layer: bool) {\n\t\t// If a layer is set to a node, set upstream nodes to absolute position, and upstream siblings to absolute position\n\t\tlet child_id = { self.upstream_flow_back_from_nodes(vec![*node_id], network_path, FlowType::HorizontalFlow).nth(1) };\n\t\tlet upstream_sibling_id = { self.upstream_flow_back_from_nodes(vec![*node_id], network_path, FlowType::PrimaryFlow).nth(1) };\n\t\tmatch (self.is_layer(node_id, network_path), is_layer) {\n\t\t\t(true, false) => {\n\t\t\t\tif let Some(child_id) = child_id {\n\t\t\t\t\tself.set_upstream_chain_to_absolute(&child_id, network_path);\n\t\t\t\t}\n\t\t\t\tif let Some(upstream_sibling_id) = upstream_sibling_id {\n\t\t\t\t\tlet Some(upstream_sibling_position) = self.position(&upstream_sibling_id, network_path) else {\n\t\t\t\t\t\tlog::error!(\"Could not get upstream sibling position in set_to_node_or_layer\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t};\n\t\t\t\t\tself.set_absolute_position(&upstream_sibling_id, upstream_sibling_position, network_path);\n\t\t\t\t}\n\t\t\t}\n\t\t\t(false, true) => {\n\t\t\t\t// If a node is set to a layer\n\t\t\t\tif let Some(upstream_sibling_id) = upstream_sibling_id {\n\t\t\t\t\t// If the upstream sibling layer has a single output, then set it to stack position\n\t\t\t\t\tif self.is_layer(&upstream_sibling_id, network_path)\n\t\t\t\t\t\t&& self\n\t\t\t\t\t\t\t.outward_wires(network_path)\n\t\t\t\t\t\t\t.and_then(|outward_wires| outward_wires.get(&OutputConnector::node(upstream_sibling_id, 0)))\n\t\t\t\t\t\t\t.is_some_and(|outward_wires| outward_wires.len() == 1)\n\t\t\t\t\t{\n\t\t\t\t\t\tself.set_stack_position_calculated_offset(&upstream_sibling_id, node_id, network_path);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tself.set_upstream_chain_to_absolute(&upstream_sibling_id, network_path);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t_ => return,\n\t\t};\n\n\t\tlet Some(position) = self.position(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get position in set_to_node_or_layer\");\n\t\t\treturn;\n\t\t};\n\n\t\tlet single_downstream_layer_position = self\n\t\t\t.outward_wires(network_path)\n\t\t\t.and_then(|outward_wires| {\n\t\t\t\toutward_wires\n\t\t\t\t\t.get(&OutputConnector::node(*node_id, 0))\n\t\t\t\t\t.and_then(|outward_wires| (outward_wires.len() == 1).then(|| outward_wires[0]))\n\t\t\t\t\t.and_then(|downstream_connector| if downstream_connector.input_index() == 0 { downstream_connector.node_id() } else { None })\n\t\t\t})\n\t\t\t.filter(|downstream_node_id| self.is_layer(downstream_node_id, network_path))\n\t\t\t.and_then(|downstream_layer| self.position(&downstream_layer, network_path));\n\n\t\tlet Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get node_metadata for node {node_id}\");\n\t\t\treturn;\n\t\t};\n\n\t\t// First set the position to absolute\n\t\tnode_metadata.persistent_metadata.node_type_metadata = if is_layer {\n\t\t\tNodeTypePersistentMetadata::Layer(LayerPersistentMetadata {\n\t\t\t\tposition: LayerPosition::Absolute(position),\n\t\t\t\towned_nodes: TransientMetadata::Unloaded,\n\t\t\t})\n\t\t} else {\n\t\t\tNodeTypePersistentMetadata::Node(NodePersistentMetadata {\n\t\t\t\tposition: NodePosition::Absolute(position),\n\t\t\t})\n\t\t};\n\n\t\t// Try build the chain\n\t\tif is_layer {\n\t\t\tself.try_set_upstream_to_chain(&InputConnector::node(*node_id, 1), network_path);\n\t\t} else {\n\t\t\tself.try_set_node_to_chain(node_id, network_path);\n\t\t}\n\n\t\tlet Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get node_metadata for node {node_id}\");\n\t\t\treturn;\n\t\t};\n\t\t// Set the position to stack if necessary\n\t\tif let Some(downstream_position) = is_layer.then_some(single_downstream_layer_position).flatten() {\n\t\t\tnode_metadata.persistent_metadata.node_type_metadata = NodeTypePersistentMetadata::Layer(LayerPersistentMetadata {\n\t\t\t\tposition: LayerPosition::Stack((position.y - downstream_position.y - 3).max(0) as u32),\n\t\t\t\towned_nodes: TransientMetadata::Unloaded,\n\t\t\t})\n\t\t}\n\n\t\tif is_layer {\n\t\t\tnode_metadata.transient_metadata.node_type_metadata = NodeTypeTransientMetadata::Layer(LayerTransientMetadata::default());\n\t\t} else {\n\t\t\tnode_metadata.transient_metadata.node_type_metadata = NodeTypeTransientMetadata::Node;\n\t\t}\n\n\t\tself.transaction_modified();\n\t\tself.unload_stack_dependents(network_path);\n\t\tself.unload_upstream_node_click_targets(vec![*node_id], network_path);\n\t\tself.unload_all_nodes_bounding_box(network_path);\n\t\tself.unload_import_export_ports(network_path);\n\t\tself.unload_modify_import_export(network_path);\n\t\tself.load_structure();\n\t}\n\n\tpub fn toggle_preview(&mut self, toggle_id: NodeId, network_path: &[NodeId]) {\n\t\tlet Some(network) = self.nested_network(network_path) else {\n\t\t\treturn;\n\t\t};\n\t\t// If new_export is None then disconnect\n\t\tlet mut new_export = None;\n\t\tlet mut new_previewing_state = Previewing::No;\n\t\tif let Some(export) = network.exports.first() {\n\t\t\t// If there currently an export\n\t\t\tif let NodeInput::Node { node_id, output_index, .. } = export {\n\t\t\t\tlet previous_export_id = *node_id;\n\t\t\t\tlet previous_output_index = *output_index;\n\n\t\t\t\t// The export is clicked\n\t\t\t\tif *node_id == toggle_id {\n\t\t\t\t\t// If the current export is clicked and is being previewed end the preview and set either export back to root node or disconnect\n\t\t\t\t\tif let Previewing::Yes { root_node_to_restore } = self.previewing(network_path) {\n\t\t\t\t\t\tnew_export = root_node_to_restore.map(|root_node| root_node.to_connector());\n\t\t\t\t\t\tnew_previewing_state = Previewing::No;\n\t\t\t\t\t}\n\t\t\t\t\t// The export is clicked and there is no preview\n\t\t\t\t\telse {\n\t\t\t\t\t\tnew_previewing_state = Previewing::Yes {\n\t\t\t\t\t\t\troot_node_to_restore: Some(RootNode {\n\t\t\t\t\t\t\t\tnode_id: previous_export_id,\n\t\t\t\t\t\t\t\toutput_index: previous_output_index,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// The export is not clicked\n\t\t\t\telse {\n\t\t\t\t\tnew_export = Some(OutputConnector::node(toggle_id, 0));\n\n\t\t\t\t\t// There is currently a dashed line being drawn\n\t\t\t\t\tif let Previewing::Yes { root_node_to_restore } = self.previewing(network_path) {\n\t\t\t\t\t\t// There is also a solid line being drawn\n\t\t\t\t\t\tif let Some(root_node_to_restore) = root_node_to_restore {\n\t\t\t\t\t\t\t// If the node with the solid line is clicked, then start previewing that node without restore\n\t\t\t\t\t\t\tif root_node_to_restore.node_id == toggle_id {\n\t\t\t\t\t\t\t\tnew_export = Some(OutputConnector::node(toggle_id, 0));\n\t\t\t\t\t\t\t\tnew_previewing_state = Previewing::Yes { root_node_to_restore: None };\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// Root node to restore does not change\n\t\t\t\t\t\t\t\tnew_previewing_state = Previewing::Yes {\n\t\t\t\t\t\t\t\t\troot_node_to_restore: Some(root_node_to_restore),\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// There is a dashed line without a solid line.\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tnew_previewing_state = Previewing::Yes { root_node_to_restore: None };\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t// Not previewing, there is no dashed line being drawn\n\t\t\t\t\telse {\n\t\t\t\t\t\tnew_export = Some(OutputConnector::node(toggle_id, 0));\n\t\t\t\t\t\tnew_previewing_state = Previewing::Yes {\n\t\t\t\t\t\t\troot_node_to_restore: Some(RootNode {\n\t\t\t\t\t\t\t\tnode_id: previous_export_id,\n\t\t\t\t\t\t\t\toutput_index: previous_output_index,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t// The primary export is disconnected\n\t\t\telse {\n\t\t\t\t// Set node as export and cancel any preview\n\t\t\t\tnew_export = Some(OutputConnector::node(toggle_id, 0));\n\t\t\t\tself.start_previewing_without_restore(network_path);\n\t\t\t}\n\t\t}\n\t\tmatch new_export {\n\t\t\tSome(new_export) => {\n\t\t\t\tself.create_wire(&new_export, &InputConnector::Export(0), network_path);\n\t\t\t}\n\t\t\tNone => {\n\t\t\t\tself.disconnect_input(&InputConnector::Export(0), network_path);\n\t\t\t}\n\t\t}\n\t\tlet Some(network_metadata) = self.network_metadata_mut(network_path) else {\n\t\t\treturn;\n\t\t};\n\t\tnetwork_metadata.persistent_metadata.previewing = new_previewing_state;\n\t}\n\n\t/// Sets the position of a node to an absolute position\n\tfn set_absolute_position(&mut self, node_id: &NodeId, position: IVec2, network_path: &[NodeId]) {\n\t\tlet Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get node_metadata for node {node_id}\");\n\t\t\treturn;\n\t\t};\n\n\t\tif let NodeTypePersistentMetadata::Node(node_metadata) = &mut node_metadata.persistent_metadata.node_type_metadata {\n\t\t\tif node_metadata.position == NodePosition::Absolute(position) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tnode_metadata.position = NodePosition::Absolute(position);\n\t\t\tself.transaction_modified();\n\t\t} else if let NodeTypePersistentMetadata::Layer(layer_metadata) = &mut node_metadata.persistent_metadata.node_type_metadata {\n\t\t\tif layer_metadata.position == LayerPosition::Absolute(position) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tlayer_metadata.position = LayerPosition::Absolute(position);\n\t\t\tself.transaction_modified();\n\t\t}\n\t}\n\n\t/// Sets the position of a layer to a stack position\n\tpub fn set_stack_position(&mut self, node_id: &NodeId, y_offset: u32, network_path: &[NodeId]) {\n\t\tlet Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get node_metadata for node {node_id}\");\n\t\t\treturn;\n\t\t};\n\t\tmatch &mut node_metadata.persistent_metadata.node_type_metadata {\n\t\t\tNodeTypePersistentMetadata::Layer(layer_metadata) => {\n\t\t\t\tif layer_metadata.position == LayerPosition::Stack(y_offset) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlayer_metadata.position = LayerPosition::Stack(y_offset);\n\t\t\t\tself.transaction_modified();\n\t\t\t}\n\t\t\t_ => {\n\t\t\t\tlog::error!(\"Could not set stack position for non layer node {node_id}\");\n\t\t\t}\n\t\t}\n\t\tself.unload_upstream_node_click_targets(vec![*node_id], network_path);\n\t}\n\n\t/// Sets the position of a node to a stack position without changing its y offset\n\tpub fn set_stack_position_calculated_offset(&mut self, node_id: &NodeId, downstream_layer: &NodeId, network_path: &[NodeId]) {\n\t\tlet Some(node_position) = self.position(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get node position for node {node_id}\");\n\t\t\treturn;\n\t\t};\n\t\tlet Some(downstream_position) = self.position(downstream_layer, network_path) else {\n\t\t\tlog::error!(\"Could not get downstream position for node {downstream_layer}\");\n\t\t\treturn;\n\t\t};\n\n\t\tself.set_stack_position(node_id, (node_position.y - downstream_position.y - 3).max(0) as u32, network_path);\n\t}\n\n\t/// Sets the position of a node to a chain position\n\tpub fn set_chain_position(&mut self, node_id: &NodeId, network_path: &[NodeId]) {\n\t\tlet Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get node_metadata for node {node_id}\");\n\t\t\treturn;\n\t\t};\n\t\t// Set any absolute nodes to chain positioning\n\t\tif let NodeTypePersistentMetadata::Node(NodePersistentMetadata { position }) = &mut node_metadata.persistent_metadata.node_type_metadata {\n\t\t\tif *position == NodePosition::Chain {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t*position = NodePosition::Chain;\n\t\t\tself.transaction_modified();\n\t\t}\n\t\t// If there is an upstream layer then stop breaking the chain\n\t\telse {\n\t\t\tlog::error!(\"Could not set chain position for layer node {node_id}\");\n\t\t}\n\t\t// let previous_upstream_node = self.upstream_output_connector(&InputConnector::node(*node_id, 0), network_path).and_then(|output| output.node_id());\n\t\t// let Some(previous_upstream_node_position) = previous_upstream_node.and_then(|upstream| self.position_from_downstream_node(&upstream, network_path)) else {\n\t\t// \tlog::error!(\"Could not get previous_upstream_node_position\");\n\t\t// \treturn;\n\t\t// };\n\t\tself.unload_upstream_node_click_targets(vec![*node_id], network_path);\n\t\t// Reload click target of the layer which encapsulate the chain\n\t\tif let Some(downstream_layer) = self.downstream_layer_for_chain_node(node_id, network_path) {\n\t\t\tself.unload_node_click_targets(&downstream_layer, network_path);\n\t\t}\n\t\tself.unload_all_nodes_bounding_box(network_path);\n\n\t\t// let Some(new_upstream_node_position) = previous_upstream_node.and_then(|upstream| self.position_from_downstream_node(&upstream, network_path)) else {\n\t\t// \tlog::error!(\"Could not get new_upstream_node_position\");\n\t\t// \treturn;\n\t\t// };\n\t\t// if let Some(previous_upstream_node) =   {\n\t\t// \tlet x_delta = new_upstream_node_position.x - previous_upstream_node_position.x;\n\t\t// \t// Upstream node got shifted to left, so shift all upstream absolute sole dependents\n\t\t// \tif x_delta != 0 {\n\t\t// \t\tlet upstream_absolute_nodes = SelectedNodes(\n\t\t// \t\t\tself.upstream_flow_back_from_nodes(vec![previous_upstream_node], network_path, FlowType::UpstreamFlow)\n\t\t// \t\t\t\t.into_iter()\n\t\t// \t\t\t\t.filter(|node_id| self.is_absolute(node_id, network_path))\n\t\t// \t\t\t\t.collect::<Vec<_>>(),\n\t\t// \t\t);\n\t\t// \t\tlet old_selected_nodes = std::mem::replace(self.selected_nodes_mut(network_path).unwrap(), upstream_absolute_nodes);\n\t\t// \t\tif x_delta < 0 {\n\t\t// \t\t\tfor _ in 0..x_delta.abs() {\n\t\t// \t\t\t\tself.shift_selected_nodes(Direction::Left, false, network_path);\n\t\t// \t\t\t}\n\t\t// \t\t} else {\n\t\t// \t\t\tfor _ in 0..x_delta.abs() {\n\t\t// \t\t\t\tself.shift_selected_nodes(Direction::Right, false, network_path);\n\t\t// \t\t\t}\n\t\t// \t\t}\n\t\t// \t\tlet _ = std::mem::replace(self.selected_nodes_mut(network_path).unwrap(), old_selected_nodes);\n\t\t// \t}\n\t\t// }\n\t}\n\n\tfn valid_upstream_chain_nodes(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Vec<NodeId> {\n\t\tlet InputConnector::Node {\n\t\t\tnode_id: input_connector_node_id,\n\t\t\tinput_index,\n\t\t} = input_connector\n\t\telse {\n\t\t\treturn Vec::new();\n\t\t};\n\t\tlet mut set_position_to_chain = Vec::new();\n\t\tif self.is_layer(input_connector_node_id, network_path) && *input_index == 1 || self.is_chain(input_connector_node_id, network_path) && *input_index == 0 {\n\t\t\tlet mut downstream_id = *input_connector_node_id;\n\t\t\tfor upstream_node in self\n\t\t\t\t.upstream_flow_back_from_nodes(vec![*input_connector_node_id], network_path, FlowType::HorizontalFlow)\n\t\t\t\t.skip(1)\n\t\t\t\t.collect::<Vec<_>>()\n\t\t\t{\n\t\t\t\tif self.is_layer(&upstream_node, network_path) || self.hidden_primary_output(&upstream_node, network_path) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tlet Some(outward_wires) = self.outward_wires(network_path).and_then(|outward_wires| outward_wires.get(&OutputConnector::node(upstream_node, 0))) else {\n\t\t\t\t\tlog::error!(\"Could not get outward wires in try_set_upstream_to_chain\");\n\t\t\t\t\tbreak;\n\t\t\t\t};\n\t\t\t\tif outward_wires.len() != 1 {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tlet downstream_position = self.position(&downstream_id, network_path);\n\t\t\t\tlet upstream_node_position = self.position(&upstream_node, network_path);\n\t\t\t\tif let (Some(input_connector_position), Some(new_upstream_node_position)) = (downstream_position, upstream_node_position) {\n\t\t\t\t\tif input_connector_position.y == new_upstream_node_position.y\n\t\t\t\t\t\t&& new_upstream_node_position.x >= input_connector_position.x - 9\n\t\t\t\t\t\t&& new_upstream_node_position.x <= input_connector_position.x\n\t\t\t\t\t{\n\t\t\t\t\t\tset_position_to_chain.push(upstream_node);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tdownstream_id = upstream_node;\n\t\t\t}\n\t\t}\n\t\tset_position_to_chain\n\t}\n\n\t/// Input connector is the input to the layer\n\tpub fn try_set_upstream_to_chain(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) {\n\t\t// If the new input is to a non layer node on the same y position as the input connector, or the input connector is the side input of a layer, then set it to chain position\n\t\tlet valid_upstream_chain_nodes = self.valid_upstream_chain_nodes(input_connector, network_path);\n\n\t\tfor node_id in &valid_upstream_chain_nodes {\n\t\t\tself.set_chain_position(node_id, network_path);\n\t\t}\n\n\t\t// Reload click target of the layer which used to encapsulate the node\n\t\tif !valid_upstream_chain_nodes.is_empty() {\n\t\t\tlet mut downstream_layer = Some(input_connector.node_id().unwrap());\n\t\t\twhile let Some(downstream_layer_id) = downstream_layer {\n\t\t\t\tif downstream_layer_id == input_connector.node_id().unwrap() || !self.is_layer(&downstream_layer_id, network_path) {\n\t\t\t\t\tlet Some(outward_wires) = self.outward_wires(network_path) else {\n\t\t\t\t\t\tlog::error!(\"Could not get outward wires in try_set_upstream_to_chain\");\n\t\t\t\t\t\tdownstream_layer = None;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t};\n\t\t\t\t\tdownstream_layer = outward_wires\n\t\t\t\t\t\t.get(&OutputConnector::node(downstream_layer_id, 0))\n\t\t\t\t\t\t.and_then(|outward_wires| if outward_wires.len() == 1 { outward_wires[0].node_id() } else { None });\n\t\t\t\t} else {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif let Some(downstream_layer) = downstream_layer {\n\t\t\t\tself.unload_node_click_targets(&downstream_layer, network_path);\n\t\t\t}\n\t\t}\n\t}\n\n\tfn try_set_node_to_chain(&mut self, node_id: &NodeId, network_path: &[NodeId]) {\n\t\tif let Some(outward_wires) = self\n\t\t\t.outward_wires(network_path)\n\t\t\t.and_then(|outward_wires| outward_wires.get(&OutputConnector::node(*node_id, 0)))\n\t\t\t.cloned() && outward_wires.len() == 1\n\t\t{\n\t\t\tself.try_set_upstream_to_chain(&outward_wires[0], network_path)\n\t\t}\n\t}\n\n\tpub fn force_set_upstream_to_chain(&mut self, node_id: &NodeId, network_path: &[NodeId]) {\n\t\tfor upstream_id in self.upstream_flow_back_from_nodes(vec![*node_id], network_path, FlowType::HorizontalFlow).collect::<Vec<_>>().iter() {\n\t\t\tif !self.is_layer(upstream_id, network_path)\n\t\t\t\t&& self\n\t\t\t\t\t.outward_wires(network_path)\n\t\t\t\t\t.is_some_and(|outward_wires| outward_wires.get(&OutputConnector::node(*upstream_id, 0)).is_some_and(|outward_wires| outward_wires.len() == 1))\n\t\t\t{\n\t\t\t\tself.set_chain_position(upstream_id, network_path);\n\t\t\t}\n\t\t\t// If there is an upstream layer then stop breaking the chain\n\t\t\telse {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t/// node_id is the first chain node, not the layer\n\tfn set_upstream_chain_to_absolute(&mut self, node_id: &NodeId, network_path: &[NodeId]) {\n\t\tlet Some(downstream_layer) = self.downstream_layer_for_chain_node(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get downstream layer in set_upstream_chain_to_absolute\");\n\t\t\treturn;\n\t\t};\n\t\tfor upstream_id in self.upstream_flow_back_from_nodes(vec![*node_id], network_path, FlowType::HorizontalFlow).collect::<Vec<_>>().iter() {\n\t\t\tlet Some(previous_position) = self.position(upstream_id, network_path) else {\n\t\t\t\tlog::error!(\"Could not get position in set_upstream_chain_to_absolute\");\n\t\t\t\treturn;\n\t\t\t};\n\t\t\t// Set any chain nodes to absolute positioning\n\t\t\tif self.is_chain(upstream_id, network_path) {\n\t\t\t\tself.set_absolute_position(upstream_id, previous_position, network_path);\n\t\t\t\t// Reload click target of the layer which used to encapsulate the chain\n\t\t\t\tself.unload_node_click_targets(&downstream_layer, network_path);\n\t\t\t}\n\t\t\t// If there is an upstream layer then stop breaking the chain\n\t\t\telse {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn nodes_sorted_top_to_bottom<'a>(&mut self, node_ids: impl Iterator<Item = &'a NodeId>, network_path: &[NodeId]) -> Option<Vec<NodeId>> {\n\t\tlet mut node_ids_with_position = node_ids\n\t\t\t.filter_map(|&node_id| {\n\t\t\t\tlet Some(position) = self.position(&node_id, network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get position for node {node_id} in shift_selected_nodes\");\n\t\t\t\t\treturn None;\n\t\t\t\t};\n\t\t\t\tSome((node_id, position.y))\n\t\t\t})\n\t\t\t.collect::<Vec<(NodeId, i32)>>();\n\n\t\tnode_ids_with_position.sort_unstable_by(|a, b| a.1.cmp(&b.1));\n\t\tSome(node_ids_with_position.into_iter().map(|(node_id, _)| node_id).collect::<Vec<_>>())\n\t}\n\n\t/// Used when moving layer by the layer panel, does not run any pushing logic. Moves all sole dependents of the layer as well.\n\t/// Ensure that the layer is absolute position.\n\tpub fn shift_absolute_node_position(&mut self, layer: &NodeId, shift: IVec2, network_path: &[NodeId]) {\n\t\tif shift == IVec2::ZERO {\n\t\t\treturn;\n\t\t}\n\t\tlet mut nodes_to_shift = self.upstream_nodes_below_layer(layer, network_path);\n\t\tnodes_to_shift.insert(*layer);\n\n\t\tfor node_id in nodes_to_shift {\n\t\t\tlet Some(node_to_shift_metadata) = self.node_metadata_mut(&node_id, network_path) else {\n\t\t\t\tlog::error!(\"Could not get node metadata for node {node_id} in set_layer_position\");\n\t\t\t\tcontinue;\n\t\t\t};\n\t\t\tmatch &mut node_to_shift_metadata.persistent_metadata.node_type_metadata {\n\t\t\t\tNodeTypePersistentMetadata::Layer(layer_metadata) => {\n\t\t\t\t\tif let LayerPosition::Absolute(layer_position) = &mut layer_metadata.position {\n\t\t\t\t\t\t*layer_position += shift;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tNodeTypePersistentMetadata::Node(node_metadata) => {\n\t\t\t\t\tif let NodePosition::Absolute(node_position) = &mut node_metadata.position {\n\t\t\t\t\t\t*node_position += shift;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tself.transaction_modified();\n\t\tself.unload_upstream_node_click_targets(vec![*layer], network_path);\n\t}\n\n\tpub fn shift_selected_nodes(&mut self, direction: Direction, shift_without_push: bool, network_path: &[NodeId]) {\n\t\tlet Some(mut node_ids) = self\n\t\t\t.selected_nodes_in_nested_network(network_path)\n\t\t\t.map(|selected_nodes| selected_nodes.selected_nodes().cloned().collect::<HashSet<_>>())\n\t\telse {\n\t\t\tlog::error!(\"Could not get selected nodes in shift_selected_nodes\");\n\t\t\treturn;\n\t\t};\n\t\tif !shift_without_push {\n\t\t\tfor node_id in node_ids.clone() {\n\t\t\t\tif self.is_layer(&node_id, network_path)\n\t\t\t\t\t&& let Some(owned_nodes) = self.owned_nodes(&node_id, network_path)\n\t\t\t\t{\n\t\t\t\t\tfor owned_node in owned_nodes {\n\t\t\t\t\t\tnode_ids.remove(owned_node);\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tfor selected_node in &node_ids.clone() {\n\t\t\t// Deselect chain nodes upstream from a selected layer\n\t\t\tif self.is_chain(selected_node, network_path)\n\t\t\t\t&& self\n\t\t\t\t\t.downstream_layer_for_chain_node(selected_node, network_path)\n\t\t\t\t\t.is_some_and(|downstream_layer| node_ids.contains(&downstream_layer))\n\t\t\t{\n\t\t\t\tnode_ids.remove(selected_node);\n\t\t\t}\n\t\t}\n\n\t\t// If shifting up without a push, cancel the shift if there is a stack node that cannot move up\n\t\tif direction == Direction::Up && shift_without_push {\n\t\t\tfor node_id in &node_ids {\n\t\t\t\tlet Some(node_metadata) = self.node_metadata(node_id, network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get node metadata for node {node_id} in shift_selected_nodes\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tif let NodeTypePersistentMetadata::Layer(layer_metadata) = &node_metadata.persistent_metadata.node_type_metadata\n\t\t\t\t\t&& let LayerPosition::Stack(offset) = layer_metadata.position\n\t\t\t\t{\n\t\t\t\t\t// If the upstream layer is selected, then skip\n\t\t\t\t\tlet Some(outward_wires) = self.outward_wires(network_path).and_then(|outward_wires| outward_wires.get(&OutputConnector::node(*node_id, 0))) else {\n\t\t\t\t\t\tlog::error!(\"Could not get outward wires in shift_selected_nodes\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t};\n\t\t\t\t\tif let Some(upstream_node) = outward_wires.first()\n\t\t\t\t\t\t&& node_ids.contains(&upstream_node.node_id().expect(\"Stack layer should have downstream layer\"))\n\t\t\t\t\t{\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\t// Offset cannot be negative, so cancel the shift\n\t\t\t\t\tif offset == 0 {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tlet Some(mut sorted_node_ids) = self.nodes_sorted_top_to_bottom(node_ids.iter(), network_path) else {\n\t\t\treturn;\n\t\t};\n\n\t\tif sorted_node_ids.len() != node_ids.len() {\n\t\t\tlog::error!(\"Could not get position for all nodes in shift_selected_nodes\");\n\t\t\treturn;\n\t\t}\n\n\t\t// If shifting down, then the lowest node (greatest y value) should be shifted first\n\t\tif direction == Direction::Down {\n\t\t\tsorted_node_ids.reverse();\n\t\t}\n\n\t\t// Ensure the top of each stack is only shifted left/right once (this is only for performance)\n\t\tlet mut shifted_absolute_layers = Vec::new();\n\n\t\tlet mut shifted_nodes = HashSet::new();\n\n\t\tlet shift_sign = if direction == Direction::Left || direction == Direction::Up { -1 } else { 1 };\n\n\t\tfor node_id in &sorted_node_ids {\n\t\t\tmatch direction {\n\t\t\t\tDirection::Left | Direction::Right => {\n\t\t\t\t\t// If the node is a non layer, then directly shift it\n\t\t\t\t\tif !self.is_layer(node_id, network_path) {\n\t\t\t\t\t\tself.try_shift_node(node_id, IVec2::new(shift_sign, 0), &mut shifted_nodes, network_path);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Get the downstream absolute layer (inclusive)\n\t\t\t\t\t\tlet mut downstream_absolute_layer = *node_id;\n\t\t\t\t\t\tloop {\n\t\t\t\t\t\t\tif self.is_absolute(&downstream_absolute_layer, network_path) {\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tlet Some(downstream_node) = self\n\t\t\t\t\t\t\t\t.outward_wires(network_path)\n\t\t\t\t\t\t\t\t.and_then(|outward_wires| outward_wires.get(&OutputConnector::node(downstream_absolute_layer, 0)))\n\t\t\t\t\t\t\t\t.and_then(|downstream_nodes| downstream_nodes.first())\n\t\t\t\t\t\t\t\t.and_then(|downstream_node| downstream_node.node_id())\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tlog::error!(\"Could not get downstream node when deselecting stack layer in shift_selected_nodes\");\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tdownstream_absolute_layer = downstream_node;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Shift the upstream nodes below the stack layers only once\n\t\t\t\t\t\tif !shifted_absolute_layers.contains(&downstream_absolute_layer) {\n\t\t\t\t\t\t\tshifted_absolute_layers.push(downstream_absolute_layer);\n\n\t\t\t\t\t\t\tself.try_shift_node(&downstream_absolute_layer, IVec2::new(shift_sign, 0), &mut shifted_nodes, network_path);\n\n\t\t\t\t\t\t\tif !shift_without_push {\n\t\t\t\t\t\t\t\tfor stack_nodes in self\n\t\t\t\t\t\t\t\t\t.upstream_flow_back_from_nodes(vec![downstream_absolute_layer], network_path, FlowType::PrimaryFlow)\n\t\t\t\t\t\t\t\t\t.take_while(|layer| self.is_layer(layer, network_path))\n\t\t\t\t\t\t\t\t\t.collect::<Vec<_>>()\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tfor sole_dependent in &self.upstream_nodes_below_layer(&stack_nodes, network_path) {\n\t\t\t\t\t\t\t\t\t\tif self.is_absolute(sole_dependent, network_path) {\n\t\t\t\t\t\t\t\t\t\t\tself.try_shift_node(sole_dependent, IVec2::new(shift_sign, 0), &mut shifted_nodes, network_path);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tDirection::Up | Direction::Down => {\n\t\t\t\t\tif !shift_without_push && self.is_layer(node_id, network_path) {\n\t\t\t\t\t\tself.shift_node_or_parent(node_id, shift_sign, &mut shifted_nodes, network_path);\n\t\t\t\t\t} else if !shifted_nodes.contains(node_id) {\n\t\t\t\t\t\tshifted_nodes.insert(*node_id);\n\t\t\t\t\t\tself.shift_node(node_id, IVec2::new(0, shift_sign), network_path);\n\n\t\t\t\t\t\tlet Some(network_metadata) = self.network_metadata_mut(network_path) else {\n\t\t\t\t\t\t\tlog::error!(\"Could not get nested network_metadata in export_ports\");\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t};\n\t\t\t\t\t\tif let TransientMetadata::Loaded(stack_dependents) = &mut network_metadata.transient_metadata.stack_dependents\n\t\t\t\t\t\t\t&& let Some(LayerOwner::None(offset)) = stack_dependents.get_mut(node_id)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t*offset += shift_sign;\n\t\t\t\t\t\t\tself.transaction_modified();\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\t// Shift the upstream layer so that it stays in the same place\n\t\t\t\t\t\tif self.is_layer(node_id, network_path) {\n\t\t\t\t\t\t\tlet upstream_layer = {\n\t\t\t\t\t\t\t\tself.upstream_flow_back_from_nodes(vec![*node_id], network_path, FlowType::PrimaryFlow)\n\t\t\t\t\t\t\t\t\t.nth(1)\n\t\t\t\t\t\t\t\t\t.filter(|upstream_node| self.is_stack(upstream_node, network_path))\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tif let Some(upstream_layer) = upstream_layer {\n\t\t\t\t\t\t\t\tself.shift_node(&upstream_layer, IVec2::new(0, -shift_sign), network_path);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tlet Some(stack_dependents) = self\n\t\t\t.stack_dependents(network_path)\n\t\t\t.map(|stack_dependents| stack_dependents.iter().map(|(node_id, owner)| (*node_id, owner.clone())).collect::<Vec<_>>())\n\t\telse {\n\t\t\tlog::error!(\"Could not load stack dependents in shift_selected_nodes\");\n\t\t\treturn;\n\t\t};\n\n\t\tlet mut stack_dependents_with_position = stack_dependents\n\t\t\t.iter()\n\t\t\t.filter_map(|(node_id, owner)| {\n\t\t\t\tlet LayerOwner::None(offset) = owner else {\n\t\t\t\t\treturn None;\n\t\t\t\t};\n\t\t\t\tif *offset == 0 {\n\t\t\t\t\treturn None;\n\t\t\t\t}\n\t\t\t\tif self.selected_nodes_in_nested_network(network_path).is_some_and(|selected_nodes| {\n\t\t\t\t\tselected_nodes\n\t\t\t\t\t\t.selected_nodes()\n\t\t\t\t\t\t.any(|selected_node| selected_node == node_id || self.owned_nodes(node_id, network_path).is_some_and(|owned_nodes| owned_nodes.contains(selected_node)))\n\t\t\t\t}) {\n\t\t\t\t\treturn None;\n\t\t\t\t};\n\t\t\t\tlet Some(position) = self.position(node_id, network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get position for node {node_id} in shift_selected_nodes\");\n\t\t\t\t\treturn None;\n\t\t\t\t};\n\t\t\t\tSome((*node_id, *offset, position.y))\n\t\t\t})\n\t\t\t.collect::<Vec<(NodeId, i32, i32)>>();\n\n\t\tstack_dependents_with_position.sort_unstable_by(|a, b| {\n\t\t\ta.1.signum().cmp(&b.1.signum()).then_with(|| {\n\t\t\t\t// If the node has a positive offset, then it is shifted up, so shift the top nodes first\n\t\t\t\tif a.1.signum() == 1 { a.2.cmp(&b.2) } else { b.2.cmp(&a.2) }\n\t\t\t})\n\t\t});\n\n\t\t// Try shift every node that is offset from its original position\n\t\tfor &(ref node_id, mut offset, _) in stack_dependents_with_position.iter() {\n\t\t\twhile offset != 0 {\n\t\t\t\tif self.check_collision_with_stack_dependents(node_id, -offset.signum(), network_path).is_empty() {\n\t\t\t\t\tself.vertical_shift_with_push(node_id, -offset.signum(), &mut HashSet::new(), network_path);\n\t\t\t\t\toffset += -offset.signum();\n\t\t\t\t} else {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfn try_shift_node(&mut self, node_id: &NodeId, shift: IVec2, shifted_nodes: &mut HashSet<NodeId>, network_path: &[NodeId]) {\n\t\tif !shifted_nodes.contains(node_id) {\n\t\t\tself.shift_node(node_id, shift, network_path);\n\t\t\tshifted_nodes.insert(*node_id);\n\t\t}\n\t}\n\n\tfn vertical_shift_with_push(&mut self, node_id: &NodeId, shift_sign: i32, shifted_nodes: &mut HashSet<NodeId>, network_path: &[NodeId]) {\n\t\t// Do not shift a node more than once\n\t\tif shifted_nodes.contains(node_id) {\n\t\t\treturn;\n\t\t}\n\t\tshifted_nodes.insert(*node_id);\n\n\t\tlet nodes_to_shift = self.check_collision_with_stack_dependents(node_id, shift_sign, network_path);\n\n\t\tfor node_to_shift in nodes_to_shift {\n\t\t\tself.shift_node_or_parent(&node_to_shift.0, shift_sign, shifted_nodes, network_path);\n\t\t}\n\n\t\tself.shift_node(node_id, IVec2::new(0, shift_sign), network_path);\n\n\t\tlet Some(network_metadata) = self.network_metadata_mut(network_path) else {\n\t\t\tlog::error!(\"Could not get nested network_metadata in export_ports\");\n\t\t\treturn;\n\t\t};\n\t\tlet TransientMetadata::Loaded(stack_dependents) = &mut network_metadata.transient_metadata.stack_dependents else {\n\t\t\tlog::error!(\"Stack dependents should be loaded in vertical_shift_with_push\");\n\t\t\treturn;\n\t\t};\n\n\t\tlet mut default_layer_owner = LayerOwner::None(0);\n\t\tlet layer_owner = stack_dependents.get_mut(node_id).unwrap_or_else(|| {\n\t\t\tlog::error!(\"Could not get layer owner in vertical_shift_with_push for node {node_id}\");\n\t\t\t&mut default_layer_owner\n\t\t});\n\n\t\tmatch layer_owner {\n\t\t\tLayerOwner::None(offset) => {\n\t\t\t\t*offset += shift_sign;\n\t\t\t\tself.transaction_modified();\n\t\t\t}\n\t\t\tLayerOwner::Layer(_) => {\n\t\t\t\tlog::error!(\"Node being shifted with a push should not be owned\");\n\t\t\t}\n\t\t}\n\n\t\t// Shift the upstream layer so that it stays in the same place\n\t\tif self.is_layer(node_id, network_path) {\n\t\t\tlet upstream_layer = {\n\t\t\t\tself.upstream_flow_back_from_nodes(vec![*node_id], network_path, FlowType::PrimaryFlow)\n\t\t\t\t\t.nth(1)\n\t\t\t\t\t.filter(|upstream_node| self.is_stack(upstream_node, network_path))\n\t\t\t};\n\t\t\tif let Some(upstream_layer) = upstream_layer {\n\t\t\t\tself.shift_node(&upstream_layer, IVec2::new(0, -shift_sign), network_path);\n\t\t\t}\n\t\t}\n\n\t\t// Shift the nodes that are owned by the layer (if any)\n\t\tif let Some(owned_nodes) = self.owned_nodes(node_id, network_path).cloned() {\n\t\t\tfor owned_node in owned_nodes {\n\t\t\t\tif self.is_absolute(&owned_node, network_path) {\n\t\t\t\t\tself.try_shift_node(&owned_node, IVec2::new(0, shift_sign), shifted_nodes, network_path);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfn check_collision_with_stack_dependents(&mut self, node_id: &NodeId, shift_sign: i32, network_path: &[NodeId]) -> Vec<(NodeId, LayerOwner)> {\n\t\tself.try_load_all_node_click_targets(network_path);\n\t\tself.try_load_stack_dependents(network_path);\n\t\tlet Some(stack_dependents) = self.try_get_stack_dependents(network_path) else {\n\t\t\tlog::error!(\"Could not load stack dependents in shift_selected_nodes\");\n\t\t\treturn Vec::new();\n\t\t};\n\t\t// Check collisions and for all owned nodes and recursively shift them\n\t\tlet mut nodes_to_shift = Vec::new();\n\n\t\tlet default_hashset = HashSet::new();\n\t\tlet owned_nodes = self.owned_nodes(node_id, network_path).unwrap_or(&default_hashset);\n\n\t\tfor current_node in owned_nodes.iter().chain(std::iter::once(node_id)) {\n\t\t\tfor node_to_check_collision in stack_dependents {\n\t\t\t\t// Do not check collision between any of the owned nodes or the shifted node\n\t\t\t\tif owned_nodes.contains(node_to_check_collision.0) || node_to_check_collision.0 == node_id {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif node_to_check_collision.0 == current_node {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tlet Some(mut current_node_bounding_box) = self.try_get_node_bounding_box(current_node, network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get bounding box for node {node_id} in shift_selected_nodes\");\n\t\t\t\t\tcontinue;\n\t\t\t\t};\n\n\t\t\t\tlet Some(node_bounding_box) = self.try_get_node_bounding_box(node_to_check_collision.0, network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get bounding box for node {node_to_check_collision:?} in shift_selected_nodes\");\n\t\t\t\t\tcontinue;\n\t\t\t\t};\n\t\t\t\t// If the nodes do not intersect horizontally, then there is no collision\n\t\t\t\tif current_node_bounding_box[1].x < node_bounding_box[0].x || current_node_bounding_box[0].x > node_bounding_box[1].x {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\t// Do not check collision if the nodes are currently intersecting\n\t\t\t\tif current_node_bounding_box[1].y >= node_bounding_box[0].y - 0.1 && current_node_bounding_box[0].y <= node_bounding_box[1].y + 0.1 {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tcurrent_node_bounding_box[1].y += GRID_SIZE as f64 * shift_sign as f64;\n\t\t\t\tcurrent_node_bounding_box[0].y += GRID_SIZE as f64 * shift_sign as f64;\n\n\t\t\t\tlet collision = current_node_bounding_box[1].y >= node_bounding_box[0].y - 0.1 && current_node_bounding_box[0].y <= node_bounding_box[1].y + 0.1;\n\t\t\t\tif collision {\n\t\t\t\t\tnodes_to_shift.push((*node_to_check_collision.0, node_to_check_collision.1.clone()));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tnodes_to_shift\n\t}\n\n\tfn shift_node_or_parent(&mut self, node_id: &NodeId, shift_sign: i32, shifted_nodes: &mut HashSet<NodeId>, network_path: &[NodeId]) {\n\t\tlet Some(stack_dependents) = self.stack_dependents(network_path) else {\n\t\t\tlog::error!(\"Could not load stack dependents in shift_selected_nodes\");\n\t\t\treturn;\n\t\t};\n\t\tlet Some(layer_owner) = stack_dependents.get(node_id) else {\n\t\t\tlog::error!(\"Could not get layer owner in shift_node_or_parent for node {node_id}\");\n\t\t\treturn;\n\t\t};\n\t\tmatch layer_owner {\n\t\t\tLayerOwner::Layer(layer_owner) => {\n\t\t\t\tlet layer_owner = *layer_owner;\n\t\t\t\tself.shift_node_or_parent(&layer_owner, shift_sign, shifted_nodes, network_path)\n\t\t\t}\n\t\t\tLayerOwner::None(_) => self.vertical_shift_with_push(node_id, shift_sign, shifted_nodes, network_path),\n\t\t}\n\t}\n\n\t/// Shifts a node by a certain offset without the auto layout system. If the node is a layer in a stack, the y_offset is shifted. If the node is a node in a chain, its position gets set to absolute.\n\t// TODO: Check for unnecessary unloading of click targets\n\tpub fn shift_node(&mut self, node_id: &NodeId, shift: IVec2, network_path: &[NodeId]) {\n\t\tlet Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get node_metadata for node {node_id}\");\n\t\t\treturn;\n\t\t};\n\t\tif let NodeTypePersistentMetadata::Layer(layer_metadata) = &mut node_metadata.persistent_metadata.node_type_metadata {\n\t\t\tif let LayerPosition::Absolute(layer_position) = &mut layer_metadata.position {\n\t\t\t\t*layer_position += shift;\n\t\t\t\tself.transaction_modified();\n\t\t\t} else if let LayerPosition::Stack(y_offset) = &mut layer_metadata.position {\n\t\t\t\tlet shifted_y_offset = *y_offset as i32 + shift.y;\n\n\t\t\t\t// A layer can only be shifted to a positive y_offset\n\t\t\t\tif shifted_y_offset < 0 {\n\t\t\t\t\tlog::error!(\n\t\t\t\t\t\t\"Space should be made above the layer before shifting it up. Layer {node_id} current y_offset: {y_offset} shift: {}\",\n\t\t\t\t\t\tshift.y\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif shift.x != 0 {\n\t\t\t\t\tlog::error!(\"Stack layer {node_id} cannot be shifted horizontally.\");\n\t\t\t\t}\n\n\t\t\t\tlet new_y_offset = shifted_y_offset.max(0) as u32;\n\t\t\t\tif *y_offset == new_y_offset {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t*y_offset = new_y_offset;\n\t\t\t\tself.transaction_modified();\n\t\t\t}\n\t\t\t// Unload click targets for all upstream nodes, since they may have been derived from the node that was shifted\n\t\t\tself.unload_upstream_node_click_targets(vec![*node_id], network_path);\n\t\t} else if let NodeTypePersistentMetadata::Node(node_metadata) = &mut node_metadata.persistent_metadata.node_type_metadata {\n\t\t\tif let NodePosition::Absolute(node_metadata) = &mut node_metadata.position {\n\t\t\t\t*node_metadata += shift;\n\t\t\t\tself.transaction_modified();\n\t\t\t\t// Unload click targets for all upstream nodes, since they may have been derived from the node that was shifted\n\t\t\t\tself.unload_upstream_node_click_targets(vec![*node_id], network_path);\n\t\t\t\tself.try_set_node_to_chain(node_id, network_path);\n\t\t\t} else if let NodePosition::Chain = node_metadata.position {\n\t\t\t\tself.set_upstream_chain_to_absolute(node_id, network_path);\n\t\t\t\tself.shift_node(node_id, shift, network_path);\n\t\t\t}\n\t\t}\n\t\t// Unload click targets for all upstream nodes, since they may have been derived from the node that was shifted\n\t\tself.unload_upstream_node_click_targets(vec![*node_id], network_path);\n\t\tself.unload_all_nodes_bounding_box(network_path);\n\t}\n\n\t/// Disconnect the layers primary output and the input to the last non layer node feeding into it through primary flow, reconnects, then moves the layer to the new layer and stack index\n\tpub fn move_layer_to_stack(&mut self, layer: LayerNodeIdentifier, mut parent: LayerNodeIdentifier, mut insert_index: usize, network_path: &[NodeId]) {\n\t\t// Prevent moving an artboard anywhere but to the ROOT_PARENT child stack\n\t\tif self.is_artboard(&layer.to_node(), network_path) && parent != LayerNodeIdentifier::ROOT_PARENT {\n\t\t\tlog::error!(\"Artboard can only be moved to the root parent stack\");\n\t\t\treturn;\n\t\t}\n\n\t\t// A layer is considered to be the height of that layer plus the height to the upstream layer sibling\n\t\t// If a non artboard layer is attempted to be connected to the exports, and there is already an artboard connected, then connect the layer to the artboard.\n\t\tif let Some(first_layer) = LayerNodeIdentifier::ROOT_PARENT.children(&self.document_metadata).next()\n\t\t\t&& parent == LayerNodeIdentifier::ROOT_PARENT\n\t\t\t&& self\n\t\t\t\t.reference(&layer.to_node(), network_path)\n\t\t\t\t.is_none_or(|reference| reference != DefinitionIdentifier::Network(\"Artboard\".into()))\n\t\t\t&& self.is_artboard(&first_layer.to_node(), network_path)\n\t\t{\n\t\t\tparent = first_layer;\n\t\t\tinsert_index = 0;\n\t\t}\n\n\t\tlet Some(layer_to_move_position) = self.position(&layer.to_node(), network_path) else {\n\t\t\tlog::error!(\"Could not get layer_to_move_position in move_layer_to_stack\");\n\t\t\treturn;\n\t\t};\n\n\t\tlet mut lowest_upstream_node_height = 0;\n\t\tfor upstream_node in self\n\t\t\t.upstream_flow_back_from_nodes(vec![layer.to_node()], network_path, FlowType::LayerChildrenUpstreamFlow)\n\t\t\t.collect::<Vec<_>>()\n\t\t{\n\t\t\tlet Some(upstream_node_position) = self.position(&upstream_node, network_path) else {\n\t\t\t\tlog::error!(\"Could not get upstream node position in move_layer_to_stack\");\n\t\t\t\treturn;\n\t\t\t};\n\t\t\tlowest_upstream_node_height = lowest_upstream_node_height.max((upstream_node_position.y - layer_to_move_position.y).max(0) as u32);\n\t\t}\n\n\t\t// If the moved layer is a child of the new parent, then get its index after the disconnect\n\t\tif let Some(moved_layer_previous_index) = parent.children(&self.document_metadata).position(|child| child == layer) {\n\t\t\t// Adjust the insert index if the layer's previous index is less than the insert index\n\t\t\tif moved_layer_previous_index < insert_index {\n\t\t\t\tinsert_index -= 1;\n\t\t\t}\n\t\t}\n\n\t\t// Disconnect layer to move\n\t\tself.remove_references_from_network(&layer.to_node(), network_path);\n\n\t\tlet post_node = ModifyInputsContext::get_post_node_with_index(self, parent, insert_index);\n\n\t\t// Get the previous input to the post node before inserting the layer\n\t\tlet Some(post_node_input) = self.input_from_connector(&post_node, network_path).cloned() else {\n\t\t\tlog::error!(\"Could not get previous input in move_layer_to_stack for parent {parent:?} and insert_index {insert_index}\");\n\t\t\treturn;\n\t\t};\n\n\t\tlet Some(previous_layer_position) = self.position(&layer.to_node(), network_path) else {\n\t\t\tlog::error!(\"Could not get previous layer position in move_layer_to_stack\");\n\t\t\treturn;\n\t\t};\n\n\t\tlet after_move_post_layer_position = if let Some(post_node_id) = post_node.node_id() {\n\t\t\tself.position(&post_node_id, network_path)\n\t\t} else {\n\t\t\tSome(IVec2::new(8, -3))\n\t\t};\n\n\t\tlet Some(after_move_post_layer_position) = after_move_post_layer_position else {\n\t\t\tlog::error!(\"Could not get post node position in move_layer_to_stack\");\n\t\t\treturn;\n\t\t};\n\n\t\t// Get the height of the downstream node if inserting into a stack\n\t\tlet mut downstream_height = 0;\n\t\tlet inserting_into_stack =\n\t\t\t!(post_node.input_index() == 1 || matches!(post_node, InputConnector::Export(_)) || !post_node.node_id().is_some_and(|post_node_id| self.is_layer(&post_node_id, network_path)));\n\t\tif inserting_into_stack && let Some(downstream_node) = post_node.node_id() {\n\t\t\tlet Some(downstream_node_position) = self.position(&downstream_node, network_path) else {\n\t\t\t\tlog::error!(\"Could not get downstream node position in move_layer_to_stack\");\n\t\t\t\treturn;\n\t\t\t};\n\t\t\tlet mut lowest_y_position = downstream_node_position.y + 3;\n\n\t\t\tfor bottom_position in self.upstream_nodes_below_layer(&downstream_node, network_path).iter().filter_map(|node_id| {\n\t\t\t\tlet is_layer = self.is_layer(node_id, network_path);\n\t\t\t\tself.position(node_id, network_path).map(|position| position.y + if is_layer { 3 } else { 2 })\n\t\t\t}) {\n\t\t\t\tlowest_y_position = lowest_y_position.max(bottom_position);\n\t\t\t}\n\t\t\tdownstream_height = lowest_y_position - (downstream_node_position.y + 3);\n\t\t}\n\n\t\tlet mut highest_y_position = layer_to_move_position.y;\n\t\tlet mut lowest_y_position = layer_to_move_position.y;\n\n\t\tfor (bottom_position, top_position) in self.upstream_nodes_below_layer(&layer.to_node(), network_path).iter().filter_map(|node_id| {\n\t\t\tlet is_layer = self.is_layer(node_id, network_path);\n\t\t\tlet bottom_position = self.position(node_id, network_path).map(|position| position.y + if is_layer { 3 } else { 2 });\n\t\t\tlet top_position = self.position(node_id, network_path).map(|position| if is_layer { position.y - 1 } else { position.y });\n\t\t\tbottom_position.zip(top_position)\n\t\t}) {\n\t\t\thighest_y_position = highest_y_position.min(top_position);\n\t\t\tlowest_y_position = lowest_y_position.max(bottom_position);\n\t\t}\n\t\tlet height_above_layer = layer_to_move_position.y - highest_y_position + downstream_height;\n\t\tlet height_below_layer = lowest_y_position - layer_to_move_position.y - 3;\n\n\t\t// If there is an upstream node in the new location for the layer, create space for the moved layer by shifting the upstream node down\n\t\tif let Some(upstream_node_id) = post_node_input.as_node() {\n\t\t\t// Select the layer to move to ensure the shifting works correctly\n\t\t\tlet Some(selected_nodes) = self.selected_nodes_mut(network_path) else {\n\t\t\t\tlog::error!(\"Could not get selected nodes in move_layer_to_stack\");\n\t\t\t\treturn;\n\t\t\t};\n\t\t\tlet old_selected_nodes = selected_nodes.replace_with(vec![upstream_node_id]);\n\n\t\t\t// Create the minimum amount space for the moved layer\n\t\t\tfor _ in 0..3 {\n\t\t\t\tself.vertical_shift_with_push(&upstream_node_id, 1, &mut HashSet::new(), network_path);\n\t\t\t}\n\n\t\t\tlet Some(stack_position) = self.position(&upstream_node_id, network_path) else {\n\t\t\t\tlog::error!(\"Could not get stack position in move_layer_to_stack\");\n\t\t\t\treturn;\n\t\t\t};\n\n\t\t\tlet current_gap = stack_position.y - (after_move_post_layer_position.y + 2);\n\t\t\tlet target_gap = 1 + height_above_layer + 2 + height_below_layer + 1;\n\n\t\t\tfor _ in 0..(target_gap - current_gap).max(0) {\n\t\t\t\tself.vertical_shift_with_push(&upstream_node_id, 1, &mut HashSet::new(), network_path);\n\t\t\t}\n\n\t\t\tlet _ = self.selected_nodes_mut(network_path).unwrap().replace_with(old_selected_nodes);\n\t\t}\n\n\t\t// If true, this node should be inserted before the post node (toward root from the layer), and all outward wires from the pre node should be moved to its output.\n\t\tlet mut insert_node_after_post = false;\n\n\t\t// Connect the layer to a parent layer/node at the top of the stack, or a non layer node midway down the stack\n\t\tif !inserting_into_stack {\n\t\t\tmatch post_node_input {\n\t\t\t\t// Create a new stack\n\t\t\t\tNodeInput::Value { .. } | NodeInput::Scope(_) | NodeInput::Inline(_) | NodeInput::Reflection(_) => {\n\t\t\t\t\tself.create_wire(&OutputConnector::node(layer.to_node(), 0), &post_node, network_path);\n\n\t\t\t\t\tlet final_layer_position = after_move_post_layer_position + IVec2::new(-8, 3);\n\t\t\t\t\tlet shift = final_layer_position - previous_layer_position;\n\t\t\t\t\tself.shift_absolute_node_position(&layer.to_node(), shift, network_path);\n\t\t\t\t}\n\t\t\t\t// Move to the top of a stack.\n\t\t\t\tNodeInput::Node { node_id, .. } => {\n\t\t\t\t\tlet Some(stack_top_position) = self.position(&node_id, network_path) else {\n\t\t\t\t\t\tlog::error!(\"Could not get stack x position in move_layer_to_stack\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t};\n\n\t\t\t\t\tlet final_layer_position = IVec2::new(stack_top_position.x, after_move_post_layer_position.y + 3 + height_above_layer);\n\t\t\t\t\tlet shift = final_layer_position - previous_layer_position;\n\t\t\t\t\tself.shift_absolute_node_position(&layer.to_node(), shift, network_path);\n\t\t\t\t\tinsert_node_after_post = true;\n\t\t\t\t}\n\t\t\t\tNodeInput::Import { .. } => {\n\t\t\t\t\tlog::error!(\"Cannot move post node to parent which connects to the imports\")\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tmatch post_node_input {\n\t\t\t\t// Move to the bottom of the stack\n\t\t\t\tNodeInput::Value { .. } | NodeInput::Scope(_) | NodeInput::Inline(_) | NodeInput::Reflection(_) => {\n\t\t\t\t\tlet offset = after_move_post_layer_position - previous_layer_position + IVec2::new(0, 3 + height_above_layer);\n\t\t\t\t\tself.shift_absolute_node_position(&layer.to_node(), offset, network_path);\n\t\t\t\t\tself.create_wire(&OutputConnector::node(layer.to_node(), 0), &post_node, network_path);\n\t\t\t\t}\n\t\t\t\t// Insert into the stack\n\t\t\t\tNodeInput::Node { .. } => {\n\t\t\t\t\tlet final_layer_position = after_move_post_layer_position + IVec2::new(0, 3 + height_above_layer);\n\t\t\t\t\tlet shift = final_layer_position - previous_layer_position;\n\t\t\t\t\tself.shift_absolute_node_position(&layer.to_node(), shift, network_path);\n\t\t\t\t\tinsert_node_after_post = true;\n\t\t\t\t}\n\t\t\t\tNodeInput::Import { .. } => {\n\t\t\t\t\tlog::error!(\"Cannot move post node to parent which connects to the imports\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif insert_node_after_post {\n\t\t\tself.insert_node_between(&layer.to_node(), &post_node, 0, network_path);\n\n\t\t\t// Get the other wires which need to be moved to the output of the moved layer\n\t\t\tlet layer_input_connector = InputConnector::node(layer.to_node(), 0);\n\t\t\tlet other_outward_wires = self\n\t\t\t\t.upstream_output_connector(&layer_input_connector, network_path)\n\t\t\t\t.and_then(|pre_node_output| self.outward_wires(network_path).and_then(|wires| wires.get(&pre_node_output)))\n\t\t\t\t.map(|other| {\n\t\t\t\t\tother\n\t\t\t\t\t\t.iter()\n\t\t\t\t\t\t.filter(|other_input_connector| **other_input_connector != layer_input_connector)\n\t\t\t\t\t\t.cloned()\n\t\t\t\t\t\t.collect::<Vec<_>>()\n\t\t\t\t})\n\t\t\t\t.unwrap_or_default();\n\n\t\t\t// Disconnect and reconnect\n\t\t\tfor other_outward_wire in &other_outward_wires {\n\t\t\t\tself.disconnect_input(other_outward_wire, network_path);\n\t\t\t\tself.create_wire(&OutputConnector::node(layer.to_node(), 0), other_outward_wire, network_path);\n\t\t\t}\n\t\t}\n\t\tself.unload_upstream_node_click_targets(vec![layer.to_node()], network_path);\n\t}\n\n\t// Insert a node onto a wire. Ensure insert_node_input_index is an exposed input\n\tpub fn insert_node_between(&mut self, node_id: &NodeId, input_connector: &InputConnector, insert_node_input_index: usize, network_path: &[NodeId]) {\n\t\tif self.number_of_displayed_inputs(node_id, network_path) == 0 {\n\t\t\tlog::error!(\"Cannot insert a node onto a wire with no exposed inputs\");\n\t\t\treturn;\n\t\t}\n\n\t\tlet Some(upstream_output) = self.upstream_output_connector(input_connector, network_path) else {\n\t\t\tlog::error!(\"Could not get upstream output in insert_node_between\");\n\t\t\treturn;\n\t\t};\n\n\t\t// Disconnect the previous input\n\t\tself.disconnect_input(input_connector, network_path);\n\n\t\t// Connect the input connector to the new node\n\t\tself.create_wire(&OutputConnector::node(*node_id, 0), input_connector, network_path);\n\n\t\t// Connect the new node to the previous node\n\t\tself.create_wire(&upstream_output, &InputConnector::node(*node_id, insert_node_input_index), network_path);\n\t}\n\n\t// Moves a node and to the start of a layer chain (feeding into the secondary input of the layer)\n\tpub fn move_node_to_chain_start(&mut self, node_id: &NodeId, parent: LayerNodeIdentifier, network_path: &[NodeId]) {\n\t\tlet Some(current_input) = self.input_from_connector(&InputConnector::node(parent.to_node(), 1), network_path) else {\n\t\t\tlog::error!(\"Could not get input for node {node_id}\");\n\t\t\treturn;\n\t\t};\n\t\tif matches!(current_input, NodeInput::Value { .. }) {\n\t\t\tself.create_wire(&OutputConnector::node(*node_id, 0), &InputConnector::node(parent.to_node(), 1), network_path);\n\t\t\tself.set_chain_position(node_id, network_path);\n\t\t} else {\n\t\t\t// Insert the node in the gap and set the upstream to a chain\n\t\t\tself.insert_node_between(node_id, &InputConnector::node(parent.to_node(), 1), 0, network_path);\n\t\t\tself.force_set_upstream_to_chain(node_id, network_path);\n\t\t}\n\t}\n}\n\n#[derive(PartialEq)]\npub enum FlowType {\n\t/// Iterate over all upstream nodes (inclusive) from every input (the primary and all secondary).\n\tUpstreamFlow,\n\t/// Iterate over nodes (inclusive) connected to the primary input.\n\tPrimaryFlow,\n\t/// Iterate over the secondary input (inclusive) for layer nodes and primary input for non layer nodes.\n\tHorizontalFlow,\n\t/// Same as horizontal flow, but only iterates over connections to primary outputs\n\tHorizontalPrimaryOutputFlow,\n\t/// Upstream flow starting from the either the node (inclusive) or secondary input of the layer (not inclusive).\n\tLayerChildrenUpstreamFlow,\n}\n/// Iterate over upstream nodes. The behavior changes based on the `flow_type` that's set.\n/// - [`FlowType::UpstreamFlow`]: iterates over all upstream nodes from every input (the primary and all secondary).\n/// - [`FlowType::PrimaryFlow`]: iterates along the horizontal inputs of nodes, so in the case of a node chain `a -> b -> c`, this would yield `c, b, a` if we started from `c`.\n/// - [`FlowType::HorizontalFlow`]: iterates over the secondary input for layer nodes and primary input for non layer nodes.\n/// - [`FlowType::LayerChildrenUpstreamFlow`]: iterates over all upstream nodes from the secondary input of the node.\nstruct FlowIter<'a> {\n\tstack: Vec<NodeId>,\n\tnetwork: &'a NodeNetwork,\n\tnetwork_metadata: &'a NodeNetworkMetadata,\n\tflow_type: FlowType,\n}\nimpl Iterator for FlowIter<'_> {\n\ttype Item = NodeId;\n\tfn next(&mut self) -> Option<Self::Item> {\n\t\tloop {\n\t\t\tlet node_id = self.stack.pop()?;\n\n\t\t\tif let (Some(document_node), Some(node_metadata)) = (self.network.nodes.get(&node_id), self.network_metadata.persistent_metadata.node_metadata.get(&node_id)) {\n\t\t\t\tlet skip = if matches!(self.flow_type, FlowType::HorizontalFlow | FlowType::HorizontalPrimaryOutputFlow) && node_metadata.persistent_metadata.is_layer() {\n\t\t\t\t\t1\n\t\t\t\t} else {\n\t\t\t\t\t0\n\t\t\t\t};\n\t\t\t\tlet take = if self.flow_type == FlowType::UpstreamFlow { u32::MAX as usize } else { 1 };\n\t\t\t\tlet inputs = document_node.inputs.iter().skip(skip).take(take);\n\n\t\t\t\tlet node_ids = inputs.filter_map(|input| match input {\n\t\t\t\t\tNodeInput::Node { output_index, .. } if self.flow_type == FlowType::HorizontalPrimaryOutputFlow && *output_index != 0 => None,\n\t\t\t\t\tNodeInput::Node { node_id, .. } => Some(node_id),\n\t\t\t\t\t_ => None,\n\t\t\t\t});\n\n\t\t\t\tself.stack.extend(node_ids);\n\n\t\t\t\treturn Some(node_id);\n\t\t\t}\n\t\t}\n\t}\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]\npub enum ImportOrExport {\n\tImport(usize),\n\tExport(usize),\n}\n\n/// Represents an input connector with index based on the [`DocumentNode::inputs`] index, not the visible input index\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]\npub enum InputConnector {\n\t#[serde(rename = \"node\")]\n\tNode {\n\t\t#[serde(rename = \"nodeId\")]\n\t\tnode_id: NodeId,\n\t\t#[serde(rename = \"inputIndex\")]\n\t\tinput_index: usize,\n\t},\n\t#[serde(rename = \"export\")]\n\tExport(usize),\n}\n\nimpl Default for InputConnector {\n\tfn default() -> Self {\n\t\tInputConnector::Export(0)\n\t}\n}\n\nimpl InputConnector {\n\tpub fn node(node_id: NodeId, input_index: usize) -> Self {\n\t\tInputConnector::Node { node_id, input_index }\n\t}\n\n\tpub fn input_index(&self) -> usize {\n\t\tmatch self {\n\t\t\tInputConnector::Node { input_index, .. } => *input_index,\n\t\t\tInputConnector::Export(input_index) => *input_index,\n\t\t}\n\t}\n\n\tpub fn node_id(&self) -> Option<NodeId> {\n\t\tmatch self {\n\t\t\tInputConnector::Node { node_id, .. } => Some(*node_id),\n\t\t\t_ => None,\n\t\t}\n\t}\n}\n\n/// Represents an output connector\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize)]\npub enum OutputConnector {\n\t#[serde(rename = \"node\")]\n\tNode {\n\t\t#[serde(rename = \"nodeId\")]\n\t\tnode_id: NodeId,\n\t\t#[serde(rename = \"outputIndex\")]\n\t\toutput_index: usize,\n\t},\n\t#[serde(rename = \"import\")]\n\tImport(usize),\n}\n\nimpl Default for OutputConnector {\n\tfn default() -> Self {\n\t\tOutputConnector::Import(0)\n\t}\n}\n\nimpl OutputConnector {\n\tpub fn node(node_id: NodeId, output_index: usize) -> Self {\n\t\tOutputConnector::Node { node_id, output_index }\n\t}\n\n\tpub fn index(&self) -> usize {\n\t\tmatch self {\n\t\t\tOutputConnector::Node { output_index, .. } => *output_index,\n\t\t\tOutputConnector::Import(output_index) => *output_index,\n\t\t}\n\t}\n\n\tpub fn node_id(&self) -> Option<NodeId> {\n\t\tmatch self {\n\t\t\tOutputConnector::Node { node_id, .. } => Some(*node_id),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tpub fn from_input(input: &NodeInput) -> Option<Self> {\n\t\tmatch input {\n\t\t\tNodeInput::Import { import_index, .. } => Some(Self::Import(*import_index)),\n\t\t\tNodeInput::Node { node_id, output_index, .. } => Some(Self::node(*node_id, *output_index)),\n\t\t\t_ => None,\n\t\t}\n\t}\n}\n\n#[derive(Debug, Clone)]\npub struct Ports {\n\tinput_ports: Vec<(usize, ClickTarget)>,\n\toutput_ports: Vec<(usize, ClickTarget)>,\n}\n\nimpl Default for Ports {\n\tfn default() -> Self {\n\t\tSelf::new()\n\t}\n}\n\nimpl Ports {\n\tpub fn new() -> Ports {\n\t\tPorts {\n\t\t\tinput_ports: Vec::new(),\n\t\t\toutput_ports: Vec::new(),\n\t\t}\n\t}\n\n\tpub fn click_targets(&self) -> impl Iterator<Item = &ClickTarget> {\n\t\tself.input_ports\n\t\t\t.iter()\n\t\t\t.map(|(_, click_target)| click_target)\n\t\t\t.chain(self.output_ports.iter().map(|(_, click_target)| click_target))\n\t}\n\n\tpub fn input_ports(&self) -> impl Iterator<Item = &(usize, ClickTarget)> {\n\t\tself.input_ports.iter()\n\t}\n\n\tpub fn output_ports(&self) -> impl Iterator<Item = &(usize, ClickTarget)> {\n\t\tself.output_ports.iter()\n\t}\n\n\tfn insert_input_port_at_center(&mut self, input_index: usize, center: DVec2) {\n\t\tlet subpath = Subpath::new_ellipse(center - DVec2::new(8., 8.), center + DVec2::new(8., 8.));\n\t\tself.insert_custom_input_port(input_index, ClickTarget::new_with_subpath(subpath, 0.));\n\t}\n\n\tfn insert_custom_input_port(&mut self, input_index: usize, click_target: ClickTarget) {\n\t\tself.input_ports.push((input_index, click_target));\n\t}\n\n\tfn insert_output_port_at_center(&mut self, output_index: usize, center: DVec2) {\n\t\tlet subpath = Subpath::new_ellipse(center - DVec2::new(8., 8.), center + DVec2::new(8., 8.));\n\t\tself.insert_custom_output_port(output_index, ClickTarget::new_with_subpath(subpath, 0.));\n\t}\n\n\tfn insert_custom_output_port(&mut self, output_index: usize, click_target: ClickTarget) {\n\t\tself.output_ports.push((output_index, click_target));\n\t}\n\n\tfn insert_node_input(&mut self, input_index: usize, row_index: usize, node_top_left: DVec2) {\n\t\t// The center of the click target is always 24 px down from the top left corner of the node\n\t\tlet center = node_top_left + DVec2::new(0., 24. + 24. * row_index as f64);\n\t\tself.insert_input_port_at_center(input_index, center);\n\t}\n\n\tfn insert_node_output(&mut self, output_index: usize, node_top_left: DVec2) {\n\t\t// The center of the click target is always 24 px down from the top left corner of the node\n\t\tlet center = node_top_left + DVec2::new(5. * 24., 24. + 24. * output_index as f64);\n\t\tself.insert_output_port_at_center(output_index, center);\n\t}\n\n\tfn insert_layer_input(&mut self, input_index: usize, node_top_left: DVec2) {\n\t\tlet center = if input_index == 0 {\n\t\t\tnode_top_left + DVec2::new(2. * 24., 24. * 2. + 8.)\n\t\t} else {\n\t\t\tnode_top_left + DVec2::new(0., 24. * 1.)\n\t\t};\n\t\tself.insert_input_port_at_center(input_index, center);\n\t}\n\n\tfn insert_layer_output(&mut self, node_top_left: DVec2) {\n\t\t// The center of the click target is always 24 px down from the top left corner of the node\n\t\tlet center = node_top_left + DVec2::new(2. * 24., -8.);\n\t\tself.insert_output_port_at_center(0, center);\n\t}\n\n\tpub fn clicked_input_port_from_point(&self, point: DVec2) -> Option<usize> {\n\t\tself.input_ports.iter().find_map(|(port, click_target)| click_target.intersect_point_no_stroke(point).then_some(*port))\n\t}\n\n\tpub fn clicked_output_port_from_point(&self, point: DVec2) -> Option<usize> {\n\t\tself.output_ports.iter().find_map(|(port, click_target)| click_target.intersect_point_no_stroke(point).then_some(*port))\n\t}\n\n\tpub fn input_port_position(&self, index: usize) -> Option<DVec2> {\n\t\tself.input_ports.iter().find_map(|(port_index, click_target)| {\n\t\t\tif *port_index == index {\n\t\t\t\tclick_target.bounding_box().map(|bounds| bounds[0] + DVec2::new(8., 8.))\n\t\t\t} else {\n\t\t\t\tNone\n\t\t\t}\n\t\t})\n\t}\n\n\tpub fn output_port_position(&self, index: usize) -> Option<DVec2> {\n\t\tself.output_ports.iter().find_map(|(port_index, click_target)| {\n\t\t\tif *port_index == index {\n\t\t\t\tclick_target.bounding_box().map(|bounds| bounds[0] + DVec2::new(8., 8.))\n\t\t\t} else {\n\t\t\t\tNone\n\t\t\t}\n\t\t})\n\t}\n}\n\n#[derive(PartialEq, Debug, Clone, Copy, Hash, Default, serde::Serialize, serde::Deserialize)]\npub struct RootNode {\n\tpub node_id: NodeId,\n\tpub output_index: usize,\n}\n\nimpl RootNode {\n\tpub fn to_connector(&self) -> OutputConnector {\n\t\tOutputConnector::Node {\n\t\t\tnode_id: self.node_id,\n\t\t\toutput_index: self.output_index,\n\t\t}\n\t}\n}\n\n#[derive(PartialEq, Debug, Clone, Copy, Hash, Default, serde::Serialize, serde::Deserialize)]\npub enum Previewing {\n\t/// If there is a node to restore the connection to the export for, then it is stored in the option.\n\t/// Otherwise, nothing gets restored and the primary export is disconnected.\n\tYes { root_node_to_restore: Option<RootNode> },\n\t#[default]\n\tNo,\n}\n\n/// All fields in NetworkMetadata should automatically be updated by using the network interface API. If a field is none then it should be calculated based on the network state.\n#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]\npub struct NodeNetworkMetadata {\n\tpub persistent_metadata: NodeNetworkPersistentMetadata,\n\t#[serde(skip)]\n\tpub transient_metadata: NodeNetworkTransientMetadata,\n}\n\nimpl Clone for NodeNetworkMetadata {\n\tfn clone(&self) -> Self {\n\t\tNodeNetworkMetadata {\n\t\t\tpersistent_metadata: self.persistent_metadata.clone(),\n\t\t\ttransient_metadata: Default::default(),\n\t\t}\n\t}\n}\n\nimpl PartialEq for NodeNetworkMetadata {\n\tfn eq(&self, other: &Self) -> bool {\n\t\tself.persistent_metadata == other.persistent_metadata\n\t}\n}\n\nimpl NodeNetworkMetadata {\n\tpub fn nested_metadata(&self, nested_path: &[NodeId]) -> Option<&Self> {\n\t\tlet mut network_metadata = Some(self);\n\n\t\tfor segment in nested_path {\n\t\t\tnetwork_metadata = network_metadata\n\t\t\t\t.and_then(|network| network.persistent_metadata.node_metadata.get(segment))\n\t\t\t\t.and_then(|node| node.persistent_metadata.network_metadata.as_ref());\n\t\t}\n\t\tnetwork_metadata\n\t}\n\n\t/// Get the mutable nested network given by the path of node ids\n\tpub fn nested_metadata_mut(&mut self, nested_path: &[NodeId]) -> Option<&mut Self> {\n\t\tlet mut network_metadata = Some(self);\n\n\t\tfor segment in nested_path {\n\t\t\tnetwork_metadata = network_metadata\n\t\t\t\t.and_then(|network: &mut NodeNetworkMetadata| network.persistent_metadata.node_metadata.get_mut(segment))\n\t\t\t\t.and_then(|node| node.persistent_metadata.network_metadata.as_mut());\n\t\t}\n\t\tnetwork_metadata\n\t}\n}\n\n#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct NodeNetworkPersistentMetadata {\n\t/// The identifier for the node definition created for custom network nodes in [`DocumentNodeDefinition`].\n\t/// It is only used to associate network nodes with their definition. Protonodes use their ProtonodeIdentifier.\n\t/// The reference is removed once the node is modified, since the node now stores its own implementation and inputs.\n\t/// TODO: Used during serialization/deserialization to prevent storing implementation or inputs (and possible other fields) if they are the same as the definition.\n\t/// TODO: Implement node versioning so that references to old nodes can be updated to the new node definition.\n\tpub reference: Option<String>,\n\t/// Node metadata must exist for every document node in the network\n\t#[serde(serialize_with = \"graphene_std::vector::serialize_hashmap\", deserialize_with = \"graphene_std::vector::deserialize_hashmap\")]\n\tpub node_metadata: HashMap<NodeId, DocumentNodeMetadata>,\n\t/// Cached metadata for each node, which is calculated when adding a node to node_metadata\n\t/// Indicates whether the network is currently rendered with a particular node that is previewed, and if so, which connection should be restored when the preview ends.\n\tpub previewing: Previewing,\n\t// Stores the transform and navigation state for the network\n\tpub navigation_metadata: NavigationMetadata,\n\t/// Stack of selection snapshots for previous history states.\n\t// TODO: Use `#[serde(skip)]` here instead? @TrueDoctor claims this isn't valid but hasn't satisfactorily explained how it differs from the situation where `#[serde(default)]` fills in the default value. From brief testing, skip seems to work without issue.\n\t#[serde(default)]\n\tpub selection_undo_history: VecDeque<SelectedNodes>,\n\t/// Stack of selection snapshots for future history states.\n\t// TODO: Use `#[serde(skip)]` here instead? See above.\n\t#[serde(default)]\n\tpub selection_redo_history: VecDeque<SelectedNodes>,\n}\n\n/// This is the same as Option, but more clear in the context of having cached metadata either being loaded or unloaded\n#[derive(Debug, Default, Clone)]\npub enum TransientMetadata<T> {\n\tLoaded(T),\n\t#[default]\n\tUnloaded,\n}\n\nimpl<T> TransientMetadata<T> {\n\t/// Set the current transient metadata to unloaded\n\tpub fn unload(&mut self) {\n\t\t*self = TransientMetadata::Unloaded;\n\t}\n\n\tpub fn is_loaded(&self) -> bool {\n\t\tmatches!(self, TransientMetadata::Loaded(_))\n\t}\n}\n\n/// If some network calculation is too slow to compute for every usage, cache the data here\n#[derive(Debug, Default, Clone)]\npub struct NodeNetworkTransientMetadata {\n\tpub selected_nodes: SelectedNodes,\n\t/// Sole dependents of the top of the stacks of all selected nodes. Used to determine which nodes are checked for collision when shifting.\n\t/// The LayerOwner is used to determine whether the collided node should be shifted, or the layer that owns it.\n\tpub stack_dependents: TransientMetadata<HashMap<NodeId, LayerOwner>>,\n\t/// Cache for the bounding box around all nodes in node graph space.\n\tpub all_nodes_bounding_box: TransientMetadata<[DVec2; 2]>,\n\t// /// Cache bounding box for all \"groups of nodes\", which will be used to prevent overlapping nodes\n\t// node_group_bounding_box: Vec<(Subpath<ManipulatorGroupId>, Vec<Nodes>)>,\n\t/// Cache for all outward wire connections\n\tpub outward_wires: TransientMetadata<HashMap<OutputConnector, Vec<InputConnector>>>,\n\t/// All export connector click targets\n\tpub import_export_ports: TransientMetadata<Ports>,\n\t/// Click targets for adding, removing, and moving import/export ports\n\tpub modify_import_export: TransientMetadata<ModifyImportExportClickTarget>,\n\n\t// Wires from the exports\n\tpub wires: Vec<TransientMetadata<WirePathUpdate>>,\n}\n\n#[derive(Debug, Clone)]\npub struct ModifyImportExportClickTarget {\n\t// Subtract icon that appears when hovering over an import/export\n\tpub remove_imports_exports: Ports,\n\t// Grip drag icon that appears when hovering over an import/export\n\tpub reorder_imports_exports: Ports,\n}\n\n#[derive(Debug, Clone)]\npub struct NetworkEdgeDistance {\n\t/// The viewport pixel distance between the left edge of the node graph and the exports.\n\tpub exports_to_edge_distance: DVec2,\n\t/// The viewport pixel distance between the left edge of the node graph and the imports.\n\tpub imports_to_edge_distance: DVec2,\n}\n\n#[derive(Debug, Clone)]\npub enum LayerOwner {\n\t// Used to get the layer that should be shifted when there is a collision.\n\tLayer(NodeId),\n\t// The vertical offset of a node from the start of its shift. Should be reset when the drag ends.\n\tNone(i32),\n}\n\n#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]\npub struct DocumentNodeMetadata {\n\t#[serde(deserialize_with = \"deserialize_node_persistent_metadata\")]\n\tpub persistent_metadata: DocumentNodePersistentMetadata,\n\t#[serde(skip)]\n\tpub transient_metadata: DocumentNodeTransientMetadata,\n}\n\nimpl Clone for DocumentNodeMetadata {\n\tfn clone(&self) -> Self {\n\t\tDocumentNodeMetadata {\n\t\t\tpersistent_metadata: self.persistent_metadata.clone(),\n\t\t\ttransient_metadata: Default::default(),\n\t\t}\n\t}\n}\n\nimpl PartialEq for DocumentNodeMetadata {\n\tfn eq(&self, other: &Self) -> bool {\n\t\tself.persistent_metadata == other.persistent_metadata\n\t}\n}\n\n#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]\npub struct NumberInputSettings {\n\tpub unit: Option<String>,\n\tpub min: Option<f64>,\n\tpub max: Option<f64>,\n\tpub step: Option<f64>,\n\tpub mode: NumberInputMode,\n\tpub range_min: Option<f64>,\n\tpub range_max: Option<f64>,\n\tpub is_integer: bool,\n\tpub blank_assist: bool,\n}\n\nimpl Default for NumberInputSettings {\n\tfn default() -> Self {\n\t\tNumberInputSettings {\n\t\t\tunit: None,\n\t\t\tmin: None,\n\t\t\tmax: None,\n\t\t\tstep: None,\n\t\t\tmode: NumberInputMode::default(),\n\t\t\trange_min: None,\n\t\t\trange_max: None,\n\t\t\tis_integer: false,\n\t\t\tblank_assist: true,\n\t\t}\n\t}\n}\n\n#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]\npub struct Vec2InputSettings {\n\tpub x: String,\n\tpub y: String,\n\tpub unit: String,\n\tpub min: Option<f64>,\n\tpub is_integer: bool,\n}\n\n#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]\npub enum WidgetOverride {\n\tNone,\n\tHidden,\n\tString(String),\n\tNumber(NumberInputSettings),\n\tVec2(Vec2InputSettings),\n\tCustom(String),\n}\n\n// TODO: Custom deserialization/serialization to ensure number of properties row matches number of node inputs\n#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct InputPersistentMetadata {\n\t/// A general datastore than can store key value pairs of any types for any input\n\t/// Each instance of the input node needs to store its own data, since it can lose the reference to its\n\t/// node definition if the node signature is modified by the user. For example adding/removing/renaming an import/export of a network node.\n\tpub input_data: HashMap<String, Value>,\n\t// An input can override a widget, which would otherwise be automatically generated from the type\n\t// The string is the identifier to the widget override function stored in INPUT_OVERRIDES\n\tpub widget_override: Option<String>,\n\t/// An empty input name means to use the type as the name.\n\tpub input_name: String,\n\t/// Displayed as the tooltip description.\n\tpub input_description: String,\n}\n\nimpl InputPersistentMetadata {\n\tpub fn with_name(mut self, input_name: &str) -> Self {\n\t\tself.input_name = input_name.to_string();\n\t\tself\n\t}\n\tpub fn with_override(mut self, widget_override: WidgetOverride) -> Self {\n\t\tmatch widget_override {\n\t\t\t// Uses the default widget for the type\n\t\t\tWidgetOverride::None => {\n\t\t\t\tself.widget_override = None;\n\t\t\t}\n\t\t\tWidgetOverride::Hidden => {\n\t\t\t\tself.widget_override = Some(\"hidden\".to_string());\n\t\t\t}\n\t\t\tWidgetOverride::String(string_properties) => {\n\t\t\t\tself.input_data.insert(\"string_properties\".to_string(), Value::String(string_properties));\n\t\t\t\tself.widget_override = Some(\"string\".to_string());\n\t\t\t}\n\t\t\tWidgetOverride::Number(mut number_properties) => {\n\t\t\t\tif let Some(unit) = number_properties.unit.take() {\n\t\t\t\t\tself.input_data.insert(\"unit\".to_string(), json!(unit));\n\t\t\t\t}\n\t\t\t\tif let Some(min) = number_properties.min.take() {\n\t\t\t\t\tself.input_data.insert(\"min\".to_string(), json!(min));\n\t\t\t\t}\n\t\t\t\tif let Some(max) = number_properties.max.take() {\n\t\t\t\t\tself.input_data.insert(\"max\".to_string(), json!(max));\n\t\t\t\t}\n\t\t\t\tif let Some(step) = number_properties.step.take() {\n\t\t\t\t\tself.input_data.insert(\"step\".to_string(), json!(step));\n\t\t\t\t}\n\t\t\t\tif let Some(range_min) = number_properties.range_min.take() {\n\t\t\t\t\tself.input_data.insert(\"range_min\".to_string(), json!(range_min));\n\t\t\t\t}\n\t\t\t\tif let Some(range_max) = number_properties.range_max.take() {\n\t\t\t\t\tself.input_data.insert(\"range_max\".to_string(), json!(range_max));\n\t\t\t\t}\n\t\t\t\tself.input_data.insert(\"mode\".to_string(), json!(number_properties.mode));\n\t\t\t\tself.input_data.insert(\"is_integer\".to_string(), Value::Bool(number_properties.is_integer));\n\t\t\t\tself.input_data.insert(\"blank_assist\".to_string(), Value::Bool(number_properties.blank_assist));\n\t\t\t\tself.widget_override = Some(\"number\".to_string());\n\t\t\t}\n\t\t\tWidgetOverride::Vec2(vec2_properties) => {\n\t\t\t\tself.input_data.insert(\"x\".to_string(), json!(vec2_properties.x));\n\t\t\t\tself.input_data.insert(\"y\".to_string(), json!(vec2_properties.y));\n\t\t\t\tself.input_data.insert(\"unit\".to_string(), json!(vec2_properties.unit));\n\t\t\t\tself.input_data.insert(\"is_integer\".to_string(), Value::Bool(vec2_properties.is_integer));\n\t\t\t\tif let Some(min) = vec2_properties.min {\n\t\t\t\t\tself.input_data.insert(\"min\".to_string(), json!(min));\n\t\t\t\t}\n\t\t\t\tself.widget_override = Some(\"vec2\".to_string());\n\t\t\t}\n\t\t\tWidgetOverride::Custom(lambda_name) => {\n\t\t\t\tself.widget_override = Some(lambda_name);\n\t\t\t}\n\t\t};\n\t\tself\n\t}\n\n\tpub fn with_description(mut self, description: &str) -> Self {\n\t\tself.input_description = description.to_string();\n\t\tself\n\t}\n}\n\n#[derive(Debug, Clone, Default)]\nstruct InputTransientMetadata {\n\twire: TransientMetadata<WirePathUpdate>,\n\t// downstream_protonode: populated for all inputs after each compile\n\t// types: populated for each protonode after each\n}\n\n/// Persistent metadata for each node in the network, which must be included when creating, serializing, and deserializing saving a node.\n#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]\n#[serde(deny_unknown_fields)]\npub struct DocumentNodePersistentMetadata {\n\t/// A name chosen by the user for this instance of the node. Empty indicates no given name, in which case the implementation name is displayed to the user in italics.\n\t#[serde(default)]\n\tpub display_name: String,\n\t/// Stores metadata to override the properties in the properties panel for each input. These can either be generated automatically based on the type, or with a custom function.\n\t/// Must match the length of node inputs\n\tpub input_metadata: Vec<InputMetadata>,\n\tpub output_names: Vec<String>,\n\t/// Represents the lock icon for locking/unlocking the node in the graph UI. When locked, a node cannot be moved in the graph UI.\n\t#[serde(default)]\n\tpub locked: bool,\n\t/// Indicates that the node will be shown in the Properties panel when it would otherwise be empty, letting a user easily edit its properties by just deselecting everything.\n\t#[serde(default)]\n\tpub pinned: bool,\n\t/// Metadata that is specific to either nodes or layers, which are chosen states for displaying as a left-to-right node or bottom-to-top layer.\n\t/// All fields in NodeTypePersistentMetadata should automatically be updated by using the network interface API\n\tpub node_type_metadata: NodeTypePersistentMetadata,\n\t/// This should always be Some for nodes with a [`DocumentNodeImplementation::Network`], and none for [`DocumentNodeImplementation::ProtoNode`]\n\tpub network_metadata: Option<NodeNetworkMetadata>,\n}\n\nimpl DocumentNodePersistentMetadata {\n\tpub fn is_layer(&self) -> bool {\n\t\tmatches!(self.node_type_metadata, NodeTypePersistentMetadata::Layer(_))\n\t}\n}\n\n#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]\npub struct InputMetadata {\n\tpub persistent_metadata: InputPersistentMetadata,\n\t#[serde(skip)]\n\ttransient_metadata: InputTransientMetadata,\n}\n\nimpl Clone for InputMetadata {\n\tfn clone(&self) -> Self {\n\t\tInputMetadata {\n\t\t\tpersistent_metadata: self.persistent_metadata.clone(),\n\t\t\ttransient_metadata: Default::default(),\n\t\t}\n\t}\n}\n\nimpl PartialEq for InputMetadata {\n\tfn eq(&self, other: &Self) -> bool {\n\t\tself.persistent_metadata == other.persistent_metadata\n\t}\n}\n\nimpl From<(&str, &str)> for InputMetadata {\n\tfn from(input_name_and_description: (&str, &str)) -> Self {\n\t\tInputMetadata {\n\t\t\tpersistent_metadata: InputPersistentMetadata::default()\n\t\t\t\t.with_name(input_name_and_description.0)\n\t\t\t\t.with_description(input_name_and_description.1),\n\t\t\t..Default::default()\n\t\t}\n\t}\n}\n\nimpl InputMetadata {\n\tpub fn with_name_description_override(input_name: &str, description: &str, widget_override: WidgetOverride) -> Self {\n\t\tInputMetadata {\n\t\t\tpersistent_metadata: InputPersistentMetadata::default().with_name(input_name).with_description(description).with_override(widget_override),\n\t\t\t..Default::default()\n\t\t}\n\t}\n}\n\n#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]\npub enum NodeTypePersistentMetadata {\n\tLayer(LayerPersistentMetadata),\n\tNode(NodePersistentMetadata),\n}\n\nimpl Default for NodeTypePersistentMetadata {\n\tfn default() -> Self {\n\t\tNodeTypePersistentMetadata::node(IVec2::ZERO)\n\t}\n}\n\nimpl NodeTypePersistentMetadata {\n\tpub fn node(position: IVec2) -> NodeTypePersistentMetadata {\n\t\tNodeTypePersistentMetadata::Node(NodePersistentMetadata {\n\t\t\tposition: NodePosition::Absolute(position),\n\t\t})\n\t}\n\tpub fn layer(position: IVec2) -> NodeTypePersistentMetadata {\n\t\tNodeTypePersistentMetadata::Layer(LayerPersistentMetadata {\n\t\t\tposition: LayerPosition::Absolute(position),\n\t\t\towned_nodes: TransientMetadata::default(),\n\t\t})\n\t}\n}\n\n/// All fields in LayerMetadata should automatically be updated by using the network interface API\n#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]\npub struct LayerPersistentMetadata {\n\t// TODO: Store click target for the preview button, which will appear when the node is a selected/(hovered?) layer node\n\t// preview_click_target: Option<ClickTarget>,\n\t/// Stores the position of a layer node, which can either be Absolute or Stack\n\tpub position: LayerPosition,\n\t/// All nodes that should be moved when the layer is moved.\n\t#[serde(skip)]\n\tpub owned_nodes: TransientMetadata<HashSet<NodeId>>,\n}\n\nimpl PartialEq for LayerPersistentMetadata {\n\tfn eq(&self, other: &Self) -> bool {\n\t\tself.position == other.position\n\t}\n}\n\n#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct NodePersistentMetadata {\n\t/// Stores the position of a non layer node, which can either be Absolute or Chain\n\tposition: NodePosition,\n}\n\nimpl NodePersistentMetadata {\n\tpub fn new(position: NodePosition) -> Self {\n\t\tSelf { position }\n\t}\n}\n\n/// A layer can either be position as Absolute or in a Stack\n#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]\npub enum LayerPosition {\n\t// Position of the node in grid spaces\n\tAbsolute(IVec2),\n\t// A layer is in a Stack when it feeds into the bottom input of a layer. The Y position stores the vertical distance between the layer and its upstream sibling/parent.\n\tStack(u32),\n}\n\n/// A node can either be position as Absolute or in a Chain\n#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]\npub enum NodePosition {\n\t// Position of the node in grid spaces\n\tAbsolute(IVec2),\n\t// In a chain the position is based on the number of nodes to the first layer node\n\tChain,\n}\n\n/// Cached metadata that should be calculated when creating a node, and should be recalculated when modifying a node property that affects one of the cached fields.\n#[derive(Debug, Default, Clone)]\npub struct DocumentNodeTransientMetadata {\n\t// The click targets are stored as a single struct since it is very rare for only one to be updated, and recomputing all click targets in one function is more efficient than storing them separately.\n\tpub click_targets: TransientMetadata<DocumentNodeClickTargets>,\n\t// Metadata that is specific to either nodes or layers, which are chosen states for displaying as a left-to-right node or bottom-to-top layer.\n\tpub node_type_metadata: NodeTypeTransientMetadata,\n}\n\n#[derive(Debug, Clone)]\npub struct DocumentNodeClickTargets {\n\t/// In order to keep the displayed position of the node in sync with the click target, the displayed position of a node is derived from the top left of the click target\n\t/// Ensure node_click_target is kept in sync when modifying a node property that changes its size. Currently this is alias, inputs, is_layer, and metadata\n\tpub node_click_target: ClickTarget,\n\t/// Stores all port click targets in node graph space.\n\tpub port_click_targets: Ports,\n\t// Click targets that are specific to either nodes or layers, which are chosen states for displaying as a left-to-right node or bottom-to-top layer.\n\tpub node_type_metadata: NodeTypeClickTargets,\n}\n\n#[derive(Debug, Default, Clone)]\npub enum NodeTypeTransientMetadata {\n\tLayer(LayerTransientMetadata),\n\t#[default]\n\tNode, // No transient data is stored exclusively for nodes\n}\n\n#[derive(Debug, Default, Clone)]\npub struct LayerTransientMetadata {\n\t// Stores the width in grid cell units for layer nodes from the left edge of the thumbnail (+12px padding since thumbnail ends between grid spaces) to the left end of the node\n\t/// This is necessary since calculating the layer width through web_sys is very slow\n\tpub layer_width: TransientMetadata<u32>,\n\t// Should not be a performance concern to calculate when needed with chain_width.\n\t// Stores the width in grid cell units for layer nodes from the left edge of the thumbnail to the end of the chain\n\t// chain_width: u32,\n}\n\n#[derive(Debug, Clone)]\npub enum NodeTypeClickTargets {\n\tLayer(LayerClickTargets),\n\tNode, // No transient click targets are stored exclusively for nodes\n}\n\n/// All fields in TransientLayerMetadata should automatically be updated by using the network interface API\n#[derive(Debug, Clone)]\npub struct LayerClickTargets {\n\t/// Cache for all visibility buttons. Should be automatically updated when update_click_target is called\n\tpub visibility_click_target: ClickTarget,\n\t/// Cache for the lock icon button, only present when the layer is locked.\n\tpub lock_click_target: Option<ClickTarget>,\n\t/// Cache for the grip icon, which is next to the visibility button.\n\tpub grip_click_target: ClickTarget,\n\t// TODO: Store click target for the preview button, which will appear when the node is a selected/(hovered?) layer node\n\t// preview_click_target: ClickTarget,\n}\n\npub enum LayerClickTargetTypes {\n\tVisibility,\n\tLock,\n\tGrip,\n\t// Preview,\n}\n\n#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct NavigationMetadata {\n\t/// The current pan, and zoom state of the viewport's view of the node graph.\n\t/// Ensure `DocumentMessage::UpdateDocumentTransform` is called when the pan, zoom, or transform changes.\n\tpub node_graph_ptz: PTZ,\n\t// TODO: Eventually remove once te click targets are extracted from the native render\n\t/// Transform from node graph space to viewport space.\n\tpub node_graph_to_viewport: DAffine2,\n\t// TODO: Eventually remove once the import/export positions are extracted from the native render\n\t/// The width of the node graph in viewport space\n\t#[serde(default)]\n\tpub node_graph_width: f64,\n}\n\n// PartialEq required by message handlers\n/// All persistent editor and Graphene data for a node. Used to serialize and deserialize a node, pass it through the editor, and create definitions.\n#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct NodeTemplate {\n\tpub document_node: DocumentNode,\n\tpub persistent_node_metadata: DocumentNodePersistentMetadata,\n}\n\n#[derive(Clone, Copy, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]\npub enum TransactionStatus {\n\tStarted,\n\tModified,\n\t#[default]\n\tFinished,\n}\n\n#[cfg(test)]\nmod network_interface_tests {\n\tuse crate::test_utils::test_prelude::*;\n\t#[tokio::test]\n\tasync fn copy_isolated_node() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\tlet rectangle = editor\n\t\t\t.create_node_by_name(DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::rectangle::IDENTIFIER))\n\t\t\t.await;\n\t\teditor.handle_message(NodeGraphMessage::SelectedNodesSet { nodes: vec![rectangle] }).await;\n\t\tlet frontend_messages = editor.handle_message(NodeGraphMessage::Copy).await;\n\t\tlet serialized_nodes = frontend_messages\n\t\t\t.into_iter()\n\t\t\t.find_map(|msg| match msg {\n\t\t\t\tFrontendMessage::TriggerClipboardWrite { content } => Some(content),\n\t\t\t\t_ => None,\n\t\t\t})\n\t\t\t.expect(\"copy message should be dispatched\")\n\t\t\t.strip_prefix(\"graphite/nodes: \")\n\t\t\t.expect(\"should start with magic string\")\n\t\t\t.to_string();\n\t\tprintln!(\"Serialized: {serialized_nodes}\");\n\t\teditor.handle_message(NodeGraphMessage::PasteNodes { serialized_nodes }).await;\n\t\tlet nodes = &mut editor.active_document_mut().network_interface.network_mut(&[]).unwrap().nodes;\n\t\tlet orignal = nodes.remove(&rectangle).expect(\"original node should exist\");\n\t\tassert!(\n\t\t\tnodes.values().any(|other| *other == orignal),\n\t\t\t\"duplicated node should exist\\nother nodes: {nodes:#?}\\norignal {orignal:#?}\"\n\t\t);\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/utility_types/nodes.rs",
    "content": "use super::document_metadata::{DocumentMetadata, LayerNodeIdentifier};\nuse super::network_interface::NodeNetworkInterface;\nuse crate::messages::frontend::IconName;\nuse crate::messages::tool::common_functionality::graph_modification_utils;\nuse glam::DVec2;\nuse graph_craft::document::{NodeId, NodeNetwork};\n\n/// Represents an entry in the layer tree hierarchy, sent to the frontend.\n/// Each entry contains its layer ID and a list of its visible children.\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]\npub struct LayerStructureEntry {\n\t#[serde(rename = \"layerId\")]\n\tpub layer_id: NodeId,\n\tpub children: Vec<LayerStructureEntry>,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]\npub struct LayerPanelEntry {\n\tpub id: NodeId,\n\t#[serde(rename = \"implementationName\")]\n\tpub implementation_name: String,\n\t#[serde(rename = \"iconName\")]\n\tpub icon_name: Option<IconName>,\n\tpub alias: String,\n\t#[serde(rename = \"inSelectedNetwork\")]\n\tpub in_selected_network: bool,\n\t#[serde(rename = \"childrenAllowed\")]\n\tpub children_allowed: bool,\n\t#[serde(rename = \"childrenPresent\")]\n\tpub children_present: bool,\n\tpub expanded: bool,\n\tpub depth: u32,\n\tpub visible: bool,\n\t#[serde(rename = \"parentsVisible\")]\n\tpub parents_visible: bool,\n\tpub unlocked: bool,\n\t#[serde(rename = \"parentsUnlocked\")]\n\tpub parents_unlocked: bool,\n\t#[serde(rename = \"parentId\")]\n\tpub parent_id: Option<NodeId>,\n\tpub selected: bool,\n\t#[serde(rename = \"ancestorOfSelected\")]\n\tpub ancestor_of_selected: bool,\n\t#[serde(rename = \"descendantOfSelected\")]\n\tpub descendant_of_selected: bool,\n\tpub clipped: bool,\n\tpub clippable: bool,\n}\n\n/// IMPORTANT: the same node may appear multiple times.\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, PartialEq, Eq)]\npub struct SelectedNodes(pub Vec<NodeId>);\n\nimpl SelectedNodes {\n\tpub fn layer_visible(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> bool {\n\t\tlayer.ancestors(network_interface.document_metadata()).all(|layer| {\n\t\t\tif layer != LayerNodeIdentifier::ROOT_PARENT {\n\t\t\t\tnetwork_interface.is_visible(&layer.to_node(), &[])\n\t\t\t} else {\n\t\t\t\ttrue\n\t\t\t}\n\t\t})\n\t}\n\n\tpub fn selected_visible_layers<'a>(&'a self, network_interface: &'a NodeNetworkInterface) -> impl Iterator<Item = LayerNodeIdentifier> + 'a {\n\t\tself.selected_layers(network_interface.document_metadata())\n\t\t\t.filter(move |&layer| self.layer_visible(layer, network_interface))\n\t}\n\n\tpub fn layer_locked(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> bool {\n\t\tlayer.ancestors(network_interface.document_metadata()).any(|layer| {\n\t\t\tif layer != LayerNodeIdentifier::ROOT_PARENT {\n\t\t\t\tnetwork_interface.is_locked(&layer.to_node(), &[])\n\t\t\t} else {\n\t\t\t\tfalse\n\t\t\t}\n\t\t})\n\t}\n\n\tpub fn selected_unlocked_layers<'a>(&'a self, network_interface: &'a NodeNetworkInterface) -> impl Iterator<Item = LayerNodeIdentifier> + 'a {\n\t\tself.selected_layers(network_interface.document_metadata())\n\t\t\t.filter(move |&layer| !self.layer_locked(layer, network_interface))\n\t}\n\n\tpub fn selected_visible_and_unlocked_layers<'a>(&'a self, network_interface: &'a NodeNetworkInterface) -> impl Iterator<Item = LayerNodeIdentifier> + 'a {\n\t\tself.selected_layers(network_interface.document_metadata())\n\t\t\t.filter(move |&layer| self.layer_visible(layer, network_interface) && !self.layer_locked(layer, network_interface))\n\t}\n\n\tpub fn selected_visible_and_unlocked_layers_mean_average_origin<'a>(&'a self, network_interface: &'a NodeNetworkInterface) -> DVec2 {\n\t\tlet (sum, count) = self\n\t\t\t.selected_visible_and_unlocked_layers(network_interface)\n\t\t\t.map(|layer| graph_modification_utils::get_viewport_origin(layer, network_interface))\n\t\t\t.fold((glam::DVec2::ZERO, 0), |(sum, count), item| (sum + item, count + 1));\n\t\tif count == 0 { DVec2::ZERO } else { sum / count as f64 }\n\t}\n\n\tpub fn selected_visible_and_unlocked_median_points<'a>(&'a self, network_interface: &'a NodeNetworkInterface) -> DVec2 {\n\t\tlet (sum, count) = self\n\t\t\t.selected_visible_and_unlocked_layers(network_interface)\n\t\t\t.map(|layer| graph_modification_utils::get_viewport_center(layer, network_interface))\n\t\t\t.fold((glam::DVec2::ZERO, 0), |(sum, count), item| (sum + item, count + 1));\n\t\tif count == 0 { DVec2::ZERO } else { sum / count as f64 }\n\t}\n\n\tpub fn selected_layers<'a>(&'a self, metadata: &'a DocumentMetadata) -> impl Iterator<Item = LayerNodeIdentifier> + 'a {\n\t\tmetadata.all_layers().filter(|layer| self.0.contains(&layer.to_node()))\n\t}\n\n\tpub fn selected_layers_except_artboards<'a>(&'a self, network_interface: &'a NodeNetworkInterface) -> impl Iterator<Item = LayerNodeIdentifier> + 'a {\n\t\tself.selected_layers(network_interface.document_metadata())\n\t\t\t.filter(move |&layer| !network_interface.is_artboard(&layer.to_node(), &[]))\n\t}\n\n\tpub fn selected_layers_contains(&self, layer: LayerNodeIdentifier, metadata: &DocumentMetadata) -> bool {\n\t\tself.selected_layers(metadata).any(|selected| selected == layer)\n\t}\n\n\t/// IMPORTANT: the same node may appear multiple times.\n\tpub fn selected_nodes(&self) -> impl Iterator<Item = &NodeId> + '_ {\n\t\tself.0.iter()\n\t}\n\n\tpub fn selected_nodes_ref(&self) -> &Vec<NodeId> {\n\t\t&self.0\n\t}\n\n\tpub fn network_has_selected_nodes(&self, network: &NodeNetwork) -> bool {\n\t\tself.0.iter().any(|node_id| network.nodes.contains_key(node_id))\n\t}\n\n\tpub fn has_selected_nodes(&self) -> bool {\n\t\t!self.0.is_empty()\n\t}\n\n\tpub fn retain_selected_nodes(&mut self, f: impl FnMut(&NodeId) -> bool) {\n\t\tself.0.retain(f);\n\t}\n\n\tpub fn set_selected_nodes(&mut self, new: Vec<NodeId>) {\n\t\tself.0 = new;\n\t}\n\n\tpub fn add_selected_nodes(&mut self, new: Vec<NodeId>) {\n\t\tself.0.extend(new);\n\t}\n\n\tpub fn clear_selected_nodes(&mut self) {\n\t\tself.0 = Vec::new();\n\t}\n\n\tpub fn replace_with(&mut self, new: Vec<NodeId>) -> Vec<NodeId> {\n\t\tstd::mem::replace(&mut self.0, new)\n\t}\n\n\tpub fn filtered_selected_nodes(&self, filter: impl Fn(&NodeId) -> bool) -> SelectedNodes {\n\t\tSelectedNodes(self.0.iter().copied().filter(filter).collect())\n\t}\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, PartialEq, Eq)]\npub struct CollapsedLayers(pub Vec<LayerNodeIdentifier>);\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/utility_types/transformation.rs",
    "content": "use super::network_interface::NodeNetworkInterface;\nuse crate::consts::{ROTATE_INCREMENT, SCALE_INCREMENT};\nuse crate::messages::portfolio::document::graph_operation::transform_utils;\nuse crate::messages::portfolio::document::graph_operation::utility_types::{ModifyInputsContext, TransformIn};\nuse crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;\nuse crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};\nuse crate::messages::prelude::*;\nuse crate::messages::tool::common_functionality::shape_editor::ShapeState;\nuse crate::messages::tool::transform_layer::transform_layer_message_handler::TransformationState;\nuse crate::messages::tool::utility_types::ToolType;\nuse glam::{DAffine2, DMat2, DVec2};\nuse graphene_std::renderer::Quad;\nuse graphene_std::vector::misc::{HandleId, ManipulatorPointId};\nuse graphene_std::vector::{HandleExt, PointId, VectorModificationType};\nuse std::collections::{HashMap, VecDeque};\n\n#[derive(Debug, PartialEq, Clone, Copy)]\nstruct AnchorPoint {\n\tinitial: DVec2,\n\tcurrent: DVec2,\n}\n\n#[derive(Debug, PartialEq, Clone, Copy)]\nstruct HandlePoint {\n\tinitial: DVec2,\n\trelative: DVec2,\n\tanchor: PointId,\n\tmirror: Option<(HandleId, DVec2)>,\n}\n\n#[derive(Debug, PartialEq, Clone)]\npub struct InitialPoints {\n\tanchors: HashMap<PointId, AnchorPoint>,\n\thandles: HashMap<HandleId, HandlePoint>,\n}\n\n#[derive(Debug, PartialEq, Clone)]\npub enum OriginalTransforms {\n\tLayer(HashMap<LayerNodeIdentifier, DAffine2>),\n\tPath(HashMap<LayerNodeIdentifier, InitialPoints>),\n}\nimpl Default for OriginalTransforms {\n\tfn default() -> Self {\n\t\tOriginalTransforms::Path(HashMap::new())\n\t}\n}\nimpl OriginalTransforms {\n\tpub fn clear(&mut self) {\n\t\tmatch self {\n\t\t\tOriginalTransforms::Layer(layer_map) => layer_map.clear(),\n\t\t\tOriginalTransforms::Path(path_map) => path_map.clear(),\n\t\t}\n\t}\n\n\t/// Gets the transform from the most downstream transform node\n\tfn get_layer_transform(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<DAffine2> {\n\t\tlet transform_node_id = ModifyInputsContext::locate_node_in_layer_chain(&DefinitionIdentifier::Network(\"Transform\".into()), layer, network_interface)?;\n\n\t\tlet document_node = network_interface.document_network().nodes.get(&transform_node_id)?;\n\t\tSome(transform_utils::get_current_transform(&document_node.inputs))\n\t}\n\n\tpub fn update<'a>(&mut self, selected: &'a [LayerNodeIdentifier], network_interface: &NodeNetworkInterface, shape_editor: Option<&'a ShapeState>) {\n\t\tmatch self {\n\t\t\tOriginalTransforms::Layer(layer_map) => {\n\t\t\t\tlayer_map.retain(|layer, _| selected.contains(layer));\n\t\t\t\tfor &layer in selected {\n\t\t\t\t\tif layer == LayerNodeIdentifier::ROOT_PARENT {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tlayer_map.entry(layer).or_insert_with(|| Self::get_layer_transform(layer, network_interface).unwrap_or_default());\n\t\t\t\t}\n\t\t\t}\n\t\t\tOriginalTransforms::Path(path_map) => {\n\t\t\t\tlet Some(shape_editor) = shape_editor else {\n\t\t\t\t\twarn!(\"No shape editor structure found, which only happens in select tool, which cannot reach this point as we check for ToolType\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tfor &layer in selected {\n\t\t\t\t\tif path_map.contains_key(&layer) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tlet Some(vector) = network_interface.compute_modified_vector(layer) else {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t};\n\t\t\t\t\tlet Some(selected_points) = shape_editor.selected_points_in_layer(layer) else {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t};\n\t\t\t\t\tlet Some(selected_segments) = shape_editor.selected_segments_in_layer(layer) else {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t};\n\n\t\t\t\t\tlet mut selected_points = selected_points.clone();\n\n\t\t\t\t\tfor (segment_id, _, start, end) in vector.segment_bezier_iter() {\n\t\t\t\t\t\tif selected_segments.contains(&segment_id) {\n\t\t\t\t\t\t\tselected_points.insert(ManipulatorPointId::Anchor(start));\n\t\t\t\t\t\t\tselected_points.insert(ManipulatorPointId::Anchor(end));\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Anchors also move their handles\n\t\t\t\t\tlet anchor_ids = selected_points.iter().filter_map(|point| point.as_anchor());\n\t\t\t\t\tlet anchors = anchor_ids.filter_map(|id| vector.point_domain.position_from_id(id).map(|pos| (id, AnchorPoint { initial: pos, current: pos })));\n\t\t\t\t\tlet anchors = anchors.collect();\n\n\t\t\t\t\tlet selected_handles = selected_points.iter().filter_map(|point| point.as_handle());\n\t\t\t\t\tlet anchor_ids = selected_points.iter().filter_map(|point| point.as_anchor());\n\t\t\t\t\tlet connected_handles = anchor_ids.flat_map(|point| vector.all_connected(point));\n\t\t\t\t\tlet all_handles = selected_handles.chain(connected_handles);\n\n\t\t\t\t\tlet handles = all_handles\n\t\t\t\t\t\t.filter_map(|id| {\n\t\t\t\t\t\t\tlet anchor = id.to_manipulator_point().get_anchor(&vector)?;\n\t\t\t\t\t\t\tlet initial = id.to_manipulator_point().get_position(&vector)?;\n\t\t\t\t\t\t\tlet relative = vector.point_domain.position_from_id(anchor)?;\n\t\t\t\t\t\t\tlet other_handle = vector\n\t\t\t\t\t\t\t\t.other_colinear_handle(id)\n\t\t\t\t\t\t\t\t.filter(|other| !selected_points.contains(&other.to_manipulator_point()) && !selected_points.contains(&ManipulatorPointId::Anchor(anchor)));\n\t\t\t\t\t\t\tlet mirror = other_handle.and_then(|id| Some((id, id.to_manipulator_point().get_position(&vector)?)));\n\n\t\t\t\t\t\t\tSome((id, HandlePoint { initial, relative, anchor, mirror }))\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.collect();\n\n\t\t\t\t\tpath_map.insert(layer, InitialPoints { anchors, handles });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n#[derive(Default, Debug, Clone, PartialEq, Eq, Copy)]\npub enum Axis {\n\t#[default]\n\tBoth,\n\tX,\n\tY,\n}\n\nimpl Axis {\n\tpub fn contrainted_to_axis(self, target: Axis, local: bool) -> (Self, bool) {\n\t\tif self != target {\n\t\t\treturn (target, false);\n\t\t}\n\n\t\tif local { (Axis::Both, false) } else { (self, true) }\n\t}\n}\n\n#[derive(Default, Debug, Clone, PartialEq, Copy)]\npub struct Translation {\n\tpub dragged_distance: DVec2,\n\tpub typed_distance: Option<f64>,\n\tpub constraint: Axis,\n}\n\nimpl Translation {\n\tpub fn to_dvec(self, state: &TransformationState, document: &DocumentMessageHandler) -> DVec2 {\n\t\tlet document_to_viewport = document.metadata().document_to_viewport;\n\t\tlet displacement = if let Some(value) = self.typed_distance {\n\t\t\tmatch self.constraint {\n\t\t\t\tAxis::X => DVec2::X * value,\n\t\t\t\tAxis::Y => DVec2::Y * value,\n\t\t\t\tAxis::Both => self.dragged_distance,\n\t\t\t}\n\t\t} else {\n\t\t\tmatch self.constraint {\n\t\t\t\tAxis::Both => self.dragged_distance,\n\t\t\t\tAxis::X => DVec2::X * self.dragged_distance.dot(state.constraint_axis(self.constraint).unwrap_or_default()),\n\t\t\t\tAxis::Y => DVec2::Y * self.dragged_distance.dot(state.constraint_axis(self.constraint).unwrap_or_default()),\n\t\t\t}\n\t\t};\n\t\tlet displacement_viewport = displacement * document_to_viewport.matrix2.y_axis.length(); // Values are local to the viewport but scaled so values are relative to the current scale.\n\t\tlet displacement_document = document_to_viewport.inverse().transform_vector2(displacement_viewport);\n\t\tlet displacement_document = if state.is_rounded_to_intervals { displacement_document.round() } else { displacement_document }; // It rounds in document space?\n\t\tdocument_to_viewport.transform_vector2(displacement_document)\n\t}\n\n\t#[must_use]\n\tpub fn increment_amount(self, delta: DVec2) -> Self {\n\t\tSelf {\n\t\t\tdragged_distance: self.dragged_distance + delta,\n\t\t\ttyped_distance: None,\n\t\t\tconstraint: self.constraint,\n\t\t}\n\t}\n\tpub fn set_amount(self, change: DVec2) -> Self {\n\t\tSelf {\n\t\t\tdragged_distance: change,\n\t\t\ttyped_distance: None,\n\t\t\tconstraint: self.constraint,\n\t\t}\n\t}\n\n\tpub fn negate(self) -> Self {\n\t\tlet dragged_distance = -self.dragged_distance;\n\t\tSelf { dragged_distance, ..self }\n\t}\n\n\tpub fn with_constraint(self, target: Axis, local: bool) -> (Self, bool) {\n\t\tlet (constraint, local) = self.constraint.contrainted_to_axis(target, local);\n\t\t(Self { constraint, ..self }, local)\n\t}\n}\n\n#[derive(Default, Debug, Clone, PartialEq, Copy)]\npub struct Rotation {\n\tpub dragged_angle: f64,\n\tpub typed_angle: Option<f64>,\n}\n\nimpl Rotation {\n\tpub fn to_f64(self, increment_mode: bool) -> f64 {\n\t\tif let Some(value) = self.typed_angle {\n\t\t\tvalue.to_radians()\n\t\t} else if increment_mode {\n\t\t\tlet increment_resolution = ROTATE_INCREMENT.to_radians();\n\t\t\t(self.dragged_angle / increment_resolution).round() * increment_resolution\n\t\t} else {\n\t\t\tself.dragged_angle\n\t\t}\n\t}\n\n\t#[must_use]\n\tpub fn increment_amount(self, delta: f64) -> Self {\n\t\tSelf {\n\t\t\tdragged_angle: self.dragged_angle + delta,\n\t\t\ttyped_angle: None,\n\t\t}\n\t}\n\tpub fn set_amount(self, angle: f64) -> Self {\n\t\tSelf {\n\t\t\tdragged_angle: angle,\n\t\t\ttyped_angle: None,\n\t\t}\n\t}\n\n\tpub fn negate(self) -> Self {\n\t\tlet dragged_angle = -self.dragged_angle;\n\t\tSelf { dragged_angle, ..self }\n\t}\n}\n\n#[derive(Debug, Clone, PartialEq, Copy)]\npub struct Scale {\n\tpub dragged_factor: f64,\n\tpub typed_factor: Option<f64>,\n\tpub constraint: Axis,\n}\n\nimpl Default for Scale {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tdragged_factor: 1.,\n\t\t\ttyped_factor: None,\n\t\t\tconstraint: Axis::default(),\n\t\t}\n\t}\n}\n\nimpl Scale {\n\tpub fn to_f64(self, increment: bool) -> f64 {\n\t\tlet factor = if let Some(value) = self.typed_factor { value } else { self.dragged_factor };\n\t\tif increment { (factor / SCALE_INCREMENT).round() * SCALE_INCREMENT } else { factor }\n\t}\n\n\tpub fn to_dvec(self, increment_mode: bool) -> DVec2 {\n\t\tlet factor = self.to_f64(increment_mode);\n\n\t\tmatch self.constraint {\n\t\t\tAxis::Both => DVec2::splat(factor),\n\t\t\tAxis::X => DVec2::new(factor, 1.),\n\t\t\tAxis::Y => DVec2::new(1., factor),\n\t\t}\n\t}\n\n\tpub fn negate(self) -> Self {\n\t\tlet dragged_factor = -self.dragged_factor;\n\t\tSelf { dragged_factor, ..self }\n\t}\n\n\t#[must_use]\n\tpub fn increment_amount(self, delta: f64) -> Self {\n\t\tSelf {\n\t\t\tdragged_factor: (self.dragged_factor + delta),\n\t\t\ttyped_factor: None,\n\t\t\tconstraint: self.constraint,\n\t\t}\n\t}\n\n\tpub fn set_amount(self, change: f64) -> Self {\n\t\tSelf {\n\t\t\tdragged_factor: 1. + change,\n\t\t\ttyped_factor: None,\n\t\t\tconstraint: self.constraint,\n\t\t}\n\t}\n\n\tpub fn with_constraint(self, target: Axis, local: bool) -> (Self, bool) {\n\t\tlet (constraint, local) = self.constraint.contrainted_to_axis(target, local);\n\t\t(Self { constraint, ..self }, local)\n\t}\n}\n\n#[derive(Default, Debug, Clone, PartialEq, Copy)]\npub enum TransformOperation {\n\t#[default]\n\tNone,\n\tGrabbing(Translation),\n\tRotating(Rotation),\n\tScaling(Scale),\n}\n\n#[derive(Debug, Clone, PartialEq, Copy, serde::Serialize, serde::Deserialize)]\npub enum TransformType {\n\tGrab,\n\tRotate,\n\tScale,\n}\n\nimpl TransformType {\n\tpub fn equivalent_to(&self, operation: TransformOperation) -> bool {\n\t\tmatches!(\n\t\t\t(operation, self),\n\t\t\t(TransformOperation::Scaling(_), TransformType::Scale) | (TransformOperation::Grabbing(_), TransformType::Grab) | (TransformOperation::Rotating(_), TransformType::Rotate)\n\t\t)\n\t}\n}\n\nimpl TransformOperation {\n\t#[allow(clippy::too_many_arguments)]\n\tpub fn apply_transform_operation(&self, selected: &mut Selected, state: &TransformationState, document: &DocumentMessageHandler) {\n\t\tif self != &TransformOperation::None {\n\t\t\tlet mut transformation = match self {\n\t\t\t\tTransformOperation::Grabbing(translation) => DAffine2::from_translation(translation.to_dvec(state, document)),\n\t\t\t\tTransformOperation::Rotating(rotation) => DAffine2::from_angle(rotation.to_f64(state.is_rounded_to_intervals)),\n\t\t\t\tTransformOperation::Scaling(scale) => DAffine2::from_scale(scale.to_dvec(state.is_rounded_to_intervals)),\n\t\t\t\tTransformOperation::None => unreachable!(),\n\t\t\t};\n\t\t\tlet normalized_transform = state.local_to_viewport_transform();\n\t\t\ttransformation = normalized_transform * transformation * normalized_transform.inverse();\n\n\t\t\tselected.update_transforms(transformation, Some(state.pivot_viewport(document)), Some(*self));\n\t\t\tself.hints(selected.responses, state.is_transforming_in_local_space);\n\t\t}\n\t}\n\n\tpub fn axis_constraint(&self) -> Axis {\n\t\tmatch self {\n\t\t\tTransformOperation::Grabbing(grabbing) => grabbing.constraint,\n\t\t\tTransformOperation::Scaling(scaling) => scaling.constraint,\n\t\t\t_ => Axis::Both,\n\t\t}\n\t}\n\n\tpub fn can_begin_typing(&self) -> bool {\n\t\tself.is_constraint_to_axis() || !matches!(self, TransformOperation::Grabbing(_))\n\t}\n\n\t#[allow(clippy::too_many_arguments)]\n\tpub fn constrain_axis(&mut self, axis: Axis, selected: &mut Selected, state: &TransformationState, document: &DocumentMessageHandler) -> bool {\n\t\tlet resulting_local;\n\t\t(*self, resulting_local) = match self {\n\t\t\tTransformOperation::Grabbing(translation) => {\n\t\t\t\tlet (translation, resulting_local) = translation.with_constraint(axis, state.is_transforming_in_local_space);\n\t\t\t\t(TransformOperation::Grabbing(translation), resulting_local)\n\t\t\t}\n\t\t\tTransformOperation::Scaling(scale) => {\n\t\t\t\tlet (scale, resulting_local) = scale.with_constraint(axis, state.is_transforming_in_local_space);\n\t\t\t\t(TransformOperation::Scaling(scale), resulting_local)\n\t\t\t}\n\t\t\t_ => (*self, false),\n\t\t};\n\n\t\tself.apply_transform_operation(selected, state, document);\n\n\t\tresulting_local\n\t}\n\n\t#[allow(clippy::too_many_arguments)]\n\tpub fn grs_typed(&mut self, typed: Option<f64>, selected: &mut Selected, state: &TransformationState, document: &DocumentMessageHandler) {\n\t\tmatch self {\n\t\t\tTransformOperation::None => (),\n\t\t\tTransformOperation::Grabbing(translation) => translation.typed_distance = typed,\n\t\t\tTransformOperation::Rotating(rotation) => rotation.typed_angle = typed,\n\t\t\tTransformOperation::Scaling(scale) => scale.typed_factor = typed,\n\t\t};\n\n\t\tself.apply_transform_operation(selected, state, document);\n\t}\n\n\tpub fn hints(&self, responses: &mut VecDeque<Message>, local: bool) {\n\t\tuse crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};\n\t\tuse crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};\n\n\t\tlet mut input_hints = Vec::new();\n\t\tlet clear_constraint = \"Clear Constraint\";\n\t\tmatch self.axis_constraint() {\n\t\t\tAxis::Both => {\n\t\t\t\tinput_hints.push(HintInfo::keys([Key::KeyX], \"X-Axis Constraint\"));\n\t\t\t\tinput_hints.push(HintInfo::keys([Key::KeyY], \"Y-Axis Constraint\"));\n\t\t\t}\n\t\t\tAxis::X => {\n\t\t\t\tlet x_label = if local { clear_constraint } else { \"Local X-Axis Constraint\" };\n\t\t\t\tinput_hints.push(HintInfo::keys([Key::KeyX], x_label));\n\t\t\t\tinput_hints.push(HintInfo::keys([Key::KeyY], \"Y-Axis Constraint\"));\n\t\t\t\tif !local {\n\t\t\t\t\tinput_hints.push(HintInfo::keys([Key::KeyX, Key::KeyX], clear_constraint));\n\t\t\t\t}\n\t\t\t}\n\t\t\tAxis::Y => {\n\t\t\t\tlet y_label = if local { clear_constraint } else { \"Local Y-Axis Constraint\" };\n\t\t\t\tinput_hints.push(HintInfo::keys([Key::KeyX], \"X-Axis Constraint\"));\n\t\t\t\tinput_hints.push(HintInfo::keys([Key::KeyY], y_label));\n\t\t\t\tif !local {\n\t\t\t\t\tinput_hints.push(HintInfo::keys([Key::KeyY, Key::KeyY], clear_constraint));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tlet grs_hint_group = match self {\n\t\t\tTransformOperation::None => unreachable!(),\n\t\t\tTransformOperation::Scaling(_) => HintGroup(vec![HintInfo::multi_keys([[Key::KeyG], [Key::KeyR]], \"Grab/Rotate Selected\")]),\n\t\t\tTransformOperation::Grabbing(_) => HintGroup(vec![HintInfo::multi_keys([[Key::KeyR], [Key::KeyS]], \"Rotate/Scale Selected\")]),\n\t\t\tTransformOperation::Rotating(_) => HintGroup(vec![HintInfo::multi_keys([[Key::KeyG], [Key::KeyS]], \"Grab/Scale Selected\")]),\n\t\t};\n\n\t\tlet confirm_and_cancel_group = HintGroup(vec![\n\t\t\tHintInfo::mouse(MouseMotion::Lmb, \"\"),\n\t\t\tHintInfo::keys([Key::Enter], \"Confirm\").prepend_slash(),\n\t\t\tHintInfo::mouse(MouseMotion::Rmb, \"\"),\n\t\t\tHintInfo::keys([Key::Escape], \"Cancel\").prepend_slash(),\n\t\t]);\n\t\tlet mut hint_groups = vec![confirm_and_cancel_group, grs_hint_group];\n\t\tif !self.is_typing() {\n\t\t\tlet modifiers = vec![\n\t\t\t\tHintInfo::keys([Key::Shift], \"Slow\"),\n\t\t\t\tHintInfo::keys([Key::Control], if matches!(self, TransformOperation::Rotating(_)) { \"15° Increments\" } else { \"Increments\" }),\n\t\t\t];\n\t\t\thint_groups.push(HintGroup(modifiers));\n\t\t}\n\t\tif !matches!(self, TransformOperation::Rotating(_)) {\n\t\t\thint_groups.push(HintGroup(input_hints));\n\t\t}\n\t\tlet mut typing_hints = vec![HintInfo::keys([Key::Minus], \"Negate Direction\")];\n\t\tif self.can_begin_typing() {\n\t\t\ttyping_hints.push(HintInfo::keys([Key::FakeKeyNumbers], \"Enter Number\"));\n\t\t\tif self.is_typing() {\n\t\t\t\ttyping_hints.push(HintInfo::keys([Key::Backspace], \"Delete Digit\"));\n\t\t\t}\n\t\t}\n\t\thint_groups.push(HintGroup(typing_hints));\n\n\t\tHintData(hint_groups).send_layout(responses);\n\t}\n\n\tpub fn is_constraint_to_axis(&self) -> bool {\n\t\tself.axis_constraint() != Axis::Both\n\t}\n\n\tpub fn is_typing(&self) -> bool {\n\t\tmatch self {\n\t\t\tTransformOperation::None => false,\n\t\t\tTransformOperation::Grabbing(translation) => translation.typed_distance.is_some(),\n\t\t\tTransformOperation::Rotating(rotation) => rotation.typed_angle.is_some(),\n\t\t\tTransformOperation::Scaling(scale) => scale.typed_factor.is_some(),\n\t\t}\n\t}\n\n\t#[allow(clippy::too_many_arguments)]\n\tpub fn negate(&mut self, selected: &mut Selected, state: &TransformationState, document: &DocumentMessageHandler) {\n\t\tif *self != TransformOperation::None {\n\t\t\t*self = match self {\n\t\t\t\tTransformOperation::Scaling(scale) => TransformOperation::Scaling(scale.negate()),\n\t\t\t\tTransformOperation::Rotating(rotation) => TransformOperation::Rotating(rotation.negate()),\n\t\t\t\tTransformOperation::Grabbing(translation) => TransformOperation::Grabbing(translation.negate()),\n\t\t\t\t_ => *self,\n\t\t\t};\n\n\t\t\tself.apply_transform_operation(selected, state, document);\n\t\t}\n\t}\n}\n\npub struct Selected<'a> {\n\tpub selected: &'a [LayerNodeIdentifier],\n\tpub responses: &'a mut VecDeque<Message>,\n\tpub network_interface: &'a NodeNetworkInterface,\n\tpub original_transforms: &'a mut OriginalTransforms,\n\tpub pivot: &'a mut DVec2,\n\tpub shape_editor: Option<&'a ShapeState>,\n\tpub tool_type: &'a ToolType,\n\t// Only for the Pen tool\n\tpub pen_handle: Option<&'a mut DVec2>,\n}\n\nimpl<'a> Selected<'a> {\n\t#[allow(clippy::too_many_arguments)]\n\tpub fn new(\n\t\toriginal_transforms: &'a mut OriginalTransforms,\n\t\tpivot: &'a mut DVec2,\n\t\tselected: &'a [LayerNodeIdentifier],\n\t\tresponses: &'a mut VecDeque<Message>,\n\t\tnetwork_interface: &'a NodeNetworkInterface,\n\t\tshape_editor: Option<&'a ShapeState>,\n\t\ttool_type: &'a ToolType,\n\t\tpen_handle: Option<&'a mut DVec2>,\n\t) -> Self {\n\t\t// If user is using the Select tool or Shape tool then use the original layer transforms\n\t\tif (*tool_type == ToolType::Select || *tool_type == ToolType::Shape) && (*original_transforms == OriginalTransforms::Path(HashMap::new())) {\n\t\t\t*original_transforms = OriginalTransforms::Layer(HashMap::new());\n\t\t}\n\n\t\toriginal_transforms.update(selected, network_interface, shape_editor);\n\n\t\tSelf {\n\t\t\tselected,\n\t\t\tresponses,\n\t\t\tnetwork_interface,\n\t\t\toriginal_transforms,\n\t\t\tpivot,\n\t\t\tshape_editor,\n\t\t\ttool_type,\n\t\t\tpen_handle,\n\t\t}\n\t}\n\n\tpub fn center_of_aabb(&mut self) -> DVec2 {\n\t\tlet [min, max] = self\n\t\t\t.selected\n\t\t\t.iter()\n\t\t\t.filter_map(|&layer| self.network_interface.document_metadata().bounding_box_viewport(layer))\n\t\t\t.reduce(Quad::combine_bounds)\n\t\t\t.unwrap_or_default();\n\t\t(min + max) / 2.\n\t}\n\n\tpub fn bounding_box(&mut self) -> Quad {\n\t\tlet metadata = self.network_interface.document_metadata();\n\n\t\tlet mut transform = self\n\t\t\t.network_interface\n\t\t\t.selected_nodes()\n\t\t\t.selected_visible_and_unlocked_layers(self.network_interface)\n\t\t\t.find(|layer| !self.network_interface.is_artboard(&layer.to_node(), &[]))\n\t\t\t.map(|layer| metadata.transform_to_viewport(layer))\n\t\t\t.unwrap_or(DAffine2::IDENTITY);\n\n\t\tif transform.matrix2.determinant().abs() <= f64::EPSILON {\n\t\t\ttransform.matrix2 += DMat2::IDENTITY * 1e-4; // TODO: Is this the cleanest way to handle this?\n\t\t}\n\n\t\tlet bounds = self\n\t\t\t.selected\n\t\t\t.iter()\n\t\t\t.filter_map(|&layer| metadata.bounding_box_with_transform(layer, transform.inverse() * metadata.transform_to_viewport(layer)))\n\t\t\t.reduce(Quad::combine_bounds)\n\t\t\t.unwrap_or_default();\n\n\t\ttransform * Quad::from_box(bounds)\n\t}\n\n\tfn transform_layer(document_metadata: &DocumentMetadata, layer: LayerNodeIdentifier, original_transform: Option<&DAffine2>, transformation: DAffine2, responses: &mut VecDeque<Message>) {\n\t\tlet Some(&original_transform) = original_transform else { return };\n\t\tlet to = document_metadata.downstream_transform_to_viewport(layer);\n\t\tlet new = to.inverse() * transformation * to * original_transform;\n\t\tresponses.add(GraphOperationMessage::TransformSet {\n\t\t\tlayer,\n\t\t\ttransform: new,\n\t\t\ttransform_in: TransformIn::Local,\n\t\t\tskip_rerender: false,\n\t\t});\n\t}\n\n\tfn transform_path(\n\t\tdocument_metadata: &DocumentMetadata,\n\t\tlayer: LayerNodeIdentifier,\n\t\tinitial_points: &mut InitialPoints,\n\t\ttransformation: DAffine2,\n\t\tresponses: &mut VecDeque<Message>,\n\t\ttransform_operation: Option<TransformOperation>,\n\t) {\n\t\tlet viewspace = document_metadata.transform_to_viewport(layer);\n\t\tlet layerspace_rotation = viewspace.inverse() * transformation;\n\n\t\tfor (&point, anchor) in initial_points.anchors.iter_mut() {\n\t\t\tlet new_pos_viewport = layerspace_rotation.transform_point2(viewspace.transform_point2(anchor.initial));\n\t\t\tlet delta = new_pos_viewport - anchor.current;\n\t\t\tanchor.current += delta;\n\t\t\tlet modification_type = VectorModificationType::ApplyPointDelta { point, delta };\n\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t}\n\n\t\tif transform_operation.is_some_and(|transform_operation| matches!(transform_operation, TransformOperation::Scaling(_))) && (initial_points.anchors.len() == 2) {\n\t\t\treturn;\n\t\t}\n\n\t\tfor (&id, handle) in initial_points.handles.iter() {\n\t\t\tlet new_pos_viewport = layerspace_rotation.transform_point2(viewspace.transform_point2(handle.initial));\n\t\t\tlet relative = initial_points.anchors.get(&handle.anchor).map_or(handle.relative, |anchor| anchor.current);\n\t\t\tlet modification_type = id.set_relative_position(new_pos_viewport - relative);\n\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\n\t\t\tif let Some((id, initial)) = handle.mirror {\n\t\t\t\t// When the handle is scaled to zero, don't update the mirror handle\n\t\t\t\tif (new_pos_viewport - relative).length_squared() > f64::EPSILON {\n\t\t\t\t\tlet direction = viewspace.transform_vector2(new_pos_viewport - relative).try_normalize();\n\t\t\t\t\tlet length = viewspace.transform_vector2(initial - relative).length();\n\t\t\t\t\tlet new_relative = direction.map_or(initial - relative, |direction| viewspace.inverse().transform_vector2(-direction * length));\n\t\t\t\t\tlet modification_type = id.set_relative_position(new_relative);\n\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn apply_transform_pen(&mut self, transformation: DAffine2) {\n\t\tif let Some(pen_handle) = &self.pen_handle {\n\t\t\tlet final_position = transformation.transform_point2(**pen_handle);\n\t\t\tself.responses.add(PenToolMessage::FinalPosition { final_position });\n\t\t}\n\t}\n\n\tpub fn apply_transformation(&mut self, transformation: DAffine2, transform_operation: Option<TransformOperation>) {\n\t\tif self.selected.is_empty() {\n\t\t\treturn;\n\t\t}\n\n\t\t// TODO: Cache the result of `shallowest_unique_layers` to avoid this heavy computation every frame of movement, see https://github.com/GraphiteEditor/Graphite/pull/481\n\t\tfor layer in self.network_interface.shallowest_unique_layers(&[]) {\n\t\t\tmatch &mut self.original_transforms {\n\t\t\t\tOriginalTransforms::Layer(layer_transforms) => Self::transform_layer(self.network_interface.document_metadata(), layer, layer_transforms.get(&layer), transformation, self.responses),\n\t\t\t\tOriginalTransforms::Path(path_transforms) => {\n\t\t\t\t\tif let Some(initial_points) = path_transforms.get_mut(&layer) {\n\t\t\t\t\t\tSelf::transform_path(self.network_interface.document_metadata(), layer, initial_points, transformation, self.responses, transform_operation)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn update_transforms(&mut self, delta: DAffine2, pivot: Option<DVec2>, transform_operation: Option<TransformOperation>) {\n\t\tlet pivot = DAffine2::from_translation(pivot.unwrap_or(*self.pivot));\n\t\tlet transformation = pivot * delta * pivot.inverse();\n\t\tmatch self.tool_type {\n\t\t\tToolType::Pen => self.apply_transform_pen(transformation),\n\t\t\t_ => self.apply_transformation(transformation, transform_operation),\n\t\t}\n\t}\n\n\tpub fn revert_operation(&mut self) {\n\t\tfor layer in self.selected.iter().copied() {\n\t\t\tlet original_transform = &self.original_transforms;\n\t\t\tmatch original_transform {\n\t\t\t\tOriginalTransforms::Layer(hash) => {\n\t\t\t\t\tlet Some(matrix) = hash.get(&layer) else { continue };\n\t\t\t\t\tself.responses.add(GraphOperationMessage::TransformSet {\n\t\t\t\t\t\tlayer,\n\t\t\t\t\t\ttransform: *matrix,\n\t\t\t\t\t\ttransform_in: TransformIn::Local,\n\t\t\t\t\t\tskip_rerender: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tOriginalTransforms::Path(path) => {\n\t\t\t\t\tfor (&layer, points) in path {\n\t\t\t\t\t\tfor (&point, &anchor) in &points.anchors {\n\t\t\t\t\t\t\tlet delta = anchor.initial - anchor.current;\n\t\t\t\t\t\t\tlet modification_type = VectorModificationType::ApplyPointDelta { point, delta };\n\t\t\t\t\t\t\tself.responses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tfor (&point, &handle) in &points.handles {\n\t\t\t\t\t\t\tlet modification_type = point.set_relative_position(handle.initial - handle.relative);\n\t\t\t\t\t\t\tself.responses.add(GraphOperationMessage::Vector { layer, modification_type });\n\n\t\t\t\t\t\t\tif let Some((id, initial)) = handle.mirror {\n\t\t\t\t\t\t\t\tlet modification_type = id.set_relative_position(initial - handle.relative);\n\t\t\t\t\t\t\t\tself.responses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, Default)]\npub struct Typing {\n\tpub digits: Vec<u8>,\n\tpub string: String,\n\tpub contains_decimal: bool,\n\tpub negative: bool,\n}\n\nconst DECIMAL_POINT: u8 = 10;\n\nimpl Typing {\n\tpub fn type_number(&mut self, number: u8) -> Option<f64> {\n\t\tself.digits.push(number);\n\t\tself.string.push((b'0' + number) as char);\n\n\t\tself.evaluate()\n\t}\n\n\tpub fn type_backspace(&mut self) -> Option<f64> {\n\t\tif self.digits.is_empty() {\n\t\t\treturn None;\n\t\t}\n\n\t\tmatch self.digits.pop() {\n\t\t\tSome(DECIMAL_POINT) => self.contains_decimal = false,\n\t\t\tSome(_) => (),\n\t\t\tNone => self.negative = false,\n\t\t}\n\t\tself.string.pop();\n\t\tself.evaluate()\n\t}\n\n\tpub fn type_decimal_point(&mut self) -> Option<f64> {\n\t\tif !self.contains_decimal {\n\t\t\tself.contains_decimal = true;\n\t\t\tself.digits.push(DECIMAL_POINT);\n\t\t\tself.string.push('.');\n\t\t}\n\n\t\tself.evaluate()\n\t}\n\n\tpub fn type_negate(&mut self) -> Option<f64> {\n\t\tself.negative = !self.negative;\n\t\tif self.negative {\n\t\t\tself.string.insert(0, '-');\n\t\t} else {\n\t\t\tself.string.remove(0);\n\t\t}\n\n\t\tself.evaluate()\n\t}\n\n\tpub fn evaluate(&self) -> Option<f64> {\n\t\tif self.digits.is_empty() {\n\t\t\treturn None;\n\t\t}\n\n\t\tlet mut result = 0_f64;\n\t\tlet mut running_decimal_place = 0_i32;\n\n\t\tfor digit in &self.digits {\n\t\t\tif *digit == DECIMAL_POINT {\n\t\t\t\tif running_decimal_place == 0 {\n\t\t\t\t\trunning_decimal_place = 1;\n\t\t\t\t}\n\t\t\t} else if running_decimal_place == 0 {\n\t\t\t\tresult *= 10.;\n\t\t\t\tresult += *digit as f64;\n\t\t\t} else {\n\t\t\t\tresult += *digit as f64 * 0.1_f64.powi(running_decimal_place);\n\t\t\t\trunning_decimal_place += 1;\n\t\t\t}\n\t\t}\n\n\t\tif self.negative {\n\t\t\tresult = -result;\n\t\t}\n\n\t\tSome(result)\n\t}\n\n\tpub fn clear(&mut self) {\n\t\tself.digits.clear();\n\t\tself.string.clear();\n\t\tself.contains_decimal = false;\n\t\tself.negative = false;\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/document/utility_types/wires.rs",
    "content": "use crate::messages::portfolio::document::node_graph::utility_types::FrontendGraphDataType;\nuse glam::{DVec2, IVec2};\nuse graphene_std::{uuid::NodeId, vector::misc::dvec2_to_point};\nuse kurbo::{BezPath, DEFAULT_ACCURACY, Line, Point, Shape};\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct WirePath {\n\t#[serde(rename = \"pathString\")]\n\tpub path_string: String,\n\t#[serde(rename = \"dataType\")]\n\tpub data_type: FrontendGraphDataType,\n\tpub thick: bool,\n\tpub dashed: bool,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct WirePathUpdate {\n\tpub id: NodeId,\n\t#[serde(rename = \"inputIndex\")]\n\tpub input_index: usize,\n\t// If none, then remove the wire from the map\n\t#[serde(rename = \"wirePathUpdate\")]\n\tpub wire_path_update: Option<WirePath>,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Copy, Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize)]\npub enum GraphWireStyle {\n\t#[default]\n\tDirect = 0,\n\tGridAligned = 1,\n}\n\nimpl std::fmt::Display for GraphWireStyle {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tmatch self {\n\t\t\tGraphWireStyle::GridAligned => write!(f, \"Grid-Aligned\"),\n\t\t\tGraphWireStyle::Direct => write!(f, \"Direct\"),\n\t\t}\n\t}\n}\n\nimpl GraphWireStyle {\n\tpub fn tooltip_description(&self) -> &'static str {\n\t\tmatch self {\n\t\t\tGraphWireStyle::GridAligned => \"Wires follow the grid, running in straight lines between nodes.\",\n\t\t\tGraphWireStyle::Direct => \"Wires bend to run at an angle directly between nodes.\",\n\t\t}\n\t}\n\n\tpub fn is_direct(&self) -> bool {\n\t\t*self == GraphWireStyle::Direct\n\t}\n}\n\npub fn build_vector_wire(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool, graph_wire_style: GraphWireStyle) -> BezPath {\n\tlet grid_spacing = 24.;\n\tmatch graph_wire_style {\n\t\tGraphWireStyle::Direct => {\n\t\t\tlet horizontal_gap = (output_position.x - input_position.x).abs();\n\t\t\tlet vertical_gap = (output_position.y - input_position.y).abs();\n\n\t\t\tlet curve_length = grid_spacing;\n\t\t\tlet curve_falloff_rate = curve_length * std::f64::consts::TAU;\n\n\t\t\tlet horizontal_curve_amount = -(2_f64.powf((-10. * horizontal_gap) / curve_falloff_rate)) + 1.;\n\t\t\tlet vertical_curve_amount = -(2_f64.powf((-10. * vertical_gap) / curve_falloff_rate)) + 1.;\n\t\t\tlet horizontal_curve = horizontal_curve_amount * curve_length;\n\t\t\tlet vertical_curve = vertical_curve_amount * curve_length;\n\n\t\t\tlet locations = [\n\t\t\t\toutput_position,\n\t\t\t\tDVec2::new(\n\t\t\t\t\tif vertical_out { output_position.x } else { output_position.x + horizontal_curve },\n\t\t\t\t\tif vertical_out { output_position.y - vertical_curve } else { output_position.y },\n\t\t\t\t),\n\t\t\t\tDVec2::new(\n\t\t\t\t\tif vertical_in { input_position.x } else { input_position.x - horizontal_curve },\n\t\t\t\t\tif vertical_in { input_position.y + vertical_curve } else { input_position.y },\n\t\t\t\t),\n\t\t\t\tDVec2::new(input_position.x, input_position.y),\n\t\t\t];\n\n\t\t\tlet smoothing = 0.5;\n\t\t\tlet delta01 = DVec2::new((locations[1].x - locations[0].x) * smoothing, (locations[1].y - locations[0].y) * smoothing);\n\t\t\tlet delta23 = DVec2::new((locations[3].x - locations[2].x) * smoothing, (locations[3].y - locations[2].y) * smoothing);\n\n\t\t\tlet mut wire = BezPath::new();\n\t\t\twire.move_to(dvec2_to_point(locations[0]));\n\t\t\twire.line_to(dvec2_to_point(locations[1]));\n\t\t\twire.curve_to(dvec2_to_point(locations[1] + delta01), dvec2_to_point(locations[2] - delta23), dvec2_to_point(locations[2]));\n\t\t\twire.line_to(dvec2_to_point(locations[3]));\n\t\t\twire\n\t\t}\n\t\tGraphWireStyle::GridAligned => {\n\t\t\tlet locations = straight_wire_path(output_position, input_position, vertical_out, vertical_in);\n\t\t\tstraight_wire_to_bezpath(locations)\n\t\t}\n\t}\n}\n\nfn straight_wire_path(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool) -> Vec<IVec2> {\n\tlet grid_spacing = 24;\n\tlet line_width = 2;\n\n\tlet in_x = input_position.x as i32;\n\tlet in_y = input_position.y as i32;\n\tlet out_x = output_position.x as i32;\n\tlet out_y = output_position.y as i32;\n\n\tlet mid_x = (in_x + out_x) / 2 + (((in_x + out_x) / 2) % grid_spacing);\n\tlet mid_y = (in_y + out_y) / 2 + (((in_y + out_y) / 2) % grid_spacing);\n\tlet mid_y_alternate = (in_y + in_y) / 2 - (((in_y + in_y) / 2) % grid_spacing);\n\n\tlet x1 = out_x;\n\tlet x2 = out_x + grid_spacing;\n\tlet x3 = in_x - 2 * grid_spacing;\n\tlet x4 = in_x;\n\tlet x5 = in_x - 2 * grid_spacing + line_width;\n\tlet x6 = out_x + grid_spacing + line_width;\n\tlet x7 = out_x + 2 * grid_spacing + line_width;\n\tlet x8 = in_x + line_width;\n\tlet x9 = out_x + 2 * grid_spacing;\n\tlet x10 = mid_x + line_width;\n\tlet x11 = out_x - grid_spacing;\n\tlet x12 = out_x - 4 * grid_spacing;\n\tlet x13 = mid_x;\n\tlet x14 = in_x + grid_spacing;\n\tlet x15 = in_x - 4 * grid_spacing;\n\tlet x16 = in_x + 8 * grid_spacing;\n\tlet x17 = mid_x - 2 * line_width;\n\tlet x18 = out_x + grid_spacing - 2 * line_width;\n\tlet x19 = out_x - 2 * line_width;\n\tlet x20 = mid_x - line_width;\n\n\tlet y1 = out_y;\n\tlet y2 = out_y - grid_spacing;\n\tlet y3 = in_y;\n\tlet y4 = out_y - grid_spacing + 5 * line_width + 1;\n\tlet y5 = in_y - 2 * grid_spacing;\n\tlet y6 = out_y + 4 * line_width;\n\tlet y7 = out_y + 5 * line_width;\n\tlet y8 = out_y - 2 * grid_spacing + 5 * line_width + 1;\n\tlet y9 = out_y + 6 * line_width;\n\tlet y10 = in_y + 2 * grid_spacing;\n\tlet y111 = in_y + grid_spacing + 6 * line_width + 1;\n\tlet y12 = in_y + grid_spacing - 5 * line_width + 1;\n\tlet y13 = in_y - grid_spacing;\n\tlet y14 = in_y + grid_spacing;\n\tlet y15 = mid_y;\n\tlet y16 = mid_y_alternate;\n\n\tlet wire1 = vec![IVec2::new(x1, y1), IVec2::new(x1, y4), IVec2::new(x5, y4), IVec2::new(x5, y3), IVec2::new(x4, y3)];\n\n\tlet wire2 = vec![IVec2::new(x1, y1), IVec2::new(x1, y16), IVec2::new(x3, y16), IVec2::new(x3, y3), IVec2::new(x4, y3)];\n\n\tlet wire3 = vec![\n\t\tIVec2::new(x1, y1),\n\t\tIVec2::new(x1, y4),\n\t\tIVec2::new(x12, y4),\n\t\tIVec2::new(x12, y10),\n\t\tIVec2::new(x3, y10),\n\t\tIVec2::new(x3, y3),\n\t\tIVec2::new(x4, y3),\n\t];\n\n\tlet wire4 = vec![\n\t\tIVec2::new(x1, y1),\n\t\tIVec2::new(x1, y4),\n\t\tIVec2::new(x13, y4),\n\t\tIVec2::new(x13, y10),\n\t\tIVec2::new(x3, y10),\n\t\tIVec2::new(x3, y3),\n\t\tIVec2::new(x4, y3),\n\t];\n\n\tif out_y == in_y && out_x > in_x && (vertical_out || !vertical_in) {\n\t\treturn vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y2), IVec2::new(x3, y2), IVec2::new(x3, y3), IVec2::new(x4, y3)];\n\t}\n\n\t// `outConnector` point and `inConnector` point lying on the same horizontal grid line and `outConnector` point lies to the right of `inConnector` point\n\tif out_y == in_y && out_x > in_x && (vertical_out || !vertical_in) {\n\t\treturn vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y2), IVec2::new(x3, y2), IVec2::new(x3, y3), IVec2::new(x4, y3)];\n\t};\n\n\t// Handle straight lines\n\tif out_y == in_y || (out_x == in_x && vertical_out) {\n\t\treturn vec![IVec2::new(x1, y1), IVec2::new(x4, y3)];\n\t};\n\n\t// Handle standard right-angle paths\n\t// Start vertical, then horizontal\n\n\t// `outConnector` point lies to the left of `inConnector` point\n\tif vertical_out && in_x > out_x {\n\t\t// `outConnector` point lies above `inConnector` point\n\t\tif out_y < in_y {\n\t\t\t// `outConnector` point lies on the vertical grid line 4 units to the left of `inConnector` point point\n\t\t\tif -4 * grid_spacing <= out_x - in_x && out_x - in_x < -3 * grid_spacing {\n\t\t\t\treturn wire1;\n\t\t\t};\n\n\t\t\t// `outConnector` point lying on vertical grid lines 3 and 2 units to the left of `inConnector` point\n\t\t\tif -3 * grid_spacing <= out_x - in_x && out_x - in_x <= -grid_spacing {\n\t\t\t\tif -2 * grid_spacing <= out_y - in_y && out_y - in_y <= -grid_spacing {\n\t\t\t\t\treturn vec![IVec2::new(x1, y1), IVec2::new(x1, y2), IVec2::new(x2, y2), IVec2::new(x2, y3), IVec2::new(x4, y3)];\n\t\t\t\t};\n\n\t\t\t\tif -grid_spacing <= out_y - in_y && out_y - in_y <= 0 {\n\t\t\t\t\treturn vec![IVec2::new(x1, y1), IVec2::new(x1, y4), IVec2::new(x6, y4), IVec2::new(x6, y3), IVec2::new(x4, y3)];\n\t\t\t\t};\n\n\t\t\t\treturn vec![\n\t\t\t\t\tIVec2::new(x1, y1),\n\t\t\t\t\tIVec2::new(x1, y4),\n\t\t\t\t\tIVec2::new(x7, y4),\n\t\t\t\t\tIVec2::new(x7, y5),\n\t\t\t\t\tIVec2::new(x3, y5),\n\t\t\t\t\tIVec2::new(x3, y3),\n\t\t\t\t\tIVec2::new(x4, y3),\n\t\t\t\t];\n\t\t\t}\n\n\t\t\t// `outConnector` point lying on vertical grid line 1 units to the left of `inConnector` point\n\t\t\tif -grid_spacing < out_x - in_x && out_x - in_x <= 0 {\n\t\t\t\t// `outConnector` point lying on horizontal grid line 1 unit above `inConnector` point\n\t\t\t\tif -2 * grid_spacing <= out_y - in_y && out_y - in_y <= -grid_spacing {\n\t\t\t\t\treturn vec![IVec2::new(x1, y6), IVec2::new(x2, y6), IVec2::new(x8, y3)];\n\t\t\t\t};\n\n\t\t\t\t// `outConnector` point lying on the same horizontal grid line as `inConnector` point\n\t\t\t\tif -grid_spacing <= out_y - in_y && out_y - in_y <= 0 {\n\t\t\t\t\treturn vec![IVec2::new(x1, y7), IVec2::new(x4, y3)];\n\t\t\t\t};\n\n\t\t\t\treturn vec![\n\t\t\t\t\tIVec2::new(x1, y1),\n\t\t\t\t\tIVec2::new(x1, y2),\n\t\t\t\t\tIVec2::new(x9, y2),\n\t\t\t\t\tIVec2::new(x9, y5),\n\t\t\t\t\tIVec2::new(x3, y5),\n\t\t\t\t\tIVec2::new(x3, y3),\n\t\t\t\t\tIVec2::new(x4, y3),\n\t\t\t\t];\n\t\t\t}\n\n\t\t\treturn vec![IVec2::new(x1, y1), IVec2::new(x1, y4), IVec2::new(x10, y4), IVec2::new(x10, y3), IVec2::new(x4, y3)];\n\t\t}\n\n\t\t// `outConnector` point lies below `inConnector` point\n\t\t// `outConnector` point lying on vertical grid line 1 unit to the left of `inConnector` point\n\t\tif -grid_spacing <= out_x - in_x && out_x - in_x <= 0 {\n\t\t\t// `outConnector` point lying on the horizontal grid lines 1 and 2 units below the `inConnector` point\n\t\t\tif 0 <= out_y - in_y && out_y - in_y <= 2 * grid_spacing {\n\t\t\t\treturn vec![IVec2::new(x1, y6), IVec2::new(x11, y6), IVec2::new(x11, y3), IVec2::new(x4, y3)];\n\t\t\t};\n\n\t\t\treturn wire2;\n\t\t}\n\n\t\treturn vec![IVec2::new(x1, y1), IVec2::new(x1, y3), IVec2::new(x4, y3)];\n\t}\n\n\t// `outConnector` point lies to the right of `inConnector` point\n\tif vertical_out && in_x <= out_x {\n\t\t// `outConnector` point lying on any horizontal grid line above `inConnector` point\n\t\tif out_y < in_y {\n\t\t\t// `outConnector` point lying on horizontal grid line 1 unit above `inConnector` point\n\t\t\tif -2 * grid_spacing < out_y - in_y && out_y - in_y <= -grid_spacing {\n\t\t\t\treturn wire1;\n\t\t\t};\n\n\t\t\t// `outConnector` point lying on the same horizontal grid line as `inConnector` point\n\t\t\tif -grid_spacing < out_y - in_y && out_y - in_y <= 0 {\n\t\t\t\treturn vec![IVec2::new(x1, y1), IVec2::new(x1, y8), IVec2::new(x5, y8), IVec2::new(x5, y3), IVec2::new(x4, y3)];\n\t\t\t};\n\n\t\t\t// `outConnector` point lying on vertical grid lines 1 and 2 units to the right of `inConnector` point\n\t\t\tif grid_spacing <= out_x - in_x && out_x - in_x <= 3 * grid_spacing {\n\t\t\t\treturn vec![\n\t\t\t\t\tIVec2::new(x1, y1),\n\t\t\t\t\tIVec2::new(x1, y4),\n\t\t\t\t\tIVec2::new(x9, y4),\n\t\t\t\t\tIVec2::new(x9, y5),\n\t\t\t\t\tIVec2::new(x3, y5),\n\t\t\t\t\tIVec2::new(x3, y3),\n\t\t\t\t\tIVec2::new(x4, y3),\n\t\t\t\t];\n\t\t\t}\n\n\t\t\treturn vec![\n\t\t\t\tIVec2::new(x1, y1),\n\t\t\t\tIVec2::new(x1, y4),\n\t\t\t\tIVec2::new(x10, y4),\n\t\t\t\tIVec2::new(x10, y5),\n\t\t\t\tIVec2::new(x5, y5),\n\t\t\t\tIVec2::new(x5, y3),\n\t\t\t\tIVec2::new(x4, y3),\n\t\t\t];\n\t\t}\n\n\t\t// `outConnector` point lies below `inConnector` point\n\t\tif out_y - in_y <= grid_spacing {\n\t\t\t// `outConnector` point lies on the horizontal grid line 1 unit below the `inConnector` Point\n\t\t\tif 0 <= out_x - in_x && out_x - in_x <= 13 * grid_spacing {\n\t\t\t\treturn vec![IVec2::new(x1, y9), IVec2::new(x3, y9), IVec2::new(x3, y3), IVec2::new(x4, y3)];\n\t\t\t};\n\n\t\t\tif 13 < out_x - in_x && out_x - in_x <= 18 * grid_spacing {\n\t\t\t\treturn wire3;\n\t\t\t};\n\n\t\t\treturn wire4;\n\t\t}\n\n\t\t// `outConnector` point lies on the horizontal grid line 2 units below `outConnector` point\n\t\tif grid_spacing <= out_y - in_y && out_y - in_y <= 2 * grid_spacing {\n\t\t\tif 0 <= out_x - in_x && out_x - in_x <= 13 * grid_spacing {\n\t\t\t\treturn vec![IVec2::new(x1, y7), IVec2::new(x5, y7), IVec2::new(x5, y3), IVec2::new(x4, y3)];\n\t\t\t};\n\n\t\t\tif 13 < out_x - in_x && out_x - in_x <= 18 * grid_spacing {\n\t\t\t\treturn wire3;\n\t\t\t};\n\n\t\t\treturn wire4;\n\t\t}\n\n\t\t// 0 to 4 units below the `outConnector` Point\n\t\tif out_y - in_y <= 4 * grid_spacing {\n\t\t\treturn wire1;\n\t\t};\n\n\t\treturn wire2;\n\t}\n\n\t// Start horizontal, then vertical\n\tif vertical_in {\n\t\t// when `outConnector` lies below `inConnector`\n\t\tif out_y > in_y {\n\t\t\t// `out_x` lies to the left of `in_x`\n\t\t\tif out_x < in_x {\n\t\t\t\treturn vec![IVec2::new(x1, y1), IVec2::new(x4, y1), IVec2::new(x4, y3)];\n\t\t\t};\n\n\t\t\t// `out_x` lies to the right of `in_x`\n\t\t\tif out_y - in_y <= grid_spacing {\n\t\t\t\t// `outConnector` point directly below `inConnector` point\n\t\t\t\tif 0 <= out_x - in_x && out_x - in_x <= grid_spacing {\n\t\t\t\t\treturn vec![IVec2::new(x1, y1), IVec2::new(x14, y1), IVec2::new(x14, y2), IVec2::new(x4, y2), IVec2::new(x4, y3)];\n\t\t\t\t};\n\n\t\t\t\t// `outConnector` point lies below `inConnector` point and strictly to the right of `inConnector` point\n\t\t\t\treturn vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y111), IVec2::new(x4, y111), IVec2::new(x4, y3)];\n\t\t\t}\n\n\t\t\treturn vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y2), IVec2::new(x4, y2), IVec2::new(x4, y3)];\n\t\t}\n\n\t\t// `out_y` lies on or above the `in_y` point\n\t\tif -6 * grid_spacing < in_x - out_x && in_x - out_x < 4 * grid_spacing {\n\t\t\t// edge case: `outConnector` point lying on vertical grid lines ranging from 4 units to left to 5 units to right of `inConnector` point\n\t\t\tif -grid_spacing < in_x - out_x && in_x - out_x < 4 * grid_spacing {\n\t\t\t\treturn vec![\n\t\t\t\t\tIVec2::new(x1, y1),\n\t\t\t\t\tIVec2::new(x2, y1),\n\t\t\t\t\tIVec2::new(x2, y2),\n\t\t\t\t\tIVec2::new(x15, y2),\n\t\t\t\t\tIVec2::new(x15, y12),\n\t\t\t\t\tIVec2::new(x4, y12),\n\t\t\t\t\tIVec2::new(x4, y3),\n\t\t\t\t];\n\t\t\t}\n\n\t\t\treturn vec![IVec2::new(x1, y1), IVec2::new(x16, y1), IVec2::new(x16, y12), IVec2::new(x4, y12), IVec2::new(x4, y3)];\n\t\t}\n\n\t\t// left of edge case: `outConnector` point lying on vertical grid lines more than 4 units to left of `inConnector` point\n\t\tif 4 * grid_spacing < in_x - out_x {\n\t\t\treturn vec![IVec2::new(x1, y1), IVec2::new(x17, y1), IVec2::new(x17, y12), IVec2::new(x4, y12), IVec2::new(x4, y3)];\n\t\t};\n\n\t\t// right of edge case: `outConnector` point lying on the vertical grid lines more than 5 units to right of `inConnector` point\n\t\tif 6 * grid_spacing > in_x - out_x {\n\t\t\treturn vec![IVec2::new(x1, y1), IVec2::new(x18, y1), IVec2::new(x18, y12), IVec2::new(x4, y12), IVec2::new(x4, y3)];\n\t\t};\n\t}\n\n\t// Both horizontal - use horizontal middle point\n\t// When `inConnector` point is one of the two closest diagonally opposite points\n\tif 0 <= in_x - out_x && in_x - out_x <= grid_spacing && in_y - out_y >= -grid_spacing && in_y - out_y <= grid_spacing {\n\t\treturn vec![IVec2::new(x19, y1), IVec2::new(x19, y3), IVec2::new(x4, y3)];\n\t}\n\n\t// When `inConnector` point lies on the horizontal line 1 unit above and below the `outConnector` point\n\tif -grid_spacing <= out_y - in_y && out_y - in_y <= grid_spacing && out_x > in_x {\n\t\t// Horizontal line above `out_y`\n\t\tif in_y < out_y {\n\t\t\treturn vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y13), IVec2::new(x3, y13), IVec2::new(x3, y3), IVec2::new(x4, y3)];\n\t\t};\n\n\t\t// Horizontal line below `out_y`\n\t\treturn vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y14), IVec2::new(x3, y14), IVec2::new(x3, y3), IVec2::new(x4, y3)];\n\t}\n\n\t// `outConnector` point to the right of `inConnector` point\n\tif out_x > in_x - grid_spacing {\n\t\treturn vec![\n\t\t\tIVec2::new(x1, y1),\n\t\t\tIVec2::new(x18, y1),\n\t\t\tIVec2::new(x18, y15),\n\t\t\tIVec2::new(x5, y15),\n\t\t\tIVec2::new(x5, y3),\n\t\t\tIVec2::new(x4, y3),\n\t\t];\n\t};\n\n\t// When `inConnector` point lies on the vertical grid line two units to the right of `outConnector` point\n\tif grid_spacing <= in_x - out_x && in_x - out_x <= 2 * grid_spacing {\n\t\treturn vec![IVec2::new(x1, y1), IVec2::new(x18, y1), IVec2::new(x18, y3), IVec2::new(x4, y3)];\n\t};\n\n\tvec![IVec2::new(x1, y1), IVec2::new(x20, y1), IVec2::new(x20, y3), IVec2::new(x4, y3)]\n}\n\nfn straight_wire_to_bezpath(locations: Vec<IVec2>) -> BezPath {\n\tif locations.is_empty() {\n\t\treturn BezPath::new();\n\t}\n\n\tlet to_point = |location: IVec2| Point::new(location.x as f64, location.y as f64);\n\n\tif locations.len() == 2 {\n\t\tlet p1 = to_point(locations[0]);\n\t\tlet p2 = to_point(locations[1]);\n\t\tLine::new(p1, p2).to_path(DEFAULT_ACCURACY);\n\t}\n\n\tlet corner_radius = 10;\n\n\t// Create path with rounded corners\n\tlet mut path = BezPath::new();\n\tpath.move_to(to_point(locations[0]));\n\n\tfor i in 1..(locations.len() - 1) {\n\t\tlet prev = locations[i - 1];\n\t\tlet curr = locations[i];\n\t\tlet next = locations[i + 1];\n\n\t\tlet corner_start = IVec2::new(\n\t\t\tcurr.x\n\t\t\t\t+ if curr.x == prev.x {\n\t\t\t\t\t0\n\t\t\t\t} else if prev.x > curr.x {\n\t\t\t\t\tcorner_radius\n\t\t\t\t} else {\n\t\t\t\t\t-corner_radius\n\t\t\t\t},\n\t\t\tcurr.y\n\t\t\t\t+ if curr.y == prev.y {\n\t\t\t\t\t0\n\t\t\t\t} else if prev.y > curr.y {\n\t\t\t\t\tcorner_radius\n\t\t\t\t} else {\n\t\t\t\t\t-corner_radius\n\t\t\t\t},\n\t\t);\n\n\t\tlet corner_start_mid = IVec2::new(\n\t\t\tcurr.x\n\t\t\t\t+ if curr.x == prev.x {\n\t\t\t\t\t0\n\t\t\t\t} else if prev.x > curr.x {\n\t\t\t\t\tcorner_radius / 2\n\t\t\t\t} else {\n\t\t\t\t\t-corner_radius / 2\n\t\t\t\t},\n\t\t\tcurr.y\n\t\t\t\t+ if curr.y == prev.y {\n\t\t\t\t\t0\n\t\t\t\t} else {\n\t\t\t\t\tmatch prev.y > curr.y {\n\t\t\t\t\t\ttrue => corner_radius / 2,\n\t\t\t\t\t\tfalse => -corner_radius / 2,\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t);\n\n\t\tlet corner_end = IVec2::new(\n\t\t\tcurr.x\n\t\t\t\t+ if curr.x == next.x {\n\t\t\t\t\t0\n\t\t\t\t} else if next.x > curr.x {\n\t\t\t\t\tcorner_radius\n\t\t\t\t} else {\n\t\t\t\t\t-corner_radius\n\t\t\t\t},\n\t\t\tcurr.y\n\t\t\t\t+ if curr.y == next.y {\n\t\t\t\t\t0\n\t\t\t\t} else if next.y > curr.y {\n\t\t\t\t\tcorner_radius\n\t\t\t\t} else {\n\t\t\t\t\t-corner_radius\n\t\t\t\t},\n\t\t);\n\n\t\tlet corner_end_mid = IVec2::new(\n\t\t\tcurr.x\n\t\t\t\t+ if curr.x == next.x {\n\t\t\t\t\t0\n\t\t\t\t} else if next.x > curr.x {\n\t\t\t\t\tcorner_radius / 2\n\t\t\t\t} else {\n\t\t\t\t\t-corner_radius / 2\n\t\t\t\t},\n\t\t\tcurr.y\n\t\t\t\t+ if curr.y == next.y {\n\t\t\t\t\t0\n\t\t\t\t} else if next.y > curr.y {\n\t\t\t\t\t10 / 2\n\t\t\t\t} else {\n\t\t\t\t\t-corner_radius / 2\n\t\t\t\t},\n\t\t);\n\n\t\tpath.line_to(to_point(corner_start));\n\t\tpath.curve_to(to_point(corner_start_mid), to_point(corner_end_mid), to_point(corner_end));\n\t}\n\tpath.line_to(to_point(*locations.last().unwrap()));\n\tpath\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/document_migration.rs",
    "content": "// TODO: Eventually remove this document upgrade code\n// This file contains lots of hacky code for upgrading old documents to the new format\n\nuse crate::messages::portfolio::document::node_graph::document_node_definitions::{DefinitionIdentifier, resolve_document_node_type, resolve_network_node_type, resolve_proto_node_type};\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate, OutputConnector};\nuse crate::messages::prelude::DocumentMessageHandler;\nuse glam::{DVec2, IVec2};\nuse graph_craft::document::DocumentNode;\nuse graph_craft::document::{DocumentNodeImplementation, NodeInput, value::TaggedValue};\nuse graphene_std::ProtoNodeIdentifier;\nuse graphene_std::subpath::Subpath;\nuse graphene_std::table::Table;\nuse graphene_std::text::{TextAlign, TypesettingConfig};\nuse graphene_std::uuid::NodeId;\nuse graphene_std::vector::Vector;\nuse graphene_std::vector::style::{PaintOrder, StrokeAlign};\nuse std::collections::HashMap;\nuse std::f64::consts::PI;\n\nconst TEXT_REPLACEMENTS: &[(&str, &str)] = &[\n\t(\"graphene_core::vector::vector_nodes::SamplePointsNode\", \"graphene_core::vector::SamplePolylineNode\"),\n\t(\"graphene_core::vector::vector_nodes::SubpathSegmentLengthsNode\", \"graphene_core::vector::SubpathSegmentLengthsNode\"),\n\t(\"\\\"manual_composition\\\":null\", \"\\\"manual_composition\\\":{\\\"Generic\\\":\\\"T\\\"}\"),\n\t(\n\t\t\"core::option::Option<alloc::sync::Arc<graphene_core::context::OwnedContextImpl>>\",\n\t\t\"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>\",\n\t),\n\t(\"graphene_core::transform::Footprint\", \"graphene_core::transform::Footprint\"),\n\t(\"\\\"OptionalF64\\\":\", \"\\\"F64\\\":\"),\n\t(\"\\\"path_bool_nodes::BooleanOperation\\\"\", \"\\\"vector_types::vector::misc::BooleanOperation\\\"\"),\n];\n\npub struct NodeReplacement<'a> {\n\tnode: ProtoNodeIdentifier,\n\taliases: &'a [&'a str],\n}\n\nconst NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[\n\t// ================================\n\t// blending\n\t// ================================\n\tNodeReplacement {\n\t\tnode: graphene_std::blending_nodes::blending::IDENTIFIER,\n\t\taliases: &[\"graphene_core::raster::BlendingNode\", \"graphene_core::blending_nodes::BlendingNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::blending_nodes::blend_mode::IDENTIFIER,\n\t\taliases: &[\"graphene_core::raster::BlendModeNode\", \"graphene_core::blending_nodes::BlendModeNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::blending_nodes::opacity::IDENTIFIER,\n\t\taliases: &[\"graphene_core::raster::OpacityNode\", \"graphene_core::blending_nodes::OpacityNode\"],\n\t},\n\t// ================================\n\t// brush\n\t// ================================\n\tNodeReplacement {\n\t\tnode: graphene_std::brush::brush::blit::IDENTIFIER,\n\t\taliases: &[\"graphene_brush::BlitNode\", \"graphene_std::brush::BlitNode\", \"graphene_brush::brush::BlitNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::brush::brush::brush::IDENTIFIER,\n\t\taliases: &[\"graphene_brush::BrushNode\", \"graphene_std::brush::BrushNode\", \"graphene_brush::brush::BrushNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::brush::brush::brush_stamp_generator::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_brush::BrushStampGeneratorNode\",\n\t\t\t\"graphene_std::brush::BrushStampGeneratorNode\",\n\t\t\t\"graphene_brush::brush::BrushStampGeneratorNode\",\n\t\t],\n\t},\n\t// ================================\n\t// gcore\n\t// ================================\n\tNodeReplacement {\n\t\tnode: graphene_std::animation::animation_time::IDENTIFIER,\n\t\taliases: &[\"graphene_core::animation::AnimationTimeNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::debug::clone::IDENTIFIER,\n\t\taliases: &[\"graphene_core::ops::CloneNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::extract_xy::extract_xy::IDENTIFIER,\n\t\taliases: &[\"graphene_core::ops::ExtractXyNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::ops::identity::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_core::transform::CullNode\",\n\t\t\t\"graphene_core::transform::BoundlessFootprintNode\",\n\t\t\t\"graphene_core::transform::FreezeRealTimeNode\",\n\t\t\t\"graphene_core::transform_nodes::BoundlessFootprintNode\",\n\t\t\t\"graphene_core::transform_nodes::FreezeRealTimeNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::memo::monitor::IDENTIFIER,\n\t\taliases: &[\"graphene_core::memo::MonitorNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::memo::memo::IDENTIFIER,\n\t\taliases: &[\"graphene_core::memo::MemoNode\", \"graphene_core::memo::ImpureMemoNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::animation::real_time::IDENTIFIER,\n\t\taliases: &[\"graphene_core::animation::RealTimeNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::logic::serialize::IDENTIFIER,\n\t\taliases: &[\"graphene_core::logic::SerializeNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::debug::size_of::IDENTIFIER,\n\t\taliases: &[\"graphene_core::ops::SizeOfNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::debug::some::IDENTIFIER,\n\t\taliases: &[\"graphene_core::ops::SomeNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::logic::string_concatenate::IDENTIFIER,\n\t\taliases: &[\"graphene_core::logic::StringConcatenateNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::logic::string_length::IDENTIFIER,\n\t\taliases: &[\"graphene_core::logic::StringLengthNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::logic::string_replace::IDENTIFIER,\n\t\taliases: &[\"graphene_core::logic::StringReplaceNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::logic::string_slice::IDENTIFIER,\n\t\taliases: &[\"graphene_core::logic::StringSliceNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::logic::string_split::IDENTIFIER,\n\t\taliases: &[\"graphene_core::logic::StringSplitNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::logic::switch::IDENTIFIER,\n\t\taliases: &[\"graphene_core::logic::SwitchNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::logic::to_string::IDENTIFIER,\n\t\taliases: &[\"graphene_core::logic::ToStringNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::debug::unwrap_option::IDENTIFIER,\n\t\taliases: &[\"graphene_core::ops::UnwrapNode\", \"graphene_core::debug::UnwrapNode\"],\n\t},\n\t// ================================\n\t// graphic\n\t// ================================\n\tNodeReplacement {\n\t\tnode: graphene_std::artboard::create_artboard::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_core::artboard::CreateArtboardNode\",\n\t\t\t\"graphene_core::ConstructArtboardNode\",\n\t\t\t\"graphene_core::graphic_element::ToArtboardNode\",\n\t\t\t\"graphene_core::artboard::ToArtboardNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::graphic::extend::IDENTIFIER,\n\t\taliases: &[\"graphene_core::graphic::graphic::ExtendNode\", \"graphene_core::graphic::ExtendNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::graphic::flatten_graphic::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_core::graphic::FlattenGraphicNode\",\n\t\t\t\"graphene_core::graphic_element::FlattenGroupNode\",\n\t\t\t\"graphene_core::graphic_types::FlattenGroupNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::graphic::flatten_vector::IDENTIFIER,\n\t\taliases: &[\"graphene_core::graphic::FlattenVectorNode\", \"graphene_core::graphic_element::FlattenVectorNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::graphic::index_elements::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_core::graphic_element::IndexNode\",\n\t\t\t\"graphene_core::graphic::IndexNode\",\n\t\t\t\"graphene_core::graphic::IndexElementsNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::graphic::legacy_layer_extend::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_core::graphic_element::LayerNode\",\n\t\t\t\"graphene_core::graphic_types::LayerNode\",\n\t\t\t// Converted from \"Append Artboard\"\n\t\t\t\"graphene_core::AddArtboardNode\",\n\t\t\t\"graphene_core::graphic_element::AppendArtboardNode\",\n\t\t\t\"graphene_core::graphic_types::AppendArtboardNode\",\n\t\t\t\"graphene_core::artboard::AppendArtboardNode\",\n\t\t\t\"graphene_core::graphic::LegacyLayerExtendNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::graphic_nodes::source_node_id::IDENTIFIER,\n\t\taliases: &[\"graphene_core::graphic::graphic::SourceNodeIdNode\", \"graphene_core::graphic::SourceNodeIdNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::graphic::to_graphic::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_core::ToGraphicGroupNode\",\n\t\t\t\"graphene_core::graphic_element::ToGroupNode\",\n\t\t\t\"graphene_core::graphic_types::ToGroupNode\",\n\t\t\t\"graphene_core::graphic::ToGraphicNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::graphic::wrap_graphic::IDENTIFIER,\n\t\taliases: &[\n\t\t\t// Converted from \"To Element\"\n\t\t\t\"graphene_core::ToGraphicElementNode\",\n\t\t\t\"graphene_core::graphic_element::ToElementNode\",\n\t\t\t\"graphene_core::graphic_types::ToElementNode\",\n\t\t\t\"graphene_core::graphic::WrapGraphicNode\",\n\t\t],\n\t},\n\t// ================================\n\t// math\n\t// ================================\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::absolute_value::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::AbsoluteValueNode\", \"graphene_core::ops::AbsoluteValueNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::add::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::AddNode\", \"graphene_core::ops::AddNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::bool_value::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::BoolValueNode\", \"graphene_core::ops::BoolValueNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::ceiling::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::CeilingNode\", \"graphene_core::ops::CeilingNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::clamp::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::ClampNode\", \"graphene_core::ops::ClampNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::color_value::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::ColorValueNode\", \"graphene_core::ops::ColorValueNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::cosine::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::CosineNode\", \"graphene_core::ops::CosineNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::cosine_inverse::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::CosineInverseNode\", \"graphene_core::ops::CosineInverseNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::divide::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::DivideNode\", \"graphene_core::ops::DivideNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::dot_product::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::DotProductNode\", \"graphene_core::ops::DotProductNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::equals::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::EqualsNode\", \"graphene_core::ops::EqualsNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::exponent::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::ExponentNode\", \"graphene_core::ops::ExponentNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::floor::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::FloorNode\", \"graphene_core::ops::FloorNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::footprint_value::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::FootprintValueNode\", \"graphene_core::ops::FootprintValueNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::gradient_value::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_math_nodes::GradientValueNode\",\n\t\t\t\"graphene_core::ops::GradientValueNode\",\n\t\t\t\"graphene_math_nodes::GradientTableValueNode\",\n\t\t\t\"graphene_core::ops::GradientTableValueNode\",\n\t\t\t\"math_nodes::GradientTableValueNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::greater_than::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::GreaterThanNode\", \"graphene_core::ops::GreaterThanNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::greatest_common_divisor::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_math_nodes::GreatestCommonDivisor\",\n\t\t\t\"graphene_core::ops::GreatestCommonDivisor\",\n\t\t\t\"graphene_math_nodes::GreatestCommonDivisorNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::least_common_multiple::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_math_nodes::LeastCommonMultiple\",\n\t\t\t\"graphene_core::ops::LeastCommonMultiple\",\n\t\t\t\"graphene_math_nodes::LeastCommonMultipleNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::length::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::LengthNode\", \"graphene_core::ops::LenghtNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::less_than::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::LessThanNode\", \"graphene_core::ops::LessThanNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::logarithm::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::LogarithmNode\", \"graphene_core::ops::LogarithmNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::logical_and::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_core::ops::LogicalAndNode\",\n\t\t\t\"graphene_core::ops::LogicNotNode\",\n\t\t\t\"graphene_core::logic::LogicNotNode\",\n\t\t\t\"graphene_math_nodes::LogicalAndNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::logical_not::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_core::ops::LogicalNotNode\",\n\t\t\t\"graphene_core::ops::LogicOrNode\",\n\t\t\t\"graphene_core::logic::LogicOrNode\",\n\t\t\t\"graphene_math_nodes::LogicalNotNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::logical_or::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_core::ops::LogicalOrNode\",\n\t\t\t\"graphene_core::ops::LogicAndNode\",\n\t\t\t\"graphene_core::logic::LogicAndNode\",\n\t\t\t\"graphene_math_nodes::LogicalOrNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::math::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::MathNode\", \"graphene_core::ops::MathNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::max::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::MaxNode\", \"graphene_core::ops::MaxNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::min::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::MinNode\", \"graphene_core::ops::MinNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::modulo::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::ModuloNode\", \"graphene_core::ops::ModuloNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::multiply::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::MultiplyNode\", \"graphene_core::ops::MultiplyNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::normalize::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::NormalizeNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::not_equals::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::NotEqualsNode\", \"graphene_core::ops::NotEqualsNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::number_value::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::NumberValueNode\", \"graphene_core::ops::NumberValueNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::percentage_value::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::PercentageValueNode\", \"graphene_core::ops::PercentageValueNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::random::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::RandomNode\", \"graphene_core::ops::RandomNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::remap::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::RemapNode\", \"graphene_core::ops::RemapNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::root::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::RootNode\", \"graphene_core::ops::RootNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::round::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::RoundNode\", \"graphene_core::ops::RoundNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::sample_gradient::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::SampleGradientNode\", \"graphene_core::ops::SampleGradientNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::sine::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::SineNode\", \"graphene_core::ops::SineNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::sine_inverse::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::SineInverseNode\", \"graphene_core::ops::SineInverseNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::string_value::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::StringValueNode\", \"graphene_core::ops::StringValueNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::subtract::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::SubtractNode\", \"graphene_core::ops::SubtractNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::tangent::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::TangentNode\", \"graphene_core::ops::TangentNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::tangent_inverse::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::TangentInverseNode\", \"graphene_core::ops::TangentInverseNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::to_f_64::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::ToF64Node\", \"graphene_core::ops::ToF64Node\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::to_u_32::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::ToU32Node\", \"graphene_core::ops::ToU32Node\", \"math_nodes::ToU32Node\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::to_u_64::IDENTIFIER,\n\t\taliases: &[\"graphene_math_nodes::ToU64Node\", \"graphene_core::ops::ToU64Node\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::math_nodes::vec_2_value::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_math_nodes::Vec2ValueNode\",\n\t\t\t\"graphene_core::ops::ConstructVector2\",\n\t\t\t\"graphene_core::ops::Vector2ValueNode\",\n\t\t\t\"graphene_core::ops::CoordinateValueNode\",\n\t\t\t\"graphene_math_nodes::CoordinateValueNode\",\n\t\t],\n\t},\n\t// ================================\n\t// path bool\n\t// ================================\n\tNodeReplacement {\n\t\tnode: graphene_std::path_bool_nodes::boolean_operation::IDENTIFIER,\n\t\taliases: &[\"graphene_path_bool::BooleanOperationNode\", \"graphene_std::vector::BooleanOperationNode\"],\n\t},\n\t// ================================\n\t// raster\n\t// ================================\n\tNodeReplacement {\n\t\tnode: graphene_std::raster_nodes::adjustments::black_and_white::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_raster_nodes::adjustments::BlackAndWhiteNode\",\n\t\t\t\"graphene_core::raster::adjustments::BlackAndWhiteNode\",\n\t\t\t\"graphene_core::raster::BlackAndWhiteNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::raster_nodes::blending_nodes::blend::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_raster_nodes::adjustments::BlendNode\",\n\t\t\t\"raster_nodes::adjustments::BlendNode\",\n\t\t\t\"graphene_core::raster::adjustments::BlendNode\",\n\t\t\t\"graphene_core::raster::BlendNode\",\n\t\t\t\"graphene_raster_nodes::blending_nodes::BlendNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::raster_nodes::filter::blur::IDENTIFIER,\n\t\taliases: &[\"graphene_raster_nodes::filter::BlurNode\", \"graphene_std::filter::BlurNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::raster_nodes::adjustments::brightness_contrast::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_raster_nodes::adjustments::BrightnessContrastNode\",\n\t\t\t\"graphene_core::raster::adjustments::BrightnessContrastNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::raster_nodes::adjustments::brightness_contrast_classic::IDENTIFIER,\n\t\taliases: &[\"graphene_raster_nodes::adjustments::BrightnessContrastClassicNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::raster_nodes::adjustments::channel_mixer::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_raster_nodes::adjustments::ChannelMixerNode\",\n\t\t\t\"graphene_core::raster::adjustments::ChannelMixerNode\",\n\t\t\t\"graphene_core::raster::ChannelMixerNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::raster_nodes::blending_nodes::color_overlay::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_raster_nodes::adjustments::ColorOverlayNode\",\n\t\t\t\"graphene_raster_nodes::generate_curves::ColorOverlayNode\",\n\t\t\t\"raster_nodes::adjustments::ColorOverlayNode\",\n\t\t\t\"graphene_core::raster::adjustments::ColorOverlayNode\",\n\t\t\t\"raster_nodes::generate_curves::ColorOverlayNode\",\n\t\t\t\"graphene_raster_nodes::blending_nodes::ColorOverlayNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::raster_nodes::std_nodes::combine_channels::IDENTIFIER,\n\t\taliases: &[\"graphene_raster_nodes::std_nodes::CombineChannelsNode\", \"graphene_std::raster::CombineChannelsNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::raster_nodes::dehaze::dehaze::IDENTIFIER,\n\t\taliases: &[\"graphene_raster_nodes::dehaze::DehazeNode\", \"graphene_std::dehaze::DehazeNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::raster_nodes::std_nodes::empty_image::IDENTIFIER,\n\t\taliases: &[\"graphene_raster_nodes::std_nodes::EmptyImageNode\", \"graphene_std::raster::EmptyImageNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::raster_nodes::adjustments::exposure::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_raster_nodes::adjustments::ExposureNode\",\n\t\t\t\"graphene_core::raster::adjustments::ExposureNode\",\n\t\t\t\"graphene_core::raster::ExposureNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::raster_nodes::std_nodes::extend_image_to_bounds::IDENTIFIER,\n\t\taliases: &[\"graphene_raster_nodes::std_nodes::ExtendImageToBoundsNode\", \"graphene_std::raster::ExtendImageToBoundsNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::raster_nodes::adjustments::extract_channel::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_raster_nodes::adjustments::ExtractChannelNode\",\n\t\t\t\"graphene_core::raster::adjustments::ExtractChannelNode\",\n\t\t\t\"graphene_core::raster::ExtractChannelNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::raster_nodes::adjustments::gamma_correction::IDENTIFIER,\n\t\taliases: &[\"graphene_raster_nodes::adjustments::GammaCorrectionNode\", \"graphene_core::raster::adjustments::GammaCorrectionNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::raster_nodes::generate_curves::generate_curves::IDENTIFIER,\n\t\taliases: &[\"graphene_raster_nodes::generate_curves::GenerateCurvesNode\", \"graphene_core::raster::adjustments::GenerateCurvesNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::raster_nodes::gradient_map::gradient_map::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_raster_nodes::gradient_map::GradientMapNode\",\n\t\t\t\"graphene_raster_nodes::adjustments::GradientMapNode\",\n\t\t\t\"raster_nodes::gradient_map::GradientMapNode\",\n\t\t\t\"raster_nodes::adjustments::GradientMapNode\",\n\t\t\t\"graphene_core::raster::adjustments::GradientMapNode\",\n\t\t\t\"graphene_core::raster::GradientMapNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::raster_nodes::adjustments::hue_saturation::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_raster_nodes::adjustments::HueSaturationNode\",\n\t\t\t\"graphene_core::raster::adjustments::HueSaturationNode\",\n\t\t\t\"graphene_core::raster::HueSaturationNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::raster_nodes::image_color_palette::image_color_palette::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_raster_nodes::image_color_palette::ImageColorPaletteNode\",\n\t\t\t\"graphene_std::image_color_palette::ImageColorPaletteNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::raster_nodes::std_nodes::image_value::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_raster_nodes::std_nodes::ImageValueNode\",\n\t\t\t\"graphene_std::raster::ImageValueNode\",\n\t\t\t\"graphene_std::raster::ImageNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::raster_nodes::adjustments::invert::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_raster_nodes::adjustments::InvertNode\",\n\t\t\t\"graphene_core::raster::adjustments::InvertNode\",\n\t\t\t\"graphene_core::raster::InvertNode\",\n\t\t\t\"graphene_core::raster::InvertRGBNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::raster_nodes::adjustments::levels::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_raster_nodes::adjustments::LevelsNode\",\n\t\t\t\"graphene_core::raster::adjustments::LevelsNode\",\n\t\t\t\"graphene_core::raster::LevelsNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::raster_nodes::adjustments::luminance::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_raster_nodes::adjustments::LuminanceNode\",\n\t\t\t\"graphene_core::raster::adjustments::LuminanceNode\",\n\t\t\t\"graphene_core::raster::LuminanceNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::raster_nodes::adjustments::make_opaque::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_raster_nodes::adjustments::MakeOpaqueNode\",\n\t\t\t\"graphene_core::raster::adjustments::MakeOpaqueNode\",\n\t\t\t\"graphene_core::raster::ExtractOpaqueNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::raster_nodes::std_nodes::mandelbrot::IDENTIFIER,\n\t\taliases: &[\"graphene_raster_nodes::std_nodes::MandelbrotNode\", \"graphene_std::raster::MandelbrotNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::raster_nodes::std_nodes::mask::IDENTIFIER,\n\t\taliases: &[\"graphene_raster_nodes::std_nodes::MaskNode\", \"graphene_std::raster::MaskNode\", \"graphene_std::raster::MaskImageNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::raster_nodes::adjustments::posterize::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_raster_nodes::adjustments::PosterizeNode\",\n\t\t\t\"graphene_core::raster::adjustments::PosterizeNode\",\n\t\t\t\"graphene_core::raster::PosterizeNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::raster_nodes::std_nodes::noise_pattern::IDENTIFIER,\n\t\taliases: &[\"graphene_raster_nodes::std_nodes::NoisePatternNode\", \"graphene_std::raster::NoisePatternNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::raster_nodes::std_nodes::sample_image::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_raster_nodes::std_nodes::SampleImageNode\",\n\t\t\t\"graphene_std::raster::SampleImageNode\",\n\t\t\t\"graphene_std::raster::SampleNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::raster_nodes::adjustments::selective_color::IDENTIFIER,\n\t\taliases: &[\"graphene_raster_nodes::adjustments::SelectiveColorNode\", \"graphene_core::raster::adjustments::SelectiveColorNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::raster_nodes::adjustments::threshold::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_raster_nodes::adjustments::ThresholdNode\",\n\t\t\t\"graphene_core::raster::adjustments::ThresholdNode\",\n\t\t\t\"graphene_core::raster::ThresholdNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::raster_nodes::adjustments::vibrance::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_raster_nodes::adjustments::VibranceNode\",\n\t\t\t\"graphene_core::raster::adjustments::VibranceNode\",\n\t\t\t\"graphene_core::raster::VibranceNode\",\n\t\t],\n\t},\n\t// ================================\n\t// text\n\t// ================================\n\tNodeReplacement {\n\t\tnode: graphene_std::text::text::IDENTIFIER,\n\t\taliases: &[\"graphene_core::text::text::TextNode\", \"graphene_core::text::TextGeneratorNode\", \"graphene_core::text::TextNode\"],\n\t},\n\t// ================================\n\t// transform\n\t// ================================\n\tNodeReplacement {\n\t\tnode: graphene_std::transform_nodes::decompose_rotation::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_core::transform_nodes::RotationScaleNode\",\n\t\t\t\"graphene_core::transform::RotationScaleNode\",\n\t\t\t\"graphene_core::transform_nodes::DecomposeRotationNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::transform_nodes::decompose_scale::IDENTIFIER,\n\t\taliases: &[\"graphene_core::transform_nodes::DecomposeScaleNode\", \"graphene_core::transform::DecomposeScaleNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::transform_nodes::decompose_translation::IDENTIFIER,\n\t\taliases: &[\"graphene_core::transform_nodes::DecomposeTranslationNode\", \"graphene_core::transform::DecomposeTranslationNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::transform_nodes::extract_transform::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_core::transform_nodes::ExtractTransformNode\",\n\t\t\t\"graphene_core::transform::ExtractTransformNode\",\n\t\t\t\"graphene_core::vector::ExtractTransformNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::transform_nodes::invert_transform::IDENTIFIER,\n\t\taliases: &[\"graphene_core::transform_nodes::InvertTransformNode\", \"graphene_core::transform::InvertTransformNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::transform_nodes::replace_transform::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_core::transform_nodes::ReplaceTransformNode\",\n\t\t\t\"graphene_core::transform::SetTransformNode\",\n\t\t\t\"graphene_core::transform::ReplaceTransformNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::transform_nodes::transform::IDENTIFIER,\n\t\taliases: &[\"graphene_core::transform_nodes::TransformNode\", \"graphene_core::transform::TransformNode\"],\n\t},\n\t// ================================\n\t// vector\n\t// ================================\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::apply_transform::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::ApplyTransformNode\", \"graphene_core::vector::vector_modification::ApplyTransformNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::area::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::AreaNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::assign_colors::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::AssignColorsNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::auto_tangents::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::vector_nodes::AutoTangentsNode\", \"graphene_core::vector::AutoTangentsNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::bevel::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::BevelNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::bounding_box::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::BoundingBoxNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::box_warp::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::BoxWarpNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::centroid::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::CentroidNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::close_path::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::ClosePathNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::count_elements::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::CountElementsNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::cut_path::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::vector_nodes::SplitPathNode\", \"graphene_core::vector::SplitPathNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::cut_segments::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_core::vector::vector_nodes::SplitSegmentsNode\",\n\t\t\t\"graphene_core::vector::SplitSegmentsNode\",\n\t\t\t\"graphene_core::vector::CutSegmentsNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::dimensions::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::DimensionsNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector_nodes::fill::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::vector_nodes::FillNode\", \"graphene_core::vector::FillNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::flatten_path::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_core::vector::vector_nodes::FlattenPathNode\",\n\t\t\t\"graphene_core::vector::FlattenVectorElementsNode\",\n\t\t\t\"graphene_core::vector::FlattenPathNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::generator_nodes::arc::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::generator_nodes::ArcNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::generator_nodes::circle::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::generator_nodes::CircleNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::generator_nodes::ellipse::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::generator_nodes::EllipseNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::generator_nodes::grid::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::generator_nodes::GridNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::generator_nodes::line::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::generator_nodes::LineNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::generator_nodes::rectangle::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::generator_nodes::RectangleNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::generator_nodes::regular_polygon::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::generator_nodes::RegularPolygonNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::generator_nodes::spiral::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::generator_nodes::SpiralNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::generator_nodes::star::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::generator_nodes::StarNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::context::read_index::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::InstanceIndexNode\", \"core_types::vector::InstanceIndexNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::graphic::map::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::InstanceMapNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::context::read_position::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::InstancePositionNode\", \"core_types::vector::InstancePositionNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::context::read_vector::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::InstanceVectorNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::repeat::repeat::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::InstanceRepeatNode\", \"core_types::vector::InstanceRepeatNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::repeat::repeat_array::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::RepeatNode\", \"core_types::vector::RepeatNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::repeat::repeat_radial::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::CircularRepeatNode\", \"core_types::vector::CircularRepeatNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::repeat::repeat_on_points::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::InstanceOnPointsNode\", \"core_types::vector::InstanceOnPointsNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::jitter_points::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::JitterPointsNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::merge_by_distance::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::MergeByDistanceNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::graphic::mirror::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::MirrorNode\", \"core_types::vector::MirrorNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::morph::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::MorphNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::offset_path::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::OffsetPathNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::path_length::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::PathLengthNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::path_modify::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_core::vector::vector_modification_nodes::PathModifyNode\",\n\t\t\t\"graphene_core::vector::vector_data::modification::PathModifyNode\",\n\t\t\t\"graphene_core::vector::vector_modification::PathModifyNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::point_inside::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::PointInsideNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::points_to_polyline::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::PointsToPolylineNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::poisson_disk_points::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::PoissonDiskPointsNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::position_on_path::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::PositionOnPathNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::round_corners::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::RoundCornersNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::sample_polyline::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_core::vector::SamplePolylineNode\",\n\t\t\t\"graphene_core::vector::SamplePointsNode\",\n\t\t\t\"graphene_core::vector::vector_nodes::SamplePointsNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::separate_subpaths::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::SeparateSubpathsNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::solidify_stroke::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::SolidifyStrokeNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::spline::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_core::vector::vector_nodes::SplineNode\",\n\t\t\t\"graphene_core::vector::SplinesFromPointsNode\",\n\t\t\t\"graphene_core::vector::SplineNode\",\n\t\t],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::stroke::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::StrokeNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::subpath_segment_lengths::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::SubpathSegmentLengthsNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::tangent_on_path::IDENTIFIER,\n\t\taliases: &[\"graphene_core::vector::TangentOnPathNode\"],\n\t},\n\tNodeReplacement {\n\t\tnode: graphene_std::vector::vec_2_to_point::IDENTIFIER,\n\t\taliases: &[\n\t\t\t\"graphene_core::vector::vector_nodes::PositionToPointNode\",\n\t\t\t\"graphene_core::vector::PositionToPointNode\",\n\t\t\t\"graphene_core::vector::Vec2ToPointNode\",\n\t\t],\n\t},\n];\n\nconst REPLACEMENTS: &[(&str, &str)] = &[];\n\npub fn document_migration_string_preprocessing(document_serialized_content: String) -> String {\n\tlet document_serialized_content = replace_optional_f64_null(&document_serialized_content);\n\n\tTEXT_REPLACEMENTS\n\t\t.iter()\n\t\t.fold(document_serialized_content, |document_serialized_content, (old, new)| document_serialized_content.replace(old, new))\n}\n\nfn replace_optional_f64_null(input: &str) -> String {\n\tlet mut result = String::new();\n\tlet mut last_end = 0;\n\tlet key = \"\\\"OptionalF64\\\":\";\n\n\tfor (start, _) in input.match_indices(key) {\n\t\tlet search_start = start + key.len();\n\t\tif search_start >= input.len() {\n\t\t\tcontinue;\n\t\t}\n\n\t\tlet mut after_key_start = search_start;\n\t\tfor (i, c) in input[search_start..].char_indices() {\n\t\t\tif !c.is_whitespace() {\n\t\t\t\tafter_key_start = search_start + i;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t// If we reach the end and it's all whitespace, update after_key_start\n\t\t\tif search_start + i + c.len_utf8() == input.len() {\n\t\t\t\tafter_key_start = input.len();\n\t\t\t}\n\t\t}\n\n\t\tif input[after_key_start..].starts_with(\"null\") {\n\t\t\tresult.push_str(&input[last_end..start]);\n\t\t\tresult.push_str(key);\n\t\t\tresult.push_str(\"0.0\");\n\t\t\tlast_end = after_key_start + \"null\".len();\n\t\t}\n\t}\n\n\tresult.push_str(&input[last_end..]);\n\tresult\n}\n\npub fn document_migration_reset_node_definition(document_serialized_content: &str) -> bool {\n\t// Upgrade a document being opened to use fresh copies of all nodes\n\tif document_serialized_content.contains(\"node_output_index\") {\n\t\treturn true;\n\t}\n\n\t// Upgrade layer implementation from https://github.com/GraphiteEditor/Graphite/pull/1946 (see also `fn fix_nodes()` in `main.rs` of Graphene CLI)\n\tif document_serialized_content.contains(\"graphene_core::ConstructLayerNode\") || document_serialized_content.contains(\"graphene_core::AddArtboardNode\") {\n\t\treturn true;\n\t}\n\n\tfalse\n}\n\npub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_node_definitions_on_open: bool) {\n\tdocument.network_interface.migrate_path_modify_node();\n\n\tlet network = document.network_interface.document_network().clone();\n\n\t// Apply string and node replacements to each node\n\tlet mut replacements = HashMap::<&str, ProtoNodeIdentifier>::new();\n\tIterator::chain(\n\t\tNODE_REPLACEMENTS.iter().flat_map(|NodeReplacement { node, aliases }| aliases.iter().map(|old| (*old, node.clone()))),\n\t\tREPLACEMENTS.iter().map(|(old, new)| (*old, ProtoNodeIdentifier::new(new))),\n\t)\n\t.for_each(|(old, new)| {\n\t\tif replacements.insert(old, new).is_some() {\n\t\t\tpanic!(\"Duplicate old name `{old}`\");\n\t\t}\n\t});\n\n\tfor (node_id, node, network_path) in network.recursive_nodes() {\n\t\tif let DocumentNodeImplementation::ProtoNode(protonode_id) = &node.implementation {\n\t\t\tlet node_path_without_type_args = protonode_id.as_str().split('<').next();\n\t\t\tif let Some(new) = node_path_without_type_args.and_then(|node_path| replacements.get(node_path)) {\n\t\t\t\tlet mut default_template = NodeTemplate::default();\n\t\t\t\tdefault_template.document_node.implementation = DocumentNodeImplementation::ProtoNode(new.clone());\n\t\t\t\tdocument.network_interface.replace_implementation(node_id, &network_path, &mut default_template);\n\t\t\t\tdocument.network_interface.set_call_argument(node_id, &network_path, default_template.document_node.call_argument);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Apply upgrades to each unmodified node.\n\tlet nodes = document\n\t\t.network_interface\n\t\t.document_network()\n\t\t.recursive_nodes()\n\t\t.map(|(node_id, node, path)| (*node_id, node.clone(), path))\n\t\t.collect::<Vec<(NodeId, graph_craft::document::DocumentNode, Vec<NodeId>)>>();\n\tfor (node_id, node, network_path) in &nodes {\n\t\tmigrate_node(node_id, node, network_path, document, reset_node_definitions_on_open);\n\t}\n}\n\nfn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], document: &mut DocumentMessageHandler, reset_node_definitions_on_open: bool) -> Option<()> {\n\tif reset_node_definitions_on_open && let Some(reference) = document.network_interface.reference(node_id, network_path) {\n\t\tlet node_definition = resolve_document_node_type(&reference)?;\n\t\tdocument.network_interface.replace_implementation(node_id, network_path, &mut node_definition.default_node_template());\n\t}\n\n\t// Upgrade old nodes to use `Context` instead of `()` or `Footprint` as their call argument\n\tif node.call_argument == graph_craft::concrete!(()) || node.call_argument == graph_craft::concrete!(graphene_std::transform::Footprint) {\n\t\tdocument.network_interface.set_call_argument(node_id, network_path, graph_craft::concrete!(graphene_std::Context));\n\t}\n\n\t// Only nodes that have not been modified and still refer to a definition can be updated\n\tlet reference = document.network_interface.reference(node_id, network_path)?;\n\n\tlet mut inputs_count = node.inputs.len();\n\n\t// Upgrade Stroke node to reorder parameters and add \"Align\" and \"Paint Order\" (#2644)\n\tif reference == DefinitionIdentifier::ProtoNode(graphene_std::vector::stroke::IDENTIFIER) && inputs_count == 8 {\n\t\tlet mut node_template = resolve_document_node_type(&reference)?.default_node_template();\n\t\tlet old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;\n\n\t\tlet align_input = NodeInput::value(TaggedValue::StrokeAlign(StrokeAlign::Center), false);\n\t\tlet paint_order_input = NodeInput::value(TaggedValue::PaintOrder(PaintOrder::StrokeAbove), false);\n\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path);\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 3), align_input, network_path);\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path);\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[6].clone(), network_path);\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 6), old_inputs[7].clone(), network_path);\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 7), paint_order_input, network_path);\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 8), old_inputs[3].clone(), network_path);\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 9), old_inputs[4].clone(), network_path);\n\t}\n\n\t// Upgrade the old \"Spline\" node to the new \"Spline\" node\n\tif reference == DefinitionIdentifier::ProtoNode(graphene_std::vector::spline::IDENTIFIER)\n\t\t|| reference == DefinitionIdentifier::ProtoNode(ProtoNodeIdentifier::new(\"graphene_core::vector::generator_nodes::SplineNode\"))\n\t\t|| reference == DefinitionIdentifier::ProtoNode(ProtoNodeIdentifier::new(\"graphene_core::vector::SplineNode\"))\n\t\t|| reference == DefinitionIdentifier::ProtoNode(ProtoNodeIdentifier::new(\"graphene_core::vector::SplinesFromPointsNode\"))\n\t{\n\t\t// Retrieve the proto node identifier and verify it is the old \"Spline\" node, otherwise skip it if this is the new \"Spline\" node\n\t\tlet identifier = document\n\t\t\t.network_interface\n\t\t\t.implementation(node_id, network_path)\n\t\t\t.and_then(|implementation| implementation.get_proto_node());\n\t\tif identifier.map(|identifier| identifier.as_str()) != Some(\"graphene_core::vector::generator_nodes::SplineNode\") {\n\t\t\treturn None;\n\t\t}\n\n\t\t// Obtain the document node for the given node ID, extract the vector points, and create a Vector path from the list of points\n\t\tlet node = document.network_interface.document_node(node_id, network_path)?;\n\t\tlet Some(TaggedValue::VecDVec2(points)) = node.inputs.get(1).and_then(|tagged_value| tagged_value.as_value()) else {\n\t\t\tlog::error!(\"The old Spline node's input at index 1 is not a TaggedValue::VecDVec2\");\n\t\t\treturn None;\n\t\t};\n\t\tlet vector = Vector::from_subpath(Subpath::from_anchors(points.to_vec(), false));\n\n\t\t// Retrieve the output connectors linked to the \"Spline\" node's output connector\n\t\tlet Some(spline_outputs) = document.network_interface.outward_wires(network_path)?.get(&OutputConnector::node(*node_id, 0)).cloned() else {\n\t\t\tlog::error!(\"Vec of InputConnector Spline node is connected to its output connector 0.\");\n\t\t\treturn None;\n\t\t};\n\n\t\t// Get the node's current position in the graph\n\t\tlet Some(node_position) = document.network_interface.position(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get position of spline node.\");\n\t\t\treturn None;\n\t\t};\n\n\t\t// Get the \"Path\" node definition and fill it in with the Vector path and default vector modification\n\t\tlet Some(path_node_type) = resolve_network_node_type(\"Path\") else {\n\t\t\tlog::error!(\"Path node does not exist.\");\n\t\t\treturn None;\n\t\t};\n\t\tlet path_node = path_node_type.node_template_input_override([\n\t\t\tSome(NodeInput::value(TaggedValue::Vector(Table::new_from_element(vector)), true)),\n\t\t\tSome(NodeInput::value(TaggedValue::VectorModification(Default::default()), false)),\n\t\t]);\n\n\t\t// Get the \"Spline\" node definition and wire it up with the \"Path\" node as input\n\t\tlet Some(spline_node_type) = resolve_proto_node_type(graphene_std::vector::spline::IDENTIFIER) else {\n\t\t\tlog::error!(\"Spline node does not exist.\");\n\t\t\treturn None;\n\t\t};\n\t\tlet spline_node = spline_node_type.node_template_input_override([Some(NodeInput::node(NodeId(1), 0))]);\n\n\t\t// Create a new node group with the \"Path\" and \"Spline\" nodes and generate new node IDs for them\n\t\tlet nodes = vec![(NodeId(1), path_node), (NodeId(0), spline_node)];\n\t\tlet new_ids = nodes.iter().map(|(id, _)| (*id, NodeId::new())).collect::<HashMap<_, _>>();\n\t\tlet new_spline_id = *new_ids.get(&NodeId(0))?;\n\t\tlet new_path_id = *new_ids.get(&NodeId(1))?;\n\n\t\t// Remove the old \"Spline\" node from the document\n\t\tdocument.network_interface.delete_nodes(vec![*node_id], false, network_path);\n\n\t\t// Insert the new \"Path\" and \"Spline\" nodes into the network interface with generated IDs\n\t\tdocument.network_interface.insert_node_group(nodes.clone(), new_ids, network_path);\n\n\t\t// Reposition the new \"Spline\" node to match the original \"Spline\" node's position\n\t\tdocument.network_interface.shift_node(&new_spline_id, node_position, network_path);\n\n\t\t// Reposition the new \"Path\" node with an offset relative to the original \"Spline\" node's position\n\t\tdocument.network_interface.shift_node(&new_path_id, node_position + IVec2::new(-7, 0), network_path);\n\n\t\t// Redirect each output connection from the old node to the new \"Spline\" node's output connector\n\t\tfor input_connector in spline_outputs {\n\t\t\tdocument.network_interface.set_input(&input_connector, NodeInput::node(new_spline_id, 0), network_path);\n\t\t}\n\t}\n\n\t// Upgrade Text node to include line height and character spacing, which were previously hardcoded to 1, from https://github.com/GraphiteEditor/Graphite/pull/2016\n\tif reference == DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER) && inputs_count == 8 {\n\t\tlet mut template: NodeTemplate = resolve_document_node_type(&reference)?.default_node_template();\n\t\tdocument.network_interface.replace_implementation(node_id, network_path, &mut template);\n\t\tlet old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut template)?;\n\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path);\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[3].clone(), network_path);\n\t\tdocument.network_interface.set_input(\n\t\t\t&InputConnector::node(*node_id, 4),\n\t\t\tif inputs_count == 6 {\n\t\t\t\told_inputs[4].clone()\n\t\t\t} else {\n\t\t\t\tNodeInput::value(TaggedValue::F64(TypesettingConfig::default().line_height_ratio), false)\n\t\t\t},\n\t\t\tnetwork_path,\n\t\t);\n\t\tdocument.network_interface.set_input(\n\t\t\t&InputConnector::node(*node_id, 5),\n\t\t\tif inputs_count == 6 {\n\t\t\t\told_inputs[5].clone()\n\t\t\t} else {\n\t\t\t\tNodeInput::value(TaggedValue::F64(TypesettingConfig::default().character_spacing), false)\n\t\t\t},\n\t\t\tnetwork_path,\n\t\t);\n\t\tdocument.network_interface.set_input(\n\t\t\t&InputConnector::node(*node_id, 6),\n\t\t\tif inputs_count >= 7 {\n\t\t\t\told_inputs[6].clone()\n\t\t\t} else {\n\t\t\t\tNodeInput::value(TaggedValue::F64(TypesettingConfig::default().max_width.unwrap_or_default()), false)\n\t\t\t},\n\t\t\tnetwork_path,\n\t\t);\n\t\tdocument.network_interface.set_input(\n\t\t\t&InputConnector::node(*node_id, 7),\n\t\t\tif inputs_count >= 8 {\n\t\t\t\told_inputs[7].clone()\n\t\t\t} else {\n\t\t\t\tNodeInput::value(TaggedValue::F64(TypesettingConfig::default().max_width.unwrap_or_default()), false)\n\t\t\t},\n\t\t\tnetwork_path,\n\t\t);\n\t\tdocument.network_interface.set_input(\n\t\t\t&InputConnector::node(*node_id, 8),\n\t\t\tif inputs_count >= 9 {\n\t\t\t\told_inputs[8].clone()\n\t\t\t} else {\n\t\t\t\tNodeInput::value(TaggedValue::F64(TypesettingConfig::default().tilt), false)\n\t\t\t},\n\t\t\tnetwork_path,\n\t\t);\n\t\tdocument.network_interface.set_input(\n\t\t\t&InputConnector::node(*node_id, 9),\n\t\t\tif inputs_count >= 10 {\n\t\t\t\told_inputs[9].clone()\n\t\t\t} else {\n\t\t\t\tNodeInput::value(TaggedValue::TextAlign(TextAlign::default()), false)\n\t\t\t},\n\t\t\tnetwork_path,\n\t\t);\n\t\tdocument.network_interface.set_input(\n\t\t\t&InputConnector::node(*node_id, 10),\n\t\t\tif inputs_count >= 11 {\n\t\t\t\told_inputs[10].clone()\n\t\t\t} else {\n\t\t\t\tNodeInput::value(TaggedValue::Bool(false), false)\n\t\t\t},\n\t\t\tnetwork_path,\n\t\t);\n\t\tinputs_count = 11\n\t}\n\n\t// Insert bool parameters for `has_max_width` and `has_max_height`:\n\t// https://github.com/GraphiteEditor/Graphite/pull/3643\n\tif reference == DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER) && inputs_count == 11 {\n\t\tlet mut template: NodeTemplate = resolve_document_node_type(&reference)?.default_node_template();\n\t\tdocument.network_interface.replace_implementation(node_id, network_path, &mut template);\n\t\tlet old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut template)?;\n\n\t\t// Copy over old inputs\n\t\t#[allow(clippy::needless_range_loop)]\n\t\tfor i in 0..=5 {\n\t\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, i), old_inputs[i].clone(), network_path);\n\t\t}\n\n\t\t// Max Width\n\t\tlet Some(&TaggedValue::F64(old_max_width)) = old_inputs[6].as_value() else { return None };\n\t\tdocument\n\t\t\t.network_interface\n\t\t\t.set_input(&InputConnector::node(*node_id, 6), NodeInput::value(TaggedValue::Bool(old_max_width != 0.), false), network_path);\n\t\tdocument.network_interface.set_input(\n\t\t\t&InputConnector::node(*node_id, 7),\n\t\t\tNodeInput::value(TaggedValue::F64(if old_max_width == 0. { 100. } else { old_max_width }), false),\n\t\t\tnetwork_path,\n\t\t);\n\n\t\t// Max Height\n\t\tlet Some(&TaggedValue::F64(old_max_height)) = old_inputs[7].as_value() else { return None };\n\t\tdocument\n\t\t\t.network_interface\n\t\t\t.set_input(&InputConnector::node(*node_id, 8), NodeInput::value(TaggedValue::Bool(old_max_height != 0.), false), network_path);\n\t\tdocument.network_interface.set_input(\n\t\t\t&InputConnector::node(*node_id, 9),\n\t\t\tNodeInput::value(TaggedValue::F64(if old_max_height == 0. { 100. } else { old_max_height }), false),\n\t\t\tnetwork_path,\n\t\t);\n\n\t\t// Copy over old inputs\n\t\t#[allow(clippy::needless_range_loop)]\n\t\tfor i in 10..=12 {\n\t\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, i), old_inputs[i - 2].clone(), network_path);\n\t\t}\n\t}\n\n\t// Upgrade Sine, Cosine, and Tangent nodes to include a boolean input for whether the output should be in radians, which was previously the only option but is now not the default\n\tif inputs_count == 1\n\t\t&& (reference == DefinitionIdentifier::ProtoNode(graphene_std::math_nodes::sine::IDENTIFIER)\n\t\t\t|| reference == DefinitionIdentifier::ProtoNode(graphene_std::math_nodes::cosine::IDENTIFIER)\n\t\t\t|| reference == DefinitionIdentifier::ProtoNode(graphene_std::math_nodes::tangent::IDENTIFIER))\n\t{\n\t\tlet mut node_template = resolve_document_node_type(&reference)?.default_node_template();\n\t\tdocument.network_interface.replace_implementation(node_id, network_path, &mut node_template);\n\n\t\tlet old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;\n\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);\n\t\tdocument\n\t\t\t.network_interface\n\t\t\t.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::Bool(true), false), network_path);\n\t}\n\n\t// Upgrade the 'Tangent on Path' node to include a boolean input for whether the output should be in radians, which was previously the only option but is now not the default\n\tif reference == DefinitionIdentifier::ProtoNode(graphene_std::vector::tangent_on_path::IDENTIFIER) && inputs_count == 4 {\n\t\tlet mut node_template = resolve_document_node_type(&reference)?.default_node_template();\n\t\tdocument.network_interface.replace_implementation(node_id, network_path, &mut node_template);\n\n\t\tlet old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;\n\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path);\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[3].clone(), network_path);\n\t\tdocument\n\t\t\t.network_interface\n\t\t\t.set_input(&InputConnector::node(*node_id, 4), NodeInput::value(TaggedValue::Bool(true), false), network_path);\n\t}\n\n\t// Upgrade the Modulo node to include a boolean input for whether the output should be always positive, which was previously not an option\n\tif reference == DefinitionIdentifier::ProtoNode(graphene_std::math_nodes::modulo::IDENTIFIER) && inputs_count == 2 {\n\t\tlet mut node_template = resolve_document_node_type(&reference)?.default_node_template();\n\t\tdocument.network_interface.replace_implementation(node_id, network_path, &mut node_template);\n\n\t\tlet old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;\n\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);\n\t\tdocument\n\t\t\t.network_interface\n\t\t\t.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(false), false), network_path);\n\t}\n\n\t// Upgrade the Mirror node to add the `keep_original` boolean input\n\tif reference == DefinitionIdentifier::ProtoNode(graphene_std::graphic::mirror::IDENTIFIER) && inputs_count == 3 {\n\t\tlet mut node_template = resolve_document_node_type(&reference)?.default_node_template();\n\t\tdocument.network_interface.replace_implementation(node_id, network_path, &mut node_template);\n\n\t\tlet old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;\n\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path);\n\t\tdocument\n\t\t\t.network_interface\n\t\t\t.set_input(&InputConnector::node(*node_id, 3), NodeInput::value(TaggedValue::Bool(true), false), network_path);\n\t}\n\n\t// Upgrade the Mirror node to add the `reference_point` input and change `offset` from `DVec2` to `f64`\n\tif reference == DefinitionIdentifier::ProtoNode(graphene_std::graphic::mirror::IDENTIFIER) && inputs_count == 4 {\n\t\tlet mut node_template = resolve_document_node_type(&reference)?.default_node_template();\n\t\tdocument.network_interface.replace_implementation(node_id, network_path, &mut node_template);\n\n\t\tlet old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;\n\n\t\tlet Some(&TaggedValue::DVec2(old_offset)) = old_inputs[1].as_value() else { return None };\n\t\tlet old_offset = if old_offset.x.abs() > old_offset.y.abs() { old_offset.x } else { old_offset.y };\n\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);\n\t\tdocument.network_interface.set_input(\n\t\t\t&InputConnector::node(*node_id, 1),\n\t\t\tNodeInput::value(TaggedValue::ReferencePoint(graphene_std::transform::ReferencePoint::Center), false),\n\t\t\tnetwork_path,\n\t\t);\n\t\tdocument\n\t\t\t.network_interface\n\t\t\t.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::F64(old_offset), false), network_path);\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[2].clone(), network_path);\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[3].clone(), network_path);\n\t}\n\n\t// Upgrade artboard name being passed as hidden value input to \"Create Artboard\"\n\tif reference == DefinitionIdentifier::Network(\"Artboard\".into()) && reset_node_definitions_on_open {\n\t\tlet label = document.network_interface.display_name(node_id, network_path);\n\t\tdocument\n\t\t\t.network_interface\n\t\t\t.set_input(&InputConnector::node(NodeId(0), 1), NodeInput::value(TaggedValue::String(label), false), &[*node_id]);\n\t}\n\n\tif reference == DefinitionIdentifier::ProtoNode(graphene_std::raster_nodes::std_nodes::image_value::IDENTIFIER) && inputs_count == 1 {\n\t\tlet mut node_template = resolve_document_node_type(&reference)?.default_node_template();\n\t\tdocument.network_interface.replace_implementation(node_id, network_path, &mut node_template);\n\n\t\t// Insert a new empty input for the image\n\t\tdocument.network_interface.add_import(TaggedValue::None, false, 0, \"Empty\", \"\", &[*node_id]);\n\t}\n\n\tif reference == DefinitionIdentifier::ProtoNode(graphene_std::raster_nodes::std_nodes::noise_pattern::IDENTIFIER) && inputs_count == 15 {\n\t\tlet mut node_template = resolve_document_node_type(&reference)?.default_node_template();\n\t\tdocument.network_interface.replace_implementation(node_id, network_path, &mut node_template);\n\n\t\tlet old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;\n\n\t\tdocument\n\t\t\t.network_interface\n\t\t\t.set_input(&InputConnector::node(*node_id, 0), NodeInput::value(TaggedValue::None, false), network_path);\n\t\tfor (i, input) in old_inputs.iter().enumerate() {\n\t\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, i + 1), input.clone(), network_path);\n\t\t}\n\t}\n\n\tif reference == DefinitionIdentifier::ProtoNode(graphene_std::repeat::repeat_on_points::IDENTIFIER) && inputs_count == 2 {\n\t\tlet mut node_template = resolve_document_node_type(&reference)?.default_node_template();\n\t\tdocument.network_interface.replace_implementation(node_id, network_path, &mut node_template);\n\n\t\tlet old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;\n\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);\n\t}\n\n\tif reference == DefinitionIdentifier::ProtoNode(graphene_std::brush::brush::brush::IDENTIFIER) && inputs_count == 4 {\n\t\tlet mut node_template = resolve_document_node_type(&reference)?.default_node_template();\n\t\tdocument.network_interface.replace_implementation(node_id, network_path, &mut node_template);\n\n\t\tlet old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;\n\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);\n\t\t// We have removed the second input (\"bounds\"), so we don't add index 1 and we shift the rest of the inputs down by one\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[2].clone(), network_path);\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[3].clone(), network_path);\n\t}\n\n\tif reference == DefinitionIdentifier::ProtoNode(ProtoNodeIdentifier::new(\"graphene_core::vector::RemoveHandlesNode\")) {\n\t\tlet mut node_template = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(graphene_std::vector::auto_tangents::IDENTIFIER))?.default_node_template();\n\t\tdocument.network_interface.replace_implementation(node_id, network_path, &mut node_template);\n\n\t\tlet old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;\n\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);\n\t\tdocument\n\t\t\t.network_interface\n\t\t\t.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::F64(0.), false), network_path);\n\t\tdocument\n\t\t\t.network_interface\n\t\t\t.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(false), false), network_path);\n\t}\n\n\tif reference == DefinitionIdentifier::ProtoNode(ProtoNodeIdentifier::new(\"graphene_core::vector::GenerateHandlesNode\")) {\n\t\tlet mut node_template = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(graphene_std::vector::auto_tangents::IDENTIFIER))?.default_node_template();\n\t\tdocument.network_interface.replace_implementation(node_id, network_path, &mut node_template);\n\n\t\tlet old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;\n\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);\n\t\tdocument\n\t\t\t.network_interface\n\t\t\t.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(true), false), network_path);\n\t}\n\n\tif reference == DefinitionIdentifier::ProtoNode(graphene_std::vector::merge_by_distance::IDENTIFIER) && inputs_count == 2 {\n\t\tlet mut node_template = resolve_document_node_type(&reference)?.default_node_template();\n\t\tdocument.network_interface.replace_implementation(node_id, network_path, &mut node_template);\n\n\t\tlet old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;\n\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);\n\t\tdocument.network_interface.set_input(\n\t\t\t&InputConnector::node(*node_id, 2),\n\t\t\tNodeInput::value(TaggedValue::MergeByDistanceAlgorithm(graphene_std::vector::misc::MergeByDistanceAlgorithm::Topological), false),\n\t\t\tnetwork_path,\n\t\t);\n\t}\n\n\tif reference == DefinitionIdentifier::ProtoNode(ProtoNodeIdentifier::new(\"graphene_core::vector::SpatialMergeByDistanceNode\")) {\n\t\tlet mut node_template = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(graphene_std::vector::merge_by_distance::IDENTIFIER))?.default_node_template();\n\t\tdocument.network_interface.replace_implementation(node_id, network_path, &mut node_template);\n\n\t\tlet old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;\n\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);\n\t\tdocument.network_interface.set_input(\n\t\t\t&InputConnector::node(*node_id, 2),\n\t\t\tNodeInput::value(TaggedValue::MergeByDistanceAlgorithm(graphene_std::vector::misc::MergeByDistanceAlgorithm::Spatial), false),\n\t\t\tnetwork_path,\n\t\t);\n\t}\n\n\tif reference == DefinitionIdentifier::Network(\"Sample Points\".into()) && inputs_count == 5 {\n\t\tlet mut node_template = resolve_network_node_type(\"Sample Polyline\")?.default_node_template();\n\t\tdocument.network_interface.replace_implementation(node_id, network_path, &mut node_template);\n\n\t\tlet old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;\n\t\tlet new_spacing_value = NodeInput::value(TaggedValue::PointSpacingType(graphene_std::vector::misc::PointSpacingType::Separation), false);\n\t\tlet new_quantity_value = NodeInput::value(TaggedValue::U32(100), false);\n\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 1), new_spacing_value, network_path);\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[1].clone(), network_path);\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 3), new_quantity_value, network_path);\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[2].clone(), network_path);\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path);\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 6), old_inputs[4].clone(), network_path);\n\t}\n\n\t// Make the \"Quantity\" parameter a u32 instead of f64\n\tif reference == DefinitionIdentifier::ProtoNode(graphene_std::vector::sample_polyline::IDENTIFIER) {\n\t\t// Get the inputs, obtain the quantity value, and put the inputs back\n\t\tlet quantity_value = document\n\t\t\t.network_interface\n\t\t\t.input_from_connector(&InputConnector::Node { node_id: *node_id, input_index: 3 }, network_path)?;\n\n\t\tif let NodeInput::Value { tagged_value, exposed } = quantity_value\n\t\t\t&& let TaggedValue::F64(value) = **tagged_value\n\t\t{\n\t\t\tlet new_quantity_value = NodeInput::value(TaggedValue::U32(value as u32), *exposed);\n\t\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 3), new_quantity_value, network_path);\n\t\t}\n\t}\n\n\t// Make the \"Grid\" node, if its input of index 3 is a DVec2 for \"angles\" instead of a u32 for the \"columns\" input that now succeeds \"angles\", move the angle to index 5 (after \"columns\" and \"rows\")\n\tif reference == DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::grid::IDENTIFIER) && inputs_count == 6 {\n\t\tlet node_definition = resolve_document_node_type(&reference)?;\n\t\tlet mut new_node_template = node_definition.default_node_template();\n\n\t\tlet mut current_node_template = document.network_interface.create_node_template(node_id, network_path)?;\n\t\tlet old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut new_node_template)?;\n\t\tlet index_3_value = old_inputs.get(3).cloned();\n\n\t\tlet mut upgraded = false;\n\n\t\tif let Some(NodeInput::Value { tagged_value, exposed: _ }) = index_3_value\n\t\t\t&& matches!(*tagged_value, TaggedValue::DVec2(_))\n\t\t{\n\t\t\t// Move index 3 to the end\n\t\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);\n\t\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);\n\t\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path);\n\t\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[4].clone(), network_path);\n\t\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path);\n\t\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path);\n\n\t\t\tupgraded = true;\n\t\t}\n\n\t\tif !upgraded {\n\t\t\tlet _ = document.network_interface.replace_inputs(node_id, network_path, &mut current_node_template);\n\t\t}\n\t}\n\n\t// Add the \"Depth\" parameter to the \"Read Index\" node\n\tif reference == DefinitionIdentifier::ProtoNode(graphene_std::context::read_index::IDENTIFIER) && inputs_count == 0 {\n\t\tlet mut node_template = resolve_document_node_type(&reference)?.default_node_template();\n\t\tdocument.network_interface.replace_implementation(node_id, network_path, &mut node_template);\n\t\tdocument.network_interface.set_display_name(node_id, \"Read Index\".to_string(), network_path);\n\n\t\tlet mut node_path = network_path.to_vec();\n\t\tnode_path.push(*node_id);\n\n\t\tdocument.network_interface.add_import(TaggedValue::None, false, 0, \"Primary\", \"\", &node_path);\n\t\tdocument.network_interface.add_import(TaggedValue::U32(0), false, 1, \"Loop Level\", \"TODO\", &node_path);\n\t}\n\n\t// Migrate the Transform node to use degrees instead of radians\n\tif reference == DefinitionIdentifier::Network(\"Transform\".into()) && node.inputs.get(6).is_none() {\n\t\tlet mut node_template = resolve_network_node_type(\"Transform\")?.default_node_template();\n\t\tdocument.network_interface.replace_implementation(node_id, network_path, &mut node_template);\n\n\t\tlet old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;\n\n\t\t// Value\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);\n\t\t// Translation\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);\n\t\t// Rotation\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path);\n\t\t// Scale\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[3].clone(), network_path);\n\t\t// Skew\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[4].clone(), network_path);\n\t\t// Origin Offset\n\t\tdocument\n\t\t\t.network_interface\n\t\t\t.set_input(&InputConnector::node(*node_id, 5), NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false), network_path);\n\t\t// Scale Appearance\n\t\tdocument\n\t\t\t.network_interface\n\t\t\t.set_input(&InputConnector::node(*node_id, 6), NodeInput::value(TaggedValue::Bool(true), false), network_path);\n\n\t\t// Migrate rotation from radians to degrees\n\t\tmatch node.inputs.get(2)? {\n\t\t\tNodeInput::Value { tagged_value, exposed } => {\n\t\t\t\t// Read the existing Properties panel number value, which used to be in radians\n\t\t\t\tlet TaggedValue::F64(radians) = *tagged_value.clone().into_inner() else { return None };\n\n\t\t\t\t// Convert the radians to degrees and set it back as the new input value\n\t\t\t\tlet degrees = NodeInput::value(TaggedValue::F64(radians.to_degrees()), *exposed);\n\t\t\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 2), degrees, network_path);\n\t\t\t}\n\t\t\tNodeInput::Node { .. } => {\n\t\t\t\t// Construct a new Multiply node for converting from degrees to radians\n\t\t\t\tlet Some(multiply_node) = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(graphene_std::math_nodes::multiply::IDENTIFIER)) else {\n\t\t\t\t\tlog::error!(\"Could not get multiply node from definition when upgrading transform\");\n\t\t\t\t\treturn None;\n\t\t\t\t};\n\t\t\t\tlet mut multiply_template = multiply_node.default_node_template();\n\t\t\t\tmultiply_template.document_node.inputs[1] = NodeInput::value(TaggedValue::F64(180. / PI), false);\n\n\t\t\t\t// Decide on the placement position of the new Multiply node\n\t\t\t\tlet multiply_node_id = NodeId::new();\n\t\t\t\tlet Some(transform_position) = document.network_interface.position_from_downstream_node(node_id, network_path) else {\n\t\t\t\t\tlog::error!(\"Could not get positon for transform node {node_id}\");\n\t\t\t\t\treturn None;\n\t\t\t\t};\n\t\t\t\tlet multiply_position = transform_position + IVec2::new(-7, 1);\n\n\t\t\t\t// Insert the new Multiply node into the network directly before it's used\n\t\t\t\tdocument.network_interface.insert_node(multiply_node_id, multiply_template, network_path);\n\t\t\t\tdocument.network_interface.shift_absolute_node_position(&multiply_node_id, multiply_position, network_path);\n\t\t\t\tdocument.network_interface.insert_node_between(&multiply_node_id, &InputConnector::node(*node_id, 2), 0, network_path);\n\t\t\t}\n\t\t\t_ => {}\n\t\t};\n\n\t\t// Migrate skew from radians to degrees\n\t\tif let NodeInput::Value { tagged_value, exposed } = node.inputs.get(4)? {\n\t\t\t// Read the existing Properties panel number value, which used to be in radians\n\t\t\tlet TaggedValue::DVec2(old_value) = *tagged_value.clone().into_inner() else { return None };\n\n\t\t\t// The previous value stored the tangent of the displayed degrees. Now it stores the degrees, so take the arctan of it and convert to degrees.\n\t\t\tlet new_value = DVec2::new(old_value.x.atan().to_degrees(), old_value.y.atan().to_degrees());\n\t\t\tlet new_input = NodeInput::value(TaggedValue::DVec2(new_value), *exposed);\n\t\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 4), new_input, network_path);\n\t\t}\n\t}\n\n\t// Upgrade the \"Animation\" node to add the \"Rate\" input\n\tif reference == DefinitionIdentifier::ProtoNode(graphene_std::animation::animation_time::IDENTIFIER) && inputs_count < 2 {\n\t\tlet mut node_template = resolve_document_node_type(&reference)?.default_node_template();\n\t\tdocument.network_interface.replace_implementation(node_id, network_path, &mut node_template);\n\t\tlet _ = document.network_interface.replace_inputs(node_id, network_path, &mut node_template);\n\n\t\tdocument\n\t\t\t.network_interface\n\t\t\t.set_input(&InputConnector::node(*node_id, 0), NodeInput::value(TaggedValue::None, false), network_path);\n\t\tdocument\n\t\t\t.network_interface\n\t\t\t.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::F64(1.), false), network_path);\n\t}\n\n\t// Upgrade the \"Read Position\" node to add the \"Loop Level\" input\n\tif reference == DefinitionIdentifier::ProtoNode(graphene_std::context::read_position::IDENTIFIER) && inputs_count < 2 {\n\t\tlet mut node_template = resolve_document_node_type(&reference)?.default_node_template();\n\t\tdocument.network_interface.replace_implementation(node_id, network_path, &mut node_template);\n\t\tlet _ = document.network_interface.replace_inputs(node_id, network_path, &mut node_template);\n\n\t\tdocument\n\t\t\t.network_interface\n\t\t\t.set_input(&InputConnector::node(*node_id, 0), NodeInput::value(TaggedValue::None, false), network_path);\n\t\tdocument\n\t\t\t.network_interface\n\t\t\t.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::U32(0), false), network_path);\n\t}\n\n\t// Migrate from the old source/target \"Morph\" node to the new vector table based \"Morph\" node.\n\t// This doesn't produce exactly equivalent results in cases involving input vector tables with multiple rows.\n\t// The old version would zip the source and target table rows, interpoleating each pair together.\n\t// The migrated version will instead deeply flatten both merged tables and morph sequentially between all source vectors and all target vector elements.\n\t// This migration assumes most usages didn't involve multiple parallel vector elements, and instead morphed from a single source to a single target vector element.\n\tif reference == DefinitionIdentifier::ProtoNode(graphene_std::vector::morph::IDENTIFIER) && (inputs_count == 3 || inputs_count == 4) {\n\t\t// 3 inputs - old signature (#3405):\n\t\t// async fn morph(_: impl Ctx, source: Table<Vector>, #[expose] target: Table<Vector>, #[default(0.5)] time: Fraction) -> Table<Vector> { ... }\n\t\t//\n\t\t// 4 inputs - even older signature (commit 80b8df8d4298b6669f124b929ce61bfabfc44e41):\n\t\t// async fn morph(_: impl Ctx, source: Table<Vector>, #[expose] target: Table<Vector>, #[default(0.5)] time: Fraction, #[min(0.)] start_index: IntegerCount) -> Table<Vector> { ... }\n\t\t//\n\t\t// New signature:\n\t\t// async fn morph<I: IntoGraphicTable>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Vector>)] content: I, progression: Progression) -> Table<Vector> { ... }\n\n\t\tlet mut node_template = resolve_document_node_type(&reference)?.default_node_template();\n\t\tlet old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;\n\n\t\t// Create a new Merge node\n\t\tlet Some(merge_node_type) = resolve_network_node_type(\"Merge\") else {\n\t\t\tlog::error!(\"Could not get merge node from definition when upgrading morph\");\n\t\t\treturn None;\n\t\t};\n\t\tlet merge_template = merge_node_type.default_node_template();\n\t\tlet merge_node_id = NodeId::new();\n\n\t\t// Decide on the placement position of the new Merge node\n\t\tlet Some(morph_position) = document.network_interface.position_from_downstream_node(node_id, network_path) else {\n\t\t\tlog::error!(\"Could not get position for morph node {node_id}\");\n\t\t\treturn None;\n\t\t};\n\t\tlet merge_position = morph_position + IVec2::new(-7, 0);\n\n\t\t// Insert the new Merge node into the network\n\t\tdocument.network_interface.insert_node(merge_node_id, merge_template, network_path);\n\t\tdocument.network_interface.set_to_node_or_layer(&merge_node_id, network_path, false);\n\t\tdocument.network_interface.shift_absolute_node_position(&merge_node_id, merge_position, network_path);\n\n\t\t// Connect the old 'source' and 'target' inputs to the new Merge node\n\t\tdocument.network_interface.set_input(&InputConnector::node(merge_node_id, 0), old_inputs[0].clone(), network_path);\n\t\tdocument.network_interface.set_input(&InputConnector::node(merge_node_id, 1), old_inputs[1].clone(), network_path);\n\n\t\t// Connect the new Merge node to the 'content' input of the Morph node\n\t\tdocument\n\t\t\t.network_interface\n\t\t\t.set_input(&InputConnector::node(*node_id, 0), NodeInput::node(merge_node_id, 0), network_path);\n\t\t// Connect the old 'progression' input to the new 'progression' input of the Morph node\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[2].clone(), network_path);\n\t}\n\n\t// Migrate old Arrow node from (start, end, shaft_width, head_width, head_length) to (arrow_to, shaft_width, head_width, head_length) with a Transform node for positioning\n\tif reference == DefinitionIdentifier::ProtoNode(graphene_std::vector_nodes::arrow::IDENTIFIER) && inputs_count == 6 {\n\t\t// Read old start and end values\n\t\tlet start = match node.inputs.get(1)? {\n\t\t\tNodeInput::Value { tagged_value, .. } => {\n\t\t\t\tif let TaggedValue::DVec2(v) = *tagged_value.clone().into_inner() {\n\t\t\t\t\tv\n\t\t\t\t} else {\n\t\t\t\t\tDVec2::ZERO\n\t\t\t\t}\n\t\t\t}\n\t\t\t_ => DVec2::ZERO,\n\t\t};\n\t\tlet end = match node.inputs.get(2)? {\n\t\t\tNodeInput::Value { tagged_value, .. } => {\n\t\t\t\tif let TaggedValue::DVec2(v) = *tagged_value.clone().into_inner() {\n\t\t\t\t\tv\n\t\t\t\t} else {\n\t\t\t\t\tDVec2::new(100., 0.)\n\t\t\t\t}\n\t\t\t}\n\t\t\t_ => DVec2::new(100., 0.),\n\t\t};\n\n\t\t// Replace inputs with the new node definition (primary + arrow_to + shaft_width + head_width + head_length)\n\t\tlet mut node_template = resolve_document_node_type(&reference)?.default_node_template();\n\t\tlet old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;\n\n\t\t// Preserve primary input connection\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);\n\t\t// Set arrow_to = end - start\n\t\tdocument\n\t\t\t.network_interface\n\t\t\t.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::DVec2(end - start), false), network_path);\n\t\t// Preserve shaft_width, head_width, head_length (shifted from indices 3,4,5 to 2,3,4)\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[3].clone(), network_path);\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[4].clone(), network_path);\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path);\n\n\t\t// Find downstream connection to insert Transform node\n\t\tlet downstream = document\n\t\t\t.network_interface\n\t\t\t.outward_wires(network_path)\n\t\t\t.and_then(|wires| wires.get(&OutputConnector::node(*node_id, 0)))\n\t\t\t.and_then(|connections| connections.first().cloned());\n\n\t\tif let Some(downstream_input) = downstream {\n\t\t\t// Create a Transform node with translation = start\n\t\t\tlet Some(transform_node_type) = resolve_network_node_type(\"Transform\") else {\n\t\t\t\tlog::error!(\"Transform node definition not found during Arrow migration\");\n\t\t\t\treturn None;\n\t\t\t};\n\t\t\tlet mut transform_template = transform_node_type.default_node_template();\n\t\t\ttransform_template.document_node.inputs[1] = NodeInput::value(TaggedValue::DVec2(start), false);\n\n\t\t\tlet transform_id = NodeId::new();\n\n\t\t\t// Position the Transform node to the right of the Arrow node\n\t\t\tlet arrow_position = document.network_interface.position(node_id, network_path).unwrap_or_default();\n\t\t\tdocument.network_interface.insert_node(transform_id, transform_template, network_path);\n\t\t\tdocument.network_interface.shift_absolute_node_position(&transform_id, arrow_position + IVec2::new(7, 0), network_path);\n\t\t\tdocument.network_interface.insert_node_between(&transform_id, &downstream_input, 0, network_path);\n\t\t}\n\t}\n\n\t// Migrate old Line node from (start, end) to (line_to) with a Transform node for positioning\n\tif reference == DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::line::IDENTIFIER) && inputs_count == 3 {\n\t\t// Read old start and end values\n\t\tlet start = match node.inputs.get(1)? {\n\t\t\tNodeInput::Value { tagged_value, .. } => {\n\t\t\t\tif let TaggedValue::DVec2(v) = *tagged_value.clone().into_inner() {\n\t\t\t\t\tv\n\t\t\t\t} else {\n\t\t\t\t\tDVec2::ZERO\n\t\t\t\t}\n\t\t\t}\n\t\t\t_ => DVec2::ZERO,\n\t\t};\n\t\tlet end = match node.inputs.get(2)? {\n\t\t\tNodeInput::Value { tagged_value, .. } => {\n\t\t\t\tif let TaggedValue::DVec2(v) = *tagged_value.clone().into_inner() {\n\t\t\t\t\tv\n\t\t\t\t} else {\n\t\t\t\t\tDVec2::new(100., 100.)\n\t\t\t\t}\n\t\t\t}\n\t\t\t_ => DVec2::new(100., 100.),\n\t\t};\n\n\t\t// Replace inputs with the new node definition (primary + line_to)\n\t\tlet mut node_template = resolve_document_node_type(&reference)?.default_node_template();\n\t\tlet old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;\n\n\t\t// Preserve primary input connection\n\t\tdocument.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);\n\t\t// Set line_to = end - start\n\t\tdocument\n\t\t\t.network_interface\n\t\t\t.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::DVec2(end - start), false), network_path);\n\n\t\t// Find downstream connection to insert Transform node\n\t\tlet downstream = document\n\t\t\t.network_interface\n\t\t\t.outward_wires(network_path)\n\t\t\t.and_then(|wires| wires.get(&OutputConnector::node(*node_id, 0)))\n\t\t\t.and_then(|connections| connections.first().cloned());\n\n\t\tif let Some(downstream_input) = downstream {\n\t\t\t// Create a Transform node with translation = start\n\t\t\tlet Some(transform_node_type) = resolve_network_node_type(\"Transform\") else {\n\t\t\t\tlog::error!(\"Transform node definition not found during Line migration\");\n\t\t\t\treturn None;\n\t\t\t};\n\t\t\tlet mut transform_template = transform_node_type.default_node_template();\n\t\t\ttransform_template.document_node.inputs[1] = NodeInput::value(TaggedValue::DVec2(start), false);\n\n\t\t\tlet transform_id = NodeId::new();\n\n\t\t\t// Position the Transform node to the right of the Line node\n\t\t\tlet line_position = document.network_interface.position(node_id, network_path).unwrap_or_default();\n\t\t\tdocument.network_interface.insert_node(transform_id, transform_template, network_path);\n\t\t\tdocument.network_interface.shift_absolute_node_position(&transform_id, line_position + IVec2::new(7, 0), network_path);\n\t\t\tdocument.network_interface.insert_node_between(&transform_id, &downstream_input, 0, network_path);\n\t\t}\n\t}\n\n\t// Add context features to nodes that don't have them (fine-grained context caching migration)\n\tif node.context_features == graphene_std::ContextDependencies::default()\n\t\t&& let Some(reference) = document.network_interface.reference(node_id, network_path).clone()\n\t\t&& let Some(node_definition) = resolve_document_node_type(&reference)\n\t{\n\t\tlet context_features = node_definition.node_template.document_node.context_features;\n\t\tdocument.network_interface.set_context_features(node_id, network_path, context_features);\n\t}\n\n\t// ==================================\n\t// PUT ALL MIGRATIONS ABOVE THIS LINE\n\t// ==================================\n\n\t// Ensure layers are positioned as stacks if they are upstream siblings of another layer\n\tdocument.network_interface.load_structure();\n\tlet all_layers = LayerNodeIdentifier::ROOT_PARENT.descendants(document.network_interface.document_metadata()).collect::<Vec<_>>();\n\tfor layer in all_layers {\n\t\tlet (downstream_node, input_index) = document\n\t\t\t.network_interface\n\t\t\t.outward_wires(&[])\n\t\t\t.and_then(|outward_wires| outward_wires.get(&OutputConnector::node(layer.to_node(), 0)))\n\t\t\t.and_then(|outward_wires| outward_wires.first())\n\t\t\t.and_then(|input_connector| input_connector.node_id().map(|node_id| (node_id, input_connector.input_index())))?;\n\t\t// If the downstream node is a layer and the input is the first input and the current layer is not in a stack\n\t\tif input_index == 0 && document.network_interface.is_layer(&downstream_node, &[]) && !document.network_interface.is_stack(&layer.to_node(), &[]) {\n\t\t\t// Ensure the layer is horizontally aligned with the downstream layer to prevent changing the layout of old files\n\t\t\tlet (Some(layer_position), Some(downstream_position)) = (document.network_interface.position(&layer.to_node(), &[]), document.network_interface.position(&downstream_node, &[])) else {\n\t\t\t\tlog::error!(\"Could not get position for layer {:?} or downstream node {} when opening file\", layer.to_node(), downstream_node);\n\t\t\t\treturn None;\n\t\t\t};\n\t\t\tif layer_position.x == downstream_position.x {\n\t\t\t\tdocument.network_interface.set_stack_position_calculated_offset(&layer.to_node(), &downstream_node, &[]);\n\t\t\t}\n\t\t}\n\t}\n\n\tSome(())\n}\n\n#[cfg(test)]\nmod tests {\n\tuse super::*;\n\n\t#[test]\n\tfn test_no_duplicate_node_replacements() {\n\t\tlet mut hashmap = HashMap::<ProtoNodeIdentifier, u32>::new();\n\t\tNODE_REPLACEMENTS.iter().for_each(|node| {\n\t\t\t*hashmap.entry(node.node.clone()).or_default() += 1;\n\t\t});\n\t\tlet duplicates = hashmap.iter().filter(|(_, count)| **count > 1).map(|(node, _)| node.as_str()).collect::<Vec<_>>();\n\t\tif !duplicates.is_empty() {\n\t\t\tpanic!(\"Duplicate entries in `NODE_REPLACEMENTS`: {duplicates:?}\");\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/mod.rs",
    "content": "mod portfolio_message;\nmod portfolio_message_handler;\n\npub mod document;\npub mod document_migration;\npub mod utility_types;\n\n#[doc(inline)]\npub use portfolio_message::{PortfolioMessage, PortfolioMessageDiscriminant};\n#[doc(inline)]\npub use portfolio_message_handler::{PortfolioMessageContext, PortfolioMessageHandler};\n"
  },
  {
    "path": "editor/src/messages/portfolio/portfolio_message.rs",
    "content": "use super::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse super::utility_types::PanelType;\nuse crate::messages::frontend::utility_types::{ExportBounds, FileType};\nuse crate::messages::portfolio::document::utility_types::clipboards::Clipboard;\nuse crate::messages::portfolio::utility_types::FontCatalog;\nuse crate::messages::prelude::*;\nuse graphene_std::Color;\nuse graphene_std::raster::Image;\nuse graphene_std::text::Font;\nuse std::path::PathBuf;\n\n#[impl_message(Message, Portfolio)]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum PortfolioMessage {\n\t// Sub-messages\n\t#[child]\n\tDocument(DocumentMessage),\n\n\t// Messages\n\tInit,\n\tDocumentPassMessage {\n\t\tdocument_id: DocumentId,\n\t\tmessage: DocumentMessage,\n\t},\n\tAutoSaveActiveDocument,\n\tAutoSaveAllDocuments,\n\tAutoSaveDocument {\n\t\tdocument_id: DocumentId,\n\t},\n\tCloseActiveDocumentWithConfirmation,\n\tCloseAllDocuments,\n\tCloseAllDocumentsWithConfirmation,\n\tCloseDocument {\n\t\tdocument_id: DocumentId,\n\t},\n\tCloseDocumentWithConfirmation {\n\t\tdocument_id: DocumentId,\n\t},\n\tCopy {\n\t\tclipboard: Clipboard,\n\t},\n\tCut {\n\t\tclipboard: Clipboard,\n\t},\n\tDeleteDocument {\n\t\tdocument_id: DocumentId,\n\t},\n\tDestroyAllDocuments,\n\tEditorPreferences,\n\tFontCatalogLoaded {\n\t\tcatalog: FontCatalog,\n\t},\n\tLoadFontData {\n\t\tfont: Font,\n\t},\n\tFontLoaded {\n\t\tfont_family: String,\n\t\tfont_style: String,\n\t\tdata: Vec<u8>,\n\t},\n\tLoadDocumentResources {\n\t\tdocument_id: DocumentId,\n\t},\n\tNewDocumentWithName {\n\t\tname: String,\n\t},\n\tNextDocument,\n\tOpen,\n\tImport,\n\tOpenFile {\n\t\tpath: PathBuf,\n\t\tcontent: Vec<u8>,\n\t},\n\tImportFile {\n\t\tpath: PathBuf,\n\t\tcontent: Vec<u8>,\n\t},\n\tOpenDocumentFile {\n\t\tdocument_name: Option<String>,\n\t\tdocument_path: Option<PathBuf>,\n\t\tdocument_serialized_content: String,\n\t},\n\tOpenDocumentFileWithId {\n\t\tdocument_id: DocumentId,\n\t\tdocument_name: Option<String>,\n\t\tdocument_path: Option<PathBuf>,\n\t\tdocument_is_auto_saved: bool,\n\t\tdocument_is_saved: bool,\n\t\tdocument_serialized_content: String,\n\t\tto_front: bool,\n\t\tselect_after_open: bool,\n\t},\n\tOpenImage {\n\t\tname: Option<String>,\n\t\timage: Image<Color>,\n\t},\n\tOpenSvg {\n\t\tname: Option<String>,\n\t\tsvg: String,\n\t},\n\tPasteSerializedData {\n\t\tdata: String,\n\t},\n\tPasteSerializedVector {\n\t\tdata: String,\n\t},\n\tPasteImage {\n\t\tname: Option<String>,\n\t\timage: Image<Color>,\n\t\tmouse: Option<(f64, f64)>,\n\t\tparent_and_insert_index: Option<(LayerNodeIdentifier, usize)>,\n\t},\n\tPasteSvg {\n\t\tname: Option<String>,\n\t\tsvg: String,\n\t\tmouse: Option<(f64, f64)>,\n\t\tparent_and_insert_index: Option<(LayerNodeIdentifier, usize)>,\n\t},\n\t// TODO: Unused except by tests, remove?\n\tPasteIntoFolder {\n\t\tclipboard: Clipboard,\n\t\tparent: LayerNodeIdentifier,\n\t\tinsert_index: usize,\n\t},\n\tCenterPastedLayers {\n\t\tlayers: Vec<LayerNodeIdentifier>,\n\t},\n\tPrevDocument,\n\tRequestWelcomeScreenButtonsLayout,\n\tRequestStatusBarInfoLayout,\n\tSetActivePanel {\n\t\tpanel: PanelType,\n\t},\n\tSelectDocument {\n\t\tdocument_id: DocumentId,\n\t},\n\tSubmitDocumentExport {\n\t\tname: String,\n\t\tfile_type: FileType,\n\t\tscale_factor: f64,\n\t\tbounds: ExportBounds,\n\t\ttransparent_background: bool,\n\t\tartboard_name: Option<String>,\n\t\tartboard_count: usize,\n\t},\n\tSubmitActiveGraphRender,\n\tSubmitGraphRender {\n\t\tdocument_id: DocumentId,\n\t\tignore_hash: bool,\n\t},\n\tSubmitEyedropperPreviewRender,\n\tToggleResetNodesToDefinitionsOnOpen,\n\tToggleFocusDocument,\n\tToggleDataPanelOpen,\n\tTogglePropertiesPanelOpen,\n\tToggleLayersPanelOpen,\n\tToggleRulers,\n\tUpdateDocumentWidgets,\n\tUpdateOpenDocumentsList,\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/portfolio_message_handler.rs",
    "content": "use super::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse super::document::utility_types::network_interface;\nuse super::utility_types::{PanelType, PersistentData};\nuse crate::application::{Editor, generate_uuid};\nuse crate::consts::{DEFAULT_DOCUMENT_NAME, DEFAULT_STROKE_WIDTH, FILE_EXTENSION};\nuse crate::messages::animation::TimingInformation;\nuse crate::messages::clipboard::utility_types::ClipboardContent;\nuse crate::messages::dialog::simple_dialogs;\nuse crate::messages::frontend::utility_types::{DocumentDetails, OpenDocument};\nuse crate::messages::input_mapper::utility_types::input_keyboard::Key;\nuse crate::messages::input_mapper::utility_types::macros::{action_shortcut, action_shortcut_manual};\nuse crate::messages::layout::utility_types::widget_prelude::*;\nuse crate::messages::portfolio::document::DocumentMessageContext;\nuse crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;\nuse crate::messages::portfolio::document::node_graph::document_node_definitions::{self, resolve_network_node_type};\nuse crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT};\nuse crate::messages::portfolio::document::utility_types::network_interface::OutputConnector;\nuse crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;\nuse crate::messages::portfolio::document_migration::*;\nuse crate::messages::portfolio::utility_types::FileContent;\nuse crate::messages::preferences::SelectionMode;\nuse crate::messages::prelude::*;\nuse crate::messages::tool::common_functionality::graph_modification_utils;\nuse crate::messages::tool::utility_types::{HintData, ToolType};\nuse crate::messages::viewport::ToPhysical;\nuse crate::node_graph_executor::{ExportConfig, NodeGraphExecutor};\nuse derivative::*;\nuse glam::{DAffine2, DVec2};\nuse graph_craft::document::NodeId;\nuse graphene_std::Color;\nuse graphene_std::raster_types::Image;\nuse graphene_std::renderer::Quad;\nuse graphene_std::subpath::BezierHandles;\nuse graphene_std::text::Font;\nuse graphene_std::vector::misc::HandleId;\nuse graphene_std::vector::{PointId, SegmentId, Vector, VectorModificationType};\nuse std::path::PathBuf;\nuse std::vec;\n\n#[derive(ExtractField)]\npub struct PortfolioMessageContext<'a> {\n\tpub ipp: &'a InputPreprocessorMessageHandler,\n\tpub preferences: &'a PreferencesMessageHandler,\n\tpub animation: &'a AnimationMessageHandler,\n\tpub current_tool: &'a ToolType,\n\tpub reset_node_definitions_on_open: bool,\n\tpub timing_information: TimingInformation,\n\tpub viewport: &'a ViewportMessageHandler,\n}\n\n#[derive(Debug, Derivative, ExtractField)]\n#[derivative(Default)]\npub struct PortfolioMessageHandler {\n\tpub documents: HashMap<DocumentId, DocumentMessageHandler>,\n\tdocument_ids: VecDeque<DocumentId>,\n\tactive_panel: PanelType,\n\tpub(crate) active_document_id: Option<DocumentId>,\n\tcopy_buffer: [Vec<CopyBufferEntry>; INTERNAL_CLIPBOARD_COUNT as usize],\n\tpub persistent_data: PersistentData,\n\tpub executor: NodeGraphExecutor,\n\tpub selection_mode: SelectionMode,\n\tpub reset_node_definitions_on_open: bool,\n\tpub focus_document: bool,\n\t#[derivative(Default(value = \"true\"))]\n\tpub properties_panel_open: bool,\n\t#[derivative(Default(value = \"true\"))]\n\tpub layers_panel_open: bool,\n\tpub data_panel_open: bool,\n}\n\n#[message_handler_data]\nimpl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for PortfolioMessageHandler {\n\tfn process_message(&mut self, message: PortfolioMessage, responses: &mut VecDeque<Message>, context: PortfolioMessageContext) {\n\t\tlet PortfolioMessageContext {\n\t\t\tipp,\n\t\t\tpreferences,\n\t\t\tanimation,\n\t\t\tcurrent_tool,\n\t\t\treset_node_definitions_on_open,\n\t\t\ttiming_information,\n\t\t\tviewport,\n\t\t} = context;\n\n\t\tmatch message {\n\t\t\t// Sub-messages\n\t\t\tPortfolioMessage::Document(message) => {\n\t\t\t\tif let Some(document_id) = self.active_document_id\n\t\t\t\t\t&& let Some(document) = self.documents.get_mut(&document_id)\n\t\t\t\t{\n\t\t\t\t\tlet document_inputs = DocumentMessageContext {\n\t\t\t\t\t\tdocument_id,\n\t\t\t\t\t\tipp,\n\t\t\t\t\t\tpersistent_data: &self.persistent_data,\n\t\t\t\t\t\texecutor: &mut self.executor,\n\t\t\t\t\t\tcurrent_tool,\n\t\t\t\t\t\tpreferences,\n\t\t\t\t\t\tviewport,\n\t\t\t\t\t\tdata_panel_open: self.data_panel_open && !self.focus_document,\n\t\t\t\t\t\tlayers_panel_open: self.layers_panel_open && !self.focus_document,\n\t\t\t\t\t\tproperties_panel_open: self.properties_panel_open && !self.focus_document,\n\t\t\t\t\t};\n\t\t\t\t\tdocument.process_message(message, responses, document_inputs)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Messages\n\t\t\tPortfolioMessage::Init => {\n\t\t\t\t// Initialize the frontend with environment information\n\t\t\t\tresponses.add(FrontendMessage::UpdatePlatform {\n\t\t\t\t\tplatform: Editor::environment().into(),\n\t\t\t\t});\n\n\t\t\t\t// Tell frontend to load persistent preferences\n\t\t\t\tresponses.add(FrontendMessage::TriggerLoadPreferences);\n\n\t\t\t\t// Before loading any documents, initially prepare the welcome screen buttons layout\n\t\t\t\tresponses.add(PortfolioMessage::RequestWelcomeScreenButtonsLayout);\n\n\t\t\t\t// Tell frontend to load the current document\n\t\t\t\tresponses.add(FrontendMessage::TriggerLoadFirstAutoSaveDocument);\n\n\t\t\t\t// Display the menu bar at the top of the window\n\t\t\t\tresponses.add(MenuBarMessage::SendLayout);\n\n\t\t\t\t// Send the information for tooltips and categories for each node/input.\n\t\t\t\tresponses.add(FrontendMessage::SendUIMetadata {\n\t\t\t\t\tnode_descriptions: document_node_definitions::collect_node_descriptions(),\n\t\t\t\t\tnode_types: document_node_definitions::collect_node_types(),\n\t\t\t\t});\n\n\t\t\t\t// Send shortcuts for widgets created in the frontend which need shortcut tooltips\n\t\t\t\tresponses.add(FrontendMessage::SendShortcutFullscreen {\n\t\t\t\t\tshortcut: action_shortcut_manual!(Key::F11),\n\t\t\t\t\tshortcut_mac: action_shortcut_manual!(Key::Control, Key::Command, Key::KeyF),\n\t\t\t\t});\n\t\t\t\tresponses.add(FrontendMessage::SendShortcutAltClick {\n\t\t\t\t\tshortcut: action_shortcut_manual!(Key::Alt, Key::MouseLeft),\n\t\t\t\t});\n\t\t\t\tresponses.add(FrontendMessage::SendShortcutShiftClick {\n\t\t\t\t\tshortcut: action_shortcut_manual!(Key::Shift, Key::MouseLeft),\n\t\t\t\t});\n\n\t\t\t\t// Request status bar info layout\n\t\t\t\tresponses.add(PortfolioMessage::RequestStatusBarInfoLayout);\n\n\t\t\t\t// Tell frontend to finish loading persistent documents\n\t\t\t\tresponses.add(FrontendMessage::TriggerLoadRestAutoSaveDocuments);\n\n\t\t\t\t// Tell frontend to load documented passed in as launch arguments\n\t\t\t\tresponses.add(FrontendMessage::TriggerOpenLaunchDocuments);\n\t\t\t}\n\t\t\tPortfolioMessage::DocumentPassMessage { document_id, message } => {\n\t\t\t\tif let Some(document) = self.documents.get_mut(&document_id) {\n\t\t\t\t\tlet document_inputs = DocumentMessageContext {\n\t\t\t\t\t\tdocument_id,\n\t\t\t\t\t\tipp,\n\t\t\t\t\t\tpersistent_data: &self.persistent_data,\n\t\t\t\t\t\texecutor: &mut self.executor,\n\t\t\t\t\t\tcurrent_tool,\n\t\t\t\t\t\tpreferences,\n\t\t\t\t\t\tviewport,\n\t\t\t\t\t\tdata_panel_open: self.data_panel_open && !self.focus_document,\n\t\t\t\t\t\tlayers_panel_open: self.layers_panel_open && !self.focus_document,\n\t\t\t\t\t\tproperties_panel_open: self.properties_panel_open && !self.focus_document,\n\t\t\t\t\t};\n\t\t\t\t\tdocument.process_message(message, responses, document_inputs)\n\t\t\t\t}\n\t\t\t}\n\t\t\tPortfolioMessage::AutoSaveActiveDocument => {\n\t\t\t\tif let Some(document_id) = self.active_document_id\n\t\t\t\t\t&& let Some(document) = self.active_document_mut()\n\t\t\t\t{\n\t\t\t\t\tdocument.set_auto_save_state(true);\n\t\t\t\t\tresponses.add(PortfolioMessage::AutoSaveDocument { document_id });\n\t\t\t\t}\n\t\t\t}\n\t\t\tPortfolioMessage::AutoSaveAllDocuments => {\n\t\t\t\tfor (document_id, document) in self.documents.iter_mut() {\n\t\t\t\t\tif !document.is_auto_saved() {\n\t\t\t\t\t\tdocument.set_auto_save_state(true);\n\t\t\t\t\t\tresponses.add(PortfolioMessage::AutoSaveDocument { document_id: *document_id });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tPortfolioMessage::AutoSaveDocument { document_id } => {\n\t\t\t\tlet document = self.documents.get(&document_id).unwrap();\n\t\t\t\tresponses.add(FrontendMessage::TriggerPersistenceWriteDocument {\n\t\t\t\t\tdocument_id,\n\t\t\t\t\tdocument: document.serialize_document(),\n\t\t\t\t\tdetails: DocumentDetails {\n\t\t\t\t\t\tname: document.name.clone(),\n\t\t\t\t\t\tpath: document.path.clone(),\n\t\t\t\t\t\tis_saved: document.is_saved(),\n\t\t\t\t\t\tis_auto_saved: document.is_auto_saved(),\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t}\n\t\t\tPortfolioMessage::CloseActiveDocumentWithConfirmation => {\n\t\t\t\tif let Some(document_id) = self.active_document_id {\n\t\t\t\t\tresponses.add(PortfolioMessage::CloseDocumentWithConfirmation { document_id });\n\t\t\t\t}\n\t\t\t}\n\t\t\tPortfolioMessage::CloseAllDocuments => {\n\t\t\t\tif self.active_document_id.is_some() {\n\t\t\t\t\tresponses.add(EventMessage::ToolAbort);\n\t\t\t\t\tresponses.add(ToolMessage::DeactivateTools);\n\n\t\t\t\t\t// Clear relevant UI layouts if there are no documents\n\t\t\t\t\tresponses.add(PropertiesPanelMessage::Clear);\n\t\t\t\t\tresponses.add(DocumentMessage::ClearLayersPanel);\n\t\t\t\t\tresponses.add(DataPanelMessage::ClearLayout);\n\t\t\t\t\tHintData::clear_layout(responses);\n\t\t\t\t}\n\n\t\t\t\tfor document_id in &self.document_ids {\n\t\t\t\t\tresponses.add(FrontendMessage::TriggerPersistenceRemoveDocument { document_id: *document_id });\n\t\t\t\t}\n\n\t\t\t\tresponses.add(PortfolioMessage::DestroyAllDocuments);\n\t\t\t\tresponses.add(PortfolioMessage::UpdateOpenDocumentsList);\n\t\t\t}\n\t\t\tPortfolioMessage::CloseAllDocumentsWithConfirmation => {\n\t\t\t\tif self.unsaved_document_names().is_empty() {\n\t\t\t\t\tresponses.add(PortfolioMessage::CloseAllDocuments)\n\t\t\t\t} else {\n\t\t\t\t\tresponses.add(DialogMessage::CloseAllDocumentsWithConfirmation)\n\t\t\t\t}\n\t\t\t}\n\t\t\tPortfolioMessage::CloseDocument { document_id } => {\n\t\t\t\t// Is this the last document?\n\t\t\t\tif self.documents.len() == 1 && self.document_ids[0] == document_id {\n\t\t\t\t\t// Clear UI layouts that assume the existence of a document\n\t\t\t\t\tresponses.add(PropertiesPanelMessage::Clear);\n\t\t\t\t\tresponses.add(DocumentMessage::ClearLayersPanel);\n\t\t\t\t\tresponses.add(DataPanelMessage::ClearLayout);\n\t\t\t\t\tHintData::clear_layout(responses);\n\t\t\t\t}\n\n\t\t\t\t// Actually delete the document (delay to delete document is required to let the document and properties panel messages above get processed)\n\t\t\t\tresponses.add(PortfolioMessage::DeleteDocument { document_id });\n\t\t\t\tresponses.add(FrontendMessage::TriggerPersistenceRemoveDocument { document_id });\n\n\t\t\t\t// Send the new list of document tab names\n\t\t\t\tresponses.add(PortfolioMessage::UpdateOpenDocumentsList);\n\t\t\t}\n\t\t\tPortfolioMessage::CloseDocumentWithConfirmation { document_id } => {\n\t\t\t\tlet target_document = self.documents.get(&document_id).unwrap();\n\t\t\t\tif target_document.is_saved() {\n\t\t\t\t\tresponses.add(EventMessage::ToolAbort);\n\t\t\t\t\tresponses.add(PortfolioMessage::CloseDocument { document_id });\n\t\t\t\t} else {\n\t\t\t\t\tlet dialog = simple_dialogs::CloseDocumentDialog {\n\t\t\t\t\t\tdocument_name: target_document.name.clone(),\n\t\t\t\t\t\tdocument_id,\n\t\t\t\t\t};\n\t\t\t\t\tdialog.send_dialog_to_frontend(responses);\n\n\t\t\t\t\t// Select the document being closed\n\t\t\t\t\tresponses.add(PortfolioMessage::SelectDocument { document_id });\n\t\t\t\t}\n\t\t\t}\n\t\t\tPortfolioMessage::Copy { clipboard } => {\n\t\t\t\tif context.current_tool == &ToolType::Path {\n\t\t\t\t\tresponses.add(PathToolMessage::Copy { clipboard });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// We can't use `self.active_document()` because it counts as an immutable borrow of the entirety of `self`\n\t\t\t\tlet Some(active_document) = self.active_document_id.and_then(|id| self.documents.get_mut(&id)) else {\n\t\t\t\t\treturn;\n\t\t\t\t};\n\n\t\t\t\tif active_document.graph_view_overlay_open() {\n\t\t\t\t\tresponses.add(NodeGraphMessage::Copy);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet mut copy_val = |buffer: &mut Vec<CopyBufferEntry>| {\n\t\t\t\t\tlet mut ordered_last_elements = active_document.network_interface.shallowest_unique_layers(&[]).collect::<Vec<_>>();\n\n\t\t\t\t\tordered_last_elements.sort_by_key(|layer| {\n\t\t\t\t\t\tlet Some(parent) = layer.parent(active_document.metadata()) else { return usize::MAX };\n\t\t\t\t\t\tDocumentMessageHandler::get_calculated_insert_index(active_document.metadata(), &SelectedNodes(vec![layer.to_node()]), parent)\n\t\t\t\t\t});\n\n\t\t\t\t\tfor layer in ordered_last_elements.into_iter() {\n\t\t\t\t\t\tlet layer_node_id = layer.to_node();\n\n\t\t\t\t\t\tlet mut copy_ids = HashMap::new();\n\t\t\t\t\t\tcopy_ids.insert(layer_node_id, NodeId(0));\n\n\t\t\t\t\t\tactive_document\n\t\t\t\t\t\t\t.network_interface\n\t\t\t\t\t\t\t.upstream_flow_back_from_nodes(vec![layer_node_id], &[], network_interface::FlowType::LayerChildrenUpstreamFlow)\n\t\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t\t.for_each(|(index, node_id)| {\n\t\t\t\t\t\t\t\tcopy_ids.insert(node_id, NodeId((index + 1) as u64));\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\tbuffer.push(CopyBufferEntry {\n\t\t\t\t\t\t\tnodes: active_document.network_interface.copy_nodes(&copy_ids, &[]).collect(),\n\t\t\t\t\t\t\tselected: active_document.network_interface.selected_nodes().selected_layers_contains(layer, active_document.metadata()),\n\t\t\t\t\t\t\tvisible: active_document.network_interface.selected_nodes().layer_visible(layer, &active_document.network_interface),\n\t\t\t\t\t\t\tlocked: active_document.network_interface.selected_nodes().layer_locked(layer, &active_document.network_interface),\n\t\t\t\t\t\t\tcollapsed: false,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tif clipboard == Clipboard::Device {\n\t\t\t\t\tlet mut buffer = Vec::new();\n\t\t\t\t\tcopy_val(&mut buffer);\n\t\t\t\t\tlet Ok(data) = serde_json::to_string(&buffer) else {\n\t\t\t\t\t\tlog::error!(\"Failed to serialize nodes for clipboard\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t};\n\t\t\t\t\tresponses.add(ClipboardMessage::Write {\n\t\t\t\t\t\tcontent: ClipboardContent::Layer(data),\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tlet copy_buffer = &mut self.copy_buffer;\n\t\t\t\t\tcopy_buffer[clipboard as usize].clear();\n\t\t\t\t\tcopy_val(&mut copy_buffer[clipboard as usize]);\n\t\t\t\t}\n\t\t\t}\n\t\t\tPortfolioMessage::Cut { clipboard } => {\n\t\t\t\tif context.current_tool == &ToolType::Path {\n\t\t\t\t\tresponses.add(PathToolMessage::Cut { clipboard });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif let Some(active_document) = self.active_document()\n\t\t\t\t\t&& active_document.graph_view_overlay_open()\n\t\t\t\t{\n\t\t\t\t\tresponses.add(NodeGraphMessage::Cut);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresponses.add(PortfolioMessage::Copy { clipboard });\n\t\t\t\tresponses.add(DocumentMessage::DeleteSelectedLayers);\n\t\t\t}\n\t\t\tPortfolioMessage::DeleteDocument { document_id } => {\n\t\t\t\tlet document_index = self.document_index(document_id);\n\t\t\t\tself.documents.remove(&document_id);\n\t\t\t\tself.document_ids.remove(document_index);\n\n\t\t\t\tif self.document_ids.is_empty() {\n\t\t\t\t\tself.active_document_id = None;\n\t\t\t\t\tresponses.add(MenuBarMessage::SendLayout);\n\t\t\t\t} else if self.active_document_id.is_some() {\n\t\t\t\t\tlet document_id = if document_index == self.document_ids.len() {\n\t\t\t\t\t\t// If we closed the last document take the one previous (same as last)\n\t\t\t\t\t\t*self.document_ids.back().unwrap()\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Move to the next tab\n\t\t\t\t\t\tself.document_ids[document_index]\n\t\t\t\t\t};\n\t\t\t\t\tresponses.add(PortfolioMessage::SelectDocument { document_id });\n\t\t\t\t}\n\t\t\t}\n\t\t\tPortfolioMessage::DestroyAllDocuments => {\n\t\t\t\t// Empty the list of internal document data\n\t\t\t\tself.documents.clear();\n\t\t\t\tself.document_ids.clear();\n\t\t\t\tself.active_document_id = None;\n\t\t\t\tresponses.add(MenuBarMessage::SendLayout);\n\t\t\t}\n\t\t\tPortfolioMessage::FontCatalogLoaded { catalog } => {\n\t\t\t\tself.persistent_data.font_catalog = catalog;\n\n\t\t\t\tif let Some(document_id) = self.active_document_id {\n\t\t\t\t\tresponses.add(PortfolioMessage::LoadDocumentResources { document_id });\n\t\t\t\t}\n\n\t\t\t\t// Load the default font\n\t\t\t\tlet font = Font::new(graphene_std::consts::DEFAULT_FONT_FAMILY.into(), graphene_std::consts::DEFAULT_FONT_STYLE.into());\n\t\t\t\tresponses.add(PortfolioMessage::LoadFontData { font });\n\t\t\t}\n\t\t\tPortfolioMessage::LoadFontData { font } => {\n\t\t\t\tif let Some(style) = self.persistent_data.font_catalog.find_font_style_in_catalog(&font) {\n\t\t\t\t\tlet font = Font::new(font.font_family, style.to_named_style());\n\n\t\t\t\t\tif !self.persistent_data.font_cache.loaded_font(&font) {\n\t\t\t\t\t\tresponses.add(FrontendMessage::TriggerFontDataLoad { font, url: style.url });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tPortfolioMessage::FontLoaded { font_family, font_style, data } => {\n\t\t\t\tlet font = Font::new(font_family, font_style);\n\t\t\t\tself.persistent_data.font_cache.insert(font, data);\n\t\t\t\tself.executor.update_font_cache(self.persistent_data.font_cache.clone());\n\n\t\t\t\tfor document_id in self.document_ids.iter() {\n\t\t\t\t\tlet node_to_inspect = self.node_to_inspect();\n\n\t\t\t\t\tlet Some(document) = self.documents.get_mut(document_id) else {\n\t\t\t\t\t\tlog::error!(\"Tried to render non-existent document\");\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t};\n\n\t\t\t\t\tlet document_to_viewport = document\n\t\t\t\t\t\t.navigation_handler\n\t\t\t\t\t\t.calculate_offset_transform(viewport.center_in_viewport_space().into(), &document.document_ptz);\n\t\t\t\t\tlet pointer_position = document_to_viewport.inverse().transform_point2(ipp.mouse.position);\n\n\t\t\t\t\tlet scale = viewport.scale();\n\t\t\t\t\t// Use exact physical dimensions from browser (via ResizeObserver's devicePixelContentBoxSize)\n\t\t\t\t\tlet physical_resolution = viewport.size().to_physical().into_dvec2().round().as_uvec2();\n\n\t\t\t\t\t// TODO: Remove this when we do the SVG rendering with a separate library on desktop, thus avoiding a need for the hole punch.\n\t\t\t\t\t// TODO: See #3796. There is a second instance of this todo comment and code block (be sure to remove both).\n\t\t\t\t\t#[cfg(not(target_family = \"wasm\"))]\n\t\t\t\t\tresponses.add_front(FrontendMessage::UpdateViewportHolePunch {\n\t\t\t\t\t\tactive: document.render_mode != graphene_std::vector::style::RenderMode::SvgPreview,\n\t\t\t\t\t});\n\n\t\t\t\t\tif let Ok(message) = self.executor.submit_node_graph_evaluation(\n\t\t\t\t\t\tself.documents.get_mut(document_id).expect(\"Tried to render non-existent document\"),\n\t\t\t\t\t\t*document_id,\n\t\t\t\t\t\tphysical_resolution,\n\t\t\t\t\t\tscale,\n\t\t\t\t\t\ttiming_information,\n\t\t\t\t\t\tnode_to_inspect,\n\t\t\t\t\t\ttrue,\n\t\t\t\t\t\tpointer_position,\n\t\t\t\t\t) {\n\t\t\t\t\t\tresponses.add_front(message);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif self.active_document_mut().is_some() {\n\t\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\t}\n\n\t\t\t\tif current_tool == &ToolType::Text {\n\t\t\t\t\tresponses.add(TextToolMessage::RefreshEditingFontData);\n\t\t\t\t}\n\t\t\t}\n\t\t\tPortfolioMessage::EditorPreferences => self.executor.update_editor_preferences(preferences.editor_preferences()),\n\t\t\tPortfolioMessage::LoadDocumentResources { document_id } => {\n\t\t\t\tlet catalog = &self.persistent_data.font_catalog;\n\n\t\t\t\tif catalog.0.is_empty() {\n\t\t\t\t\tresponses.add_front(FrontendMessage::TriggerFontCatalogLoad);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif let Some(document) = self.documents.get_mut(&document_id) {\n\t\t\t\t\tdocument.load_layer_resources(responses);\n\t\t\t\t}\n\t\t\t}\n\t\t\tPortfolioMessage::NewDocumentWithName { name } => {\n\t\t\t\tlet mut new_document = DocumentMessageHandler::default();\n\t\t\t\tnew_document.name = name;\n\n\t\t\t\tresponses.add(DocumentMessage::PTZUpdate);\n\n\t\t\t\tlet document_id = DocumentId(generate_uuid());\n\t\t\t\tif self.active_document().is_some() {\n\t\t\t\t\tresponses.add(EventMessage::ToolAbort);\n\t\t\t\t\tresponses.add(NavigationMessage::CanvasPan { delta: (0., 0.).into() });\n\t\t\t\t}\n\n\t\t\t\tself.load_document(new_document, document_id, responses, false);\n\t\t\t\tresponses.add(PortfolioMessage::SelectDocument { document_id });\n\t\t\t}\n\t\t\tPortfolioMessage::NextDocument => {\n\t\t\t\tif let Some(active_document_id) = self.active_document_id {\n\t\t\t\t\tlet current_index = self.document_index(active_document_id);\n\t\t\t\t\tlet next_index = (current_index + 1) % self.document_ids.len();\n\t\t\t\t\tlet next_id = self.document_ids[next_index];\n\n\t\t\t\t\tresponses.add(PortfolioMessage::SelectDocument { document_id: next_id });\n\t\t\t\t}\n\t\t\t}\n\t\t\tPortfolioMessage::Open => {\n\t\t\t\t// This portfolio message wraps the frontend message so it can be listed as an action, which isn't possible for frontend messages\n\t\t\t\tresponses.add(FrontendMessage::TriggerOpen);\n\t\t\t}\n\t\t\tPortfolioMessage::Import => {\n\t\t\t\t// This portfolio message wraps the frontend message so it can be listed as an action, which isn't possible for frontend messages\n\t\t\t\tresponses.add(FrontendMessage::TriggerImport);\n\t\t\t}\n\t\t\tPortfolioMessage::OpenFile { path, content } => {\n\t\t\t\tlet name = path.file_stem().map(|n| n.to_string_lossy().to_string());\n\t\t\t\tmatch Self::read_file(&path, content) {\n\t\t\t\t\tFileContent::Document(content) => {\n\t\t\t\t\t\tresponses.add(PortfolioMessage::OpenDocumentFile {\n\t\t\t\t\t\t\tdocument_name: name,\n\t\t\t\t\t\t\tdocument_path: Some(path),\n\t\t\t\t\t\t\tdocument_serialized_content: content,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tFileContent::Svg(svg) => {\n\t\t\t\t\t\tresponses.add(PortfolioMessage::OpenSvg { name, svg });\n\t\t\t\t\t}\n\t\t\t\t\tFileContent::Image(image) => {\n\t\t\t\t\t\tresponses.add(PortfolioMessage::OpenImage { name, image });\n\t\t\t\t\t}\n\t\t\t\t\tFileContent::Unsupported => {\n\t\t\t\t\t\t// TODO: Show a more thoughtfully designed error message to the user\n\t\t\t\t\t\tresponses.add(DialogMessage::DisplayDialogError {\n\t\t\t\t\t\t\ttitle: \"Unsupported format\".into(),\n\t\t\t\t\t\t\tdescription: \"This file cannot be opened because it is not a supported image file type.\".into(),\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tPortfolioMessage::ImportFile { path, content } => {\n\t\t\t\tlet name = path.file_stem().map(|n| n.to_string_lossy().to_string());\n\t\t\t\tmatch Self::read_file(&path, content) {\n\t\t\t\t\tFileContent::Document(content) => {\n\t\t\t\t\t\t// TODO: Consider importing a document as a node into the current document\n\t\t\t\t\t\t// For now treat importing a document as opening it\n\t\t\t\t\t\tresponses.add(PortfolioMessage::OpenDocumentFile {\n\t\t\t\t\t\t\tdocument_name: name,\n\t\t\t\t\t\t\tdocument_path: Some(path),\n\t\t\t\t\t\t\tdocument_serialized_content: content,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tFileContent::Svg(svg) => {\n\t\t\t\t\t\tresponses.add(PortfolioMessage::PasteSvg {\n\t\t\t\t\t\t\tname,\n\t\t\t\t\t\t\tsvg,\n\t\t\t\t\t\t\tmouse: None,\n\t\t\t\t\t\t\tparent_and_insert_index: None,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tFileContent::Image(image) => {\n\t\t\t\t\t\tresponses.add(PortfolioMessage::PasteImage {\n\t\t\t\t\t\t\tname,\n\t\t\t\t\t\t\timage,\n\t\t\t\t\t\t\tmouse: None,\n\t\t\t\t\t\t\tparent_and_insert_index: None,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tFileContent::Unsupported => {\n\t\t\t\t\t\t// TODO: Show a more thoughtfully designed error message to the user\n\t\t\t\t\t\tresponses.add(DialogMessage::DisplayDialogError {\n\t\t\t\t\t\t\ttitle: \"Unsupported format\".into(),\n\t\t\t\t\t\t\tdescription: \"This file cannot be imported because it is not a supported image file type.\".into(),\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tPortfolioMessage::OpenDocumentFile {\n\t\t\t\tdocument_name,\n\t\t\t\tdocument_path,\n\t\t\t\tdocument_serialized_content,\n\t\t\t} => {\n\t\t\t\tresponses.add(PortfolioMessage::OpenDocumentFileWithId {\n\t\t\t\t\tdocument_id: DocumentId(generate_uuid()),\n\t\t\t\t\tdocument_name,\n\t\t\t\t\tdocument_path,\n\t\t\t\t\tdocument_is_auto_saved: false,\n\t\t\t\t\tdocument_is_saved: true,\n\t\t\t\t\tdocument_serialized_content,\n\t\t\t\t\tto_front: false,\n\t\t\t\t\tselect_after_open: true,\n\t\t\t\t});\n\t\t\t}\n\t\t\tPortfolioMessage::ToggleResetNodesToDefinitionsOnOpen => {\n\t\t\t\tself.reset_node_definitions_on_open = !self.reset_node_definitions_on_open;\n\t\t\t\tresponses.add(MenuBarMessage::SendLayout);\n\t\t\t}\n\t\t\tPortfolioMessage::OpenDocumentFileWithId {\n\t\t\t\tdocument_id,\n\t\t\t\tdocument_name,\n\t\t\t\tdocument_path,\n\t\t\t\tdocument_is_auto_saved,\n\t\t\t\tdocument_is_saved,\n\t\t\t\tdocument_serialized_content,\n\t\t\t\tto_front,\n\t\t\t\tselect_after_open,\n\t\t\t} => {\n\t\t\t\t// Upgrade the document being opened to use fresh copies of all nodes\n\t\t\t\tlet reset_node_definitions_on_open = reset_node_definitions_on_open || document_migration_reset_node_definition(&document_serialized_content);\n\t\t\t\t// Upgrade the document being opened with string replacements on the original JSON\n\t\t\t\tlet document_serialized_content = document_migration_string_preprocessing(document_serialized_content);\n\n\t\t\t\t// Deserialize the document\n\t\t\t\tlet document = DocumentMessageHandler::deserialize_document(&document_serialized_content);\n\n\t\t\t\t// Display an error to the user if the document could not be opened\n\t\t\t\tlet mut document = match document {\n\t\t\t\t\tOk(document) => document,\n\t\t\t\t\tErr(e) => {\n\t\t\t\t\t\tif !document_is_auto_saved {\n\t\t\t\t\t\t\tresponses.add(DialogMessage::DisplayDialogError {\n\t\t\t\t\t\t\t\ttitle: \"Failed to open document\".to_string(),\n\t\t\t\t\t\t\t\tdescription: e.to_string(),\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\t// Upgrade the document's nodes to be compatible with the latest version\n\t\t\t\tdocument_migration_upgrades(&mut document, reset_node_definitions_on_open);\n\n\t\t\t\t// Ensure each node has the metadata for its inputs\n\t\t\t\tfor (node_id, node, path) in document.network_interface.document_network().clone().recursive_nodes() {\n\t\t\t\t\tdocument.network_interface.validate_input_metadata(node_id, node, &path);\n\t\t\t\t\tdocument.network_interface.validate_output_names(node_id, node, &path);\n\t\t\t\t}\n\n\t\t\t\t// Ensure layers are positioned as stacks if they are upstream siblings of another layer\n\t\t\t\tdocument.network_interface.load_structure();\n\t\t\t\tlet all_layers = LayerNodeIdentifier::ROOT_PARENT.descendants(document.network_interface.document_metadata()).collect::<Vec<_>>();\n\t\t\t\tfor layer in all_layers {\n\t\t\t\t\tlet Some((downstream_node, input_index)) = document\n\t\t\t\t\t\t.network_interface\n\t\t\t\t\t\t.outward_wires(&[])\n\t\t\t\t\t\t.and_then(|outward_wires| outward_wires.get(&OutputConnector::node(layer.to_node(), 0)))\n\t\t\t\t\t\t.and_then(|outward_wires| outward_wires.first())\n\t\t\t\t\t\t.and_then(|input_connector| input_connector.node_id().map(|node_id| (node_id, input_connector.input_index())))\n\t\t\t\t\telse {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t};\n\n\t\t\t\t\t// If the downstream node is a layer and the input is the first input and the current layer is not in a stack\n\t\t\t\t\tif input_index == 0 && document.network_interface.is_layer(&downstream_node, &[]) && !document.network_interface.is_stack(&layer.to_node(), &[]) {\n\t\t\t\t\t\t// Ensure the layer is horizontally aligned with the downstream layer to prevent changing the layout of old files\n\t\t\t\t\t\tlet (Some(layer_position), Some(downstream_position)) =\n\t\t\t\t\t\t\t(document.network_interface.position(&layer.to_node(), &[]), document.network_interface.position(&downstream_node, &[]))\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tlog::error!(\"Could not get position for layer {:?} or downstream node {} when opening file\", layer.to_node(), downstream_node);\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tif layer_position.x == downstream_position.x {\n\t\t\t\t\t\t\tdocument.network_interface.set_stack_position_calculated_offset(&layer.to_node(), &downstream_node, &[]);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Set the save state of the document based on what's given to us by the caller to this message\n\t\t\t\tdocument.set_auto_save_state(document_is_auto_saved);\n\t\t\t\tdocument.set_save_state(document_is_saved);\n\n\t\t\t\tlet document_name_from_path = document_path.as_ref().and_then(|path| {\n\t\t\t\t\tif path.extension().is_some_and(|e| e == FILE_EXTENSION) {\n\t\t\t\t\t\tpath.file_stem().map(|n| n.to_string_lossy().to_string())\n\t\t\t\t\t} else {\n\t\t\t\t\t\tNone\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tmatch (document_name, document_path, document_name_from_path) {\n\t\t\t\t\t(Some(name), _, None) => {\n\t\t\t\t\t\tdocument.name = name;\n\t\t\t\t\t}\n\t\t\t\t\t(_, Some(path), Some(name)) => {\n\t\t\t\t\t\tdocument.name = name;\n\t\t\t\t\t\tdocument.path = Some(path);\n\t\t\t\t\t}\n\t\t\t\t\t(_, _, Some(name)) => {\n\t\t\t\t\t\tdocument.name = name;\n\t\t\t\t\t}\n\t\t\t\t\t_ => {\n\t\t\t\t\t\tdocument.name = DEFAULT_DOCUMENT_NAME.to_string();\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Load the document into the portfolio so it opens in the editor\n\t\t\t\tself.load_document(document, document_id, responses, to_front);\n\n\t\t\t\tif select_after_open {\n\t\t\t\t\tresponses.add(PortfolioMessage::SelectDocument { document_id });\n\t\t\t\t}\n\t\t\t}\n\t\t\tPortfolioMessage::OpenImage { name, image } => {\n\t\t\t\tresponses.add(PortfolioMessage::NewDocumentWithName {\n\t\t\t\t\tname: name.clone().unwrap_or(DEFAULT_DOCUMENT_NAME.into()),\n\t\t\t\t});\n\n\t\t\t\tresponses.add(DocumentMessage::PasteImage {\n\t\t\t\t\tname,\n\t\t\t\t\timage,\n\t\t\t\t\tmouse: None,\n\t\t\t\t\tparent_and_insert_index: None,\n\t\t\t\t});\n\n\t\t\t\t// Wait for the document to be rendered so the click targets can be calculated in order to determine the artboard size that will encompass the pasted image\n\t\t\t\tresponses.add(DeferMessage::AfterGraphRun {\n\t\t\t\t\tmessages: vec![DocumentMessage::WrapContentInArtboard { place_artboard_at_origin: true }.into()],\n\t\t\t\t});\n\t\t\t\tresponses.add(DeferMessage::AfterNavigationReady {\n\t\t\t\t\tmessages: vec![DocumentMessage::ZoomCanvasToFitAll.into()],\n\t\t\t\t});\n\t\t\t}\n\t\t\tPortfolioMessage::OpenSvg { name, svg } => {\n\t\t\t\tresponses.add(PortfolioMessage::NewDocumentWithName {\n\t\t\t\t\tname: name.clone().unwrap_or(DEFAULT_DOCUMENT_NAME.into()),\n\t\t\t\t});\n\n\t\t\t\tresponses.add(DocumentMessage::PasteSvg {\n\t\t\t\t\tname,\n\t\t\t\t\tsvg,\n\t\t\t\t\tmouse: None,\n\t\t\t\t\tparent_and_insert_index: None,\n\t\t\t\t});\n\n\t\t\t\t// Wait for the document to be rendered so the click targets can be calculated in order to determine the artboard size that will encompass the pasted SVG\n\t\t\t\tresponses.add(DeferMessage::AfterGraphRun {\n\t\t\t\t\tmessages: vec![DocumentMessage::WrapContentInArtboard { place_artboard_at_origin: true }.into()],\n\t\t\t\t});\n\t\t\t\tresponses.add(DeferMessage::AfterNavigationReady {\n\t\t\t\t\tmessages: vec![DocumentMessage::ZoomCanvasToFitAll.into()],\n\t\t\t\t});\n\t\t\t}\n\t\t\t// TODO: Unused except by tests, remove?\n\t\t\tPortfolioMessage::PasteIntoFolder { clipboard, parent, insert_index } => {\n\t\t\t\tlet mut all_new_ids = Vec::new();\n\t\t\t\tlet paste = |entry: &CopyBufferEntry, responses: &mut VecDeque<_>, all_new_ids: &mut Vec<NodeId>| {\n\t\t\t\t\tif self.active_document().is_some() {\n\t\t\t\t\t\ttrace!(\"Pasting into folder {parent:?} as index: {insert_index}\");\n\t\t\t\t\t\tlet nodes = entry.clone().nodes;\n\t\t\t\t\t\tlet new_ids: HashMap<_, _> = nodes.iter().map(|(id, _)| (*id, NodeId::new())).collect();\n\t\t\t\t\t\tlet layer = LayerNodeIdentifier::new_unchecked(new_ids[&NodeId(0)]);\n\t\t\t\t\t\tall_new_ids.extend(new_ids.values().cloned());\n\t\t\t\t\t\tresponses.add(NodeGraphMessage::AddNodes { nodes, new_ids: new_ids.clone() });\n\t\t\t\t\t\tresponses.add(NodeGraphMessage::MoveLayerToStack { layer, parent, insert_index });\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tresponses.add(DocumentMessage::DeselectAllLayers);\n\n\t\t\t\tfor entry in self.copy_buffer[clipboard as usize].iter().rev() {\n\t\t\t\t\tpaste(entry, responses, &mut all_new_ids)\n\t\t\t\t}\n\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes: all_new_ids });\n\t\t\t}\n\t\t\tPortfolioMessage::PasteSerializedData { data } => {\n\t\t\t\tif let Some(document) = self.active_document() {\n\t\t\t\t\tlet mut all_new_ids = Vec::new();\n\t\t\t\t\tif let Ok(data) = serde_json::from_str::<Vec<CopyBufferEntry>>(&data) {\n\t\t\t\t\t\tlet parent = document.new_layer_parent(false);\n\t\t\t\t\t\tlet mut layers = Vec::new();\n\n\t\t\t\t\t\tlet mut added_nodes = false;\n\n\t\t\t\t\t\tfor entry in data.into_iter().rev() {\n\t\t\t\t\t\t\tif !added_nodes {\n\t\t\t\t\t\t\t\tresponses.add(DocumentMessage::DeselectAllLayers);\n\t\t\t\t\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\t\t\t\t\t\t\t\tadded_nodes = true;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tdocument.load_layer_resources(responses);\n\t\t\t\t\t\t\tlet new_ids: HashMap<_, _> = entry.nodes.iter().map(|(id, _)| (*id, NodeId::new())).collect();\n\t\t\t\t\t\t\tlet layer = LayerNodeIdentifier::new_unchecked(new_ids[&NodeId(0)]);\n\t\t\t\t\t\t\tall_new_ids.extend(new_ids.values().cloned());\n\n\t\t\t\t\t\t\tresponses.add(NodeGraphMessage::AddNodes { nodes: entry.nodes, new_ids });\n\t\t\t\t\t\t\tresponses.add(NodeGraphMessage::MoveLayerToStack { layer, parent, insert_index: 0 });\n\t\t\t\t\t\t\tlayers.push(layer);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes: all_new_ids });\n\t\t\t\t\t\tresponses.add(DeferMessage::AfterGraphRun {\n\t\t\t\t\t\t\tmessages: vec![PortfolioMessage::CenterPastedLayers { layers }.into()],\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Custom paste implementation for Path tool\n\t\t\tPortfolioMessage::PasteSerializedVector { data } => {\n\t\t\t\t// If using Path tool then send the operation to Path tool\n\t\t\t\tif *current_tool == ToolType::Path {\n\t\t\t\t\tresponses.add(PathToolMessage::Paste { data });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// If not using Path tool, create new layers and add paths into those\n\t\t\t\tif let Some(document) = self.active_document() {\n\t\t\t\t\tlet Ok(data) = serde_json::from_str::<Vec<(LayerNodeIdentifier, Vector, DAffine2)>>(&data) else {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t};\n\n\t\t\t\t\tlet mut layers = Vec::new();\n\n\t\t\t\t\tfor (_, new_vector, transform) in data {\n\t\t\t\t\t\tlet Some(node_type) = resolve_network_node_type(\"Path\") else {\n\t\t\t\t\t\t\terror!(\"Path node does not exist\");\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t};\n\t\t\t\t\t\tlet nodes = vec![(NodeId(0), node_type.default_node_template())];\n\n\t\t\t\t\t\tlet parent = document.new_layer_parent(false);\n\n\t\t\t\t\t\tlet layer = graph_modification_utils::new_custom(NodeId::new(), nodes, parent, responses);\n\t\t\t\t\t\tlayers.push(layer);\n\n\t\t\t\t\t\t// Adding the transform back into the layer\n\t\t\t\t\t\tresponses.add(GraphOperationMessage::TransformSet {\n\t\t\t\t\t\t\tlayer,\n\t\t\t\t\t\t\ttransform,\n\t\t\t\t\t\t\ttransform_in: TransformIn::Local,\n\t\t\t\t\t\t\tskip_rerender: false,\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// Add default fill and stroke to the layer\n\t\t\t\t\t\tlet fill_color = Color::WHITE;\n\t\t\t\t\t\tlet stroke_color = Color::BLACK;\n\n\t\t\t\t\t\tlet fill = graphene_std::vector::style::Fill::solid(fill_color.to_gamma_srgb());\n\t\t\t\t\t\tresponses.add(GraphOperationMessage::FillSet { layer, fill });\n\n\t\t\t\t\t\tlet stroke = graphene_std::vector::style::Stroke::new(Some(stroke_color.to_gamma_srgb()), DEFAULT_STROKE_WIDTH);\n\t\t\t\t\t\tresponses.add(GraphOperationMessage::StrokeSet { layer, stroke });\n\n\t\t\t\t\t\t// Create new point ids and add those into the existing Vector path\n\t\t\t\t\t\tlet mut points_map = HashMap::new();\n\t\t\t\t\t\tfor (point, position) in new_vector.point_domain.iter() {\n\t\t\t\t\t\t\tlet new_point_id = PointId::generate();\n\t\t\t\t\t\t\tpoints_map.insert(point, new_point_id);\n\t\t\t\t\t\t\tlet modification_type = VectorModificationType::InsertPoint { id: new_point_id, position };\n\t\t\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Create new segment ids and add the segments into the existing Vector path\n\t\t\t\t\t\tlet mut segments_map = HashMap::new();\n\t\t\t\t\t\tfor (segment_id, bezier, start, end) in new_vector.segment_bezier_iter() {\n\t\t\t\t\t\t\tlet new_segment_id = SegmentId::generate();\n\n\t\t\t\t\t\t\tsegments_map.insert(segment_id, new_segment_id);\n\n\t\t\t\t\t\t\tlet handles = match bezier.handles {\n\t\t\t\t\t\t\t\tBezierHandles::Linear => [None, None],\n\t\t\t\t\t\t\t\tBezierHandles::Quadratic { handle } => [Some(handle - bezier.start), None],\n\t\t\t\t\t\t\t\tBezierHandles::Cubic { handle_start, handle_end } => [Some(handle_start - bezier.start), Some(handle_end - bezier.end)],\n\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\tlet points = [points_map[&start], points_map[&end]];\n\t\t\t\t\t\t\tlet modification_type = VectorModificationType::InsertSegment { id: new_segment_id, points, handles };\n\t\t\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Set G1 continuity\n\t\t\t\t\t\tfor handles in new_vector.colinear_manipulators {\n\t\t\t\t\t\t\tlet to_new_handle = |handle: HandleId| -> HandleId {\n\t\t\t\t\t\t\t\tHandleId {\n\t\t\t\t\t\t\t\t\tty: handle.ty,\n\t\t\t\t\t\t\t\t\tsegment: segments_map[&handle.segment],\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tlet new_handles = [to_new_handle(handles[0]), to_new_handle(handles[1])];\n\t\t\t\t\t\t\tlet modification_type = VectorModificationType::SetG1Continuous { handles: new_handles, enabled: true };\n\t\t\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\t\tresponses.add(Message::Defer(DeferMessage::AfterGraphRun {\n\t\t\t\t\t\tmessages: vec![PortfolioMessage::CenterPastedLayers { layers }.into()],\n\t\t\t\t\t}));\n\t\t\t\t}\n\t\t\t}\n\t\t\tPortfolioMessage::CenterPastedLayers { layers } => {\n\t\t\t\tif let Some(document) = self.active_document_mut() {\n\t\t\t\t\tlet viewport_bounds_quad_pixels = Quad::from_box([DVec2::ZERO, viewport.size().into_dvec2()]); // In viewport pixel coordinates\n\t\t\t\t\tlet viewport_center_pixels = viewport_bounds_quad_pixels.center(); // In viewport pixel coordinates\n\n\t\t\t\t\tlet doc_to_viewport_transform = document.metadata().document_to_viewport;\n\t\t\t\t\tlet viewport_to_doc_transform = doc_to_viewport_transform.inverse();\n\n\t\t\t\t\tlet viewport_quad_doc_space = viewport_to_doc_transform * viewport_bounds_quad_pixels;\n\n\t\t\t\t\tlet mut top_level_items_to_center: Vec<LayerNodeIdentifier> = Vec::new();\n\t\t\t\t\tlet mut artboards_in_selection: Vec<LayerNodeIdentifier> = Vec::new();\n\n\t\t\t\t\tfor &layer_id in &layers {\n\t\t\t\t\t\tif document.network_interface.is_artboard(&layer_id.to_node(), &document.node_graph_handler.network) {\n\t\t\t\t\t\t\tartboards_in_selection.push(layer_id);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tfor &layer_id in &layers {\n\t\t\t\t\t\tlet is_child_of_selected_artboard = artboards_in_selection.iter().any(|&artboard_id| {\n\t\t\t\t\t\t\tif layer_id == artboard_id {\n\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tlayer_id.ancestors(document.metadata()).any(|ancestor| ancestor == artboard_id)\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tif !is_child_of_selected_artboard {\n\t\t\t\t\t\t\ttop_level_items_to_center.push(layer_id);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif top_level_items_to_center.is_empty() {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tlet mut combined_min_doc = DVec2::MAX;\n\t\t\t\t\tlet mut combined_max_doc = DVec2::MIN;\n\t\t\t\t\tlet mut has_any_bounds = false;\n\n\t\t\t\t\tfor &item_id in &top_level_items_to_center {\n\t\t\t\t\t\tif let Some(bounds_doc) = document.metadata().bounding_box_document(item_id) {\n\t\t\t\t\t\t\tcombined_min_doc = combined_min_doc.min(bounds_doc[0]);\n\t\t\t\t\t\t\tcombined_max_doc = combined_max_doc.max(bounds_doc[1]);\n\t\t\t\t\t\t\thas_any_bounds = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif !has_any_bounds {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tlet combined_bounds_doc_quad = Quad::from_box([combined_min_doc, combined_max_doc]);\n\n\t\t\t\t\tif combined_bounds_doc_quad.intersects(viewport_quad_doc_space) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tlet combined_center_doc = combined_bounds_doc_quad.center();\n\t\t\t\t\tlet combined_center_viewport_pixels = doc_to_viewport_transform.transform_point2(combined_center_doc);\n\t\t\t\t\tlet translation_viewport_pixels_rounded = (viewport_center_pixels - combined_center_viewport_pixels).round();\n\n\t\t\t\t\tlet final_translation_offset_doc = viewport_to_doc_transform.transform_vector2(translation_viewport_pixels_rounded);\n\n\t\t\t\t\tif final_translation_offset_doc.abs_diff_eq(glam::DVec2::ZERO, 1e-9) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\n\t\t\t\t\tfor &item_id in &top_level_items_to_center {\n\t\t\t\t\t\tif document.network_interface.is_artboard(&item_id.to_node(), &document.node_graph_handler.network) {\n\t\t\t\t\t\t\tif let Some(bounds_doc) = document.metadata().bounding_box_document(item_id) {\n\t\t\t\t\t\t\t\tlet current_artboard_origin_doc = bounds_doc[0];\n\t\t\t\t\t\t\t\tlet dimensions_doc = bounds_doc[1] - bounds_doc[0];\n\t\t\t\t\t\t\t\tlet new_artboard_origin_doc = current_artboard_origin_doc + final_translation_offset_doc;\n\n\t\t\t\t\t\t\t\tresponses.add(GraphOperationMessage::ResizeArtboard {\n\t\t\t\t\t\t\t\t\tlayer: item_id,\n\t\t\t\t\t\t\t\t\tlocation: new_artboard_origin_doc.round().as_ivec2(),\n\t\t\t\t\t\t\t\t\tdimensions: dimensions_doc.round().as_ivec2(),\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tlet current_abs_doc_transform = document.metadata().transform_to_document(item_id);\n\n\t\t\t\t\t\t\tlet new_abs_doc_transform = DAffine2 {\n\t\t\t\t\t\t\t\tmatrix2: current_abs_doc_transform.matrix2,\n\t\t\t\t\t\t\t\ttranslation: current_abs_doc_transform.translation + final_translation_offset_doc,\n\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\tlet transform = doc_to_viewport_transform * new_abs_doc_transform;\n\n\t\t\t\t\t\t\tresponses.add(GraphOperationMessage::TransformSet {\n\t\t\t\t\t\t\t\tlayer: item_id,\n\t\t\t\t\t\t\t\ttransform,\n\t\t\t\t\t\t\t\ttransform_in: TransformIn::Viewport,\n\t\t\t\t\t\t\t\tskip_rerender: false,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\t}\n\t\t\t}\n\t\t\tPortfolioMessage::PasteImage {\n\t\t\t\tname,\n\t\t\t\timage,\n\t\t\t\tmouse,\n\t\t\t\tparent_and_insert_index,\n\t\t\t} => {\n\t\t\t\tif self.documents.is_empty() {\n\t\t\t\t\tresponses.add(PortfolioMessage::OpenImage { name, image });\n\t\t\t\t} else {\n\t\t\t\t\tresponses.add(DocumentMessage::PasteImage {\n\t\t\t\t\t\tname,\n\t\t\t\t\t\timage,\n\t\t\t\t\t\tmouse,\n\t\t\t\t\t\tparent_and_insert_index,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\tPortfolioMessage::PasteSvg {\n\t\t\t\tname,\n\t\t\t\tsvg,\n\t\t\t\tmouse,\n\t\t\t\tparent_and_insert_index,\n\t\t\t} => {\n\t\t\t\tif self.documents.is_empty() {\n\t\t\t\t\tresponses.add(PortfolioMessage::OpenSvg { name, svg });\n\t\t\t\t} else {\n\t\t\t\t\tresponses.add(DocumentMessage::PasteSvg {\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tsvg,\n\t\t\t\t\t\tmouse,\n\t\t\t\t\t\tparent_and_insert_index,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\tPortfolioMessage::PrevDocument => {\n\t\t\t\tif let Some(active_document_id) = self.active_document_id {\n\t\t\t\t\tlet len = self.document_ids.len();\n\t\t\t\t\tlet current_index = self.document_index(active_document_id);\n\t\t\t\t\tlet prev_index = (current_index + len - 1) % len;\n\t\t\t\t\tlet prev_id = self.document_ids[prev_index];\n\t\t\t\t\tresponses.add(PortfolioMessage::SelectDocument { document_id: prev_id });\n\t\t\t\t}\n\t\t\t}\n\t\t\tPortfolioMessage::RequestWelcomeScreenButtonsLayout => {\n\t\t\t\tlet donate = \"https://graphite.art/donate/\";\n\n\t\t\t\tlet table = LayoutGroup::table(\n\t\t\t\t\tvec![\n\t\t\t\t\t\tvec![\n\t\t\t\t\t\t\tTextButton::new(\"New Document\")\n\t\t\t\t\t\t\t\t.icon(\"File\")\n\t\t\t\t\t\t\t\t.flush(true)\n\t\t\t\t\t\t\t\t.on_commit(|_| DialogMessage::RequestNewDocumentDialog.into())\n\t\t\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t\t\t\tShortcutLabel::new(action_shortcut!(DialogMessageDiscriminant::RequestNewDocumentDialog)).widget_instance(),\n\t\t\t\t\t\t],\n\t\t\t\t\t\tvec![\n\t\t\t\t\t\t\tTextButton::new(\"Open Document\")\n\t\t\t\t\t\t\t\t.icon(\"Folder\")\n\t\t\t\t\t\t\t\t.flush(true)\n\t\t\t\t\t\t\t\t.on_commit(|_| PortfolioMessage::Open.into())\n\t\t\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t\t\t\tShortcutLabel::new(action_shortcut!(PortfolioMessageDiscriminant::Open)).widget_instance(),\n\t\t\t\t\t\t],\n\t\t\t\t\t\tvec![\n\t\t\t\t\t\t\tTextButton::new(\"Open Demo Artwork\")\n\t\t\t\t\t\t\t\t.icon(\"Image\")\n\t\t\t\t\t\t\t\t.flush(true)\n\t\t\t\t\t\t\t\t.on_commit(|_| DialogMessage::RequestDemoArtworkDialog.into())\n\t\t\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t\t\t],\n\t\t\t\t\t\tvec![\n\t\t\t\t\t\t\tTextButton::new(\"Support the Development Fund\")\n\t\t\t\t\t\t\t\t.icon(\"Heart\")\n\t\t\t\t\t\t\t\t.flush(true)\n\t\t\t\t\t\t\t\t.on_commit(move |_| FrontendMessage::TriggerVisitLink { url: donate.to_string() }.into())\n\t\t\t\t\t\t\t\t.widget_instance(),\n\t\t\t\t\t\t],\n\t\t\t\t\t],\n\t\t\t\t\ttrue,\n\t\t\t\t);\n\n\t\t\t\tresponses.add(LayoutMessage::DestroyLayout {\n\t\t\t\t\tlayout_target: LayoutTarget::WelcomeScreenButtons,\n\t\t\t\t});\n\t\t\t\tresponses.add(LayoutMessage::SendLayout {\n\t\t\t\t\tlayout: Layout(vec![table]),\n\t\t\t\t\tlayout_target: LayoutTarget::WelcomeScreenButtons,\n\t\t\t\t});\n\t\t\t}\n\t\t\tPortfolioMessage::RequestStatusBarInfoLayout => {\n\t\t\t\t#[cfg(not(target_family = \"wasm\"))]\n\t\t\t\tlet widgets = vec![TextLabel::new(\"Graphite 1.0.0-RC4\").disabled(true).widget_instance()]; // TODO: After the RCs, call this \"Graphite (beta) x.y.z\"\n\t\t\t\t#[cfg(target_family = \"wasm\")]\n\t\t\t\tlet widgets = vec![];\n\n\t\t\t\tlet row = LayoutGroup::row(widgets);\n\n\t\t\t\tresponses.add(LayoutMessage::SendLayout {\n\t\t\t\t\tlayout: Layout(vec![row]),\n\t\t\t\t\tlayout_target: LayoutTarget::StatusBarInfo,\n\t\t\t\t});\n\t\t\t}\n\t\t\tPortfolioMessage::SetActivePanel { panel } => {\n\t\t\t\tself.active_panel = panel;\n\t\t\t\tresponses.add(DocumentMessage::SetActivePanel { active_panel: self.active_panel });\n\t\t\t}\n\t\t\tPortfolioMessage::SelectDocument { document_id } => {\n\t\t\t\t// Auto-save the document we are leaving\n\t\t\t\tlet mut node_graph_open = false;\n\t\t\t\tif let Some(document) = self.active_document() {\n\t\t\t\t\tif !document.is_auto_saved() {\n\t\t\t\t\t\tresponses.add(PortfolioMessage::AutoSaveDocument {\n\t\t\t\t\t\t\t// Safe to unwrap since we know that there is an active document\n\t\t\t\t\t\t\tdocument_id: self.active_document_id.unwrap(),\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tnode_graph_open = document.is_graph_overlay_open();\n\t\t\t\t}\n\n\t\t\t\t// Set the new active document ID\n\t\t\t\tself.active_document_id = Some(document_id);\n\n\t\t\t\tresponses.add(MenuBarMessage::SendLayout);\n\t\t\t\tresponses.add(PortfolioMessage::UpdateOpenDocumentsList);\n\t\t\t\tresponses.add(FrontendMessage::UpdateActiveDocument { document_id });\n\t\t\t\tresponses.add(FrontendMessage::TriggerSaveActiveDocument { document_id });\n\t\t\t\tresponses.add(ToolMessage::InitTools);\n\t\t\t\tresponses.add(NodeGraphMessage::Init);\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\tresponses.add(EventMessage::ToolAbort);\n\t\t\t\tresponses.add(EventMessage::SelectionChanged);\n\t\t\t\tresponses.add(NavigationMessage::CanvasPan { delta: (0., 0.).into() });\n\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\tresponses.add(DocumentMessage::GraphViewOverlay { open: node_graph_open });\n\t\t\t\tif node_graph_open {\n\t\t\t\t\tresponses.add(NodeGraphMessage::UpdateGraphBarRight);\n\t\t\t\t\tresponses.add(NodeGraphMessage::UnloadWires);\n\t\t\t\t\tresponses.add(NodeGraphMessage::SendWires)\n\t\t\t\t} else {\n\t\t\t\t\tresponses.add(PortfolioMessage::UpdateDocumentWidgets);\n\t\t\t\t}\n\n\t\t\t\tlet Some(document) = self.documents.get_mut(&document_id) else {\n\t\t\t\t\twarn!(\"Tried to read non existent document\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tif !document.is_loaded {\n\t\t\t\t\tdocument.is_loaded = true;\n\t\t\t\t\tresponses.add(PortfolioMessage::LoadDocumentResources { document_id });\n\t\t\t\t\tresponses.add(PortfolioMessage::UpdateDocumentWidgets);\n\t\t\t\t\tresponses.add(PropertiesPanelMessage::Clear);\n\t\t\t\t}\n\t\t\t}\n\t\t\tPortfolioMessage::SubmitDocumentExport {\n\t\t\t\tname,\n\t\t\t\tfile_type,\n\t\t\t\tscale_factor,\n\t\t\t\tbounds,\n\t\t\t\ttransparent_background,\n\t\t\t\tartboard_name,\n\t\t\t\tartboard_count,\n\t\t\t} => {\n\t\t\t\tlet document = self.active_document_id.and_then(|id| self.documents.get_mut(&id)).expect(\"Tried to render non-existent document\");\n\t\t\t\tlet export_config = ExportConfig {\n\t\t\t\t\tname,\n\t\t\t\t\tfile_type,\n\t\t\t\t\tscale_factor,\n\t\t\t\t\tbounds,\n\t\t\t\t\ttransparent_background,\n\t\t\t\t\tartboard_name,\n\t\t\t\t\tartboard_count,\n\t\t\t\t\t..Default::default()\n\t\t\t\t};\n\t\t\t\tlet result = self.executor.submit_document_export(document, self.active_document_id.unwrap(), export_config);\n\n\t\t\t\tif let Err(description) = result {\n\t\t\t\t\tresponses.add(DialogMessage::DisplayDialogError {\n\t\t\t\t\t\ttitle: \"Unable to export document\".to_string(),\n\t\t\t\t\t\tdescription,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\tPortfolioMessage::SubmitActiveGraphRender => {\n\t\t\t\tif let Some(document_id) = self.active_document_id {\n\t\t\t\t\tresponses.add(PortfolioMessage::SubmitGraphRender { document_id, ignore_hash: false });\n\t\t\t\t}\n\t\t\t}\n\t\t\tPortfolioMessage::SubmitGraphRender { document_id, ignore_hash } => {\n\t\t\t\tlet node_to_inspect = self.node_to_inspect();\n\n\t\t\t\tlet Some(document) = self.documents.get_mut(&document_id) else {\n\t\t\t\t\tlog::error!(\"Tried to render non-existent document {:?}\", document_id);\n\t\t\t\t\treturn;\n\t\t\t\t};\n\n\t\t\t\tlet document_to_viewport = document\n\t\t\t\t\t.navigation_handler\n\t\t\t\t\t.calculate_offset_transform(viewport.center_in_viewport_space().into(), &document.document_ptz);\n\t\t\t\tlet pointer_position = document_to_viewport.inverse().transform_point2(ipp.mouse.position);\n\n\t\t\t\tlet scale = viewport.scale();\n\t\t\t\t// Use exact physical dimensions from browser (via ResizeObserver's devicePixelContentBoxSize)\n\t\t\t\tlet physical_resolution = viewport.size().to_physical().into_dvec2().round().as_uvec2();\n\n\t\t\t\t// TODO: Remove this when we do the SVG rendering with a separate library on desktop, thus avoiding a need for the hole punch.\n\t\t\t\t// TODO: See #3796. There is a second instance of this todo comment and code block (be sure to remove both).\n\t\t\t\t#[cfg(not(target_family = \"wasm\"))]\n\t\t\t\tresponses.add_front(FrontendMessage::UpdateViewportHolePunch {\n\t\t\t\t\tactive: document.render_mode != graphene_std::vector::style::RenderMode::SvgPreview,\n\t\t\t\t});\n\n\t\t\t\tlet result = self\n\t\t\t\t\t.executor\n\t\t\t\t\t.submit_node_graph_evaluation(document, document_id, physical_resolution, scale, timing_information, node_to_inspect, ignore_hash, pointer_position);\n\n\t\t\t\tmatch result {\n\t\t\t\t\tErr(description) => {\n\t\t\t\t\t\tresponses.add(DialogMessage::DisplayDialogError {\n\t\t\t\t\t\t\ttitle: \"Unable to update node graph\".to_string(),\n\t\t\t\t\t\t\tdescription,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tOk(message) => responses.add_front(message),\n\t\t\t\t}\n\t\t\t}\n\t\t\tPortfolioMessage::SubmitEyedropperPreviewRender => {\n\t\t\t\tuse crate::consts::EYEDROPPER_PREVIEW_AREA_RESOLUTION;\n\n\t\t\t\tlet Some(document_id) = self.active_document_id else { return };\n\t\t\t\tlet Some(document) = self.documents.get_mut(&document_id) else { return };\n\n\t\t\t\tlet resolution = glam::UVec2::splat(EYEDROPPER_PREVIEW_AREA_RESOLUTION);\n\t\t\t\tlet scale = viewport.scale();\n\n\t\t\t\tlet preview_offset_in_viewport = ipp.mouse.position - (glam::DVec2::splat(EYEDROPPER_PREVIEW_AREA_RESOLUTION as f64 / 2.));\n\t\t\t\tlet preview_offset_in_viewport = DAffine2::from_translation(preview_offset_in_viewport);\n\n\t\t\t\tlet document_to_viewport = document.metadata().document_to_viewport;\n\n\t\t\t\tlet preview_transform = preview_offset_in_viewport.inverse() * document_to_viewport;\n\t\t\t\tlet pointer_position = document_to_viewport.inverse().transform_point2(ipp.mouse.position);\n\n\t\t\t\tlet result = self\n\t\t\t\t\t.executor\n\t\t\t\t\t.submit_eyedropper_preview(document, document_id, preview_transform, pointer_position, resolution, scale, timing_information);\n\n\t\t\t\tmatch result {\n\t\t\t\t\tErr(description) => {\n\t\t\t\t\t\tresponses.add(DialogMessage::DisplayDialogError {\n\t\t\t\t\t\t\ttitle: \"Unable to update node graph\".to_string(),\n\t\t\t\t\t\t\tdescription,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tOk(message) => responses.add_front(message),\n\t\t\t\t}\n\t\t\t}\n\t\t\tPortfolioMessage::ToggleFocusDocument => {\n\t\t\t\tself.focus_document = !self.focus_document;\n\t\t\t\tresponses.add(MenuBarMessage::SendLayout);\n\n\t\t\t\tif self.focus_document {\n\t\t\t\t\tif self.properties_panel_open {\n\t\t\t\t\t\tresponses.add(PropertiesPanelMessage::Clear);\n\t\t\t\t\t\tresponses.add(FrontendMessage::UpdatePropertiesPanelState { open: false });\n\t\t\t\t\t}\n\n\t\t\t\t\tif self.layers_panel_open {\n\t\t\t\t\t\tresponses.add(DocumentMessage::ClearLayersPanel);\n\t\t\t\t\t\tresponses.add(FrontendMessage::UpdateLayersPanelState { open: false });\n\t\t\t\t\t}\n\n\t\t\t\t\tif self.data_panel_open {\n\t\t\t\t\t\tresponses.add(DataPanelMessage::ClearLayout);\n\t\t\t\t\t\tresponses.add(FrontendMessage::UpdateDataPanelState { open: false });\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif self.properties_panel_open {\n\t\t\t\t\t\tresponses.add(FrontendMessage::UpdatePropertiesPanelState { open: true });\n\t\t\t\t\t}\n\t\t\t\t\tif self.layers_panel_open {\n\t\t\t\t\t\tresponses.add(FrontendMessage::UpdateLayersPanelState { open: true });\n\t\t\t\t\t}\n\t\t\t\t\tif self.data_panel_open {\n\t\t\t\t\t\tresponses.add(FrontendMessage::UpdateDataPanelState { open: true });\n\t\t\t\t\t}\n\n\t\t\t\t\t// Run the graph to grab the data\n\t\t\t\t\tif self.properties_panel_open || self.layers_panel_open || self.data_panel_open {\n\t\t\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\t\t}\n\n\t\t\t\t\tif self.properties_panel_open {\n\t\t\t\t\t\tresponses.add(PropertiesPanelMessage::Refresh);\n\t\t\t\t\t}\n\t\t\t\t\tif self.layers_panel_open && self.active_document_id.is_some() {\n\t\t\t\t\t\tresponses.add(DeferMessage::AfterGraphRun {\n\t\t\t\t\t\t\tmessages: vec![NodeGraphMessage::UpdateLayerPanel.into(), DocumentMessage::DocumentStructureChanged.into()],\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tPortfolioMessage::TogglePropertiesPanelOpen => {\n\t\t\t\tif self.focus_document {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tself.properties_panel_open = !self.properties_panel_open;\n\t\t\t\tresponses.add(MenuBarMessage::SendLayout);\n\n\t\t\t\t// Run the graph to grab the data\n\t\t\t\tif self.properties_panel_open {\n\t\t\t\t\tresponses.add(FrontendMessage::UpdatePropertiesPanelState { open: self.properties_panel_open });\n\t\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\t\tresponses.add(PropertiesPanelMessage::Refresh);\n\t\t\t\t} else {\n\t\t\t\t\tresponses.add(PropertiesPanelMessage::Clear);\n\t\t\t\t\tresponses.add(FrontendMessage::UpdatePropertiesPanelState { open: self.properties_panel_open });\n\t\t\t\t}\n\t\t\t}\n\t\t\tPortfolioMessage::ToggleLayersPanelOpen => {\n\t\t\t\tif self.focus_document {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tself.layers_panel_open = !self.layers_panel_open;\n\t\t\t\tresponses.add(MenuBarMessage::SendLayout);\n\n\t\t\t\t// Run the graph to grab the data\n\t\t\t\tif self.layers_panel_open {\n\t\t\t\t\t// When opening, we make the frontend show the panel first so it can start receiving its message subscriptions for the data it will display\n\t\t\t\t\tresponses.add(FrontendMessage::UpdateLayersPanelState { open: self.layers_panel_open });\n\n\t\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\t\tif self.active_document_id.is_some() {\n\t\t\t\t\t\tresponses.add(DeferMessage::AfterGraphRun {\n\t\t\t\t\t\t\tmessages: vec![NodeGraphMessage::UpdateLayerPanel.into(), DocumentMessage::DocumentStructureChanged.into()],\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// If we don't clear the panel, the layout diffing system will assume widgets still exist when it attempts to update the layers panel next time it is opened\n\t\t\t\t\tresponses.add(DocumentMessage::ClearLayersPanel);\n\n\t\t\t\t\t// When closing, we make the frontend hide the panel last so it can finish receiving its message subscriptions before it is destroyed\n\t\t\t\t\tresponses.add(FrontendMessage::UpdateLayersPanelState { open: self.layers_panel_open });\n\t\t\t\t}\n\t\t\t}\n\t\t\tPortfolioMessage::ToggleDataPanelOpen => {\n\t\t\t\tif self.focus_document {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tself.data_panel_open = !self.data_panel_open;\n\t\t\t\tresponses.add(MenuBarMessage::SendLayout);\n\n\t\t\t\t// Run the graph to grab the data\n\t\t\t\tif self.data_panel_open {\n\t\t\t\t\t// When opening, we make the frontend show the panel first so it can start receiving its message subscriptions for the data it will display\n\t\t\t\t\tresponses.add(FrontendMessage::UpdateDataPanelState { open: self.data_panel_open });\n\n\t\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\t} else {\n\t\t\t\t\t// If we don't clear the panel, the layout diffing system will assume widgets still exist when it attempts to update the data panel next time it is opened\n\t\t\t\t\tresponses.add(DataPanelMessage::ClearLayout);\n\n\t\t\t\t\t// When closing, we make the frontend hide the panel last so it can finish receiving its message subscriptions before it is destroyed\n\t\t\t\t\tresponses.add(FrontendMessage::UpdateDataPanelState { open: self.data_panel_open });\n\t\t\t\t}\n\t\t\t}\n\t\t\tPortfolioMessage::ToggleRulers => {\n\t\t\t\tif let Some(document) = self.active_document_mut() {\n\t\t\t\t\tdocument.rulers_visible = !document.rulers_visible;\n\n\t\t\t\t\tresponses.add(DocumentMessage::RenderRulers);\n\t\t\t\t\tresponses.add(MenuBarMessage::SendLayout);\n\t\t\t\t}\n\t\t\t}\n\t\t\tPortfolioMessage::UpdateDocumentWidgets => {\n\t\t\t\tif let Some(document) = self.active_document() {\n\t\t\t\t\tdocument.update_document_widgets(responses, animation.is_playing(), timing_information.animation_time);\n\t\t\t\t}\n\t\t\t}\n\t\t\tPortfolioMessage::UpdateOpenDocumentsList => {\n\t\t\t\t// Send the list of document tab names\n\t\t\t\tlet open_documents = self\n\t\t\t\t\t.document_ids\n\t\t\t\t\t.iter()\n\t\t\t\t\t.filter_map(|id| {\n\t\t\t\t\t\tself.documents.get(id).map(|document| OpenDocument {\n\t\t\t\t\t\t\tid: *id,\n\t\t\t\t\t\t\tdetails: DocumentDetails {\n\t\t\t\t\t\t\t\tname: document.name.clone(),\n\t\t\t\t\t\t\t\tpath: document.path.clone(),\n\t\t\t\t\t\t\t\tis_saved: document.is_saved(),\n\t\t\t\t\t\t\t\tis_auto_saved: document.is_auto_saved(),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t\t\t.collect::<Vec<_>>();\n\n\t\t\t\tlet no_open_documents = open_documents.is_empty();\n\n\t\t\t\tresponses.add(FrontendMessage::UpdateOpenDocumentsList { open_documents });\n\n\t\t\t\tif no_open_documents {\n\t\t\t\t\tresponses.add(PortfolioMessage::RequestWelcomeScreenButtonsLayout);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfn actions(&self) -> ActionList {\n\t\tlet mut common = actions!(PortfolioMessageDiscriminant;\n\t\t\tOpen,\n\t\t\tToggleFocusDocument,\n\t\t);\n\n\t\t// Extend with actions that require an active document\n\t\tif let Some(document) = self.active_document() {\n\t\t\tcommon.extend(document.actions());\n\t\t\tcommon.extend(actions!(PortfolioMessageDiscriminant;\n\t\t\t\tCloseActiveDocumentWithConfirmation,\n\t\t\t\tCloseAllDocuments,\n\t\t\t\tCloseAllDocumentsWithConfirmation,\n\t\t\t\tToggleRulers,\n\t\t\t\tNextDocument,\n\t\t\t\tPrevDocument,\n\t\t\t\tImport,\n\t\t\t));\n\n\t\t\t// Extend with actions that must have a selected layer\n\t\t\tif document.network_interface.selected_nodes().selected_layers(document.metadata()).next().is_some() {\n\t\t\t\tcommon.extend(actions!(PortfolioMessageDiscriminant;\n\t\t\t\t\tCopy,\n\t\t\t\t\tCut,\n\t\t\t\t));\n\t\t\t}\n\t\t}\n\n\t\t// Extend with actions that are disabled when focusing the document\n\t\tif !self.focus_document {\n\t\t\tcommon.extend(actions!(PortfolioMessageDiscriminant;\n\t\t\t\tTogglePropertiesPanelOpen,\n\t\t\t\tToggleLayersPanelOpen,\n\t\t\t\tToggleDataPanelOpen,\n\t\t\t));\n\t\t}\n\n\t\tcommon\n\t}\n}\n\nimpl PortfolioMessageHandler {\n\tpub fn with_executor(executor: crate::node_graph_executor::NodeGraphExecutor) -> Self {\n\t\tSelf { executor, ..Default::default() }\n\t}\n\n\tpub fn document(&self, document_id: DocumentId) -> Option<&DocumentMessageHandler> {\n\t\tself.documents.get(&document_id)\n\t}\n\n\tpub fn document_mut(&mut self, document_id: DocumentId) -> Option<&mut DocumentMessageHandler> {\n\t\tself.documents.get_mut(&document_id)\n\t}\n\n\tpub fn active_document(&self) -> Option<&DocumentMessageHandler> {\n\t\tself.active_document_id.and_then(|id| self.documents.get(&id))\n\t}\n\n\tpub fn active_document_mut(&mut self) -> Option<&mut DocumentMessageHandler> {\n\t\tself.active_document_id.and_then(|id| self.documents.get_mut(&id))\n\t}\n\n\tpub fn active_document_id(&self) -> Option<DocumentId> {\n\t\tself.active_document_id\n\t}\n\n\tpub fn unsaved_document_names(&self) -> Vec<String> {\n\t\tself.documents.values().filter(|document| !document.is_saved()).map(|document| document.name.clone()).collect()\n\t}\n\n\tpub fn generate_new_document_name(&self) -> String {\n\t\tlet mut doc_title_numbers = self\n\t\t\t.ordered_document_iterator()\n\t\t\t.filter_map(|doc| {\n\t\t\t\tdoc.name\n\t\t\t\t\t.rsplit_once(DEFAULT_DOCUMENT_NAME)\n\t\t\t\t\t.map(|(prefix, number)| (prefix.is_empty()).then(|| number.trim().parse::<isize>().ok()).flatten().unwrap_or(1))\n\t\t\t})\n\t\t\t.collect::<Vec<isize>>();\n\n\t\tdoc_title_numbers.sort_unstable();\n\t\tdoc_title_numbers.iter_mut().enumerate().for_each(|(i, number)| *number = *number - i as isize - 2);\n\t\t// Uses binary search to find the index of the element where number is bigger than i\n\t\tlet new_doc_title_num = doc_title_numbers.binary_search(&0).unwrap_or_else(|e| e) + 1;\n\n\t\tmatch new_doc_title_num {\n\t\t\t1 => DEFAULT_DOCUMENT_NAME.to_string(),\n\t\t\t_ => format!(\"{DEFAULT_DOCUMENT_NAME} {new_doc_title_num}\"),\n\t\t}\n\t}\n\n\tfn read_file(path: &PathBuf, content: Vec<u8>) -> FileContent {\n\t\tlet extension = path.extension().and_then(|ext| ext.to_str()).unwrap_or_default().to_lowercase();\n\t\tmatch extension.as_str() {\n\t\t\tFILE_EXTENSION => match String::from_utf8(content) {\n\t\t\t\tOk(content) => FileContent::Document(content),\n\t\t\t\tErr(_) => FileContent::Unsupported,\n\t\t\t},\n\t\t\t\"svg\" => match String::from_utf8(content) {\n\t\t\t\tOk(content) => FileContent::Svg(content),\n\t\t\t\tErr(_) => FileContent::Unsupported,\n\t\t\t},\n\t\t\t_ => {\n\t\t\t\tlet format = image::guess_format(&content).unwrap_or_else(|_| image::ImageFormat::from_path(path).unwrap_or(image::ImageFormat::Png));\n\t\t\t\tmatch image::load_from_memory_with_format(&content, format) {\n\t\t\t\t\tOk(image) => {\n\t\t\t\t\t\t// TODO: Handle Image formats with more than 8 bits per channel\n\t\t\t\t\t\tlet image_data = image.to_rgba8();\n\t\t\t\t\t\tlet image = Image::<Color>::from_image_data(image_data.as_raw(), image.width(), image.height());\n\t\t\t\t\t\tFileContent::Image(image)\n\t\t\t\t\t}\n\t\t\t\t\tErr(_) => FileContent::Unsupported,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfn load_document(&mut self, mut new_document: DocumentMessageHandler, document_id: DocumentId, responses: &mut VecDeque<Message>, to_front: bool) {\n\t\tif to_front {\n\t\t\tself.document_ids.push_front(document_id);\n\t\t} else {\n\t\t\tself.document_ids.push_back(document_id);\n\t\t}\n\t\tnew_document.update_layers_panel_control_bar_widgets(self.layers_panel_open && !self.focus_document, responses);\n\t\tnew_document.update_layers_panel_bottom_bar_widgets(self.layers_panel_open && !self.focus_document, responses);\n\n\t\tself.documents.insert(document_id, new_document);\n\n\t\tif self.active_document().is_some() {\n\t\t\tresponses.add(EventMessage::ToolAbort);\n\t\t\tresponses.add(ToolMessage::DeactivateTools);\n\t\t}\n\n\t\t// TODO: Remove this and find a way to fix the issue where creating a new document when the node graph is open causes the transform in the new document to be incorrect\n\t\tresponses.add(DocumentMessage::GraphViewOverlay { open: false });\n\t\tresponses.add(PortfolioMessage::UpdateOpenDocumentsList);\n\t}\n\n\t/// Returns an iterator over the open documents in order.\n\tpub fn ordered_document_iterator(&self) -> impl Iterator<Item = &DocumentMessageHandler> {\n\t\tself.document_ids.iter().map(|id| self.documents.get(id).expect(\"Document id was not found in the document hashmap\"))\n\t}\n\n\tfn document_index(&self, document_id: DocumentId) -> usize {\n\t\tself.document_ids.iter().position(|id| id == &document_id).expect(\"Active document is missing from document ids\")\n\t}\n\n\tpub fn poll_node_graph_evaluation(&mut self, responses: &mut VecDeque<Message>) -> Result<(), String> {\n\t\tlet Some(active_document) = self.active_document_id.and_then(|id| self.documents.get_mut(&id)) else {\n\t\t\treturn Err(\"No active document\".to_string());\n\t\t};\n\n\t\tlet result = self.executor.poll_node_graph_evaluation(active_document, responses);\n\t\tif result.is_err() {\n\t\t\tlet error = r#\"\n\t\t\t\t<rect x=\"50%\" y=\"50%\" width=\"460\" height=\"100\" transform=\"translate(-230 -50)\" rx=\"4\" fill=\"var(--color-warning-yellow)\" />\n\t\t\t\t<text x=\"50%\" y=\"50%\" dominant-baseline=\"middle\" text-anchor=\"middle\" font-size=\"18\" fill=\"var(--color-2-mildblack)\">\n\t\t\t\t\t<tspan x=\"50%\" dy=\"-24\" font-weight=\"bold\">The document cannot render in its current state.</tspan>\n\t\t\t\t\t<tspan x=\"50%\" dy=\"24\">Undo to go back, if available, or check for error details</tspan>\n\t\t\t\t\t<tspan x=\"50%\" dy=\"24\">by clicking the <tspan font-style=\"italic\">Node Graph</tspan> button up at the top right.</tspan>\n\t\t\t\t/text>\"#\n\t\t\t\t// It's a mystery why the `/text>` tag above needs to be missing its `<`, but when it exists it prints the `<` character in the text. However this works with it removed.\n\t\t\t\t.to_string();\n\t\t\tresponses.add(FrontendMessage::UpdateDocumentArtwork { svg: error });\n\t\t}\n\t\tresult\n\t}\n\n\t/// Get the ID of the selected node that should be used as the current source for the Data panel.\n\tpub fn node_to_inspect(&self) -> Option<NodeId> {\n\t\t// Skip if the Data panel is not open\n\t\tif !self.data_panel_open || self.focus_document {\n\t\t\treturn None;\n\t\t}\n\n\t\tlet document = self.documents.get(&self.active_document_id?)?;\n\t\tlet selected_nodes = document.network_interface.selected_nodes().0;\n\n\t\t// Skip if there is not exactly one selected node\n\t\tif selected_nodes.len() != 1 {\n\t\t\treturn None;\n\t\t}\n\n\t\tselected_nodes.first().copied()\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/portfolio/utility_types.rs",
    "content": "use graphene_std::Color;\nuse graphene_std::raster::Image;\nuse graphene_std::text::{Font, FontCache};\n\n#[derive(Debug, Default)]\npub struct PersistentData {\n\tpub font_cache: FontCache,\n\tpub font_catalog: FontCatalog,\n}\n\n// TODO: Should this be a BTreeMap instead?\n#[derive(Clone, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct FontCatalog(pub Vec<FontCatalogFamily>);\n\nimpl FontCatalog {\n\tpub fn find_font_style_in_catalog(&self, font: &Font) -> Option<FontCatalogStyle> {\n\t\tlet family = self.0.iter().find(|family| family.name == font.font_family);\n\n\t\tlet found_style = family.map(|family| {\n\t\t\tlet FontCatalogStyle { weight, italic, .. } = FontCatalogStyle::from_named_style(&font.font_style, \"\");\n\t\t\tfamily.closest_style(weight, italic).clone()\n\t\t});\n\n\t\tif found_style.is_none() {\n\t\t\tlog::warn!(\"Font not found in catalog: {:?}\", font);\n\t\t}\n\n\t\tfound_style\n\t}\n}\n\n#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct FontCatalogFamily {\n\t/// The font family name.\n\tpub name: String,\n\t/// The font styles (variants) available for the font family.\n\tpub styles: Vec<FontCatalogStyle>,\n}\n\nimpl FontCatalogFamily {\n\t/// Finds the closest style to the given weight and italic setting.\n\t/// Aims to find the nearest weight while maintaining the italic setting if possible, but italic may change if no other option is available.\n\tpub fn closest_style(&self, weight: u32, italic: bool) -> &FontCatalogStyle {\n\t\tself.styles\n\t\t\t.iter()\n\t\t\t.map(|style| ((style.weight as i32 - weight as i32).unsigned_abs() + 10000 * (style.italic != italic) as u32, style))\n\t\t\t.min_by_key(|(distance, _)| *distance)\n\t\t\t.map(|(_, style)| style)\n\t\t\t.unwrap_or(&self.styles[0])\n\t}\n}\n\n#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct FontCatalogStyle {\n\tpub weight: u32,\n\tpub italic: bool,\n\tpub url: String,\n}\n\nimpl FontCatalogStyle {\n\tpub fn to_named_style(&self) -> String {\n\t\tlet weight = self.weight;\n\t\tlet italic = self.italic;\n\n\t\tlet named_weight = Font::named_weight(weight);\n\t\tlet maybe_italic = if italic { \" Italic\" } else { \"\" };\n\n\t\tformat!(\"{named_weight}{maybe_italic} ({weight})\")\n\t}\n\n\tpub fn from_named_style(named_style: &str, url: impl Into<String>) -> FontCatalogStyle {\n\t\tlet weight = named_style.split_terminator(['(', ')']).next_back().and_then(|x| x.parse::<u32>().ok()).unwrap_or(400);\n\t\tlet italic = named_style.contains(\"Italic (\");\n\t\tFontCatalogStyle { weight, italic, url: url.into() }\n\t}\n\n\t/// Get the URL for the stylesheet for loading a font preview for this style of the given family name, subsetted to only the letters in the family name.\n\tpub fn preview_url(&self, family: impl Into<String>) -> String {\n\t\tlet name = family.into().replace(' ', \"+\");\n\t\tlet italic = if self.italic { \"ital,\" } else { \"\" };\n\t\tlet weight = self.weight;\n\t\tformat!(\"https://fonts.googleapis.com/css2?display=swap&family={name}:{italic}wght@{weight}&text={name}\")\n\t}\n}\n\n#[derive(PartialEq, Eq, Clone, Copy, Debug, Default, serde::Serialize, serde::Deserialize)]\npub enum PanelType {\n\t#[default]\n\tDocument,\n\tWelcome,\n\tLayers,\n\tProperties,\n\tDataPanel,\n}\n\nimpl From<String> for PanelType {\n\tfn from(value: String) -> Self {\n\t\tmatch value.as_str() {\n\t\t\t\"Document\" => PanelType::Document,\n\t\t\t\"Welcome\" => PanelType::Welcome,\n\t\t\t\"Layers\" => PanelType::Layers,\n\t\t\t\"Properties\" => PanelType::Properties,\n\t\t\t\"Data\" => PanelType::DataPanel,\n\t\t\t_ => panic!(\"Unknown panel type: {value}\"),\n\t\t}\n\t}\n}\n\npub enum FileContent {\n\t/// A Graphite document.\n\tDocument(String),\n\t/// A bitmap image.\n\tImage(Image<Color>),\n\t/// An SVG file string.\n\tSvg(String),\n\t/// Any other unsupported/unrecognized file type.\n\tUnsupported,\n}\n"
  },
  {
    "path": "editor/src/messages/preferences/mod.rs",
    "content": "mod preferences_message;\npub mod preferences_message_handler;\npub mod utility_types;\n\n#[doc(inline)]\npub use preferences_message::{PreferencesMessage, PreferencesMessageDiscriminant};\n#[doc(inline)]\npub use preferences_message_handler::PreferencesMessageHandler;\n#[doc(inline)]\npub use utility_types::SelectionMode;\n"
  },
  {
    "path": "editor/src/messages/preferences/preferences_message.rs",
    "content": "use crate::messages::portfolio::document::utility_types::wires::GraphWireStyle;\nuse crate::messages::preferences::SelectionMode;\nuse crate::messages::prelude::*;\n\n#[impl_message(Message, Preferences)]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum PreferencesMessage {\n\t// Management messages\n\tLoad {\n\t\tpreferences: PreferencesMessageHandler,\n\t},\n\tResetToDefaults,\n\n\t// Per-preference messages\n\tSelectionMode {\n\t\tselection_mode: SelectionMode,\n\t},\n\tBrushTool {\n\t\tenabled: bool,\n\t},\n\tModifyLayout {\n\t\tzoom_with_scroll: bool,\n\t},\n\tGraphWireStyle {\n\t\tstyle: GraphWireStyle,\n\t},\n\tViewportZoomWheelRate {\n\t\trate: f64,\n\t},\n\tUIScale {\n\t\tscale: f64,\n\t},\n\tMaxRenderRegionSize {\n\t\tsize: u32,\n\t},\n\tDisableUIAcceleration {\n\t\tdisable_ui_acceleration: bool,\n\t},\n\t#[cfg(target_os = \"macos\")]\n\tVSync {\n\t\tvsync: bool,\n\t},\n}\n"
  },
  {
    "path": "editor/src/messages/preferences/preferences_message_handler.rs",
    "content": "use crate::consts::{UI_SCALE_DEFAULT, VIEWPORT_ZOOM_WHEEL_RATE};\nuse crate::messages::input_mapper::key_mapping::MappingVariant;\nuse crate::messages::portfolio::document::utility_types::wires::GraphWireStyle;\nuse crate::messages::preferences::SelectionMode;\nuse crate::messages::prelude::*;\nuse crate::messages::tool::utility_types::ToolType;\nuse graph_craft::wasm_application_io::EditorPreferences;\n\n#[derive(ExtractField)]\npub struct PreferencesMessageContext<'a> {\n\tpub tool_message_handler: &'a ToolMessageHandler,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize, ExtractField)]\n#[serde(default)]\npub struct PreferencesMessageHandler {\n\tpub selection_mode: SelectionMode,\n\tpub zoom_with_scroll: bool,\n\tpub brush_tool: bool,\n\tpub graph_wire_style: GraphWireStyle,\n\tpub viewport_zoom_wheel_rate: f64,\n\tpub ui_scale: f64,\n\tpub max_render_region_size: u32,\n\tpub disable_ui_acceleration: bool,\n\t#[cfg(target_os = \"macos\")]\n\tpub vsync: bool,\n}\n\nimpl PreferencesMessageHandler {\n\tpub fn preferences_requiring_restart(&self, other: &Self) -> Vec<String> {\n\t\tlet mut requiring_restart = Vec::new();\n\t\tif self.disable_ui_acceleration != other.disable_ui_acceleration {\n\t\t\trequiring_restart.push(\"Disable UI Acceleration\");\n\t\t}\n\t\t#[cfg(target_os = \"macos\")]\n\t\tif self.vsync != other.vsync {\n\t\t\trequiring_restart.push(\"Enable V-Sync\");\n\t\t}\n\t\trequiring_restart.into_iter().map(String::from).collect()\n\t}\n\n\tpub fn get_selection_mode(&self) -> SelectionMode {\n\t\tself.selection_mode\n\t}\n\n\tpub fn editor_preferences(&self) -> EditorPreferences {\n\t\tEditorPreferences {\n\t\t\tmax_render_region_size: self.max_render_region_size,\n\t\t}\n\t}\n\n\tpub fn supports_wgpu(&self) -> bool {\n\t\tgraph_craft::wasm_application_io::wgpu_available().unwrap_or_default()\n\t}\n}\n\nimpl Default for PreferencesMessageHandler {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tselection_mode: SelectionMode::Touched,\n\t\t\tzoom_with_scroll: matches!(MappingVariant::default(), MappingVariant::ZoomWithScroll),\n\t\t\tbrush_tool: false,\n\t\t\tgraph_wire_style: GraphWireStyle::default(),\n\t\t\tviewport_zoom_wheel_rate: VIEWPORT_ZOOM_WHEEL_RATE,\n\t\t\tui_scale: UI_SCALE_DEFAULT,\n\t\t\tmax_render_region_size: EditorPreferences::default().max_render_region_size,\n\t\t\tdisable_ui_acceleration: false,\n\t\t\t#[cfg(target_os = \"macos\")]\n\t\t\tvsync: false,\n\t\t}\n\t}\n}\n\n#[message_handler_data]\nimpl MessageHandler<PreferencesMessage, PreferencesMessageContext<'_>> for PreferencesMessageHandler {\n\tfn process_message(&mut self, message: PreferencesMessage, responses: &mut VecDeque<Message>, context: PreferencesMessageContext) {\n\t\tlet PreferencesMessageContext { tool_message_handler } = context;\n\n\t\tmatch message {\n\t\t\t// Management messages\n\t\t\tPreferencesMessage::Load { preferences } => {\n\t\t\t\t*self = preferences;\n\n\t\t\t\tresponses.add(PortfolioMessage::EditorPreferences);\n\t\t\t\tresponses.add(PreferencesMessage::ModifyLayout {\n\t\t\t\t\tzoom_with_scroll: self.zoom_with_scroll,\n\t\t\t\t});\n\t\t\t\tresponses.add(FrontendMessage::UpdateUIScale { scale: self.ui_scale });\n\t\t\t}\n\t\t\tPreferencesMessage::ResetToDefaults => {\n\t\t\t\tresponses.add(PreferencesMessage::Load { preferences: Self::default() });\n\t\t\t\tresponses.add(DialogMessage::RequestPreferencesDialog);\n\t\t\t}\n\n\t\t\t// Per-preference messages\n\t\t\tPreferencesMessage::BrushTool { enabled } => {\n\t\t\t\tself.brush_tool = enabled;\n\n\t\t\t\tif !enabled && tool_message_handler.tool_state.tool_data.active_tool_type == ToolType::Brush {\n\t\t\t\t\tresponses.add(ToolMessage::ActivateToolSelect);\n\t\t\t\t}\n\n\t\t\t\tresponses.add(ToolMessage::RefreshToolShelf);\n\t\t\t}\n\t\t\tPreferencesMessage::ModifyLayout { zoom_with_scroll } => {\n\t\t\t\tself.zoom_with_scroll = zoom_with_scroll;\n\n\t\t\t\tlet variant = if zoom_with_scroll { MappingVariant::ZoomWithScroll } else { MappingVariant::Default };\n\t\t\t\tresponses.add(KeyMappingMessage::ModifyMapping { mapping: variant });\n\t\t\t}\n\t\t\tPreferencesMessage::SelectionMode { selection_mode } => {\n\t\t\t\tself.selection_mode = selection_mode;\n\t\t\t}\n\t\t\tPreferencesMessage::GraphWireStyle { style } => {\n\t\t\t\tself.graph_wire_style = style;\n\t\t\t\tresponses.add(NodeGraphMessage::UnloadWires);\n\t\t\t\tresponses.add(NodeGraphMessage::SendWires);\n\t\t\t}\n\t\t\tPreferencesMessage::ViewportZoomWheelRate { rate } => {\n\t\t\t\tself.viewport_zoom_wheel_rate = rate;\n\t\t\t}\n\t\t\tPreferencesMessage::UIScale { scale } => {\n\t\t\t\tself.ui_scale = scale;\n\t\t\t\tresponses.add(FrontendMessage::UpdateUIScale { scale: self.ui_scale });\n\t\t\t}\n\t\t\tPreferencesMessage::MaxRenderRegionSize { size } => {\n\t\t\t\tself.max_render_region_size = size;\n\t\t\t\tresponses.add(PortfolioMessage::EditorPreferences);\n\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t}\n\t\t\tPreferencesMessage::DisableUIAcceleration { disable_ui_acceleration } => {\n\t\t\t\tself.disable_ui_acceleration = disable_ui_acceleration;\n\t\t\t}\n\t\t\t#[cfg(target_os = \"macos\")]\n\t\t\tPreferencesMessage::VSync { vsync } => {\n\t\t\t\tself.vsync = vsync;\n\t\t\t}\n\t\t}\n\n\t\tresponses.add(FrontendMessage::TriggerSavePreferences { preferences: self.clone() });\n\t}\n\n\tadvertise_actions!(PreferencesMessageDiscriminant;\n\t);\n}\n"
  },
  {
    "path": "editor/src/messages/preferences/utility_types.rs",
    "content": "#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash)]\npub enum SelectionMode {\n\t#[default]\n\tTouched = 0,\n\tEnclosed = 1,\n\tDirectional = 2,\n}\n\nimpl std::fmt::Display for SelectionMode {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tmatch self {\n\t\t\tSelectionMode::Touched => write!(f, \"Touched\"),\n\t\t\tSelectionMode::Enclosed => write!(f, \"Enclosed\"),\n\t\t\tSelectionMode::Directional => write!(f, \"Directional\"),\n\t\t}\n\t}\n}\n\nimpl SelectionMode {\n\tpub fn tooltip_description(&self) -> &'static str {\n\t\tmatch self {\n\t\t\tSelectionMode::Touched => \"Select all layers at least partially covered by the dragged selection area.\",\n\t\t\tSelectionMode::Enclosed => \"Select only layers fully enclosed by the dragged selection area.\",\n\t\t\tSelectionMode::Directional => r#\"\"Touched\" for leftward drags, \"Enclosed\" for rightward drags.\"#,\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/prelude.rs",
    "content": "// Message-related\npub use crate::utility_traits::{ActionList, AsMessage, ExtractField, HierarchicalTree, MessageHandler, ToDiscriminant, TransitiveChild};\npub use crate::utility_types::{DebugMessageTree, MessageData};\n\n// Message, MessageData, MessageDiscriminant, MessageHandler\npub use crate::messages::animation::{AnimationMessage, AnimationMessageDiscriminant, AnimationMessageHandler};\npub use crate::messages::app_window::{AppWindowMessage, AppWindowMessageDiscriminant, AppWindowMessageHandler};\npub use crate::messages::broadcast::event::{EventMessage, EventMessageContext, EventMessageDiscriminant, EventMessageHandler};\npub use crate::messages::broadcast::{BroadcastMessage, BroadcastMessageDiscriminant, BroadcastMessageHandler};\npub use crate::messages::clipboard::{ClipboardMessage, ClipboardMessageDiscriminant, ClipboardMessageHandler};\npub use crate::messages::debug::{DebugMessage, DebugMessageDiscriminant, DebugMessageHandler};\npub use crate::messages::defer::{DeferMessage, DeferMessageDiscriminant, DeferMessageHandler};\npub use crate::messages::dialog::export_dialog::{ExportDialogMessage, ExportDialogMessageContext, ExportDialogMessageDiscriminant, ExportDialogMessageHandler};\npub use crate::messages::dialog::new_document_dialog::{NewDocumentDialogMessage, NewDocumentDialogMessageDiscriminant, NewDocumentDialogMessageHandler};\npub use crate::messages::dialog::preferences_dialog::{PreferencesDialogMessage, PreferencesDialogMessageContext, PreferencesDialogMessageDiscriminant, PreferencesDialogMessageHandler};\npub use crate::messages::dialog::{DialogMessage, DialogMessageContext, DialogMessageDiscriminant, DialogMessageHandler};\npub use crate::messages::frontend::{FrontendMessage, FrontendMessageDiscriminant};\npub use crate::messages::input_mapper::key_mapping::{KeyMappingMessage, KeyMappingMessageContext, KeyMappingMessageDiscriminant, KeyMappingMessageHandler};\npub use crate::messages::input_mapper::{InputMapperMessage, InputMapperMessageContext, InputMapperMessageDiscriminant, InputMapperMessageHandler};\npub use crate::messages::input_preprocessor::{InputPreprocessorMessage, InputPreprocessorMessageContext, InputPreprocessorMessageDiscriminant, InputPreprocessorMessageHandler};\npub use crate::messages::layout::{LayoutMessage, LayoutMessageDiscriminant, LayoutMessageHandler};\npub use crate::messages::menu_bar::{MenuBarMessage, MenuBarMessageDiscriminant, MenuBarMessageHandler};\npub use crate::messages::portfolio::document::data_panel::{DataPanelMessage, DataPanelMessageDiscriminant};\npub use crate::messages::portfolio::document::graph_operation::{GraphOperationMessage, GraphOperationMessageContext, GraphOperationMessageDiscriminant, GraphOperationMessageHandler};\npub use crate::messages::portfolio::document::navigation::{NavigationMessage, NavigationMessageContext, NavigationMessageDiscriminant, NavigationMessageHandler};\npub use crate::messages::portfolio::document::node_graph::{NodeGraphMessage, NodeGraphMessageDiscriminant, NodeGraphMessageHandler};\npub use crate::messages::portfolio::document::overlays::{OverlaysMessage, OverlaysMessageContext, OverlaysMessageDiscriminant, OverlaysMessageHandler};\npub use crate::messages::portfolio::document::properties_panel::{PropertiesPanelMessage, PropertiesPanelMessageDiscriminant, PropertiesPanelMessageHandler};\npub use crate::messages::portfolio::document::{DocumentMessage, DocumentMessageContext, DocumentMessageDiscriminant, DocumentMessageHandler};\npub use crate::messages::portfolio::{PortfolioMessage, PortfolioMessageContext, PortfolioMessageDiscriminant, PortfolioMessageHandler};\npub use crate::messages::preferences::{PreferencesMessage, PreferencesMessageDiscriminant, PreferencesMessageHandler};\npub use crate::messages::tool::transform_layer::{TransformLayerMessage, TransformLayerMessageDiscriminant, TransformLayerMessageHandler};\npub use crate::messages::tool::{ToolMessage, ToolMessageContext, ToolMessageDiscriminant, ToolMessageHandler};\npub use crate::messages::viewport::{ViewportMessage, ViewportMessageDiscriminant, ViewportMessageHandler};\n\n// Message, MessageDiscriminant\npub use crate::messages::message::{Message, MessageDiscriminant};\npub use crate::messages::tool::tool_messages::artboard_tool::{ArtboardToolMessage, ArtboardToolMessageDiscriminant};\npub use crate::messages::tool::tool_messages::brush_tool::{BrushToolMessage, BrushToolMessageDiscriminant};\npub use crate::messages::tool::tool_messages::eyedropper_tool::{EyedropperToolMessage, EyedropperToolMessageDiscriminant};\npub use crate::messages::tool::tool_messages::fill_tool::{FillToolMessage, FillToolMessageDiscriminant};\npub use crate::messages::tool::tool_messages::freehand_tool::{FreehandToolMessage, FreehandToolMessageDiscriminant};\npub use crate::messages::tool::tool_messages::gradient_tool::{GradientOptionsUpdate, GradientToolMessage, GradientToolMessageDiscriminant};\npub use crate::messages::tool::tool_messages::navigate_tool::{NavigateToolMessage, NavigateToolMessageDiscriminant};\npub use crate::messages::tool::tool_messages::path_tool::{PathToolMessage, PathToolMessageDiscriminant};\npub use crate::messages::tool::tool_messages::pen_tool::{PenToolMessage, PenToolMessageDiscriminant};\npub use crate::messages::tool::tool_messages::select_tool::{SelectToolMessage, SelectToolMessageDiscriminant};\npub use crate::messages::tool::tool_messages::shape_tool::{ShapeToolMessage, ShapeToolMessageDiscriminant};\npub use crate::messages::tool::tool_messages::spline_tool::{SplineToolMessage, SplineToolMessageDiscriminant};\npub use crate::messages::tool::tool_messages::text_tool::{TextToolMessage, TextToolMessageDiscriminant};\n\n// Helper/miscellaneous\npub use crate::messages::portfolio::document::utility_types::misc::DocumentId;\npub use graphite_proc_macros::*;\npub use std::collections::{HashMap, HashSet, VecDeque};\n\npub trait Responses {\n\tfn add(&mut self, message: impl Into<Message>);\n\n\tfn add_front(&mut self, message: impl Into<Message>);\n\n\tfn try_add(&mut self, message: Option<impl Into<Message>>) {\n\t\tif let Some(message) = message {\n\t\t\tself.add(message);\n\t\t}\n\t}\n}\n\nimpl Responses for VecDeque<Message> {\n\t#[inline(always)]\n\tfn add(&mut self, message: impl Into<Message>) {\n\t\tself.push_back(message.into());\n\t}\n\n\t#[inline(always)]\n\tfn add_front(&mut self, message: impl Into<Message>) {\n\t\tself.push_front(message.into());\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/auto_panning.rs",
    "content": "use crate::consts::{DRAG_BEYOND_VIEWPORT_MAX_OVEREXTENSION_PIXELS, DRAG_BEYOND_VIEWPORT_SPEED_FACTOR};\nuse crate::messages::prelude::*;\nuse crate::messages::tool::tool_messages::tool_prelude::*;\n\n#[derive(Clone, Debug, Default)]\npub struct AutoPanning {\n\tsubscribed_to_animation_frame: bool,\n}\n\nimpl AutoPanning {\n\tpub fn start(&mut self, messages: &[Message], responses: &mut VecDeque<Message>) {\n\t\tif !self.subscribed_to_animation_frame {\n\t\t\tself.subscribed_to_animation_frame = true;\n\n\t\t\tfor message in messages {\n\t\t\t\tresponses.add(BroadcastMessage::SubscribeEvent {\n\t\t\t\t\ton: EventMessage::AnimationFrame,\n\t\t\t\t\tsend: Box::new(message.clone()),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn stop(&mut self, messages: &[Message], responses: &mut VecDeque<Message>) {\n\t\tif self.subscribed_to_animation_frame {\n\t\t\tself.subscribed_to_animation_frame = false;\n\n\t\t\tfor message in messages {\n\t\t\t\tresponses.add(BroadcastMessage::UnsubscribeEvent {\n\t\t\t\t\ton: EventMessage::AnimationFrame,\n\t\t\t\t\tsend: Box::new(message.clone()),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn setup_by_mouse_position(&mut self, input: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler, messages: &[Message], responses: &mut VecDeque<Message>) {\n\t\tlet mouse_position = input.mouse.position;\n\t\tlet viewport_size = viewport.size().into_dvec2();\n\t\tlet is_pointer_outside_edge = mouse_position.x < 0. || mouse_position.x > viewport_size.x || mouse_position.y < 0. || mouse_position.y > viewport_size.y;\n\n\t\tmatch is_pointer_outside_edge {\n\t\t\ttrue => self.start(messages, responses),\n\t\t\tfalse => self.stop(messages, responses),\n\t\t}\n\t}\n\n\t/// Shifts the viewport when the mouse reaches the edge of the viewport.\n\t///\n\t/// If the mouse was beyond any edge, it returns the amount shifted. Otherwise it returns None.\n\t/// The shift is proportional to the distance between edge and mouse, and to the duration of the frame.\n\t/// It is also guaranteed to be integral.\n\tpub fn shift_viewport(&self, input: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler, responses: &mut VecDeque<Message>) -> Option<DVec2> {\n\t\tif !self.subscribed_to_animation_frame {\n\t\t\treturn None;\n\t\t}\n\n\t\tlet viewport_size = viewport.size().into_dvec2();\n\t\tlet mouse_position = input.mouse.position.clamp(\n\t\t\tDVec2::ZERO - DVec2::splat(DRAG_BEYOND_VIEWPORT_MAX_OVEREXTENSION_PIXELS),\n\t\t\tviewport_size + DVec2::splat(DRAG_BEYOND_VIEWPORT_MAX_OVEREXTENSION_PIXELS),\n\t\t);\n\t\tlet mouse_position_percent = mouse_position / viewport_size;\n\n\t\tlet mut shift_percent = DVec2::ZERO;\n\n\t\tif mouse_position_percent.x < 0. {\n\t\t\tshift_percent.x = -mouse_position_percent.x;\n\t\t} else if mouse_position_percent.x > 1. {\n\t\t\tshift_percent.x = 1. - mouse_position_percent.x;\n\t\t}\n\n\t\tif mouse_position_percent.y < 0. {\n\t\t\tshift_percent.y = -mouse_position_percent.y;\n\t\t} else if mouse_position_percent.y > 1. {\n\t\t\tshift_percent.y = 1. - mouse_position_percent.y;\n\t\t}\n\n\t\tif shift_percent.x == 0. && shift_percent.y == 0. {\n\t\t\treturn None;\n\t\t}\n\n\t\tlet time_delta = input.frame_time.frame_duration()?.as_secs_f64();\n\t\tlet delta = (shift_percent * DRAG_BEYOND_VIEWPORT_SPEED_FACTOR * viewport_size * time_delta).round();\n\t\tresponses.add(NavigationMessage::CanvasPan { delta });\n\t\tSome(delta)\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/color_selector.rs",
    "content": "use crate::messages::layout::utility_types::widget_prelude::*;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::prelude::*;\nuse graphene_std::Color;\nuse graphene_std::vector::style::FillChoice;\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum ToolColorType {\n\tPrimary,\n\tSecondary,\n\tCustom,\n}\n\n/// Color selector widgets seen in [`LayoutTarget::ToolOptions`] bar.\npub struct ToolColorOptions {\n\tpub custom_color: Option<Color>,\n\tpub primary_working_color: Option<Color>,\n\tpub secondary_working_color: Option<Color>,\n\tpub color_type: ToolColorType,\n}\n\nimpl Default for ToolColorOptions {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tcolor_type: ToolColorType::Primary,\n\t\t\tcustom_color: Some(Color::BLACK),\n\t\t\tprimary_working_color: Some(Color::BLACK),\n\t\t\tsecondary_working_color: Some(Color::WHITE),\n\t\t}\n\t}\n}\n\nimpl ToolColorOptions {\n\tpub fn new_primary() -> Self {\n\t\tSelf::default()\n\t}\n\n\tpub fn new_secondary() -> Self {\n\t\tSelf {\n\t\t\tcolor_type: ToolColorType::Secondary,\n\t\t\t..Default::default()\n\t\t}\n\t}\n\n\tpub fn new_none() -> Self {\n\t\tSelf {\n\t\t\tcolor_type: ToolColorType::Custom,\n\t\t\tcustom_color: None,\n\t\t\t..Default::default()\n\t\t}\n\t}\n\n\tpub fn active_color(&self) -> Option<Color> {\n\t\tmatch self.color_type {\n\t\t\tToolColorType::Custom => self.custom_color,\n\t\t\tToolColorType::Primary => self.primary_working_color,\n\t\t\tToolColorType::Secondary => self.secondary_working_color,\n\t\t}\n\t}\n\n\tpub fn apply_fill(&self, layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>) {\n\t\tif let Some(color) = self.active_color() {\n\t\t\tlet fill = graphene_std::vector::style::Fill::solid(color.to_gamma_srgb());\n\t\t\tresponses.add(GraphOperationMessage::FillSet { layer, fill });\n\t\t}\n\t}\n\n\tpub fn apply_stroke(&self, weight: f64, layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>) {\n\t\tif let Some(color) = self.active_color() {\n\t\t\tlet stroke = graphene_std::vector::style::Stroke::new(Some(color.to_gamma_srgb()), weight);\n\t\t\tresponses.add(GraphOperationMessage::StrokeSet { layer, stroke });\n\t\t}\n\t}\n\n\tpub fn create_widgets(\n\t\t&self,\n\t\tlabel_text: impl Into<String>,\n\t\tcolor_allow_none: bool,\n\t\treset_callback: impl Fn(&IconButton) -> Message + 'static + Send + Sync,\n\t\tradio_callback: fn(ToolColorType) -> WidgetCallback<()>,\n\t\tcolor_callback: impl Fn(&ColorInput) -> Message + 'static + Send + Sync,\n\t) -> Vec<WidgetInstance> {\n\t\tlet mut widgets = vec![TextLabel::new(label_text).widget_instance()];\n\n\t\tif !color_allow_none {\n\t\t\twidgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\t\t} else {\n\t\t\tlet reset = IconButton::new(\"CloseX\", 12)\n\t\t\t\t.disabled(self.custom_color.is_none() && self.color_type == ToolColorType::Custom)\n\t\t\t\t.tooltip_label(\"Clear Color\")\n\t\t\t\t.on_update(reset_callback);\n\n\t\t\twidgets.push(Separator::new(SeparatorStyle::Related).widget_instance());\n\t\t\twidgets.push(reset.widget_instance());\n\t\t\twidgets.push(Separator::new(SeparatorStyle::Related).widget_instance());\n\t\t};\n\n\t\tlet entries = vec![\n\t\t\t(\"WorkingColorsPrimary\", \"Primary Working Color\", ToolColorType::Primary),\n\t\t\t(\"WorkingColorsSecondary\", \"Secondary Working Color\", ToolColorType::Secondary),\n\t\t\t(\"CustomColor\", \"Custom Color\", ToolColorType::Custom),\n\t\t]\n\t\t.into_iter()\n\t\t.map(|(icon, label, color_type)| {\n\t\t\tlet mut entry = RadioEntryData::new(format!(\"{color_type:?}\")).tooltip_label(label).icon(icon);\n\t\t\tentry.on_update = radio_callback(color_type);\n\t\t\tentry\n\t\t})\n\t\t.collect();\n\t\tlet radio = RadioInput::new(entries).selected_index(Some(self.color_type.clone() as u32)).widget_instance();\n\t\twidgets.push(radio);\n\t\twidgets.push(Separator::new(SeparatorStyle::Related).widget_instance());\n\n\t\tlet fill_choice = match self.active_color() {\n\t\t\tSome(color) => FillChoice::Solid(color.to_gamma_srgb()),\n\t\t\tNone => FillChoice::None,\n\t\t};\n\t\tlet color_button = ColorInput::new(fill_choice).allow_none(color_allow_none).on_update(color_callback);\n\t\twidgets.push(color_button.widget_instance());\n\n\t\twidgets\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/compass_rose.rs",
    "content": "use crate::consts::{COMPASS_ROSE_ARROW_CLICK_TARGET_ANGLE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER};\nuse crate::messages::prelude::DocumentMessageHandler;\nuse glam::{DAffine2, DVec2};\nuse std::f64::consts::FRAC_PI_2;\n\n#[derive(Clone, Default, Debug)]\npub struct CompassRose {\n\tcompass_center: DVec2,\n}\n\nimpl CompassRose {\n\tpub fn refresh_position(&mut self, document: &DocumentMessageHandler) {\n\t\tlet selected = document.network_interface.selected_nodes();\n\n\t\tif !selected.has_selected_nodes() {\n\t\t\treturn;\n\t\t}\n\n\t\tlet transform = selected\n\t\t\t.selected_visible_and_unlocked_layers(&document.network_interface)\n\t\t\t.find(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]))\n\t\t\t.map(|layer| document.metadata().transform_to_viewport_with_first_transform_node_if_group(layer, &document.network_interface))\n\t\t\t.unwrap_or_default();\n\n\t\tlet bounds = document\n\t\t\t.network_interface\n\t\t\t.selected_nodes()\n\t\t\t.selected_visible_and_unlocked_layers(&document.network_interface)\n\t\t\t.filter_map(|layer| {\n\t\t\t\tif transform.matrix2.determinant().abs() <= f64::EPSILON {\n\t\t\t\t\treturn None;\n\t\t\t\t}\n\t\t\t\tdocument\n\t\t\t\t\t.metadata()\n\t\t\t\t\t.bounding_box_with_transform(layer, transform.inverse() * document.metadata().transform_to_viewport(layer))\n\t\t\t})\n\t\t\t.reduce(graphene_std::renderer::Quad::combine_bounds);\n\n\t\tlet [min, max] = bounds.unwrap_or([DVec2::ZERO, DVec2::ONE]);\n\t\tlet transform = transform * DAffine2::from_translation(min) * DAffine2::from_scale(max - min);\n\n\t\tself.compass_center = transform.transform_point2(DVec2::splat(0.5));\n\t}\n\n\tpub fn compass_rose_position(&self) -> DVec2 {\n\t\tself.compass_center\n\t}\n\n\tpub fn compass_rose_state(&self, mouse: DVec2, angle: f64) -> CompassRoseState {\n\t\tconst COMPASS_ROSE_RING_INNER_RADIUS_SQUARED: f64 = (COMPASS_ROSE_RING_INNER_DIAMETER / 2.) * (COMPASS_ROSE_RING_INNER_DIAMETER / 2.);\n\t\tconst COMPASS_ROSE_HOVER_RING_RADIUS_SQUARED: f64 = (COMPASS_ROSE_HOVER_RING_DIAMETER / 2.) * (COMPASS_ROSE_HOVER_RING_DIAMETER / 2.);\n\n\t\tlet compass_distance_squared = mouse.distance_squared(self.compass_center);\n\n\t\tif !(COMPASS_ROSE_RING_INNER_RADIUS_SQUARED..COMPASS_ROSE_HOVER_RING_RADIUS_SQUARED).contains(&compass_distance_squared) {\n\t\t\treturn CompassRoseState::None;\n\t\t}\n\n\t\tlet angle = (mouse - self.compass_center).angle_to(DVec2::from_angle(angle)).abs();\n\t\tlet resolved_angle = (FRAC_PI_2 - angle).abs();\n\t\tlet angular_width = COMPASS_ROSE_ARROW_CLICK_TARGET_ANGLE.to_radians();\n\n\t\tif resolved_angle < angular_width {\n\t\t\tCompassRoseState::AxisY\n\t\t} else if resolved_angle > (FRAC_PI_2 - angular_width) {\n\t\t\tCompassRoseState::AxisX\n\t\t} else {\n\t\t\tCompassRoseState::Ring\n\t\t}\n\t}\n}\n\n#[derive(Clone, Debug, PartialEq)]\npub enum CompassRoseState {\n\tRing,\n\tAxisX,\n\tAxisY,\n\tNone,\n}\n\nimpl CompassRoseState {\n\tpub fn can_grab(&self) -> bool {\n\t\tmatches!(self, Self::Ring | Self::AxisX | Self::AxisY)\n\t}\n\n\tpub fn is_ring(&self) -> bool {\n\t\tmatches!(self, Self::Ring)\n\t}\n\n\tpub fn axis_type(&self) -> Option<Axis> {\n\t\tmatch self {\n\t\t\tCompassRoseState::AxisX => Some(Axis::X),\n\t\t\tCompassRoseState::AxisY => Some(Axis::Y),\n\t\t\tCompassRoseState::Ring => Some(Axis::None),\n\t\t\t_ => None,\n\t\t}\n\t}\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]\npub enum Axis {\n\t#[default]\n\tNone,\n\tX,\n\tY,\n}\n\nimpl Axis {\n\tpub fn is_constraint(&self) -> bool {\n\t\tmatches!(self, Self::X | Self::Y)\n\t}\n}\n\nimpl From<Axis> for DVec2 {\n\tfn from(value: Axis) -> Self {\n\t\tmatch value {\n\t\t\tAxis::X => DVec2::X,\n\t\t\tAxis::Y => DVec2::Y,\n\t\t\tAxis::None => DVec2::ZERO,\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs",
    "content": "use crate::messages::frontend::utility_types::MouseCursorIcon;\nuse crate::messages::message::Message;\nuse crate::messages::portfolio::document::overlays::utility_types::OverlayContext;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler};\nuse crate::messages::tool::common_functionality::graph_modification_utils;\nuse crate::messages::tool::common_functionality::shape_editor::ShapeState;\nuse crate::messages::tool::common_functionality::shapes::arc_shape::ArcGizmoHandler;\nuse crate::messages::tool::common_functionality::shapes::circle_shape::CircleGizmoHandler;\nuse crate::messages::tool::common_functionality::shapes::grid_shape::GridGizmoHandler;\nuse crate::messages::tool::common_functionality::shapes::polygon_shape::PolygonGizmoHandler;\nuse crate::messages::tool::common_functionality::shapes::shape_utility::ShapeGizmoHandler;\nuse crate::messages::tool::common_functionality::shapes::spiral_shape::SpiralGizmoHandler;\nuse crate::messages::tool::common_functionality::shapes::star_shape::StarGizmoHandler;\nuse glam::DVec2;\nuse std::collections::VecDeque;\n\n/// A unified enum wrapper around all available shape-specific gizmo handlers.\n///\n/// This abstraction allows `GizmoManager` to interact with different shape gizmos (like Star or Polygon)\n/// using a common interface without needing to know the specific shape type at compile time.\n///\n/// Each variant stores a concrete handler (e.g., `StarGizmoHandler`, `PolygonGizmoHandler`) that implements\n/// the shape-specific logic for rendering overlays, responding to input, and modifying shape parameters.\n#[derive(Clone, Debug, Default)]\npub enum ShapeGizmoHandlers {\n\t#[default]\n\tNone,\n\tStar(StarGizmoHandler),\n\tPolygon(PolygonGizmoHandler),\n\tArc(ArcGizmoHandler),\n\tCircle(CircleGizmoHandler),\n\tGrid(GridGizmoHandler),\n\tSpiral(SpiralGizmoHandler),\n}\n\nimpl ShapeGizmoHandlers {\n\t/// Returns the kind of shape the handler is managing, such as `\"star\"` or `\"polygon\"`.\n\t/// Used for grouping logic and distinguishing between handler types at runtime.\n\tpub fn kind(&self) -> &'static str {\n\t\tmatch self {\n\t\t\tSelf::Star(_) => \"star\",\n\t\t\tSelf::Polygon(_) => \"polygon\",\n\t\t\tSelf::Arc(_) => \"arc\",\n\t\t\tSelf::Circle(_) => \"circle\",\n\t\t\tSelf::Grid(_) => \"grid\",\n\t\t\tSelf::Spiral(_) => \"spiral\",\n\t\t\tSelf::None => \"none\",\n\t\t}\n\t}\n\n\t/// Dispatches interaction state updates to the corresponding shape-specific handler.\n\tpub fn handle_state(&mut self, layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {\n\t\tmatch self {\n\t\t\tSelf::Star(h) => h.handle_state(layer, mouse_position, document, responses),\n\t\t\tSelf::Polygon(h) => h.handle_state(layer, mouse_position, document, responses),\n\t\t\tSelf::Arc(h) => h.handle_state(layer, mouse_position, document, responses),\n\t\t\tSelf::Circle(h) => h.handle_state(layer, mouse_position, document, responses),\n\t\t\tSelf::Grid(h) => h.handle_state(layer, mouse_position, document, responses),\n\t\t\tSelf::Spiral(h) => h.handle_state(layer, mouse_position, document, responses),\n\t\t\tSelf::None => {}\n\t\t}\n\t}\n\n\t/// Checks if any interactive part of the gizmo is currently hovered.\n\tpub fn is_any_gizmo_hovered(&self) -> bool {\n\t\tmatch self {\n\t\t\tSelf::Star(h) => h.is_any_gizmo_hovered(),\n\t\t\tSelf::Polygon(h) => h.is_any_gizmo_hovered(),\n\t\t\tSelf::Arc(h) => h.is_any_gizmo_hovered(),\n\t\t\tSelf::Circle(h) => h.is_any_gizmo_hovered(),\n\t\t\tSelf::Grid(h) => h.is_any_gizmo_hovered(),\n\t\t\tSelf::Spiral(h) => h.is_any_gizmo_hovered(),\n\t\t\tSelf::None => false,\n\t\t}\n\t}\n\n\t/// Passes the click interaction to the appropriate gizmo handler if one is hovered.\n\tpub fn handle_click(&mut self) {\n\t\tmatch self {\n\t\t\tSelf::Star(h) => h.handle_click(),\n\t\t\tSelf::Polygon(h) => h.handle_click(),\n\t\t\tSelf::Arc(h) => h.handle_click(),\n\t\t\tSelf::Circle(h) => h.handle_click(),\n\t\t\tSelf::Grid(h) => h.handle_click(),\n\t\t\tSelf::Spiral(h) => h.handle_click(),\n\t\t\tSelf::None => {}\n\t\t}\n\t}\n\n\t/// Updates the gizmo state while the user is dragging a handle (e.g., adjusting radius).\n\tpub fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {\n\t\tmatch self {\n\t\t\tSelf::Star(h) => h.handle_update(drag_start, document, input, responses),\n\t\t\tSelf::Polygon(h) => h.handle_update(drag_start, document, input, responses),\n\t\t\tSelf::Arc(h) => h.handle_update(drag_start, document, input, responses),\n\t\t\tSelf::Circle(h) => h.handle_update(drag_start, document, input, responses),\n\t\t\tSelf::Grid(h) => h.handle_update(drag_start, document, input, responses),\n\t\t\tSelf::Spiral(h) => h.handle_update(drag_start, document, input, responses),\n\t\t\tSelf::None => {}\n\t\t}\n\t}\n\n\t/// Cleans up any state used by the gizmo handler.\n\tpub fn cleanup(&mut self) {\n\t\tmatch self {\n\t\t\tSelf::Star(h) => h.cleanup(),\n\t\t\tSelf::Polygon(h) => h.cleanup(),\n\t\t\tSelf::Arc(h) => h.cleanup(),\n\t\t\tSelf::Circle(h) => h.cleanup(),\n\t\t\tSelf::Grid(h) => h.cleanup(),\n\t\t\tSelf::Spiral(h) => h.cleanup(),\n\t\t\tSelf::None => {}\n\t\t}\n\t}\n\n\t/// Draws overlays like control points or outlines for the shape handled by this gizmo.\n\tpub fn overlays(\n\t\t&self,\n\t\tdocument: &DocumentMessageHandler,\n\t\tlayer: Option<LayerNodeIdentifier>,\n\t\tinput: &InputPreprocessorMessageHandler,\n\t\tshape_editor: &mut &mut ShapeState,\n\t\tmouse_position: DVec2,\n\t\toverlay_context: &mut OverlayContext,\n\t) {\n\t\tmatch self {\n\t\t\tSelf::Star(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),\n\t\t\tSelf::Polygon(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),\n\t\t\tSelf::Arc(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),\n\t\t\tSelf::Circle(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),\n\t\t\tSelf::Grid(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),\n\t\t\tSelf::Spiral(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),\n\t\t\tSelf::None => {}\n\t\t}\n\t}\n\n\t/// Draws live-updating overlays during drag interactions for the shape handled by this gizmo.\n\tpub fn dragging_overlays(\n\t\t&self,\n\t\tdocument: &DocumentMessageHandler,\n\t\tinput: &InputPreprocessorMessageHandler,\n\t\tshape_editor: &mut &mut ShapeState,\n\t\tmouse_position: DVec2,\n\t\toverlay_context: &mut OverlayContext,\n\t) {\n\t\tmatch self {\n\t\t\tSelf::Star(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),\n\t\t\tSelf::Polygon(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),\n\t\t\tSelf::Arc(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),\n\t\t\tSelf::Circle(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),\n\t\t\tSelf::Grid(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),\n\t\t\tSelf::Spiral(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),\n\t\t\tSelf::None => {}\n\t\t}\n\t}\n\n\tpub fn gizmo_cursor_icon(&self) -> Option<MouseCursorIcon> {\n\t\tmatch self {\n\t\t\tSelf::Star(h) => h.mouse_cursor_icon(),\n\t\t\tSelf::Polygon(h) => h.mouse_cursor_icon(),\n\t\t\tSelf::Arc(h) => h.mouse_cursor_icon(),\n\t\t\tSelf::Circle(h) => h.mouse_cursor_icon(),\n\t\t\tSelf::Grid(h) => h.mouse_cursor_icon(),\n\t\t\tSelf::Spiral(h) => h.mouse_cursor_icon(),\n\t\t\tSelf::None => None,\n\t\t}\n\t}\n}\n\n/// Central manager that coordinates shape gizmo handlers for interactive editing on the canvas.\n///\n/// The `GizmoManager` is responsible for detecting which shapes are selected, activating the appropriate\n/// shape-specific gizmo, and routing user interactions (hover, click, drag) to the correct handler.\n/// It allows editing multiple shapes of the same type or focusing on a single active shape when a gizmo is hovered.\n///\n/// ## Responsibilities:\n/// - Detect which selected layers support shape gizmos (e.g., stars, polygons)\n/// - Activate the correct handler and manage state between frames\n/// - Route click, hover, and drag events to the proper shape gizmo\n/// - Render overlays and dragging visuals\n#[derive(Clone, Debug, Default)]\npub struct GizmoManager {\n\tactive_shape_handler: Option<ShapeGizmoHandlers>,\n\tlayers_handlers: Vec<(ShapeGizmoHandlers, Vec<LayerNodeIdentifier>)>,\n}\n\nimpl GizmoManager {\n\t/// Detects and returns a shape gizmo handler based on the layer type (e.g., star, polygon).\n\t///\n\t/// Returns `None` if the given layer does not represent a shape with a registered gizmo.\n\tpub fn detect_shape_handler(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Option<ShapeGizmoHandlers> {\n\t\t// Star\n\t\tif graph_modification_utils::get_star_id(layer, &document.network_interface).is_some() {\n\t\t\treturn Some(ShapeGizmoHandlers::Star(StarGizmoHandler::default()));\n\t\t}\n\t\t// Polygon\n\t\tif graph_modification_utils::get_polygon_id(layer, &document.network_interface).is_some() {\n\t\t\treturn Some(ShapeGizmoHandlers::Polygon(PolygonGizmoHandler::default()));\n\t\t}\n\t\t// Arc\n\t\tif graph_modification_utils::get_arc_id(layer, &document.network_interface).is_some() {\n\t\t\treturn Some(ShapeGizmoHandlers::Arc(ArcGizmoHandler::new()));\n\t\t}\n\t\t// Circle\n\t\tif graph_modification_utils::get_circle_id(layer, &document.network_interface).is_some() {\n\t\t\treturn Some(ShapeGizmoHandlers::Circle(CircleGizmoHandler::default()));\n\t\t}\n\t\t// Grid\n\t\tif graph_modification_utils::get_grid_id(layer, &document.network_interface).is_some() {\n\t\t\treturn Some(ShapeGizmoHandlers::Grid(GridGizmoHandler::default()));\n\t\t}\n\t\t// Spiral\n\t\tif graph_modification_utils::get_spiral_id(layer, &document.network_interface).is_some() {\n\t\t\treturn Some(ShapeGizmoHandlers::Spiral(SpiralGizmoHandler::default()));\n\t\t}\n\n\t\tNone\n\t}\n\n\t/// Returns `true` if a gizmo is currently active (hovered or being interacted with).\n\tpub fn hovering_over_gizmo(&self) -> bool {\n\t\tself.active_shape_handler.is_some()\n\t}\n\n\t/// Called every frame to check selected layers and update the active shape gizmo, if hovered.\n\t///\n\t/// Also groups all shape layers with the same kind of gizmo to support overlays for multi-shape editing.\n\tpub fn handle_actions(&mut self, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {\n\t\tlet mut handlers_layer: Vec<(ShapeGizmoHandlers, Vec<LayerNodeIdentifier>)> = Vec::new();\n\n\t\tfor layer in document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface) {\n\t\t\tif let Some(mut handler) = Self::detect_shape_handler(layer, document) {\n\t\t\t\thandler.handle_state(layer, mouse_position, document, responses);\n\t\t\t\tlet is_hovered = handler.is_any_gizmo_hovered();\n\n\t\t\t\tif is_hovered {\n\t\t\t\t\tself.layers_handlers.clear();\n\t\t\t\t\tself.active_shape_handler = Some(handler);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Try to group this handler with others of the same type\n\t\t\t\tif let Some((_, layers)) = handlers_layer.iter_mut().find(|(existing_handler, _)| existing_handler.kind() == handler.kind()) {\n\t\t\t\t\tlayers.push(layer);\n\t\t\t\t} else {\n\t\t\t\t\thandlers_layer.push((handler, vec![layer]));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tself.layers_handlers = handlers_layer;\n\t\tself.active_shape_handler = None;\n\t}\n\n\t/// Handles click interactions if a gizmo is active. Returns `true` if a gizmo handled the click.\n\tpub fn handle_click(&mut self) -> bool {\n\t\tif let Some(handle) = &mut self.active_shape_handler {\n\t\t\thandle.handle_click();\n\t\t\treturn true;\n\t\t}\n\t\tfalse\n\t}\n\n\tpub fn handle_cleanup(&mut self) {\n\t\tif let Some(handle) = &mut self.active_shape_handler {\n\t\t\thandle.cleanup();\n\t\t}\n\t}\n\n\t/// Passes drag update data to the active gizmo to update shape parameters live.\n\tpub fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {\n\t\tif let Some(handle) = &mut self.active_shape_handler {\n\t\t\thandle.handle_update(drag_start, document, input, responses);\n\t\t}\n\t}\n\n\t/// Draws overlays for the currently active shape gizmo during a drag interaction.\n\tpub fn dragging_overlays(\n\t\t&self,\n\t\tdocument: &DocumentMessageHandler,\n\t\tinput: &InputPreprocessorMessageHandler,\n\t\tshape_editor: &mut &mut ShapeState,\n\t\tmouse_position: DVec2,\n\t\toverlay_context: &mut OverlayContext,\n\t) {\n\t\tif let Some(handle) = &self.active_shape_handler {\n\t\t\thandle.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context);\n\t\t}\n\t}\n\n\t/// Draws overlays for either the active gizmo (if hovered) or all grouped selected gizmos.\n\t///\n\t/// If no single gizmo is active, it renders overlays for all grouped layers with associated handlers.\n\tpub fn overlays(\n\t\t&self,\n\t\tdocument: &DocumentMessageHandler,\n\t\tinput: &InputPreprocessorMessageHandler,\n\t\tshape_editor: &mut &mut ShapeState,\n\t\tmouse_position: DVec2,\n\t\toverlay_context: &mut OverlayContext,\n\t) {\n\t\tif let Some(handler) = &self.active_shape_handler {\n\t\t\thandler.overlays(document, None, input, shape_editor, mouse_position, overlay_context);\n\t\t\treturn;\n\t\t}\n\n\t\tfor (handler, selected_layers) in &self.layers_handlers {\n\t\t\tfor layer in selected_layers {\n\t\t\t\thandler.overlays(document, Some(*layer), input, shape_editor, mouse_position, overlay_context);\n\t\t\t}\n\t\t}\n\t}\n\n\t/// Returns the cursor icon to display when hovering or dragging a gizmo.\n\t///\n\t/// If a gizmo is active (hovered or being manipulated), it returns the cursor icon associated with that gizmo;\n\t/// otherwise, returns `None` to indicate the default crosshair cursor should be used.\n\tpub fn mouse_cursor_icon(&self) -> Option<MouseCursorIcon> {\n\t\tself.active_shape_handler.as_ref().and_then(|h| h.gizmo_cursor_icon())\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/gizmos/mod.rs",
    "content": "pub mod gizmo_manager;\npub mod shape_gizmos;\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/circle_arc_radius_handle.rs",
    "content": "use crate::consts::GIZMO_HIDE_THRESHOLD;\nuse crate::messages::frontend::utility_types::MouseCursorIcon;\nuse crate::messages::message::Message;\nuse crate::messages::portfolio::document::overlays::utility_types::OverlayContext;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::portfolio::document::utility_types::network_interface::InputConnector;\nuse crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler, NodeGraphMessage};\nuse crate::messages::prelude::{FrontendMessage, Responses};\nuse crate::messages::tool::common_functionality::graph_modification_utils::{self, get_arc_id, get_stroke_width};\nuse crate::messages::tool::common_functionality::shapes::shape_utility::{extract_arc_parameters, extract_circle_radius};\nuse glam::{DAffine2, DVec2};\nuse graph_craft::document::NodeInput;\nuse graph_craft::document::value::TaggedValue;\nuse std::collections::VecDeque;\nuse std::f64::consts::FRAC_PI_2;\n\n#[derive(Clone, Debug, Default, PartialEq)]\npub enum RadiusHandleState {\n\t#[default]\n\tInactive,\n\tHover,\n\tDragging,\n}\n\n#[derive(Clone, Debug, Default, PartialEq)]\npub struct RadiusHandle {\n\tpub layer: Option<LayerNodeIdentifier>,\n\tinitial_radius: f64,\n\thandle_state: RadiusHandleState,\n\tangle: f64,\n\tprevious_mouse_position: DVec2,\n}\n\nimpl RadiusHandle {\n\tpub fn cleanup(&mut self) {\n\t\tself.handle_state = RadiusHandleState::Inactive;\n\t\tself.layer = None;\n\t}\n\n\tpub fn hovered(&self) -> bool {\n\t\tself.handle_state == RadiusHandleState::Hover\n\t}\n\n\tpub fn is_dragging(&self) -> bool {\n\t\tself.handle_state == RadiusHandleState::Dragging\n\t}\n\n\tpub fn update_state(&mut self, state: RadiusHandleState) {\n\t\tself.handle_state = state;\n\t}\n\n\tpub fn check_if_inside_dash_lines(angle: f64, mouse_position: DVec2, viewport: DAffine2, radius: f64, document: &DocumentMessageHandler, layer: LayerNodeIdentifier) -> bool {\n\t\tlet center = viewport.transform_point2(DVec2::ZERO);\n\t\tif let Some(stroke_width) = get_stroke_width(layer, &document.network_interface) {\n\t\t\tlet circle_point = calculate_circle_point_position(angle, radius.abs());\n\t\t\tlet Some(direction) = circle_point.try_normalize() else { return false };\n\t\t\tlet mouse_distance = mouse_position.distance(center);\n\n\t\t\tlet spacing = Self::calculate_extra_spacing(viewport, radius, center, stroke_width, 15.);\n\n\t\t\tlet inner_point = viewport.transform_point2(circle_point - direction * spacing).distance(center);\n\t\t\tlet outer_point = viewport.transform_point2(circle_point + direction * spacing).distance(center);\n\n\t\t\tmouse_distance >= inner_point && mouse_distance <= outer_point\n\t\t} else {\n\t\t\tlet point_position = viewport.transform_point2(calculate_circle_point_position(angle, radius.abs()));\n\t\t\tmouse_position.distance(center) <= point_position.distance(center)\n\t\t}\n\t}\n\n\tfn calculate_extra_spacing(viewport: DAffine2, radius: f64, viewport_center: DVec2, stroke_width: f64, threshold: f64) -> f64 {\n\t\tlet start_point = viewport.transform_point2(calculate_circle_point_position(0., radius)).distance(viewport_center);\n\t\tlet end_point = viewport.transform_point2(calculate_circle_point_position(FRAC_PI_2, radius)).distance(viewport_center);\n\t\tlet min_radius = start_point.min(end_point);\n\t\tlet extra_spacing = if min_radius < threshold { 10. * (min_radius / threshold) } else { 10. };\n\n\t\tstroke_width + extra_spacing\n\t}\n\n\tpub fn handle_actions(&mut self, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, mouse_position: DVec2, responses: &mut VecDeque<Message>) {\n\t\tmatch &self.handle_state {\n\t\t\tRadiusHandleState::Inactive => {\n\t\t\t\tlet Some(radius) = extract_circle_radius(layer, document).or(extract_arc_parameters(Some(layer), document).map(|(r, _, _, _)| r)) else {\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tlet viewport = document.metadata().transform_to_viewport(layer);\n\t\t\t\tlet angle = viewport.inverse().transform_point2(mouse_position).angle_to(DVec2::X);\n\t\t\t\tlet point_position = viewport.transform_point2(calculate_circle_point_position(angle, radius.abs()));\n\t\t\t\tlet center = viewport.transform_point2(DVec2::ZERO);\n\n\t\t\t\tif point_position.distance(center) < GIZMO_HIDE_THRESHOLD {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif Self::check_if_inside_dash_lines(angle, mouse_position, viewport, radius.abs(), document, layer) {\n\t\t\t\t\tself.layer = Some(layer);\n\t\t\t\t\tself.initial_radius = radius;\n\t\t\t\t\tself.previous_mouse_position = mouse_position;\n\t\t\t\t\tself.angle = angle;\n\n\t\t\t\t\tself.update_state(RadiusHandleState::Hover);\n\n\t\t\t\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::EWResize });\n\t\t\t\t}\n\t\t\t}\n\t\t\tRadiusHandleState::Dragging | RadiusHandleState::Hover => {}\n\t\t}\n\t}\n\n\tpub fn overlays(&self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) {\n\t\tmatch &self.handle_state {\n\t\t\tRadiusHandleState::Inactive => {}\n\t\t\tRadiusHandleState::Dragging | RadiusHandleState::Hover => {\n\t\t\t\tlet Some(layer) = self.layer else { return };\n\t\t\t\tlet Some(radius) = extract_circle_radius(layer, document).or(extract_arc_parameters(Some(layer), document).map(|(r, _, _, _)| r)) else {\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tlet viewport = document.metadata().transform_to_viewport(layer);\n\t\t\t\tlet center = viewport.transform_point2(DVec2::ZERO);\n\n\t\t\t\tlet x_point = viewport.transform_point2(calculate_circle_point_position(0., radius));\n\t\t\t\tlet y_point = viewport.transform_point2(calculate_circle_point_position(FRAC_PI_2, radius));\n\n\t\t\t\tlet direction_x = viewport.transform_vector2(DVec2::X);\n\t\t\t\tlet direction_y = viewport.transform_vector2(-DVec2::Y);\n\n\t\t\t\tif let Some(stroke_width) = get_stroke_width(layer, &document.network_interface) {\n\t\t\t\t\tlet spacing = Self::calculate_extra_spacing(viewport, radius, center, stroke_width, 15.);\n\t\t\t\t\tlet smaller_radius_x = (x_point - direction_x * spacing).distance(center);\n\t\t\t\t\tlet smaller_radius_y = (y_point - direction_y * spacing).distance(center);\n\n\t\t\t\t\tlet larger_radius_x = (x_point + direction_x * spacing).distance(center);\n\t\t\t\t\tlet larger_radius_y = (y_point + direction_y * spacing).distance(center);\n\n\t\t\t\t\toverlay_context.dashed_ellipse(center, smaller_radius_x, smaller_radius_y, None, None, None, None, None, None, Some(4.), Some(4.), Some(0.5));\n\t\t\t\t\toverlay_context.dashed_ellipse(center, larger_radius_x, larger_radius_y, None, None, None, None, None, None, Some(4.), Some(4.), Some(0.5));\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet radius_x = x_point.distance(center);\n\t\t\t\tlet radius_y = y_point.distance(center);\n\t\t\t\toverlay_context.dashed_ellipse(center, radius_x, radius_y, None, None, None, None, None, None, Some(4.), Some(4.), Some(0.5));\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn update_inner_radius(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>, drag_start: DVec2) {\n\t\tlet Some(layer) = self.layer else { return };\n\t\tlet Some(node_id) = graph_modification_utils::get_circle_id(layer, &document.network_interface).or(get_arc_id(layer, &document.network_interface)) else {\n\t\t\treturn;\n\t\t};\n\t\tlet Some(current_radius) = extract_circle_radius(layer, document).or(extract_arc_parameters(Some(layer), document).map(|(r, _, _, _)| r)) else {\n\t\t\treturn;\n\t\t};\n\n\t\tlet viewport_transform = document.network_interface.document_metadata().transform_to_viewport(layer);\n\t\tlet center = viewport_transform.transform_point2(DVec2::ZERO);\n\n\t\tlet delta_vector = viewport_transform.inverse().transform_point2(input.mouse.position) - viewport_transform.inverse().transform_point2(self.previous_mouse_position);\n\t\tlet radius = drag_start - center;\n\t\tlet sign = radius.dot(delta_vector).signum();\n\n\t\tlet net_delta = delta_vector.length() * sign * self.initial_radius.signum();\n\t\tself.previous_mouse_position = input.mouse.position;\n\n\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\tinput_connector: InputConnector::node(node_id, 1),\n\t\t\tinput: NodeInput::value(TaggedValue::F64(current_radius + net_delta), false),\n\t\t});\n\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t}\n}\n\nfn calculate_circle_point_position(theta: f64, radius: f64) -> DVec2 {\n\tDVec2::new(radius * theta.cos(), -radius * theta.sin())\n}\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_rows_columns_gizmo.rs",
    "content": "use crate::consts::GRID_ROW_COLUMN_GIZMO_OFFSET;\nuse crate::messages::frontend::utility_types::MouseCursorIcon;\nuse crate::messages::message::Message;\nuse crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;\nuse crate::messages::portfolio::document::overlays::utility_types::OverlayContext;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::portfolio::document::utility_types::network_interface::InputConnector;\nuse crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler, NodeGraphMessage};\nuse crate::messages::prelude::{GraphOperationMessage, Responses};\nuse crate::messages::tool::common_functionality::graph_modification_utils;\nuse crate::messages::tool::common_functionality::shape_editor::ShapeState;\nuse crate::messages::tool::common_functionality::shapes::shape_utility::extract_grid_parameters;\nuse glam::{DAffine2, DVec2};\nuse graph_craft::document::NodeInput;\nuse graph_craft::document::value::TaggedValue;\nuse graphene_std::NodeInputDecleration;\nuse graphene_std::vector::misc::{GridType, dvec2_to_point, get_line_endpoints};\nuse kurbo::{Line, ParamCurveNearest, Rect};\nuse std::collections::VecDeque;\n\n#[derive(Clone, Debug, Default, PartialEq)]\npub enum RowColumnGizmoState {\n\t#[default]\n\tInactive,\n\tHover,\n\tDragging,\n}\n\n#[derive(Clone, Debug, Default)]\npub struct RowColumnGizmo {\n\tpub layer: Option<LayerNodeIdentifier>,\n\tpub gizmo_type: RowColumnGizmoType,\n\tinitial_rows: u32,\n\tinitial_columns: u32,\n\tspacing: DVec2,\n\tinitial_mouse_start: Option<DVec2>,\n\tgizmo_state: RowColumnGizmoState,\n}\n\nimpl RowColumnGizmo {\n\tpub fn cleanup(&mut self) {\n\t\tself.layer = None;\n\t\tself.gizmo_state = RowColumnGizmoState::Inactive;\n\t\tself.initial_mouse_start = None;\n\t}\n\n\tpub fn update_state(&mut self, state: RowColumnGizmoState) {\n\t\tself.gizmo_state = state;\n\t}\n\n\tpub fn is_hovered(&self) -> bool {\n\t\tself.gizmo_state == RowColumnGizmoState::Hover\n\t}\n\n\tpub fn is_dragging(&self) -> bool {\n\t\tself.gizmo_state == RowColumnGizmoState::Dragging\n\t}\n\n\tfn initial_dimension(&self) -> u32 {\n\t\tmatch &self.gizmo_type {\n\t\t\tRowColumnGizmoType::Top | RowColumnGizmoType::Bottom => self.initial_rows,\n\t\t\tRowColumnGizmoType::Left | RowColumnGizmoType::Right => self.initial_columns,\n\t\t\tRowColumnGizmoType::None => panic!(\"RowColumnGizmoType::None does not have a mouse_icon\"),\n\t\t}\n\t}\n\n\tpub fn handle_actions(&mut self, layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler) {\n\t\tlet Some((grid_type, spacing, columns, rows, angles)) = extract_grid_parameters(layer, document) else {\n\t\t\treturn;\n\t\t};\n\t\tlet viewport = document.metadata().transform_to_viewport(layer);\n\n\t\tif let Some(gizmo_type) = check_if_over_gizmo(grid_type, columns, rows, spacing, angles, mouse_position, viewport) {\n\t\t\tself.layer = Some(layer);\n\t\t\tself.gizmo_type = gizmo_type;\n\t\t\tself.initial_rows = rows;\n\t\t\tself.initial_columns = columns;\n\t\t\tself.spacing = spacing;\n\t\t\tself.initial_mouse_start = None;\n\t\t\tself.update_state(RowColumnGizmoState::Hover);\n\t\t}\n\t}\n\n\tpub fn overlays(&self, document: &DocumentMessageHandler, layer: Option<LayerNodeIdentifier>, _shape_editor: &mut &mut ShapeState, _mouse_position: DVec2, overlay_context: &mut OverlayContext) {\n\t\tlet Some(layer) = layer.or(self.layer) else { return };\n\t\tlet Some((grid_type, spacing, columns, rows, angles)) = extract_grid_parameters(layer, document) else {\n\t\t\treturn;\n\t\t};\n\t\tlet viewport = document.metadata().transform_to_viewport(layer);\n\n\t\tif !matches!(self.gizmo_state, RowColumnGizmoState::Inactive) {\n\t\t\tlet line = self.gizmo_type.line(grid_type, columns, rows, spacing, angles, viewport);\n\t\t\tlet (p0, p1) = get_line_endpoints(line);\n\t\t\toverlay_context.dashed_line(p0, p1, None, None, Some(5.), Some(5.), Some(0.5));\n\t\t}\n\t}\n\n\tpub fn update(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>, drag_start: DVec2) {\n\t\tlet Some(layer) = self.layer else { return };\n\t\tlet viewport = document.metadata().transform_to_viewport(layer);\n\n\t\tlet Some((grid_type, _, columns, rows, angles)) = extract_grid_parameters(layer, document) else {\n\t\t\treturn;\n\t\t};\n\t\tlet direction = self.gizmo_type.direction(viewport);\n\t\tlet delta_vector = input.mouse.position - self.initial_mouse_start.unwrap_or(drag_start);\n\n\t\tlet projection = delta_vector.project_onto(self.gizmo_type.direction(viewport));\n\t\tlet delta = viewport.inverse().transform_vector2(projection).length() * delta_vector.dot(direction).signum();\n\n\t\tif delta.abs() < 1e-6 {\n\t\t\treturn;\n\t\t}\n\n\t\tlet dimensions_to_add = (delta / (self.gizmo_type.spacing(self.spacing, grid_type, angles))).floor() as i32;\n\t\tlet new_dimension = (self.initial_dimension() as i32 + dimensions_to_add).max(1) as u32;\n\n\t\tlet Some(node_id) = graph_modification_utils::get_grid_id(layer, &document.network_interface) else {\n\t\t\treturn;\n\t\t};\n\n\t\tlet dimensions_delta = new_dimension as i32 - self.gizmo_type.initial_dimension(rows, columns) as i32;\n\t\tlet transform = self.transform_grid(dimensions_delta, self.spacing, grid_type, angles, viewport);\n\n\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\tinput_connector: InputConnector::node(node_id, self.gizmo_type.index()),\n\t\t\tinput: NodeInput::value(TaggedValue::U32((self.initial_dimension() as i32 + dimensions_to_add).max(1) as u32), false),\n\t\t});\n\n\t\tresponses.add(GraphOperationMessage::TransformChange {\n\t\t\tlayer,\n\t\t\ttransform,\n\t\t\ttransform_in: TransformIn::Viewport,\n\t\t\tskip_rerender: false,\n\t\t});\n\n\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\n\t\tif self.initial_dimension() as i32 + dimensions_to_add < 1 {\n\t\t\tself.initial_mouse_start = Some(input.mouse.position);\n\t\t\tself.gizmo_type = self.gizmo_type.opposite_gizmo_type();\n\t\t\tself.initial_rows = 1;\n\t\t\tself.initial_columns = 1;\n\t\t}\n\t}\n\n\tfn transform_grid(&self, dimensions_delta: i32, spacing: DVec2, grid_type: GridType, angles: DVec2, viewport: DAffine2) -> DAffine2 {\n\t\tmatch &self.gizmo_type {\n\t\t\tRowColumnGizmoType::Top => {\n\t\t\t\tlet move_up_by = self.gizmo_type.direction(viewport) * dimensions_delta as f64 * spacing.y;\n\t\t\t\tDAffine2::from_translation(move_up_by)\n\t\t\t}\n\t\t\tRowColumnGizmoType::Left => {\n\t\t\t\tlet move_left_by = self.gizmo_type.direction(viewport) * dimensions_delta as f64 * self.gizmo_type.spacing(spacing, grid_type, angles);\n\t\t\t\tDAffine2::from_translation(move_left_by)\n\t\t\t}\n\t\t\tRowColumnGizmoType::Bottom | RowColumnGizmoType::Right | RowColumnGizmoType::None => DAffine2::IDENTITY,\n\t\t}\n\t}\n}\n\nfn check_if_over_gizmo(grid_type: GridType, columns: u32, rows: u32, spacing: DVec2, angles: DVec2, mouse_position: DVec2, viewport: DAffine2) -> Option<RowColumnGizmoType> {\n\tlet mouse_point = dvec2_to_point(mouse_position);\n\tlet accuracy = 1e-6;\n\tlet threshold = 32.;\n\n\tfor gizmo_type in RowColumnGizmoType::all() {\n\t\tlet line = gizmo_type.line(grid_type, columns, rows, spacing, angles, viewport);\n\t\tlet rect = gizmo_type.rect(grid_type, columns, rows, spacing, angles, viewport);\n\n\t\tif rect.contains(mouse_point) || line.nearest(mouse_point, accuracy).distance_sq < threshold {\n\t\t\treturn Some(gizmo_type);\n\t\t}\n\t}\n\n\tNone\n}\n\nfn convert_to_gizmo_line(p0: DVec2, p1: DVec2) -> Line {\n\tLine {\n\t\tp0: dvec2_to_point(p0),\n\t\tp1: dvec2_to_point(p1),\n\t}\n}\n\n/// Get corners of the rectangular-grid.\n/// Returns a tuple of (topleft,topright,bottomright,bottomleft)\nfn get_corners(columns: u32, rows: u32, spacing: DVec2) -> (DVec2, DVec2, DVec2, DVec2) {\n\tlet (width, height) = (spacing.x, spacing.y);\n\n\tlet x_distance = (columns - 1) as f64 * width;\n\tlet y_distance = (rows - 1) as f64 * height;\n\n\tlet point0 = DVec2::ZERO;\n\tlet point1 = DVec2::new(x_distance, 0.);\n\tlet point2 = DVec2::new(x_distance, y_distance);\n\tlet point3 = DVec2::new(0., y_distance);\n\n\t(point0, point1, point2, point3)\n}\n\nfn get_rectangle_top_line_points(columns: u32, rows: u32, spacing: DVec2) -> (DVec2, DVec2) {\n\tlet (top_left, top_right, _, _) = get_corners(columns, rows, spacing);\n\tlet offset = if columns == 1 || rows == 1 {\n\t\tDVec2::ZERO\n\t} else if columns == 2 {\n\t\tDVec2::new(spacing.x * 0.25, 0.)\n\t} else {\n\t\tDVec2::new(spacing.x * 0.5, 0.)\n\t};\n\n\t(top_left + offset, top_right - offset)\n}\n\nfn get_rectangle_bottom_line_points(columns: u32, rows: u32, spacing: DVec2) -> (DVec2, DVec2) {\n\tlet (_, _, bottom_right, bottom_left) = get_corners(columns, rows, spacing);\n\tlet offset = if columns == 1 || rows == 1 {\n\t\tDVec2::ZERO\n\t} else if columns == 2 {\n\t\tDVec2::new(spacing.x * 0.25, 0.)\n\t} else {\n\t\tDVec2::new(spacing.x * 0.5, 0.)\n\t};\n\n\t(bottom_left + offset, bottom_right - offset)\n}\n\nfn get_rectangle_right_line_points(columns: u32, rows: u32, spacing: DVec2) -> (DVec2, DVec2) {\n\tlet (_, top_right, bottom_right, _) = get_corners(columns, rows, spacing);\n\tlet offset = if columns == 1 || rows == 1 {\n\t\tDVec2::ZERO\n\t} else if rows == 2 {\n\t\tDVec2::new(0., -spacing.y * 0.25)\n\t} else {\n\t\tDVec2::new(0., -spacing.y * 0.5)\n\t};\n\n\t(top_right - offset, bottom_right + offset)\n}\n\nfn get_rectangle_left_line_points(columns: u32, rows: u32, spacing: DVec2) -> (DVec2, DVec2) {\n\tlet (top_left, _, _, bottom_left) = get_corners(columns, rows, spacing);\n\tlet offset = if columns == 1 || rows == 1 {\n\t\tDVec2::ZERO\n\t} else if rows == 2 {\n\t\tDVec2::new(0., -spacing.y * 0.25)\n\t} else {\n\t\tDVec2::new(0., -spacing.y * 0.5)\n\t};\n\n\t(top_left - offset, bottom_left + offset)\n}\n\nfn calculate_isometric_point(column: u32, row: u32, angles: DVec2, spacing: DVec2) -> DVec2 {\n\tlet tan_a = angles.x.to_radians().tan();\n\tlet tan_b = angles.y.to_radians().tan();\n\n\tlet spacing = DVec2::new(spacing.y / (tan_a + tan_b), spacing.y);\n\n\tlet a_angles_eaten = column.div_ceil(2) as f64;\n\tlet b_angles_eaten = (column / 2) as f64;\n\n\tlet offset_y_fraction = b_angles_eaten * tan_b - a_angles_eaten * tan_a;\n\n\tDVec2::new(spacing.x * column as f64, spacing.y * row as f64 + offset_y_fraction * spacing.x)\n}\n\nfn calculate_isometric_top_line_points(columns: u32, rows: u32, spacing: DVec2, angles: DVec2) -> (DVec2, DVec2) {\n\tlet top_left = calculate_isometric_point(0, 0, angles, spacing);\n\tlet top_right = calculate_isometric_point(columns - 1, 0, angles, spacing);\n\n\tlet offset = if columns == 1 || rows == 1 { DVec2::ZERO } else { DVec2::new(spacing.x * 0.5, 0.) };\n\tlet isometric_spacing = calculate_isometric_offset(spacing, angles);\n\tlet isometric_offset = DVec2::new(0., isometric_spacing.y);\n\tlet end_isometric_offset = if columns.is_multiple_of(2) { DVec2::ZERO } else { DVec2::new(0., isometric_spacing.y) };\n\n\t(top_left + offset - isometric_offset, top_right - offset - end_isometric_offset)\n}\n\nfn calculate_isometric_bottom_line_points(columns: u32, rows: u32, spacing: DVec2, angles: DVec2) -> (DVec2, DVec2) {\n\tlet bottom_left = calculate_isometric_point(0, rows - 1, angles, spacing);\n\tlet bottom_right = calculate_isometric_point(columns - 1, rows - 1, angles, spacing);\n\n\tlet offset = if columns == 1 || rows == 1 { DVec2::ZERO } else { DVec2::new(spacing.x * 0.5, 0.) };\n\tlet isometric_offset = if columns.is_multiple_of(2) {\n\t\tlet offset = calculate_isometric_offset(spacing, angles);\n\t\tDVec2::new(0., offset.y)\n\t} else {\n\t\tDVec2::ZERO\n\t};\n\n\t(bottom_left + offset, bottom_right - offset + isometric_offset)\n}\n\nfn calculate_isometric_offset(spacing: DVec2, angles: DVec2) -> DVec2 {\n\tlet first_point = calculate_isometric_point(0, 0, angles, spacing);\n\tlet second_point = calculate_isometric_point(1, 0, angles, spacing);\n\n\tDVec2::new(first_point.x - second_point.x, first_point.y - second_point.y)\n}\n\nfn calculate_isometric_right_line_points(columns: u32, rows: u32, spacing: DVec2, angles: DVec2) -> (DVec2, DVec2) {\n\tlet top_right = calculate_isometric_point(columns - 1, 0, angles, spacing);\n\tlet bottom_right = calculate_isometric_point(columns - 1, rows - 1, angles, spacing);\n\n\tlet offset = if columns == 1 || rows == 1 { DVec2::ZERO } else { DVec2::new(0., -spacing.y * 0.5) };\n\n\t(top_right - offset, bottom_right + offset)\n}\n\nfn calculate_isometric_left_line_points(columns: u32, rows: u32, spacing: DVec2, angles: DVec2) -> (DVec2, DVec2) {\n\tlet top_left = calculate_isometric_point(0, 0, angles, spacing);\n\tlet bottom_left = calculate_isometric_point(0, rows - 1, angles, spacing);\n\n\tlet offset = if columns == 1 || rows == 1 { DVec2::ZERO } else { DVec2::new(0., -spacing.y * 0.5) };\n\n\t(top_left - offset, bottom_left + offset)\n}\n\n#[derive(Clone, Debug, Default, PartialEq)]\npub enum RowColumnGizmoType {\n\t#[default]\n\tNone,\n\tTop,\n\tBottom,\n\tLeft,\n\tRight,\n}\n\nimpl RowColumnGizmoType {\n\tpub fn get_line_points(&self, grid_type: GridType, columns: u32, rows: u32, spacing: DVec2, angles: DVec2) -> (DVec2, DVec2) {\n\t\tmatch grid_type {\n\t\t\tGridType::Rectangular => match self {\n\t\t\t\tSelf::Top => get_rectangle_top_line_points(columns, rows, spacing),\n\t\t\t\tSelf::Right => get_rectangle_right_line_points(columns, rows, spacing),\n\t\t\t\tSelf::Bottom => get_rectangle_bottom_line_points(columns, rows, spacing),\n\t\t\t\tSelf::Left => get_rectangle_left_line_points(columns, rows, spacing),\n\t\t\t\tSelf::None => panic!(\"RowColumnGizmoType::None does not have line points\"),\n\t\t\t},\n\t\t\tGridType::Isometric => match self {\n\t\t\t\tSelf::Top => calculate_isometric_top_line_points(columns, rows, spacing, angles),\n\t\t\t\tSelf::Right => calculate_isometric_right_line_points(columns, rows, spacing, angles),\n\t\t\t\tSelf::Bottom => calculate_isometric_bottom_line_points(columns, rows, spacing, angles),\n\t\t\t\tSelf::Left => calculate_isometric_left_line_points(columns, rows, spacing, angles),\n\t\t\t\tSelf::None => panic!(\"RowColumnGizmoType::None does not have line points\"),\n\t\t\t},\n\t\t}\n\t}\n\n\tfn line(&self, grid_type: GridType, columns: u32, rows: u32, spacing: DVec2, angles: DVec2, viewport: DAffine2) -> Line {\n\t\tlet (p0, p1) = self.get_line_points(grid_type, columns, rows, spacing, angles);\n\t\tlet direction = self.direction(viewport);\n\t\tlet gap = GRID_ROW_COLUMN_GIZMO_OFFSET * viewport.inverse().transform_vector2(direction).normalize();\n\n\t\tconvert_to_gizmo_line(viewport.transform_point2(p0 + gap), viewport.transform_point2(p1 + gap))\n\t}\n\n\tfn rect(&self, grid_type: GridType, columns: u32, rows: u32, spacing: DVec2, angles: DVec2, viewport: DAffine2) -> Rect {\n\t\tlet (p0, p1) = self.get_line_points(grid_type, columns, rows, spacing, angles);\n\t\tlet direction = self.direction(viewport);\n\t\tlet gap = GRID_ROW_COLUMN_GIZMO_OFFSET * direction.normalize();\n\n\t\tlet (x0, x1) = match self {\n\t\t\tSelf::Top | Self::Left => (viewport.transform_point2(p0 + gap), viewport.transform_point2(p1)),\n\t\t\tSelf::Bottom | Self::Right => (viewport.transform_point2(p0), viewport.transform_point2(p1 + gap)),\n\t\t\tSelf::None => panic!(\"RowColumnGizmoType::None does not have opposite\"),\n\t\t};\n\n\t\tRect::new(x0.x, x0.y, x1.x, x1.y)\n\t}\n\n\tfn opposite_gizmo_type(&self) -> Self {\n\t\tmatch self {\n\t\t\tSelf::Top => Self::Bottom,\n\t\t\tSelf::Right => Self::Left,\n\t\t\tSelf::Bottom => Self::Top,\n\t\t\tSelf::Left => Self::Right,\n\t\t\tSelf::None => panic!(\"RowColumnGizmoType::None does not have opposite\"),\n\t\t}\n\t}\n\n\tpub fn direction(&self, viewport: DAffine2) -> DVec2 {\n\t\tmatch self {\n\t\t\tRowColumnGizmoType::Top => viewport.transform_vector2(-DVec2::Y),\n\t\t\tRowColumnGizmoType::Bottom => viewport.transform_vector2(DVec2::Y),\n\t\t\tRowColumnGizmoType::Right => viewport.transform_vector2(DVec2::X),\n\t\t\tRowColumnGizmoType::Left => viewport.transform_vector2(-DVec2::X),\n\t\t\tRowColumnGizmoType::None => panic!(\"RowColumnGizmoType::None does not have a line\"),\n\t\t}\n\t}\n\n\tfn initial_dimension(&self, rows: u32, columns: u32) -> u32 {\n\t\tmatch self {\n\t\t\tRowColumnGizmoType::Top | RowColumnGizmoType::Bottom => rows,\n\t\t\tRowColumnGizmoType::Left | RowColumnGizmoType::Right => columns,\n\t\t\tRowColumnGizmoType::None => panic!(\"RowColumnGizmoType::None does not have a mouse_icon\"),\n\t\t}\n\t}\n\n\tfn spacing(&self, spacing: DVec2, grid_type: GridType, angles: DVec2) -> f64 {\n\t\tmatch self {\n\t\t\tRowColumnGizmoType::Top | RowColumnGizmoType::Bottom => spacing.y,\n\t\t\tRowColumnGizmoType::Left | RowColumnGizmoType::Right => {\n\t\t\t\tif grid_type == GridType::Rectangular {\n\t\t\t\t\tspacing.x\n\t\t\t\t} else {\n\t\t\t\t\tspacing.y / (angles.x.to_radians().tan() + angles.y.to_radians().tan())\n\t\t\t\t}\n\t\t\t}\n\t\t\tRowColumnGizmoType::None => panic!(\"RowColumnGizmoType::None does not have a mouse_icon\"),\n\t\t}\n\t}\n\n\tfn index(&self) -> usize {\n\t\tuse graphene_std::vector::generator_nodes::grid::*;\n\n\t\tmatch self {\n\t\t\tRowColumnGizmoType::Top | RowColumnGizmoType::Bottom => RowsInput::INDEX,\n\t\t\tRowColumnGizmoType::Left | RowColumnGizmoType::Right => ColumnsInput::INDEX,\n\t\t\tRowColumnGizmoType::None => panic!(\"RowColumnGizmoType::None does not have a mouse_icon\"),\n\t\t}\n\t}\n\n\tpub fn mouse_icon(&self) -> MouseCursorIcon {\n\t\tmatch self {\n\t\t\tRowColumnGizmoType::Top | RowColumnGizmoType::Bottom => MouseCursorIcon::NSResize,\n\t\t\tRowColumnGizmoType::Left | RowColumnGizmoType::Right => MouseCursorIcon::EWResize,\n\t\t\tRowColumnGizmoType::None => panic!(\"RowColumnGizmoType::None does not have a mouse_icon\"),\n\t\t}\n\t}\n\n\tpub fn all() -> [Self; 4] {\n\t\t[Self::Top, Self::Right, Self::Bottom, Self::Left]\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/mod.rs",
    "content": "pub mod circle_arc_radius_handle;\npub mod grid_rows_columns_gizmo;\npub mod number_of_points_dial;\npub mod point_radius_handle;\npub mod spiral_turns_handle;\npub mod sweep_angle_gizmo;\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/number_of_points_dial.rs",
    "content": "use crate::consts::{GIZMO_HIDE_THRESHOLD, NUMBER_OF_POINTS_DIAL_SPOKE_EXTENSION, NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH, POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD};\nuse crate::messages::frontend::utility_types::MouseCursorIcon;\nuse crate::messages::message::Message;\nuse crate::messages::portfolio::document::overlays::utility_types::OverlayContext;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::portfolio::document::utility_types::network_interface::InputConnector;\nuse crate::messages::prelude::Responses;\nuse crate::messages::prelude::{DocumentMessageHandler, FrontendMessage, InputPreprocessorMessageHandler, NodeGraphMessage};\nuse crate::messages::tool::common_functionality::graph_modification_utils;\nuse crate::messages::tool::common_functionality::shape_editor::ShapeState;\nuse crate::messages::tool::common_functionality::shapes::shape_utility::{extract_polygon_parameters, inside_polygon, inside_star, polygon_outline, polygon_vertex_position, star_outline};\nuse crate::messages::tool::common_functionality::shapes::shape_utility::{extract_star_parameters, star_vertex_position};\nuse glam::{DAffine2, DVec2};\nuse graph_craft::document::NodeInput;\nuse graph_craft::document::value::TaggedValue;\nuse std::collections::VecDeque;\nuse std::f64::consts::TAU;\n\n#[derive(Clone, Debug, Default, PartialEq)]\npub enum NumberOfPointsDialState {\n\t#[default]\n\tInactive,\n\tHover,\n\tDragging,\n}\n\n#[derive(Clone, Debug, Default)]\npub struct NumberOfPointsDial {\n\tpub layer: Option<LayerNodeIdentifier>,\n\tpub initial_points: u32,\n\tpub handle_state: NumberOfPointsDialState,\n}\n\nimpl NumberOfPointsDial {\n\tpub fn cleanup(&mut self) {\n\t\tself.handle_state = NumberOfPointsDialState::Inactive;\n\t\tself.layer = None;\n\t}\n\n\tpub fn update_state(&mut self, state: NumberOfPointsDialState) {\n\t\tself.handle_state = state;\n\t}\n\n\tpub fn is_hovering(&self) -> bool {\n\t\tself.handle_state == NumberOfPointsDialState::Hover\n\t}\n\n\tpub fn is_dragging(&self) -> bool {\n\t\tself.handle_state == NumberOfPointsDialState::Dragging\n\t}\n\n\tpub fn handle_actions(&mut self, layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {\n\t\tmatch &self.handle_state {\n\t\t\tNumberOfPointsDialState::Inactive => {\n\t\t\t\t// Star\n\t\t\t\tif let Some((sides, radius1, radius2)) = extract_star_parameters(Some(layer), document) {\n\t\t\t\t\tlet viewport = document.metadata().transform_to_viewport(layer);\n\t\t\t\t\tlet center = viewport.transform_point2(DVec2::ZERO);\n\n\t\t\t\t\tlet point_on_max_radius = star_vertex_position(viewport, 0, sides, radius1, radius2);\n\n\t\t\t\t\tif mouse_position.distance(center) < NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH && point_on_max_radius.distance(center) > GIZMO_HIDE_THRESHOLD {\n\t\t\t\t\t\tself.layer = Some(layer);\n\t\t\t\t\t\tself.initial_points = sides;\n\t\t\t\t\t\tself.update_state(NumberOfPointsDialState::Hover);\n\t\t\t\t\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::EWResize });\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Polygon\n\t\t\t\tif let Some((sides, radius)) = extract_polygon_parameters(Some(layer), document) {\n\t\t\t\t\tlet viewport = document.metadata().transform_to_viewport(layer);\n\t\t\t\t\tlet center = viewport.transform_point2(DVec2::ZERO);\n\n\t\t\t\t\tlet point_on_max_radius = polygon_vertex_position(viewport, 0, sides, radius);\n\n\t\t\t\t\tif mouse_position.distance(center) < NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH && point_on_max_radius.distance(center) > GIZMO_HIDE_THRESHOLD {\n\t\t\t\t\t\tself.layer = Some(layer);\n\t\t\t\t\t\tself.initial_points = sides;\n\t\t\t\t\t\tself.update_state(NumberOfPointsDialState::Hover);\n\t\t\t\t\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::EWResize });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tNumberOfPointsDialState::Hover | NumberOfPointsDialState::Dragging => {\n\t\t\t\tlet Some(layer) = self.layer else { return };\n\n\t\t\t\tlet viewport = document.metadata().transform_to_viewport(layer);\n\t\t\t\tlet center = viewport.transform_point2(DVec2::ZERO);\n\n\t\t\t\tif mouse_position.distance(center) > NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH && matches!(&self.handle_state, NumberOfPointsDialState::Hover) {\n\t\t\t\t\tself.update_state(NumberOfPointsDialState::Inactive);\n\t\t\t\t\tself.layer = None;\n\t\t\t\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn overlays(&self, document: &DocumentMessageHandler, layer: Option<LayerNodeIdentifier>, shape_editor: &mut &mut ShapeState, mouse_position: DVec2, overlay_context: &mut OverlayContext) {\n\t\tmatch &self.handle_state {\n\t\t\tNumberOfPointsDialState::Inactive => {\n\t\t\t\tlet Some(layer) = layer else { return };\n\n\t\t\t\t// Star\n\t\t\t\tif let Some((sides, radius1, radius2)) = extract_star_parameters(Some(layer), document) {\n\t\t\t\t\tlet radius = radius1.max(radius2);\n\t\t\t\t\tlet viewport = document.metadata().transform_to_viewport(layer);\n\t\t\t\t\tlet center = viewport.transform_point2(DVec2::ZERO);\n\n\t\t\t\t\tif let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, mouse_position, POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD)\n\t\t\t\t\t\t&& closest_segment.layer() == layer\n\t\t\t\t\t{\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tlet point_on_max_radius = star_vertex_position(viewport, 0, sides, radius1, radius2);\n\n\t\t\t\t\tif inside_star(viewport, sides, radius1, radius2, mouse_position) && point_on_max_radius.distance(center) > GIZMO_HIDE_THRESHOLD {\n\t\t\t\t\t\tself.draw_spokes(center, viewport, sides, radius, overlay_context);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Polygon\n\t\t\t\tif let Some((sides, radius)) = extract_polygon_parameters(Some(layer), document) {\n\t\t\t\t\tlet viewport = document.metadata().transform_to_viewport(layer);\n\t\t\t\t\tlet center = viewport.transform_point2(DVec2::ZERO);\n\n\t\t\t\t\tif let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, mouse_position, POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD)\n\t\t\t\t\t\t&& closest_segment.layer() == layer\n\t\t\t\t\t{\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tlet point_on_max_radius = polygon_vertex_position(viewport, 0, sides, radius);\n\n\t\t\t\t\tif inside_polygon(viewport, sides, radius, mouse_position) && point_on_max_radius.distance(center) > GIZMO_HIDE_THRESHOLD {\n\t\t\t\t\t\tself.draw_spokes(center, viewport, sides, radius, overlay_context);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tNumberOfPointsDialState::Hover | NumberOfPointsDialState::Dragging => {\n\t\t\t\tlet Some(layer) = self.layer else {\n\t\t\t\t\treturn;\n\t\t\t\t};\n\n\t\t\t\t// Get the star's greater radius or polygon's radius, as well as the number of sides\n\t\t\t\tlet Some((sides, radius)) = extract_star_parameters(Some(layer), document)\n\t\t\t\t\t.map(|(sides, r1, r2)| (sides, r1.max(r2)))\n\t\t\t\t\t.or_else(|| extract_polygon_parameters(Some(layer), document))\n\t\t\t\telse {\n\t\t\t\t\treturn;\n\t\t\t\t};\n\n\t\t\t\tlet viewport = document.metadata().transform_to_viewport(layer);\n\t\t\t\tlet center = viewport.transform_point2(DVec2::ZERO);\n\n\t\t\t\t// Draw either the star or polygon outline\n\t\t\t\tstar_outline(Some(layer), document, overlay_context);\n\t\t\t\tpolygon_outline(Some(layer), document, overlay_context);\n\n\t\t\t\tself.draw_spokes(center, viewport, sides, radius, overlay_context);\n\t\t\t}\n\t\t}\n\t}\n\n\tfn draw_spokes(&self, center: DVec2, viewport: DAffine2, sides: u32, radius: f64, overlay_context: &mut OverlayContext) {\n\t\tfor i in 0..sides {\n\t\t\tlet angle = ((i as f64) * TAU) / (sides as f64);\n\n\t\t\tlet point = viewport.transform_point2(DVec2 {\n\t\t\t\tx: radius * angle.sin(),\n\t\t\t\ty: -radius * angle.cos(),\n\t\t\t});\n\n\t\t\tlet Some(direction) = (point - center).try_normalize() else { continue };\n\n\t\t\t// If the user zooms out such that shape is very small hide the gizmo\n\t\t\tif point.distance(center) < GIZMO_HIDE_THRESHOLD {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet end_point = direction * NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH;\n\t\t\tif matches!(self.handle_state, NumberOfPointsDialState::Hover | NumberOfPointsDialState::Dragging) {\n\t\t\t\toverlay_context.line(center, end_point * NUMBER_OF_POINTS_DIAL_SPOKE_EXTENSION + center, None, None);\n\t\t\t} else {\n\t\t\t\toverlay_context.line(center, end_point + center, None, None);\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn update_number_of_sides(&self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>, drag_start: DVec2) {\n\t\tlet delta = input.mouse.position - drag_start;\n\t\tlet sign = (input.mouse.position.x - drag_start.x).signum();\n\t\tlet net_delta = (delta.length() / 25.).round() * sign;\n\n\t\tlet Some(layer) = self.layer else { return };\n\t\tlet Some(node_id) = graph_modification_utils::get_star_id(layer, &document.network_interface).or(graph_modification_utils::get_polygon_id(layer, &document.network_interface)) else {\n\t\t\treturn;\n\t\t};\n\n\t\tlet new_point_count = ((self.initial_points as i32) + (net_delta as i32)).max(3);\n\n\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\tinput_connector: InputConnector::node(node_id, 1),\n\t\t\tinput: NodeInput::value(TaggedValue::U32(new_point_count as u32), false),\n\t\t});\n\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/point_radius_handle.rs",
    "content": "use crate::consts::GIZMO_HIDE_THRESHOLD;\nuse crate::consts::{COLOR_OVERLAY_RED, POINT_RADIUS_HANDLE_SNAP_THRESHOLD};\nuse crate::messages::frontend::utility_types::MouseCursorIcon;\nuse crate::messages::message::Message;\nuse crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::portfolio::document::{overlays::utility_types::OverlayContext, utility_types::network_interface::InputConnector};\nuse crate::messages::prelude::FrontendMessage;\nuse crate::messages::prelude::Responses;\nuse crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler, NodeGraphMessage};\nuse crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer};\nuse crate::messages::tool::common_functionality::shapes::shape_utility::{draw_snapping_ticks, extract_polygon_parameters, polygon_outline, polygon_vertex_position, star_outline};\nuse crate::messages::tool::common_functionality::shapes::shape_utility::{extract_star_parameters, star_vertex_position};\nuse glam::DVec2;\nuse graph_craft::document::NodeInput;\nuse graph_craft::document::value::TaggedValue;\nuse std::collections::VecDeque;\nuse std::f64::consts::{FRAC_1_SQRT_2, FRAC_PI_4, PI, SQRT_2};\n\n#[derive(Clone, Debug, Default, PartialEq)]\npub enum PointRadiusHandleState {\n\t#[default]\n\tInactive,\n\tHover,\n\tDragging,\n\tSnapped(usize),\n}\n\n#[derive(Clone, Debug, Default, PartialEq)]\npub struct PointRadiusHandle {\n\tpub layer: Option<LayerNodeIdentifier>,\n\tpoint: u32,\n\tradius_index: usize,\n\tsnap_radii: Vec<f64>,\n\tinitial_radius: f64,\n\thandle_state: PointRadiusHandleState,\n}\n\nimpl PointRadiusHandle {\n\tpub fn cleanup(&mut self) {\n\t\tself.handle_state = PointRadiusHandleState::Inactive;\n\t\tself.snap_radii.clear();\n\t\tself.layer = None;\n\t}\n\n\tpub fn hovered(&self) -> bool {\n\t\tself.handle_state == PointRadiusHandleState::Hover\n\t}\n\n\tpub fn is_dragging_or_snapped(&self) -> bool {\n\t\tself.handle_state == PointRadiusHandleState::Dragging || matches!(self.handle_state, PointRadiusHandleState::Snapped(_))\n\t}\n\n\tpub fn update_state(&mut self, state: PointRadiusHandleState) {\n\t\tself.handle_state = state;\n\t}\n\n\tpub fn handle_actions(&mut self, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, mouse_position: DVec2, responses: &mut VecDeque<Message>) {\n\t\tmatch &self.handle_state {\n\t\t\tPointRadiusHandleState::Inactive => {\n\t\t\t\t// Draw the point handle gizmo for the star shape\n\t\t\t\tif let Some((sides, radius1, radius2)) = extract_star_parameters(Some(layer), document) {\n\t\t\t\t\tlet viewport = document.metadata().transform_to_viewport(layer);\n\n\t\t\t\t\tfor i in 0..2 * sides {\n\t\t\t\t\t\tlet (radius, radius_index) = if i % 2 == 0 { (radius1, 2) } else { (radius2, 3) };\n\t\t\t\t\t\tlet point = star_vertex_position(viewport, i as i32, sides, radius1, radius2);\n\t\t\t\t\t\tlet center = viewport.transform_point2(DVec2::ZERO);\n\n\t\t\t\t\t\t// If the user zooms out such that shape is very small hide the gizmo\n\t\t\t\t\t\tif point.distance(center) < GIZMO_HIDE_THRESHOLD {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif point.distance(mouse_position) < 5. {\n\t\t\t\t\t\t\tself.radius_index = radius_index;\n\t\t\t\t\t\t\tself.layer = Some(layer);\n\t\t\t\t\t\t\tself.point = i;\n\t\t\t\t\t\t\tself.snap_radii = Self::calculate_snap_radii(document, layer, radius_index);\n\t\t\t\t\t\t\tself.initial_radius = radius;\n\t\t\t\t\t\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });\n\t\t\t\t\t\t\tself.update_state(PointRadiusHandleState::Hover);\n\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Draw the point handle gizmo for the polygon shape\n\t\t\t\tif let Some((sides, radius)) = extract_polygon_parameters(Some(layer), document) {\n\t\t\t\t\tlet viewport = document.metadata().transform_to_viewport(layer);\n\n\t\t\t\t\tfor i in 0..sides {\n\t\t\t\t\t\tlet point = polygon_vertex_position(viewport, i as i32, sides, radius);\n\t\t\t\t\t\tlet center = viewport.transform_point2(DVec2::ZERO);\n\n\t\t\t\t\t\t// If the user zooms out such that shape is very small hide the gizmo\n\t\t\t\t\t\tif point.distance(center) < GIZMO_HIDE_THRESHOLD {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif point.distance(mouse_position) < 5. {\n\t\t\t\t\t\t\tself.radius_index = 2;\n\t\t\t\t\t\t\tself.layer = Some(layer);\n\t\t\t\t\t\t\tself.point = i;\n\t\t\t\t\t\t\tself.snap_radii.clear();\n\t\t\t\t\t\t\tself.initial_radius = radius;\n\t\t\t\t\t\t\tself.update_state(PointRadiusHandleState::Hover);\n\t\t\t\t\t\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tPointRadiusHandleState::Dragging | PointRadiusHandleState::Hover => {\n\t\t\t\tlet Some(layer) = self.layer else { return };\n\n\t\t\t\tlet viewport = document.metadata().transform_to_viewport(layer);\n\n\t\t\t\t// Star\n\t\t\t\tif let Some((sides, radius1, radius2)) = extract_star_parameters(Some(layer), document) {\n\t\t\t\t\tlet point = star_vertex_position(viewport, self.point as i32, sides, radius1, radius2);\n\n\t\t\t\t\tif matches!(&self.handle_state, PointRadiusHandleState::Hover) && (mouse_position - point).length() > 5. {\n\t\t\t\t\t\tself.update_state(PointRadiusHandleState::Inactive);\n\t\t\t\t\t\tself.layer = None;\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Polygon\n\t\t\t\tif let Some((sides, radius)) = extract_polygon_parameters(Some(layer), document) {\n\t\t\t\t\tlet point = polygon_vertex_position(viewport, self.point as i32, sides, radius);\n\n\t\t\t\t\tif matches!(&self.handle_state, PointRadiusHandleState::Hover) && (mouse_position - point).length() > 5. {\n\t\t\t\t\t\tself.update_state(PointRadiusHandleState::Inactive);\n\t\t\t\t\t\tself.layer = None;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tPointRadiusHandleState::Snapped(_) => {}\n\t\t}\n\t}\n\n\tpub fn overlays(&self, selected_star_layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) {\n\t\tmatch &self.handle_state {\n\t\t\tPointRadiusHandleState::Inactive => {\n\t\t\t\tlet Some(layer) = selected_star_layer else { return };\n\n\t\t\t\t// Draw the point handle gizmo for the star shape\n\t\t\t\tif let Some((sides, radius1, radius2)) = extract_star_parameters(Some(layer), document) {\n\t\t\t\t\tlet viewport = document.metadata().transform_to_viewport(layer);\n\n\t\t\t\t\tfor i in 0..(2 * sides) {\n\t\t\t\t\t\tlet point = star_vertex_position(viewport, i as i32, sides, radius1, radius2);\n\t\t\t\t\t\tlet center = viewport.transform_point2(DVec2::ZERO);\n\n\t\t\t\t\t\t// If the user zooms out such that shape is very small hide the gizmo\n\t\t\t\t\t\tif point.distance(center) < GIZMO_HIDE_THRESHOLD {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\toverlay_context.manipulator_handle(point, false, None);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Draw the point handle gizmo for the Polygon shape\n\t\t\t\tif let Some((sides, radius)) = extract_polygon_parameters(Some(layer), document) {\n\t\t\t\t\tlet viewport = document.metadata().transform_to_viewport(layer);\n\n\t\t\t\t\tfor i in 0..sides {\n\t\t\t\t\t\tlet point = polygon_vertex_position(viewport, i as i32, sides, radius);\n\t\t\t\t\t\tlet center = viewport.transform_point2(DVec2::ZERO);\n\n\t\t\t\t\t\t// If the user zooms out such that shape is very small hide the gizmo\n\t\t\t\t\t\tif point.distance(center) < GIZMO_HIDE_THRESHOLD {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\toverlay_context.manipulator_handle(point, false, None);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tPointRadiusHandleState::Dragging | PointRadiusHandleState::Hover => {\n\t\t\t\tlet Some(layer) = self.layer else { return };\n\n\t\t\t\tlet viewport = document.metadata().transform_to_viewport(layer);\n\t\t\t\tlet center = viewport.transform_point2(DVec2::ZERO);\n\t\t\t\tlet viewport_diagonal = overlay_context.viewport.size().into_dvec2().length();\n\n\t\t\t\t// Star\n\t\t\t\tif let Some((sides, radius1, radius2)) = extract_star_parameters(Some(layer), document) {\n\t\t\t\t\tlet angle = ((self.point as f64) * PI) / (sides as f64);\n\t\t\t\t\tlet point = star_vertex_position(viewport, self.point as i32, sides, radius1, radius2);\n\n\t\t\t\t\tlet Some(direction) = (point - center).try_normalize() else { return };\n\n\t\t\t\t\t// Draws the ray from the center to the dragging point extending till the viewport\n\t\t\t\t\toverlay_context.manipulator_handle(point, true, None);\n\t\t\t\t\toverlay_context.line(center, center + direction * viewport_diagonal, None, None);\n\t\t\t\t\tstar_outline(Some(layer), document, overlay_context);\n\n\t\t\t\t\t// Make the ticks for snapping\n\t\t\t\t\tif (radius1.signum() * radius2.signum()).is_sign_positive() {\n\t\t\t\t\t\tdraw_snapping_ticks(&self.snap_radii, direction, viewport, angle, overlay_context);\n\t\t\t\t\t}\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Polygon\n\t\t\t\tif let Some((sides, radius)) = extract_polygon_parameters(Some(layer), document) {\n\t\t\t\t\tlet point = polygon_vertex_position(viewport, self.point as i32, sides, radius);\n\n\t\t\t\t\tlet Some(direction) = (point - center).try_normalize() else { return };\n\n\t\t\t\t\t// Draws the ray from the center to the dragging point extending till the viewport\n\t\t\t\t\toverlay_context.manipulator_handle(point, true, None);\n\t\t\t\t\toverlay_context.line(center, center + direction * viewport_diagonal, None, None);\n\n\t\t\t\t\tpolygon_outline(Some(layer), document, overlay_context);\n\t\t\t\t}\n\t\t\t}\n\t\t\tPointRadiusHandleState::Snapped(snapping_index) => {\n\t\t\t\tlet Some(layer) = self.layer else { return };\n\t\t\t\tlet Some((sides, radius1, radius2)) = extract_star_parameters(Some(layer), document) else {\n\t\t\t\t\treturn;\n\t\t\t\t};\n\n\t\t\t\tlet viewport = document.metadata().transform_to_viewport(layer);\n\n\t\t\t\tmatch snapping_index {\n\t\t\t\t\t// Make a triangle with previous two points\n\t\t\t\t\t0 => {\n\t\t\t\t\t\tlet before_outer_position = star_vertex_position(viewport, (self.point as i32) - 2, sides, radius1, radius2);\n\t\t\t\t\t\tlet outer_position = star_vertex_position(viewport, (self.point as i32) - 1, sides, radius1, radius2);\n\t\t\t\t\t\tlet point_position = star_vertex_position(viewport, self.point as i32, sides, radius1, radius2);\n\n\t\t\t\t\t\toverlay_context.line(before_outer_position, outer_position, Some(COLOR_OVERLAY_RED), Some(3.));\n\t\t\t\t\t\toverlay_context.line(outer_position, point_position, Some(COLOR_OVERLAY_RED), Some(3.));\n\n\t\t\t\t\t\tlet before_outer_position = viewport.inverse().transform_point2(before_outer_position);\n\t\t\t\t\t\tlet outer_position = viewport.inverse().transform_point2(outer_position);\n\t\t\t\t\t\tlet point_position = viewport.inverse().transform_point2(point_position);\n\n\t\t\t\t\t\tlet l1 = (before_outer_position - outer_position).length() * 0.2;\n\t\t\t\t\t\tlet Some(l1_direction) = (before_outer_position - outer_position).try_normalize() else { return };\n\t\t\t\t\t\tlet Some(l2_direction) = (point_position - outer_position).try_normalize() else { return };\n\t\t\t\t\t\tlet Some(direction) = (-outer_position).try_normalize() else { return };\n\n\t\t\t\t\t\tlet new_point = SQRT_2 * l1 * direction + outer_position;\n\n\t\t\t\t\t\tlet before_outer_position = l1 * l1_direction + outer_position;\n\t\t\t\t\t\tlet point_position = l1 * l2_direction + outer_position;\n\n\t\t\t\t\t\toverlay_context.line(\n\t\t\t\t\t\t\tviewport.transform_point2(before_outer_position),\n\t\t\t\t\t\t\tviewport.transform_point2(new_point),\n\t\t\t\t\t\t\tSome(COLOR_OVERLAY_RED),\n\t\t\t\t\t\t\tSome(3.),\n\t\t\t\t\t\t);\n\t\t\t\t\t\toverlay_context.line(viewport.transform_point2(new_point), viewport.transform_point2(point_position), Some(COLOR_OVERLAY_RED), Some(3.));\n\t\t\t\t\t}\n\t\t\t\t\t1 => {\n\t\t\t\t\t\tlet before_outer_position = star_vertex_position(viewport, (self.point as i32) - 1, sides, radius1, radius2);\n\t\t\t\t\t\tlet after_point_position = star_vertex_position(viewport, (self.point as i32) + 1, sides, radius1, radius2);\n\t\t\t\t\t\tlet point_position = star_vertex_position(viewport, self.point as i32, sides, radius1, radius2);\n\n\t\t\t\t\t\toverlay_context.line(before_outer_position, point_position, Some(COLOR_OVERLAY_RED), Some(3.));\n\t\t\t\t\t\toverlay_context.line(point_position, after_point_position, Some(COLOR_OVERLAY_RED), Some(3.));\n\n\t\t\t\t\t\tlet before_outer_position = viewport.inverse().transform_point2(before_outer_position);\n\t\t\t\t\t\tlet after_point_position = viewport.inverse().transform_point2(after_point_position);\n\t\t\t\t\t\tlet point_position = viewport.inverse().transform_point2(point_position);\n\n\t\t\t\t\t\tlet l1 = (before_outer_position - point_position).length() * 0.2;\n\t\t\t\t\t\tlet Some(l1_direction) = (before_outer_position - point_position).try_normalize() else { return };\n\t\t\t\t\t\tlet Some(l2_direction) = (after_point_position - point_position).try_normalize() else { return };\n\t\t\t\t\t\tlet Some(direction) = (-point_position).try_normalize() else { return };\n\n\t\t\t\t\t\tlet new_point = SQRT_2 * l1 * direction + point_position;\n\n\t\t\t\t\t\tlet before_outer_position = l1 * l1_direction + point_position;\n\t\t\t\t\t\tlet after_point_position = l1 * l2_direction + point_position;\n\n\t\t\t\t\t\toverlay_context.line(\n\t\t\t\t\t\t\tviewport.transform_point2(before_outer_position),\n\t\t\t\t\t\t\tviewport.transform_point2(new_point),\n\t\t\t\t\t\t\tSome(COLOR_OVERLAY_RED),\n\t\t\t\t\t\t\tSome(3.),\n\t\t\t\t\t\t);\n\t\t\t\t\t\toverlay_context.line(viewport.transform_point2(new_point), viewport.transform_point2(after_point_position), Some(COLOR_OVERLAY_RED), Some(3.));\n\t\t\t\t\t}\n\t\t\t\t\ti => {\n\t\t\t\t\t\t// Use `self.point` as absolute reference as it matches the index of vertices of the star starting from 0\n\t\t\t\t\t\tif i % 2 != 0 {\n\t\t\t\t\t\t\t// Flipped case\n\t\t\t\t\t\t\tlet point_position = star_vertex_position(viewport, self.point as i32, sides, radius1, radius2);\n\t\t\t\t\t\t\tlet target_index = (1 - (*i as i32)).abs() + (self.point as i32);\n\t\t\t\t\t\t\tlet target_point_position = star_vertex_position(viewport, target_index, sides, radius1, radius2);\n\n\t\t\t\t\t\t\tlet mirrored_index = 2 * (self.point as i32) - target_index;\n\t\t\t\t\t\t\tlet mirrored = star_vertex_position(viewport, mirrored_index, sides, radius1, radius2);\n\n\t\t\t\t\t\t\toverlay_context.line(point_position, target_point_position, Some(COLOR_OVERLAY_RED), Some(3.));\n\t\t\t\t\t\t\toverlay_context.line(point_position, mirrored, Some(COLOR_OVERLAY_RED), Some(3.));\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tlet outer_index = (self.point as i32) - 1;\n\t\t\t\t\t\t\tlet outer_position = star_vertex_position(viewport, outer_index, sides, radius1, radius2);\n\n\t\t\t\t\t\t\t// The vertex which is colinear with the point we are dragging and its previous outer vertex\n\t\t\t\t\t\t\tlet target_index = (self.point as i32) + (*i as i32) - 1;\n\t\t\t\t\t\t\tlet target_point_position = star_vertex_position(viewport, target_index, sides, radius1, radius2);\n\n\t\t\t\t\t\t\tlet mirrored_index = 2 * outer_index - target_index;\n\n\t\t\t\t\t\t\tlet mirrored = star_vertex_position(viewport, mirrored_index, sides, radius1, radius2);\n\n\t\t\t\t\t\t\toverlay_context.line(outer_position, target_point_position, Some(COLOR_OVERLAY_RED), Some(3.));\n\t\t\t\t\t\t\toverlay_context.line(outer_position, mirrored, Some(COLOR_OVERLAY_RED), Some(3.));\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tstar_outline(Some(layer), document, overlay_context);\n\t\t\t}\n\t\t}\n\t}\n\n\tfn calculate_snap_radii(document: &DocumentMessageHandler, layer: LayerNodeIdentifier, radius_index: usize) -> Vec<f64> {\n\t\tlet mut snap_radii = Vec::new();\n\n\t\tlet Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::star::IDENTIFIER))\n\t\telse {\n\t\t\treturn snap_radii;\n\t\t};\n\n\t\tlet (Some(&TaggedValue::F64(radius_1)), Some(&TaggedValue::F64(radius_2))) = (node_inputs[2].as_value(), node_inputs[3].as_value()) else {\n\t\t\treturn snap_radii;\n\t\t};\n\n\t\tlet other_radius = if radius_index == 3 { radius_1 } else { radius_2 };\n\n\t\tlet Some(&TaggedValue::U32(sides)) = node_inputs[1].as_value() else {\n\t\t\treturn snap_radii;\n\t\t};\n\n\t\tlet both_radii_negative = radius_1.is_sign_negative() && radius_2.is_sign_negative();\n\t\tlet both_radii_same_sign = (radius_1.signum() * radius_2.signum()).is_sign_positive();\n\n\t\t// When only one of the radii is negative, no need for snapping\n\t\tif !both_radii_same_sign {\n\t\t\treturn snap_radii;\n\t\t}\n\n\t\tlet sign = if both_radii_negative { -1. } else { 1. };\n\n\t\t// Inner radius for 90°\n\t\tlet b = FRAC_PI_4 * 3. - PI / (sides as f64);\n\t\tlet angle = b.sin();\n\t\tlet required_radius = (other_radius.abs() * sign / angle) * FRAC_1_SQRT_2;\n\n\t\tsnap_radii.push(required_radius);\n\n\t\t// Also push the case when the when it length increases more than the other\n\n\t\tlet flipped = other_radius.abs() * sign * angle * SQRT_2;\n\n\t\tsnap_radii.push(flipped);\n\n\t\tfor i in 1..sides {\n\t\t\tlet sides = sides as f64;\n\t\t\tlet i = i as f64;\n\t\t\tlet denominator = 2. * ((PI * (i - 1.)) / sides).cos() * ((PI * i) / sides).sin();\n\t\t\tlet numerator = ((2. * PI * i) / sides).sin();\n\t\t\tlet factor = numerator / denominator;\n\n\t\t\tif factor < 0. {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif other_radius.abs() * factor > 1e-6 {\n\t\t\t\tsnap_radii.push(other_radius.abs() * sign * factor);\n\t\t\t}\n\n\t\t\tsnap_radii.push((other_radius.abs() * sign) / factor);\n\t\t}\n\n\t\tsnap_radii\n\t}\n\n\tfn check_snapping(&self, new_radius: f64, original_radius: f64) -> Option<(usize, f64)> {\n\t\tself.snap_radii\n\t\t\t.iter()\n\t\t\t.enumerate()\n\t\t\t.filter(|(_, rad)| (**rad - new_radius).abs() < POINT_RADIUS_HANDLE_SNAP_THRESHOLD)\n\t\t\t.min_by(|(i_a, a), (i_b, b)| {\n\t\t\t\tlet dist_a = (**a - new_radius).abs();\n\t\t\t\tlet dist_b = (**b - new_radius).abs();\n\n\t\t\t\t// Check if either index is 0 or 1 and prioritize them\n\t\t\t\tmatch (*i_a == 0 || *i_a == 1, *i_b == 0 || *i_b == 1) {\n\t\t\t\t\t// `a` is priority index, `b` is not\n\t\t\t\t\t(true, false) => std::cmp::Ordering::Less,\n\t\t\t\t\t// `b` is priority index, `a` is not\n\t\t\t\t\t(false, true) => std::cmp::Ordering::Greater,\n\t\t\t\t\t// Normal comparison\n\t\t\t\t\t_ => dist_a.partial_cmp(&dist_b).unwrap_or(std::cmp::Ordering::Equal),\n\t\t\t\t}\n\t\t\t})\n\t\t\t.map(|(i, rad)| (i, *rad - original_radius))\n\t}\n\n\tpub fn update_inner_radius(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>, drag_start: DVec2) {\n\t\tlet Some(layer) = self.layer else { return };\n\n\t\tlet Some(node_id) = graph_modification_utils::get_star_id(layer, &document.network_interface).or(graph_modification_utils::get_polygon_id(layer, &document.network_interface)) else {\n\t\t\treturn;\n\t\t};\n\n\t\tlet viewport_transform = document.network_interface.document_metadata().transform_to_viewport(layer);\n\t\tlet center = viewport_transform.transform_point2(DVec2::ZERO);\n\t\tlet radius_index = self.radius_index;\n\n\t\tlet original_radius = self.initial_radius;\n\n\t\tlet delta = viewport_transform.inverse().transform_point2(input.mouse.position) - viewport_transform.inverse().transform_point2(drag_start);\n\t\tlet radius = drag_start - center;\n\t\tlet projection = delta.project_onto(radius);\n\t\tlet sign = radius.dot(delta).signum();\n\n\t\tlet mut net_delta = projection.length() * sign * original_radius.signum();\n\t\tlet new_radius = original_radius + net_delta;\n\n\t\tself.update_state(PointRadiusHandleState::Dragging);\n\n\t\tself.check_if_radius_flipped(original_radius, new_radius, document, layer, radius_index);\n\n\t\tif let Some((index, snapped_delta)) = self.check_snapping(new_radius, original_radius) {\n\t\t\tnet_delta = snapped_delta;\n\t\t\tself.update_state(PointRadiusHandleState::Snapped(index));\n\t\t}\n\n\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\tinput_connector: InputConnector::node(node_id, radius_index),\n\t\t\tinput: NodeInput::value(TaggedValue::F64(original_radius + net_delta), false),\n\t\t});\n\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t}\n\n\tfn check_if_radius_flipped(&mut self, original_radius: f64, new_radius: f64, document: &DocumentMessageHandler, layer: LayerNodeIdentifier, radius_index: usize) {\n\t\tlet Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::star::IDENTIFIER))\n\t\telse {\n\t\t\treturn;\n\t\t};\n\n\t\tlet (Some(&TaggedValue::F64(radius_1)), Some(&TaggedValue::F64(radius_2))) = (node_inputs[2].as_value(), node_inputs[3].as_value()) else {\n\t\t\treturn;\n\t\t};\n\n\t\tlet other_radius = if radius_index == 3 { radius_1 } else { radius_2 };\n\n\t\tlet flipped = (other_radius.is_sign_positive() && original_radius.is_sign_negative() && new_radius.is_sign_positive())\n\t\t\t|| (other_radius.is_sign_negative() && original_radius.is_sign_positive() && new_radius.is_sign_negative());\n\n\t\tif flipped {\n\t\t\tself.snap_radii = Self::calculate_snap_radii(document, layer, radius_index);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/spiral_turns_handle.rs",
    "content": "use crate::consts::{COLOR_OVERLAY_RED, POINT_RADIUS_HANDLE_SNAP_THRESHOLD};\nuse crate::messages::message::Message;\nuse crate::messages::portfolio::document::overlays::utility_types::OverlayContext;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::portfolio::document::utility_types::network_interface::InputConnector;\nuse crate::messages::prelude::Responses;\nuse crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler, NodeGraphMessage};\nuse crate::messages::tool::common_functionality::graph_modification_utils;\nuse crate::messages::tool::common_functionality::shape_editor::ShapeState;\nuse crate::messages::tool::common_functionality::shapes::shape_utility::extract_spiral_parameters;\nuse crate::messages::tool::common_functionality::shapes::spiral_shape::calculate_spiral_endpoints;\nuse glam::DVec2;\nuse graph_craft::document::NodeInput;\nuse graph_craft::document::value::TaggedValue;\nuse graphene_std::NodeInputDecleration;\nuse graphene_std::subpath::{calculate_growth_factor, spiral_point};\nuse graphene_std::vector::misc::SpiralType;\nuse std::collections::VecDeque;\nuse std::f64::consts::TAU;\n\n#[derive(Clone, Debug, Default, PartialEq)]\npub enum GizmoType {\n\t#[default]\n\tNone,\n\tStart,\n\tEnd,\n}\n\n#[derive(Clone, Debug, Default, PartialEq)]\npub enum SpiralTurnsState {\n\t#[default]\n\tInactive,\n\tHover,\n\tDragging,\n}\n\n#[derive(Clone, Debug, Default)]\npub struct SpiralTurns {\n\tpub layer: Option<LayerNodeIdentifier>,\n\tpub handle_state: SpiralTurnsState,\n\tinitial_turns: f64,\n\tinitial_outer_radius: f64,\n\tinitial_inner_radius: f64,\n\tinitial_growth_factor: f64,\n\tinitial_start_angle: f64,\n\tprevious_mouse_position: DVec2,\n\ttotal_angle_delta: f64,\n\tgizmo_type: GizmoType,\n\tspiral_type: SpiralType,\n}\n\nimpl SpiralTurns {\n\tpub fn cleanup(&mut self) {\n\t\tself.handle_state = SpiralTurnsState::Inactive;\n\t\tself.total_angle_delta = 0.;\n\t\tself.gizmo_type = GizmoType::None;\n\t\tself.layer = None;\n\t}\n\n\tpub fn update_state(&mut self, state: SpiralTurnsState) {\n\t\tself.handle_state = state;\n\t}\n\n\tpub fn hovered(&self) -> bool {\n\t\tself.handle_state == SpiralTurnsState::Hover\n\t}\n\n\tpub fn is_dragging(&self) -> bool {\n\t\tself.handle_state == SpiralTurnsState::Dragging\n\t}\n\n\tpub fn store_initial_parameters(\n\t\t&mut self,\n\t\tlayer: LayerNodeIdentifier,\n\t\tinner_radius: f64,\n\t\touter_radius: f64,\n\t\tturns: f64,\n\t\tstart_angle: f64,\n\t\tmouse_position: DVec2,\n\t\tgizmo_type: GizmoType,\n\t\tspiral_type: SpiralType,\n\t) {\n\t\tself.layer = Some(layer);\n\t\tself.initial_turns = turns;\n\t\tself.initial_growth_factor = calculate_growth_factor(inner_radius, turns, outer_radius, spiral_type);\n\t\tself.initial_inner_radius = inner_radius;\n\t\tself.initial_outer_radius = outer_radius;\n\t\tself.initial_start_angle = start_angle;\n\t\tself.previous_mouse_position = mouse_position;\n\t\tself.spiral_type = spiral_type;\n\t\tself.gizmo_type = gizmo_type;\n\t\tself.update_state(SpiralTurnsState::Hover);\n\t}\n\n\tpub fn handle_actions(&mut self, layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, _responses: &mut VecDeque<Message>) {\n\t\tlet viewport = document.metadata().transform_to_viewport(layer);\n\n\t\tmatch &self.handle_state {\n\t\t\tSpiralTurnsState::Inactive => {\n\t\t\t\tif let Some((spiral_type, start_angle, inner_radius, outer_radius, turns, _)) = extract_spiral_parameters(layer, document) {\n\t\t\t\t\tlet growth_factor = calculate_growth_factor(inner_radius, turns, outer_radius, spiral_type);\n\t\t\t\t\tlet end_point = viewport.transform_point2(spiral_point(turns * TAU + start_angle.to_radians(), inner_radius, growth_factor, spiral_type));\n\t\t\t\t\tlet start_point = viewport.transform_point2(spiral_point(0. + start_angle.to_radians(), inner_radius, growth_factor, spiral_type));\n\n\t\t\t\t\tif mouse_position.distance(end_point) < POINT_RADIUS_HANDLE_SNAP_THRESHOLD {\n\t\t\t\t\t\tself.store_initial_parameters(layer, inner_radius, outer_radius, turns, start_angle, mouse_position, GizmoType::End, spiral_type);\n\t\t\t\t\t} else if mouse_position.distance(start_point) < POINT_RADIUS_HANDLE_SNAP_THRESHOLD {\n\t\t\t\t\t\tself.store_initial_parameters(layer, inner_radius, outer_radius, turns, start_angle, mouse_position, GizmoType::Start, spiral_type);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tSpiralTurnsState::Hover | SpiralTurnsState::Dragging => {}\n\t\t}\n\t}\n\n\tpub fn overlays(&self, document: &DocumentMessageHandler, layer: Option<LayerNodeIdentifier>, _shape_editor: &mut &mut ShapeState, _mouse_position: DVec2, overlay_context: &mut OverlayContext) {\n\t\tlet Some(layer) = layer.or(self.layer) else { return };\n\t\tlet viewport = document.metadata().transform_to_viewport(layer);\n\n\t\tmatch &self.handle_state {\n\t\t\tSpiralTurnsState::Inactive => {\n\t\t\t\tif let Some((p1, p2)) = calculate_spiral_endpoints(layer, document, viewport, 0.).zip(calculate_spiral_endpoints(layer, document, viewport, TAU)) {\n\t\t\t\t\toverlay_context.manipulator_handle(p1, false, None);\n\t\t\t\t\toverlay_context.manipulator_handle(p2, false, None);\n\t\t\t\t}\n\t\t\t}\n\t\t\tSpiralTurnsState::Hover | SpiralTurnsState::Dragging => {\n\t\t\t\t// Is true only when hovered over the gizmo\n\t\t\t\tlet selected = self.layer.is_some();\n\t\t\t\tlet angle = match self.gizmo_type {\n\t\t\t\t\tGizmoType::End => TAU,\n\t\t\t\t\tGizmoType::Start => 0.,\n\t\t\t\t\tGizmoType::None => return,\n\t\t\t\t};\n\n\t\t\t\tif let Some(endpoint) = calculate_spiral_endpoints(layer, document, viewport, angle) {\n\t\t\t\t\toverlay_context.manipulator_handle(endpoint, selected, Some(COLOR_OVERLAY_RED));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn update_number_of_turns(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {\n\t\tuse graphene_std::vector::generator_nodes::spiral::*;\n\n\t\tlet Some(layer) = self.layer else {\n\t\t\treturn;\n\t\t};\n\n\t\tlet viewport = document.metadata().transform_to_viewport(layer);\n\t\tlet center = viewport.transform_point2(DVec2::ZERO);\n\n\t\tlet angle_delta = viewport\n\t\t\t.inverse()\n\t\t\t.transform_vector2(input.mouse.position - center)\n\t\t\t.angle_to(viewport.inverse().transform_vector2(self.previous_mouse_position - center))\n\t\t\t.to_degrees();\n\n\t\t// Skip update if angle calculation produced NaN or infinity (can happen when mouse is at center)\n\t\t// Also skip very small angle changes to reduce jitter near center\n\t\tif !angle_delta.is_finite() || angle_delta.abs() < 0.5 {\n\t\t\tself.previous_mouse_position = input.mouse.position;\n\t\t\treturn;\n\t\t}\n\n\t\t// Increase the number of turns and outer radius in unison such that growth and tightness remain same\n\t\tlet total_delta = self.total_angle_delta + angle_delta;\n\t\t// Convert the total angle (in degrees) to number of full turns\n\t\tlet turns_delta = total_delta / 360.;\n\n\t\t// Calculate the new outer radius based on spiral type and turn change\n\t\tlet outer_radius_change = match self.spiral_type {\n\t\t\tSpiralType::Archimedean => turns_delta * (self.initial_growth_factor) * TAU,\n\t\t\tSpiralType::Logarithmic => self.initial_outer_radius * ((self.initial_growth_factor * TAU * turns_delta).exp() - 1.),\n\t\t};\n\n\t\t// Skip if outer_radius calculation produced invalid values\n\t\tif !outer_radius_change.is_finite() {\n\t\t\treturn;\n\t\t}\n\n\t\tlet Some(node_id) = graph_modification_utils::get_spiral_id(layer, &document.network_interface) else {\n\t\t\treturn;\n\t\t};\n\n\t\tmatch self.gizmo_type {\n\t\t\tGizmoType::Start => {\n\t\t\t\tlet sign = -1.;\n\t\t\t\tlet new_turns = (self.initial_turns + turns_delta * sign).max(0.5);\n\t\t\t\tlet new_outer_radius = (self.initial_outer_radius + outer_radius_change * sign).max(0.1);\n\n\t\t\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\t\t\tinput_connector: InputConnector::node(node_id, StartAngleInput::INDEX),\n\t\t\t\t\tinput: NodeInput::value(TaggedValue::F64(self.initial_start_angle + total_delta), false),\n\t\t\t\t});\n\t\t\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\t\t\tinput_connector: InputConnector::node(node_id, TurnsInput::INDEX),\n\t\t\t\t\tinput: NodeInput::value(TaggedValue::F64(new_turns), false),\n\t\t\t\t});\n\t\t\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\t\t\tinput_connector: InputConnector::node(node_id, OuterRadiusInput::INDEX),\n\t\t\t\t\tinput: NodeInput::value(TaggedValue::F64(new_outer_radius), false),\n\t\t\t\t});\n\t\t\t}\n\t\t\tGizmoType::End => {\n\t\t\t\tlet new_turns = (self.initial_turns + turns_delta).max(0.5);\n\t\t\t\tlet new_outer_radius = (self.initial_outer_radius + outer_radius_change).max(0.1);\n\n\t\t\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\t\t\tinput_connector: InputConnector::node(node_id, TurnsInput::INDEX),\n\t\t\t\t\tinput: NodeInput::value(TaggedValue::F64(new_turns), false),\n\t\t\t\t});\n\t\t\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\t\t\tinput_connector: InputConnector::node(node_id, OuterRadiusInput::INDEX),\n\t\t\t\t\tinput: NodeInput::value(TaggedValue::F64(new_outer_radius), false),\n\t\t\t\t});\n\t\t\t}\n\t\t\tGizmoType::None => {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\tself.total_angle_delta += angle_delta;\n\t\tself.previous_mouse_position = input.mouse.position;\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/sweep_angle_gizmo.rs",
    "content": "use crate::consts::{ARC_SNAP_THRESHOLD, GIZMO_HIDE_THRESHOLD};\nuse crate::messages::message::Message;\nuse crate::messages::portfolio::document::overlays::utility_functions::text_width;\nuse crate::messages::portfolio::document::overlays::utility_types::OverlayContext;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::portfolio::document::utility_types::network_interface::InputConnector;\nuse crate::messages::prelude::DocumentMessageHandler;\nuse crate::messages::tool::common_functionality::graph_modification_utils;\nuse crate::messages::tool::common_functionality::shapes::shape_utility::{arc_end_points, calculate_arc_text_transform, extract_arc_parameters, format_rounded};\nuse crate::messages::tool::tool_messages::tool_prelude::*;\nuse glam::DVec2;\nuse graph_craft::document::value::TaggedValue;\nuse graph_craft::document::{NodeId, NodeInput};\nuse std::collections::VecDeque;\nuse std::f64::consts::FRAC_PI_4;\n\n#[derive(Clone, Debug, Default, PartialEq)]\npub enum SweepAngleGizmoState {\n\t#[default]\n\tInactive,\n\tHover,\n\tDragging,\n\tSnapped,\n}\n\n#[derive(Clone, Debug, Default, PartialEq)]\npub enum EndpointType {\n\t#[default]\n\tNone,\n\tStart,\n\tEnd,\n}\n\n#[derive(Clone, Debug, Default)]\npub struct SweepAngleGizmo {\n\tpub layer: Option<LayerNodeIdentifier>,\n\tendpoint: EndpointType,\n\tinitial_start_angle: f64,\n\tinitial_sweep_angle: f64,\n\tposition_before_rotation: DVec2,\n\tprevious_mouse_position: DVec2,\n\ttotal_angle_delta: f64,\n\tsnap_angles: Vec<f64>,\n\thandle_state: SweepAngleGizmoState,\n}\n\nimpl SweepAngleGizmo {\n\tpub fn hovered(&self) -> bool {\n\t\tself.handle_state == SweepAngleGizmoState::Hover\n\t}\n\n\tpub fn update_state(&mut self, state: SweepAngleGizmoState) {\n\t\tself.handle_state = state;\n\t}\n\n\tpub fn is_dragging_or_snapped(&self) -> bool {\n\t\tself.handle_state == SweepAngleGizmoState::Dragging || self.handle_state == SweepAngleGizmoState::Snapped\n\t}\n\n\tpub fn handle_actions(&mut self, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, mouse_position: DVec2) {\n\t\tif self.handle_state == SweepAngleGizmoState::Inactive {\n\t\t\tlet Some((start, end)) = arc_end_points(Some(layer), document) else { return };\n\t\t\tlet Some((_, start_angle, sweep_angle, _)) = extract_arc_parameters(Some(layer), document) else {\n\t\t\t\treturn;\n\t\t\t};\n\n\t\t\tlet center = document.metadata().transform_to_viewport(layer).transform_point2(DVec2::ZERO);\n\n\t\t\tif center.distance(start) < GIZMO_HIDE_THRESHOLD {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet (close_to_gizmo, endpoint_type) = if mouse_position.distance(start) < 5. {\n\t\t\t\t(true, EndpointType::Start)\n\t\t\t} else if mouse_position.distance(end) < 5. {\n\t\t\t\t(true, EndpointType::End)\n\t\t\t} else {\n\t\t\t\t(false, EndpointType::None)\n\t\t\t};\n\n\t\t\tif close_to_gizmo {\n\t\t\t\tself.layer = Some(layer);\n\t\t\t\tself.initial_start_angle = start_angle;\n\t\t\t\tself.initial_sweep_angle = sweep_angle;\n\t\t\t\tself.previous_mouse_position = mouse_position;\n\t\t\t\tself.total_angle_delta = 0.;\n\t\t\t\tself.position_before_rotation = if endpoint_type == EndpointType::End { end } else { start };\n\t\t\t\tself.endpoint = endpoint_type;\n\t\t\t\tself.snap_angles = Self::calculate_snap_angles();\n\n\t\t\t\tself.update_state(SweepAngleGizmoState::Hover);\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn overlays(\n\t\t&self,\n\t\tselected_arc_layer: Option<LayerNodeIdentifier>,\n\t\tdocument: &DocumentMessageHandler,\n\t\t_input: &InputPreprocessorMessageHandler,\n\t\t_mouse_position: DVec2,\n\t\toverlay_context: &mut OverlayContext,\n\t) {\n\t\tlet tilt_offset = document.document_ptz.unmodified_tilt();\n\n\t\tmatch self.handle_state {\n\t\t\tSweepAngleGizmoState::Inactive => {\n\t\t\t\tlet Some((point1, point2)) = arc_end_points(selected_arc_layer, document) else { return };\n\t\t\t\toverlay_context.manipulator_handle(point1, false, None);\n\t\t\t\toverlay_context.manipulator_handle(point2, false, None);\n\t\t\t}\n\t\t\tSweepAngleGizmoState::Hover => {\n\t\t\t\t// Highlight the currently hovered endpoint only\n\t\t\t\tlet Some((point1, point2)) = arc_end_points(self.layer, document) else { return };\n\n\t\t\t\tlet (point, other_point) = if self.endpoint == EndpointType::Start { (point1, point2) } else { (point2, point1) };\n\t\t\t\toverlay_context.manipulator_handle(point, true, None);\n\t\t\t\toverlay_context.manipulator_handle(other_point, false, None);\n\t\t\t}\n\t\t\tSweepAngleGizmoState::Dragging => {\n\t\t\t\t// Show snapping guides and angle arc while dragging\n\t\t\t\tlet Some(layer) = self.layer else { return };\n\t\t\t\tlet Some((current_start, current_end)) = arc_end_points(self.layer, document) else { return };\n\t\t\t\tlet viewport = document.metadata().transform_to_viewport(layer);\n\n\t\t\t\t// Depending on which endpoint is being dragged, draw guides relative to the static point\n\t\t\t\tlet (point, other_point) = if self.endpoint == EndpointType::End {\n\t\t\t\t\t(current_end, current_start)\n\t\t\t\t} else {\n\t\t\t\t\t(current_start, current_end)\n\t\t\t\t};\n\n\t\t\t\t// Draw the dashed line from center to drag start position\n\t\t\t\toverlay_context.dashed_line(self.position_before_rotation, viewport.transform_point2(DVec2::ZERO), None, None, Some(5.), Some(5.), Some(0.5));\n\n\t\t\t\toverlay_context.manipulator_handle(other_point, false, None);\n\n\t\t\t\t// Draw the angle, text and the bold line\n\t\t\t\tself.dragging_snapping_overlays(self.position_before_rotation, point, tilt_offset, viewport, overlay_context);\n\t\t\t}\n\t\t\tSweepAngleGizmoState::Snapped => {\n\t\t\t\t// When snapping is active, draw snapping arcs and angular guidelines\n\t\t\t\tlet Some((start, end)) = arc_end_points(self.layer, document) else { return };\n\t\t\t\tlet Some(layer) = self.layer else { return };\n\t\t\t\tlet viewport = document.metadata().transform_to_viewport(layer);\n\t\t\t\tlet center = viewport.transform_point2(DVec2::ZERO);\n\n\t\t\t\t// Draw snapping arc and angle overlays between the two points\n\t\t\t\tlet (a, b) = if self.endpoint == EndpointType::Start { (end, start) } else { (start, end) };\n\t\t\t\tself.dragging_snapping_overlays(a, b, tilt_offset, viewport, overlay_context);\n\n\t\t\t\t// Draw lines from endpoints to the arc center\n\t\t\t\toverlay_context.line(start, center, None, Some(2.));\n\t\t\t\toverlay_context.line(end, center, None, Some(2.));\n\n\t\t\t\t// Draw the line from drag start to arc center\n\t\t\t\toverlay_context.dashed_line(self.position_before_rotation, center, None, None, Some(5.), Some(5.), Some(0.5));\n\t\t\t}\n\t\t}\n\t}\n\n\t/// Draws the visual overlay during arc handle dragging or snapping interactions.\n\t/// This includes the dynamic arc sweep, angle label, and visual guides centered around the arc's origin.\n\tpub fn dragging_snapping_overlays(&self, initial_point: DVec2, final_point: DVec2, tilt_offset: f64, viewport: DAffine2, overlay_context: &mut OverlayContext) {\n\t\tlet center = viewport.transform_point2(DVec2::ZERO);\n\t\tlet initial_vector = initial_point - center;\n\t\tlet final_vector = final_point - center;\n\t\tlet offset_angle = initial_vector.to_angle() + tilt_offset;\n\n\t\tlet bold_radius = final_point.distance(center);\n\n\t\tlet angle = initial_vector.angle_to(final_vector).to_degrees();\n\t\tlet display_angle = viewport\n\t\t\t.inverse()\n\t\t\t.transform_point2(final_point)\n\t\t\t.angle_to(viewport.inverse().transform_point2(initial_point))\n\t\t\t.to_degrees();\n\n\t\tlet text = format!(\"{}°\", format_rounded(display_angle, 2));\n\t\tconst FONT_SIZE: f64 = 12.;\n\n\t\tlet text_width = text_width(&text, FONT_SIZE);\n\n\t\tlet text_texture_width = text_width / 2.;\n\n\t\tlet transform = calculate_arc_text_transform(angle, offset_angle, center, text_texture_width);\n\n\t\toverlay_context.arc_sweep_angle(offset_angle, angle, final_point, bold_radius, center, &text, transform);\n\t}\n\n\tpub fn update_arc(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {\n\t\tlet Some(layer) = self.layer else { return };\n\t\tlet Some((_, current_start_angle, current_sweep_angle, _)) = extract_arc_parameters(Some(layer), document) else {\n\t\t\treturn;\n\t\t};\n\n\t\tlet viewport = document.metadata().transform_to_viewport(layer);\n\t\tlet angle_delta = viewport\n\t\t\t.inverse()\n\t\t\t.transform_point2(self.previous_mouse_position)\n\t\t\t.angle_to(viewport.inverse().transform_point2(input.mouse.position))\n\t\t\t.to_degrees();\n\t\tlet angle = self.total_angle_delta + angle_delta;\n\n\t\tlet Some(node_id) = graph_modification_utils::get_arc_id(layer, &document.network_interface) else {\n\t\t\treturn;\n\t\t};\n\n\t\tself.update_state(SweepAngleGizmoState::Dragging);\n\n\t\tmatch self.endpoint {\n\t\t\tEndpointType::Start => {\n\t\t\t\t// Dragging start changes both start and sweep\n\n\t\t\t\tlet sign = -angle.signum();\n\t\t\t\tlet mut total = angle;\n\n\t\t\t\tlet new_start_angle = self.initial_start_angle + total;\n\t\t\t\tlet new_sweep_angle = self.initial_sweep_angle + total.abs() * sign;\n\n\t\t\t\tmatch () {\n\t\t\t\t\t// Clamp sweep angle to 360°\n\t\t\t\t\t() if new_sweep_angle > 360. => {\n\t\t\t\t\t\tlet wrapped = new_sweep_angle % 360.;\n\t\t\t\t\t\tself.total_angle_delta = -wrapped;\n\n\t\t\t\t\t\tself.endpoint = EndpointType::End;\n\n\t\t\t\t\t\tself.initial_sweep_angle = 360.;\n\t\t\t\t\t\tself.initial_start_angle = current_start_angle;\n\t\t\t\t\t\tself.update_state(SweepAngleGizmoState::Snapped);\n\n\t\t\t\t\t\tself.apply_arc_update(node_id, self.initial_start_angle, self.initial_sweep_angle - wrapped, input, responses);\n\t\t\t\t\t}\n\t\t\t\t\t() if new_sweep_angle < 0. => {\n\t\t\t\t\t\tlet rest_angle = angle_delta + new_sweep_angle;\n\n\t\t\t\t\t\tself.total_angle_delta = new_sweep_angle.abs();\n\t\t\t\t\t\tself.endpoint = EndpointType::End;\n\n\t\t\t\t\t\tself.initial_sweep_angle = 0.;\n\t\t\t\t\t\tself.initial_start_angle = current_start_angle + rest_angle;\n\n\t\t\t\t\t\tself.apply_arc_update(node_id, self.initial_start_angle, new_sweep_angle.abs(), input, responses);\n\t\t\t\t\t}\n\t\t\t\t\t// Wrap start angle > 180° back into [-180°, 180°] and adjust sweep\n\t\t\t\t\t() if new_start_angle > 180. => {\n\t\t\t\t\t\tlet overflow = new_start_angle % 180.;\n\t\t\t\t\t\tlet rest_angle = angle_delta - overflow;\n\n\t\t\t\t\t\t// We wrap the angle back into [-180°, 180°] range by jumping from +180° to -180°\n\t\t\t\t\t\t// Example: dragging past 190° becomes -170°, and we subtract the overshoot from sweep\n\t\t\t\t\t\t// Sweep angle must shrink to maintain consistent arc\n\t\t\t\t\t\tself.total_angle_delta = rest_angle;\n\t\t\t\t\t\tself.initial_start_angle = -180.;\n\t\t\t\t\t\tself.initial_sweep_angle = current_sweep_angle - rest_angle;\n\n\t\t\t\t\t\tself.apply_arc_update(node_id, self.initial_start_angle + overflow, self.initial_sweep_angle - overflow, input, responses);\n\t\t\t\t\t}\n\t\t\t\t\t// Wrap start angle < -180° back into [-180°, 180°] and adjust sweep\n\t\t\t\t\t() if new_start_angle < -180. => {\n\t\t\t\t\t\tlet underflow = new_start_angle % 180.;\n\t\t\t\t\t\tlet rest_angle = angle_delta - underflow;\n\n\t\t\t\t\t\t// We wrap the angle back into [-180°, 180°] by jumping from -190° to +170°\n\t\t\t\t\t\t// Sweep must grow to reflect continued clockwise drag past -180°\n\t\t\t\t\t\t// Start angle flips from -190° to +170°, and sweep increases accordingly\n\t\t\t\t\t\tself.total_angle_delta = underflow;\n\t\t\t\t\t\tself.initial_start_angle = 180.;\n\t\t\t\t\t\tself.initial_sweep_angle = current_sweep_angle + rest_angle.abs();\n\n\t\t\t\t\t\tself.apply_arc_update(node_id, self.initial_start_angle + underflow, self.initial_sweep_angle + underflow.abs(), input, responses);\n\t\t\t\t\t}\n\t\t\t\t\t_ => {\n\t\t\t\t\t\tif let Some(snapped_delta) = self.check_snapping(self.initial_sweep_angle + total.abs() * sign) {\n\t\t\t\t\t\t\ttotal += snapped_delta;\n\t\t\t\t\t\t\tself.update_state(SweepAngleGizmoState::Snapped);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tself.total_angle_delta = angle;\n\t\t\t\t\t\tself.apply_arc_update(node_id, self.initial_start_angle + total, self.initial_sweep_angle + total.abs() * sign, input, responses);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tEndpointType::End => {\n\t\t\t\t// Dragging the end only changes sweep angle\n\n\t\t\t\tlet mut total = angle;\n\t\t\t\tlet new_sweep_angle = self.initial_sweep_angle + angle;\n\n\t\t\t\tmatch () {\n\t\t\t\t\t// Clamp sweep angle below 0°, switch to start\n\t\t\t\t\t() if new_sweep_angle < 0. => {\n\t\t\t\t\t\tlet delta = angle_delta - current_sweep_angle;\n\t\t\t\t\t\tlet sign = -delta.signum();\n\n\t\t\t\t\t\tself.initial_sweep_angle = 0.;\n\t\t\t\t\t\tself.total_angle_delta = delta;\n\t\t\t\t\t\tself.endpoint = EndpointType::Start;\n\n\t\t\t\t\t\tself.apply_arc_update(node_id, self.initial_start_angle + delta, self.initial_sweep_angle + delta.abs() * sign, input, responses);\n\t\t\t\t\t}\n\t\t\t\t\t// Clamp sweep angle above 360°, switch to start\n\t\t\t\t\t() if new_sweep_angle > 360. => {\n\t\t\t\t\t\tlet delta = angle_delta - (360. - new_sweep_angle);\n\t\t\t\t\t\tlet sign = -delta.signum();\n\n\t\t\t\t\t\tself.total_angle_delta = angle_delta - (360. - new_sweep_angle);\n\t\t\t\t\t\tself.initial_sweep_angle = 360.;\n\t\t\t\t\t\tself.endpoint = EndpointType::Start;\n\t\t\t\t\t\tself.update_state(SweepAngleGizmoState::Snapped);\n\n\t\t\t\t\t\tself.apply_arc_update(node_id, self.initial_start_angle + angle_delta, self.initial_sweep_angle + angle_delta.abs() * sign, input, responses);\n\t\t\t\t\t}\n\t\t\t\t\t_ => {\n\t\t\t\t\t\tif let Some(snapped_delta) = self.check_snapping(self.initial_sweep_angle + angle) {\n\t\t\t\t\t\t\ttotal += snapped_delta;\n\t\t\t\t\t\t\tself.update_state(SweepAngleGizmoState::Snapped);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tself.total_angle_delta = angle;\n\t\t\t\t\t\tself.apply_arc_update(node_id, self.initial_start_angle, self.initial_sweep_angle + total, input, responses);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tEndpointType::None => {}\n\t\t}\n\t}\n\n\t/// Applies the updated start and sweep angles to the arc.\n\tfn apply_arc_update(&mut self, node_id: NodeId, start_angle: f64, sweep_angle: f64, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {\n\t\tself.snap_angles = Self::calculate_snap_angles();\n\n\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\tinput_connector: InputConnector::node(node_id, 2),\n\t\t\tinput: NodeInput::value(TaggedValue::F64(start_angle), false),\n\t\t});\n\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\tinput_connector: InputConnector::node(node_id, 3),\n\t\t\tinput: NodeInput::value(TaggedValue::F64(sweep_angle), false),\n\t\t});\n\n\t\tself.previous_mouse_position = input.mouse.position;\n\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t}\n\n\tpub fn check_snapping(&self, new_sweep_angle: f64) -> Option<f64> {\n\t\tself.snap_angles.iter().find(|angle| (**angle - new_sweep_angle).abs() <= ARC_SNAP_THRESHOLD).map(|angle| {\n\t\t\tlet delta = angle - new_sweep_angle;\n\t\t\tif self.endpoint == EndpointType::End { delta } else { -delta }\n\t\t})\n\t}\n\n\tpub fn calculate_snap_angles() -> Vec<f64> {\n\t\tlet mut snap_points = Vec::new();\n\n\t\tfor i in 0..=8 {\n\t\t\tlet snap_point = i as f64 * FRAC_PI_4;\n\t\t\tsnap_points.push(snap_point.to_degrees());\n\t\t}\n\n\t\tsnap_points\n\t}\n\n\tpub fn cleanup(&mut self) {\n\t\tself.layer = None;\n\t\tself.endpoint = EndpointType::None;\n\t\tself.handle_state = SweepAngleGizmoState::Inactive;\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/graph_modification_utils.rs",
    "content": "use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;\nuse crate::messages::portfolio::document::node_graph::document_node_definitions::{self, DefinitionIdentifier};\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, NodeNetworkInterface, NodeTemplate};\nuse crate::messages::prelude::*;\nuse glam::DVec2;\nuse graph_craft::document::value::TaggedValue;\nuse graph_craft::document::{NodeId, NodeInput};\nuse graph_craft::{ProtoNodeIdentifier, concrete};\nuse graphene_std::Color;\nuse graphene_std::NodeInputDecleration;\nuse graphene_std::raster::BlendMode;\nuse graphene_std::raster_types::{CPU, GPU, Raster};\nuse graphene_std::subpath::Subpath;\nuse graphene_std::table::Table;\nuse graphene_std::text::{Font, TypesettingConfig};\nuse graphene_std::vector::misc::ManipulatorPointId;\nuse graphene_std::vector::style::{Fill, Gradient};\nuse graphene_std::vector::{PointId, SegmentId, VectorModificationType};\nuse std::collections::VecDeque;\n\n/// Returns the ID of the first Spline node in the horizontal flow which is not followed by a `Path` node, or `None` if none exists.\npub fn find_spline(document: &DocumentMessageHandler, layer: LayerNodeIdentifier) -> Option<NodeId> {\n\tdocument\n\t\t.network_interface\n\t\t.upstream_flow_back_from_nodes([layer.to_node()].to_vec(), &[], FlowType::HorizontalFlow)\n\t\t.map(|node_id| (document.network_interface.reference(&node_id, &[]), node_id))\n\t\t.take_while(|(reference, _)| reference.as_ref().is_some_and(|node_ref| node_ref != &DefinitionIdentifier::Network(\"Path\".into())))\n\t\t.find(|(reference, _)| {\n\t\t\treference\n\t\t\t\t.as_ref()\n\t\t\t\t.is_some_and(|node_ref| *node_ref == DefinitionIdentifier::ProtoNode(graphene_std::vector::spline::IDENTIFIER))\n\t\t})\n\t\t.map(|node| node.1)\n}\n\n/// Merge `second_layer` to the `first_layer`.\npub fn merge_layers(document: &DocumentMessageHandler, first_layer: LayerNodeIdentifier, second_layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>) {\n\t// Skip layers that are children of each other (or the same)\n\tif first_layer.ancestors(document.metadata()).any(|l| l == second_layer) || second_layer.ancestors(document.metadata()).any(|l| l == first_layer) {\n\t\treturn;\n\t}\n\t// Calculate the downstream transforms in order to bring the other vector geometry into the same layer space\n\tlet first_layer_transform = document.metadata().downstream_transform_to_document(first_layer);\n\tlet second_layer_transform = document.metadata().downstream_transform_to_document(second_layer);\n\n\t// Represents the change in position that would occur if the other layer was moved below the current layer\n\tlet transform_delta = first_layer_transform * second_layer_transform.inverse();\n\tlet offset = transform_delta.inverse();\n\tresponses.add(GraphOperationMessage::TransformChange {\n\t\tlayer: second_layer,\n\t\ttransform: offset,\n\t\ttransform_in: TransformIn::Local,\n\t\tskip_rerender: false,\n\t});\n\n\tlet mut current_and_other_layer_is_spline = false;\n\n\tif let (Some(current_layer_spline), Some(other_layer_spline)) = (find_spline(document, first_layer), find_spline(document, second_layer)) {\n\t\tresponses.add(NodeGraphMessage::DeleteNodes {\n\t\t\tnode_ids: [current_layer_spline, other_layer_spline].to_vec(),\n\t\t\tdelete_children: false,\n\t\t});\n\t\tcurrent_and_other_layer_is_spline = true;\n\t}\n\n\t// Move the `second_layer` below the `first_layer` for positioning purposes\n\tlet Some(first_layer_parent) = first_layer.parent(document.metadata()) else { return };\n\tlet Some(first_layer_index) = first_layer_parent.children(document.metadata()).position(|child| child == first_layer) else {\n\t\treturn;\n\t};\n\tresponses.add(NodeGraphMessage::MoveLayerToStack {\n\t\tlayer: second_layer,\n\t\tparent: first_layer_parent,\n\t\tinsert_index: first_layer_index + 1,\n\t});\n\n\t// Merge the inputs of the two layers\n\tlet merge_node_id = NodeId::new();\n\tlet merge_node = document_node_definitions::resolve_network_node_type(\"Merge\")\n\t\t.expect(\"Failed to create merge node\")\n\t\t.default_node_template();\n\tresponses.add(NodeGraphMessage::InsertNode {\n\t\tnode_id: merge_node_id,\n\t\tnode_template: Box::new(merge_node),\n\t});\n\tresponses.add(NodeGraphMessage::SetToNodeOrLayer {\n\t\tnode_id: merge_node_id,\n\t\tis_layer: false,\n\t});\n\tresponses.add(NodeGraphMessage::MoveNodeToChainStart {\n\t\tnode_id: merge_node_id,\n\t\tparent: first_layer,\n\t});\n\tresponses.add(NodeGraphMessage::ConnectUpstreamOutputToInput {\n\t\tdownstream_input: InputConnector::node(second_layer.to_node(), 1),\n\t\tinput_connector: InputConnector::node(merge_node_id, 1),\n\t});\n\tresponses.add(NodeGraphMessage::DeleteNodes {\n\t\tnode_ids: vec![second_layer.to_node()],\n\t\tdelete_children: false,\n\t});\n\n\t// Add a Flatten Path node after the merge\n\tlet flatten_node_id = NodeId::new();\n\tlet flatten_node = document_node_definitions::resolve_proto_node_type(graphene_std::vector::flatten_path::IDENTIFIER)\n\t\t.expect(\"Failed to create flatten node\")\n\t\t.default_node_template();\n\tresponses.add(NodeGraphMessage::InsertNode {\n\t\tnode_id: flatten_node_id,\n\t\tnode_template: Box::new(flatten_node),\n\t});\n\tresponses.add(NodeGraphMessage::MoveNodeToChainStart {\n\t\tnode_id: flatten_node_id,\n\t\tparent: first_layer,\n\t});\n\n\t// Add a path node after the flatten node\n\tlet path_node_id = NodeId::new();\n\tlet path_node = document_node_definitions::resolve_network_node_type(\"Path\")\n\t\t.expect(\"Failed to create path node\")\n\t\t.default_node_template();\n\tresponses.add(NodeGraphMessage::InsertNode {\n\t\tnode_id: path_node_id,\n\t\tnode_template: Box::new(path_node),\n\t});\n\tresponses.add(NodeGraphMessage::MoveNodeToChainStart {\n\t\tnode_id: path_node_id,\n\t\tparent: first_layer,\n\t});\n\n\t// Add a Spline node after the Path node if both the layers we are merging is spline.\n\tif current_and_other_layer_is_spline {\n\t\tlet spline_node_id = NodeId::new();\n\t\tlet spline_node = document_node_definitions::resolve_proto_node_type(graphene_std::vector::spline::IDENTIFIER)\n\t\t\t.expect(\"Failed to create Spline node\")\n\t\t\t.default_node_template();\n\t\tresponses.add(NodeGraphMessage::InsertNode {\n\t\t\tnode_id: spline_node_id,\n\t\t\tnode_template: Box::new(spline_node),\n\t\t});\n\t\tresponses.add(NodeGraphMessage::MoveNodeToChainStart {\n\t\t\tnode_id: spline_node_id,\n\t\t\tparent: first_layer,\n\t\t});\n\t}\n\n\t// Add a transform node to ensure correct tooling modifications\n\tlet transform_node_id = NodeId::new();\n\tlet transform_node = document_node_definitions::resolve_network_node_type(\"Transform\")\n\t\t.expect(\"Failed to create transform node\")\n\t\t.default_node_template();\n\tresponses.add(NodeGraphMessage::InsertNode {\n\t\tnode_id: transform_node_id,\n\t\tnode_template: Box::new(transform_node),\n\t});\n\tresponses.add(NodeGraphMessage::MoveNodeToChainStart {\n\t\tnode_id: transform_node_id,\n\t\tparent: first_layer,\n\t});\n\n\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\tresponses.add(DeferMessage::AfterGraphRun {\n\t\tmessages: vec![PenToolMessage::RecalculateLatestPointsPosition.into()],\n\t});\n}\n\n/// Merge the `first_endpoint` with `second_endpoint`.\npub fn merge_points(document: &DocumentMessageHandler, layer: LayerNodeIdentifier, first_endpoint: PointId, second_endpont: PointId, responses: &mut VecDeque<Message>) {\n\tlet transform = document.metadata().transform_to_document(layer);\n\tlet Some(vector) = document.network_interface.compute_modified_vector(layer) else { return };\n\n\tlet segment = vector.segment_bezier_iter().find(|(_, _, start, end)| *end == second_endpont || *start == second_endpont);\n\tlet Some((segment, _, mut segment_start_point, mut segment_end_point)) = segment else {\n\t\tlog::error!(\"Could not get the segment for second_endpoint.\");\n\t\treturn;\n\t};\n\n\tlet mut handles = [None; 2];\n\tif let Some(handle_position) = ManipulatorPointId::PrimaryHandle(segment).get_position(&vector) {\n\t\tlet anchor_position = ManipulatorPointId::Anchor(segment_start_point).get_position(&vector).unwrap();\n\t\tlet handle_position = transform.transform_point2(handle_position);\n\t\tlet anchor_position = transform.transform_point2(anchor_position);\n\t\tlet anchor_to_handle = handle_position - anchor_position;\n\t\thandles[0] = Some(anchor_to_handle);\n\t}\n\tif let Some(handle_position) = ManipulatorPointId::EndHandle(segment).get_position(&vector) {\n\t\tlet anchor_position = ManipulatorPointId::Anchor(segment_end_point).get_position(&vector).unwrap();\n\t\tlet handle_position = transform.transform_point2(handle_position);\n\t\tlet anchor_position = transform.transform_point2(anchor_position);\n\t\tlet anchor_to_handle = handle_position - anchor_position;\n\t\thandles[1] = Some(anchor_to_handle);\n\t}\n\n\tif segment_start_point == second_endpont {\n\t\tcore::mem::swap(&mut segment_start_point, &mut segment_end_point);\n\t\thandles.reverse();\n\t}\n\n\tlet modification_type = VectorModificationType::RemovePoint { id: second_endpont };\n\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\tlet modification_type = VectorModificationType::RemoveSegment { id: segment };\n\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\n\tlet points = [segment_start_point, first_endpoint];\n\tlet id = SegmentId::generate();\n\tlet modification_type = VectorModificationType::InsertSegment { id, points, handles };\n\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n}\n\n/// Create a new vector layer.\npub fn new_vector_layer(subpaths: Vec<Subpath<PointId>>, id: NodeId, parent: LayerNodeIdentifier, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier {\n\tlet insert_index = 0;\n\tresponses.add(GraphOperationMessage::NewVectorLayer { id, subpaths, parent, insert_index });\n\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![id] });\n\n\tLayerNodeIdentifier::new_unchecked(id)\n}\n\n/// Create a new bitmap layer.\npub fn new_image_layer(image_frame: Table<Raster<CPU>>, id: NodeId, parent: LayerNodeIdentifier, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier {\n\tlet insert_index = 0;\n\tresponses.add(GraphOperationMessage::NewBitmapLayer {\n\t\tid,\n\t\timage_frame,\n\t\tparent,\n\t\tinsert_index,\n\t});\n\tLayerNodeIdentifier::new_unchecked(id)\n}\n\n/// Create a new group layer from an SVG string.\npub fn new_svg_layer(svg: String, transform: glam::DAffine2, id: NodeId, parent: LayerNodeIdentifier, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier {\n\tlet insert_index = 0;\n\tresponses.add(GraphOperationMessage::NewSvg {\n\t\tid,\n\t\tsvg,\n\t\ttransform,\n\t\tparent,\n\t\tinsert_index,\n\t});\n\tLayerNodeIdentifier::new_unchecked(id)\n}\n\npub fn new_custom(id: NodeId, nodes: Vec<(NodeId, NodeTemplate)>, parent: LayerNodeIdentifier, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier {\n\tresponses.add(GraphOperationMessage::NewCustomLayer { id, nodes, parent, insert_index: 0 });\n\tresponses.add(GraphOperationMessage::SetUpstreamToChain {\n\t\tlayer: LayerNodeIdentifier::new_unchecked(id),\n\t});\n\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![id] });\n\tLayerNodeIdentifier::new_unchecked(id)\n}\n\n/// Locate the origin of the \"Transform\" node.\npub fn get_origin(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<DVec2> {\n\tuse graphene_std::transform_nodes::transform::*;\n\n\tif let TaggedValue::DVec2(origin) = NodeGraphLayer::new(layer, network_interface).find_input(&DefinitionIdentifier::Network(\"Transform\".into()), TranslationInput::INDEX)? {\n\t\tSome(*origin)\n\t} else {\n\t\tNone\n\t}\n}\n\npub fn get_viewport_origin(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DVec2 {\n\tlet origin = get_origin(layer, network_interface).unwrap_or_default();\n\tnetwork_interface.document_metadata().downstream_transform_to_viewport(layer).transform_point2(origin)\n}\n\npub fn get_viewport_center(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DVec2 {\n\tlet [min, max] = network_interface.document_metadata().nonzero_bounding_box(layer);\n\tlet center = DVec2::splat(0.5);\n\tnetwork_interface.document_metadata().transform_to_viewport(layer).transform_point2(min + (max - min) * center)\n}\n\n/// Get the current gradient of a layer from the closest \"Fill\" node.\npub fn get_gradient(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<Gradient> {\n\tlet fill_index = 1;\n\n\tlet inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER))?;\n\tlet TaggedValue::Fill(Fill::Gradient(gradient)) = inputs.get(fill_index)?.as_value()? else {\n\t\treturn None;\n\t};\n\tSome(gradient.clone())\n}\n\n/// Get the current fill of a layer from the closest \"Fill\" node.\npub fn get_fill_color(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<Color> {\n\tlet fill_index = 1;\n\n\tlet inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER))?;\n\tlet TaggedValue::Fill(Fill::Solid(color)) = inputs.get(fill_index)?.as_value()? else {\n\t\treturn None;\n\t};\n\tSome(color.to_linear_srgb())\n}\n\n/// Get the current blend mode of a layer from the closest \"Blending\" node.\npub fn get_blend_mode(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<BlendMode> {\n\tlet inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::blending_nodes::blending::IDENTIFIER))?;\n\tlet TaggedValue::BlendMode(blend_mode) = inputs.get(1)?.as_value()? else {\n\t\treturn None;\n\t};\n\tSome(*blend_mode)\n}\n\n/// Get the current opacity of a layer from the closest \"Blending\" node.\n/// This may differ from the actual opacity contained within the data type reaching this layer, because that actual opacity may be:\n/// - Multiplied with additional opacity nodes earlier in the chain\n/// - Set by an Opacity node with an exposed input value driven by another node\n/// - Already factored into the pixel alpha channel of an image\n/// - The default value of 100% if no Opacity node is present, but this function returns None in that case\n///\n/// With those limitations in mind, the intention of this function is to show just the value already present in an upstream Opacity node so that value can be directly edited.\npub fn get_opacity(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<f64> {\n\tlet inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::blending_nodes::blending::IDENTIFIER))?;\n\tlet TaggedValue::F64(opacity) = inputs.get(2)?.as_value()? else {\n\t\treturn None;\n\t};\n\tSome(*opacity)\n}\n\npub fn get_clip_mode(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<bool> {\n\tlet inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::blending_nodes::blending::IDENTIFIER))?;\n\tlet TaggedValue::Bool(clip) = inputs.get(4)?.as_value()? else {\n\t\treturn None;\n\t};\n\tSome(*clip)\n}\n\npub fn get_fill(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<f64> {\n\tlet inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::blending_nodes::blending::IDENTIFIER))?;\n\tlet TaggedValue::F64(fill) = inputs.get(3)?.as_value()? else {\n\t\treturn None;\n\t};\n\tSome(*fill)\n}\n\npub fn get_fill_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {\n\tNodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector_nodes::fill::IDENTIFIER))\n}\n\npub fn get_circle_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {\n\tNodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector_nodes::circle::IDENTIFIER))\n}\n\npub fn get_ellipse_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {\n\tNodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector_nodes::ellipse::IDENTIFIER))\n}\n\npub fn get_line_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {\n\tNodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector_nodes::line::IDENTIFIER))\n}\n\npub fn get_polygon_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {\n\tNodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector_nodes::regular_polygon::IDENTIFIER))\n}\n\npub fn get_rectangle_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {\n\tNodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector_nodes::rectangle::IDENTIFIER))\n}\n\npub fn get_star_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {\n\tNodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector_nodes::star::IDENTIFIER))\n}\n\npub fn get_arc_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {\n\tNodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector_nodes::arc::IDENTIFIER))\n}\n\npub fn get_arrow_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {\n\tNodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector_nodes::arrow::IDENTIFIER))\n}\n\npub fn get_spiral_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {\n\tNodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector_nodes::spiral::IDENTIFIER))\n}\n\npub fn get_text_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {\n\tNodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER))\n}\n\npub fn get_grid_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {\n\tNodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::grid::IDENTIFIER))\n}\n\n/// Gets properties from the Text node\npub fn get_text(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<(&String, &Font, TypesettingConfig, bool)> {\n\tlet inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER))?;\n\n\tlet Some(TaggedValue::String(text)) = &inputs[graphene_std::text::text::TextInput::INDEX].as_value() else {\n\t\treturn None;\n\t};\n\tlet Some(TaggedValue::Font(font)) = &inputs[graphene_std::text::text::FontInput::INDEX].as_value() else {\n\t\treturn None;\n\t};\n\tlet Some(&TaggedValue::F64(font_size)) = inputs[graphene_std::text::text::SizeInput::INDEX].as_value() else {\n\t\treturn None;\n\t};\n\tlet Some(&TaggedValue::F64(line_height_ratio)) = inputs[graphene_std::text::text::LineHeightInput::INDEX].as_value() else {\n\t\treturn None;\n\t};\n\tlet Some(&TaggedValue::F64(character_spacing)) = inputs[graphene_std::text::text::CharacterSpacingInput::INDEX].as_value() else {\n\t\treturn None;\n\t};\n\tlet Some(&TaggedValue::Bool(has_max_width)) = inputs[graphene_std::text::text::HasMaxWidthInput::INDEX].as_value() else {\n\t\treturn None;\n\t};\n\tlet Some(&TaggedValue::F64(max_width)) = inputs[graphene_std::text::text::MaxWidthInput::INDEX].as_value() else {\n\t\treturn None;\n\t};\n\tlet Some(&TaggedValue::Bool(has_max_height)) = inputs[graphene_std::text::text::HasMaxHeightInput::INDEX].as_value() else {\n\t\treturn None;\n\t};\n\tlet Some(&TaggedValue::F64(max_height)) = inputs[graphene_std::text::text::MaxHeightInput::INDEX].as_value() else {\n\t\treturn None;\n\t};\n\tlet Some(&TaggedValue::F64(tilt)) = inputs[graphene_std::text::text::TiltInput::INDEX].as_value() else {\n\t\treturn None;\n\t};\n\tlet Some(&TaggedValue::TextAlign(align)) = inputs[graphene_std::text::text::AlignInput::INDEX].as_value() else {\n\t\treturn None;\n\t};\n\tlet Some(&TaggedValue::Bool(per_glyph_instances)) = inputs[graphene_std::text::text::SeparateGlyphElementsInput::INDEX].as_value() else {\n\t\treturn None;\n\t};\n\n\tlet typesetting = TypesettingConfig {\n\t\tfont_size,\n\t\tline_height_ratio,\n\t\tmax_width: has_max_width.then_some(max_width),\n\t\tmax_height: has_max_height.then_some(max_height),\n\t\tcharacter_spacing,\n\t\ttilt,\n\t\talign,\n\t};\n\tSome((text, font, typesetting, per_glyph_instances))\n}\n\npub fn get_stroke_width(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<f64> {\n\tlet weight_node_input_index = graphene_std::vector::stroke::WeightInput::INDEX;\n\tif let TaggedValue::F64(width) = NodeGraphLayer::new(layer, network_interface).find_input(&DefinitionIdentifier::ProtoNode(graphene_std::vector::stroke::IDENTIFIER), weight_node_input_index)? {\n\t\tSome(*width)\n\t} else {\n\t\tNone\n\t}\n}\n\n/// Checks if a specified layer uses an upstream node matching the given name.\npub fn is_layer_fed_by_node_of_name(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface, identifier: &DefinitionIdentifier) -> bool {\n\tNodeGraphLayer::new(layer, network_interface).find_node_inputs(identifier).is_some()\n}\n\n/// An immutable reference to a layer within the document node graph for easy access.\npub struct NodeGraphLayer<'a> {\n\tnetwork_interface: &'a NodeNetworkInterface,\n\tlayer_node: NodeId,\n}\n\nimpl<'a> NodeGraphLayer<'a> {\n\t/// Get the layer node from the document\n\tpub fn new(layer: LayerNodeIdentifier, network_interface: &'a NodeNetworkInterface) -> Self {\n\t\tdebug_assert!(layer != LayerNodeIdentifier::ROOT_PARENT, \"Cannot create new NodeGraphLayer from ROOT_PARENT\");\n\t\tSelf {\n\t\t\tnetwork_interface,\n\t\t\tlayer_node: layer.to_node(),\n\t\t}\n\t}\n\n\t/// Return an iterator up the horizontal flow of the layer\n\tpub fn horizontal_layer_flow(&self) -> impl Iterator<Item = NodeId> + use<'a> {\n\t\tself.network_interface.upstream_flow_back_from_nodes(vec![self.layer_node], &[], FlowType::HorizontalFlow)\n\t}\n\n\t/// Node id of a node if it exists in the layer's primary flow\n\tpub fn upstream_node_id_from_name(&self, identifier: &DefinitionIdentifier) -> Option<NodeId> {\n\t\tself.horizontal_layer_flow()\n\t\t\t.find(|node_id| self.network_interface.reference(node_id, &[]).is_some_and(|reference| reference == *identifier))\n\t}\n\n\t/// Node id of a visible node if it exists in the layer's primary flow until another layer\n\tpub fn upstream_visible_node_id_from_name_in_layer(&self, identifier: &DefinitionIdentifier) -> Option<NodeId> {\n\t\t// `.skip(1)` is used to skip self\n\t\tself.horizontal_layer_flow()\n\t\t\t.skip(1)\n\t\t\t.take_while(|node_id| !self.network_interface.is_layer(node_id, &[]))\n\t\t\t.filter(|node_id| self.network_interface.is_visible(node_id, &[]))\n\t\t\t.find(|node_id| self.network_interface.reference(node_id, &[]).is_some_and(|reference| reference == *identifier))\n\t}\n\n\t/// Node id of a protonode if it exists in the layer's primary flow\n\tpub fn upstream_node_id_from_protonode(&self, protonode_identifier: ProtoNodeIdentifier) -> Option<NodeId> {\n\t\tself.horizontal_layer_flow()\n\t\t\t// Take until a different layer is reached\n\t\t\t.take_while(|&node_id| node_id == self.layer_node || !self.network_interface.is_layer(&node_id, &[]))\n\t\t\t.find(|node_id| {\n\t\t\t\tself.network_interface\n\t\t\t\t\t.implementation(node_id, &[])\n\t\t\t\t\t.is_some_and(|implementation| *implementation == graph_craft::document::DocumentNodeImplementation::ProtoNode(protonode_identifier.clone()))\n\t\t\t})\n\t}\n\n\t/// Find all of the inputs of a specific node within the layer's primary flow, up until the next layer is reached.\n\tpub fn find_node_inputs(&self, identifier: &DefinitionIdentifier) -> Option<&'a Vec<NodeInput>> {\n\t\t// `.skip(1)` is used to skip self\n\t\tself.horizontal_layer_flow()\n\t\t\t.skip(1)\n\t\t\t.take_while(|node_id| !self.network_interface.is_layer(node_id, &[]))\n\t\t\t.find(|node_id| self.network_interface.reference(node_id, &[]).is_some_and(|reference| reference == *identifier))\n\t\t\t.and_then(|node_id| self.network_interface.document_network().nodes.get(&node_id).map(|node| &node.inputs))\n\t}\n\n\t/// Find a specific input of a node within the layer's primary flow\n\tpub fn find_input(&self, identifier: &DefinitionIdentifier, index: usize) -> Option<&'a TaggedValue> {\n\t\t// TODO: Find a better way to accept a node input rather than using its index (which is quite unclear and fragile)\n\t\tself.find_node_inputs(identifier)?.get(index)?.as_value()\n\t}\n\n\t/// Check if a layer is a raster layer\n\tpub fn is_raster_layer(layer: LayerNodeIdentifier, network_interface: &mut NodeNetworkInterface) -> bool {\n\t\tlet layer_input_type = network_interface.input_type(&InputConnector::node(layer.to_node(), 1), &[]);\n\n\t\tlayer_input_type.compiled_nested_type() == Some(&concrete!(Table<Raster<CPU>>)) || layer_input_type.compiled_nested_type() == Some(&concrete!(Table<Raster<GPU>>))\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/layer_origin_cross.rs",
    "content": "use crate::consts::{COLOR_OVERLAY_BLUE, LAYER_ORIGIN_CROSS_DIAMETER, LAYER_ORIGIN_CROSS_THICKNESS};\nuse crate::messages::portfolio::document::overlays::utility_types::OverlayContext;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::tool::tool_messages::tool_prelude::DocumentMessageHandler;\nuse glam::DVec2;\n\n/// Draws a cross overlay at the origin point of the layers in layer space.\n/// This cross is orientated based on the +X vector of the layer.\npub fn draw_for_selected_layers(overlay_context: &mut OverlayContext, document: &DocumentMessageHandler) {\n\t// Don't draw if it is a disabled overlay\n\tif !overlay_context.visibility_settings.layer_origin_cross() {\n\t\treturn;\n\t}\n\n\t// Only show for layers that are visible, unlocked, and selected\n\tfor layer in document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface) {\n\t\t// Don't show for artboards\n\t\tif document.network_interface.is_artboard(&layer.to_node(), &[]) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Don't crash if we accidentally have the root\n\t\tif layer == LayerNodeIdentifier::ROOT_PARENT {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Some layers such as groups don't have a local transform (although we'll likely design a fix for that fact later)\n\t\tif !document.metadata().local_transforms.contains_key(&layer.to_node()) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// A transformation from the layer's local space to the viewport space (where overlays are drawn)\n\t\tlet transform_to_viewport = document.metadata().transform_to_viewport(layer);\n\n\t\t// The origin of the layer in viewport space which is the center of the origin cross\n\t\tlet origin_viewport = transform_to_viewport.transform_point2(DVec2::ZERO);\n\t\t// The forward +X direction vector from layer space (used to orient the origin cross)\n\t\tlet forward = transform_to_viewport.transform_vector2(DVec2::X).normalize_or_zero();\n\n\t\t// Draw the origin cross\n\t\tlet offsets = [forward + forward.perp(), forward - forward.perp()].map(|offset| offset * core::f64::consts::FRAC_1_SQRT_2 * LAYER_ORIGIN_CROSS_DIAMETER / 2.);\n\t\tfor offset in offsets {\n\t\t\toverlay_context.line(origin_viewport - offset, origin_viewport + offset, Some(COLOR_OVERLAY_BLUE), Some(LAYER_ORIGIN_CROSS_THICKNESS));\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/measure.rs",
    "content": "use crate::consts::COLOR_OVERLAY_BLUE;\nuse crate::messages::portfolio::document::overlays::utility_types::{OverlayContext, Pivot};\nuse crate::messages::tool::tool_messages::tool_prelude::*;\nuse graphene_std::renderer::Rect;\n\n/// Draws a dashed line between two points transformed by the given affine transformation.\nfn draw_dashed_line(line_start: DVec2, line_end: DVec2, transform: DAffine2, overlay_context: &mut OverlayContext) {\n\tlet min_viewport = transform.transform_point2(line_start);\n\tlet max_viewport = transform.transform_point2(line_end);\n\n\toverlay_context.dashed_line(min_viewport, max_viewport, None, None, Some(2.), Some(2.), Some(0.5));\n}\n\n/// Draws a solid line with a length annotation between two points transformed by the given affine transformations.\nfn draw_line_with_length(line_start: DVec2, line_end: DVec2, transform: DAffine2, document_to_viewport: DAffine2, overlay_context: &mut OverlayContext, label_alignment: LabelAlignment) {\n\tlet transform_to_document = document_to_viewport.inverse() * transform;\n\tlet min_viewport = transform.transform_point2(line_start);\n\tlet max_viewport = transform.transform_point2(line_end);\n\n\toverlay_context.line(min_viewport, max_viewport, None, None);\n\n\t// Remove trailing zeros from the formatted string\n\tlet length = format!(\"{:.2}\", transform_to_document.transform_vector2(line_end - line_start).length())\n\t\t.trim_end_matches('0')\n\t\t.trim_end_matches('.')\n\t\t.to_string();\n\n\tconst TOLERANCE: f64 = 0.01;\n\tif transform_to_document.transform_vector2(line_end - line_start).length() >= TOLERANCE {\n\t\tconst TEXT_PADDING: f64 = 5.;\n\t\t// Calculate midpoint of the line\n\t\tlet midpoint = (min_viewport + max_viewport) / 2.;\n\n\t\t// Adjust text position based on line orientation and flags\n\t\t// Determine text position based on line orientation and flags\n\t\tlet (pivot_x, pivot_y) = match (label_alignment.is_vertical_line, label_alignment.text_on_left, label_alignment.text_on_top) {\n\t\t\t(true, true, _) => (Pivot::End, Pivot::Middle),     // Vertical line, text on the left\n\t\t\t(true, false, _) => (Pivot::Start, Pivot::Middle),  // Vertical line, text on the right\n\t\t\t(false, _, true) => (Pivot::Middle, Pivot::End),    // Horizontal line, text on top\n\t\t\t(false, _, false) => (Pivot::Middle, Pivot::Start), // Horizontal line, text on bottom\n\t\t};\n\t\toverlay_context.text(&length, COLOR_OVERLAY_BLUE, None, DAffine2::from_translation(midpoint), TEXT_PADDING, [pivot_x, pivot_y]);\n\t}\n}\n\n/// Draws a dashed outline around the given rectangle (assumed to be in document space).\n/// The provided transform is applied to convert coordinates (e.g., to viewport space) during rendering.\nfn draw_dashed_rect_outline(rect: Rect, transform: DAffine2, overlay_context: &mut OverlayContext) {\n\tlet min = rect.min();\n\tlet max = rect.max();\n\n\t// Define corners in document space\n\tlet top_left = DVec2::new(min.x, min.y);\n\tlet top_right = DVec2::new(max.x, min.y);\n\tlet bottom_right = DVec2::new(max.x, max.y);\n\tlet bottom_left = DVec2::new(min.x, max.y);\n\n\t// Draw each edge using document-space coordinates; transform is applied inside draw_dashed_line\n\tdraw_dashed_line(top_left, top_right, transform, overlay_context);\n\tdraw_dashed_line(top_right, bottom_right, transform, overlay_context);\n\tdraw_dashed_line(bottom_right, bottom_left, transform, overlay_context);\n\tdraw_dashed_line(bottom_left, top_left, transform, overlay_context);\n}\n\n/// Checks if the selected bounds overlap with the hovered bounds on the Y-axis.\nfn does_overlap_y(selected_bounds: Rect, hovered_bounds: Rect) -> bool {\n\tselected_bounds.min().x <= hovered_bounds.max().x && selected_bounds.max().x >= hovered_bounds.min().x\n}\n\n/// Checks if the selected bounds overlap with the hovered bounds on the X-axis.\nfn does_overlap_x(selected_bounds: Rect, hovered_bounds: Rect) -> bool {\n\tselected_bounds.min().y <= hovered_bounds.max().y && selected_bounds.max().y >= hovered_bounds.min().y\n}\n\n/// Draws measurements when both X and Y axes are involved in the overlap between selected and hovered bounds.\nfn draw_zero_axis_crossings(selected_bounds: Rect, hovered_bounds: Rect, transform: DAffine2, document_to_viewport: DAffine2, overlay_context: &mut OverlayContext) {\n\tlet (selected_min, selected_max) = (selected_bounds.min(), selected_bounds.max());\n\tlet (hovered_min, hovered_max) = (hovered_bounds.min(), hovered_bounds.max());\n\n\tlet selected_on_right = selected_min.x > hovered_max.x;\n\tlet selected_on_bottom = selected_min.y > hovered_max.y;\n\n\tlet selected_y = if selected_on_bottom { selected_min.y } else { selected_max.y };\n\tlet hovered_y = if selected_on_bottom { hovered_max.y } else { hovered_min.y };\n\tlet selected_x = if selected_on_right { selected_min.x } else { selected_max.x };\n\tlet hovered_x = if selected_on_right { hovered_max.x } else { hovered_min.x };\n\n\t// Draw horizontal solid line with length\n\tlet line_start = DVec2::new(selected_x, selected_y);\n\tlet line_end = DVec2::new(hovered_x, selected_y);\n\tlet label_alignment = LabelAlignment::new(false, false, !selected_on_bottom);\n\tdraw_line_with_length(line_start, line_end, transform, document_to_viewport, overlay_context, label_alignment);\n\n\t// Draw horizontal dashed line\n\tlet line_start = DVec2::new(selected_x, hovered_y);\n\tlet line_end = DVec2::new(hovered_x, hovered_y);\n\tdraw_dashed_line(line_start, line_end, transform, overlay_context);\n\n\t// Draw vertical solid line with length\n\tlet line_start = DVec2::new(selected_x, selected_y);\n\tlet line_end = DVec2::new(selected_x, hovered_y);\n\tlet label_alignment = LabelAlignment::new(true, !selected_on_right, false);\n\tdraw_line_with_length(line_start, line_end, transform, document_to_viewport, overlay_context, label_alignment);\n\n\t// Draw vertical dashed line\n\tlet line_start = DVec2::new(hovered_x, selected_y);\n\tlet line_end = DVec2::new(hovered_x, hovered_y);\n\tdraw_dashed_line(line_start, line_end, transform, overlay_context);\n}\n\n/// Draws measurements when only one axis is involved in the overlap between selected and hovered bounds.\nfn draw_single_axis_zero_crossings(selected_bounds: Rect, hovered_bounds: Rect, transform: DAffine2, document_to_viewport: DAffine2, overlay_context: &mut OverlayContext) {\n\tlet (selected_min, selected_max) = (selected_bounds.min(), selected_bounds.max());\n\tlet (hovered_min, hovered_max) = (hovered_bounds.min(), hovered_bounds.max());\n\n\tlet overlap_y = does_overlap_y(selected_bounds, hovered_bounds) || does_overlap_y(hovered_bounds, selected_bounds);\n\tlet overlap_x = does_overlap_x(selected_bounds, hovered_bounds) || does_overlap_x(hovered_bounds, selected_bounds);\n\n\tlet selected_on_bottom = selected_bounds.center().y > hovered_bounds.center().y;\n\tlet selected_on_right = selected_bounds.center().x > hovered_bounds.center().x;\n\tif overlap_y {\n\t\tlet selected_facing_edge = if hovered_max.y < selected_min.y { selected_min.y } else { selected_max.y };\n\t\tlet hovered_facing_edge = if hovered_max.y < selected_min.y { hovered_max.y } else { hovered_min.y };\n\t\tlet vertical_line_start_x = if hovered_max.x > selected_max.x { selected_max.x } else { selected_min.x };\n\t\tlet dashed_vertical_line_start_x = if hovered_max.x > selected_max.x { hovered_min.x } else { hovered_max.x };\n\n\t\t// Draw horizontal solid line with length\n\t\tlet line_start = DVec2::new(f64::min(hovered_max.x, selected_max.x), selected_facing_edge);\n\t\tlet line_end = DVec2::new(f64::max(hovered_min.x, selected_min.x), selected_facing_edge);\n\t\tlet label_alignment = LabelAlignment::new(false, false, selected_on_bottom);\n\t\tdraw_line_with_length(line_start, line_end, transform, document_to_viewport, overlay_context, label_alignment);\n\n\t\t// Draw vertical solid line with length\n\t\tlet line_start = DVec2::new(vertical_line_start_x, selected_facing_edge);\n\t\tlet line_end = DVec2::new(vertical_line_start_x, hovered_facing_edge);\n\t\tlet label_alignment = LabelAlignment::new(true, !selected_on_right, false);\n\t\tdraw_line_with_length(line_start, line_end, transform, document_to_viewport, overlay_context, label_alignment);\n\n\t\t// Draw vertical dashed line\n\t\tlet dashed_line_start = DVec2::new(dashed_vertical_line_start_x, selected_facing_edge);\n\t\tlet dashed_line_end = DVec2::new(dashed_vertical_line_start_x, hovered_facing_edge);\n\t\tdraw_dashed_line(dashed_line_start, dashed_line_end, transform, overlay_context);\n\t} else if overlap_x {\n\t\tlet selected_facing_edge = if hovered_max.x < selected_min.x { selected_min.x } else { selected_max.x };\n\t\tlet hovered_facing_edge = if hovered_max.x < selected_min.x { hovered_max.x } else { hovered_min.x };\n\t\tlet horizontal_line_start_y = if hovered_max.y > selected_max.y { selected_max.y } else { selected_min.y };\n\t\tlet dashed_horizontal_line_start_y = if hovered_max.y > selected_max.y { hovered_min.y } else { hovered_max.y };\n\n\t\t// Draw vertical solid line with length\n\t\tlet line_start = DVec2::new(selected_facing_edge, f64::min(hovered_max.y, selected_max.y));\n\t\tlet line_end = DVec2::new(selected_facing_edge, f64::max(hovered_min.y, selected_min.y));\n\t\tlet label_alignment = LabelAlignment::new(true, selected_on_right, false);\n\t\tdraw_line_with_length(line_start, line_end, transform, document_to_viewport, overlay_context, label_alignment);\n\n\t\t// Draw horizontal solid line with length\n\t\tlet line_start = DVec2::new(selected_facing_edge, horizontal_line_start_y);\n\t\tlet line_end = DVec2::new(hovered_facing_edge, horizontal_line_start_y);\n\t\tlet label_alignment = LabelAlignment::new(false, false, !selected_on_bottom);\n\t\tdraw_line_with_length(line_start, line_end, transform, document_to_viewport, overlay_context, label_alignment);\n\n\t\t// Draw horizontal dashed line\n\t\tlet dashed_line_start = DVec2::new(selected_facing_edge, dashed_horizontal_line_start_y);\n\t\tlet dashed_line_end = DVec2::new(hovered_facing_edge, dashed_horizontal_line_start_y);\n\t\tdraw_dashed_line(dashed_line_start, dashed_line_end, transform, overlay_context);\n\t}\n}\n\n/// Draws measurements when only one axis is involved and there is one crossing between selected and hovered bounds.\nfn draw_single_axis_one_crossings(selected_bounds: Rect, hovered_bounds: Rect, transform: DAffine2, document_to_viewport: DAffine2, overlay_context: &mut OverlayContext) {\n\tlet (selected_min, selected_max) = (selected_bounds.min(), selected_bounds.max());\n\tlet (hovered_min, hovered_max) = (hovered_bounds.min(), hovered_bounds.max());\n\n\tlet selected_center = selected_bounds.center();\n\tlet hovered_center = hovered_bounds.center();\n\n\tlet overlap_y = does_overlap_y(selected_bounds, hovered_bounds) || does_overlap_y(hovered_bounds, selected_bounds);\n\tlet overlap_x = does_overlap_x(selected_bounds, hovered_bounds) || does_overlap_x(hovered_bounds, selected_bounds);\n\n\tif overlap_y {\n\t\tlet selected_facing_edge = if hovered_max.y < selected_min.y { selected_min.y } else { selected_max.y };\n\t\tlet hovered_facing_edge = if hovered_max.y < selected_min.y { hovered_max.y } else { hovered_min.y };\n\t\tlet vertical_line_start = if selected_center.x < hovered_max.x && selected_center.x > hovered_min.x {\n\t\t\tselected_center.x\n\t\t} else {\n\t\t\thovered_center.x\n\t\t};\n\n\t\t// Draw vertical solid line with length\n\t\tlet line_start = DVec2::new(vertical_line_start, selected_facing_edge);\n\t\tlet line_end = DVec2::new(vertical_line_start, hovered_facing_edge);\n\t\tlet label_alignment = LabelAlignment::new(true, true, false);\n\t\tdraw_line_with_length(line_start, line_end, transform, document_to_viewport, overlay_context, label_alignment);\n\t} else if overlap_x {\n\t\tlet selected_facing_edge = if hovered_max.x < selected_min.x { selected_min.x } else { selected_max.x };\n\t\tlet hovered_facing_edge = if hovered_max.x < selected_min.x { hovered_max.x } else { hovered_min.x };\n\t\tlet horizontal_line_start_y = if selected_center.y < hovered_max.y && selected_center.y > hovered_min.y {\n\t\t\tselected_center.y\n\t\t} else {\n\t\t\thovered_center.y\n\t\t};\n\n\t\t// Draw horizontal solid line with length\n\t\tlet line_start = DVec2::new(selected_facing_edge, horizontal_line_start_y);\n\t\tlet line_end = DVec2::new(hovered_facing_edge, horizontal_line_start_y);\n\t\tlet label_alignment = LabelAlignment::new(false, false, true);\n\t\tdraw_line_with_length(line_start, line_end, transform, document_to_viewport, overlay_context, label_alignment);\n\t}\n}\n\n/// Draws measurements for cases where lines cross on both X and Y axes, handling diagonal intersections.\nfn draw_two_axis_one_one_crossing(selected_bounds: Rect, hovered_bounds: Rect, transform: DAffine2, document_to_viewport: DAffine2, overlay_context: &mut OverlayContext) {\n\tlet (selected_min, selected_max) = (selected_bounds.min(), selected_bounds.max());\n\tlet (hovered_min, hovered_max) = (hovered_bounds.min(), hovered_bounds.max());\n\n\tlet mut top_y_bound = f64::min(selected_min.y, hovered_min.y);\n\tlet mut bottom_y_bound = f64::max(selected_max.y, hovered_max.y);\n\tlet mut top_x_bound = f64::max(selected_max.x, hovered_max.x);\n\tlet mut bottom_x_bound = f64::min(selected_min.x, hovered_min.x);\n\n\t// Handle diagonal intersection cases by swapping bounds if necessary\n\tif (hovered_bounds.center().x > selected_bounds.center().x && hovered_bounds.center().y < selected_bounds.center().y)\n\t\t|| (hovered_bounds.center().x < selected_bounds.center().x && hovered_bounds.center().y > selected_bounds.center().y)\n\t{\n\t\tstd::mem::swap(&mut top_y_bound, &mut bottom_y_bound);\n\t\tstd::mem::swap(&mut top_x_bound, &mut bottom_x_bound);\n\t}\n\n\t// Draw horizontal solid lines with length\n\tlet top_x_start = DVec2::new(f64::min(selected_max.x, hovered_max.x), top_y_bound);\n\tlet top_x_end = DVec2::new(f64::max(selected_max.x, hovered_max.x), top_y_bound);\n\tlet label_alignment = LabelAlignment::new(false, false, true);\n\tdraw_line_with_length(top_x_start, top_x_end, transform, document_to_viewport, overlay_context, label_alignment);\n\n\tlet bottom_x_start = DVec2::new(f64::min(selected_min.x, hovered_min.x), bottom_y_bound);\n\tlet bottom_x_end = DVec2::new(f64::max(selected_min.x, hovered_min.x), bottom_y_bound);\n\tlet label_alignment = LabelAlignment::new(false, false, false);\n\tdraw_line_with_length(bottom_x_start, bottom_x_end, transform, document_to_viewport, overlay_context, label_alignment);\n\n\t// Draw vertical solid lines with length\n\tlet top_y_start = DVec2::new(top_x_bound, f64::min(selected_min.y, hovered_min.y));\n\tlet top_y_end = DVec2::new(top_x_bound, f64::max(selected_min.y, hovered_min.y));\n\tlet label_alignment = LabelAlignment::new(true, false, false);\n\tdraw_line_with_length(top_y_start, top_y_end, transform, document_to_viewport, overlay_context, label_alignment);\n\n\tlet bottom_y_start = DVec2::new(bottom_x_bound, f64::min(selected_max.y, hovered_max.y));\n\tlet bottom_y_end = DVec2::new(bottom_x_bound, f64::max(selected_max.y, hovered_max.y));\n\tlet label_alignment = LabelAlignment::new(true, true, false);\n\tdraw_line_with_length(bottom_y_start, bottom_y_end, transform, document_to_viewport, overlay_context, label_alignment);\n}\n\n/// Draws measurements for partial overlaps with two vertical or horizontal edge intersections.\nfn draw_two_axis_one_one_two_zero_crossing(\n\tselected_bounds: Rect,\n\thovered_bounds: Rect,\n\ttransform: DAffine2,\n\tdocument_to_viewport: DAffine2,\n\toverlay_context: &mut OverlayContext,\n\ttwo_vertical_edge_intersect: bool,\n) {\n\tlet (selected_min, selected_max) = (selected_bounds.min(), selected_bounds.max());\n\tlet (hovered_min, hovered_max) = (hovered_bounds.min(), hovered_bounds.max());\n\n\tif two_vertical_edge_intersect {\n\t\tlet selected_bound_edge = if selected_bounds.center().y >= hovered_bounds.center().y {\n\t\t\tselected_max.y\n\t\t} else {\n\t\t\tselected_min.y\n\t\t};\n\t\tlet hovered_bound_edge = if selected_bounds.center().y >= hovered_bounds.center().y { hovered_max.y } else { hovered_min.y };\n\n\t\t// Draw vertical solid lines with length\n\t\tlet y_start_left = DVec2::new(hovered_min.x, f64::min(selected_bound_edge, hovered_bound_edge));\n\t\tlet y_end_left = DVec2::new(hovered_min.x, f64::max(selected_bound_edge, hovered_bound_edge));\n\t\tlet label_alignment = LabelAlignment::new(true, true, false);\n\t\tdraw_line_with_length(y_start_left, y_end_left, transform, document_to_viewport, overlay_context, label_alignment);\n\n\t\tlet y_start_right = DVec2::new(hovered_max.x, f64::min(selected_bound_edge, hovered_bound_edge));\n\t\tlet y_end_right = DVec2::new(hovered_max.x, f64::max(selected_bound_edge, hovered_bound_edge));\n\t\tlet label_alignment = LabelAlignment::new(true, false, false);\n\t\tdraw_line_with_length(y_start_right, y_end_right, transform, document_to_viewport, overlay_context, label_alignment);\n\n\t\t// Draw horizontal solid lines with length\n\t\tlet horizontal_line_y_bound = if selected_bounds.center().y >= hovered_bounds.center().y {\n\t\t\tf64::max(selected_bound_edge, hovered_bound_edge)\n\t\t} else {\n\t\t\tf64::min(selected_bound_edge, hovered_bound_edge)\n\t\t};\n\n\t\tlet x_start_left = DVec2::new(hovered_min.x, horizontal_line_y_bound);\n\t\tlet x_end_left = DVec2::new(selected_min.x, horizontal_line_y_bound);\n\t\tlet label_alignment = LabelAlignment::new(false, false, false);\n\t\tdraw_line_with_length(x_start_left, x_end_left, transform, document_to_viewport, overlay_context, label_alignment);\n\n\t\tlet x_start_right = DVec2::new(hovered_max.x, horizontal_line_y_bound);\n\t\tlet x_end_right = DVec2::new(selected_max.x, horizontal_line_y_bound);\n\t\tlet label_alignment = LabelAlignment::new(false, false, false);\n\t\tdraw_line_with_length(x_start_right, x_end_right, transform, document_to_viewport, overlay_context, label_alignment);\n\t} else {\n\t\tlet selected_bound_edge = if selected_bounds.center().x >= hovered_bounds.center().x {\n\t\t\tselected_max.x\n\t\t} else {\n\t\t\tselected_min.x\n\t\t};\n\t\tlet hovered_bound_edge = if selected_bounds.center().x >= hovered_bounds.center().x { hovered_max.x } else { hovered_min.x };\n\n\t\t// Determine the outermost X position for vertical lines\n\t\tlet vertical_line_x = if selected_bounds.center().x >= hovered_bounds.center().x {\n\t\t\tf64::max(selected_bound_edge, hovered_bound_edge)\n\t\t} else {\n\t\t\tf64::min(selected_bound_edge, hovered_bound_edge)\n\t\t};\n\n\t\t// Draw vertical solid lines with length\n\t\tlet y_start_up = DVec2::new(vertical_line_x, selected_min.y);\n\t\tlet y_end_up = DVec2::new(vertical_line_x, hovered_min.y);\n\t\tlet label_alignment = LabelAlignment::new(true, false, false);\n\t\tdraw_line_with_length(y_start_up, y_end_up, transform, document_to_viewport, overlay_context, label_alignment);\n\n\t\tlet y_start_down = DVec2::new(vertical_line_x, selected_max.y);\n\t\tlet y_end_down = DVec2::new(vertical_line_x, hovered_max.y);\n\t\tlet label_alignment = LabelAlignment::new(true, false, false);\n\t\tdraw_line_with_length(y_start_down, y_end_down, transform, document_to_viewport, overlay_context, label_alignment);\n\n\t\t// Draw horizontal solid lines with length\n\t\tlet horizontal_line_inner_x = if selected_bounds.center().x >= hovered_bounds.center().x {\n\t\t\tf64::min(selected_bound_edge, hovered_bound_edge)\n\t\t} else {\n\t\t\tf64::max(selected_bound_edge, hovered_bound_edge)\n\t\t};\n\t\tlet x_start_up = DVec2::new(vertical_line_x, f64::min(selected_min.y, hovered_min.y));\n\t\tlet x_end_up = DVec2::new(horizontal_line_inner_x, f64::min(selected_min.y, hovered_min.y));\n\t\tlet label_alignment = LabelAlignment::new(false, false, true);\n\t\tdraw_line_with_length(x_start_up, x_end_up, transform, document_to_viewport, overlay_context, label_alignment);\n\n\t\tlet x_start_down = DVec2::new(vertical_line_x, f64::max(selected_max.y, hovered_max.y));\n\t\tlet x_end_down = DVec2::new(horizontal_line_inner_x, f64::max(selected_max.y, hovered_max.y));\n\t\tlet label_alignment = LabelAlignment::new(false, false, false);\n\t\tdraw_line_with_length(x_start_down, x_end_down, transform, document_to_viewport, overlay_context, label_alignment);\n\t}\n}\n\n/// Draws measurements for cases with two vertical and two horizontal zero crossings.\nfn draw_two_axis_two_zero_zero_two(selected_bounds: Rect, hovered_bounds: Rect, transform: DAffine2, document_to_viewport: DAffine2, overlay_context: &mut OverlayContext) {\n\tlet (selected_min, selected_max) = (selected_bounds.min(), selected_bounds.max());\n\tlet (hovered_min, hovered_max) = (hovered_bounds.min(), hovered_bounds.max());\n\n\t// Draw vertical solid lines with length\n\tlet y_start_left_top = DVec2::new(f64::min(hovered_min.x, selected_min.x), f64::min(hovered_min.y, selected_min.y));\n\tlet y_end_left_top = DVec2::new(f64::min(hovered_min.x, selected_min.x), f64::max(hovered_min.y, selected_min.y));\n\tlet label_alignment = LabelAlignment::new(true, true, false);\n\tdraw_line_with_length(y_start_left_top, y_end_left_top, transform, document_to_viewport, overlay_context, label_alignment);\n\tlet label_alignment = LabelAlignment::new(true, true, false);\n\tdraw_line_with_length(y_start_left_top, y_end_left_top, transform, document_to_viewport, overlay_context, label_alignment);\n\n\tlet y_start_left_bottom = DVec2::new(f64::min(hovered_min.x, selected_min.x), f64::min(hovered_max.y, selected_max.y));\n\tlet y_end_left_bottom = DVec2::new(f64::min(hovered_min.x, selected_min.x), f64::max(hovered_max.y, selected_max.y));\n\tlet label_alignment = LabelAlignment::new(true, true, false);\n\tdraw_line_with_length(y_start_left_bottom, y_end_left_bottom, transform, document_to_viewport, overlay_context, label_alignment);\n\n\tlet y_start_right_top = DVec2::new(f64::max(hovered_max.x, selected_max.x), f64::min(hovered_min.y, selected_min.y));\n\tlet y_end_right_top = DVec2::new(f64::max(hovered_max.x, selected_max.x), f64::max(hovered_min.y, selected_min.y));\n\tlet label_alignment = LabelAlignment::new(true, false, false);\n\tdraw_line_with_length(y_start_right_top, y_end_right_top, transform, document_to_viewport, overlay_context, label_alignment);\n\n\tlet y_start_right_bottom = DVec2::new(f64::max(hovered_max.x, selected_max.x), f64::min(hovered_max.y, selected_max.y));\n\tlet y_end_right_bottom = DVec2::new(f64::max(hovered_max.x, selected_max.x), f64::max(hovered_max.y, selected_max.y));\n\tlet label_alignment = LabelAlignment::new(true, false, false);\n\tdraw_line_with_length(y_start_right_bottom, y_end_right_bottom, transform, document_to_viewport, overlay_context, label_alignment);\n\n\t// Draw horizontal solid lines with length\n\tlet x_start_left_top = DVec2::new(f64::min(hovered_min.x, selected_min.x), f64::min(hovered_min.y, selected_min.y));\n\tlet x_end_left_top = DVec2::new(f64::max(hovered_min.x, selected_min.x), f64::min(hovered_min.y, selected_min.y));\n\tlet label_alignment = LabelAlignment::new(false, false, true);\n\tdraw_line_with_length(x_start_left_top, x_end_left_top, transform, document_to_viewport, overlay_context, label_alignment);\n\n\tlet x_start_right_top = DVec2::new(f64::min(hovered_max.x, selected_max.x), f64::min(hovered_min.y, selected_min.y));\n\tlet x_end_right_top = DVec2::new(f64::max(hovered_max.x, selected_max.x), f64::min(hovered_min.y, selected_min.y));\n\tlet label_alignment = LabelAlignment::new(false, false, true);\n\tdraw_line_with_length(x_start_right_top, x_end_right_top, transform, document_to_viewport, overlay_context, label_alignment);\n\n\tlet x_start_left_bottom = DVec2::new(f64::min(hovered_min.x, selected_min.x), f64::max(hovered_max.y, selected_max.y));\n\tlet x_end_left_bottom = DVec2::new(f64::max(hovered_min.x, selected_min.x), f64::max(hovered_max.y, selected_max.y));\n\tlet label_alignment = LabelAlignment::new(false, false, false);\n\tdraw_line_with_length(x_start_left_bottom, x_end_left_bottom, transform, document_to_viewport, overlay_context, label_alignment);\n\n\tlet x_start_right_bottom = DVec2::new(f64::min(hovered_max.x, selected_max.x), f64::max(hovered_max.y, selected_max.y));\n\tlet x_end_right_bottom = DVec2::new(f64::max(hovered_max.x, selected_max.x), f64::max(hovered_max.y, selected_max.y));\n\tlet label_alignment = LabelAlignment::new(false, false, false);\n\tdraw_line_with_length(x_start_right_bottom, x_end_right_bottom, transform, document_to_viewport, overlay_context, label_alignment);\n}\n\n/// Draws measurements where selected and hovered bounds have two vertical edges crossing each other.\nfn draw_two_axis_two_zero_two_zero(selected_bounds: Rect, hovered_bounds: Rect, transform: DAffine2, document_to_viewport: DAffine2, overlay_context: &mut OverlayContext) {\n\tlet (selected_min, selected_max) = (selected_bounds.min(), selected_bounds.max());\n\tlet (hovered_min, hovered_max) = (hovered_bounds.min(), hovered_bounds.max());\n\n\t// Draw horizontal solid lines with length\n\tlet x_start_left = DVec2::new(f64::max(hovered_min.x, selected_min.x), selected_bounds.center().y);\n\tlet x_end_left = DVec2::new(f64::min(hovered_min.x, selected_min.x), selected_bounds.center().y);\n\tlet label_alignment = LabelAlignment::new(false, false, true);\n\tdraw_line_with_length(x_start_left, x_end_left, transform, document_to_viewport, overlay_context, label_alignment);\n\n\tlet x_start_right = DVec2::new(f64::min(hovered_max.x, selected_max.x), selected_bounds.center().y);\n\tlet x_end_right = DVec2::new(f64::max(hovered_max.x, selected_max.x), selected_bounds.center().y);\n\tlet label_alignment = LabelAlignment::new(false, false, true);\n\tdraw_line_with_length(x_start_right, x_end_right, transform, document_to_viewport, overlay_context, label_alignment);\n\n\t// Draw vertical solid lines with length\n\tlet y_start_top = DVec2::new(selected_bounds.center().x, f64::max(hovered_min.y, selected_min.y));\n\tlet y_end_top = DVec2::new(selected_bounds.center().x, f64::min(hovered_min.y, selected_min.y));\n\tlet label_alignment = LabelAlignment::new(true, false, false);\n\tdraw_line_with_length(y_start_top, y_end_top, transform, document_to_viewport, overlay_context, label_alignment);\n\n\tlet y_start_bottom = DVec2::new(selected_bounds.center().x, f64::min(hovered_max.y, selected_max.y));\n\tlet y_end_bottom = DVec2::new(selected_bounds.center().x, f64::max(hovered_max.y, selected_max.y));\n\tlet label_alignment = LabelAlignment::new(true, false, false);\n\tdraw_line_with_length(y_start_bottom, y_end_bottom, transform, document_to_viewport, overlay_context, label_alignment);\n}\n\n/// Handles overlapping scenarios involving two axes between selected and hovered bounds.\nfn handle_two_axis_overlap(selected_bounds: Rect, hovered_bounds: Rect, transform: DAffine2, document_to_viewport: DAffine2, overlay_context: &mut OverlayContext) {\n\tlet (selected_min, selected_max) = (selected_bounds.min(), selected_bounds.max());\n\tlet (hovered_min, hovered_max) = (hovered_bounds.min(), hovered_bounds.max());\n\n\t// Calculate edge crossings on the X-axis\n\tlet selected_x_crosses = (selected_min.y >= hovered_min.y && selected_min.y <= hovered_max.y) as u8 + (selected_max.y >= hovered_min.y && selected_max.y <= hovered_max.y) as u8;\n\tlet hovered_x_crosses = (hovered_min.y >= selected_min.y && hovered_min.y <= selected_max.y) as u8 + (hovered_max.y >= selected_min.y && hovered_max.y <= selected_max.y) as u8;\n\n\t// Calculate edge crossings on the Y-axis\n\tlet selected_y_crosses = (selected_min.x >= hovered_min.x && selected_min.x <= hovered_max.x) as u8 + (selected_max.x >= hovered_min.x && selected_max.x <= hovered_max.x) as u8;\n\tlet hovered_y_crosses = (hovered_min.x >= selected_min.x && hovered_min.x <= selected_max.x) as u8 + (hovered_max.x >= selected_min.x && hovered_max.x <= selected_max.x) as u8;\n\n\t// Determine the overlap case based on edge crossings\n\tmatch ((selected_x_crosses, hovered_x_crosses), (selected_y_crosses, hovered_y_crosses)) {\n\t\t((1, 1), (1, 1)) => draw_two_axis_one_one_crossing(selected_bounds, hovered_bounds, transform, document_to_viewport, overlay_context),\n\t\t((1, 1), (2, 0)) => draw_two_axis_one_one_two_zero_crossing(selected_bounds, hovered_bounds, transform, document_to_viewport, overlay_context, true),\n\t\t((1, 1), (0, 2)) => draw_two_axis_one_one_two_zero_crossing(hovered_bounds, selected_bounds, transform, document_to_viewport, overlay_context, true),\n\t\t((2, 0), (1, 1)) => draw_two_axis_one_one_two_zero_crossing(selected_bounds, hovered_bounds, transform, document_to_viewport, overlay_context, false),\n\t\t((0, 2), (1, 1)) => draw_two_axis_one_one_two_zero_crossing(hovered_bounds, selected_bounds, transform, document_to_viewport, overlay_context, false),\n\t\t((2, 0), (0, 2)) | ((0, 2), (2, 0)) => draw_two_axis_two_zero_zero_two(selected_bounds, hovered_bounds, transform, document_to_viewport, overlay_context),\n\t\t((2, 0), (2, 0)) | ((0, 2), (0, 2)) => draw_two_axis_two_zero_two_zero(selected_bounds, hovered_bounds, transform, document_to_viewport, overlay_context),\n\t\t_ => (),\n\t}\n}\n\n/// Overlays measurement lines between selected and hovered bounds based on their spatial relationships.\npub fn overlay(selected_bounds: Rect, hovered_bounds: Rect, transform: DAffine2, document_to_viewport: DAffine2, overlay_context: &mut OverlayContext) {\n\tdraw_dashed_rect_outline(selected_bounds, transform, overlay_context);\n\tdraw_dashed_rect_outline(hovered_bounds, transform, overlay_context);\n\tlet (selected_min, selected_max) = (selected_bounds.min(), selected_bounds.max());\n\tlet (hovered_min, hovered_max) = (hovered_bounds.min(), hovered_bounds.max());\n\n\t// Determine axis overlaps\n\tlet overlap_y = does_overlap_y(selected_bounds, hovered_bounds) || does_overlap_y(hovered_bounds, selected_bounds);\n\tlet overlap_x = does_overlap_x(selected_bounds, hovered_bounds) || does_overlap_x(hovered_bounds, selected_bounds);\n\tlet overlap_axes = match (overlap_x, overlap_y) {\n\t\t(true, true) => 2,\n\t\t(true, false) | (false, true) => 1,\n\t\t_ => 0,\n\t};\n\n\t// Determine centerline crossings\n\tlet center_x_intersects =\n\t\t(selected_bounds.center().y >= hovered_min.y && selected_bounds.center().y <= hovered_max.y) || (hovered_bounds.center().y >= selected_min.y && hovered_bounds.center().y <= selected_max.y);\n\tlet center_y_intersects =\n\t\t(selected_bounds.center().x >= hovered_min.x && selected_bounds.center().x <= hovered_max.x) || (hovered_bounds.center().x >= selected_min.x && hovered_bounds.center().x <= selected_max.x);\n\tlet centerline_crosses = match (center_x_intersects, center_y_intersects) {\n\t\t(true, true) => 2,\n\t\t(true, false) | (false, true) => 1,\n\t\t_ => 0,\n\t};\n\n\t// Handle each overlap case\n\tmatch (overlap_axes, centerline_crosses) {\n\t\t(0, _) => draw_zero_axis_crossings(selected_bounds, hovered_bounds, transform, document_to_viewport, overlay_context),\n\t\t(1, 0) => draw_single_axis_zero_crossings(selected_bounds, hovered_bounds, transform, document_to_viewport, overlay_context),\n\t\t(1, 1) | (1, 2) => draw_single_axis_one_crossings(selected_bounds, hovered_bounds, transform, document_to_viewport, overlay_context),\n\t\t(2, _) => handle_two_axis_overlap(selected_bounds, hovered_bounds, transform, document_to_viewport, overlay_context),\n\t\t_ => (), // Fallback case, should not typically happen\n\t}\n}\n\nstruct LabelAlignment {\n\tis_vertical_line: bool,\n\ttext_on_left: bool,\n\ttext_on_top: bool,\n}\n\nimpl LabelAlignment {\n\tfn new(is_vertical_line: bool, text_on_left: bool, text_on_top: bool) -> Self {\n\t\tSelf {\n\t\t\tis_vertical_line,\n\t\t\ttext_on_left,\n\t\t\ttext_on_top,\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/mod.rs",
    "content": "pub mod auto_panning;\npub mod color_selector;\npub mod compass_rose;\npub mod gizmos;\npub mod graph_modification_utils;\npub mod layer_origin_cross;\npub mod measure;\npub mod pivot;\npub mod resize;\npub mod shape_editor;\npub mod shapes;\npub mod snapping;\npub mod transformation_cage;\npub mod utility_functions;\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/pivot.rs",
    "content": "//! Handler for the pivot overlay visible on the selected layer(s) whilst using the Select tool which controls the center of rotation/scale.\n\nuse crate::consts::PIVOT_DIAMETER;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::prelude::*;\nuse crate::messages::tool::common_functionality::graph_modification_utils;\nuse crate::messages::tool::tool_messages::path_tool::PathOptionsUpdate;\nuse crate::messages::tool::tool_messages::select_tool::SelectOptionsUpdate;\nuse crate::messages::tool::tool_messages::tool_prelude::*;\nuse glam::{DAffine2, DVec2};\nuse graphene_std::transform::ReferencePoint;\nuse graphene_std::vector::misc::ManipulatorPointId;\nuse std::fmt;\n\npub fn pin_pivot_widget(active: bool, enabled: bool, source: PivotToolSource) -> WidgetInstance {\n\tIconButton::new(if active { \"PinActive\" } else { \"PinInactive\" }, 24)\n\t\t.tooltip_label(if active { \"Unpin Custom Pivot\" } else { \"Pin Custom Pivot\" })\n\t\t.tooltip_description(\"Unless pinned, the pivot will return to its prior reference point when a new selection is made.\")\n\t\t.disabled(!enabled)\n\t\t.on_update(move |_| match source {\n\t\t\tPivotToolSource::Select => SelectToolMessage::SelectOptions {\n\t\t\t\toptions: SelectOptionsUpdate::TogglePivotPinned,\n\t\t\t}\n\t\t\t.into(),\n\t\t\tPivotToolSource::Path => PathToolMessage::UpdateOptions {\n\t\t\t\toptions: PathOptionsUpdate::TogglePivotPinned,\n\t\t\t}\n\t\t\t.into(),\n\t\t})\n\t\t.widget_instance()\n}\n\npub fn pivot_reference_point_widget(disabled: bool, reference_point: ReferencePoint, source: PivotToolSource) -> WidgetInstance {\n\tReferencePointInput::new(reference_point)\n\t\t.tooltip_label(\"Custom Pivot Reference Point\")\n\t\t.tooltip_description(\"Places the pivot at a corner, edge, or center of the selection bounds, unless it is dragged elsewhere.\")\n\t\t.disabled(disabled)\n\t\t.on_update(move |pivot_input: &ReferencePointInput| match source {\n\t\t\tPivotToolSource::Select => SelectToolMessage::SetPivot { position: pivot_input.value }.into(),\n\t\t\tPivotToolSource::Path => PathToolMessage::SetPivot { position: pivot_input.value }.into(),\n\t\t})\n\t\t.widget_instance()\n}\n\npub fn pivot_gizmo_type_widget(state: PivotGizmoState, source: PivotToolSource) -> Vec<WidgetInstance> {\n\tlet gizmo_type_entries = [PivotGizmoType::Pivot, PivotGizmoType::Average, PivotGizmoType::Active]\n\t\t.iter()\n\t\t.map(|gizmo_type| {\n\t\t\tMenuListEntry::new(format!(\"{gizmo_type:?}\")).label(gizmo_type.to_string()).on_commit({\n\t\t\t\tlet value = source.clone();\n\t\t\t\tmove |_| match value {\n\t\t\t\t\tPivotToolSource::Select => SelectToolMessage::SelectOptions {\n\t\t\t\t\t\toptions: SelectOptionsUpdate::PivotGizmoType(*gizmo_type),\n\t\t\t\t\t}\n\t\t\t\t\t.into(),\n\t\t\t\t\tPivotToolSource::Path => PathToolMessage::UpdateOptions {\n\t\t\t\t\t\toptions: PathOptionsUpdate::PivotGizmoType(*gizmo_type),\n\t\t\t\t\t}\n\t\t\t\t\t.into(),\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t\t.collect();\n\n\tvec![\n\t\tCheckboxInput::new(state.enabled)\n\t\t\t.tooltip_label(\"Pivot Gizmo\")\n\t\t\t.tooltip_description(\n\t\t\t\t\"\n\t\t\t\tEnabled: the chosen gizmo type is shown and used to control rotation and scaling.\\n\\\n\t\t\t\tDisabled: rotation and scaling occurs about the center of the selection bounds.\n\t\t\t\t\"\n\t\t\t\t.trim(),\n\t\t\t)\n\t\t\t.on_update(move |optional_input: &CheckboxInput| match source {\n\t\t\t\tPivotToolSource::Select => SelectToolMessage::SelectOptions {\n\t\t\t\t\toptions: SelectOptionsUpdate::SetPivotGizmoEnabled(optional_input.checked),\n\t\t\t\t}\n\t\t\t\t.into(),\n\t\t\t\tPivotToolSource::Path => PathToolMessage::UpdateOptions {\n\t\t\t\t\toptions: PathOptionsUpdate::SetPivotGizmoEnabled(optional_input.checked),\n\t\t\t\t}\n\t\t\t\t.into(),\n\t\t\t})\n\t\t\t.widget_instance(),\n\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\tDropdownInput::new(vec![gizmo_type_entries])\n\t\t\t.selected_index(Some(match state.gizmo_type {\n\t\t\t\tPivotGizmoType::Pivot => 0,\n\t\t\t\tPivotGizmoType::Average => 1,\n\t\t\t\tPivotGizmoType::Active => 2,\n\t\t\t}))\n\t\t\t.tooltip_label(\"Pivot Gizmo Type\")\n\t\t\t.tooltip_description(\n\t\t\t\t\"\n\t\t\t\tSelects which gizmo type is shown and used as the center of rotation/scaling transformations.\\n\\\n\t\t\t\t\\n\\\n\t\t\t\tCustom Pivot: rotates and scales relative to the selection bounds, or elsewhere if dragged.\\n\\\n\t\t\t\tOrigin (Average Point): rotates and scales about the average point of all selected layer origins.\\n\\\n\t\t\t\tOrigin (Active Object): rotates and scales about the origin of the most recently selected layer.\n\t\t\t\t\"\n\t\t\t\t.trim(),\n\t\t\t)\n\t\t\t.disabled(!state.enabled)\n\t\t\t.widget_instance(),\n\t]\n}\n\n#[derive(PartialEq, Clone, Debug, Default, serde::Serialize, serde::Deserialize)]\npub enum PivotToolSource {\n\tPath,\n\t#[default]\n\tSelect,\n}\n\n#[derive(PartialEq, Clone, Debug, Default, serde::Serialize, serde::Deserialize)]\npub struct PivotGizmo {\n\tpub pivot: Pivot,\n\tpub state: PivotGizmoState,\n\tpub layer: Option<LayerNodeIdentifier>,\n\tpub point: Option<ManipulatorPointId>,\n}\n\nimpl PivotGizmo {\n\tpub fn position(&self, document: &DocumentMessageHandler) -> DVec2 {\n\t\tlet network = &document.network_interface;\n\t\t(self.state.enabled)\n\t\t\t.then_some({\n\t\t\t\tmatch self.state.gizmo_type {\n\t\t\t\t\tPivotGizmoType::Average => Some(network.selected_nodes().selected_visible_and_unlocked_layers_mean_average_origin(network)),\n\t\t\t\t\tPivotGizmoType::Pivot => self.pivot.pivot,\n\t\t\t\t\tPivotGizmoType::Active => self.layer.map(|layer| graph_modification_utils::get_viewport_origin(layer, network)),\n\t\t\t\t}\n\t\t\t})\n\t\t\t.flatten()\n\t\t\t.unwrap_or_else(|| self.pivot.transform_from_normalized.transform_point2(DVec2::splat(0.5)))\n\t}\n\n\tpub fn recalculate_transform(&mut self, document: &DocumentMessageHandler) -> DAffine2 {\n\t\tself.pivot.recalculate_pivot(document);\n\t\tself.pivot.transform_from_normalized\n\t}\n\n\tpub fn pin_active(&self) -> bool {\n\t\tself.pivot.pinned && self.state.is_pivot_type()\n\t}\n\n\tpub fn pivot_disconnected(&self) -> bool {\n\t\tself.pivot.old_pivot_position == ReferencePoint::None\n\t}\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize)]\npub enum PivotGizmoType {\n\t// Pivot\n\t#[default]\n\tPivot,\n\t// Origin\n\tAverage,\n\tActive,\n\t// TODO: Add \"Individual\"\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, Hash, serde::Serialize, serde::Deserialize)]\npub struct PivotGizmoState {\n\tpub enabled: bool,\n\tpub gizmo_type: PivotGizmoType,\n}\n\nimpl PivotGizmoState {\n\tpub fn is_pivot_type(&self) -> bool {\n\t\t// A disabled pivot is considered a pivot-type gizmo that is always centered\n\t\tself.gizmo_type == PivotGizmoType::Pivot || !self.enabled\n\t}\n\n\tpub fn is_pivot(&self) -> bool {\n\t\tself.gizmo_type == PivotGizmoType::Pivot && self.enabled\n\t}\n}\n\nimpl fmt::Display for PivotGizmoType {\n\tfn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n\t\tmatch self {\n\t\t\tPivotGizmoType::Pivot => write!(f, \"Custom Pivot\"),\n\t\t\tPivotGizmoType::Average => write!(f, \"Origin (Average Point)\"),\n\t\t\tPivotGizmoType::Active => write!(f, \"Origin (Active Object)\"),\n\t\t\t// TODO: Add \"Origin (Individual)\"\n\t\t}\n\t}\n}\n\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub struct Pivot {\n\t/// Pivot between (0,0) and (1,1)\n\tnormalized_pivot: DVec2,\n\t/// Transform to get from normalized pivot to viewspace\n\tpub transform_from_normalized: DAffine2,\n\t/// The viewspace pivot position\n\tpub pivot: Option<DVec2>,\n\t/// The old pivot position in the GUI, used to reduce refreshes of the document bar\n\tpub old_pivot_position: ReferencePoint,\n\t/// The last ReferencePoint which wasn't none\n\tpub last_non_none_reference_point: ReferencePoint,\n\t/// Used to enable and disable the pivot\n\tpub pinned: bool,\n\t/// Had selected_visible_and_unlocked_layers\n\tpub empty: bool,\n}\n\nimpl Default for Pivot {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tnormalized_pivot: DVec2::splat(0.5),\n\t\t\ttransform_from_normalized: Default::default(),\n\t\t\tpivot: Default::default(),\n\t\t\told_pivot_position: ReferencePoint::Center,\n\t\t\tlast_non_none_reference_point: ReferencePoint::Center,\n\t\t\tpinned: false,\n\t\t\tempty: true,\n\t\t}\n\t}\n}\n\nimpl Pivot {\n\t/// Recomputes the pivot position and transform.\n\tpub fn recalculate_pivot(&mut self, document: &DocumentMessageHandler) {\n\t\tlet selected = document.network_interface.selected_nodes();\n\t\tself.empty = !selected.has_selected_nodes();\n\t\tif !selected.has_selected_nodes() {\n\t\t\treturn;\n\t\t}\n\n\t\tlet transform = selected\n\t\t\t.selected_visible_and_unlocked_layers(&document.network_interface)\n\t\t\t.find(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]))\n\t\t\t.map(|layer| document.metadata().transform_to_viewport_with_first_transform_node_if_group(layer, &document.network_interface))\n\t\t\t.unwrap_or_default();\n\n\t\tlet bounds = document\n\t\t\t.network_interface\n\t\t\t.selected_nodes()\n\t\t\t.selected_visible_and_unlocked_layers(&document.network_interface)\n\t\t\t.filter_map(|layer| {\n\t\t\t\tif transform.matrix2.determinant().abs() <= f64::EPSILON {\n\t\t\t\t\treturn None;\n\t\t\t\t}\n\t\t\t\tdocument\n\t\t\t\t\t.metadata()\n\t\t\t\t\t.bounding_box_with_transform(layer, transform.inverse() * document.metadata().transform_to_viewport(layer))\n\t\t\t})\n\t\t\t.reduce(graphene_std::renderer::Quad::combine_bounds);\n\n\t\tlet [min, max] = bounds.unwrap_or([DVec2::ZERO, DVec2::ONE]);\n\t\tself.transform_from_normalized = transform * DAffine2::from_translation(min) * DAffine2::from_scale(max - min);\n\n\t\tif self.old_pivot_position != ReferencePoint::None {\n\t\t\tself.pivot = Some(self.transform_from_normalized.transform_point2(self.normalized_pivot));\n\t\t}\n\t}\n\n\tpub fn recalculate_pivot_for_layer(&mut self, document: &DocumentMessageHandler, bounds: Option<[DVec2; 2]>) {\n\t\tlet selected = document.network_interface.selected_nodes();\n\t\tif !selected.has_selected_nodes() {\n\t\t\tself.normalized_pivot = DVec2::splat(0.5);\n\t\t\tself.pivot = None;\n\t\t\treturn;\n\t\t};\n\n\t\tlet [min, max] = bounds.unwrap_or([DVec2::ZERO, DVec2::ONE]);\n\t\tself.transform_from_normalized = DAffine2::from_translation(min) * DAffine2::from_scale(max - min);\n\t\tself.pivot = Some(self.transform_from_normalized.transform_point2(self.normalized_pivot));\n\t}\n\n\t/// Answers if the pivot widget has changed (so we should refresh the tool bar at the top of the canvas).\n\tpub fn should_refresh_pivot_position(&mut self) -> bool {\n\t\tlet new = self.to_pivot_position();\n\t\tlet should_refresh = new != self.old_pivot_position;\n\t\tself.old_pivot_position = new;\n\t\tshould_refresh\n\t}\n\n\tpub fn to_pivot_position(&self) -> ReferencePoint {\n\t\tself.normalized_pivot.into()\n\t}\n\n\t/// Sets the viewport position of the pivot.\n\tpub fn set_viewport_position(&mut self, position: DVec2) {\n\t\tif self.transform_from_normalized.matrix2.determinant().abs() <= f64::EPSILON {\n\t\t\treturn;\n\t\t};\n\n\t\tself.normalized_pivot = self.transform_from_normalized.inverse().transform_point2(position);\n\t\tself.pivot = Some(position);\n\t}\n\n\t/// Set the pivot using a normalized position.\n\tpub fn set_normalized_position(&mut self, position: DVec2) {\n\t\tself.normalized_pivot = position;\n\t\tself.pivot = Some(self.transform_from_normalized.transform_point2(position));\n\t}\n\n\t/// Answers if the pointer is currently positioned over the pivot.\n\tpub fn is_over(&self, mouse: DVec2) -> bool {\n\t\tself.pivot.filter(|&pivot| mouse.distance_squared(pivot) < (PIVOT_DIAMETER / 2.).powi(2)).is_some()\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/resize.rs",
    "content": "use crate::messages::input_mapper::utility_types::input_keyboard::Key;\nuse crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::prelude::*;\nuse crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnapTypeConfiguration};\nuse glam::{DAffine2, DVec2, Vec2Swizzles};\n\n#[derive(Clone, Debug, Default)]\npub struct Resize {\n\t/// Stored as a document position so the start doesn't move if the canvas is panned.\n\tpub drag_start: DVec2,\n\tpub layer: Option<LayerNodeIdentifier>,\n\tpub snap_manager: SnapManager,\n}\n\nimpl Resize {\n\t/// Starts a resize, assigning the snap targets and snapping the starting position.\n\tpub fn start(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler) {\n\t\tlet root_transform = document.metadata().document_to_viewport;\n\t\tlet point = SnapCandidatePoint::handle(root_transform.inverse().transform_point2(input.mouse.position));\n\t\tlet snapped = self.snap_manager.free_snap(&SnapData::new(document, input, viewport), &point, SnapTypeConfiguration::default());\n\t\tself.drag_start = snapped.snapped_point_document;\n\t}\n\n\t/// Calculate the drag start position in viewport space.\n\tpub fn viewport_drag_start(&self, document: &DocumentMessageHandler) -> DVec2 {\n\t\tlet root_transform = document.metadata().document_to_viewport;\n\t\troot_transform.transform_point2(self.drag_start)\n\t}\n\n\t/// Compute the drag start and end based on the current mouse position. If the layer doesn't exist, returns [`None`].\n\t/// If you want to draw even without a layer, use [`Resize::calculate_points_ignore_layer`].\n\tpub fn calculate_points(\n\t\t&mut self,\n\t\tdocument: &DocumentMessageHandler,\n\t\tinput: &InputPreprocessorMessageHandler,\n\t\tviewport: &ViewportMessageHandler,\n\t\tcenter: Key,\n\t\tlock_ratio: Key,\n\t) -> Option<[DVec2; 2]> {\n\t\tlet layer = self.layer?;\n\n\t\tif layer == LayerNodeIdentifier::ROOT_PARENT {\n\t\t\tlog::error!(\"Resize layer cannot be ROOT_PARENT\");\n\t\t\treturn None;\n\t\t}\n\n\t\tif !document.network_interface.document_network().nodes.contains_key(&layer.to_node()) {\n\t\t\tself.layer.take();\n\t\t\treturn None;\n\t\t}\n\t\tSome(self.calculate_points_ignore_layer(document, input, viewport, center, lock_ratio, false))\n\t}\n\n\t/// Compute the drag start and end based on the current mouse position. Ignores the state of the layer.\n\t/// If you want to only draw whilst a layer exists, use [`Resize::calculate_points`].\n\tpub fn calculate_points_ignore_layer(\n\t\t&mut self,\n\t\tdocument: &DocumentMessageHandler,\n\t\tinput: &InputPreprocessorMessageHandler,\n\t\tviewport: &ViewportMessageHandler,\n\t\tcenter: Key,\n\t\tlock_ratio: Key,\n\t\tin_document: bool,\n\t) -> [DVec2; 2] {\n\t\tlet ratio = input.keyboard.get(lock_ratio as usize);\n\t\tlet center = input.keyboard.get(center as usize);\n\n\t\t// Use shared snapping logic with optional center and ratio constraints, considering if coordinates are in document space.\n\t\tself.compute_snapped_resize_points(document, input, viewport, center, ratio, in_document)\n\t}\n\n\tpub fn calculate_transform(\n\t\t&mut self,\n\t\tdocument: &DocumentMessageHandler,\n\t\tinput: &InputPreprocessorMessageHandler,\n\t\tviewport: &ViewportMessageHandler,\n\t\tcenter: Key,\n\t\tlock_ratio: Key,\n\t\tskip_rerender: bool,\n\t) -> Option<Message> {\n\t\tlet points_viewport = self.calculate_points(document, input, viewport, center, lock_ratio)?;\n\t\tSome(\n\t\t\tGraphOperationMessage::TransformSet {\n\t\t\t\tlayer: self.layer?,\n\t\t\t\ttransform: DAffine2::from_scale_angle_translation(points_viewport[1] - points_viewport[0], 0., points_viewport[0]),\n\t\t\t\ttransform_in: TransformIn::Viewport,\n\t\t\t\tskip_rerender,\n\t\t\t}\n\t\t\t.into(),\n\t\t)\n\t}\n\n\tpub fn calculate_circle_points(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler, center: Key) -> [DVec2; 2] {\n\t\tlet center = input.keyboard.get(center as usize);\n\n\t\t// Use shared snapping logic with enforced aspect ratio and optional center snapping.\n\t\tself.compute_snapped_resize_points(document, input, viewport, center, true, false)\n\t}\n\n\t/// Calculates two points in viewport space from a drag, applying snapping, optional center mode, and aspect ratio locking.\n\tfn compute_snapped_resize_points(\n\t\t&mut self,\n\t\tdocument: &DocumentMessageHandler,\n\t\tinput: &InputPreprocessorMessageHandler,\n\t\tviewport: &ViewportMessageHandler,\n\t\tcenter: bool,\n\t\tlock_ratio: bool,\n\t\tin_document: bool,\n\t) -> [DVec2; 2] {\n\t\tlet start = self.viewport_drag_start(document);\n\t\tlet mouse = input.mouse.position;\n\t\tlet document_to_viewport = document\n\t\t\t.navigation_handler\n\t\t\t.calculate_offset_transform(viewport.center_in_viewport_space().into(), &document.document_ptz);\n\t\tlet drag_start = self.drag_start;\n\t\tlet mut points_viewport = [start, mouse];\n\n\t\tlet ignore = if let Some(layer) = self.layer { vec![layer] } else { vec![] };\n\t\tlet snap_data = &SnapData::ignore(document, input, viewport, &ignore);\n\n\t\tif lock_ratio {\n\t\t\tlet viewport_size = points_viewport[1] - points_viewport[0];\n\t\t\tlet raw_size = if in_document {\n\t\t\t\tdocument_to_viewport.inverse().transform_vector2(viewport_size)\n\t\t\t} else {\n\t\t\t\tviewport_size\n\t\t\t};\n\n\t\t\tlet adjusted_size = raw_size.abs().max(raw_size.abs().yx()) * raw_size.signum();\n\t\t\tlet size = if in_document { document_to_viewport.transform_vector2(adjusted_size) } else { adjusted_size };\n\n\t\t\tpoints_viewport[1] = points_viewport[0] + size;\n\t\t\tlet end_document = document_to_viewport.inverse().transform_point2(points_viewport[1]);\n\t\t\tlet constraint = SnapConstraint::Line {\n\t\t\t\torigin: drag_start,\n\t\t\t\tdirection: end_document - drag_start,\n\t\t\t};\n\n\t\t\tif center {\n\t\t\t\tlet snapped = self\n\t\t\t\t\t.snap_manager\n\t\t\t\t\t.constrained_snap(snap_data, &SnapCandidatePoint::handle(end_document), constraint, SnapTypeConfiguration::default());\n\t\t\t\tlet far = SnapCandidatePoint::handle(2. * drag_start - end_document);\n\t\t\t\tlet snapped_far = self.snap_manager.constrained_snap(snap_data, &far, constraint, SnapTypeConfiguration::default());\n\t\t\t\tlet best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far };\n\n\t\t\t\tpoints_viewport[0] = document_to_viewport.transform_point2(best.snapped_point_document);\n\t\t\t\tpoints_viewport[1] = document_to_viewport.transform_point2(drag_start * 2. - best.snapped_point_document);\n\t\t\t\tself.snap_manager.update_indicator(best);\n\t\t\t} else {\n\t\t\t\tlet snapped = self\n\t\t\t\t\t.snap_manager\n\t\t\t\t\t.constrained_snap(snap_data, &SnapCandidatePoint::handle(end_document), constraint, SnapTypeConfiguration::default());\n\t\t\t\tpoints_viewport[1] = document_to_viewport.transform_point2(snapped.snapped_point_document);\n\t\t\t\tself.snap_manager.update_indicator(snapped);\n\t\t\t}\n\t\t} else {\n\t\t\tlet document_mouse = document_to_viewport.inverse().transform_point2(mouse);\n\t\t\tif center {\n\t\t\t\tlet snapped = self.snap_manager.free_snap(snap_data, &SnapCandidatePoint::handle(document_mouse), SnapTypeConfiguration::default());\n\t\t\t\tlet opposite = 2. * drag_start - document_mouse;\n\t\t\t\tlet snapped_far = self.snap_manager.free_snap(snap_data, &SnapCandidatePoint::handle(opposite), SnapTypeConfiguration::default());\n\t\t\t\tlet best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far };\n\n\t\t\t\tpoints_viewport[0] = document_to_viewport.transform_point2(best.snapped_point_document);\n\t\t\t\tpoints_viewport[1] = document_to_viewport.transform_point2(drag_start * 2. - best.snapped_point_document);\n\t\t\t\tself.snap_manager.update_indicator(best);\n\t\t\t} else {\n\t\t\t\tlet snapped = self.snap_manager.free_snap(snap_data, &SnapCandidatePoint::handle(document_mouse), SnapTypeConfiguration::default());\n\t\t\t\tpoints_viewport[1] = document_to_viewport.transform_point2(snapped.snapped_point_document);\n\t\t\t\tself.snap_manager.update_indicator(snapped);\n\t\t\t}\n\t\t}\n\n\t\tpoints_viewport\n\t}\n\n\tpub fn cleanup(&mut self, responses: &mut VecDeque<Message>) {\n\t\tself.snap_manager.cleanup(responses);\n\t\tself.layer = None;\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/shape_editor.rs",
    "content": "use super::graph_modification_utils::merge_layers;\nuse super::snapping::{SnapCache, SnapCandidatePoint, SnapData, SnapManager, SnappedPoint};\nuse super::utility_functions::{adjust_handle_colinearity, calculate_segment_angle, restore_g1_continuity, restore_previous_handle_position};\nuse crate::consts::HANDLE_LENGTH_FACTOR;\nuse crate::messages::portfolio::document::overlays::utility_functions::selected_segments_for_layer;\nuse crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};\nuse crate::messages::portfolio::document::utility_types::misc::{PathSnapSource, SnapSource};\nuse crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;\nuse crate::messages::preferences::SelectionMode;\nuse crate::messages::prelude::*;\nuse crate::messages::tool::common_functionality::snapping::SnapTypeConfiguration;\nuse crate::messages::tool::common_functionality::utility_functions::is_visible_point;\nuse crate::messages::tool::tool_messages::path_tool::{PathOverlayMode, PointSelectState};\nuse glam::{DAffine2, DVec2};\nuse graphene_std::subpath::{BezierHandles, Subpath};\nuse graphene_std::subpath::{PathSegPoints, pathseg_points};\nuse graphene_std::vector::algorithms::bezpath_algorithms::pathseg_compute_lookup_table;\nuse graphene_std::vector::misc::{HandleId, ManipulatorPointId, dvec2_to_point, point_to_dvec2};\nuse graphene_std::vector::{HandleExt, PointId, SegmentId, Vector, VectorModificationType};\nuse kurbo::{Affine, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveNearest, PathSeg, Rect, Shape};\nuse std::f64::consts::TAU;\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq)]\npub enum SelectionChange {\n\tClear,\n\tExtend,\n\tShrink,\n}\n\n#[derive(Clone, Copy, Debug)]\npub enum SelectionShape<'a> {\n\tBox(Rect),\n\tLasso(&'a Vec<DVec2>),\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]\npub enum SelectionShapeType {\n\tBox,\n\tLasso,\n}\n\n#[derive(Debug, PartialEq, Eq, Copy, Clone, Default)]\npub enum ManipulatorAngle {\n\t#[default]\n\tColinear,\n\tFree,\n\tMixed,\n}\n\n#[derive(Clone, Debug, Default)]\npub struct SelectedLayerState {\n\tselected_points: HashSet<ManipulatorPointId>,\n\tselected_segments: HashSet<SegmentId>,\n\t/// Keeps track of the current state; helps avoid unnecessary computation when called by [`ShapeState`].\n\tignore_handles: bool,\n\tignore_anchors: bool,\n\t/// Points that are selected but ignored (when their overlays are disabled) are stored here.\n\tignored_handle_points: HashSet<ManipulatorPointId>,\n\tignored_anchor_points: HashSet<ManipulatorPointId>,\n}\n\nimpl SelectedLayerState {\n\tpub fn is_empty(&self) -> bool {\n\t\tself.selected_points.is_empty() && self.selected_segments.is_empty()\n\t}\n\n\tpub fn selected_points(&self) -> impl Iterator<Item = ManipulatorPointId> + '_ {\n\t\tself.selected_points.iter().copied()\n\t}\n\n\tpub fn selected_segments(&self) -> impl Iterator<Item = SegmentId> + '_ {\n\t\tself.selected_segments.iter().copied()\n\t}\n\n\tpub fn selected_points_count(&self) -> usize {\n\t\tself.selected_points.len()\n\t}\n\n\tpub fn selected_segments_count(&self) -> usize {\n\t\tself.selected_segments.len()\n\t}\n\n\tpub fn is_segment_selected(&self, segment: SegmentId) -> bool {\n\t\tself.selected_segments.contains(&segment)\n\t}\n\n\tpub fn is_point_selected(&self, point: ManipulatorPointId) -> bool {\n\t\tself.selected_points.contains(&point)\n\t}\n\n\tpub fn select_point(&mut self, point: ManipulatorPointId) {\n\t\tself.selected_points.insert(point);\n\t}\n\n\tpub fn select_segment(&mut self, segment: SegmentId) {\n\t\tself.selected_segments.insert(segment);\n\t}\n\n\tpub fn deselect_point(&mut self, point: ManipulatorPointId) {\n\t\tself.selected_points.remove(&point);\n\t}\n\n\tpub fn deselect_segment(&mut self, segment: SegmentId) {\n\t\tself.selected_segments.remove(&segment);\n\t}\n\n\tpub fn deselect_all_points_in_layer(&mut self) {\n\t\tself.selected_points.clear();\n\t}\n\n\tpub fn deselect_all_segments_in_layer(&mut self) {\n\t\tself.selected_segments.clear();\n\t}\n\n\tpub fn clear_points(&mut self) {\n\t\tself.selected_points.clear();\n\t}\n\n\tpub fn clear_segments(&mut self) {\n\t\tself.selected_segments.clear();\n\t}\n\n\tpub fn ignore_handles(&mut self, status: bool) {\n\t\tif self.ignore_handles != status {\n\t\t\treturn;\n\t\t}\n\n\t\tself.ignore_handles = !status;\n\n\t\tif self.ignore_handles {\n\t\t\tself.ignored_handle_points.extend(self.selected_points.iter().copied().filter(|point| point.as_handle().is_some()));\n\t\t\tself.selected_points.retain(|point| !self.ignored_handle_points.contains(point));\n\t\t} else {\n\t\t\tself.selected_points.extend(self.ignored_handle_points.iter().copied());\n\t\t\tself.ignored_handle_points.clear();\n\t\t}\n\t}\n\n\tpub fn ignore_anchors(&mut self, status: bool) {\n\t\tif self.ignore_anchors != status {\n\t\t\treturn;\n\t\t}\n\n\t\tself.ignore_anchors = !status;\n\n\t\tif self.ignore_anchors {\n\t\t\tself.ignored_anchor_points.extend(self.selected_points.iter().copied().filter(|point| point.as_anchor().is_some()));\n\t\t\tself.selected_points.retain(|point| !self.ignored_anchor_points.contains(point));\n\t\t} else {\n\t\t\tself.selected_points.extend(self.ignored_anchor_points.iter().copied());\n\t\t\tself.ignored_anchor_points.clear();\n\t\t}\n\t}\n}\n\npub type SelectedShapeState = HashMap<LayerNodeIdentifier, SelectedLayerState>;\n\n#[derive(Debug, Default)]\npub struct ShapeState {\n\t/// The layers we can select and edit manipulators (anchors and handles) from.\n\tpub selected_shape_state: SelectedShapeState,\n\tignore_handles: bool,\n\tignore_anchors: bool,\n}\n\n#[derive(Debug)]\npub struct SelectedPointsInfo {\n\tpub points: Vec<ManipulatorPointInfo>,\n\tpub offset: DVec2,\n\tpub vector: Vector,\n}\n\n#[derive(Debug)]\npub struct SelectedSegmentsInfo {\n\tpub segments: Vec<SegmentId>,\n\tpub vector: Vector,\n}\n\n#[derive(Clone, Copy, Debug, Eq, PartialEq)]\npub struct ManipulatorPointInfo {\n\tpub layer: LayerNodeIdentifier,\n\tpub point_id: ManipulatorPointId,\n}\n\npub type OpposingHandleLengths = HashMap<LayerNodeIdentifier, HashMap<HandleId, f64>>;\n\n#[derive(Clone)]\npub struct ClosestSegment {\n\tlayer: LayerNodeIdentifier,\n\tsegment: SegmentId,\n\tbezier: PathSeg,\n\tpoints: [PointId; 2],\n\tcolinear: [Option<HandleId>; 2],\n\tt: f64,\n\tbezier_point_to_viewport: DVec2,\n}\n\nimpl ClosestSegment {\n\tpub fn layer(&self) -> LayerNodeIdentifier {\n\t\tself.layer\n\t}\n\n\tpub fn segment(&self) -> SegmentId {\n\t\tself.segment\n\t}\n\n\tpub fn points(&self) -> [PointId; 2] {\n\t\tself.points\n\t}\n\n\tpub fn pathseg(&self) -> PathSeg {\n\t\tself.bezier\n\t}\n\n\tpub fn closest_point_document(&self) -> DVec2 {\n\t\tpoint_to_dvec2(self.bezier.eval(self.t))\n\t}\n\n\tpub fn closest_point_to_viewport(&self) -> DVec2 {\n\t\tself.bezier_point_to_viewport\n\t}\n\n\tpub fn closest_point(&self, document_metadata: &DocumentMetadata, network_interface: &NodeNetworkInterface) -> DVec2 {\n\t\tlet transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface);\n\t\tlet bezier_point = point_to_dvec2(self.bezier.eval(self.t));\n\t\ttransform.transform_point2(bezier_point)\n\t}\n\n\t/// Updates this [`ClosestSegment`] with the viewport-space location of the closest point on the segment to the given mouse position.\n\tpub fn update_closest_point(&mut self, document_metadata: &DocumentMetadata, network_interface: &NodeNetworkInterface, mouse_position: DVec2) {\n\t\tlet transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface);\n\t\tlet layer_mouse_pos = transform.inverse().transform_point2(mouse_position);\n\n\t\tlet t = self.bezier.nearest(dvec2_to_point(layer_mouse_pos), DEFAULT_ACCURACY).t.clamp(0., 1.);\n\t\tself.t = t;\n\n\t\tlet bezier_point = point_to_dvec2(self.bezier.eval(t));\n\t\tlet bezier_point = transform.transform_point2(bezier_point);\n\t\tself.bezier_point_to_viewport = bezier_point;\n\t}\n\n\tpub fn distance_squared(&self, mouse_position: DVec2) -> f64 {\n\t\tself.bezier_point_to_viewport.distance_squared(mouse_position)\n\t}\n\n\tpub fn too_far(&self, mouse_position: DVec2, tolerance: f64) -> bool {\n\t\ttolerance.powi(2) < self.distance_squared(mouse_position)\n\t}\n\n\tpub fn handle_positions(&self, document_metadata: &DocumentMetadata, network_interface: &NodeNetworkInterface) -> (Option<DVec2>, Option<DVec2>) {\n\t\t// Transform to viewport space\n\t\tlet transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface);\n\n\t\t// Split the Bezier at the parameter `t`\n\t\tlet first = self.bezier.subsegment(0_f64..self.t);\n\t\tlet second = self.bezier.subsegment(self.t..1.);\n\n\t\t// Transform the handle positions to viewport space\n\t\tlet first_handle = pathseg_points(first).p2.map(|handle| transform.transform_point2(handle));\n\t\tlet second_handle = pathseg_points(second).p1.map(|handle| transform.transform_point2(handle));\n\n\t\t(first_handle, second_handle)\n\t}\n\n\tpub fn adjusted_insert(&self, responses: &mut VecDeque<Message>) -> (PointId, [SegmentId; 2]) {\n\t\tlet layer = self.layer;\n\t\tlet first = pathseg_points(self.bezier.subsegment(0_f64..self.t));\n\t\tlet second = pathseg_points(self.bezier.subsegment(self.t..1.));\n\n\t\t// Point\n\t\tlet midpoint = PointId::generate();\n\t\tlet modification_type = VectorModificationType::InsertPoint { id: midpoint, position: first.p3 };\n\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\n\t\t// First segment\n\t\tlet segment_ids = [SegmentId::generate(), SegmentId::generate()];\n\t\tlet modification_type = VectorModificationType::InsertSegment {\n\t\t\tid: segment_ids[0],\n\t\t\tpoints: [self.points[0], midpoint],\n\t\t\thandles: [first.p1.map(|handle| handle - first.p0), first.p2.map(|handle| handle - first.p3)],\n\t\t};\n\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\n\t\t// Last segment\n\t\tlet modification_type = VectorModificationType::InsertSegment {\n\t\t\tid: segment_ids[1],\n\t\t\tpoints: [midpoint, self.points[1]],\n\t\t\thandles: [second.p1.map(|handle| handle - second.p0), second.p2.map(|handle| handle - second.p3)],\n\t\t};\n\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\n\t\t// G1 continuous on new handles\n\t\tif pathseg_points(self.bezier).p2.is_some() {\n\t\t\tlet handles = [HandleId::end(segment_ids[0]), HandleId::primary(segment_ids[1])];\n\t\t\tlet modification_type = VectorModificationType::SetG1Continuous { handles, enabled: true };\n\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t}\n\n\t\t// Remove old segment\n\t\tlet modification_type = VectorModificationType::RemoveSegment { id: self.segment };\n\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\n\t\t// Restore mirroring on end handles\n\t\tfor (handle, other) in self.colinear.into_iter().zip([HandleId::primary(segment_ids[0]), HandleId::end(segment_ids[1])]) {\n\t\t\tlet Some(handle) = handle else { continue };\n\t\t\tlet handles = [handle, other];\n\t\t\tlet modification_type = VectorModificationType::SetG1Continuous { handles, enabled: true };\n\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t}\n\n\t\t(midpoint, segment_ids)\n\t}\n\n\tpub fn adjusted_insert_and_select(&self, shape_editor: &mut ShapeState, responses: &mut VecDeque<Message>, extend_selection: bool, point_mode: bool, is_segment_selected: bool) {\n\t\tlet (id, segments) = self.adjusted_insert(responses);\n\t\tif point_mode || is_segment_selected {\n\t\t\tshape_editor.select_anchor_point_by_id(self.layer, id, extend_selection);\n\t\t}\n\n\t\tif is_segment_selected {\n\t\t\tlet Some(state) = shape_editor.selected_shape_state.get_mut(&self.layer) else { return };\n\t\t\tsegments.iter().for_each(|segment| state.select_segment(*segment));\n\t\t}\n\t}\n\n\tpub fn calculate_perp(&self, document: &DocumentMessageHandler) -> DVec2 {\n\t\tlet tangent = if let (Some(handle1), Some(handle2)) = self.handle_positions(document.metadata(), &document.network_interface) {\n\t\t\t(handle1 - handle2).try_normalize()\n\t\t} else {\n\t\t\tlet [first_point, last_point] = self.points();\n\t\t\tif let Some(vector) = document.network_interface.compute_modified_vector(self.layer()) {\n\t\t\t\tif let (Some(pos1), Some(pos2)) = (\n\t\t\t\t\tManipulatorPointId::Anchor(first_point).get_position(&vector),\n\t\t\t\t\tManipulatorPointId::Anchor(last_point).get_position(&vector),\n\t\t\t\t) {\n\t\t\t\t\t(pos1 - pos2).try_normalize()\n\t\t\t\t} else {\n\t\t\t\t\tNone\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tNone\n\t\t\t}\n\t\t}\n\t\t.unwrap_or(DVec2::ZERO);\n\t\ttangent.perp()\n\t}\n\n\t/// Molding the bezier curve.\n\t/// Returns adjacent handles' [`HandleId`] if colinearity is broken temporarily.\n\tpub fn mold_handle_positions(\n\t\t&self,\n\t\tdocument: &DocumentMessageHandler,\n\t\tresponses: &mut VecDeque<Message>,\n\t\t(c1, c2): (DVec2, DVec2),\n\t\tnew_b: DVec2,\n\t\tbreak_colinear_molding: bool,\n\t\ttemporary_adjacent_handles_while_molding: Option<[Option<HandleId>; 2]>,\n\t) -> Option<[Option<HandleId>; 2]> {\n\t\tlet transform = document.metadata().transform_to_viewport_if_feeds(self.layer, &document.network_interface);\n\n\t\tlet start = point_to_dvec2(self.bezier.start());\n\t\tlet end = point_to_dvec2(self.bezier.end());\n\n\t\t// Apply the drag delta to the segment's handles\n\t\tlet b = self.bezier_point_to_viewport;\n\t\tlet delta = transform.inverse().transform_vector2(new_b - b);\n\t\tlet (nc1, nc2) = (c1 + delta, c2 + delta);\n\n\t\tlet handle1 = HandleId::primary(self.segment);\n\t\tlet handle2 = HandleId::end(self.segment);\n\t\tlet layer = self.layer;\n\n\t\tlet modification_type = handle1.set_relative_position(nc1 - start);\n\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\n\t\tlet modification_type = handle2.set_relative_position(nc2 - end);\n\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\n\t\t// If adjacent segments have colinear handles, their direction is changed but their handle lengths is preserved\n\t\t// TODO: Find something which is more appropriate\n\t\tlet vector = document.network_interface.compute_modified_vector(self.layer())?;\n\n\t\tif break_colinear_molding {\n\t\t\t// Disable G1 continuity\n\t\t\tlet other_handles = [\n\t\t\t\trestore_previous_handle_position(handle1, c1, start, &vector, layer, responses),\n\t\t\t\trestore_previous_handle_position(handle2, c2, end, &vector, layer, responses),\n\t\t\t];\n\n\t\t\t// Store other HandleId in tool data to regain colinearity later\n\t\t\tif temporary_adjacent_handles_while_molding.is_some() {\n\t\t\t\ttemporary_adjacent_handles_while_molding\n\t\t\t} else {\n\t\t\t\tSome(other_handles)\n\t\t\t}\n\t\t} else {\n\t\t\t// Move the colinear handles so that colinearity is maintained\n\t\t\tadjust_handle_colinearity(handle1, start, nc1, &vector, layer, responses);\n\t\t\tadjust_handle_colinearity(handle2, end, nc2, &vector, layer, responses);\n\n\t\t\tif let Some(adjacent_handles) = temporary_adjacent_handles_while_molding {\n\t\t\t\tif let Some(other_handle1) = adjacent_handles[0] {\n\t\t\t\t\trestore_g1_continuity(handle1, other_handle1, nc1, start, &vector, layer, responses);\n\t\t\t\t}\n\t\t\t\tif let Some(other_handle2) = adjacent_handles[1] {\n\t\t\t\t\trestore_g1_continuity(handle2, other_handle2, nc2, end, &vector, layer, responses);\n\t\t\t\t}\n\t\t\t}\n\t\t\tNone\n\t\t}\n\t}\n}\n\n// TODO Consider keeping a list of selected manipulators to minimize traversals of the layers\nimpl ShapeState {\n\tpub fn is_selected_layer(&self, layer: LayerNodeIdentifier) -> bool {\n\t\tself.selected_shape_state.contains_key(&layer)\n\t}\n\n\tpub fn is_point_ignored(&self, point: &ManipulatorPointId) -> bool {\n\t\t(point.as_handle().is_some() && self.ignore_handles) || (point.as_anchor().is_some() && self.ignore_anchors)\n\t}\n\n\t/// Applies a dummy vector modification to the layer. In the case where a group containing some vector data is selected, this triggers the creation of a «Flatten Path» node.\n\tfn add_dummy_modification_to_trigger_graph_reorganization(layer: LayerNodeIdentifier, start_point: PointId, _end_point: PointId, responses: &mut VecDeque<Message>) {\n\t\t// Apply a zero-delta to one of the points to trigger reorganization\n\t\tlet dummy_modification = VectorModificationType::ApplyPointDelta {\n\t\t\tpoint: start_point,\n\t\t\tdelta: DVec2::ZERO,\n\t\t};\n\t\tresponses.add(GraphOperationMessage::Vector {\n\t\t\tlayer,\n\t\t\tmodification_type: dummy_modification,\n\t\t});\n\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t}\n\n\tfn defer_connect_points_by_position(\n\t\tdocument: &DocumentMessageHandler,\n\t\tlayer1: LayerNodeIdentifier,\n\t\tstart_point: PointId,\n\t\tlayer2: LayerNodeIdentifier,\n\t\tend_point: PointId,\n\t\ttarget_layer: LayerNodeIdentifier,\n\t\tresponses: &mut VecDeque<Message>,\n\t) {\n\t\t// Get the local positions of the selected points\n\t\tlet start_local_pos = document.network_interface.compute_modified_vector(layer1).and_then(|v| v.point_domain.position_from_id(start_point));\n\t\tlet end_local_pos = document.network_interface.compute_modified_vector(layer2).and_then(|v| v.point_domain.position_from_id(end_point));\n\n\t\t// Transform to document/world space\n\t\tlet start_transform = document.metadata().transform_to_document(layer1);\n\t\tlet end_transform = document.metadata().transform_to_document(layer2);\n\n\t\tlet (Some(start_local), Some(end_local)) = (start_local_pos, end_local_pos) else {\n\t\t\twarn!(\"Unable to resolve point ids for joining\");\n\t\t\treturn;\n\t\t};\n\t\t// Transform positions to document/world space\n\t\t// These positions are stable (won't change during reorganization)\n\t\tlet start_pos = start_transform.transform_point2(start_local);\n\t\tlet end_pos = end_transform.transform_point2(end_local);\n\n\t\t// Defer position-based connection to run after reorganization completes\n\t\t// By then, PointIds will be stable with their new remapped values\n\t\tresponses.add(DeferMessage::AfterGraphRun {\n\t\t\tmessages: vec![\n\t\t\t\tToolMessage::Path(PathToolMessage::ConnectPointsByPosition {\n\t\t\t\t\tlayer: target_layer,\n\t\t\t\t\tstart_position: start_pos,\n\t\t\t\t\tend_position: end_pos,\n\t\t\t\t})\n\t\t\t\t.into(),\n\t\t\t],\n\t\t});\n\t}\n\n\tfn handle_grouped_transform_close_path(document: &DocumentMessageHandler, layer: LayerNodeIdentifier, start_point: PointId, end_point: PointId, responses: &mut VecDeque<Message>) {\n\t\t// This zero-delta modification triggers point domain reorganization\n\t\tSelf::add_dummy_modification_to_trigger_graph_reorganization(layer, start_point, end_point, responses);\n\n\t\t// Use the helper to defer the connection until after reorganization\n\t\tSelf::defer_connect_points_by_position(document, layer, start_point, layer, end_point, layer, responses);\n\t}\n\n\tpub fn close_selected_path(&self, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {\n\t\t// First collect all selected anchor points across all layers\n\t\tlet all_selected_points: Vec<(LayerNodeIdentifier, PointId)> = self\n\t\t\t.selected_shape_state\n\t\t\t.iter()\n\t\t\t.flat_map(|(&layer, state)| {\n\t\t\t\tif document.network_interface.compute_modified_vector(layer).is_none() {\n\t\t\t\t\treturn Vec::new().into_iter();\n\t\t\t\t};\n\n\t\t\t\t// Collect selected anchor points from this layer\n\t\t\t\tstate\n\t\t\t\t\t.selected_points\n\t\t\t\t\t.iter()\n\t\t\t\t\t.filter_map(|&point| if let ManipulatorPointId::Anchor(id) = point { Some((layer, id)) } else { None })\n\t\t\t\t\t.collect::<Vec<_>>()\n\t\t\t\t\t.into_iter()\n\t\t\t})\n\t\t\t.collect();\n\n\t\t// If exactly two points are selected (regardless of layer), connect them\n\t\tif all_selected_points.len() == 2 {\n\t\t\tlet (layer1, start_point) = all_selected_points[0];\n\t\t\tlet (layer2, end_point) = all_selected_points[1];\n\n\t\t\tif layer1 == layer2 {\n\t\t\t\t// Same layer case\n\t\t\t\tif start_point == end_point {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Check if this layer has multiple children (is a merged/grouped layer created with Cmd+G)\n\t\t\t\tlet num_children = layer1.children(document.metadata()).count();\n\t\t\t\tlet is_grouped = num_children > 1;\n\n\t\t\t\tif is_grouped {\n\t\t\t\t\t// Grouped/merged layer: use helper function to handle reorganization\n\t\t\t\t\tSelf::handle_grouped_transform_close_path(document, layer1, start_point, end_point, responses);\n\t\t\t\t} else {\n\t\t\t\t\t// Single segment: PointIDs are stable, use immediate insertion\n\t\t\t\t\tlet segment_id = SegmentId::generate();\n\t\t\t\t\tlet modification_type = VectorModificationType::InsertSegment {\n\t\t\t\t\t\tid: segment_id,\n\t\t\t\t\t\tpoints: [end_point, start_point],\n\t\t\t\t\t\thandles: [None, None],\n\t\t\t\t\t};\n\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer: layer1, modification_type });\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Different layers: merge first, then create segment\n\t\t\t\tmerge_layers(document, layer1, layer2, responses);\n\n\t\t\t\t// Use the helper to defer the connection until after reorganization\n\t\t\t\tSelf::defer_connect_points_by_position(document, layer1, start_point, layer2, end_point, layer1, responses);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// If no points are selected, try to find a single continuous subpath in each layer to connect the endpoints of\n\t\tfor &layer in self.selected_shape_state.keys() {\n\t\t\tlet Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };\n\n\t\t\tlet endpoints: Vec<PointId> = vector.point_domain.ids().iter().copied().filter(|&point_id| vector.all_connected(point_id).count() == 1).collect();\n\n\t\t\tif endpoints.len() == 2 {\n\t\t\t\tlet start_point = endpoints[0];\n\t\t\t\tlet end_point = endpoints[1];\n\n\t\t\t\tlet segment_id = SegmentId::generate();\n\t\t\t\tlet modification_type = VectorModificationType::InsertSegment {\n\t\t\t\t\tid: segment_id,\n\t\t\t\t\tpoints: [end_point, start_point],\n\t\t\t\t\thandles: [None, None],\n\t\t\t\t};\n\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t}\n\t\t}\n\t}\n\n\t// Snap, returning a viewport delta\n\tpub fn snap(\n\t\t&self,\n\t\tsnap_manager: &mut SnapManager,\n\t\tsnap_cache: &SnapCache,\n\t\tdocument: &DocumentMessageHandler,\n\t\tinput: &InputPreprocessorMessageHandler,\n\t\tviewport: &ViewportMessageHandler,\n\t\tprevious_mouse: DVec2,\n\t) -> DVec2 {\n\t\tlet snap_data = SnapData::new_snap_cache(document, input, viewport, snap_cache);\n\n\t\tlet mouse_delta = document\n\t\t\t.network_interface\n\t\t\t.document_metadata()\n\t\t\t.document_to_viewport\n\t\t\t.inverse()\n\t\t\t.transform_vector2(input.mouse.position - previous_mouse);\n\t\tlet mut offset = mouse_delta;\n\t\tlet mut best_snapped = SnappedPoint::infinite_snap(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position));\n\t\tfor (layer, state) in &self.selected_shape_state {\n\t\t\tlet Some(vector) = document.network_interface.compute_modified_vector(*layer) else { continue };\n\n\t\t\tlet to_document = document.metadata().transform_to_document_if_feeds(*layer, &document.network_interface);\n\n\t\t\tfor &selected in &state.selected_points {\n\t\t\t\tlet source = match selected {\n\t\t\t\t\tManipulatorPointId::Anchor(_) if vector.colinear(selected) => SnapSource::Path(PathSnapSource::AnchorPointWithColinearHandles),\n\t\t\t\t\tManipulatorPointId::Anchor(_) => SnapSource::Path(PathSnapSource::AnchorPointWithFreeHandles),\n\t\t\t\t\t// TODO: This doesn't actually work for handles, instead handles enter the arm above for free handles\n\t\t\t\t\tManipulatorPointId::PrimaryHandle(_) | ManipulatorPointId::EndHandle(_) => SnapSource::Path(PathSnapSource::HandlePoint),\n\t\t\t\t};\n\n\t\t\t\tlet Some(position) = selected.get_position(&vector) else { continue };\n\t\t\t\tlet mut point = SnapCandidatePoint::new_source(to_document.transform_point2(position) + mouse_delta, source);\n\n\t\t\t\tif let Some(id) = selected.as_anchor() {\n\t\t\t\t\tfor neighbor in vector.connected_points(id) {\n\t\t\t\t\t\tif state.is_point_selected(ManipulatorPointId::Anchor(neighbor)) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlet Some(position) = vector.point_domain.position_from_id(neighbor) else { continue };\n\t\t\t\t\t\tpoint.neighbors.push(to_document.transform_point2(position));\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tlet snapped = snap_manager.free_snap(&snap_data, &point, SnapTypeConfiguration::default());\n\t\t\t\tif best_snapped.other_snap_better(&snapped) {\n\t\t\t\t\toffset = snapped.snapped_point_document - point.document_point + mouse_delta;\n\t\t\t\t\tbest_snapped = snapped;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tsnap_manager.update_indicator(best_snapped);\n\t\tdocument.metadata().document_to_viewport.transform_vector2(offset)\n\t}\n\n\t/// Select/deselect the first point within the selection threshold.\n\t/// Returns a tuple of the points if found and the offset, or `None` otherwise.\n\tpub fn change_point_selection(\n\t\t&mut self,\n\t\tnetwork_interface: &NodeNetworkInterface,\n\t\tmouse_position: DVec2,\n\t\tselect_threshold: f64,\n\t\textend_selection: bool,\n\t\tpath_overlay_mode: PathOverlayMode,\n\t\tfrontier_handles_info: Option<&HashMap<LayerNodeIdentifier, HashMap<SegmentId, Vec<PointId>>>>,\n\t) -> Option<Option<SelectedPointsInfo>> {\n\t\tif self.selected_shape_state.is_empty() {\n\t\t\treturn None;\n\t\t}\n\n\t\tif let Some((layer, manipulator_point_id)) = self.find_nearest_visible_point_indices(network_interface, mouse_position, select_threshold, path_overlay_mode, frontier_handles_info) {\n\t\t\tlet vector = network_interface.compute_modified_vector(layer)?;\n\t\t\tlet point_position = manipulator_point_id.get_position(&vector)?;\n\n\t\t\tlet selected_shape_state = self.selected_shape_state.get(&layer)?;\n\t\t\tlet already_selected = selected_shape_state.is_point_selected(manipulator_point_id);\n\n\t\t\t// Offset to snap the selected point to the cursor\n\t\t\tlet offset = mouse_position\n\t\t\t\t- network_interface\n\t\t\t\t\t.document_metadata()\n\t\t\t\t\t.transform_to_viewport_if_feeds(layer, network_interface)\n\t\t\t\t\t.transform_point2(point_position);\n\n\t\t\t// This is selecting the manipulator only for now, next to generalize to points\n\n\t\t\tlet retain_existing_selection = extend_selection || already_selected;\n\t\t\tif !retain_existing_selection {\n\t\t\t\tself.deselect_all_points();\n\t\t\t\tself.deselect_all_segments();\n\t\t\t}\n\n\t\t\t// Add to the selected points (deselect is managed in DraggingState, DragStop)\n\t\t\tlet selected_shape_state = self.selected_shape_state.get_mut(&layer)?;\n\t\t\tselected_shape_state.select_point(manipulator_point_id);\n\n\t\t\tlet points = self\n\t\t\t\t.selected_shape_state\n\t\t\t\t.iter()\n\t\t\t\t.flat_map(|(layer, state)| state.selected_points.iter().map(|&point_id| ManipulatorPointInfo { layer: *layer, point_id }))\n\t\t\t\t.collect();\n\n\t\t\treturn Some(Some(SelectedPointsInfo { points, offset, vector }));\n\t\t}\n\t\tNone\n\t}\n\n\tpub fn get_point_selection_state(\n\t\t&mut self,\n\t\tnetwork_interface: &NodeNetworkInterface,\n\t\tmouse_position: DVec2,\n\t\tselect_threshold: f64,\n\t\tpath_overlay_mode: PathOverlayMode,\n\t\tfrontier_handles_info: Option<&HashMap<LayerNodeIdentifier, HashMap<SegmentId, Vec<PointId>>>>,\n\t\tpoint_editing_mode: bool,\n\t) -> Option<(bool, Option<SelectedPointsInfo>)> {\n\t\tif self.selected_shape_state.is_empty() {\n\t\t\treturn None;\n\t\t}\n\n\t\tif let Some((layer, manipulator_point_id)) = self.find_nearest_point_indices(network_interface, mouse_position, select_threshold) {\n\t\t\t// If not point editing mode then only handles are allowed to be dragged\n\t\t\tif !point_editing_mode && matches!(manipulator_point_id, ManipulatorPointId::Anchor(_)) {\n\t\t\t\treturn None;\n\t\t\t}\n\t\t\tlet vector = network_interface.compute_modified_vector(layer)?;\n\t\t\tlet point_position = manipulator_point_id.get_position(&vector)?;\n\t\t\tlet selected_shape_state = self.selected_shape_state.get(&layer)?;\n\t\t\t// Check if point is visible under current overlay mode or not\n\t\t\tlet selected_segments_for_layer = selected_segments_for_layer(&vector, selected_shape_state);\n\t\t\tlet selected_points = self.selected_points().cloned().collect::<HashSet<_>>();\n\t\t\tlet frontier_handles_for_layer = frontier_handles_info.and_then(|frontier_handles| frontier_handles.get(&layer));\n\t\t\tif !is_visible_point(\n\t\t\t\tmanipulator_point_id,\n\t\t\t\t&vector,\n\t\t\t\tpath_overlay_mode,\n\t\t\t\tfrontier_handles_for_layer,\n\t\t\t\t&selected_segments_for_layer,\n\t\t\t\t&selected_points,\n\t\t\t) {\n\t\t\t\treturn None;\n\t\t\t}\n\n\t\t\tlet already_selected = selected_shape_state.is_point_selected(manipulator_point_id);\n\n\t\t\t// Offset to snap the selected point to the cursor\n\t\t\tlet offset = mouse_position\n\t\t\t\t- network_interface\n\t\t\t\t\t.document_metadata()\n\t\t\t\t\t.transform_to_viewport_if_feeds(layer, network_interface)\n\t\t\t\t\t.transform_point2(point_position);\n\n\t\t\t// Gather current selection information\n\t\t\tlet points = self\n\t\t\t\t.selected_shape_state\n\t\t\t\t.iter()\n\t\t\t\t.flat_map(|(layer, state)| state.selected_points.iter().map(|&point_id| ManipulatorPointInfo { layer: *layer, point_id }))\n\t\t\t\t.collect();\n\n\t\t\tlet selection_info = SelectedPointsInfo { points, offset, vector };\n\n\t\t\t// Return the current selection state and info\n\t\t\treturn Some((already_selected, Some(selection_info)));\n\t\t}\n\n\t\tNone\n\t}\n\n\tpub fn select_anchor_point_by_id(&mut self, layer: LayerNodeIdentifier, id: PointId, extend_selection: bool) {\n\t\tif !extend_selection {\n\t\t\tself.deselect_all_points();\n\t\t}\n\t\tlet point = ManipulatorPointId::Anchor(id);\n\t\tlet Some(selected_state) = self.selected_shape_state.get_mut(&layer) else { return };\n\t\tselected_state.select_point(point);\n\t}\n\n\t/// Selects all anchors connected to the selected subpath, and deselects all handles, for the given layer.\n\tpub fn select_connected(&mut self, document: &DocumentMessageHandler, layer: LayerNodeIdentifier, mouse: DVec2, points: bool, segments: bool) {\n\t\tlet Some(vector) = document.network_interface.compute_modified_vector(layer) else { return };\n\t\tlet to_viewport = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);\n\t\tlet layer_mouse = to_viewport.inverse().transform_point2(mouse);\n\t\tlet state = self.selected_shape_state.entry(layer).or_default();\n\n\t\tlet mut selected_stack = Vec::new();\n\t\t// Find all subpaths that have been clicked\n\t\tfor stroke in vector.stroke_bezier_paths() {\n\t\t\tif stroke.contains_point(layer_mouse)\n\t\t\t\t&& let Some(first) = stroke.manipulator_groups().first()\n\t\t\t{\n\t\t\t\tselected_stack.push(first.id);\n\t\t\t}\n\t\t}\n\t\tstate.clear_points();\n\n\t\tif selected_stack.is_empty() {\n\t\t\t// Fall back on just selecting all points/segments in the layer\n\t\t\tif points {\n\t\t\t\tfor &point in vector.point_domain.ids() {\n\t\t\t\t\tstate.select_point(ManipulatorPointId::Anchor(point));\n\t\t\t\t}\n\t\t\t}\n\t\t\tif segments {\n\t\t\t\tfor &segment in vector.segment_domain.ids() {\n\t\t\t\t\tstate.select_segment(segment);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tlet mut connected_points = HashSet::new();\n\n\t\twhile let Some(point) = selected_stack.pop() {\n\t\t\tif !connected_points.contains(&point) {\n\t\t\t\tconnected_points.insert(point);\n\t\t\t\tselected_stack.extend(vector.connected_points(point));\n\t\t\t}\n\t\t}\n\n\t\tif points {\n\t\t\tconnected_points.iter().for_each(|point| state.select_point(ManipulatorPointId::Anchor(*point)));\n\t\t}\n\n\t\tif segments {\n\t\t\tfor (id, _, start, end) in vector.segment_bezier_iter() {\n\t\t\t\tif connected_points.contains(&start) || connected_points.contains(&end) {\n\t\t\t\t\tstate.select_segment(id);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/// Selects all anchors, and deselects all handles, for the given layer.\n\tpub fn select_all_anchors_in_layer(&mut self, document: &DocumentMessageHandler, layer: LayerNodeIdentifier) {\n\t\tlet state = self.selected_shape_state.entry(layer).or_default();\n\t\tSelf::select_all_anchors_in_layer_with_state(document, layer, state);\n\t}\n\n\t/// Selects all anchors, and deselects all handles, for the selected layers.\n\tpub fn select_all_anchors_in_selected_layers(&mut self, document: &DocumentMessageHandler) {\n\t\tfor (&layer, state) in self.selected_shape_state.iter_mut() {\n\t\t\tSelf::select_all_anchors_in_layer_with_state(document, layer, state);\n\t\t}\n\t}\n\n\t/// Selects all segments for the selected layers.\n\tpub fn select_all_segments_in_selected_layers(&mut self, document: &DocumentMessageHandler) {\n\t\tfor (&layer, state) in self.selected_shape_state.iter_mut() {\n\t\t\tSelf::select_all_segments_in_layer_with_state(document, layer, state);\n\t\t}\n\t}\n\n\t/// Internal helper function that selects all anchors, and deselects all handles, for a layer given its [`LayerNodeIdentifier`] and [`SelectedLayerState`].\n\tfn select_all_anchors_in_layer_with_state(document: &DocumentMessageHandler, layer: LayerNodeIdentifier, state: &mut SelectedLayerState) {\n\t\tlet Some(vector) = document.network_interface.compute_modified_vector(layer) else { return };\n\n\t\tstate.clear_points();\n\n\t\tfor &point in vector.point_domain.ids() {\n\t\t\tstate.select_point(ManipulatorPointId::Anchor(point))\n\t\t}\n\t}\n\n\t/// Internal helper function that selects all segments, for a layer given its [`LayerNodeIdentifier`] and [`SelectedLayerState`].\n\tfn select_all_segments_in_layer_with_state(document: &DocumentMessageHandler, layer: LayerNodeIdentifier, state: &mut SelectedLayerState) {\n\t\tlet Some(vector) = document.network_interface.compute_modified_vector(layer) else { return };\n\n\t\tfor &segment in vector.segment_domain.ids() {\n\t\t\tstate.select_segment(segment);\n\t\t}\n\t}\n\n\t/// Deselects all points (anchors and handles) across every selected layer.\n\tpub fn deselect_all_points(&mut self) {\n\t\tfor state in self.selected_shape_state.values_mut() {\n\t\t\tstate.selected_points.clear()\n\t\t}\n\t}\n\n\t/// Deselects all segments across every selected layer\n\tpub fn deselect_all_segments(&mut self) {\n\t\tfor state in self.selected_shape_state.values_mut() {\n\t\t\tstate.selected_segments.clear()\n\t\t}\n\t}\n\n\tpub fn update_selected_anchors_status(&mut self, status: bool) {\n\t\tfor state in self.selected_shape_state.values_mut() {\n\t\t\tself.ignore_anchors = !status;\n\t\t\tstate.ignore_anchors(status);\n\t\t}\n\t}\n\n\tpub fn update_selected_handles_status(&mut self, status: bool) {\n\t\tfor state in self.selected_shape_state.values_mut() {\n\t\t\tself.ignore_handles = !status;\n\t\t\tstate.ignore_handles(status);\n\t\t}\n\t}\n\n\t/// Deselects all the anchors across every selected layer.\n\tpub fn deselect_all_anchors(&mut self) {\n\t\tfor (_, state) in self.selected_shape_state.iter_mut() {\n\t\t\tlet selected_anchor_points: Vec<ManipulatorPointId> = state.selected_points.iter().filter(|selected_point| selected_point.as_anchor().is_some()).cloned().collect();\n\n\t\t\tfor point in selected_anchor_points {\n\t\t\t\tstate.deselect_point(point);\n\t\t\t}\n\t\t}\n\t}\n\n\t/// Deselects all the handles across every selected layer.\n\tpub fn deselect_all_handles(&mut self) {\n\t\tfor (_, state) in self.selected_shape_state.iter_mut() {\n\t\t\tlet selected_handle_points: Vec<ManipulatorPointId> = state.selected_points.iter().filter(|selected_point| selected_point.as_handle().is_some()).cloned().collect();\n\n\t\t\tfor point in selected_handle_points {\n\t\t\t\tstate.deselect_point(point);\n\t\t\t}\n\t\t}\n\t}\n\n\t/// Set the shapes we consider for selection, we will choose draggable manipulators from these shapes.\n\tpub fn set_selected_layers(&mut self, target_layers: Vec<LayerNodeIdentifier>) {\n\t\tself.selected_shape_state.retain(|layer_path, _| target_layers.contains(layer_path));\n\t\tfor layer in target_layers {\n\t\t\tself.selected_shape_state.entry(layer).or_default();\n\t\t}\n\t}\n\n\t/// Returns an iterator over the currently selected layers to get their [`LayerNodeIdentifier`]s.\n\tpub fn selected_layers(&self) -> impl Iterator<Item = &LayerNodeIdentifier> {\n\t\tself.selected_shape_state.keys()\n\t}\n\n\t/// iterate over all selected layers in order from top to bottom\n\t/// # WARN\n\t/// iterate over all layers of the document\n\tpub fn sorted_selected_layers<'a>(&'a self, document_metadata: &'a DocumentMetadata) -> impl Iterator<Item = LayerNodeIdentifier> + 'a {\n\t\tdocument_metadata.all_layers().filter(|layer| self.selected_shape_state.contains_key(layer))\n\t}\n\n\tpub fn has_selected_layers(&self) -> bool {\n\t\t!self.selected_shape_state.is_empty()\n\t}\n\n\t/// Provide the currently selected points by reference.\n\tpub fn selected_points(&self) -> impl Iterator<Item = &'_ ManipulatorPointId> {\n\t\tself.selected_shape_state.values().flat_map(|state| &state.selected_points)\n\t}\n\n\tpub fn selected_segments(&self) -> impl Iterator<Item = &'_ SegmentId> {\n\t\tself.selected_shape_state.values().flat_map(|state| &state.selected_segments)\n\t}\n\n\tpub fn selected_points_in_layer(&self, layer: LayerNodeIdentifier) -> Option<&HashSet<ManipulatorPointId>> {\n\t\tself.selected_shape_state.get(&layer).map(|state| &state.selected_points)\n\t}\n\n\tpub fn selected_segments_in_layer(&self, layer: LayerNodeIdentifier) -> Option<&HashSet<SegmentId>> {\n\t\tself.selected_shape_state.get(&layer).map(|state| &state.selected_segments)\n\t}\n\n\tpub fn move_primary(&self, segment: SegmentId, delta: DVec2, layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>) {\n\t\tresponses.add(GraphOperationMessage::Vector {\n\t\t\tlayer,\n\t\t\tmodification_type: VectorModificationType::ApplyPrimaryDelta { segment, delta },\n\t\t});\n\t}\n\n\tpub fn move_end(&self, segment: SegmentId, delta: DVec2, layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>) {\n\t\tresponses.add(GraphOperationMessage::Vector {\n\t\t\tlayer,\n\t\t\tmodification_type: VectorModificationType::ApplyEndDelta { segment, delta },\n\t\t});\n\t}\n\n\tpub fn move_anchor(&self, point: PointId, vector: &Vector, delta: DVec2, layer: LayerNodeIdentifier, selected: Option<&SelectedLayerState>, responses: &mut VecDeque<Message>) {\n\t\t// Move anchor\n\t\tresponses.add(GraphOperationMessage::Vector {\n\t\t\tlayer,\n\t\t\tmodification_type: VectorModificationType::ApplyPointDelta { point, delta },\n\t\t});\n\n\t\t// Move the other handle for a quadratic bezier\n\t\tfor segment in vector.end_connected(point) {\n\t\t\tlet Some((start, _end, bezier)) = vector.segment_points_from_id(segment) else { continue };\n\n\t\t\tif let BezierHandles::Quadratic { handle } = bezier.handles {\n\t\t\t\tif selected.is_some_and(|selected| selected.is_point_selected(ManipulatorPointId::Anchor(start))) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tlet relative_position = handle - bezier.start + delta;\n\t\t\t\tlet modification_type = VectorModificationType::SetPrimaryHandle { segment, relative_position };\n\n\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t}\n\t\t}\n\t}\n\n\t/// Moves a control point to a `new_position` in document space.\n\t/// Returns `Some(())` if successful and `None` otherwise.\n\tpub fn reposition_control_point(\n\t\t&self,\n\t\tpoint: &ManipulatorPointId,\n\t\tnetwork_interface: &NodeNetworkInterface,\n\t\tnew_position: DVec2,\n\t\tlayer: LayerNodeIdentifier,\n\t\tresponses: &mut VecDeque<Message>,\n\t) -> Option<()> {\n\t\tif self.is_point_ignored(point) {\n\t\t\treturn None;\n\t\t}\n\n\t\tlet vector = network_interface.compute_modified_vector(layer)?;\n\t\tlet transform = network_interface.document_metadata().transform_to_document_if_feeds(layer, network_interface).inverse();\n\t\tlet position = transform.transform_point2(new_position);\n\t\tlet current_position = point.get_position(&vector)?;\n\t\tlet delta = position - current_position;\n\n\t\tmatch *point {\n\t\t\tManipulatorPointId::Anchor(point) => self.move_anchor(point, &vector, delta, layer, None, responses),\n\t\t\tManipulatorPointId::PrimaryHandle(segment) => {\n\t\t\t\tself.move_primary(segment, delta, layer, responses);\n\t\t\t\tif let Some(handle) = point.as_handle()\n\t\t\t\t\t&& let Some(handles) = vector.colinear_manipulators.iter().find(|handles| handles[0] == handle || handles[1] == handle)\n\t\t\t\t{\n\t\t\t\t\tlet modification_type = VectorModificationType::SetG1Continuous { handles: *handles, enabled: false };\n\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t\t}\n\t\t\t}\n\t\t\tManipulatorPointId::EndHandle(segment) => {\n\t\t\t\tself.move_end(segment, delta, layer, responses);\n\t\t\t\tif let Some(handle) = point.as_handle()\n\t\t\t\t\t&& let Some(handles) = vector.colinear_manipulators.iter().find(|handles| handles[0] == handle || handles[1] == handle)\n\t\t\t\t{\n\t\t\t\t\tlet modification_type = VectorModificationType::SetG1Continuous { handles: *handles, enabled: false };\n\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tSome(())\n\t}\n\n\t/// Iterates over the selected manipulator groups excluding endpoints, returning whether their handles have mixed, colinear, or free angles.\n\t/// If there are no points selected this function returns mixed.\n\tpub fn selected_manipulator_angles(&self, network_interface: &NodeNetworkInterface) -> ManipulatorAngle {\n\t\t// This iterator contains a bool indicating whether or not selected points' manipulator groups have colinear handles.\n\t\tlet mut points_colinear_status = self\n\t\t\t.selected_shape_state\n\t\t\t.iter()\n\t\t\t.map(|(&layer, selection_state)| (network_interface.compute_modified_vector(layer), selection_state))\n\t\t\t.flat_map(|(data, selection_state)| {\n\t\t\t\tselection_state.selected_points.iter().filter_map(move |&point| {\n\t\t\t\t\tlet Some(data) = &data else { return None };\n\t\t\t\t\tlet _ = point.get_handle_pair(data)?; // ignores the endpoints.\n\t\t\t\t\tSome(data.colinear(point))\n\t\t\t\t})\n\t\t\t});\n\n\t\tlet Some(first_is_colinear) = points_colinear_status.next() else { return ManipulatorAngle::Mixed };\n\t\tif points_colinear_status.any(|point| first_is_colinear != point) {\n\t\t\treturn ManipulatorAngle::Mixed;\n\t\t}\n\t\tif first_is_colinear { ManipulatorAngle::Colinear } else { ManipulatorAngle::Free }\n\t}\n\n\tpub fn convert_manipulator_handles_to_colinear(&self, vector: &Vector, point_id: PointId, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier) {\n\t\tlet Some(anchor_position) = ManipulatorPointId::Anchor(point_id).get_position(vector) else {\n\t\t\treturn;\n\t\t};\n\t\tlet handles = vector.all_connected(point_id).take(2).collect::<Vec<_>>();\n\t\tlet non_zero_handles = handles.iter().filter(|handle| handle.length(vector) > 1e-6).count();\n\t\tlet handle_segments = handles.iter().map(|handles| handles.segment).collect::<Vec<_>>();\n\n\t\t// Check if the anchor is connected to linear segments and has no handles\n\t\tlet linear_segments = vector.connected_linear_segments(point_id) != 0;\n\n\t\t// Grab the next and previous manipulator groups by simply looking at the next / previous index\n\t\tlet points = handles.iter().map(|handle| vector.other_point(handle.segment, point_id));\n\t\tlet anchor_positions = points.map(|point| point.and_then(|point| ManipulatorPointId::Anchor(point).get_position(vector))).collect::<Vec<_>>();\n\n\t\tlet mut segment_angle = 0.;\n\t\tlet mut segment_count = 0.;\n\n\t\tfor segment in &handle_segments {\n\t\t\tlet Some(angle) = calculate_segment_angle(point_id, *segment, vector, false) else {\n\t\t\t\tcontinue;\n\t\t\t};\n\t\t\tsegment_angle += angle;\n\t\t\tsegment_count += 1.;\n\t\t}\n\n\t\t// For a non-endpoint anchor, handles are perpendicular to the average tangent of adjacent segments.(Refer:https://github.com/GraphiteEditor/Graphite/pull/2620#issuecomment-2881501494)\n\t\tlet mut handle_direction = if segment_count > 1. {\n\t\t\tsegment_angle /= segment_count;\n\t\t\tsegment_angle += std::f64::consts::FRAC_PI_2;\n\t\t\tDVec2::new(segment_angle.cos(), segment_angle.sin())\n\t\t} else {\n\t\t\tDVec2::new(segment_angle.cos(), segment_angle.sin())\n\t\t};\n\n\t\t// Set the manipulator to have colinear handles\n\t\tif let (Some(a), Some(b)) = (handles.first(), handles.get(1)) {\n\t\t\tlet handles = [*a, *b];\n\t\t\tlet modification_type = VectorModificationType::SetG1Continuous { handles, enabled: true };\n\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t}\n\n\t\t// Flip the vector if it is not facing towards the same direction as the anchor\n\t\tlet [first, second] = [anchor_positions.first().copied().flatten(), anchor_positions.get(1).copied().flatten()];\n\t\tif first.is_some_and(|group| (group - anchor_position).normalize_or_zero().dot(handle_direction) < 0.)\n\t\t\t|| second.is_some_and(|group| (group - anchor_position).normalize_or_zero().dot(handle_direction) > 0.)\n\t\t{\n\t\t\thandle_direction *= -1.;\n\t\t}\n\n\t\tif non_zero_handles != 0 && !linear_segments {\n\t\t\tlet [a, b] = handles.as_slice() else { return };\n\t\t\tlet (non_zero_handle, zero_handle) = if a.length(vector) > 1e-6 { (a, b) } else { (b, a) };\n\t\t\tlet Some(direction) = non_zero_handle\n\t\t\t\t.to_manipulator_point()\n\t\t\t\t.get_position(vector)\n\t\t\t\t.and_then(|position| (position - anchor_position).try_normalize())\n\t\t\telse {\n\t\t\t\treturn;\n\t\t\t};\n\t\t\tlet new_position = -direction * non_zero_handle.length(vector);\n\t\t\tlet modification_type = zero_handle.set_relative_position(new_position);\n\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t} else {\n\t\t\t// Push both in and out handles into the correct position\n\t\t\tfor ((handle, sign), other_anchor) in handles.iter().zip([1., -1.]).zip(&anchor_positions) {\n\t\t\t\tlet Some(anchor_vector) = other_anchor.map(|position| position - anchor_position) else {\n\t\t\t\t\tcontinue;\n\t\t\t\t};\n\n\t\t\t\tlet Some(unit_vector) = anchor_vector.try_normalize() else {\n\t\t\t\t\tcontinue;\n\t\t\t\t};\n\n\t\t\t\tlet projection = anchor_vector.length() * HANDLE_LENGTH_FACTOR * handle_direction.dot(unit_vector).abs();\n\n\t\t\t\tlet new_position = handle_direction * projection * sign;\n\t\t\t\tlet modification_type = handle.set_relative_position(new_position);\n\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\n\t\t\t\t// Create the opposite handle if it doesn't exist (if it is not a cubic segment)\n\t\t\t\tif handle.opposite().to_manipulator_point().get_position(vector).is_none() {\n\t\t\t\t\tlet modification_type = handle.opposite().set_relative_position(DVec2::ZERO);\n\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/// Converts all selected points to colinear while moving the handles to ensure their 180° angle separation.\n\t/// If only one handle is selected, the other handle will be moved to match the angle of the selected handle.\n\t/// If both or neither handles are selected, the angle of both handles will be averaged from their current angles, weighted by their lengths.\n\t/// Assumes all selected manipulators have handles that are already not colinear.\n\t///\n\t/// For vector meshes, the non-colinear handle which is nearest in the direction of 180° angle separation becomes colinear with current handle.\n\t/// If there is no such handle, nothing happens.\n\tpub fn convert_selected_manipulators_to_colinear_handles(&self, responses: &mut VecDeque<Message>, document: &DocumentMessageHandler) {\n\t\tlet mut skip_set = HashSet::new();\n\n\t\tfor (&layer, layer_state) in self.selected_shape_state.iter() {\n\t\t\tlet Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };\n\t\t\tlet transform = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface);\n\n\t\t\tfor &point in layer_state.selected_points.iter() {\n\t\t\t\t// Skip a point which has more than 2 segments connected (vector meshes)\n\t\t\t\tif let ManipulatorPointId::Anchor(anchor) = point\n\t\t\t\t\t&& vector.all_connected(anchor).count() > 2\n\t\t\t\t{\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Here we take handles as the current handle and the most opposite non-colinear-handle\n\n\t\t\t\tlet is_handle_colinear = |handle: HandleId| -> bool { vector.colinear_manipulators.iter().any(|&handles| handles[0] == handle || handles[1] == handle) };\n\n\t\t\t\tlet other_handles = if matches!(point, ManipulatorPointId::Anchor(_)) {\n\t\t\t\t\tpoint.get_handle_pair(&vector)\n\t\t\t\t} else {\n\t\t\t\t\tpoint.get_all_connected_handles(&vector).and_then(|handles| {\n\t\t\t\t\t\tlet mut non_colinear_handles = handles.iter().filter(|&handle| !is_handle_colinear(*handle)).clone().collect::<Vec<_>>();\n\n\t\t\t\t\t\t// Sort these by angle from the current handle\n\t\t\t\t\t\tnon_colinear_handles.sort_by(|&handle_a, &handle_b| {\n\t\t\t\t\t\t\tlet anchor = point.get_anchor_position(&vector).expect(\"No anchor position for handle\");\n\t\t\t\t\t\t\tlet orig_handle_pos = point.get_position(&vector).expect(\"No handle position\");\n\n\t\t\t\t\t\t\tlet a_pos = handle_a.to_manipulator_point().get_position(&vector).expect(\"No handle position\");\n\t\t\t\t\t\t\tlet b_pos = handle_b.to_manipulator_point().get_position(&vector).expect(\"No handle position\");\n\n\t\t\t\t\t\t\tlet v_orig = (orig_handle_pos - anchor).normalize_or_zero();\n\n\t\t\t\t\t\t\tlet v_a = (a_pos - anchor).normalize_or_zero();\n\t\t\t\t\t\t\tlet v_b = (b_pos - anchor).normalize_or_zero();\n\n\t\t\t\t\t\t\tlet angle_a = v_orig.angle_to(v_a).abs();\n\t\t\t\t\t\t\tlet angle_b = v_orig.angle_to(v_b).abs();\n\n\t\t\t\t\t\t\t// Sort by descending angle (180° is furthest)\n\t\t\t\t\t\t\tangle_b.partial_cmp(&angle_a).unwrap_or(std::cmp::Ordering::Equal)\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tlet current = match point {\n\t\t\t\t\t\t\tManipulatorPointId::EndHandle(segment) => HandleId::end(segment),\n\t\t\t\t\t\t\tManipulatorPointId::PrimaryHandle(segment) => HandleId::primary(segment),\n\t\t\t\t\t\t\tManipulatorPointId::Anchor(_) => unreachable!(),\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tnon_colinear_handles.first().map(|other| [current, **other])\n\t\t\t\t\t})\n\t\t\t\t};\n\n\t\t\t\tlet Some(handles) = other_handles else { continue };\n\n\t\t\t\tif skip_set.contains(&handles) || skip_set.contains(&[handles[1], handles[0]]) {\n\t\t\t\t\tcontinue;\n\t\t\t\t};\n\n\t\t\t\tskip_set.insert(handles);\n\n\t\t\t\tlet [selected0, selected1] = handles.map(|handle| layer_state.selected_points.contains(&handle.to_manipulator_point()));\n\t\t\t\tlet handle_positions = handles.map(|handle| handle.to_manipulator_point().get_position(&vector));\n\n\t\t\t\tlet Some(anchor_id) = point.get_anchor(&vector) else { continue };\n\t\t\t\tlet Some(anchor) = vector.point_domain.position_from_id(anchor_id) else { continue };\n\n\t\t\t\tlet anchor_points = handles.map(|handle| vector.other_point(handle.segment, anchor_id));\n\t\t\t\tlet anchor_positions = anchor_points.map(|point| point.and_then(|point| vector.point_domain.position_from_id(point)));\n\n\t\t\t\t// If one handle is selected (but both exist), only move the other handle\n\t\t\t\tif let (true, [Some(pos0), Some(pos1)]) = ((selected0 ^ selected1), handle_positions) {\n\t\t\t\t\tlet [(_selected_handle, selected_position), (unselected_handle, unselected_position)] = if selected0 {\n\t\t\t\t\t\t[(handles[0], pos0), (handles[1], pos1)]\n\t\t\t\t\t} else {\n\t\t\t\t\t\t[(handles[1], pos1), (handles[0], pos0)]\n\t\t\t\t\t};\n\t\t\t\t\tlet direction = transform\n\t\t\t\t\t\t.transform_vector2(anchor - selected_position)\n\t\t\t\t\t\t.try_normalize()\n\t\t\t\t\t\t.unwrap_or_else(|| transform.transform_vector2(unselected_position - anchor).normalize_or_zero());\n\n\t\t\t\t\tlet length = transform.transform_vector2(unselected_position - anchor).length();\n\t\t\t\t\tlet position = transform.inverse().transform_vector2(direction * length);\n\t\t\t\t\tlet modification_type = unselected_handle.set_relative_position(position);\n\t\t\t\t\tif (anchor - selected_position).length() > 1e-6 {\n\t\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// If both handles are selected, average the angles of the handles\n\t\t\t\telse {\n\t\t\t\t\t// We could normalize these directions?\n\t\t\t\t\tlet mut handle_directions = handle_positions.map(|handle| handle.map(|handle| handle - anchor));\n\n\t\t\t\t\tlet mut normalized = handle_directions[0].and_then(|a| handle_directions[1].and_then(|b| (a - b).try_normalize()));\n\n\t\t\t\t\tif normalized.is_none() || handle_directions.iter().any(|&d| d.is_some_and(|d| d.length_squared() < f64::EPSILON * 1e5)) {\n\t\t\t\t\t\thandle_directions = anchor_positions.map(|relative_anchor| relative_anchor.map(|relative_anchor| (relative_anchor - anchor) / 3.));\n\t\t\t\t\t\tnormalized = handle_directions[0].and_then(|a| handle_directions[1].and_then(|b| (a - b).try_normalize()))\n\t\t\t\t\t}\n\t\t\t\t\tlet Some(normalized) = normalized else { continue };\n\n\t\t\t\t\t// Push both in and out handles into the correct position\n\t\t\t\t\tfor (index, sign) in [(0, 1.), (1, -1.)] {\n\t\t\t\t\t\tlet Some(direction) = handle_directions[index] else { continue };\n\t\t\t\t\t\tlet new_position = direction.length() * normalized * sign;\n\t\t\t\t\t\tlet modification_type = handles[index].set_relative_position(new_position);\n\t\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\n\t\t\t\t\t\t// Create the opposite handle if it doesn't exist (if it is not a cubic segment)\n\t\t\t\t\t\tif handles[index].opposite().to_manipulator_point().get_position(&vector).is_none() {\n\t\t\t\t\t\t\tlet modification_type = handles[index].opposite().set_relative_position(DVec2::ZERO);\n\t\t\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tlet modification_type = VectorModificationType::SetG1Continuous { handles, enabled: true };\n\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t}\n\t\t}\n\t}\n\n\t/// Move the selected points and segments by dragging the mouse.\n\t#[allow(clippy::too_many_arguments)]\n\tpub fn move_selected_points_and_segments(\n\t\t&self,\n\t\thandle_lengths: Option<OpposingHandleLengths>,\n\t\tdocument: &DocumentMessageHandler,\n\t\tdelta: DVec2,\n\t\tequidistant: bool,\n\t\tin_viewport_space: bool,\n\t\twas_alt_dragging: bool,\n\t\topposite_handle_position: Option<DVec2>,\n\t\tskip_opposite_handle: bool,\n\t\tresponses: &mut VecDeque<Message>,\n\t) {\n\t\tfor (&layer, state) in &self.selected_shape_state {\n\t\t\tlet Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };\n\n\t\t\tlet opposing_handles = handle_lengths.as_ref().and_then(|handle_lengths| handle_lengths.get(&layer));\n\n\t\t\tlet transform_to_viewport_space = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);\n\t\t\tlet transform_to_document_space = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface);\n\t\t\tlet delta_transform = if in_viewport_space {\n\t\t\t\ttransform_to_viewport_space\n\t\t\t} else {\n\t\t\t\tDAffine2::from_angle(document.document_ptz.tilt()) * transform_to_document_space\n\t\t\t};\n\t\t\tlet delta = delta_transform.inverse().transform_vector2(delta);\n\n\t\t\t// Make a new collection of anchor points which needs to be moved\n\t\t\tlet mut affected_points = state.selected_points.clone();\n\n\t\t\tfor (segment_id, _, start, end) in vector.segment_bezier_iter() {\n\t\t\t\tif state.is_segment_selected(segment_id) {\n\t\t\t\t\taffected_points.insert(ManipulatorPointId::Anchor(start));\n\t\t\t\t\taffected_points.insert(ManipulatorPointId::Anchor(end));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor &point in affected_points.iter() {\n\t\t\t\tif self.is_point_ignored(&point) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tlet handle = match point {\n\t\t\t\t\tManipulatorPointId::Anchor(point) => {\n\t\t\t\t\t\tself.move_anchor(point, &vector, delta, layer, Some(state), responses);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tManipulatorPointId::PrimaryHandle(segment) => HandleId::primary(segment),\n\t\t\t\t\tManipulatorPointId::EndHandle(segment) => HandleId::end(segment),\n\t\t\t\t};\n\n\t\t\t\tlet Some(anchor_id) = point.get_anchor(&vector) else { continue };\n\t\t\t\tif state.is_point_selected(ManipulatorPointId::Anchor(anchor_id)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tlet Some(anchor_position) = vector.point_domain.position_from_id(anchor_id) else { continue };\n\n\t\t\t\tlet Some(handle_position) = point.get_position(&vector) else { continue };\n\t\t\t\tlet handle_position = handle_position + delta;\n\n\t\t\t\tlet modification_type = handle.set_relative_position(handle_position - anchor_position);\n\n\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\n\t\t\t\tlet Some(other) = vector.other_colinear_handle(handle) else { continue };\n\n\t\t\t\tif skip_opposite_handle {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif state.is_point_selected(other.to_manipulator_point()) {\n\t\t\t\t\t// If two colinear handles are being dragged at the same time but not the anchor, it is necessary to break the colinear state.\n\t\t\t\t\tlet handles = [handle, other];\n\t\t\t\t\tlet modification_type = VectorModificationType::SetG1Continuous { handles, enabled: false };\n\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tlet new_relative = if equidistant {\n\t\t\t\t\t-(handle_position - anchor_position)\n\t\t\t\t}\n\t\t\t\t// If the handle is very close to the anchor, return the original position\n\t\t\t\telse if (handle_position - anchor_position).length_squared() < f64::EPSILON * 1e5 {\n\t\t\t\t\tlet Some(opposite_handle_position) = opposite_handle_position else { continue };\n\t\t\t\t\topposite_handle_position - anchor_position\n\t\t\t\t} else {\n\t\t\t\t\t// TODO: Is this equivalent to `transform_to_document_space`? If changed, the before and after should be tested.\n\t\t\t\t\tlet transform = document.metadata().document_to_viewport.inverse() * transform_to_viewport_space;\n\t\t\t\t\tlet Some(other_position) = other.to_manipulator_point().get_position(&vector) else {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t};\n\t\t\t\t\tlet direction = transform.transform_vector2(handle_position - anchor_position).try_normalize();\n\t\t\t\t\tlet opposing_handle = opposing_handles.and_then(|handles| handles.get(&other));\n\t\t\t\t\tlet length = opposing_handle.copied().unwrap_or_else(|| transform.transform_vector2(other_position - anchor_position).length());\n\t\t\t\t\tdirection.map_or(other_position - anchor_position, |direction| transform.inverse().transform_vector2(-direction * length))\n\t\t\t\t};\n\n\t\t\t\tif !was_alt_dragging {\n\t\t\t\t\tlet modification_type = other.set_relative_position(new_relative);\n\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/// The opposing handle lengths.\n\tpub fn opposing_handle_lengths(&self, document: &DocumentMessageHandler) -> OpposingHandleLengths {\n\t\tself.selected_shape_state\n\t\t\t.iter()\n\t\t\t.filter_map(|(&layer, state)| {\n\t\t\t\tlet vector = document.network_interface.compute_modified_vector(layer)?;\n\t\t\t\tlet transform = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface);\n\t\t\t\tlet opposing_handle_lengths = vector\n\t\t\t\t\t.colinear_manipulators\n\t\t\t\t\t.iter()\n\t\t\t\t\t.filter_map(|&handles| {\n\t\t\t\t\t\t// We will keep track of the opposing handle length when:\n\t\t\t\t\t\t// i) Exactly one handle is selected.\n\t\t\t\t\t\t// ii) The anchor is not selected.\n\n\t\t\t\t\t\tlet anchor = handles[0].to_manipulator_point().get_anchor(&vector)?;\n\t\t\t\t\t\tlet anchor_selected = state.is_point_selected(ManipulatorPointId::Anchor(anchor));\n\t\t\t\t\t\tif anchor_selected {\n\t\t\t\t\t\t\treturn None;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlet handles_selected = handles.map(|handle| state.is_point_selected(handle.to_manipulator_point()));\n\n\t\t\t\t\t\tlet other = match handles_selected {\n\t\t\t\t\t\t\t[true, false] => handles[1],\n\t\t\t\t\t\t\t[false, true] => handles[0],\n\t\t\t\t\t\t\t_ => return None,\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tlet opposing_handle_position = other.to_manipulator_point().get_position(&vector)?;\n\t\t\t\t\t\tlet anchor_position = vector.point_domain.position_from_id(anchor)?;\n\n\t\t\t\t\t\tlet opposing_handle_length = transform.transform_vector2(opposing_handle_position - anchor_position).length();\n\t\t\t\t\t\tSome((other, opposing_handle_length))\n\t\t\t\t\t})\n\t\t\t\t\t.collect::<HashMap<_, _>>();\n\t\t\t\tSome((layer, opposing_handle_lengths))\n\t\t\t})\n\t\t\t.collect::<HashMap<_, _>>()\n\t}\n\n\tpub fn dissolve_segment(&self, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, vector: &Vector, segment: SegmentId, points: [PointId; 2]) {\n\t\t// Checking which point is terminal point\n\t\tlet is_point1_terminal = vector.connected_count(points[0]) == 1;\n\t\tlet is_point2_terminal = vector.connected_count(points[1]) == 1;\n\n\t\t// Delete the segment and terminal points\n\t\tlet modification_type = VectorModificationType::RemoveSegment { id: segment };\n\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\tfor &handles in vector.colinear_manipulators.iter().filter(|handles| handles.iter().any(|handle| handle.segment == segment)) {\n\t\t\tlet modification_type = VectorModificationType::SetG1Continuous { handles, enabled: false };\n\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t}\n\n\t\tif is_point1_terminal {\n\t\t\tlet modification_type = VectorModificationType::RemovePoint { id: points[0] };\n\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t}\n\t\tif is_point2_terminal {\n\t\t\tlet modification_type = VectorModificationType::RemovePoint { id: points[1] };\n\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t}\n\t}\n\n\tfn dissolve_anchor(anchor: PointId, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, vector: &Vector) -> Option<[(HandleId, PointId); 2]> {\n\t\t// Delete point\n\t\tlet modification_type = VectorModificationType::RemovePoint { id: anchor };\n\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\n\t\t// Delete connected segments\n\t\tfor HandleId { segment, .. } in vector.all_connected(anchor) {\n\t\t\tlet modification_type = VectorModificationType::RemoveSegment { id: segment };\n\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\tfor &handles in vector.colinear_manipulators.iter().filter(|handles| handles.iter().any(|handle| handle.segment == segment)) {\n\t\t\t\tlet modification_type = VectorModificationType::SetG1Continuous { handles, enabled: false };\n\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t}\n\t\t}\n\n\t\t// Add in new segment if possible\n\t\tlet mut handles = ManipulatorPointId::Anchor(anchor).get_handle_pair(vector)?;\n\t\thandles.reverse();\n\t\tlet opposites = handles.map(|handle| handle.opposite());\n\n\t\tlet [Some(start), Some(end)] = opposites.map(|opposite| opposite.to_manipulator_point().get_anchor(vector)) else {\n\t\t\treturn None;\n\t\t};\n\t\tSome([(handles[0], start), (handles[1], end)])\n\t}\n\n\t/// Dissolve the selected points.\n\tpub fn delete_selected_points(&mut self, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>, start_transaction: bool) {\n\t\tlet mut transaction_started = false;\n\n\t\tfor (&layer, state) in &mut self.selected_shape_state {\n\t\t\tlet mut missing_anchors = HashMap::new();\n\t\t\tlet mut deleted_anchors = HashSet::new();\n\t\t\tlet Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };\n\n\t\t\tlet selected_segments = &state.selected_segments;\n\n\t\t\tfor point in std::mem::take(&mut state.selected_points) {\n\t\t\t\tif !transaction_started && start_transaction {\n\t\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\t\t\t\t\ttransaction_started = true;\n\t\t\t\t}\n\n\t\t\t\tmatch point {\n\t\t\t\t\tManipulatorPointId::Anchor(anchor) => {\n\t\t\t\t\t\tif let Some(handles) = Self::dissolve_anchor(anchor, responses, layer, &vector)\n\t\t\t\t\t\t\t&& !vector.all_connected(anchor).any(|a| selected_segments.contains(&a.segment))\n\t\t\t\t\t\t\t&& vector.all_connected(anchor).count() <= 2\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tmissing_anchors.insert(anchor, handles);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdeleted_anchors.insert(anchor);\n\t\t\t\t\t}\n\t\t\t\t\tManipulatorPointId::PrimaryHandle(_) | ManipulatorPointId::EndHandle(_) => {\n\t\t\t\t\t\tlet Some(handle) = point.as_handle() else { continue };\n\n\t\t\t\t\t\t// Place the handle on top of the anchor\n\t\t\t\t\t\tlet modification_type = handle.set_relative_position(DVec2::ZERO);\n\t\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\n\t\t\t\t\t\t// Disable the g1 continuous\n\t\t\t\t\t\tfor &handles in &vector.colinear_manipulators {\n\t\t\t\t\t\t\tif handles.contains(&handle) {\n\t\t\t\t\t\t\t\tlet modification_type = VectorModificationType::SetG1Continuous { handles, enabled: false };\n\t\t\t\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet mut visited = Vec::new();\n\t\t\twhile let Some((anchor, handles)) = missing_anchors.keys().next().copied().and_then(|id| missing_anchors.remove_entry(&id)) {\n\t\t\t\tvisited.push(anchor);\n\n\t\t\t\t// If the adjacent point is just this point then skip\n\t\t\t\tlet mut handles = handles.map(|handle| (handle.1 != anchor).then_some(handle));\n\n\t\t\t\t// If the adjacent points are themselves being deleted, then repeatedly visit the newest agacent points.\n\t\t\t\tfor handle in &mut handles {\n\t\t\t\t\twhile let Some((point, connected)) = (*handle).and_then(|(_, point)| missing_anchors.remove_entry(&point)) {\n\t\t\t\t\t\tvisited.push(point);\n\n\t\t\t\t\t\t*handle = connected.into_iter().find(|(_, point)| !visited.contains(point));\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tlet [Some(start), Some(end)] = handles else { continue };\n\n\t\t\t\t// Avoid reconnecting to points that are being deleted (this can happen if a whole loop is deleted)\n\t\t\t\tif deleted_anchors.contains(&start.1) || deleted_anchors.contains(&end.1) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Avoid reconnecting to points which have adjacent segments selected\n\n\t\t\t\t// Grab the handles from the opposite side of the segment(s) being deleted and make it relative to the anchor\n\t\t\t\tlet [handle_start, handle_end] = [start, end].map(|(handle, _)| {\n\t\t\t\t\tlet handle = handle.opposite();\n\t\t\t\t\tlet handle_position = handle.to_manipulator_point().get_position(&vector);\n\t\t\t\t\tlet relative_position = handle.to_manipulator_point().get_anchor(&vector).and_then(|anchor| vector.point_domain.position_from_id(anchor));\n\t\t\t\t\thandle_position.and_then(|handle| relative_position.map(|relative| handle - relative)).unwrap_or_default()\n\t\t\t\t});\n\n\t\t\t\tlet segment = start.0.segment;\n\n\t\t\t\tlet modification_type = VectorModificationType::InsertSegment {\n\t\t\t\t\tid: segment,\n\t\t\t\t\tpoints: [start.1, end.1],\n\t\t\t\t\thandles: [Some(handle_start), Some(handle_end)],\n\t\t\t\t};\n\n\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\n\t\t\t\tfor &handles in vector.colinear_manipulators.iter() {\n\t\t\t\t\tif !handles.iter().any(|&handle| handle == start.0.opposite() || handle == end.0.opposite()) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tlet Some(anchor) = handles[0].to_manipulator_point().get_anchor(&vector) else { continue };\n\t\t\t\t\tlet Some(other) = handles.iter().find(|&&handle| handle != start.0.opposite() && handle != end.0.opposite()) else {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t};\n\n\t\t\t\t\tlet handle_ty = if anchor == start.1 {\n\t\t\t\t\t\tHandleId::primary(segment)\n\t\t\t\t\t} else if anchor == end.1 {\n\t\t\t\t\t\tHandleId::end(segment)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t};\n\t\t\t\t\tlet handles = [*other, handle_ty];\n\t\t\t\t\tlet modification_type = VectorModificationType::SetG1Continuous { handles, enabled: true };\n\n\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn delete_selected_segments(&mut self, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>, start_transaction: bool) -> bool {\n\t\tlet mut transaction_started = false;\n\n\t\tfor (&layer, state) in &self.selected_shape_state {\n\t\t\tlet Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };\n\n\t\t\tfor (segment, _, start, end) in vector.segment_bezier_iter() {\n\t\t\t\tif state.selected_segments.contains(&segment) {\n\t\t\t\t\tif start_transaction && !transaction_started {\n\t\t\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\t\t\t\t\t\ttransaction_started = true;\n\t\t\t\t\t}\n\t\t\t\t\tself.dissolve_segment(responses, layer, &vector, segment, [start, end]);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\ttransaction_started\n\t}\n\n\tpub fn delete_hanging_selected_anchors(&mut self, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>, start_transaction: bool) {\n\t\tlet mut transaction_started = false;\n\n\t\tfor (&layer, state) in &self.selected_shape_state {\n\t\t\tlet Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };\n\n\t\t\tfor point in &state.selected_points {\n\t\t\t\tif let ManipulatorPointId::Anchor(anchor) = point\n\t\t\t\t\t&& vector.all_connected(*anchor).all(|segment| state.is_segment_selected(segment.segment))\n\t\t\t\t{\n\t\t\t\t\tif !transaction_started && start_transaction {\n\t\t\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\t\t\t\t\t\ttransaction_started = true\n\t\t\t\t\t}\n\t\t\t\t\tlet modification_type = VectorModificationType::RemovePoint { id: *anchor };\n\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/// Note: this also adds a history transaction if there is some change in state.\n\tpub fn break_path_at_selected_point(&self, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {\n\t\tlet mut transaction_started = false;\n\n\t\tfor (&layer, state) in &self.selected_shape_state {\n\t\t\tlet Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };\n\n\t\t\tfor &delete in &state.selected_points {\n\t\t\t\tlet Some(point) = delete.get_anchor(&vector) else { continue };\n\t\t\t\tlet Some(pos) = vector.point_domain.position_from_id(point) else { continue };\n\n\t\t\t\tlet mut used_initial_point = false;\n\t\t\t\tfor handle in vector.all_connected(point) {\n\t\t\t\t\t// Disable the g1 continuous\n\t\t\t\t\tfor &handles in &vector.colinear_manipulators {\n\t\t\t\t\t\tif !transaction_started {\n\t\t\t\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\t\t\t\t\t\t\ttransaction_started = true;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif handles.contains(&handle) {\n\t\t\t\t\t\t\tlet modification_type = VectorModificationType::SetG1Continuous { handles, enabled: false };\n\t\t\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Keep the existing point for the first segment\n\t\t\t\t\tif !used_initial_point {\n\t\t\t\t\t\tused_initial_point = true;\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tif !transaction_started {\n\t\t\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\t\t\t\t\t\ttransaction_started = true;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Create new point\n\t\t\t\t\tlet id = PointId::generate();\n\t\t\t\t\tlet modification_type = VectorModificationType::InsertPoint { id, position: pos };\n\n\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\n\t\t\t\t\t// Update segment\n\t\t\t\t\tlet HandleId { ty, segment } = handle;\n\t\t\t\t\tlet modification_type = match ty {\n\t\t\t\t\t\tgraphene_std::vector::misc::HandleType::Primary => VectorModificationType::SetStartPoint { segment, id },\n\t\t\t\t\t\tgraphene_std::vector::misc::HandleType::End => VectorModificationType::SetEndPoint { segment, id },\n\t\t\t\t\t};\n\n\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/// Delete point(s) and adjacent segments.\n\t/// Note: this also adds a history transaction if there is some change in state, and true is returned if so.\n\tpub fn delete_point_and_break_path(&mut self, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) -> bool {\n\t\tlet mut transaction_started = false;\n\n\t\tfor (&layer, state) in &mut self.selected_shape_state {\n\t\t\tlet Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };\n\n\t\t\tfor delete in std::mem::take(&mut state.selected_points) {\n\t\t\t\tlet Some(point) = delete.get_anchor(&vector) else { continue };\n\n\t\t\t\tif !transaction_started {\n\t\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\t\t\t\t\ttransaction_started = true;\n\t\t\t\t}\n\t\t\t\t// Delete point\n\t\t\t\tlet modification_type = VectorModificationType::RemovePoint { id: point };\n\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\n\t\t\t\t// Delete connected segments\n\t\t\t\tfor HandleId { segment, .. } in vector.all_connected(point) {\n\t\t\t\t\tlet modification_type = VectorModificationType::RemoveSegment { id: segment };\n\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\ttransaction_started\n\t}\n\n\t/// Disable colinear handles colinear.\n\tpub fn disable_colinear_handles_state_on_selected(&self, network_interface: &NodeNetworkInterface, responses: &mut VecDeque<Message>) {\n\t\tfor (&layer, state) in &self.selected_shape_state {\n\t\t\tlet Some(vector) = network_interface.compute_modified_vector(layer) else { continue };\n\n\t\t\tfor &point in &state.selected_points {\n\t\t\t\tif let ManipulatorPointId::Anchor(point) = point {\n\t\t\t\t\tfor connected in vector.all_connected(point) {\n\t\t\t\t\t\tif let Some(&handles) = vector.colinear_manipulators.iter().find(|target| target.contains(&connected)) {\n\t\t\t\t\t\t\tlet modification_type = VectorModificationType::SetG1Continuous { handles, enabled: false };\n\t\t\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else if let Some(handle) = point.as_handle()\n\t\t\t\t\t&& let Some(handles) = vector.colinear_manipulators.iter().find(|handles| handles[0] == handle || handles[1] == handle)\n\t\t\t\t{\n\t\t\t\t\tlet modification_type = VectorModificationType::SetG1Continuous { handles: *handles, enabled: false };\n\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/// Find a [ManipulatorPoint] that is within the selection threshold and return the layer path, an index to the [ManipulatorGroup], and an enum index for [ManipulatorPoint].\n\tpub fn find_nearest_point_indices(&mut self, network_interface: &NodeNetworkInterface, mouse_position: DVec2, select_threshold: f64) -> Option<(LayerNodeIdentifier, ManipulatorPointId)> {\n\t\tif self.selected_shape_state.is_empty() {\n\t\t\treturn None;\n\t\t}\n\n\t\tlet select_threshold_squared = select_threshold * select_threshold;\n\n\t\t// Find the closest control point among all elements of shapes_to_modify\n\t\tfor &layer in self.selected_shape_state.keys() {\n\t\t\tif let Some((manipulator_point_id, distance_squared)) = Self::closest_point_in_layer(network_interface, layer, mouse_position) {\n\t\t\t\t// Choose the first point under the threshold\n\t\t\t\tif distance_squared < select_threshold_squared {\n\t\t\t\t\ttrace!(\"Selecting... manipulator point: {manipulator_point_id:?}\");\n\t\t\t\t\treturn Some((layer, manipulator_point_id));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tNone\n\t}\n\n\tpub fn find_nearest_visible_point_indices(\n\t\t&mut self,\n\t\tnetwork_interface: &NodeNetworkInterface,\n\t\tmouse_position: DVec2,\n\t\tselect_threshold: f64,\n\t\tpath_overlay_mode: PathOverlayMode,\n\t\tfrontier_handles_info: Option<&HashMap<LayerNodeIdentifier, HashMap<SegmentId, Vec<PointId>>>>,\n\t) -> Option<(LayerNodeIdentifier, ManipulatorPointId)> {\n\t\tif self.selected_shape_state.is_empty() {\n\t\t\treturn None;\n\t\t}\n\n\t\tlet select_threshold_squared = select_threshold.powi(2);\n\n\t\t// Find the closest control point among all elements of shapes_to_modify\n\t\tfor &layer in self.selected_shape_state.keys() {\n\t\t\tif let Some((manipulator_point_id, distance_squared)) = Self::closest_point_in_layer(network_interface, layer, mouse_position) {\n\t\t\t\t// Choose the first point under the threshold\n\t\t\t\tif distance_squared < select_threshold_squared {\n\t\t\t\t\t// Check if point is visible in current PathOverlayMode\n\t\t\t\t\tlet vector = network_interface.compute_modified_vector(layer)?;\n\t\t\t\t\tlet Some(state) = self.selected_shape_state.get(&layer) else { continue };\n\t\t\t\t\tlet selected_segments = selected_segments_for_layer(&vector, state);\n\t\t\t\t\tlet selected_points = self.selected_points().cloned().collect::<HashSet<_>>();\n\t\t\t\t\tlet frontier_handles_for_layer = frontier_handles_info.and_then(|handles_info| handles_info.get(&layer));\n\t\t\t\t\tif !is_visible_point(manipulator_point_id, &vector, path_overlay_mode, frontier_handles_for_layer, &selected_segments, &selected_points) {\n\t\t\t\t\t\treturn None;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn Some((layer, manipulator_point_id));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tNone\n\t}\n\n\t// TODO Use quadtree or some equivalent spatial acceleration structure to improve this to O(log(n))\n\t/// Find the closest manipulator, manipulator point, and distance so we can select path elements.\n\t/// Brute force comparison to determine which manipulator (handle or anchor) we want to select taking O(n) time.\n\t/// Return value is an `Option` of the tuple representing `(ManipulatorPointId, distance squared)`.\n\tfn closest_point_in_layer(network_interface: &NodeNetworkInterface, layer: LayerNodeIdentifier, pos: glam::DVec2) -> Option<(ManipulatorPointId, f64)> {\n\t\tlet mut closest_distance_squared: f64 = f64::MAX;\n\t\tlet mut manipulator_point = None;\n\n\t\tlet vector = network_interface.compute_modified_vector(layer)?;\n\t\tlet viewspace = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface);\n\n\t\t// Handles\n\t\tfor (segment_id, bezier, _, _) in vector.segment_bezier_iter() {\n\t\t\tlet bezier = bezier.apply_transformation(|point| viewspace.transform_point2(point));\n\t\t\tlet valid = |handle: DVec2, control: DVec2| handle.distance_squared(control) > crate::consts::HIDE_HANDLE_DISTANCE.powi(2);\n\n\t\t\tif let Some(primary_handle) = bezier.handle_start()\n\t\t\t\t&& valid(primary_handle, bezier.start)\n\t\t\t\t&& (bezier.handle_end().is_some() || valid(primary_handle, bezier.end))\n\t\t\t\t&& primary_handle.distance_squared(pos) <= closest_distance_squared\n\t\t\t{\n\t\t\t\tclosest_distance_squared = primary_handle.distance_squared(pos);\n\t\t\t\tmanipulator_point = Some(ManipulatorPointId::PrimaryHandle(segment_id));\n\t\t\t}\n\t\t\tif let Some(end_handle) = bezier.handle_end()\n\t\t\t\t&& valid(end_handle, bezier.end)\n\t\t\t\t&& end_handle.distance_squared(pos) <= closest_distance_squared\n\t\t\t{\n\t\t\t\tclosest_distance_squared = end_handle.distance_squared(pos);\n\t\t\t\tmanipulator_point = Some(ManipulatorPointId::EndHandle(segment_id));\n\t\t\t}\n\t\t}\n\n\t\t// Anchors\n\t\tfor (&id, &point) in vector.point_domain.ids().iter().zip(vector.point_domain.positions()) {\n\t\t\tlet point = viewspace.transform_point2(point);\n\n\t\t\tif point.distance_squared(pos) <= closest_distance_squared {\n\t\t\t\tclosest_distance_squared = point.distance_squared(pos);\n\t\t\t\tmanipulator_point = Some(ManipulatorPointId::Anchor(id));\n\t\t\t}\n\t\t}\n\n\t\tmanipulator_point.map(|id| (id, closest_distance_squared))\n\t}\n\n\t/// Find the `t` value along the path segment we have clicked upon, together with that segment ID.\n\tfn closest_segment(&self, network_interface: &NodeNetworkInterface, layer: LayerNodeIdentifier, position: glam::DVec2, tolerance: f64) -> Option<ClosestSegment> {\n\t\tlet transform = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface);\n\t\tlet layer_pos = transform.inverse().transform_point2(position);\n\n\t\tlet tolerance = tolerance + 0.5;\n\n\t\tlet mut closest = None;\n\t\tlet mut closest_distance_squared: f64 = tolerance * tolerance;\n\n\t\tlet vector = network_interface.compute_modified_vector(layer)?;\n\n\t\tfor (segment_id, mut segment, start, end) in vector.segment_iter() {\n\t\t\tlet t = segment.nearest(dvec2_to_point(layer_pos), DEFAULT_ACCURACY).t;\n\t\t\tlet layerspace = point_to_dvec2(segment.eval(t));\n\n\t\t\tlet screenspace = transform.transform_point2(layerspace);\n\t\t\tlet distance_squared = screenspace.distance_squared(position);\n\n\t\t\tif distance_squared < closest_distance_squared {\n\t\t\t\tclosest_distance_squared = distance_squared;\n\n\t\t\t\t// Convert to linear if handes are on top of control points\n\t\t\t\tlet PathSegPoints { p0: _, p1, p2, p3: _ } = pathseg_points(segment);\n\t\t\t\tif let (Some(p1), Some(p2)) = (p1, p2) {\n\t\t\t\t\tlet segment_points = pathseg_points(segment);\n\t\t\t\t\tif p1.abs_diff_eq(segment_points.p0, f64::EPSILON * 100.) && p2.abs_diff_eq(segment_points.p3, f64::EPSILON * 100.) {\n\t\t\t\t\t\tsegment = PathSeg::Line(Line::new(segment.start(), segment.end()));\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tlet primary_handle = vector.colinear_manipulators.iter().find(|handles| handles.contains(&HandleId::primary(segment_id)));\n\t\t\t\tlet end_handle = vector.colinear_manipulators.iter().find(|handles| handles.contains(&HandleId::end(segment_id)));\n\t\t\t\tlet primary_handle = primary_handle.and_then(|&handles| handles.into_iter().find(|handle| handle.segment != segment_id));\n\t\t\t\tlet end_handle = end_handle.and_then(|&handles| handles.into_iter().find(|handle| handle.segment != segment_id));\n\n\t\t\t\tclosest = Some(ClosestSegment {\n\t\t\t\t\tsegment: segment_id,\n\t\t\t\t\tbezier: segment,\n\t\t\t\t\tpoints: [start, end],\n\t\t\t\t\tcolinear: [primary_handle, end_handle],\n\t\t\t\t\tt,\n\t\t\t\t\tbezier_point_to_viewport: screenspace,\n\t\t\t\t\tlayer,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tclosest\n\t}\n\n\t/// find closest to the position segment on selected layers. If there is more than one layers with close enough segment it return upper from them\n\tpub fn upper_closest_segment(&self, network_interface: &NodeNetworkInterface, position: glam::DVec2, tolerance: f64) -> Option<ClosestSegment> {\n\t\tlet closest_seg = |layer| self.closest_segment(network_interface, layer, position, tolerance);\n\t\tmatch self.selected_shape_state.len() {\n\t\t\t0 => None,\n\t\t\t1 => self.selected_layers().next().copied().and_then(closest_seg),\n\t\t\t_ => self.sorted_selected_layers(network_interface.document_metadata()).find_map(closest_seg),\n\t\t}\n\t}\n\n\tpub fn get_dragging_state(&self, network_interface: &NodeNetworkInterface) -> PointSelectState {\n\t\tfor &layer in self.selected_shape_state.keys() {\n\t\t\tlet Some(vector) = network_interface.compute_modified_vector(layer) else { continue };\n\n\t\t\tfor point in self.selected_points() {\n\t\t\t\tif point.as_anchor().is_some() {\n\t\t\t\t\treturn PointSelectState::Anchor;\n\t\t\t\t}\n\t\t\t\tif point.get_handle_pair(&vector).is_some() {\n\t\t\t\t\treturn PointSelectState::HandleWithPair;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tPointSelectState::HandleNoPair\n\t}\n\n\t/// Returns true if at least one handle with pair is selected\n\tpub fn handle_with_pair_selected(&mut self, network_interface: &NodeNetworkInterface) -> bool {\n\t\tfor &layer in self.selected_shape_state.keys() {\n\t\t\tlet Some(vector) = network_interface.compute_modified_vector(layer) else { continue };\n\n\t\t\tfor point in self.selected_points() {\n\t\t\t\tif point.as_anchor().is_some() {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tif point.get_handle_pair(&vector).is_some() {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfalse\n\t}\n\n\t/// Alternate selected handles to mirrors\n\tpub fn alternate_selected_handles(&mut self, network_interface: &NodeNetworkInterface) {\n\t\tlet mut handles_to_update = Vec::new();\n\n\t\tfor &layer in self.selected_shape_state.keys() {\n\t\t\tlet Some(vector) = network_interface.compute_modified_vector(layer) else { continue };\n\n\t\t\tfor point in self.selected_points() {\n\t\t\t\tif point.as_anchor().is_some() {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif let Some(other_handles) = point.get_all_connected_handles(&vector) {\n\t\t\t\t\t// Find the next closest handle in the clockwise sense\n\t\t\t\t\tlet mut candidates = other_handles.clone();\n\t\t\t\t\tcandidates.sort_by(|&handle_a, &handle_b| {\n\t\t\t\t\t\tlet anchor = point.get_anchor_position(&vector).expect(\"No anchor position for handle\");\n\t\t\t\t\t\tlet orig_handle_pos = point.get_position(&vector).expect(\"No handle position\");\n\n\t\t\t\t\t\tlet a_pos = handle_a.to_manipulator_point().get_position(&vector).expect(\"No handle position\");\n\t\t\t\t\t\tlet b_pos = handle_b.to_manipulator_point().get_position(&vector).expect(\"No handle position\");\n\n\t\t\t\t\t\tlet v_orig = (orig_handle_pos - anchor).normalize_or_zero();\n\n\t\t\t\t\t\tlet v_a = (a_pos - anchor).normalize_or_zero();\n\t\t\t\t\t\tlet v_b = (b_pos - anchor).normalize_or_zero();\n\n\t\t\t\t\t\tlet signed_angle = |base: DVec2, to: DVec2| -> f64 {\n\t\t\t\t\t\t\tlet angle = base.angle_to(to);\n\t\t\t\t\t\t\tlet cross = base.perp_dot(to);\n\n\t\t\t\t\t\t\tif cross < 0. { TAU - angle } else { angle }\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tlet angle_a = signed_angle(v_orig, v_a);\n\t\t\t\t\t\tlet angle_b = signed_angle(v_orig, v_b);\n\n\t\t\t\t\t\tangle_a.partial_cmp(&angle_b).unwrap_or(std::cmp::Ordering::Equal)\n\t\t\t\t\t});\n\n\t\t\t\t\tif candidates.is_empty() {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\thandles_to_update.push((layer, *point, candidates[0].to_manipulator_point()));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor (layer, handle_to_deselect, handle_to_select) in handles_to_update {\n\t\t\tif let Some(state) = self.selected_shape_state.get_mut(&layer) {\n\t\t\t\tlet points = &state.selected_points;\n\t\t\t\tlet both_selected = points.contains(&handle_to_deselect) && points.contains(&handle_to_select);\n\t\t\t\tif both_selected {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tstate.deselect_point(handle_to_deselect);\n\t\t\t\tstate.select_point(handle_to_select);\n\t\t\t}\n\t\t}\n\t}\n\n\t/// Selects handles and anchor connected to current handle\n\tpub fn select_handles_and_anchor_connected_to_current_handle(&mut self, network_interface: &NodeNetworkInterface) {\n\t\tlet mut points_to_select: Vec<(LayerNodeIdentifier, Option<PointId>, Option<ManipulatorPointId>)> = Vec::new();\n\n\t\tfor &layer in self.selected_shape_state.keys() {\n\t\t\tlet Some(vector) = network_interface.compute_modified_vector(layer) else { continue };\n\n\t\t\tfor point in self.selected_points().filter(|point| point.as_handle().is_some()) {\n\t\t\t\tlet anchor = point.get_anchor(&vector);\n\t\t\t\tmatch point.get_handle_pair(&vector) {\n\t\t\t\t\tSome(handles) => {\n\t\t\t\t\t\tpoints_to_select.push((layer, anchor, Some(handles[1].to_manipulator_point())));\n\t\t\t\t\t}\n\t\t\t\t\t_ => {\n\t\t\t\t\t\tpoints_to_select.push((layer, anchor, None));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor (layer, anchor, handle) in points_to_select {\n\t\t\tif let Some(state) = self.selected_shape_state.get_mut(&layer) {\n\t\t\t\tif let Some(anchor) = anchor {\n\t\t\t\t\tstate.select_point(ManipulatorPointId::Anchor(anchor));\n\t\t\t\t}\n\t\t\t\tif let Some(handle) = handle {\n\t\t\t\t\tstate.select_point(handle);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn select_anchor_and_connected_handles(&mut self, network_interface: &NodeNetworkInterface) {\n\t\tlet mut non_empty_layers = self.selected_shape_state.iter_mut().filter(|(_, state)| !state.is_empty());\n\n\t\tlet Some((layer, state)) = non_empty_layers.next() else { return };\n\t\tif non_empty_layers.next().is_some() {\n\t\t\treturn;\n\t\t}\n\t\tlet Some(vector) = network_interface.compute_modified_vector(*layer) else { return };\n\n\t\t// Get the current point and its connected handles\n\t\tlet selected_points = state.selected_points.clone();\n\t\tif let Some(point) = selected_points.iter().next() {\n\t\t\tif let Some(anchor) = point.get_anchor(&vector) {\n\t\t\t\tstate.select_point(ManipulatorPointId::Anchor(anchor));\n\t\t\t}\n\t\t\tif let Some(handles) = point.get_handle_pair(&vector) {\n\t\t\t\tstate.select_point(handles[0].to_manipulator_point());\n\t\t\t\tstate.select_point(handles[1].to_manipulator_point());\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn select_points_by_layer_and_id(&mut self, points: &HashMap<LayerNodeIdentifier, Vec<ManipulatorPointId>>) {\n\t\tfor (layer, points) in points {\n\t\t\tif let Some(state) = self.selected_shape_state.get_mut(layer) {\n\t\t\t\tpoints.iter().for_each(|point| state.select_point(*point));\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn select_point_by_layer_and_id(&mut self, point: ManipulatorPointId, layer: LayerNodeIdentifier) {\n\t\tif let Some(state) = self.selected_shape_state.get_mut(&layer) {\n\t\t\tstate.select_point(point);\n\t\t}\n\t}\n\n\t/// Converts all selected anchor points' handles between sharp (zero-length handles) and smooth (pulled-apart colinear handle(s)).\n\t/// If both handles aren't zero-length, they are set to that. If both are zero-length, they are stretched apart by a reasonable amount.\n\t/// This can can be activated by double clicking on an anchor with the Path tool.\n\tpub fn flip_smooth_sharp(&self, network_interface: &NodeNetworkInterface, responses: &mut VecDeque<Message>) {\n\t\tlet mut process_layer = |layer: LayerNodeIdentifier, selected_points: &HashSet<ManipulatorPointId>| {\n\t\t\tlet vector = network_interface.compute_modified_vector(layer)?;\n\n\t\t\t// Check by comparing the handle positions to the anchor if this manipulator group is a point\n\t\t\tfor point in selected_points {\n\t\t\t\tlet Some(point_id) = point.as_anchor() else { continue };\n\t\t\t\tlet anchor = point.get_position(&vector)?;\n\t\t\t\tlet handles = vector.all_connected(point_id);\n\n\t\t\t\t// TODO: Check if this method of finding non-colinear is really required\n\t\t\t\tlet positions = handles\n\t\t\t\t\t.filter_map(|handle| handle.to_manipulator_point().get_position(&vector))\n\t\t\t\t\t.filter(|&handle| anchor.abs_diff_eq(handle, 1e-5))\n\t\t\t\t\t.count();\n\n\t\t\t\t// Check if the anchor is connected to linear segments.\n\t\t\t\tlet one_or_more_segment_linear = vector.connected_linear_segments(point_id) != 0;\n\n\t\t\t\tif positions != 0 || one_or_more_segment_linear {\n\t\t\t\t\tself.convert_manipulator_handles_to_colinear(&vector, point_id, responses, layer);\n\t\t\t\t} else {\n\t\t\t\t\tfor handle in vector.all_connected(point_id) {\n\t\t\t\t\t\tlet Some(bezier) = vector.segment_from_id(handle.segment) else { continue };\n\n\t\t\t\t\t\tmatch bezier.handles {\n\t\t\t\t\t\t\tBezierHandles::Linear => {}\n\t\t\t\t\t\t\tBezierHandles::Quadratic { .. } => {\n\t\t\t\t\t\t\t\tlet segment = handle.segment;\n\t\t\t\t\t\t\t\t// Convert to linear\n\t\t\t\t\t\t\t\tlet modification_type = VectorModificationType::SetHandles { segment, handles: [None; 2] };\n\t\t\t\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\n\t\t\t\t\t\t\t\t// Set the manipulator to have non-colinear handles\n\t\t\t\t\t\t\t\tfor &handles in &vector.colinear_manipulators {\n\t\t\t\t\t\t\t\t\tif handles.contains(&HandleId::primary(segment)) {\n\t\t\t\t\t\t\t\t\t\tlet modification_type = VectorModificationType::SetG1Continuous { handles, enabled: false };\n\t\t\t\t\t\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tBezierHandles::Cubic { .. } => {\n\t\t\t\t\t\t\t\t// Set handle position to anchor position\n\t\t\t\t\t\t\t\tlet modification_type = handle.set_relative_position(DVec2::ZERO);\n\t\t\t\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\n\t\t\t\t\t\t\t\t// Set the manipulator to have non-colinear handles\n\t\t\t\t\t\t\t\tfor &handles in &vector.colinear_manipulators {\n\t\t\t\t\t\t\t\t\tif handles.contains(&handle) {\n\t\t\t\t\t\t\t\t\t\tlet modification_type = VectorModificationType::SetG1Continuous { handles, enabled: false };\n\t\t\t\t\t\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tSome(true)\n\t\t};\n\n\t\tself.selected_shape_state.iter().for_each(|(layer, state)| {\n\t\t\tlet selected_points = &state.selected_points;\n\t\t\tprocess_layer(*layer, selected_points);\n\t\t});\n\t}\n\n\t#[allow(clippy::too_many_arguments)]\n\tpub fn select_all_in_shape(\n\t\t&mut self,\n\t\tnetwork_interface: &NodeNetworkInterface,\n\t\tselection_shape: SelectionShape,\n\t\tselection_change: SelectionChange,\n\t\tpath_overlay_mode: PathOverlayMode,\n\t\tfrontier_handles_info: Option<&HashMap<LayerNodeIdentifier, HashMap<SegmentId, Vec<PointId>>>>,\n\t\tselect_segments: bool,\n\t\tselect_points: bool,\n\t\t// Here, \"selection mode\" represents touched or enclosed, not to be confused with editing modes\n\t\tselection_mode: SelectionMode,\n\t) {\n\t\tlet (points_inside, segments_inside) = self.get_inside_points_and_segments(\n\t\t\tnetwork_interface,\n\t\t\tselection_shape,\n\t\t\tpath_overlay_mode,\n\t\t\tfrontier_handles_info,\n\t\t\tselect_segments,\n\t\t\tselect_points,\n\t\t\tselection_mode,\n\t\t);\n\n\t\tif selection_change == SelectionChange::Clear {\n\t\t\tself.deselect_all_points();\n\t\t\tself.deselect_all_segments();\n\t\t}\n\n\t\tfor (layer, points) in points_inside {\n\t\t\tlet Some(state) = self.selected_shape_state.get_mut(&layer) else { continue };\n\t\t\tlet Some(vector) = network_interface.compute_modified_vector(layer) else { continue };\n\n\t\t\tfor point in points {\n\t\t\t\tmatch (point, selection_change) {\n\t\t\t\t\t(_, SelectionChange::Shrink) => state.deselect_point(point),\n\t\t\t\t\t(ManipulatorPointId::EndHandle(_) | ManipulatorPointId::PrimaryHandle(_), _) => {\n\t\t\t\t\t\tlet handle = point.as_handle().expect(\"Handle cannot be converted\");\n\t\t\t\t\t\tif handle.length(&vector) > 0. {\n\t\t\t\t\t\t\tstate.select_point(point);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t(_, _) => state.select_point(point),\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor (layer, segments) in segments_inside {\n\t\t\tlet Some(state) = self.selected_shape_state.get_mut(&layer) else { continue };\n\t\t\tmatch selection_change {\n\t\t\t\tSelectionChange::Shrink => segments.iter().for_each(|segment| state.deselect_segment(*segment)),\n\t\t\t\t_ => segments.iter().for_each(|segment| state.select_segment(*segment)),\n\t\t\t}\n\n\t\t\t// Also select/deselect the endpoints of respective segments\n\t\t\tlet Some(vector) = network_interface.compute_modified_vector(layer) else { continue };\n\t\t\tif !select_points && select_segments {\n\t\t\t\tvector\n\t\t\t\t\t.segment_bezier_iter()\n\t\t\t\t\t.filter(|(segment, _, _, _)| segments.contains(segment))\n\t\t\t\t\t.for_each(|(_, _, start, end)| match selection_change {\n\t\t\t\t\t\tSelectionChange::Shrink => {\n\t\t\t\t\t\t\tstate.deselect_point(ManipulatorPointId::Anchor(start));\n\t\t\t\t\t\t\tstate.deselect_point(ManipulatorPointId::Anchor(end));\n\t\t\t\t\t\t}\n\t\t\t\t\t\t_ => {\n\t\t\t\t\t\t\tstate.select_point(ManipulatorPointId::Anchor(start));\n\t\t\t\t\t\t\tstate.select_point(ManipulatorPointId::Anchor(end));\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t#[allow(clippy::too_many_arguments)]\n\tpub fn get_inside_points_and_segments(\n\t\t&mut self,\n\t\tnetwork_interface: &NodeNetworkInterface,\n\t\tselection_shape: SelectionShape,\n\t\tpath_overlay_mode: PathOverlayMode,\n\t\tfrontier_handles_info: Option<&HashMap<LayerNodeIdentifier, HashMap<SegmentId, Vec<PointId>>>>,\n\t\tselect_segments: bool,\n\t\tselect_points: bool,\n\t\t// Represents if the box/lasso selection touches or encloses the targets (not to be confused with editing modes).\n\t\tselection_mode: SelectionMode,\n\t) -> (HashMap<LayerNodeIdentifier, HashSet<ManipulatorPointId>>, HashMap<LayerNodeIdentifier, HashSet<SegmentId>>) {\n\t\tlet selected_points = self.selected_points().cloned().collect::<HashSet<_>>();\n\n\t\tlet mut points_inside: HashMap<LayerNodeIdentifier, HashSet<ManipulatorPointId>> = HashMap::new();\n\t\tlet mut segments_inside: HashMap<LayerNodeIdentifier, HashSet<SegmentId>> = HashMap::new();\n\n\t\tfor &layer in self.selected_shape_state.keys() {\n\t\t\tlet vector = network_interface.compute_modified_vector(layer);\n\t\t\tlet Some(vector) = vector else { continue };\n\t\t\tlet transform = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface);\n\n\t\t\tassert_eq!(vector.segment_domain.ids().len(), vector.start_point().count());\n\t\t\tassert_eq!(vector.segment_domain.ids().len(), vector.end_point().count());\n\t\t\tfor start in vector.start_point() {\n\t\t\t\tassert!(vector.point_domain.ids().contains(&start));\n\t\t\t}\n\t\t\tfor end in vector.end_point() {\n\t\t\t\tassert!(vector.point_domain.ids().contains(&end));\n\t\t\t}\n\n\t\t\tlet polygon_subpath = if let SelectionShape::Lasso(polygon) = selection_shape {\n\t\t\t\tif polygon.len() < 2 {\n\t\t\t\t\treturn (points_inside, segments_inside);\n\t\t\t\t}\n\t\t\t\tlet polygon: Subpath<PointId> = Subpath::from_anchors(polygon.to_vec(), true);\n\t\t\t\tSome(polygon)\n\t\t\t} else {\n\t\t\t\tNone\n\t\t\t};\n\n\t\t\t// Selection segments\n\t\t\tfor (id, segment, _, _) in vector.segment_iter() {\n\t\t\t\tif select_segments {\n\t\t\t\t\t// Select segments if they lie inside the bounding box or lasso polygon\n\t\t\t\t\tlet transformed_segment = Affine::new(transform.to_cols_array()) * segment;\n\t\t\t\t\tlet segment_bbox = transformed_segment.bounding_box();\n\n\t\t\t\t\tlet select = match selection_shape {\n\t\t\t\t\t\tSelectionShape::Box(rect) => {\n\t\t\t\t\t\t\tlet enclosed = rect.contains_rect(segment_bbox);\n\t\t\t\t\t\t\tmatch selection_mode {\n\t\t\t\t\t\t\t\tSelectionMode::Enclosed => enclosed,\n\t\t\t\t\t\t\t\t_ => {\n\t\t\t\t\t\t\t\t\t// Check for intersection with the segment\n\t\t\t\t\t\t\t\t\tenclosed\n\t\t\t\t\t\t\t\t\t\t|| rect\n\t\t\t\t\t\t\t\t\t\t\t.path_segments(DEFAULT_ACCURACY)\n\t\t\t\t\t\t\t\t\t\t\t.map(|seg| seg.as_line().unwrap())\n\t\t\t\t\t\t\t\t\t\t\t.any(|line| !transformed_segment.intersect_line(line).is_empty())\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tSelectionShape::Lasso(_) => {\n\t\t\t\t\t\t\tlet polygon = polygon_subpath.as_ref().expect(\"If `selection_shape` is a polygon then subpath is constructed beforehand.\");\n\n\t\t\t\t\t\t\t// Sample 10 points on the bezier and check if all or some lie inside the polygon\n\t\t\t\t\t\t\tlet points = pathseg_compute_lookup_table(segment, Some(10), false);\n\t\t\t\t\t\t\tmatch selection_mode {\n\t\t\t\t\t\t\t\tSelectionMode::Enclosed => points.map(|p| transform.transform_point2(p)).all(|p| polygon.contains_point(p)),\n\t\t\t\t\t\t\t\t_ => points.map(|p| transform.transform_point2(p)).any(|p| polygon.contains_point(p)),\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\tif select {\n\t\t\t\t\t\tsegments_inside.entry(layer).or_default().insert(id);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tlet segment_points = pathseg_points(segment);\n\n\t\t\t\t// Selecting handles\n\t\t\t\tfor (position, id) in [(segment_points.p1, ManipulatorPointId::PrimaryHandle(id)), (segment_points.p2, ManipulatorPointId::EndHandle(id))] {\n\t\t\t\t\tlet Some(position) = position else { continue };\n\t\t\t\t\tlet transformed_position = transform.transform_point2(position);\n\n\t\t\t\t\tlet select = match selection_shape {\n\t\t\t\t\t\tSelectionShape::Box(rect) => rect.contains(dvec2_to_point(transformed_position)),\n\t\t\t\t\t\tSelectionShape::Lasso(_) => polygon_subpath\n\t\t\t\t\t\t\t.as_ref()\n\t\t\t\t\t\t\t.expect(\"If `selection_shape` is a polygon then subpath is constructed beforehand.\")\n\t\t\t\t\t\t\t.contains_point(transformed_position),\n\t\t\t\t\t};\n\n\t\t\t\t\tif select && select_points {\n\t\t\t\t\t\tlet frontier_handles_for_layer = frontier_handles_info.and_then(|frontier_handles| frontier_handles.get(&layer));\n\t\t\t\t\t\tlet state = self.selected_shape_state.get(&layer).expect(\"Cannot find state for layer\");\n\t\t\t\t\t\tlet selected_segments_for_layer = selected_segments_for_layer(&vector, state);\n\t\t\t\t\t\tlet is_visible_handle = is_visible_point(id, &vector, path_overlay_mode, frontier_handles_for_layer, &selected_segments_for_layer, &selected_points);\n\n\t\t\t\t\t\tif is_visible_handle {\n\t\t\t\t\t\t\tpoints_inside.entry(layer).or_default().insert(id);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Checking for selection of anchor points\n\t\t\tfor (&id, &position) in vector.point_domain.ids().iter().zip(vector.point_domain.positions()) {\n\t\t\t\tlet transformed_position = transform.transform_point2(position);\n\n\t\t\t\tlet select = match selection_shape {\n\t\t\t\t\tSelectionShape::Box(rect) => rect.contains(dvec2_to_point(transformed_position)),\n\t\t\t\t\tSelectionShape::Lasso(_) => polygon_subpath\n\t\t\t\t\t\t.as_ref()\n\t\t\t\t\t\t.expect(\"If `selection_shape` is a polygon then subpath is constructed beforehand.\")\n\t\t\t\t\t\t.contains_point(transformed_position),\n\t\t\t\t};\n\n\t\t\t\tif select && select_points {\n\t\t\t\t\tpoints_inside.entry(layer).or_default().insert(ManipulatorPointId::Anchor(id));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t(points_inside, segments_inside)\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/shapes/arc_shape.rs",
    "content": "use super::shape_utility::ShapeToolModifierKey;\nuse super::*;\nuse crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;\nuse crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_proto_node_type;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate};\nuse crate::messages::tool::common_functionality::gizmos::shape_gizmos::circle_arc_radius_handle::{RadiusHandle, RadiusHandleState};\nuse crate::messages::tool::common_functionality::gizmos::shape_gizmos::sweep_angle_gizmo::{SweepAngleGizmo, SweepAngleGizmoState};\nuse crate::messages::tool::common_functionality::graph_modification_utils;\nuse crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeGizmoHandler, arc_outline};\nuse crate::messages::tool::tool_messages::tool_prelude::*;\nuse glam::DAffine2;\nuse graph_craft::document::NodeInput;\nuse graph_craft::document::value::TaggedValue;\nuse graphene_std::vector::misc::ArcType;\nuse std::collections::VecDeque;\n\n#[derive(Clone, Debug, Default)]\npub struct ArcGizmoHandler {\n\tsweep_angle_gizmo: SweepAngleGizmo,\n\tarc_radius_handle: RadiusHandle,\n}\n\nimpl ArcGizmoHandler {\n\tpub fn new() -> Self {\n\t\tSelf { ..Default::default() }\n\t}\n}\n\nimpl ShapeGizmoHandler for ArcGizmoHandler {\n\tfn handle_state(&mut self, selected_shape_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {\n\t\tself.sweep_angle_gizmo.handle_actions(selected_shape_layer, document, mouse_position);\n\t\tself.arc_radius_handle.handle_actions(selected_shape_layer, document, mouse_position, responses);\n\t}\n\n\tfn is_any_gizmo_hovered(&self) -> bool {\n\t\tself.sweep_angle_gizmo.hovered() || self.arc_radius_handle.hovered()\n\t}\n\n\tfn handle_click(&mut self) {\n\t\t// If hovering over both the gizmos give priority to sweep angle gizmo\n\t\tif self.sweep_angle_gizmo.hovered() && self.arc_radius_handle.hovered() {\n\t\t\tself.sweep_angle_gizmo.update_state(SweepAngleGizmoState::Dragging);\n\t\t\tself.arc_radius_handle.update_state(RadiusHandleState::Inactive);\n\t\t\treturn;\n\t\t}\n\n\t\tif self.sweep_angle_gizmo.hovered() {\n\t\t\tself.sweep_angle_gizmo.update_state(SweepAngleGizmoState::Dragging);\n\t\t}\n\n\t\tif self.arc_radius_handle.hovered() {\n\t\t\tself.arc_radius_handle.update_state(RadiusHandleState::Dragging);\n\t\t}\n\t}\n\n\tfn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {\n\t\tif self.sweep_angle_gizmo.is_dragging_or_snapped() {\n\t\t\tself.sweep_angle_gizmo.update_arc(document, input, responses);\n\t\t}\n\n\t\tif self.arc_radius_handle.is_dragging() {\n\t\t\tself.arc_radius_handle.update_inner_radius(document, input, responses, drag_start);\n\t\t}\n\t}\n\n\tfn dragging_overlays(\n\t\t&self,\n\t\tdocument: &DocumentMessageHandler,\n\t\tinput: &InputPreprocessorMessageHandler,\n\t\t_shape_editor: &mut &mut crate::messages::tool::common_functionality::shape_editor::ShapeState,\n\t\tmouse_position: DVec2,\n\t\toverlay_context: &mut crate::messages::portfolio::document::overlays::utility_types::OverlayContext,\n\t) {\n\t\tif self.sweep_angle_gizmo.is_dragging_or_snapped() {\n\t\t\tself.sweep_angle_gizmo.overlays(None, document, input, mouse_position, overlay_context);\n\t\t\tarc_outline(self.sweep_angle_gizmo.layer, document, overlay_context);\n\t\t}\n\n\t\tif self.arc_radius_handle.is_dragging() {\n\t\t\tself.sweep_angle_gizmo.overlays(self.arc_radius_handle.layer, document, input, mouse_position, overlay_context);\n\t\t\tself.arc_radius_handle.overlays(document, overlay_context);\n\t\t}\n\t}\n\n\tfn overlays(\n\t\t&self,\n\t\tdocument: &DocumentMessageHandler,\n\t\tselected_shape_layer: Option<LayerNodeIdentifier>,\n\t\tinput: &InputPreprocessorMessageHandler,\n\t\t_shape_editor: &mut &mut crate::messages::tool::common_functionality::shape_editor::ShapeState,\n\t\tmouse_position: DVec2,\n\t\toverlay_context: &mut crate::messages::portfolio::document::overlays::utility_types::OverlayContext,\n\t) {\n\t\t// If hovering over both the gizmos give priority to sweep angle gizmo\n\t\tif self.sweep_angle_gizmo.hovered() && self.arc_radius_handle.hovered() {\n\t\t\tself.sweep_angle_gizmo.overlays(selected_shape_layer, document, input, mouse_position, overlay_context);\n\t\t\treturn;\n\t\t}\n\n\t\tif self.arc_radius_handle.hovered() {\n\t\t\tlet layer = self.arc_radius_handle.layer;\n\n\t\t\tself.arc_radius_handle.overlays(document, overlay_context);\n\t\t\tself.sweep_angle_gizmo.overlays(layer, document, input, mouse_position, overlay_context);\n\t\t}\n\n\t\tself.sweep_angle_gizmo.overlays(selected_shape_layer, document, input, mouse_position, overlay_context);\n\t\tself.arc_radius_handle.overlays(document, overlay_context);\n\n\t\tarc_outline(selected_shape_layer.or(self.sweep_angle_gizmo.layer), document, overlay_context);\n\t}\n\n\tfn mouse_cursor_icon(&self) -> Option<MouseCursorIcon> {\n\t\tif self.sweep_angle_gizmo.hovered() || self.sweep_angle_gizmo.is_dragging_or_snapped() {\n\t\t\treturn Some(MouseCursorIcon::Default);\n\t\t}\n\n\t\tif self.arc_radius_handle.hovered() || self.arc_radius_handle.is_dragging() {\n\t\t\treturn Some(MouseCursorIcon::EWResize);\n\t\t}\n\n\t\tNone\n\t}\n\n\tfn cleanup(&mut self) {\n\t\tself.sweep_angle_gizmo.cleanup();\n\t\tself.arc_radius_handle.cleanup();\n\t}\n}\n#[derive(Default)]\npub struct Arc;\n\nimpl Arc {\n\tpub fn create_node(arc_type: ArcType) -> NodeTemplate {\n\t\tlet node_type = resolve_proto_node_type(graphene_std::vector::generator_nodes::arc::IDENTIFIER).expect(\"Ellipse node does not exist\");\n\t\tnode_type.node_template_input_override([\n\t\t\tNone,\n\t\t\tSome(NodeInput::value(TaggedValue::F64(0.5), false)),\n\t\t\tSome(NodeInput::value(TaggedValue::F64(0.), false)),\n\t\t\tSome(NodeInput::value(TaggedValue::F64(270.), false)),\n\t\t\tSome(NodeInput::value(TaggedValue::ArcType(arc_type), false)),\n\t\t])\n\t}\n\n\tpub fn update_shape(\n\t\tdocument: &DocumentMessageHandler,\n\t\tipp: &InputPreprocessorMessageHandler,\n\t\tviewport: &ViewportMessageHandler,\n\t\tlayer: LayerNodeIdentifier,\n\t\tshape_tool_data: &mut ShapeToolData,\n\t\tmodifier: ShapeToolModifierKey,\n\t\tresponses: &mut VecDeque<Message>,\n\t) {\n\t\tlet (center, lock_ratio) = (modifier[0], modifier[1]);\n\t\tif let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, viewport, center, lock_ratio) {\n\t\t\tlet Some(node_id) = graph_modification_utils::get_arc_id(layer, &document.network_interface) else {\n\t\t\t\treturn;\n\t\t\t};\n\n\t\t\tlet dimensions = (start - end).abs();\n\t\t\tlet mut scale = DVec2::ONE;\n\t\t\tlet radius: f64;\n\n\t\t\t// We keep the smaller dimension's scale at 1 and scale the other dimension accordingly\n\t\t\tif dimensions.x > dimensions.y {\n\t\t\t\tscale.x = dimensions.x / dimensions.y;\n\t\t\t\tradius = dimensions.y / 2.;\n\t\t\t} else {\n\t\t\t\tscale.y = dimensions.y / dimensions.x;\n\t\t\t\tradius = dimensions.x / 2.;\n\t\t\t}\n\n\t\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\t\tinput_connector: InputConnector::node(node_id, 1),\n\t\t\t\tinput: NodeInput::value(TaggedValue::F64(radius), false),\n\t\t\t});\n\n\t\t\tresponses.add(GraphOperationMessage::TransformSet {\n\t\t\t\tlayer,\n\t\t\t\ttransform: DAffine2::from_scale_angle_translation(scale, 0., start.midpoint(end)),\n\t\t\t\ttransform_in: TransformIn::Viewport,\n\t\t\t\tskip_rerender: false,\n\t\t\t});\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/shapes/arrow_shape.rs",
    "content": "use super::line_shape::{LineEnd, generate_line};\nuse super::shape_utility::ShapeToolModifierKey;\nuse super::*;\nuse crate::consts::BOUNDS_SELECT_THRESHOLD;\nuse crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;\nuse crate::messages::portfolio::document::node_graph::document_node_definitions::{DefinitionIdentifier, resolve_document_node_type};\nuse crate::messages::portfolio::document::overlays::utility_types::OverlayContext;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate};\nuse crate::messages::prelude::*;\nuse crate::messages::tool::common_functionality::graph_modification_utils;\npub use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer;\nuse crate::messages::tool::common_functionality::snapping::SnapData;\nuse glam::{DAffine2, DVec2};\nuse graph_craft::document::NodeInput;\nuse graph_craft::document::value::TaggedValue;\nuse std::collections::VecDeque;\n\n#[derive(Default)]\npub struct Arrow;\n\nimpl Arrow {\n\tpub fn create_node(shaft_width: f64, head_width: f64, head_length: f64) -> NodeTemplate {\n\t\tlet identifier = DefinitionIdentifier::ProtoNode(graphene_std::vector_nodes::arrow::IDENTIFIER);\n\t\tlet node_type = resolve_document_node_type(&identifier).expect(\"Arrow node can't be found\");\n\t\tnode_type.node_template_input_override([\n\t\t\tNone,\n\t\t\tSome(NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false)), // arrow_to\n\t\t\tSome(NodeInput::value(TaggedValue::F64(shaft_width), false)),   // shaft_width\n\t\t\tSome(NodeInput::value(TaggedValue::F64(head_width), false)),    // head_width\n\t\t\tSome(NodeInput::value(TaggedValue::F64(head_length), false)),   // head_length\n\t\t])\n\t}\n\n\tpub fn update_shape(\n\t\tdocument: &DocumentMessageHandler,\n\t\tinput: &InputPreprocessorMessageHandler,\n\t\tviewport: &ViewportMessageHandler,\n\t\tlayer: LayerNodeIdentifier,\n\t\ttool_data: &mut ShapeToolData,\n\t\tmodifier: ShapeToolModifierKey,\n\t\tresponses: &mut VecDeque<Message>,\n\t) {\n\t\tlet [center, snap_angle, lock_angle] = modifier;\n\n\t\ttool_data.line_data.drag_current = input.mouse.position;\n\n\t\tlet keyboard = &input.keyboard;\n\t\tlet ignore = [layer];\n\t\tlet snap_data = SnapData::ignore(document, input, viewport, &ignore);\n\t\tlet mut document_points = generate_line(tool_data, snap_data, keyboard.key(lock_angle), keyboard.key(snap_angle), keyboard.key(center));\n\n\t\tif tool_data.line_data.dragging_endpoint == Some(LineEnd::Start) {\n\t\t\tdocument_points.swap(0, 1);\n\t\t}\n\n\t\tlet arrow_to = document_points[1] - document_points[0];\n\n\t\tif arrow_to.length() < 1e-6 {\n\t\t\treturn;\n\t\t}\n\n\t\tlet Some(node_id) = graph_modification_utils::get_arrow_id(layer, &document.network_interface) else {\n\t\t\treturn;\n\t\t};\n\n\t\tlet document_to_viewport = document.metadata().document_to_viewport;\n\n\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\tinput_connector: InputConnector::node(node_id, 1),\n\t\t\tinput: NodeInput::value(TaggedValue::DVec2(arrow_to), false),\n\t\t});\n\t\tlet downstream = document.metadata().downstream_transform_to_viewport(layer);\n\t\tlet scope = downstream.inverse() * document_to_viewport;\n\t\tresponses.add(GraphOperationMessage::TransformSet {\n\t\t\tlayer,\n\t\t\ttransform: DAffine2::from_translation(document_points[0]),\n\t\t\ttransform_in: TransformIn::Scope { scope },\n\t\t\tskip_rerender: false,\n\t\t});\n\n\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t}\n\n\tpub fn overlays(document: &DocumentMessageHandler, shape_tool_data: &mut ShapeToolData, mouse_position: DVec2, overlay_context: &mut OverlayContext) {\n\t\tlet arrow_layers: HashMap<LayerNodeIdentifier, [DVec2; 2]> = document\n\t\t\t.network_interface\n\t\t\t.selected_nodes()\n\t\t\t.selected_visible_and_unlocked_layers(&document.network_interface)\n\t\t\t.filter_map(|layer| {\n\t\t\t\tlet node_inputs = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector_nodes::arrow::IDENTIFIER))?;\n\t\t\t\tlet Some(&TaggedValue::DVec2(arrow_to)) = node_inputs[1].as_value() else { return None };\n\n\t\t\t\tlet transform = document.metadata().transform_to_viewport(layer);\n\t\t\t\tlet viewport_start = transform.transform_point2(DVec2::ZERO);\n\t\t\t\tlet viewport_end = transform.transform_point2(arrow_to);\n\n\t\t\t\tif !arrow_to.abs_diff_eq(DVec2::ZERO, f64::EPSILON * 1000.) {\n\t\t\t\t\tlet is_editing = shape_tool_data.line_data.editing_layer == Some(layer);\n\t\t\t\t\tfor (i, pos) in [viewport_start, viewport_end].into_iter().enumerate() {\n\t\t\t\t\t\tlet is_dragged = is_editing && matches!((i, &shape_tool_data.line_data.dragging_endpoint), (0, Some(LineEnd::Start)) | (1, Some(LineEnd::End)));\n\t\t\t\t\t\tif is_dragged || (pos - mouse_position).length_squared() < BOUNDS_SELECT_THRESHOLD.powi(2) {\n\t\t\t\t\t\t\toverlay_context.hover_manipulator_anchor(pos, is_dragged);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\toverlay_context.square(pos, Some(6.), None, None);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tSome((layer, [DVec2::ZERO, arrow_to]))\n\t\t\t})\n\t\t\t.collect();\n\n\t\tshape_tool_data.line_data.selected_layers_with_position.extend(arrow_layers);\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/shapes/circle_shape.rs",
    "content": "use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;\nuse crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_proto_node_type;\nuse crate::messages::portfolio::document::overlays::utility_types::OverlayContext;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate};\nuse crate::messages::tool::common_functionality::gizmos::shape_gizmos::circle_arc_radius_handle::{RadiusHandle, RadiusHandleState};\nuse crate::messages::tool::common_functionality::graph_modification_utils;\nuse crate::messages::tool::common_functionality::shape_editor::ShapeState;\nuse crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeGizmoHandler, ShapeToolModifierKey};\nuse crate::messages::tool::tool_messages::shape_tool::ShapeToolData;\nuse crate::messages::tool::tool_messages::tool_prelude::*;\nuse glam::DAffine2;\nuse graph_craft::document::NodeInput;\nuse graph_craft::document::value::TaggedValue;\n\n#[derive(Clone, Debug, Default)]\npub struct CircleGizmoHandler {\n\tcircle_radius_handle: RadiusHandle,\n}\n\nimpl ShapeGizmoHandler for CircleGizmoHandler {\n\tfn is_any_gizmo_hovered(&self) -> bool {\n\t\tself.circle_radius_handle.hovered()\n\t}\n\n\tfn handle_state(&mut self, selected_circle_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {\n\t\tself.circle_radius_handle.handle_actions(selected_circle_layer, document, mouse_position, responses);\n\t}\n\n\tfn handle_click(&mut self) {\n\t\tif self.circle_radius_handle.hovered() {\n\t\t\tself.circle_radius_handle.update_state(RadiusHandleState::Dragging);\n\t\t}\n\t}\n\n\tfn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {\n\t\tif self.circle_radius_handle.is_dragging() {\n\t\t\tself.circle_radius_handle.update_inner_radius(document, input, responses, drag_start);\n\t\t}\n\t}\n\n\tfn overlays(\n\t\t&self,\n\t\tdocument: &DocumentMessageHandler,\n\t\t_selected_circle_layer: Option<LayerNodeIdentifier>,\n\t\t_input: &InputPreprocessorMessageHandler,\n\t\t_shape_editor: &mut &mut ShapeState,\n\t\t_mouse_position: DVec2,\n\t\toverlay_context: &mut OverlayContext,\n\t) {\n\t\tself.circle_radius_handle.overlays(document, overlay_context);\n\t}\n\n\tfn dragging_overlays(\n\t\t&self,\n\t\tdocument: &DocumentMessageHandler,\n\t\t_input: &InputPreprocessorMessageHandler,\n\t\t_shape_editor: &mut &mut ShapeState,\n\t\t_mouse_position: DVec2,\n\t\toverlay_context: &mut OverlayContext,\n\t) {\n\t\tif self.circle_radius_handle.is_dragging() {\n\t\t\tself.circle_radius_handle.overlays(document, overlay_context);\n\t\t}\n\t}\n\n\tfn cleanup(&mut self) {\n\t\tself.circle_radius_handle.cleanup();\n\t}\n\n\tfn mouse_cursor_icon(&self) -> Option<MouseCursorIcon> {\n\t\tif self.circle_radius_handle.hovered() || self.circle_radius_handle.is_dragging() {\n\t\t\treturn Some(MouseCursorIcon::EWResize);\n\t\t}\n\n\t\tNone\n\t}\n}\n\n#[derive(Default)]\npub struct Circle;\n\nimpl Circle {\n\tpub fn create_node() -> NodeTemplate {\n\t\tlet node_type = resolve_proto_node_type(graphene_std::vector::generator_nodes::circle::IDENTIFIER).expect(\"Circle can't be found\");\n\t\tnode_type.node_template_input_override([None, Some(NodeInput::value(TaggedValue::F64(0.), false))])\n\t}\n\n\tpub fn update_shape(\n\t\tdocument: &DocumentMessageHandler,\n\t\tipp: &InputPreprocessorMessageHandler,\n\t\tviewport: &ViewportMessageHandler,\n\t\tlayer: LayerNodeIdentifier,\n\t\tshape_tool_data: &mut ShapeToolData,\n\t\tmodifier: ShapeToolModifierKey,\n\t\tresponses: &mut VecDeque<Message>,\n\t) {\n\t\tlet center = modifier[0];\n\t\tlet [start, end] = shape_tool_data.data.calculate_circle_points(document, ipp, viewport, center);\n\t\tlet Some(node_id) = graph_modification_utils::get_circle_id(layer, &document.network_interface) else {\n\t\t\treturn;\n\t\t};\n\n\t\tlet dimensions = (start - end).abs();\n\n\t\t// We keep the smaller dimension's scale at 1 and scale the other dimension accordingly\n\t\tlet radius: f64 = if dimensions.x > dimensions.y { dimensions.y / 2. } else { dimensions.x / 2. };\n\n\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\tinput_connector: InputConnector::node(node_id, 1),\n\t\t\tinput: NodeInput::value(TaggedValue::F64(radius), false),\n\t\t});\n\n\t\tresponses.add(GraphOperationMessage::TransformSet {\n\t\t\tlayer,\n\t\t\ttransform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., start.midpoint(end)),\n\t\t\ttransform_in: TransformIn::Viewport,\n\t\t\tskip_rerender: false,\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/shapes/ellipse_shape.rs",
    "content": "use super::shape_utility::ShapeToolModifierKey;\nuse super::*;\nuse crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;\nuse crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_proto_node_type;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate};\nuse crate::messages::tool::common_functionality::graph_modification_utils;\nuse crate::messages::tool::tool_messages::tool_prelude::*;\nuse glam::DAffine2;\nuse graph_craft::document::NodeInput;\nuse graph_craft::document::value::TaggedValue;\nuse std::collections::VecDeque;\n\n#[derive(Default)]\npub struct Ellipse;\n\nimpl Ellipse {\n\tpub fn create_node() -> NodeTemplate {\n\t\tlet node_type = resolve_proto_node_type(graphene_std::vector::generator_nodes::ellipse::IDENTIFIER).expect(\"Ellipse node can't be found\");\n\t\tnode_type.node_template_input_override([None, Some(NodeInput::value(TaggedValue::F64(0.5), false)), Some(NodeInput::value(TaggedValue::F64(0.5), false))])\n\t}\n\n\tpub fn update_shape(\n\t\tdocument: &DocumentMessageHandler,\n\t\tipp: &InputPreprocessorMessageHandler,\n\t\tviewport: &ViewportMessageHandler,\n\t\tlayer: LayerNodeIdentifier,\n\t\tshape_tool_data: &mut ShapeToolData,\n\t\tmodifier: ShapeToolModifierKey,\n\t\tresponses: &mut VecDeque<Message>,\n\t) {\n\t\tlet [center, lock_ratio, _] = modifier;\n\n\t\tif let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, viewport, center, lock_ratio) {\n\t\t\tlet Some(node_id) = graph_modification_utils::get_ellipse_id(layer, &document.network_interface) else {\n\t\t\t\treturn;\n\t\t\t};\n\n\t\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\t\tinput_connector: InputConnector::node(node_id, 1),\n\t\t\t\tinput: NodeInput::value(TaggedValue::F64(((start.x - end.x) / 2.).abs()), false),\n\t\t\t});\n\t\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\t\tinput_connector: InputConnector::node(node_id, 2),\n\t\t\t\tinput: NodeInput::value(TaggedValue::F64(((start.y - end.y) / 2.).abs()), false),\n\t\t\t});\n\t\t\tresponses.add(GraphOperationMessage::TransformSet {\n\t\t\t\tlayer,\n\t\t\t\ttransform: DAffine2::from_translation(start.midpoint(end)),\n\t\t\t\ttransform_in: TransformIn::Viewport,\n\t\t\t\tskip_rerender: false,\n\t\t\t});\n\t\t}\n\t}\n}\n\n#[cfg(test)]\nmod test_ellipse {\n\tpub use crate::test_utils::test_prelude::*;\n\tuse glam::DAffine2;\n\tuse graphene_std::vector::generator_nodes::ellipse;\n\n\t#[derive(Debug, PartialEq)]\n\tstruct ResolvedEllipse {\n\t\tradius_x: f64,\n\t\tradius_y: f64,\n\t\ttransform: DAffine2,\n\t}\n\n\tasync fn get_ellipse(editor: &mut EditorTestUtils) -> Vec<ResolvedEllipse> {\n\t\tlet instrumented = match editor.eval_graph().await {\n\t\t\tOk(instrumented) => instrumented,\n\t\t\tErr(e) => panic!(\"Failed to evaluate graph: {e}\"),\n\t\t};\n\n\t\tlet document = editor.active_document();\n\t\tlet layers = document.metadata().all_layers();\n\t\tlayers\n\t\t\t.filter_map(|layer| {\n\t\t\t\tlet node_graph_layer = NodeGraphLayer::new(layer, &document.network_interface);\n\t\t\t\tlet ellipse_node = node_graph_layer.upstream_node_id_from_protonode(ellipse::IDENTIFIER)?;\n\t\t\t\tSome(ResolvedEllipse {\n\t\t\t\t\tradius_x: instrumented.grab_protonode_input::<ellipse::RadiusXInput>(&vec![ellipse_node], &editor.runtime).unwrap(),\n\t\t\t\t\tradius_y: instrumented.grab_protonode_input::<ellipse::RadiusYInput>(&vec![ellipse_node], &editor.runtime).unwrap(),\n\t\t\t\t\ttransform: document.metadata().transform_to_document(layer),\n\t\t\t\t})\n\t\t\t})\n\t\t\t.collect()\n\t}\n\n\t#[tokio::test]\n\tasync fn ellipse_draw_simple() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor.drag_tool(ToolType::Ellipse, 10., 10., 19., 0., ModifierKeys::empty()).await;\n\n\t\tassert_eq!(editor.active_document().metadata().all_layers().count(), 1);\n\n\t\tlet ellipse = get_ellipse(&mut editor).await;\n\t\tassert_eq!(ellipse.len(), 1);\n\t\tassert_eq!(\n\t\t\tellipse[0],\n\t\t\tResolvedEllipse {\n\t\t\t\tradius_x: 4.5,\n\t\t\t\tradius_y: 5.,\n\t\t\t\ttransform: DAffine2::from_translation(DVec2::new(14.5, 5.)) // Uses center\n\t\t\t}\n\t\t);\n\t}\n\n\t#[tokio::test]\n\tasync fn ellipse_draw_circle() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor.drag_tool(ToolType::Ellipse, 10., 10., -10., 11., ModifierKeys::SHIFT).await;\n\n\t\tlet ellipse = get_ellipse(&mut editor).await;\n\t\tassert_eq!(ellipse.len(), 1);\n\t\tassert_eq!(\n\t\t\tellipse[0],\n\t\t\tResolvedEllipse {\n\t\t\t\tradius_x: 10.,\n\t\t\t\tradius_y: 10.,\n\t\t\t\ttransform: DAffine2::from_translation(DVec2::new(0., 20.)) // Uses center\n\t\t\t}\n\t\t);\n\t}\n\n\t#[tokio::test]\n\tasync fn ellipse_draw_square_rotated() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor\n\t\t\t.handle_message(NavigationMessage::CanvasTiltSet {\n\t\t\t\t// 45 degree rotation of content clockwise\n\t\t\t\tangle_radians: f64::consts::FRAC_PI_4,\n\t\t\t})\n\t\t\t.await;\n\t\teditor.drag_tool(ToolType::Ellipse, 0., 0., 1., 10., ModifierKeys::SHIFT).await; // Viewport coordinates\n\n\t\tlet ellipse = get_ellipse(&mut editor).await;\n\t\tassert_eq!(ellipse.len(), 1);\n\t\tprintln!(\"{ellipse:?}\");\n\t\tassert_eq!(ellipse[0].radius_x, 5.);\n\t\tassert_eq!(ellipse[0].radius_y, 5.);\n\n\t\tassert!(\n\t\t\tellipse[0]\n\t\t\t\t.transform\n\t\t\t\t.abs_diff_eq(DAffine2::from_angle_translation(-f64::consts::FRAC_PI_4, DVec2::X * f64::consts::FRAC_1_SQRT_2 * 10.), 0.001)\n\t\t);\n\t}\n\n\t#[tokio::test]\n\tasync fn ellipse_draw_center_square_rotated() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor\n\t\t\t.handle_message(NavigationMessage::CanvasTiltSet {\n\t\t\t\t// 45 degree rotation of content clockwise\n\t\t\t\tangle_radians: f64::consts::FRAC_PI_4,\n\t\t\t})\n\t\t\t.await;\n\t\teditor.drag_tool(ToolType::Ellipse, 0., 0., 1., 10., ModifierKeys::SHIFT | ModifierKeys::ALT).await; // Viewport coordinates\n\n\t\tlet ellipse = get_ellipse(&mut editor).await;\n\t\tassert_eq!(ellipse.len(), 1);\n\t\tassert_eq!(ellipse[0].radius_x, 10.);\n\t\tassert_eq!(ellipse[0].radius_y, 10.);\n\t\tassert!(ellipse[0].transform.abs_diff_eq(DAffine2::from_angle(-f64::consts::FRAC_PI_4), 0.001));\n\t}\n\n\t#[tokio::test]\n\tasync fn ellipse_cancel() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor.drag_tool_cancel_rmb(ToolType::Ellipse).await;\n\n\t\tlet ellipse = get_ellipse(&mut editor).await;\n\t\tassert_eq!(ellipse.len(), 0);\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/shapes/grid_shape.rs",
    "content": "use super::shape_utility::ShapeToolModifierKey;\nuse super::*;\nuse crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;\nuse crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_proto_node_type;\nuse crate::messages::portfolio::document::overlays::utility_types::OverlayContext;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate};\nuse crate::messages::tool::common_functionality::gizmos::shape_gizmos::grid_rows_columns_gizmo::{RowColumnGizmo, RowColumnGizmoState};\nuse crate::messages::tool::common_functionality::graph_modification_utils;\nuse crate::messages::tool::common_functionality::shape_editor::ShapeState;\nuse crate::messages::tool::common_functionality::shapes::shape_utility::ShapeGizmoHandler;\nuse crate::messages::tool::tool_messages::tool_prelude::*;\nuse glam::DAffine2;\nuse graph_craft::document::NodeInput;\nuse graph_craft::document::value::TaggedValue;\nuse graphene_std::NodeInputDecleration;\nuse graphene_std::vector::misc::GridType;\nuse std::collections::VecDeque;\n\n#[derive(Clone, Debug, Default)]\npub struct GridGizmoHandler {\n\trow_column_gizmo: RowColumnGizmo,\n}\n\nimpl ShapeGizmoHandler for GridGizmoHandler {\n\tfn is_any_gizmo_hovered(&self) -> bool {\n\t\tself.row_column_gizmo.is_hovered()\n\t}\n\n\tfn handle_state(&mut self, selected_grid_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, _responses: &mut VecDeque<Message>) {\n\t\tself.row_column_gizmo.handle_actions(selected_grid_layer, mouse_position, document);\n\t}\n\n\tfn handle_click(&mut self) {\n\t\tif self.row_column_gizmo.is_hovered() {\n\t\t\tself.row_column_gizmo.update_state(RowColumnGizmoState::Dragging);\n\t\t}\n\t}\n\n\tfn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {\n\t\tif self.row_column_gizmo.is_dragging() {\n\t\t\tself.row_column_gizmo.update(document, input, responses, drag_start);\n\t\t}\n\t}\n\n\tfn overlays(\n\t\t&self,\n\t\tdocument: &DocumentMessageHandler,\n\t\tselected_grid_layer: Option<LayerNodeIdentifier>,\n\t\t_input: &InputPreprocessorMessageHandler,\n\t\tshape_editor: &mut &mut ShapeState,\n\t\tmouse_position: DVec2,\n\t\toverlay_context: &mut OverlayContext,\n\t) {\n\t\tself.row_column_gizmo.overlays(document, selected_grid_layer, shape_editor, mouse_position, overlay_context);\n\t}\n\n\tfn dragging_overlays(\n\t\t&self,\n\t\tdocument: &DocumentMessageHandler,\n\t\t_input: &InputPreprocessorMessageHandler,\n\t\tshape_editor: &mut &mut ShapeState,\n\t\tmouse_position: DVec2,\n\t\toverlay_context: &mut OverlayContext,\n\t) {\n\t\tif self.row_column_gizmo.is_dragging() {\n\t\t\tself.row_column_gizmo.overlays(document, None, shape_editor, mouse_position, overlay_context);\n\t\t}\n\t}\n\n\tfn cleanup(&mut self) {\n\t\tself.row_column_gizmo.cleanup();\n\t}\n\n\tfn mouse_cursor_icon(&self) -> Option<MouseCursorIcon> {\n\t\tif self.row_column_gizmo.is_hovered() || self.row_column_gizmo.is_dragging() {\n\t\t\treturn Some(self.row_column_gizmo.gizmo_type.mouse_icon());\n\t\t}\n\n\t\tNone\n\t}\n}\n\n#[derive(Default)]\npub struct Grid;\n\nimpl Grid {\n\tpub fn create_node(grid_type: GridType) -> NodeTemplate {\n\t\tlet node_type = resolve_proto_node_type(graphene_std::vector::generator_nodes::grid::IDENTIFIER).expect(\"Grid can't be found\");\n\t\tnode_type.node_template_input_override([\n\t\t\tNone,\n\t\t\tSome(NodeInput::value(TaggedValue::GridType(grid_type), false)),\n\t\t\tSome(NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false)),\n\t\t])\n\t}\n\n\tpub fn update_shape(\n\t\tdocument: &DocumentMessageHandler,\n\t\tipp: &InputPreprocessorMessageHandler,\n\t\tlayer: LayerNodeIdentifier,\n\t\tgrid_type: GridType,\n\t\tshape_tool_data: &mut ShapeToolData,\n\t\tmodifier: ShapeToolModifierKey,\n\t\tresponses: &mut VecDeque<Message>,\n\t) {\n\t\tuse graphene_std::vector::generator_nodes::grid::*;\n\n\t\tlet [center, lock_ratio, _] = modifier;\n\t\tlet is_isometric = grid_type == GridType::Isometric;\n\n\t\tlet Some(node_id) = graph_modification_utils::get_grid_id(layer, &document.network_interface) else {\n\t\t\treturn;\n\t\t};\n\n\t\tlet start = shape_tool_data.data.viewport_drag_start(document);\n\t\tlet end = ipp.mouse.position;\n\n\t\tlet (translation, dimensions, angle) = calculate_grid_params(start, end, is_isometric, ipp.keyboard.key(center), ipp.keyboard.key(lock_ratio));\n\n\t\t// Set dimensions/spacing\n\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\tinput_connector: InputConnector::node(node_id, SpacingInput::<f64>::INDEX),\n\t\t\tinput: NodeInput::value(TaggedValue::DVec2(dimensions), false),\n\t\t});\n\n\t\t// Set angle for isometric grids\n\t\tif let Some(angle_deg) = angle {\n\t\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\t\tinput_connector: InputConnector::node(node_id, AnglesInput::INDEX),\n\t\t\t\tinput: NodeInput::value(TaggedValue::DVec2(DVec2::splat(angle_deg)), false),\n\t\t\t});\n\t\t}\n\n\t\t// Set transform\n\t\tresponses.add(GraphOperationMessage::TransformSet {\n\t\t\tlayer,\n\t\t\ttransform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., translation),\n\t\t\ttransform_in: TransformIn::Viewport,\n\t\t\tskip_rerender: false,\n\t\t});\n\t}\n}\n\nfn calculate_grid_params(start: DVec2, end: DVec2, is_isometric: bool, center: bool, lock_ratio: bool) -> (DVec2, DVec2, Option<f64>) {\n\tlet raw_dimensions = (start - end).abs();\n\tlet mouse_delta = end - start;\n\tlet dimensions;\n\tlet mut translation = start;\n\tlet mut angle = None;\n\n\tmatch (center, lock_ratio) {\n\t\t// Both center and lock_ratio: centered + square/fixed-angle grid\n\t\t(true, true) => {\n\t\t\tif is_isometric {\n\t\t\t\t// Fix angle at 30° - standardized isometric view\n\t\t\t\tangle = Some(30.);\n\n\t\t\t\t// Calculate the width based on given height and angle 30°\n\t\t\t\tlet width = calculate_isometric_x_position(raw_dimensions.y / 9., 30_f64.to_radians(), 30_f64.to_radians()).abs();\n\n\t\t\t\t// To make draw from center: shift x by half of width and y by half of height (mouse_delta.y)\n\t\t\t\ttranslation -= DVec2::new(width / 2., mouse_delta.y / 2.);\n\t\t\t\tdimensions = DVec2::splat(raw_dimensions.y) / 9.;\n\n\t\t\t\t// Adjust for negative upward drag - compensate for coordinate system\n\t\t\t\tif end.y < start.y {\n\t\t\t\t\ttranslation -= DVec2::new(0., start.y - end.y);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// We want to make both dimensions the same so we choose whichever is bigger and shift to make center\n\t\t\t\tlet max = raw_dimensions.x.max(raw_dimensions.y);\n\t\t\t\tlet distance_to_center = max;\n\t\t\t\ttranslation = start - distance_to_center;\n\t\t\t\tdimensions = 2. * DVec2::splat(max) / 9.; // 2x because centering halves the effective area\n\t\t\t}\n\t\t}\n\n\t\t// Only center: centered grid with free aspect ratio\n\t\t(true, false) => {\n\t\t\tif is_isometric {\n\t\t\t\t// Calculate angle from mouse movement - dynamic angle based on drag direction\n\t\t\t\tangle = Some((raw_dimensions.y / (mouse_delta.x * 2.)).atan().to_degrees());\n\n\t\t\t\t// To make draw from center: shift by half of mouse movement\n\t\t\t\ttranslation -= mouse_delta / 2.;\n\t\t\t\tdimensions = DVec2::splat(raw_dimensions.y) / 9.;\n\n\t\t\t\t// Adjust for upward drag - maintain proper grid positioning\n\t\t\t\tif end.y < start.y {\n\t\t\t\t\ttranslation -= DVec2::new(0., start.y - end.y);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Logic: Rectangular centered grid using exact drag proportions\n\t\t\t\tlet distance_to_center = raw_dimensions;\n\t\t\t\ttranslation = start - distance_to_center;\n\t\t\t\tdimensions = 2. * raw_dimensions / 9.; // 2x for centering\n\t\t\t}\n\t\t}\n\n\t\t// Only lock_ratio: square/fixed-angle grid from drag start point\n\t\t(false, true) => {\n\t\t\tlet max: f64;\n\t\t\tif is_isometric {\n\t\t\t\tdimensions = DVec2::splat(raw_dimensions.y) / 9.;\n\n\t\t\t\t// Use 30° for angle - consistent isometric standard\n\t\t\t\tangle = Some(30.);\n\t\t\t\tmax = raw_dimensions.y;\n\t\t\t} else {\n\t\t\t\t// Logic: Force square grid by using larger dimension\n\t\t\t\tmax = raw_dimensions.x.max(raw_dimensions.y);\n\t\t\t\tdimensions = DVec2::splat(max) / 9.;\n\t\t\t}\n\n\t\t\t// Adjust for negative drag directions - maintain grid at intended position\n\t\t\tif end.y < start.y {\n\t\t\t\ttranslation -= DVec2::new(0., max);\n\t\t\t}\n\t\t\tif end.x < start.x {\n\t\t\t\ttranslation -= DVec2::new(max, 0.);\n\t\t\t}\n\t\t}\n\n\t\t// Neither center nor lock_ratio: free-form grid following exact user input\n\t\t(false, false) => {\n\t\t\tif is_isometric {\n\t\t\t\t// Calculate angle from mouse movement - fully dynamic\n\t\t\t\t// Logic: angle represents user's exact intended perspective\n\t\t\t\tangle = Some((raw_dimensions.y / (mouse_delta.x * 2.)).atan().to_degrees());\n\t\t\t\tdimensions = DVec2::splat(raw_dimensions.y) / 9.;\n\t\t\t} else {\n\t\t\t\t// Use exact drag dimensions for grid spacing - what you drag is what you get\n\t\t\t\t// Logic: Direct mapping of user gesture to grid parameters\n\t\t\t\tdimensions = raw_dimensions / 9.;\n\n\t\t\t\t// Adjust for leftward drag - keep grid positioned correctly\n\t\t\t\tif end.x < start.x {\n\t\t\t\t\ttranslation -= DVec2::new(start.x - end.x, 0.);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Adjust for upward drag (common to both grid types)\n\t\t\t// Logic: compensate for coordinate system where Y increases downward\n\t\t\tif end.y < start.y {\n\t\t\t\ttranslation -= DVec2::new(0., start.y - end.y);\n\t\t\t}\n\t\t}\n\t}\n\n\t(translation, dimensions, angle)\n}\n\nfn calculate_isometric_x_position(y_spacing: f64, rad_a: f64, rad_b: f64) -> f64 {\n\tlet spacing_x = y_spacing / (rad_a.tan() + rad_b.tan());\n\tspacing_x * 9.\n}\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/shapes/line_shape.rs",
    "content": "use super::shape_utility::ShapeToolModifierKey;\nuse crate::consts::{BOUNDS_SELECT_THRESHOLD, LINE_ROTATE_SNAP_ANGLE};\nuse crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;\nuse crate::messages::portfolio::document::node_graph::document_node_definitions::{DefinitionIdentifier, resolve_document_node_type};\nuse crate::messages::portfolio::document::overlays::utility_types::OverlayContext;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate};\nuse crate::messages::tool::common_functionality::graph_modification_utils;\npub use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer;\nuse crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapTypeConfiguration};\nuse crate::messages::tool::tool_messages::shape_tool::ShapeToolData;\nuse crate::messages::tool::tool_messages::tool_prelude::*;\nuse glam::{DAffine2, DVec2};\nuse graph_craft::document::NodeInput;\nuse graph_craft::document::value::TaggedValue;\nuse std::collections::VecDeque;\n\n#[derive(Clone, PartialEq, Debug, Default)]\npub enum LineEnd {\n\t#[default]\n\tStart,\n\tEnd,\n}\n\n#[derive(Clone, Debug, Default)]\npub struct LineToolData {\n\tpub drag_start: DVec2,\n\tpub drag_current: DVec2,\n\tpub angle: f64,\n\tpub weight: f64,\n\tpub selected_layers_with_position: HashMap<LayerNodeIdentifier, [DVec2; 2]>,\n\tpub editing_layer: Option<LayerNodeIdentifier>,\n\tpub dragging_endpoint: Option<LineEnd>,\n}\n\n#[derive(Default)]\npub struct Line;\n\nimpl Line {\n\tpub fn create_node() -> NodeTemplate {\n\t\tlet identifier = DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::line::IDENTIFIER);\n\t\tlet node_type = resolve_document_node_type(&identifier).expect(\"Line node can't be found\");\n\t\tnode_type.node_template_input_override([None, Some(NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false))])\n\t}\n\n\tpub fn update_shape(\n\t\tdocument: &DocumentMessageHandler,\n\t\tipp: &InputPreprocessorMessageHandler,\n\t\tviewport: &ViewportMessageHandler,\n\t\tlayer: LayerNodeIdentifier,\n\t\tshape_tool_data: &mut ShapeToolData,\n\t\tmodifier: ShapeToolModifierKey,\n\t\tresponses: &mut VecDeque<Message>,\n\t) {\n\t\tlet [center, snap_angle, lock_angle] = modifier;\n\n\t\tshape_tool_data.line_data.drag_current = ipp.mouse.position;\n\n\t\tlet keyboard = &ipp.keyboard;\n\t\tlet ignore = [layer];\n\t\tlet snap_data = SnapData::ignore(document, ipp, viewport, &ignore);\n\t\tlet mut document_points = generate_line(shape_tool_data, snap_data, keyboard.key(lock_angle), keyboard.key(snap_angle), keyboard.key(center));\n\n\t\tif shape_tool_data.line_data.dragging_endpoint == Some(LineEnd::Start) {\n\t\t\tdocument_points.swap(0, 1);\n\t\t}\n\n\t\tlet Some(node_id) = graph_modification_utils::get_line_id(layer, &document.network_interface) else {\n\t\t\treturn;\n\t\t};\n\n\t\t// Compute line_to in document space\n\t\tlet line_to = document_points[1] - document_points[0];\n\n\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\tinput_connector: InputConnector::node(node_id, 1),\n\t\t\tinput: NodeInput::value(TaggedValue::DVec2(line_to), false),\n\t\t});\n\t\tlet document_to_viewport = document.metadata().document_to_viewport;\n\t\tlet downstream = document.metadata().downstream_transform_to_viewport(layer);\n\t\tlet scope = downstream.inverse() * document_to_viewport;\n\t\tresponses.add(GraphOperationMessage::TransformSet {\n\t\t\tlayer,\n\t\t\ttransform: DAffine2::from_translation(document_points[0]),\n\t\t\ttransform_in: TransformIn::Scope { scope },\n\t\t\tskip_rerender: false,\n\t\t});\n\t}\n\n\tpub fn overlays(document: &DocumentMessageHandler, shape_tool_data: &mut ShapeToolData, mouse_position: DVec2, overlay_context: &mut OverlayContext) {\n\t\tshape_tool_data.line_data.selected_layers_with_position = document\n\t\t\t.network_interface\n\t\t\t.selected_nodes()\n\t\t\t.selected_visible_and_unlocked_layers(&document.network_interface)\n\t\t\t.filter_map(|layer| {\n\t\t\t\tlet node_inputs =\n\t\t\t\t\tNodeGraphLayer::new(layer, &document.network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::line::IDENTIFIER))?;\n\n\t\t\t\tlet Some(&TaggedValue::DVec2(line_to)) = node_inputs[1].as_value() else {\n\t\t\t\t\treturn None;\n\t\t\t\t};\n\n\t\t\t\t// Line goes from local origin (0,0) to line_to, positioned by the Transform node\n\t\t\t\tlet transform = document.metadata().transform_to_viewport(layer);\n\t\t\t\tlet viewport_start = transform.transform_point2(DVec2::ZERO);\n\t\t\t\tlet viewport_end = transform.transform_point2(line_to);\n\t\t\t\tif !line_to.abs_diff_eq(DVec2::ZERO, f64::EPSILON * 1000.) {\n\t\t\t\t\toverlay_context.line(viewport_start, viewport_end, None, None);\n\t\t\t\t\tlet is_editing = shape_tool_data.line_data.editing_layer == Some(layer);\n\t\t\t\t\tfor (i, pos) in [viewport_start, viewport_end].into_iter().enumerate() {\n\t\t\t\t\t\tlet is_dragged = is_editing && matches!((i, &shape_tool_data.line_data.dragging_endpoint), (0, Some(LineEnd::Start)) | (1, Some(LineEnd::End)));\n\t\t\t\t\t\tif is_dragged || (pos - mouse_position).length_squared() < BOUNDS_SELECT_THRESHOLD.powi(2) {\n\t\t\t\t\t\t\toverlay_context.hover_manipulator_anchor(pos, is_dragged);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\toverlay_context.square(pos, Some(6.), None, None);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Store local-space positions for endpoint editing\n\t\t\t\tSome((layer, [DVec2::ZERO, line_to]))\n\t\t\t})\n\t\t\t.collect::<HashMap<LayerNodeIdentifier, [DVec2; 2]>>();\n\t}\n}\n\npub fn generate_line(tool_data: &mut ShapeToolData, snap_data: SnapData, lock_angle: bool, snap_angle: bool, center: bool) -> [DVec2; 2] {\n\tlet document_to_viewport = snap_data.document.metadata().document_to_viewport;\n\tlet mut document_points = [tool_data.data.drag_start, document_to_viewport.inverse().transform_point2(tool_data.line_data.drag_current)];\n\n\tlet mut angle = -(document_points[1] - document_points[0]).angle_to(DVec2::X);\n\tlet mut line_length = (document_points[1] - document_points[0]).length();\n\n\tif lock_angle {\n\t\tangle = tool_data.line_data.angle;\n\t} else if snap_angle {\n\t\tlet snap_resolution = LINE_ROTATE_SNAP_ANGLE.to_radians();\n\t\tangle = (angle / snap_resolution).round() * snap_resolution;\n\t}\n\n\ttool_data.line_data.angle = angle;\n\n\tif lock_angle {\n\t\tlet angle_vec = DVec2::new(angle.cos(), angle.sin());\n\t\tline_length = (document_points[1] - document_points[0]).dot(angle_vec);\n\t}\n\n\tdocument_points[1] = document_points[0] + line_length * DVec2::new(angle.cos(), angle.sin());\n\n\tlet constrained = snap_angle || lock_angle;\n\tlet snap = &mut tool_data.data.snap_manager;\n\n\tlet near_point = SnapCandidatePoint::handle_neighbors(document_points[1], [tool_data.data.drag_start]);\n\tlet far_point = SnapCandidatePoint::handle_neighbors(2. * document_points[0] - document_points[1], [tool_data.data.drag_start]);\n\tlet config = SnapTypeConfiguration::default();\n\n\tif constrained {\n\t\tlet constraint = SnapConstraint::Line {\n\t\t\torigin: document_points[0],\n\t\t\tdirection: document_points[1] - document_points[0],\n\t\t};\n\t\tif center {\n\t\t\tlet snapped = snap.constrained_snap(&snap_data, &near_point, constraint, config);\n\t\t\tlet snapped_far = snap.constrained_snap(&snap_data, &far_point, constraint, config);\n\t\t\tlet best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far };\n\t\t\tdocument_points[1] = document_points[0] * 2. - best.snapped_point_document;\n\t\t\tdocument_points[0] = best.snapped_point_document;\n\t\t\tsnap.update_indicator(best);\n\t\t} else {\n\t\t\tlet snapped = snap.constrained_snap(&snap_data, &near_point, constraint, config);\n\t\t\tdocument_points[1] = snapped.snapped_point_document;\n\t\t\tsnap.update_indicator(snapped);\n\t\t}\n\t} else if center {\n\t\tlet snapped = snap.free_snap(&snap_data, &near_point, config);\n\t\tlet snapped_far = snap.free_snap(&snap_data, &far_point, config);\n\t\tlet best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far };\n\t\tdocument_points[1] = document_points[0] * 2. - best.snapped_point_document;\n\t\tdocument_points[0] = best.snapped_point_document;\n\t\tsnap.update_indicator(best);\n\t} else {\n\t\tlet snapped = snap.free_snap(&snap_data, &near_point, config);\n\t\tdocument_points[1] = snapped.snapped_point_document;\n\t\tsnap.update_indicator(snapped);\n\t}\n\n\tdocument_points\n}\n\n#[cfg(test)]\nmod test_line_tool {\n\tuse crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;\n\tuse crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer;\n\tuse crate::test_utils::test_prelude::*;\n\tuse glam::DAffine2;\n\tuse graph_craft::document::value::TaggedValue;\n\n\t/// Get the line's document-space start and end points by reading line_to from the node\n\t/// and computing the actual positions via the layer's transform.\n\tasync fn get_line_endpoints_document(editor: &mut EditorTestUtils) -> Option<(DVec2, DVec2)> {\n\t\tlet document = editor.active_document();\n\t\tlet network_interface = &document.network_interface;\n\n\t\tnetwork_interface\n\t\t\t.selected_nodes()\n\t\t\t.selected_visible_and_unlocked_layers(network_interface)\n\t\t\t.filter_map(|layer| {\n\t\t\t\tlet node_inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::line::IDENTIFIER))?;\n\t\t\t\tlet Some(&TaggedValue::DVec2(line_to)) = node_inputs[1].as_value() else {\n\t\t\t\t\treturn None;\n\t\t\t\t};\n\n\t\t\t\tlet transform_to_doc = document.metadata().transform_to_document(layer);\n\t\t\t\tlet doc_start = transform_to_doc.transform_point2(DVec2::ZERO);\n\t\t\t\tlet doc_end = transform_to_doc.transform_point2(line_to);\n\t\t\t\tSome((doc_start, doc_end))\n\t\t\t})\n\t\t\t.next()\n\t}\n\n\t#[tokio::test]\n\tasync fn test_line_tool_basicdraw() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor.drag_tool(ToolType::Line, 0., 0., 100., 100., ModifierKeys::empty()).await;\n\t\tif let Some((start, end)) = get_line_endpoints_document(&mut editor).await {\n\t\t\tassert!((start - DVec2::ZERO).length() < 1., \"Start point should be near (0,0)\");\n\t\t\tassert!((end - DVec2::new(100., 100.)).length() < 1., \"End point should be near (100,100)\");\n\t\t}\n\t}\n\n\t#[tokio::test]\n\tasync fn test_line_tool_with_transformed_viewport() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor.handle_message(NavigationMessage::CanvasZoomSet { zoom_factor: 2. }).await;\n\t\teditor.handle_message(NavigationMessage::CanvasPan { delta: DVec2::new(100., 50.) }).await;\n\t\teditor.handle_message(NavigationMessage::CanvasTiltSet { angle_radians: 30_f64.to_radians() }).await;\n\t\teditor.drag_tool(ToolType::Line, 0., 0., 100., 100., ModifierKeys::empty()).await;\n\t\tif let Some((start, end)) = get_line_endpoints_document(&mut editor).await {\n\t\t\tlet document = editor.active_document();\n\t\t\tlet document_to_viewport = document.metadata().document_to_viewport;\n\t\t\tlet viewport_to_document = document_to_viewport.inverse();\n\n\t\t\tlet expected_start = viewport_to_document.transform_point2(DVec2::ZERO);\n\t\t\tlet expected_end = viewport_to_document.transform_point2(DVec2::new(100., 100.));\n\n\t\t\tassert!(\n\t\t\t\t(start - expected_start).length() < 1.,\n\t\t\t\t\"Start point should match expected document coordinates. Got {start:?}, expected {expected_start:?}\"\n\t\t\t);\n\t\t\tassert!(\n\t\t\t\t(end - expected_end).length() < 1.,\n\t\t\t\t\"End point should match expected document coordinates. Got {end:?}, expected {expected_end:?}\"\n\t\t\t);\n\t\t} else {\n\t\t\tpanic!(\"Line was not created successfully with transformed viewport\");\n\t\t}\n\t}\n\n\t#[tokio::test]\n\tasync fn test_line_tool_ctrl_anglelock() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor.drag_tool(ToolType::Line, 0., 0., 100., 100., ModifierKeys::CONTROL).await;\n\t\tif let Some((start, end)) = get_line_endpoints_document(&mut editor).await {\n\t\t\tlet line_vec = end - start;\n\t\t\tlet original_angle = line_vec.angle_to(DVec2::X);\n\t\t\teditor.drag_tool(ToolType::Line, 0., 0., 200., 50., ModifierKeys::CONTROL).await;\n\t\t\tif let Some((updated_start, updated_end)) = get_line_endpoints_document(&mut editor).await {\n\t\t\t\tlet updated_line_vec = updated_end - updated_start;\n\t\t\t\tlet updated_angle = updated_line_vec.angle_to(DVec2::X);\n\t\t\t\tprint!(\"{original_angle:?}\");\n\t\t\t\tprint!(\"{updated_angle:?}\");\n\t\t\t\tassert!(\n\t\t\t\t\tline_vec.normalize().dot(updated_line_vec.normalize()).abs() - 1. < 1e-6,\n\t\t\t\t\t\"Line angle should be locked when Ctrl is kept pressed\"\n\t\t\t\t);\n\t\t\t\tassert!((updated_start - updated_end).length() > 1., \"Line should be able to change length when Ctrl is kept pressed\");\n\t\t\t}\n\t\t}\n\t}\n\n\t#[tokio::test]\n\tasync fn test_line_tool_alt() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor.drag_tool(ToolType::Line, 100., 100., 200., 100., ModifierKeys::ALT).await;\n\t\tif let Some((start, end)) = get_line_endpoints_document(&mut editor).await {\n\t\t\tlet expected_start = DVec2::new(0., 100.);\n\t\t\tlet expected_end = DVec2::new(200., 100.);\n\t\t\tassert!((start - expected_start).length() < 1., \"Start point should be near (0, 100)\");\n\t\t\tassert!((end - expected_end).length() < 1., \"End point should be near (200, 100)\");\n\t\t}\n\t}\n\n\t#[tokio::test]\n\tasync fn test_line_tool_alt_shift_drag() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor.drag_tool(ToolType::Line, 100., 100., 150., 120., ModifierKeys::ALT | ModifierKeys::SHIFT).await;\n\t\tif let Some((start, end)) = get_line_endpoints_document(&mut editor).await {\n\t\t\tlet line_vec = end - start;\n\t\t\tlet angle_radians = line_vec.angle_to(DVec2::X);\n\t\t\tlet angle_degrees = angle_radians.to_degrees();\n\t\t\tlet nearest_angle = (angle_degrees / 15.).round() * 15.;\n\n\t\t\tassert!((angle_degrees - nearest_angle).abs() < 1., \"Angle should snap to the nearest 15 degrees\");\n\t\t}\n\t}\n\n\t#[tokio::test]\n\tasync fn test_line_tool_with_transformed_artboard() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor.drag_tool(ToolType::Artboard, 0., 0., 200., 200., ModifierKeys::empty()).await;\n\n\t\tlet artboard_id = editor.get_selected_layer().await.expect(\"Should have selected the artboard\");\n\n\t\teditor\n\t\t\t.handle_message(GraphOperationMessage::TransformChange {\n\t\t\t\tlayer: artboard_id,\n\t\t\t\ttransform: DAffine2::from_angle(45_f64.to_radians()),\n\t\t\t\ttransform_in: TransformIn::Local,\n\t\t\t\tskip_rerender: false,\n\t\t\t})\n\t\t\t.await;\n\n\t\teditor.drag_tool(ToolType::Line, 50., 50., 150., 150., ModifierKeys::empty()).await;\n\n\t\tlet (start, end) = get_line_endpoints_document(&mut editor).await.expect(\"Line was not created successfully within transformed artboard\");\n\t\t// The line should still be diagonal with equal change in x and y\n\t\tlet line_vector = end - start;\n\t\t// Verifying the line is approximately 100*sqrt(2) units in length (diagonal of 100x100 square)\n\t\tlet line_length = line_vector.length();\n\t\tassert!(\n\t\t\t(line_length - 141.42).abs() < 1., // 100 * sqrt(2) ~= 141.42\n\t\t\t\"Line length should be approximately 141.42 units. Got: {line_length}\"\n\t\t);\n\t\tassert!((line_vector.x - 100.).abs() < 1., \"X-component of line vector should be approximately 100. Got: {}\", line_vector.x);\n\t\tassert!(\n\t\t\t(line_vector.y.abs() - 100.).abs() < 1.,\n\t\t\t\"Absolute Y-component of line vector should be approximately 100. Got: {}\",\n\t\t\tline_vector.y.abs()\n\t\t);\n\t\tlet angle_degrees = line_vector.angle_to(DVec2::X).to_degrees();\n\t\tassert!((angle_degrees - (-45.)).abs() < 1., \"Line angle should be close to -45 degrees. Got: {angle_degrees}\");\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/shapes/mod.rs",
    "content": "pub mod arc_shape;\npub mod arrow_shape;\npub mod circle_shape;\npub mod ellipse_shape;\npub mod grid_shape;\npub mod line_shape;\npub mod polygon_shape;\npub mod rectangle_shape;\npub mod shape_utility;\npub mod spiral_shape;\npub mod star_shape;\n\npub use super::shapes::arrow_shape::Arrow;\npub use super::shapes::ellipse_shape::Ellipse;\npub use super::shapes::line_shape::{Line, LineEnd};\npub use super::shapes::rectangle_shape::Rectangle;\npub use crate::messages::tool::tool_messages::shape_tool::ShapeToolData;\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/shapes/polygon_shape.rs",
    "content": "use super::shape_utility::{ShapeToolModifierKey, update_radius_sign};\nuse super::*;\nuse crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;\nuse crate::messages::portfolio::document::node_graph::document_node_definitions::{DefinitionIdentifier, resolve_document_node_type};\nuse crate::messages::portfolio::document::overlays::utility_types::OverlayContext;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate};\nuse crate::messages::tool::common_functionality::gizmos::shape_gizmos::number_of_points_dial::{NumberOfPointsDial, NumberOfPointsDialState};\nuse crate::messages::tool::common_functionality::gizmos::shape_gizmos::point_radius_handle::{PointRadiusHandle, PointRadiusHandleState};\nuse crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer};\nuse crate::messages::tool::common_functionality::shape_editor::ShapeState;\nuse crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeGizmoHandler, polygon_outline};\nuse crate::messages::tool::tool_messages::shape_tool::ShapeOptionsUpdate;\nuse crate::messages::tool::tool_messages::tool_prelude::*;\nuse glam::DAffine2;\nuse graph_craft::document::NodeInput;\nuse graph_craft::document::value::TaggedValue;\nuse std::collections::VecDeque;\n\n#[derive(Clone, Debug, Default)]\npub struct PolygonGizmoHandler {\n\tnumber_of_points_dial: NumberOfPointsDial,\n\tpoint_radius_handle: PointRadiusHandle,\n}\n\nimpl ShapeGizmoHandler for PolygonGizmoHandler {\n\tfn is_any_gizmo_hovered(&self) -> bool {\n\t\tself.number_of_points_dial.is_hovering() || self.point_radius_handle.hovered()\n\t}\n\n\tfn handle_state(&mut self, selected_star_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {\n\t\tself.number_of_points_dial.handle_actions(selected_star_layer, mouse_position, document, responses);\n\t\tself.point_radius_handle.handle_actions(selected_star_layer, document, mouse_position, responses);\n\t}\n\n\tfn handle_click(&mut self) {\n\t\tif self.number_of_points_dial.is_hovering() {\n\t\t\tself.number_of_points_dial.update_state(NumberOfPointsDialState::Dragging);\n\t\t\treturn;\n\t\t}\n\n\t\tif self.point_radius_handle.hovered() {\n\t\t\tself.point_radius_handle.update_state(PointRadiusHandleState::Dragging);\n\t\t}\n\t}\n\n\tfn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {\n\t\tif self.number_of_points_dial.is_dragging() {\n\t\t\tself.number_of_points_dial.update_number_of_sides(document, input, responses, drag_start);\n\t\t}\n\n\t\tif self.point_radius_handle.is_dragging_or_snapped() {\n\t\t\tself.point_radius_handle.update_inner_radius(document, input, responses, drag_start);\n\t\t}\n\t}\n\n\tfn overlays(\n\t\t&self,\n\t\tdocument: &DocumentMessageHandler,\n\t\tselected_polygon_layer: Option<LayerNodeIdentifier>,\n\t\t_input: &InputPreprocessorMessageHandler,\n\t\tshape_editor: &mut &mut ShapeState,\n\t\tmouse_position: DVec2,\n\t\toverlay_context: &mut OverlayContext,\n\t) {\n\t\tself.number_of_points_dial.overlays(document, selected_polygon_layer, shape_editor, mouse_position, overlay_context);\n\t\tself.point_radius_handle.overlays(selected_polygon_layer, document, overlay_context);\n\n\t\tpolygon_outline(selected_polygon_layer, document, overlay_context);\n\t}\n\n\tfn dragging_overlays(\n\t\t&self,\n\t\tdocument: &DocumentMessageHandler,\n\t\t_input: &InputPreprocessorMessageHandler,\n\t\tshape_editor: &mut &mut ShapeState,\n\t\tmouse_position: DVec2,\n\t\toverlay_context: &mut OverlayContext,\n\t) {\n\t\tif self.number_of_points_dial.is_dragging() {\n\t\t\tself.number_of_points_dial.overlays(document, None, shape_editor, mouse_position, overlay_context);\n\t\t}\n\n\t\tif self.point_radius_handle.is_dragging_or_snapped() {\n\t\t\tself.point_radius_handle.overlays(None, document, overlay_context);\n\t\t}\n\t}\n\n\tfn mouse_cursor_icon(&self) -> Option<MouseCursorIcon> {\n\t\tif self.number_of_points_dial.is_dragging() || self.number_of_points_dial.is_hovering() {\n\t\t\treturn Some(MouseCursorIcon::EWResize);\n\t\t}\n\n\t\tif self.point_radius_handle.is_dragging_or_snapped() || self.point_radius_handle.hovered() {\n\t\t\treturn Some(MouseCursorIcon::Default);\n\t\t}\n\n\t\tNone\n\t}\n\n\tfn cleanup(&mut self) {\n\t\tself.number_of_points_dial.cleanup();\n\t\tself.point_radius_handle.cleanup();\n\t}\n}\n\n#[derive(Default)]\npub struct Polygon;\n\nimpl Polygon {\n\tpub fn create_node(vertices: u32) -> NodeTemplate {\n\t\tlet identifier = DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::regular_polygon::IDENTIFIER);\n\t\tlet node_type = resolve_document_node_type(&identifier).expect(\"Regular Polygon can't be found\");\n\t\tnode_type.node_template_input_override([None, Some(NodeInput::value(TaggedValue::U32(vertices), false)), Some(NodeInput::value(TaggedValue::F64(0.5), false))])\n\t}\n\n\tpub fn update_shape(\n\t\tdocument: &DocumentMessageHandler,\n\t\tipp: &InputPreprocessorMessageHandler,\n\t\tviewport: &ViewportMessageHandler,\n\t\tlayer: LayerNodeIdentifier,\n\t\tshape_tool_data: &mut ShapeToolData,\n\t\tmodifier: ShapeToolModifierKey,\n\t\tresponses: &mut VecDeque<Message>,\n\t) {\n\t\tlet [center, lock_ratio, _] = modifier;\n\n\t\tif let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, viewport, center, lock_ratio) {\n\t\t\t// TODO: We need to determine how to allow the polygon node to make irregular shapes\n\t\t\tupdate_radius_sign(end, start, layer, document, responses);\n\n\t\t\tlet dimensions = (start - end).abs();\n\n\t\t\t// We keep the smaller dimension's scale at 1 and scale the other dimension accordingly\n\t\t\tlet mut scale = DVec2::ONE;\n\t\t\tlet radius;\n\t\t\tif dimensions.x > dimensions.y {\n\t\t\t\tscale.x = dimensions.x / dimensions.y;\n\t\t\t\tradius = dimensions.y / 2.;\n\t\t\t} else {\n\t\t\t\tscale.y = dimensions.y / dimensions.x;\n\t\t\t\tradius = dimensions.x / 2.;\n\t\t\t}\n\n\t\t\tlet Some(node_id) = graph_modification_utils::get_polygon_id(layer, &document.network_interface) else {\n\t\t\t\treturn;\n\t\t\t};\n\n\t\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\t\tinput_connector: InputConnector::node(node_id, 2),\n\t\t\t\tinput: NodeInput::value(TaggedValue::F64(radius), false),\n\t\t\t});\n\n\t\t\tresponses.add(GraphOperationMessage::TransformSet {\n\t\t\t\tlayer,\n\t\t\t\ttransform: DAffine2::from_scale_angle_translation(scale, 0., (start + end) / 2.),\n\t\t\t\ttransform_in: TransformIn::Viewport,\n\t\t\t\tskip_rerender: false,\n\t\t\t});\n\t\t}\n\t}\n\n\t/// Updates the number of sides of a polygon or star node and syncs the Shape tool UI widget accordingly.\n\t/// Increases or decreases the side count based on user input, clamped to a minimum of 3.\n\tpub fn decrease_or_increase_sides(decrease: bool, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {\n\t\tlet Some(node_id) = graph_modification_utils::get_polygon_id(layer, &document.network_interface).or(graph_modification_utils::get_star_id(layer, &document.network_interface)) else {\n\t\t\treturn;\n\t\t};\n\n\t\tlet Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface)\n\t\t\t.find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::regular_polygon::IDENTIFIER))\n\t\t\t.or(NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::star::IDENTIFIER)))\n\t\telse {\n\t\t\treturn;\n\t\t};\n\n\t\tlet Some(&TaggedValue::U32(n)) = node_inputs.get(1).unwrap().as_value() else {\n\t\t\treturn;\n\t\t};\n\n\t\tlet new_dimension = if decrease { (n - 1).max(3) } else { n + 1 };\n\n\t\tresponses.add(ShapeToolMessage::UpdateOptions {\n\t\t\toptions: ShapeOptionsUpdate::Vertices(new_dimension),\n\t\t});\n\n\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\tinput_connector: InputConnector::node(node_id, 1),\n\t\t\tinput: NodeInput::value(TaggedValue::U32(new_dimension), false),\n\t\t});\n\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/shapes/rectangle_shape.rs",
    "content": "use super::shape_utility::ShapeToolModifierKey;\nuse super::*;\nuse crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;\nuse crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_proto_node_type;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate};\nuse crate::messages::tool::common_functionality::graph_modification_utils;\nuse crate::messages::tool::tool_messages::tool_prelude::*;\nuse glam::DAffine2;\nuse graph_craft::document::NodeInput;\nuse graph_craft::document::value::TaggedValue;\nuse std::collections::VecDeque;\n\n#[derive(Default)]\npub struct Rectangle;\n\nimpl Rectangle {\n\tpub fn create_node() -> NodeTemplate {\n\t\tlet node_type = resolve_proto_node_type(graphene_std::vector::generator_nodes::rectangle::IDENTIFIER).expect(\"Rectangle node can't be found\");\n\t\tnode_type.node_template_input_override([None, Some(NodeInput::value(TaggedValue::F64(1.), false)), Some(NodeInput::value(TaggedValue::F64(1.), false))])\n\t}\n\n\tpub fn update_shape(\n\t\tdocument: &DocumentMessageHandler,\n\t\tipp: &InputPreprocessorMessageHandler,\n\t\tviewport: &ViewportMessageHandler,\n\t\tlayer: LayerNodeIdentifier,\n\t\tshape_tool_data: &mut ShapeToolData,\n\t\tmodifier: ShapeToolModifierKey,\n\t\tresponses: &mut VecDeque<Message>,\n\t) {\n\t\tlet [center, lock_ratio, _] = modifier;\n\n\t\tif let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, viewport, center, lock_ratio) {\n\t\t\tlet Some(node_id) = graph_modification_utils::get_rectangle_id(layer, &document.network_interface) else {\n\t\t\t\treturn;\n\t\t\t};\n\n\t\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\t\tinput_connector: InputConnector::node(node_id, 1),\n\t\t\t\tinput: NodeInput::value(TaggedValue::F64((start.x - end.x).abs()), false),\n\t\t\t});\n\t\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\t\tinput_connector: InputConnector::node(node_id, 2),\n\t\t\t\tinput: NodeInput::value(TaggedValue::F64((start.y - end.y).abs()), false),\n\t\t\t});\n\t\t\tresponses.add(GraphOperationMessage::TransformSet {\n\t\t\t\tlayer,\n\t\t\t\ttransform: DAffine2::from_translation(start.midpoint(end)),\n\t\t\t\ttransform_in: TransformIn::Viewport,\n\t\t\t\tskip_rerender: false,\n\t\t\t});\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/shapes/shape_utility.rs",
    "content": "use super::ShapeToolData;\nuse super::line_shape::LineEnd;\nuse crate::consts::{ARC_SWEEP_GIZMO_RADIUS, ARC_SWEEP_GIZMO_TEXT_HEIGHT, BOUNDS_SELECT_THRESHOLD};\nuse crate::messages::frontend::utility_types::MouseCursorIcon;\nuse crate::messages::message::Message;\nuse crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;\nuse crate::messages::portfolio::document::overlays::utility_types::OverlayContext;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::portfolio::document::utility_types::network_interface::InputConnector;\nuse crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler, NodeGraphMessage, Responses};\nuse crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer;\nuse crate::messages::tool::common_functionality::shape_editor::ShapeState;\nuse crate::messages::tool::common_functionality::transformation_cage::BoundingBoxManager;\nuse crate::messages::tool::tool_messages::tool_prelude::Key;\nuse crate::messages::tool::utility_types::*;\nuse glam::{DAffine2, DMat2, DVec2};\nuse graph_craft::document::NodeInput;\nuse graph_craft::document::value::TaggedValue;\nuse graphene_std::NodeInputDecleration;\nuse graphene_std::subpath::{self, Subpath};\nuse graphene_std::vector::click_target::ClickTargetType;\nuse graphene_std::vector::misc::{ArcType, GridType, SpiralType, dvec2_to_point};\nuse kurbo::{BezPath, PathEl, Shape};\nuse std::collections::VecDeque;\nuse std::f64::consts::{PI, TAU};\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, Copy, Eq, PartialEq, Default, serde::Serialize, serde::Deserialize)]\npub enum ShapeType {\n\t#[default]\n\tPolygon = 0,\n\tStar,\n\tCircle,\n\tArc,\n\tSpiral,\n\tGrid,\n\tArrow,\n\tLine,      // KEEP THIS AT THE END\n\tRectangle, // KEEP THIS AT THE END\n\tEllipse,   // KEEP THIS AT THE END\n}\n\nimpl ShapeType {\n\tpub fn name(&self) -> String {\n\t\t(match self {\n\t\t\tSelf::Polygon => \"Polygon\",\n\t\t\tSelf::Star => \"Star\",\n\t\t\tSelf::Circle => \"Circle\",\n\t\t\tSelf::Arc => \"Arc\",\n\t\t\tSelf::Spiral => \"Spiral\",\n\t\t\tSelf::Grid => \"Grid\",\n\t\t\tSelf::Arrow => \"Arrow\",\n\t\t\tSelf::Line => \"Line\",\n\t\t\tSelf::Rectangle => \"Rectangle\",\n\t\t\tSelf::Ellipse => \"Ellipse\",\n\t\t})\n\t\t.into()\n\t}\n\n\tpub fn tooltip_label(&self) -> String {\n\t\t(match self {\n\t\t\tSelf::Line => \"Line Tool\",\n\t\t\tSelf::Rectangle => \"Rectangle Tool\",\n\t\t\tSelf::Ellipse => \"Ellipse Tool\",\n\t\t\t_ => \"\",\n\t\t})\n\t\t.into()\n\t}\n\n\tpub fn tooltip_description(&self) -> String {\n\t\t(match self {\n\t\t\t// TODO: Add descriptions to all the shape tools\n\t\t\t_ => \"\",\n\t\t})\n\t\t.into()\n\t}\n\n\tpub fn icon_name(&self) -> String {\n\t\t(match self {\n\t\t\tSelf::Line => \"VectorLineTool\",\n\t\t\tSelf::Rectangle => \"VectorRectangleTool\",\n\t\t\tSelf::Ellipse => \"VectorEllipseTool\",\n\t\t\t_ => \"\",\n\t\t})\n\t\t.into()\n\t}\n\n\tpub fn tool_type(&self) -> ToolType {\n\t\tmatch self {\n\t\t\tSelf::Line => ToolType::Line,\n\t\t\tSelf::Rectangle => ToolType::Rectangle,\n\t\t\tSelf::Ellipse => ToolType::Ellipse,\n\t\t\t_ => ToolType::Shape,\n\t\t}\n\t}\n}\n\npub type ShapeToolModifierKey = [Key; 3];\n\n/// The `ShapeGizmoHandler` trait defines the interactive behavior and overlay logic for shape-specific tools in the editor.\n/// A gizmo is a visual handle or control point used to manipulate a shape's properties (e.g., number of sides, radius, angle).\npub trait ShapeGizmoHandler {\n\t/// Called every frame to update the gizmo's interaction state based on the mouse position and selection.\n\t///\n\t/// This includes detecting hover states and preparing interaction flags or visual feedback (e.g., highlighting a hovered handle).\n\tfn handle_state(&mut self, selected_shape_layers: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>);\n\n\t/// Called when a mouse click occurs over the canvas and a gizmo handle is hovered.\n\t///\n\t/// Used to initiate drag interactions or toggle states on the handle, depending on the tool.\n\t/// For example, a hovered \"number of points\" handle might enter a \"Dragging\" state.\n\tfn handle_click(&mut self);\n\n\t/// Called during a drag interaction to update the shape's parameters in real time.\n\t///\n\t/// For example, a handle might calculate the distance from the drag start to determine a new radius or update the number of points.\n\tfn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>);\n\n\t/// Draws the static or hover-dependent overlays associated with the gizmo.\n\t///\n\t/// These overlays include visual indicators like shape outlines, control points, and hover highlights.\n\tfn overlays(\n\t\t&self,\n\t\tdocument: &DocumentMessageHandler,\n\t\tselected_shape_layers: Option<LayerNodeIdentifier>,\n\t\tinput: &InputPreprocessorMessageHandler,\n\t\tshape_editor: &mut &mut ShapeState,\n\t\tmouse_position: DVec2,\n\t\toverlay_context: &mut OverlayContext,\n\t);\n\n\t/// Draws overlays specifically during a drag operation.\n\t///\n\t/// Used to give real-time visual feedback based on drag progress, such as showing the updated shape preview or snapping guides.\n\tfn dragging_overlays(\n\t\t&self,\n\t\tdocument: &DocumentMessageHandler,\n\t\tinput: &InputPreprocessorMessageHandler,\n\t\tshape_editor: &mut &mut ShapeState,\n\t\tmouse_position: DVec2,\n\t\toverlay_context: &mut OverlayContext,\n\t);\n\n\t/// Returns `true` if any handle or control point in the gizmo is currently being hovered.\n\tfn is_any_gizmo_hovered(&self) -> bool;\n\n\t/// Resets or clears any internal state maintained by the gizmo when it is no longer active.\n\t///\n\t/// For example, dragging states or hover flags should be cleared to avoid visual glitches when switching tools or shapes.\n\tfn cleanup(&mut self);\n\n\tfn mouse_cursor_icon(&self) -> Option<MouseCursorIcon>;\n}\n\n/// Check if the mouse clicked on either endpoint of a line-like shape (Line or Arrow).\npub fn clicked_on_shape_endpoints(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, shape_tool_data: &mut ShapeToolData) -> bool {\n\tlet line_like_shape_nodes = [\n\t\tDefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::line::IDENTIFIER),\n\t\tDefinitionIdentifier::ProtoNode(graphene_std::vector_nodes::arrow::IDENTIFIER),\n\t];\n\n\tlet node_graph_layer = NodeGraphLayer::new(layer, &document.network_interface);\n\tlet endpoint = line_like_shape_nodes.iter().find_map(|id| {\n\t\tlet node_inputs = node_graph_layer.find_node_inputs(id)?;\n\t\tlet &TaggedValue::DVec2(endpoint) = node_inputs[1].as_value()? else { return None };\n\t\tSome(endpoint)\n\t});\n\tlet Some(endpoint) = endpoint else { return false };\n\n\tlet local_start = DVec2::ZERO;\n\tlet local_end = endpoint;\n\n\tlet transform = document.metadata().transform_to_viewport(layer);\n\tlet mouse_pos = input.mouse.position;\n\tlet [start, end] = [local_start, local_end].map(|point| transform.transform_point2(point));\n\n\tlet start_click = (mouse_pos - start).length_squared() < BOUNDS_SELECT_THRESHOLD.powi(2);\n\tlet end_click = (mouse_pos - end).length_squared() < BOUNDS_SELECT_THRESHOLD.powi(2);\n\tlet endpoint_click = start_click || end_click;\n\n\tif endpoint_click {\n\t\tshape_tool_data.line_data.dragging_endpoint = Some(if end_click { LineEnd::End } else { LineEnd::Start });\n\t\tlet anchor_local = if end_click { local_start } else { local_end };\n\t\tshape_tool_data.data.drag_start = document.metadata().transform_to_document(layer).transform_point2(anchor_local);\n\t\tshape_tool_data.line_data.editing_layer = Some(layer);\n\t}\n\n\tendpoint_click\n}\n\n/// Center, Lock Ratio, Lock Angle, Snap Angle, Increase/Decrease Side\npub fn update_radius_sign(end: DVec2, start: DVec2, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {\n\tlet sign_num = if end[1] > start[1] { 1. } else { -1. };\n\tlet new_layer = NodeGraphLayer::new(layer, &document.network_interface);\n\n\tif new_layer\n\t\t.find_input(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::regular_polygon::IDENTIFIER), 1)\n\t\t.unwrap_or(&TaggedValue::U32(0))\n\t\t.to_u32()\n\t\t% 2 == 1\n\t{\n\t\tlet Some(polygon_node_id) = new_layer.upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector_nodes::regular_polygon::IDENTIFIER)) else {\n\t\t\treturn;\n\t\t};\n\n\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\tinput_connector: InputConnector::node(polygon_node_id, 2),\n\t\t\tinput: NodeInput::value(TaggedValue::F64(sign_num * 0.5), false),\n\t\t});\n\t\treturn;\n\t}\n\n\tif new_layer\n\t\t.find_input(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::star::IDENTIFIER), 1)\n\t\t.unwrap_or(&TaggedValue::U32(0))\n\t\t.to_u32()\n\t\t% 2 == 1\n\t{\n\t\tlet Some(star_node_id) = new_layer.upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector_nodes::star::IDENTIFIER)) else {\n\t\t\treturn;\n\t\t};\n\n\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\tinput_connector: InputConnector::node(star_node_id, 2),\n\t\t\tinput: NodeInput::value(TaggedValue::F64(sign_num * 0.5), false),\n\t\t});\n\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\tinput_connector: InputConnector::node(star_node_id, 3),\n\t\t\tinput: NodeInput::value(TaggedValue::F64(sign_num * 0.25), false),\n\t\t});\n\t}\n}\n\npub fn transform_cage_overlays(document: &DocumentMessageHandler, tool_data: &mut ShapeToolData, overlay_context: &mut OverlayContext) {\n\tlet mut transform = document\n\t\t.network_interface\n\t\t.selected_nodes()\n\t\t.selected_visible_and_unlocked_layers(&document.network_interface)\n\t\t.find(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]))\n\t\t.map(|layer| document.metadata().transform_to_viewport_with_first_transform_node_if_group(layer, &document.network_interface))\n\t\t.unwrap_or_default();\n\n\t// Check if the matrix is not invertible\n\tlet mut transform_tampered = false;\n\tif transform.matrix2.determinant() == 0. {\n\t\ttransform.matrix2 += DMat2::IDENTITY * 1e-4; // TODO: Is this the cleanest way to handle this?\n\t\ttransform_tampered = true;\n\t}\n\n\tlet bounds = document\n\t\t.network_interface\n\t\t.selected_nodes()\n\t\t.selected_visible_and_unlocked_layers(&document.network_interface)\n\t\t.filter(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]))\n\t\t.filter_map(|layer| {\n\t\t\tdocument\n\t\t\t\t.metadata()\n\t\t\t\t.bounding_box_with_transform(layer, transform.inverse() * document.metadata().transform_to_viewport(layer))\n\t\t})\n\t\t.reduce(graphene_std::renderer::Quad::combine_bounds);\n\n\tif let Some(bounds) = bounds {\n\t\tlet bounding_box_manager = tool_data.bounding_box_manager.get_or_insert(BoundingBoxManager::default());\n\n\t\tbounding_box_manager.bounds = bounds;\n\t\tbounding_box_manager.transform = transform;\n\t\tbounding_box_manager.transform_tampered = transform_tampered;\n\t\tbounding_box_manager.render_overlays(overlay_context, true);\n\t} else {\n\t\ttool_data.bounding_box_manager.take();\n\t}\n}\n\npub fn anchor_overlays(document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) {\n\tfor layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) {\n\t\tlet Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };\n\t\tlet transform = document.metadata().transform_to_viewport(layer);\n\n\t\toverlay_context.outline_vector(&vector, transform);\n\n\t\tfor (_, &position) in vector.point_domain.ids().iter().zip(vector.point_domain.positions()) {\n\t\t\toverlay_context.manipulator_anchor(transform.transform_point2(position), false, None);\n\t\t}\n\t}\n}\n\n/// Extract the node input values of Star.\n/// Returns an option of (sides, radius1, radius2).\npub fn extract_star_parameters(layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler) -> Option<(u32, f64, f64)> {\n\tlet node_inputs = NodeGraphLayer::new(layer?, &document.network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::star::IDENTIFIER))?;\n\n\tlet (Some(&TaggedValue::U32(sides)), Some(&TaggedValue::F64(radius_1)), Some(&TaggedValue::F64(radius_2))) =\n\t\t(node_inputs.get(1)?.as_value(), node_inputs.get(2)?.as_value(), node_inputs.get(3)?.as_value())\n\telse {\n\t\treturn None;\n\t};\n\n\tSome((sides, radius_1, radius_2))\n}\n\n/// Extract the node input values of Polygon.\n/// Returns an option of (sides, radius).\npub fn extract_polygon_parameters(layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler) -> Option<(u32, f64)> {\n\tlet node_inputs =\n\t\tNodeGraphLayer::new(layer?, &document.network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::regular_polygon::IDENTIFIER))?;\n\n\tlet (Some(&TaggedValue::U32(n)), Some(&TaggedValue::F64(radius))) = (node_inputs.get(1)?.as_value(), node_inputs.get(2)?.as_value()) else {\n\t\treturn None;\n\t};\n\n\tSome((n, radius))\n}\n\n/// Extract the node input values of an arc.\n/// Returns an option of (radius, start angle, sweep angle, arc type).\npub fn extract_arc_parameters(layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler) -> Option<(f64, f64, f64, ArcType)> {\n\tlet node_inputs = NodeGraphLayer::new(layer?, &document.network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::arc::IDENTIFIER))?;\n\n\tlet (Some(&TaggedValue::F64(radius)), Some(&TaggedValue::F64(start_angle)), Some(&TaggedValue::F64(sweep_angle)), Some(&TaggedValue::ArcType(arc_type))) = (\n\t\tnode_inputs.get(1)?.as_value(),\n\t\tnode_inputs.get(2)?.as_value(),\n\t\tnode_inputs.get(3)?.as_value(),\n\t\tnode_inputs.get(4)?.as_value(),\n\t) else {\n\t\treturn None;\n\t};\n\n\tSome((radius, start_angle, sweep_angle, arc_type))\n}\n\n/// Extract the node input values of spiral.\n/// Returns an option of (spiral type, start angle, inner radius, outer radius, turns, angle resolution).\npub fn extract_spiral_parameters(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Option<(SpiralType, f64, f64, f64, f64, f64)> {\n\tuse graphene_std::vector::generator_nodes::spiral::*;\n\n\tlet node_inputs = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::spiral::IDENTIFIER))?;\n\n\tlet (\n\t\tSome(&TaggedValue::SpiralType(spiral_type)),\n\t\tSome(&TaggedValue::F64(start_angle)),\n\t\tSome(&TaggedValue::F64(inner_radius)),\n\t\tSome(&TaggedValue::F64(outer_radius)),\n\t\tSome(&TaggedValue::F64(turns)),\n\t\tSome(&TaggedValue::F64(angle_resolution)),\n\t) = (\n\t\tnode_inputs.get(SpiralTypeInput::INDEX)?.as_value(),\n\t\tnode_inputs.get(StartAngleInput::INDEX)?.as_value(),\n\t\tnode_inputs.get(InnerRadiusInput::INDEX)?.as_value(),\n\t\tnode_inputs.get(OuterRadiusInput::INDEX)?.as_value(),\n\t\tnode_inputs.get(TurnsInput::INDEX)?.as_value(),\n\t\tnode_inputs.get(AngularResolutionInput::INDEX)?.as_value(),\n\t)\n\telse {\n\t\treturn None;\n\t};\n\n\tSome((spiral_type, start_angle, inner_radius, outer_radius, turns, angle_resolution))\n}\n\n/// Calculate the viewport positions of arc endpoints\npub fn arc_end_points(layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler) -> Option<(DVec2, DVec2)> {\n\tlet (radius, start_angle, sweep_angle, _) = extract_arc_parameters(Some(layer?), document)?;\n\n\tlet viewport = document.metadata().transform_to_viewport(layer?);\n\n\tarc_end_points_ignore_layer(radius, start_angle, sweep_angle, Some(viewport))\n}\n\npub fn arc_end_points_ignore_layer(radius: f64, start_angle: f64, sweep_angle: f64, viewport: Option<DAffine2>) -> Option<(DVec2, DVec2)> {\n\tlet end_angle = start_angle.to_radians() + sweep_angle.to_radians();\n\n\tlet start_point = radius * DVec2::from_angle(start_angle.to_radians());\n\tlet end_point = radius * DVec2::from_angle(end_angle);\n\n\tif let Some(transform) = viewport {\n\t\treturn Some((transform.transform_point2(start_point), transform.transform_point2(end_point)));\n\t}\n\n\tSome((start_point, end_point))\n}\n\n/// Calculate the viewport position of a star vertex given its index\n/// Extract the node input values of Circle.\n/// Returns an option of (radius).\npub fn extract_circle_radius(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Option<f64> {\n\tlet node_inputs = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::circle::IDENTIFIER))?;\n\n\tlet Some(&TaggedValue::F64(radius)) = node_inputs.get(1)?.as_value() else {\n\t\treturn None;\n\t};\n\n\tSome(radius)\n}\n\n/// Calculate the viewport position of as a star vertex given its index\npub fn star_vertex_position(viewport: DAffine2, vertex_index: i32, n: u32, radius1: f64, radius2: f64) -> DVec2 {\n\tlet angle = ((vertex_index as f64) * PI) / (n as f64);\n\tlet radius = if vertex_index % 2 == 0 { radius1 } else { radius2 };\n\n\tviewport.transform_point2(DVec2 {\n\t\tx: radius * angle.sin(),\n\t\ty: -radius * angle.cos(),\n\t})\n}\n\n/// Calculate the viewport position of a polygon vertex given its index\npub fn polygon_vertex_position(viewport: DAffine2, vertex_index: i32, n: u32, radius: f64) -> DVec2 {\n\tlet angle = ((vertex_index as f64) * TAU) / (n as f64);\n\n\tviewport.transform_point2(DVec2 {\n\t\tx: radius * angle.sin(),\n\t\ty: -radius * angle.cos(),\n\t})\n}\n\n/// Outlines the geometric shape made by star-node\npub fn star_outline(layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) {\n\tlet Some(layer) = layer else { return };\n\tlet Some((sides, radius1, radius2)) = extract_star_parameters(Some(layer), document) else {\n\t\treturn;\n\t};\n\n\tlet viewport = document.metadata().transform_to_viewport(layer);\n\n\tlet points = sides as u64;\n\tlet diameter: f64 = radius1 * 2.;\n\tlet inner_diameter = radius2 * 2.;\n\n\tlet subpath: Vec<ClickTargetType> = vec![ClickTargetType::Subpath(Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter))];\n\n\toverlay_context.outline(subpath.iter(), viewport, None);\n}\n\n/// Outlines the geometric shape made by polygon-node\npub fn polygon_outline(layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) {\n\tlet Some(layer) = layer else { return };\n\tlet Some((sides, radius)) = extract_polygon_parameters(Some(layer), document) else {\n\t\treturn;\n\t};\n\n\tlet viewport = document.metadata().transform_to_viewport(layer);\n\n\tlet points = sides as u64;\n\tlet radius: f64 = radius * 2.;\n\n\tlet subpath: Vec<ClickTargetType> = vec![ClickTargetType::Subpath(Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius))];\n\n\toverlay_context.outline(subpath.iter(), viewport, None);\n}\n\n/// Outlines the geometric shape made by an Arc node\npub fn arc_outline(layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) {\n\tlet Some(layer) = layer else { return };\n\n\tlet Some((radius, start_angle, sweep_angle, arc_type)) = extract_arc_parameters(Some(layer), document) else {\n\t\treturn;\n\t};\n\n\tlet subpath: Vec<ClickTargetType> = vec![ClickTargetType::Subpath(Subpath::new_arc(\n\t\tradius,\n\t\tstart_angle / 360. * std::f64::consts::TAU,\n\t\tsweep_angle / 360. * std::f64::consts::TAU,\n\t\tmatch arc_type {\n\t\t\tArcType::Open => subpath::ArcType::Open,\n\t\t\tArcType::Closed => subpath::ArcType::Closed,\n\t\t\tArcType::PieSlice => subpath::ArcType::PieSlice,\n\t\t},\n\t))];\n\tlet viewport = document.metadata().transform_to_viewport(layer);\n\n\toverlay_context.outline(subpath.iter(), viewport, None);\n}\n\n/// Check if the the cursor is inside the geometric star shape made by the Star node without any upstream node modifications\npub fn inside_star(viewport: DAffine2, n: u32, radius1: f64, radius2: f64, mouse_position: DVec2) -> bool {\n\tlet mut paths = Vec::new();\n\n\tfor i in 0..2 * n {\n\t\tlet new_point = dvec2_to_point(star_vertex_position(viewport, i as i32, n, radius1, radius2));\n\n\t\tif i == 0 {\n\t\t\tpaths.push(PathEl::MoveTo(new_point));\n\t\t} else {\n\t\t\tpaths.push(PathEl::LineTo(new_point));\n\t\t}\n\t}\n\n\tpaths.push(PathEl::ClosePath);\n\n\tlet bez_path = BezPath::from_vec(paths);\n\tlet (shape, bbox) = (bez_path.clone(), bez_path.bounding_box());\n\n\tif bbox.x0 > mouse_position.x || bbox.y0 > mouse_position.y || bbox.x1 < mouse_position.x || bbox.y1 < mouse_position.y {\n\t\treturn false;\n\t}\n\n\tlet winding = shape.winding(dvec2_to_point(mouse_position));\n\n\t// Non-zero fill rule\n\twinding != 0\n}\n\n/// Check if the the cursor is inside the geometric polygon shape made by the Polygon node without any upstream node modifications\npub fn inside_polygon(viewport: DAffine2, n: u32, radius: f64, mouse_position: DVec2) -> bool {\n\tlet mut paths = Vec::new();\n\n\tfor i in 0..n {\n\t\tlet new_point = dvec2_to_point(polygon_vertex_position(viewport, i as i32, n, radius));\n\n\t\tif i == 0 {\n\t\t\tpaths.push(PathEl::MoveTo(new_point));\n\t\t} else {\n\t\t\tpaths.push(PathEl::LineTo(new_point));\n\t\t}\n\t}\n\n\tpaths.push(PathEl::ClosePath);\n\n\tlet bez_path = BezPath::from_vec(paths);\n\tlet (shape, bbox) = (bez_path.clone(), bez_path.bounding_box());\n\n\tif bbox.x0 > mouse_position.x || bbox.y0 > mouse_position.y || bbox.x1 < mouse_position.x || bbox.y1 < mouse_position.y {\n\t\treturn false;\n\t}\n\n\tlet winding = shape.winding(dvec2_to_point(mouse_position));\n\n\t// Non-zero fill rule\n\twinding != 0\n}\n\npub fn draw_snapping_ticks(snap_radii: &[f64], direction: DVec2, viewport: DAffine2, angle: f64, overlay_context: &mut OverlayContext) {\n\tfor &snapped_radius in snap_radii {\n\t\tlet Some(tick_direction) = direction.perp().try_normalize() else {\n\t\t\treturn;\n\t\t};\n\n\t\tlet tick_position = viewport.transform_point2(DVec2 {\n\t\t\tx: snapped_radius * angle.sin(),\n\t\t\ty: -snapped_radius * angle.cos(),\n\t\t});\n\n\t\toverlay_context.line(tick_position, tick_position + tick_direction * 5., None, Some(2.));\n\t\toverlay_context.line(tick_position, tick_position - tick_direction * 5., None, Some(2.));\n\t}\n}\n\n/// Wraps an angle (in radians) into the range [0, 2π).\npub fn wrap_to_tau(angle: f64) -> f64 {\n\t(angle % TAU + TAU) % TAU\n}\n\npub fn format_rounded(value: f64, precision: usize) -> String {\n\tformat!(\"{value:.precision$}\").trim_end_matches('0').trim_end_matches('.').to_string()\n}\n\n/// Gives the approximated angle to display in degrees, given an angle in degrees.\npub fn calculate_display_angle(angle: f64) -> f64 {\n\tif angle.is_sign_positive() {\n\t\tangle - (angle / 360.).floor() * 360.\n\t} else if angle.is_sign_negative() {\n\t\tangle - ((angle / 360.).floor() + 1.) * 360.\n\t} else {\n\t\tangle\n\t}\n}\n\npub fn calculate_arc_text_transform(angle: f64, offset_angle: f64, center: DVec2, width: f64) -> DAffine2 {\n\tlet text_angle_on_unit_circle = DVec2::from_angle((angle.to_radians() % TAU) / 2. + offset_angle);\n\tlet text_texture_position = DVec2::new(\n\t\t(ARC_SWEEP_GIZMO_RADIUS + 4. + width) * text_angle_on_unit_circle.x,\n\t\t(ARC_SWEEP_GIZMO_RADIUS + ARC_SWEEP_GIZMO_TEXT_HEIGHT) * text_angle_on_unit_circle.y,\n\t);\n\tDAffine2::from_translation(text_texture_position + center)\n}\n\n/// Extract the node input values of Grid.\n/// Returns an option of (grid_type, spacing, columns, rows, angles).\npub fn extract_grid_parameters(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Option<(GridType, DVec2, u32, u32, DVec2)> {\n\tuse graphene_std::vector::generator_nodes::grid::*;\n\n\tlet node_inputs = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::grid::IDENTIFIER))?;\n\n\tlet (Some(&TaggedValue::GridType(grid_type)), Some(&TaggedValue::DVec2(spacing)), Some(&TaggedValue::U32(columns)), Some(&TaggedValue::U32(rows)), Some(&TaggedValue::DVec2(angles))) = (\n\t\tnode_inputs.get(GridTypeInput::INDEX)?.as_value(),\n\t\tnode_inputs.get(SpacingInput::<f64>::INDEX)?.as_value(),\n\t\tnode_inputs.get(ColumnsInput::INDEX)?.as_value(),\n\t\tnode_inputs.get(RowsInput::INDEX)?.as_value(),\n\t\tnode_inputs.get(AnglesInput::INDEX)?.as_value(),\n\t) else {\n\t\treturn None;\n\t};\n\n\tSome((grid_type, spacing, columns, rows, angles))\n}\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/shapes/spiral_shape.rs",
    "content": "use super::*;\nuse crate::messages::frontend::utility_types::MouseCursorIcon;\nuse crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;\nuse crate::messages::portfolio::document::node_graph::document_node_definitions::{DefinitionIdentifier, resolve_document_node_type};\nuse crate::messages::portfolio::document::overlays::utility_types::OverlayContext;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate};\nuse crate::messages::tool::common_functionality::gizmos::shape_gizmos::spiral_turns_handle::{SpiralTurns, SpiralTurnsState};\nuse crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer};\nuse crate::messages::tool::common_functionality::shape_editor::ShapeState;\nuse crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeGizmoHandler, extract_spiral_parameters};\nuse crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapData, SnapTypeConfiguration};\nuse crate::messages::tool::tool_messages::shape_tool::ShapeOptionsUpdate;\nuse crate::messages::tool::tool_messages::tool_prelude::*;\nuse glam::DAffine2;\nuse graph_craft::document::NodeInput;\nuse graph_craft::document::value::TaggedValue;\nuse graphene_std::NodeInputDecleration;\nuse graphene_std::subpath::{calculate_growth_factor, spiral_point};\nuse graphene_std::vector::misc::SpiralType;\nuse std::collections::VecDeque;\n\n#[derive(Clone, Debug, Default)]\npub struct SpiralGizmoHandler {\n\tturns_handle: SpiralTurns,\n}\n\nimpl ShapeGizmoHandler for SpiralGizmoHandler {\n\tfn is_any_gizmo_hovered(&self) -> bool {\n\t\tself.turns_handle.hovered()\n\t}\n\n\tfn handle_state(&mut self, selected_spiral_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {\n\t\tself.turns_handle.handle_actions(selected_spiral_layer, mouse_position, document, responses);\n\t}\n\n\tfn handle_click(&mut self) {\n\t\tif self.turns_handle.hovered() {\n\t\t\tself.turns_handle.update_state(SpiralTurnsState::Dragging);\n\t\t}\n\t}\n\n\tfn handle_update(&mut self, _drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {\n\t\tif self.turns_handle.is_dragging() {\n\t\t\tself.turns_handle.update_number_of_turns(document, input, responses);\n\t\t}\n\t}\n\n\tfn overlays(\n\t\t&self,\n\t\tdocument: &DocumentMessageHandler,\n\t\tselected_spiral_layer: Option<LayerNodeIdentifier>,\n\t\t_input: &InputPreprocessorMessageHandler,\n\t\tshape_editor: &mut &mut ShapeState,\n\t\tmouse_position: DVec2,\n\t\toverlay_context: &mut OverlayContext,\n\t) {\n\t\tself.turns_handle.overlays(document, selected_spiral_layer, shape_editor, mouse_position, overlay_context);\n\t}\n\n\tfn dragging_overlays(\n\t\t&self,\n\t\tdocument: &DocumentMessageHandler,\n\t\t_input: &InputPreprocessorMessageHandler,\n\t\tshape_editor: &mut &mut ShapeState,\n\t\tmouse_position: DVec2,\n\t\toverlay_context: &mut OverlayContext,\n\t) {\n\t\tif self.turns_handle.is_dragging() {\n\t\t\tself.turns_handle.overlays(document, None, shape_editor, mouse_position, overlay_context);\n\t\t}\n\t}\n\n\tfn mouse_cursor_icon(&self) -> Option<MouseCursorIcon> {\n\t\tif self.turns_handle.hovered() || self.turns_handle.is_dragging() {\n\t\t\treturn Some(MouseCursorIcon::Default);\n\t\t}\n\t\tNone\n\t}\n\n\tfn cleanup(&mut self) {\n\t\tself.turns_handle.cleanup();\n\t}\n}\n\n/// Calculates the position of a spiral endpoint at a given angle offset (0 = start, TAU = end).\npub fn calculate_spiral_endpoints(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, viewport: DAffine2, theta: f64) -> Option<DVec2> {\n\tlet (spiral_type, start_angle, a, outer_radius, turns, _) = extract_spiral_parameters(layer, document)?;\n\tlet b = calculate_growth_factor(a, turns, outer_radius, spiral_type);\n\tlet theta = turns * theta + start_angle.to_radians();\n\n\tSome(viewport.transform_point2(spiral_point(theta, a, b, spiral_type)))\n}\n\n#[derive(Default)]\npub struct Spiral;\n\nimpl Spiral {\n\tpub fn create_node(spiral_type: SpiralType, turns: f64) -> NodeTemplate {\n\t\tlet inner_radius = match spiral_type {\n\t\t\tSpiralType::Archimedean => 0.,\n\t\t\tSpiralType::Logarithmic => 0.1,\n\t\t};\n\n\t\tlet identifier = DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::spiral::IDENTIFIER);\n\t\tlet node_type = resolve_document_node_type(&identifier).expect(\"Spiral node can't be found\");\n\t\tnode_type.node_template_input_override([\n\t\t\tNone,\n\t\t\tSome(NodeInput::value(TaggedValue::SpiralType(spiral_type), false)),\n\t\t\tSome(NodeInput::value(TaggedValue::F64(turns), false)),\n\t\t\tSome(NodeInput::value(TaggedValue::F64(0.), false)),\n\t\t\tSome(NodeInput::value(TaggedValue::F64(inner_radius), false)),\n\t\t\tSome(NodeInput::value(TaggedValue::F64(0.1), false)),\n\t\t\tSome(NodeInput::value(TaggedValue::F64(90.), false)),\n\t\t])\n\t}\n\n\tpub fn update_shape(\n\t\tdocument: &DocumentMessageHandler,\n\t\tipp: &InputPreprocessorMessageHandler,\n\t\tviewport: &ViewportMessageHandler,\n\t\tlayer: LayerNodeIdentifier,\n\t\tshape_tool_data: &mut ShapeToolData,\n\t\tresponses: &mut VecDeque<Message>,\n\t) {\n\t\tuse graphene_std::vector::generator_nodes::spiral::*;\n\n\t\tlet viewport_drag_start = shape_tool_data.data.viewport_drag_start(document);\n\n\t\tlet ignore = vec![layer];\n\t\tlet snap_data = SnapData::ignore(document, ipp, viewport, &ignore);\n\t\tlet config = SnapTypeConfiguration::default();\n\t\tlet document_mouse = document.metadata().document_to_viewport.inverse().transform_point2(ipp.mouse.position);\n\t\tlet snapped = shape_tool_data.data.snap_manager.free_snap(&snap_data, &SnapCandidatePoint::handle(document_mouse), config);\n\t\tlet snapped_viewport_point = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document);\n\t\tshape_tool_data.data.snap_manager.update_indicator(snapped);\n\n\t\tlet dragged_distance = (viewport_drag_start - snapped_viewport_point).length();\n\n\t\tlet Some(node_id) = graph_modification_utils::get_spiral_id(layer, &document.network_interface) else {\n\t\t\treturn;\n\t\t};\n\n\t\tlet Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::spiral::IDENTIFIER))\n\t\telse {\n\t\t\treturn;\n\t\t};\n\n\t\tlet Some(&TaggedValue::SpiralType(spiral_type)) = node_inputs.get(SpiralTypeInput::INDEX).unwrap().as_value() else {\n\t\t\treturn;\n\t\t};\n\n\t\tlet new_radius = match spiral_type {\n\t\t\tSpiralType::Archimedean => dragged_distance,\n\t\t\tSpiralType::Logarithmic => (dragged_distance).max(0.1),\n\t\t};\n\n\t\tresponses.add(GraphOperationMessage::TransformSet {\n\t\t\tlayer,\n\t\t\ttransform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., viewport_drag_start),\n\t\t\ttransform_in: TransformIn::Viewport,\n\t\t\tskip_rerender: false,\n\t\t});\n\n\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\tinput_connector: InputConnector::node(node_id, OuterRadiusInput::INDEX),\n\t\t\tinput: NodeInput::value(TaggedValue::F64(new_radius), false),\n\t\t});\n\t}\n\n\t/// Updates the number of turns of a Spiral node and recalculates its radius based on drag distance.\n\t/// Also updates the Shape tool's turns UI widget to reflect the change.\n\tpub fn update_turns(decrease: bool, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {\n\t\tuse graphene_std::vector::generator_nodes::spiral::*;\n\n\t\tlet Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::spiral::IDENTIFIER))\n\t\telse {\n\t\t\treturn;\n\t\t};\n\n\t\tlet Some(node_id) = graph_modification_utils::get_spiral_id(layer, &document.network_interface) else {\n\t\t\treturn;\n\t\t};\n\n\t\tlet Some(&TaggedValue::F64(mut turns)) = node_inputs.get(TurnsInput::INDEX).unwrap().as_value() else {\n\t\t\treturn;\n\t\t};\n\n\t\tif decrease {\n\t\t\tturns = (turns - 1.).max(1.);\n\t\t} else {\n\t\t\tturns += 1.;\n\t\t}\n\n\t\tresponses.add(ShapeToolMessage::UpdateOptions {\n\t\t\toptions: ShapeOptionsUpdate::Turns(turns),\n\t\t});\n\n\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\tinput_connector: InputConnector::node(node_id, TurnsInput::INDEX),\n\t\t\tinput: NodeInput::value(TaggedValue::F64(turns), false),\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/shapes/star_shape.rs",
    "content": "use super::shape_utility::{ShapeToolModifierKey, update_radius_sign};\nuse super::*;\nuse crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;\nuse crate::messages::portfolio::document::node_graph::document_node_definitions::{DefinitionIdentifier, resolve_document_node_type};\nuse crate::messages::portfolio::document::overlays::utility_types::OverlayContext;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate};\nuse crate::messages::tool::common_functionality::gizmos::shape_gizmos::number_of_points_dial::{NumberOfPointsDial, NumberOfPointsDialState};\nuse crate::messages::tool::common_functionality::gizmos::shape_gizmos::point_radius_handle::{PointRadiusHandle, PointRadiusHandleState};\nuse crate::messages::tool::common_functionality::graph_modification_utils;\nuse crate::messages::tool::common_functionality::shape_editor::ShapeState;\nuse crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeGizmoHandler, star_outline};\nuse crate::messages::tool::tool_messages::tool_prelude::*;\nuse core::f64;\nuse glam::DAffine2;\nuse graph_craft::document::NodeInput;\nuse graph_craft::document::value::TaggedValue;\nuse std::collections::VecDeque;\n\n#[derive(Clone, Debug, Default)]\npub struct StarGizmoHandler {\n\tnumber_of_points_dial: NumberOfPointsDial,\n\tpoint_radius_handle: PointRadiusHandle,\n}\n\nimpl ShapeGizmoHandler for StarGizmoHandler {\n\tfn is_any_gizmo_hovered(&self) -> bool {\n\t\tself.number_of_points_dial.is_hovering() || self.point_radius_handle.hovered()\n\t}\n\n\tfn handle_state(&mut self, selected_star_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {\n\t\tself.number_of_points_dial.handle_actions(selected_star_layer, mouse_position, document, responses);\n\t\tself.point_radius_handle.handle_actions(selected_star_layer, document, mouse_position, responses);\n\t}\n\n\tfn handle_click(&mut self) {\n\t\tif self.number_of_points_dial.is_hovering() {\n\t\t\tself.number_of_points_dial.update_state(NumberOfPointsDialState::Dragging);\n\t\t\treturn;\n\t\t}\n\n\t\tif self.point_radius_handle.hovered() {\n\t\t\tself.point_radius_handle.update_state(PointRadiusHandleState::Dragging);\n\t\t}\n\t}\n\n\tfn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {\n\t\tif self.number_of_points_dial.is_dragging() {\n\t\t\tself.number_of_points_dial.update_number_of_sides(document, input, responses, drag_start);\n\t\t}\n\n\t\tif self.point_radius_handle.is_dragging_or_snapped() {\n\t\t\tself.point_radius_handle.update_inner_radius(document, input, responses, drag_start);\n\t\t}\n\t}\n\n\tfn overlays(\n\t\t&self,\n\t\tdocument: &DocumentMessageHandler,\n\t\tselected_star_layer: Option<LayerNodeIdentifier>,\n\t\t_input: &InputPreprocessorMessageHandler,\n\t\tshape_editor: &mut &mut ShapeState,\n\t\tmouse_position: DVec2,\n\t\toverlay_context: &mut OverlayContext,\n\t) {\n\t\tself.number_of_points_dial.overlays(document, selected_star_layer, shape_editor, mouse_position, overlay_context);\n\t\tself.point_radius_handle.overlays(selected_star_layer, document, overlay_context);\n\n\t\tstar_outline(selected_star_layer, document, overlay_context);\n\t}\n\n\tfn dragging_overlays(\n\t\t&self,\n\t\tdocument: &DocumentMessageHandler,\n\t\t_input: &InputPreprocessorMessageHandler,\n\t\tshape_editor: &mut &mut ShapeState,\n\t\tmouse_position: DVec2,\n\t\toverlay_context: &mut OverlayContext,\n\t) {\n\t\tif self.number_of_points_dial.is_dragging() {\n\t\t\tself.number_of_points_dial.overlays(document, None, shape_editor, mouse_position, overlay_context);\n\t\t}\n\n\t\tif self.point_radius_handle.is_dragging_or_snapped() {\n\t\t\tself.point_radius_handle.overlays(None, document, overlay_context);\n\t\t}\n\t}\n\n\tfn cleanup(&mut self) {\n\t\tself.number_of_points_dial.cleanup();\n\t\tself.point_radius_handle.cleanup();\n\t}\n\n\tfn mouse_cursor_icon(&self) -> Option<MouseCursorIcon> {\n\t\tif self.number_of_points_dial.is_dragging() || self.number_of_points_dial.is_hovering() {\n\t\t\treturn Some(MouseCursorIcon::EWResize);\n\t\t}\n\n\t\tif self.point_radius_handle.is_dragging_or_snapped() || self.point_radius_handle.hovered() {\n\t\t\treturn Some(MouseCursorIcon::Default);\n\t\t}\n\n\t\tNone\n\t}\n}\n\n#[derive(Default)]\npub struct Star;\n\nimpl Star {\n\tpub fn create_node(vertices: u32) -> NodeTemplate {\n\t\tlet identifier = DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::star::IDENTIFIER);\n\t\tlet node_type = resolve_document_node_type(&identifier).expect(\"Star node can't be found\");\n\t\tnode_type.node_template_input_override([\n\t\t\tNone,\n\t\t\tSome(NodeInput::value(TaggedValue::U32(vertices), false)),\n\t\t\tSome(NodeInput::value(TaggedValue::F64(0.5), false)),\n\t\t\tSome(NodeInput::value(TaggedValue::F64(0.25), false)),\n\t\t])\n\t}\n\n\tpub fn update_shape(\n\t\tdocument: &DocumentMessageHandler,\n\t\tipp: &InputPreprocessorMessageHandler,\n\t\tviewport: &ViewportMessageHandler,\n\t\tlayer: LayerNodeIdentifier,\n\t\tshape_tool_data: &mut ShapeToolData,\n\t\tmodifier: ShapeToolModifierKey,\n\t\tresponses: &mut VecDeque<Message>,\n\t) {\n\t\tlet [center, lock_ratio, _] = modifier;\n\n\t\tif let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, viewport, center, lock_ratio) {\n\t\t\t// TODO: We need to determine how to allow the polygon node to make irregular shapes\n\t\t\tupdate_radius_sign(end, start, layer, document, responses);\n\n\t\t\tlet dimensions = (start - end).abs();\n\n\t\t\t// We keep the smaller dimension's scale at 1 and scale the other dimension accordingly\n\t\t\tlet mut scale = DVec2::ONE;\n\t\t\tlet radius: f64;\n\t\t\tif dimensions.x > dimensions.y {\n\t\t\t\tscale.x = dimensions.x / dimensions.y;\n\t\t\t\tradius = dimensions.y / 2.;\n\t\t\t} else {\n\t\t\t\tscale.y = dimensions.y / dimensions.x;\n\t\t\t\tradius = dimensions.x / 2.;\n\t\t\t}\n\n\t\t\tlet Some(node_id) = graph_modification_utils::get_star_id(layer, &document.network_interface) else {\n\t\t\t\treturn;\n\t\t\t};\n\n\t\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\t\tinput_connector: InputConnector::node(node_id, 2),\n\t\t\t\tinput: NodeInput::value(TaggedValue::F64(radius), false),\n\t\t\t});\n\n\t\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\t\tinput_connector: InputConnector::node(node_id, 3),\n\t\t\t\tinput: NodeInput::value(TaggedValue::F64(radius / 2.), false),\n\t\t\t});\n\n\t\t\tresponses.add(GraphOperationMessage::TransformSet {\n\t\t\t\tlayer,\n\t\t\t\ttransform: DAffine2::from_scale_angle_translation(scale, 0., (start + end) / 2.),\n\t\t\t\ttransform_in: TransformIn::Viewport,\n\t\t\t\tskip_rerender: false,\n\t\t\t});\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/snapping/alignment_snapper.rs",
    "content": "use super::*;\nuse crate::messages::portfolio::document::utility_types::misc::*;\nuse glam::{DAffine2, DVec2};\nuse graphene_std::renderer::Quad;\n\n#[derive(Clone, Debug, Default)]\npub struct AlignmentSnapper {\n\tbounding_box_points: Vec<SnapCandidatePoint>,\n}\n\nimpl AlignmentSnapper {\n\tpub fn collect_bounding_box_points(&mut self, snap_data: &mut SnapData, first_point: bool) {\n\t\tif !first_point {\n\t\t\treturn;\n\t\t}\n\n\t\tlet document = snap_data.document;\n\n\t\tself.bounding_box_points.clear();\n\t\tif !document.snapping_state.bounding_box.align_with_edges {\n\t\t\treturn;\n\t\t}\n\n\t\tfor layer in document.metadata().all_layers() {\n\t\t\tif !document.network_interface.is_artboard(&layer.to_node(), &[]) || snap_data.ignore.contains(&layer) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif document.snapping_state.target_enabled(SnapTarget::Artboard(ArtboardSnapTarget::CornerPoint)) {\n\t\t\t\tlet Some(bounds) = document.metadata().bounding_box_with_transform(layer, document.metadata().transform_to_document(layer)) else {\n\t\t\t\t\tcontinue;\n\t\t\t\t};\n\n\t\t\t\tget_bbox_points(Quad::from_box(bounds), &mut self.bounding_box_points, BBoxSnapValues::ALIGN_ARTBOARD, document);\n\t\t\t}\n\t\t}\n\t\tfor &layer in snap_data.alignment_candidates.map_or([].as_slice(), |candidates| candidates.as_slice()) {\n\t\t\tif snap_data.ignore_bounds(layer) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tlet Some(bounds) = document.metadata().bounding_box_with_transform(layer, DAffine2::IDENTITY) else {\n\t\t\t\tcontinue;\n\t\t\t};\n\n\t\t\tlet quad = document.metadata().transform_to_document(layer) * Quad::from_box(bounds);\n\t\t\tlet values = BBoxSnapValues::ALIGN_BOUNDING_BOX;\n\t\t\tget_bbox_points(quad, &mut self.bounding_box_points, values, document);\n\t\t}\n\t}\n\n\tpub fn snap_bbox_points(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, constraint: SnapConstraint, config: SnapTypeConfiguration) {\n\t\tself.collect_bounding_box_points(snap_data, !config.use_existing_candidates);\n\t\tlet unselected_geometry = if snap_data.document.snapping_state.target_enabled(SnapTarget::Alignment(AlignmentSnapTarget::AlignWithAnchorPoint)) {\n\t\t\tsnap_data.node_snap_cache.map(|cache| cache.unselected.as_slice()).unwrap_or(&[])\n\t\t} else {\n\t\t\t&[]\n\t\t};\n\n\t\t// TODO: snap handle points\n\t\tlet document = snap_data.document;\n\t\tlet tolerance = snap_tolerance(document);\n\t\tlet tolerance_squared = tolerance.powi(2);\n\t\tlet mut snap_x: Option<SnappedPoint> = None;\n\t\tlet mut snap_y: Option<SnappedPoint> = None;\n\n\t\tfor target_point in self.bounding_box_points.iter().chain(unselected_geometry) {\n\t\t\tlet target_position = target_point.document_point;\n\n\t\t\t// Perpendicular snap for line's endpoints\n\t\t\tif let Some(quad) = target_point.quad.map(|q| q.0)\n\t\t\t\t&& quad[0] == quad[3]\n\t\t\t\t&& quad[1] == quad[2]\n\t\t\t\t&& quad[0] == target_point.document_point\n\t\t\t{\n\t\t\t\tlet [p1, p2, ..] = quad;\n\t\t\t\tlet Some(direction) = (p2 - p1).try_normalize() else { return };\n\t\t\t\tlet normal = DVec2::new(-direction.y, direction.x);\n\n\t\t\t\tfor endpoint in [p1, p2] {\n\t\t\t\t\tif let Some(perpendicular_snap) = Quad::intersect_rays(point.document_point, direction, endpoint, normal) {\n\t\t\t\t\t\tlet distance_squared = point.document_point.distance_squared(perpendicular_snap);\n\t\t\t\t\t\tif distance_squared < tolerance_squared {\n\t\t\t\t\t\t\tlet distance = distance_squared.sqrt();\n\t\t\t\t\t\t\tlet distance_to_align_target = perpendicular_snap.distance_squared(endpoint).sqrt();\n\n\t\t\t\t\t\t\tlet snap_point = SnappedPoint {\n\t\t\t\t\t\t\t\tsnapped_point_document: perpendicular_snap,\n\t\t\t\t\t\t\t\tsource: point.source,\n\t\t\t\t\t\t\t\ttarget: SnapTarget::Alignment(AlignmentSnapTarget::PerpendicularToEndpoint),\n\t\t\t\t\t\t\t\ttarget_bounds: Some(Quad(quad)),\n\t\t\t\t\t\t\t\tdistance,\n\t\t\t\t\t\t\t\ttolerance,\n\t\t\t\t\t\t\t\tdistance_to_align_target,\n\t\t\t\t\t\t\t\tfully_constrained: false,\n\t\t\t\t\t\t\t\tat_intersection: true,\n\t\t\t\t\t\t\t\talignment_target_horizontal: Some(endpoint),\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tsnap_results.points.push(snap_point);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tlet [point_on_x, point_on_y] = if let SnapConstraint::Line { origin, direction } = constraint {\n\t\t\t\t[\n\t\t\t\t\tQuad::intersect_rays(target_point.document_point, DVec2::Y, origin, direction),\n\t\t\t\t\tQuad::intersect_rays(target_point.document_point, DVec2::X, origin, direction),\n\t\t\t\t]\n\t\t\t} else {\n\t\t\t\tlet Some(quad) = target_point.quad.map(|quad| quad.0) else { continue };\n\t\t\t\tlet edges = [quad[1] - quad[0], quad[3] - quad[0]];\n\t\t\t\tedges.map(|edge| edge.try_normalize().map(|edge| (point.document_point - target_position).project_onto(edge) + target_position))\n\t\t\t};\n\n\t\t\tlet target_path = matches!(target_point.target, SnapTarget::Path(_));\n\t\t\tlet updated_target = if target_path {\n\t\t\t\tSnapTarget::Alignment(AlignmentSnapTarget::AlignWithAnchorPoint)\n\t\t\t} else {\n\t\t\t\ttarget_point.target\n\t\t\t};\n\n\t\t\tif let Some(point_on_x) = point_on_x {\n\t\t\t\tlet distance_to_snapped = point.document_point.distance(point_on_x);\n\t\t\t\tlet distance_to_align_target = point_on_x.distance(target_position);\n\t\t\t\tif distance_to_snapped < tolerance && snap_x.as_ref().is_none_or(|point| distance_to_align_target < point.distance_to_align_target) {\n\t\t\t\t\tsnap_x = Some(SnappedPoint {\n\t\t\t\t\t\tsnapped_point_document: point_on_x,\n\t\t\t\t\t\tsource: point.source, // TODO(0Hypercube): map source\n\t\t\t\t\t\ttarget: updated_target,\n\t\t\t\t\t\ttarget_bounds: target_point.quad,\n\t\t\t\t\t\tdistance: distance_to_snapped,\n\t\t\t\t\t\ttolerance,\n\t\t\t\t\t\tdistance_to_align_target,\n\t\t\t\t\t\talignment_target_horizontal: Some(target_position),\n\t\t\t\t\t\tfully_constrained: true,\n\t\t\t\t\t\tat_intersection: matches!(constraint, SnapConstraint::Line { .. }),\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\tif let Some(point_on_y) = point_on_y {\n\t\t\t\tlet distance_to_snapped = point.document_point.distance(point_on_y);\n\t\t\t\tlet distance_to_align_target = point_on_y.distance(target_position);\n\t\t\t\tif distance_to_snapped < tolerance && snap_y.as_ref().is_none_or(|point| distance_to_align_target < point.distance_to_align_target) {\n\t\t\t\t\tsnap_y = Some(SnappedPoint {\n\t\t\t\t\t\tsnapped_point_document: point_on_y,\n\t\t\t\t\t\tsource: point.source, // TODO(0Hypercube): map source\n\t\t\t\t\t\ttarget: updated_target,\n\t\t\t\t\t\ttarget_bounds: target_point.quad,\n\t\t\t\t\t\tdistance: distance_to_snapped,\n\t\t\t\t\t\ttolerance,\n\t\t\t\t\t\tdistance_to_align_target,\n\t\t\t\t\t\talignment_target_vertical: Some(target_position),\n\t\t\t\t\t\tfully_constrained: true,\n\t\t\t\t\t\tat_intersection: matches!(constraint, SnapConstraint::Line { .. }),\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tmatch (snap_x, snap_y) {\n\t\t\t(Some(snap_x), Some(snap_y)) if !matches!(constraint, SnapConstraint::Line { .. }) => {\n\t\t\t\tlet intersection = DVec2::new(snap_y.snapped_point_document.x, snap_x.snapped_point_document.y);\n\t\t\t\tlet distance = intersection.distance(point.document_point);\n\n\t\t\t\tif distance >= tolerance {\n\t\t\t\t\tsnap_results.points.push(if snap_x.distance < snap_y.distance { snap_x } else { snap_y });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tsnap_results.points.push(SnappedPoint {\n\t\t\t\t\tsnapped_point_document: intersection,\n\t\t\t\t\tsource: point.source, // TODO: map source\n\t\t\t\t\ttarget: SnapTarget::Alignment(AlignmentSnapTarget::IntersectionPoint),\n\t\t\t\t\ttarget_bounds: snap_x.target_bounds,\n\t\t\t\t\tdistance,\n\t\t\t\t\ttolerance,\n\t\t\t\t\talignment_target_horizontal: snap_x.alignment_target_horizontal,\n\t\t\t\t\talignment_target_vertical: snap_y.alignment_target_vertical,\n\t\t\t\t\tconstrained: true,\n\t\t\t\t\tat_intersection: true,\n\t\t\t\t\t..Default::default()\n\t\t\t\t});\n\t\t\t}\n\t\t\t(Some(snap_x), Some(snap_y)) => snap_results.points.push(if snap_x.distance < snap_y.distance { snap_x } else { snap_y }),\n\t\t\t(Some(snap_x), _) => snap_results.points.push(snap_x),\n\t\t\t(_, Some(snap_y)) => snap_results.points.push(snap_y),\n\t\t\t_ => {}\n\t\t}\n\t}\n\n\tpub fn free_snap(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, config: SnapTypeConfiguration) {\n\t\tlet is_bbox = matches!(point.source, SnapSource::BoundingBox(_));\n\t\tlet is_path = matches!(point.source, SnapSource::Path(_));\n\t\tlet path_selected = snap_data.has_manipulators();\n\n\t\tif is_bbox || (is_path && path_selected) || (is_path && point.alignment) {\n\t\t\tself.snap_bbox_points(snap_data, point, snap_results, SnapConstraint::None, config);\n\t\t}\n\t}\n\n\tpub fn constrained_snap(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, constraint: SnapConstraint, config: SnapTypeConfiguration) {\n\t\tlet is_bbox = matches!(point.source, SnapSource::BoundingBox(_));\n\t\tlet is_path = matches!(point.source, SnapSource::Path(_));\n\t\tlet path_selected = snap_data.has_manipulators();\n\n\t\tif is_bbox || (is_path && path_selected) || (is_path && point.alignment) {\n\t\t\tself.snap_bbox_points(snap_data, point, snap_results, constraint, config);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/snapping/distribution_snapper.rs",
    "content": "use super::*;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::portfolio::document::utility_types::misc::*;\nuse glam::DVec2;\nuse graphene_std::renderer::Quad;\nuse std::collections::VecDeque;\n\n#[derive(Clone, Debug, Default)]\npub struct DistributionSnapper {\n\tright: Vec<Rect>,\n\tleft: Vec<Rect>,\n\tdown: Vec<Rect>,\n\tup: Vec<Rect>,\n}\n\n#[cfg_attr(test, derive(Debug, PartialEq))]\npub struct DistributionMatch {\n\tpub equal: f64,\n\tpub first: f64,\n}\n\nfn dist_right(a: Rect, b: Rect) -> f64 {\n\t-a.max().x + b.min().x\n}\nfn dist_left(a: Rect, b: Rect) -> f64 {\n\ta.min().x - b.max().x\n}\nfn dist_down(a: Rect, b: Rect) -> f64 {\n\t-a.max().y + b.min().y\n}\nfn dist_up(a: Rect, b: Rect) -> f64 {\n\ta.min().y - b.max().y\n}\n\nimpl DistributionSnapper {\n\tfn add_bounds(&mut self, layer: LayerNodeIdentifier, snap_data: &mut SnapData, bbox_to_snap: Rect, max_extent: f64) {\n\t\tlet document = snap_data.document;\n\n\t\tlet Some(bounds) = document.metadata().bounding_box_with_transform(layer, document.metadata().transform_to_document(layer)) else {\n\t\t\treturn;\n\t\t};\n\t\tlet bounds = Rect::from_box(bounds);\n\t\tif bounds.intersects(bbox_to_snap) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet difference = bounds.center() - bbox_to_snap.center();\n\n\t\tlet x_bounds = bbox_to_snap.expand_by(max_extent, 0.);\n\t\tlet y_bounds = bbox_to_snap.expand_by(0., max_extent);\n\n\t\tif x_bounds.intersects(bounds) {\n\t\t\tif difference.x > 0. {\n\t\t\t\tself.right.push(bounds);\n\t\t\t} else {\n\t\t\t\tself.left.push(bounds);\n\t\t\t}\n\t\t} else if y_bounds.intersects(bounds) {\n\t\t\tif difference.y > 0. {\n\t\t\t\tself.down.push(bounds);\n\t\t\t} else {\n\t\t\t\tself.up.push(bounds);\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn collect_bounding_box_points(&mut self, snap_data: &mut SnapData, first_point: bool, bbox_to_snap: Rect) {\n\t\tif !first_point {\n\t\t\treturn;\n\t\t}\n\n\t\tlet document = snap_data.document;\n\n\t\tself.right.clear();\n\t\tself.left.clear();\n\t\tself.down.clear();\n\t\tself.up.clear();\n\n\t\tlet screen_bounds = (document.metadata().document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, snap_data.viewport.size().into()])).bounding_box();\n\t\tlet max_extent = (screen_bounds[1] - screen_bounds[0]).abs().max_element();\n\n\t\t// Collect artboard bounds\n\t\tfor layer in document.metadata().all_layers() {\n\t\t\tif document.network_interface.is_artboard(&layer.to_node(), &[]) && !snap_data.ignore.contains(&layer) {\n\t\t\t\tself.add_bounds(layer, snap_data, bbox_to_snap, max_extent);\n\t\t\t}\n\t\t}\n\n\t\t// Collect alignment candidate bounds\n\t\tfor &layer in snap_data.alignment_candidates.map_or([].as_slice(), |candidates| candidates.as_slice()) {\n\t\t\tif !snap_data.ignore_bounds(layer) {\n\t\t\t\tself.add_bounds(layer, snap_data, bbox_to_snap, max_extent);\n\t\t\t}\n\t\t}\n\n\t\t// Sort and merge intersecting rectangles\n\t\tself.right.sort_unstable_by(|a, b| a.center().x.total_cmp(&b.center().x));\n\t\tself.left.sort_unstable_by(|a, b| b.center().x.total_cmp(&a.center().x));\n\t\tself.down.sort_unstable_by(|a, b| a.center().y.total_cmp(&b.center().y));\n\t\tself.up.sort_unstable_by(|a, b| b.center().y.total_cmp(&a.center().y));\n\n\t\tSelf::merge_intersecting(&mut self.right);\n\t\tSelf::merge_intersecting(&mut self.left);\n\t\tSelf::merge_intersecting(&mut self.down);\n\t\tSelf::merge_intersecting(&mut self.up);\n\t}\n\n\tfn merge_intersecting(rectangles: &mut Vec<Rect>) {\n\t\tlet mut index = 0;\n\t\twhile index < rectangles.len() {\n\t\t\tlet insert_index = index;\n\t\t\tlet mut obelisk = rectangles[index];\n\n\t\t\twhile index + 1 < rectangles.len() && rectangles[index].intersects(rectangles[index + 1]) {\n\t\t\t\tindex += 1;\n\t\t\t\tobelisk = Rect::combine_bounds(obelisk, rectangles[index]);\n\t\t\t}\n\n\t\t\tif index > insert_index {\n\t\t\t\trectangles.insert(insert_index, obelisk);\n\t\t\t\tindex += 1;\n\t\t\t}\n\n\t\t\tindex += 1;\n\t\t}\n\t}\n\n\tfn exact_further_matches(source: Rect, rectangles: &[Rect], dist_fn: fn(Rect, Rect) -> f64, first_dist: f64, depth: u8) -> VecDeque<Rect> {\n\t\tif rectangles.is_empty() || depth > 10 {\n\t\t\treturn VecDeque::from([source]);\n\t\t}\n\n\t\tfor (index, &rect) in rectangles.iter().enumerate() {\n\t\t\tlet next_dist = dist_fn(source, rect);\n\n\t\t\tif (first_dist - next_dist).abs() < 5e-5 * depth as f64 {\n\t\t\t\tlet mut results = Self::exact_further_matches(rect, &rectangles[(index + 1)..], dist_fn, first_dist, depth + 1);\n\t\t\t\tresults.push_front(source);\n\t\t\t\treturn results;\n\t\t\t}\n\t\t}\n\n\t\tVecDeque::from([source])\n\t}\n\n\tfn matches_within_tolerance(source: Rect, rectangles: &[Rect], tolerance: f64, dist_fn: fn(Rect, Rect) -> f64, first_dist: f64) -> Option<(f64, VecDeque<Rect>)> {\n\t\tfor (index, &rect) in rectangles.iter().enumerate() {\n\t\t\tlet next_dist = dist_fn(source, rect);\n\n\t\t\tif (first_dist - next_dist).abs() < tolerance {\n\t\t\t\tlet this_dist = next_dist;\n\t\t\t\tlet results = Self::exact_further_matches(rect, &rectangles[(index + 1)..], dist_fn, this_dist, 2);\n\t\t\t\treturn Some((this_dist, results));\n\t\t\t}\n\t\t}\n\n\t\tNone\n\t}\n\n\tfn top_level_matches(source: Rect, rectangles: &[Rect], tolerance: f64, dist_fn: fn(Rect, Rect) -> f64) -> (Option<DistributionMatch>, VecDeque<Rect>) {\n\t\tif rectangles.is_empty() {\n\t\t\treturn (None, VecDeque::new());\n\t\t}\n\n\t\tlet mut best: Option<(DistributionMatch, Rect, VecDeque<Rect>)> = None;\n\t\tfor (index, &rect) in rectangles.iter().enumerate() {\n\t\t\tlet first_dist = dist_fn(source, rect);\n\n\t\t\tlet Some((equal_dist, results)) = Self::matches_within_tolerance(rect, &rectangles[(index + 1)..], tolerance, dist_fn, first_dist) else {\n\t\t\t\tcontinue;\n\t\t\t};\n\t\t\tif best.as_ref().is_some_and(|(_, _, best)| best.len() >= results.len()) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tbest = Some((DistributionMatch { first: first_dist, equal: equal_dist }, rect, results));\n\t\t}\n\n\t\tif let Some((dist, rect, mut results)) = best {\n\t\t\tresults.push_front(rect);\n\t\t\t(Some(dist), results)\n\t\t} else {\n\t\t\t(None, VecDeque::from([rectangles[0]]))\n\t\t}\n\t}\n\n\tfn snap_bbox_points(&self, tolerance: f64, point: &SnapCandidatePoint, snap_results: &mut SnapResults, constraint: SnapConstraint, bounds: Rect) {\n\t\tlet mut consider_x = true;\n\t\tlet mut consider_y = true;\n\n\t\tif let SnapConstraint::Line { direction, .. } = constraint {\n\t\t\tlet direction = direction.normalize_or_zero();\n\t\t\tconsider_x = direction.x != 0.;\n\t\t\tconsider_y = direction.y != 0.;\n\t\t}\n\n\t\tlet mut snap_x: Option<SnappedPoint> = None;\n\t\tlet mut snap_y: Option<SnappedPoint> = None;\n\n\t\tself.horizontal_snap(consider_x, bounds, tolerance, &mut snap_x, point);\n\t\tself.vertical_snap(consider_y, bounds, tolerance, &mut snap_y, point);\n\n\t\tmatch (snap_x, snap_y) {\n\t\t\t(Some(x), Some(y)) => {\n\t\t\t\tlet x_bounds = Rect::from_box(x.source_bounds.unwrap_or_default().bounding_box());\n\t\t\t\tlet y_bounds = Rect::from_box(y.source_bounds.unwrap_or_default().bounding_box());\n\t\t\t\tlet final_bounds = Rect::from_box([0, 1].map(|index| DVec2::new(x_bounds[index].x, y_bounds[index].y)));\n\n\t\t\t\tlet mut final_point = x;\n\t\t\t\tfinal_point.snapped_point_document += y.snapped_point_document - point.document_point;\n\t\t\t\tfinal_point.source_bounds = Some(final_bounds.into());\n\t\t\t\tfinal_point.target = SnapTarget::DistributeEvenly(DistributionSnapTarget::XY);\n\t\t\t\tfinal_point.distribution_boxes_vertical = y.distribution_boxes_vertical;\n\t\t\t\tfinal_point.distribution_equal_distance_vertical = y.distribution_equal_distance_vertical;\n\t\t\t\tfinal_point.distance = (final_point.distance * final_point.distance + y.distance * y.distance).sqrt();\n\t\t\t\tsnap_results.points.push(final_point);\n\t\t\t}\n\t\t\t(Some(x), None) => snap_results.points.push(x),\n\t\t\t(None, Some(y)) => snap_results.points.push(y),\n\t\t\t(None, None) => {}\n\t\t}\n\t}\n\n\tfn horizontal_snap(&self, consider_x: bool, bounds: Rect, tolerance: f64, snap_x: &mut Option<SnappedPoint>, point: &SnapCandidatePoint) {\n\t\tif !consider_x {\n\t\t\treturn;\n\t\t}\n\n\t\t// Try right distribution first\n\t\tif !self.right.is_empty() {\n\t\t\tlet (equal_dist, mut vec_right) = Self::top_level_matches(bounds, &self.right, tolerance, dist_right);\n\t\t\tif let Some(distances) = equal_dist {\n\t\t\t\tlet translation = DVec2::X * (distances.first - distances.equal);\n\t\t\t\tvec_right.push_front(bounds.translate(translation));\n\n\t\t\t\t// Find matching left distribution\n\t\t\t\tfor &left in Self::exact_further_matches(bounds.translate(translation), &self.left, dist_left, distances.equal, 2).iter().skip(1) {\n\t\t\t\t\tvec_right.push_front(left);\n\t\t\t\t}\n\n\t\t\t\t// Adjust bounds to maintain alignment\n\t\t\t\tif vec_right.len() > 1 {\n\t\t\t\t\tvec_right[0][0].y = vec_right[0][0].y.min(vec_right[1][1].y);\n\t\t\t\t\tvec_right[0][1].y = vec_right[0][1].y.min(vec_right[1][1].y);\n\t\t\t\t}\n\n\t\t\t\t*snap_x = Some(SnappedPoint::distribute(point, DistributionSnapTarget::Right, vec_right, distances, bounds, translation, tolerance));\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Try left distribution if right didn't work\n\t\tif !self.left.is_empty() {\n\t\t\tlet (equal_dist, mut vec_left) = Self::top_level_matches(bounds, &self.left, tolerance, dist_left);\n\t\t\tif let Some(distances) = equal_dist {\n\t\t\t\tlet translation = -DVec2::X * (distances.first - distances.equal);\n\t\t\t\tvec_left.make_contiguous().reverse();\n\t\t\t\tvec_left.push_back(bounds.translate(translation));\n\n\t\t\t\t// Find matching right distribution\n\t\t\t\tfor &right in Self::exact_further_matches(bounds.translate(translation), &self.right, dist_right, distances.equal, 2).iter().skip(1) {\n\t\t\t\t\tvec_left.push_back(right);\n\t\t\t\t}\n\n\t\t\t\t*snap_x = Some(SnappedPoint::distribute(point, DistributionSnapTarget::Left, vec_left, distances, bounds, translation, tolerance));\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Try center distribution if both sides exist\n\t\tif !self.left.is_empty() && !self.right.is_empty() {\n\t\t\tlet target_x = (self.right[0].min() + self.left[0].max()).x / 2.;\n\t\t\tlet offset = target_x - bounds.center().x;\n\n\t\t\tif offset.abs() < tolerance {\n\t\t\t\tlet translation = DVec2::X * offset;\n\t\t\t\tlet equal = bounds.translate(translation).min().x - self.left[0].max().x;\n\t\t\t\tlet first = equal + offset;\n\t\t\t\tlet distances = DistributionMatch { first, equal };\n\n\t\t\t\tlet mut boxes = VecDeque::from([self.left[0], bounds.translate(translation), self.right[0]]);\n\n\t\t\t\t// Adjust bounds to maintain alignment\n\t\t\t\tif boxes.len() > 1 {\n\t\t\t\t\tboxes[1][0].y = boxes[1][0].y.min(boxes[0][1].y);\n\t\t\t\t\tboxes[1][1].y = boxes[1][1].y.min(boxes[0][1].y);\n\t\t\t\t}\n\n\t\t\t\t*snap_x = Some(SnappedPoint::distribute(point, DistributionSnapTarget::X, boxes, distances, bounds, translation, tolerance));\n\t\t\t}\n\t\t}\n\t}\n\n\tfn vertical_snap(&self, consider_y: bool, bounds: Rect, tolerance: f64, snap_y: &mut Option<SnappedPoint>, point: &SnapCandidatePoint) {\n\t\tif !consider_y {\n\t\t\treturn;\n\t\t}\n\n\t\t// Try down distribution first\n\t\tif !self.down.is_empty() {\n\t\t\tlet (equal_dist, mut vec_down) = Self::top_level_matches(bounds, &self.down, tolerance, dist_down);\n\t\t\tif let Some(distances) = equal_dist {\n\t\t\t\tlet translation = DVec2::Y * (distances.first - distances.equal);\n\t\t\t\tvec_down.push_front(bounds.translate(translation));\n\n\t\t\t\t// Find matching up distribution\n\t\t\t\tfor &up in Self::exact_further_matches(bounds.translate(translation), &self.up, dist_up, distances.equal, 2).iter().skip(1) {\n\t\t\t\t\tvec_down.push_front(up);\n\t\t\t\t}\n\n\t\t\t\t// Adjust bounds to maintain alignment\n\t\t\t\tif vec_down.len() > 1 {\n\t\t\t\t\tvec_down[0][0].x = vec_down[0][0].x.min(vec_down[1][1].x);\n\t\t\t\t\tvec_down[0][1].x = vec_down[0][1].x.min(vec_down[1][1].x);\n\t\t\t\t}\n\n\t\t\t\t*snap_y = Some(SnappedPoint::distribute(point, DistributionSnapTarget::Down, vec_down, distances, bounds, translation, tolerance));\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Try up distribution if down didn't work\n\t\tif !self.up.is_empty() {\n\t\t\tlet (equal_dist, mut vec_up) = Self::top_level_matches(bounds, &self.up, tolerance, dist_up);\n\t\t\tif let Some(distances) = equal_dist {\n\t\t\t\tlet translation = -DVec2::Y * (distances.first - distances.equal);\n\t\t\t\tvec_up.make_contiguous().reverse();\n\t\t\t\tvec_up.push_back(bounds.translate(translation));\n\n\t\t\t\t// Find matching down distribution\n\t\t\t\tfor &down in Self::exact_further_matches(bounds.translate(translation), &self.down, dist_down, distances.equal, 2).iter().skip(1) {\n\t\t\t\t\tvec_up.push_back(down);\n\t\t\t\t}\n\n\t\t\t\t*snap_y = Some(SnappedPoint::distribute(point, DistributionSnapTarget::Up, vec_up, distances, bounds, translation, tolerance));\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Try center distribution if both sides exist\n\t\tif !self.up.is_empty() && !self.down.is_empty() {\n\t\t\tlet target_y = (self.down[0].min() + self.up[0].max()).y / 2.;\n\t\t\tlet offset = target_y - bounds.center().y;\n\n\t\t\tif offset.abs() < tolerance {\n\t\t\t\tlet translation = DVec2::Y * offset;\n\t\t\t\tlet equal = bounds.translate(translation).min().y - self.up[0].max().y;\n\t\t\t\tlet first = equal + offset;\n\t\t\t\tlet distances = DistributionMatch { first, equal };\n\n\t\t\t\tlet mut boxes = VecDeque::from([self.up[0], bounds.translate(translation), self.down[0]]);\n\n\t\t\t\t// Adjust bounds to maintain alignment\n\t\t\t\tif boxes.len() > 1 {\n\t\t\t\t\tboxes[1][0].x = boxes[1][0].x.min(boxes[0][1].x);\n\t\t\t\t\tboxes[1][1].x = boxes[1][1].x.min(boxes[0][1].x);\n\t\t\t\t}\n\n\t\t\t\t*snap_y = Some(SnappedPoint::distribute(point, DistributionSnapTarget::Y, boxes, distances, bounds, translation, tolerance));\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn free_snap(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, config: SnapTypeConfiguration) {\n\t\tlet Some(bounds) = config.bbox else { return };\n\t\tif !snap_data.document.snapping_state.bounding_box.distribute_evenly {\n\t\t\treturn;\n\t\t}\n\n\t\tself.collect_bounding_box_points(snap_data, config.accept_distribution, bounds);\n\t\tself.snap_bbox_points(snap_tolerance(snap_data.document), point, snap_results, SnapConstraint::None, bounds);\n\t}\n\n\tpub fn constrained_snap(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, constraint: SnapConstraint, config: SnapTypeConfiguration) {\n\t\tlet Some(bounds) = config.bbox else { return };\n\t\tif !snap_data.document.snapping_state.bounding_box.distribute_evenly {\n\t\t\treturn;\n\t\t}\n\n\t\tself.collect_bounding_box_points(snap_data, config.accept_distribution, bounds);\n\t\tself.snap_bbox_points(snap_tolerance(snap_data.document), point, snap_results, constraint, bounds);\n\t}\n}\n\n#[cfg(test)]\nmod tests {\n\tuse super::*;\n\t#[test]\n\tfn merge_intersecting_test() {\n\t\tlet mut rectangles = vec![Rect::from_square(DVec2::ZERO, 2.), Rect::from_square(DVec2::new(10., 0.), 2.)];\n\t\tDistributionSnapper::merge_intersecting(&mut rectangles);\n\t\tassert_eq!(rectangles.len(), 2);\n\n\t\tlet mut rectangles = vec![\n\t\t\tRect::from_square(DVec2::ZERO, 2.),\n\t\t\tRect::from_square(DVec2::new(1., 0.), 2.),\n\t\t\tRect::from_square(DVec2::new(10., 0.), 2.),\n\t\t\tRect::from_square(DVec2::new(11., 0.), 2.),\n\t\t];\n\t\tDistributionSnapper::merge_intersecting(&mut rectangles);\n\t\tassert_eq!(rectangles.len(), 6);\n\t\tassert_eq!(rectangles[0], Rect::from_box([DVec2::new(-2., -2.), DVec2::new(3., 2.)]));\n\t\tassert_eq!(rectangles[3], Rect::from_box([DVec2::new(8., -2.), DVec2::new(13., 2.)]));\n\t}\n\n\t#[test]\n\tfn dist_simple_2() {\n\t\tlet rectangles = [10., 20.].map(|x| Rect::from_square(DVec2::new(x, 0.), 2.));\n\t\tlet source = Rect::from_square(DVec2::new(0.5, 0.), 2.);\n\t\tlet (offset, rectangles) = DistributionSnapper::top_level_matches(source, &rectangles, 1., dist_right);\n\t\tassert_eq!(offset, Some(DistributionMatch { first: 5.5, equal: 6. }));\n\t\tassert_eq!(rectangles.len(), 2);\n\t}\n\n\t#[test]\n\tfn dist_simple_3() {\n\t\tlet rectangles = [10., 20., 30.].map(|x| Rect::from_square(DVec2::new(x, 0.), 2.));\n\t\tlet source = Rect::from_square(DVec2::new(0.5, 0.), 2.);\n\t\tlet (offset, rectangles) = DistributionSnapper::top_level_matches(source, &rectangles, 1., dist_right);\n\t\tassert_eq!(offset, Some(DistributionMatch { first: 5.5, equal: 6. }));\n\t\tassert_eq!(rectangles.len(), 3);\n\t}\n\n\t#[test]\n\tfn dist_out_of_tolerance() {\n\t\tlet rectangles = [10., 20.].map(|x| Rect::from_square(DVec2::new(x, 0.), 2.));\n\t\tlet source = Rect::from_square(DVec2::new(0.5, 0.), 2.);\n\t\tlet (offset, rectangles) = DistributionSnapper::top_level_matches(source, &rectangles, 0.4, dist_right);\n\t\tassert_eq!(offset, None);\n\t\tassert_eq!(rectangles.len(), 1);\n\t}\n\n\t#[test]\n\tfn dist_with_nonsense() {\n\t\tlet source = Rect::from_square(DVec2::new(0.5, 0.), 2.);\n\t\tlet rectangles = [2., 10., 15., 20.].map(|x| Rect::from_square(DVec2::new(x, 0.), 2.));\n\t\tlet (offset, rectangles) = DistributionSnapper::top_level_matches(source, &rectangles, 1., dist_right);\n\t\tassert_eq!(offset, Some(DistributionMatch { first: 5.5, equal: 6. }));\n\t\tassert_eq!(rectangles.len(), 2);\n\t}\n\n\t#[cfg(test)]\n\tfn assert_boxes_in_order(rectangles: &VecDeque<Rect>, index: usize) {\n\t\tfor (&first, &second) in rectangles.iter().zip(rectangles.iter().skip(1)) {\n\t\t\tassert!(first.max()[index] < second.min()[index], \"{first:?} {second:?} {index}\")\n\t\t}\n\t}\n\n\t#[test]\n\tfn dist_snap_point_right() {\n\t\tlet dist_snapper = DistributionSnapper {\n\t\t\tright: [2., 10., 15., 20.].map(|x| Rect::from_square(DVec2::new(x, 0.), 2.)).to_vec(),\n\t\t\tleft: [-2.].map(|x| Rect::from_square(DVec2::new(x, 0.), 2.)).to_vec(),\n\t\t\t..Default::default()\n\t\t};\n\t\tlet source = Rect::from_square(DVec2::new(0.5, 0.), 2.);\n\t\tlet snap_results = &mut SnapResults::default();\n\t\tdist_snapper.snap_bbox_points(1., &SnapCandidatePoint::default(), snap_results, SnapConstraint::None, source);\n\t\tassert_eq!(snap_results.points.len(), 1);\n\t\tassert_eq!(snap_results.points[0].distance, 0.5);\n\t\tassert_eq!(snap_results.points[0].distribution_equal_distance_horizontal, Some(6.));\n\t\tlet mut expected_box = Rect::from_square(DVec2::new(0., 0.), 2.);\n\t\texpected_box[0].y = expected_box[0].y.min(dist_snapper.left[0][1].y);\n\t\texpected_box[1].y = expected_box[1].y.min(dist_snapper.left[0][1].y);\n\n\t\tassert_eq!(snap_results.points[0].distribution_boxes_horizontal.len(), 3);\n\t\tassert_eq!(snap_results.points[0].distribution_boxes_horizontal[0], expected_box);\n\t\tassert_boxes_in_order(&snap_results.points[0].distribution_boxes_horizontal, 0);\n\t}\n\n\t#[test]\n\tfn dist_snap_point_right_left() {\n\t\tlet dist_snapper = DistributionSnapper {\n\t\t\tright: [2., 10., 15., 20.].map(|x| Rect::from_square(DVec2::new(x, 0.), 2.)).to_vec(),\n\t\t\tleft: [-2., -10., -15., -20.].map(|x| Rect::from_square(DVec2::new(x, 0.), 2.)).to_vec(),\n\t\t\t..Default::default()\n\t\t};\n\n\t\tlet source = Rect::from_square(DVec2::new(0.5, 0.), 2.);\n\t\tlet snap_results = &mut SnapResults::default();\n\t\tdist_snapper.snap_bbox_points(1., &SnapCandidatePoint::default(), snap_results, SnapConstraint::None, source);\n\n\t\tassert_eq!(snap_results.points.len(), 1);\n\t\tassert_eq!(snap_results.points[0].distance, 0.5);\n\t\tassert_eq!(snap_results.points[0].distribution_equal_distance_horizontal, Some(6.));\n\t\tassert_eq!(snap_results.points[0].distribution_boxes_horizontal.len(), 5);\n\n\t\tlet mut expected_left1 = dist_snapper.left[1];\n\t\tlet mut expected_center = Rect::from_square(DVec2::new(0., 0.), 2.);\n\n\t\texpected_center[0].y = expected_center[0].y.min(dist_snapper.left[1][1].y).min(dist_snapper.right[0][1].y);\n\t\texpected_center[1].y = expected_center[1].y.min(dist_snapper.left[1][1].y).min(dist_snapper.right[0][1].y);\n\n\t\texpected_left1[0].y = expected_left1[0].y.min(dist_snapper.left[0][1].y).min(expected_center[1].y);\n\t\texpected_left1[1].y = expected_left1[1].y.min(dist_snapper.left[0][1].y).min(expected_center[1].y);\n\n\t\tassert_eq!(snap_results.points[0].distribution_boxes_horizontal[1], expected_left1);\n\t\tassert_eq!(snap_results.points[0].distribution_boxes_horizontal[2], expected_center);\n\t\tassert_boxes_in_order(&snap_results.points[0].distribution_boxes_horizontal, 0);\n\t}\n\n\t#[test]\n\tfn dist_snap_point_left() {\n\t\tlet dist_snapper = DistributionSnapper {\n\t\t\tleft: [-2., -10., -15., -20.].map(|x| Rect::from_square(DVec2::new(x, 0.), 2.)).to_vec(),\n\t\t\t..Default::default()\n\t\t};\n\t\tlet source = Rect::from_square(DVec2::new(0.5, 0.), 2.);\n\t\tlet snap_results = &mut SnapResults::default();\n\t\tdist_snapper.snap_bbox_points(1., &SnapCandidatePoint::default(), snap_results, SnapConstraint::None, source);\n\t\tassert_eq!(snap_results.points.len(), 1);\n\t\tassert_eq!(snap_results.points[0].distance, 0.5);\n\t\tassert_eq!(snap_results.points[0].distribution_equal_distance_horizontal, Some(6.));\n\t\tassert_eq!(snap_results.points[0].distribution_boxes_horizontal.len(), 3);\n\t\tassert_eq!(snap_results.points[0].distribution_boxes_horizontal[2], Rect::from_square(DVec2::new(0., 0.), 2.));\n\t\tassert_boxes_in_order(&snap_results.points[0].distribution_boxes_horizontal, 0);\n\t}\n\n\t#[test]\n\tfn dist_snap_point_left_right() {\n\t\tlet dist_snapper = DistributionSnapper {\n\t\t\tleft: [-2., -10., -15., -20.].map(|x| Rect::from_square(DVec2::new(x, 0.), 2.)).to_vec(),\n\t\t\tright: [2., 10., 15.].map(|x| Rect::from_square(DVec2::new(x, 0.), 2.)).to_vec(),\n\t\t\t..Default::default()\n\t\t};\n\t\tlet source = Rect::from_square(DVec2::new(0.5, 0.), 2.);\n\t\tlet snap_results = &mut SnapResults::default();\n\t\tdist_snapper.snap_bbox_points(1., &SnapCandidatePoint::default(), snap_results, SnapConstraint::None, source);\n\t\tassert_eq!(snap_results.points.len(), 1);\n\t\tassert_eq!(snap_results.points[0].distance, 0.5);\n\t\tassert_eq!(snap_results.points[0].distribution_equal_distance_horizontal, Some(6.));\n\t\tassert_eq!(snap_results.points[0].distribution_boxes_horizontal.len(), 4);\n\t\tassert_eq!(snap_results.points[0].distribution_boxes_horizontal[2], Rect::from_square(DVec2::new(0., 0.), 2.));\n\t\tassert_boxes_in_order(&snap_results.points[0].distribution_boxes_horizontal, 0);\n\t}\n\n\t#[test]\n\tfn dist_snap_point_center_x() {\n\t\tlet dist_snapper = DistributionSnapper {\n\t\t\tleft: [-10., -15.].map(|x| Rect::from_square(DVec2::new(x, 0.), 2.)).to_vec(),\n\t\t\tright: [10., 15.].map(|x| Rect::from_square(DVec2::new(x, 0.), 2.)).to_vec(),\n\t\t\t..Default::default()\n\t\t};\n\t\tlet source = Rect::from_square(DVec2::new(0.5, 0.), 2.);\n\t\tlet snap_results = &mut SnapResults::default();\n\t\tdist_snapper.snap_bbox_points(1., &SnapCandidatePoint::default(), snap_results, SnapConstraint::None, source);\n\t\tassert_eq!(snap_results.points.len(), 1);\n\t\tassert_eq!(snap_results.points[0].distance, 0.5);\n\t\tassert_eq!(snap_results.points[0].distribution_equal_distance_horizontal, Some(6.));\n\n\t\tlet mut expected_box = Rect::from_square(DVec2::new(0., 0.), 2.);\n\t\texpected_box[0].y = expected_box[0].y.min(dist_snapper.left[0][1].y);\n\t\texpected_box[1].y = expected_box[1].y.min(dist_snapper.left[0][1].y);\n\n\t\tassert_eq!(snap_results.points[0].distribution_boxes_horizontal.len(), 3);\n\t\tassert_eq!(snap_results.points[0].distribution_boxes_horizontal[1], expected_box);\n\t\tassert_boxes_in_order(&snap_results.points[0].distribution_boxes_horizontal, 0);\n\t}\n\n\t// ----------------------------------\n\n\t#[test]\n\tfn dist_snap_point_down() {\n\t\tlet dist_snapper = DistributionSnapper {\n\t\t\tdown: [2., 10., 15., 20.].map(|y| Rect::from_square(DVec2::new(0., y), 2.)).to_vec(),\n\t\t\tup: [-2.].map(|y| Rect::from_square(DVec2::new(0., y), 2.)).to_vec(),\n\t\t\t..Default::default()\n\t\t};\n\t\tlet source = Rect::from_square(DVec2::new(0., 0.5), 2.);\n\t\tlet snap_results = &mut SnapResults::default();\n\t\tdist_snapper.snap_bbox_points(1., &SnapCandidatePoint::default(), snap_results, SnapConstraint::None, source);\n\t\tassert_eq!(snap_results.points.len(), 1);\n\t\tassert_eq!(snap_results.points[0].distance, 0.5);\n\t\tassert_eq!(snap_results.points[0].distribution_equal_distance_vertical, Some(6.));\n\n\t\tlet mut expected_box = Rect::from_square(DVec2::new(0., 0.), 2.);\n\t\texpected_box[0].x = expected_box[0].x.min(dist_snapper.down[0][1].x);\n\t\texpected_box[1].x = expected_box[1].x.min(dist_snapper.down[0][1].x);\n\n\t\tassert_eq!(snap_results.points[0].distribution_boxes_vertical.len(), 3);\n\t\tassert_eq!(snap_results.points[0].distribution_boxes_vertical[0], expected_box);\n\t\tassert_boxes_in_order(&snap_results.points[0].distribution_boxes_vertical, 1);\n\t}\n\n\t#[test]\n\tfn dist_snap_point_down_up() {\n\t\tlet dist_snapper = DistributionSnapper {\n\t\t\tdown: [2., 10., 15., 20.].map(|y| Rect::from_square(DVec2::new(0., y), 2.)).to_vec(),\n\t\t\tup: [-2., -10., -15., -20.].map(|y| Rect::from_square(DVec2::new(0., y), 2.)).to_vec(),\n\t\t\t..Default::default()\n\t\t};\n\t\tlet source = Rect::from_square(DVec2::new(0., 0.5), 2.);\n\t\tlet snap_results = &mut SnapResults::default();\n\t\tdist_snapper.snap_bbox_points(1., &SnapCandidatePoint::default(), snap_results, SnapConstraint::None, source);\n\n\t\tassert_eq!(snap_results.points.len(), 1);\n\t\tassert_eq!(snap_results.points[0].distance, 0.5);\n\t\tassert_eq!(snap_results.points[0].distribution_equal_distance_vertical, Some(6.));\n\t\tassert_eq!(snap_results.points[0].distribution_boxes_vertical.len(), 5);\n\n\t\tlet mut expected_center = Rect::from_square(DVec2::new(0., 0.), 2.);\n\t\texpected_center[0].x = expected_center[0].x.min(dist_snapper.up[1][1].x).min(dist_snapper.down[0][1].x);\n\t\texpected_center[1].x = expected_center[1].x.min(dist_snapper.up[1][1].x).min(dist_snapper.down[0][1].x);\n\n\t\tlet mut expected_up = Rect::from_square(DVec2::new(0., -10.), 2.);\n\t\texpected_up[0].x = expected_up[0].x.min(dist_snapper.up[0][1].x).min(expected_center[0].x);\n\t\texpected_up[1].x = expected_up[1].x.min(dist_snapper.up[0][1].x).min(expected_center[1].x);\n\n\t\tassert_eq!(snap_results.points[0].distribution_boxes_vertical[1], expected_up);\n\t\tassert_eq!(snap_results.points[0].distribution_boxes_vertical[2], expected_center);\n\t\tassert_boxes_in_order(&snap_results.points[0].distribution_boxes_vertical, 1);\n\t}\n\n\t#[test]\n\tfn dist_snap_point_up() {\n\t\tlet dist_snapper = DistributionSnapper {\n\t\t\tup: [-2., -10., -15., -20.].map(|y| Rect::from_square(DVec2::new(0., y), 2.)).to_vec(),\n\t\t\t..Default::default()\n\t\t};\n\t\tlet source = Rect::from_square(DVec2::new(0., 0.5), 2.);\n\t\tlet snap_results = &mut SnapResults::default();\n\t\tdist_snapper.snap_bbox_points(1., &SnapCandidatePoint::default(), snap_results, SnapConstraint::None, source);\n\t\tassert_eq!(snap_results.points.len(), 1);\n\t\tassert_eq!(snap_results.points[0].distance, 0.5);\n\t\tassert_eq!(snap_results.points[0].distribution_equal_distance_vertical, Some(6.));\n\t\tassert_eq!(snap_results.points[0].distribution_boxes_vertical.len(), 3);\n\t\tassert_eq!(snap_results.points[0].distribution_boxes_vertical[2], Rect::from_square(DVec2::new(0., 0.), 2.));\n\t\tassert_boxes_in_order(&snap_results.points[0].distribution_boxes_vertical, 1);\n\t}\n\n\t#[test]\n\tfn dist_snap_point_up_down() {\n\t\tlet dist_snapper = DistributionSnapper {\n\t\t\tup: [-2., -10., -15., -20.].map(|y| Rect::from_square(DVec2::new(0., y), 2.)).to_vec(),\n\t\t\tdown: [2., 10., 15.].map(|y| Rect::from_square(DVec2::new(0., y), 2.)).to_vec(),\n\t\t\t..Default::default()\n\t\t};\n\t\tlet source = Rect::from_square(DVec2::new(0., 0.5), 2.);\n\t\tlet snap_results = &mut SnapResults::default();\n\t\tdist_snapper.snap_bbox_points(1., &SnapCandidatePoint::default(), snap_results, SnapConstraint::None, source);\n\t\tassert_eq!(snap_results.points.len(), 1);\n\t\tassert_eq!(snap_results.points[0].distance, 0.5);\n\t\tassert_eq!(snap_results.points[0].distribution_equal_distance_vertical, Some(6.));\n\t\tassert_eq!(snap_results.points[0].distribution_boxes_vertical.len(), 4);\n\t\tassert_eq!(snap_results.points[0].distribution_boxes_vertical[2], Rect::from_square(DVec2::new(0., 0.), 2.));\n\t\tassert_boxes_in_order(&snap_results.points[0].distribution_boxes_vertical, 1);\n\t}\n\n\t#[test]\n\tfn dist_snap_point_center_y() {\n\t\tlet dist_snapper = DistributionSnapper {\n\t\t\tup: [-10., -15.].map(|y| Rect::from_square(DVec2::new(0., y), 2.)).to_vec(),\n\t\t\tdown: [10., 15.].map(|y| Rect::from_square(DVec2::new(0., y), 2.)).to_vec(),\n\t\t\t..Default::default()\n\t\t};\n\t\tlet source = Rect::from_square(DVec2::new(0., 0.5), 2.);\n\t\tlet snap_results = &mut SnapResults::default();\n\t\tdist_snapper.snap_bbox_points(1., &SnapCandidatePoint::default(), snap_results, SnapConstraint::None, source);\n\n\t\tassert_eq!(snap_results.points.len(), 1);\n\t\tassert_eq!(snap_results.points[0].distance, 0.5);\n\t\tassert_eq!(snap_results.points[0].distribution_equal_distance_vertical, Some(6.));\n\t\tassert_eq!(snap_results.points[0].distribution_boxes_vertical.len(), 3);\n\n\t\tlet mut expected_box = Rect::from_square(DVec2::new(0., 0.), 2.);\n\t\texpected_box[0].x = expected_box[0].x.min(dist_snapper.up[0][1].x).min(dist_snapper.down[0][1].x);\n\t\texpected_box[1].x = expected_box[1].x.min(dist_snapper.up[0][1].x).min(dist_snapper.down[0][1].x);\n\n\t\tassert_eq!(snap_results.points[0].distribution_boxes_vertical[1], expected_box);\n\t\tassert_boxes_in_order(&snap_results.points[0].distribution_boxes_vertical, 1);\n\t}\n\n\t#[test]\n\tfn dist_snap_point_center_xy() {\n\t\tlet dist_snapper = DistributionSnapper {\n\t\t\tup: [-10., -15.].map(|y| Rect::from_square(DVec2::new(0., y), 2.)).to_vec(),\n\t\t\tdown: [10., 15.].map(|y| Rect::from_square(DVec2::new(0., y), 2.)).to_vec(),\n\t\t\tleft: [-12., -15.].map(|x| Rect::from_square(DVec2::new(x, 0.), 2.)).to_vec(),\n\t\t\tright: [12., 15.].map(|x| Rect::from_square(DVec2::new(x, 0.), 2.)).to_vec(),\n\t\t\t..Default::default()\n\t\t};\n\t\tlet source = Rect::from_square(DVec2::new(0.3, 0.4), 2.);\n\t\tlet snap_results = &mut SnapResults::default();\n\t\tdist_snapper.snap_bbox_points(1., &SnapCandidatePoint::default(), snap_results, SnapConstraint::None, source);\n\n\t\tassert_eq!(snap_results.points.len(), 1);\n\t\tassert_eq!(snap_results.points[0].distance, 0.5000000000000001);\n\t\tassert_eq!(snap_results.points[0].distribution_equal_distance_horizontal, Some(8.));\n\t\tassert_eq!(snap_results.points[0].distribution_equal_distance_vertical, Some(6.));\n\t\tassert_eq!(snap_results.points[0].distribution_boxes_horizontal.len(), 3);\n\t\tassert_eq!(snap_results.points[0].distribution_boxes_vertical.len(), 3);\n\n\t\tassert!(snap_results.points[0].distribution_boxes_horizontal[1][0].y <= dist_snapper.left[0][1].y);\n\t\tassert!(snap_results.points[0].distribution_boxes_horizontal[1][1].y <= dist_snapper.left[0][1].y);\n\t\tassert!(snap_results.points[0].distribution_boxes_vertical[1][0].x <= dist_snapper.up[0][1].x);\n\t\tassert!(snap_results.points[0].distribution_boxes_vertical[1][1].x <= dist_snapper.up[0][1].x);\n\n\t\tassert_eq!(Rect::from_box(snap_results.points[0].source_bounds.unwrap().bounding_box()), Rect::from_square(DVec2::new(0., 0.), 2.));\n\t\tassert_boxes_in_order(&snap_results.points[0].distribution_boxes_horizontal, 0);\n\t\tassert_boxes_in_order(&snap_results.points[0].distribution_boxes_vertical, 1);\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/snapping/grid_snapper.rs",
    "content": "use super::*;\nuse crate::messages::portfolio::document::utility_types::misc::{GridSnapTarget, GridSnapping, GridType, SnapTarget};\nuse glam::DVec2;\nuse graphene_std::renderer::Quad;\n\nstruct Line {\n\tpub point: DVec2,\n\tpub direction: DVec2,\n}\n\n#[derive(Clone, Debug, Default)]\n\npub struct GridSnapper;\n\nimpl GridSnapper {\n\t// Rectangular grid has 4 lines around a point, 2 on y axis and 2 on x axis.\n\tfn get_snap_lines_rectangular(&self, document_point: DVec2, snap_data: &mut SnapData, spacing: DVec2) -> Vec<Line> {\n\t\tlet document = snap_data.document;\n\t\tlet mut lines = Vec::new();\n\n\t\tlet Some(spacing) = GridSnapping::compute_rectangle_spacing(spacing, &document.document_ptz) else {\n\t\t\treturn lines;\n\t\t};\n\t\tlet origin = document.snapping_state.grid.origin;\n\t\tfor (direction, perpendicular) in [(DVec2::X, DVec2::Y), (DVec2::Y, DVec2::X)] {\n\t\t\tlines.push(Line {\n\t\t\t\tdirection,\n\t\t\t\tpoint: perpendicular * (((document_point - origin) / spacing).ceil() * spacing + origin),\n\t\t\t});\n\t\t\tlines.push(Line {\n\t\t\t\tdirection,\n\t\t\t\tpoint: perpendicular * (((document_point - origin) / spacing).floor() * spacing + origin),\n\t\t\t});\n\t\t}\n\t\tlines\n\t}\n\n\t// Isometric grid has 6 lines around a point, 2 y axis, 2 on the angle a, and 2 on the angle b.\n\tfn get_snap_lines_isometric(&self, document_point: DVec2, snap_data: &mut SnapData, y_axis_spacing: f64, angle_a: f64, angle_b: f64) -> Vec<Line> {\n\t\tlet document = snap_data.document;\n\t\tlet mut lines = Vec::new();\n\n\t\tlet origin = document.snapping_state.grid.origin;\n\n\t\tlet tan_a = angle_a.to_radians().tan();\n\t\tlet tan_b = angle_b.to_radians().tan();\n\t\tlet spacing = DVec2::new(y_axis_spacing / (tan_a + tan_b), y_axis_spacing);\n\t\tlet Some(spacing_multiplier) = GridSnapping::compute_isometric_multiplier(y_axis_spacing, tan_a + tan_b, &document.document_ptz) else {\n\t\t\treturn lines;\n\t\t};\n\t\tlet spacing = spacing * spacing_multiplier;\n\n\t\tlet x_max = ((document_point.x - origin.x) / spacing.x).ceil() * spacing.x + origin.x;\n\t\tlet x_min = ((document_point.x - origin.x) / spacing.x).floor() * spacing.x + origin.x;\n\t\tlines.push(Line {\n\t\t\tpoint: DVec2::new(x_max, 0.),\n\t\t\tdirection: DVec2::Y,\n\t\t});\n\t\tlines.push(Line {\n\t\t\tpoint: DVec2::new(x_min, 0.),\n\t\t\tdirection: DVec2::Y,\n\t\t});\n\n\t\tlet y_projected_onto_x = document_point.y + tan_a * (document_point.x - origin.x);\n\t\tlet y_onto_x_max = ((y_projected_onto_x - origin.y) / spacing.y).ceil() * spacing.y + origin.y;\n\t\tlet y_onto_x_min = ((y_projected_onto_x - origin.y) / spacing.y).floor() * spacing.y + origin.y;\n\t\tlines.push(Line {\n\t\t\tpoint: DVec2::new(origin.x, y_onto_x_max),\n\t\t\tdirection: DVec2::new(1., -tan_a),\n\t\t});\n\t\tlines.push(Line {\n\t\t\tpoint: DVec2::new(origin.x, y_onto_x_min),\n\t\t\tdirection: DVec2::new(1., -tan_a),\n\t\t});\n\n\t\tlet y_projected_onto_z = document_point.y - tan_b * (document_point.x - origin.x);\n\t\tlet y_onto_z_max = ((y_projected_onto_z - origin.y) / spacing.y).ceil() * spacing.y + origin.y;\n\t\tlet y_onto_z_min = ((y_projected_onto_z - origin.y) / spacing.y).floor() * spacing.y + origin.y;\n\t\tlines.push(Line {\n\t\t\tpoint: DVec2::new(origin.x, y_onto_z_max),\n\t\t\tdirection: DVec2::new(1., tan_b),\n\t\t});\n\t\tlines.push(Line {\n\t\t\tpoint: DVec2::new(origin.x, y_onto_z_min),\n\t\t\tdirection: DVec2::new(1., tan_b),\n\t\t});\n\n\t\tlines\n\t}\n\n\tfn get_snap_lines(&self, document_point: DVec2, snap_data: &mut SnapData) -> Vec<Line> {\n\t\tmatch snap_data.document.snapping_state.grid.grid_type {\n\t\t\tGridType::Rectangular { spacing } => self.get_snap_lines_rectangular(document_point, snap_data, spacing),\n\t\t\tGridType::Isometric { y_axis_spacing, angle_a, angle_b } => self.get_snap_lines_isometric(document_point, snap_data, y_axis_spacing, angle_a, angle_b),\n\t\t}\n\t}\n\n\tpub fn free_snap(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults) {\n\t\tlet lines = self.get_snap_lines(point.document_point, snap_data);\n\t\tlet tolerance = snap_tolerance(snap_data.document);\n\n\t\tfor line in lines {\n\t\t\tlet projected = (point.document_point - line.point).project_onto(line.direction) + line.point;\n\t\t\tlet distance = point.document_point.distance(projected);\n\t\t\tif !distance.is_finite() {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif distance > tolerance {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif snap_data.document.snapping_state.target_enabled(SnapTarget::Grid(GridSnapTarget::Line))\n\t\t\t\t|| snap_data.document.snapping_state.target_enabled(SnapTarget::Grid(GridSnapTarget::Intersection))\n\t\t\t{\n\t\t\t\tsnap_results.grid_lines.push(SnappedLine {\n\t\t\t\t\tdirection: line.direction,\n\t\t\t\t\tpoint: SnappedPoint {\n\t\t\t\t\t\tsnapped_point_document: projected,\n\t\t\t\t\t\tsource: point.source,\n\t\t\t\t\t\ttarget: SnapTarget::Grid(GridSnapTarget::Line),\n\t\t\t\t\t\tsource_bounds: point.quad,\n\t\t\t\t\t\tdistance,\n\t\t\t\t\t\ttolerance,\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tlet normal_target = SnapTarget::Grid(GridSnapTarget::LineNormal);\n\t\t\tif snap_data.document.snapping_state.target_enabled(normal_target) {\n\t\t\t\tfor &neighbor in &point.neighbors {\n\t\t\t\t\tlet projected = (neighbor - line.point).project_onto(line.direction) + line.point;\n\t\t\t\t\tlet distance = point.document_point.distance(projected);\n\t\t\t\t\tif distance > tolerance {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tsnap_results.points.push(SnappedPoint {\n\t\t\t\t\t\tsnapped_point_document: projected,\n\t\t\t\t\t\tsource: point.source,\n\t\t\t\t\t\tsource_bounds: point.quad,\n\t\t\t\t\t\ttarget: normal_target,\n\t\t\t\t\t\tdistance,\n\t\t\t\t\t\ttolerance,\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn constrained_snap(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, constraint: SnapConstraint) {\n\t\tlet tolerance = snap_tolerance(snap_data.document);\n\t\tlet projected = constraint.projection(point.document_point);\n\t\tlet lines = self.get_snap_lines(projected, snap_data);\n\t\tlet (constraint_start, constraint_direction) = match constraint {\n\t\t\tSnapConstraint::Line { origin, direction } => (origin, direction.normalize_or_zero()),\n\t\t\tSnapConstraint::Direction(direction) => (projected, direction.normalize_or_zero()),\n\t\t\t_ => unimplemented!(),\n\t\t};\n\t\tfor line in lines {\n\t\t\tlet Some(intersection) = Quad::intersect_rays(line.point, line.direction, constraint_start, constraint_direction) else {\n\t\t\t\tcontinue;\n\t\t\t};\n\t\t\tlet distance = intersection.distance(point.document_point);\n\t\t\tif distance < tolerance && snap_data.document.snapping_state.target_enabled(SnapTarget::Grid(GridSnapTarget::Line)) {\n\t\t\t\tsnap_results.points.push(SnappedPoint {\n\t\t\t\t\tsnapped_point_document: intersection,\n\t\t\t\t\tsource: point.source,\n\t\t\t\t\ttarget: SnapTarget::Grid(GridSnapTarget::Line),\n\t\t\t\t\tat_intersection: false,\n\t\t\t\t\tconstrained: true,\n\t\t\t\t\tsource_bounds: point.quad,\n\t\t\t\t\tdistance,\n\t\t\t\t\ttolerance,\n\t\t\t\t\t..Default::default()\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/snapping/layer_snapper.rs",
    "content": "use super::*;\nuse crate::consts::HIDE_HANDLE_DISTANCE;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::portfolio::document::utility_types::misc::*;\nuse crate::messages::prelude::*;\nuse glam::{DAffine2, DVec2, FloatExt};\nuse graphene_std::math::math_ext::QuadExt;\nuse graphene_std::renderer::Quad;\nuse graphene_std::subpath::pathseg_points;\nuse graphene_std::subpath::{Identifier, ManipulatorGroup, Subpath};\nuse graphene_std::vector::PointId;\nuse graphene_std::vector::algorithms::bezpath_algorithms::{pathseg_normals_to_point, pathseg_tangents_to_point};\nuse graphene_std::vector::algorithms::intersection::filtered_segment_intersections;\nuse graphene_std::vector::misc::dvec2_to_point;\nuse graphene_std::vector::misc::point_to_dvec2;\nuse kurbo::{Affine, ParamCurve, PathSeg};\n\n#[derive(Clone, Debug, Default)]\npub struct LayerSnapper {\n\tpoints_to_snap: Vec<SnapCandidatePoint>,\n\tpaths_to_snap: Vec<SnapCandidatePath>,\n}\nimpl LayerSnapper {\n\tpub fn add_layer_bounds(&mut self, document: &DocumentMessageHandler, layer: LayerNodeIdentifier, target: SnapTarget) {\n\t\tif !document.snapping_state.target_enabled(target) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet bounds = if document.network_interface.is_artboard(&layer.to_node(), &[]) {\n\t\t\tdocument\n\t\t\t\t.metadata()\n\t\t\t\t.bounding_box_with_transform(layer, document.metadata().transform_to_document(layer))\n\t\t\t\t.map(Quad::from_box)\n\t\t} else {\n\t\t\tdocument\n\t\t\t\t.metadata()\n\t\t\t\t.bounding_box_with_transform(layer, DAffine2::IDENTITY)\n\t\t\t\t.map(|bounds| document.metadata().transform_to_document(layer) * Quad::from_box(bounds))\n\t\t};\n\t\tlet Some(bounds) = bounds else { return };\n\n\t\tif bounds.0.iter().any(|point| !point.is_finite()) {\n\t\t\treturn;\n\t\t}\n\n\t\tfor document_curve in bounds.to_lines() {\n\t\t\tself.paths_to_snap.push(SnapCandidatePath {\n\t\t\t\tdocument_curve,\n\t\t\t\tlayer,\n\t\t\t\tstart: PointId::new(),\n\t\t\t\ttarget,\n\t\t\t\tbounds: Some(bounds),\n\t\t\t});\n\t\t}\n\t}\n\n\tpub fn collect_paths(&mut self, snap_data: &mut SnapData, first_point: bool) {\n\t\tif !first_point {\n\t\t\treturn;\n\t\t}\n\t\tlet document = snap_data.document;\n\t\tself.paths_to_snap.clear();\n\n\t\tfor layer in document.metadata().all_layers() {\n\t\t\tif !document.network_interface.is_artboard(&layer.to_node(), &[]) || snap_data.ignore.contains(&layer) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tself.add_layer_bounds(document, layer, SnapTarget::Artboard(ArtboardSnapTarget::AlongEdge));\n\t\t}\n\t\tfor &layer in snap_data.get_candidates() {\n\t\t\tlet transform = document.metadata().transform_to_document(layer);\n\t\t\tif !transform.is_finite() {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif document.snapping_state.target_enabled(SnapTarget::Path(PathSnapTarget::IntersectionPoint)) || document.snapping_state.target_enabled(SnapTarget::Path(PathSnapTarget::AlongPath)) {\n\t\t\t\tfor subpath in document.metadata().layer_outline(layer) {\n\t\t\t\t\tfor (start_index, curve) in subpath.iter().enumerate() {\n\t\t\t\t\t\tlet document_curve = Affine::new(transform.to_cols_array()) * curve;\n\t\t\t\t\t\tlet start = subpath.manipulator_groups()[start_index].id;\n\t\t\t\t\t\tif snap_data.ignore_manipulator(layer, start) || snap_data.ignore_manipulator(layer, subpath.manipulator_groups()[(start_index + 1) % subpath.len()].id) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tself.paths_to_snap.push(SnapCandidatePath {\n\t\t\t\t\t\t\tdocument_curve,\n\t\t\t\t\t\t\tlayer,\n\t\t\t\t\t\t\tstart,\n\t\t\t\t\t\t\ttarget: SnapTarget::Path(PathSnapTarget::AlongPath),\n\t\t\t\t\t\t\tbounds: None,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn free_snap_paths(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, config: SnapTypeConfiguration) {\n\t\tself.collect_paths(snap_data, !config.use_existing_candidates);\n\n\t\tlet document = snap_data.document;\n\t\tlet normals = document.snapping_state.target_enabled(SnapTarget::Path(PathSnapTarget::NormalToPath));\n\t\tlet tangents = document.snapping_state.target_enabled(SnapTarget::Path(PathSnapTarget::TangentToPath));\n\t\tlet tolerance = snap_tolerance(document);\n\n\t\tfor path in &self.paths_to_snap {\n\t\t\t// Skip very short paths\n\t\t\tif path.document_curve.start().distance_squared(path.document_curve.end()) < tolerance * tolerance * 2. {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tlet Some((distance_squared, closest)) = path.approx_nearest_point(point.document_point, 10) else {\n\t\t\t\tcontinue;\n\t\t\t};\n\t\t\tlet snapped_point_document = point_to_dvec2(closest);\n\t\t\tlet distance = distance_squared.sqrt();\n\n\t\t\tif distance < tolerance {\n\t\t\t\tsnap_results.curves.push(SnappedCurve {\n\t\t\t\t\tlayer: path.layer,\n\t\t\t\t\tstart: path.start,\n\t\t\t\t\tdocument_curve: path.document_curve,\n\t\t\t\t\tpoint: SnappedPoint {\n\t\t\t\t\t\tsnapped_point_document,\n\t\t\t\t\t\ttarget: path.target,\n\t\t\t\t\t\tdistance,\n\t\t\t\t\t\ttolerance,\n\t\t\t\t\t\toutline_layers: [path.bounds.is_none().then_some(path.layer), None],\n\t\t\t\t\t\tsource: point.source,\n\t\t\t\t\t\ttarget_bounds: path.bounds,\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t\tnormals_and_tangents(path, normals, tangents, point, tolerance, snap_results);\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn snap_paths_constrained(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, constraint: SnapConstraint, config: SnapTypeConfiguration) {\n\t\tlet document = snap_data.document;\n\t\tself.collect_paths(snap_data, !config.use_existing_candidates);\n\n\t\tlet tolerance = snap_tolerance(document);\n\t\tlet constraint_path = if let SnapConstraint::Circle { center, radius } = constraint {\n\t\t\tSubpath::new_ellipse(center - DVec2::splat(radius), center + DVec2::splat(radius))\n\t\t} else {\n\t\t\tlet constrained_point = constraint.projection(point.document_point);\n\t\t\tlet direction = constraint.direction().normalize_or_zero();\n\t\t\tlet start = constrained_point - tolerance * direction;\n\t\t\tlet end = constrained_point + tolerance * direction;\n\t\t\tSubpath::<PointId>::new_line(start, end)\n\t\t};\n\n\t\tfor path in &self.paths_to_snap {\n\t\t\tfor constraint_path in constraint_path.iter() {\n\t\t\t\tfor time in filtered_segment_intersections(path.document_curve, constraint_path, None, None) {\n\t\t\t\t\tlet snapped_point_document = point_to_dvec2(path.document_curve.eval(time));\n\n\t\t\t\t\tlet distance = snapped_point_document.distance(point.document_point);\n\n\t\t\t\t\tif distance < tolerance {\n\t\t\t\t\t\tsnap_results.points.push(SnappedPoint {\n\t\t\t\t\t\t\tsnapped_point_document,\n\t\t\t\t\t\t\ttarget: path.target,\n\t\t\t\t\t\t\tdistance,\n\t\t\t\t\t\t\ttolerance,\n\t\t\t\t\t\t\toutline_layers: [path.bounds.is_none().then_some(path.layer), None],\n\t\t\t\t\t\t\tsource: point.source,\n\t\t\t\t\t\t\ttarget_bounds: path.bounds,\n\t\t\t\t\t\t\tat_intersection: true,\n\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn collect_anchors(&mut self, snap_data: &mut SnapData, first_point: bool) {\n\t\tif !first_point {\n\t\t\treturn;\n\t\t}\n\t\tlet document = snap_data.document;\n\t\tself.points_to_snap.clear();\n\n\t\tfor layer in document.metadata().all_layers() {\n\t\t\tif !document.network_interface.is_artboard(&layer.to_node(), &[]) || snap_data.ignore.contains(&layer) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif self.points_to_snap.len() >= crate::consts::MAX_LAYER_SNAP_POINTS {\n\t\t\t\twarn!(\"Snap point overflow; skipping.\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif document.snapping_state.target_enabled(SnapTarget::Artboard(ArtboardSnapTarget::CornerPoint)) {\n\t\t\t\tlet Some(bounds) = document\n\t\t\t\t\t.network_interface\n\t\t\t\t\t.document_metadata()\n\t\t\t\t\t.bounding_box_with_transform(layer, document.metadata().transform_to_document(layer))\n\t\t\t\telse {\n\t\t\t\t\tcontinue;\n\t\t\t\t};\n\n\t\t\t\tget_bbox_points(Quad::from_box(bounds), &mut self.points_to_snap, BBoxSnapValues::ARTBOARD, document);\n\t\t\t}\n\t\t}\n\t\tfor &layer in snap_data.get_candidates() {\n\t\t\tget_layer_snap_points(layer, snap_data, &mut self.points_to_snap);\n\n\t\t\tif snap_data.ignore_bounds(layer) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif self.points_to_snap.len() >= crate::consts::MAX_LAYER_SNAP_POINTS {\n\t\t\t\twarn!(\"Snap point overflow; skipping.\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tlet Some(bounds) = document.metadata().bounding_box_with_transform(layer, DAffine2::IDENTITY) else {\n\t\t\t\tcontinue;\n\t\t\t};\n\t\t\tlet quad = document.metadata().transform_to_document(layer) * Quad::from_box(bounds);\n\t\t\tlet values = BBoxSnapValues::BOUNDING_BOX;\n\t\t\tget_bbox_points(quad, &mut self.points_to_snap, values, document);\n\t\t}\n\t}\n\n\tpub fn snap_anchors(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, c: SnapConstraint, constrained_point: DVec2) {\n\t\tlet mut best = None;\n\t\tfor candidate in &self.points_to_snap {\n\t\t\t// Candidate is not on constraint\n\t\t\tif !candidate.document_point.abs_diff_eq(c.projection(candidate.document_point), 1e-5) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tlet distance = candidate.document_point.distance(constrained_point);\n\t\t\tlet tolerance = snap_tolerance(snap_data.document);\n\n\t\t\tlet candidate_better = |best: &SnappedPoint| {\n\t\t\t\tif best.snapped_point_document.abs_diff_eq(candidate.document_point, 1e-5) {\n\t\t\t\t\t!candidate.target.bounding_box()\n\t\t\t\t} else {\n\t\t\t\t\tdistance < best.distance\n\t\t\t\t}\n\t\t\t};\n\t\t\tif distance < tolerance && (best.is_none() || best.as_ref().is_some_and(candidate_better)) {\n\t\t\t\tbest = Some(SnappedPoint {\n\t\t\t\t\tsnapped_point_document: candidate.document_point,\n\t\t\t\t\tsource: point.source,\n\t\t\t\t\ttarget: candidate.target,\n\t\t\t\t\tdistance,\n\t\t\t\t\ttolerance,\n\t\t\t\t\tconstrained: true,\n\t\t\t\t\ttarget_bounds: candidate.quad,\n\t\t\t\t\toutline_layers: [candidate.outline_layer, None],\n\t\t\t\t\t..Default::default()\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tif let Some(result) = best {\n\t\t\tsnap_results.points.push(result);\n\t\t}\n\t}\n\n\tpub fn free_snap(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, config: SnapTypeConfiguration) {\n\t\tself.collect_anchors(snap_data, !config.use_existing_candidates);\n\t\tself.snap_anchors(snap_data, point, snap_results, SnapConstraint::None, point.document_point);\n\t\tself.free_snap_paths(snap_data, point, snap_results, config);\n\t}\n\n\tpub fn constrained_snap(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, constraint: SnapConstraint, config: SnapTypeConfiguration) {\n\t\tself.collect_anchors(snap_data, !config.use_existing_candidates);\n\t\tself.snap_anchors(snap_data, point, snap_results, constraint, constraint.projection(point.document_point));\n\t\tself.snap_paths_constrained(snap_data, point, snap_results, constraint, config);\n\t}\n}\n\nfn normals_and_tangents(path: &SnapCandidatePath, normals: bool, tangents: bool, point: &SnapCandidatePoint, tolerance: f64, snap_results: &mut SnapResults) {\n\tif normals && path.bounds.is_none() {\n\t\tfor &neighbor in &point.neighbors {\n\t\t\tfor t in pathseg_normals_to_point(path.document_curve, dvec2_to_point(neighbor)) {\n\t\t\t\tlet normal_point = point_to_dvec2(path.document_curve.eval(t));\n\t\t\t\tlet distance = normal_point.distance(point.document_point);\n\t\t\t\tif distance > tolerance {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tsnap_results.points.push(SnappedPoint {\n\t\t\t\t\tsnapped_point_document: normal_point,\n\t\t\t\t\ttarget: SnapTarget::Path(PathSnapTarget::NormalToPath),\n\t\t\t\t\tdistance,\n\t\t\t\t\ttolerance,\n\t\t\t\t\toutline_layers: [Some(path.layer), None],\n\t\t\t\t\tsource: point.source,\n\t\t\t\t\tconstrained: true,\n\t\t\t\t\t..Default::default()\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\tif tangents && path.bounds.is_none() {\n\t\tfor &neighbor in &point.neighbors {\n\t\t\tfor t in pathseg_tangents_to_point(path.document_curve, dvec2_to_point(neighbor)) {\n\t\t\t\tlet tangent_point = point_to_dvec2(path.document_curve.eval(t));\n\t\t\t\tlet distance = tangent_point.distance(point.document_point);\n\t\t\t\tif distance > tolerance {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tsnap_results.points.push(SnappedPoint {\n\t\t\t\t\tsnapped_point_document: tangent_point,\n\t\t\t\t\ttarget: SnapTarget::Path(PathSnapTarget::TangentToPath),\n\t\t\t\t\tdistance,\n\t\t\t\t\ttolerance,\n\t\t\t\t\toutline_layers: [Some(path.layer), None],\n\t\t\t\t\tsource: point.source,\n\t\t\t\t\tconstrained: true,\n\t\t\t\t\t..Default::default()\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n}\n\n#[derive(Clone, Debug)]\nstruct SnapCandidatePath {\n\tdocument_curve: PathSeg,\n\tlayer: LayerNodeIdentifier,\n\tstart: PointId,\n\ttarget: SnapTarget,\n\tbounds: Option<Quad>,\n}\n\nimpl SnapCandidatePath {\n\t/// Calculates the point on the curve which lies closest to `point`.\n\t///\n\t/// ## Algorithm:\n\t/// 1. We first perform a coarse scan of the path segment to find the most promising starting point.\n\t/// 2. Afterwards we refine this point by performing a binary search to either side assuming that the segment contains at most one extremal point.\n\t/// 3. The smaller of the two resulting distances is returned.\n\t///\n\t/// ## Visualization:\n\t/// ```text\n\t///        Query Point (×)\n\t///             ×\n\t///            /|\\\n\t///           / | \\  distance checks\n\t///          /  |  \\\n\t///         v   v   v\n\t///     ●---●---●---●---●  <- Curve with coarse scan points\n\t///     0  0.25 0.5 0.75 1  (parameter t values)\n\t///         ^       ^\n\t///         |   |   |\n\t///        min mid max\n\t///    Find closest scan point\n\t///\n\t///    Refine left region using binary search:\n\t///\n\t///            ●------●------●\n\t///           0.25  0.375   0.5\n\t///\n\t///    Result:           |     (=0.4)\n\t///    And the right region:\n\t///\n\t///            ●------●------●\n\t///           0.5   0.625   0.75\n\t///    Result: |               (=0.5)\n\t///\n\t///    The t value with minimal dist is thus 0.4\n\t///    Return: (dist_closest, point_on_curve)\n\t/// ```\n\tpub fn approx_nearest_point(&self, point: DVec2, lut_steps: usize) -> Option<(f64, kurbo::Point)> {\n\t\tlet point = dvec2_to_point(point);\n\n\t\tlet time_values = (0..lut_steps).map(|x| x as f64 / lut_steps as f64);\n\t\tlet points = time_values.map(|t| (t, self.document_curve.eval(t)));\n\t\tlet points_with_distances = points.map(|(t, p)| (t, p.distance_squared(point), p));\n\t\tlet (t, _, _) = points_with_distances.min_by(|(_, a, _), (_, b, _)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))?;\n\n\t\tlet min_t = (t - (lut_steps as f64).recip()).max(0.);\n\t\tlet max_t = (t + (lut_steps as f64).recip()).min(1.);\n\t\tlet left = self.refine_nearest_point(point, min_t, t);\n\t\tlet right = self.refine_nearest_point(point, t, max_t);\n\n\t\tif left.0 < right.0 { Some(left) } else { Some(right) }\n\t}\n\n\t/// Refines the nearest point search within a given parameter range using binary search.\n\t///\n\t/// This method performs iterative refinement by:\n\t/// 1. Evaluating the midpoint of the current parameter range\n\t/// 2. Comparing distances at the endpoints and midpoint\n\t/// 3. Narrowing the search range to the side with the shorter distance\n\t/// 4. Continuing until convergence (when the range becomes very small)\n\t///\n\t/// Returns a tuple of (parameter_t, closest_point) where parameter_t is in the range [min_t, max_t].\n\tfn refine_nearest_point(&self, point: kurbo::Point, mut min_t: f64, mut max_t: f64) -> (f64, kurbo::Point) {\n\t\tlet mut min_dist = self.document_curve.eval(min_t).distance_squared(point);\n\t\tlet mut max_dist = self.document_curve.eval(max_t).distance_squared(point);\n\t\tlet mut mid_t = max_t.lerp(min_t, 0.5);\n\t\tlet mut mid_point = self.document_curve.eval(mid_t);\n\t\tlet mut mid_dist = mid_point.distance_squared(point);\n\n\t\tfor _ in 0..10 {\n\t\t\tif (min_dist - max_dist).abs() < 1e-3 {\n\t\t\t\treturn (mid_dist, mid_point);\n\t\t\t}\n\t\t\tif mid_dist > min_dist && mid_dist > max_dist {\n\t\t\t\treturn (mid_dist, mid_point);\n\t\t\t}\n\t\t\tif max_dist > min_dist {\n\t\t\t\tmax_t = mid_t;\n\t\t\t\tmax_dist = mid_dist;\n\t\t\t} else {\n\t\t\t\tmin_t = mid_t;\n\t\t\t\tmin_dist = mid_dist;\n\t\t\t}\n\t\t\tmid_t = max_t.lerp(min_t, 0.5);\n\t\t\tmid_point = self.document_curve.eval(mid_t);\n\t\t\tmid_dist = mid_point.distance_squared(point);\n\t\t}\n\n\t\t(mid_dist, mid_point)\n\t}\n}\n\n#[derive(Clone, Debug, Default)]\npub struct SnapCandidatePoint {\n\tpub document_point: DVec2,\n\tpub source: SnapSource,\n\tpub target: SnapTarget,\n\tpub quad: Option<Quad>,\n\t/// This layer is outlined if the snap candidate is used.\n\tpub outline_layer: Option<LayerNodeIdentifier>,\n\tpub neighbors: Vec<DVec2>,\n\tpub alignment: bool,\n}\nimpl SnapCandidatePoint {\n\tpub fn new(document_point: DVec2, source: SnapSource, target: SnapTarget, outline_layer: Option<LayerNodeIdentifier>) -> Self {\n\t\tSelf::new_quad(document_point, source, target, None, outline_layer, true)\n\t}\n\n\tpub fn new_quad(document_point: DVec2, source: SnapSource, target: SnapTarget, quad: Option<Quad>, outline_layer: Option<LayerNodeIdentifier>, alignment: bool) -> Self {\n\t\tSelf {\n\t\t\tdocument_point,\n\t\t\tsource,\n\t\t\ttarget,\n\t\t\tquad,\n\t\t\toutline_layer,\n\t\t\talignment,\n\t\t\t..Default::default()\n\t\t}\n\t}\n\n\tpub fn new_source(document_point: DVec2, source: SnapSource) -> Self {\n\t\tSelf::new(document_point, source, SnapTarget::None, None)\n\t}\n\n\tpub fn handle(document_point: DVec2) -> Self {\n\t\tSelf::new_source(document_point, SnapSource::Path(PathSnapSource::AnchorPointWithFreeHandles))\n\t}\n\n\tpub fn gradient_handle(document_point: DVec2) -> Self {\n\t\tSelf::new_source(document_point, SnapSource::Gradient(GradientSnapSource::Endpoint))\n\t}\n\n\tpub fn handle_neighbors(document_point: DVec2, neighbors: impl Into<Vec<DVec2>>) -> Self {\n\t\tlet mut point = Self::new_source(document_point, SnapSource::Path(PathSnapSource::AnchorPointWithFreeHandles));\n\t\tpoint.neighbors = neighbors.into();\n\t\tpoint\n\t}\n}\n\n#[derive(Default)]\npub struct BBoxSnapValues {\n\tcorner_source: SnapSource,\n\tcorner_target: SnapTarget,\n\tedge_source: SnapSource,\n\tedge_target: SnapTarget,\n\tcenter_source: SnapSource,\n\tcenter_target: SnapTarget,\n}\nimpl BBoxSnapValues {\n\tpub const BOUNDING_BOX: Self = Self {\n\t\tcorner_source: SnapSource::BoundingBox(BoundingBoxSnapSource::CornerPoint),\n\t\tcorner_target: SnapTarget::BoundingBox(BoundingBoxSnapTarget::CornerPoint),\n\t\tedge_source: SnapSource::BoundingBox(BoundingBoxSnapSource::EdgeMidpoint),\n\t\tedge_target: SnapTarget::BoundingBox(BoundingBoxSnapTarget::EdgeMidpoint),\n\t\tcenter_source: SnapSource::BoundingBox(BoundingBoxSnapSource::CenterPoint),\n\t\tcenter_target: SnapTarget::BoundingBox(BoundingBoxSnapTarget::CenterPoint),\n\t};\n\n\tpub const ARTBOARD: Self = Self {\n\t\tcorner_source: SnapSource::Artboard(ArtboardSnapSource::CornerPoint),\n\t\tcorner_target: SnapTarget::Artboard(ArtboardSnapTarget::CornerPoint),\n\t\tedge_source: SnapSource::None,\n\t\tedge_target: SnapTarget::None,\n\t\tcenter_source: SnapSource::Artboard(ArtboardSnapSource::CenterPoint),\n\t\tcenter_target: SnapTarget::Artboard(ArtboardSnapTarget::CenterPoint),\n\t};\n\n\tpub const ALIGN_BOUNDING_BOX: Self = Self {\n\t\tcorner_source: SnapSource::Alignment(AlignmentSnapSource::BoundingBoxCornerPoint),\n\t\tcorner_target: SnapTarget::Alignment(AlignmentSnapTarget::BoundingBoxCornerPoint),\n\t\tedge_source: SnapSource::None,\n\t\tedge_target: SnapTarget::None,\n\t\tcenter_source: SnapSource::Alignment(AlignmentSnapSource::BoundingBoxCenterPoint),\n\t\tcenter_target: SnapTarget::Alignment(AlignmentSnapTarget::BoundingBoxCenterPoint),\n\t};\n\n\tpub const ALIGN_ARTBOARD: Self = Self {\n\t\tcorner_source: SnapSource::Alignment(AlignmentSnapSource::ArtboardCornerPoint),\n\t\tcorner_target: SnapTarget::Alignment(AlignmentSnapTarget::ArtboardCornerPoint),\n\t\tedge_source: SnapSource::None,\n\t\tedge_target: SnapTarget::None,\n\t\tcenter_source: SnapSource::Alignment(AlignmentSnapSource::ArtboardCenterPoint),\n\t\tcenter_target: SnapTarget::Alignment(AlignmentSnapTarget::ArtboardCenterPoint),\n\t};\n}\n\npub fn get_bbox_points(quad: Quad, points: &mut Vec<SnapCandidatePoint>, values: BBoxSnapValues, document: &DocumentMessageHandler) {\n\tfor index in 0..4 {\n\t\tlet start = quad.0[index];\n\t\tlet end = quad.0[(index + 1) % 4];\n\t\tif document.snapping_state.target_enabled(values.corner_target) {\n\t\t\tpoints.push(SnapCandidatePoint::new_quad(start, values.corner_source, values.corner_target, Some(quad), None, false));\n\t\t}\n\t\tif document.snapping_state.target_enabled(values.edge_target) {\n\t\t\tpoints.push(SnapCandidatePoint::new_quad((start + end) / 2., values.edge_source, values.edge_target, Some(quad), None, false));\n\t\t}\n\t}\n\n\tif document.snapping_state.target_enabled(values.center_target) {\n\t\tpoints.push(SnapCandidatePoint::new_quad(quad.center(), values.center_source, values.center_target, Some(quad), None, false));\n\t}\n}\n\nfn handle_not_under(to_document: DAffine2) -> impl Fn(&DVec2) -> bool {\n\tmove |&offset: &DVec2| to_document.transform_vector2(offset).length_squared() >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE\n}\n\nfn subpath_anchor_snap_points(layer: LayerNodeIdentifier, subpath: &Subpath<PointId>, snap_data: &SnapData, points: &mut Vec<SnapCandidatePoint>, to_document: DAffine2) {\n\tlet document = snap_data.document;\n\n\t// Midpoints of linear segments\n\tif document.snapping_state.target_enabled(SnapTarget::Path(PathSnapTarget::LineMidpoint)) {\n\t\tfor (index, curve) in subpath.iter().enumerate() {\n\t\t\tif snap_data.ignore_manipulator(layer, subpath.manipulator_groups()[index].id) || snap_data.ignore_manipulator(layer, subpath.manipulator_groups()[(index + 1) % subpath.len()].id) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif points.len() >= crate::consts::MAX_LAYER_SNAP_POINTS {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tlet curve = pathseg_points(curve);\n\n\t\t\tlet in_handle = curve.p1.map(|handle| handle - curve.p0).filter(handle_not_under(to_document));\n\t\t\tlet out_handle = curve.p2.map(|handle| handle - curve.p3).filter(handle_not_under(to_document));\n\t\t\tif in_handle.is_none() && out_handle.is_none() {\n\t\t\t\tpoints.push(SnapCandidatePoint::new(\n\t\t\t\t\tto_document.transform_point2(curve.p0 * 0.5 + curve.p3 * 0.5),\n\t\t\t\t\tSnapSource::Path(PathSnapSource::LineMidpoint),\n\t\t\t\t\tSnapTarget::Path(PathSnapTarget::LineMidpoint),\n\t\t\t\t\tSome(layer),\n\t\t\t\t));\n\t\t\t}\n\t\t}\n\t}\n\n\t// Anchors\n\tfor (index, manipulators) in subpath.manipulator_groups().iter().enumerate() {\n\t\tif snap_data.ignore_manipulator(layer, manipulators.id) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif points.len() >= crate::consts::MAX_LAYER_SNAP_POINTS {\n\t\t\treturn;\n\t\t}\n\n\t\tlet colinear = are_manipulator_handles_colinear(manipulators, to_document, subpath, index);\n\n\t\t// Colinear handles\n\t\tif colinear && document.snapping_state.target_enabled(SnapTarget::Path(PathSnapTarget::AnchorPointWithColinearHandles)) {\n\t\t\tpoints.push(SnapCandidatePoint::new(\n\t\t\t\tto_document.transform_point2(manipulators.anchor),\n\t\t\t\tSnapSource::Path(PathSnapSource::AnchorPointWithColinearHandles),\n\t\t\t\tSnapTarget::Path(PathSnapTarget::AnchorPointWithColinearHandles),\n\t\t\t\tSome(layer),\n\t\t\t));\n\t\t}\n\t\t// Free handles\n\t\telse if !colinear && document.snapping_state.target_enabled(SnapTarget::Path(PathSnapTarget::AnchorPointWithFreeHandles)) {\n\t\t\tpoints.push(SnapCandidatePoint::new(\n\t\t\t\tto_document.transform_point2(manipulators.anchor),\n\t\t\t\tSnapSource::Path(PathSnapSource::AnchorPointWithFreeHandles),\n\t\t\t\tSnapTarget::Path(PathSnapTarget::AnchorPointWithFreeHandles),\n\t\t\t\tSome(layer),\n\t\t\t));\n\t\t}\n\t}\n}\n\npub fn are_manipulator_handles_colinear(manipulators: &ManipulatorGroup<PointId>, to_document: DAffine2, subpath: &Subpath<PointId>, index: usize) -> bool {\n\tlet anchor = manipulators.anchor;\n\tlet handle_in = manipulators.in_handle.map(|handle| anchor - handle).filter(handle_not_under(to_document));\n\tlet handle_out = manipulators.out_handle.map(|handle| handle - anchor).filter(handle_not_under(to_document));\n\tlet anchor_is_endpoint = !subpath.closed() && (index == 0 || index == subpath.len() - 1);\n\n\t// Unless this is an endpoint, check if both handles are colinear (within an angular epsilon)\n\t!anchor_is_endpoint && handle_in.is_some_and(|handle_in| handle_out.is_some_and(|handle_out| handle_in.angle_to(handle_out) < 1e-5))\n}\n\npub fn get_layer_snap_points(layer: LayerNodeIdentifier, snap_data: &SnapData, points: &mut Vec<SnapCandidatePoint>) {\n\tlet document = snap_data.document;\n\n\tif document.network_interface.is_artboard(&layer.to_node(), &[]) {\n\t\treturn;\n\t}\n\tif points.len() >= crate::consts::MAX_LAYER_SNAP_POINTS {\n\t\treturn;\n\t}\n\n\tif layer.has_children(document.metadata()) {\n\t\tfor child in layer.descendants(document.metadata()) {\n\t\t\tget_layer_snap_points(child, snap_data, points);\n\t\t}\n\t} else if document.metadata().layer_outline(layer).next().is_some() {\n\t\tlet to_document = document.metadata().transform_to_document(layer);\n\t\tfor subpath in document.metadata().layer_outline(layer) {\n\t\t\tsubpath_anchor_snap_points(layer, subpath, snap_data, points, to_document);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/snapping/snap_results.rs",
    "content": "use super::DistributionMatch;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::portfolio::document::utility_types::misc::{DistributionSnapTarget, SnapSource, SnapTarget};\nuse crate::messages::tool::common_functionality::snapping::SnapCandidatePoint;\nuse glam::DVec2;\nuse graphene_std::renderer::Quad;\nuse graphene_std::renderer::Rect;\nuse graphene_std::vector::PointId;\nuse kurbo::PathSeg;\nuse std::collections::VecDeque;\n\n#[derive(Clone, Debug, Default)]\npub struct SnapResults {\n\tpub points: Vec<SnappedPoint>,\n\tpub grid_lines: Vec<SnappedLine>,\n\tpub curves: Vec<SnappedCurve>,\n}\n#[derive(Default, Debug, Clone)]\npub struct SnappedPoint {\n\tpub snapped_point_document: DVec2,\n\tpub source: SnapSource,\n\tpub target: SnapTarget,\n\tpub at_intersection: bool,\n\tpub constrained: bool, // Found when looking for constrained\n\tpub fully_constrained: bool,\n\tpub target_bounds: Option<Quad>,\n\tpub source_bounds: Option<Quad>,\n\t/// These layer(s) are outlined in the overlays when the snap is used.\n\tpub outline_layers: [Option<LayerNodeIdentifier>; 2],\n\tpub distance: f64,\n\tpub tolerance: f64,\n\tpub distribution_boxes_horizontal: VecDeque<Rect>,\n\tpub distribution_equal_distance_horizontal: Option<f64>,\n\tpub distribution_boxes_vertical: VecDeque<Rect>,\n\tpub distribution_equal_distance_vertical: Option<f64>,\n\tpub distance_to_align_target: f64, // If aligning so that the top is aligned but the X pos is 200 from the target, this is 200.\n\tpub alignment_target_horizontal: Option<DVec2>,\n\tpub alignment_target_vertical: Option<DVec2>,\n}\nimpl SnappedPoint {\n\tpub fn align(&self) -> bool {\n\t\tself.alignment_target_horizontal.is_some() || self.alignment_target_vertical.is_some()\n\t}\n\tpub fn infinite_snap(snapped_point_document: DVec2) -> Self {\n\t\tSelf {\n\t\t\tsnapped_point_document,\n\t\t\tdistance: f64::INFINITY,\n\t\t\t..Default::default()\n\t\t}\n\t}\n\tpub fn from_source_point(snapped_point_document: DVec2, source: SnapSource) -> Self {\n\t\tSelf {\n\t\t\tsnapped_point_document,\n\t\t\tsource,\n\t\t\t..Default::default()\n\t\t}\n\t}\n\tpub fn distribute(point: &SnapCandidatePoint, target: DistributionSnapTarget, boxes: VecDeque<Rect>, distances: DistributionMatch, bounds: Rect, translation: DVec2, tolerance: f64) -> Self {\n\t\tlet is_x = target.is_x();\n\n\t\tlet [distribution_boxes_horizontal, distribution_boxes_vertical] = if is_x { [boxes, Default::default()] } else { [Default::default(), boxes] };\n\t\tSelf {\n\t\t\tsnapped_point_document: point.document_point + translation,\n\t\t\tsource: point.source,\n\t\t\ttarget: SnapTarget::DistributeEvenly(target),\n\t\t\tdistribution_boxes_horizontal,\n\t\t\tdistribution_equal_distance_horizontal: is_x.then_some(distances.equal),\n\t\t\tdistribution_boxes_vertical,\n\t\t\tdistribution_equal_distance_vertical: (!is_x).then_some(distances.equal),\n\t\t\tdistance: (distances.first - distances.equal).abs(),\n\t\t\tconstrained: true,\n\t\t\tsource_bounds: Some(bounds.translate(translation).into()),\n\t\t\ttolerance,\n\t\t\t..Default::default()\n\t\t}\n\t}\n\tpub fn other_snap_better(&self, other: &Self) -> bool {\n\t\tif self.distance.is_finite() && !other.distance.is_finite() {\n\t\t\treturn false;\n\t\t}\n\t\tif !self.distance.is_finite() && other.distance.is_finite() {\n\t\t\treturn true;\n\t\t}\n\n\t\tlet my_dist = self.distance;\n\t\tlet other_dist = other.distance;\n\n\t\t// Prevent flickering when two points are equally close\n\t\tlet bias = 1e-2;\n\n\t\t// Prefer closest\n\t\tlet other_closer = other_dist < my_dist + bias;\n\n\t\t// We should prefer the most constrained option (e.g. intersection > path)\n\t\tlet other_more_constrained = other.constrained && !self.constrained;\n\t\tlet self_more_constrained = self.constrained && !other.constrained;\n\n\t\tlet both_align = other.align() && self.align();\n\t\tlet other_better_align = !other.align() && self.align() || (both_align && !self.source.center() && other.source.center());\n\t\tlet self_better_align = !self.align() && other.align() || (both_align && !other.source.center() && self.source.center());\n\n\t\t// Prefer nodes to intersections if both are at the same position\n\t\tlet constrained_at_same_pos = other.constrained && self.constrained && self.snapped_point_document.abs_diff_eq(other.snapped_point_document, 1.);\n\t\tlet other_better_constraint = constrained_at_same_pos && self.at_intersection && !other.at_intersection;\n\t\tlet self_better_constraint = constrained_at_same_pos && other.at_intersection && !self.at_intersection;\n\n\t\t(other_closer || other_more_constrained || other_better_align || other_better_constraint) && !self_more_constrained && !self_better_align && !self_better_constraint\n\t}\n\tpub fn is_snapped(&self) -> bool {\n\t\tself.distance.is_finite()\n\t}\n}\n#[derive(Clone, Debug, Default)]\npub struct SnappedLine {\n\tpub point: SnappedPoint,\n\tpub direction: DVec2,\n}\n#[derive(Clone, Debug)]\npub struct SnappedCurve {\n\tpub layer: LayerNodeIdentifier,\n\tpub start: PointId,\n\tpub point: SnappedPoint,\n\tpub document_curve: PathSeg,\n}\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/snapping.rs",
    "content": "mod alignment_snapper;\nmod distribution_snapper;\nmod grid_snapper;\nmod layer_snapper;\nmod snap_results;\n\nuse crate::consts::{COLOR_OVERLAY_BLACK_75, COLOR_OVERLAY_BLUE, COLOR_OVERLAY_WHITE};\nuse crate::messages::portfolio::document::overlays::utility_types::{OverlayContext, Pivot};\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::portfolio::document::utility_types::misc::{GridSnapTarget, PathSnapTarget, SnapTarget};\nuse crate::messages::prelude::*;\npub use alignment_snapper::*;\npub use distribution_snapper::*;\nuse glam::{DAffine2, DVec2};\nuse graphene_std::renderer::Quad;\nuse graphene_std::renderer::Rect;\nuse graphene_std::vector::NoHashBuilder;\nuse graphene_std::vector::PointId;\nuse graphene_std::vector::algorithms::intersection::filtered_segment_intersections;\nuse graphene_std::vector::misc::point_to_dvec2;\npub use grid_snapper::*;\nuse kurbo::ParamCurve;\npub use layer_snapper::*;\npub use snap_results::*;\nuse std::cmp::Ordering;\n\n/// Configuration for the relevant snap type\n#[derive(Debug, Clone, Copy, Default)]\npub struct SnapTypeConfiguration {\n\tpub only_path: bool,\n\tpub use_existing_candidates: bool,\n\tpub accept_distribution: bool,\n\tpub bbox: Option<Rect>,\n}\n\n/// Handles snapping and snap overlays\n#[derive(Debug, Clone, Default)]\npub struct SnapManager {\n\tindicator: Option<SnappedPoint>,\n\tlayer_snapper: LayerSnapper,\n\tgrid_snapper: GridSnapper,\n\talignment_snapper: AlignmentSnapper,\n\tdistribution_snapper: DistributionSnapper,\n\tcandidates: Option<Vec<LayerNodeIdentifier>>,\n\talignment_candidates: Option<Vec<LayerNodeIdentifier>>,\n}\n\n#[derive(Clone, Copy, Debug, Default)]\npub enum SnapConstraint {\n\t#[default]\n\tNone,\n\tLine {\n\t\torigin: DVec2,\n\t\tdirection: DVec2,\n\t},\n\tDirection(DVec2),\n\tCircle {\n\t\tcenter: DVec2,\n\t\tradius: f64,\n\t},\n}\nimpl SnapConstraint {\n\tpub fn projection(&self, point: DVec2) -> DVec2 {\n\t\tmatch *self {\n\t\t\tSelf::Line { origin, direction } if direction != DVec2::ZERO => (point - origin).project_onto(direction) + origin,\n\t\t\tSelf::Circle { center, radius } => {\n\t\t\t\tlet from_center = point - center;\n\t\t\t\tlet distance = from_center.length();\n\t\t\t\tif distance > 0. {\n\t\t\t\t\tcenter + radius * from_center / distance\n\t\t\t\t} else {\n\t\t\t\t\t// Point is exactly at the center, so project right\n\t\t\t\t\tcenter + DVec2::new(radius, 0.)\n\t\t\t\t}\n\t\t\t}\n\t\t\t_ => point,\n\t\t}\n\t}\n\tpub fn direction(&self) -> DVec2 {\n\t\tmatch *self {\n\t\t\tSelf::Line { direction, .. } | Self::Direction(direction) => direction,\n\t\t\t_ => DVec2::ZERO,\n\t\t}\n\t}\n}\n\npub fn snap_tolerance(document: &DocumentMessageHandler) -> f64 {\n\tdocument.snapping_state.tolerance / document.document_ptz.zoom()\n}\n\nfn compare_points(a: &&SnappedPoint, b: &&SnappedPoint) -> Ordering {\n\tif (a.target.bounding_box() && !b.target.bounding_box()) || (a.at_intersection && !b.at_intersection) || (a.source.bounding_box() && !b.source.bounding_box()) {\n\t\tOrdering::Greater\n\t} else if (!a.target.bounding_box() && b.target.bounding_box()) || (!a.at_intersection && b.at_intersection) || (!a.source.bounding_box() && b.source.bounding_box()) {\n\t\tOrdering::Less\n\t} else {\n\t\ta.distance.partial_cmp(&b.distance).unwrap()\n\t}\n}\n\nfn find_align(a: &SnappedPoint, b: &SnappedPoint) -> Ordering {\n\t(a.distance, a.distance_to_align_target).partial_cmp(&(b.distance, b.distance_to_align_target)).unwrap()\n}\n\nfn get_closest_point(points: Vec<SnappedPoint>) -> Option<SnappedPoint> {\n\tlet mut best_not_align = None;\n\tlet mut best_align = None;\n\tfor point in points {\n\t\tif !point.align() && !best_not_align.as_ref().is_some_and(|best| compare_points(&best, &&point).is_ge()) {\n\t\t\tbest_not_align = Some(point);\n\t\t} else if point.align() && !best_align.as_ref().is_some_and(|best| find_align(best, &point).is_ge()) {\n\t\t\tbest_align = Some(point)\n\t\t}\n\t}\n\tmatch (best_not_align, best_align) {\n\t\t(None, None) => None,\n\t\t(Some(result), None) | (None, Some(result)) => Some(result),\n\t\t(Some(mut result), Some(align)) => {\n\t\t\tlet SnapTarget::DistributeEvenly(distribution) = result.target else { return Some(result) };\n\t\t\tif distribution.is_x() && align.alignment_target_horizontal.is_some() {\n\t\t\t\tresult.snapped_point_document.y = align.snapped_point_document.y;\n\t\t\t\tresult.alignment_target_horizontal = align.alignment_target_horizontal;\n\t\t\t}\n\t\t\tif distribution.is_y() && align.alignment_target_vertical.is_some() {\n\t\t\t\tresult.snapped_point_document.x = align.snapped_point_document.x;\n\t\t\t\tresult.alignment_target_vertical = align.alignment_target_vertical;\n\t\t\t}\n\n\t\t\tSome(result)\n\t\t}\n\t}\n}\n\nfn get_closest_curve(curves: &[SnappedCurve], exclude_paths: bool) -> Option<&SnappedPoint> {\n\tlet keep_curve = |curve: &&SnappedCurve| !exclude_paths || curve.point.target != SnapTarget::Path(PathSnapTarget::AlongPath);\n\tcurves.iter().filter(keep_curve).map(|curve| &curve.point).min_by(compare_points)\n}\n\nfn get_closest_line(lines: &[SnappedLine]) -> Option<&SnappedPoint> {\n\tlines.iter().map(|curve| &curve.point).min_by(compare_points)\n}\n\nfn get_closest_intersection(snap_to: DVec2, curves: &[SnappedCurve]) -> Option<SnappedPoint> {\n\tlet mut best = None;\n\tfor curve_i in curves {\n\t\tfor curve_j in curves {\n\t\t\tif curve_i.start == curve_j.start && curve_i.layer == curve_j.layer {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tfor curve_i_t in filtered_segment_intersections(curve_i.document_curve, curve_j.document_curve, None, None) {\n\t\t\t\tlet snapped_point_document = point_to_dvec2(curve_i.document_curve.eval(curve_i_t));\n\t\t\t\tlet distance = snap_to.distance(snapped_point_document);\n\t\t\t\tlet i_closer = curve_i.point.distance < curve_j.point.distance;\n\t\t\t\tlet close = if i_closer { curve_i } else { curve_j };\n\t\t\t\tlet far = if i_closer { curve_j } else { curve_i };\n\t\t\t\tif !best.as_ref().is_some_and(|best: &SnappedPoint| best.distance < distance) {\n\t\t\t\t\tbest = Some(SnappedPoint {\n\t\t\t\t\t\tsnapped_point_document,\n\t\t\t\t\t\tdistance,\n\t\t\t\t\t\ttarget: SnapTarget::Path(PathSnapTarget::IntersectionPoint),\n\t\t\t\t\t\ttolerance: close.point.tolerance,\n\t\t\t\t\t\toutline_layers: [Some(close.layer), Some(far.layer)],\n\t\t\t\t\t\tsource: close.point.source,\n\t\t\t\t\t\tat_intersection: true,\n\t\t\t\t\t\tconstrained: true,\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tbest\n}\n\nfn get_grid_intersection(snap_to: DVec2, lines: &[SnappedLine]) -> Option<SnappedPoint> {\n\tlet mut best = None;\n\tfor line_i in lines {\n\t\tfor line_j in lines {\n\t\t\tif let Some(snapped_point_document) = Quad::intersect_rays(line_i.point.snapped_point_document, line_i.direction, line_j.point.snapped_point_document, line_j.direction) {\n\t\t\t\tlet distance = snap_to.distance(snapped_point_document);\n\t\t\t\tif !best.as_ref().is_some_and(|best: &SnappedPoint| best.distance < distance) {\n\t\t\t\t\tbest = Some(SnappedPoint {\n\t\t\t\t\t\tsnapped_point_document,\n\t\t\t\t\t\tdistance,\n\t\t\t\t\t\ttarget: SnapTarget::Grid(GridSnapTarget::Intersection),\n\t\t\t\t\t\ttolerance: line_i.point.tolerance,\n\t\t\t\t\t\tsource: line_i.point.source,\n\t\t\t\t\t\tat_intersection: true,\n\t\t\t\t\t\tconstrained: true,\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tbest\n}\n\n#[derive(Default, Clone, Debug)]\npub struct SnapCache {\n\tpub manipulators: HashMap<LayerNodeIdentifier, HashSet<PointId, NoHashBuilder>, NoHashBuilder>,\n\tpub unselected: Vec<SnapCandidatePoint>,\n}\n\n#[derive(Clone)]\npub struct SnapData<'a> {\n\tpub document: &'a DocumentMessageHandler,\n\tpub input: &'a InputPreprocessorMessageHandler,\n\tpub viewport: &'a ViewportMessageHandler,\n\tpub ignore: &'a [LayerNodeIdentifier],\n\tpub node_snap_cache: Option<&'a SnapCache>,\n\tpub candidates: Option<&'a Vec<LayerNodeIdentifier>>,\n\tpub alignment_candidates: Option<&'a Vec<LayerNodeIdentifier>>,\n}\nimpl<'a> SnapData<'a> {\n\tpub fn new(document: &'a DocumentMessageHandler, input: &'a InputPreprocessorMessageHandler, viewport: &'a ViewportMessageHandler) -> Self {\n\t\tSelf::ignore(document, input, viewport, &[])\n\t}\n\tpub fn ignore(document: &'a DocumentMessageHandler, input: &'a InputPreprocessorMessageHandler, viewport: &'a ViewportMessageHandler, ignore: &'a [LayerNodeIdentifier]) -> Self {\n\t\tSelf {\n\t\t\tdocument,\n\t\t\tinput,\n\t\t\tviewport,\n\t\t\tignore,\n\t\t\tcandidates: None,\n\t\t\talignment_candidates: None,\n\t\t\tnode_snap_cache: None,\n\t\t}\n\t}\n\tpub fn new_snap_cache(document: &'a DocumentMessageHandler, input: &'a InputPreprocessorMessageHandler, viewport: &'a ViewportMessageHandler, snap_cache: &'a SnapCache) -> Self {\n\t\tSelf {\n\t\t\tnode_snap_cache: Some(snap_cache),\n\t\t\t..Self::new(document, input, viewport)\n\t\t}\n\t}\n\tfn get_candidates(&self) -> &[LayerNodeIdentifier] {\n\t\tself.candidates.map_or([].as_slice(), |candidates| candidates.as_slice())\n\t}\n\tfn ignore_bounds(&self, layer: LayerNodeIdentifier) -> bool {\n\t\tself.node_snap_cache.is_some_and(|cache| cache.manipulators.contains_key(&layer))\n\t}\n\tfn ignore_manipulator(&self, layer: LayerNodeIdentifier, target: PointId) -> bool {\n\t\tself.node_snap_cache.and_then(|cache| cache.manipulators.get(&layer)).is_some_and(|points| points.contains(&target))\n\t}\n\tfn has_manipulators(&self) -> bool {\n\t\tself.node_snap_cache.is_some_and(|cache| !cache.manipulators.is_empty())\n\t}\n}\n\nimpl SnapManager {\n\tpub fn update_indicator(&mut self, snapped_point: SnappedPoint) {\n\t\tself.indicator = snapped_point.is_snapped().then_some(snapped_point);\n\t}\n\n\tpub fn clear_indicator(&mut self) {\n\t\tself.indicator = None;\n\t}\n\n\tpub fn preview_draw(&mut self, snap_data: &SnapData, mouse: DVec2) {\n\t\tlet point = SnapCandidatePoint::handle(snap_data.document.metadata().document_to_viewport.inverse().transform_point2(mouse));\n\t\tlet snapped = self.free_snap(snap_data, &point, SnapTypeConfiguration::default());\n\t\tself.update_indicator(snapped);\n\t}\n\n\tpub fn preview_draw_gradient(&mut self, snap_data: &SnapData, mouse: DVec2) {\n\t\tlet point = SnapCandidatePoint::gradient_handle(snap_data.document.metadata().document_to_viewport.inverse().transform_point2(mouse));\n\t\tlet snapped = self.free_snap(snap_data, &point, SnapTypeConfiguration::default());\n\t\tself.update_indicator(snapped);\n\t}\n\n\tpub fn indicator_pos(&self) -> Option<DVec2> {\n\t\tself.indicator.as_ref().map(|point| point.snapped_point_document)\n\t}\n\n\tfn find_best_snap(snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: SnapResults, constrained: bool, off_screen: bool, to_path: bool) -> SnappedPoint {\n\t\tlet mut snapped_points = Vec::new();\n\t\tlet document = snap_data.document;\n\n\t\tif let Some(closest_point) = get_closest_point(snap_results.points) {\n\t\t\tsnapped_points.push(closest_point);\n\t\t}\n\t\tlet exclude_paths = !document.snapping_state.target_enabled(SnapTarget::Path(PathSnapTarget::AlongPath));\n\t\tif let Some(closest_curve) = get_closest_curve(&snap_results.curves, exclude_paths) {\n\t\t\tsnapped_points.push(closest_curve.clone());\n\t\t}\n\n\t\tif document.snapping_state.target_enabled(SnapTarget::Grid(GridSnapTarget::Line))\n\t\t\t&& let Some(closest_line) = get_closest_line(&snap_results.grid_lines)\n\t\t{\n\t\t\tsnapped_points.push(closest_line.clone());\n\t\t}\n\n\t\tif !constrained {\n\t\t\tif document.snapping_state.target_enabled(SnapTarget::Path(PathSnapTarget::IntersectionPoint))\n\t\t\t\t&& let Some(closest_curves_intersection) = get_closest_intersection(point.document_point, &snap_results.curves)\n\t\t\t{\n\t\t\t\tsnapped_points.push(closest_curves_intersection);\n\t\t\t}\n\t\t\tif document.snapping_state.target_enabled(SnapTarget::Grid(GridSnapTarget::Intersection))\n\t\t\t\t&& let Some(closest_grid_intersection) = get_grid_intersection(point.document_point, &snap_results.grid_lines)\n\t\t\t{\n\t\t\t\tsnapped_points.push(closest_grid_intersection);\n\t\t\t}\n\t\t}\n\n\t\tif to_path {\n\t\t\tsnapped_points.retain(|i| matches!(i.target, SnapTarget::Path(_)));\n\t\t}\n\n\t\tlet mut best_point = None;\n\n\t\tfor point in snapped_points {\n\t\t\tlet viewport_point = document.metadata().document_to_viewport.transform_point2(point.snapped_point_document);\n\t\t\tlet on_screen = viewport_point.cmpgt(DVec2::ZERO).all() && viewport_point.cmplt(snap_data.viewport.size().into()).all();\n\t\t\tif !on_screen && !off_screen {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif point.distance > point.tolerance {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif best_point.as_ref().is_some_and(|best: &SnappedPoint| point.other_snap_better(best)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tbest_point = Some(point);\n\t\t}\n\n\t\tbest_point.unwrap_or(SnappedPoint::infinite_snap(point.document_point))\n\t}\n\n\tfn add_candidates(&mut self, layer: LayerNodeIdentifier, snap_data: &SnapData, quad: Quad) {\n\t\tlet document = snap_data.document;\n\n\t\tif !document.network_interface.is_visible(&layer.to_node(), &[]) {\n\t\t\treturn;\n\t\t}\n\t\tif snap_data.ignore.contains(&layer) {\n\t\t\treturn;\n\t\t}\n\t\tif layer.has_children(document.metadata()) {\n\t\t\tfor layer in layer.children(document.metadata()) {\n\t\t\t\tself.add_candidates(layer, snap_data, quad);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\t// We use a loose bounding box here since these are potential candidates which will be filtered later anyway\n\t\tlet Some(bounds) = document.metadata().loose_bounding_box_with_transform(layer, DAffine2::IDENTITY) else {\n\t\t\treturn;\n\t\t};\n\t\tlet layer_bounds = document.metadata().transform_to_document(layer) * Quad::from_box(bounds);\n\t\tlet screen_bounds = document.metadata().document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, snap_data.viewport.size().into()]);\n\t\tif screen_bounds.intersects(layer_bounds) {\n\t\t\tif self.alignment_candidates.as_ref().is_none_or(|candidates| candidates.len() <= 100) {\n\t\t\t\tself.alignment_candidates.get_or_insert_with(Vec::new).push(layer);\n\t\t\t}\n\t\t\tif quad.intersects(layer_bounds) && self.candidates.as_ref().is_none_or(|candidates| candidates.len() <= 10) {\n\t\t\t\tself.candidates.get_or_insert_with(Vec::new).push(layer);\n\t\t\t}\n\t\t}\n\t}\n\n\tfn find_candidates(&mut self, snap_data: &SnapData, point: &SnapCandidatePoint, bbox: Option<Rect>) {\n\t\tlet document = snap_data.document;\n\t\tlet offset = snap_tolerance(document);\n\t\tlet quad = bbox.map_or_else(|| Quad::from_square(point.document_point, offset), |quad| Quad::from_box(quad.0).inflate(offset));\n\n\t\tself.candidates = None;\n\t\tself.alignment_candidates = None;\n\t\tfor layer in LayerNodeIdentifier::ROOT_PARENT.children(document.metadata()) {\n\t\t\tself.add_candidates(layer, snap_data, quad);\n\t\t}\n\n\t\tif self.alignment_candidates.as_ref().is_some_and(|candidates| candidates.len() > crate::consts::MAX_ALIGNMENT_CANDIDATES) {\n\t\t\twarn!(\"Alignment candidate overflow\");\n\t\t}\n\t\tif self.candidates.as_ref().is_some_and(|candidates| candidates.len() > crate::consts::MAX_SNAP_CANDIDATES) {\n\t\t\twarn!(\"Snap candidate overflow\");\n\t\t}\n\t}\n\n\tpub fn free_snap(&mut self, snap_data: &SnapData, point: &SnapCandidatePoint, config: SnapTypeConfiguration) -> SnappedPoint {\n\t\tif !point.document_point.is_finite() {\n\t\t\twarn!(\"Snapping non-finite position\");\n\t\t\treturn SnappedPoint::infinite_snap(DVec2::ZERO);\n\t\t}\n\n\t\tlet mut snap_results = SnapResults::default();\n\t\tif !config.use_existing_candidates {\n\t\t\tself.candidates = None;\n\t\t}\n\n\t\tlet mut snap_data = snap_data.clone();\n\t\tif snap_data.candidates.is_none() {\n\t\t\tself.find_candidates(&snap_data, point, config.bbox);\n\t\t}\n\t\tsnap_data.candidates = self.candidates.as_ref();\n\t\tsnap_data.alignment_candidates = self.alignment_candidates.as_ref();\n\n\t\tself.layer_snapper.free_snap(&mut snap_data, point, &mut snap_results, config);\n\t\tself.grid_snapper.free_snap(&mut snap_data, point, &mut snap_results);\n\t\tself.alignment_snapper.free_snap(&mut snap_data, point, &mut snap_results, config);\n\t\tself.distribution_snapper.free_snap(&mut snap_data, point, &mut snap_results, config);\n\n\t\tSelf::find_best_snap(&mut snap_data, point, snap_results, false, false, config.only_path)\n\t}\n\n\tpub fn constrained_snap(&mut self, snap_data: &SnapData, point: &SnapCandidatePoint, constraint: SnapConstraint, config: SnapTypeConfiguration) -> SnappedPoint {\n\t\tif !point.document_point.is_finite() {\n\t\t\twarn!(\"Snapping non-finite position\");\n\t\t\treturn SnappedPoint::infinite_snap(DVec2::ZERO);\n\t\t}\n\n\t\tlet mut snap_results = SnapResults::default();\n\t\tif !config.use_existing_candidates {\n\t\t\tself.candidates = None;\n\t\t}\n\n\t\tlet mut snap_data = snap_data.clone();\n\t\tif snap_data.candidates.is_none() {\n\t\t\tself.find_candidates(&snap_data, point, config.bbox);\n\t\t}\n\t\tsnap_data.candidates = self.candidates.as_ref();\n\t\tsnap_data.alignment_candidates = self.alignment_candidates.as_ref();\n\n\t\tself.layer_snapper.constrained_snap(&mut snap_data, point, &mut snap_results, constraint, config);\n\t\tself.grid_snapper.constrained_snap(&mut snap_data, point, &mut snap_results, constraint);\n\t\tself.alignment_snapper.constrained_snap(&mut snap_data, point, &mut snap_results, constraint, config);\n\t\tself.distribution_snapper.constrained_snap(&mut snap_data, point, &mut snap_results, constraint, config);\n\n\t\tSelf::find_best_snap(&mut snap_data, point, snap_results, true, false, config.only_path)\n\t}\n\n\tfn alignment_x_overlay(boxes: &VecDeque<Rect>, transform: DAffine2, overlay_context: &mut OverlayContext) {\n\t\tlet y_size = transform.inverse().transform_vector2(DVec2::Y * 8.).length();\n\t\tfor (&first, &second) in boxes.iter().zip(boxes.iter().skip(1)) {\n\t\t\tlet bottom = first.center().y < second.center().y + y_size;\n\t\t\tlet y = if bottom { first.max() } else { first.min() }.y;\n\t\t\tlet start = DVec2::new(first.max().x, y);\n\t\t\tlet end = DVec2::new(second.min().x, y);\n\t\t\tlet signed_size = if bottom { y_size } else { -y_size };\n\t\t\toverlay_context.line(transform.transform_point2(start), transform.transform_point2(start + DVec2::Y * signed_size), None, None);\n\t\t\toverlay_context.line(transform.transform_point2(end), transform.transform_point2(end + DVec2::Y * signed_size), None, None);\n\t\t\toverlay_context.line(\n\t\t\t\ttransform.transform_point2(start + DVec2::Y * signed_size / 2.),\n\t\t\t\ttransform.transform_point2(end + DVec2::Y * signed_size / 2.),\n\t\t\t\tNone,\n\t\t\t\tNone,\n\t\t\t);\n\t\t}\n\t}\n\n\tfn alignment_y_overlay(boxes: &VecDeque<Rect>, transform: DAffine2, overlay_context: &mut OverlayContext) {\n\t\tlet x_size = transform.inverse().transform_vector2(DVec2::X * 8.).length();\n\t\tfor (&first, &second) in boxes.iter().zip(boxes.iter().skip(1)) {\n\t\t\tlet right = first.center().x < second.center().x + x_size;\n\t\t\tlet x = if right { first.max() } else { first.min() }.x;\n\t\t\tlet start = DVec2::new(x, first.max().y);\n\t\t\tlet end = DVec2::new(x, second.min().y);\n\t\t\tlet signed_size = if right { x_size } else { -x_size };\n\t\t\toverlay_context.line(transform.transform_point2(start), transform.transform_point2(start + DVec2::X * signed_size), None, None);\n\t\t\toverlay_context.line(transform.transform_point2(end), transform.transform_point2(end + DVec2::X * signed_size), None, None);\n\t\t\toverlay_context.line(\n\t\t\t\ttransform.transform_point2(start + DVec2::X * signed_size / 2.),\n\t\t\t\ttransform.transform_point2(end + DVec2::X * signed_size / 2.),\n\t\t\t\tNone,\n\t\t\t\tNone,\n\t\t\t);\n\t\t}\n\t}\n\n\tpub fn draw_overlays(&mut self, snap_data: SnapData, overlay_context: &mut OverlayContext) {\n\t\tlet to_viewport = snap_data.document.metadata().document_to_viewport;\n\t\tif let Some(ind) = &self.indicator {\n\t\t\tfor layer in &ind.outline_layers {\n\t\t\t\tlet &Some(layer) = layer else { continue };\n\t\t\t\toverlay_context.outline(\n\t\t\t\t\tsnap_data.document.metadata().layer_with_free_points_outline(layer),\n\t\t\t\t\tsnap_data.document.metadata().transform_to_viewport(layer),\n\t\t\t\t\tNone,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif let Some(quad) = ind.target_bounds {\n\t\t\t\toverlay_context.quad(to_viewport * quad, None, None);\n\t\t\t}\n\t\t\tlet viewport = to_viewport.transform_point2(ind.snapped_point_document);\n\n\t\t\tSelf::alignment_x_overlay(&ind.distribution_boxes_horizontal, to_viewport, overlay_context);\n\t\t\tSelf::alignment_y_overlay(&ind.distribution_boxes_vertical, to_viewport, overlay_context);\n\n\t\t\tlet align = [ind.alignment_target_horizontal, ind.alignment_target_vertical].map(|target| target.map(|target| to_viewport.transform_point2(target)));\n\t\t\tlet any_align = align.iter().flatten().next().is_some();\n\t\t\tfor &target in align.iter().flatten() {\n\t\t\t\toverlay_context.line(viewport, target, None, None);\n\t\t\t}\n\t\t\tfor &target in align.iter().flatten() {\n\t\t\t\toverlay_context.manipulator_handle(target, false, None);\n\t\t\t}\n\t\t\tif any_align {\n\t\t\t\toverlay_context.manipulator_handle(viewport, false, None);\n\t\t\t}\n\n\t\t\tif !any_align && ind.distribution_equal_distance_horizontal.is_none() && ind.distribution_equal_distance_vertical.is_none() {\n\t\t\t\tlet text = format!(\"[{}] from [{}]\", ind.target, ind.source);\n\t\t\t\tlet transform = DAffine2::from_translation(viewport - DVec2::new(0., 4.));\n\t\t\t\toverlay_context.text(&text, COLOR_OVERLAY_WHITE, Some(COLOR_OVERLAY_BLACK_75), transform, 4., [Pivot::Start, Pivot::End]);\n\t\t\t\toverlay_context.square(viewport, Some(4.), Some(COLOR_OVERLAY_BLUE), Some(COLOR_OVERLAY_BLUE));\n\t\t\t}\n\t\t}\n\t}\n\n\t/// Removes snap target data and overlays. Call this when snapping is done.\n\tpub fn cleanup(&mut self, responses: &mut VecDeque<Message>) {\n\t\tself.candidates = None;\n\t\tself.indicator = None;\n\t\tresponses.add(OverlaysMessage::Draw);\n\t}\n}\n\n/// Converts a bounding box into a set of points for snapping\n///\n/// Puts a point in the middle of each edge (top, bottom, left, right)\npub fn expand_bounds([bound1, bound2]: [DVec2; 2]) -> [DVec2; 4] {\n\t[\n\t\tDVec2::new((bound1.x + bound2.x) / 2., bound1.y),\n\t\tDVec2::new((bound1.x + bound2.x) / 2., bound2.y),\n\t\tDVec2::new(bound1.x, (bound1.y + bound2.y) / 2.),\n\t\tDVec2::new(bound2.x, (bound1.y + bound2.y) / 2.),\n\t]\n}\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/transformation_cage.rs",
    "content": "use super::snapping::{self, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnappedPoint};\nuse crate::consts::{\n\tBOUNDS_ROTATE_THRESHOLD, BOUNDS_SELECT_THRESHOLD, MAX_LENGTH_FOR_NO_WIDTH_OR_HEIGHT, MAXIMUM_ALT_SCALE_FACTOR, MIN_LENGTH_FOR_CORNERS_VISIBILITY, MIN_LENGTH_FOR_EDGE_RESIZE_PRIORITY_OVER_CORNERS,\n\tMIN_LENGTH_FOR_MIDPOINT_VISIBILITY, MIN_LENGTH_FOR_RESIZE_TO_INCLUDE_INTERIOR, MIN_LENGTH_FOR_SKEW_TRIANGLE_VISIBILITY, RESIZE_HANDLE_SIZE, SELECTION_DRAG_ANGLE, SKEW_TRIANGLE_OFFSET,\n\tSKEW_TRIANGLE_SIZE,\n};\nuse crate::messages::frontend::utility_types::MouseCursorIcon;\nuse crate::messages::portfolio::document::overlays::utility_types::OverlayContext;\nuse crate::messages::portfolio::document::utility_types::transformation::OriginalTransforms;\nuse crate::messages::prelude::*;\nuse crate::messages::tool::common_functionality::compass_rose::Axis;\nuse crate::messages::tool::common_functionality::snapping::SnapTypeConfiguration;\nuse glam::{DAffine2, DMat2, DVec2};\nuse graphene_std::renderer::Quad;\nuse graphene_std::renderer::Rect;\n\n/// (top, bottom, left, right)\npub type EdgeBool = (bool, bool, bool, bool);\n\npub struct SizeSnapData<'a> {\n\tpub manager: &'a mut SnapManager,\n\tpub points: &'a mut Vec<SnapCandidatePoint>,\n\tpub snap_data: SnapData<'a>,\n}\n\n/// Contains the edges that are being dragged along with the original bounds.\n#[derive(Clone, Debug, Default)]\npub struct SelectedEdges {\n\tpub bounds: [DVec2; 2],\n\tpub top: bool,\n\tpub bottom: bool,\n\tpub left: bool,\n\tpub right: bool,\n\t// Aspect ratio in the form of width/height, so x:1 = width:height\n\taspect_ratio: f64,\n}\n\n/// The different possible configurations for how the transform cage is presently viewed, depending on its per-axis sizes and the level of zoom.\n/// See doc comments in each variant for a diagram of the configuration.\n#[derive(Clone, Debug, Default, PartialEq)]\nenum TransformCageSizeCategory {\n\t#[default]\n\t/// - ![Diagram](https://files.keavon.com/-/OrganicHelplessWalleye/capture.png)\n\tFull,\n\t/// - ![Diagram](https://files.keavon.com/-/AnyGoldenrodHawk/capture.png)\n\tReducedLandscape,\n\t/// - ![Diagram](https://files.keavon.com/-/DarkslategrayAcidicFirebelliedtoad/capture.png)\n\tReducedPortrait,\n\t/// - ![Diagram](https://files.keavon.com/-/GlisteningComplexSeagull/capture.png)\n\tReducedBoth,\n\t/// - ![Diagram](https://files.keavon.com/-/InconsequentialCharmingLynx/capture.png)\n\tNarrow,\n\t/// - ![Diagram](https://files.keavon.com/-/OpenPaleturquoiseArthropods/capture.png)\n\tFlat,\n\t/// A single point in space with no width or height.\n\tPoint,\n}\n\nimpl SelectedEdges {\n\tpub fn new(top: bool, bottom: bool, left: bool, right: bool, bounds: [DVec2; 2]) -> Self {\n\t\tlet size = (bounds[0] - bounds[1]).abs();\n\t\tlet aspect_ratio = size.x / size.y;\n\t\tSelf {\n\t\t\ttop,\n\t\t\tbottom,\n\t\t\tleft,\n\t\t\tright,\n\t\t\tbounds,\n\t\t\taspect_ratio,\n\t\t}\n\t}\n\n\t/// Calculate the pivot for the operation (the opposite point to the edge dragged)\n\tpub fn calculate_pivot(&self) -> DVec2 {\n\t\tself.pivot_from_bounds(self.bounds[0], self.bounds[1])\n\t}\n\n\tfn pivot_from_bounds(&self, min: DVec2, max: DVec2) -> DVec2 {\n\t\tlet x = if self.left {\n\t\t\tmax.x\n\t\t} else if self.right {\n\t\t\tmin.x\n\t\t} else {\n\t\t\t(min.x + max.x) / 2.\n\t\t};\n\n\t\tlet y = if self.top {\n\t\t\tmax.y\n\t\t} else if self.bottom {\n\t\t\tmin.y\n\t\t} else {\n\t\t\t(min.y + max.y) / 2.\n\t\t};\n\n\t\tDVec2::new(x, y)\n\t}\n\n\t/// Computes the new bounds with the given mouse move and modifier keys\n\tpub fn new_size(&self, mouse: DVec2, transform: DAffine2, center_around: Option<DVec2>, constrain: bool, snap: Option<SizeSnapData>) -> (DVec2, DVec2) {\n\t\tlet mouse = transform.inverse().transform_point2(mouse);\n\n\t\tlet mut min = self.bounds[0];\n\t\tlet mut max = self.bounds[1];\n\n\t\tif self.top {\n\t\t\tmin.y = mouse.y;\n\t\t} else if self.bottom {\n\t\t\tmax.y = mouse.y;\n\t\t}\n\t\tif self.left {\n\t\t\tmin.x = mouse.x;\n\t\t} else if self.right {\n\t\t\tmax.x = mouse.x;\n\t\t}\n\n\t\tlet mut pivot = self.pivot_from_bounds(min, max);\n\n\t\t// Alt: Scaling around the pivot\n\t\tif let Some(center_around) = center_around {\n\t\t\tlet center_around = transform.inverse().transform_point2(center_around);\n\n\t\t\tlet calculate_distance = |moving_opposite_to_drag: &mut f64, center: f64, dragging: f64, original_dragging: f64, current_side: bool| {\n\t\t\t\tif !current_side {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\t// The motion of the user's cursor by an `x` pixel offset results in `x * scale_factor` pixels of offset on the other side\n\t\t\t\tlet scale_factor = (center - *moving_opposite_to_drag) / (center - original_dragging);\n\t\t\t\tlet new_distance = center - scale_factor * (center - dragging);\n\n\t\t\t\t// Ignore the Alt key press and scale the dragged edge normally\n\t\t\t\tif !new_distance.is_finite() || scale_factor.abs() > MAXIMUM_ALT_SCALE_FACTOR {\n\t\t\t\t\t// Don't go on to check the other sides since this side is already invalid, so Alt-dragging is disabled and updating the pivot would be incorrect\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\t*moving_opposite_to_drag = new_distance;\n\n\t\t\t\ttrue\n\t\t\t};\n\n\t\t\t// Update the value of the first argument through mutation, and if we make it through all of them without\n\t\t\t// encountering a case where the pivot is too near the edge, we also update the pivot so scaling occurs around it\n\t\t\tif calculate_distance(&mut max.y, center_around.y, min.y, self.bounds[0].y, self.top)\n\t\t\t\t&& calculate_distance(&mut min.y, center_around.y, max.y, self.bounds[1].y, self.bottom)\n\t\t\t\t&& calculate_distance(&mut max.x, center_around.x, min.x, self.bounds[0].x, self.left)\n\t\t\t\t&& calculate_distance(&mut min.x, center_around.x, max.x, self.bounds[1].x, self.right)\n\t\t\t{\n\t\t\t\tpivot = center_around;\n\t\t\t}\n\t\t}\n\n\t\t// Shift: Aspect ratio constraint\n\t\tif constrain {\n\t\t\tlet size = max - min;\n\t\t\tlet min_pivot = (pivot - min) / size;\n\t\t\tlet new_size = match ((self.top || self.bottom), (self.left || self.right)) {\n\t\t\t\t(true, true) => DVec2::new(size.x, size.x / self.aspect_ratio).abs().max(DVec2::new(size.y * self.aspect_ratio, size.y).abs()) * size.signum(),\n\t\t\t\t(true, false) => DVec2::new(size.y * self.aspect_ratio, size.y),\n\t\t\t\t(false, true) => DVec2::new(size.x, size.x / self.aspect_ratio),\n\t\t\t\t_ => size,\n\t\t\t};\n\t\t\tlet delta_size = new_size - size;\n\t\t\tmin -= delta_size * min_pivot;\n\t\t\tmax = min + new_size;\n\t\t}\n\n\t\tif let Some(SizeSnapData { manager, points, snap_data }) = snap {\n\t\t\tlet view_to_doc = snap_data.document.metadata().document_to_viewport.inverse();\n\t\t\tlet bounds_to_doc = view_to_doc * transform;\n\t\t\tlet mut best_snap = SnappedPoint::infinite_snap(pivot);\n\t\t\tlet mut best_scale_factor = DVec2::ONE;\n\t\t\tlet tolerance = snapping::snap_tolerance(snap_data.document);\n\n\t\t\tlet bbox = Some(Rect::from_box((bounds_to_doc * Quad::from_box([min, max])).bounding_box()));\n\t\t\tfor (index, point) in points.iter_mut().enumerate() {\n\t\t\t\tlet config = SnapTypeConfiguration {\n\t\t\t\t\tbbox,\n\t\t\t\t\tuse_existing_candidates: index != 0,\n\t\t\t\t\t..Default::default()\n\t\t\t\t};\n\n\t\t\t\tlet old_position = point.document_point;\n\t\t\t\tlet bounds_space = bounds_to_doc.inverse().transform_point2(point.document_point);\n\t\t\t\tlet normalized = (bounds_space - self.bounds[0]) / (self.bounds[1] - self.bounds[0]);\n\t\t\t\tlet updated = normalized * (max - min) + min;\n\t\t\t\tpoint.document_point = bounds_to_doc.transform_point2(updated);\n\t\t\t\tlet mut snapped = if constrain {\n\t\t\t\t\tlet constraint = SnapConstraint::Line {\n\t\t\t\t\t\torigin: point.document_point,\n\t\t\t\t\t\tdirection: (point.document_point - bounds_to_doc.transform_point2(pivot)).normalize_or_zero(),\n\t\t\t\t\t};\n\t\t\t\t\tmanager.constrained_snap(&snap_data, point, constraint, config)\n\t\t\t\t} else if !(self.top || self.bottom) || !(self.left || self.right) {\n\t\t\t\t\tlet axis = if !(self.top || self.bottom) { DVec2::X } else { DVec2::Y };\n\t\t\t\t\tlet constraint = SnapConstraint::Line {\n\t\t\t\t\t\torigin: point.document_point,\n\t\t\t\t\t\tdirection: bounds_to_doc.transform_vector2(axis),\n\t\t\t\t\t};\n\t\t\t\t\tmanager.constrained_snap(&snap_data, point, constraint, config)\n\t\t\t\t} else {\n\t\t\t\t\tmanager.free_snap(&snap_data, point, config)\n\t\t\t\t};\n\t\t\t\tpoint.document_point = old_position;\n\n\t\t\t\tif !snapped.is_snapped() {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tlet snapped_bounds = bounds_to_doc.inverse().transform_point2(snapped.snapped_point_document);\n\n\t\t\t\tlet new_from_pivot = snapped_bounds - pivot; // The new vector from the snapped point to the pivot\n\t\t\t\tlet original_from_pivot = updated - pivot; // The original vector from the point to the pivot\n\t\t\t\tlet mut scale_factor = new_from_pivot / original_from_pivot;\n\n\t\t\t\t// Constrain should always scale by the same factor in x and y\n\t\t\t\tif constrain {\n\t\t\t\t\t// When the point is on the pivot, we simply copy the other axis.\n\t\t\t\t\tif original_from_pivot.x.abs() < 1e-5 {\n\t\t\t\t\t\tscale_factor.x = scale_factor.y;\n\t\t\t\t\t} else if original_from_pivot.y.abs() < 1e-5 {\n\t\t\t\t\t\tscale_factor.y = scale_factor.x;\n\t\t\t\t\t}\n\n\t\t\t\t\tdebug_assert!((scale_factor.x - scale_factor.y).abs() < 1e-5);\n\t\t\t\t}\n\n\t\t\t\tif !(self.left || self.right || constrain) {\n\t\t\t\t\tscale_factor.x = 1.\n\t\t\t\t}\n\t\t\t\tif !(self.top || self.bottom || constrain) {\n\t\t\t\t\tscale_factor.y = 1.\n\t\t\t\t}\n\n\t\t\t\tsnapped.distance = bounds_to_doc.transform_vector2((max - min) * (scale_factor - DVec2::ONE)).length();\n\t\t\t\tif snapped.distance > tolerance || !snapped.distance.is_finite() {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif best_snap.other_snap_better(&snapped) {\n\t\t\t\t\tbest_snap = snapped;\n\t\t\t\t\tbest_scale_factor = scale_factor;\n\t\t\t\t}\n\t\t\t}\n\t\t\tmanager.update_indicator(best_snap);\n\n\t\t\tmin = pivot - (pivot - min) * best_scale_factor;\n\t\t\tmax = pivot - (pivot - max) * best_scale_factor;\n\t\t}\n\n\t\t(min, max - min)\n\t}\n\n\t/// Calculates the required scaling to resize the bounding box\n\tpub fn bounds_to_scale_transform(&self, position: DVec2, size: DVec2) -> (DAffine2, DVec2) {\n\t\tlet old_size = self.bounds[1] - self.bounds[0];\n\t\tlet mut enlargement_factor = size / old_size;\n\t\tif !enlargement_factor.x.is_finite() || old_size.x.abs() < f64::EPSILON * 1000. {\n\t\t\tenlargement_factor.x = 1.;\n\t\t}\n\t\tif !enlargement_factor.y.is_finite() || old_size.y.abs() < f64::EPSILON * 1000. {\n\t\t\tenlargement_factor.y = 1.;\n\t\t}\n\t\tlet mut pivot = (self.bounds[0] * enlargement_factor - position) / (enlargement_factor - DVec2::ONE);\n\t\tif !pivot.x.is_finite() {\n\t\t\tpivot.x = 0.;\n\t\t}\n\t\tif !pivot.y.is_finite() {\n\t\t\tpivot.y = 0.;\n\t\t}\n\t\t(DAffine2::from_scale(enlargement_factor), pivot)\n\t}\n\n\tpub fn skew_transform(&self, mouse: DVec2, to_viewport_transform: DAffine2, free_movement: bool) -> DAffine2 {\n\t\t// Skip if the matrix is singular\n\t\tif !to_viewport_transform.matrix2.determinant().recip().is_finite() {\n\t\t\treturn DAffine2::IDENTITY;\n\t\t}\n\n\t\tlet opposite = self.pivot_from_bounds(self.bounds[0], self.bounds[1]);\n\t\tlet dragging_point = self.pivot_from_bounds(self.bounds[1], self.bounds[0]);\n\n\t\tlet viewport_dragging_point = to_viewport_transform.transform_point2(dragging_point);\n\t\tlet parallel_to_x = self.top || self.bottom;\n\t\tlet parallel_to_y = !parallel_to_x && (self.left || self.right);\n\n\t\tlet drag_vector = mouse - viewport_dragging_point;\n\t\tlet document_drag_vector = to_viewport_transform.inverse().transform_vector2(drag_vector);\n\n\t\tlet bounds = (self.bounds[1] - self.bounds[0]).abs();\n\t\tlet sign = if self.top || self.left { -1. } else { 1. };\n\t\tlet signed_bounds = sign * bounds;\n\n\t\tlet scale_factor = if parallel_to_x { signed_bounds.y.recip() } else { signed_bounds.x.recip() };\n\t\tlet scaled_document_drag = document_drag_vector * scale_factor;\n\n\t\tlet skew = DAffine2::from_mat2(DMat2::from_cols_array(&[\n\t\t\t1. + if parallel_to_y && free_movement { scaled_document_drag.x } else { 0. },\n\t\t\tif parallel_to_y { scaled_document_drag.y } else { 0. },\n\t\t\tif parallel_to_x { scaled_document_drag.x } else { 0. },\n\t\t\t1. + if parallel_to_x && free_movement { scaled_document_drag.y } else { 0. },\n\t\t]));\n\n\t\tDAffine2::from_translation(opposite) * skew * DAffine2::from_translation(-opposite)\n\t}\n}\n\n/// Aligns the mouse position to the closest axis\npub fn axis_align_drag(axis_align: bool, axis: Axis, position: DVec2, start: DVec2) -> DVec2 {\n\tif axis_align {\n\t\tlet mouse_position = position - start;\n\t\tlet snap_resolution = SELECTION_DRAG_ANGLE.to_radians();\n\t\tlet angle = -mouse_position.angle_to(DVec2::X);\n\t\tlet snapped_angle = (angle / snap_resolution).round() * snap_resolution;\n\t\tlet axis_vector = DVec2::from_angle(snapped_angle);\n\t\tif snapped_angle.is_finite() {\n\t\t\tstart + axis_vector * mouse_position.dot(axis_vector).abs()\n\t\t} else {\n\t\t\tstart\n\t\t}\n\t} else if axis.is_constraint() {\n\t\tlet mouse_position = position - start;\n\t\tlet axis_vector: DVec2 = axis.into();\n\t\tstart + axis_vector * mouse_position.dot(axis_vector)\n\t} else {\n\t\tposition\n\t}\n}\n\n/// Snaps a dragging event from the artboard or select tool\npub fn snap_drag(start: DVec2, current: DVec2, snap_to_axis: bool, axis: Axis, snap_data: SnapData, snap_manager: &mut SnapManager, candidates: &[SnapCandidatePoint]) -> DVec2 {\n\tlet mouse_position = axis_align_drag(snap_to_axis, axis, snap_data.input.mouse.position, start);\n\tlet document = snap_data.document;\n\tlet total_mouse_delta_document = document.metadata().document_to_viewport.inverse().transform_vector2(mouse_position - start);\n\tlet mouse_delta_document = document.metadata().document_to_viewport.inverse().transform_vector2(mouse_position - current);\n\tlet mut offset = mouse_delta_document;\n\tlet mut best_snap = SnappedPoint::infinite_snap(document.metadata().document_to_viewport.inverse().transform_point2(mouse_position));\n\n\tlet bbox = Rect::point_iter(candidates.iter().map(|candidate| candidate.document_point + total_mouse_delta_document));\n\n\tfor (index, point) in candidates.iter().enumerate() {\n\t\tlet config = SnapTypeConfiguration {\n\t\t\tbbox,\n\t\t\taccept_distribution: true,\n\t\t\tuse_existing_candidates: index != 0,\n\t\t\t..Default::default()\n\t\t};\n\n\t\tlet mut point = point.clone();\n\t\tpoint.document_point += total_mouse_delta_document;\n\n\t\tlet constrained_along_axis = snap_to_axis || axis.is_constraint();\n\t\tlet snapped = if constrained_along_axis {\n\t\t\tlet constraint = SnapConstraint::Line {\n\t\t\t\torigin: point.document_point,\n\t\t\t\tdirection: total_mouse_delta_document.try_normalize().unwrap_or(DVec2::X),\n\t\t\t};\n\t\t\tsnap_manager.constrained_snap(&snap_data, &point, constraint, config)\n\t\t} else {\n\t\t\tsnap_manager.free_snap(&snap_data, &point, config)\n\t\t};\n\n\t\tif best_snap.other_snap_better(&snapped) {\n\t\t\toffset = snapped.snapped_point_document - point.document_point + mouse_delta_document;\n\t\t\tbest_snap = snapped;\n\t\t}\n\t}\n\n\tsnap_manager.update_indicator(best_snap);\n\n\tdocument.metadata().document_to_viewport.transform_vector2(offset)\n}\n\n/// Contains info on the overlays for the bounding box and transform handles\n#[derive(Clone, Debug, Default)]\npub struct BoundingBoxManager {\n\t/// The corners of the box. Transform with original_bound_transform to get viewport co-ordinates.\n\tpub bounds: [DVec2; 2],\n\t/// The transform to viewport space for the bounds co-ordinates when the bounds were last updated.\n\tpub transform: DAffine2,\n\t/// Whether the transform is actually singular but adjusted to not be so.\n\tpub transform_tampered: bool,\n\t/// The transform to viewport space for the bounds co-ordinates when the transformation was started.\n\tpub original_bound_transform: DAffine2,\n\tpub selected_edges: Option<SelectedEdges>,\n\tpub original_transforms: OriginalTransforms,\n\tpub opposite_pivot: DVec2,\n\tpub center_of_transformation: DVec2,\n}\n\nimpl BoundingBoxManager {\n\t/// Calculates the transformed handle positions based on the bounding box and the transform\n\tpub fn evaluate_transform_handle_positions(&self) -> [DVec2; 8] {\n\t\tlet (left, top): (f64, f64) = self.bounds[0].into();\n\t\tlet (right, bottom): (f64, f64) = self.bounds[1].into();\n\t\t[\n\t\t\tDVec2::new(left, top),\n\t\t\tDVec2::new(left, (top + bottom) / 2.),\n\t\t\tDVec2::new(left, bottom),\n\t\t\tDVec2::new((left + right) / 2., top),\n\t\t\tDVec2::new((left + right) / 2., bottom),\n\t\t\tDVec2::new(right, top),\n\t\t\tDVec2::new(right, (top + bottom) / 2.),\n\t\t\tDVec2::new(right, bottom),\n\t\t]\n\t}\n\n\tpub fn get_closest_edge(&self, edges: EdgeBool, cursor: DVec2) -> EdgeBool {\n\t\tif !edges.0 && !edges.1 && !edges.2 && !edges.3 {\n\t\t\treturn (false, false, false, false);\n\t\t}\n\n\t\tlet cursor = self.transform.inverse().transform_point2(cursor);\n\t\tlet min = self.bounds[0].min(self.bounds[1]);\n\t\tlet max = self.bounds[0].max(self.bounds[1]);\n\n\t\tlet distances = [\n\t\t\tedges.0.then(|| (cursor - DVec2::new(cursor.x, min.y)).length_squared()),\n\t\t\tedges.1.then(|| (cursor - DVec2::new(cursor.x, max.y)).length_squared()),\n\t\t\tedges.2.then(|| (cursor - DVec2::new(min.x, cursor.y)).length_squared()),\n\t\t\tedges.3.then(|| (cursor - DVec2::new(max.x, cursor.y)).length_squared()),\n\t\t];\n\n\t\tlet min_distance = distances.iter().filter_map(|&x| x).min_by(|a, b| a.partial_cmp(b).unwrap());\n\n\t\tmatch min_distance {\n\t\t\tSome(min) => (\n\t\t\t\tedges.0 && distances[0].is_some_and(|d| (d - min).abs() < f64::EPSILON),\n\t\t\t\tedges.1 && distances[1].is_some_and(|d| (d - min).abs() < f64::EPSILON),\n\t\t\t\tedges.2 && distances[2].is_some_and(|d| (d - min).abs() < f64::EPSILON),\n\t\t\t\tedges.3 && distances[3].is_some_and(|d| (d - min).abs() < f64::EPSILON),\n\t\t\t),\n\t\t\tNone => (false, false, false, false),\n\t\t}\n\t}\n\n\tpub fn check_skew_handle(&self, cursor: DVec2, edge: EdgeBool) -> bool {\n\t\tlet Some([start, end]) = self.edge_endpoints_vector_from_edge_bool(edge) else { return false };\n\t\tif (end - start).length_squared() < MIN_LENGTH_FOR_SKEW_TRIANGLE_VISIBILITY.powi(2) {\n\t\t\treturn false;\n\t\t};\n\n\t\tlet edge_dir = (end - start).normalize();\n\t\tlet mid = start.midpoint(end);\n\n\t\tfor direction in [-edge_dir, edge_dir] {\n\t\t\tlet base = mid + direction * (3. + SKEW_TRIANGLE_OFFSET + SKEW_TRIANGLE_SIZE / 2.);\n\t\t\tlet extension = cursor - base;\n\t\t\tlet along_edge = extension.dot(edge_dir).abs();\n\t\t\tlet along_perp = extension.perp_dot(edge_dir).abs();\n\n\t\t\tif along_edge <= SKEW_TRIANGLE_SIZE / 2. && along_perp <= BOUNDS_SELECT_THRESHOLD {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\tfalse\n\t}\n\n\tpub fn edge_endpoints_vector_from_edge_bool(&self, edges: EdgeBool) -> Option<[DVec2; 2]> {\n\t\tlet quad = self.transform * Quad::from_box(self.bounds);\n\t\tlet category = self.overlay_display_category();\n\n\t\tif matches!(\n\t\t\tcategory,\n\t\t\tTransformCageSizeCategory::Full | TransformCageSizeCategory::Narrow | TransformCageSizeCategory::ReducedLandscape\n\t\t) {\n\t\t\tif edges.0 {\n\t\t\t\treturn Some([quad.top_left(), quad.top_right()]);\n\t\t\t}\n\t\t\tif edges.1 {\n\t\t\t\treturn Some([quad.bottom_left(), quad.bottom_right()]);\n\t\t\t}\n\t\t}\n\n\t\tif matches!(\n\t\t\tcategory,\n\t\t\tTransformCageSizeCategory::Full | TransformCageSizeCategory::Narrow | TransformCageSizeCategory::ReducedPortrait\n\t\t) {\n\t\t\tif edges.2 {\n\t\t\t\treturn Some([quad.top_left(), quad.bottom_left()]);\n\t\t\t}\n\t\t\tif edges.3 {\n\t\t\t\treturn Some([quad.top_right(), quad.bottom_right()]);\n\t\t\t}\n\t\t}\n\t\tNone\n\t}\n\n\tpub fn render_skew_gizmos(&mut self, overlay_context: &mut OverlayContext, hover_edge: EdgeBool) {\n\t\tlet mut draw_edge_triangles = |start: DVec2, end: DVec2| {\n\t\t\tif (end - start).length() < MIN_LENGTH_FOR_SKEW_TRIANGLE_VISIBILITY {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\toverlay_context.skew_handles(start, end);\n\t\t};\n\n\t\tif let Some([start, end]) = self.edge_endpoints_vector_from_edge_bool(hover_edge) {\n\t\t\tdraw_edge_triangles(start, end);\n\t\t}\n\t}\n\n\tpub fn over_extended_edge_midpoint(&self, mouse: DVec2, hover_edge: EdgeBool) -> bool {\n\t\tconst HALF_WIDTH_OUTER_RECT: f64 = RESIZE_HANDLE_SIZE / 2. + SKEW_TRIANGLE_OFFSET + SKEW_TRIANGLE_SIZE;\n\t\tconst HALF_WIDTH_INNER_RECT: f64 = SKEW_TRIANGLE_OFFSET + RESIZE_HANDLE_SIZE / 2.;\n\n\t\tconst INNER_QUAD_CORNER: DVec2 = DVec2::new(HALF_WIDTH_INNER_RECT, RESIZE_HANDLE_SIZE / 2.);\n\t\tconst FULL_QUAD_CORNER: DVec2 = DVec2::new(HALF_WIDTH_OUTER_RECT, BOUNDS_SELECT_THRESHOLD);\n\n\t\tlet quad = self.transform * Quad::from_box(self.bounds);\n\n\t\tlet Some([start, end]) = self.edge_endpoints_vector_from_edge_bool(hover_edge) else {\n\t\t\treturn false;\n\t\t};\n\t\tif (end - start).length() < MIN_LENGTH_FOR_SKEW_TRIANGLE_VISIBILITY {\n\t\t\treturn false;\n\t\t}\n\n\t\tlet angle;\n\t\tlet is_compact;\n\t\tif hover_edge.0 || hover_edge.1 {\n\t\t\tangle = (quad.top_left() - quad.top_right()).to_angle();\n\t\t\tis_compact = (quad.top_left() - quad.bottom_left()).length_squared() < MIN_LENGTH_FOR_RESIZE_TO_INCLUDE_INTERIOR.powi(2);\n\t\t} else if hover_edge.2 || hover_edge.3 {\n\t\t\tangle = (quad.top_left() - quad.bottom_left()).to_angle();\n\t\t\tis_compact = (quad.top_left() - quad.top_right()).length_squared() < MIN_LENGTH_FOR_RESIZE_TO_INCLUDE_INTERIOR.powi(2);\n\t\t} else {\n\t\t\treturn false;\n\t\t};\n\n\t\tlet has_triangle_hover = self.check_skew_handle(mouse, hover_edge);\n\t\tlet point = start.midpoint(end);\n\n\t\tif is_compact {\n\t\t\tlet upper_rect = DAffine2::from_angle_translation(angle, point) * Quad::from_box([-FULL_QUAD_CORNER.with_y(0.), FULL_QUAD_CORNER]);\n\t\t\tlet inter_triangle_quad = DAffine2::from_angle_translation(angle, point) * Quad::from_box([-INNER_QUAD_CORNER, INNER_QUAD_CORNER]);\n\n\t\t\tupper_rect.contains(mouse) || has_triangle_hover || inter_triangle_quad.contains(mouse)\n\t\t} else {\n\t\t\tlet rect = DAffine2::from_angle_translation(angle, point) * Quad::from_box([-FULL_QUAD_CORNER, FULL_QUAD_CORNER]);\n\n\t\t\trect.contains(mouse) || has_triangle_hover\n\t\t}\n\t}\n\n\tpub fn render_quad(&self, overlay_context: &mut OverlayContext) {\n\t\tlet quad = self.transform * Quad::from_box(self.bounds);\n\n\t\t// Draw the bounding box rectangle\n\t\toverlay_context.quad(quad, None, None);\n\t}\n\n\t/// Update the position of the bounding box and transform handles\n\tpub fn render_overlays(&mut self, overlay_context: &mut OverlayContext, render_quad: bool) {\n\t\tlet quad = self.transform * Quad::from_box(self.bounds);\n\t\tlet category = self.overlay_display_category();\n\n\t\tlet horizontal_edges = [quad.top_right().midpoint(quad.bottom_right()), quad.bottom_left().midpoint(quad.top_left())];\n\t\tlet vertical_edges = [quad.top_left().midpoint(quad.top_right()), quad.bottom_right().midpoint(quad.bottom_left())];\n\n\t\tif render_quad {\n\t\t\tself.render_quad(overlay_context);\n\t\t}\n\n\t\tlet horizontal_angle = (quad.top_left() - quad.bottom_left()).to_angle();\n\t\tlet vertical_angle = (quad.top_left() - quad.top_right()).to_angle();\n\n\t\t// Draw the horizontal midpoint drag handles\n\t\tif matches!(\n\t\t\tcategory,\n\t\t\tTransformCageSizeCategory::Full | TransformCageSizeCategory::Narrow | TransformCageSizeCategory::ReducedLandscape\n\t\t) {\n\t\t\tfor point in horizontal_edges {\n\t\t\t\toverlay_context.resize_handle(point, horizontal_angle);\n\t\t\t}\n\t\t}\n\n\t\t// Draw the vertical midpoint drag handles\n\t\tif matches!(\n\t\t\tcategory,\n\t\t\tTransformCageSizeCategory::Full | TransformCageSizeCategory::Narrow | TransformCageSizeCategory::ReducedPortrait\n\t\t) {\n\t\t\tfor point in vertical_edges {\n\t\t\t\toverlay_context.resize_handle(point, vertical_angle);\n\t\t\t}\n\t\t}\n\n\t\tlet angle = quad\n\t\t\t.edges()\n\t\t\t.map(|[x, y]| x.distance_squared(y))\n\t\t\t.into_iter()\n\t\t\t.reduce(|horizontal_distance, vertical_distance| if horizontal_distance > vertical_distance { horizontal_angle } else { vertical_angle })\n\t\t\t.unwrap_or_default();\n\n\t\t// Draw the corner drag handles\n\t\tif matches!(\n\t\t\tcategory,\n\t\t\tTransformCageSizeCategory::Full | TransformCageSizeCategory::ReducedBoth | TransformCageSizeCategory::ReducedLandscape | TransformCageSizeCategory::ReducedPortrait\n\t\t) {\n\t\t\tfor point in quad.0 {\n\t\t\t\toverlay_context.resize_handle(point, angle);\n\t\t\t}\n\t\t}\n\n\t\t// Draw the flat line endpoint drag handles\n\t\tif category == TransformCageSizeCategory::Flat {\n\t\t\toverlay_context.resize_handle(self.transform.transform_point2(self.bounds[0]), angle);\n\t\t\toverlay_context.resize_handle(self.transform.transform_point2(self.bounds[1]), angle);\n\t\t}\n\t}\n\n\t/// Find the [`TransformCageSizeCategory`] of this bounding box based on size thresholds.\n\tfn overlay_display_category(&self) -> TransformCageSizeCategory {\n\t\tlet quad = self.transform * Quad::from_box(self.bounds);\n\n\t\t// Check if the bounds are essentially the same because the width and height are smaller than MAX_LENGTH_FOR_NO_WIDTH_OR_HEIGHT\n\t\tif self.is_bounds_point() {\n\t\t\treturn TransformCageSizeCategory::Point;\n\t\t}\n\n\t\t// Check if the area is essentially zero because either the width or height is smaller than MAX_LENGTH_FOR_NO_WIDTH_OR_HEIGHT\n\t\tif self.is_bounds_flat() {\n\t\t\treturn TransformCageSizeCategory::Flat;\n\t\t}\n\n\t\tlet vertical_length = (quad.top_left() - quad.top_right()).length_squared();\n\t\tlet horizontal_length = (quad.bottom_left() - quad.top_left()).length_squared();\n\t\tlet corners_visible = vertical_length >= MIN_LENGTH_FOR_CORNERS_VISIBILITY.powi(2) && horizontal_length >= MIN_LENGTH_FOR_CORNERS_VISIBILITY.powi(2);\n\n\t\tif corners_visible {\n\t\t\tlet vertical_edge_visible = vertical_length > MIN_LENGTH_FOR_MIDPOINT_VISIBILITY.powi(2);\n\t\t\tlet horizontal_edge_visible = horizontal_length > MIN_LENGTH_FOR_MIDPOINT_VISIBILITY.powi(2);\n\n\t\t\treturn match (vertical_edge_visible, horizontal_edge_visible) {\n\t\t\t\t(true, true) => TransformCageSizeCategory::Full,\n\t\t\t\t(true, false) => TransformCageSizeCategory::ReducedPortrait,\n\t\t\t\t(false, true) => TransformCageSizeCategory::ReducedLandscape,\n\t\t\t\t(false, false) => TransformCageSizeCategory::ReducedBoth,\n\t\t\t};\n\t\t}\n\n\t\tTransformCageSizeCategory::Narrow\n\t}\n\n\t/// Determine if these bounds are flat ([`TransformCageSizeCategory::Flat`]), which means that the width and/or height is essentially zero and the bounds are a line with effectively no area. This can happen on actual lines (axis-aligned, i.e. drawn horizontally or vertically) or when an element is scaled to zero in X or Y. A flat transform cage can still be rotated by a transformation, but its local space remains flat.\n\tfn is_bounds_flat(&self) -> bool {\n\t\t(self.bounds[0] - self.bounds[1]).abs().cmple(DVec2::splat(MAX_LENGTH_FOR_NO_WIDTH_OR_HEIGHT)).any()\n\t}\n\n\t/// Determine if these bounds are point ([`TransformCageSizeCategory::Point`]), which means that the width and height are essentially zero and the bounds are a point with no area. This can happen on points when an element is scaled to zero in both X and Y, or if an element is just a single anchor point. A point transform cage cannot be rotated by a transformation, and its local space remains a point.\n\tfn is_bounds_point(&self) -> bool {\n\t\t(self.bounds[0] - self.bounds[1]).abs().cmple(DVec2::splat(MAX_LENGTH_FOR_NO_WIDTH_OR_HEIGHT)).all()\n\t}\n\n\t/// Determine if the given point in viewport space falls within the bounds of `self`.\n\tfn is_contained_in_bounds(&self, point: DVec2) -> bool {\n\t\tlet document_point = self.transform.inverse().transform_point2(point);\n\t\tQuad::from_box(self.bounds).contains(document_point)\n\t}\n\n\t/// Compute the threshold in viewport space. This only works with affine transforms as it assumes lines remain parallel.\n\tfn compute_viewport_threshold(&self, scalar: f64) -> [f64; 2] {\n\t\tlet inverse = self.transform.inverse();\n\n\t\tlet viewport_x = self.transform.transform_vector2(DVec2::X).normalize_or_zero() * scalar;\n\t\tlet viewport_y = self.transform.transform_vector2(DVec2::Y).normalize_or_zero() * scalar;\n\n\t\tlet threshold_x = inverse.transform_vector2(viewport_x).length();\n\t\tlet threshold_y = inverse.transform_vector2(viewport_y).length();\n\n\t\t[threshold_x, threshold_y]\n\t}\n\n\t/// Check if the user has selected the edge for dragging.\n\t///\n\t/// Returns which edge in the order:\n\t///\n\t/// `top, bottom, left, right`\n\tpub fn check_selected_edges(&self, cursor: DVec2) -> Option<EdgeBool> {\n\t\tlet cursor = self.transform.inverse().transform_point2(cursor);\n\n\t\tlet min = self.bounds[0].min(self.bounds[1]);\n\t\tlet max = self.bounds[0].max(self.bounds[1]);\n\n\t\tlet [threshold_x, threshold_y] = self.compute_viewport_threshold(BOUNDS_SELECT_THRESHOLD);\n\t\tlet [corner_min_x, corner_min_y] = self.compute_viewport_threshold(MIN_LENGTH_FOR_CORNERS_VISIBILITY);\n\t\tlet [edge_min_x, edge_min_y] = self.compute_viewport_threshold(MIN_LENGTH_FOR_RESIZE_TO_INCLUDE_INTERIOR);\n\t\tlet [midpoint_threshold_x, midpoint_threshold_y] = self.compute_viewport_threshold(MIN_LENGTH_FOR_EDGE_RESIZE_PRIORITY_OVER_CORNERS);\n\n\t\tif (min.x - cursor.x < threshold_x && min.y - cursor.y < threshold_y) && (cursor.x - max.x < threshold_x && cursor.y - max.y < threshold_y) {\n\t\t\tlet mut top = (cursor.y - min.y).abs() < threshold_y;\n\t\t\tlet mut bottom = (max.y - cursor.y).abs() < threshold_y;\n\t\t\tlet mut left = (cursor.x - min.x).abs() < threshold_x;\n\t\t\tlet mut right = (max.x - cursor.x).abs() < threshold_x;\n\n\t\t\tlet width = max.x - min.x;\n\t\t\tlet height = max.y - min.y;\n\n\t\t\tif (left || right) && (top || bottom) {\n\t\t\t\tlet horizontal_midpoint_x = (min.x + max.x) / 2.;\n\t\t\t\tlet vertical_midpoint_y = (min.y + max.y) / 2.;\n\n\t\t\t\tif (cursor.x - horizontal_midpoint_x).abs() < midpoint_threshold_x {\n\t\t\t\t\tleft = false;\n\t\t\t\t\tright = false;\n\t\t\t\t} else if (cursor.y - vertical_midpoint_y).abs() < midpoint_threshold_y {\n\t\t\t\t\ttop = false;\n\t\t\t\t\tbottom = false;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif width < edge_min_x || height <= edge_min_y {\n\t\t\t\tif self.transform_tampered {\n\t\t\t\t\treturn None;\n\t\t\t\t}\n\n\t\t\t\tif min.x < cursor.x && cursor.x < max.x && cursor.y < max.y && cursor.y > min.y {\n\t\t\t\t\treturn None;\n\t\t\t\t}\n\n\t\t\t\t// Prioritize single axis transformations on very small bounds\n\t\t\t\tif height < corner_min_y && (left || right) {\n\t\t\t\t\ttop = false;\n\t\t\t\t\tbottom = false;\n\t\t\t\t}\n\t\t\t\tif width < corner_min_x && (top || bottom) {\n\t\t\t\t\tleft = false;\n\t\t\t\t\tright = false;\n\t\t\t\t}\n\n\t\t\t\t// On bounds with no width/height, disallow transformation in the relevant axis\n\t\t\t\tif width < MAX_LENGTH_FOR_NO_WIDTH_OR_HEIGHT {\n\t\t\t\t\tleft = false;\n\t\t\t\t\tright = false;\n\t\t\t\t}\n\t\t\t\tif height < MAX_LENGTH_FOR_NO_WIDTH_OR_HEIGHT {\n\t\t\t\t\ttop = false;\n\t\t\t\t\tbottom = false;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif top || bottom || left || right {\n\t\t\t\treturn Some((top, bottom, left, right));\n\t\t\t}\n\t\t}\n\n\t\tNone\n\t}\n\n\t/// Check if the user is rotating with the bounds\n\tpub fn check_rotate(&self, cursor: DVec2) -> bool {\n\t\tif self.is_contained_in_bounds(cursor) {\n\t\t\treturn false;\n\t\t}\n\t\tlet [threshold_x, threshold_y] = self.compute_viewport_threshold(BOUNDS_ROTATE_THRESHOLD);\n\t\tlet cursor = self.transform.inverse().transform_point2(cursor);\n\n\t\tlet flat = self.is_bounds_flat();\n\t\tlet point = self.is_bounds_point();\n\t\tlet within_square_bounds = |center: &DVec2| center.x - threshold_x < cursor.x && cursor.x < center.x + threshold_x && center.y - threshold_y < cursor.y && cursor.y < center.y + threshold_y;\n\t\tif point {\n\t\t\tfalse\n\t\t} else if flat {\n\t\t\t[self.bounds[0], self.bounds[1]].iter().any(within_square_bounds)\n\t\t} else {\n\t\t\tself.evaluate_transform_handle_positions().iter().any(within_square_bounds)\n\t\t}\n\t}\n\n\t/// Gets the required mouse cursor to show resizing bounds or optionally rotation\n\tpub fn get_cursor(&self, input: &InputPreprocessorMessageHandler, rotate: bool, dragging_bounds: bool, skew_edge: Option<EdgeBool>) -> MouseCursorIcon {\n\t\tlet edges = self.check_selected_edges(input.mouse.position);\n\n\t\tlet is_near_square = edges.is_some_and(|hover_edge| self.over_extended_edge_midpoint(input.mouse.position, hover_edge));\n\t\tif dragging_bounds\n\t\t\t&& is_near_square\n\t\t\t&& let Some(skew_edge) = skew_edge\n\t\t\t&& self.check_skew_handle(input.mouse.position, skew_edge)\n\t\t{\n\t\t\tif skew_edge.0 || skew_edge.1 {\n\t\t\t\treturn MouseCursorIcon::EWResize;\n\t\t\t} else if skew_edge.2 || skew_edge.3 {\n\t\t\t\treturn MouseCursorIcon::NSResize;\n\t\t\t}\n\t\t};\n\n\t\tmatch edges {\n\t\t\tSome((top, bottom, left, right)) => match (top, bottom, left, right) {\n\t\t\t\t(true, _, false, false) | (_, true, false, false) => MouseCursorIcon::NSResize,\n\t\t\t\t(false, false, true, _) | (false, false, _, true) => MouseCursorIcon::EWResize,\n\t\t\t\t(true, _, true, _) | (_, true, _, true) => MouseCursorIcon::NWSEResize,\n\t\t\t\t(true, _, _, true) | (_, true, true, _) => MouseCursorIcon::NESWResize,\n\t\t\t\t_ => MouseCursorIcon::Default,\n\t\t\t},\n\t\t\t_ if rotate && self.check_rotate(input.mouse.position) => MouseCursorIcon::Rotate,\n\t\t\t_ => MouseCursorIcon::Default,\n\t\t}\n\t}\n}\n\n#[cfg(test)]\nmod tests {\n\tuse super::*;\n\n\t#[test]\n\tfn skew_transform_singular() {\n\t\tfor edge in [\n\t\t\tSelectedEdges::new(true, false, false, false, [DVec2::NEG_ONE, DVec2::ONE]),\n\t\t\tSelectedEdges::new(false, true, false, false, [DVec2::NEG_ONE, DVec2::ONE]),\n\t\t\tSelectedEdges::new(false, false, true, false, [DVec2::NEG_ONE, DVec2::ONE]),\n\t\t\tSelectedEdges::new(false, false, false, true, [DVec2::NEG_ONE, DVec2::ONE]),\n\t\t] {\n\t\t\t// The determinant is 0.\n\t\t\tlet transform = DAffine2::from_cols_array(&[2.; 6]);\n\t\t\t// This shouldn't panic. We don't really care about the behavior in this test.\n\t\t\tlet _ = edge.skew_transform(DVec2::new(1.5, 1.5), transform, false);\n\t\t}\n\t}\n\n\t#[test]\n\tfn skew_transform_correct() {\n\t\tfor edge in [\n\t\t\tSelectedEdges::new(true, false, false, false, [DVec2::NEG_ONE, DVec2::ONE]),\n\t\t\tSelectedEdges::new(false, true, false, false, [DVec2::NEG_ONE, DVec2::ONE]),\n\t\t\tSelectedEdges::new(false, false, true, false, [DVec2::NEG_ONE, DVec2::ONE]),\n\t\t\tSelectedEdges::new(false, false, false, true, [DVec2::NEG_ONE, DVec2::ONE]),\n\t\t] {\n\t\t\t// Random transform with det != 0.\n\t\t\tlet to_viewport_transform = DAffine2::from_cols_array(&[2., 1., 0., 1., 2., 3.]);\n\t\t\t// Random mouse position.\n\t\t\tlet mouse = DVec2::new(1.5, 1.5);\n\t\t\tlet final_transform = edge.skew_transform(mouse, to_viewport_transform, false);\n\n\t\t\t// This is the current handle that goes under the mouse.\n\t\t\tlet opposite = edge.pivot_from_bounds(edge.bounds[0], edge.bounds[1]);\n\t\t\tlet dragging_point = edge.pivot_from_bounds(edge.bounds[1], edge.bounds[0]);\n\n\t\t\tlet viewport_dragging_point = to_viewport_transform.transform_point2(dragging_point);\n\t\t\tlet parallel_to_x = edge.top || edge.bottom;\n\t\t\tlet parallel_to_y = !parallel_to_x && (edge.left || edge.right);\n\n\t\t\tlet drag_vector = mouse - viewport_dragging_point;\n\t\t\tlet document_drag_vector = to_viewport_transform.inverse().transform_vector2(drag_vector);\n\n\t\t\tlet sign = if edge.top || edge.left { -1. } else { 1. };\n\t\t\tlet scale_factor = (edge.bounds[1] - edge.bounds[0])[parallel_to_x as usize].abs().recip() * sign;\n\t\t\tlet scaled_document_drag = document_drag_vector * scale_factor;\n\n\t\t\tlet skew = DAffine2::from_mat2(DMat2::from_cols_array(&[\n\t\t\t\t1.,\n\t\t\t\tif parallel_to_y { scaled_document_drag.y } else { 0. },\n\t\t\t\tif parallel_to_x { scaled_document_drag.x } else { 0. },\n\t\t\t\t1.,\n\t\t\t]));\n\n\t\t\tlet constructed_transform = DAffine2::from_translation(opposite) * skew * DAffine2::from_translation(-opposite);\n\n\t\t\tassert_eq!(constructed_transform, final_transform);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/common_functionality/utility_functions.rs",
    "content": "use super::snapping::{SnapCandidatePoint, SnapData, SnapManager};\nuse super::transformation_cage::{BoundingBoxManager, SizeSnapData};\nuse crate::consts::ROTATE_INCREMENT;\nuse crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::portfolio::document::utility_types::network_interface::{NodeNetworkInterface, OutputConnector};\nuse crate::messages::portfolio::document::utility_types::transformation::Selected;\nuse crate::messages::prelude::*;\nuse crate::messages::tool::common_functionality::graph_modification_utils::{NodeGraphLayer, get_text};\nuse crate::messages::tool::common_functionality::transformation_cage::SelectedEdges;\nuse crate::messages::tool::tool_messages::path_tool::PathOverlayMode;\nuse crate::messages::tool::utility_types::ToolType;\nuse glam::{DAffine2, DVec2};\nuse graph_craft::concrete;\nuse graph_craft::document::value::TaggedValue;\nuse graphene_std::renderer::Quad;\nuse graphene_std::subpath::{Bezier, BezierHandles};\nuse graphene_std::table::Table;\nuse graphene_std::text::FontCache;\nuse graphene_std::vector::algorithms::bezpath_algorithms::pathseg_compute_lookup_table;\nuse graphene_std::vector::misc::{HandleId, ManipulatorPointId, dvec2_to_point};\nuse graphene_std::vector::{HandleExt, PointId, SegmentId, Vector, VectorModification, VectorModificationType};\nuse kurbo::{CubicBez, DEFAULT_ACCURACY, Line, ParamCurve, PathSeg, Point, QuadBez, Shape};\n\n/// Determines if a path should be extended. Goal in viewport space. Returns the path and if it is extending from the start, if applicable.\npub fn should_extend(document: &DocumentMessageHandler, goal: DVec2, tolerance: f64, layers: impl Iterator<Item = LayerNodeIdentifier>) -> Option<(LayerNodeIdentifier, PointId, DVec2)> {\n\tclosest_point(document, goal, tolerance, layers, |_| false)\n}\n\n/// Determine the closest point to the goal point under max_distance.\n/// Additionally exclude checking closeness to the point which given to exclude() returns true.\npub fn closest_point<T>(\n\tdocument: &DocumentMessageHandler,\n\tgoal: DVec2,\n\tmax_distance: f64,\n\tlayers: impl Iterator<Item = LayerNodeIdentifier>,\n\texclude: T,\n) -> Option<(LayerNodeIdentifier, PointId, DVec2)>\nwhere\n\tT: Fn(PointId) -> bool,\n{\n\tlet mut best = None;\n\tlet mut best_distance_squared = max_distance * max_distance;\n\tfor layer in layers {\n\t\tlet viewspace = document.metadata().transform_to_viewport(layer);\n\t\tlet Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };\n\t\tfor id in vector.anchor_points() {\n\t\t\tif exclude(id) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tlet Some(point) = vector.point_domain.position_from_id(id) else { continue };\n\n\t\t\tlet distance_squared = viewspace.transform_point2(point).distance_squared(goal);\n\n\t\t\tif distance_squared < best_distance_squared {\n\t\t\t\tbest = Some((layer, id, point));\n\t\t\t\tbest_distance_squared = distance_squared;\n\t\t\t}\n\t\t}\n\t}\n\n\tbest\n}\n\n/// Calculates the bounding box of the layer's text, based on the settings for max width and height specified in the typesetting config.\npub fn text_bounding_box(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, font_cache: &FontCache) -> Quad {\n\tlet Some((text, font, typesetting, per_glyph_instances)) = get_text(layer, &document.network_interface) else {\n\t\treturn Quad::from_box([DVec2::ZERO, DVec2::ZERO]);\n\t};\n\n\tlet far = graphene_std::text::bounding_box(text, font, font_cache, typesetting, false);\n\n\t// TODO: Once the instance tables refactor is complete and per_glyph_instances can be removed (since it'll be the default),\n\t// TODO: remove this because the top of the dashed bounding overlay should no longer be based on the first line's baseline.\n\tlet vertical_offset = if per_glyph_instances {\n\t\tDVec2::NEG_Y * typesetting.font_size * (1. + (typesetting.line_height_ratio - 1.) / 2.)\n\t} else {\n\t\tDVec2::ZERO\n\t};\n\n\tQuad::from_box([DVec2::ZERO + vertical_offset, far + vertical_offset])\n}\n\npub fn calculate_segment_angle(anchor: PointId, segment: SegmentId, vector: &Vector, prefer_handle_direction: bool) -> Option<f64> {\n\tlet is_start = |point: PointId, segment: SegmentId| vector.segment_start_from_id(segment) == Some(point);\n\tlet anchor_position = vector.point_domain.position_from_id(anchor)?;\n\tlet end_handle = ManipulatorPointId::EndHandle(segment).get_position(vector);\n\tlet start_handle = ManipulatorPointId::PrimaryHandle(segment).get_position(vector);\n\n\tlet start_point = if is_start(anchor, segment) {\n\t\tvector.segment_end_from_id(segment).and_then(|id| vector.point_domain.position_from_id(id))\n\t} else {\n\t\tvector.segment_start_from_id(segment).and_then(|id| vector.point_domain.position_from_id(id))\n\t};\n\n\tlet required_handle = if is_start(anchor, segment) {\n\t\tstart_handle\n\t\t\t.filter(|&handle| prefer_handle_direction && handle != anchor_position)\n\t\t\t.or(end_handle.filter(|&handle| Some(handle) != start_point))\n\t\t\t.or(start_point)\n\t} else {\n\t\tend_handle\n\t\t\t.filter(|&handle| prefer_handle_direction && handle != anchor_position)\n\t\t\t.or(start_handle.filter(|&handle| Some(handle) != start_point))\n\t\t\t.or(start_point)\n\t};\n\n\trequired_handle.map(|handle| -(handle - anchor_position).angle_to(DVec2::X))\n}\n\npub fn adjust_handle_colinearity(handle: HandleId, anchor_position: DVec2, target_control_point: DVec2, vector: &Vector, layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>) {\n\tlet Some(other_handle) = vector.other_colinear_handle(handle) else { return };\n\tlet Some(handle_position) = other_handle.to_manipulator_point().get_position(vector) else {\n\t\treturn;\n\t};\n\tlet Some(direction) = (anchor_position - target_control_point).try_normalize() else { return };\n\n\tlet new_relative_position = (handle_position - anchor_position).length() * direction;\n\tlet modification_type = other_handle.set_relative_position(new_relative_position);\n\n\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n}\n\npub fn restore_previous_handle_position(\n\thandle: HandleId,\n\toriginal_c: DVec2,\n\tanchor_position: DVec2,\n\tvector: &Vector,\n\tlayer: LayerNodeIdentifier,\n\tresponses: &mut VecDeque<Message>,\n) -> Option<HandleId> {\n\tlet other_handle = vector.other_colinear_handle(handle)?;\n\tlet handle_position = other_handle.to_manipulator_point().get_position(vector)?;\n\tlet direction = (anchor_position - original_c).try_normalize()?;\n\n\tlet old_relative_position = (handle_position - anchor_position).length() * direction;\n\tlet modification_type = other_handle.set_relative_position(old_relative_position);\n\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\n\tlet handles = [handle, other_handle];\n\tlet modification_type = VectorModificationType::SetG1Continuous { handles, enabled: false };\n\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\n\tSome(other_handle)\n}\n\npub fn restore_g1_continuity(handle: HandleId, other_handle: HandleId, control_point: DVec2, anchor_position: DVec2, vector: &Vector, layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>) {\n\tlet Some(handle_position) = other_handle.to_manipulator_point().get_position(vector) else {\n\t\treturn;\n\t};\n\tlet Some(direction) = (anchor_position - control_point).try_normalize() else { return };\n\n\tlet new_relative_position = (handle_position - anchor_position).length() * direction;\n\tlet modification_type = other_handle.set_relative_position(new_relative_position);\n\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\n\tlet handles = [handle, other_handle];\n\tlet modification_type = VectorModificationType::SetG1Continuous { handles, enabled: true };\n\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n}\n\n/// Check whether a point is visible in the current overlay mode.\npub fn is_visible_point(\n\tmanipulator_point_id: ManipulatorPointId,\n\tvector: &Vector,\n\tpath_overlay_mode: PathOverlayMode,\n\tfrontier_handles_for_layer: Option<&HashMap<SegmentId, Vec<PointId>>>,\n\tselected_segments: &[SegmentId],\n\tselected_points: &HashSet<ManipulatorPointId>,\n) -> bool {\n\tmatch manipulator_point_id {\n\t\tManipulatorPointId::Anchor(_) => true,\n\t\tManipulatorPointId::EndHandle(segment_id) | ManipulatorPointId::PrimaryHandle(segment_id) => {\n\t\t\tmatch (path_overlay_mode, selected_points.len() == 1) {\n\t\t\t\t(PathOverlayMode::AllHandles, _) => true,\n\t\t\t\t(PathOverlayMode::SelectedPointHandles, _) | (PathOverlayMode::FrontierHandles, true) => {\n\t\t\t\t\tif selected_segments.contains(&segment_id) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Either the segment is a part of selected segments or the opposite handle is a part of existing selection\n\t\t\t\t\tlet Some(handle_pair) = manipulator_point_id.get_handle_pair(vector) else { return false };\n\t\t\t\t\tlet other_handle = handle_pair[1].to_manipulator_point();\n\n\t\t\t\t\t// Return whether the list of selected points contain the other handle\n\t\t\t\t\tselected_points.contains(&other_handle)\n\t\t\t\t}\n\t\t\t\t(PathOverlayMode::FrontierHandles, false) => {\n\t\t\t\t\tlet Some(anchor) = manipulator_point_id.get_anchor(vector) else {\n\t\t\t\t\t\twarn!(\"No anchor for selected handle\");\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t};\n\t\t\t\t\tlet Some(frontier_handles) = frontier_handles_for_layer else {\n\t\t\t\t\t\twarn!(\"No frontier handles info provided\");\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t};\n\n\t\t\t\t\tfrontier_handles.get(&segment_id).map(|anchors| anchors.contains(&anchor)).unwrap_or_default()\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\npub fn is_intersecting(bezier: Bezier, quad: [DVec2; 2], transform: DAffine2) -> bool {\n\tlet to_layerspace = transform.inverse();\n\tlet quad = [to_layerspace.transform_point2(quad[0]), to_layerspace.transform_point2(quad[1])];\n\tlet start = Point::new(bezier.start.x, bezier.start.y);\n\tlet end = Point::new(bezier.end.x, bezier.end.y);\n\tlet segment = match bezier.handles {\n\t\tBezierHandles::Cubic { handle_start, handle_end } => {\n\t\t\tlet p1 = Point::new(handle_start.x, handle_start.y);\n\t\t\tlet p2 = Point::new(handle_end.x, handle_end.y);\n\t\t\tPathSeg::Cubic(CubicBez::new(start, p1, p2, end))\n\t\t}\n\t\tBezierHandles::Quadratic { handle } => {\n\t\t\tlet p1 = Point::new(handle.x, handle.y);\n\t\t\tPathSeg::Quad(QuadBez::new(start, p1, end))\n\t\t}\n\t\tBezierHandles::Linear => PathSeg::Line(Line::new(start, end)),\n\t};\n\n\t// Create a list of all the sides\n\tlet sides = [\n\t\tLine::new((quad[0].x, quad[0].y), (quad[1].x, quad[0].y)),\n\t\tLine::new((quad[0].x, quad[0].y), (quad[0].x, quad[1].y)),\n\t\tLine::new((quad[1].x, quad[1].y), (quad[1].x, quad[0].y)),\n\t\tLine::new((quad[1].x, quad[1].y), (quad[0].x, quad[1].y)),\n\t];\n\n\tlet mut is_intersecting = false;\n\tfor line in sides {\n\t\tlet intersections = segment.intersect_line(line);\n\t\tlet mut intersects = false;\n\t\tfor intersection in intersections {\n\t\t\tif intersection.line_t <= 1. && intersection.line_t >= 0. && intersection.segment_t <= 1. && intersection.segment_t >= 0. {\n\t\t\t\t// There is a valid intersection point\n\t\t\t\tintersects = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif intersects {\n\t\t\tis_intersecting = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\tis_intersecting\n}\n\n#[allow(clippy::too_many_arguments)]\npub fn resize_bounds(\n\tdocument: &DocumentMessageHandler,\n\tresponses: &mut VecDeque<Message>,\n\tbounds: &mut BoundingBoxManager,\n\tdragging_layers: &mut Vec<LayerNodeIdentifier>,\n\tsnap_manager: &mut SnapManager,\n\tsnap_candidates: &mut Vec<SnapCandidatePoint>,\n\tinput: &InputPreprocessorMessageHandler,\n\tviewport: &ViewportMessageHandler,\n\tcenter: bool,\n\tconstrain: bool,\n\ttool: ToolType,\n) {\n\tif let Some(movement) = &mut bounds.selected_edges {\n\t\tlet center = center.then_some(bounds.center_of_transformation);\n\t\tlet snap = Some(SizeSnapData {\n\t\t\tmanager: snap_manager,\n\t\t\tpoints: snap_candidates,\n\t\t\tsnap_data: SnapData::ignore(document, input, viewport, dragging_layers),\n\t\t});\n\t\tlet (position, size) = movement.new_size(input.mouse.position, bounds.original_bound_transform, center, constrain, snap);\n\t\tlet (delta, mut pivot) = movement.bounds_to_scale_transform(position, size);\n\n\t\tlet pivot_transform = DAffine2::from_translation(pivot);\n\t\tlet transformation = pivot_transform * delta * pivot_transform.inverse();\n\n\t\tdragging_layers.retain(|layer| {\n\t\t\tif *layer != LayerNodeIdentifier::ROOT_PARENT {\n\t\t\t\tdocument.network_interface.document_network().nodes.contains_key(&layer.to_node())\n\t\t\t} else {\n\t\t\t\tlog::error!(\"ROOT_PARENT should not be part of layers_dragging\");\n\t\t\t\tfalse\n\t\t\t}\n\t\t});\n\n\t\tlet mut selected = Selected::new(&mut bounds.original_transforms, &mut pivot, dragging_layers, responses, &document.network_interface, None, &tool, None);\n\t\tselected.apply_transformation(bounds.original_bound_transform * transformation * bounds.original_bound_transform.inverse(), None);\n\t}\n}\n\n#[allow(clippy::too_many_arguments)]\npub fn rotate_bounds(\n\tdocument: &DocumentMessageHandler,\n\tresponses: &mut VecDeque<Message>,\n\tbounds: &mut BoundingBoxManager,\n\tdragging_layers: &mut Vec<LayerNodeIdentifier>,\n\tdrag_start: DVec2,\n\tmouse_position: DVec2,\n\tsnap_angle: bool,\n\ttool: ToolType,\n) {\n\tlet angle = {\n\t\tlet start_offset = drag_start - bounds.center_of_transformation;\n\t\tlet end_offset = mouse_position - bounds.center_of_transformation;\n\t\tstart_offset.angle_to(end_offset)\n\t};\n\n\tlet snapped_angle = if snap_angle {\n\t\tlet snap_resolution = ROTATE_INCREMENT.to_radians();\n\t\t(angle / snap_resolution).round() * snap_resolution\n\t} else {\n\t\tangle\n\t};\n\n\tlet delta = DAffine2::from_angle(snapped_angle);\n\n\tdragging_layers.retain(|layer| {\n\t\tif *layer != LayerNodeIdentifier::ROOT_PARENT {\n\t\t\tdocument.network_interface.document_network().nodes.contains_key(&layer.to_node())\n\t\t} else {\n\t\t\tlog::error!(\"ROOT_PARENT should not be part of replacement_selected_layers\");\n\t\t\tfalse\n\t\t}\n\t});\n\n\tlet mut selected = Selected::new(\n\t\t&mut bounds.original_transforms,\n\t\t&mut bounds.center_of_transformation,\n\t\tdragging_layers,\n\t\tresponses,\n\t\t&document.network_interface,\n\t\tNone,\n\t\t&tool,\n\t\tNone,\n\t);\n\tselected.update_transforms(delta, None, None);\n}\n\npub fn skew_bounds(\n\tdocument: &DocumentMessageHandler,\n\tresponses: &mut VecDeque<Message>,\n\tbounds: &mut BoundingBoxManager,\n\tfree_movement: bool,\n\tlayers: &mut Vec<LayerNodeIdentifier>,\n\tmouse_position: DVec2,\n\ttool: ToolType,\n) {\n\tif let Some(movement) = &mut bounds.selected_edges {\n\t\tlet mut pivot = DVec2::ZERO;\n\n\t\tlet transformation = movement.skew_transform(mouse_position, bounds.original_bound_transform, free_movement);\n\n\t\tlayers.retain(|layer| {\n\t\t\tif *layer != LayerNodeIdentifier::ROOT_PARENT {\n\t\t\t\tdocument.network_interface.document_network().nodes.contains_key(&layer.to_node())\n\t\t\t} else {\n\t\t\t\tlog::error!(\"ROOT_PARENT should not be part of layers_dragging\");\n\t\t\t\tfalse\n\t\t\t}\n\t\t});\n\n\t\tlet mut selected = Selected::new(&mut bounds.original_transforms, &mut pivot, layers, responses, &document.network_interface, None, &tool, None);\n\t\tselected.apply_transformation(bounds.original_bound_transform * transformation * bounds.original_bound_transform.inverse(), None);\n\t}\n}\n\n// TODO: Replace returned tuple (where at most 1 element is true at a time) with an enum.\n/// Returns the tuple (resize, rotate, skew).\npub fn transforming_transform_cage(\n\tdocument: &DocumentMessageHandler,\n\tmut bounding_box_manager: &mut Option<BoundingBoxManager>,\n\tinput: &InputPreprocessorMessageHandler,\n\tresponses: &mut VecDeque<Message>,\n\tlayers_dragging: &mut Vec<LayerNodeIdentifier>,\n\tcenter_of_transformation: Option<DVec2>,\n) -> (bool, bool, bool) {\n\tlet dragging_bounds = bounding_box_manager.as_mut().and_then(|bounding_box| {\n\t\tlet edges = bounding_box.check_selected_edges(input.mouse.position);\n\n\t\tbounding_box.selected_edges = edges.map(|(top, bottom, left, right)| {\n\t\t\tlet selected_edges = SelectedEdges::new(top, bottom, left, right, bounding_box.bounds);\n\t\t\tbounding_box.opposite_pivot = selected_edges.calculate_pivot();\n\t\t\tselected_edges\n\t\t});\n\n\t\tedges\n\t});\n\n\tlet rotating_bounds = bounding_box_manager.as_ref().map(|bounding_box| bounding_box.check_rotate(input.mouse.position)).unwrap_or_default();\n\n\tlet selected: Vec<_> = document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface).collect();\n\n\tlet is_flat_layer = bounding_box_manager.as_ref().map(|bounding_box_manager| bounding_box_manager.transform_tampered).unwrap_or(true);\n\n\tif dragging_bounds.is_some() && !is_flat_layer {\n\t\tresponses.add(DocumentMessage::StartTransaction);\n\n\t\t*layers_dragging = selected;\n\n\t\tif let Some(bounds) = &mut bounding_box_manager {\n\t\t\tbounds.original_bound_transform = bounds.transform;\n\n\t\t\tlayers_dragging.retain(|layer| {\n\t\t\t\tif *layer != LayerNodeIdentifier::ROOT_PARENT {\n\t\t\t\t\tdocument.network_interface.document_network().nodes.contains_key(&layer.to_node())\n\t\t\t\t} else {\n\t\t\t\t\tlog::error!(\"ROOT_PARENT should not be part of layers_dragging\");\n\t\t\t\t\tfalse\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tbounds.center_of_transformation = center_of_transformation.unwrap_or_else(|| {\n\t\t\t\tdocument\n\t\t\t\t\t.network_interface\n\t\t\t\t\t.selected_nodes()\n\t\t\t\t\t.selected_visible_and_unlocked_layers_mean_average_origin(&document.network_interface)\n\t\t\t});\n\n\t\t\t// Check if we're hovering over a skew triangle\n\t\t\tlet edges = bounds.check_selected_edges(input.mouse.position);\n\t\t\tif let Some(edges) = edges {\n\t\t\t\tlet closest_edge = bounds.get_closest_edge(edges, input.mouse.position);\n\t\t\t\tif bounds.check_skew_handle(input.mouse.position, closest_edge) {\n\t\t\t\t\t// No resize or rotate, just skew\n\t\t\t\t\treturn (false, false, true);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Just resize, no rotate or skew\n\t\treturn (true, false, false);\n\t}\n\n\tif rotating_bounds {\n\t\tresponses.add(DocumentMessage::StartTransaction);\n\n\t\tif let Some(bounds) = &mut bounding_box_manager {\n\t\t\tlayers_dragging.retain(|layer| {\n\t\t\t\tif *layer != LayerNodeIdentifier::ROOT_PARENT {\n\t\t\t\t\tdocument.network_interface.document_network().nodes.contains_key(&layer.to_node())\n\t\t\t\t} else {\n\t\t\t\t\tlog::error!(\"ROOT_PARENT should not be part of layers_dragging\");\n\t\t\t\t\tfalse\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tbounds.center_of_transformation = center_of_transformation.unwrap_or_else(|| {\n\t\t\t\tdocument\n\t\t\t\t\t.network_interface\n\t\t\t\t\t.selected_nodes()\n\t\t\t\t\t.selected_visible_and_unlocked_layers_mean_average_origin(&document.network_interface)\n\t\t\t});\n\t\t}\n\n\t\t*layers_dragging = selected;\n\n\t\t// No resize or skew, just rotate\n\t\treturn (false, true, false);\n\t}\n\n\t// No resize, rotate, or skew\n\t(false, false, false)\n}\n\n/// Calculates similarity metric between new bezier curve and two old beziers by using sampled points.\n#[allow(clippy::too_many_arguments)]\npub fn log_optimization(a: f64, b: f64, p1: DVec2, p3: DVec2, d1: DVec2, d2: DVec2, points1: &[DVec2], n: usize) -> f64 {\n\tlet start_handle_length = a.exp();\n\tlet end_handle_length = b.exp();\n\n\t// Compute the handle positions of new bezier curve\n\tlet c1 = p1 + d1 * start_handle_length;\n\tlet c2 = p3 + d2 * end_handle_length;\n\n\tlet new_curve = PathSeg::Cubic(CubicBez::new(Point::new(p1.x, p1.y), Point::new(c1.x, c1.y), Point::new(c2.x, c2.y), Point::new(p3.x, p3.y)));\n\n\t// Sample 2*n points from new curve and get the L2 metric between all of points\n\tlet points = pathseg_compute_lookup_table(new_curve, Some(2 * n), false);\n\n\tlet dist = points1.iter().zip(points).map(|(p1, p2)| (p1.x - p2.x).powi(2) + (p1.y - p2.y).powi(2)).sum::<f64>();\n\n\tdist / (2 * n) as f64\n}\n\n/// Calculates optimal handle lengths with adam optimization.\n#[allow(clippy::too_many_arguments)]\npub fn find_two_param_best_approximate(p1: DVec2, p3: DVec2, d1: DVec2, d2: DVec2, min_len1: f64, min_len2: f64, further_segment: PathSeg, other_segment: PathSeg) -> (DVec2, DVec2) {\n\tlet h = 1e-6;\n\tlet tol = 1e-6;\n\tlet max_iter = 200;\n\n\tlet mut a = (5_f64).ln();\n\tlet mut b = (5_f64).ln();\n\n\tlet mut m_a = 0.;\n\tlet mut v_a = 0.;\n\tlet mut m_b = 0.;\n\tlet mut v_b = 0.;\n\n\tlet initial_alpha = 0.05;\n\tlet decay_rate: f64 = 0.99;\n\n\tlet beta1 = 0.9;\n\tlet beta2 = 0.999;\n\tlet epsilon = 1e-8;\n\n\tlet n = 20;\n\n\tlet further_segment = if further_segment.start().distance(dvec2_to_point(p1)) >= f64::EPSILON {\n\t\tfurther_segment.reverse()\n\t} else {\n\t\tfurther_segment\n\t};\n\n\tlet other_segment = if other_segment.end().distance(dvec2_to_point(p3)) >= f64::EPSILON {\n\t\tother_segment.reverse()\n\t} else {\n\t\tother_segment\n\t};\n\n\t// Now we sample points proportional to the lengths of the beziers\n\tlet l1 = further_segment.perimeter(DEFAULT_ACCURACY);\n\tlet l2 = other_segment.perimeter(DEFAULT_ACCURACY);\n\tlet ratio = l1 / (l1 + l2);\n\tlet n_points1 = ((2 * n) as f64 * ratio).floor() as usize;\n\tlet mut points1 = pathseg_compute_lookup_table(further_segment, Some(n_points1), false).collect::<Vec<_>>();\n\tlet mut points2 = pathseg_compute_lookup_table(other_segment, Some(n), false).collect::<Vec<_>>();\n\tpoints1.append(&mut points2);\n\n\tlet f = |a: f64, b: f64| -> f64 { log_optimization(a, b, p1, p3, d1, d2, &points1, n) };\n\n\tfor t in 1..=max_iter {\n\t\tlet dfa = (f(a + h, b) - f(a - h, b)) / (2. * h);\n\t\tlet dfb = (f(a, b + h) - f(a, b - h)) / (2. * h);\n\n\t\tm_a = beta1 * m_a + (1. - beta1) * dfa;\n\t\tm_b = beta1 * m_b + (1. - beta1) * dfb;\n\n\t\tv_a = beta2 * v_a + (1. - beta2) * dfa * dfa;\n\t\tv_b = beta2 * v_b + (1. - beta2) * dfb * dfb;\n\n\t\tlet m_a_hat = m_a / (1. - beta1.powi(t));\n\t\tlet v_a_hat = v_a / (1. - beta2.powi(t));\n\t\tlet m_b_hat = m_b / (1. - beta1.powi(t));\n\t\tlet v_b_hat = v_b / (1. - beta2.powi(t));\n\n\t\tlet alpha_t = initial_alpha * decay_rate.powi(t);\n\n\t\t// Update log-lengths\n\t\ta -= alpha_t * m_a_hat / (v_a_hat.sqrt() + epsilon);\n\t\tb -= alpha_t * m_b_hat / (v_b_hat.sqrt() + epsilon);\n\n\t\t// Convergence check\n\t\tif dfa.abs() < tol && dfb.abs() < tol {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tlet len1 = a.exp().max(min_len1);\n\tlet len2 = b.exp().max(min_len2);\n\n\t(d1 * len1, d2 * len2)\n}\n\npub fn make_path_editable_is_allowed(network_interface: &mut NodeNetworkInterface) -> Option<LayerNodeIdentifier> {\n\t// Must have exactly one layer selected\n\tlet selected_nodes = network_interface.selected_nodes();\n\tlet mut selected_layers = selected_nodes.selected_layers(network_interface.document_metadata());\n\tlet first_layer = selected_layers.next()?;\n\tif selected_layers.next().is_some() {\n\t\treturn None;\n\t}\n\tfor _ in selected_layers {}\n\n\t// Must be a layer of type Table<Vector>\n\tlet node_id = NodeGraphLayer::new(first_layer, network_interface).horizontal_layer_flow().nth(1)?;\n\n\tlet output_type = network_interface.output_type(&OutputConnector::node(node_id, 0), &[]);\n\tif output_type.compiled_nested_type() != Some(&concrete!(Table<Vector>)) {\n\t\treturn None;\n\t}\n\n\t// Must not already have an existing Path node, in the right-most part of the layer chain, which has an empty set of modifications\n\t// (otherwise users could repeatedly keep running this command and stacking up empty Path nodes)\n\tif let Some(TaggedValue::VectorModification(modifications)) = NodeGraphLayer::new(first_layer, network_interface).find_input(&DefinitionIdentifier::Network(\"Path\".into()), 1)\n\t\t&& modifications.as_ref() == &VectorModification::default()\n\t{\n\t\treturn None;\n\t}\n\n\tSome(first_layer)\n}\n"
  },
  {
    "path": "editor/src/messages/tool/mod.rs",
    "content": "mod tool_message;\nmod tool_message_handler;\n\npub mod common_functionality;\npub mod tool_messages;\npub mod transform_layer;\npub mod utility_types;\n\n#[doc(inline)]\npub use tool_message::{ToolMessage, ToolMessageDiscriminant};\n#[doc(inline)]\npub use tool_message_handler::{ToolMessageContext, ToolMessageHandler};\n#[doc(inline)]\npub use transform_layer::{TransformLayerMessage, TransformLayerMessageDiscriminant};\n"
  },
  {
    "path": "editor/src/messages/tool/tool_message.rs",
    "content": "use super::utility_types::ToolType;\nuse crate::messages::preferences::SelectionMode;\nuse crate::messages::prelude::*;\nuse graphene_std::raster::color::Color;\n\n#[impl_message(Message, Tool)]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum ToolMessage {\n\t// Sub-messages\n\t#[child]\n\tTransformLayer(TransformLayerMessage),\n\n\t#[child]\n\tSelect(SelectToolMessage),\n\t#[child]\n\tArtboard(ArtboardToolMessage),\n\t#[child]\n\tNavigate(NavigateToolMessage),\n\t#[child]\n\tEyedropper(EyedropperToolMessage),\n\t#[child]\n\tFill(FillToolMessage),\n\t#[child]\n\tGradient(GradientToolMessage),\n\n\t#[child]\n\tPath(PathToolMessage),\n\t#[child]\n\tPen(PenToolMessage),\n\t#[child]\n\tFreehand(FreehandToolMessage),\n\t#[child]\n\tSpline(SplineToolMessage),\n\t#[child]\n\tShape(ShapeToolMessage),\n\t#[child]\n\tText(TextToolMessage),\n\n\t#[child]\n\tBrush(BrushToolMessage),\n\t// \t// #[child]\n\t// Heal(HealToolMessage),\n\t// \t// #[child]\n\t// Clone(CloneToolMessage),\n\t// \t// #[child]\n\t// Patch(PatchToolMessage),\n\t// \t// #[child]\n\t// Relight(RelightToolMessage),\n\t// \t// #[child]\n\t// Detail(DetailToolMessage),\n\n\t// Messages\n\n\t// General tools\n\tActivateToolSelect,\n\tActivateToolArtboard,\n\tActivateToolNavigate,\n\tActivateToolEyedropper,\n\tActivateToolFill,\n\tActivateToolGradient,\n\t// Vector tools\n\tActivateToolPath,\n\tActivateToolPen,\n\tActivateToolFreehand,\n\tActivateToolSpline,\n\tActivateToolShapeLine,\n\tActivateToolShapeRectangle,\n\tActivateToolShapeEllipse,\n\tActivateToolShape,\n\tActivateToolText,\n\t// Raster tools\n\tActivateToolBrush,\n\n\tActivateTool {\n\t\ttool_type: ToolType,\n\t},\n\tDeactivateTools,\n\tInitTools,\n\tPreUndo,\n\tRedo,\n\tRefreshToolOptions,\n\tRefreshToolShelf,\n\tResetColors,\n\tSelectWorkingColor {\n\t\tcolor: Color,\n\t\tprimary: bool,\n\t},\n\tSelectRandomWorkingColor {\n\t\tprimary: bool,\n\t},\n\tToggleSelectVsPath,\n\tSwapColors,\n\tUndo,\n\tUpdateCursor,\n\tUpdateHints,\n\tUpdateSelectionMode {\n\t\tselection_mode: SelectionMode,\n\t},\n}\n"
  },
  {
    "path": "editor/src/messages/tool/tool_message_handler.rs",
    "content": "use super::common_functionality::shape_editor::ShapeState;\nuse super::common_functionality::shapes::shape_utility::ShapeType::{Ellipse, Line, Rectangle};\nuse super::utility_types::{ToolActionMessageContext, ToolFsmState, tool_message_to_tool_type};\nuse crate::application::generate_uuid;\nuse crate::messages::layout::utility_types::widget_prelude::*;\nuse crate::messages::portfolio::document::overlays::utility_types::OverlayProvider;\nuse crate::messages::portfolio::utility_types::PersistentData;\nuse crate::messages::prelude::*;\nuse crate::messages::tool::transform_layer::transform_layer_message_handler::TransformLayerMessageContext;\nuse crate::messages::tool::utility_types::{HintData, ToolType};\nuse crate::node_graph_executor::NodeGraphExecutor;\nuse graphene_std::raster::color::Color;\n\nconst ARTBOARD_OVERLAY_PROVIDER: OverlayProvider = |context| DocumentMessage::DrawArtboardOverlays { context }.into();\n\n#[derive(ExtractField)]\npub struct ToolMessageContext<'a> {\n\tpub document_id: DocumentId,\n\tpub document: &'a mut DocumentMessageHandler,\n\tpub input: &'a InputPreprocessorMessageHandler,\n\tpub persistent_data: &'a PersistentData,\n\tpub node_graph: &'a NodeGraphExecutor,\n\tpub preferences: &'a PreferencesMessageHandler,\n\tpub viewport: &'a ViewportMessageHandler,\n}\n\n#[derive(Debug, Default, ExtractField)]\npub struct ToolMessageHandler {\n\tpub tool_state: ToolFsmState,\n\tpub transform_layer_handler: TransformLayerMessageHandler,\n\tpub shape_editor: ShapeState,\n\tpub tool_is_active: bool,\n}\n\n#[message_handler_data]\nimpl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler {\n\tfn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: ToolMessageContext) {\n\t\tlet ToolMessageContext {\n\t\t\tdocument_id,\n\t\t\tdocument,\n\t\t\tinput,\n\t\t\tpersistent_data,\n\t\t\tnode_graph,\n\t\t\tpreferences,\n\t\t\tviewport,\n\t\t} = context;\n\n\t\tmatch message {\n\t\t\t// Messages\n\t\t\tToolMessage::TransformLayer(message) => self.transform_layer_handler.process_message(\n\t\t\t\tmessage,\n\t\t\t\tresponses,\n\t\t\t\tTransformLayerMessageContext {\n\t\t\t\t\tdocument,\n\t\t\t\t\tinput,\n\t\t\t\t\ttool_data: &self.tool_state.tool_data,\n\t\t\t\t\tshape_editor: &mut self.shape_editor,\n\t\t\t\t\tviewport,\n\t\t\t\t},\n\t\t\t),\n\n\t\t\tToolMessage::ActivateToolSelect => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Select }),\n\t\t\tToolMessage::ActivateToolArtboard => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Artboard }),\n\t\t\tToolMessage::ActivateToolNavigate => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Navigate }),\n\t\t\tToolMessage::ActivateToolEyedropper => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Eyedropper }),\n\t\t\tToolMessage::ActivateToolText => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Text }),\n\t\t\tToolMessage::ActivateToolFill => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Fill }),\n\t\t\tToolMessage::ActivateToolGradient => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Gradient }),\n\n\t\t\tToolMessage::ActivateToolPath => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Path }),\n\t\t\tToolMessage::ActivateToolPen => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Pen }),\n\t\t\tToolMessage::ActivateToolFreehand => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Freehand }),\n\t\t\tToolMessage::ActivateToolSpline => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Spline }),\n\t\t\tToolMessage::ActivateToolShape => {\n\t\t\t\tif self.tool_state.tool_data.active_shape_type.is_some() {\n\t\t\t\t\tself.tool_state.tool_data.active_shape_type = None;\n\t\t\t\t\tself.tool_state.tool_data.active_tool_type = ToolType::Shape;\n\t\t\t\t}\n\t\t\t\tresponses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Shape });\n\t\t\t\t// Sync current_shape with the dropdown selection (options.shape_type)\n\t\t\t\tresponses.add(ShapeToolMessage::SyncShapeWithOptions);\n\t\t\t\tresponses.add(ShapeToolMessage::HideShapeTypeWidget { hide: false })\n\t\t\t}\n\t\t\tToolMessage::ActivateToolBrush => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Brush }),\n\t\t\tToolMessage::ActivateToolShapeLine | ToolMessage::ActivateToolShapeRectangle | ToolMessage::ActivateToolShapeEllipse => {\n\t\t\t\tlet shape = match message {\n\t\t\t\t\tToolMessage::ActivateToolShapeLine => Line,\n\t\t\t\t\tToolMessage::ActivateToolShapeRectangle => Rectangle,\n\t\t\t\t\tToolMessage::ActivateToolShapeEllipse => Ellipse,\n\t\t\t\t\t_ => unreachable!(),\n\t\t\t\t};\n\n\t\t\t\tself.tool_state.tool_data.active_shape_type = Some(shape.tool_type());\n\t\t\t\tresponses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Shape });\n\t\t\t\tresponses.add(ShapeToolMessage::HideShapeTypeWidget { hide: true });\n\t\t\t\tresponses.add(ShapeToolMessage::SetShape { shape });\n\t\t\t}\n\t\t\tToolMessage::ActivateTool { tool_type } => {\n\t\t\t\tlet tool_data = &mut self.tool_state.tool_data;\n\t\t\t\tlet old_tool = tool_data.active_tool_type.get_tool();\n\t\t\t\tlet tool_type = tool_type.get_tool();\n\n\t\t\t\tresponses.add(ToolMessage::RefreshToolOptions);\n\t\t\t\tresponses.add(ToolMessage::RefreshToolShelf);\n\n\t\t\t\t// Do nothing if switching to the same tool\n\t\t\t\tif self.tool_is_active && tool_type == old_tool {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif tool_type != ToolType::Shape {\n\t\t\t\t\ttool_data.active_shape_type = None;\n\t\t\t\t}\n\n\t\t\t\tself.tool_is_active = true;\n\n\t\t\t\t// Send the old and new tools a transition to their FSM Abort states\n\t\t\t\tlet mut send_abort_to_tool = |old_tool: ToolType, new_tool: ToolType, update_hints_and_cursor: bool| {\n\t\t\t\t\tif let Some(tool) = tool_data.tools.get_mut(&new_tool) {\n\t\t\t\t\t\tlet mut data = ToolActionMessageContext {\n\t\t\t\t\t\t\tdocument,\n\t\t\t\t\t\t\tdocument_id,\n\t\t\t\t\t\t\tglobal_tool_data: &self.tool_state.document_tool_data,\n\t\t\t\t\t\t\tinput,\n\t\t\t\t\t\t\tshape_editor: &mut self.shape_editor,\n\t\t\t\t\t\t\tnode_graph,\n\t\t\t\t\t\t\tpreferences,\n\t\t\t\t\t\t\tviewport,\n\t\t\t\t\t\t\tpersistent_data,\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tif let Some(tool_abort_message) = tool.event_to_message_map().tool_abort {\n\t\t\t\t\t\t\ttool.process_message(tool_abort_message, responses, &mut data);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif update_hints_and_cursor {\n\t\t\t\t\t\t\tif self.transform_layer_handler.is_transforming() {\n\t\t\t\t\t\t\t\tself.transform_layer_handler.hints(responses);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\ttool.process_message(ToolMessage::UpdateHints, responses, &mut data);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\ttool.process_message(ToolMessage::UpdateCursor, responses, &mut data);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// If a G/R/S transform is active while using Path, Select, Pen, or Shape,\n\t\t\t\t\t// and the user switches to a different tool, cancel the current transform\n\t\t\t\t\t// operation to avoid leaving it in an inconsistent state\n\t\t\t\t\tif matches!(old_tool, ToolType::Path | ToolType::Select | ToolType::Pen | ToolType::Shape) {\n\t\t\t\t\t\tresponses.add(TransformLayerMessage::CancelTransformOperation);\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tsend_abort_to_tool(old_tool, tool_type, true);\n\t\t\t\tsend_abort_to_tool(old_tool, old_tool, false);\n\n\t\t\t\t// Unsubscribe old tool from the broadcaster\n\t\t\t\ttool_data.tools.get(&tool_type).unwrap().deactivate(responses);\n\n\t\t\t\t// Store the new active tool\n\t\t\t\ttool_data.active_tool_type = tool_type;\n\n\t\t\t\t// Subscribe new tool\n\t\t\t\ttool_data.tools.get(&tool_type).unwrap().activate(responses);\n\n\t\t\t\t// Re-add the artboard overlay provider when tools are reactivated\n\t\t\t\tresponses.add(OverlaysMessage::AddProvider { provider: ARTBOARD_OVERLAY_PROVIDER });\n\n\t\t\t\t// Send the SelectionChanged message to the active tool, this will ensure the selection is updated\n\t\t\t\tresponses.add(EventMessage::SelectionChanged);\n\n\t\t\t\t// Update the working colors for the active tool\n\t\t\t\tresponses.add(EventMessage::WorkingColorChanged);\n\n\t\t\t\t// Send tool options to the frontend\n\t\t\t\tresponses.add(ToolMessage::RefreshToolOptions);\n\n\t\t\t\t// Notify the frontend about the new active tool to be displayed\n\t\t\t\tresponses.add(ToolMessage::RefreshToolShelf);\n\t\t\t}\n\t\t\tToolMessage::DeactivateTools => {\n\t\t\t\tlet tool_data = &mut self.tool_state.tool_data;\n\t\t\t\ttool_data.tools.get(&tool_data.active_tool_type).unwrap().deactivate(responses);\n\n\t\t\t\t// Unsubscribe the transform layer to selection change events\n\t\t\t\tresponses.add(BroadcastMessage::UnsubscribeEvent {\n\t\t\t\t\ton: EventMessage::SelectionChanged,\n\t\t\t\t\tsend: Box::new(TransformLayerMessage::SelectionChanged.into()),\n\t\t\t\t});\n\n\t\t\t\tresponses.add(OverlaysMessage::RemoveProvider { provider: ARTBOARD_OVERLAY_PROVIDER });\n\n\t\t\t\tHintData::clear_layout(responses);\n\t\t\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor: Default::default() });\n\n\t\t\t\tself.tool_is_active = false;\n\t\t\t}\n\t\t\tToolMessage::InitTools => {\n\t\t\t\t// Subscribe the transform layer to selection change events\n\t\t\t\tresponses.add(BroadcastMessage::SubscribeEvent {\n\t\t\t\t\ton: EventMessage::SelectionChanged,\n\t\t\t\t\tsend: Box::new(TransformLayerMessage::SelectionChanged.into()),\n\t\t\t\t});\n\n\t\t\t\tresponses.add(BroadcastMessage::SubscribeEvent {\n\t\t\t\t\ton: EventMessage::SelectionChanged,\n\t\t\t\t\tsend: Box::new(SelectToolMessage::SyncHistory.into()),\n\t\t\t\t});\n\n\t\t\t\tself.tool_is_active = true;\n\n\t\t\t\tlet tool_data = &mut self.tool_state.tool_data;\n\t\t\t\tlet document_data = &self.tool_state.document_tool_data;\n\t\t\t\tlet active_tool = &tool_data.active_tool_type;\n\n\t\t\t\t// Subscribe tool to broadcast messages\n\t\t\t\ttool_data.tools.get(active_tool).unwrap().activate(responses);\n\n\t\t\t\t// Register initial properties\n\t\t\t\ttool_data.tools.get(active_tool).unwrap().refresh_options(responses, persistent_data);\n\n\t\t\t\t// Notify the frontend about the initial active tool\n\t\t\t\ttool_data.send_layout(responses, LayoutTarget::ToolShelf, preferences.brush_tool);\n\n\t\t\t\t// Notify the frontend about the initial working colors\n\t\t\t\tdocument_data.update_working_colors(responses);\n\n\t\t\t\tlet mut data = ToolActionMessageContext {\n\t\t\t\t\tdocument,\n\t\t\t\t\tdocument_id,\n\t\t\t\t\tglobal_tool_data: &self.tool_state.document_tool_data,\n\t\t\t\t\tinput,\n\t\t\t\t\tshape_editor: &mut self.shape_editor,\n\t\t\t\t\tnode_graph,\n\t\t\t\t\tpreferences,\n\t\t\t\t\tviewport,\n\t\t\t\t\tpersistent_data,\n\t\t\t\t};\n\n\t\t\t\t// Set initial hints and cursor\n\t\t\t\ttool_data.active_tool_mut().process_message(ToolMessage::UpdateHints, responses, &mut data);\n\t\t\t\ttool_data.active_tool_mut().process_message(ToolMessage::UpdateCursor, responses, &mut data);\n\n\t\t\t\tresponses.add(OverlaysMessage::AddProvider { provider: ARTBOARD_OVERLAY_PROVIDER });\n\t\t\t}\n\t\t\tToolMessage::PreUndo => {\n\t\t\t\tlet tool_data = &mut self.tool_state.tool_data;\n\t\t\t\tif tool_data.active_tool_type != ToolType::Pen {\n\t\t\t\t\tresponses.add(EventMessage::ToolAbort);\n\t\t\t\t}\n\t\t\t}\n\t\t\tToolMessage::Redo => {\n\t\t\t\tlet tool_data = &mut self.tool_state.tool_data;\n\t\t\t\tif tool_data.active_tool_type == ToolType::Pen {\n\t\t\t\t\tresponses.add(PenToolMessage::Redo);\n\t\t\t\t}\n\t\t\t}\n\t\t\tToolMessage::RefreshToolOptions => {\n\t\t\t\tlet tool_data = &mut self.tool_state.tool_data;\n\n\t\t\t\ttool_data.tools.get(&tool_data.active_tool_type).unwrap().refresh_options(responses, persistent_data);\n\t\t\t}\n\t\t\tToolMessage::RefreshToolShelf => {\n\t\t\t\tlet tool_data = &mut self.tool_state.tool_data;\n\t\t\t\ttool_data.send_layout(responses, LayoutTarget::ToolShelf, preferences.brush_tool);\n\t\t\t}\n\t\t\tToolMessage::ResetColors => {\n\t\t\t\tlet document_data = &mut self.tool_state.document_tool_data;\n\n\t\t\t\tdocument_data.primary_color = Color::BLACK;\n\t\t\t\tdocument_data.secondary_color = Color::WHITE;\n\n\t\t\t\tdocument_data.update_working_colors(responses); // TODO: Make this an event\n\t\t\t}\n\t\t\tToolMessage::SelectRandomWorkingColor { primary } => {\n\t\t\t\t// Select a random working color (RGBA) based on an UUID\n\t\t\t\tlet document_data = &mut self.tool_state.document_tool_data;\n\n\t\t\t\tlet random_number = generate_uuid();\n\t\t\t\tlet r = (random_number >> 16) as u8;\n\t\t\t\tlet g = (random_number >> 8) as u8;\n\t\t\t\tlet b = random_number as u8;\n\t\t\t\tlet random_color = Color::from_rgba8_srgb(r, g, b, 255);\n\n\t\t\t\tif primary {\n\t\t\t\t\tdocument_data.primary_color = random_color;\n\t\t\t\t} else {\n\t\t\t\t\tdocument_data.secondary_color = random_color;\n\t\t\t\t}\n\n\t\t\t\tdocument_data.update_working_colors(responses); // TODO: Make this an event\n\t\t\t}\n\t\t\tToolMessage::SelectWorkingColor { color, primary } => {\n\t\t\t\tlet document_data = &mut self.tool_state.document_tool_data;\n\n\t\t\t\tif primary {\n\t\t\t\t\tdocument_data.primary_color = color;\n\t\t\t\t} else {\n\t\t\t\t\tdocument_data.secondary_color = color;\n\t\t\t\t}\n\n\t\t\t\tdocument_data.update_working_colors(responses); // TODO: Make this an event\n\t\t\t}\n\t\t\tToolMessage::ToggleSelectVsPath => {\n\t\t\t\t// If we have the select tool active, toggle to the path tool and vice versa\n\t\t\t\tlet tool_data = &mut self.tool_state.tool_data;\n\t\t\t\tlet active_tool_type = tool_data.active_tool_type;\n\t\t\t\tif active_tool_type == ToolType::Select {\n\t\t\t\t\tresponses.add(ToolMessage::ActivateTool { tool_type: ToolType::Path });\n\t\t\t\t} else {\n\t\t\t\t\tresponses.add(ToolMessage::ActivateTool { tool_type: ToolType::Select });\n\t\t\t\t}\n\t\t\t}\n\t\t\tToolMessage::SwapColors => {\n\t\t\t\tlet document_data = &mut self.tool_state.document_tool_data;\n\n\t\t\t\tstd::mem::swap(&mut document_data.primary_color, &mut document_data.secondary_color);\n\n\t\t\t\tdocument_data.update_working_colors(responses); // TODO: Make this an event\n\t\t\t}\n\t\t\tToolMessage::Undo => {\n\t\t\t\tlet tool_data = &mut self.tool_state.tool_data;\n\t\t\t\tif tool_data.active_tool_type == ToolType::Pen {\n\t\t\t\t\tresponses.add(PenToolMessage::Undo);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Sub-messages\n\t\t\ttool_message => {\n\t\t\t\tlet tool_type = match &tool_message {\n\t\t\t\t\tToolMessage::UpdateCursor | ToolMessage::UpdateHints => self.tool_state.tool_data.active_tool_type,\n\t\t\t\t\ttool_message => tool_message_to_tool_type(tool_message),\n\t\t\t\t};\n\t\t\t\tlet tool_data = &mut self.tool_state.tool_data;\n\n\t\t\t\tif let Some(tool) = tool_data.tools.get_mut(&tool_type) {\n\t\t\t\t\tlet graph_view_overlay_open = document.graph_view_overlay_open();\n\n\t\t\t\t\tif tool_type == tool_data.active_tool_type {\n\t\t\t\t\t\tlet mut data = ToolActionMessageContext {\n\t\t\t\t\t\t\tdocument,\n\t\t\t\t\t\t\tdocument_id,\n\t\t\t\t\t\t\tglobal_tool_data: &self.tool_state.document_tool_data,\n\t\t\t\t\t\t\tinput,\n\t\t\t\t\t\t\tshape_editor: &mut self.shape_editor,\n\t\t\t\t\t\t\tnode_graph,\n\t\t\t\t\t\t\tpreferences,\n\t\t\t\t\t\t\tviewport,\n\t\t\t\t\t\t\tpersistent_data,\n\t\t\t\t\t\t};\n\t\t\t\t\t\tif matches!(tool_message, ToolMessage::UpdateHints) {\n\t\t\t\t\t\t\tif graph_view_overlay_open {\n\t\t\t\t\t\t\t\t// When graph view is open, forward the hint update to the node graph handler\n\t\t\t\t\t\t\t\tresponses.add(NodeGraphMessage::UpdateHints);\n\t\t\t\t\t\t\t} else if self.transform_layer_handler.is_transforming() {\n\t\t\t\t\t\t\t\tself.transform_layer_handler.hints(responses);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\ttool.process_message(ToolMessage::UpdateHints, responses, &mut data)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ttool.process_message(tool_message, responses, &mut data);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfn actions(&self) -> ActionList {\n\t\tlet mut list = actions!(ToolMessageDiscriminant;\n\t\t\tActivateToolSelect,\n\t\t\tActivateToolArtboard,\n\t\t\tActivateToolNavigate,\n\t\t\tActivateToolEyedropper,\n\t\t\tActivateToolFill,\n\t\t\tActivateToolGradient,\n\n\t\t\tActivateToolPath,\n\t\t\tActivateToolPen,\n\t\t\tActivateToolFreehand,\n\t\t\tActivateToolSpline,\n\t\t\tActivateToolShapeLine,\n\t\t\tActivateToolShapeRectangle,\n\t\t\tActivateToolShapeEllipse,\n\t\t\tActivateToolShape,\n\t\t\tActivateToolText,\n\n\t\t\tToggleSelectVsPath,\n\n\t\t\tSelectRandomWorkingColor,\n\t\t\tResetColors,\n\t\t\tSwapColors,\n\n\t\t\tUndo,\n\t\t);\n\t\tlist.extend(self.tool_state.tool_data.active_tool().actions());\n\t\tlist.extend(self.transform_layer_handler.actions());\n\n\t\tlist\n\t}\n}\n\nimpl ToolMessageHandler {\n\tpub fn actions_with_preferences(&self, preferences: &PreferencesMessageHandler) -> ActionList {\n\t\tlet mut list = self.actions();\n\n\t\tif preferences.brush_tool {\n\t\t\tlist.extend(actions!(ToolMessageDiscriminant;\n\t\t\t\tActivateToolBrush,\n\t\t\t));\n\t\t}\n\n\t\tlist\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/tool_messages/artboard_tool.rs",
    "content": "use super::tool_prelude::*;\nuse crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;\nuse crate::messages::portfolio::document::overlays::utility_types::OverlayContext;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::tool::common_functionality::auto_panning::AutoPanning;\nuse crate::messages::tool::common_functionality::compass_rose::Axis;\nuse crate::messages::tool::common_functionality::measure;\nuse crate::messages::tool::common_functionality::resize::Resize;\nuse crate::messages::tool::common_functionality::snapping;\nuse crate::messages::tool::common_functionality::snapping::SnapCandidatePoint;\nuse crate::messages::tool::common_functionality::snapping::SnapData;\nuse crate::messages::tool::common_functionality::transformation_cage::*;\nuse graph_craft::document::NodeId;\nuse graphene_std::Artboard;\nuse graphene_std::renderer::{Quad, Rect};\nuse graphene_std::table::Table;\n\n#[derive(Default, ExtractField)]\npub struct ArtboardTool {\n\tfsm_state: ArtboardToolFsmState,\n\tdata: ArtboardToolData,\n}\n\n#[impl_message(Message, ToolMessage, Artboard)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum ArtboardToolMessage {\n\t// Standard messages\n\tAbort,\n\tOverlays { context: OverlayContext },\n\n\t// Tool-specific messages\n\tUpdateSelectedArtboard,\n\tDeleteSelected,\n\tNudgeSelected { delta_x: f64, delta_y: f64, resize: Key, resize_opposite_corner: Key },\n\tPointerDown,\n\tPointerMove { constrain_axis_or_aspect: Key, center: Key },\n\tPointerOutsideViewport { constrain_axis_or_aspect: Key, center: Key },\n\tPointerUp,\n}\n\nimpl ToolMetadata for ArtboardTool {\n\tfn icon_name(&self) -> String {\n\t\t\"GeneralArtboardTool\".into()\n\t}\n\tfn tooltip_label(&self) -> String {\n\t\t\"Artboard Tool\".into()\n\t}\n\tfn tool_type(&self) -> crate::messages::tool::utility_types::ToolType {\n\t\tToolType::Artboard\n\t}\n}\n\n#[message_handler_data]\nimpl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for ArtboardTool {\n\tfn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {\n\t\tself.fsm_state.process_event(message, &mut self.data, context, &(), responses, false);\n\t}\n\n\tfn actions(&self) -> ActionList {\n\t\tlet mut common = actions!(ArtboardToolMessageDiscriminant;\n\t\t\tDeleteSelected,\n\t\t\tNudgeSelected,\n\t\t\tPointerMove,\n\t\t);\n\n\t\tlet additional = match self.fsm_state {\n\t\t\tArtboardToolFsmState::Ready { .. } => actions!(ArtboardToolMessageDiscriminant; PointerDown),\n\t\t\t_ => actions!(ArtboardToolMessageDiscriminant; PointerUp, Abort),\n\t\t};\n\t\tcommon.extend(additional);\n\n\t\tcommon\n\t}\n}\n\nimpl LayoutHolder for ArtboardTool {\n\tfn layout(&self) -> Layout {\n\t\tLayout::default()\n\t}\n}\n\nimpl ToolTransition for ArtboardTool {\n\tfn event_to_message_map(&self) -> EventToMessageMap {\n\t\tEventToMessageMap {\n\t\t\ttool_abort: Some(ArtboardToolMessage::Abort.into()),\n\t\t\toverlay_provider: Some(|context| ArtboardToolMessage::Overlays { context }.into()),\n\t\t\t..Default::default()\n\t\t}\n\t}\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\nenum ArtboardToolFsmState {\n\tReady { hovered: bool },\n\tDrawing,\n\tResizingBounds,\n\tDragging,\n}\n\nimpl Default for ArtboardToolFsmState {\n\tfn default() -> Self {\n\t\tSelf::Ready { hovered: false }\n\t}\n}\n\n#[derive(Clone, Debug, Default)]\nstruct ArtboardToolData {\n\tbounding_box_manager: Option<BoundingBoxManager>,\n\tselected_artboard: Option<LayerNodeIdentifier>,\n\tcursor: MouseCursorIcon,\n\tdrag_start: DVec2,\n\tdrag_current: DVec2,\n\tauto_panning: AutoPanning,\n\tsnap_candidates: Vec<SnapCandidatePoint>,\n\tdragging_current_artboard_location: glam::IVec2,\n\tdraw: Resize,\n}\n\nimpl ArtboardToolData {\n\tfn get_snap_candidates(&mut self, document: &DocumentMessageHandler, _input: &InputPreprocessorMessageHandler) {\n\t\tself.snap_candidates.clear();\n\n\t\tlet Some(layer) = self.selected_artboard else { return };\n\n\t\tif let Some(bounds) = document.metadata().bounding_box_with_transform(layer, document.metadata().transform_to_document(layer)) {\n\t\t\tsnapping::get_bbox_points(Quad::from_box(bounds), &mut self.snap_candidates, snapping::BBoxSnapValues::ARTBOARD, document);\n\t\t}\n\t}\n\n\tfn check_dragging_bounds(&mut self, cursor: DVec2) -> Option<(bool, bool, bool, bool)> {\n\t\tlet bounding_box = self.bounding_box_manager.as_mut()?;\n\t\tlet edges = bounding_box.check_selected_edges(cursor)?;\n\t\tlet (top, bottom, left, right) = edges;\n\t\tlet selected_edges = SelectedEdges::new(top, bottom, left, right, bounding_box.bounds);\n\t\tbounding_box.opposite_pivot = selected_edges.calculate_pivot();\n\t\tbounding_box.selected_edges = Some(selected_edges);\n\n\t\tSome(edges)\n\t}\n\n\tfn start_resizing(&mut self, _selected_edges: (bool, bool, bool, bool), _document: &DocumentMessageHandler, _input: &InputPreprocessorMessageHandler) {\n\t\tif let Some(bounds) = &mut self.bounding_box_manager {\n\t\t\tbounds.center_of_transformation = bounds.transform.transform_point2((bounds.bounds[0] + bounds.bounds[1]) / 2.);\n\t\t\tself.dragging_current_artboard_location = bounds.bounds[0].round().as_ivec2();\n\t\t}\n\t}\n\n\tfn hovered_artboard(document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler) -> Option<LayerNodeIdentifier> {\n\t\tdocument.click_xray(input, viewport).find(|&layer| document.network_interface.is_artboard(&layer.to_node(), &[]))\n\t}\n\n\tfn select_artboard(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler, responses: &mut VecDeque<Message>) -> bool {\n\t\tif let Some(intersection) = Self::hovered_artboard(document, input, viewport) {\n\t\t\tself.selected_artboard = Some(intersection);\n\n\t\t\tif let Some(bounds) = document.metadata().bounding_box_document(intersection) {\n\t\t\t\tlet bounding_box_manager = self.bounding_box_manager.get_or_insert(BoundingBoxManager::default());\n\t\t\t\tbounding_box_manager.bounds = bounds;\n\t\t\t\tbounding_box_manager.transform = document.metadata().document_to_viewport;\n\t\t\t}\n\n\t\t\tresponses.add_front(NodeGraphMessage::SelectedNodesSet { nodes: vec![intersection.to_node()] });\n\n\t\t\ttrue\n\t\t} else {\n\t\t\tself.selected_artboard = None;\n\n\t\t\tresponses.add(PropertiesPanelMessage::Clear);\n\n\t\t\tfalse\n\t\t}\n\t}\n\n\tfn resize_artboard(\n\t\t&mut self,\n\t\tresponses: &mut VecDeque<Message>,\n\t\tdocument: &DocumentMessageHandler,\n\t\tinput: &InputPreprocessorMessageHandler,\n\t\tviewport: &ViewportMessageHandler,\n\t\tfrom_center: bool,\n\t\tconstrain_square: bool,\n\t) {\n\t\tlet Some(bounds) = &self.bounding_box_manager else {\n\t\t\treturn;\n\t\t};\n\t\tlet Some(movement) = &bounds.selected_edges else {\n\t\t\treturn;\n\t\t};\n\t\tlet Some(selected_artboard) = self.selected_artboard else {\n\t\t\twarn!(\"Attempted to resize artboard with no selected artboard\");\n\t\t\tself.bounding_box_manager.take(); // Remove the bounding box manager if there is no artboard.\n\t\t\treturn; // Just do nothing instead of crashing since the state likely isn't too broken.\n\t\t};\n\t\tif selected_artboard == LayerNodeIdentifier::ROOT_PARENT {\n\t\t\tlog::error!(\"Selected artboard cannot be ROOT_PARENT\");\n\t\t\treturn;\n\t\t}\n\n\t\tlet center = from_center.then_some(bounds.center_of_transformation);\n\t\tlet ignore = vec![selected_artboard];\n\t\tlet snap = Some(SizeSnapData {\n\t\t\tmanager: &mut self.draw.snap_manager,\n\t\t\tpoints: &mut self.snap_candidates,\n\t\t\tsnap_data: SnapData::ignore(document, input, viewport, &ignore),\n\t\t});\n\t\tlet (min, size) = movement.new_size(input.mouse.position, bounds.transform, center, constrain_square, snap);\n\t\tlet max = min + size;\n\t\tlet position = min.min(max);\n\t\tlet size = (max - min).abs();\n\n\t\tresponses.add(GraphOperationMessage::ResizeArtboard {\n\t\t\tlayer: selected_artboard,\n\t\t\tlocation: position.round().as_ivec2(),\n\t\t\tdimensions: size.round().as_ivec2(),\n\t\t});\n\n\t\tlet translation = position.round().as_ivec2() - self.dragging_current_artboard_location;\n\t\tself.dragging_current_artboard_location = position.round().as_ivec2();\n\t\tfor child in selected_artboard.children(document.metadata()) {\n\t\t\tlet local_translation = document.metadata().downstream_transform_to_document(child).inverse().transform_vector2(-translation.as_dvec2());\n\t\t\tresponses.add(GraphOperationMessage::TransformChange {\n\t\t\t\tlayer: child,\n\t\t\t\ttransform: DAffine2::from_translation(local_translation),\n\t\t\t\ttransform_in: TransformIn::Local,\n\t\t\t\tskip_rerender: false,\n\t\t\t});\n\t\t}\n\t}\n}\n\nimpl Fsm for ArtboardToolFsmState {\n\ttype ToolData = ArtboardToolData;\n\ttype ToolOptions = ();\n\n\tfn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionMessageContext, _tool_options: &(), responses: &mut VecDeque<Message>) -> Self {\n\t\tlet ToolActionMessageContext { document, input, viewport, .. } = tool_action_data;\n\n\t\tlet hovered = ArtboardToolData::hovered_artboard(document, input, viewport).is_some();\n\n\t\tlet ToolMessage::Artboard(event) = event else { return self };\n\t\tmatch (self, event) {\n\t\t\t(state, ArtboardToolMessage::Overlays { context: mut overlay_context }) => {\n\t\t\t\tlet display_transform_cage = overlay_context.visibility_settings.transform_cage();\n\t\t\t\tif display_transform_cage && state != ArtboardToolFsmState::Drawing {\n\t\t\t\t\tif let Some(bounds) = tool_data.selected_artboard.and_then(|layer| document.metadata().bounding_box_document(layer)) {\n\t\t\t\t\t\tlet bounding_box_manager = tool_data.bounding_box_manager.get_or_insert(BoundingBoxManager::default());\n\t\t\t\t\t\tbounding_box_manager.bounds = bounds;\n\t\t\t\t\t\tbounding_box_manager.transform = document.metadata().document_to_viewport;\n\n\t\t\t\t\t\tbounding_box_manager.render_overlays(&mut overlay_context, true);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// If the bounding box is not resolved (e.g. if the artboard is deleted), then discard the bounding box.\n\t\t\t\t\t\ttool_data.bounding_box_manager.take();\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\ttool_data.bounding_box_manager.take();\n\t\t\t\t}\n\n\t\t\t\t// Measure with Alt held down between selected artboard and hovered layers/artboards\n\t\t\t\t// TODO: Don't use `Key::Alt` directly, instead take it as a variable from the input mappings list like in all other places\n\t\t\t\tlet alt_pressed = input.keyboard.get(Key::Alt as usize);\n\t\t\t\tlet quick_measurement_enabled = overlay_context.visibility_settings.quick_measurement();\n\t\t\t\tlet not_resizing = !matches!(state, ArtboardToolFsmState::ResizingBounds);\n\n\t\t\t\tif quick_measurement_enabled && not_resizing && alt_pressed {\n\t\t\t\t\t// Get the selected artboard bounds\n\t\t\t\t\tlet selected_artboard_bounds = tool_data.selected_artboard.and_then(|layer| document.metadata().bounding_box_document(layer)).map(Rect::from_box);\n\n\t\t\t\t\t// Find hovered artboard or regular layer\n\t\t\t\t\tlet hovered_artboard = ArtboardToolData::hovered_artboard(document, input, viewport);\n\t\t\t\t\tlet hovered_layer = document.click_xray(input, viewport).find(|&layer| !document.network_interface.is_artboard(&layer.to_node(), &[]));\n\n\t\t\t\t\t// Get bounds for the hovered object (prioritize artboards)\n\t\t\t\t\tlet hovered_bounds = if let Some(artboard) = hovered_artboard {\n\t\t\t\t\t\tdocument.metadata().bounding_box_document(artboard).map(Rect::from_box)\n\t\t\t\t\t} else if let Some(layer) = hovered_layer {\n\t\t\t\t\t\tdocument.metadata().bounding_box_document(layer).map(Rect::from_box)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tNone\n\t\t\t\t\t};\n\n\t\t\t\t\t// If both selected artboard and hovered object bounds exist, overlay measurement lines\n\t\t\t\t\tif let (Some(selected_bounds), Some(hovered_bounds)) = (selected_artboard_bounds, hovered_bounds) {\n\t\t\t\t\t\t// Don't measure if it's the same artboard\n\t\t\t\t\t\tif selected_artboard_bounds != Some(hovered_bounds) {\n\t\t\t\t\t\t\tlet document_to_viewport = document.metadata().document_to_viewport;\n\t\t\t\t\t\t\tmeasure::overlay(selected_bounds, hovered_bounds, document_to_viewport, document_to_viewport, &mut overlay_context);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\ttool_data.draw.snap_manager.draw_overlays(SnapData::new(document, input, viewport), &mut overlay_context);\n\n\t\t\t\tself\n\t\t\t}\n\t\t\t(ArtboardToolFsmState::Ready { .. }, ArtboardToolMessage::PointerDown) => {\n\t\t\t\tlet to_viewport = document.metadata().document_to_viewport;\n\t\t\t\tlet to_document = to_viewport.inverse();\n\t\t\t\ttool_data.drag_start = to_document.transform_point2(input.mouse.position);\n\t\t\t\ttool_data.drag_current = to_document.transform_point2(input.mouse.position);\n\n\t\t\t\tlet state = if let Some(selected_edges) = tool_data.check_dragging_bounds(input.mouse.position) {\n\t\t\t\t\ttool_data.start_resizing(selected_edges, document, input);\n\t\t\t\t\ttool_data.get_snap_candidates(document, input);\n\t\t\t\t\tArtboardToolFsmState::ResizingBounds\n\t\t\t\t} else if tool_data.select_artboard(document, input, viewport, responses) {\n\t\t\t\t\ttool_data.get_snap_candidates(document, input);\n\t\t\t\t\tArtboardToolFsmState::Dragging\n\t\t\t\t} else {\n\t\t\t\t\ttool_data.draw.start(document, input, viewport);\n\n\t\t\t\t\tArtboardToolFsmState::Drawing\n\t\t\t\t};\n\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\t\t\t\tstate\n\t\t\t}\n\t\t\t(ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => {\n\t\t\t\tlet from_center = input.keyboard.get(center as usize);\n\t\t\t\tlet constrain_square = input.keyboard.get(constrain_axis_or_aspect as usize);\n\t\t\t\ttool_data.resize_artboard(responses, document, input, viewport, from_center, constrain_square);\n\n\t\t\t\t// Auto-panning\n\t\t\t\tlet messages = [\n\t\t\t\t\tArtboardToolMessage::PointerOutsideViewport { constrain_axis_or_aspect, center }.into(),\n\t\t\t\t\tArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }.into(),\n\t\t\t\t];\n\t\t\t\ttool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses);\n\n\t\t\t\tArtboardToolFsmState::ResizingBounds\n\t\t\t}\n\t\t\t(ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => {\n\t\t\t\tif let Some(bounds) = &mut tool_data.bounding_box_manager {\n\t\t\t\t\tlet axis_align = input.keyboard.get(constrain_axis_or_aspect as usize);\n\n\t\t\t\t\tlet ignore = tool_data.selected_artboard.map_or(Vec::new(), |layer| vec![layer]);\n\t\t\t\t\tlet snap_data = SnapData::ignore(document, input, viewport, &ignore);\n\t\t\t\t\tlet document_to_viewport = document.metadata().document_to_viewport;\n\t\t\t\t\tlet [start, current] = [tool_data.drag_start, tool_data.drag_current].map(|point| document_to_viewport.transform_point2(point));\n\t\t\t\t\tlet mouse_delta = snap_drag(start, current, axis_align, Axis::None, snap_data, &mut tool_data.draw.snap_manager, &tool_data.snap_candidates);\n\n\t\t\t\t\tlet size = bounds.bounds[1] - bounds.bounds[0];\n\t\t\t\t\tlet position = bounds.bounds[0] + bounds.transform.inverse().transform_vector2(mouse_delta);\n\n\t\t\t\t\tif tool_data.selected_artboard.unwrap() == LayerNodeIdentifier::ROOT_PARENT {\n\t\t\t\t\t\tlog::error!(\"Selected artboard cannot be ROOT_PARENT\");\n\t\t\t\t\t\treturn ArtboardToolFsmState::Ready { hovered };\n\t\t\t\t\t}\n\t\t\t\t\tresponses.add(GraphOperationMessage::ResizeArtboard {\n\t\t\t\t\t\tlayer: tool_data.selected_artboard.unwrap(),\n\t\t\t\t\t\tlocation: position.round().as_ivec2(),\n\t\t\t\t\t\tdimensions: size.round().as_ivec2(),\n\t\t\t\t\t});\n\n\t\t\t\t\t// The second term is added to prevent the slow change in position due to rounding errors.\n\t\t\t\t\ttool_data.drag_current += (document_to_viewport.inverse() * bounds.transform).transform_vector2(position.round() - bounds.bounds[0]);\n\n\t\t\t\t\t// Update bounds if another `PointerMove` message comes before `ResizeArtboard` is finished.\n\t\t\t\t\tbounds.bounds[0] = position.round();\n\t\t\t\t\tbounds.bounds[1] = position.round() + size.round();\n\n\t\t\t\t\t// Auto-panning\n\t\t\t\t\tlet messages = [\n\t\t\t\t\t\tArtboardToolMessage::PointerOutsideViewport { constrain_axis_or_aspect, center }.into(),\n\t\t\t\t\t\tArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }.into(),\n\t\t\t\t\t];\n\t\t\t\t\ttool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses);\n\t\t\t\t}\n\t\t\t\tArtboardToolFsmState::Dragging\n\t\t\t}\n\t\t\t(ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => {\n\t\t\t\t// The draw.calculate_points_ignore_layer uses this value to avoid snapping to itself.\n\t\t\t\ttool_data.draw.layer = tool_data.selected_artboard;\n\t\t\t\tlet [start, end] = tool_data.draw.calculate_points_ignore_layer(document, input, viewport, center, constrain_axis_or_aspect, true);\n\t\t\t\tlet viewport_to_document = document.metadata().document_to_viewport.inverse();\n\t\t\t\tlet [start, end] = [start, end].map(|point| viewport_to_document.transform_point2(point));\n\t\t\t\tif let Some(artboard) = tool_data.selected_artboard {\n\t\t\t\t\tassert_ne!(artboard, LayerNodeIdentifier::ROOT_PARENT, \"Selected artboard cannot be ROOT_PARENT\");\n\n\t\t\t\t\tresponses.add(GraphOperationMessage::ResizeArtboard {\n\t\t\t\t\t\tlayer: artboard,\n\t\t\t\t\t\tlocation: start.min(end).round().as_ivec2(),\n\t\t\t\t\t\tdimensions: (start.round() - end.round()).abs().as_ivec2(),\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tlet id = NodeId::new();\n\n\t\t\t\t\ttool_data.selected_artboard = Some(LayerNodeIdentifier::new_unchecked(id));\n\n\t\t\t\t\tresponses.add(GraphOperationMessage::NewArtboard {\n\t\t\t\t\t\tid,\n\t\t\t\t\t\tartboard: Artboard {\n\t\t\t\t\t\t\tcontent: Table::new(),\n\t\t\t\t\t\t\tlabel: String::from(\"Artboard\"),\n\t\t\t\t\t\t\tlocation: start.min(end).round().as_ivec2(),\n\t\t\t\t\t\t\tdimensions: (start.round() - end.round()).abs().as_ivec2(),\n\t\t\t\t\t\t\tbackground: graphene_std::Color::WHITE,\n\t\t\t\t\t\t\tclip: false,\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t}\n\n\t\t\t\t// Auto-panning\n\t\t\t\tlet messages = [\n\t\t\t\t\tArtboardToolMessage::PointerOutsideViewport { constrain_axis_or_aspect, center }.into(),\n\t\t\t\t\tArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }.into(),\n\t\t\t\t];\n\t\t\t\ttool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses);\n\n\t\t\t\tArtboardToolFsmState::Drawing\n\t\t\t}\n\n\t\t\t(ArtboardToolFsmState::Ready { .. }, ArtboardToolMessage::PointerMove { .. }) => {\n\t\t\t\tlet mut cursor = tool_data\n\t\t\t\t\t.bounding_box_manager\n\t\t\t\t\t.as_ref()\n\t\t\t\t\t.map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, false, false, None));\n\n\t\t\t\tif cursor == MouseCursorIcon::Default && !hovered {\n\t\t\t\t\ttool_data.draw.snap_manager.preview_draw(&SnapData::new(document, input, viewport), input.mouse.position);\n\t\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\t\tcursor = MouseCursorIcon::Crosshair;\n\t\t\t\t} else {\n\t\t\t\t\ttool_data.draw.cleanup(responses);\n\t\t\t\t}\n\n\t\t\t\tif tool_data.cursor != cursor {\n\t\t\t\t\ttool_data.cursor = cursor;\n\t\t\t\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor });\n\t\t\t\t}\n\n\t\t\t\tArtboardToolFsmState::Ready { hovered }\n\t\t\t}\n\t\t\t(ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerOutsideViewport { .. }) => {\n\t\t\t\t// Auto-panning\n\t\t\t\tlet _ = tool_data.auto_panning.shift_viewport(input, viewport, responses);\n\n\t\t\t\tArtboardToolFsmState::ResizingBounds\n\t\t\t}\n\t\t\t(ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerOutsideViewport { .. }) => {\n\t\t\t\t// Auto-panning\n\t\t\t\ttool_data.auto_panning.shift_viewport(input, viewport, responses);\n\n\t\t\t\tArtboardToolFsmState::Dragging\n\t\t\t}\n\t\t\t(ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerOutsideViewport { .. }) => {\n\t\t\t\t// Auto-panning\n\t\t\t\ttool_data.auto_panning.shift_viewport(input, viewport, responses);\n\n\t\t\t\tArtboardToolFsmState::Drawing\n\t\t\t}\n\t\t\t(state, ArtboardToolMessage::PointerOutsideViewport { constrain_axis_or_aspect, center }) => {\n\t\t\t\t// Auto-panning\n\t\t\t\tlet messages = [\n\t\t\t\t\tArtboardToolMessage::PointerOutsideViewport { constrain_axis_or_aspect, center }.into(),\n\t\t\t\t\tArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }.into(),\n\t\t\t\t];\n\t\t\t\ttool_data.auto_panning.stop(&messages, responses);\n\n\t\t\t\tstate\n\t\t\t}\n\t\t\t(ArtboardToolFsmState::Drawing | ArtboardToolFsmState::ResizingBounds | ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerUp) => {\n\t\t\t\tresponses.add(DocumentMessage::EndTransaction);\n\n\t\t\t\ttool_data.draw.cleanup(responses);\n\n\t\t\t\tif let Some(bounds) = &mut tool_data.bounding_box_manager {\n\t\t\t\t\tbounds.original_transforms.clear();\n\t\t\t\t}\n\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\n\t\t\t\tArtboardToolFsmState::Ready { hovered }\n\t\t\t}\n\t\t\t(_, ArtboardToolMessage::UpdateSelectedArtboard) => {\n\t\t\t\ttool_data.selected_artboard = document\n\t\t\t\t\t.network_interface\n\t\t\t\t\t.selected_nodes()\n\t\t\t\t\t.selected_layers(document.metadata())\n\t\t\t\t\t.find(|layer| document.network_interface.is_artboard(&layer.to_node(), &[]));\n\t\t\t\tself\n\t\t\t}\n\t\t\t(_, ArtboardToolMessage::DeleteSelected) => {\n\t\t\t\ttool_data.selected_artboard.take();\n\t\t\t\tresponses.add(DocumentMessage::DeleteSelectedLayers);\n\n\t\t\t\tArtboardToolFsmState::Ready { hovered }\n\t\t\t}\n\t\t\t(\n\t\t\t\t_,\n\t\t\t\tArtboardToolMessage::NudgeSelected {\n\t\t\t\t\tdelta_x,\n\t\t\t\t\tdelta_y,\n\t\t\t\t\tresize,\n\t\t\t\t\tresize_opposite_corner,\n\t\t\t\t},\n\t\t\t) => {\n\t\t\t\tlet Some(bounds) = &mut tool_data.bounding_box_manager else {\n\t\t\t\t\treturn ArtboardToolFsmState::Ready { hovered };\n\t\t\t\t};\n\t\t\t\tlet Some(selected_artboard) = tool_data.selected_artboard else {\n\t\t\t\t\treturn ArtboardToolFsmState::Ready { hovered };\n\t\t\t\t};\n\t\t\t\tif selected_artboard == LayerNodeIdentifier::ROOT_PARENT {\n\t\t\t\t\tlog::error!(\"Selected artboard cannot be ROOT_PARENT\");\n\t\t\t\t\treturn ArtboardToolFsmState::Ready { hovered };\n\t\t\t\t}\n\n\t\t\t\tlet resize = input.keyboard.key(resize);\n\t\t\t\tlet resize_opposite_corner = input.keyboard.key(resize_opposite_corner);\n\t\t\t\tlet [existing_top_left, existing_bottom_right] = bounds.bounds;\n\n\t\t\t\t// Nudge translation without resizing\n\t\t\t\tif !resize {\n\t\t\t\t\tlet delta = DVec2::from_angle(-document.document_ptz.tilt()).rotate(DVec2::new(delta_x, delta_y));\n\n\t\t\t\t\tresponses.add(GraphOperationMessage::ResizeArtboard {\n\t\t\t\t\t\tlayer: selected_artboard,\n\t\t\t\t\t\tlocation: DVec2::new(existing_top_left.x + delta.x, existing_top_left.y + delta.y).round().as_ivec2(),\n\t\t\t\t\t\tdimensions: (existing_bottom_right - existing_top_left).round().as_ivec2(),\n\t\t\t\t\t});\n\n\t\t\t\t\treturn ArtboardToolFsmState::Ready { hovered };\n\t\t\t\t}\n\n\t\t\t\t// Swap and negate coordinates as needed to match the resize direction that's closest to the current tilt angle\n\t\t\t\tlet tilt = (document.document_ptz.tilt() + std::f64::consts::TAU) % std::f64::consts::TAU;\n\t\t\t\tlet (delta_x, delta_y, opposite_x, opposite_y) = match ((tilt + std::f64::consts::FRAC_PI_4) / std::f64::consts::FRAC_PI_2).floor() as i32 % 4 {\n\t\t\t\t\t0 => (delta_x, delta_y, false, false),\n\t\t\t\t\t1 => (delta_y, -delta_x, false, true),\n\t\t\t\t\t2 => (-delta_x, -delta_y, true, true),\n\t\t\t\t\t3 => (-delta_y, delta_x, true, false),\n\t\t\t\t\t_ => unreachable!(),\n\t\t\t\t};\n\n\t\t\t\tlet size = existing_bottom_right - existing_top_left;\n\t\t\t\tlet enlargement = DVec2::new(\n\t\t\t\t\tif resize_opposite_corner != opposite_x { -delta_x } else { delta_x },\n\t\t\t\t\tif resize_opposite_corner != opposite_y { -delta_y } else { delta_y },\n\t\t\t\t);\n\t\t\t\tlet enlargement_factor = (enlargement + size) / size;\n\n\t\t\t\tlet position = DVec2::new(\n\t\t\t\t\texisting_top_left.x + if resize_opposite_corner != opposite_x { delta_x } else { 0. },\n\t\t\t\t\texisting_top_left.y + if resize_opposite_corner != opposite_y { delta_y } else { 0. },\n\t\t\t\t);\n\t\t\t\tlet mut pivot = (existing_top_left * enlargement_factor - position) / (enlargement_factor - DVec2::ONE);\n\t\t\t\tif !pivot.x.is_finite() {\n\t\t\t\t\tpivot.x = 0.;\n\t\t\t\t}\n\t\t\t\tif !pivot.y.is_finite() {\n\t\t\t\t\tpivot.y = 0.;\n\t\t\t\t}\n\t\t\t\tlet scale = DAffine2::from_scale(enlargement_factor);\n\t\t\t\tlet pivot = DAffine2::from_translation(pivot);\n\t\t\t\tlet transformation = pivot * scale * pivot.inverse();\n\t\t\t\tlet document_to_viewport = document\n\t\t\t\t\t.navigation_handler\n\t\t\t\t\t.calculate_offset_transform(viewport.center_in_viewport_space().into(), &document.document_ptz);\n\t\t\t\tlet to = document_to_viewport.inverse() * document.metadata().downstream_transform_to_viewport(selected_artboard);\n\t\t\t\tlet original_transform = document.metadata().upstream_transform(selected_artboard.to_node());\n\t\t\t\tlet new = to.inverse() * transformation * to * original_transform;\n\n\t\t\t\tresponses.add(GraphOperationMessage::ResizeArtboard {\n\t\t\t\t\tlayer: selected_artboard,\n\t\t\t\t\tlocation: position.round().as_ivec2(),\n\t\t\t\t\tdimensions: new.transform_vector2(existing_bottom_right - existing_top_left).round().as_ivec2(),\n\t\t\t\t});\n\n\t\t\t\tArtboardToolFsmState::Ready { hovered }\n\t\t\t}\n\t\t\t(ArtboardToolFsmState::Dragging | ArtboardToolFsmState::Drawing | ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::Abort) => {\n\t\t\t\tresponses.add(DocumentMessage::AbortTransaction);\n\n\t\t\t\ttool_data.draw.cleanup(responses);\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\n\t\t\t\tArtboardToolFsmState::Ready { hovered }\n\t\t\t}\n\t\t\t_ => self,\n\t\t}\n\t}\n\n\tfn update_hints(&self, responses: &mut VecDeque<Message>) {\n\t\tlet hint_data = match self {\n\t\t\tArtboardToolFsmState::Ready { .. } => HintData(vec![\n\t\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, \"Draw Artboard\")]),\n\t\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, \"Move Artboard\")]),\n\t\t\t\tHintGroup(vec![HintInfo::keys([Key::Backspace], \"Delete Artboard\")]),\n\t\t\t]),\n\t\t\tArtboardToolFsmState::Dragging => HintData(vec![\n\t\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"\"), HintInfo::keys([Key::Escape], \"Cancel\").prepend_slash()]),\n\t\t\t\tHintGroup(vec![HintInfo::keys([Key::Shift], \"Constrain to Axis\")]),\n\t\t\t]),\n\t\t\tArtboardToolFsmState::Drawing => HintData(vec![\n\t\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"\"), HintInfo::keys([Key::Escape], \"Cancel\").prepend_slash()]),\n\t\t\t\tHintGroup(vec![HintInfo::keys([Key::Shift], \"Constrain Square\"), HintInfo::keys([Key::Alt], \"From Center\")]),\n\t\t\t]),\n\t\t\tArtboardToolFsmState::ResizingBounds => HintData(vec![\n\t\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"\"), HintInfo::keys([Key::Escape], \"Cancel\").prepend_slash()]),\n\t\t\t\tHintGroup(vec![HintInfo::keys([Key::Shift], \"Preserve Aspect Ratio\"), HintInfo::keys([Key::Alt], \"From Center\")]),\n\t\t\t]),\n\t\t};\n\n\t\thint_data.send_layout(responses);\n\t}\n\n\tfn update_cursor(&self, responses: &mut VecDeque<Message>) {\n\t\tif let Self::Ready { hovered: false } = self {\n\t\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair });\n\t\t} else {\n\t\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });\n\t\t}\n\t}\n}\n\n#[cfg(test)]\nmod test_artboard {\n\tpub use crate::test_utils::test_prelude::*;\n\tuse graphene_std::table::Table;\n\n\tasync fn get_artboards(editor: &mut EditorTestUtils) -> Table<graphene_std::Artboard> {\n\t\tlet instrumented = match editor.eval_graph().await {\n\t\t\tOk(instrumented) => instrumented,\n\t\t\tErr(e) => panic!(\"Failed to evaluate graph: {e}\"),\n\t\t};\n\t\tinstrumented\n\t\t\t.grab_all_input::<graphene_std::graphic::extend::NewInput<graphene_std::Artboard>>(&editor.runtime)\n\t\t\t.flatten()\n\t\t\t.collect()\n\t}\n\n\t#[derive(Debug, PartialEq)]\n\tstruct ArtboardLayoutDocument {\n\t\tposition: IVec2,\n\t\tdimensions: IVec2,\n\t}\n\timpl ArtboardLayoutDocument {\n\t\tpub fn new(position: impl Into<IVec2>, dimensions: impl Into<IVec2>) -> Self {\n\t\t\tSelf {\n\t\t\t\tposition: position.into(),\n\t\t\t\tdimensions: dimensions.into(),\n\t\t\t}\n\t\t}\n\t}\n\n\t/// Check if all of the artboards exist in any ordering\n\tasync fn has_artboards(editor: &mut EditorTestUtils, mut expected: Vec<ArtboardLayoutDocument>) {\n\t\tlet artboards = get_artboards(editor)\n\t\t\t.await\n\t\t\t.iter()\n\t\t\t.map(|row| ArtboardLayoutDocument::new(row.element.location, row.element.dimensions))\n\t\t\t.collect::<Vec<_>>();\n\t\tassert_eq!(artboards.len(), expected.len(), \"incorrect len: actual {:?}, expected {:?}\", artboards, expected);\n\n\t\tfor artboard in artboards {\n\t\t\tlet Some(index) = expected.iter().position(|expected| *expected == artboard) else {\n\t\t\t\tpanic!(\"found {:?} that did not match any expected artboards\\nexpected {:?}\", artboard, expected);\n\t\t\t};\n\t\t\texpected.remove(index);\n\t\t}\n\t}\n\n\t#[tokio::test]\n\tasync fn artboard_draw_simple() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor.drag_tool(ToolType::Artboard, 10.1, 10.8, 19.9, 0.2, ModifierKeys::empty()).await;\n\t\thas_artboards(&mut editor, vec![ArtboardLayoutDocument::new((10, 0), (10, 11))]).await;\n\t}\n\n\t#[tokio::test]\n\tasync fn artboard_snapping() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor.set_viewport_size(DVec2::splat(-1000.), DVec2::splat(1000.)).await; // Necessary for doing snapping since snaps outside of the viewport are discarded\n\t\teditor.drag_tool(ToolType::Artboard, 10., 10., 20., 20., ModifierKeys::empty()).await;\n\t\teditor.drag_tool(ToolType::Artboard, 11., 50., 19., 60., ModifierKeys::empty()).await;\n\t\thas_artboards(&mut editor, vec![ArtboardLayoutDocument::new((10, 10), (10, 10)), ArtboardLayoutDocument::new((10, 50), (10, 10))]).await;\n\t}\n\n\t#[tokio::test]\n\tasync fn artboard_draw_square() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor.drag_tool(ToolType::Artboard, 10., 10., -10., 11., ModifierKeys::SHIFT).await;\n\t\thas_artboards(&mut editor, vec![ArtboardLayoutDocument::new((-10, 10), (20, 20))]).await;\n\t}\n\n\t#[tokio::test]\n\tasync fn artboard_draw_square_rotated() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor\n\t\t\t.handle_message(NavigationMessage::CanvasTiltSet {\n\t\t\t\t// 45 degree rotation of content clockwise\n\t\t\t\tangle_radians: f64::consts::FRAC_PI_4,\n\t\t\t})\n\t\t\t.await;\n\t\t// Viewport coordinates\n\t\teditor.drag_tool(ToolType::Artboard, 0., 0., 0., 10., ModifierKeys::SHIFT).await;\n\t\tlet desired_size = DVec2::splat(f64::consts::FRAC_1_SQRT_2 * 10.);\n\n\t\thas_artboards(&mut editor, vec![ArtboardLayoutDocument::new(IVec2::new(0, 0), desired_size.round().as_ivec2())]).await;\n\t}\n\n\t#[tokio::test]\n\tasync fn artboard_draw_center_square_rotated() {\n\t\tlet mut editor = EditorTestUtils::create();\n\n\t\teditor.new_document().await;\n\t\teditor\n\t\t\t.handle_message(NavigationMessage::CanvasTiltSet {\n\t\t\t\t// 45 degree rotation of content clockwise\n\t\t\t\tangle_radians: f64::consts::FRAC_PI_4,\n\t\t\t})\n\t\t\t.await;\n\t\t// Viewport coordinates\n\t\teditor.drag_tool(ToolType::Artboard, 0., 0., 0., 10., ModifierKeys::SHIFT | ModifierKeys::ALT).await;\n\t\tlet desired_location = DVec2::splat(f64::consts::FRAC_1_SQRT_2 * -10.).as_ivec2();\n\t\tlet desired_size = DVec2::splat(f64::consts::FRAC_1_SQRT_2 * 20.).as_ivec2();\n\t\thas_artboards(&mut editor, vec![ArtboardLayoutDocument::new(desired_location, desired_size)]).await;\n\t}\n\n\t#[tokio::test]\n\tasync fn artboard_delete() {\n\t\tlet mut editor = EditorTestUtils::create();\n\n\t\teditor.new_document().await;\n\t\teditor.drag_tool(ToolType::Artboard, 10.1, 10.8, 19.9, 0.2, ModifierKeys::default()).await;\n\t\teditor.press(Key::Delete, ModifierKeys::default()).await;\n\n\t\thas_artboards(&mut editor, vec![]).await;\n\t}\n\n\t#[tokio::test]\n\tasync fn artboard_cancel() {\n\t\tlet mut editor = EditorTestUtils::create();\n\n\t\teditor.new_document().await;\n\n\t\teditor.drag_tool_cancel_rmb(ToolType::Artboard).await;\n\t\thas_artboards(&mut editor, vec![]).await;\n\t}\n\n\t#[tokio::test]\n\tasync fn artboard_move() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor.drag_tool(ToolType::Artboard, 10., 10., 20., 22., ModifierKeys::empty()).await; // Artboard to drag\n\t\teditor.drag_tool(ToolType::Artboard, 15., 15., 65., 65., ModifierKeys::empty()).await; // Drag from the middle by (50,50)\n\n\t\thas_artboards(&mut editor, vec![ArtboardLayoutDocument::new((60, 60), (10, 12))]).await;\n\t}\n\t#[tokio::test]\n\tasync fn artboard_move_snapping() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor.set_viewport_size(DVec2::splat(-1000.), DVec2::splat(1000.)).await; // Necessary for doing snapping since snaps outside of the viewport are discarded\n\t\teditor.drag_tool(ToolType::Artboard, 10., 10., 20., 22., ModifierKeys::empty()).await; // Artboard to drag\n\t\teditor.drag_tool(ToolType::Artboard, 70., 0., 80., 100., ModifierKeys::empty()).await; // Artboard to snap to\n\t\teditor.drag_tool(ToolType::Artboard, 15., 15., 15. + 49., 15., ModifierKeys::empty()).await; // Drag the artboard so it should snap to the edge\n\n\t\thas_artboards(&mut editor, vec![ArtboardLayoutDocument::new((60, 10), (10, 12)), ArtboardLayoutDocument::new((70, 0), (10, 100))]).await;\n\t}\n\n\t#[tokio::test]\n\tasync fn first_artboard() {\n\t\tlet mut editor = EditorTestUtils::create();\n\n\t\teditor.new_document().await;\n\t\t// Put rectangles in before making the artboard\n\t\teditor.drag_tool(ToolType::Rectangle, 10., 10., 20., 16., ModifierKeys::empty()).await;\n\t\teditor.drag_tool(ToolType::Rectangle, 15., 15., 25., 25., ModifierKeys::empty()).await;\n\n\t\t// Put the artboard in\n\t\teditor.drag_tool(ToolType::Artboard, 5., 5., 30., 10., ModifierKeys::empty()).await;\n\t\thas_artboards(&mut editor, vec![ArtboardLayoutDocument::new((5, 5), (25, 5))]).await;\n\t\tlet document = editor.active_document();\n\n\t\t// artboard\n\t\t// ├── rectangle1\n\t\t// └── rectangle2\n\t\tlet artboard = document.metadata().all_layers().next().unwrap();\n\t\tlet rectangle2 = artboard.first_child(document.metadata()).unwrap();\n\t\tlet rectangle1 = rectangle2.next_sibling(document.metadata()).unwrap();\n\n\t\t// The document bounding boxes should remain the same (content shouldn't shift)\n\t\tassert_eq!(document.metadata().bounding_box_document(rectangle1).unwrap(), [DVec2::new(10., 10.), DVec2::new(20., 16.)]);\n\t\tassert_eq!(document.metadata().bounding_box_document(rectangle2).unwrap(), [DVec2::new(15., 15.), DVec2::new(25., 25.)]);\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/tool_messages/brush_tool.rs",
    "content": "use super::tool_prelude::*;\nuse crate::consts::DEFAULT_BRUSH_SIZE;\nuse crate::messages::portfolio::document::graph_operation::transform_utils::get_current_transform;\nuse crate::messages::portfolio::document::node_graph::document_node_definitions::{DefinitionIdentifier, resolve_network_node_type};\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::portfolio::document::utility_types::network_interface::FlowType;\nuse crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};\nuse graph_craft::document::NodeId;\nuse graph_craft::document::value::TaggedValue;\nuse graphene_std::Color;\nuse graphene_std::brush::brush_stroke::{BrushInputSample, BrushStroke, BrushStyle};\nuse graphene_std::raster::BlendMode;\n\nconst BRUSH_MAX_SIZE: f64 = 5000.;\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Copy, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum DrawMode {\n\tDraw = 0,\n\tErase,\n\tRestore,\n}\n\n#[derive(Default, ExtractField)]\npub struct BrushTool {\n\tfsm_state: BrushToolFsmState,\n\tdata: BrushToolData,\n\toptions: BrushOptions,\n}\n\npub struct BrushOptions {\n\tdiameter: f64,\n\thardness: f64,\n\tflow: f64,\n\tspacing: f64,\n\tcolor: ToolColorOptions,\n\tblend_mode: BlendMode,\n\tdraw_mode: DrawMode,\n}\n\nimpl Default for BrushOptions {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tdiameter: DEFAULT_BRUSH_SIZE,\n\t\t\thardness: 0.,\n\t\t\tflow: 100.,\n\t\t\tspacing: 20.,\n\t\t\tcolor: ToolColorOptions::default(),\n\t\t\tblend_mode: BlendMode::Normal,\n\t\t\tdraw_mode: DrawMode::Draw,\n\t\t}\n\t}\n}\n\n#[impl_message(Message, ToolMessage, Brush)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum BrushToolMessage {\n\t// Standard messages\n\tAbort,\n\tWorkingColorChanged,\n\n\t// Tool-specific messages\n\tDragStart,\n\tDragStop,\n\tPointerMove,\n\tUpdateOptions { options: BrushToolMessageOptionsUpdate },\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum BrushToolMessageOptionsUpdate {\n\tBlendMode(BlendMode),\n\tChangeDiameter(f64),\n\tColor(Option<Color>),\n\tColorType(ToolColorType),\n\tDiameter(f64),\n\tDrawMode(DrawMode),\n\tFlow(f64),\n\tHardness(f64),\n\tSpacing(f64),\n\tWorkingColors(Option<Color>, Option<Color>),\n}\n\n#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]\nenum BrushToolFsmState {\n\t#[default]\n\tReady,\n\tDrawing,\n}\n\nimpl ToolMetadata for BrushTool {\n\tfn icon_name(&self) -> String {\n\t\t\"RasterBrushTool\".into()\n\t}\n\tfn tooltip_label(&self) -> String {\n\t\t\"Brush Tool\".into()\n\t}\n\tfn tool_type(&self) -> crate::messages::tool::utility_types::ToolType {\n\t\tToolType::Brush\n\t}\n}\n\nimpl LayoutHolder for BrushTool {\n\tfn layout(&self) -> Layout {\n\t\tlet mut widgets = vec![\n\t\t\tNumberInput::new(Some(self.options.diameter))\n\t\t\t\t.label(\"Diameter\")\n\t\t\t\t.min(1.)\n\t\t\t\t.max(BRUSH_MAX_SIZE) /* Anything bigger would cause the application to be unresponsive and eventually die */\n\t\t\t\t.unit(\" px\")\n\t\t\t\t.on_update(|number_input: &NumberInput| BrushToolMessage::UpdateOptions { options: BrushToolMessageOptionsUpdate::Diameter(number_input.value.unwrap()) }.into())\n\t\t\t\t.widget_instance(),\n\t\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\t\tNumberInput::new(Some(self.options.hardness))\n\t\t\t\t.label(\"Hardness\")\n\t\t\t\t.min(0.)\n\t\t\t\t.max(100.)\n\t\t\t\t.mode_range()\n\t\t\t\t.unit(\"%\")\n\t\t\t\t.on_update(|number_input: &NumberInput| {\n\t\t\t\t\tBrushToolMessage::UpdateOptions {\n\t\t\t\t\t\toptions: BrushToolMessageOptionsUpdate::Hardness(number_input.value.unwrap()),\n\t\t\t\t\t}\n\t\t\t\t\t.into()\n\t\t\t\t})\n\t\t\t\t.widget_instance(),\n\t\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\t\tNumberInput::new(Some(self.options.flow))\n\t\t\t\t.label(\"Flow\")\n\t\t\t\t.min(1.)\n\t\t\t\t.max(100.)\n\t\t\t\t.mode_range()\n\t\t\t\t.unit(\"%\")\n\t\t\t\t.on_update(|number_input: &NumberInput| {\n\t\t\t\t\tBrushToolMessage::UpdateOptions {\n\t\t\t\t\t\toptions: BrushToolMessageOptionsUpdate::Flow(number_input.value.unwrap()),\n\t\t\t\t\t}\n\t\t\t\t\t.into()\n\t\t\t\t})\n\t\t\t\t.widget_instance(),\n\t\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\t\tNumberInput::new(Some(self.options.spacing))\n\t\t\t\t.label(\"Spacing\")\n\t\t\t\t.min(1.)\n\t\t\t\t.max(100.)\n\t\t\t\t.mode_range()\n\t\t\t\t.unit(\"%\")\n\t\t\t\t.on_update(|number_input: &NumberInput| {\n\t\t\t\t\tBrushToolMessage::UpdateOptions {\n\t\t\t\t\t\toptions: BrushToolMessageOptionsUpdate::Spacing(number_input.value.unwrap()),\n\t\t\t\t\t}\n\t\t\t\t\t.into()\n\t\t\t\t})\n\t\t\t\t.widget_instance(),\n\t\t];\n\n\t\twidgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\n\t\tlet draw_mode_entries: Vec<_> = [DrawMode::Draw, DrawMode::Erase, DrawMode::Restore]\n\t\t\t.into_iter()\n\t\t\t.map(|draw_mode| {\n\t\t\t\tRadioEntryData::new(format!(\"{draw_mode:?}\")).label(format!(\"{draw_mode:?}\")).on_update(move |_| {\n\t\t\t\t\tBrushToolMessage::UpdateOptions {\n\t\t\t\t\t\toptions: BrushToolMessageOptionsUpdate::DrawMode(draw_mode),\n\t\t\t\t\t}\n\t\t\t\t\t.into()\n\t\t\t\t})\n\t\t\t})\n\t\t\t.collect();\n\t\twidgets.push(RadioInput::new(draw_mode_entries).selected_index(Some(self.options.draw_mode as u32)).widget_instance());\n\n\t\twidgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\n\t\twidgets.append(&mut self.options.color.create_widgets(\n\t\t\t\"Color\",\n\t\t\tfalse,\n\t\t\t|_| {\n\t\t\t\tBrushToolMessage::UpdateOptions {\n\t\t\t\t\toptions: BrushToolMessageOptionsUpdate::Color(None),\n\t\t\t\t}\n\t\t\t\t.into()\n\t\t\t},\n\t\t\t|color_type: ToolColorType| {\n\t\t\t\tWidgetCallback::new(move |_| {\n\t\t\t\t\tBrushToolMessage::UpdateOptions {\n\t\t\t\t\t\toptions: BrushToolMessageOptionsUpdate::ColorType(color_type.clone()),\n\t\t\t\t\t}\n\t\t\t\t\t.into()\n\t\t\t\t})\n\t\t\t},\n\t\t\t|color: &ColorInput| {\n\t\t\t\tBrushToolMessage::UpdateOptions {\n\t\t\t\t\toptions: BrushToolMessageOptionsUpdate::Color(color.value.as_solid().map(|color| color.to_linear_srgb())),\n\t\t\t\t}\n\t\t\t\t.into()\n\t\t\t},\n\t\t));\n\n\t\twidgets.push(Separator::new(SeparatorStyle::Related).widget_instance());\n\n\t\tlet blend_mode_entries: Vec<Vec<_>> = BlendMode::list()\n\t\t\t.iter()\n\t\t\t.map(|section| {\n\t\t\t\tsection\n\t\t\t\t\t.iter()\n\t\t\t\t\t.map(|blend_mode| {\n\t\t\t\t\t\tMenuListEntry::new(format!(\"{blend_mode:?}\")).label(blend_mode.to_string()).on_commit(|_| {\n\t\t\t\t\t\t\tBrushToolMessage::UpdateOptions {\n\t\t\t\t\t\t\t\toptions: BrushToolMessageOptionsUpdate::BlendMode(*blend_mode),\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t.into()\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t\t\t.collect()\n\t\t\t})\n\t\t\t.collect();\n\t\twidgets.push(\n\t\t\tDropdownInput::new(blend_mode_entries)\n\t\t\t\t.selected_index(self.options.blend_mode.index_in_list().map(|index| index as u32))\n\t\t\t\t.tooltip_description(\"The blend mode used with the background when performing a brush stroke. Only used in draw mode.\")\n\t\t\t\t.disabled(self.options.draw_mode != DrawMode::Draw)\n\t\t\t\t.widget_instance(),\n\t\t);\n\n\t\tLayout(vec![LayoutGroup::row(widgets)])\n\t}\n}\n\n#[message_handler_data]\nimpl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for BrushTool {\n\tfn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {\n\t\tlet ToolMessage::Brush(BrushToolMessage::UpdateOptions { options }) = message else {\n\t\t\tself.fsm_state.process_event(message, &mut self.data, context, &self.options, responses, true);\n\t\t\treturn;\n\t\t};\n\t\tmatch options {\n\t\t\tBrushToolMessageOptionsUpdate::BlendMode(blend_mode) => self.options.blend_mode = blend_mode,\n\t\t\tBrushToolMessageOptionsUpdate::ChangeDiameter(change) => {\n\t\t\t\tlet needs_rounding = ((self.options.diameter + change.abs() / 2.) % change.abs() - change.abs() / 2.).abs() > 0.5;\n\t\t\t\tif needs_rounding && change > 0. {\n\t\t\t\t\tself.options.diameter = (self.options.diameter / change.abs()).ceil() * change.abs();\n\t\t\t\t} else if needs_rounding && change < 0. {\n\t\t\t\t\tself.options.diameter = (self.options.diameter / change.abs()).floor() * change.abs();\n\t\t\t\t} else {\n\t\t\t\t\tself.options.diameter = (self.options.diameter / change.abs()).round() * change.abs() + change;\n\t\t\t\t}\n\t\t\t\tself.options.diameter = self.options.diameter.max(1.);\n\t\t\t\tself.send_layout(responses, LayoutTarget::ToolOptions);\n\t\t\t}\n\t\t\tBrushToolMessageOptionsUpdate::Diameter(diameter) => self.options.diameter = diameter,\n\t\t\tBrushToolMessageOptionsUpdate::DrawMode(draw_mode) => self.options.draw_mode = draw_mode,\n\t\t\tBrushToolMessageOptionsUpdate::Hardness(hardness) => self.options.hardness = hardness,\n\t\t\tBrushToolMessageOptionsUpdate::Flow(flow) => self.options.flow = flow,\n\t\t\tBrushToolMessageOptionsUpdate::Spacing(spacing) => self.options.spacing = spacing,\n\t\t\tBrushToolMessageOptionsUpdate::Color(color) => {\n\t\t\t\tself.options.color.custom_color = color;\n\t\t\t\tself.options.color.color_type = ToolColorType::Custom;\n\t\t\t}\n\t\t\tBrushToolMessageOptionsUpdate::ColorType(color_type) => self.options.color.color_type = color_type,\n\t\t\tBrushToolMessageOptionsUpdate::WorkingColors(primary, secondary) => {\n\t\t\t\tself.options.color.primary_working_color = primary;\n\t\t\t\tself.options.color.secondary_working_color = secondary;\n\t\t\t}\n\t\t}\n\n\t\tself.send_layout(responses, LayoutTarget::ToolOptions);\n\t}\n\n\tfn actions(&self) -> ActionList {\n\t\tmatch self.fsm_state {\n\t\t\tBrushToolFsmState::Ready => actions!(BrushToolMessageDiscriminant;\n\t\t\t\tDragStart,\n\t\t\t\tDragStop,\n\t\t\t\tUpdateOptions,\n\t\t\t),\n\t\t\tBrushToolFsmState::Drawing => actions!(BrushToolMessageDiscriminant;\n\t\t\t\tDragStop,\n\t\t\t\tPointerMove,\n\t\t\t\tAbort,\n\t\t\t\tUpdateOptions,\n\t\t\t),\n\t\t}\n\t}\n}\n\nimpl ToolTransition for BrushTool {\n\tfn event_to_message_map(&self) -> EventToMessageMap {\n\t\tEventToMessageMap {\n\t\t\ttool_abort: Some(BrushToolMessage::Abort.into()),\n\t\t\tworking_color_changed: Some(BrushToolMessage::WorkingColorChanged.into()),\n\t\t\t..Default::default()\n\t\t}\n\t}\n}\n\n#[derive(Clone, Debug, Default)]\nstruct BrushToolData {\n\tstrokes: Vec<BrushStroke>,\n\tlayer: Option<LayerNodeIdentifier>,\n\ttransform: DAffine2,\n}\n\nimpl BrushToolData {\n\tfn load_existing_strokes(&mut self, document: &DocumentMessageHandler) -> Option<LayerNodeIdentifier> {\n\t\tself.transform = DAffine2::IDENTITY;\n\n\t\tif document.network_interface.selected_nodes().selected_layers(document.metadata()).count() != 1 {\n\t\t\treturn None;\n\t\t}\n\t\tlet layer = document.network_interface.selected_nodes().selected_layers(document.metadata()).next()?;\n\n\t\tself.layer = Some(layer);\n\t\tfor node_id in document.network_interface.upstream_flow_back_from_nodes(vec![layer.to_node()], &[], FlowType::HorizontalFlow) {\n\t\t\tlet Some(node) = document.network_interface.document_network().nodes.get(&node_id) else {\n\t\t\t\tcontinue;\n\t\t\t};\n\t\t\tlet Some(reference) = document.network_interface.reference(&node_id, &[]) else {\n\t\t\t\tcontinue;\n\t\t\t};\n\n\t\t\tif reference == DefinitionIdentifier::Network(\"Brush\".into()) && node_id != layer.to_node() {\n\t\t\t\tlet points_input = node.inputs.get(1)?;\n\t\t\t\tlet Some(TaggedValue::BrushStrokes(strokes)) = points_input.as_value() else { continue };\n\t\t\t\tself.strokes.clone_from(strokes);\n\n\t\t\t\treturn Some(layer);\n\t\t\t}\n\n\t\t\tif reference == DefinitionIdentifier::Network(\"Transform\".into()) {\n\t\t\t\tself.transform = get_current_transform(&node.inputs) * self.transform;\n\t\t\t}\n\t\t}\n\n\t\tself.transform = DAffine2::IDENTITY;\n\t\tNone\n\t}\n\n\tfn update_strokes(&self, responses: &mut VecDeque<Message>) {\n\t\tlet Some(layer) = self.layer else { return };\n\t\tlet strokes = self.strokes.clone();\n\t\tresponses.add(GraphOperationMessage::Brush { layer, strokes });\n\t}\n}\n\nimpl Fsm for BrushToolFsmState {\n\ttype ToolData = BrushToolData;\n\ttype ToolOptions = BrushOptions;\n\n\tfn transition(\n\t\tself,\n\t\tevent: ToolMessage,\n\t\ttool_data: &mut Self::ToolData,\n\t\ttool_action_data: &mut ToolActionMessageContext,\n\t\ttool_options: &Self::ToolOptions,\n\t\tresponses: &mut VecDeque<Message>,\n\t) -> Self {\n\t\tlet ToolActionMessageContext {\n\t\t\tdocument, global_tool_data, input, ..\n\t\t} = tool_action_data;\n\n\t\tlet ToolMessage::Brush(event) = event else { return self };\n\t\tmatch (self, event) {\n\t\t\t(BrushToolFsmState::Ready, BrushToolMessage::DragStart) => {\n\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\t\t\t\tlet loaded_layer = tool_data.load_existing_strokes(document);\n\n\t\t\t\tif let Some(layer) = loaded_layer {\n\t\t\t\t\tlet pos = document\n\t\t\t\t\t\t.network_interface\n\t\t\t\t\t\t.document_metadata()\n\t\t\t\t\t\t.downstream_transform_to_viewport(layer)\n\t\t\t\t\t\t.inverse()\n\t\t\t\t\t\t.transform_point2(input.mouse.position);\n\t\t\t\t\tlet layer_position = tool_data.transform.inverse().transform_point2(pos);\n\t\t\t\t\tlet layer_document_scale = document.metadata().downstream_transform_to_viewport(layer) * tool_data.transform;\n\n\t\t\t\t\t// TODO: Also scale it based on the input image ('Background' input).\n\t\t\t\t\t// TODO: Resizing the input image results in a different brush size from the chosen diameter.\n\t\t\t\t\tlet layer_scale = 0.0001_f64 // Safety against division by zero\n\t\t\t\t\t\t.max((layer_document_scale.matrix2 * glam::DVec2::X).length())\n\t\t\t\t\t\t.max((layer_document_scale.matrix2 * glam::DVec2::Y).length());\n\n\t\t\t\t\t// Start a new stroke with a single sample\n\t\t\t\t\tlet blend_mode = match tool_options.draw_mode {\n\t\t\t\t\t\tDrawMode::Draw => tool_options.blend_mode,\n\t\t\t\t\t\tDrawMode::Erase => BlendMode::Erase,\n\t\t\t\t\t\tDrawMode::Restore => BlendMode::Restore,\n\t\t\t\t\t};\n\t\t\t\t\ttool_data.strokes.push(BrushStroke {\n\t\t\t\t\t\ttrace: vec![BrushInputSample { position: layer_position }],\n\t\t\t\t\t\tstyle: BrushStyle {\n\t\t\t\t\t\t\tcolor: tool_options.color.active_color().unwrap_or_default(),\n\t\t\t\t\t\t\tdiameter: tool_options.diameter / layer_scale,\n\t\t\t\t\t\t\thardness: tool_options.hardness,\n\t\t\t\t\t\t\tflow: tool_options.flow,\n\t\t\t\t\t\t\tspacing: tool_options.spacing,\n\t\t\t\t\t\t\tblend_mode,\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\n\t\t\t\t\ttool_data.update_strokes(responses);\n\t\t\t\t\tBrushToolFsmState::Drawing\n\t\t\t\t}\n\t\t\t\t// Create the new layer, wait for the render output to return its transform, and then create the rest of the layer\n\t\t\t\telse {\n\t\t\t\t\tnew_brush_layer(document, responses);\n\t\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\t\tresponses.add(DeferMessage::AfterGraphRun {\n\t\t\t\t\t\tmessages: vec![BrushToolMessage::DragStart.into()],\n\t\t\t\t\t});\n\t\t\t\t\tBrushToolFsmState::Ready\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t(BrushToolFsmState::Drawing, BrushToolMessage::PointerMove) => {\n\t\t\t\tif let Some(layer) = tool_data.layer\n\t\t\t\t\t&& let Some(stroke) = tool_data.strokes.last_mut()\n\t\t\t\t{\n\t\t\t\t\tlet layer_position = document\n\t\t\t\t\t\t.network_interface\n\t\t\t\t\t\t.document_metadata()\n\t\t\t\t\t\t.downstream_transform_to_viewport(layer)\n\t\t\t\t\t\t.inverse()\n\t\t\t\t\t\t.transform_point2(input.mouse.position);\n\t\t\t\t\tlet layer_position = tool_data.transform.inverse().transform_point2(layer_position);\n\n\t\t\t\t\tstroke.trace.push(BrushInputSample { position: layer_position })\n\t\t\t\t}\n\t\t\t\ttool_data.update_strokes(responses);\n\n\t\t\t\tBrushToolFsmState::Drawing\n\t\t\t}\n\n\t\t\t(BrushToolFsmState::Drawing, BrushToolMessage::DragStop) => {\n\t\t\t\tif !tool_data.strokes.is_empty() {\n\t\t\t\t\tresponses.add(DocumentMessage::EndTransaction);\n\t\t\t\t} else {\n\t\t\t\t\tresponses.add(DocumentMessage::AbortTransaction);\n\t\t\t\t}\n\t\t\t\ttool_data.strokes.clear();\n\n\t\t\t\tBrushToolFsmState::Ready\n\t\t\t}\n\t\t\t(BrushToolFsmState::Drawing, BrushToolMessage::Abort) => {\n\t\t\t\tresponses.add(DocumentMessage::AbortTransaction);\n\t\t\t\ttool_data.strokes.clear();\n\n\t\t\t\tBrushToolFsmState::Ready\n\t\t\t}\n\t\t\t(_, BrushToolMessage::WorkingColorChanged) => {\n\t\t\t\tresponses.add(BrushToolMessage::UpdateOptions {\n\t\t\t\t\toptions: BrushToolMessageOptionsUpdate::WorkingColors(Some(global_tool_data.primary_color), Some(global_tool_data.secondary_color)),\n\t\t\t\t});\n\t\t\t\tself\n\t\t\t}\n\t\t\t_ => self,\n\t\t}\n\t}\n\n\tfn update_hints(&self, responses: &mut VecDeque<Message>) {\n\t\tlet hint_data = match self {\n\t\t\tBrushToolFsmState::Ready => HintData(vec![\n\t\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, \"Draw\")]),\n\t\t\t\tHintGroup(vec![HintInfo::multi_keys([[Key::BracketLeft], [Key::BracketRight]], \"Shrink/Grow Brush\")]),\n\t\t\t]),\n\t\t\tBrushToolFsmState::Drawing => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"\"), HintInfo::keys([Key::Escape], \"Cancel\").prepend_slash()])]),\n\t\t};\n\n\t\thint_data.send_layout(responses);\n\t}\n\n\tfn update_cursor(&self, responses: &mut VecDeque<Message>) {\n\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });\n\t}\n}\n\nfn new_brush_layer(document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier {\n\tresponses.add(DocumentMessage::DeselectAllLayers);\n\n\tlet brush_node = resolve_network_node_type(\"Brush\").expect(\"Brush node does not exist\").default_node_template();\n\n\tlet id = NodeId::new();\n\tresponses.add(GraphOperationMessage::NewCustomLayer {\n\t\tid,\n\t\tnodes: vec![(NodeId(0), brush_node)],\n\t\tparent: document.new_layer_parent(true),\n\t\tinsert_index: 0,\n\t});\n\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![id] });\n\n\tLayerNodeIdentifier::new_unchecked(id)\n}\n"
  },
  {
    "path": "editor/src/messages/tool/tool_messages/eyedropper_tool.rs",
    "content": "use super::tool_prelude::*;\nuse crate::messages::frontend::utility_types::EyedropperPreviewImage;\nuse crate::messages::tool::utility_types::DocumentToolData;\nuse graphene_std::vector::style::RenderMode;\n\n#[derive(Default, ExtractField)]\npub struct EyedropperTool {\n\tfsm_state: EyedropperToolFsmState,\n\tdata: EyedropperToolData,\n}\n\n#[impl_message(Message, ToolMessage, Eyedropper)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)]\npub enum EyedropperToolMessage {\n\t// Standard messages\n\tAbort,\n\n\t// Tool-specific messages\n\tSamplePrimaryColorBegin,\n\tSamplePrimaryColorEnd,\n\tPointerMove,\n\tSampleSecondaryColorBegin,\n\tSampleSecondaryColorEnd,\n\n\tPreviewImage { data: Vec<u8>, width: u32, height: u32 },\n}\n\nimpl ToolMetadata for EyedropperTool {\n\tfn icon_name(&self) -> String {\n\t\t\"GeneralEyedropperTool\".into()\n\t}\n\tfn tooltip_label(&self) -> String {\n\t\t\"Eyedropper Tool\".into()\n\t}\n\tfn tool_type(&self) -> crate::messages::tool::utility_types::ToolType {\n\t\tToolType::Eyedropper\n\t}\n}\n\nimpl LayoutHolder for EyedropperTool {\n\tfn layout(&self) -> Layout {\n\t\tLayout::default()\n\t}\n}\n\n#[message_handler_data]\nimpl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for EyedropperTool {\n\tfn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {\n\t\tif let ToolMessage::Eyedropper(EyedropperToolMessage::PreviewImage { data, width, height }) = message {\n\t\t\tlet image = EyedropperPreviewImage { data: data.into(), width, height };\n\n\t\t\tupdate_cursor_preview_common(responses, Some(image), context.input, context.global_tool_data, self.data.color_choice);\n\n\t\t\tif !self.data.preview {\n\t\t\t\tdisable_cursor_preview(responses, &mut self.data);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tself.fsm_state.process_event(message, &mut self.data, context, &(), responses, true);\n\t}\n\n\tadvertise_actions!(EyedropperToolMessageDiscriminant;\n\t\tSamplePrimaryColorBegin,\n\t\tSamplePrimaryColorEnd,\n\t\tSampleSecondaryColorBegin,\n\t\tSampleSecondaryColorEnd,\n\t\tPointerMove,\n\t\tAbort,\n\t);\n}\n\nimpl ToolTransition for EyedropperTool {\n\tfn event_to_message_map(&self) -> EventToMessageMap {\n\t\tEventToMessageMap {\n\t\t\ttool_abort: Some(EyedropperToolMessage::Abort.into()),\n\t\t\tworking_color_changed: Some(EyedropperToolMessage::PointerMove.into()),\n\t\t\t..Default::default()\n\t\t}\n\t}\n}\n\n#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]\nenum EyedropperToolFsmState {\n\t#[default]\n\tReady,\n\tSamplingPrimary,\n\tSamplingSecondary,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]\npub enum PrimarySecondary {\n\t#[default]\n\tPrimary,\n\tSecondary,\n}\n\n#[derive(Clone, Debug, Default)]\nstruct EyedropperToolData {\n\tpreview: bool,\n\tcolor_choice: Option<PrimarySecondary>,\n}\n\nimpl Fsm for EyedropperToolFsmState {\n\ttype ToolData = EyedropperToolData;\n\ttype ToolOptions = ();\n\n\tfn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionMessageContext, _tool_options: &(), responses: &mut VecDeque<Message>) -> Self {\n\t\tlet ToolActionMessageContext {\n\t\t\tdocument,\n\t\t\tglobal_tool_data,\n\t\t\tinput,\n\t\t\tviewport,\n\t\t\t..\n\t\t} = tool_action_data;\n\t\tlet render_mode = document.render_mode;\n\n\t\tlet ToolMessage::Eyedropper(event) = event else { return self };\n\t\tmatch (self, event) {\n\t\t\t// Ready -> Sampling\n\t\t\t(EyedropperToolFsmState::Ready, mouse_down) if matches!(mouse_down, EyedropperToolMessage::SamplePrimaryColorBegin | EyedropperToolMessage::SampleSecondaryColorBegin) => {\n\t\t\t\tupdate_cursor_preview(responses, tool_data, input, global_tool_data, None, render_mode);\n\n\t\t\t\tif mouse_down == EyedropperToolMessage::SamplePrimaryColorBegin {\n\t\t\t\t\tEyedropperToolFsmState::SamplingPrimary\n\t\t\t\t} else {\n\t\t\t\t\tEyedropperToolFsmState::SamplingSecondary\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Sampling -> Sampling\n\t\t\t(EyedropperToolFsmState::SamplingPrimary | EyedropperToolFsmState::SamplingSecondary, EyedropperToolMessage::PointerMove) => {\n\t\t\t\tlet mouse_position = viewport.logical(input.mouse.position);\n\t\t\t\tif viewport.is_in_bounds(mouse_position + viewport.offset()) {\n\t\t\t\t\tupdate_cursor_preview(responses, tool_data, input, global_tool_data, None, render_mode);\n\t\t\t\t} else {\n\t\t\t\t\tdisable_cursor_preview(responses, tool_data);\n\t\t\t\t}\n\n\t\t\t\tself\n\t\t\t}\n\t\t\t// Sampling -> Ready\n\t\t\t(EyedropperToolFsmState::SamplingPrimary, EyedropperToolMessage::SamplePrimaryColorEnd) | (EyedropperToolFsmState::SamplingSecondary, EyedropperToolMessage::SampleSecondaryColorEnd) => {\n\t\t\t\tlet set_color_choice = match self {\n\t\t\t\t\tEyedropperToolFsmState::SamplingPrimary => PrimarySecondary::Primary,\n\t\t\t\t\tEyedropperToolFsmState::SamplingSecondary => PrimarySecondary::Secondary,\n\t\t\t\t\t_ => unreachable!(),\n\t\t\t\t};\n\t\t\t\tupdate_cursor_preview(responses, tool_data, input, global_tool_data, Some(set_color_choice), render_mode);\n\t\t\t\tdisable_cursor_preview(responses, tool_data);\n\n\t\t\t\tEyedropperToolFsmState::Ready\n\t\t\t}\n\t\t\t// Any -> Ready\n\t\t\t(_, EyedropperToolMessage::Abort) => {\n\t\t\t\tdisable_cursor_preview(responses, tool_data);\n\n\t\t\t\tEyedropperToolFsmState::Ready\n\t\t\t}\n\t\t\t// Ready -> Ready\n\t\t\t_ => self,\n\t\t}\n\t}\n\n\tfn update_hints(&self, responses: &mut VecDeque<Message>) {\n\t\tlet hint_data = match self {\n\t\t\tEyedropperToolFsmState::Ready => HintData(vec![HintGroup(vec![\n\t\t\t\tHintInfo::mouse(MouseMotion::Lmb, \"Sample to Primary\"),\n\t\t\t\tHintInfo::keys_and_mouse([Key::Shift], MouseMotion::Lmb, \"Sample to Secondary\"),\n\t\t\t])]),\n\t\t\tEyedropperToolFsmState::SamplingPrimary | EyedropperToolFsmState::SamplingSecondary => {\n\t\t\t\tHintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"\"), HintInfo::keys([Key::Escape], \"Cancel\").prepend_slash()])])\n\t\t\t}\n\t\t};\n\n\t\thint_data.send_layout(responses);\n\t}\n\n\tfn update_cursor(&self, responses: &mut VecDeque<Message>) {\n\t\tlet cursor = match *self {\n\t\t\tEyedropperToolFsmState::Ready => MouseCursorIcon::Default,\n\t\t\tEyedropperToolFsmState::SamplingPrimary | EyedropperToolFsmState::SamplingSecondary => MouseCursorIcon::None,\n\t\t};\n\n\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor });\n\t}\n}\n\nfn disable_cursor_preview(responses: &mut VecDeque<Message>, tool_data: &mut EyedropperToolData) {\n\ttool_data.preview = false;\n\tresponses.add(FrontendMessage::UpdateEyedropperSamplingState {\n\t\timage: None,\n\t\tmouse_position: None,\n\t\tprimary_color: \"\".into(),\n\t\tsecondary_color: \"\".into(),\n\t\tset_color_choice: None,\n\t});\n}\n\nfn update_cursor_preview(\n\tresponses: &mut VecDeque<Message>,\n\ttool_data: &mut EyedropperToolData,\n\tinput: &InputPreprocessorMessageHandler,\n\tglobal_tool_data: &DocumentToolData,\n\tset_color_choice: Option<PrimarySecondary>,\n\trender_mode: RenderMode,\n) {\n\ttool_data.preview = true;\n\ttool_data.color_choice = set_color_choice;\n\n\t// On web, SVG Preview mode uses the frontend's SVG rasterization to sample pixels directly\n\t#[cfg(target_family = \"wasm\")]\n\tif render_mode == RenderMode::SvgPreview {\n\t\tupdate_cursor_preview_common(responses, None, input, global_tool_data, set_color_choice);\n\t\treturn;\n\t}\n\n\tlet _ = (&input, &global_tool_data, &render_mode);\n\n\t// For Vello-rendered modes (Normal, Outline, and Pixel Preview), submit a backend render request\n\t// which will return a zoomed-in pixel preview image via the EyedropperToolMessage::PreviewImage path\n\tresponses.add(PortfolioMessage::SubmitEyedropperPreviewRender);\n}\n\nfn update_cursor_preview_common(\n\tresponses: &mut VecDeque<Message>,\n\timage: Option<EyedropperPreviewImage>,\n\tinput: &InputPreprocessorMessageHandler,\n\tglobal_tool_data: &DocumentToolData,\n\tset_color_choice: Option<PrimarySecondary>,\n) {\n\tresponses.add(FrontendMessage::UpdateEyedropperSamplingState {\n\t\timage,\n\t\tmouse_position: Some(input.mouse.position.into()),\n\t\tprimary_color: \"#\".to_string() + global_tool_data.primary_color.to_rgb_hex_srgb().as_str(),\n\t\tsecondary_color: \"#\".to_string() + global_tool_data.secondary_color.to_rgb_hex_srgb().as_str(),\n\t\tset_color_choice,\n\t});\n}\n"
  },
  {
    "path": "editor/src/messages/tool/tool_messages/fill_tool.rs",
    "content": "use super::tool_prelude::*;\nuse crate::messages::portfolio::document::overlays::utility_types::OverlayContext;\nuse crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer;\nuse graphene_std::vector::style::Fill;\n\n#[derive(Default, ExtractField)]\npub struct FillTool {\n\tfsm_state: FillToolFsmState,\n}\n\n#[impl_message(Message, ToolMessage, Fill)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)]\npub enum FillToolMessage {\n\t// Standard messages\n\tAbort,\n\tWorkingColorChanged,\n\tOverlays { context: OverlayContext },\n\n\t// Tool-specific messages\n\tPointerMove,\n\tPointerUp,\n\tFillPrimaryColor,\n\tFillSecondaryColor,\n}\n\nimpl ToolMetadata for FillTool {\n\tfn icon_name(&self) -> String {\n\t\t\"GeneralFillTool\".into()\n\t}\n\tfn tooltip_label(&self) -> String {\n\t\t\"Fill Tool\".into()\n\t}\n\tfn tool_type(&self) -> crate::messages::tool::utility_types::ToolType {\n\t\tToolType::Fill\n\t}\n}\n\nimpl LayoutHolder for FillTool {\n\tfn layout(&self) -> Layout {\n\t\tLayout::default()\n\t}\n}\n\n#[message_handler_data]\nimpl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for FillTool {\n\tfn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {\n\t\tself.fsm_state.process_event(message, &mut (), context, &(), responses, true);\n\t}\n\tfn actions(&self) -> ActionList {\n\t\tmatch self.fsm_state {\n\t\t\tFillToolFsmState::Ready => actions!(FillToolMessageDiscriminant;\n\t\t\t\tFillPrimaryColor,\n\t\t\t\tFillSecondaryColor,\n\t\t\t\tPointerMove,\n\t\t\t),\n\t\t\tFillToolFsmState::Filling => actions!(FillToolMessageDiscriminant;\n\t\t\t\tPointerMove,\n\t\t\t\tPointerUp,\n\t\t\t\tAbort,\n\t\t\t),\n\t\t}\n\t}\n}\n\nimpl ToolTransition for FillTool {\n\tfn event_to_message_map(&self) -> EventToMessageMap {\n\t\tEventToMessageMap {\n\t\t\ttool_abort: Some(FillToolMessage::Abort.into()),\n\t\t\tworking_color_changed: Some(FillToolMessage::WorkingColorChanged.into()),\n\t\t\toverlay_provider: Some(|context| FillToolMessage::Overlays { context }.into()),\n\t\t\t..Default::default()\n\t\t}\n\t}\n}\n\n#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]\nenum FillToolFsmState {\n\t#[default]\n\tReady,\n\t// Implemented as a fake dragging state that can be used to abort unwanted fills\n\tFilling,\n}\n\nimpl Fsm for FillToolFsmState {\n\ttype ToolData = ();\n\ttype ToolOptions = ();\n\n\tfn transition(\n\t\tself,\n\t\tevent: ToolMessage,\n\t\t_tool_data: &mut Self::ToolData,\n\t\thandler_data: &mut ToolActionMessageContext,\n\t\t_tool_options: &Self::ToolOptions,\n\t\tresponses: &mut VecDeque<Message>,\n\t) -> Self {\n\t\tlet ToolActionMessageContext {\n\t\t\tdocument,\n\t\t\tglobal_tool_data,\n\t\t\tinput,\n\t\t\tviewport,\n\t\t\t..\n\t\t} = handler_data;\n\n\t\tlet ToolMessage::Fill(event) = event else { return self };\n\t\tmatch (self, event) {\n\t\t\t(_, FillToolMessage::Overlays { context: mut overlay_context }) => {\n\t\t\t\t// Choose the working color to preview\n\t\t\t\tlet use_secondary = input.keyboard.get(Key::Shift as usize);\n\t\t\t\tlet preview_color = if use_secondary { global_tool_data.secondary_color } else { global_tool_data.primary_color };\n\n\t\t\t\t// Get the layer the user is hovering over\n\t\t\t\tif let Some(layer) = document.click(input, viewport) {\n\t\t\t\t\tlet color_hex = format!(\"#{}\", preview_color.to_rgba_hex_srgb());\n\t\t\t\t\toverlay_context.fill_path_pattern(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer), &color_hex);\n\t\t\t\t}\n\n\t\t\t\tself\n\t\t\t}\n\t\t\t(_, FillToolMessage::PointerMove | FillToolMessage::WorkingColorChanged) => {\n\t\t\t\t// Generate the hover outline\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\tself\n\t\t\t}\n\t\t\t(FillToolFsmState::Ready, color_event) => {\n\t\t\t\tlet Some(layer_identifier) = document.click(input, viewport) else {\n\t\t\t\t\treturn self;\n\t\t\t\t};\n\t\t\t\t// If the layer is a raster layer, don't fill it, wait till the flood fill tool is implemented\n\t\t\t\tif NodeGraphLayer::is_raster_layer(layer_identifier, &mut document.network_interface) {\n\t\t\t\t\treturn self;\n\t\t\t\t}\n\t\t\t\tlet fill = match color_event {\n\t\t\t\t\tFillToolMessage::FillPrimaryColor => Fill::Solid(global_tool_data.primary_color.to_gamma_srgb()),\n\t\t\t\t\tFillToolMessage::FillSecondaryColor => Fill::Solid(global_tool_data.secondary_color.to_gamma_srgb()),\n\t\t\t\t\t_ => return self,\n\t\t\t\t};\n\n\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\t\t\t\tresponses.add(GraphOperationMessage::FillSet { layer: layer_identifier, fill });\n\n\t\t\t\tFillToolFsmState::Filling\n\t\t\t}\n\t\t\t(FillToolFsmState::Filling, FillToolMessage::PointerUp) => FillToolFsmState::Ready,\n\t\t\t(FillToolFsmState::Filling, FillToolMessage::Abort) => {\n\t\t\t\tresponses.add(DocumentMessage::AbortTransaction);\n\n\t\t\t\tFillToolFsmState::Ready\n\t\t\t}\n\t\t\t_ => self,\n\t\t}\n\t}\n\n\tfn update_hints(&self, responses: &mut VecDeque<Message>) {\n\t\tlet hint_data = match self {\n\t\t\tFillToolFsmState::Ready => HintData(vec![HintGroup(vec![\n\t\t\t\tHintInfo::mouse(MouseMotion::Lmb, \"Fill with Primary\"),\n\t\t\t\tHintInfo::keys([Key::Shift], \"Fill with Secondary\").prepend_plus(),\n\t\t\t])]),\n\t\t\tFillToolFsmState::Filling => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"\"), HintInfo::keys([Key::Escape], \"Cancel\").prepend_slash()])]),\n\t\t};\n\n\t\thint_data.send_layout(responses);\n\t}\n\n\tfn update_cursor(&self, responses: &mut VecDeque<Message>) {\n\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });\n\t}\n}\n\n#[cfg(test)]\nmod test_fill {\n\tpub use crate::test_utils::test_prelude::*;\n\tuse graphene_std::vector::fill;\n\tuse graphene_std::vector::style::Fill;\n\n\tasync fn get_fills(editor: &mut EditorTestUtils) -> Vec<Fill> {\n\t\tlet instrumented = match editor.eval_graph().await {\n\t\t\tOk(instrumented) => instrumented,\n\t\t\tErr(e) => panic!(\"Failed to evaluate graph: {e}\"),\n\t\t};\n\n\t\tinstrumented.grab_all_input::<fill::FillInput<Fill>>(&editor.runtime).collect()\n\t}\n\n\t#[tokio::test]\n\tasync fn ignore_artboard() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor.drag_tool(ToolType::Artboard, 0., 0., 100., 100., ModifierKeys::empty()).await;\n\t\teditor.click_tool(ToolType::Fill, MouseKeys::LEFT, DVec2::new(2., 2.), ModifierKeys::empty()).await;\n\t\tassert!(get_fills(&mut editor,).await.is_empty());\n\t}\n\n\t#[tokio::test]\n\tasync fn ignore_raster() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor.create_raster_image(Image::new(100, 100, Color::WHITE), Some((0., 0.))).await;\n\t\teditor.click_tool(ToolType::Fill, MouseKeys::LEFT, DVec2::new(2., 2.), ModifierKeys::empty()).await;\n\t\tassert!(get_fills(&mut editor,).await.is_empty());\n\t}\n\n\t#[tokio::test]\n\tasync fn primary() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor.drag_tool(ToolType::Rectangle, 0., 0., 100., 100., ModifierKeys::empty()).await;\n\t\teditor.select_primary_color(Color::GREEN).await;\n\t\teditor.click_tool(ToolType::Fill, MouseKeys::LEFT, DVec2::new(2., 2.), ModifierKeys::empty()).await;\n\t\tlet fills = get_fills(&mut editor).await;\n\t\tassert_eq!(fills.len(), 1);\n\t\tassert_eq!(fills[0].as_solid().unwrap().to_rgba8_srgb(), Color::GREEN.to_rgba8_srgb());\n\t}\n\n\t#[tokio::test]\n\tasync fn secondary() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor.drag_tool(ToolType::Rectangle, 0., 0., 100., 100., ModifierKeys::empty()).await;\n\t\teditor.select_secondary_color(Color::YELLOW).await;\n\t\teditor.click_tool(ToolType::Fill, MouseKeys::LEFT, DVec2::new(2., 2.), ModifierKeys::SHIFT).await;\n\t\tlet fills = get_fills(&mut editor).await;\n\t\tassert_eq!(fills.len(), 1);\n\t\tassert_eq!(fills[0].as_solid().unwrap().to_rgba8_srgb(), Color::YELLOW.to_rgba8_srgb());\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/tool_messages/freehand_tool.rs",
    "content": "use super::tool_prelude::*;\nuse crate::consts::DEFAULT_STROKE_WIDTH;\nuse crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;\nuse crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_network_node_type;\nuse crate::messages::portfolio::document::overlays::utility_functions::path_endpoint_overlays;\nuse crate::messages::portfolio::document::overlays::utility_types::OverlayContext;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};\nuse crate::messages::tool::common_functionality::graph_modification_utils;\nuse crate::messages::tool::common_functionality::utility_functions::should_extend;\nuse glam::DVec2;\nuse graph_craft::document::NodeId;\nuse graphene_std::Color;\nuse graphene_std::vector::VectorModificationType;\nuse graphene_std::vector::{PointId, SegmentId};\n\n#[derive(Default, ExtractField)]\npub struct FreehandTool {\n\tfsm_state: FreehandToolFsmState,\n\tdata: FreehandToolData,\n\toptions: FreehandOptions,\n}\n\npub struct FreehandOptions {\n\tline_weight: f64,\n\tfill: ToolColorOptions,\n\tstroke: ToolColorOptions,\n}\n\nimpl Default for FreehandOptions {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tline_weight: DEFAULT_STROKE_WIDTH,\n\t\t\tfill: ToolColorOptions::new_none(),\n\t\t\tstroke: ToolColorOptions::new_primary(),\n\t\t}\n\t}\n}\n\n#[impl_message(Message, ToolMessage, Freehand)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum FreehandToolMessage {\n\t// Standard messages\n\tOverlays { context: OverlayContext },\n\tAbort,\n\tWorkingColorChanged,\n\n\t// Tool-specific messages\n\tDragStart { append_to_selected: Key },\n\tDragStop,\n\tPointerMove,\n\tUpdateOptions { options: FreehandOptionsUpdate },\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum FreehandOptionsUpdate {\n\tFillColor(Option<Color>),\n\tFillColorType(ToolColorType),\n\tLineWeight(f64),\n\tStrokeColor(Option<Color>),\n\tStrokeColorType(ToolColorType),\n\tWorkingColors(Option<Color>, Option<Color>),\n}\n\n#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]\nenum FreehandToolFsmState {\n\t#[default]\n\tReady,\n\tDrawing,\n}\n\nimpl ToolMetadata for FreehandTool {\n\tfn icon_name(&self) -> String {\n\t\t\"VectorFreehandTool\".into()\n\t}\n\tfn tooltip_label(&self) -> String {\n\t\t\"Freehand Tool\".into()\n\t}\n\tfn tool_type(&self) -> crate::messages::tool::utility_types::ToolType {\n\t\tToolType::Freehand\n\t}\n}\n\nfn create_weight_widget(line_weight: f64) -> WidgetInstance {\n\tNumberInput::new(Some(line_weight))\n\t\t.unit(\" px\")\n\t\t.label(\"Weight\")\n\t\t.min(1.)\n\t\t.max((1_u64 << f64::MANTISSA_DIGITS) as f64)\n\t\t.on_update(|number_input: &NumberInput| {\n\t\t\tFreehandToolMessage::UpdateOptions {\n\t\t\t\toptions: FreehandOptionsUpdate::LineWeight(number_input.value.unwrap()),\n\t\t\t}\n\t\t\t.into()\n\t\t})\n\t\t.widget_instance()\n}\n\nimpl LayoutHolder for FreehandTool {\n\tfn layout(&self) -> Layout {\n\t\tlet mut widgets = self.options.fill.create_widgets(\n\t\t\t\"Fill\",\n\t\t\ttrue,\n\t\t\t|_| {\n\t\t\t\tFreehandToolMessage::UpdateOptions {\n\t\t\t\t\toptions: FreehandOptionsUpdate::FillColor(None),\n\t\t\t\t}\n\t\t\t\t.into()\n\t\t\t},\n\t\t\t|color_type: ToolColorType| {\n\t\t\t\tWidgetCallback::new(move |_| {\n\t\t\t\t\tFreehandToolMessage::UpdateOptions {\n\t\t\t\t\t\toptions: FreehandOptionsUpdate::FillColorType(color_type.clone()),\n\t\t\t\t\t}\n\t\t\t\t\t.into()\n\t\t\t\t})\n\t\t\t},\n\t\t\t|color: &ColorInput| {\n\t\t\t\tFreehandToolMessage::UpdateOptions {\n\t\t\t\t\toptions: FreehandOptionsUpdate::FillColor(color.value.as_solid().map(|color| color.to_linear_srgb())),\n\t\t\t\t}\n\t\t\t\t.into()\n\t\t\t},\n\t\t);\n\n\t\twidgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\n\t\twidgets.append(&mut self.options.stroke.create_widgets(\n\t\t\t\"Stroke\",\n\t\t\ttrue,\n\t\t\t|_| {\n\t\t\t\tFreehandToolMessage::UpdateOptions {\n\t\t\t\t\toptions: FreehandOptionsUpdate::StrokeColor(None),\n\t\t\t\t}\n\t\t\t\t.into()\n\t\t\t},\n\t\t\t|color_type: ToolColorType| {\n\t\t\t\tWidgetCallback::new(move |_| {\n\t\t\t\t\tFreehandToolMessage::UpdateOptions {\n\t\t\t\t\t\toptions: FreehandOptionsUpdate::StrokeColorType(color_type.clone()),\n\t\t\t\t\t}\n\t\t\t\t\t.into()\n\t\t\t\t})\n\t\t\t},\n\t\t\t|color: &ColorInput| {\n\t\t\t\tFreehandToolMessage::UpdateOptions {\n\t\t\t\t\toptions: FreehandOptionsUpdate::StrokeColor(color.value.as_solid().map(|color| color.to_linear_srgb())),\n\t\t\t\t}\n\t\t\t\t.into()\n\t\t\t},\n\t\t));\n\t\twidgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\t\twidgets.push(create_weight_widget(self.options.line_weight));\n\n\t\tLayout(vec![LayoutGroup::row(widgets)])\n\t}\n}\n\n#[message_handler_data]\nimpl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for FreehandTool {\n\tfn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {\n\t\tlet ToolMessage::Freehand(FreehandToolMessage::UpdateOptions { options }) = message else {\n\t\t\tself.fsm_state.process_event(message, &mut self.data, context, &self.options, responses, true);\n\t\t\treturn;\n\t\t};\n\t\tmatch options {\n\t\t\tFreehandOptionsUpdate::FillColor(color) => {\n\t\t\t\tself.options.fill.custom_color = color;\n\t\t\t\tself.options.fill.color_type = ToolColorType::Custom;\n\t\t\t}\n\t\t\tFreehandOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,\n\t\t\tFreehandOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,\n\t\t\tFreehandOptionsUpdate::StrokeColor(color) => {\n\t\t\t\tself.options.stroke.custom_color = color;\n\t\t\t\tself.options.stroke.color_type = ToolColorType::Custom;\n\t\t\t}\n\t\t\tFreehandOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,\n\t\t\tFreehandOptionsUpdate::WorkingColors(primary, secondary) => {\n\t\t\t\tself.options.stroke.primary_working_color = primary;\n\t\t\t\tself.options.stroke.secondary_working_color = secondary;\n\t\t\t\tself.options.fill.primary_working_color = primary;\n\t\t\t\tself.options.fill.secondary_working_color = secondary;\n\t\t\t}\n\t\t}\n\n\t\tself.send_layout(responses, LayoutTarget::ToolOptions);\n\t}\n\n\tfn actions(&self) -> ActionList {\n\t\tmatch self.fsm_state {\n\t\t\tFreehandToolFsmState::Ready => actions!(FreehandToolMessageDiscriminant;\n\t\t\t\tDragStart,\n\t\t\t\tDragStop,\n\t\t\t),\n\t\t\tFreehandToolFsmState::Drawing => actions!(FreehandToolMessageDiscriminant;\n\t\t\t\tDragStop,\n\t\t\t\tPointerMove,\n\t\t\t\tAbort,\n\t\t\t),\n\t\t}\n\t}\n}\n\nimpl ToolTransition for FreehandTool {\n\tfn event_to_message_map(&self) -> EventToMessageMap {\n\t\tEventToMessageMap {\n\t\t\toverlay_provider: Some(|context: OverlayContext| FreehandToolMessage::Overlays { context }.into()),\n\t\t\ttool_abort: Some(FreehandToolMessage::Abort.into()),\n\t\t\tworking_color_changed: Some(FreehandToolMessage::WorkingColorChanged.into()),\n\t\t\t..Default::default()\n\t\t}\n\t}\n}\n\n#[derive(Clone, Debug, Default)]\nstruct FreehandToolData {\n\tend_point: Option<(DVec2, PointId)>,\n\tdragged: bool,\n\tweight: f64,\n\tlayer: Option<LayerNodeIdentifier>,\n\t/// Viewport-space start position for newly created layers, used to compute local-space\n\t/// positions before the deferred TransformSet has been reflected in metadata.\n\tnew_layer_viewport_start: Option<DVec2>,\n}\n\nimpl Fsm for FreehandToolFsmState {\n\ttype ToolData = FreehandToolData;\n\ttype ToolOptions = FreehandOptions;\n\n\tfn transition(\n\t\tself,\n\t\tevent: ToolMessage,\n\t\ttool_data: &mut Self::ToolData,\n\t\ttool_action_data: &mut ToolActionMessageContext,\n\t\ttool_options: &Self::ToolOptions,\n\t\tresponses: &mut VecDeque<Message>,\n\t) -> Self {\n\t\tlet ToolActionMessageContext {\n\t\t\tdocument,\n\t\t\tglobal_tool_data,\n\t\t\tinput,\n\t\t\tshape_editor,\n\t\t\tviewport,\n\t\t\t..\n\t\t} = tool_action_data;\n\n\t\tlet ToolMessage::Freehand(event) = event else { return self };\n\t\tmatch (self, event) {\n\t\t\t(_, FreehandToolMessage::Overlays { context: mut overlay_context }) => {\n\t\t\t\tpath_endpoint_overlays(document, shape_editor, &mut overlay_context);\n\n\t\t\t\tself\n\t\t\t}\n\t\t\t(FreehandToolFsmState::Ready, FreehandToolMessage::DragStart { append_to_selected }) => {\n\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\n\t\t\t\ttool_data.dragged = false;\n\t\t\t\ttool_data.end_point = None;\n\t\t\t\ttool_data.weight = tool_options.line_weight;\n\t\t\t\ttool_data.new_layer_viewport_start = None;\n\n\t\t\t\t// Extend an endpoint of the selected path\n\t\t\t\tlet selected_nodes = document.network_interface.selected_nodes();\n\t\t\t\tlet tolerance = crate::consts::SNAP_POINT_TOLERANCE;\n\t\t\t\tif let Some((layer, point, position)) = should_extend(document, input.mouse.position, tolerance, selected_nodes.selected_layers(document.metadata())) {\n\t\t\t\t\ttool_data.layer = Some(layer);\n\t\t\t\t\ttool_data.end_point = Some((position, point));\n\n\t\t\t\t\textend_path_with_next_segment(tool_data, position, true, responses);\n\n\t\t\t\t\treturn FreehandToolFsmState::Drawing;\n\t\t\t\t}\n\n\t\t\t\tif input.keyboard.key(append_to_selected) {\n\t\t\t\t\tlet mut selected_layers_except_artboards = selected_nodes.selected_layers_except_artboards(&document.network_interface);\n\t\t\t\t\tlet existing_layer = selected_layers_except_artboards.next().filter(|_| selected_layers_except_artboards.next().is_none());\n\t\t\t\t\tif let Some(layer) = existing_layer {\n\t\t\t\t\t\ttool_data.layer = Some(layer);\n\n\t\t\t\t\t\tlet transform = document.metadata().transform_to_viewport(layer);\n\t\t\t\t\t\tlet position = transform.inverse().transform_point2(input.mouse.position);\n\n\t\t\t\t\t\textend_path_with_next_segment(tool_data, position, false, responses);\n\n\t\t\t\t\t\treturn FreehandToolFsmState::Drawing;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tresponses.add(DocumentMessage::DeselectAllLayers);\n\n\t\t\t\tlet parent = document.new_layer_bounding_artboard(input, viewport);\n\n\t\t\t\tlet node_type = resolve_network_node_type(\"Path\").expect(\"Path node does not exist\");\n\t\t\t\tlet node = node_type.default_node_template();\n\t\t\t\tlet nodes = vec![(NodeId(0), node)];\n\n\t\t\t\tlet layer = graph_modification_utils::new_custom(NodeId::new(), nodes, parent, responses);\n\t\t\t\ttool_options.stroke.apply_stroke(tool_data.weight, layer, responses);\n\t\t\t\ttool_options.fill.apply_fill(layer, responses);\n\t\t\t\ttool_data.layer = Some(layer);\n\t\t\t\ttool_data.new_layer_viewport_start = Some(input.mouse.position);\n\n\t\t\t\t// Position the layer at the initial mouse position via Transform\n\t\t\t\tresponses.add(DeferMessage::AfterGraphRun {\n\t\t\t\t\tmessages: vec![\n\t\t\t\t\t\tGraphOperationMessage::TransformSet {\n\t\t\t\t\t\t\tlayer,\n\t\t\t\t\t\t\ttransform: DAffine2::from_translation(input.mouse.position),\n\t\t\t\t\t\t\ttransform_in: TransformIn::Viewport,\n\t\t\t\t\t\t\tskip_rerender: false,\n\t\t\t\t\t\t}\n\t\t\t\t\t\t.into(),\n\t\t\t\t\t\tNodeGraphMessage::RunDocumentGraph.into(),\n\t\t\t\t\t],\n\t\t\t\t});\n\n\t\t\t\tFreehandToolFsmState::Drawing\n\t\t\t}\n\t\t\t(FreehandToolFsmState::Drawing, FreehandToolMessage::PointerMove) => {\n\t\t\t\tif let Some(layer) = tool_data.layer {\n\t\t\t\t\tlet transform = document.metadata().transform_to_viewport(layer);\n\n\t\t\t\t\t// For newly created layers, the deferred TransformSet may not yet be reflected\n\t\t\t\t\t// in the metadata, so compute local position from the known viewport start.\n\t\t\t\t\t// Once the metadata catches up (origin maps to start), switch to using it so\n\t\t\t\t\t// that mid-stroke pan/tilt/zoom works correctly.\n\t\t\t\t\tif let Some(start) = tool_data.new_layer_viewport_start\n\t\t\t\t\t\t&& transform.transform_point2(DVec2::ZERO).abs_diff_eq(start, 1e-5)\n\t\t\t\t\t{\n\t\t\t\t\t\ttool_data.new_layer_viewport_start = None;\n\t\t\t\t\t}\n\t\t\t\t\tlet position = if let Some(start) = tool_data.new_layer_viewport_start {\n\t\t\t\t\t\tinput.mouse.position - start\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttransform.inverse().transform_point2(input.mouse.position)\n\t\t\t\t\t};\n\n\t\t\t\t\textend_path_with_next_segment(tool_data, position, true, responses);\n\t\t\t\t}\n\n\t\t\t\tFreehandToolFsmState::Drawing\n\t\t\t}\n\t\t\t(FreehandToolFsmState::Drawing, FreehandToolMessage::DragStop) => {\n\t\t\t\tif tool_data.dragged {\n\t\t\t\t\tresponses.add(DocumentMessage::CommitTransaction);\n\t\t\t\t} else {\n\t\t\t\t\tresponses.add(DocumentMessage::EndTransaction);\n\t\t\t\t}\n\n\t\t\t\ttool_data.end_point = None;\n\t\t\t\ttool_data.layer = None;\n\t\t\t\ttool_data.new_layer_viewport_start = None;\n\n\t\t\t\tFreehandToolFsmState::Ready\n\t\t\t}\n\t\t\t(FreehandToolFsmState::Drawing, FreehandToolMessage::Abort) => {\n\t\t\t\tresponses.add(DocumentMessage::AbortTransaction);\n\t\t\t\ttool_data.layer = None;\n\t\t\t\ttool_data.end_point = None;\n\t\t\t\ttool_data.new_layer_viewport_start = None;\n\n\t\t\t\tFreehandToolFsmState::Ready\n\t\t\t}\n\t\t\t(_, FreehandToolMessage::WorkingColorChanged) => {\n\t\t\t\tresponses.add(FreehandToolMessage::UpdateOptions {\n\t\t\t\t\toptions: FreehandOptionsUpdate::WorkingColors(Some(global_tool_data.primary_color), Some(global_tool_data.secondary_color)),\n\t\t\t\t});\n\t\t\t\tself\n\t\t\t}\n\t\t\t_ => self,\n\t\t}\n\t}\n\n\tfn update_hints(&self, responses: &mut VecDeque<Message>) {\n\t\tlet hint_data = match self {\n\t\t\tFreehandToolFsmState::Ready => HintData(vec![HintGroup(vec![\n\t\t\t\tHintInfo::mouse(MouseMotion::LmbDrag, \"Draw Polyline\"),\n\t\t\t\t// TODO: Only show this if a single layer is selected and it's of a valid type (e.g. a vector path but not raster or artboard)\n\t\t\t\tHintInfo::keys([Key::Shift], \"Append to Selected Layer\").prepend_plus(),\n\t\t\t])]),\n\t\t\tFreehandToolFsmState::Drawing => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"\"), HintInfo::keys([Key::Escape], \"Cancel\").prepend_slash()])]),\n\t\t};\n\n\t\thint_data.send_layout(responses);\n\t}\n\n\tfn update_cursor(&self, responses: &mut VecDeque<Message>) {\n\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });\n\t}\n}\n\nfn extend_path_with_next_segment(tool_data: &mut FreehandToolData, position: DVec2, extend: bool, responses: &mut VecDeque<Message>) {\n\tif !tool_data.end_point.is_none_or(|(last_pos, _)| position != last_pos) || !position.is_finite() {\n\t\treturn;\n\t}\n\n\tlet Some(layer) = tool_data.layer else { return };\n\n\tlet id = PointId::generate();\n\tresponses.add(GraphOperationMessage::Vector {\n\t\tlayer,\n\t\tmodification_type: VectorModificationType::InsertPoint { id, position },\n\t});\n\n\tif extend && let Some((_, previous_position)) = tool_data.end_point {\n\t\tlet next_id = SegmentId::generate();\n\t\tlet points = [previous_position, id];\n\n\t\tresponses.add(GraphOperationMessage::Vector {\n\t\t\tlayer,\n\t\t\tmodification_type: VectorModificationType::InsertSegment {\n\t\t\t\tid: next_id,\n\t\t\t\tpoints,\n\t\t\t\thandles: [None, None],\n\t\t\t},\n\t\t});\n\t}\n\n\ttool_data.dragged = true;\n\ttool_data.end_point = Some((position, id));\n}\n\n#[cfg(test)]\nmod test_freehand {\n\tuse crate::messages::input_mapper::utility_types::input_mouse::{EditorMouseState, MouseKeys, ScrollDelta};\n\tuse crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;\n\tuse crate::messages::tool::common_functionality::graph_modification_utils::{NodeGraphLayer, get_stroke_width};\n\tuse crate::messages::tool::tool_messages::freehand_tool::FreehandOptionsUpdate;\n\tuse crate::test_utils::test_prelude::*;\n\tuse glam::{DAffine2, DVec2};\n\tuse graphene_std::vector::Vector;\n\n\tasync fn get_vector_and_transform_list(editor: &mut EditorTestUtils) -> Vec<(Vector, DAffine2)> {\n\t\tlet document = editor.active_document();\n\t\tlet layers = document.metadata().all_layers();\n\n\t\tlayers\n\t\t\t.filter_map(|layer| {\n\t\t\t\tlet graph_layer = NodeGraphLayer::new(layer, &document.network_interface);\n\t\t\t\t// Only get layers with path nodes\n\t\t\t\tlet _ = graph_layer.upstream_visible_node_id_from_name_in_layer(&DefinitionIdentifier::Network(\"Path\".into()))?;\n\n\t\t\t\tlet vector = document.network_interface.compute_modified_vector(layer)?;\n\t\t\t\tlet transform = document.metadata().transform_to_viewport(layer);\n\t\t\t\tSome((vector, transform))\n\t\t\t})\n\t\t\t.collect()\n\t}\n\n\tfn verify_path_points(vector_and_transform_list: &[(Vector, DAffine2)], expected_captured_points: &[DVec2], tolerance: f64) -> Result<(), String> {\n\t\tassert_eq!(vector_and_transform_list.len(), 1, \"There should be one row of Vector geometry\");\n\n\t\tlet (vector, transform) = vector_and_transform_list\n\t\t\t.iter()\n\t\t\t.find(|(data, _)| !data.point_domain.ids().is_empty())\n\t\t\t.ok_or(\"Could not find path data\")?;\n\n\t\tlet point_count = vector.point_domain.ids().len();\n\t\tlet segment_count = vector.segment_domain.ids().len();\n\n\t\tlet actual_positions: Vec<DVec2> = vector.point_domain.positions().iter().map(|&position| transform.transform_point2(position)).collect();\n\n\t\tif segment_count != point_count - 1 {\n\t\t\treturn Err(format!(\"Expected segments to be one less than points, got {segment_count} segments for {point_count} points\"));\n\t\t}\n\n\t\tif point_count != expected_captured_points.len() {\n\t\t\treturn Err(format!(\"Expected {} points, got {}\", expected_captured_points.len(), point_count));\n\t\t}\n\n\t\tfor (i, (&expected, &actual)) in expected_captured_points.iter().zip(actual_positions.iter()).enumerate() {\n\t\t\tlet distance = (expected - actual).length();\n\t\t\tif distance >= tolerance {\n\t\t\t\treturn Err(format!(\"Point {i} position mismatch: expected {expected:?}, got {actual:?} (distance: {distance})\"));\n\t\t\t}\n\t\t}\n\n\t\tOk(())\n\t}\n\n\t#[tokio::test]\n\tasync fn test_freehand_transformed_artboard() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\n\t\teditor.drag_tool(ToolType::Artboard, 0., 0., 500., 500., ModifierKeys::empty()).await;\n\n\t\tlet metadata = editor.active_document().metadata();\n\t\tlet artboard = metadata.all_layers().next().unwrap();\n\n\t\teditor\n\t\t\t.handle_message(GraphOperationMessage::TransformSet {\n\t\t\t\tlayer: artboard,\n\t\t\t\ttransform: DAffine2::from_scale_angle_translation(DVec2::new(1.5, 0.8), 0.3, DVec2::new(10., -5.)),\n\t\t\t\ttransform_in: TransformIn::Local,\n\t\t\t\tskip_rerender: false,\n\t\t\t})\n\t\t\t.await;\n\n\t\teditor.select_tool(ToolType::Freehand).await;\n\n\t\tlet mouse_points = [DVec2::new(150., 100.), DVec2::new(200., 150.), DVec2::new(250., 130.), DVec2::new(300., 170.)];\n\n\t\t// Expected points that will actually be captured by the tool\n\t\tlet expected_captured_points = &mouse_points[1..];\n\t\teditor.drag_path(&mouse_points, ModifierKeys::empty()).await;\n\n\t\tlet vector_and_transform_list = get_vector_and_transform_list(&mut editor).await;\n\t\tverify_path_points(&vector_and_transform_list, expected_captured_points, 1.).expect(\"Path points verification failed\");\n\t}\n\n\t#[tokio::test]\n\tasync fn test_extend_existing_path() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\n\t\tlet initial_points = [DVec2::new(100., 100.), DVec2::new(200., 200.), DVec2::new(300., 100.)];\n\n\t\teditor.select_tool(ToolType::Freehand).await;\n\n\t\tlet first_point = initial_points[0];\n\t\teditor.move_mouse(first_point.x, first_point.y, ModifierKeys::empty(), MouseKeys::empty()).await;\n\t\teditor.left_mousedown(first_point.x, first_point.y, ModifierKeys::empty()).await;\n\n\t\tfor &point in &initial_points[1..] {\n\t\t\teditor.move_mouse(point.x, point.y, ModifierKeys::empty(), MouseKeys::LEFT).await;\n\t\t}\n\n\t\tlet last_initial_point = initial_points[initial_points.len() - 1];\n\t\teditor\n\t\t\t.mouseup(\n\t\t\t\tEditorMouseState {\n\t\t\t\t\teditor_position: last_initial_point,\n\t\t\t\t\tmouse_keys: MouseKeys::empty(),\n\t\t\t\t\tscroll_delta: ScrollDelta::default(),\n\t\t\t\t},\n\t\t\t\tModifierKeys::empty(),\n\t\t\t)\n\t\t\t.await;\n\n\t\tlet initial_vector_and_transform_list = get_vector_and_transform_list(&mut editor).await;\n\t\tassert!(!initial_vector_and_transform_list.is_empty(), \"No Vector geometry found after initial drawing\");\n\n\t\tlet (initial_vector, initial_transform) = &initial_vector_and_transform_list[0];\n\t\tlet initial_point_count = initial_vector.point_domain.ids().len();\n\t\tlet initial_segment_count = initial_vector.segment_domain.ids().len();\n\n\t\tassert!(initial_point_count >= 2, \"Expected at least 2 points in initial path, found {initial_point_count}\");\n\t\tassert_eq!(\n\t\t\tinitial_segment_count,\n\t\t\tinitial_point_count - 1,\n\t\t\t\"Expected {} segments in initial path, found {}\",\n\t\t\tinitial_point_count - 1,\n\t\t\tinitial_segment_count\n\t\t);\n\n\t\tlet endpoints = initial_vector.anchor_endpoints().collect::<Vec<_>>();\n\t\tassert!(!endpoints.is_empty(), \"No extendable points found in the path\");\n\n\t\tlet endpoint_id = endpoints[0];\n\t\tlet endpoint_pos_option = initial_vector.point_domain.position_from_id(endpoint_id);\n\t\tassert!(endpoint_pos_option.is_some(), \"Could not find position for endpoint\");\n\n\t\tlet endpoint_pos = endpoint_pos_option.unwrap();\n\t\tlet endpoint_viewport_pos = initial_transform.transform_point2(endpoint_pos);\n\n\t\tassert!(endpoint_viewport_pos.is_finite(), \"Endpoint position is not finite\");\n\n\t\tlet extension_points = [DVec2::new(400., 200.), DVec2::new(500., 100.)];\n\n\t\tlet layer_node_id = {\n\t\t\tlet document = editor.active_document();\n\t\t\tlet layer = document.metadata().all_layers().next().unwrap();\n\t\t\tlayer.to_node()\n\t\t};\n\n\t\teditor.handle_message(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer_node_id] }).await;\n\n\t\teditor.select_tool(ToolType::Freehand).await;\n\n\t\teditor.move_mouse(endpoint_viewport_pos.x, endpoint_viewport_pos.y, ModifierKeys::empty(), MouseKeys::empty()).await;\n\t\teditor.left_mousedown(endpoint_viewport_pos.x, endpoint_viewport_pos.y, ModifierKeys::empty()).await;\n\n\t\tfor &point in &extension_points {\n\t\t\teditor.move_mouse(point.x, point.y, ModifierKeys::empty(), MouseKeys::LEFT).await;\n\t\t}\n\n\t\tlet last_extension_point = extension_points[extension_points.len() - 1];\n\t\teditor\n\t\t\t.mouseup(\n\t\t\t\tEditorMouseState {\n\t\t\t\t\teditor_position: last_extension_point,\n\t\t\t\t\tmouse_keys: MouseKeys::empty(),\n\t\t\t\t\tscroll_delta: ScrollDelta::default(),\n\t\t\t\t},\n\t\t\t\tModifierKeys::empty(),\n\t\t\t)\n\t\t\t.await;\n\n\t\tlet extended_vector_and_transform = get_vector_and_transform_list(&mut editor).await;\n\t\tassert!(!extended_vector_and_transform.is_empty(), \"No Vector geometry found after extension\");\n\n\t\tlet (extended_vector, _) = &extended_vector_and_transform[0];\n\t\tlet extended_point_count = extended_vector.point_domain.ids().len();\n\t\tlet extended_segment_count = extended_vector.segment_domain.ids().len();\n\n\t\tassert!(\n\t\t\textended_point_count > initial_point_count,\n\t\t\t\"Expected more points after extension, initial: {initial_point_count}, after extension: {extended_point_count}\"\n\t\t);\n\n\t\tassert_eq!(\n\t\t\textended_segment_count,\n\t\t\textended_point_count - 1,\n\t\t\t\"Expected segments to be one less than points, points: {extended_point_count}, segments: {extended_segment_count}\"\n\t\t);\n\n\t\tlet layer_count = {\n\t\t\tlet document = editor.active_document();\n\t\t\tdocument.metadata().all_layers().count()\n\t\t};\n\t\tassert_eq!(layer_count, 1, \"Expected only one layer after extending path\");\n\t}\n\n\t#[tokio::test]\n\tasync fn test_append_to_selected_layer_with_shift() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\n\t\teditor.select_tool(ToolType::Freehand).await;\n\n\t\tlet initial_points = [DVec2::new(100., 100.), DVec2::new(200., 200.), DVec2::new(300., 100.)];\n\n\t\tlet first_point = initial_points[0];\n\t\teditor.move_mouse(first_point.x, first_point.y, ModifierKeys::empty(), MouseKeys::empty()).await;\n\t\teditor.left_mousedown(first_point.x, first_point.y, ModifierKeys::empty()).await;\n\n\t\tfor &point in &initial_points[1..] {\n\t\t\teditor.move_mouse(point.x, point.y, ModifierKeys::empty(), MouseKeys::LEFT).await;\n\t\t}\n\n\t\tlet last_initial_point = initial_points[initial_points.len() - 1];\n\t\teditor\n\t\t\t.mouseup(\n\t\t\t\tEditorMouseState {\n\t\t\t\t\teditor_position: last_initial_point,\n\t\t\t\t\tmouse_keys: MouseKeys::empty(),\n\t\t\t\t\tscroll_delta: ScrollDelta::default(),\n\t\t\t\t},\n\t\t\t\tModifierKeys::empty(),\n\t\t\t)\n\t\t\t.await;\n\n\t\tlet initial_vector_and_transform = get_vector_and_transform_list(&mut editor).await;\n\t\tassert!(!initial_vector_and_transform.is_empty(), \"No vector geometry found after initial drawing\");\n\n\t\tlet (initial_vector, _) = &initial_vector_and_transform[0];\n\t\tlet initial_point_count = initial_vector.point_domain.ids().len();\n\t\tlet initial_segment_count = initial_vector.segment_domain.ids().len();\n\n\t\tlet existing_layer_id = {\n\t\t\tlet document = editor.active_document();\n\n\t\t\tdocument.metadata().all_layers().next().unwrap()\n\t\t};\n\n\t\teditor\n\t\t\t.handle_message(NodeGraphMessage::SelectedNodesSet {\n\t\t\t\tnodes: vec![existing_layer_id.to_node()],\n\t\t\t})\n\t\t\t.await;\n\n\t\tlet second_path_points = [DVec2::new(400., 100.), DVec2::new(500., 200.), DVec2::new(600., 100.)];\n\n\t\tlet first_second_point = second_path_points[0];\n\t\teditor.move_mouse(first_second_point.x, first_second_point.y, ModifierKeys::SHIFT, MouseKeys::empty()).await;\n\n\t\teditor\n\t\t\t.mousedown(\n\t\t\t\tEditorMouseState {\n\t\t\t\t\teditor_position: first_second_point,\n\t\t\t\t\tmouse_keys: MouseKeys::LEFT,\n\t\t\t\t\tscroll_delta: ScrollDelta::default(),\n\t\t\t\t},\n\t\t\t\tModifierKeys::SHIFT,\n\t\t\t)\n\t\t\t.await;\n\n\t\tfor &point in &second_path_points[1..] {\n\t\t\teditor.move_mouse(point.x, point.y, ModifierKeys::SHIFT, MouseKeys::LEFT).await;\n\t\t}\n\n\t\tlet last_second_point = second_path_points[second_path_points.len() - 1];\n\t\teditor\n\t\t\t.mouseup(\n\t\t\t\tEditorMouseState {\n\t\t\t\t\teditor_position: last_second_point,\n\t\t\t\t\tmouse_keys: MouseKeys::empty(),\n\t\t\t\t\tscroll_delta: ScrollDelta::default(),\n\t\t\t\t},\n\t\t\t\tModifierKeys::SHIFT,\n\t\t\t)\n\t\t\t.await;\n\n\t\tlet final_vector_and_transform = get_vector_and_transform_list(&mut editor).await;\n\t\tassert!(!final_vector_and_transform.is_empty(), \"No vector geometry found after second drawing\");\n\n\t\t// Verify we still have only one layer\n\t\tlet layer_count = {\n\t\t\tlet document = editor.active_document();\n\t\t\tdocument.metadata().all_layers().count()\n\t\t};\n\t\tassert_eq!(layer_count, 1, \"Expected only one layer after drawing with Shift key\");\n\n\t\tlet (final_vector, _) = &final_vector_and_transform[0];\n\t\tlet final_point_count = final_vector.point_domain.ids().len();\n\t\tlet final_segment_count = final_vector.segment_domain.ids().len();\n\n\t\tassert!(\n\t\t\tfinal_point_count > initial_point_count,\n\t\t\t\"Expected more points after appending to layer, initial: {initial_point_count}, after append: {final_point_count}\"\n\t\t);\n\n\t\tlet expected_new_points = second_path_points.len();\n\t\tlet expected_new_segments = expected_new_points - 1;\n\n\t\tassert_eq!(\n\t\t\tfinal_point_count,\n\t\t\tinitial_point_count + expected_new_points,\n\t\t\t\"Expected {} total points after append\",\n\t\t\tinitial_point_count + expected_new_points\n\t\t);\n\n\t\tassert_eq!(\n\t\t\tfinal_segment_count,\n\t\t\tinitial_segment_count + expected_new_segments,\n\t\t\t\"Expected {} total segments after append\",\n\t\t\tinitial_segment_count + expected_new_segments\n\t\t);\n\t}\n\n\t#[tokio::test]\n\tasync fn test_line_weight_affects_stroke_width() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\n\t\teditor.select_tool(ToolType::Freehand).await;\n\n\t\tlet custom_line_weight = 5.;\n\t\teditor\n\t\t\t.handle_message(ToolMessage::Freehand(FreehandToolMessage::UpdateOptions {\n\t\t\t\toptions: FreehandOptionsUpdate::LineWeight(custom_line_weight),\n\t\t\t}))\n\t\t\t.await;\n\n\t\tlet points = [DVec2::new(100., 100.), DVec2::new(200., 200.), DVec2::new(300., 100.)];\n\n\t\tlet first_point = points[0];\n\t\teditor.move_mouse(first_point.x, first_point.y, ModifierKeys::empty(), MouseKeys::empty()).await;\n\t\teditor.left_mousedown(first_point.x, first_point.y, ModifierKeys::empty()).await;\n\n\t\tfor &point in &points[1..] {\n\t\t\teditor.move_mouse(point.x, point.y, ModifierKeys::empty(), MouseKeys::LEFT).await;\n\t\t}\n\n\t\tlet last_point = points[points.len() - 1];\n\t\teditor\n\t\t\t.mouseup(\n\t\t\t\tEditorMouseState {\n\t\t\t\t\teditor_position: last_point,\n\t\t\t\t\tmouse_keys: MouseKeys::empty(),\n\t\t\t\t\tscroll_delta: ScrollDelta::default(),\n\t\t\t\t},\n\t\t\t\tModifierKeys::empty(),\n\t\t\t)\n\t\t\t.await;\n\n\t\tlet document = editor.active_document();\n\t\tlet layer = document.metadata().all_layers().next().unwrap();\n\n\t\tlet stroke_width = get_stroke_width(layer, &document.network_interface);\n\n\t\tassert!(stroke_width.is_some(), \"Stroke width should be available on the created path\");\n\n\t\tassert_eq!(\n\t\t\tstroke_width.unwrap(),\n\t\t\tcustom_line_weight,\n\t\t\t\"Stroke width should match the custom line weight (expected {}, got {})\",\n\t\t\tcustom_line_weight,\n\t\t\tstroke_width.unwrap()\n\t\t);\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/tool_messages/gradient_tool.rs",
    "content": "use super::tool_prelude::*;\nuse crate::consts::{\n\tCOLOR_OVERLAY_BLUE, DRAG_THRESHOLD, GRADIENT_MIDPOINT_DIAMOND_RADIUS, GRADIENT_MIDPOINT_MAX, GRADIENT_MIDPOINT_MIN, GRADIENT_STOP_MIN_VIEWPORT_GAP, LINE_ROTATE_SNAP_ANGLE,\n\tMANIPULATOR_GROUP_MARKER_SIZE, SEGMENT_INSERTION_DISTANCE, SEGMENT_OVERLAY_SIZE, SELECTION_THRESHOLD,\n};\nuse crate::messages::portfolio::document::overlays::utility_types::{GizmoEmphasis, OverlayContext};\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::tool::common_functionality::auto_panning::AutoPanning;\nuse crate::messages::tool::common_functionality::graph_modification_utils::{NodeGraphLayer, get_gradient};\nuse crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnapTypeConfiguration};\nuse graphene_std::raster::color::Color;\nuse graphene_std::vector::style::{Fill, Gradient, GradientStops, GradientType};\n\n#[derive(Default, ExtractField)]\npub struct GradientTool {\n\tfsm_state: GradientToolFsmState,\n\tdata: GradientToolData,\n\toptions: GradientOptions,\n}\n\n#[derive(Default)]\npub struct GradientOptions {\n\tgradient_type: GradientType,\n}\n\n#[impl_message(Message, ToolMessage, Gradient)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)]\npub enum GradientToolMessage {\n\t// Standard messages\n\tAbort,\n\tOverlays { context: OverlayContext },\n\tSelectionChanged,\n\n\t// Tool-specific messages\n\tDeleteStop,\n\tDoubleClick,\n\tInsertStop,\n\tPointerDown,\n\tPointerMove { constrain_axis: Key, lock_angle: Key },\n\tPointerOutsideViewport { constrain_axis: Key, lock_angle: Key },\n\tPointerUp,\n\tStartTransactionForColorStop,\n\tCommitTransactionForColorStop,\n\tCloseStopColorPicker,\n\tUpdateStopColor { color: Color },\n\tUpdateOptions { options: GradientOptionsUpdate },\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)]\npub enum GradientOptionsUpdate {\n\tType(GradientType),\n\tReverseStops,\n\tReverseDirection,\n}\n\nimpl ToolMetadata for GradientTool {\n\tfn icon_name(&self) -> String {\n\t\t\"GeneralGradientTool\".into()\n\t}\n\tfn tooltip_label(&self) -> String {\n\t\t\"Gradient Tool\".into()\n\t}\n\tfn tool_type(&self) -> crate::messages::tool::utility_types::ToolType {\n\t\tToolType::Gradient\n\t}\n}\n\n#[message_handler_data]\nimpl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for GradientTool {\n\tfn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {\n\t\tmatch message {\n\t\t\tToolMessage::Gradient(GradientToolMessage::UpdateOptions { options }) => match options {\n\t\t\t\tGradientOptionsUpdate::Type(gradient_type) => {\n\t\t\t\t\tself.options.gradient_type = gradient_type;\n\t\t\t\t\tapply_gradient_update(&mut self.data, context, responses, |g| g.gradient_type != gradient_type, |g| g.gradient_type = gradient_type);\n\t\t\t\t\tresponses.add(ToolMessage::UpdateHints);\n\t\t\t\t\tresponses.add(ToolMessage::UpdateCursor);\n\t\t\t\t}\n\t\t\t\tGradientOptionsUpdate::ReverseStops => {\n\t\t\t\t\tapply_gradient_update(&mut self.data, context, responses, |_| true, |g| g.stops = g.stops.reversed());\n\t\t\t\t}\n\t\t\t\tGradientOptionsUpdate::ReverseDirection => {\n\t\t\t\t\tapply_gradient_update(&mut self.data, context, responses, |_| true, |g| std::mem::swap(&mut g.start, &mut g.end));\n\t\t\t\t}\n\t\t\t},\n\t\t\tToolMessage::Gradient(GradientToolMessage::StartTransactionForColorStop) => {\n\t\t\t\tif self.data.color_picker_transaction_open {\n\t\t\t\t\tresponses.add(DocumentMessage::EndTransaction);\n\t\t\t\t}\n\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\t\t\t\tself.data.color_picker_transaction_open = true;\n\t\t\t}\n\t\t\tToolMessage::Gradient(GradientToolMessage::CommitTransactionForColorStop) => {\n\t\t\t\tif self.data.color_picker_transaction_open {\n\t\t\t\t\tresponses.add(DocumentMessage::EndTransaction);\n\t\t\t\t\tself.data.color_picker_transaction_open = false;\n\t\t\t\t}\n\t\t\t}\n\t\t\tToolMessage::Gradient(GradientToolMessage::UpdateStopColor { color }) => {\n\t\t\t\tif let Some(stop_index) = self.data.color_picker_editing_color_stop\n\t\t\t\t\t&& let Some(selected_gradient) = &mut self.data.selected_gradient\n\t\t\t\t\t&& stop_index < selected_gradient.gradient.stops.color.len()\n\t\t\t\t{\n\t\t\t\t\tselected_gradient.gradient.stops.color[stop_index] = color;\n\t\t\t\t\tselected_gradient.render_gradient(responses);\n\t\t\t\t\tresponses.add(PropertiesPanelMessage::Refresh);\n\t\t\t\t}\n\t\t\t}\n\t\t\tToolMessage::Gradient(GradientToolMessage::CloseStopColorPicker) => {\n\t\t\t\tif self.data.color_picker_transaction_open {\n\t\t\t\t\tresponses.add(DocumentMessage::EndTransaction);\n\t\t\t\t\tself.data.color_picker_transaction_open = false;\n\t\t\t\t}\n\t\t\t\tself.data.color_picker_editing_color_stop = None;\n\t\t\t}\n\t\t\t_ => {\n\t\t\t\tself.fsm_state.process_event(message, &mut self.data, context, &self.options, responses, false);\n\n\t\t\t\tlet has_gradient = has_gradient_on_selected_layers(context.document);\n\t\t\t\tif has_gradient != self.data.has_selected_gradient {\n\t\t\t\t\tself.data.has_selected_gradient = has_gradient;\n\t\t\t\t\tresponses.add(ToolMessage::RefreshToolOptions);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tadvertise_actions!(GradientToolMessageDiscriminant;\n\t\tPointerDown,\n\t\tPointerUp,\n\t\tPointerMove,\n\t\tDoubleClick,\n\t\tAbort,\n\t\tDeleteStop,\n\t);\n}\n\nimpl LayoutHolder for GradientTool {\n\tfn layout(&self) -> Layout {\n\t\tlet gradient_type = RadioInput::new(vec![\n\t\t\tRadioEntryData::new(\"Linear\").label(\"Linear\").tooltip_label(\"Linear Gradient\").on_update(move |_| {\n\t\t\t\tGradientToolMessage::UpdateOptions {\n\t\t\t\t\toptions: GradientOptionsUpdate::Type(GradientType::Linear),\n\t\t\t\t}\n\t\t\t\t.into()\n\t\t\t}),\n\t\t\tRadioEntryData::new(\"Radial\").label(\"Radial\").tooltip_label(\"Radial Gradient\").on_update(move |_| {\n\t\t\t\tGradientToolMessage::UpdateOptions {\n\t\t\t\t\toptions: GradientOptionsUpdate::Type(GradientType::Radial),\n\t\t\t\t}\n\t\t\t\t.into()\n\t\t\t}),\n\t\t])\n\t\t.selected_index(Some((self.options.gradient_type == GradientType::Radial) as u32))\n\t\t.widget_instance();\n\n\t\tlet reverse_stops = IconButton::new(\"Reverse\", 24)\n\t\t\t.tooltip_label(\"Reverse Stops\")\n\t\t\t.tooltip_description(\"Reverse the gradient color stops.\")\n\t\t\t.disabled(!self.data.has_selected_gradient)\n\t\t\t.on_update(|_| {\n\t\t\t\tGradientToolMessage::UpdateOptions {\n\t\t\t\t\toptions: GradientOptionsUpdate::ReverseStops,\n\t\t\t\t}\n\t\t\t\t.into()\n\t\t\t})\n\t\t\t.widget_instance();\n\n\t\tlet mut widgets = vec![gradient_type, Separator::new(SeparatorStyle::Unrelated).widget_instance(), reverse_stops];\n\n\t\tif self.options.gradient_type == GradientType::Radial {\n\t\t\tlet orientation = self\n\t\t\t\t.data\n\t\t\t\t.selected_gradient\n\t\t\t\t.as_ref()\n\t\t\t\t.map(|selected_gradient| {\n\t\t\t\t\tlet (start, end) = (selected_gradient.gradient.start, selected_gradient.gradient.end);\n\t\t\t\t\tif (end.x - start.x).abs() > f64::EPSILON * 1e6 {\n\t\t\t\t\t\tend.x > start.x\n\t\t\t\t\t} else {\n\t\t\t\t\t\t(start.x + start.y) < (end.x + end.y)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.unwrap_or(true);\n\n\t\t\tlet reverse_direction = IconButton::new(if orientation { \"ReverseRadialGradientToRight\" } else { \"ReverseRadialGradientToLeft\" }, 24)\n\t\t\t\t.tooltip_label(\"Reverse Direction\")\n\t\t\t\t.tooltip_description(\"Reverse which end the gradient radiates from.\")\n\t\t\t\t.disabled(!self.data.has_selected_gradient)\n\t\t\t\t.on_update(|_| {\n\t\t\t\t\tGradientToolMessage::UpdateOptions {\n\t\t\t\t\t\toptions: GradientOptionsUpdate::ReverseDirection,\n\t\t\t\t\t}\n\t\t\t\t\t.into()\n\t\t\t\t})\n\t\t\t\t.widget_instance();\n\n\t\t\twidgets.push(Separator::new(SeparatorStyle::Related).widget_instance());\n\t\t\twidgets.push(reverse_direction);\n\t\t}\n\n\t\tLayout(vec![LayoutGroup::row(widgets)])\n\t}\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\nenum GradientToolFsmState {\n\tReady { hovering: GradientHoverTarget, selected: GradientSelectedTarget },\n\tDrawing { drag_hint: GradientDragHintState },\n}\n\nimpl Default for GradientToolFsmState {\n\tfn default() -> Self {\n\t\tSelf::Ready {\n\t\t\thovering: GradientHoverTarget::None,\n\t\t\tselected: GradientSelectedTarget::None,\n\t\t}\n\t}\n}\n\n/// Computes the transform from gradient space to viewport space (where gradient space is 0..1)\nfn gradient_space_transform(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> DAffine2 {\n\tlet bounds = document.metadata().nonzero_bounding_box(layer);\n\tlet bound_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);\n\n\tlet multiplied = document.metadata().transform_to_viewport(layer);\n\n\tmultiplied * bound_transform\n}\n\n/// Whether two adjacent stops are too closely packed in viewport space for a midpoint diamond to be shown or interacted with.\nfn midpoint_hidden_by_proximity(left_stop_pos: f64, right_stop_pos: f64, viewport_line_length: f64) -> bool {\n\t(right_stop_pos - left_stop_pos) * viewport_line_length < GRADIENT_STOP_MIN_VIEWPORT_GAP * 2.\n}\n\n#[derive(PartialEq, Eq, Clone, Copy, Debug, Default)]\npub enum GradientDragTarget {\n\tStart,\n\t#[default]\n\tEnd,\n\tStop(usize),\n\tMidpoint(usize),\n\tNew,\n}\n\n/// Contains information about the selected gradient handle\n#[derive(Clone, Debug, Default)]\nstruct SelectedGradient {\n\tlayer: Option<LayerNodeIdentifier>,\n\ttransform: DAffine2,\n\tgradient: Gradient,\n\tdragging: GradientDragTarget,\n\tinitial_gradient: Gradient,\n}\n\nfn calculate_insertion(start: DVec2, end: DVec2, stops: &GradientStops, mouse: DVec2) -> Option<f64> {\n\tlet distance = (end - start).angle_to(mouse - start).sin() * (mouse - start).length();\n\tlet projection = ((end - start).angle_to(mouse - start)).cos() * start.distance(mouse) / start.distance(end);\n\n\tif distance.abs() < SEGMENT_INSERTION_DISTANCE && (0. ..=1.).contains(&projection) {\n\t\tfor stop in stops {\n\t\t\tlet stop_pos = start.lerp(end, stop.position);\n\t\t\tif stop_pos.distance_squared(mouse) < (MANIPULATOR_GROUP_MARKER_SIZE * 2.).powi(2) {\n\t\t\t\treturn None;\n\t\t\t}\n\t\t}\n\t\tif start.distance_squared(mouse) < (MANIPULATOR_GROUP_MARKER_SIZE * 2.).powi(2) || end.distance_squared(mouse) < (MANIPULATOR_GROUP_MARKER_SIZE * 2.).powi(2) {\n\t\t\treturn None;\n\t\t}\n\n\t\t// Don't insert when clicking near a (currently visible) midpoint diamond\n\t\tlet line_length = start.distance(end);\n\t\tfor i in 0..stops.position.len().saturating_sub(1) {\n\t\t\tlet left = stops.position[i];\n\t\t\tlet right = stops.position[i + 1];\n\n\t\t\tif midpoint_hidden_by_proximity(left, right, line_length) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet midpoint_pos = left + stops.midpoint[i] * (right - left);\n\t\t\tlet midpoint_viewport = start.lerp(end, midpoint_pos);\n\t\t\tif midpoint_viewport.distance_squared(mouse) < GRADIENT_MIDPOINT_DIAMOND_RADIUS.powi(2) {\n\t\t\t\treturn None;\n\t\t\t}\n\t\t}\n\n\t\treturn Some(projection);\n\t}\n\n\tNone\n}\n\nimpl SelectedGradient {\n\tpub fn new(gradient: Gradient, layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Self {\n\t\tlet transform = gradient_space_transform(layer, document);\n\t\tSelf {\n\t\t\tlayer: Some(layer),\n\t\t\ttransform,\n\t\t\tgradient: gradient.clone(),\n\t\t\tdragging: GradientDragTarget::End,\n\t\t\tinitial_gradient: gradient,\n\t\t}\n\t}\n\n\t#[allow(clippy::too_many_arguments)]\n\tpub fn update_gradient(\n\t\t&mut self,\n\t\tmut mouse: DVec2,\n\t\tresponses: &mut VecDeque<Message>,\n\t\tsnap_rotate: bool,\n\t\tlock_angle: bool,\n\t\tgradient_type: GradientType,\n\t\tdrag_start: DVec2,\n\t\tsnap_data: SnapData,\n\t\tsnap_manager: &mut SnapManager,\n\t\tgradient_angle: &mut f64,\n\t) {\n\t\tif mouse.distance(drag_start) < DRAG_THRESHOLD {\n\t\t\tself.gradient = self.initial_gradient.clone();\n\t\t\tself.render_gradient(responses);\n\t\t\treturn;\n\t\t}\n\n\t\tself.gradient.gradient_type = gradient_type;\n\n\t\tif (lock_angle || snap_rotate) && matches!(self.dragging, GradientDragTarget::End | GradientDragTarget::Start | GradientDragTarget::New) {\n\t\t\tlet point = if self.dragging == GradientDragTarget::Start {\n\t\t\t\tself.transform.transform_point2(self.gradient.end)\n\t\t\t} else if self.dragging == GradientDragTarget::New {\n\t\t\t\tdrag_start\n\t\t\t} else {\n\t\t\t\tself.transform.transform_point2(self.gradient.start)\n\t\t\t};\n\n\t\t\tlet delta = point - mouse;\n\n\t\t\tlet mut angle = -delta.angle_to(DVec2::X);\n\n\t\t\tif lock_angle {\n\t\t\t\tangle = *gradient_angle;\n\t\t\t} else if snap_rotate {\n\t\t\t\tlet snap_resolution = LINE_ROTATE_SNAP_ANGLE.to_radians();\n\t\t\t\tangle = (angle / snap_resolution).round() * snap_resolution;\n\t\t\t}\n\n\t\t\t*gradient_angle = angle;\n\n\t\t\tif lock_angle {\n\t\t\t\tlet unit_direction = DVec2::new(angle.cos(), angle.sin());\n\t\t\t\tlet length = delta.dot(unit_direction);\n\t\t\t\tmouse = point - length * unit_direction;\n\t\t\t} else {\n\t\t\t\tlet length = delta.length();\n\t\t\t\tlet rotated = DVec2::new(length * angle.cos(), length * angle.sin());\n\t\t\t\tmouse = point - rotated;\n\t\t\t}\n\t\t} else {\n\t\t\t// Update stored angle even when not constraining (for dragging endpoints and drawing a new gradient)\n\t\t\tif matches!(self.dragging, GradientDragTarget::End | GradientDragTarget::Start | GradientDragTarget::New) {\n\t\t\t\tlet point = if self.dragging == GradientDragTarget::Start {\n\t\t\t\t\tself.transform.transform_point2(self.gradient.end)\n\t\t\t\t} else if self.dragging == GradientDragTarget::New {\n\t\t\t\t\tdrag_start\n\t\t\t\t} else {\n\t\t\t\t\tself.transform.transform_point2(self.gradient.start)\n\t\t\t\t};\n\n\t\t\t\tlet delta = point - mouse;\n\t\t\t\t*gradient_angle = -delta.angle_to(DVec2::X);\n\t\t\t}\n\n\t\t\t// Basic point snapping when not angle-constraining\n\t\t\tlet document_to_viewport = snap_data.document.metadata().document_to_viewport;\n\t\t\tlet document_mouse = document_to_viewport.inverse().transform_point2(mouse);\n\t\t\tlet point_candidate = SnapCandidatePoint::gradient_handle(document_mouse);\n\t\t\tlet snapped = snap_manager.free_snap(&snap_data, &point_candidate, SnapTypeConfiguration::default());\n\t\t\tif snapped.is_snapped() {\n\t\t\t\tmouse = document_to_viewport.transform_point2(snapped.snapped_point_document);\n\t\t\t}\n\t\t\tsnap_manager.update_indicator(snapped);\n\t\t}\n\n\t\tlet transformed_mouse = self.transform.inverse().transform_point2(mouse);\n\n\t\tmatch self.dragging {\n\t\t\tGradientDragTarget::Start => {\n\t\t\t\tself.gradient.start = transformed_mouse;\n\t\t\t}\n\t\t\tGradientDragTarget::End => {\n\t\t\t\tself.gradient.end = transformed_mouse;\n\t\t\t}\n\t\t\tGradientDragTarget::New => {\n\t\t\t\tself.gradient.start = self.transform.inverse().transform_point2(drag_start);\n\t\t\t\tself.gradient.end = transformed_mouse;\n\t\t\t}\n\t\t\tGradientDragTarget::Stop(s) => {\n\t\t\t\tlet document_to_viewport = snap_data.document.metadata().document_to_viewport;\n\n\t\t\t\tlet (viewport_start, viewport_end) = (self.transform.transform_point2(self.gradient.start), self.transform.transform_point2(self.gradient.end));\n\n\t\t\t\tlet line_length = viewport_start.distance(viewport_end);\n\t\t\t\tif line_length < f64::EPSILON {\n\t\t\t\t\tself.render_gradient(responses);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet (document_start, document_end) = (\n\t\t\t\t\tdocument_to_viewport.inverse().transform_point2(viewport_start),\n\t\t\t\t\tdocument_to_viewport.inverse().transform_point2(viewport_end),\n\t\t\t\t);\n\n\t\t\t\tlet constraint = SnapConstraint::Line {\n\t\t\t\t\torigin: document_start,\n\t\t\t\t\tdirection: document_end - document_start,\n\t\t\t\t};\n\n\t\t\t\tlet document_mouse = document_to_viewport.inverse().transform_point2(mouse);\n\t\t\t\tlet point_candidate = SnapCandidatePoint::gradient_handle(document_mouse);\n\n\t\t\t\tlet snapped = snap_manager.constrained_snap(&snap_data, &point_candidate, constraint, SnapTypeConfiguration::default());\n\n\t\t\t\tlet projected_mouse_document = if snapped.is_snapped() {\n\t\t\t\t\tsnapped.snapped_point_document\n\t\t\t\t} else {\n\t\t\t\t\tconstraint.projection(document_mouse)\n\t\t\t\t};\n\t\t\t\tlet projected_mouse = document_to_viewport.transform_point2(projected_mouse_document);\n\t\t\t\tsnap_manager.update_indicator(snapped);\n\n\t\t\t\t// Calculate the new position by finding the closest point on the line\n\t\t\t\tlet new_pos = ((viewport_end - viewport_start).angle_to(projected_mouse - viewport_start)).cos() * viewport_start.distance(projected_mouse) / line_length;\n\n\t\t\t\tif !new_pos.is_finite() {\n\t\t\t\t\tself.render_gradient(responses);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Allow dragging through other stops (they'll reorder via sort), but clamp near\n\t\t\t\t// the endpoints at 0 and 1 if a different color stop already occupies that position\n\t\t\t\tlet min_gap = GRADIENT_STOP_MIN_VIEWPORT_GAP / line_length;\n\t\t\t\tlet last_index = self.gradient.stops.len() - 1;\n\n\t\t\t\tlet has_other_stop_at_zero = s != 0 && self.gradient.stops.position.first().is_some_and(|&p| p.abs() < f64::EPSILON * 1000.);\n\t\t\t\tlet has_other_stop_at_one = s != last_index && self.gradient.stops.position.last().is_some_and(|&p| (1. - p).abs() < f64::EPSILON * 1000.);\n\n\t\t\t\tlet left_bound = if has_other_stop_at_zero { min_gap } else { 0. };\n\t\t\t\tlet right_bound = if has_other_stop_at_one { 1. - min_gap } else { 1. };\n\n\t\t\t\tlet clamped = new_pos.clamp(left_bound, right_bound);\n\t\t\t\tself.gradient.stops.position[s] = clamped;\n\t\t\t\tlet new_position = self.gradient.stops.position[s];\n\t\t\t\tlet new_color = self.gradient.stops.color[s];\n\n\t\t\t\tself.gradient.stops.sort();\n\t\t\t\tif let Some(new_index) = self.gradient.stops.iter().position(|s| s.position == new_position && s.color == new_color) {\n\t\t\t\t\tself.dragging = GradientDragTarget::Stop(new_index);\n\t\t\t\t}\n\t\t\t}\n\t\t\tGradientDragTarget::Midpoint(midpoint_index) => {\n\t\t\t\tlet document_to_viewport = snap_data.document.metadata().document_to_viewport;\n\n\t\t\t\tlet (viewport_start, viewport_end) = (self.transform.transform_point2(self.gradient.start), self.transform.transform_point2(self.gradient.end));\n\n\t\t\t\tlet line_length = viewport_start.distance(viewport_end);\n\t\t\t\tif line_length < f64::EPSILON {\n\t\t\t\t\tself.render_gradient(responses);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet (document_start, document_end) = (\n\t\t\t\t\tdocument_to_viewport.inverse().transform_point2(viewport_start),\n\t\t\t\t\tdocument_to_viewport.inverse().transform_point2(viewport_end),\n\t\t\t\t);\n\n\t\t\t\tlet constraint = SnapConstraint::Line {\n\t\t\t\t\torigin: document_start,\n\t\t\t\t\tdirection: document_end - document_start,\n\t\t\t\t};\n\n\t\t\t\tlet document_mouse = document_to_viewport.inverse().transform_point2(mouse);\n\t\t\t\tlet point_candidate = SnapCandidatePoint::gradient_handle(document_mouse);\n\n\t\t\t\tlet snapped = snap_manager.constrained_snap(&snap_data, &point_candidate, constraint, SnapTypeConfiguration::default());\n\n\t\t\t\tlet projected_mouse_document = if snapped.is_snapped() {\n\t\t\t\t\tsnapped.snapped_point_document\n\t\t\t\t} else {\n\t\t\t\t\tconstraint.projection(document_mouse)\n\t\t\t\t};\n\t\t\t\tlet projected_mouse = document_to_viewport.transform_point2(projected_mouse_document);\n\t\t\t\tsnap_manager.update_indicator(snapped);\n\n\t\t\t\t// Calculate the position along the full gradient (0-1)\n\t\t\t\tlet full_pos = ((viewport_end - viewport_start).angle_to(projected_mouse - viewport_start)).cos() * viewport_start.distance(projected_mouse) / line_length;\n\n\t\t\t\tif !full_pos.is_finite() {\n\t\t\t\t\tself.render_gradient(responses);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Convert to a midpoint ratio within the interval between the two surrounding stops\n\t\t\t\tlet left_stop = self.gradient.stops.position[midpoint_index];\n\t\t\t\tlet right_stop = self.gradient.stops.position[midpoint_index + 1];\n\t\t\t\tlet range = right_stop - left_stop;\n\t\t\t\tif range > 0. {\n\t\t\t\t\tlet midpoint_ratio = ((full_pos - left_stop) / range).clamp(GRADIENT_MIDPOINT_MIN, GRADIENT_MIDPOINT_MAX);\n\t\t\t\t\tself.gradient.stops.midpoint[midpoint_index] = midpoint_ratio;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tself.render_gradient(responses);\n\t}\n\n\t/// Update the layer fill to the current gradient\n\tpub fn render_gradient(&mut self, responses: &mut VecDeque<Message>) {\n\t\tif let Some(layer) = self.layer {\n\t\t\tresponses.add(GraphOperationMessage::FillSet {\n\t\t\t\tlayer,\n\t\t\t\tfill: Fill::Gradient(self.gradient.clone()),\n\t\t\t});\n\t\t}\n\t}\n}\n\nimpl GradientTool {\n\t/// Get the gradient type of the selected gradient (if it exists)\n\tpub fn selected_gradient(&self) -> Option<GradientType> {\n\t\tself.data.selected_gradient.as_ref().map(|selected| selected.gradient.gradient_type)\n\t}\n}\n\nimpl ToolTransition for GradientTool {\n\tfn event_to_message_map(&self) -> EventToMessageMap {\n\t\tEventToMessageMap {\n\t\t\ttool_abort: Some(GradientToolMessage::Abort.into()),\n\t\t\tselection_changed: Some(GradientToolMessage::SelectionChanged.into()),\n\t\t\toverlay_provider: Some(|context| GradientToolMessage::Overlays { context }.into()),\n\t\t\t..Default::default()\n\t\t}\n\t}\n}\n\n#[derive(Clone, Debug, Default)]\nstruct GradientToolData {\n\tselected_gradient: Option<SelectedGradient>,\n\tsnap_manager: SnapManager,\n\tdrag_start: DVec2,\n\tauto_panning: AutoPanning,\n\tauto_pan_shift: DVec2,\n\tgradient_angle: f64,\n\thas_selected_gradient: bool,\n\tcolor_picker_editing_color_stop: Option<usize>,\n\tcolor_picker_transaction_open: bool,\n}\n\nimpl Fsm for GradientToolFsmState {\n\ttype ToolData = GradientToolData;\n\ttype ToolOptions = GradientOptions;\n\n\tfn transition(\n\t\tself,\n\t\tevent: ToolMessage,\n\t\ttool_data: &mut Self::ToolData,\n\t\ttool_action_data: &mut ToolActionMessageContext,\n\t\ttool_options: &Self::ToolOptions,\n\t\tresponses: &mut VecDeque<Message>,\n\t) -> Self {\n\t\tlet ToolActionMessageContext {\n\t\t\tdocument,\n\t\t\tglobal_tool_data,\n\t\t\tinput,\n\t\t\tviewport,\n\t\t\t..\n\t\t} = tool_action_data;\n\n\t\tlet ToolMessage::Gradient(event) = event else { return self };\n\t\tmatch (self, event) {\n\t\t\t(_, GradientToolMessage::Overlays { context: mut overlay_context }) => {\n\t\t\t\tlet selected = tool_data.selected_gradient.as_ref();\n\t\t\t\tlet mouse = input.mouse.position;\n\n\t\t\t\tfor layer in document.network_interface.selected_nodes().selected_visible_layers(&document.network_interface) {\n\t\t\t\t\tlet Some(gradient) = get_gradient(layer, &document.network_interface) else { continue };\n\t\t\t\t\tlet transform = gradient_space_transform(layer, document);\n\t\t\t\t\tlet dragging = selected\n\t\t\t\t\t\t.filter(|selected| selected.layer.is_some_and(|selected_layer| selected_layer == layer))\n\t\t\t\t\t\t.map(|selected| selected.dragging);\n\n\t\t\t\t\tlet gradient = if matches!(self, GradientToolFsmState::Drawing { .. })\n\t\t\t\t\t\t&& dragging.is_some()\n\t\t\t\t\t\t&& let Some(selected_gradient) = selected.filter(|s| s.layer == Some(layer))\n\t\t\t\t\t{\n\t\t\t\t\t\t&selected_gradient.gradient\n\t\t\t\t\t} else {\n\t\t\t\t\t\t&gradient\n\t\t\t\t\t};\n\n\t\t\t\t\tlet Gradient { start, end, stops, .. } = gradient;\n\t\t\t\t\tlet (start, end) = (transform.transform_point2(*start), transform.transform_point2(*end));\n\n\t\t\t\t\tfn color_to_hex(color: graphene_std::Color) -> String {\n\t\t\t\t\t\tformat!(\"#{}\", color.to_rgb_hex_srgb_from_gamma())\n\t\t\t\t\t}\n\n\t\t\t\t\tlet start_hex = stops.color.first().map(|&c| color_to_hex(c)).unwrap_or(String::from(COLOR_OVERLAY_BLUE));\n\t\t\t\t\tlet end_hex = stops.color.last().map(|&c| color_to_hex(c)).unwrap_or(String::from(COLOR_OVERLAY_BLUE));\n\n\t\t\t\t\t// Check if the first/last stops are at position ~0/~1 (rendered as the endpoint dots rather than as separate stops)\n\t\t\t\t\tlet first_at_start = stops.position.first().is_some_and(|&p| p.abs() < f64::EPSILON * 1000.);\n\t\t\t\t\tlet last_at_end = stops.position.last().is_some_and(|&p| (1. - p).abs() < f64::EPSILON * 1000.);\n\n\t\t\t\t\toverlay_context.line(start, end, None, None);\n\n\t\t\t\t\t// Determine which stop is selected (being dragged) and hovered (closest to mouse)\n\t\t\t\t\t// so they can be drawn last to appear on top of other overlapping stops\n\t\t\t\t\tlet selected_stop_id: Option<StopId> = match dragging {\n\t\t\t\t\t\tSome(GradientDragTarget::Start) => Some(StopId::Start),\n\t\t\t\t\t\tSome(GradientDragTarget::End) => Some(StopId::End),\n\t\t\t\t\t\tSome(GradientDragTarget::Stop(0)) if first_at_start => Some(StopId::Start),\n\t\t\t\t\t\tSome(GradientDragTarget::Stop(i)) if last_at_end && i == stops.len() - 1 => Some(StopId::End),\n\t\t\t\t\t\tSome(GradientDragTarget::Stop(i)) => Some(StopId::Middle(i)),\n\t\t\t\t\t\t_ => None,\n\t\t\t\t\t};\n\t\t\t\t\tlet stop_tolerance = (MANIPULATOR_GROUP_MARKER_SIZE * 2.).powi(2);\n\t\t\t\t\tlet hovered_stop_id: Option<StopId> = if !matches!(self, GradientToolFsmState::Drawing { .. }) {\n\t\t\t\t\t\t// Find the closest stop to the mouse (matching the click detection logic)\n\t\t\t\t\t\tlet mut best: Option<(f64, StopId)> = None;\n\t\t\t\t\t\tlet mut check = |dist_sq: f64, id: StopId| {\n\t\t\t\t\t\t\tif dist_sq < stop_tolerance && best.as_ref().is_none_or(|&(d, _)| dist_sq < d) {\n\t\t\t\t\t\t\t\tbest = Some((dist_sq, id));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\t\t\t\t\t\tcheck(start.distance_squared(mouse), StopId::Start);\n\t\t\t\t\t\tcheck(end.distance_squared(mouse), StopId::End);\n\t\t\t\t\t\tfor (index, stop) in stops.iter().enumerate() {\n\t\t\t\t\t\t\tif stop.position.abs() < f64::EPSILON * 1000. || (1. - stop.position).abs() < f64::EPSILON * 1000. {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcheck(start.lerp(end, stop.position).distance_squared(mouse), StopId::Middle(index));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbest.map(|(_, id)| id)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tNone\n\t\t\t\t\t};\n\n\t\t\t\t\t// Draw order: regular stops first, then selected, then hovered (so hovered appears on top)\n\t\t\t\t\tlet is_deferred = |id: StopId| -> bool { Some(id) == selected_stop_id || Some(id) == hovered_stop_id };\n\t\t\t\t\tlet emphasis_for = |id: StopId| -> GizmoEmphasis {\n\t\t\t\t\t\tif Some(id) == selected_stop_id {\n\t\t\t\t\t\t\tGizmoEmphasis::Active\n\t\t\t\t\t\t} else if Some(id) == hovered_stop_id {\n\t\t\t\t\t\t\tGizmoEmphasis::Hovered\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tGizmoEmphasis::Regular\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t\tlet mut draw_stop = |id: StopId, emphasis: GizmoEmphasis| match id {\n\t\t\t\t\t\tStopId::Start => overlay_context.gradient_color_stop(start, emphasis, &start_hex, !first_at_start),\n\t\t\t\t\t\tStopId::End => overlay_context.gradient_color_stop(end, emphasis, &end_hex, !last_at_end),\n\t\t\t\t\t\tStopId::Middle(i) => {\n\t\t\t\t\t\t\tif let Some(stop) = stops.iter().nth(i) {\n\t\t\t\t\t\t\t\toverlay_context.gradient_color_stop(start.lerp(end, stop.position), emphasis, &color_to_hex(stop.color), false);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\t// Draw regular (non-deferred) stops\n\t\t\t\t\tif !is_deferred(StopId::Start) {\n\t\t\t\t\t\tdraw_stop(StopId::Start, emphasis_for(StopId::Start));\n\t\t\t\t\t}\n\t\t\t\t\tif !is_deferred(StopId::End) {\n\t\t\t\t\t\tdraw_stop(StopId::End, emphasis_for(StopId::End));\n\t\t\t\t\t}\n\t\t\t\t\tfor (index, stop) in stops.iter().enumerate() {\n\t\t\t\t\t\tif stop.position.abs() < f64::EPSILON * 1000. || (1. - stop.position).abs() < f64::EPSILON * 1000. {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlet id = StopId::Middle(index);\n\t\t\t\t\t\tif !is_deferred(id) {\n\t\t\t\t\t\t\tdraw_stop(id, emphasis_for(id));\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Draw selected stop (if not also hovered)\n\t\t\t\t\tif let Some(selected_id) = selected_stop_id\n\t\t\t\t\t\t&& Some(selected_id) != hovered_stop_id\n\t\t\t\t\t{\n\t\t\t\t\t\tdraw_stop(selected_id, GizmoEmphasis::Active);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Draw hovered stop last (on top of everything)\n\t\t\t\t\tif let Some(hov_id) = hovered_stop_id {\n\t\t\t\t\t\tlet emphasis = if Some(hov_id) == selected_stop_id { GizmoEmphasis::Active } else { GizmoEmphasis::Hovered };\n\t\t\t\t\t\tdraw_stop(hov_id, emphasis);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Draw midpoint diamonds between adjacent stops (hidden when stops are too close in viewport space)\n\t\t\t\t\tlet line_angle = (end - start).to_angle();\n\t\t\t\t\tlet line_length = start.distance(end);\n\t\t\t\t\tlet midpoint_tolerance = GRADIENT_MIDPOINT_DIAMOND_RADIUS.powi(2);\n\t\t\t\t\tfor i in 0..stops.position.len().saturating_sub(1) {\n\t\t\t\t\t\tlet left = stops.position[i];\n\t\t\t\t\t\tlet right = stops.position[i + 1];\n\n\t\t\t\t\t\tif midpoint_hidden_by_proximity(left, right, line_length) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlet midpoint_pos = left + stops.midpoint[i] * (right - left);\n\t\t\t\t\t\tlet midpoint_viewport = start.lerp(end, midpoint_pos);\n\n\t\t\t\t\t\tlet emphasis = if dragging == Some(GradientDragTarget::Midpoint(i)) {\n\t\t\t\t\t\t\tGizmoEmphasis::Active\n\t\t\t\t\t\t} else if !matches!(self, GradientToolFsmState::Drawing { .. }) && midpoint_viewport.distance_squared(mouse) < midpoint_tolerance {\n\t\t\t\t\t\t\tGizmoEmphasis::Hovered\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tGizmoEmphasis::Regular\n\t\t\t\t\t\t};\n\t\t\t\t\t\toverlay_context.gradient_midpoint(midpoint_viewport, emphasis, line_angle);\n\t\t\t\t\t}\n\n\t\t\t\t\tif !matches!(self, GradientToolFsmState::Drawing { .. })\n\t\t\t\t\t\t&& calculate_insertion(start, end, stops, mouse).is_some()\n\t\t\t\t\t\t&& let Some(dir) = (end - start).try_normalize()\n\t\t\t\t\t{\n\t\t\t\t\t\tlet perp = dir.perp();\n\n\t\t\t\t\t\t// Snap the insertion point along the gradient line\n\t\t\t\t\t\tlet document_to_viewport = document.metadata().document_to_viewport;\n\n\t\t\t\t\t\tlet (document_start, document_end) = (document_to_viewport.inverse().transform_point2(start), document_to_viewport.inverse().transform_point2(end));\n\t\t\t\t\t\tlet constraint = SnapConstraint::Line {\n\t\t\t\t\t\t\torigin: document_start,\n\t\t\t\t\t\t\tdirection: document_end - document_start,\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tlet document_mouse = document_to_viewport.inverse().transform_point2(mouse);\n\t\t\t\t\t\tlet point_candidate = SnapCandidatePoint::gradient_handle(document_mouse);\n\n\t\t\t\t\t\tlet snap_data = SnapData::new(document, input, viewport);\n\t\t\t\t\t\tlet snapped = tool_data.snap_manager.constrained_snap(&snap_data, &point_candidate, constraint, SnapTypeConfiguration::default());\n\n\t\t\t\t\t\tlet snapped_point = if snapped.is_snapped() {\n\t\t\t\t\t\t\tdocument_to_viewport.transform_point2(snapped.snapped_point_document)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tlet projected = constraint.projection(document_mouse);\n\t\t\t\t\t\t\tdocument_to_viewport.transform_point2(projected)\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\toverlay_context.line(\n\t\t\t\t\t\t\tsnapped_point - perp * SEGMENT_OVERLAY_SIZE,\n\t\t\t\t\t\t\tsnapped_point + perp * SEGMENT_OVERLAY_SIZE,\n\t\t\t\t\t\t\tSome(COLOR_OVERLAY_BLUE),\n\t\t\t\t\t\t\tSome(1.),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tlet snap_data = SnapData::new(document, input, viewport);\n\t\t\t\ttool_data.snap_manager.draw_overlays(snap_data, &mut overlay_context);\n\n\t\t\t\t// Update color picker position if active (keeps it anchored to the stop during pan/zoom)\n\t\t\t\tif let Some(stop_index) = tool_data.color_picker_editing_color_stop\n\t\t\t\t\t&& let Some(selected_gradient) = tool_data.selected_gradient.as_ref()\n\t\t\t\t\t&& let Some(layer) = selected_gradient.layer\n\t\t\t\t{\n\t\t\t\t\tlet transform = gradient_space_transform(layer, document);\n\t\t\t\t\tlet gradient = &selected_gradient.gradient;\n\t\t\t\t\tif stop_index < gradient.stops.position.len() {\n\t\t\t\t\t\tlet color = gradient.stops.color[stop_index].to_gamma_srgb();\n\t\t\t\t\t\tlet position = gradient.stops.position[stop_index];\n\t\t\t\t\t\tlet position = transform.transform_point2(gradient.start.lerp(gradient.end, position)).into();\n\t\t\t\t\t\tresponses.add(FrontendMessage::UpdateGradientStopColorPickerPosition { color, position });\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tself\n\t\t\t}\n\t\t\t(GradientToolFsmState::Ready { .. }, GradientToolMessage::SelectionChanged) => {\n\t\t\t\tif tool_data.color_picker_editing_color_stop.is_some() {\n\t\t\t\t\tif tool_data.color_picker_transaction_open {\n\t\t\t\t\t\tresponses.add(DocumentMessage::EndTransaction);\n\t\t\t\t\t\ttool_data.color_picker_transaction_open = false;\n\t\t\t\t\t}\n\t\t\t\t\ttool_data.color_picker_editing_color_stop = None;\n\t\t\t\t}\n\t\t\t\ttool_data.selected_gradient = None;\n\t\t\t\tGradientToolFsmState::Ready {\n\t\t\t\t\thovering: GradientHoverTarget::None,\n\t\t\t\t\tselected: GradientSelectedTarget::None,\n\t\t\t\t}\n\t\t\t}\n\t\t\t(_, GradientToolMessage::DoubleClick) => {\n\t\t\t\t// Only reset if the mouse hasn't moved so we don't trigger from a click-then-click-and-drag being reported as a double-click\n\t\t\t\tlet drag_start_viewport = document.metadata().document_to_viewport.transform_point2(tool_data.drag_start);\n\t\t\t\tif input.mouse.position.distance(drag_start_viewport) <= DRAG_THRESHOLD\n\t\t\t\t\t&& let Some(selected_gradient) = &mut tool_data.selected_gradient\n\t\t\t\t{\n\t\t\t\t\tmatch selected_gradient.dragging {\n\t\t\t\t\t\tGradientDragTarget::Midpoint(index) => {\n\t\t\t\t\t\t\tselected_gradient.gradient.stops.midpoint[index] = 0.5;\n\t\t\t\t\t\t\tselected_gradient.render_gradient(responses);\n\t\t\t\t\t\t\tresponses.add(PropertiesPanelMessage::Refresh);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tGradientDragTarget::Start | GradientDragTarget::End | GradientDragTarget::Stop(_) => {\n\t\t\t\t\t\t\t// Find the stop index from the drag target\n\t\t\t\t\t\t\tlet stop_index = match selected_gradient.dragging {\n\t\t\t\t\t\t\t\tGradientDragTarget::Stop(i) => Some(i),\n\t\t\t\t\t\t\t\tGradientDragTarget::Start => selected_gradient.gradient.stops.position.iter().position(|&p| p.abs() < f64::EPSILON * 1000.),\n\t\t\t\t\t\t\t\tGradientDragTarget::End => selected_gradient.gradient.stops.position.iter().position(|&p| (1. - p).abs() < f64::EPSILON * 1000.),\n\t\t\t\t\t\t\t\t_ => None,\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tif let Some(stop_index) = stop_index\n\t\t\t\t\t\t\t\t&& stop_index < selected_gradient.gradient.stops.color.len()\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t// Dismiss any existing color picker first\n\t\t\t\t\t\t\t\tif tool_data.color_picker_editing_color_stop.is_some() && tool_data.color_picker_transaction_open {\n\t\t\t\t\t\t\t\t\tresponses.add(DocumentMessage::EndTransaction);\n\t\t\t\t\t\t\t\t\ttool_data.color_picker_transaction_open = false;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tlet stop_pos = selected_gradient.gradient.stops.position[stop_index];\n\t\t\t\t\t\t\t\tlet viewport_pos = selected_gradient\n\t\t\t\t\t\t\t\t\t.transform\n\t\t\t\t\t\t\t\t\t.transform_point2(selected_gradient.gradient.start.lerp(selected_gradient.gradient.end, stop_pos));\n\t\t\t\t\t\t\t\tlet position = viewport_pos.into();\n\t\t\t\t\t\t\t\tlet color = selected_gradient.gradient.stops.color[stop_index].to_gamma_srgb();\n\t\t\t\t\t\t\t\ttool_data.color_picker_editing_color_stop = Some(stop_index);\n\t\t\t\t\t\t\t\tresponses.add(FrontendMessage::UpdateGradientStopColorPickerPosition { color, position });\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t_ => {}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tself\n\t\t\t}\n\t\t\t(state, GradientToolMessage::DeleteStop) => {\n\t\t\t\tlet ready_default = GradientToolFsmState::Ready {\n\t\t\t\t\thovering: GradientHoverTarget::None,\n\t\t\t\t\tselected: GradientSelectedTarget::None,\n\t\t\t\t};\n\n\t\t\t\tlet Some(selected_gradient) = &mut tool_data.selected_gradient else {\n\t\t\t\t\treturn ready_default;\n\t\t\t\t};\n\n\t\t\t\t// Skip if invalid gradient\n\t\t\t\tif selected_gradient.gradient.stops.len() < 2 {\n\t\t\t\t\treturn ready_default;\n\t\t\t\t}\n\n\t\t\t\t// If we're in the middle of a drag, abort it first and revert to the initial gradient\n\t\t\t\tif matches!(state, GradientToolFsmState::Drawing { .. }) {\n\t\t\t\t\tselected_gradient.gradient = selected_gradient.initial_gradient.clone();\n\t\t\t\t\tselected_gradient.render_gradient(responses);\n\t\t\t\t\tresponses.add(DocumentMessage::AbortTransaction);\n\t\t\t\t\ttool_data.snap_manager.cleanup(responses);\n\t\t\t\t}\n\n\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\n\t\t\t\t// Remove the selected point\n\t\t\t\tmatch selected_gradient.dragging {\n\t\t\t\t\tGradientDragTarget::Start => {\n\t\t\t\t\t\t// Only delete if there's a real color stop at position ~0 (not the endpoint of the line which isn't itself a color stop)\n\t\t\t\t\t\tif selected_gradient.gradient.stops.position.first().is_some_and(|&p| p.abs() < f64::EPSILON * 1000.) {\n\t\t\t\t\t\t\tselected_gradient.gradient.stops.remove(0);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tresponses.add(DocumentMessage::AbortTransaction);\n\t\t\t\t\t\t\treturn ready_default;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tGradientDragTarget::End => {\n\t\t\t\t\t\t// Only delete if there's a real color stop at position ~1 (not the endpoint of the line which isn't itself a color stop)\n\t\t\t\t\t\tif selected_gradient.gradient.stops.position.last().is_some_and(|&p| (1. - p).abs() < f64::EPSILON * 1000.) {\n\t\t\t\t\t\t\tlet _ = selected_gradient.gradient.stops.pop();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tresponses.add(DocumentMessage::AbortTransaction);\n\t\t\t\t\t\t\treturn ready_default;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tGradientDragTarget::New => {\n\t\t\t\t\t\tresponses.add(DocumentMessage::AbortTransaction);\n\t\t\t\t\t\treturn ready_default;\n\t\t\t\t\t}\n\t\t\t\t\tGradientDragTarget::Stop(index) => {\n\t\t\t\t\t\tselected_gradient.gradient.stops.remove(index);\n\t\t\t\t\t}\n\t\t\t\t\tGradientDragTarget::Midpoint(index) => {\n\t\t\t\t\t\tselected_gradient.gradient.stops.midpoint[index] = 0.5;\n\t\t\t\t\t\tselected_gradient.render_gradient(responses);\n\n\t\t\t\t\t\tresponses.add(DocumentMessage::CommitTransaction);\n\t\t\t\t\t\tresponses.add(PropertiesPanelMessage::Refresh);\n\n\t\t\t\t\t\treturn ready_default;\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\t// The gradient has only one point and so should become a fill\n\t\t\t\tif selected_gradient.gradient.stops.len() == 1 {\n\t\t\t\t\tif let Some(layer) = selected_gradient.layer {\n\t\t\t\t\t\tresponses.add(GraphOperationMessage::FillSet {\n\t\t\t\t\t\t\tlayer,\n\t\t\t\t\t\t\tfill: Fill::Solid(selected_gradient.gradient.stops.color[0]),\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tresponses.add(DocumentMessage::CommitTransaction);\n\t\t\t\t\tresponses.add(PropertiesPanelMessage::Refresh);\n\t\t\t\t\treturn ready_default;\n\t\t\t\t}\n\n\t\t\t\t// Find the minimum and maximum positions\n\t\t\t\tlet min_position = selected_gradient.gradient.stops.position.iter().copied().reduce(f64::min).expect(\"No min\");\n\t\t\t\tlet max_position = selected_gradient.gradient.stops.position.iter().copied().reduce(f64::max).expect(\"No max\");\n\n\t\t\t\t// Recompute the start and end position of the gradient (in viewport transform)\n\t\t\t\tif let Some(layer) = selected_gradient.layer {\n\t\t\t\t\tselected_gradient.transform = gradient_space_transform(layer, document);\n\t\t\t\t}\n\t\t\t\tlet transform = selected_gradient.transform;\n\t\t\t\tlet (start, end) = (transform.transform_point2(selected_gradient.gradient.start), transform.transform_point2(selected_gradient.gradient.end));\n\t\t\t\tlet (new_start, new_end) = (start.lerp(end, min_position), start.lerp(end, max_position));\n\t\t\t\tselected_gradient.gradient.start = transform.inverse().transform_point2(new_start);\n\t\t\t\tselected_gradient.gradient.end = transform.inverse().transform_point2(new_end);\n\n\t\t\t\t// Remap the positions\n\t\t\t\tfor position in selected_gradient.gradient.stops.position.iter_mut() {\n\t\t\t\t\t*position = (*position - min_position) / (max_position - min_position);\n\t\t\t\t}\n\n\t\t\t\t// Render the new gradient\n\t\t\t\tselected_gradient.render_gradient(responses);\n\t\t\t\tresponses.add(DocumentMessage::CommitTransaction);\n\t\t\t\tresponses.add(PropertiesPanelMessage::Refresh);\n\t\t\t\ttool_data.selected_gradient = None;\n\n\t\t\t\tready_default\n\t\t\t}\n\t\t\t(_, GradientToolMessage::InsertStop) => {\n\t\t\t\tfor layer in document.network_interface.selected_nodes().selected_visible_layers(&document.network_interface) {\n\t\t\t\t\tlet Some(mut gradient) = get_gradient(layer, &document.network_interface) else { continue };\n\t\t\t\t\t// TODO: This transform is incorrect. I think this is since it is based on the Footprint which has not been updated yet\n\t\t\t\t\tlet transform = gradient_space_transform(layer, document);\n\t\t\t\t\tlet mouse = input.mouse.position;\n\t\t\t\t\tlet (start, end) = (transform.transform_point2(gradient.start), transform.transform_point2(gradient.end));\n\n\t\t\t\t\t// Compute the distance from the mouse to the gradient line in viewport space\n\t\t\t\t\tlet distance = (end - start).angle_to(mouse - start).sin() * (mouse - start).length();\n\n\t\t\t\t\t// If click is on the line then insert point\n\t\t\t\t\tif distance < (SELECTION_THRESHOLD * 2.) {\n\t\t\t\t\t\t// Try and insert the new stop\n\t\t\t\t\t\tif let Some(index) = gradient.insert_stop(mouse, transform) {\n\t\t\t\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\n\t\t\t\t\t\t\tlet mut selected_gradient = SelectedGradient::new(gradient, layer, document);\n\n\t\t\t\t\t\t\t// Select the new point\n\t\t\t\t\t\t\tselected_gradient.dragging = GradientDragTarget::Stop(index);\n\n\t\t\t\t\t\t\t// Update the layer fill\n\t\t\t\t\t\t\tselected_gradient.render_gradient(responses);\n\n\t\t\t\t\t\t\ttool_data.selected_gradient = Some(selected_gradient);\n\t\t\t\t\t\t\tresponses.add(DocumentMessage::CommitTransaction);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tself\n\t\t\t}\n\t\t\t(GradientToolFsmState::Ready { .. }, GradientToolMessage::PointerDown) => {\n\t\t\t\tlet document_to_viewport = document.metadata().document_to_viewport;\n\n\t\t\t\tlet mut mouse = input.mouse.position;\n\n\t\t\t\tlet snap_data = SnapData::new(document, input, viewport);\n\t\t\t\tlet point = SnapCandidatePoint::gradient_handle(document_to_viewport.inverse().transform_point2(mouse));\n\t\t\t\tlet snapped = tool_data.snap_manager.free_snap(&snap_data, &point, SnapTypeConfiguration::default());\n\n\t\t\t\tif snapped.is_snapped() {\n\t\t\t\t\tmouse = document_to_viewport.transform_point2(snapped.snapped_point_document);\n\t\t\t\t}\n\n\t\t\t\ttool_data.drag_start = document_to_viewport.inverse().transform_point2(mouse);\n\t\t\t\ttool_data.auto_pan_shift = DVec2::ZERO;\n\t\t\t\tlet tolerance = (MANIPULATOR_GROUP_MARKER_SIZE * 2.).powi(2);\n\n\t\t\t\tlet mut drag_hint: Option<GradientDragHintState> = None;\n\t\t\t\tlet mut transaction_started = false;\n\t\t\t\tfor layer in document.network_interface.selected_nodes().selected_visible_layers(&document.network_interface) {\n\t\t\t\t\tlet Some(gradient) = get_gradient(layer, &document.network_interface) else { continue };\n\t\t\t\t\tlet transform = gradient_space_transform(layer, document);\n\n\t\t\t\t\t// Check for dragging a midpoint diamond\n\t\t\t\t\tif drag_hint.is_none() {\n\t\t\t\t\t\tlet (start, end) = (transform.transform_point2(gradient.start), transform.transform_point2(gradient.end));\n\t\t\t\t\t\tlet line_length = start.distance(end);\n\t\t\t\t\t\tlet midpoint_tolerance = GRADIENT_MIDPOINT_DIAMOND_RADIUS.powi(2);\n\t\t\t\t\t\tfor i in 0..gradient.stops.position.len().saturating_sub(1) {\n\t\t\t\t\t\t\tlet left = gradient.stops.position[i];\n\t\t\t\t\t\t\tlet right = gradient.stops.position[i + 1];\n\n\t\t\t\t\t\t\tif midpoint_hidden_by_proximity(left, right, line_length) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tlet midpoint_pos = left + gradient.stops.midpoint[i] * (right - left);\n\t\t\t\t\t\t\tlet midpoint_viewport = start.lerp(end, midpoint_pos);\n\n\t\t\t\t\t\t\tif midpoint_viewport.distance_squared(mouse) < midpoint_tolerance {\n\t\t\t\t\t\t\t\tlet resettable = midpoint_is_resettable(gradient.stops.midpoint[i]);\n\t\t\t\t\t\t\t\tdrag_hint = Some(GradientDragHintState::Midpoint { resettable });\n\n\t\t\t\t\t\t\t\ttool_data.selected_gradient = Some(SelectedGradient {\n\t\t\t\t\t\t\t\t\tlayer: Some(layer),\n\t\t\t\t\t\t\t\t\ttransform,\n\t\t\t\t\t\t\t\t\tgradient: gradient.clone(),\n\t\t\t\t\t\t\t\t\tdragging: GradientDragTarget::Midpoint(i),\n\t\t\t\t\t\t\t\t\tinitial_gradient: gradient.clone(),\n\t\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check for dragging the closest stop to the mouse pointer\n\t\t\t\t\tif drag_hint.is_none() {\n\t\t\t\t\t\tlet mut best: Option<(f64, usize)> = None;\n\t\t\t\t\t\tfor (index, stop) in gradient.stops.iter().enumerate() {\n\t\t\t\t\t\t\tlet pos = transform.transform_point2(gradient.start.lerp(gradient.end, stop.position));\n\t\t\t\t\t\t\tlet dist_sq = pos.distance_squared(mouse);\n\t\t\t\t\t\t\tif dist_sq < tolerance && best.as_ref().is_none_or(|&(best_dist, _)| dist_sq < best_dist) {\n\t\t\t\t\t\t\t\tbest = Some((dist_sq, index));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif let Some((_, index)) = best {\n\t\t\t\t\t\t\tlet stop_position = gradient.stops.position[index];\n\t\t\t\t\t\t\t// Stops at position 0 or 1 are locked endpoints: dragging moves the\n\t\t\t\t\t\t\t// gradient line endpoint geometry (start/end) instead of stop position\n\t\t\t\t\t\t\tlet drag_target = if stop_position.abs() < f64::EPSILON * 1000. {\n\t\t\t\t\t\t\t\tGradientDragTarget::Start\n\t\t\t\t\t\t\t} else if (1. - stop_position).abs() < f64::EPSILON * 1000. {\n\t\t\t\t\t\t\t\tGradientDragTarget::End\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tGradientDragTarget::Stop(index)\n\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\tdrag_hint = Some(match drag_target {\n\t\t\t\t\t\t\t\tGradientDragTarget::Start | GradientDragTarget::End => GradientDragHintState::EndStop,\n\t\t\t\t\t\t\t\t_ => GradientDragHintState::Stop,\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\ttool_data.selected_gradient = Some(SelectedGradient {\n\t\t\t\t\t\t\t\tlayer: Some(layer),\n\t\t\t\t\t\t\t\ttransform,\n\t\t\t\t\t\t\t\tgradient: gradient.clone(),\n\t\t\t\t\t\t\t\tdragging: drag_target,\n\t\t\t\t\t\t\t\tinitial_gradient: gradient.clone(),\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check dragging start or end handle\n\t\t\t\t\tif drag_hint.is_none() {\n\t\t\t\t\t\tfor (pos, dragging_target) in [(gradient.start, GradientDragTarget::Start), (gradient.end, GradientDragTarget::End)] {\n\t\t\t\t\t\t\tlet pos = transform.transform_point2(pos);\n\t\t\t\t\t\t\tif pos.distance_squared(mouse) < tolerance {\n\t\t\t\t\t\t\t\tdrag_hint = Some(GradientDragHintState::Endpoint);\n\t\t\t\t\t\t\t\ttool_data.selected_gradient = Some(SelectedGradient {\n\t\t\t\t\t\t\t\t\tlayer: Some(layer),\n\t\t\t\t\t\t\t\t\ttransform,\n\t\t\t\t\t\t\t\t\tgradient: gradient.clone(),\n\t\t\t\t\t\t\t\t\tdragging: dragging_target,\n\t\t\t\t\t\t\t\t\tinitial_gradient: gradient.clone(),\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Insert stop if clicking on line\n\t\t\t\t\tif drag_hint.is_none() {\n\t\t\t\t\t\tlet (start, end) = (transform.transform_point2(gradient.start), transform.transform_point2(gradient.end));\n\t\t\t\t\t\tlet distance = (end - start).angle_to(mouse - start).sin() * (mouse - start).length();\n\t\t\t\t\t\tlet projection = ((end - start).angle_to(mouse - start)).cos() * start.distance(mouse) / start.distance(end);\n\n\t\t\t\t\t\tif distance.abs() < SEGMENT_INSERTION_DISTANCE && (0. ..=1.).contains(&projection) {\n\t\t\t\t\t\t\tlet mut new_gradient = gradient.clone();\n\t\t\t\t\t\t\tif let Some(index) = new_gradient.insert_stop(mouse, transform) {\n\t\t\t\t\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\t\t\t\t\t\t\t\ttransaction_started = true;\n\n\t\t\t\t\t\t\t\tlet mut selected_gradient = SelectedGradient::new(new_gradient, layer, document);\n\t\t\t\t\t\t\t\tselected_gradient.dragging = GradientDragTarget::Stop(index);\n\t\t\t\t\t\t\t\t// No offset when inserting a new stop, it should be exactly under the mouse\n\t\t\t\t\t\t\t\tselected_gradient.render_gradient(responses);\n\t\t\t\t\t\t\t\ttool_data.selected_gradient = Some(selected_gradient);\n\t\t\t\t\t\t\t\tdrag_hint = Some(GradientDragHintState::Stop);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Initialize `gradient_angle` from the existing gradient so Ctrl (lock angle) works from the first mouse move\n\t\t\t\tif let Some(selected_gradient) = &tool_data.selected_gradient {\n\t\t\t\t\tlet (vp_start, vp_end) = (\n\t\t\t\t\t\tselected_gradient.transform.transform_point2(selected_gradient.gradient.start),\n\t\t\t\t\t\tselected_gradient.transform.transform_point2(selected_gradient.gradient.end),\n\t\t\t\t\t);\n\t\t\t\t\tlet delta = match selected_gradient.dragging {\n\t\t\t\t\t\t// When dragging End, the fixed point is start and the mouse begins at end\n\t\t\t\t\t\tGradientDragTarget::End => vp_start - vp_end,\n\t\t\t\t\t\t// When dragging Start, the fixed point is end and the mouse begins at start\n\t\t\t\t\t\tGradientDragTarget::Start => vp_end - vp_start,\n\t\t\t\t\t\t_ => vp_start - vp_end,\n\t\t\t\t\t};\n\t\t\t\t\ttool_data.gradient_angle = -delta.angle_to(DVec2::X);\n\t\t\t\t}\n\n\t\t\t\tlet gradient_state = if let Some(hint) = drag_hint {\n\t\t\t\t\tGradientToolFsmState::Drawing { drag_hint: hint }\n\t\t\t\t} else {\n\t\t\t\t\tlet document_mouse = document.metadata().document_to_viewport.inverse().transform_point2(mouse);\n\t\t\t\t\tlet selected_layer = document.click_based_on_position(document_mouse);\n\n\t\t\t\t\t// Apply the gradient to the selected layer\n\t\t\t\t\tif let Some(layer) = selected_layer {\n\t\t\t\t\t\t// Add check for raster layer\n\t\t\t\t\t\tif NodeGraphLayer::is_raster_layer(layer, &mut document.network_interface) {\n\t\t\t\t\t\t\treturn GradientToolFsmState::Ready {\n\t\t\t\t\t\t\t\thovering: GradientHoverTarget::None,\n\t\t\t\t\t\t\t\tselected: GradientSelectedTarget::None,\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif !document.network_interface.selected_nodes().selected_layers_contains(layer, document.metadata()) {\n\t\t\t\t\t\t\tlet nodes = vec![layer.to_node()];\n\n\t\t\t\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes });\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Use the already existing gradient if it exists\n\t\t\t\t\t\tlet gradient = if let Some(gradient) = get_gradient(layer, &document.network_interface) {\n\t\t\t\t\t\t\tgradient.clone()\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Generate a new gradient\n\t\t\t\t\t\t\tGradient::new(DVec2::ZERO, global_tool_data.secondary_color, DVec2::ONE, global_tool_data.primary_color, tool_options.gradient_type)\n\t\t\t\t\t\t};\n\t\t\t\t\t\tlet mut selected_gradient = SelectedGradient::new(gradient, layer, document);\n\t\t\t\t\t\tselected_gradient.dragging = GradientDragTarget::New;\n\n\t\t\t\t\t\ttool_data.selected_gradient = Some(selected_gradient);\n\n\t\t\t\t\t\tGradientToolFsmState::Drawing {\n\t\t\t\t\t\t\tdrag_hint: GradientDragHintState::NewGradient,\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tGradientToolFsmState::Ready {\n\t\t\t\t\t\t\thovering: GradientHoverTarget::None,\n\t\t\t\t\t\t\tselected: GradientSelectedTarget::None,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tif matches!(gradient_state, GradientToolFsmState::Drawing { .. }) && !transaction_started {\n\t\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\t\t\t\t}\n\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\n\t\t\t\tgradient_state\n\t\t\t}\n\t\t\t(GradientToolFsmState::Drawing { drag_hint }, GradientToolMessage::PointerMove { constrain_axis, lock_angle }) => {\n\t\t\t\tif let Some(selected_gradient) = &mut tool_data.selected_gradient {\n\t\t\t\t\tlet mouse = input.mouse.position;\n\t\t\t\t\tlet snap_data = SnapData::new(document, input, viewport);\n\n\t\t\t\t\t// Recompute the gradient-to-viewport transform fresh each frame so zoom/pan mid-drag works correctly\n\t\t\t\t\tif let Some(layer) = selected_gradient.layer {\n\t\t\t\t\t\tselected_gradient.transform = gradient_space_transform(layer, document);\n\t\t\t\t\t\tselected_gradient.transform.translation += tool_data.auto_pan_shift;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Convert drag_start from document space to effective viewport space\n\t\t\t\t\tlet document_to_viewport = document.metadata().document_to_viewport;\n\t\t\t\t\tlet drag_start_viewport = document_to_viewport.transform_point2(tool_data.drag_start) + tool_data.auto_pan_shift;\n\t\t\t\t\ttool_data.auto_pan_shift = DVec2::ZERO;\n\n\t\t\t\t\tselected_gradient.update_gradient(\n\t\t\t\t\t\tmouse,\n\t\t\t\t\t\tresponses,\n\t\t\t\t\t\tinput.keyboard.get(constrain_axis as usize),\n\t\t\t\t\t\tinput.keyboard.get(lock_angle as usize),\n\t\t\t\t\t\tselected_gradient.gradient.gradient_type,\n\t\t\t\t\t\tdrag_start_viewport,\n\t\t\t\t\t\tsnap_data,\n\t\t\t\t\t\t&mut tool_data.snap_manager,\n\t\t\t\t\t\t&mut tool_data.gradient_angle,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// Auto-panning\n\t\t\t\tlet messages = [\n\t\t\t\t\tGradientToolMessage::PointerOutsideViewport { constrain_axis, lock_angle }.into(),\n\t\t\t\t\tGradientToolMessage::PointerMove { constrain_axis, lock_angle }.into(),\n\t\t\t\t];\n\t\t\t\ttool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses);\n\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\n\t\t\t\tGradientToolFsmState::Drawing { drag_hint }\n\t\t\t}\n\t\t\t(GradientToolFsmState::Drawing { drag_hint }, GradientToolMessage::PointerOutsideViewport { .. }) => {\n\t\t\t\t// Auto-panning\n\t\t\t\tif let Some(shift) = tool_data.auto_panning.shift_viewport(input, viewport, responses) {\n\t\t\t\t\ttool_data.auto_pan_shift += shift;\n\t\t\t\t}\n\n\t\t\t\tGradientToolFsmState::Drawing { drag_hint }\n\t\t\t}\n\t\t\t(state, GradientToolMessage::PointerOutsideViewport { constrain_axis, lock_angle }) => {\n\t\t\t\t// Auto-panning\n\t\t\t\tlet messages = [\n\t\t\t\t\tGradientToolMessage::PointerOutsideViewport { constrain_axis, lock_angle }.into(),\n\t\t\t\t\tGradientToolMessage::PointerMove { constrain_axis, lock_angle }.into(),\n\t\t\t\t];\n\t\t\t\ttool_data.auto_panning.stop(&messages, responses);\n\n\t\t\t\tstate\n\t\t\t}\n\t\t\t(GradientToolFsmState::Drawing { .. }, GradientToolMessage::PointerUp) => {\n\t\t\t\tresponses.add(DocumentMessage::EndTransaction);\n\t\t\t\ttool_data.snap_manager.cleanup(responses);\n\n\t\t\t\t// Clear the selection if we were dragging an endpoint of the gradient which isn't a stop\n\t\t\t\tif tool_data.selected_gradient.as_ref().is_some_and(|s| match s.dragging {\n\t\t\t\t\tGradientDragTarget::Start => !s.gradient.stops.position.first().is_some_and(|&p| p.abs() < f64::EPSILON * 1000.),\n\t\t\t\t\tGradientDragTarget::End => !s.gradient.stops.position.last().is_some_and(|&p| (1. - p).abs() < f64::EPSILON * 1000.),\n\t\t\t\t\t_ => false,\n\t\t\t\t}) {\n\t\t\t\t\ttool_data.selected_gradient = None;\n\t\t\t\t}\n\n\t\t\t\tlet selected = compute_selected_target(tool_data);\n\t\t\t\tGradientToolFsmState::Ready {\n\t\t\t\t\thovering: GradientHoverTarget::None,\n\t\t\t\t\tselected,\n\t\t\t\t}\n\t\t\t}\n\t\t\t(GradientToolFsmState::Ready { .. }, GradientToolMessage::PointerMove { .. }) => {\n\t\t\t\tlet mouse = input.mouse.position;\n\t\t\t\tlet hovering = detect_hover_target(mouse, document);\n\t\t\t\tlet selected = compute_selected_target(tool_data);\n\n\t\t\t\tlet snap_data = SnapData::new(document, input, viewport);\n\t\t\t\ttool_data.snap_manager.preview_draw_gradient(&snap_data, mouse);\n\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\tGradientToolFsmState::Ready { hovering, selected }\n\t\t\t}\n\n\t\t\t(GradientToolFsmState::Drawing { .. }, GradientToolMessage::Abort) => {\n\t\t\t\tresponses.add(DocumentMessage::AbortTransaction);\n\t\t\t\ttool_data.snap_manager.cleanup(responses);\n\t\t\t\ttool_data.selected_gradient = None;\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\n\t\t\t\tdismiss_color_stop_color_picker(tool_data, responses);\n\n\t\t\t\tGradientToolFsmState::Ready {\n\t\t\t\t\thovering: GradientHoverTarget::None,\n\t\t\t\t\tselected: GradientSelectedTarget::None,\n\t\t\t\t}\n\t\t\t}\n\t\t\t(_, GradientToolMessage::Abort) => {\n\t\t\t\tdismiss_color_stop_color_picker(tool_data, responses);\n\n\t\t\t\tGradientToolFsmState::Ready {\n\t\t\t\t\thovering: GradientHoverTarget::None,\n\t\t\t\t\tselected: GradientSelectedTarget::None,\n\t\t\t\t}\n\t\t\t}\n\t\t\t_ => self,\n\t\t}\n\t}\n\n\tfn update_hints(&self, responses: &mut VecDeque<Message>) {\n\t\tlet hint_data = match self {\n\t\t\tGradientToolFsmState::Ready { hovering, selected } => {\n\t\t\t\tlet mut groups = Vec::new();\n\n\t\t\t\t// Primary hints based on hover target\n\t\t\t\tmatch hovering {\n\t\t\t\t\tGradientHoverTarget::None => {\n\t\t\t\t\t\tgroups.push(HintGroup(vec![\n\t\t\t\t\t\t\tHintInfo::mouse(MouseMotion::LmbDrag, \"Draw Gradient\"),\n\t\t\t\t\t\t\tHintInfo::keys([Key::Shift], \"15° Increments\").prepend_plus(),\n\t\t\t\t\t\t\tHintInfo::keys([Key::Control], \"Lock Angle\").prepend_plus(),\n\t\t\t\t\t\t]));\n\t\t\t\t\t}\n\t\t\t\t\tGradientHoverTarget::InsertionPoint => {\n\t\t\t\t\t\tgroups.push(HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, \"Insert Color Stop\")]));\n\t\t\t\t\t}\n\t\t\t\t\tGradientHoverTarget::Stop => {\n\t\t\t\t\t\tgroups.push(HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, \"Move Color Stop\")]));\n\t\t\t\t\t}\n\t\t\t\t\tGradientHoverTarget::Endpoint => {\n\t\t\t\t\t\tgroups.push(HintGroup(vec![\n\t\t\t\t\t\t\tHintInfo::mouse(MouseMotion::LmbDrag, \"Move Gradient End\"),\n\t\t\t\t\t\t\tHintInfo::keys([Key::Shift], \"15° Increments\").prepend_plus(),\n\t\t\t\t\t\t\tHintInfo::keys([Key::Control], \"Lock Angle\").prepend_plus(),\n\t\t\t\t\t\t]));\n\t\t\t\t\t}\n\t\t\t\t\tGradientHoverTarget::Midpoint { resettable } => {\n\t\t\t\t\t\tgroups.push(HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, \"Move Midpoint\")]));\n\t\t\t\t\t\tif *resettable {\n\t\t\t\t\t\t\tgroups.push(HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDouble, \"Reset Midpoint\")]));\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Delete/reset hint based on selection\n\t\t\t\tmatch selected {\n\t\t\t\t\tGradientSelectedTarget::Stop => {\n\t\t\t\t\t\tgroups.push(HintGroup(vec![HintInfo::keys([Key::Backspace], \"Delete Color Stop\")]));\n\t\t\t\t\t}\n\t\t\t\t\tGradientSelectedTarget::Midpoint { resettable: true } => {\n\t\t\t\t\t\tgroups.push(HintGroup(vec![HintInfo::keys([Key::Backspace], \"Reset Midpoint\")]));\n\t\t\t\t\t}\n\t\t\t\t\t_ => {}\n\t\t\t\t}\n\n\t\t\t\tHintData(groups)\n\t\t\t}\n\t\t\tGradientToolFsmState::Drawing { drag_hint } => {\n\t\t\t\tlet mut groups = Vec::new();\n\n\t\t\t\t// Abort hints\n\t\t\t\tgroups.push(HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"\"), HintInfo::keys([Key::Escape], \"Cancel\").prepend_slash()]));\n\n\t\t\t\t// Angle constraint hint (only for endpoint/end color stop/new gradient dragging)\n\t\t\t\tif matches!(drag_hint, GradientDragHintState::NewGradient | GradientDragHintState::Endpoint | GradientDragHintState::EndStop) {\n\t\t\t\t\tgroups.push(HintGroup(vec![HintInfo::keys([Key::Shift], \"15° Increments\"), HintInfo::keys([Key::Control], \"Lock Angle\")]));\n\t\t\t\t}\n\n\t\t\t\t// Delete/reset hint while dragging\n\t\t\t\tmatch drag_hint {\n\t\t\t\t\tGradientDragHintState::EndStop | GradientDragHintState::Stop => {\n\t\t\t\t\t\tgroups.push(HintGroup(vec![HintInfo::keys([Key::Backspace], \"Delete Color Stop\")]));\n\t\t\t\t\t}\n\t\t\t\t\tGradientDragHintState::Midpoint { resettable: true } => {\n\t\t\t\t\t\tgroups.push(HintGroup(vec![HintInfo::keys([Key::Backspace], \"Reset Midpoint\")]));\n\t\t\t\t\t}\n\t\t\t\t\t_ => {}\n\t\t\t\t}\n\n\t\t\t\tHintData(groups)\n\t\t\t}\n\t\t};\n\n\t\thint_data.send_layout(responses);\n\t}\n\n\tfn update_cursor(&self, responses: &mut VecDeque<Message>) {\n\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });\n\t}\n}\n\nfn dismiss_color_stop_color_picker(tool_data: &mut GradientToolData, responses: &mut VecDeque<Message>) {\n\tif tool_data.color_picker_editing_color_stop.is_some() {\n\t\tif tool_data.color_picker_transaction_open {\n\t\t\tresponses.add(DocumentMessage::EndTransaction);\n\t\t\ttool_data.color_picker_transaction_open = false;\n\t\t}\n\t\ttool_data.color_picker_editing_color_stop = None;\n\t}\n}\n\nfn detect_hover_target(mouse: DVec2, document: &DocumentMessageHandler) -> GradientHoverTarget {\n\tlet stop_tolerance = (MANIPULATOR_GROUP_MARKER_SIZE * 2.).powi(2);\n\tlet midpoint_tolerance = GRADIENT_MIDPOINT_DIAMOND_RADIUS.powi(2);\n\n\tfor layer in document.network_interface.selected_nodes().selected_visible_layers(&document.network_interface) {\n\t\tlet Some(gradient) = get_gradient(layer, &document.network_interface) else { continue };\n\t\tlet transform = gradient_space_transform(layer, document);\n\t\tlet (start, end) = (transform.transform_point2(gradient.start), transform.transform_point2(gradient.end));\n\t\tlet line_length = start.distance(end);\n\n\t\t// Check midpoint diamonds first (smaller hit area, higher priority)\n\t\tfor i in 0..gradient.stops.position.len().saturating_sub(1) {\n\t\t\tlet left = gradient.stops.position[i];\n\t\t\tlet right = gradient.stops.position[i + 1];\n\t\t\tif midpoint_hidden_by_proximity(left, right, line_length) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet midpoint_position = left + gradient.stops.midpoint[i] * (right - left);\n\t\t\tlet midpoint_viewport = start.lerp(end, midpoint_position);\n\n\t\t\tif midpoint_viewport.distance_squared(mouse) < midpoint_tolerance {\n\t\t\t\tlet resettable = midpoint_is_resettable(gradient.stops.midpoint[i]);\n\t\t\t\treturn GradientHoverTarget::Midpoint { resettable };\n\t\t\t}\n\t\t}\n\n\t\t// Check stops\n\t\tfor stop in gradient.stops.iter() {\n\t\t\tlet pos = transform.transform_point2(gradient.start.lerp(gradient.end, stop.position));\n\t\t\tif pos.distance_squared(mouse) < stop_tolerance {\n\t\t\t\treturn if stop.position.abs() < f64::EPSILON * 1000. || (1. - stop.position).abs() < f64::EPSILON * 1000. {\n\t\t\t\t\tGradientHoverTarget::Endpoint\n\t\t\t\t} else {\n\t\t\t\t\tGradientHoverTarget::Stop\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\t// Check start/end handles (pure endpoints without stops)\n\t\tfor endpoint_position in [gradient.start, gradient.end] {\n\t\t\tlet endpoint_position = transform.transform_point2(endpoint_position);\n\t\t\tif endpoint_position.distance_squared(mouse) < stop_tolerance {\n\t\t\t\treturn GradientHoverTarget::Endpoint;\n\t\t\t}\n\t\t}\n\n\t\t// Check insertion point on line\n\t\tif calculate_insertion(start, end, &gradient.stops, mouse).is_some() {\n\t\t\treturn GradientHoverTarget::InsertionPoint;\n\t\t}\n\t}\n\n\tGradientHoverTarget::None\n}\n\nfn compute_selected_target(tool_data: &GradientToolData) -> GradientSelectedTarget {\n\tlet Some(selected_gradient) = &tool_data.selected_gradient else {\n\t\treturn GradientSelectedTarget::None;\n\t};\n\n\tmatch selected_gradient.dragging {\n\t\tGradientDragTarget::Stop(_) | GradientDragTarget::Start | GradientDragTarget::End => GradientSelectedTarget::Stop,\n\t\tGradientDragTarget::Midpoint(i) => {\n\t\t\tlet resettable = selected_gradient.gradient.stops.midpoint.get(i).is_some_and(|&midpoint_value| midpoint_is_resettable(midpoint_value));\n\t\t\tGradientSelectedTarget::Midpoint { resettable }\n\t\t}\n\t\tGradientDragTarget::New => GradientSelectedTarget::None,\n\t}\n}\n\nfn apply_gradient_update(\n\tdata: &mut GradientToolData,\n\tcontext: &mut ToolActionMessageContext,\n\tresponses: &mut VecDeque<Message>,\n\tcondition: impl Fn(&Gradient) -> bool,\n\tupdate: impl Fn(&mut Gradient),\n) {\n\tlet selected_layers: Vec<_> = context\n\t\t.document\n\t\t.network_interface\n\t\t.selected_nodes()\n\t\t.selected_visible_layers(&context.document.network_interface)\n\t\t.collect();\n\n\tlet mut transaction_started = false;\n\tfor layer in selected_layers {\n\t\tif NodeGraphLayer::is_raster_layer(layer, &mut context.document.network_interface) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif let Some(mut gradient) = get_gradient(layer, &context.document.network_interface)\n\t\t\t&& condition(&gradient)\n\t\t{\n\t\t\tif !transaction_started {\n\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\t\t\t\ttransaction_started = true;\n\t\t\t}\n\t\t\tupdate(&mut gradient);\n\t\t\tresponses.add(GraphOperationMessage::FillSet {\n\t\t\t\tlayer,\n\t\t\t\tfill: Fill::Gradient(gradient),\n\t\t\t});\n\t\t}\n\t}\n\n\tif transaction_started {\n\t\tresponses.add(DocumentMessage::EndTransaction);\n\t}\n\tif let Some(selected_gradient) = &mut data.selected_gradient\n\t\t&& let Some(layer) = selected_gradient.layer\n\t\t&& !NodeGraphLayer::is_raster_layer(layer, &mut context.document.network_interface)\n\t{\n\t\tupdate(&mut selected_gradient.gradient);\n\t}\n\tresponses.add(PropertiesPanelMessage::Refresh);\n\tdata.has_selected_gradient = has_gradient_on_selected_layers(context.document);\n\tresponses.add(ToolMessage::RefreshToolOptions);\n}\n\nfn has_gradient_on_selected_layers(document: &DocumentMessageHandler) -> bool {\n\tdocument\n\t\t.network_interface\n\t\t.selected_nodes()\n\t\t.selected_visible_layers(&document.network_interface)\n\t\t.any(|layer| get_gradient(layer, &document.network_interface).is_some())\n}\n\n#[inline(always)]\nfn midpoint_is_resettable(value: f64) -> bool {\n\t(value - 0.5).abs() >= f64::EPSILON * 1000.\n}\n\n#[derive(Clone, Copy, PartialEq, Eq)]\nenum StopId {\n\tStart,\n\tEnd,\n\tMiddle(usize),\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]\nenum GradientHoverTarget {\n\t#[default]\n\tNone,\n\tInsertionPoint,\n\tStop,\n\tEndpoint,\n\tMidpoint {\n\t\tresettable: bool,\n\t},\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]\nenum GradientSelectedTarget {\n\t#[default]\n\tNone,\n\tStop,\n\tMidpoint {\n\t\tresettable: bool,\n\t},\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]\nenum GradientDragHintState {\n\t#[default]\n\tNewGradient,\n\tEndpoint,\n\tEndStop,\n\tStop,\n\tMidpoint {\n\t\tresettable: bool,\n\t},\n}\n\n#[cfg(test)]\nmod test_gradient {\n\tuse crate::messages::input_mapper::utility_types::input_mouse::EditorMouseState;\n\tuse crate::messages::input_mapper::utility_types::input_mouse::ScrollDelta;\n\tuse crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;\n\tuse crate::messages::portfolio::document::utility_types::misc::GroupFolderType;\n\tpub use crate::test_utils::test_prelude::*;\n\tuse glam::DAffine2;\n\tuse graphene_std::vector::fill;\n\tuse graphene_std::vector::style::Fill;\n\tuse graphene_std::vector::style::Gradient;\n\n\tuse super::gradient_space_transform;\n\n\tasync fn get_fills(editor: &mut EditorTestUtils) -> Vec<(Fill, DAffine2)> {\n\t\tlet instrumented = match editor.eval_graph().await {\n\t\t\tOk(instrumented) => instrumented,\n\t\t\tErr(e) => panic!(\"Failed to evaluate graph: {e}\"),\n\t\t};\n\n\t\tlet document = editor.active_document();\n\t\tlet layers = document.metadata().all_layers();\n\t\tlayers\n\t\t\t.filter_map(|layer| {\n\t\t\t\tlet fill = instrumented.grab_input_from_layer::<fill::FillInput<Fill>>(layer, &document.network_interface, &editor.runtime)?;\n\t\t\t\tlet transform = gradient_space_transform(layer, document);\n\t\t\t\tSome((fill, transform))\n\t\t\t})\n\t\t\t.collect()\n\t}\n\n\tasync fn get_gradient(editor: &mut EditorTestUtils) -> (Gradient, DAffine2) {\n\t\tlet fills = get_fills(editor).await;\n\t\tassert_eq!(fills.len(), 1, \"Expected 1 gradient fill, found {}\", fills.len());\n\n\t\tlet (fill, transform) = fills.first().unwrap();\n\t\tlet gradient = fill.as_gradient().expect(\"Expected gradient fill type\");\n\n\t\t(gradient.clone(), *transform)\n\t}\n\n\tfn assert_stops_at_positions(actual_positions: &[f64], expected_positions: &[f64], tolerance: f64) {\n\t\tassert_eq!(\n\t\t\tactual_positions.len(),\n\t\t\texpected_positions.len(),\n\t\t\t\"Expected {} stops, found {}\",\n\t\t\texpected_positions.len(),\n\t\t\tactual_positions.len()\n\t\t);\n\n\t\tfor (i, (actual, expected)) in actual_positions.iter().zip(expected_positions.iter()).enumerate() {\n\t\t\tassert!((actual - expected).abs() < tolerance, \"Stop {i}: Expected position near {expected}, got {actual}\");\n\t\t}\n\t}\n\n\t#[tokio::test]\n\tasync fn ignore_artboard() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor.drag_tool(ToolType::Artboard, 0., 0., 100., 100., ModifierKeys::empty()).await;\n\t\teditor.drag_tool(ToolType::Gradient, 2., 2., 4., 4., ModifierKeys::empty()).await;\n\t\tassert!(get_fills(&mut editor).await.is_empty());\n\t}\n\n\t#[tokio::test]\n\tasync fn ignore_raster() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor.create_raster_image(Image::new(100, 100, Color::WHITE), Some((0., 0.))).await;\n\t\teditor.drag_tool(ToolType::Gradient, 2., 2., 4., 4., ModifierKeys::empty()).await;\n\t\tassert!(get_fills(&mut editor).await.is_empty());\n\t}\n\n\t#[tokio::test]\n\tasync fn simple_draw() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor.drag_tool(ToolType::Rectangle, -5., -3., 100., 100., ModifierKeys::empty()).await;\n\t\teditor.select_primary_color(Color::GREEN).await;\n\t\teditor.select_secondary_color(Color::BLUE).await;\n\t\teditor.drag_tool(ToolType::Gradient, 2., 3., 24., 4., ModifierKeys::empty()).await;\n\n\t\tlet (gradient, transform) = get_gradient(&mut editor).await;\n\n\t\t// Gradient goes from secondary color to primary color\n\t\tlet stops = gradient.stops.iter().map(|stop| (stop.position, stop.color.to_rgba8_srgb())).collect::<Vec<_>>();\n\t\tassert_eq!(stops, vec![(0., Color::BLUE.to_rgba8_srgb()), (1., Color::GREEN.to_rgba8_srgb())]);\n\t\tassert!(transform.transform_point2(gradient.start).abs_diff_eq(DVec2::new(2., 3.), 1e-10));\n\t\tassert!(transform.transform_point2(gradient.end).abs_diff_eq(DVec2::new(24., 4.), 1e-10));\n\t}\n\n\t#[tokio::test]\n\tasync fn snap_simple_draw() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor\n\t\t\t.handle_message(NavigationMessage::CanvasTiltSet {\n\t\t\t\tangle_radians: f64::consts::FRAC_PI_8,\n\t\t\t})\n\t\t\t.await;\n\t\tlet start = DVec2::new(0., 0.);\n\t\tlet end = DVec2::new(24., 4.);\n\t\teditor.drag_tool(ToolType::Rectangle, -5., -3., 100., 100., ModifierKeys::empty()).await;\n\t\teditor.drag_tool(ToolType::Gradient, start.x, start.y, end.x, end.y, ModifierKeys::SHIFT).await;\n\n\t\tlet (gradient, transform) = get_gradient(&mut editor).await;\n\n\t\tassert!(transform.transform_point2(gradient.start).abs_diff_eq(start, 1e-10));\n\n\t\t// 15 degrees from horizontal\n\t\tlet angle = f64::to_radians(15.);\n\t\tlet direction = DVec2::new(angle.cos(), angle.sin());\n\t\tlet expected = start + direction * (end - start).length();\n\t\tassert!(transform.transform_point2(gradient.end).abs_diff_eq(expected, 1e-10));\n\t}\n\n\t#[tokio::test]\n\tasync fn transformed_draw() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor\n\t\t\t.handle_message(NavigationMessage::CanvasTiltSet {\n\t\t\t\tangle_radians: f64::consts::FRAC_PI_8,\n\t\t\t})\n\t\t\t.await;\n\t\teditor.drag_tool(ToolType::Rectangle, -5., -3., 100., 100., ModifierKeys::empty()).await;\n\n\t\t// Group rectangle\n\t\tlet group_folder_type = GroupFolderType::Layer;\n\t\teditor.handle_message(DocumentMessage::GroupSelectedLayers { group_folder_type }).await;\n\t\tlet metadata = editor.active_document().metadata();\n\t\tlet mut layers = metadata.all_layers();\n\t\tlet folder = layers.next().unwrap();\n\t\tlet rectangle = layers.next().unwrap();\n\t\tassert_eq!(rectangle.parent(metadata), Some(folder));\n\n\t\t// Transform the group\n\t\teditor\n\t\t\t.handle_message(GraphOperationMessage::TransformSet {\n\t\t\t\tlayer: folder,\n\t\t\t\ttransform: DAffine2::from_scale_angle_translation(DVec2::new(1., 2.), 0., -DVec2::X * 10.),\n\t\t\t\ttransform_in: TransformIn::Local,\n\t\t\t\tskip_rerender: false,\n\t\t\t})\n\t\t\t.await;\n\n\t\teditor.drag_tool(ToolType::Gradient, 2., 3., 24., 4., ModifierKeys::empty()).await;\n\n\t\tlet (gradient, transform) = get_gradient(&mut editor).await;\n\n\t\tassert!(transform.transform_point2(gradient.start).abs_diff_eq(DVec2::new(2., 3.), 1e-10));\n\t\tassert!(transform.transform_point2(gradient.end).abs_diff_eq(DVec2::new(24., 4.), 1e-10));\n\t}\n\n\t#[tokio::test]\n\tasync fn click_to_insert_stop() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\n\t\teditor.drag_tool(ToolType::Rectangle, -5., -3., 100., 100., ModifierKeys::empty()).await;\n\t\teditor.select_primary_color(Color::GREEN).await;\n\t\teditor.select_secondary_color(Color::BLUE).await;\n\t\teditor.drag_tool(ToolType::Gradient, 0., 0., 100., 0., ModifierKeys::empty()).await;\n\n\t\t// Get initial gradient state (should have 2 stops)\n\t\tlet (initial_gradient, _) = get_gradient(&mut editor).await;\n\t\tassert_eq!(initial_gradient.stops.len(), 2, \"Expected 2 stops, found {}\", initial_gradient.stops.len());\n\n\t\teditor.select_tool(ToolType::Gradient).await;\n\t\teditor.move_mouse(25., 0., ModifierKeys::empty(), MouseKeys::empty()).await;\n\t\teditor.left_mousedown(25., 0., ModifierKeys::empty()).await;\n\t\teditor.left_mouseup(25., 0., ModifierKeys::empty()).await;\n\n\t\t// Check that a new stop has been added\n\t\tlet (updated_gradient, _) = get_gradient(&mut editor).await;\n\t\tassert_eq!(updated_gradient.stops.len(), 3, \"Expected 3 stops, found {}\", updated_gradient.stops.len());\n\n\t\tlet positions: Vec<f64> = updated_gradient.stops.iter().map(|stop| stop.position).collect();\n\t\tassert!(\n\t\t\tpositions.iter().any(|pos| (pos - 0.25).abs() < 0.1),\n\t\t\t\"Expected to find a stop near position 0.25, but found: {positions:?}\"\n\t\t);\n\t}\n\n\t#[tokio::test]\n\tasync fn dragging_endpoint_sets_correct_point() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\n\t\teditor.handle_message(NavigationMessage::CanvasZoomSet { zoom_factor: 2. }).await;\n\n\t\teditor.drag_tool(ToolType::Rectangle, -5., -3., 100., 100., ModifierKeys::empty()).await;\n\n\t\tlet document = editor.active_document();\n\t\tlet selected_layer = document.network_interface.selected_nodes().selected_layers(document.metadata()).next().unwrap();\n\t\teditor\n\t\t\t.handle_message(GraphOperationMessage::TransformSet {\n\t\t\t\tlayer: selected_layer,\n\t\t\t\ttransform: DAffine2::from_scale_angle_translation(DVec2::new(1.5, 0.8), 0.3, DVec2::new(10., -5.)),\n\t\t\t\ttransform_in: TransformIn::Local,\n\t\t\t\tskip_rerender: false,\n\t\t\t})\n\t\t\t.await;\n\n\t\teditor.select_primary_color(Color::GREEN).await;\n\t\teditor.select_secondary_color(Color::BLUE).await;\n\n\t\teditor.drag_tool(ToolType::Gradient, 0., 0., 100., 0., ModifierKeys::empty()).await;\n\n\t\t// Get the initial gradient state\n\t\tlet (initial_gradient, transform) = get_gradient(&mut editor).await;\n\t\tassert_eq!(initial_gradient.stops.len(), 2, \"Expected 2 stops, found {}\", initial_gradient.stops.len());\n\n\t\t// Verify initial gradient endpoints in viewport space\n\t\tlet initial_start = transform.transform_point2(initial_gradient.start);\n\t\tlet initial_end = transform.transform_point2(initial_gradient.end);\n\t\tassert!(initial_start.abs_diff_eq(DVec2::new(0., 0.), 1e-10));\n\t\tassert!(initial_end.abs_diff_eq(DVec2::new(100., 0.), 1e-10));\n\n\t\teditor.select_tool(ToolType::Gradient).await;\n\n\t\t// Simulate dragging the end point to a new position (100, 50)\n\t\tlet start_pos = DVec2::new(100., 0.);\n\t\tlet end_pos = DVec2::new(100., 50.);\n\n\t\teditor.move_mouse(start_pos.x, start_pos.y, ModifierKeys::empty(), MouseKeys::empty()).await;\n\t\teditor.left_mousedown(start_pos.x, start_pos.y, ModifierKeys::empty()).await;\n\t\teditor.move_mouse(end_pos.x, end_pos.y, ModifierKeys::empty(), MouseKeys::LEFT).await;\n\t\teditor\n\t\t\t.mouseup(\n\t\t\t\tEditorMouseState {\n\t\t\t\t\teditor_position: end_pos,\n\t\t\t\t\tmouse_keys: MouseKeys::empty(),\n\t\t\t\t\tscroll_delta: ScrollDelta::default(),\n\t\t\t\t},\n\t\t\t\tModifierKeys::empty(),\n\t\t\t)\n\t\t\t.await;\n\n\t\t// Check the updated gradient\n\t\tlet (updated_gradient, transform) = get_gradient(&mut editor).await;\n\n\t\t// Verify the start point hasn't changed\n\t\tlet updated_start = transform.transform_point2(updated_gradient.start);\n\t\tassert!(updated_start.abs_diff_eq(DVec2::new(0., 0.), 1e-10));\n\n\t\t// Verify the end point has been updated to the new position\n\t\tlet updated_end = transform.transform_point2(updated_gradient.end);\n\t\tassert!(updated_end.abs_diff_eq(DVec2::new(100., 50.), 1e-10), \"Expected end point at (100, 50), got {updated_end:?}\");\n\t}\n\n\t#[tokio::test]\n\tasync fn dragging_stop_reorders_gradient() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\n\t\teditor.drag_tool(ToolType::Rectangle, -5., -3., 100., 100., ModifierKeys::empty()).await;\n\t\teditor.select_primary_color(Color::GREEN).await;\n\t\teditor.select_secondary_color(Color::BLUE).await;\n\t\teditor.drag_tool(ToolType::Gradient, 0., 0., 100., 0., ModifierKeys::empty()).await;\n\n\t\teditor.select_tool(ToolType::Gradient).await;\n\n\t\t// Add a middle stop at 25%\n\t\teditor.move_mouse(25., 0., ModifierKeys::empty(), MouseKeys::empty()).await;\n\t\teditor.left_mousedown(25., 0., ModifierKeys::empty()).await;\n\t\teditor.left_mouseup(25., 0., ModifierKeys::empty()).await;\n\n\t\tlet (initial_gradient, _) = get_gradient(&mut editor).await;\n\t\tassert_eq!(initial_gradient.stops.len(), 3, \"Expected 3 stops, found {}\", initial_gradient.stops.len());\n\n\t\t// Verify initial stop positions and colors\n\t\tlet mut stops = initial_gradient.stops.clone();\n\t\tstops.sort();\n\n\t\tlet positions: Vec<f64> = stops.iter().map(|stop| stop.position).collect();\n\t\tassert_stops_at_positions(&positions, &[0., 0.25, 1.], 0.1);\n\n\t\tlet middle_color = stops.color[1].to_rgba8_srgb();\n\n\t\t// Simulate dragging the middle stop to position 0.8\n\t\tlet click_position = DVec2::new(25., 0.);\n\t\teditor\n\t\t\t.mousedown(\n\t\t\t\tEditorMouseState {\n\t\t\t\t\teditor_position: click_position,\n\t\t\t\t\tmouse_keys: MouseKeys::LEFT,\n\t\t\t\t\tscroll_delta: ScrollDelta::default(),\n\t\t\t\t},\n\t\t\t\tModifierKeys::empty(),\n\t\t\t)\n\t\t\t.await;\n\n\t\tlet drag_position = DVec2::new(80., 0.);\n\t\teditor.move_mouse(drag_position.x, drag_position.y, ModifierKeys::empty(), MouseKeys::LEFT).await;\n\n\t\teditor\n\t\t\t.mouseup(\n\t\t\t\tEditorMouseState {\n\t\t\t\t\teditor_position: drag_position,\n\t\t\t\t\tmouse_keys: MouseKeys::empty(),\n\t\t\t\t\tscroll_delta: ScrollDelta::default(),\n\t\t\t\t},\n\t\t\t\tModifierKeys::empty(),\n\t\t\t)\n\t\t\t.await;\n\n\t\tlet (updated_gradient, _) = get_gradient(&mut editor).await;\n\t\tassert_eq!(updated_gradient.stops.len(), 3, \"Expected 3 stops after dragging, found {}\", updated_gradient.stops.len());\n\n\t\t// Verify updated stop positions and colors\n\t\tlet mut updated_stops = updated_gradient.stops.clone();\n\t\tupdated_stops.sort();\n\n\t\t// Check positions are now correctly ordered\n\t\tlet updated_positions: Vec<f64> = updated_stops.iter().map(|stop| stop.position).collect();\n\t\tassert_stops_at_positions(&updated_positions, &[0., 0.8, 1.], 0.1);\n\n\t\t// Colors should maintain their associations with the stop points\n\t\tassert_eq!(updated_stops.color[0].to_rgba8_srgb(), Color::BLUE.to_rgba8_srgb());\n\t\tassert_eq!(updated_stops.color[1].to_rgba8_srgb(), middle_color);\n\t\tassert_eq!(updated_stops.color[2].to_rgba8_srgb(), Color::GREEN.to_rgba8_srgb());\n\t}\n\n\t#[tokio::test]\n\tasync fn select_and_delete_removes_stop() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\n\t\teditor.drag_tool(ToolType::Rectangle, -5., -3., 100., 100., ModifierKeys::empty()).await;\n\t\teditor.select_primary_color(Color::GREEN).await;\n\t\teditor.select_secondary_color(Color::BLUE).await;\n\t\teditor.drag_tool(ToolType::Gradient, 0., 0., 100., 0., ModifierKeys::empty()).await;\n\n\t\t// Get initial gradient state (should have 2 stops)\n\t\tlet (initial_gradient, _) = get_gradient(&mut editor).await;\n\t\tassert_eq!(initial_gradient.stops.len(), 2, \"Expected 2 stops, found {}\", initial_gradient.stops.len());\n\n\t\teditor.select_tool(ToolType::Gradient).await;\n\n\t\t// Add two middle stops\n\t\teditor.move_mouse(25., 0., ModifierKeys::empty(), MouseKeys::empty()).await;\n\t\teditor.left_mousedown(25., 0., ModifierKeys::empty()).await;\n\t\teditor.left_mouseup(25., 0., ModifierKeys::empty()).await;\n\n\t\teditor.move_mouse(75., 0., ModifierKeys::empty(), MouseKeys::empty()).await;\n\t\teditor.left_mousedown(75., 0., ModifierKeys::empty()).await;\n\t\teditor.left_mouseup(75., 0., ModifierKeys::empty()).await;\n\n\t\tlet (updated_gradient, _) = get_gradient(&mut editor).await;\n\t\tassert_eq!(updated_gradient.stops.len(), 4, \"Expected 4 stops, found {}\", updated_gradient.stops.len());\n\n\t\tlet positions: Vec<f64> = updated_gradient.stops.iter().map(|stop| stop.position).collect();\n\n\t\t// Use helper function to verify positions\n\t\tassert_stops_at_positions(&positions, &[0., 0.25, 0.75, 1.], 0.05);\n\n\t\t// Select the stop at position 0.75 and delete it\n\t\tlet position2 = DVec2::new(75., 0.);\n\t\teditor.move_mouse(position2.x, position2.y, ModifierKeys::empty(), MouseKeys::empty()).await;\n\t\teditor.left_mousedown(position2.x, position2.y, ModifierKeys::empty()).await;\n\t\teditor\n\t\t\t.mouseup(\n\t\t\t\tEditorMouseState {\n\t\t\t\t\teditor_position: position2,\n\t\t\t\t\tmouse_keys: MouseKeys::empty(),\n\t\t\t\t\tscroll_delta: ScrollDelta::default(),\n\t\t\t\t},\n\t\t\t\tModifierKeys::empty(),\n\t\t\t)\n\t\t\t.await;\n\n\t\teditor.press(Key::Delete, ModifierKeys::empty()).await;\n\n\t\t// Verify we now have 3 stops\n\t\tlet (final_gradient, _) = get_gradient(&mut editor).await;\n\t\tassert_eq!(final_gradient.stops.len(), 3, \"Expected 3 stops after deletion, found {}\", final_gradient.stops.len());\n\n\t\tlet final_positions: Vec<f64> = final_gradient.stops.iter().map(|stop| stop.position).collect();\n\n\t\t// Verify final positions with helper function\n\t\tassert_stops_at_positions(&final_positions, &[0., 0.25, 1.], 0.05);\n\n\t\t// Additional verification that 0.75 stop is gone\n\t\tassert!(!final_positions.iter().any(|pos| (pos - 0.75).abs() < 0.05), \"Stop at position 0.75 should have been deleted\");\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/tool_messages/mod.rs",
    "content": "pub mod artboard_tool;\npub mod brush_tool;\npub mod eyedropper_tool;\npub mod fill_tool;\npub mod freehand_tool;\npub mod gradient_tool;\npub mod navigate_tool;\npub mod path_tool;\npub mod pen_tool;\npub mod select_tool;\npub mod shape_tool;\npub mod spline_tool;\npub mod text_tool;\n\npub mod tool_prelude {\n\tpub use crate::messages::frontend::utility_types::MouseCursorIcon;\n\tpub use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};\n\tpub use crate::messages::layout::utility_types::widget_prelude::*;\n\tpub use crate::messages::prelude::*;\n\tpub use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionMessageContext, ToolMetadata, ToolTransition, ToolType};\n\tpub use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};\n\tpub use glam::{DAffine2, DVec2};\n}\n"
  },
  {
    "path": "editor/src/messages/tool/tool_messages/navigate_tool.rs",
    "content": "use super::tool_prelude::*;\n\n#[derive(Default, ExtractField)]\npub struct NavigateTool {\n\tfsm_state: NavigateToolFsmState,\n\ttool_data: NavigateToolData,\n}\n\n#[impl_message(Message, ToolMessage, Navigate)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)]\npub enum NavigateToolMessage {\n\t// Standard messages\n\tAbort,\n\n\t// Tool-specific messages\n\tPointerUp { zoom_in: bool },\n\tPointerMove { snap: Key },\n\tTiltCanvasBegin,\n\tZoomCanvasBegin,\n\tEnd,\n}\n\nimpl ToolMetadata for NavigateTool {\n\tfn icon_name(&self) -> String {\n\t\t\"GeneralNavigateTool\".into()\n\t}\n\tfn tooltip_label(&self) -> String {\n\t\t\"Navigate Tool\".into()\n\t}\n\tfn tool_type(&self) -> crate::messages::tool::utility_types::ToolType {\n\t\tToolType::Navigate\n\t}\n}\n\nimpl LayoutHolder for NavigateTool {\n\tfn layout(&self) -> Layout {\n\t\tLayout::default()\n\t}\n}\n\n#[message_handler_data]\nimpl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for NavigateTool {\n\tfn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {\n\t\tself.fsm_state.process_event(message, &mut self.tool_data, context, &(), responses, true);\n\t}\n\n\tfn actions(&self) -> ActionList {\n\t\tmatch self.fsm_state {\n\t\t\tNavigateToolFsmState::Ready => actions!(NavigateToolMessageDiscriminant;\n\t\t\t\tTiltCanvasBegin,\n\t\t\t\tZoomCanvasBegin,\n\t\t\t),\n\t\t\tNavigateToolFsmState::Tilting | NavigateToolFsmState::Zooming => actions!(NavigateToolMessageDiscriminant;\n\t\t\t\tPointerMove,\n\t\t\t),\n\t\t\tNavigateToolFsmState::ZoomOrClickZooming => actions!(NavigateToolMessageDiscriminant;\n\t\t\t\tPointerUp,\n\t\t\t\tPointerMove,\n\t\t\t),\n\t\t}\n\t}\n}\n\nimpl ToolTransition for NavigateTool {\n\tfn event_to_message_map(&self) -> EventToMessageMap {\n\t\tEventToMessageMap {\n\t\t\ttool_abort: Some(NavigateToolMessage::Abort.into()),\n\t\t\t..Default::default()\n\t\t}\n\t}\n}\n\n#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]\nenum NavigateToolFsmState {\n\t#[default]\n\tReady,\n\tTilting,\n\tZoomOrClickZooming,\n\tZooming,\n}\n\n#[derive(Clone, Debug, Default)]\nstruct NavigateToolData {\n\tdrag_start: Option<DVec2>,\n}\n\nimpl Fsm for NavigateToolFsmState {\n\ttype ToolData = NavigateToolData;\n\ttype ToolOptions = ();\n\n\tfn transition(\n\t\tself,\n\t\tmessage: ToolMessage,\n\t\ttool_data: &mut Self::ToolData,\n\t\tToolActionMessageContext { input, .. }: &mut ToolActionMessageContext,\n\t\t_tool_options: &Self::ToolOptions,\n\t\tresponses: &mut VecDeque<Message>,\n\t) -> Self {\n\t\tlet ToolMessage::Navigate(navigate) = message else { return self };\n\t\tmatch navigate {\n\t\t\tNavigateToolMessage::PointerUp { zoom_in } => {\n\t\t\t\tif self == NavigateToolFsmState::ZoomOrClickZooming {\n\t\t\t\t\t// Mouse has not moved from pointerdown to pointerup\n\t\t\t\t\tif tool_data.drag_start == Some(input.mouse.position) {\n\t\t\t\t\t\tresponses.add_front(if zoom_in {\n\t\t\t\t\t\t\tNavigationMessage::CanvasZoomIncrease { center_on_mouse: true }\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tNavigationMessage::CanvasZoomDecrease { center_on_mouse: true }\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tresponses.add_front(NavigationMessage::EndCanvasPTZ { abort_transform: false });\n\t\t\t\t}\n\n\t\t\t\ttool_data.drag_start = None;\n\t\t\t\tNavigateToolFsmState::Ready\n\t\t\t}\n\t\t\tNavigateToolMessage::PointerMove { snap } => {\n\t\t\t\tif self == NavigateToolFsmState::ZoomOrClickZooming {\n\t\t\t\t\tresponses.add_front(NavigationMessage::BeginCanvasZoom);\n\t\t\t\t\tNavigateToolFsmState::Zooming\n\t\t\t\t} else {\n\t\t\t\t\tresponses.add_front(NavigationMessage::PointerMove { snap });\n\t\t\t\t\tself\n\t\t\t\t}\n\t\t\t}\n\t\t\tNavigateToolMessage::TiltCanvasBegin => {\n\t\t\t\tresponses.add_front(NavigationMessage::BeginCanvasTilt { was_dispatched_from_menu: false });\n\t\t\t\tNavigateToolFsmState::Tilting\n\t\t\t}\n\t\t\tNavigateToolMessage::ZoomCanvasBegin => {\n\t\t\t\t// Wait to decide between zooming and click zooming based on whether the next event is a PointerMove or PointerUp\n\t\t\t\ttool_data.drag_start = Some(input.mouse.position);\n\t\t\t\tNavigateToolFsmState::ZoomOrClickZooming\n\t\t\t}\n\t\t\tNavigateToolMessage::End => {\n\t\t\t\ttool_data.drag_start = None;\n\t\t\t\tNavigateToolFsmState::Ready\n\t\t\t}\n\t\t\tNavigateToolMessage::Abort => {\n\t\t\t\tresponses.add_front(NavigationMessage::EndCanvasPTZ { abort_transform: false });\n\t\t\t\ttool_data.drag_start = None;\n\t\t\t\tNavigateToolFsmState::Ready\n\t\t\t}\n\t\t}\n\t}\n\n\tfn update_hints(&self, responses: &mut VecDeque<Message>) {\n\t\tlet hint_data = match self {\n\t\t\tNavigateToolFsmState::Ready | NavigateToolFsmState::ZoomOrClickZooming => HintData(vec![\n\t\t\t\tHintGroup(vec![\n\t\t\t\t\tHintInfo::mouse(MouseMotion::MmbDrag, \"\"),\n\t\t\t\t\tHintInfo::keys_and_mouse([Key::Space], MouseMotion::LmbDrag, \"Pan\").prepend_slash(),\n\t\t\t\t]),\n\t\t\t\tHintGroup(vec![HintInfo::keys_and_mouse([Key::Alt], MouseMotion::LmbDrag, \"Tilt\")]),\n\t\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, \"Zoom\"), HintInfo::keys([Key::Shift], \"Increments\").prepend_plus()]),\n\t\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, \"Zoom In\"), HintInfo::keys([Key::Shift], \"Zoom Out\").prepend_plus()]),\n\t\t\t]),\n\t\t\tNavigateToolFsmState::Tilting => HintData(vec![\n\t\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"\"), HintInfo::keys([Key::Escape], \"Cancel\").prepend_slash()]),\n\t\t\t\tHintGroup(vec![HintInfo::keys([Key::Shift], \"15° Increments\")]),\n\t\t\t]),\n\t\t\tNavigateToolFsmState::Zooming => HintData(vec![\n\t\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"\"), HintInfo::keys([Key::Escape], \"Cancel\").prepend_slash()]),\n\t\t\t\tHintGroup(vec![HintInfo::keys([Key::Shift], \"Increments\")]),\n\t\t\t]),\n\t\t};\n\n\t\thint_data.send_layout(responses);\n\t}\n\n\tfn update_cursor(&self, responses: &mut VecDeque<Message>) {\n\t\tlet cursor = match *self {\n\t\t\tNavigateToolFsmState::Ready => MouseCursorIcon::ZoomIn,\n\t\t\tNavigateToolFsmState::Tilting => MouseCursorIcon::Default,\n\t\t\tNavigateToolFsmState::Zooming | NavigateToolFsmState::ZoomOrClickZooming => MouseCursorIcon::ZoomIn,\n\t\t};\n\n\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor });\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/tool_messages/path_tool.rs",
    "content": "use super::select_tool::extend_lasso;\nuse super::tool_prelude::*;\nuse crate::consts::{\n\tCOLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_05, COLOR_OVERLAY_GRAY, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_GREEN_25, COLOR_OVERLAY_RED, COLOR_OVERLAY_RED_25, DEFAULT_STROKE_WIDTH,\n\tDOUBLE_CLICK_MILLISECONDS, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, DRAG_THRESHOLD, DRILL_THROUGH_THRESHOLD, HANDLE_ROTATE_SNAP_ANGLE, SEGMENT_INSERTION_DISTANCE, SEGMENT_OVERLAY_SIZE,\n\tSELECTION_THRESHOLD, SELECTION_TOLERANCE,\n};\nuse crate::messages::clipboard::utility_types::ClipboardContent;\nuse crate::messages::input_mapper::utility_types::macros::action_shortcut_manual;\nuse crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;\nuse crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_network_node_type;\nuse crate::messages::portfolio::document::overlays::utility_functions::{path_overlays, selected_segments};\nuse crate::messages::portfolio::document::overlays::utility_types::{DrawHandles, OverlayContext};\nuse crate::messages::portfolio::document::utility_types::clipboards::Clipboard;\nuse crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};\nuse crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;\nuse crate::messages::portfolio::document::utility_types::transformation::Axis;\nuse crate::messages::preferences::SelectionMode;\nuse crate::messages::tool::common_functionality::auto_panning::AutoPanning;\nuse crate::messages::tool::common_functionality::graph_modification_utils;\nuse crate::messages::tool::common_functionality::pivot::{PivotGizmo, PivotGizmoType, PivotToolSource, pin_pivot_widget, pivot_gizmo_type_widget, pivot_reference_point_widget};\nuse crate::messages::tool::common_functionality::shape_editor::{\n\tClosestSegment, ManipulatorAngle, OpposingHandleLengths, SelectedLayerState, SelectedPointsInfo, SelectionChange, SelectionShape, SelectionShapeType, ShapeState,\n};\nuse crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager};\nuse crate::messages::tool::common_functionality::utility_functions::{calculate_segment_angle, find_two_param_best_approximate, make_path_editable_is_allowed};\nuse graphene_std::Color;\nuse graphene_std::renderer::Quad;\nuse graphene_std::subpath::pathseg_points;\nuse graphene_std::transform::ReferencePoint;\nuse graphene_std::uuid::NodeId;\nuse graphene_std::vector::algorithms::util::pathseg_tangent;\nuse graphene_std::vector::click_target::ClickTargetType;\nuse graphene_std::vector::misc::{HandleId, ManipulatorPointId, dvec2_to_point, point_to_dvec2};\nuse graphene_std::vector::{HandleExt, NoHashBuilder, PointId, SegmentId, Vector, VectorModificationType};\nuse kurbo::{DEFAULT_ACCURACY, ParamCurve, ParamCurveNearest, PathSeg, Rect};\nuse std::vec;\n\n#[derive(Default, ExtractField)]\npub struct PathTool {\n\tfsm_state: PathToolFsmState,\n\ttool_data: PathToolData,\n\toptions: PathToolOptions,\n}\n\n#[derive(Default)]\npub struct PathToolOptions {\n\tpath_overlay_mode: PathOverlayMode,\n\tpath_editing_mode: PathEditingMode,\n}\n\n#[impl_message(Message, ToolMessage, Path)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum PathToolMessage {\n\t// Standard messages\n\tAbort,\n\tSelectionChanged,\n\tOverlays {\n\t\tcontext: OverlayContext,\n\t},\n\n\t// Tool-specific messages\n\tBreakPath,\n\tDeselectAllSelected,\n\tDelete,\n\tDeleteAndBreakPath,\n\tDragStop {\n\t\textend_selection: Key,\n\t\tshrink_selection: Key,\n\t},\n\tEnter {\n\t\textend_selection: Key,\n\t\tshrink_selection: Key,\n\t},\n\tEscape,\n\tClosePath,\n\tConnectPointsByPosition {\n\t\tlayer: LayerNodeIdentifier,\n\t\tstart_position: DVec2,\n\t\tend_position: DVec2,\n\t},\n\tDoubleClick {\n\t\textend_selection: Key,\n\t\tshrink_selection: Key,\n\t},\n\tGRS {\n\t\t// Should be `Key::KeyG` (Grab), `Key::KeyR` (Rotate), or `Key::KeyS` (Scale)\n\t\tkey: Key,\n\t},\n\tManipulatorMakeHandlesFree,\n\tManipulatorMakeHandlesColinear,\n\tMouseDown {\n\t\textend_selection: Key,\n\t\tlasso_select: Key,\n\t\thandle_drag_from_anchor: Key,\n\t\tdrag_restore_handle: Key,\n\t\tsegment_editing_modifier: Key,\n\t},\n\tNudgeSelectedPoints {\n\t\tdelta_x: f64,\n\t\tdelta_y: f64,\n\t},\n\tPointerMove {\n\t\tequidistant: Key,\n\t\ttoggle_colinear: Key,\n\t\tmove_anchor_with_handles: Key,\n\t\tsnap_angle: Key,\n\t\tlock_angle: Key,\n\t\tdelete_segment: Key,\n\t\tbreak_colinear_molding: Key,\n\t\tsegment_editing_modifier: Key,\n\t},\n\tPointerOutsideViewport {\n\t\tequidistant: Key,\n\t\ttoggle_colinear: Key,\n\t\tmove_anchor_with_handles: Key,\n\t\tsnap_angle: Key,\n\t\tlock_angle: Key,\n\t\tdelete_segment: Key,\n\t\tbreak_colinear_molding: Key,\n\t\tsegment_editing_modifier: Key,\n\t},\n\tRightClick,\n\tSelectAll,\n\tSelectedPointUpdated,\n\tSelectedPointXChanged {\n\t\tnew_x: f64,\n\t},\n\tSelectedPointYChanged {\n\t\tnew_y: f64,\n\t},\n\tSetPivot {\n\t\tposition: ReferencePoint,\n\t},\n\tSwapSelectedHandles,\n\tUpdateOptions {\n\t\toptions: PathOptionsUpdate,\n\t},\n\tUpdateSelectedPointsStatus {\n\t\toverlay_context: OverlayContext,\n\t},\n\tStartSlidingPoint,\n\tCopy {\n\t\tclipboard: Clipboard,\n\t},\n\tCut {\n\t\tclipboard: Clipboard,\n\t},\n\tPaste {\n\t\tdata: String,\n\t},\n\tDeleteSelected,\n\tDuplicate,\n\tTogglePointEditing,\n\tToggleSegmentEditing,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug, Default, serde::Serialize, serde::Deserialize)]\npub enum PathOverlayMode {\n\tAllHandles = 0,\n\t#[default]\n\tSelectedPointHandles = 1,\n\tFrontierHandles = 2,\n}\n\n#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]\npub struct PathEditingMode {\n\tpoint_editing_mode: bool,\n\tsegment_editing_mode: bool,\n}\n\nimpl Default for PathEditingMode {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tpoint_editing_mode: true,\n\t\t\tsegment_editing_mode: false,\n\t\t}\n\t}\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)]\npub enum PathOptionsUpdate {\n\tOverlayModeType(PathOverlayMode),\n\tPointEditingMode { enabled: bool },\n\tSegmentEditingMode { enabled: bool },\n\tPivotGizmoType(PivotGizmoType),\n\tSetPivotGizmoEnabled(bool),\n\tTogglePivotPinned,\n}\n\nimpl ToolMetadata for PathTool {\n\tfn icon_name(&self) -> String {\n\t\t\"VectorPathTool\".into()\n\t}\n\tfn tooltip_label(&self) -> String {\n\t\t\"Path Tool\".into()\n\t}\n\tfn tool_type(&self) -> crate::messages::tool::utility_types::ToolType {\n\t\tToolType::Path\n\t}\n}\n\nimpl LayoutHolder for PathTool {\n\tfn layout(&self) -> Layout {\n\t\tlet coordinates = self.tool_data.selection_status.as_one().as_ref().map(|point| point.coordinates);\n\t\tlet (x, y) = coordinates.map(|point| (Some(point.x), Some(point.y))).unwrap_or((None, None));\n\n\t\tlet selection_status = &self.tool_data.selection_status;\n\t\tlet manipulator_angle = selection_status.angle();\n\n\t\tlet x_location = NumberInput::new(x)\n\t\t\t.unit(\" px\")\n\t\t\t.label(\"X\")\n\t\t\t.min_width(120)\n\t\t\t.disabled(x.is_none())\n\t\t\t.min(-((1_u64 << f64::MANTISSA_DIGITS) as f64))\n\t\t\t.max((1_u64 << f64::MANTISSA_DIGITS) as f64)\n\t\t\t.on_update(move |number_input: &NumberInput| {\n\t\t\t\tif let Some(new_x) = number_input.value.or(x) {\n\t\t\t\t\tPathToolMessage::SelectedPointXChanged { new_x }.into()\n\t\t\t\t} else {\n\t\t\t\t\tMessage::NoOp\n\t\t\t\t}\n\t\t\t})\n\t\t\t.widget_instance();\n\n\t\tlet y_location = NumberInput::new(y)\n\t\t\t.unit(\" px\")\n\t\t\t.label(\"Y\")\n\t\t\t.min_width(120)\n\t\t\t.disabled(y.is_none())\n\t\t\t.min(-((1_u64 << f64::MANTISSA_DIGITS) as f64))\n\t\t\t.max((1_u64 << f64::MANTISSA_DIGITS) as f64)\n\t\t\t.on_update(move |number_input: &NumberInput| {\n\t\t\t\tif let Some(new_y) = number_input.value.or(y) {\n\t\t\t\t\tPathToolMessage::SelectedPointYChanged { new_y }.into()\n\t\t\t\t} else {\n\t\t\t\t\tMessage::NoOp\n\t\t\t\t}\n\t\t\t})\n\t\t\t.widget_instance();\n\n\t\tlet related_seperator = Separator::new(SeparatorStyle::Related).widget_instance();\n\t\tlet unrelated_seperator = Separator::new(SeparatorStyle::Unrelated).widget_instance();\n\n\t\tlet colinear_handles_description = \"Keep both handles unbent, each 180° apart, when moving either.\";\n\t\tlet colinear_handles_state = manipulator_angle.and_then(|angle| match angle {\n\t\t\tManipulatorAngle::Colinear => Some(true),\n\t\t\tManipulatorAngle::Free => Some(false),\n\t\t\tManipulatorAngle::Mixed => None,\n\t\t})\n\t\t// TODO: Remove `unwrap_or_default` once checkboxes are capable of displaying a mixed state\n\t\t.unwrap_or_default();\n\t\tlet checkbox_id = CheckboxId::new();\n\t\tlet colinear_handle_checkbox = CheckboxInput::new(colinear_handles_state)\n\t\t\t.disabled(!self.tool_data.can_toggle_colinearity)\n\t\t\t.on_update(|&CheckboxInput { checked, .. }| {\n\t\t\t\tif checked {\n\t\t\t\t\tPathToolMessage::ManipulatorMakeHandlesColinear.into()\n\t\t\t\t} else {\n\t\t\t\t\tPathToolMessage::ManipulatorMakeHandlesFree.into()\n\t\t\t\t}\n\t\t\t})\n\t\t\t.tooltip_label(\"Colinear Handles\")\n\t\t\t.tooltip_description(colinear_handles_description)\n\t\t\t.for_label(checkbox_id)\n\t\t\t.widget_instance();\n\t\tlet colinear_handles_label = TextLabel::new(\"Colinear Handles\")\n\t\t\t.disabled(!self.tool_data.can_toggle_colinearity)\n\t\t\t.tooltip_label(\"Colinear Handles\")\n\t\t\t.tooltip_description(colinear_handles_description)\n\t\t\t.for_checkbox(checkbox_id)\n\t\t\t.widget_instance();\n\n\t\tlet point_editing_mode = CheckboxInput::new(self.options.path_editing_mode.point_editing_mode)\n\t\t\t// TODO(Keavon): Replace with a real icon\n\t\t\t.icon(\"Dot\")\n\t\t\t.tooltip_label(\"Point Editing Mode\")\n\t\t\t.tooltip_description(\"To multi-select modes, perform the shortcut shown.\")\n\t\t\t.tooltip_shortcut(action_shortcut_manual!(Key::Shift, Key::MouseLeft))\n\t\t\t.on_update(|_| PathToolMessage::TogglePointEditing.into())\n\t\t\t.widget_instance();\n\t\tlet segment_editing_mode = CheckboxInput::new(self.options.path_editing_mode.segment_editing_mode)\n\t\t\t// TODO(Keavon): Replace with a real icon\n\t\t\t.icon(\"Remove\")\n\t\t\t.tooltip_label(\"Segment Editing Mode\")\n\t\t\t.tooltip_description(\"To multi-select modes, perform the shortcut shown.\")\n\t\t\t.tooltip_shortcut(action_shortcut_manual!(Key::Shift, Key::MouseLeft))\n\t\t\t.on_update(|_| PathToolMessage::ToggleSegmentEditing.into())\n\t\t\t.widget_instance();\n\n\t\tlet path_overlay_mode_widget = RadioInput::new(vec![\n\t\t\tRadioEntryData::new(\"all\")\n\t\t\t\t.icon(\"HandleVisibilityAll\")\n\t\t\t\t.tooltip_label(\"Show All Handles\")\n\t\t\t\t.tooltip_description(\"Show all handles regardless of selection.\")\n\t\t\t\t.on_update(move |_| {\n\t\t\t\t\tPathToolMessage::UpdateOptions {\n\t\t\t\t\t\toptions: PathOptionsUpdate::OverlayModeType(PathOverlayMode::AllHandles),\n\t\t\t\t\t}\n\t\t\t\t\t.into()\n\t\t\t\t}),\n\t\t\tRadioEntryData::new(\"selected\")\n\t\t\t\t.icon(\"HandleVisibilitySelected\")\n\t\t\t\t.tooltip_label(\"Show Connected Handles\")\n\t\t\t\t.tooltip_description(\"Show only handles of the segments connected to selected points.\")\n\t\t\t\t.on_update(move |_| {\n\t\t\t\t\tPathToolMessage::UpdateOptions {\n\t\t\t\t\t\toptions: PathOptionsUpdate::OverlayModeType(PathOverlayMode::SelectedPointHandles),\n\t\t\t\t\t}\n\t\t\t\t\t.into()\n\t\t\t\t}),\n\t\t\tRadioEntryData::new(\"frontier\")\n\t\t\t\t.icon(\"HandleVisibilityFrontier\")\n\t\t\t\t.tooltip_label(\"Show Frontier Handles\")\n\t\t\t\t.tooltip_description(\"Show only handles at the frontiers of the segments connected to selected points.\")\n\t\t\t\t.on_update(move |_| {\n\t\t\t\t\tPathToolMessage::UpdateOptions {\n\t\t\t\t\t\toptions: PathOptionsUpdate::OverlayModeType(PathOverlayMode::FrontierHandles),\n\t\t\t\t\t}\n\t\t\t\t\t.into()\n\t\t\t\t}),\n\t\t])\n\t\t.selected_index(Some(self.options.path_overlay_mode as u32))\n\t\t.widget_instance();\n\n\t\t// Works only if a single layer is selected and its type is Vector\n\t\tlet path_node_button = TextButton::new(\"Make Path Editable\")\n\t\t\t.icon(\"NodeShape\")\n\t\t\t.tooltip_label(\"Make Path Editable\")\n\t\t\t.tooltip_description(\n\t\t\t\t\"Enables the Pen and Path tools to directly edit layer geometry resulting from nondestructive operations. This inserts a 'Path' node as the last operation of the selected layer.\",\n\t\t\t)\n\t\t\t.on_update(|_| NodeGraphMessage::AddPathNode.into())\n\t\t\t.disabled(!self.tool_data.make_path_editable_is_allowed)\n\t\t\t.widget_instance();\n\n\t\tlet [_checkbox, _dropdown] = {\n\t\t\tlet pivot_gizmo_type_widget = pivot_gizmo_type_widget(self.tool_data.pivot_gizmo.state, PivotToolSource::Path);\n\t\t\t[pivot_gizmo_type_widget[0].clone(), pivot_gizmo_type_widget[2].clone()]\n\t\t};\n\n\t\tlet has_something = !self.tool_data.saved_points_before_anchor_convert_smooth_sharp.is_empty();\n\t\tlet _pivot_reference = pivot_reference_point_widget(\n\t\t\thas_something || !self.tool_data.pivot_gizmo.state.is_pivot(),\n\t\t\tself.tool_data.pivot_gizmo.pivot.to_pivot_position(),\n\t\t\tPivotToolSource::Path,\n\t\t);\n\n\t\tlet _pin_pivot = pin_pivot_widget(self.tool_data.pivot_gizmo.pin_active(), false, PivotToolSource::Path);\n\n\t\tLayout(vec![LayoutGroup::row(vec![\n\t\t\tx_location,\n\t\t\trelated_seperator.clone(),\n\t\t\ty_location,\n\t\t\tunrelated_seperator.clone(),\n\t\t\tcolinear_handle_checkbox,\n\t\t\trelated_seperator.clone(),\n\t\t\tcolinear_handles_label,\n\t\t\tunrelated_seperator.clone(),\n\t\t\tpoint_editing_mode,\n\t\t\trelated_seperator.clone(),\n\t\t\tsegment_editing_mode,\n\t\t\tunrelated_seperator.clone(),\n\t\t\tpath_overlay_mode_widget,\n\t\t\tunrelated_seperator.clone(),\n\t\t\tpath_node_button,\n\t\t\t// checkbox.clone(),\n\t\t\t// related_seperator.clone(),\n\t\t\t// dropdown.clone(),\n\t\t\t// unrelated_seperator,\n\t\t\t// pivot_reference,\n\t\t\t// related_seperator.clone(),\n\t\t\t// pin_pivot,\n\t\t])])\n\t}\n}\n\n#[message_handler_data]\nimpl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for PathTool {\n\tfn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {\n\t\tlet updating_point = message == ToolMessage::Path(PathToolMessage::SelectedPointUpdated);\n\n\t\tmatch message {\n\t\t\tToolMessage::Path(PathToolMessage::UpdateOptions { options }) => match options {\n\t\t\t\tPathOptionsUpdate::OverlayModeType(overlay_mode_type) => {\n\t\t\t\t\tself.options.path_overlay_mode = overlay_mode_type;\n\t\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\t}\n\t\t\t\tPathOptionsUpdate::PointEditingMode { enabled } => {\n\t\t\t\t\tself.options.path_editing_mode.point_editing_mode = enabled;\n\t\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\t}\n\t\t\t\tPathOptionsUpdate::SegmentEditingMode { enabled } => {\n\t\t\t\t\tself.options.path_editing_mode.segment_editing_mode = enabled;\n\t\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\t}\n\t\t\t\tPathOptionsUpdate::PivotGizmoType(gizmo_type) => {\n\t\t\t\t\tif !self.tool_data.pivot_gizmo.state.enabled {\n\t\t\t\t\t\tself.tool_data.pivot_gizmo.state.gizmo_type = gizmo_type;\n\t\t\t\t\t\tresponses.add(ToolMessage::UpdateHints);\n\t\t\t\t\t\tlet pivot_gizmo = self.tool_data.pivot_gizmo();\n\t\t\t\t\t\tresponses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });\n\t\t\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\t\t\tself.send_layout(responses, LayoutTarget::ToolOptions);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tPathOptionsUpdate::SetPivotGizmoEnabled(enabled) => {\n\t\t\t\t\tself.tool_data.pivot_gizmo.state.enabled = enabled;\n\t\t\t\t\tresponses.add(ToolMessage::UpdateHints);\n\t\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\t\tself.send_layout(responses, LayoutTarget::ToolOptions);\n\t\t\t\t}\n\n\t\t\t\tPathOptionsUpdate::TogglePivotPinned => {\n\t\t\t\t\tself.tool_data.pivot_gizmo.pivot.pinned = !self.tool_data.pivot_gizmo.pivot.pinned;\n\t\t\t\t\tresponses.add(ToolMessage::UpdateHints);\n\t\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\t\tself.send_layout(responses, LayoutTarget::ToolOptions);\n\t\t\t\t}\n\t\t\t},\n\t\t\tToolMessage::Path(PathToolMessage::SwapSelectedHandles) => {\n\t\t\t\tif context.shape_editor.handle_with_pair_selected(&context.document.network_interface) {\n\t\t\t\t\tcontext.shape_editor.alternate_selected_handles(&context.document.network_interface);\n\t\t\t\t\tresponses.add(PathToolMessage::SelectedPointUpdated);\n\t\t\t\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::None });\n\t\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\t}\n\t\t\t}\n\t\t\t_ => {\n\t\t\t\tself.fsm_state.process_event(message, &mut self.tool_data, context, &self.options, responses, true);\n\t\t\t}\n\t\t}\n\n\t\tif updating_point {\n\t\t\tself.send_layout(responses, LayoutTarget::ToolOptions);\n\t\t}\n\t}\n\n\t// Different actions depending on state may be wanted:\n\tfn actions(&self) -> ActionList {\n\t\tmatch self.fsm_state {\n\t\t\tPathToolFsmState::Ready => actions!(PathToolMessageDiscriminant;\n\t\t\t\tDoubleClick,\n\t\t\t\tMouseDown,\n\t\t\t\tDelete,\n\t\t\t\tNudgeSelectedPoints,\n\t\t\t\tEnter,\n\t\t\t\tSelectAll,\n\t\t\t\tDeselectAllSelected,\n\t\t\t\tBreakPath,\n\t\t\t\tDeleteAndBreakPath,\n\t\t\t\tClosePath,\n\t\t\t\tPointerMove,\n\t\t\t\tStartSlidingPoint,\n\t\t\t\tCopy,\n\t\t\t\tCut,\n\t\t\t\tDeleteSelected,\n\t\t\t\tPaste,\n\t\t\t\tDuplicate,\n\t\t\t\tTogglePointEditing,\n\t\t\t\tToggleSegmentEditing\n\t\t\t),\n\t\t\tPathToolFsmState::Dragging(_) => actions!(PathToolMessageDiscriminant;\n\t\t\t\tEscape,\n\t\t\t\tRightClick,\n\t\t\t\tDoubleClick,\n\t\t\t\tDragStop,\n\t\t\t\tPointerMove,\n\t\t\t\tDelete,\n\t\t\t\tBreakPath,\n\t\t\t\tDeleteAndBreakPath,\n\t\t\t\tSwapSelectedHandles,\n\t\t\t\tStartSlidingPoint,\n\t\t\t\tCopy,\n\t\t\t\tCut,\n\t\t\t\tDeleteSelected,\n\t\t\t\tPaste,\n\t\t\t\tDuplicate,\n\t\t\t\tTogglePointEditing,\n\t\t\t\tToggleSegmentEditing\n\t\t\t),\n\t\t\tPathToolFsmState::Drawing { .. } => actions!(PathToolMessageDiscriminant;\n\t\t\t\tDoubleClick,\n\t\t\t\tDragStop,\n\t\t\t\tPointerMove,\n\t\t\t\tDelete,\n\t\t\t\tEnter,\n\t\t\t\tBreakPath,\n\t\t\t\tDeleteAndBreakPath,\n\t\t\t\tEscape,\n\t\t\t\tRightClick,\n\t\t\t\tStartSlidingPoint,\n\t\t\t\tTogglePointEditing,\n\t\t\t\tToggleSegmentEditing\n\t\t\t),\n\t\t\tPathToolFsmState::SlidingPoint => actions!(PathToolMessageDiscriminant;\n\t\t\t\tPointerMove,\n\t\t\t\tDragStop,\n\t\t\t\tEscape,\n\t\t\t\tRightClick\n\t\t\t),\n\t\t}\n\t}\n}\n\nimpl ToolTransition for PathTool {\n\tfn event_to_message_map(&self) -> EventToMessageMap {\n\t\tEventToMessageMap {\n\t\t\ttool_abort: Some(PathToolMessage::Abort.into()),\n\t\t\tselection_changed: Some(PathToolMessage::SelectionChanged.into()),\n\t\t\toverlay_provider: Some(|context| PathToolMessage::Overlays { context }.into()),\n\t\t\t..Default::default()\n\t\t}\n\t}\n}\n#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]\npub struct DraggingState {\n\tpoint_select_state: PointSelectState,\n\tcolinear: ManipulatorAngle,\n}\n\n#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]\npub enum PointSelectState {\n\tHandleWithPair,\n\t#[default]\n\tHandleNoPair,\n\tAnchor,\n}\n\n#[derive(Clone, Copy)]\npub struct SlidingSegmentData {\n\tsegment_id: SegmentId,\n\tbezier: PathSeg,\n\tstart: PointId,\n}\n\n#[derive(Clone, Copy)]\npub struct SlidingPointInfo {\n\tanchor: PointId,\n\tlayer: LayerNodeIdentifier,\n\tconnected_segments: [SlidingSegmentData; 2],\n}\n\n#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]\nenum PathToolFsmState {\n\t#[default]\n\tReady,\n\tDragging(DraggingState),\n\tDrawing {\n\t\tselection_shape: SelectionShapeType,\n\t},\n\tSlidingPoint,\n}\n\n#[derive(Default)]\nstruct PathToolData {\n\tsnap_manager: SnapManager,\n\tlasso_polygon: Vec<DVec2>,\n\tselection_mode: Option<SelectionMode>,\n\tdrag_start_pos: DVec2,\n\tprevious_mouse_position: DVec2,\n\ttoggle_colinear_debounce: bool,\n\topposing_handle_lengths: Option<OpposingHandleLengths>,\n\t/// Describes information about the selected point(s), if any, across one or multiple shapes and manipulator point types (anchor or handle).\n\t/// The available information varies depending on whether `None`, `One`, or `Multiple` points are currently selected.\n\t/// NOTE: It must be updated using `update_selection_status` to ensure `can_toggle_colinearity` stays synchronized with the current selection.\n\tselection_status: SelectionStatus,\n\t/// `true` if we can change the current selection to colinear or not.\n\tcan_toggle_colinearity: bool,\n\tsegment: Option<ClosestSegment>,\n\tsnap_cache: SnapCache,\n\tdouble_click_handled: bool,\n\tdelete_segment_pressed: bool,\n\tsegment_editing_modifier: bool,\n\tmultiple_toggle_pressed: bool,\n\tauto_panning: AutoPanning,\n\tsaved_points_before_anchor_select_toggle: HashMap<LayerNodeIdentifier, Vec<ManipulatorPointId>>,\n\tselect_anchor_toggled: bool,\n\tsaved_selection_before_handle_drag: HashMap<LayerNodeIdentifier, (HashSet<ManipulatorPointId>, HashSet<SegmentId>)>,\n\thandle_drag_toggle: bool,\n\tsaved_points_before_anchor_convert_smooth_sharp: HashMap<LayerNodeIdentifier, Vec<ManipulatorPointId>>,\n\tlast_click_time: u64,\n\tdragging_state: DraggingState,\n\tangle: f64,\n\tpivot_gizmo: PivotGizmo,\n\tordered_points: Vec<ManipulatorPointId>,\n\topposite_handle_position: Option<DVec2>,\n\tlast_clicked_point_was_selected: bool,\n\tlast_clicked_segment_was_selected: bool,\n\tsnapping_axis: Option<Axis>,\n\talt_clicked_on_anchor: bool,\n\talt_dragging_from_anchor: bool,\n\tangle_locked: bool,\n\ttemporary_colinear_handles: bool,\n\tmolding_info: Option<(DVec2, DVec2)>,\n\tmolding_segment: bool,\n\ttemporary_adjacent_handles_while_molding: Option<[Option<HandleId>; 2]>,\n\tfrontier_handles_info: Option<HashMap<LayerNodeIdentifier, HashMap<SegmentId, Vec<PointId>>>>,\n\tadjacent_anchor_offset: Option<DVec2>,\n\tsliding_point_info: Option<SlidingPointInfo>,\n\tstarted_drawing_from_inside: bool,\n\tfirst_selected_with_single_click: bool,\n\tstored_selection: Option<HashMap<LayerNodeIdentifier, SelectedLayerState>>,\n\tlast_drill_through_click_position: Option<DVec2>,\n\tdrill_through_cycle_index: usize,\n\tdrill_through_cycle_count: usize,\n\thovered_layers: Vec<LayerNodeIdentifier>,\n\tghost_outline: Vec<(Vec<ClickTargetType>, LayerNodeIdentifier)>,\n\tmake_path_editable_is_allowed: bool,\n}\n\nimpl PathToolData {\n\tfn save_points_before_anchor_toggle(&mut self, points: HashMap<LayerNodeIdentifier, Vec<ManipulatorPointId>>) -> PathToolFsmState {\n\t\tself.saved_points_before_anchor_select_toggle = points;\n\t\tPathToolFsmState::Dragging(self.dragging_state)\n\t}\n\n\tpub fn selection_quad(&self, metadata: &DocumentMetadata) -> Quad {\n\t\tlet bbox = self.selection_box(metadata);\n\t\tQuad::from_box(bbox)\n\t}\n\n\tpub fn calculate_selection_mode_from_direction(&mut self, metadata: &DocumentMetadata) -> SelectionMode {\n\t\tlet bbox = self.selection_box(metadata);\n\t\tlet above_threshold = bbox[1].distance_squared(bbox[0]) > DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD.powi(2);\n\n\t\tif self.selection_mode.is_none() && above_threshold {\n\t\t\tlet mode = if bbox[1].x < bbox[0].x {\n\t\t\t\tSelectionMode::Touched\n\t\t\t} else {\n\t\t\t\t// This also covers the case where they're equal: the area is zero, so we use `Enclosed` to ensure the selection ends up empty, as nothing will be enclosed by an empty area\n\t\t\t\tSelectionMode::Enclosed\n\t\t\t};\n\t\t\tself.selection_mode = Some(mode);\n\t\t}\n\n\t\tself.selection_mode.unwrap_or(SelectionMode::Touched)\n\t}\n\n\tpub fn selection_box(&self, metadata: &DocumentMetadata) -> [DVec2; 2] {\n\t\t// Convert previous mouse position to viewport space first\n\t\tlet document_to_viewport = metadata.document_to_viewport;\n\t\tlet previous_mouse = document_to_viewport.transform_point2(self.previous_mouse_position);\n\t\tif previous_mouse == self.drag_start_pos {\n\t\t\tlet tolerance = DVec2::splat(SELECTION_TOLERANCE);\n\t\t\t[self.drag_start_pos - tolerance, self.drag_start_pos + tolerance]\n\t\t} else {\n\t\t\t[self.drag_start_pos, previous_mouse]\n\t\t}\n\t}\n\n\tfn update_selection_status(&mut self, shape_editor: &mut ShapeState, document: &DocumentMessageHandler) {\n\t\tlet selection_status = get_selection_status(&document.network_interface, shape_editor);\n\n\t\tself.can_toggle_colinearity = match &selection_status {\n\t\t\tSelectionStatus::None => false,\n\t\t\tSelectionStatus::One(single_selected_point) => {\n\t\t\t\tlet vector = document.network_interface.compute_modified_vector(single_selected_point.layer).unwrap();\n\t\t\t\tif single_selected_point.id.get_handle_pair(&vector).is_some() {\n\t\t\t\t\tlet anchor = single_selected_point.id.get_anchor(&vector).expect(\"Cannot find connected anchor\");\n\t\t\t\t\tvector.all_connected(anchor).count() <= 2\n\t\t\t\t} else {\n\t\t\t\t\tfalse\n\t\t\t\t}\n\t\t\t}\n\t\t\tSelectionStatus::Multiple(_) => true,\n\t\t};\n\t\tself.selection_status = selection_status;\n\t}\n\n\tfn remove_saved_points(&mut self) {\n\t\tself.saved_points_before_anchor_select_toggle.clear();\n\t}\n\n\tfn reset_drill_through_cycle(&mut self) {\n\t\tself.last_drill_through_click_position = None;\n\t\tself.drill_through_cycle_index = 0;\n\t}\n\n\tfn next_drill_through_cycle(&mut self, position: DVec2) -> usize {\n\t\tif self.last_drill_through_click_position.is_none_or(|last_pos| last_pos.distance(position) > DRILL_THROUGH_THRESHOLD) {\n\t\t\t// New position, reset cycle\n\t\t\tself.drill_through_cycle_index = 0;\n\t\t} else {\n\t\t\t// Same position, advance cycle\n\t\t\tself.drill_through_cycle_index = (self.drill_through_cycle_index + 1) % self.drill_through_cycle_count.max(1);\n\t\t}\n\t\tself.last_drill_through_click_position = Some(position);\n\t\tself.drill_through_cycle_index\n\t}\n\n\tfn peek_drill_through_index(&self) -> usize {\n\t\tif self.drill_through_cycle_count == 0 {\n\t\t\t0\n\t\t} else {\n\t\t\t(self.drill_through_cycle_index + 1) % self.drill_through_cycle_count.max(1)\n\t\t}\n\t}\n\n\tfn has_drill_through_mouse_moved(&self, position: DVec2) -> bool {\n\t\tself.last_drill_through_click_position.is_none_or(|last_pos| last_pos.distance(position) > DRILL_THROUGH_THRESHOLD)\n\t}\n\n\tfn set_ghost_outline(&mut self, shape_editor: &ShapeState, document: &DocumentMessageHandler) {\n\t\tself.ghost_outline.clear();\n\t\tfor &layer in shape_editor.selected_shape_state.keys() {\n\t\t\t// We probably need to collect here\n\t\t\tlet outline: Vec<ClickTargetType> = document.metadata().layer_with_free_points_outline(layer).cloned().collect();\n\n\t\t\tself.ghost_outline.push((outline, layer));\n\t\t}\n\t}\n\n\t// TODO: This function is for basic point select mode. We definitely need to make a new one for the segment select mode.\n\t#[allow(clippy::too_many_arguments)]\n\tfn mouse_down(\n\t\t&mut self,\n\t\tshape_editor: &mut ShapeState,\n\t\tdocument: &DocumentMessageHandler,\n\t\tinput: &InputPreprocessorMessageHandler,\n\t\tviewport: &ViewportMessageHandler,\n\t\tresponses: &mut VecDeque<Message>,\n\t\textend_selection: bool,\n\t\tlasso_select: bool,\n\t\thandle_drag_from_anchor: bool,\n\t\tdrag_zero_handle: bool,\n\t\tsegment_editing_modifier: bool,\n\t\tpath_overlay_mode: PathOverlayMode,\n\t\tsegment_editing_mode: bool,\n\t\tpoint_editing_mode: bool,\n\t) -> PathToolFsmState {\n\t\tself.double_click_handled = false;\n\t\tself.opposing_handle_lengths = None;\n\n\t\tself.drag_start_pos = input.mouse.position;\n\n\t\tif input.time - self.last_click_time > DOUBLE_CLICK_MILLISECONDS {\n\t\t\tself.saved_points_before_anchor_convert_smooth_sharp.clear();\n\t\t\tself.stored_selection = None;\n\t\t}\n\n\t\tself.last_click_time = input.time;\n\n\t\tlet mut old_selection = HashMap::new();\n\n\t\tfor (layer, state) in &shape_editor.selected_shape_state {\n\t\t\tlet selected_points = state.selected_points().collect::<HashSet<_>>();\n\t\t\tlet selected_segments = state.selected_segments().collect::<HashSet<_>>();\n\t\t\told_selection.insert(*layer, (selected_points, selected_segments));\n\t\t}\n\n\t\t// Check if the point is already selected; if not, select the first point within the threshold (in pixels)\n\t\t// Don't select the points which are not shown currently in PathOverlayMode\n\t\tif let Some((already_selected, mut selection_info)) = shape_editor.get_point_selection_state(\n\t\t\t&document.network_interface,\n\t\t\tinput.mouse.position,\n\t\t\tSELECTION_THRESHOLD,\n\t\t\tpath_overlay_mode,\n\t\t\tself.frontier_handles_info.as_ref(),\n\t\t\tpoint_editing_mode,\n\t\t) {\n\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\n\t\t\tself.set_ghost_outline(shape_editor, document);\n\n\t\t\tself.last_clicked_point_was_selected = already_selected;\n\n\t\t\t// If the point is already selected and shift (`extend_selection`) is used, keep the selection unchanged.\n\t\t\t// Otherwise, select the first point within the threshold.\n\t\t\tif !(already_selected && extend_selection)\n\t\t\t\t&& let Some(updated_selection_info) = shape_editor.change_point_selection(\n\t\t\t\t\t&document.network_interface,\n\t\t\t\t\tinput.mouse.position,\n\t\t\t\t\tSELECTION_THRESHOLD,\n\t\t\t\t\textend_selection,\n\t\t\t\t\tpath_overlay_mode,\n\t\t\t\t\tself.frontier_handles_info.as_ref(),\n\t\t\t\t) {\n\t\t\t\tselection_info = updated_selection_info;\n\t\t\t}\n\n\t\t\tif let Some(selected_points) = selection_info {\n\t\t\t\tself.drag_start_pos = input.mouse.position;\n\n\t\t\t\t// If selected points contain only handles and there was some selection before, then it is stored and becomes restored upon release\n\t\t\t\tlet mut dragging_only_handles = true;\n\t\t\t\tfor point in &selected_points.points {\n\t\t\t\t\tif matches!(point.point_id, ManipulatorPointId::Anchor(_)) {\n\t\t\t\t\t\tdragging_only_handles = false;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif dragging_only_handles && !self.handle_drag_toggle && !old_selection.is_empty() {\n\t\t\t\t\tself.saved_selection_before_handle_drag = old_selection;\n\t\t\t\t}\n\n\t\t\t\tif handle_drag_from_anchor && let Some((layer, point)) = shape_editor.find_nearest_point_indices(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD) {\n\t\t\t\t\t// Check that selected point is an anchor\n\t\t\t\t\tif let (Some(point_id), Some(vector)) = (point.as_anchor(), document.network_interface.compute_modified_vector(layer)) {\n\t\t\t\t\t\tlet handles = vector.all_connected(point_id).collect::<Vec<_>>();\n\t\t\t\t\t\tself.alt_clicked_on_anchor = true;\n\t\t\t\t\t\tfor handle in &handles {\n\t\t\t\t\t\t\tlet modification_type = handle.set_relative_position(DVec2::ZERO);\n\t\t\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t\t\t\t\tfor &handles in &vector.colinear_manipulators {\n\t\t\t\t\t\t\t\tif handles.contains(handle) {\n\t\t\t\t\t\t\t\t\tlet modification_type = VectorModificationType::SetG1Continuous { handles, enabled: false };\n\t\t\t\t\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlet manipulator_point_id = handles[0].to_manipulator_point();\n\t\t\t\t\t\tshape_editor.deselect_all_points();\n\t\t\t\t\t\tshape_editor.select_point_by_layer_and_id(manipulator_point_id, layer);\n\t\t\t\t\t\tresponses.add(PathToolMessage::SelectedPointUpdated);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif let Some((Some(point), Some(vector), layer)) = shape_editor\n\t\t\t\t\t.find_nearest_point_indices(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD)\n\t\t\t\t\t.map(|(layer, point)| (point.as_anchor(), document.network_interface.compute_modified_vector(layer), layer))\n\t\t\t\t{\n\t\t\t\t\tlet handles = vector\n\t\t\t\t\t\t.all_connected(point)\n\t\t\t\t\t\t.filter(|handle| handle.length(&vector) < 1e-6)\n\t\t\t\t\t\t.map(|handle| handle.to_manipulator_point())\n\t\t\t\t\t\t.collect::<Vec<_>>();\n\t\t\t\t\tlet endpoint = vector.anchor_endpoints().any(|anchor| point == anchor);\n\n\t\t\t\t\tif drag_zero_handle && (handles.len() == 1 && !endpoint) {\n\t\t\t\t\t\tshape_editor.deselect_all_points();\n\t\t\t\t\t\tshape_editor.select_points_by_layer_and_id(&HashMap::from([(layer, handles)]));\n\t\t\t\t\t\tshape_editor.convert_selected_manipulators_to_colinear_handles(responses, document);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tself.start_dragging_point(selected_points, input, document, shape_editor);\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t}\n\t\t\tPathToolFsmState::Dragging(self.dragging_state)\n\t\t}\n\t\t// We didn't find a point nearby, so we will see if there is a segment to select or insert a point on\n\t\telse if let Some(segment) = shape_editor.upper_closest_segment(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD) {\n\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\n\t\t\tself.set_ghost_outline(shape_editor, document);\n\n\t\t\tif segment_editing_mode && !segment_editing_modifier {\n\t\t\t\tlet layer = segment.layer();\n\t\t\t\tlet segment_id = segment.segment();\n\t\t\t\tlet already_selected = shape_editor.selected_shape_state.get(&layer).is_some_and(|state| state.is_segment_selected(segment_id));\n\t\t\t\tself.last_clicked_segment_was_selected = already_selected;\n\n\t\t\t\tif !(already_selected && extend_selection) {\n\t\t\t\t\tlet retain_existing_selection = extend_selection || already_selected;\n\t\t\t\t\tif !retain_existing_selection {\n\t\t\t\t\t\tshape_editor.deselect_all_segments();\n\t\t\t\t\t\tshape_editor.deselect_all_points();\n\t\t\t\t\t}\n\n\t\t\t\t\t// Add to selected segments\n\t\t\t\t\tif let Some(selected_shape_state) = shape_editor.selected_shape_state.get_mut(&layer) {\n\t\t\t\t\t\tselected_shape_state.select_segment(segment_id);\n\t\t\t\t\t}\n\n\t\t\t\t\t// TODO: If the segment connected to one of the endpoints is also selected then select that point\n\t\t\t\t}\n\n\t\t\t\tself.drag_start_pos = input.mouse.position;\n\t\t\t\tlet viewport_to_document = document.metadata().document_to_viewport.inverse();\n\t\t\t\tself.previous_mouse_position = viewport_to_document.transform_point2(input.mouse.position);\n\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\tPathToolFsmState::Dragging(self.dragging_state)\n\t\t\t} else {\n\t\t\t\tlet points = pathseg_points(segment.pathseg());\n\n\t\t\t\tlet [pos1, pos2] = match (points.p1, points.p2) {\n\t\t\t\t\t(Some(p1), Some(p2)) => [p1, p2],\n\t\t\t\t\t(Some(p1), None) | (None, Some(p1)) => [p1, points.p3],\n\t\t\t\t\t(None, None) => [points.p0 + (points.p3 - points.p0) / 3., points.p3 + (points.p0 - points.p3) / 3.],\n\t\t\t\t};\n\t\t\t\tself.molding_info = Some((pos1, pos2));\n\t\t\t\tPathToolFsmState::Dragging(self.dragging_state)\n\t\t\t}\n\t\t}\n\t\t// If no other layers are selected and this is a single-click, then also select the layer (exception)\n\t\telse if let Some(layer) = document.click(input, viewport) {\n\t\t\tif shape_editor.selected_shape_state.is_empty() {\n\t\t\t\tself.first_selected_with_single_click = true;\n\t\t\t\t// This ensures we don't need to double click a second time to get the drill through to work\n\t\t\t\tself.last_drill_through_click_position = Some(input.mouse.position);\n\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] });\n\t\t\t}\n\n\t\t\tself.started_drawing_from_inside = true;\n\n\t\t\tself.drag_start_pos = input.mouse.position;\n\t\t\tself.previous_mouse_position = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position);\n\n\t\t\tlet selection_shape = if lasso_select { SelectionShapeType::Lasso } else { SelectionShapeType::Box };\n\t\t\tPathToolFsmState::Drawing { selection_shape }\n\t\t}\n\t\t// Start drawing\n\t\telse {\n\t\t\tself.drag_start_pos = input.mouse.position;\n\t\t\tself.previous_mouse_position = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position);\n\n\t\t\tlet selection_shape = if lasso_select { SelectionShapeType::Lasso } else { SelectionShapeType::Box };\n\t\t\tPathToolFsmState::Drawing { selection_shape }\n\t\t}\n\t}\n\n\tfn start_dragging_point(&mut self, selected_points: SelectedPointsInfo, input: &InputPreprocessorMessageHandler, document: &DocumentMessageHandler, shape_editor: &mut ShapeState) {\n\t\tlet mut manipulators = HashMap::with_hasher(NoHashBuilder);\n\t\tlet mut unselected = Vec::new();\n\t\tfor (&layer, state) in &shape_editor.selected_shape_state {\n\t\t\tlet Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };\n\t\t\tlet transform = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface);\n\n\t\t\tlet mut layer_manipulators = HashSet::with_hasher(NoHashBuilder);\n\t\t\tfor point in state.selected_points() {\n\t\t\t\tlet Some(anchor) = point.get_anchor(&vector) else { continue };\n\t\t\t\tlayer_manipulators.insert(anchor);\n\t\t\t\tlet Some([handle1, handle2]) = point.get_handle_pair(&vector) else { continue };\n\t\t\t\tlet Some(handle) = point.as_handle() else { continue };\n\t\t\t\t// Check which handle is selected and which is opposite\n\t\t\t\tlet opposite = if handle == handle1 { handle2 } else { handle1 };\n\n\t\t\t\tself.opposite_handle_position = if self.opposite_handle_position.is_none() {\n\t\t\t\t\topposite.to_manipulator_point().get_position(&vector)\n\t\t\t\t} else {\n\t\t\t\t\tself.opposite_handle_position\n\t\t\t\t};\n\t\t\t}\n\t\t\tfor (&id, &position) in vector.point_domain.ids().iter().zip(vector.point_domain.positions()) {\n\t\t\t\tif layer_manipulators.contains(&id) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tunselected.push(SnapCandidatePoint::handle(transform.transform_point2(position)))\n\t\t\t}\n\t\t\tif !layer_manipulators.is_empty() {\n\t\t\t\tmanipulators.insert(layer, layer_manipulators);\n\t\t\t}\n\t\t}\n\t\tself.snap_cache = SnapCache { manipulators, unselected };\n\n\t\tlet viewport_to_document = document.metadata().document_to_viewport.inverse();\n\t\tself.previous_mouse_position = viewport_to_document.transform_point2(input.mouse.position - selected_points.offset);\n\t}\n\n\tfn update_colinear(&mut self, equidistant: bool, toggle_colinear: bool, shape_editor: &mut ShapeState, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) -> bool {\n\t\t// Check handle colinear state\n\t\tlet is_colinear = self\n\t\t\t.selection_status\n\t\t\t.angle()\n\t\t\t.map(|angle| match angle {\n\t\t\t\tManipulatorAngle::Colinear => true,\n\t\t\t\tManipulatorAngle::Free | ManipulatorAngle::Mixed => false,\n\t\t\t})\n\t\t\t.unwrap_or(false);\n\n\t\t// Check if the toggle_colinear key has just been pressed\n\t\tif toggle_colinear && !self.toggle_colinear_debounce {\n\t\t\tself.opposing_handle_lengths = None;\n\t\t\tif is_colinear {\n\t\t\t\tshape_editor.disable_colinear_handles_state_on_selected(&document.network_interface, responses);\n\t\t\t} else {\n\t\t\t\tshape_editor.convert_selected_manipulators_to_colinear_handles(responses, document);\n\t\t\t}\n\t\t\tself.toggle_colinear_debounce = true;\n\t\t\treturn true;\n\t\t}\n\t\tself.toggle_colinear_debounce = toggle_colinear;\n\n\t\tif equidistant && self.opposing_handle_lengths.is_none() {\n\t\t\tif !is_colinear {\n\t\t\t\t// Try to get selected handle info\n\t\t\t\tlet Some((_, _, selected_handle_id)) = self.try_get_selected_handle_and_anchor(shape_editor, document) else {\n\t\t\t\t\tself.opposing_handle_lengths = Some(shape_editor.opposing_handle_lengths(document));\n\t\t\t\t\treturn false;\n\t\t\t\t};\n\n\t\t\t\tlet Some((layer, _)) = shape_editor.selected_shape_state.iter().next() else {\n\t\t\t\t\tself.opposing_handle_lengths = Some(shape_editor.opposing_handle_lengths(document));\n\t\t\t\t\treturn false;\n\t\t\t\t};\n\n\t\t\t\tlet Some(vector) = document.network_interface.compute_modified_vector(*layer) else {\n\t\t\t\t\tself.opposing_handle_lengths = Some(shape_editor.opposing_handle_lengths(document));\n\t\t\t\t\treturn false;\n\t\t\t\t};\n\n\t\t\t\t// Check if handle has a pair (to ignore handles of edges of open paths)\n\t\t\t\tif let Some(handle_pair) = selected_handle_id.get_handle_pair(&vector) {\n\t\t\t\t\tlet opposite_handle_length = handle_pair.iter().filter(|&&h| h.to_manipulator_point() != selected_handle_id).find_map(|&h| {\n\t\t\t\t\t\tlet opp_handle_pos = h.to_manipulator_point().get_position(&vector)?;\n\t\t\t\t\t\tlet opp_anchor_id = h.to_manipulator_point().get_anchor(&vector)?;\n\t\t\t\t\t\tlet opp_anchor_pos = vector.point_domain.position_from_id(opp_anchor_id)?;\n\t\t\t\t\t\tSome((opp_handle_pos - opp_anchor_pos).length())\n\t\t\t\t\t});\n\n\t\t\t\t\t// Make handles colinear if opposite handle is zero length\n\t\t\t\t\tif opposite_handle_length == Some(0.) {\n\t\t\t\t\t\tshape_editor.convert_selected_manipulators_to_colinear_handles(responses, document);\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tself.opposing_handle_lengths = Some(shape_editor.opposing_handle_lengths(document));\n\t\t}\n\t\tfalse\n\t}\n\n\t/// Attempts to get a single selected handle. Also retrieves the position of the anchor it is connected to. Used for the purpose of snapping the angle.\n\tfn try_get_selected_handle_and_anchor(&self, shape_editor: &ShapeState, document: &DocumentMessageHandler) -> Option<(DVec2, DVec2, ManipulatorPointId)> {\n\t\t// Only count selections of a single layer\n\t\tlet (layer, selection) = shape_editor.selected_shape_state.iter().next()?;\n\n\t\t// Do not allow selections of multiple points to count\n\t\tif selection.selected_points_count() != 1 {\n\t\t\treturn None;\n\t\t}\n\n\t\t// Only count selected handles\n\t\tlet selected_handle = selection.selected_points().next()?.as_handle()?;\n\t\tlet handle_id = selected_handle.to_manipulator_point();\n\n\t\tlet layer_to_document = document.metadata().transform_to_document_if_feeds(*layer, &document.network_interface);\n\t\tlet vector = document.network_interface.compute_modified_vector(*layer)?;\n\n\t\tlet handle_position_local = selected_handle.to_manipulator_point().get_position(&vector)?;\n\t\tlet anchor_id = selected_handle.to_manipulator_point().get_anchor(&vector)?;\n\t\tlet anchor_position_local = vector.point_domain.position_from_id(anchor_id)?;\n\n\t\tlet handle_position_document = layer_to_document.transform_point2(handle_position_local);\n\t\tlet anchor_position_document = layer_to_document.transform_point2(anchor_position_local);\n\n\t\tSome((handle_position_document, anchor_position_document, handle_id))\n\t}\n\n\t#[allow(clippy::too_many_arguments)]\n\tfn calculate_handle_angle(\n\t\t&mut self,\n\t\tshape_editor: &mut ShapeState,\n\t\tdocument: &DocumentMessageHandler,\n\t\tresponses: &mut VecDeque<Message>,\n\t\trelative_vector: DVec2,\n\t\thandle_vector: DVec2,\n\t\thandle_id: ManipulatorPointId,\n\t\tlock_angle: bool,\n\t\tsnap_angle: bool,\n\t\ttangent_to_neighboring_tangents: bool,\n\t) -> f64 {\n\t\tlet current_angle = -handle_vector.angle_to(DVec2::X);\n\n\t\tif let Some((vector, layer)) = shape_editor\n\t\t\t.selected_shape_state\n\t\t\t.iter()\n\t\t\t.next()\n\t\t\t.and_then(|(layer, _)| document.network_interface.compute_modified_vector(*layer).map(|vector| (vector, layer)))\n\t\t{\n\t\t\tlet adjacent_anchor = check_handle_over_adjacent_anchor(handle_id, &vector);\n\t\t\tlet mut required_angle = None;\n\n\t\t\t// If the handle is dragged over one of its adjacent anchors while holding down the Ctrl key, compute the angle based on the tangent formed with the neighboring anchor points.\n\t\t\tif adjacent_anchor.is_some() && lock_angle && !self.angle_locked {\n\t\t\t\tlet anchor = handle_id.get_anchor(&vector);\n\t\t\t\tlet (angle, anchor_position) = calculate_adjacent_anchor_tangent(handle_id, anchor, adjacent_anchor, &vector);\n\n\t\t\t\tlet layer_to_document = document.metadata().transform_to_document_if_feeds(*layer, &document.network_interface);\n\n\t\t\t\tself.adjacent_anchor_offset = handle_id\n\t\t\t\t\t.get_anchor_position(&vector)\n\t\t\t\t\t.and_then(|handle_anchor| anchor_position.map(|adjacent_anchor| layer_to_document.transform_point2(adjacent_anchor) - layer_to_document.transform_point2(handle_anchor)));\n\n\t\t\t\trequired_angle = angle;\n\t\t\t}\n\n\t\t\t// If the handle is dragged near its adjacent anchors while holding down the Ctrl key, compute the angle using the tangent direction of neighboring segments.\n\t\t\tif relative_vector.length() < 25. && lock_angle && !self.angle_locked {\n\t\t\t\trequired_angle = calculate_lock_angle(self, shape_editor, responses, document, &vector, handle_id, tangent_to_neighboring_tangents);\n\t\t\t}\n\n\t\t\t// Finalize and apply angle locking if a valid target angle was determined.\n\t\t\tif let Some(angle) = required_angle {\n\t\t\t\tself.angle = angle;\n\t\t\t\tself.angle_locked = true;\n\t\t\t\treturn angle;\n\t\t\t}\n\t\t}\n\n\t\tif lock_angle && !self.angle_locked {\n\t\t\tself.angle_locked = true;\n\t\t\tself.angle = -relative_vector.angle_to(DVec2::X);\n\t\t\treturn -relative_vector.angle_to(DVec2::X);\n\t\t}\n\n\t\t// When the angle is locked we use the old angle\n\t\tif self.angle_locked {\n\t\t\treturn self.angle;\n\t\t}\n\n\t\t// Round the angle to the closest increment\n\t\tlet mut handle_angle = current_angle;\n\t\tif snap_angle && !lock_angle {\n\t\t\tlet snap_resolution = HANDLE_ROTATE_SNAP_ANGLE.to_radians();\n\t\t\thandle_angle = (handle_angle / snap_resolution).round() * snap_resolution;\n\t\t}\n\n\t\tself.angle = handle_angle;\n\n\t\thandle_angle\n\t}\n\n\t#[allow(clippy::too_many_arguments)]\n\tfn apply_snapping(\n\t\t&mut self,\n\t\thandle_direction: DVec2,\n\t\tnew_handle_position: DVec2,\n\t\tanchor_position: DVec2,\n\t\tusing_angle_constraints: bool,\n\t\thandle_position: DVec2,\n\t\tdocument: &DocumentMessageHandler,\n\t\tinput: &InputPreprocessorMessageHandler,\n\t\tviewport: &ViewportMessageHandler,\n\t) -> DVec2 {\n\t\tlet snap_data = SnapData::new(document, input, viewport);\n\t\tlet snap_point = SnapCandidatePoint::handle_neighbors(new_handle_position, [anchor_position]);\n\n\t\tlet snap_result = match using_angle_constraints {\n\t\t\ttrue => {\n\t\t\t\tlet snap_constraint = SnapConstraint::Line {\n\t\t\t\t\torigin: anchor_position,\n\t\t\t\t\tdirection: handle_direction.normalize_or_zero(),\n\t\t\t\t};\n\n\t\t\t\tself.snap_manager.constrained_snap(&snap_data, &snap_point, snap_constraint, Default::default())\n\t\t\t}\n\t\t\tfalse => self.snap_manager.free_snap(&snap_data, &snap_point, Default::default()),\n\t\t};\n\n\t\tself.snap_manager.update_indicator(snap_result.clone());\n\n\t\tdocument.metadata().document_to_viewport.transform_vector2(snap_result.snapped_point_document - handle_position)\n\t}\n\n\tfn start_snap_along_axis(&mut self, shape_editor: &mut ShapeState, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {\n\t\t// Find the negative delta to take the point to the drag start position\n\t\tlet current_mouse = input.mouse.position;\n\t\tlet drag_start = self.drag_start_pos;\n\t\tlet opposite_delta = drag_start - current_mouse;\n\n\t\tshape_editor.move_selected_points_and_segments(None, document, opposite_delta, false, true, false, None, false, responses);\n\n\t\t// Calculate the projected delta and shift the points along that delta\n\t\tlet delta = current_mouse - drag_start;\n\t\tlet axis = if delta.x.abs() >= delta.y.abs() { Axis::X } else { Axis::Y };\n\t\tself.snapping_axis = Some(axis);\n\t\tlet projected_delta = match axis {\n\t\t\tAxis::X => DVec2::new(delta.x, 0.),\n\t\t\tAxis::Y => DVec2::new(0., delta.y),\n\t\t\t_ => DVec2::new(delta.x, 0.),\n\t\t};\n\n\t\tshape_editor.move_selected_points_and_segments(None, document, projected_delta, false, true, false, None, false, responses);\n\t}\n\n\tfn stop_snap_along_axis(&mut self, shape_editor: &mut ShapeState, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {\n\t\t// Calculate the negative delta of the selection and move it back to the drag start\n\t\tlet current_mouse = input.mouse.position;\n\t\tlet drag_start = self.drag_start_pos;\n\n\t\tlet opposite_delta = drag_start - current_mouse;\n\t\tlet Some(axis) = self.snapping_axis else { return };\n\t\tlet opposite_projected_delta = match axis {\n\t\t\tAxis::X => DVec2::new(opposite_delta.x, 0.),\n\t\t\tAxis::Y => DVec2::new(0., opposite_delta.y),\n\t\t\t_ => DVec2::new(opposite_delta.x, 0.),\n\t\t};\n\n\t\tshape_editor.move_selected_points_and_segments(None, document, opposite_projected_delta, false, true, false, None, false, responses);\n\n\t\t// Calculate what actually would have been the original delta for the point, and apply that\n\t\tlet delta = current_mouse - drag_start;\n\n\t\tshape_editor.move_selected_points_and_segments(None, document, delta, false, true, false, None, false, responses);\n\n\t\tself.snapping_axis = None;\n\t}\n\n\tfn get_normalized_tangent(&mut self, point: PointId, segment: SegmentId, vector: &Vector) -> Option<DVec2> {\n\t\tlet other_point = vector.other_point(segment, point)?;\n\t\tlet position = ManipulatorPointId::Anchor(point).get_position(vector)?;\n\n\t\tlet mut handles = vector.all_connected(other_point);\n\t\tlet other_handle = handles.find(|handle| handle.segment == segment)?;\n\n\t\tlet target_position = if other_handle.length(vector) == 0. {\n\t\t\tManipulatorPointId::Anchor(other_point).get_position(vector)?\n\t\t} else {\n\t\t\tother_handle.to_manipulator_point().get_position(vector)?\n\t\t};\n\n\t\tlet tangent_vector = target_position - position;\n\t\ttangent_vector.try_normalize()\n\t}\n\n\tfn update_closest_segment(&mut self, shape_editor: &mut ShapeState, position: DVec2, document: &DocumentMessageHandler, path_overlay_mode: PathOverlayMode, point_editing_mode: bool) {\n\t\t// Check if there is no point nearby\n\t\t// If the point mode is deactivated then don't override closest segment even if there is a closer point\n\t\tif shape_editor\n\t\t\t.find_nearest_visible_point_indices(&document.network_interface, position, SELECTION_THRESHOLD, path_overlay_mode, self.frontier_handles_info.as_ref())\n\t\t\t.is_some()\n\t\t\t&& point_editing_mode\n\t\t{\n\t\t\tself.segment = None;\n\t\t}\n\t\t// If already hovering on a segment, then recalculate its closest point\n\t\telse if let Some(closest_segment) = &mut self.segment {\n\t\t\tclosest_segment.update_closest_point(document.metadata(), &document.network_interface, position);\n\n\t\t\tlet layer = closest_segment.layer();\n\t\t\tlet segment_id = closest_segment.segment();\n\n\t\t\tif closest_segment.too_far(position, SEGMENT_INSERTION_DISTANCE) {\n\t\t\t\tself.segment = None;\n\t\t\t}\n\n\t\t\t// Check if that segment exists or it has been removed\n\t\t\tif let Some(vector_data) = document.network_interface.compute_modified_vector(layer)\n\t\t\t\t&& !(vector_data.segment_domain.ids().contains(&segment_id))\n\t\t\t{\n\t\t\t\tself.segment = None;\n\t\t\t}\n\t\t}\n\t\t// If not, check that if there is some closest segment or not\n\t\telse if let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, position, SEGMENT_INSERTION_DISTANCE) {\n\t\t\tself.segment = Some(closest_segment);\n\t\t}\n\t}\n\n\tfn start_sliding_point(&mut self, shape_editor: &mut ShapeState, document: &DocumentMessageHandler) -> bool {\n\t\tlet single_anchor_selected = shape_editor.selected_points().count() == 1 && shape_editor.selected_points().any(|point| matches!(point, ManipulatorPointId::Anchor(_)));\n\n\t\tif single_anchor_selected {\n\t\t\tlet Some(anchor) = shape_editor.selected_points().next() else { return false };\n\t\t\tlet Some(layer) = document.network_interface.selected_nodes().selected_layers(document.metadata()).next() else {\n\t\t\t\treturn false;\n\t\t\t};\n\t\t\tlet Some(vector) = document.network_interface.compute_modified_vector(layer) else { return false };\n\n\t\t\tlet Some(point_id) = anchor.as_anchor() else { return false };\n\n\t\t\tlet mut connected_segments = [None, None];\n\t\t\tfor (segment, bezier, start, end) in vector.segment_iter() {\n\t\t\t\tif start == point_id || end == point_id {\n\t\t\t\t\tmatch (connected_segments[0], connected_segments[1]) {\n\t\t\t\t\t\t(None, None) => connected_segments[0] = Some(SlidingSegmentData { segment_id: segment, bezier, start }),\n\t\t\t\t\t\t(Some(_), None) => connected_segments[1] = Some(SlidingSegmentData { segment_id: segment, bezier, start }),\n\t\t\t\t\t\t_ => {\n\t\t\t\t\t\t\twarn!(\"more than two segments connected to the anchor point\");\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tlet connected_segments = if let [Some(seg1), Some(seg2)] = connected_segments {\n\t\t\t\t[seg1, seg2]\n\t\t\t} else {\n\t\t\t\twarn!(\"expected exactly two connected segments\");\n\t\t\t\treturn false;\n\t\t\t};\n\n\t\t\tself.sliding_point_info = Some(SlidingPointInfo {\n\t\t\t\tanchor: point_id,\n\t\t\t\tlayer,\n\t\t\t\tconnected_segments,\n\t\t\t});\n\t\t\treturn true;\n\t\t}\n\t\tfalse\n\t}\n\n\tfn slide_point(&mut self, target_position: DVec2, responses: &mut VecDeque<Message>, network_interface: &NodeNetworkInterface, shape_editor: &ShapeState) {\n\t\tlet Some(sliding_point_info) = self.sliding_point_info else { return };\n\t\tlet anchor = sliding_point_info.anchor;\n\t\tlet layer = sliding_point_info.layer;\n\n\t\tlet Some(vector) = network_interface.compute_modified_vector(layer) else { return };\n\t\tlet transform = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface);\n\t\tlet layer_pos = transform.inverse().transform_point2(target_position);\n\n\t\tlet segments = sliding_point_info.connected_segments;\n\n\t\tlet t1 = segments[0].bezier.nearest(dvec2_to_point(layer_pos), DEFAULT_ACCURACY).t;\n\t\tlet position1 = point_to_dvec2(segments[0].bezier.eval(t1));\n\n\t\tlet t2 = segments[1].bezier.nearest(dvec2_to_point(layer_pos), DEFAULT_ACCURACY).t;\n\t\tlet position2 = point_to_dvec2(segments[1].bezier.eval(t2));\n\n\t\tlet (closer_segment, further_segment, t_value, new_position) = if position2.distance(layer_pos) < position1.distance(layer_pos) {\n\t\t\t(segments[1], segments[0], t2, position2)\n\t\t} else {\n\t\t\t(segments[0], segments[1], t1, position1)\n\t\t};\n\n\t\t// Move the anchor to the new position\n\t\tlet Some(current_position) = ManipulatorPointId::Anchor(anchor).get_position(&vector) else {\n\t\t\treturn;\n\t\t};\n\t\tlet delta = new_position - current_position;\n\n\t\tshape_editor.move_anchor(anchor, &vector, delta, layer, None, responses);\n\n\t\t// Make a split at the t_value\n\t\tlet first = closer_segment.bezier.subsegment(0_f64..t_value);\n\t\tlet second = closer_segment.bezier.subsegment(t_value..1.);\n\n\t\tlet closer_segment_other_point = if anchor == closer_segment.start {\n\t\t\tcloser_segment.bezier.end()\n\t\t} else {\n\t\t\tcloser_segment.bezier.start()\n\t\t};\n\n\t\tlet (split_segment, other_segment) = if first.start() == closer_segment_other_point { (first, second) } else { (second, first) };\n\t\tlet split_segment_points = pathseg_points(split_segment);\n\n\t\t// Primary handle maps to primary handle and secondary maps to secondary\n\t\tlet closer_primary_handle = HandleId::primary(closer_segment.segment_id);\n\t\tlet Some(handle_position) = split_segment_points.p1 else { return };\n\t\tlet relative_position1 = handle_position - split_segment_points.p0;\n\t\tlet modification_type = closer_primary_handle.set_relative_position(relative_position1);\n\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\n\t\tlet closer_secondary_handle = HandleId::end(closer_segment.segment_id);\n\t\tlet Some(handle_position) = split_segment_points.p2 else { return };\n\t\tlet relative_position2 = handle_position - split_segment_points.p3;\n\t\tlet modification_type = closer_secondary_handle.set_relative_position(relative_position2);\n\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\n\t\tlet end_handle_direction = if anchor == closer_segment.start { -relative_position1 } else { -relative_position2 };\n\n\t\tlet further_segment_points = pathseg_points(further_segment.bezier);\n\n\t\tlet (further_other_point, start_handle, end_handle, start_handle_pos) = if anchor == further_segment.start {\n\t\t\t(\n\t\t\t\tfurther_segment_points.p3,\n\t\t\t\tHandleId::end(further_segment.segment_id),\n\t\t\t\tHandleId::primary(further_segment.segment_id),\n\t\t\t\tfurther_segment_points.p2,\n\t\t\t)\n\t\t} else {\n\t\t\t(\n\t\t\t\tfurther_segment_points.p0,\n\t\t\t\tHandleId::primary(further_segment.segment_id),\n\t\t\t\tHandleId::end(further_segment.segment_id),\n\t\t\t\tfurther_segment_points.p1,\n\t\t\t)\n\t\t};\n\t\tlet Some(start_handle_position) = start_handle_pos else { return };\n\t\tlet start_handle_direction = start_handle_position - further_other_point;\n\n\t\t// Get normalized direction vectors, if cubic handle is zero then we consider corresponding tangent\n\t\tlet d1 = start_handle_direction.try_normalize().unwrap_or({\n\t\t\tif anchor == further_segment.start {\n\t\t\t\t-pathseg_tangent(further_segment.bezier, 1.)\n\t\t\t} else {\n\t\t\t\tpathseg_tangent(further_segment.bezier, 0.)\n\t\t\t}\n\t\t});\n\n\t\tlet d2 = end_handle_direction.try_normalize().unwrap_or_default();\n\n\t\tlet min_len1 = start_handle_direction.length() * 0.4;\n\t\tlet min_len2 = end_handle_direction.length() * 0.4;\n\n\t\tlet (relative_pos1, relative_pos2) = find_two_param_best_approximate(further_other_point, new_position, d1, d2, min_len1, min_len2, further_segment.bezier, other_segment);\n\n\t\t// Now set those handles to these handle lengths keeping the directions d1, d2\n\t\tlet modification_type = start_handle.set_relative_position(relative_pos1);\n\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\n\t\tlet modification_type = end_handle.set_relative_position(relative_pos2);\n\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t}\n\n\t#[allow(clippy::too_many_arguments)]\n\tfn drag(\n\t\t&mut self,\n\t\tequidistant: bool,\n\t\tlock_angle: bool,\n\t\tsnap_angle: bool,\n\t\tsnap_axis: bool,\n\t\tshape_editor: &mut ShapeState,\n\t\tdocument: &DocumentMessageHandler,\n\t\tinput: &InputPreprocessorMessageHandler,\n\t\tviewport: &ViewportMessageHandler,\n\t\tresponses: &mut VecDeque<Message>,\n\t) {\n\t\t// First check if selection is not just a single handle point\n\t\tlet selected_points = shape_editor.selected_points();\n\t\tlet single_handle_selected = selected_points.count() == 1\n\t\t\t&& shape_editor\n\t\t\t\t.selected_points()\n\t\t\t\t.any(|point| matches!(point, ManipulatorPointId::EndHandle(_) | ManipulatorPointId::PrimaryHandle(_)));\n\n\t\t// This is where it starts snapping along axis\n\t\tif snap_axis && self.snapping_axis.is_none() && !single_handle_selected {\n\t\t\tself.start_snap_along_axis(shape_editor, document, input, responses);\n\t\t} else if !snap_axis && self.snapping_axis.is_some() {\n\t\t\tself.stop_snap_along_axis(shape_editor, document, input, responses);\n\t\t}\n\n\t\tlet document_to_viewport = document.metadata().document_to_viewport;\n\t\tlet previous_mouse = document_to_viewport.transform_point2(self.previous_mouse_position);\n\t\tlet current_mouse = input.mouse.position;\n\t\tlet raw_delta = document_to_viewport.inverse().transform_vector2(current_mouse - previous_mouse);\n\n\t\tlet snapped_delta = if let Some((handle_position, anchor_position, handle_id)) = self.try_get_selected_handle_and_anchor(shape_editor, document) {\n\t\t\tlet cursor_position = handle_position + raw_delta;\n\n\t\t\tlet handle_angle = self.calculate_handle_angle(\n\t\t\t\tshape_editor,\n\t\t\t\tdocument,\n\t\t\t\tresponses,\n\t\t\t\thandle_position - anchor_position,\n\t\t\t\tcursor_position - anchor_position,\n\t\t\t\thandle_id,\n\t\t\t\tlock_angle,\n\t\t\t\tsnap_angle,\n\t\t\t\tequidistant,\n\t\t\t);\n\n\t\t\tlet adjacent_anchor_offset = self.adjacent_anchor_offset.unwrap_or(DVec2::ZERO);\n\t\t\tlet constrained_direction = DVec2::new(handle_angle.cos(), handle_angle.sin());\n\t\t\tlet projected_length = (cursor_position - anchor_position - adjacent_anchor_offset).dot(constrained_direction);\n\t\t\tlet constrained_target = anchor_position + adjacent_anchor_offset + constrained_direction * projected_length;\n\t\t\tlet constrained_delta = constrained_target - handle_position;\n\n\t\t\tself.apply_snapping(\n\t\t\t\tconstrained_direction,\n\t\t\t\thandle_position + constrained_delta,\n\t\t\t\tanchor_position + adjacent_anchor_offset,\n\t\t\t\tlock_angle || snap_angle,\n\t\t\t\thandle_position,\n\t\t\t\tdocument,\n\t\t\t\tinput,\n\t\t\t\tviewport,\n\t\t\t)\n\t\t} else {\n\t\t\tshape_editor.snap(&mut self.snap_manager, &self.snap_cache, document, input, viewport, previous_mouse)\n\t\t};\n\n\t\tlet handle_lengths = if equidistant { None } else { self.opposing_handle_lengths.take() };\n\t\tlet opposite = if lock_angle { None } else { self.opposite_handle_position };\n\t\tlet unsnapped_delta = current_mouse - previous_mouse;\n\t\tlet mut was_alt_dragging = false;\n\n\t\tif self.snapping_axis.is_none() {\n\t\t\tif self.alt_clicked_on_anchor && !self.alt_dragging_from_anchor && self.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD {\n\t\t\t\t// Checking which direction the dragging begins\n\t\t\t\tself.alt_dragging_from_anchor = true;\n\t\t\t\tlet Some(layer) = document.network_interface.selected_nodes().selected_layers(document.metadata()).next() else {\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tlet Some(vector) = document.network_interface.compute_modified_vector(layer) else { return };\n\t\t\t\tlet Some(point_id) = shape_editor.selected_points().next().unwrap().get_anchor(&vector) else {\n\t\t\t\t\treturn;\n\t\t\t\t};\n\n\t\t\t\tif vector.connected_count(point_id) == 2 {\n\t\t\t\t\tlet connected_segments: Vec<HandleId> = vector.all_connected(point_id).collect();\n\t\t\t\t\tlet segment1 = connected_segments[0];\n\t\t\t\t\tlet Some(tangent1) = self.get_normalized_tangent(point_id, segment1.segment, &vector) else {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t};\n\t\t\t\t\tlet segment2 = connected_segments[1];\n\t\t\t\t\tlet Some(tangent2) = self.get_normalized_tangent(point_id, segment2.segment, &vector) else {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t};\n\n\t\t\t\t\tlet delta = input.mouse.position - self.drag_start_pos;\n\t\t\t\t\tlet handle = if delta.dot(tangent1) >= delta.dot(tangent2) {\n\t\t\t\t\t\tsegment1.to_manipulator_point()\n\t\t\t\t\t} else {\n\t\t\t\t\t\tsegment2.to_manipulator_point()\n\t\t\t\t\t};\n\n\t\t\t\t\t// Now change the selection to this handle\n\t\t\t\t\tshape_editor.deselect_all_points();\n\t\t\t\t\tshape_editor.select_point_by_layer_and_id(handle, layer);\n\n\t\t\t\t\tresponses.add(PathToolMessage::SelectionChanged);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif self.alt_dragging_from_anchor && !equidistant && self.alt_clicked_on_anchor {\n\t\t\t\twas_alt_dragging = true;\n\t\t\t\tself.alt_dragging_from_anchor = false;\n\t\t\t\tself.alt_clicked_on_anchor = false;\n\t\t\t}\n\n\t\t\tlet mut skip_opposite = false;\n\t\t\tif self.temporary_colinear_handles && !lock_angle {\n\t\t\t\tshape_editor.disable_colinear_handles_state_on_selected(&document.network_interface, responses);\n\t\t\t\tself.temporary_colinear_handles = false;\n\t\t\t\tskip_opposite = true;\n\t\t\t}\n\t\t\tshape_editor.move_selected_points_and_segments(handle_lengths, document, snapped_delta, equidistant, true, was_alt_dragging, opposite, skip_opposite, responses);\n\t\t\tself.previous_mouse_position += document_to_viewport.inverse().transform_vector2(snapped_delta);\n\t\t} else {\n\t\t\tlet Some(axis) = self.snapping_axis else { return };\n\t\t\tlet projected_delta = match axis {\n\t\t\t\tAxis::X => DVec2::new(unsnapped_delta.x, 0.),\n\t\t\t\tAxis::Y => DVec2::new(0., unsnapped_delta.y),\n\t\t\t\t_ => DVec2::new(unsnapped_delta.x, 0.),\n\t\t\t};\n\t\t\tshape_editor.move_selected_points_and_segments(handle_lengths, document, projected_delta, equidistant, true, false, opposite, false, responses);\n\t\t\tself.previous_mouse_position += document_to_viewport.inverse().transform_vector2(unsnapped_delta);\n\t\t}\n\n\t\t// Constantly checking and changing the snapping axis based on current mouse position\n\t\tif snap_axis && self.snapping_axis.is_some() {\n\t\t\tlet Some(current_axis) = self.snapping_axis else { return };\n\t\t\tlet total_delta = self.drag_start_pos - input.mouse.position;\n\n\t\t\tif (total_delta.x.abs() > total_delta.y.abs() && current_axis == Axis::Y) || (total_delta.y.abs() > total_delta.x.abs() && current_axis == Axis::X) {\n\t\t\t\tself.stop_snap_along_axis(shape_editor, document, input, responses);\n\t\t\t\tself.start_snap_along_axis(shape_editor, document, input, responses);\n\t\t\t}\n\t\t}\n\t}\n\n\tfn pivot_gizmo(&self) -> PivotGizmo {\n\t\tself.pivot_gizmo.clone()\n\t}\n\n\tfn sync_history(&mut self, points: &[ManipulatorPointId]) {\n\t\tself.ordered_points.retain(|layer| points.contains(layer));\n\t\tself.ordered_points.extend(points.iter().find(|&layer| !self.ordered_points.contains(layer)));\n\t\tself.pivot_gizmo.point = self.ordered_points.last().copied()\n\t}\n}\n\nimpl Fsm for PathToolFsmState {\n\ttype ToolData = PathToolData;\n\ttype ToolOptions = PathToolOptions;\n\n\tfn transition(\n\t\tself,\n\t\tevent: ToolMessage,\n\t\ttool_data: &mut Self::ToolData,\n\t\ttool_action_data: &mut ToolActionMessageContext,\n\t\ttool_options: &Self::ToolOptions,\n\t\tresponses: &mut VecDeque<Message>,\n\t) -> Self {\n\t\tlet ToolActionMessageContext {\n\t\t\tdocument,\n\t\t\tinput,\n\t\t\tviewport,\n\t\t\tshape_editor,\n\t\t\t..\n\t\t} = tool_action_data;\n\n\t\tupdate_dynamic_hints(self, responses, shape_editor, document, tool_data, tool_options, input.mouse.position);\n\n\t\tlet ToolMessage::Path(event) = event else { return self };\n\n\t\t// TODO(mTvare6): Remove once gizmos are implemented for path_tool\n\t\ttool_data.pivot_gizmo.state.enabled = false;\n\n\t\tmatch (self, event) {\n\t\t\t(_, PathToolMessage::SelectionChanged) => {\n\t\t\t\t// Set the newly targeted layers to visible\n\t\t\t\tlet target_layers = document.network_interface.selected_nodes().selected_layers(document.metadata()).collect();\n\n\t\t\t\tshape_editor.set_selected_layers(target_layers);\n\n\t\t\t\tlet new_state = make_path_editable_is_allowed(&mut document.network_interface).is_some();\n\t\t\t\tif tool_data.make_path_editable_is_allowed != new_state {\n\t\t\t\t\tresponses.add(MenuBarMessage::SendLayout);\n\t\t\t\t}\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\tself\n\t\t\t}\n\t\t\t(_, PathToolMessage::UpdateSelectedPointsStatus { overlay_context }) => {\n\t\t\t\tlet display_anchors = overlay_context.visibility_settings.anchors();\n\t\t\t\tlet display_handles = overlay_context.visibility_settings.handles();\n\n\t\t\t\tshape_editor.update_selected_anchors_status(display_anchors);\n\t\t\t\tshape_editor.update_selected_handles_status(display_handles);\n\n\t\t\t\tlet new_points = shape_editor.selected_points().copied().collect::<Vec<_>>();\n\t\t\t\ttool_data.sync_history(&new_points);\n\n\t\t\t\tself\n\t\t\t}\n\t\t\t(_, PathToolMessage::TogglePointEditing) => {\n\t\t\t\t// Clicked on the point edit mode button\n\t\t\t\tlet point_edit = tool_options.path_editing_mode.point_editing_mode;\n\t\t\t\tlet segment_edit = tool_options.path_editing_mode.segment_editing_mode;\n\t\t\t\tlet multiple_toggle = tool_data.multiple_toggle_pressed;\n\n\t\t\t\tif point_edit && !segment_edit {\n\t\t\t\t\treturn self;\n\t\t\t\t}\n\n\t\t\t\tmatch (multiple_toggle, point_edit) {\n\t\t\t\t\t(true, true) => {\n\t\t\t\t\t\tresponses.add(PathToolMessage::UpdateOptions {\n\t\t\t\t\t\t\toptions: PathOptionsUpdate::PointEditingMode { enabled: false },\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\t(true, false) => {\n\t\t\t\t\t\tresponses.add(PathToolMessage::UpdateOptions {\n\t\t\t\t\t\t\toptions: PathOptionsUpdate::PointEditingMode { enabled: true },\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\t(_, _) => {\n\t\t\t\t\t\tresponses.add(PathToolMessage::UpdateOptions {\n\t\t\t\t\t\t\toptions: PathOptionsUpdate::PointEditingMode { enabled: true },\n\t\t\t\t\t\t});\n\t\t\t\t\t\tresponses.add(PathToolMessage::UpdateOptions {\n\t\t\t\t\t\t\toptions: PathOptionsUpdate::SegmentEditingMode { enabled: false },\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// Select all of the end points of selected segments\n\t\t\t\t\t\tlet selected_layers = shape_editor.selected_layers().cloned().collect::<Vec<_>>();\n\n\t\t\t\t\t\tfor layer in selected_layers {\n\t\t\t\t\t\t\tlet Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };\n\n\t\t\t\t\t\t\tlet selected_state = shape_editor.selected_shape_state.entry(layer).or_default();\n\n\t\t\t\t\t\t\tfor (segment, _, start, end) in vector.segment_bezier_iter() {\n\t\t\t\t\t\t\t\tif selected_state.is_segment_selected(segment) {\n\t\t\t\t\t\t\t\t\tselected_state.select_point(ManipulatorPointId::Anchor(start));\n\t\t\t\t\t\t\t\t\tselected_state.select_point(ManipulatorPointId::Anchor(end));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Deselect all of the segments\n\t\t\t\t\t\tshape_editor.deselect_all_segments();\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tself\n\t\t\t}\n\t\t\t(_, PathToolMessage::ToggleSegmentEditing) => {\n\t\t\t\t// Clicked on the point edit mode button\n\t\t\t\tlet segment_edit = tool_options.path_editing_mode.segment_editing_mode;\n\t\t\t\tlet point_edit = tool_options.path_editing_mode.point_editing_mode;\n\n\t\t\t\tlet multiple_toggle = tool_data.multiple_toggle_pressed;\n\n\t\t\t\tif segment_edit && !point_edit {\n\t\t\t\t\treturn self;\n\t\t\t\t}\n\n\t\t\t\tmatch (multiple_toggle, segment_edit) {\n\t\t\t\t\t(true, true) => {\n\t\t\t\t\t\tresponses.add(PathToolMessage::UpdateOptions {\n\t\t\t\t\t\t\toptions: PathOptionsUpdate::SegmentEditingMode { enabled: false },\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\t(true, false) => {\n\t\t\t\t\t\tresponses.add(PathToolMessage::UpdateOptions {\n\t\t\t\t\t\t\toptions: PathOptionsUpdate::SegmentEditingMode { enabled: true },\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\t(_, _) => {\n\t\t\t\t\t\tresponses.add(PathToolMessage::UpdateOptions {\n\t\t\t\t\t\t\toptions: PathOptionsUpdate::PointEditingMode { enabled: false },\n\t\t\t\t\t\t});\n\t\t\t\t\t\tresponses.add(PathToolMessage::UpdateOptions {\n\t\t\t\t\t\t\toptions: PathOptionsUpdate::SegmentEditingMode { enabled: true },\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// Select all the segments which have both of the ends selected\n\t\t\t\t\t\tlet selected_layers = shape_editor.selected_layers().cloned().collect::<Vec<_>>();\n\n\t\t\t\t\t\tfor layer in selected_layers {\n\t\t\t\t\t\t\tlet Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };\n\n\t\t\t\t\t\t\tlet selected_state = shape_editor.selected_shape_state.entry(layer).or_default();\n\n\t\t\t\t\t\t\tfor (segment, _, start, end) in vector.segment_bezier_iter() {\n\t\t\t\t\t\t\t\tlet first_selected = selected_state.is_point_selected(ManipulatorPointId::Anchor(start));\n\t\t\t\t\t\t\t\tlet second_selected = selected_state.is_point_selected(ManipulatorPointId::Anchor(end));\n\t\t\t\t\t\t\t\tif first_selected && second_selected {\n\t\t\t\t\t\t\t\t\tselected_state.select_segment(segment);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tself\n\t\t\t}\n\t\t\t(_, PathToolMessage::Overlays { context: mut overlay_context }) => {\n\t\t\t\t// Set this to show ghost line only if drag actually happened\n\t\t\t\tif matches!(self, Self::Dragging(_)) && tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD {\n\t\t\t\t\tfor (outline, layer) in &tool_data.ghost_outline {\n\t\t\t\t\t\tlet transform = document.metadata().transform_to_viewport(*layer);\n\t\t\t\t\t\toverlay_context.outline(outline.iter(), transform, Some(COLOR_OVERLAY_GRAY));\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// TODO: find the segment ids of which the selected points are a part of\n\n\t\t\t\tmatch tool_options.path_overlay_mode {\n\t\t\t\t\tPathOverlayMode::AllHandles => {\n\t\t\t\t\t\tpath_overlays(document, DrawHandles::All, shape_editor, &mut overlay_context);\n\t\t\t\t\t\ttool_data.frontier_handles_info = None;\n\t\t\t\t\t}\n\t\t\t\t\tPathOverlayMode::SelectedPointHandles => {\n\t\t\t\t\t\tlet selected_segments = selected_segments(&document.network_interface, shape_editor);\n\n\t\t\t\t\t\tpath_overlays(document, DrawHandles::SelectedAnchors(selected_segments), shape_editor, &mut overlay_context);\n\t\t\t\t\t\ttool_data.frontier_handles_info = None;\n\t\t\t\t\t}\n\t\t\t\t\tPathOverlayMode::FrontierHandles => {\n\t\t\t\t\t\tlet selected_segments = selected_segments(&document.network_interface, shape_editor);\n\n\t\t\t\t\t\t// Match the behavior of `PathOverlayMode::SelectedPointHandles` when only one point is selected\n\t\t\t\t\t\tif shape_editor.selected_points().count() == 1 {\n\t\t\t\t\t\t\tpath_overlays(document, DrawHandles::SelectedAnchors(selected_segments), shape_editor, &mut overlay_context);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tlet mut segment_endpoints_by_layer = HashMap::new();\n\n\t\t\t\t\t\t\tfor layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) {\n\t\t\t\t\t\t\t\tlet mut segment_endpoints: HashMap<SegmentId, Vec<PointId>> = HashMap::new();\n\n\t\t\t\t\t\t\t\tlet Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };\n\t\t\t\t\t\t\t\tlet Some(state) = shape_editor.selected_shape_state.get_mut(&layer) else { continue };\n\n\t\t\t\t\t\t\t\tlet selected_points = state.selected_points();\n\t\t\t\t\t\t\t\tlet selected_anchors = selected_points\n\t\t\t\t\t\t\t\t\t.filter_map(|point_id| if let ManipulatorPointId::Anchor(p) = point_id { Some(p) } else { None })\n\t\t\t\t\t\t\t\t\t.collect::<Vec<_>>();\n\n\t\t\t\t\t\t\t\tlet Some(focused_segments) = selected_segments.get(&layer) else { continue };\n\n\t\t\t\t\t\t\t\t// The points which are part of only one segment will be rendered\n\t\t\t\t\t\t\t\tlet mut selected_segments_by_point: HashMap<PointId, Vec<SegmentId>> = HashMap::new();\n\n\t\t\t\t\t\t\t\tfor (segment_id, _bezier, start, end) in vector.segment_bezier_iter() {\n\t\t\t\t\t\t\t\t\tif focused_segments.contains(&segment_id) {\n\t\t\t\t\t\t\t\t\t\tselected_segments_by_point.entry(start).or_default().push(segment_id);\n\t\t\t\t\t\t\t\t\t\tselected_segments_by_point.entry(end).or_default().push(segment_id);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tfor (point, attached_segments) in selected_segments_by_point {\n\t\t\t\t\t\t\t\t\tif attached_segments.len() == 1 {\n\t\t\t\t\t\t\t\t\t\tsegment_endpoints.entry(attached_segments[0]).or_default().push(point);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t// Handle the edge case where a point, although not explicitly selected, is shared by two segments.\n\t\t\t\t\t\t\t\t\telse if !selected_anchors.contains(&point) {\n\t\t\t\t\t\t\t\t\t\tsegment_endpoints.entry(attached_segments[0]).or_default().push(point);\n\t\t\t\t\t\t\t\t\t\tsegment_endpoints.entry(attached_segments[1]).or_default().push(point);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tsegment_endpoints_by_layer.insert(layer, segment_endpoints);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Caching segment endpoints for use in point selection logic\n\t\t\t\t\t\t\ttool_data.frontier_handles_info = Some(segment_endpoints_by_layer.clone());\n\n\t\t\t\t\t\t\t// Now frontier anchors can be sent for rendering overlays\n\t\t\t\t\t\t\tpath_overlays(document, DrawHandles::FrontierHandles(segment_endpoints_by_layer), shape_editor, &mut overlay_context);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tmatch self {\n\t\t\t\t\tSelf::Ready => {\n\t\t\t\t\t\ttool_data.update_closest_segment(\n\t\t\t\t\t\t\tshape_editor,\n\t\t\t\t\t\t\tinput.mouse.position,\n\t\t\t\t\t\t\tdocument,\n\t\t\t\t\t\t\ttool_options.path_overlay_mode,\n\t\t\t\t\t\t\ttool_options.path_editing_mode.point_editing_mode,\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\t// If there exists an underlying anchor, we show a hover overlay\n\t\t\t\t\t\t(|| {\n\t\t\t\t\t\t\tif !tool_options.path_editing_mode.point_editing_mode {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tlet nearest_visible_point_indices = shape_editor.find_nearest_visible_point_indices(\n\t\t\t\t\t\t\t\t&document.network_interface,\n\t\t\t\t\t\t\t\tinput.mouse.position,\n\t\t\t\t\t\t\t\tSELECTION_THRESHOLD,\n\t\t\t\t\t\t\t\ttool_options.path_overlay_mode,\n\t\t\t\t\t\t\t\ttool_data.frontier_handles_info.as_ref(),\n\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\tlet Some((layer, manipulator_point_id)) = nearest_visible_point_indices else { return };\n\t\t\t\t\t\t\tlet Some(vector) = document.network_interface.compute_modified_vector(layer) else { return };\n\t\t\t\t\t\t\tlet Some(position) = manipulator_point_id.get_position(&vector) else {\n\t\t\t\t\t\t\t\terror!(\"No position for hovered point\");\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\tlet transform = document.metadata().transform_to_viewport(layer);\n\t\t\t\t\t\t\tlet position = transform.transform_point2(position);\n\t\t\t\t\t\t\tlet selected = shape_editor.selected_shape_state.entry(layer).or_default().is_point_selected(manipulator_point_id);\n\n\t\t\t\t\t\t\tmatch manipulator_point_id {\n\t\t\t\t\t\t\t\tManipulatorPointId::Anchor(_) => overlay_context.hover_manipulator_anchor(position, selected),\n\t\t\t\t\t\t\t\t_ => overlay_context.hover_manipulator_handle(position, selected),\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})();\n\n\t\t\t\t\t\tif let Some(closest_segment) = &tool_data.segment {\n\t\t\t\t\t\t\tif tool_options.path_editing_mode.segment_editing_mode && !tool_data.segment_editing_modifier {\n\t\t\t\t\t\t\t\tlet transform = document.metadata().transform_to_viewport_if_feeds(closest_segment.layer(), &document.network_interface);\n\n\t\t\t\t\t\t\t\toverlay_context.outline_overlay_bezier(closest_segment.pathseg(), transform);\n\n\t\t\t\t\t\t\t\t// Draw the anchors again\n\t\t\t\t\t\t\t\tlet display_anchors = overlay_context.visibility_settings.anchors();\n\t\t\t\t\t\t\t\tif display_anchors {\n\t\t\t\t\t\t\t\t\tlet start_pos = transform.transform_point2(point_to_dvec2(closest_segment.pathseg().start()));\n\t\t\t\t\t\t\t\t\tlet end_pos = transform.transform_point2(point_to_dvec2(closest_segment.pathseg().end()));\n\t\t\t\t\t\t\t\t\tlet start_id = closest_segment.points()[0];\n\t\t\t\t\t\t\t\t\tlet end_id = closest_segment.points()[1];\n\t\t\t\t\t\t\t\t\tif let Some(shape_state) = shape_editor.selected_shape_state.get_mut(&closest_segment.layer()) {\n\t\t\t\t\t\t\t\t\t\toverlay_context.manipulator_anchor(start_pos, shape_state.is_point_selected(ManipulatorPointId::Anchor(start_id)), None);\n\t\t\t\t\t\t\t\t\t\toverlay_context.manipulator_anchor(end_pos, shape_state.is_point_selected(ManipulatorPointId::Anchor(end_id)), None);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// We want this overlay also when in segment_editing_mode\n\t\t\t\t\t\t\t\tlet perp = closest_segment.calculate_perp(document);\n\t\t\t\t\t\t\t\tlet point = closest_segment.closest_point(document.metadata(), &document.network_interface);\n\n\t\t\t\t\t\t\t\t// Draw an X on the segment\n\t\t\t\t\t\t\t\tif tool_data.delete_segment_pressed {\n\t\t\t\t\t\t\t\t\tlet angle = 45_f64.to_radians();\n\t\t\t\t\t\t\t\t\tlet tilted_line = DVec2::from_angle(angle).rotate(perp);\n\t\t\t\t\t\t\t\t\tlet tilted_perp = tilted_line.perp();\n\n\t\t\t\t\t\t\t\t\toverlay_context.line(point - tilted_line * SEGMENT_OVERLAY_SIZE, point + tilted_line * SEGMENT_OVERLAY_SIZE, Some(COLOR_OVERLAY_BLUE), None);\n\t\t\t\t\t\t\t\t\toverlay_context.line(point - tilted_perp * SEGMENT_OVERLAY_SIZE, point + tilted_perp * SEGMENT_OVERLAY_SIZE, Some(COLOR_OVERLAY_BLUE), None);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t// Draw a line on the segment\n\t\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\t\toverlay_context.line(point - perp * SEGMENT_OVERLAY_SIZE, point + perp * SEGMENT_OVERLAY_SIZE, Some(COLOR_OVERLAY_BLUE), None);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Show outlines for hovered layers with appropriate highlighting\n\t\t\t\t\t\tlet currently_selected_layer = document.network_interface.selected_nodes().selected_layers(document.metadata()).next();\n\t\t\t\t\t\tlet next_selected_index = tool_data.peek_drill_through_index();\n\t\t\t\t\t\tlet mouse_has_moved = tool_data.has_drill_through_mouse_moved(input.mouse.position);\n\n\t\t\t\t\t\tfor (index, &hovered_layer) in tool_data.hovered_layers.iter().enumerate() {\n\t\t\t\t\t\t\t// Skip already highlighted selected layer\n\t\t\t\t\t\t\tif Some(hovered_layer) == currently_selected_layer {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tlet layer_to_viewport = document.metadata().transform_to_viewport(hovered_layer);\n\t\t\t\t\t\t\tlet outline = document.metadata().layer_with_free_points_outline(hovered_layer);\n\n\t\t\t\t\t\t\t// Determine highlight color based on drill-through state\n\t\t\t\t\t\t\tlet color = match (index, mouse_has_moved) {\n\t\t\t\t\t\t\t\t// If the layer is the next selected one and mouse has not moved, highlight it blue\n\t\t\t\t\t\t\t\t(i, false) if i == next_selected_index => COLOR_OVERLAY_BLUE,\n\t\t\t\t\t\t\t\t// If the layer is the first hovered one and mouse has moved, highlight it blue\n\t\t\t\t\t\t\t\t(0, true) => COLOR_OVERLAY_BLUE,\n\t\t\t\t\t\t\t\t// Otherwise, use gray\n\t\t\t\t\t\t\t\t_ => COLOR_OVERLAY_GRAY,\n\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\t// TODO: Make this draw underneath all other overlays\n\t\t\t\t\t\t\toverlay_context.outline(outline, layer_to_viewport, Some(color));\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tSelf::Drawing { selection_shape } => {\n\t\t\t\t\t\tlet fill_color = Some(COLOR_OVERLAY_BLUE_05);\n\n\t\t\t\t\t\tlet selection_mode = match tool_action_data.preferences.get_selection_mode() {\n\t\t\t\t\t\t\tSelectionMode::Directional => tool_data.calculate_selection_mode_from_direction(document.metadata()),\n\t\t\t\t\t\t\tselection_mode => selection_mode,\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tlet quad = tool_data.selection_quad(document.metadata());\n\n\t\t\t\t\t\tlet select_segments = tool_options.path_editing_mode.segment_editing_mode;\n\t\t\t\t\t\tlet select_points = tool_options.path_editing_mode.point_editing_mode;\n\t\t\t\t\t\tlet (points_inside, segments_inside) = match selection_shape {\n\t\t\t\t\t\t\tSelectionShapeType::Box => {\n\t\t\t\t\t\t\t\tlet previous_mouse = document.metadata().document_to_viewport.transform_point2(tool_data.previous_mouse_position);\n\t\t\t\t\t\t\t\tlet bbox = Rect::new(tool_data.drag_start_pos.x, tool_data.drag_start_pos.y, previous_mouse.x, previous_mouse.y).abs();\n\t\t\t\t\t\t\t\tshape_editor.get_inside_points_and_segments(\n\t\t\t\t\t\t\t\t\t&document.network_interface,\n\t\t\t\t\t\t\t\t\tSelectionShape::Box(bbox),\n\t\t\t\t\t\t\t\t\ttool_options.path_overlay_mode,\n\t\t\t\t\t\t\t\t\ttool_data.frontier_handles_info.as_ref(),\n\t\t\t\t\t\t\t\t\tselect_segments,\n\t\t\t\t\t\t\t\t\tselect_points,\n\t\t\t\t\t\t\t\t\tselection_mode,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tSelectionShapeType::Lasso => shape_editor.get_inside_points_and_segments(\n\t\t\t\t\t\t\t\t&document.network_interface,\n\t\t\t\t\t\t\t\tSelectionShape::Lasso(&tool_data.lasso_polygon),\n\t\t\t\t\t\t\t\ttool_options.path_overlay_mode,\n\t\t\t\t\t\t\t\ttool_data.frontier_handles_info.as_ref(),\n\t\t\t\t\t\t\t\tselect_segments,\n\t\t\t\t\t\t\t\tselect_points,\n\t\t\t\t\t\t\t\tselection_mode,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tfor (layer, points) in points_inside {\n\t\t\t\t\t\t\tlet Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };\n\n\t\t\t\t\t\t\tfor point in points {\n\t\t\t\t\t\t\t\tlet Some(position) = point.get_position(&vector) else { continue };\n\n\t\t\t\t\t\t\t\tlet transform = document.metadata().transform_to_viewport(layer);\n\t\t\t\t\t\t\t\tlet position = transform.transform_point2(position);\n\n\t\t\t\t\t\t\t\tlet selected = shape_editor.selected_shape_state.entry(layer).or_default().is_point_selected(point);\n\n\t\t\t\t\t\t\t\tmatch point {\n\t\t\t\t\t\t\t\t\tManipulatorPointId::Anchor(_) => overlay_context.hover_manipulator_anchor(position, selected),\n\t\t\t\t\t\t\t\t\t_ => overlay_context.hover_manipulator_handle(position, selected),\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tfor (layer, segments) in segments_inside {\n\t\t\t\t\t\t\tlet Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };\n\n\t\t\t\t\t\t\tlet transform = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);\n\n\t\t\t\t\t\t\tfor (segment, bezier, _, _) in vector.segment_iter() {\n\t\t\t\t\t\t\t\tif segments.contains(&segment) {\n\t\t\t\t\t\t\t\t\toverlay_context.outline_overlay_bezier(bezier, transform);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tmatch (selection_shape, selection_mode, tool_data.started_drawing_from_inside) {\n\t\t\t\t\t\t\t// Don't draw box if it is from inside a shape and selection just began\n\t\t\t\t\t\t\t(SelectionShapeType::Box, SelectionMode::Enclosed, false) => overlay_context.dashed_quad(quad, None, fill_color, Some(4.), Some(4.), Some(0.5)),\n\t\t\t\t\t\t\t(SelectionShapeType::Lasso, SelectionMode::Enclosed, _) => overlay_context.dashed_polygon(&tool_data.lasso_polygon, None, fill_color, Some(4.), Some(4.), Some(0.5)),\n\t\t\t\t\t\t\t(SelectionShapeType::Box, _, false) => overlay_context.quad(quad, None, fill_color),\n\t\t\t\t\t\t\t(SelectionShapeType::Lasso, _, _) => overlay_context.polygon(&tool_data.lasso_polygon, None, fill_color),\n\t\t\t\t\t\t\t(SelectionShapeType::Box, _, _) => {}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tSelf::Dragging(_) => {\n\t\t\t\t\t\ttool_data.snap_manager.draw_overlays(SnapData::new(document, input, viewport), &mut overlay_context);\n\n\t\t\t\t\t\t// Draw the snapping axis lines\n\t\t\t\t\t\tif tool_data.snapping_axis.is_some() {\n\t\t\t\t\t\t\tlet Some(axis) = tool_data.snapping_axis else { return self };\n\t\t\t\t\t\t\tlet origin = tool_data.drag_start_pos;\n\t\t\t\t\t\t\tlet viewport_diagonal = viewport.size().into_dvec2().length();\n\n\t\t\t\t\t\t\tmatch axis {\n\t\t\t\t\t\t\t\tAxis::Y => {\n\t\t\t\t\t\t\t\t\toverlay_context.line(origin - DVec2::Y * viewport_diagonal, origin + DVec2::Y * viewport_diagonal, Some(COLOR_OVERLAY_GREEN), None);\n\t\t\t\t\t\t\t\t\toverlay_context.line(origin - DVec2::X * viewport_diagonal, origin + DVec2::X * viewport_diagonal, Some(COLOR_OVERLAY_RED_25), None);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tAxis::X | Axis::Both => {\n\t\t\t\t\t\t\t\t\toverlay_context.line(origin - DVec2::X * viewport_diagonal, origin + DVec2::X * viewport_diagonal, Some(COLOR_OVERLAY_RED), None);\n\t\t\t\t\t\t\t\t\toverlay_context.line(origin - DVec2::Y * viewport_diagonal, origin + DVec2::Y * viewport_diagonal, Some(COLOR_OVERLAY_GREEN_25), None);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tSelf::SlidingPoint => {}\n\t\t\t\t}\n\n\t\t\t\tresponses.add(PathToolMessage::SelectedPointUpdated);\n\t\t\t\tresponses.add(PathToolMessage::UpdateSelectedPointsStatus { overlay_context });\n\t\t\t\tself\n\t\t\t}\n\n\t\t\t// Mouse down\n\t\t\t(\n\t\t\t\t_,\n\t\t\t\tPathToolMessage::MouseDown {\n\t\t\t\t\textend_selection,\n\t\t\t\t\tlasso_select,\n\t\t\t\t\thandle_drag_from_anchor,\n\t\t\t\t\tdrag_restore_handle,\n\t\t\t\t\tsegment_editing_modifier,\n\t\t\t\t},\n\t\t\t) => {\n\t\t\t\tlet extend_selection = input.keyboard.get(extend_selection as usize);\n\t\t\t\tlet lasso_select = input.keyboard.get(lasso_select as usize);\n\t\t\t\tlet handle_drag_from_anchor = input.keyboard.get(handle_drag_from_anchor as usize);\n\t\t\t\tlet drag_zero_handle = input.keyboard.get(drag_restore_handle as usize);\n\t\t\t\tlet segment_editing_modifier = input.keyboard.get(segment_editing_modifier as usize);\n\n\t\t\t\ttool_data.selection_mode = None;\n\t\t\t\ttool_data.lasso_polygon.clear();\n\n\t\t\t\ttool_data.mouse_down(\n\t\t\t\t\tshape_editor,\n\t\t\t\t\tdocument,\n\t\t\t\t\tinput,\n\t\t\t\t\tviewport,\n\t\t\t\t\tresponses,\n\t\t\t\t\textend_selection,\n\t\t\t\t\tlasso_select,\n\t\t\t\t\thandle_drag_from_anchor,\n\t\t\t\t\tdrag_zero_handle,\n\t\t\t\t\tsegment_editing_modifier,\n\t\t\t\t\ttool_options.path_overlay_mode,\n\t\t\t\t\ttool_options.path_editing_mode.segment_editing_mode,\n\t\t\t\t\ttool_options.path_editing_mode.point_editing_mode,\n\t\t\t\t)\n\t\t\t}\n\t\t\t(\n\t\t\t\tPathToolFsmState::Drawing { selection_shape },\n\t\t\t\tPathToolMessage::PointerMove {\n\t\t\t\t\tequidistant,\n\t\t\t\t\ttoggle_colinear,\n\t\t\t\t\tmove_anchor_with_handles,\n\t\t\t\t\tsnap_angle,\n\t\t\t\t\tlock_angle,\n\t\t\t\t\tdelete_segment,\n\t\t\t\t\tbreak_colinear_molding,\n\t\t\t\t\tsegment_editing_modifier,\n\t\t\t\t},\n\t\t\t) => {\n\t\t\t\ttool_data.previous_mouse_position = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position);\n\n\t\t\t\ttool_data.started_drawing_from_inside = false;\n\t\t\t\ttool_data.stored_selection = None;\n\n\t\t\t\tif selection_shape == SelectionShapeType::Lasso {\n\t\t\t\t\textend_lasso(&mut tool_data.lasso_polygon, input.mouse.position);\n\t\t\t\t}\n\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\n\t\t\t\t// Auto-panning\n\t\t\t\tlet messages = [\n\t\t\t\t\tPathToolMessage::PointerOutsideViewport {\n\t\t\t\t\t\tequidistant,\n\t\t\t\t\t\ttoggle_colinear,\n\t\t\t\t\t\tmove_anchor_with_handles,\n\t\t\t\t\t\tsnap_angle,\n\t\t\t\t\t\tlock_angle,\n\t\t\t\t\t\tdelete_segment,\n\t\t\t\t\t\tbreak_colinear_molding,\n\t\t\t\t\t\tsegment_editing_modifier,\n\t\t\t\t\t}\n\t\t\t\t\t.into(),\n\t\t\t\t\tPathToolMessage::PointerMove {\n\t\t\t\t\t\tequidistant,\n\t\t\t\t\t\ttoggle_colinear,\n\t\t\t\t\t\tmove_anchor_with_handles,\n\t\t\t\t\t\tsnap_angle,\n\t\t\t\t\t\tlock_angle,\n\t\t\t\t\t\tdelete_segment,\n\t\t\t\t\t\tbreak_colinear_molding,\n\t\t\t\t\t\tsegment_editing_modifier,\n\t\t\t\t\t}\n\t\t\t\t\t.into(),\n\t\t\t\t];\n\t\t\t\ttool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses);\n\n\t\t\t\tPathToolFsmState::Drawing { selection_shape }\n\t\t\t}\n\t\t\t(\n\t\t\t\tPathToolFsmState::Dragging(_),\n\t\t\t\tPathToolMessage::PointerMove {\n\t\t\t\t\tequidistant,\n\t\t\t\t\ttoggle_colinear,\n\t\t\t\t\tmove_anchor_with_handles,\n\t\t\t\t\tsnap_angle,\n\t\t\t\t\tlock_angle,\n\t\t\t\t\tdelete_segment,\n\t\t\t\t\tbreak_colinear_molding,\n\t\t\t\t\tsegment_editing_modifier,\n\t\t\t\t},\n\t\t\t) => {\n\t\t\t\tlet selected_only_handles = !shape_editor.selected_points().any(|point| matches!(point, ManipulatorPointId::Anchor(_)));\n\t\t\t\ttool_data.stored_selection = None;\n\n\t\t\t\tif !tool_data.saved_selection_before_handle_drag.is_empty() && (tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD) && (selected_only_handles) {\n\t\t\t\t\ttool_data.handle_drag_toggle = true;\n\t\t\t\t}\n\n\t\t\t\tif tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD {\n\t\t\t\t\ttool_data.molding_segment = true;\n\t\t\t\t}\n\n\t\t\t\tlet break_molding = input.keyboard.get(break_colinear_molding as usize);\n\n\t\t\t\t// Logic for molding segment\n\t\t\t\tif let Some(segment) = &mut tool_data.segment\n\t\t\t\t\t&& let Some(molding_segment_handles) = tool_data.molding_info\n\t\t\t\t{\n\t\t\t\t\ttool_data.temporary_adjacent_handles_while_molding = segment.mold_handle_positions(\n\t\t\t\t\t\tdocument,\n\t\t\t\t\t\tresponses,\n\t\t\t\t\t\tmolding_segment_handles,\n\t\t\t\t\t\tinput.mouse.position,\n\t\t\t\t\t\tbreak_molding,\n\t\t\t\t\t\ttool_data.temporary_adjacent_handles_while_molding,\n\t\t\t\t\t);\n\n\t\t\t\t\treturn PathToolFsmState::Dragging(tool_data.dragging_state);\n\t\t\t\t}\n\n\t\t\t\tlet anchor_and_handle_toggled = input.keyboard.get(move_anchor_with_handles as usize);\n\t\t\t\tlet initial_press = anchor_and_handle_toggled && !tool_data.select_anchor_toggled;\n\t\t\t\tlet released_from_toggle = tool_data.select_anchor_toggled && !anchor_and_handle_toggled;\n\n\t\t\t\tif initial_press {\n\t\t\t\t\tresponses.add(PathToolMessage::SelectedPointUpdated);\n\t\t\t\t\ttool_data.select_anchor_toggled = true;\n\n\t\t\t\t\tlet mut points_to_save = HashMap::new();\n\t\t\t\t\tfor (layer, state) in &shape_editor.selected_shape_state {\n\t\t\t\t\t\tpoints_to_save.insert(*layer, state.selected_points().collect::<Vec<_>>());\n\t\t\t\t\t}\n\t\t\t\t\ttool_data.save_points_before_anchor_toggle(points_to_save);\n\n\t\t\t\t\tshape_editor.select_anchor_and_connected_handles(&document.network_interface);\n\t\t\t\t} else if released_from_toggle {\n\t\t\t\t\tresponses.add(PathToolMessage::SelectedPointUpdated);\n\t\t\t\t\ttool_data.select_anchor_toggled = false;\n\t\t\t\t\tshape_editor.deselect_all_points();\n\t\t\t\t\tshape_editor.select_points_by_layer_and_id(&tool_data.saved_points_before_anchor_select_toggle);\n\t\t\t\t\ttool_data.remove_saved_points();\n\t\t\t\t}\n\n\t\t\t\tlet toggle_colinear_state = input.keyboard.get(toggle_colinear as usize);\n\t\t\t\tlet equidistant_state = input.keyboard.get(equidistant as usize);\n\t\t\t\tlet lock_angle_state = input.keyboard.get(lock_angle as usize);\n\t\t\t\tlet snap_angle_state = input.keyboard.get(snap_angle as usize);\n\n\t\t\t\tif !lock_angle_state {\n\t\t\t\t\ttool_data.angle_locked = false;\n\t\t\t\t\ttool_data.adjacent_anchor_offset = None;\n\t\t\t\t}\n\n\t\t\t\tif !tool_data.update_colinear(equidistant_state, toggle_colinear_state, tool_action_data.shape_editor, tool_action_data.document, responses) {\n\t\t\t\t\ttool_data.drag(\n\t\t\t\t\t\tequidistant_state,\n\t\t\t\t\t\tlock_angle_state,\n\t\t\t\t\t\tsnap_angle_state,\n\t\t\t\t\t\tsnap_angle_state,\n\t\t\t\t\t\ttool_action_data.shape_editor,\n\t\t\t\t\t\ttool_action_data.document,\n\t\t\t\t\t\tinput,\n\t\t\t\t\t\tviewport,\n\t\t\t\t\t\tresponses,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// Auto-panning\n\t\t\t\tlet messages = [\n\t\t\t\t\tPathToolMessage::PointerOutsideViewport {\n\t\t\t\t\t\ttoggle_colinear,\n\t\t\t\t\t\tequidistant,\n\t\t\t\t\t\tmove_anchor_with_handles,\n\t\t\t\t\t\tsnap_angle,\n\t\t\t\t\t\tlock_angle,\n\t\t\t\t\t\tdelete_segment,\n\t\t\t\t\t\tbreak_colinear_molding,\n\t\t\t\t\t\tsegment_editing_modifier,\n\t\t\t\t\t}\n\t\t\t\t\t.into(),\n\t\t\t\t\tPathToolMessage::PointerMove {\n\t\t\t\t\t\ttoggle_colinear,\n\t\t\t\t\t\tequidistant,\n\t\t\t\t\t\tmove_anchor_with_handles,\n\t\t\t\t\t\tsnap_angle,\n\t\t\t\t\t\tlock_angle,\n\t\t\t\t\t\tdelete_segment,\n\t\t\t\t\t\tbreak_colinear_molding,\n\t\t\t\t\t\tsegment_editing_modifier,\n\t\t\t\t\t}\n\t\t\t\t\t.into(),\n\t\t\t\t];\n\t\t\t\ttool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses);\n\n\t\t\t\tPathToolFsmState::Dragging(tool_data.dragging_state)\n\t\t\t}\n\t\t\t(PathToolFsmState::SlidingPoint, PathToolMessage::PointerMove { .. }) => {\n\t\t\t\ttool_data.slide_point(input.mouse.position, responses, &document.network_interface, shape_editor);\n\t\t\t\tPathToolFsmState::SlidingPoint\n\t\t\t}\n\t\t\t(\n\t\t\t\tPathToolFsmState::Ready,\n\t\t\t\tPathToolMessage::PointerMove {\n\t\t\t\t\tdelete_segment,\n\t\t\t\t\tsegment_editing_modifier,\n\t\t\t\t\tsnap_angle,\n\t\t\t\t\t..\n\t\t\t\t},\n\t\t\t) => {\n\t\t\t\ttool_data.delete_segment_pressed = input.keyboard.get(delete_segment as usize);\n\t\t\t\ttool_data.segment_editing_modifier = input.keyboard.get(segment_editing_modifier as usize);\n\t\t\t\ttool_data.multiple_toggle_pressed = input.keyboard.get(snap_angle as usize);\n\t\t\t\ttool_data.saved_points_before_anchor_convert_smooth_sharp.clear();\n\t\t\t\ttool_data.adjacent_anchor_offset = None;\n\t\t\t\ttool_data.stored_selection = None;\n\n\t\t\t\tif tool_data.has_drill_through_mouse_moved(input.mouse.position) {\n\t\t\t\t\ttool_data.reset_drill_through_cycle();\n\t\t\t\t}\n\n\t\t\t\t// When moving the cursor around we want to update the hovered layers\n\t\t\t\tlet new_hovered_layers: Vec<LayerNodeIdentifier> = document\n\t\t\t\t\t.click_list_no_parents(input, viewport)\n\t\t\t\t\t.filter(|&layer| {\n\t\t\t\t\t\t// Filter out artboards and parent holders, and already selected layers\n\t\t\t\t\t\t!document.network_interface.is_artboard(&layer.to_node(), &[])\n\t\t\t\t\t})\n\t\t\t\t\t.collect();\n\n\t\t\t\tif tool_data.hovered_layers != new_hovered_layers {\n\t\t\t\t\ttool_data.hovered_layers = new_hovered_layers;\n\t\t\t\t}\n\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\n\t\t\t\tself\n\t\t\t}\n\t\t\t(PathToolFsmState::Drawing { selection_shape: selection_type }, PathToolMessage::PointerOutsideViewport { .. }) => {\n\t\t\t\t// Auto-panning\n\t\t\t\tif let Some(offset) = tool_data.auto_panning.shift_viewport(input, viewport, responses) {\n\t\t\t\t\ttool_data.drag_start_pos += offset;\n\t\t\t\t}\n\n\t\t\t\tPathToolFsmState::Drawing { selection_shape: selection_type }\n\t\t\t}\n\t\t\t(PathToolFsmState::Dragging(dragging_state), PathToolMessage::PointerOutsideViewport { .. }) => {\n\t\t\t\t// Auto-panning\n\t\t\t\tif let Some(offset) = tool_data.auto_panning.shift_viewport(input, viewport, responses) {\n\t\t\t\t\ttool_data.drag_start_pos += offset;\n\t\t\t\t}\n\n\t\t\t\tPathToolFsmState::Dragging(dragging_state)\n\t\t\t}\n\t\t\t(\n\t\t\t\tstate,\n\t\t\t\tPathToolMessage::PointerOutsideViewport {\n\t\t\t\t\tequidistant,\n\t\t\t\t\ttoggle_colinear,\n\t\t\t\t\tmove_anchor_with_handles,\n\t\t\t\t\tsnap_angle,\n\t\t\t\t\tlock_angle,\n\t\t\t\t\tdelete_segment,\n\t\t\t\t\tbreak_colinear_molding,\n\t\t\t\t\tsegment_editing_modifier,\n\t\t\t\t},\n\t\t\t) => {\n\t\t\t\t// Auto-panning\n\t\t\t\tlet messages = [\n\t\t\t\t\tPathToolMessage::PointerOutsideViewport {\n\t\t\t\t\t\tequidistant,\n\t\t\t\t\t\ttoggle_colinear,\n\t\t\t\t\t\tmove_anchor_with_handles,\n\t\t\t\t\t\tsnap_angle,\n\t\t\t\t\t\tlock_angle,\n\t\t\t\t\t\tdelete_segment,\n\t\t\t\t\t\tbreak_colinear_molding,\n\t\t\t\t\t\tsegment_editing_modifier,\n\t\t\t\t\t}\n\t\t\t\t\t.into(),\n\t\t\t\t\tPathToolMessage::PointerMove {\n\t\t\t\t\t\tequidistant,\n\t\t\t\t\t\ttoggle_colinear,\n\t\t\t\t\t\tmove_anchor_with_handles,\n\t\t\t\t\t\tsnap_angle,\n\t\t\t\t\t\tlock_angle,\n\t\t\t\t\t\tdelete_segment,\n\t\t\t\t\t\tbreak_colinear_molding,\n\t\t\t\t\t\tsegment_editing_modifier,\n\t\t\t\t\t}\n\t\t\t\t\t.into(),\n\t\t\t\t];\n\t\t\t\ttool_data.auto_panning.stop(&messages, responses);\n\n\t\t\t\tstate\n\t\t\t}\n\t\t\t(PathToolFsmState::Drawing { selection_shape }, PathToolMessage::Enter { extend_selection, shrink_selection }) => {\n\t\t\t\tlet extend_selection = input.keyboard.get(extend_selection as usize);\n\t\t\t\tlet shrink_selection = input.keyboard.get(shrink_selection as usize);\n\n\t\t\t\tlet selection_change = if shrink_selection {\n\t\t\t\t\tSelectionChange::Shrink\n\t\t\t\t} else if extend_selection {\n\t\t\t\t\tSelectionChange::Extend\n\t\t\t\t} else {\n\t\t\t\t\tSelectionChange::Clear\n\t\t\t\t};\n\n\t\t\t\tlet document_to_viewport = document.metadata().document_to_viewport;\n\t\t\t\tlet previous_mouse = document_to_viewport.transform_point2(tool_data.previous_mouse_position);\n\t\t\t\tif tool_data.drag_start_pos == previous_mouse {\n\t\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] });\n\t\t\t\t} else {\n\t\t\t\t\tlet selection_mode = match tool_action_data.preferences.get_selection_mode() {\n\t\t\t\t\t\tSelectionMode::Directional => tool_data.calculate_selection_mode_from_direction(document.metadata()),\n\t\t\t\t\t\tselection_mode => selection_mode,\n\t\t\t\t\t};\n\n\t\t\t\t\tmatch selection_shape {\n\t\t\t\t\t\tSelectionShapeType::Box => {\n\t\t\t\t\t\t\tlet bbox = Rect::new(tool_data.drag_start_pos.x, tool_data.drag_start_pos.y, previous_mouse.x, previous_mouse.y).abs();\n\n\t\t\t\t\t\t\tshape_editor.select_all_in_shape(\n\t\t\t\t\t\t\t\t&document.network_interface,\n\t\t\t\t\t\t\t\tSelectionShape::Box(bbox),\n\t\t\t\t\t\t\t\tselection_change,\n\t\t\t\t\t\t\t\ttool_options.path_overlay_mode,\n\t\t\t\t\t\t\t\ttool_data.frontier_handles_info.as_ref(),\n\t\t\t\t\t\t\t\ttool_options.path_editing_mode.segment_editing_mode,\n\t\t\t\t\t\t\t\ttool_options.path_editing_mode.point_editing_mode,\n\t\t\t\t\t\t\t\tselection_mode,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tSelectionShapeType::Lasso => shape_editor.select_all_in_shape(\n\t\t\t\t\t\t\t&document.network_interface,\n\t\t\t\t\t\t\tSelectionShape::Lasso(&tool_data.lasso_polygon),\n\t\t\t\t\t\t\tselection_change,\n\t\t\t\t\t\t\ttool_options.path_overlay_mode,\n\t\t\t\t\t\t\ttool_data.frontier_handles_info.as_ref(),\n\t\t\t\t\t\t\ttool_options.path_editing_mode.segment_editing_mode,\n\t\t\t\t\t\t\ttool_options.path_editing_mode.point_editing_mode,\n\t\t\t\t\t\t\tselection_mode,\n\t\t\t\t\t\t),\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\n\t\t\t\tPathToolFsmState::Ready\n\t\t\t}\n\t\t\t(PathToolFsmState::Dragging { .. }, PathToolMessage::Escape | PathToolMessage::RightClick) => {\n\t\t\t\tif tool_data.handle_drag_toggle && tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD {\n\t\t\t\t\tshape_editor.deselect_all_points();\n\t\t\t\t\tshape_editor.deselect_all_segments();\n\n\t\t\t\t\tfor (layer, (selected_points, selected_segments)) in &tool_data.saved_selection_before_handle_drag {\n\t\t\t\t\t\tlet Some(state) = shape_editor.selected_shape_state.get_mut(layer) else { continue };\n\t\t\t\t\t\tselected_points.iter().for_each(|point| state.select_point(*point));\n\t\t\t\t\t\tselected_segments.iter().for_each(|segment| state.select_segment(*segment));\n\t\t\t\t\t}\n\n\t\t\t\t\ttool_data.saved_selection_before_handle_drag.clear();\n\t\t\t\t\ttool_data.handle_drag_toggle = false;\n\t\t\t\t}\n\t\t\t\ttool_data.molding_info = None;\n\t\t\t\ttool_data.molding_segment = false;\n\t\t\t\ttool_data.temporary_adjacent_handles_while_molding = None;\n\t\t\t\ttool_data.angle_locked = false;\n\t\t\t\tresponses.add(DocumentMessage::AbortTransaction);\n\t\t\t\ttool_data.snap_manager.cleanup(responses);\n\t\t\t\tPathToolFsmState::Ready\n\t\t\t}\n\t\t\t(PathToolFsmState::Drawing { .. }, PathToolMessage::Escape | PathToolMessage::RightClick) => {\n\t\t\t\ttool_data.snap_manager.cleanup(responses);\n\t\t\t\tPathToolFsmState::Ready\n\t\t\t}\n\t\t\t(PathToolFsmState::SlidingPoint, PathToolMessage::Escape | PathToolMessage::RightClick) => {\n\t\t\t\ttool_data.sliding_point_info = None;\n\n\t\t\t\tresponses.add(DocumentMessage::AbortTransaction);\n\t\t\t\ttool_data.snap_manager.cleanup(responses);\n\n\t\t\t\tPathToolFsmState::Ready\n\t\t\t}\n\t\t\t// Mouse up\n\t\t\t(PathToolFsmState::Drawing { selection_shape }, PathToolMessage::DragStop { extend_selection, shrink_selection }) => {\n\t\t\t\tlet extend_selection = input.keyboard.get(extend_selection as usize);\n\t\t\t\tlet shrink_selection = input.keyboard.get(shrink_selection as usize);\n\n\t\t\t\tlet select_kind = if shrink_selection {\n\t\t\t\t\tSelectionChange::Shrink\n\t\t\t\t} else if extend_selection {\n\t\t\t\t\tSelectionChange::Extend\n\t\t\t\t} else {\n\t\t\t\t\tSelectionChange::Clear\n\t\t\t\t};\n\n\t\t\t\tlet document_to_viewport = document.metadata().document_to_viewport;\n\t\t\t\tlet previous_mouse = document_to_viewport.transform_point2(tool_data.previous_mouse_position);\n\n\t\t\t\tlet selection_mode = match tool_action_data.preferences.get_selection_mode() {\n\t\t\t\t\tSelectionMode::Directional => tool_data.calculate_selection_mode_from_direction(document.metadata()),\n\t\t\t\t\tselection_mode => selection_mode,\n\t\t\t\t};\n\t\t\t\ttool_data.started_drawing_from_inside = false;\n\n\t\t\t\tif tool_data.drag_start_pos.distance(previous_mouse) < 1e-8 {\n\t\t\t\t\t// Clicked inside or outside the shape then deselect all of the points/segments\n\t\t\t\t\tif document.click(input, viewport).is_some() && tool_data.stored_selection.is_none() {\n\t\t\t\t\t\ttool_data.stored_selection = Some(shape_editor.selected_shape_state.clone());\n\t\t\t\t\t}\n\n\t\t\t\t\tshape_editor.deselect_all_points();\n\t\t\t\t\tshape_editor.deselect_all_segments();\n\t\t\t\t} else {\n\t\t\t\t\tmatch selection_shape {\n\t\t\t\t\t\tSelectionShapeType::Box => {\n\t\t\t\t\t\t\tlet bbox = Rect::new(tool_data.drag_start_pos.x, tool_data.drag_start_pos.y, previous_mouse.x, previous_mouse.y).abs();\n\n\t\t\t\t\t\t\tshape_editor.select_all_in_shape(\n\t\t\t\t\t\t\t\t&document.network_interface,\n\t\t\t\t\t\t\t\tSelectionShape::Box(bbox),\n\t\t\t\t\t\t\t\tselect_kind,\n\t\t\t\t\t\t\t\ttool_options.path_overlay_mode,\n\t\t\t\t\t\t\t\ttool_data.frontier_handles_info.as_ref(),\n\t\t\t\t\t\t\t\ttool_options.path_editing_mode.segment_editing_mode,\n\t\t\t\t\t\t\t\ttool_options.path_editing_mode.point_editing_mode,\n\t\t\t\t\t\t\t\tselection_mode,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tSelectionShapeType::Lasso => shape_editor.select_all_in_shape(\n\t\t\t\t\t\t\t&document.network_interface,\n\t\t\t\t\t\t\tSelectionShape::Lasso(&tool_data.lasso_polygon),\n\t\t\t\t\t\t\tselect_kind,\n\t\t\t\t\t\t\ttool_options.path_overlay_mode,\n\t\t\t\t\t\t\ttool_data.frontier_handles_info.as_ref(),\n\t\t\t\t\t\t\ttool_options.path_editing_mode.segment_editing_mode,\n\t\t\t\t\t\t\ttool_options.path_editing_mode.point_editing_mode,\n\t\t\t\t\t\t\tselection_mode,\n\t\t\t\t\t\t),\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\tresponses.add(PathToolMessage::SelectedPointUpdated);\n\n\t\t\t\tPathToolFsmState::Ready\n\t\t\t}\n\t\t\t(_, PathToolMessage::DragStop { extend_selection, .. }) => {\n\t\t\t\ttool_data.ghost_outline.clear();\n\t\t\t\tlet extend_selection = input.keyboard.get(extend_selection as usize);\n\t\t\t\tlet drag_occurred = tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD;\n\t\t\t\tlet mut segment_dissolved = false;\n\t\t\t\tlet mut point_inserted = false;\n\n\t\t\t\tlet nearest_point = shape_editor.find_nearest_visible_point_indices(\n\t\t\t\t\t&document.network_interface,\n\t\t\t\t\tinput.mouse.position,\n\t\t\t\t\tSELECTION_THRESHOLD,\n\t\t\t\t\ttool_options.path_overlay_mode,\n\t\t\t\t\ttool_data.frontier_handles_info.as_ref(),\n\t\t\t\t);\n\n\t\t\t\tlet nearest_segment = tool_data.segment.clone();\n\n\t\t\t\tif let Some(segment) = &mut tool_data.segment {\n\t\t\t\t\tlet segment_mode = tool_options.path_editing_mode.segment_editing_mode;\n\t\t\t\t\tlet point_mode = tool_options.path_editing_mode.point_editing_mode;\n\t\t\t\t\t// If segment mode and the insertion modifier is pressed or it is in point editing mode\n\n\t\t\t\t\tif !drag_occurred && !tool_data.molding_segment && ((point_mode && !segment_mode) || (segment_mode && tool_data.segment_editing_modifier)) {\n\t\t\t\t\t\tif tool_data.delete_segment_pressed {\n\t\t\t\t\t\t\tif let Some(vector) = document.network_interface.compute_modified_vector(segment.layer()) {\n\t\t\t\t\t\t\t\tshape_editor.dissolve_segment(responses, segment.layer(), &vector, segment.segment(), segment.points());\n\t\t\t\t\t\t\t\tsegment_dissolved = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tlet is_segment_selected = shape_editor\n\t\t\t\t\t\t\t\t.selected_shape_state\n\t\t\t\t\t\t\t\t.get(&segment.layer())\n\t\t\t\t\t\t\t\t.is_some_and(|state| state.is_segment_selected(segment.segment()));\n\n\t\t\t\t\t\t\tsegment.adjusted_insert_and_select(shape_editor, responses, extend_selection, point_mode, is_segment_selected);\n\t\t\t\t\t\t\tpoint_inserted = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\ttool_data.segment = None;\n\t\t\t\t\ttool_data.molding_info = None;\n\t\t\t\t\ttool_data.molding_segment = false;\n\t\t\t\t\ttool_data.temporary_adjacent_handles_while_molding = None;\n\n\t\t\t\t\tif segment_dissolved || point_inserted {\n\t\t\t\t\t\tresponses.add(DocumentMessage::EndTransaction);\n\t\t\t\t\t\treturn PathToolFsmState::Ready;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tlet segment_mode = tool_options.path_editing_mode.segment_editing_mode;\n\t\t\t\tlet point_mode = tool_options.path_editing_mode.point_editing_mode;\n\n\t\t\t\tif let (Some((layer, nearest_point)), true) = (nearest_point, point_mode) {\n\t\t\t\t\tlet clicked_selected = shape_editor.selected_points().any(|&point| nearest_point == point);\n\t\t\t\t\tif !drag_occurred && extend_selection {\n\t\t\t\t\t\tif clicked_selected && tool_data.last_clicked_point_was_selected {\n\t\t\t\t\t\t\tshape_editor.selected_shape_state.entry(layer).or_default().deselect_point(nearest_point);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tshape_editor.selected_shape_state.entry(layer).or_default().select_point(nearest_point);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\t\t}\n\t\t\t\t\tif !drag_occurred && !extend_selection && clicked_selected {\n\t\t\t\t\t\tif tool_data.saved_points_before_anchor_convert_smooth_sharp.is_empty() {\n\t\t\t\t\t\t\tlet mut saved_points = HashMap::new();\n\t\t\t\t\t\t\tfor (layer, state) in &shape_editor.selected_shape_state {\n\t\t\t\t\t\t\t\tsaved_points.insert(*layer, state.selected_points().collect::<Vec<_>>());\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\ttool_data.saved_points_before_anchor_convert_smooth_sharp = saved_points;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tshape_editor.deselect_all_points();\n\t\t\t\t\t\tshape_editor.deselect_all_segments();\n\n\t\t\t\t\t\tshape_editor.selected_shape_state.entry(layer).or_default().select_point(nearest_point);\n\n\t\t\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Segment editing mode\n\t\t\t\telse if let Some(nearest_segment) = nearest_segment {\n\t\t\t\t\tif segment_mode {\n\t\t\t\t\t\tlet clicked_selected = shape_editor.selected_segments().any(|&segment| segment == nearest_segment.segment());\n\t\t\t\t\t\tif !drag_occurred && extend_selection {\n\t\t\t\t\t\t\tif clicked_selected && tool_data.last_clicked_segment_was_selected {\n\t\t\t\t\t\t\t\tshape_editor\n\t\t\t\t\t\t\t\t\t.selected_shape_state\n\t\t\t\t\t\t\t\t\t.entry(nearest_segment.layer())\n\t\t\t\t\t\t\t\t\t.or_default()\n\t\t\t\t\t\t\t\t\t.deselect_segment(nearest_segment.segment());\n\n\t\t\t\t\t\t\t\t// If in segment editing mode only, and upon deselecting a segment, we deselect both of its anchors\n\t\t\t\t\t\t\t\tif segment_mode && !point_mode {\n\t\t\t\t\t\t\t\t\tnearest_segment.points().iter().for_each(|point_id| {\n\t\t\t\t\t\t\t\t\t\tshape_editor\n\t\t\t\t\t\t\t\t\t\t\t.selected_shape_state\n\t\t\t\t\t\t\t\t\t\t\t.entry(nearest_segment.layer())\n\t\t\t\t\t\t\t\t\t\t\t.or_default()\n\t\t\t\t\t\t\t\t\t\t\t.deselect_point(ManipulatorPointId::Anchor(*point_id));\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tshape_editor.selected_shape_state.entry(nearest_segment.layer()).or_default().select_segment(nearest_segment.segment());\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif !drag_occurred && !extend_selection && clicked_selected {\n\t\t\t\t\t\t\tshape_editor.deselect_all_segments();\n\t\t\t\t\t\t\tshape_editor.deselect_all_points();\n\t\t\t\t\t\t\tshape_editor.selected_shape_state.entry(nearest_segment.layer()).or_default().select_segment(nearest_segment.segment());\n\n\t\t\t\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// If only in segment select mode, we also select all of the endpoints of selected segments\n\t\t\t\t\tlet point_mode = tool_options.path_editing_mode.point_editing_mode;\n\t\t\t\t\tif !point_mode {\n\t\t\t\t\t\tlet [start, end] = nearest_segment.points();\n\t\t\t\t\t\tshape_editor\n\t\t\t\t\t\t\t.selected_shape_state\n\t\t\t\t\t\t\t.entry(nearest_segment.layer())\n\t\t\t\t\t\t\t.or_default()\n\t\t\t\t\t\t\t.select_point(ManipulatorPointId::Anchor(start));\n\t\t\t\t\t\tshape_editor\n\t\t\t\t\t\t\t.selected_shape_state\n\t\t\t\t\t\t\t.entry(nearest_segment.layer())\n\t\t\t\t\t\t\t.or_default()\n\t\t\t\t\t\t\t.select_point(ManipulatorPointId::Anchor(end));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Deselect all points if the user clicks the filled region of the shape\n\t\t\t\telse if tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD {\n\t\t\t\t\tshape_editor.deselect_all_points();\n\t\t\t\t\tshape_editor.deselect_all_segments();\n\t\t\t\t}\n\n\t\t\t\tif tool_data.temporary_colinear_handles {\n\t\t\t\t\ttool_data.temporary_colinear_handles = false;\n\t\t\t\t}\n\n\t\t\t\tif tool_data.handle_drag_toggle && drag_occurred {\n\t\t\t\t\tshape_editor.deselect_all_points();\n\t\t\t\t\tshape_editor.deselect_all_segments();\n\n\t\t\t\t\tfor (layer, (selected_points, selected_segments)) in &tool_data.saved_selection_before_handle_drag {\n\t\t\t\t\t\tlet Some(state) = shape_editor.selected_shape_state.get_mut(layer) else { continue };\n\t\t\t\t\t\tselected_points.iter().for_each(|point| state.select_point(*point));\n\t\t\t\t\t\tselected_segments.iter().for_each(|segment| state.select_segment(*segment));\n\t\t\t\t\t}\n\n\t\t\t\t\ttool_data.saved_selection_before_handle_drag.clear();\n\t\t\t\t\ttool_data.handle_drag_toggle = false;\n\t\t\t\t}\n\n\t\t\t\ttool_data.alt_dragging_from_anchor = false;\n\t\t\t\ttool_data.alt_clicked_on_anchor = false;\n\t\t\t\ttool_data.angle_locked = false;\n\n\t\t\t\tif tool_data.select_anchor_toggled {\n\t\t\t\t\tshape_editor.deselect_all_points();\n\t\t\t\t\tshape_editor.select_points_by_layer_and_id(&tool_data.saved_points_before_anchor_select_toggle);\n\t\t\t\t\ttool_data.remove_saved_points();\n\t\t\t\t\ttool_data.select_anchor_toggled = false;\n\t\t\t\t}\n\n\t\t\t\ttool_data.snapping_axis = None;\n\t\t\t\ttool_data.sliding_point_info = None;\n\n\t\t\t\tif drag_occurred || extend_selection {\n\t\t\t\t\tresponses.add(DocumentMessage::EndTransaction);\n\t\t\t\t}\n\t\t\t\tresponses.add(PathToolMessage::SelectedPointUpdated);\n\t\t\t\ttool_data.snap_manager.cleanup(responses);\n\t\t\t\ttool_data.opposite_handle_position = None;\n\n\t\t\t\tPathToolFsmState::Ready\n\t\t\t}\n\n\t\t\t// Delete key\n\t\t\t(_, PathToolMessage::Delete) => {\n\t\t\t\t// Delete the selected points and clean up overlays\n\t\t\t\tlet point_mode = tool_options.path_editing_mode.point_editing_mode;\n\t\t\t\tlet segment_mode = tool_options.path_editing_mode.segment_editing_mode;\n\t\t\t\tlet only_segment_mode = segment_mode && !point_mode;\n\n\t\t\t\tlet transaction_started = shape_editor.delete_selected_segments(document, responses, true);\n\t\t\t\tif only_segment_mode {\n\t\t\t\t\tshape_editor.delete_hanging_selected_anchors(document, responses, !transaction_started);\n\t\t\t\t} else {\n\t\t\t\t\tshape_editor.delete_selected_points(document, responses, !transaction_started);\n\t\t\t\t}\n\t\t\t\tresponses.add(PathToolMessage::SelectionChanged);\n\n\t\t\t\tPathToolFsmState::Ready\n\t\t\t}\n\t\t\t(_, PathToolMessage::BreakPath) => {\n\t\t\t\tshape_editor.break_path_at_selected_point(document, responses);\n\t\t\t\tPathToolFsmState::Ready\n\t\t\t}\n\t\t\t(_, PathToolMessage::DeleteAndBreakPath) => {\n\t\t\t\tshape_editor.delete_point_and_break_path(document, responses);\n\t\t\t\tPathToolFsmState::Ready\n\t\t\t}\n\t\t\t(_, PathToolMessage::ClosePath) => {\n\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\t\t\t\tshape_editor.close_selected_path(document, responses);\n\t\t\t\tresponses.add(DocumentMessage::EndTransaction);\n\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\n\t\t\t\tself\n\t\t\t}\n\t\t\t(_, PathToolMessage::ConnectPointsByPosition { layer, start_position, end_position }) => {\n\t\t\t\t// Get the merged vector\n\t\t\t\tlet Some(vector) = document.network_interface.compute_modified_vector(layer) else {\n\t\t\t\t\treturn self;\n\t\t\t\t};\n\n\t\t\t\t// Find points by their positions (with small tolerance for floating point comparison)\n\t\t\t\tconst POSITION_TOLERANCE: f64 = 1e-6;\n\n\t\t\t\tlet positions = vector.point_domain.positions();\n\t\t\t\tlet point_ids = vector.point_domain.ids();\n\n\t\t\t\tlet mut start_point_id = None;\n\t\t\t\tlet mut end_point_id = None;\n\n\t\t\t\t// Get the merged layer's transform to convert local positions to document space\n\t\t\t\tlet layer_transform = document.metadata().transform_to_document(layer);\n\n\t\t\t\tfor (i, &local_pos) in positions.iter().enumerate() {\n\t\t\t\t\t// Transform the local position to document space for comparison\n\t\t\t\t\tlet doc_pos = layer_transform.transform_point2(local_pos);\n\n\t\t\t\t\tlet start_distance = (doc_pos - start_position).length();\n\t\t\t\t\tlet end_distance = (doc_pos - end_position).length();\n\n\t\t\t\t\tif start_point_id.is_none() && start_distance < POSITION_TOLERANCE {\n\t\t\t\t\t\tstart_point_id = Some(point_ids[i]);\n\t\t\t\t\t}\n\t\t\t\t\tif end_point_id.is_none() && end_distance < POSITION_TOLERANCE {\n\t\t\t\t\t\tend_point_id = Some(point_ids[i]);\n\t\t\t\t\t}\n\t\t\t\t\tif start_point_id.is_some() && end_point_id.is_some() {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif let (Some(start_id), Some(end_id)) = (start_point_id, end_point_id) {\n\t\t\t\t\t// Create segment directly\n\t\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\n\t\t\t\t\tlet segment_id = SegmentId::generate();\n\t\t\t\t\tlet modification_type = VectorModificationType::InsertSegment {\n\t\t\t\t\t\tid: segment_id,\n\t\t\t\t\t\tpoints: [end_id, start_id],\n\t\t\t\t\t\thandles: [None, None],\n\t\t\t\t\t};\n\n\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t\t\tresponses.add(DocumentMessage::EndTransaction);\n\t\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\t}\n\n\t\t\t\tself\n\t\t\t}\n\t\t\t(_, PathToolMessage::StartSlidingPoint) => {\n\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\t\t\t\tif tool_data.start_sliding_point(shape_editor, document) {\n\t\t\t\t\tPathToolFsmState::SlidingPoint\n\t\t\t\t} else {\n\t\t\t\t\tPathToolFsmState::Ready\n\t\t\t\t}\n\t\t\t}\n\t\t\t(_, PathToolMessage::Copy { clipboard }) => {\n\t\t\t\t// TODO: Add support for selected segments\n\n\t\t\t\tlet mut buffer = Vec::new();\n\n\t\t\t\tfor (&layer, layer_selection_state) in &shape_editor.selected_shape_state {\n\t\t\t\t\tif layer_selection_state.is_empty() {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tlet Some(old_vector) = document.network_interface.compute_modified_vector(layer) else { continue };\n\n\t\t\t\t\t// Also get the transform node that is applied on the layer if it exists\n\t\t\t\t\tlet transform = document.metadata().transform_to_document(layer);\n\n\t\t\t\t\tlet mut new_vector = Vector::default();\n\n\t\t\t\t\tlet mut selected_points_by_segment = HashSet::new();\n\t\t\t\t\told_vector\n\t\t\t\t\t\t.segment_bezier_iter()\n\t\t\t\t\t\t.filter(|(segment, _, _, _)| layer_selection_state.is_segment_selected(*segment))\n\t\t\t\t\t\t.for_each(|(_, _, start, end)| {\n\t\t\t\t\t\t\tselected_points_by_segment.insert(start);\n\t\t\t\t\t\t\tselected_points_by_segment.insert(end);\n\t\t\t\t\t\t});\n\n\t\t\t\t\t// Add all the selected points\n\t\t\t\t\tfor (point, position) in old_vector.point_domain.iter() {\n\t\t\t\t\t\tif layer_selection_state.is_point_selected(ManipulatorPointId::Anchor(point)) || selected_points_by_segment.contains(&point) {\n\t\t\t\t\t\t\tnew_vector.point_domain.push(point, position);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tlet find_index = |id: PointId| new_vector.point_domain.iter().enumerate().find(|(_, (point_id, _))| *point_id == id).map(|(index, _)| index);\n\n\t\t\t\t\t// Add segments which have selected ends\n\t\t\t\t\tfor ((segment_id, bezier, start, end), stroke) in old_vector.segment_bezier_iter().zip(old_vector.segment_domain.stroke().iter()) {\n\t\t\t\t\t\tlet both_ends_selected = layer_selection_state.is_point_selected(ManipulatorPointId::Anchor(start)) && layer_selection_state.is_point_selected(ManipulatorPointId::Anchor(end));\n\n\t\t\t\t\t\tlet segment_selected = layer_selection_state.is_segment_selected(segment_id);\n\n\t\t\t\t\t\tif both_ends_selected || segment_selected {\n\t\t\t\t\t\t\tlet Some((start_index, end_index)) = find_index(start).zip(find_index(end)) else {\n\t\t\t\t\t\t\t\terror!(\"Point does not exist in point domain\");\n\t\t\t\t\t\t\t\treturn PathToolFsmState::Ready;\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tnew_vector.segment_domain.push(segment_id, start_index, end_index, bezier.handles, *stroke);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tfor handles in old_vector.colinear_manipulators {\n\t\t\t\t\t\tif new_vector.segment_domain.ids().contains(&handles[0].segment) && new_vector.segment_domain.ids().contains(&handles[1].segment) {\n\t\t\t\t\t\t\tnew_vector.colinear_manipulators.push(handles);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tbuffer.push((layer, new_vector, transform));\n\t\t\t\t}\n\n\t\t\t\tif clipboard == Clipboard::Device {\n\t\t\t\t\tif let Ok(data) = serde_json::to_string(&buffer) {\n\t\t\t\t\t\tresponses.add(ClipboardMessage::Write {\n\t\t\t\t\t\t\tcontent: ClipboardContent::Vector(data),\n\t\t\t\t\t\t});\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlog::error!(\"Failed to serialize nodes for clipboard\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// TODO: Add implementation for internal clipboard\n\n\t\t\t\tPathToolFsmState::Ready\n\t\t\t}\n\t\t\t(_, PathToolMessage::Cut { clipboard }) => {\n\t\t\t\tresponses.add(PathToolMessage::Copy { clipboard });\n\t\t\t\t// Delete the selected points/segments\n\t\t\t\tresponses.add(PathToolMessage::DeleteSelected);\n\n\t\t\t\tPathToolFsmState::Ready\n\t\t\t}\n\t\t\t(_, PathToolMessage::Paste { data }) => {\n\t\t\t\t// Deserialize the data\n\t\t\t\tif let Ok(data) = serde_json::from_str::<Vec<(LayerNodeIdentifier, Vector, DAffine2)>>(&data) {\n\t\t\t\t\tshape_editor.deselect_all_points();\n\t\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\t\t\t\t\tlet mut new_layers = Vec::new();\n\t\t\t\t\tfor (layer, new_vector, transform) in data {\n\t\t\t\t\t\t// If layer is not selected then create a new selected layer\n\t\t\t\t\t\tlet layer = if shape_editor.selected_shape_state.contains_key(&layer) {\n\t\t\t\t\t\t\tlayer\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tlet Some(node_type) = resolve_network_node_type(\"Path\") else {\n\t\t\t\t\t\t\t\terror!(\"Could not resolve node type for Path\");\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tlet nodes = vec![(NodeId(0), node_type.default_node_template())];\n\n\t\t\t\t\t\t\tlet parent = document.new_layer_parent(false);\n\n\t\t\t\t\t\t\tlet layer = graph_modification_utils::new_custom(NodeId::new(), nodes, parent, responses);\n\n\t\t\t\t\t\t\t// Defaults chosen because the pasted geometry has no inherent associated style\n\t\t\t\t\t\t\tlet stroke_color = Color::BLACK;\n\t\t\t\t\t\t\tlet fill_color = Color::WHITE;\n\n\t\t\t\t\t\t\tlet stroke = graphene_std::vector::style::Stroke::new(Some(stroke_color.to_gamma_srgb()), DEFAULT_STROKE_WIDTH);\n\t\t\t\t\t\t\tresponses.add(GraphOperationMessage::StrokeSet { layer, stroke });\n\n\t\t\t\t\t\t\tlet fill = graphene_std::vector::style::Fill::solid(fill_color.to_gamma_srgb());\n\t\t\t\t\t\t\tresponses.add(GraphOperationMessage::FillSet { layer, fill });\n\n\t\t\t\t\t\t\tnew_layers.push(layer);\n\n\t\t\t\t\t\t\tresponses.add(GraphOperationMessage::TransformSet {\n\t\t\t\t\t\t\t\tlayer,\n\t\t\t\t\t\t\t\ttransform,\n\t\t\t\t\t\t\t\ttransform_in: TransformIn::Local,\n\t\t\t\t\t\t\t\tskip_rerender: false,\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\tlayer\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\t// Create new point ids and add those into the existing vector content\n\t\t\t\t\t\tlet mut points_map = HashMap::new();\n\t\t\t\t\t\tfor (point, position) in new_vector.point_domain.iter() {\n\t\t\t\t\t\t\tlet new_point_id = PointId::generate();\n\t\t\t\t\t\t\tpoints_map.insert(point, new_point_id);\n\n\t\t\t\t\t\t\tlet modification_type = VectorModificationType::InsertPoint { id: new_point_id, position };\n\n\t\t\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Create new segment ids and add the segments into the existing vector content\n\t\t\t\t\t\tlet mut segments_map = HashMap::new();\n\t\t\t\t\t\tfor (segment_id, bezier, start, end) in new_vector.segment_iter() {\n\t\t\t\t\t\t\tlet new_segment_id = SegmentId::generate();\n\n\t\t\t\t\t\t\tsegments_map.insert(segment_id, new_segment_id);\n\n\t\t\t\t\t\t\tlet points = pathseg_points(bezier);\n\t\t\t\t\t\t\tlet handles = [points.p1.map(|handle| handle - points.p0), points.p2.map(|handle| handle - points.p3)];\n\n\t\t\t\t\t\t\tlet points = [points_map[&start], points_map[&end]];\n\t\t\t\t\t\t\tlet modification_type = VectorModificationType::InsertSegment { id: new_segment_id, points, handles };\n\n\t\t\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Set G1 continuity\n\t\t\t\t\t\tfor handles in new_vector.colinear_manipulators {\n\t\t\t\t\t\t\tlet to_new_handle = |handle: HandleId| -> HandleId {\n\t\t\t\t\t\t\t\tHandleId {\n\t\t\t\t\t\t\t\t\tty: handle.ty,\n\t\t\t\t\t\t\t\t\tsegment: segments_map[&handle.segment],\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tlet new_handles = [to_new_handle(handles[0]), to_new_handle(handles[1])];\n\t\t\t\t\t\t\tlet modification_type = VectorModificationType::SetG1Continuous { handles: new_handles, enabled: true };\n\n\t\t\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tshape_editor.selected_shape_state.entry(layer).or_insert(Default::default());\n\n\t\t\t\t\t\t// Set selection to newly inserted points\n\t\t\t\t\t\tlet Some(state) = shape_editor.selected_shape_state.get_mut(&layer) else {\n\t\t\t\t\t\t\terror!(\"No state for layer: {layer:?}\");\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\t// If point editing mode is enabled, select all the pasted points\n\t\t\t\t\t\tif tool_options.path_editing_mode.point_editing_mode {\n\t\t\t\t\t\t\tpoints_map.values().for_each(|point| state.select_point(ManipulatorPointId::Anchor(*point)));\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// If segment editing mode is enabled, select all the pasted segments\n\t\t\t\t\t\tif tool_options.path_editing_mode.segment_editing_mode {\n\t\t\t\t\t\t\tsegments_map.values().for_each(|segment| state.select_segment(*segment));\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// If there are new layers created, we need to center them in the viewport\n\t\t\t\t\tif !new_layers.is_empty() {\n\t\t\t\t\t\tresponses.add(Message::Defer(DeferMessage::AfterGraphRun {\n\t\t\t\t\t\t\tmessages: vec![PortfolioMessage::CenterPastedLayers { layers: new_layers }.into()],\n\t\t\t\t\t\t}));\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tPathToolFsmState::Ready\n\t\t\t}\n\t\t\t(_, PathToolMessage::DeleteSelected) => {\n\t\t\t\t// Delete the selected points and segments\n\t\t\t\tlet deleted_some_point = shape_editor.delete_point_and_break_path(document, responses);\n\t\t\t\tshape_editor.delete_selected_segments(document, responses, !deleted_some_point);\n\n\t\t\t\tPathToolFsmState::Ready\n\t\t\t}\n\t\t\t(_, PathToolMessage::Duplicate) => {\n\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\n\t\t\t\t// Copy the existing selected geometry and paste it in the existing layers\n\t\t\t\tfor (layer, layer_selection_state) in shape_editor.selected_shape_state.clone() {\n\t\t\t\t\tif layer_selection_state.is_empty() {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tlet Some(old_vector) = document.network_interface.compute_modified_vector(layer) else {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t};\n\n\t\t\t\t\t// Add all the selected points\n\t\t\t\t\tlet mut selected_points_by_segment = HashSet::new();\n\t\t\t\t\told_vector\n\t\t\t\t\t\t.segment_bezier_iter()\n\t\t\t\t\t\t.filter(|(segment, _, _, _)| layer_selection_state.is_segment_selected(*segment))\n\t\t\t\t\t\t.for_each(|(_, _, start, end)| {\n\t\t\t\t\t\t\tselected_points_by_segment.insert(start);\n\t\t\t\t\t\t\tselected_points_by_segment.insert(end);\n\t\t\t\t\t\t});\n\n\t\t\t\t\tlet mut points_map = HashMap::new();\n\t\t\t\t\tfor (point, position) in old_vector.point_domain.iter() {\n\t\t\t\t\t\t// TODO: Either the point is selected or it is an endpoint of a selected segment\n\n\t\t\t\t\t\tif layer_selection_state.is_point_selected(ManipulatorPointId::Anchor(point)) || selected_points_by_segment.contains(&point) {\n\t\t\t\t\t\t\t// Insert the same point with a new id\n\t\t\t\t\t\t\tlet new_id = PointId::generate();\n\t\t\t\t\t\t\tpoints_map.insert(point, new_id);\n\n\t\t\t\t\t\t\tlet modification_type = VectorModificationType::InsertPoint { id: new_id, position };\n\n\t\t\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tlet mut segments_map = HashMap::new();\n\n\t\t\t\t\tfor (segment_id, bezier, start, end) in old_vector.segment_iter() {\n\t\t\t\t\t\tlet both_ends_selected = layer_selection_state.is_point_selected(ManipulatorPointId::Anchor(start)) && layer_selection_state.is_point_selected(ManipulatorPointId::Anchor(end));\n\n\t\t\t\t\t\tlet segment_selected = layer_selection_state.is_segment_selected(segment_id);\n\n\t\t\t\t\t\tif both_ends_selected || segment_selected {\n\t\t\t\t\t\t\tlet new_id = SegmentId::generate();\n\t\t\t\t\t\t\tsegments_map.insert(segment_id, new_id);\n\n\t\t\t\t\t\t\tlet points = pathseg_points(bezier);\n\t\t\t\t\t\t\tlet handles = [points.p1.map(|handle| handle - points.p0), points.p2.map(|handle| handle - points.p3)];\n\n\t\t\t\t\t\t\tlet points = [points_map[&start], points_map[&end]];\n\t\t\t\t\t\t\tlet modification_type = VectorModificationType::InsertSegment { id: new_id, points, handles };\n\n\t\t\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tfor handles in old_vector.colinear_manipulators {\n\t\t\t\t\t\tlet to_new_handle = |handle: HandleId| -> HandleId {\n\t\t\t\t\t\t\tHandleId {\n\t\t\t\t\t\t\t\tty: handle.ty,\n\t\t\t\t\t\t\t\tsegment: segments_map[&handle.segment],\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tif segments_map.contains_key(&handles[0].segment) && segments_map.contains_key(&handles[1].segment) {\n\t\t\t\t\t\t\tlet new_handles = [to_new_handle(handles[0]), to_new_handle(handles[1])];\n\t\t\t\t\t\t\tlet modification_type = VectorModificationType::SetG1Continuous { handles: new_handles, enabled: true };\n\n\t\t\t\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tshape_editor.deselect_all_points();\n\t\t\t\t\tshape_editor.deselect_all_segments();\n\n\t\t\t\t\t// Set selection to newly inserted points and segments\n\t\t\t\t\tlet Some(state) = shape_editor.selected_shape_state.get_mut(&layer) else {\n\t\t\t\t\t\terror!(\"No state for layer: {layer:?}\");\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t};\n\t\t\t\t\tif tool_options.path_editing_mode.point_editing_mode {\n\t\t\t\t\t\tpoints_map.values().for_each(|point| state.select_point(ManipulatorPointId::Anchor(*point)));\n\t\t\t\t\t}\n\t\t\t\t\tif tool_options.path_editing_mode.segment_editing_mode {\n\t\t\t\t\t\tsegments_map.values().for_each(|segment| state.select_segment(*segment));\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tPathToolFsmState::Ready\n\t\t\t}\n\t\t\t(_, PathToolMessage::DoubleClick { extend_selection, shrink_selection }) => {\n\t\t\t\t// Double-clicked on a point (flip smooth/sharp behavior)\n\t\t\t\tlet nearest_point = shape_editor.find_nearest_point_indices(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD);\n\n\t\t\t\tlet mut get_drill_through_layer = || -> Option<LayerNodeIdentifier> {\n\t\t\t\t\tlet drill_through_layers = document.click_list_no_parents(input, viewport).collect::<Vec<LayerNodeIdentifier>>();\n\t\t\t\t\tif drill_through_layers.is_empty() {\n\t\t\t\t\t\ttool_data.reset_drill_through_cycle();\n\t\t\t\t\t\tNone\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttool_data.drill_through_cycle_count = drill_through_layers.len();\n\t\t\t\t\t\tlet cycle_index = tool_data.next_drill_through_cycle(input.mouse.position);\n\t\t\t\t\t\tlet layer = drill_through_layers.get(cycle_index);\n\t\t\t\t\t\tif cycle_index == 0 { drill_through_layers.first().copied() } else { layer.copied() }\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tif nearest_point.is_some() {\n\t\t\t\t\t// Flip the selected point between smooth and sharp\n\t\t\t\t\tif !tool_data.double_click_handled && tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD {\n\t\t\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\n\t\t\t\t\t\tshape_editor.select_points_by_layer_and_id(&tool_data.saved_points_before_anchor_convert_smooth_sharp);\n\t\t\t\t\t\tshape_editor.flip_smooth_sharp(&document.network_interface, responses);\n\t\t\t\t\t\ttool_data.saved_points_before_anchor_convert_smooth_sharp.clear();\n\n\t\t\t\t\t\tresponses.add(DocumentMessage::EndTransaction);\n\t\t\t\t\t\tresponses.add(DeferMessage::AfterGraphRun {\n\t\t\t\t\t\t\tmessages: vec![PathToolMessage::SelectedPointUpdated.into()],\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\treturn PathToolFsmState::Ready;\n\t\t\t\t}\n\t\t\t\t// Double-clicked on a filled region\n\t\t\t\telse if let Some(layer) = &get_drill_through_layer() {\n\t\t\t\t\tlet extend_selection = input.keyboard.get(extend_selection as usize);\n\t\t\t\t\tlet shrink_selection = input.keyboard.get(shrink_selection as usize);\n\n\t\t\t\t\tif shape_editor.is_selected_layer(*layer) {\n\t\t\t\t\t\tif extend_selection && !tool_data.first_selected_with_single_click {\n\t\t\t\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesRemove { nodes: vec![layer.to_node()] });\n\n\t\t\t\t\t\t\tif let Some(selection) = &tool_data.stored_selection {\n\t\t\t\t\t\t\t\tlet mut selection = selection.clone();\n\t\t\t\t\t\t\t\tselection.remove(layer);\n\t\t\t\t\t\t\t\tshape_editor.selected_shape_state = selection;\n\t\t\t\t\t\t\t\ttool_data.stored_selection = None;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if shrink_selection && !tool_data.first_selected_with_single_click {\n\t\t\t\t\t\t\t// Only deselect all the points of the double clicked layer\n\t\t\t\t\t\t\tif let Some(selection) = &tool_data.stored_selection {\n\t\t\t\t\t\t\t\tlet selection = selection.clone();\n\t\t\t\t\t\t\t\tshape_editor.selected_shape_state = selection;\n\t\t\t\t\t\t\t\ttool_data.stored_selection = None;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tlet state = shape_editor.selected_shape_state.get_mut(layer).expect(\"No state for selected layer\");\n\t\t\t\t\t\t\tstate.deselect_all_points_in_layer();\n\t\t\t\t\t\t\tstate.deselect_all_segments_in_layer();\n\t\t\t\t\t\t} else if !tool_data.first_selected_with_single_click {\n\t\t\t\t\t\t\t// Select according to the selected editing mode\n\t\t\t\t\t\t\tlet point_editing_mode = tool_options.path_editing_mode.point_editing_mode;\n\t\t\t\t\t\t\tlet segment_editing_mode = tool_options.path_editing_mode.segment_editing_mode;\n\t\t\t\t\t\t\tshape_editor.select_connected(document, *layer, input.mouse.position, point_editing_mode, segment_editing_mode);\n\n\t\t\t\t\t\t\t// Select all the other layers back again\n\t\t\t\t\t\t\tif let Some(selection) = &tool_data.stored_selection {\n\t\t\t\t\t\t\t\tlet mut selection = selection.clone();\n\t\t\t\t\t\t\t\tselection.remove(layer);\n\n\t\t\t\t\t\t\t\tfor (layer, state) in selection {\n\t\t\t\t\t\t\t\t\tshape_editor.selected_shape_state.insert(layer, state);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\ttool_data.stored_selection = None;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// If it was the very first click without there being an existing selection,\n\t\t\t\t\t\t// then the single-click behavior and double-click behavior should not collide\n\t\t\t\t\t\ttool_data.first_selected_with_single_click = false;\n\t\t\t\t\t} else if extend_selection {\n\t\t\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![layer.to_node()] });\n\n\t\t\t\t\t\tif let Some(selection) = &tool_data.stored_selection {\n\t\t\t\t\t\t\tshape_editor.selected_shape_state = selection.clone();\n\t\t\t\t\t\t\ttool_data.stored_selection = None;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] });\n\t\t\t\t\t}\n\n\t\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\t}\n\t\t\t\t// Double clicked on the background\n\t\t\t\telse {\n\t\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] });\n\t\t\t\t}\n\n\t\t\t\tPathToolFsmState::Ready\n\t\t\t}\n\t\t\t(_, PathToolMessage::Abort) => {\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\tPathToolFsmState::Ready\n\t\t\t}\n\t\t\t(_, PathToolMessage::NudgeSelectedPoints { delta_x, delta_y }) => {\n\t\t\t\tshape_editor.move_selected_points_and_segments(\n\t\t\t\t\ttool_data.opposing_handle_lengths.take(),\n\t\t\t\t\tdocument,\n\t\t\t\t\t(delta_x, delta_y).into(),\n\t\t\t\t\ttrue,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttool_data.opposite_handle_position,\n\t\t\t\t\tfalse,\n\t\t\t\t\tresponses,\n\t\t\t\t);\n\n\t\t\t\tPathToolFsmState::Ready\n\t\t\t}\n\t\t\t(_, PathToolMessage::SelectAll) => {\n\t\t\t\tshape_editor.select_all_anchors_in_selected_layers(document);\n\n\t\t\t\tlet point_editing_mode = tool_options.path_editing_mode.point_editing_mode;\n\t\t\t\tlet segment_editing_mode = tool_options.path_editing_mode.segment_editing_mode;\n\n\t\t\t\tif point_editing_mode {\n\t\t\t\t\tshape_editor.select_all_anchors_in_selected_layers(document);\n\t\t\t\t}\n\t\t\t\tif segment_editing_mode {\n\t\t\t\t\tshape_editor.select_all_segments_in_selected_layers(document);\n\t\t\t\t}\n\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\tPathToolFsmState::Ready\n\t\t\t}\n\t\t\t(_, PathToolMessage::DeselectAllSelected) => {\n\t\t\t\tshape_editor.deselect_all_points();\n\t\t\t\tshape_editor.deselect_all_segments();\n\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\n\t\t\t\tPathToolFsmState::Ready\n\t\t\t}\n\t\t\t(_, PathToolMessage::SelectedPointXChanged { new_x }) => {\n\t\t\t\tif let Some(&SingleSelectedPoint { coordinates, id, layer, .. }) = tool_data.selection_status.as_one() {\n\t\t\t\t\tshape_editor.reposition_control_point(&id, &document.network_interface, DVec2::new(new_x, coordinates.y), layer, responses);\n\t\t\t\t}\n\t\t\t\tPathToolFsmState::Ready\n\t\t\t}\n\t\t\t(_, PathToolMessage::SelectedPointYChanged { new_y }) => {\n\t\t\t\tif let Some(&SingleSelectedPoint { coordinates, id, layer, .. }) = tool_data.selection_status.as_one() {\n\t\t\t\t\tshape_editor.reposition_control_point(&id, &document.network_interface, DVec2::new(coordinates.x, new_y), layer, responses);\n\t\t\t\t}\n\t\t\t\tPathToolFsmState::Ready\n\t\t\t}\n\t\t\t(_, PathToolMessage::SelectedPointUpdated) => {\n\t\t\t\ttool_data.dragging_state = DraggingState {\n\t\t\t\t\tpoint_select_state: shape_editor.get_dragging_state(&document.network_interface),\n\t\t\t\t\tcolinear: shape_editor.selected_manipulator_angles(&document.network_interface),\n\t\t\t\t};\n\n\t\t\t\tlet old = tool_data.make_path_editable_is_allowed;\n\t\t\t\ttool_data.make_path_editable_is_allowed = make_path_editable_is_allowed(&mut document.network_interface).is_some();\n\t\t\t\ttool_data.update_selection_status(shape_editor, document);\n\n\t\t\t\tif old != tool_data.make_path_editable_is_allowed {\n\t\t\t\t\tresponses.add(MenuBarMessage::SendLayout);\n\t\t\t\t}\n\n\t\t\t\tself\n\t\t\t}\n\t\t\t(_, PathToolMessage::ManipulatorMakeHandlesColinear) => {\n\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\t\t\t\tshape_editor.convert_selected_manipulators_to_colinear_handles(responses, document);\n\t\t\t\tresponses.add(DocumentMessage::EndTransaction);\n\t\t\t\tresponses.add(PathToolMessage::SelectionChanged);\n\t\t\t\tPathToolFsmState::Ready\n\t\t\t}\n\t\t\t(_, PathToolMessage::ManipulatorMakeHandlesFree) => {\n\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\t\t\t\tshape_editor.disable_colinear_handles_state_on_selected(&document.network_interface, responses);\n\t\t\t\tresponses.add(DocumentMessage::EndTransaction);\n\t\t\t\tPathToolFsmState::Ready\n\t\t\t}\n\t\t\t(_, PathToolMessage::SetPivot { position }) => {\n\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\n\t\t\t\ttool_data.pivot_gizmo.pivot.last_non_none_reference_point = position;\n\t\t\t\tlet position: Option<DVec2> = position.into();\n\t\t\t\ttool_data.pivot_gizmo.pivot.set_normalized_position(position.unwrap());\n\t\t\t\tlet pivot_gizmo = tool_data.pivot_gizmo();\n\t\t\t\tresponses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });\n\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\n\t\t\t\tself\n\t\t\t}\n\t\t\t(_, _) => PathToolFsmState::Ready,\n\t\t}\n\t}\n\n\tfn update_hints(&self, _responses: &mut VecDeque<Message>) {\n\t\t// Moved logic to update_dynamic_hints\n\t}\n\n\tfn update_cursor(&self, responses: &mut VecDeque<Message>) {\n\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });\n\t}\n}\n\n#[derive(Debug, PartialEq, Default)]\nenum SelectionStatus {\n\t#[default]\n\tNone,\n\tOne(SingleSelectedPoint),\n\tMultiple(MultipleSelectedPoints),\n}\n\nimpl SelectionStatus {\n\tfn as_one(&self) -> Option<&SingleSelectedPoint> {\n\t\tmatch self {\n\t\t\tSelectionStatus::One(one) => Some(one),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tfn angle(&self) -> Option<ManipulatorAngle> {\n\t\tmatch self {\n\t\t\tSelf::None => None,\n\t\t\tSelf::One(one) => Some(one.manipulator_angle),\n\t\t\tSelf::Multiple(one) => Some(one.manipulator_angle),\n\t\t}\n\t}\n}\n\n#[derive(Debug, PartialEq)]\nstruct MultipleSelectedPoints {\n\tmanipulator_angle: ManipulatorAngle,\n}\n\n#[derive(Debug, PartialEq)]\nstruct SingleSelectedPoint {\n\tcoordinates: DVec2,\n\tid: ManipulatorPointId,\n\tlayer: LayerNodeIdentifier,\n\tmanipulator_angle: ManipulatorAngle,\n}\n\n/// Sets the cumulative description of the selected points: if `None` are selected, if `One` is selected, or if `Multiple` are selected.\n/// Applies to any selected points, whether they are anchors or handles; and whether they are from a single shape or across multiple shapes.\nfn get_selection_status(network_interface: &NodeNetworkInterface, shape_state: &mut ShapeState) -> SelectionStatus {\n\tlet mut selection_layers = shape_state.selected_shape_state.iter().map(|(k, v)| (*k, v.selected_points_count()));\n\tlet total_selected_points = selection_layers.clone().map(|(_, v)| v).sum::<usize>();\n\n\t// Check to see if only one manipulator group in a single shape is selected\n\tif total_selected_points == 1 {\n\t\tlet Some(layer) = selection_layers.find(|(_, v)| *v > 0).map(|(k, _)| k) else {\n\t\t\treturn SelectionStatus::None;\n\t\t};\n\t\tlet Some(vector) = network_interface.compute_modified_vector(layer) else {\n\t\t\treturn SelectionStatus::None;\n\t\t};\n\t\tlet Some(&point) = shape_state.selected_points().next() else {\n\t\t\treturn SelectionStatus::None;\n\t\t};\n\t\tlet Some(local_position) = point.get_position(&vector) else {\n\t\t\treturn SelectionStatus::None;\n\t\t};\n\n\t\tlet coordinates = network_interface\n\t\t\t.document_metadata()\n\t\t\t.transform_to_document_if_feeds(layer, network_interface)\n\t\t\t.transform_point2(local_position);\n\t\tlet manipulator_angle = if vector.colinear(point) { ManipulatorAngle::Colinear } else { ManipulatorAngle::Free };\n\n\t\treturn SelectionStatus::One(SingleSelectedPoint {\n\t\t\tcoordinates,\n\t\t\tlayer,\n\t\t\tid: point,\n\t\t\tmanipulator_angle,\n\t\t});\n\t};\n\n\t// Check to see if multiple manipulator groups are selected\n\tif total_selected_points > 1 {\n\t\treturn SelectionStatus::Multiple(MultipleSelectedPoints {\n\t\t\tmanipulator_angle: shape_state.selected_manipulator_angles(network_interface),\n\t\t});\n\t}\n\n\tSelectionStatus::None\n}\n\nfn calculate_lock_angle(\n\ttool_data: &mut PathToolData,\n\tshape_state: &mut ShapeState,\n\tresponses: &mut VecDeque<Message>,\n\tdocument: &DocumentMessageHandler,\n\tvector: &Vector,\n\thandle_id: ManipulatorPointId,\n\ttangent_to_neighboring_tangents: bool,\n) -> Option<f64> {\n\tlet anchor = handle_id.get_anchor(vector)?;\n\tlet anchor_position = vector.point_domain.position_from_id(anchor);\n\tlet current_segment = handle_id.get_segment();\n\tlet points_connected = vector.connected_count(anchor);\n\n\tlet (anchor_position, segment) = anchor_position.zip(current_segment)?;\n\tif points_connected == 1 {\n\t\tcalculate_segment_angle(anchor, segment, vector, false)\n\t} else {\n\t\tlet opposite_handle = handle_id\n\t\t\t.get_handle_pair(vector)\n\t\t\t.iter()\n\t\t\t.flatten()\n\t\t\t.find(|&h| h.to_manipulator_point() != handle_id)\n\t\t\t.copied()\n\t\t\t.map(|h| h.to_manipulator_point());\n\t\tlet opposite_handle_position = opposite_handle.and_then(|h| h.get_position(vector)).filter(|pos| (pos - anchor_position).length() > 1e-6);\n\n\t\tif let Some(opposite_pos) = opposite_handle_position {\n\t\t\tif !vector.colinear_manipulators.iter().flatten().map(|h| h.to_manipulator_point()).any(|h| h == handle_id) {\n\t\t\t\tshape_state.convert_selected_manipulators_to_colinear_handles(responses, document);\n\t\t\t\ttool_data.temporary_colinear_handles = true;\n\t\t\t}\n\t\t\tSome(-(opposite_pos - anchor_position).angle_to(DVec2::X))\n\t\t} else {\n\t\t\tlet angle_1 = vector\n\t\t\t\t.adjacent_segment(&handle_id)\n\t\t\t\t.and_then(|(_, adjacent_segment)| calculate_segment_angle(anchor, adjacent_segment, vector, false));\n\n\t\t\tlet angle_2 = calculate_segment_angle(anchor, segment, vector, false);\n\n\t\t\tmatch (angle_1, angle_2) {\n\t\t\t\t(Some(angle_1), Some(angle_2)) => {\n\t\t\t\t\tlet angle = Some((angle_1 + angle_2) / 2.);\n\t\t\t\t\tif tangent_to_neighboring_tangents {\n\t\t\t\t\t\tangle.map(|angle| angle + std::f64::consts::FRAC_PI_2)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tangle\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t(Some(angle_1), None) => Some(angle_1),\n\t\t\t\t(None, Some(angle_2)) => Some(angle_2),\n\t\t\t\t(None, None) => None,\n\t\t\t}\n\t\t}\n\t}\n}\n\nfn check_handle_over_adjacent_anchor(handle_id: ManipulatorPointId, vector: &Vector) -> Option<PointId> {\n\tlet (anchor, handle_position) = handle_id.get_anchor(vector).zip(handle_id.get_position(vector))?;\n\n\tlet check_if_close = |point_id: &PointId| {\n\t\tlet Some(anchor_position) = vector.point_domain.position_from_id(*point_id) else {\n\t\t\treturn false;\n\t\t};\n\t\t(anchor_position - handle_position).length() < 10.\n\t};\n\n\tvector.connected_points(anchor).find(check_if_close)\n}\nfn calculate_adjacent_anchor_tangent(currently_dragged_handle: ManipulatorPointId, anchor: Option<PointId>, adjacent_anchor: Option<PointId>, vector: &Vector) -> (Option<f64>, Option<DVec2>) {\n\t// Early return if no anchor or no adjacent anchors\n\n\tlet Some((dragged_handle_anchor, adjacent_anchor)) = anchor.zip(adjacent_anchor) else {\n\t\treturn (None, None);\n\t};\n\tlet adjacent_anchor_position = vector.point_domain.position_from_id(adjacent_anchor);\n\n\tlet handles: Vec<_> = vector.all_connected(adjacent_anchor).filter(|handle| handle.length(vector) > 1e-6).collect();\n\n\tmatch handles.len() {\n\t\t0 => {\n\t\t\t// Find non-shared segments\n\t\t\tlet non_shared_segment: Vec<_> = vector\n\t\t\t\t.segment_bezier_iter()\n\t\t\t\t.filter_map(|(segment_id, _, start, end)| {\n\t\t\t\t\tlet touches_adjacent = start == adjacent_anchor || end == adjacent_anchor;\n\t\t\t\t\tlet shares_with_dragged = start == dragged_handle_anchor || end == dragged_handle_anchor;\n\n\t\t\t\t\tif touches_adjacent && !shares_with_dragged { Some(segment_id) } else { None }\n\t\t\t\t})\n\t\t\t\t.collect();\n\n\t\t\tmatch non_shared_segment.first() {\n\t\t\t\tSome(&segment) => {\n\t\t\t\t\tlet angle = calculate_segment_angle(adjacent_anchor, segment, vector, true);\n\t\t\t\t\t(angle, adjacent_anchor_position)\n\t\t\t\t}\n\t\t\t\tNone => (None, None),\n\t\t\t}\n\t\t}\n\n\t\t1 => {\n\t\t\tlet segment = handles[0].segment;\n\t\t\tlet angle = calculate_segment_angle(adjacent_anchor, segment, vector, true);\n\t\t\t(angle, adjacent_anchor_position)\n\t\t}\n\n\t\t2 => {\n\t\t\t// Use the angle formed by the handle of the shared segment relative to its associated anchor point.\n\t\t\tlet Some(shared_segment_handle) = handles\n\t\t\t\t.iter()\n\t\t\t\t.find(|handle| handle.opposite().to_manipulator_point() == currently_dragged_handle)\n\t\t\t\t.map(|handle| handle.to_manipulator_point())\n\t\t\telse {\n\t\t\t\treturn (None, None);\n\t\t\t};\n\n\t\t\tlet angle = shared_segment_handle\n\t\t\t\t.get_position(vector)\n\t\t\t\t.zip(adjacent_anchor_position)\n\t\t\t\t.map(|(handle, anchor)| -(handle - anchor).angle_to(DVec2::X));\n\n\t\t\t(angle, adjacent_anchor_position)\n\t\t}\n\n\t\t_ => (None, None),\n\t}\n}\n\nfn update_dynamic_hints(\n\tstate: PathToolFsmState,\n\tresponses: &mut VecDeque<Message>,\n\tshape_editor: &mut ShapeState,\n\tdocument: &DocumentMessageHandler,\n\ttool_data: &PathToolData,\n\ttool_options: &PathToolOptions,\n\tposition: DVec2,\n) {\n\t// Condinting based on currently selected segment if it has any one g1 continuous handle\n\n\tlet hint_data = match state {\n\t\tPathToolFsmState::Ready => {\n\t\t\t// Show point sliding hints only when there is an anchor with colinear handles selected\n\t\t\tlet single_anchor_selected = shape_editor.selected_points().count() == 1 && shape_editor.selected_points().any(|point| matches!(point, ManipulatorPointId::Anchor(_)));\n\t\t\tlet at_least_one_anchor_selected = shape_editor.selected_points().any(|point| matches!(point, ManipulatorPointId::Anchor(_)));\n\t\t\tlet at_least_one_point_selected = shape_editor.selected_points().count() >= 1;\n\n\t\t\tlet mut single_colinear_anchor_selected = false;\n\t\t\tif single_anchor_selected\n\t\t\t\t&& let (Some(anchor), Some(layer)) = (\n\t\t\t\t\tshape_editor.selected_points().next(),\n\t\t\t\t\tdocument.network_interface.selected_nodes().selected_layers(document.metadata()).next(),\n\t\t\t\t) && let Some(vector) = document.network_interface.compute_modified_vector(layer)\n\t\t\t{\n\t\t\t\tsingle_colinear_anchor_selected = vector.colinear(*anchor)\n\t\t\t}\n\n\t\t\tlet drag_selected_hints = vec![HintInfo::mouse(MouseMotion::LmbDrag, \"Drag Selected\")];\n\t\t\tlet mut delete_selected_hints = vec![HintInfo::keys([Key::Backspace], \"Delete Selected\")];\n\n\t\t\tif at_least_one_anchor_selected {\n\t\t\t\tdelete_selected_hints.push(HintInfo::keys([Key::Accel], \"No Dissolve\").prepend_plus());\n\t\t\t\tdelete_selected_hints.push(HintInfo::keys([Key::Shift], \"Cut Anchor\").prepend_plus());\n\t\t\t}\n\n\t\t\tlet segment_edit = tool_options.path_editing_mode.segment_editing_mode;\n\t\t\tlet point_edit = tool_options.path_editing_mode.point_editing_mode;\n\n\t\t\tlet hovering_segment = tool_data.segment.is_some();\n\t\t\tlet hovering_point = shape_editor\n\t\t\t\t.find_nearest_visible_point_indices(\n\t\t\t\t\t&document.network_interface,\n\t\t\t\t\tposition,\n\t\t\t\t\tSELECTION_THRESHOLD,\n\t\t\t\t\ttool_options.path_overlay_mode,\n\t\t\t\t\ttool_data.frontier_handles_info.as_ref(),\n\t\t\t\t)\n\t\t\t\t.is_some();\n\n\t\t\tlet mut hint_data = if hovering_segment {\n\t\t\t\tif segment_edit {\n\t\t\t\t\t// Hovering a segment in segment editing mode\n\t\t\t\t\tvec![\n\t\t\t\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, \"Select Segment\"), HintInfo::keys([Key::Shift], \"Extend\").prepend_plus()]),\n\t\t\t\t\t\tHintGroup(vec![HintInfo::keys_and_mouse([Key::Control], MouseMotion::Lmb, \"Insert Point on Segment\")]),\n\t\t\t\t\t\tHintGroup(vec![HintInfo::keys_and_mouse([Key::Control], MouseMotion::LmbDrag, \"Mold Segment\")]),\n\t\t\t\t\t]\n\t\t\t\t} else {\n\t\t\t\t\t// Hovering a segment in point editing mode\n\t\t\t\t\tvec![\n\t\t\t\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, \"Insert Point on Segment\")]),\n\t\t\t\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, \"Mold Segment\")]),\n\t\t\t\t\t\tHintGroup(vec![HintInfo::keys_and_mouse([Key::Alt], MouseMotion::Lmb, \"Delete Segment\")]),\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t} else if hovering_point {\n\t\t\t\tif point_edit {\n\t\t\t\t\t// Hovering over a point in point editing mode\n\t\t\t\t\tvec![HintGroup(vec![\n\t\t\t\t\t\tHintInfo::mouse(MouseMotion::Lmb, \"Select Point\"),\n\t\t\t\t\t\tHintInfo::keys([Key::Shift], \"Extend\").prepend_plus(),\n\t\t\t\t\t])]\n\t\t\t\t} else {\n\t\t\t\t\t// Hovering over a point in segment selection mode (will select a nearby segment)\n\t\t\t\t\tvec![HintGroup(vec![\n\t\t\t\t\t\tHintInfo::mouse(MouseMotion::Lmb, \"Select Segment\"),\n\t\t\t\t\t\tHintInfo::keys([Key::Shift], \"Extend\").prepend_plus(),\n\t\t\t\t\t])]\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tvec![HintGroup(vec![\n\t\t\t\t\tHintInfo::mouse(MouseMotion::LmbDrag, \"Select Area\"),\n\t\t\t\t\tHintInfo::keys([Key::Control], \"Lasso\").prepend_plus(),\n\t\t\t\t])]\n\t\t\t};\n\n\t\t\tif at_least_one_anchor_selected {\n\t\t\t\t// TODO: Dynamically show either \"Smooth\" or \"Sharp\" based on the current state\n\t\t\t\thint_data.push(HintGroup(vec![\n\t\t\t\t\tHintInfo::mouse(MouseMotion::LmbDouble, \"Convert Anchor Point\"),\n\t\t\t\t\tHintInfo::keys_and_mouse([Key::Alt], MouseMotion::Lmb, \"To Sharp\"),\n\t\t\t\t\tHintInfo::keys_and_mouse([Key::Alt], MouseMotion::LmbDrag, \"To Smooth\"),\n\t\t\t\t]));\n\t\t\t}\n\n\t\t\tif at_least_one_point_selected {\n\t\t\t\tlet mut groups = vec![\n\t\t\t\t\tHintGroup(drag_selected_hints),\n\t\t\t\t\tHintGroup(vec![HintInfo::multi_keys([[Key::KeyG], [Key::KeyR], [Key::KeyS]], \"Grab/Rotate/Scale Selected\")]),\n\t\t\t\t];\n\n\t\t\t\tif single_colinear_anchor_selected {\n\t\t\t\t\tgroups.push(HintGroup(vec![HintInfo::multi_keys([[Key::KeyG], [Key::KeyG]], \"Slide\")]));\n\t\t\t\t}\n\n\t\t\t\tgroups.push(HintGroup(vec![HintInfo::arrow_keys(\"Nudge Selected\"), HintInfo::keys([Key::Shift], \"10x\").prepend_plus()]));\n\t\t\t\tgroups.push(HintGroup(delete_selected_hints));\n\n\t\t\t\thint_data.append(&mut groups);\n\t\t\t}\n\n\t\t\tHintData(hint_data)\n\t\t}\n\t\tPathToolFsmState::Dragging(dragging_state) => {\n\t\t\tlet colinear = dragging_state.colinear;\n\t\t\tlet mut dragging_hint_data = HintData(Vec::new());\n\t\t\tdragging_hint_data\n\t\t\t\t.0\n\t\t\t\t.push(HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"\"), HintInfo::keys([Key::Escape], \"Cancel\").prepend_slash()]));\n\n\t\t\tlet drag_anchor = HintInfo::keys([Key::Space], \"Drag Anchor\");\n\t\t\tlet toggle_group = match dragging_state.point_select_state {\n\t\t\t\tPointSelectState::HandleNoPair | PointSelectState::HandleWithPair => {\n\t\t\t\t\tlet mut hints = vec![HintInfo::keys([Key::Tab], \"Swap Dragged Handle\")];\n\t\t\t\t\thints.push(HintInfo::keys(\n\t\t\t\t\t\t[Key::KeyC],\n\t\t\t\t\t\tif colinear == ManipulatorAngle::Colinear {\n\t\t\t\t\t\t\t\"Break Colinear Handles\"\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\"Make Handles Colinear\"\n\t\t\t\t\t\t},\n\t\t\t\t\t));\n\t\t\t\t\thints\n\t\t\t\t}\n\t\t\t\tPointSelectState::Anchor => Vec::new(),\n\t\t\t};\n\t\t\tlet hold_group = match dragging_state.point_select_state {\n\t\t\t\tPointSelectState::HandleNoPair => {\n\t\t\t\t\tlet mut hints = vec![];\n\t\t\t\t\tif colinear != ManipulatorAngle::Free {\n\t\t\t\t\t\thints.push(HintInfo::keys([Key::Alt], \"Equidistant Handles\"));\n\t\t\t\t\t}\n\t\t\t\t\thints.push(HintInfo::keys([Key::Shift], \"15° Increments\"));\n\t\t\t\t\thints.push(HintInfo::keys([Key::Control], \"Lock Angle\"));\n\t\t\t\t\thints.push(drag_anchor);\n\t\t\t\t\thints\n\t\t\t\t}\n\t\t\t\tPointSelectState::HandleWithPair => {\n\t\t\t\t\tlet mut hints = vec![];\n\t\t\t\t\tif colinear != ManipulatorAngle::Free {\n\t\t\t\t\t\thints.push(HintInfo::keys([Key::Alt], \"Equidistant Handles\"));\n\t\t\t\t\t}\n\t\t\t\t\thints.push(HintInfo::keys([Key::Shift], \"15° Increments\"));\n\t\t\t\t\thints.push(HintInfo::keys([Key::Control], \"Lock Angle\"));\n\t\t\t\t\thints.push(drag_anchor);\n\t\t\t\t\thints\n\t\t\t\t}\n\t\t\t\tPointSelectState::Anchor => Vec::new(),\n\t\t\t};\n\n\t\t\tif !toggle_group.is_empty() {\n\t\t\t\tdragging_hint_data.0.push(HintGroup(toggle_group));\n\t\t\t}\n\n\t\t\tif !hold_group.is_empty() {\n\t\t\t\tdragging_hint_data.0.push(HintGroup(hold_group));\n\t\t\t}\n\n\t\t\tif tool_data.molding_segment {\n\t\t\t\tlet mut has_colinear_anchors = false;\n\n\t\t\t\tif let Some(segment) = &tool_data.segment {\n\t\t\t\t\tlet handle1 = HandleId::primary(segment.segment());\n\t\t\t\t\tlet handle2 = HandleId::end(segment.segment());\n\n\t\t\t\t\tif let Some(vector) = document.network_interface.compute_modified_vector(segment.layer()) {\n\t\t\t\t\t\tlet other_handle1 = vector.other_colinear_handle(handle1);\n\t\t\t\t\t\tlet other_handle2 = vector.other_colinear_handle(handle2);\n\t\t\t\t\t\tif other_handle1.is_some() || other_handle2.is_some() {\n\t\t\t\t\t\t\thas_colinear_anchors = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tlet handles_stored = if let Some(other_handles) = tool_data.temporary_adjacent_handles_while_molding {\n\t\t\t\t\tother_handles[0].is_some() || other_handles[1].is_some()\n\t\t\t\t} else {\n\t\t\t\t\tfalse\n\t\t\t\t};\n\n\t\t\t\tlet molding_disable_possible = has_colinear_anchors || handles_stored;\n\n\t\t\t\tlet mut molding_hints = vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"\"), HintInfo::keys([Key::Escape], \"Cancel\").prepend_slash()])];\n\n\t\t\t\tif molding_disable_possible {\n\t\t\t\t\tmolding_hints.push(HintGroup(vec![HintInfo::keys([Key::Alt], \"Break Colinear Handles\")]));\n\t\t\t\t}\n\n\t\t\t\tHintData(molding_hints)\n\t\t\t} else {\n\t\t\t\tdragging_hint_data\n\t\t\t}\n\t\t}\n\t\tPathToolFsmState::Drawing { .. } => HintData(vec![\n\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"\"), HintInfo::keys([Key::Escape], \"Cancel\").prepend_slash()]),\n\t\t\tHintGroup(vec![\n\t\t\t\tHintInfo::mouse(MouseMotion::LmbDrag, \"Select Area\"),\n\t\t\t\tHintInfo::keys([Key::Shift], \"Extend\").prepend_plus(),\n\t\t\t\tHintInfo::keys([Key::Alt], \"Subtract\").prepend_plus(),\n\t\t\t]),\n\t\t]),\n\t\tPathToolFsmState::SlidingPoint => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"\"), HintInfo::keys([Key::Escape], \"Cancel\").prepend_slash()])]),\n\t};\n\thint_data.send_layout(responses);\n\tresponses.add(ToolMessage::UpdateHints);\n}\n"
  },
  {
    "path": "editor/src/messages/tool/tool_messages/pen_tool.rs",
    "content": "use super::tool_prelude::*;\nuse crate::consts::{COLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_05, DEFAULT_STROKE_WIDTH, HIDE_HANDLE_DISTANCE, LINE_ROTATE_SNAP_ANGLE, SEGMENT_OVERLAY_SIZE};\nuse crate::messages::input_mapper::utility_types::input_mouse::MouseKeys;\nuse crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;\nuse crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_network_node_type;\nuse crate::messages::portfolio::document::overlays::utility_functions::path_overlays;\nuse crate::messages::portfolio::document::overlays::utility_types::{DrawHandles, OverlayContext};\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::tool::common_functionality::auto_panning::AutoPanning;\nuse crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};\nuse crate::messages::tool::common_functionality::graph_modification_utils::{self, merge_layers};\nuse crate::messages::tool::common_functionality::shape_editor::ShapeState;\nuse crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnapTypeConfiguration};\nuse crate::messages::tool::common_functionality::utility_functions::{calculate_segment_angle, closest_point, should_extend};\nuse graph_craft::document::NodeId;\nuse graphene_std::Color;\nuse graphene_std::subpath::pathseg_points;\nuse graphene_std::vector::misc::{HandleId, ManipulatorPointId, dvec2_to_point};\nuse graphene_std::vector::{NoHashBuilder, PointId, SegmentId, StrokeId, Vector, VectorModificationType};\nuse kurbo::{CubicBez, PathSeg};\n\n#[derive(Default, ExtractField)]\npub struct PenTool {\n\tfsm_state: PenToolFsmState,\n\ttool_data: PenToolData,\n\toptions: PenOptions,\n}\n\npub struct PenOptions {\n\tline_weight: f64,\n\tfill: ToolColorOptions,\n\tstroke: ToolColorOptions,\n\tpen_overlay_mode: PenOverlayMode,\n}\n\nimpl Default for PenOptions {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tline_weight: DEFAULT_STROKE_WIDTH,\n\t\t\tfill: ToolColorOptions::new_secondary(),\n\t\t\tstroke: ToolColorOptions::new_primary(),\n\t\t\tpen_overlay_mode: PenOverlayMode::FrontierHandles,\n\t\t}\n\t}\n}\n\n#[impl_message(Message, ToolMessage, Pen)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum PenToolMessage {\n\t// Standard messages\n\tAbort,\n\tSelectionChanged,\n\tWorkingColorChanged,\n\tOverlays {\n\t\tcontext: OverlayContext,\n\t},\n\n\t// Tool-specific messages\n\n\t// It is necessary to defer this until the transform of the layer can be accurately computed (quite hacky)\n\tAddPointLayerPosition {\n\t\tlayer: LayerNodeIdentifier,\n\t\tviewport: DVec2,\n\t},\n\tConfirm,\n\tDragStart {\n\t\tappend_to_selected: Key,\n\t},\n\tDragStop,\n\tPointerMove {\n\t\tsnap_angle: Key,\n\t\tbreak_handle: Key,\n\t\tlock_angle: Key,\n\t\tcolinear: Key,\n\t\tmove_anchor_with_handles: Key,\n\t},\n\tPointerOutsideViewport {\n\t\tsnap_angle: Key,\n\t\tbreak_handle: Key,\n\t\tlock_angle: Key,\n\t\tcolinear: Key,\n\t\tmove_anchor_with_handles: Key,\n\t},\n\tRedo,\n\tUndo,\n\tUpdateOptions {\n\t\toptions: PenOptionsUpdate,\n\t},\n\tRecalculateLatestPointsPosition,\n\tRemovePreviousHandle,\n\tGRS {\n\t\tgrab: Key,\n\t\trotate: Key,\n\t\tscale: Key,\n\t},\n\tFinalPosition {\n\t\tfinal_position: DVec2,\n\t},\n\tSwapHandles,\n}\n\n#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]\nenum PenToolFsmState {\n\t#[default]\n\tReady,\n\tDraggingHandle(HandleMode),\n\tPlacingAnchor,\n\tGRSHandle,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum PenOverlayMode {\n\tAllHandles = 0,\n\tFrontierHandles = 1,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum PenOptionsUpdate {\n\tFillColor(Option<Color>),\n\tFillColorType(ToolColorType),\n\tLineWeight(f64),\n\tStrokeColor(Option<Color>),\n\tStrokeColorType(ToolColorType),\n\tWorkingColors(Option<Color>, Option<Color>),\n\tOverlayModeType(PenOverlayMode),\n}\n\nimpl ToolMetadata for PenTool {\n\tfn icon_name(&self) -> String {\n\t\t\"VectorPenTool\".into()\n\t}\n\tfn tooltip_label(&self) -> String {\n\t\t\"Pen Tool\".into()\n\t}\n\tfn tool_type(&self) -> crate::messages::tool::utility_types::ToolType {\n\t\tToolType::Pen\n\t}\n}\n\nfn create_weight_widget(line_weight: f64) -> WidgetInstance {\n\tNumberInput::new(Some(line_weight))\n\t\t.unit(\" px\")\n\t\t.label(\"Weight\")\n\t\t.min(0.)\n\t\t.max((1_u64 << f64::MANTISSA_DIGITS) as f64)\n\t\t.on_update(|number_input: &NumberInput| {\n\t\t\tPenToolMessage::UpdateOptions {\n\t\t\t\toptions: PenOptionsUpdate::LineWeight(number_input.value.unwrap()),\n\t\t\t}\n\t\t\t.into()\n\t\t})\n\t\t.widget_instance()\n}\n\nimpl LayoutHolder for PenTool {\n\tfn layout(&self) -> Layout {\n\t\tlet mut widgets = self.options.fill.create_widgets(\n\t\t\t\"Fill\",\n\t\t\ttrue,\n\t\t\t|_| {\n\t\t\t\tPenToolMessage::UpdateOptions {\n\t\t\t\t\toptions: PenOptionsUpdate::FillColor(None),\n\t\t\t\t}\n\t\t\t\t.into()\n\t\t\t},\n\t\t\t|color_type: ToolColorType| {\n\t\t\t\tWidgetCallback::new(move |_| {\n\t\t\t\t\tPenToolMessage::UpdateOptions {\n\t\t\t\t\t\toptions: PenOptionsUpdate::FillColorType(color_type.clone()),\n\t\t\t\t\t}\n\t\t\t\t\t.into()\n\t\t\t\t})\n\t\t\t},\n\t\t\t|color: &ColorInput| {\n\t\t\t\tPenToolMessage::UpdateOptions {\n\t\t\t\t\toptions: PenOptionsUpdate::FillColor(color.value.as_solid().map(|color| color.to_linear_srgb())),\n\t\t\t\t}\n\t\t\t\t.into()\n\t\t\t},\n\t\t);\n\n\t\twidgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\n\t\twidgets.append(&mut self.options.stroke.create_widgets(\n\t\t\t\"Stroke\",\n\t\t\ttrue,\n\t\t\t|_| {\n\t\t\t\tPenToolMessage::UpdateOptions {\n\t\t\t\t\toptions: PenOptionsUpdate::StrokeColor(None),\n\t\t\t\t}\n\t\t\t\t.into()\n\t\t\t},\n\t\t\t|color_type: ToolColorType| {\n\t\t\t\tWidgetCallback::new(move |_| {\n\t\t\t\t\tPenToolMessage::UpdateOptions {\n\t\t\t\t\t\toptions: PenOptionsUpdate::StrokeColorType(color_type.clone()),\n\t\t\t\t\t}\n\t\t\t\t\t.into()\n\t\t\t\t})\n\t\t\t},\n\t\t\t|color: &ColorInput| {\n\t\t\t\tPenToolMessage::UpdateOptions {\n\t\t\t\t\toptions: PenOptionsUpdate::StrokeColor(color.value.as_solid().map(|color| color.to_linear_srgb())),\n\t\t\t\t}\n\t\t\t\t.into()\n\t\t\t},\n\t\t));\n\n\t\twidgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\n\t\twidgets.push(create_weight_widget(self.options.line_weight));\n\n\t\twidgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\n\t\twidgets.push(\n\t\t\tRadioInput::new(vec![\n\t\t\t\tRadioEntryData::new(\"all\")\n\t\t\t\t\t.icon(\"HandleVisibilityAll\")\n\t\t\t\t\t.tooltip_label(\"Show All Handles\")\n\t\t\t\t\t.tooltip_description(\"Show all handles regardless of selection.\")\n\t\t\t\t\t.on_update(move |_| {\n\t\t\t\t\t\tPenToolMessage::UpdateOptions {\n\t\t\t\t\t\t\toptions: PenOptionsUpdate::OverlayModeType(PenOverlayMode::AllHandles),\n\t\t\t\t\t\t}\n\t\t\t\t\t\t.into()\n\t\t\t\t\t}),\n\t\t\t\tRadioEntryData::new(\"frontier\")\n\t\t\t\t\t.icon(\"HandleVisibilityFrontier\")\n\t\t\t\t\t.tooltip_label(\"Show Frontier Handles\")\n\t\t\t\t\t.tooltip_description(\"Show only handles at the frontiers of the segments connected to selected points.\")\n\t\t\t\t\t.on_update(move |_| {\n\t\t\t\t\t\tPenToolMessage::UpdateOptions {\n\t\t\t\t\t\t\toptions: PenOptionsUpdate::OverlayModeType(PenOverlayMode::FrontierHandles),\n\t\t\t\t\t\t}\n\t\t\t\t\t\t.into()\n\t\t\t\t\t}),\n\t\t\t])\n\t\t\t.selected_index(Some(self.options.pen_overlay_mode as u32))\n\t\t\t.widget_instance(),\n\t\t);\n\n\t\tLayout(vec![LayoutGroup::row(widgets)])\n\t}\n}\n\n#[message_handler_data]\nimpl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for PenTool {\n\tfn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {\n\t\tlet ToolMessage::Pen(PenToolMessage::UpdateOptions { options }) = message else {\n\t\t\tself.fsm_state.process_event(message, &mut self.tool_data, context, &self.options, responses, true);\n\t\t\treturn;\n\t\t};\n\n\t\tmatch options {\n\t\t\tPenOptionsUpdate::OverlayModeType(overlay_mode_type) => {\n\t\t\t\tself.options.pen_overlay_mode = overlay_mode_type;\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t}\n\t\t\tPenOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,\n\t\t\tPenOptionsUpdate::FillColor(color) => {\n\t\t\t\tself.options.fill.custom_color = color;\n\t\t\t\tself.options.fill.color_type = ToolColorType::Custom;\n\t\t\t}\n\t\t\tPenOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,\n\t\t\tPenOptionsUpdate::StrokeColor(color) => {\n\t\t\t\tself.options.stroke.custom_color = color;\n\t\t\t\tself.options.stroke.color_type = ToolColorType::Custom;\n\t\t\t}\n\t\t\tPenOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,\n\t\t\tPenOptionsUpdate::WorkingColors(primary, secondary) => {\n\t\t\t\tself.options.stroke.primary_working_color = primary;\n\t\t\t\tself.options.stroke.secondary_working_color = secondary;\n\t\t\t\tself.options.fill.primary_working_color = primary;\n\t\t\t\tself.options.fill.secondary_working_color = secondary;\n\t\t\t}\n\t\t}\n\n\t\tself.send_layout(responses, LayoutTarget::ToolOptions);\n\t}\n\n\tfn actions(&self) -> ActionList {\n\t\tmatch self.fsm_state {\n\t\t\tPenToolFsmState::Ready | PenToolFsmState::GRSHandle => actions!(PenToolMessageDiscriminant;\n\t\t\t\tUndo,\n\t\t\t\tDragStart,\n\t\t\t\tDragStop,\n\t\t\t\tConfirm,\n\t\t\t\tAbort,\n\t\t\t\tPointerMove,\n\t\t\t\tFinalPosition\n\t\t\t),\n\t\t\tPenToolFsmState::DraggingHandle(_) | PenToolFsmState::PlacingAnchor => actions!(PenToolMessageDiscriminant;\n\t\t\t\tDragStart,\n\t\t\t\tDragStop,\n\t\t\t\tPointerMove,\n\t\t\t\tConfirm,\n\t\t\t\tAbort,\n\t\t\t\tRemovePreviousHandle,\n\t\t\t\tGRS,\n\t\t\t\tSwapHandles\n\t\t\t),\n\t\t}\n\t}\n}\n\nimpl ToolTransition for PenTool {\n\tfn event_to_message_map(&self) -> EventToMessageMap {\n\t\tEventToMessageMap {\n\t\t\ttool_abort: Some(PenToolMessage::Abort.into()),\n\t\t\tselection_changed: Some(PenToolMessage::SelectionChanged.into()),\n\t\t\tworking_color_changed: Some(PenToolMessage::WorkingColorChanged.into()),\n\t\t\toverlay_provider: Some(|context| PenToolMessage::Overlays { context }.into()),\n\t\t\t..Default::default()\n\t\t}\n\t}\n}\n#[derive(Clone, Debug, Default)]\nstruct ModifierState {\n\tsnap_angle: bool,\n\tlock_angle: bool,\n\tbreak_handle: bool,\n\tcolinear: bool,\n\tmove_anchor_with_handles: bool,\n}\n#[derive(Clone, Debug)]\nstruct LastPoint {\n\tid: PointId,\n\tpos: DVec2,\n\tin_segment: Option<SegmentId>,\n\thandle_start: DVec2,\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]\nenum HandleMode {\n\t/// Pressing 'C' breaks colinearity\n\tFree,\n\t/// Pressing 'Alt': Handle length is locked\n\t#[default]\n\tColinearLocked,\n\t/// Pressing 'Alt': Handles are equidistant\n\tColinearEquidistant,\n}\n\n/// The type of handle which is dragged by the cursor (under the cursor).\n///\n/// ![Terminology](https://files.keavon.com/-/EachNotedLovebird/capture.png)\n#[derive(Clone, Debug, Default, PartialEq, Copy)]\nenum TargetHandle {\n\t#[default]\n\tNone,\n\t/// This is the handle being dragged and represents the out handle of the next preview segment that will be placed\n\t/// after the current preview segment is finalized. Its position is stored in `tool_data.next_handle_start`.\n\t///\n\t/// Pressing Tab swaps to the opposite handle type. The swapped handle can be either [`ManipulatorPointId::PreviewInHandle`]\n\t/// or, in the case of a bent segment, [`ManipulatorPointId::EndHandle`] or [`ManipulatorPointId::PrimaryHandle`].\n\t///\n\t/// When closing a path, the handle being dragged becomes the end handle of the currently placed anchor.\n\t///\n\t/// ![Terminology](https://files.keavon.com/-/EachNotedLovebird/capture.png)\n\tFuturePreviewOutHandle,\n\t/// The opposite handle that is drawn after placing an anchor and starting to drag the \"next handle start\",\n\t/// continuing until Tab is pressed to swap the handles.\n\t///\n\t/// ![Terminology](https://files.keavon.com/-/EachNotedLovebird/capture.png)\n\tPreviewInHandle,\n\t/// This is the primary handle of the segment from whose endpoint a new handle is being drawn.\n\t/// When closing the path, the handle being dragged will be the [`TargetHandle::PreviewInHandle`] (see its documentation);\n\t/// otherwise, it will be [`TargetHandle::FuturePreviewOutHandle`].\n\t///\n\t/// If a handle is dragged from a different endpoint within the same layer, the opposite handle will be\n\t/// `ManipulatorPoint::Primary` if that point is the starting point of its path.\n\t///\n\t/// ![Terminology](https://files.keavon.com/-/EachNotedLovebird/capture.png)\n\tPriorOutHandle(SegmentId),\n\t/// This is the end handle of the segment from whose endpoint a new handle is being drawn (same cases apply\n\t/// as mentioned in [`TargetHandle::PriorOutHandle`]). If a handle is dragged from a different endpoint within the same\n\t/// layer, the opposite handle will be `ManipulatorPoint::EndHandle` if that point is the end point of its path.\n\t///\n\t/// ![Terminology](https://files.keavon.com/-/EachNotedLovebird/capture.png)\n\tPriorInHandle(SegmentId),\n}\n\n#[derive(Clone, Debug, Default)]\nstruct PenToolData {\n\tsnap_manager: SnapManager,\n\tlatest_points: Vec<LastPoint>,\n\tpoint_index: usize,\n\thandle_end: Option<DVec2>,\n\tnext_point: DVec2,\n\tnext_handle_start: DVec2,\n\n\tg1_continuous: bool,\n\n\tangle: f64,\n\tauto_panning: AutoPanning,\n\tmodifiers: ModifierState,\n\n\tbuffering_merged_vector: bool,\n\n\tprevious_handle_start_pos: DVec2,\n\tprevious_handle_end_pos: Option<DVec2>,\n\ttoggle_colinear_debounce: bool,\n\tcolinear: bool,\n\talt_pressed: bool,\n\tspace_pressed: bool,\n\t/// Tracks whether to switch from `HandleMode::ColinearEquidistant` to `HandleMode::Free`\n\t/// after releasing Ctrl, specifically when Ctrl was held before the handle was dragged from the anchor.\n\tswitch_to_free_on_ctrl_release: bool,\n\t/// To prevent showing cursor when `KeyC` is pressed when handles are swapped.\n\thandle_swapped: bool,\n\t/// Prevents conflicts when the handle's angle is already locked and it passes near the anchor,\n\t/// avoiding unintended direction changes. Specifically handles the case where a handle is being dragged,\n\t/// and Ctrl is pressed near the anchor to make it colinear with its opposite handle.\n\tangle_locked: bool,\n\tpath_closed: bool,\n\n\thandle_mode: HandleMode,\n\tprior_segment_layer: Option<LayerNodeIdentifier>,\n\tcurrent_layer: Option<LayerNodeIdentifier>,\n\tprior_segment_endpoint: Option<PointId>,\n\tprior_segment: Option<SegmentId>,\n\n\t/// For vector meshes, storing all the previous segments the last anchor point was connected to\n\tprior_segments: Option<Vec<SegmentId>>,\n\thandle_type: TargetHandle,\n\thandle_start_offset: Option<DVec2>,\n\thandle_end_offset: Option<DVec2>,\n\n\tsnap_cache: SnapCache,\n}\n\nimpl PenToolData {\n\tfn latest_point(&self) -> Option<&LastPoint> {\n\t\tself.latest_points.get(self.point_index)\n\t}\n\n\tfn latest_point_mut(&mut self) -> Option<&mut LastPoint> {\n\t\tself.latest_points.get_mut(self.point_index)\n\t}\n\n\tfn add_point(&mut self, point: LastPoint) {\n\t\tself.point_index = (self.point_index + 1).min(self.latest_points.len());\n\t\tself.latest_points.truncate(self.point_index);\n\t\tself.latest_points.push(point);\n\t}\n\n\tfn cleanup(&mut self, responses: &mut VecDeque<Message>) {\n\t\tself.handle_end = None;\n\t\tself.latest_points.clear();\n\t\tself.point_index = 0;\n\t\tself.snap_manager.cleanup(responses);\n\t}\n\n\t/// Check whether target handle is primary, end, or `self.handle_end`\n\tfn check_end_handle_type(&self, vector: &Vector) -> TargetHandle {\n\t\tmatch (self.handle_end, self.prior_segment_endpoint, self.prior_segment, self.path_closed) {\n\t\t\t(Some(_), _, _, false) => TargetHandle::PreviewInHandle,\n\t\t\t(None, Some(point), Some(segment), false) | (Some(_), Some(point), Some(segment), true) => {\n\t\t\t\tif vector.segment_start_from_id(segment) == Some(point) {\n\t\t\t\t\tTargetHandle::PriorOutHandle(segment)\n\t\t\t\t} else {\n\t\t\t\t\tTargetHandle::PriorInHandle(segment)\n\t\t\t\t}\n\t\t\t}\n\t\t\t_ => TargetHandle::None,\n\t\t}\n\t}\n\n\tfn check_grs_end_handle(&self, vector: &Vector) -> TargetHandle {\n\t\tlet Some(point) = self.latest_point().map(|point| point.id) else { return TargetHandle::None };\n\t\tlet Some(segment) = self.prior_segment else { return TargetHandle::None };\n\n\t\tif vector.segment_start_from_id(segment) == Some(point) {\n\t\t\tTargetHandle::PriorOutHandle(segment)\n\t\t} else {\n\t\t\tTargetHandle::PriorInHandle(segment)\n\t\t}\n\t}\n\n\tfn get_opposite_handle_type(&self, handle_type: TargetHandle, vector: &Vector) -> TargetHandle {\n\t\tmatch handle_type {\n\t\t\tTargetHandle::FuturePreviewOutHandle => self.check_end_handle_type(vector),\n\t\t\tTargetHandle::PreviewInHandle => match (self.path_closed, self.prior_segment_endpoint, self.prior_segment) {\n\t\t\t\t(true, Some(point), Some(segment)) => {\n\t\t\t\t\tif vector.segment_start_from_id(segment) == Some(point) {\n\t\t\t\t\t\tTargetHandle::PriorOutHandle(segment)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tTargetHandle::PriorInHandle(segment)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t(false, _, _) => TargetHandle::FuturePreviewOutHandle,\n\t\t\t\t_ => TargetHandle::None,\n\t\t\t},\n\t\t\t_ => {\n\t\t\t\tif self.path_closed {\n\t\t\t\t\tTargetHandle::PreviewInHandle\n\t\t\t\t} else {\n\t\t\t\t\tTargetHandle::FuturePreviewOutHandle\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfn update_handle_type(&mut self, handle_type: TargetHandle) {\n\t\tself.handle_type = handle_type;\n\t}\n\n\tfn update_target_handle_pos(&mut self, handle_type: TargetHandle, anchor_pos: DVec2, responses: &mut VecDeque<Message>, delta: DVec2, layer: LayerNodeIdentifier) {\n\t\tmatch handle_type {\n\t\t\tTargetHandle::FuturePreviewOutHandle => {\n\t\t\t\tself.next_handle_start = delta;\n\t\t\t}\n\t\t\tTargetHandle::PreviewInHandle => {\n\t\t\t\tif let Some(handle) = self.handle_end.as_mut() {\n\t\t\t\t\t*handle = delta;\n\t\t\t\t}\n\t\t\t}\n\t\t\tTargetHandle::PriorInHandle(segment) => {\n\t\t\t\tlet relative_position = delta - anchor_pos;\n\t\t\t\tlet modification_type = VectorModificationType::SetEndHandle { segment, relative_position };\n\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t}\n\t\t\tTargetHandle::PriorOutHandle(segment) => {\n\t\t\t\tlet relative_position = delta - anchor_pos;\n\t\t\t\tlet modification_type = VectorModificationType::SetPrimaryHandle { segment, relative_position };\n\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t}\n\t\t\tTargetHandle::None => {}\n\t\t}\n\t}\n\n\tfn target_handle_position(&self, handle_type: TargetHandle, vector: &Vector) -> Option<DVec2> {\n\t\tmatch handle_type {\n\t\t\tTargetHandle::PriorOutHandle(segment) => ManipulatorPointId::PrimaryHandle(segment).get_position(vector),\n\t\t\tTargetHandle::PriorInHandle(segment) => ManipulatorPointId::EndHandle(segment).get_position(vector),\n\t\t\tTargetHandle::PreviewInHandle => self.handle_end,\n\t\t\tTargetHandle::FuturePreviewOutHandle => Some(self.next_handle_start),\n\t\t\tTargetHandle::None => None,\n\t\t}\n\t}\n\n\t/// Remove the handles selected when swapping handles\n\tfn cleanup_target_selections(&self, shape_editor: &mut ShapeState, layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {\n\t\tlet Some(shape_state) = layer.and_then(|layer| shape_editor.selected_shape_state.get_mut(&layer)) else {\n\t\t\treturn;\n\t\t};\n\n\t\tlet Some(vector) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)) else {\n\t\t\treturn;\n\t\t};\n\n\t\tmatch self.check_end_handle_type(&vector) {\n\t\t\tTargetHandle::PriorInHandle(segment) => shape_state.deselect_point(ManipulatorPointId::EndHandle(segment)),\n\t\t\tTargetHandle::PriorOutHandle(segment) => shape_state.deselect_point(ManipulatorPointId::PrimaryHandle(segment)),\n\t\t\t_ => {}\n\t\t}\n\t\tresponses.add(OverlaysMessage::Draw);\n\t}\n\n\t/// Selects the handle which is currently dragged by the user.\n\tfn add_target_selections(&self, shape_editor: &mut ShapeState, layer: Option<LayerNodeIdentifier>) {\n\t\tlet Some(shape_state) = layer.and_then(|layer| shape_editor.selected_shape_state.get_mut(&layer)) else {\n\t\t\treturn;\n\t\t};\n\n\t\tmatch self.handle_type {\n\t\t\tTargetHandle::PriorInHandle(segment) => shape_state.select_point(ManipulatorPointId::EndHandle(segment)),\n\t\t\tTargetHandle::PriorOutHandle(segment) => shape_state.select_point(ManipulatorPointId::PrimaryHandle(segment)),\n\t\t\t_ => {}\n\t\t}\n\t}\n\n\t/// Check whether moving the initially created point.\n\tfn moving_start_point(&self) -> bool {\n\t\tself.latest_points.len() == 1 && self.latest_point().is_some_and(|point| point.pos == self.next_point)\n\t}\n\n\t// When the vector transform changes, the positions of the points must be recalculated.\n\tfn recalculate_latest_points_position(&mut self, document: &DocumentMessageHandler) {\n\t\tlet selected_nodes = document.network_interface.selected_nodes();\n\t\tlet mut selected_layers = selected_nodes.selected_layers(document.metadata());\n\t\tif let (Some(layer), None) = (selected_layers.next(), selected_layers.next()) {\n\t\t\tlet Some(vector) = document.network_interface.compute_modified_vector(layer) else { return };\n\t\t\tfor point in &mut self.latest_points {\n\t\t\t\tlet Some(pos) = vector.point_domain.position_from_id(point.id) else { continue };\n\t\t\t\tpoint.pos = pos;\n\t\t\t\tpoint.handle_start = point.pos;\n\t\t\t}\n\t\t}\n\t}\n\n\t/// If the user places the anchor on top of the previous anchor, it becomes sharp and the outgoing handle may be dragged.\n\tfn bend_from_previous_point(&mut self, snap_data: SnapData, transform: DAffine2, layer: LayerNodeIdentifier, shape_editor: &mut ShapeState, responses: &mut VecDeque<Message>) {\n\t\tself.g1_continuous = true;\n\t\tlet document = snap_data.document;\n\t\tself.next_handle_start = self.next_point;\n\t\tlet Some(vector) = document.network_interface.compute_modified_vector(layer) else { return };\n\t\tself.update_handle_type(TargetHandle::FuturePreviewOutHandle);\n\t\tself.handle_mode = HandleMode::ColinearLocked;\n\n\t\t// Break the control\n\t\tlet Some((last_pos, id)) = self.latest_point().map(|point| (point.pos, point.id)) else { return };\n\n\t\tlet transform = document.metadata().document_to_viewport * transform;\n\t\tlet on_top = transform.transform_point2(self.next_point).distance_squared(transform.transform_point2(last_pos)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2);\n\t\tif on_top {\n\t\t\tself.handle_end = None;\n\t\t\tself.handle_mode = HandleMode::Free;\n\n\t\t\tself.store_clicked_endpoint(document, &transform, snap_data.input, snap_data.viewport);\n\n\t\t\tif self.modifiers.lock_angle {\n\t\t\t\tself.set_lock_angle(&vector, id, self.prior_segment);\n\t\t\t\tlet last_segment = self.prior_segment;\n\t\t\t\tlet Some(point) = self.latest_point_mut() else { return };\n\t\t\t\tpoint.in_segment = last_segment;\n\t\t\t\tself.switch_to_free_on_ctrl_release = true;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif let Some(point) = self.latest_point_mut() {\n\t\t\t\tpoint.in_segment = None;\n\t\t\t}\n\t\t}\n\n\t\t// Closing path\n\t\tlet closing_path_on_point = self.close_path_on_point(snap_data, &vector, document, id, &transform);\n\t\tif !closing_path_on_point {\n\t\t\t// Attempt to find nearest segment and close path on segment by creating an anchor point on it\n\t\t\tlet tolerance = crate::consts::SNAP_POINT_TOLERANCE;\n\t\t\tif let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, transform.transform_point2(self.next_point), tolerance) {\n\t\t\t\tlet (point, _) = closest_segment.adjusted_insert(responses);\n\n\t\t\t\tself.update_handle_type(TargetHandle::PreviewInHandle);\n\t\t\t\tself.handle_end_offset = None;\n\t\t\t\tself.path_closed = true;\n\t\t\t\tself.next_handle_start = self.next_point;\n\n\t\t\t\tself.prior_segment_endpoint = Some(point);\n\t\t\t\tself.prior_segment_layer = Some(closest_segment.layer());\n\t\t\t\tself.prior_segments = None;\n\t\t\t\tself.prior_segment = None;\n\n\t\t\t\t// Should also update the SnapCache here?\n\n\t\t\t\tself.handle_mode = HandleMode::Free;\n\t\t\t\tif let (true, Some(prior_endpoint)) = (self.modifiers.lock_angle, self.prior_segment_endpoint) {\n\t\t\t\t\tself.set_lock_angle(&vector, prior_endpoint, self.prior_segment);\n\t\t\t\t\tself.switch_to_free_on_ctrl_release = true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfn close_path_on_point(&mut self, snap_data: SnapData, vector: &Vector, document: &DocumentMessageHandler, id: PointId, transform: &DAffine2) -> bool {\n\t\tfor id in vector.anchor_points().filter(|&point| point != id) {\n\t\t\tlet Some(pos) = vector.point_domain.position_from_id(id) else { continue };\n\t\t\tlet transformed_distance_between_squared = transform.transform_point2(pos).distance_squared(transform.transform_point2(self.next_point));\n\t\t\tlet snap_point_tolerance_squared = crate::consts::SNAP_POINT_TOLERANCE.powi(2);\n\n\t\t\tif transformed_distance_between_squared < snap_point_tolerance_squared {\n\t\t\t\tself.update_handle_type(TargetHandle::PreviewInHandle);\n\t\t\t\tself.handle_end_offset = None;\n\t\t\t\tself.path_closed = true;\n\t\t\t\tself.next_handle_start = self.next_point;\n\t\t\t\tself.store_clicked_endpoint(document, transform, snap_data.input, snap_data.viewport);\n\t\t\t\tself.handle_mode = HandleMode::Free;\n\t\t\t\tif let (true, Some(prior_endpoint)) = (self.modifiers.lock_angle, self.prior_segment_endpoint) {\n\t\t\t\t\tself.set_lock_angle(vector, prior_endpoint, self.prior_segment);\n\t\t\t\t\tself.switch_to_free_on_ctrl_release = true;\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\tfalse\n\t}\n\n\tfn finish_placing_handle(&mut self, snap_data: SnapData, transform: DAffine2, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {\n\t\tlet document = snap_data.document;\n\t\tlet next_handle_start = self.next_handle_start;\n\t\tlet handle_start = self.latest_point()?.handle_start;\n\t\tlet mouse = snap_data.input.mouse.position;\n\t\tself.handle_swapped = false;\n\t\tself.handle_end_offset = None;\n\t\tself.handle_start_offset = None;\n\t\tlet Some(handle_end) = self.handle_end else {\n\t\t\tresponses.add(DocumentMessage::EndTransaction);\n\t\t\tself.handle_end = Some(next_handle_start);\n\t\t\tself.place_anchor(snap_data, transform, mouse, responses);\n\t\t\tself.latest_point_mut()?.handle_start = next_handle_start;\n\t\t\treturn None;\n\t\t};\n\t\tlet next_point = self.next_point;\n\t\tself.place_anchor(snap_data, transform, mouse, responses);\n\t\tlet handles = [handle_start - self.latest_point()?.pos, handle_end - next_point].map(Some);\n\n\t\t// Get close path\n\t\tlet mut end = None;\n\t\tlet selected_nodes = document.network_interface.selected_nodes();\n\t\tlet mut selected_layers = selected_nodes.selected_layers(document.metadata());\n\t\tlet layer = selected_layers.next().filter(|_| selected_layers.next().is_none()).or(self.current_layer)?;\n\t\tlet vector = document.network_interface.compute_modified_vector(layer)?;\n\t\tlet start = self.latest_point()?.id;\n\t\tlet transform = document.metadata().document_to_viewport * transform;\n\t\tfor id in vector.anchor_points().filter(|&point| point != start) {\n\t\t\tlet Some(pos) = vector.point_domain.position_from_id(id) else { continue };\n\t\t\tlet transformed_distance_between_squared = transform.transform_point2(pos).distance_squared(transform.transform_point2(next_point));\n\t\t\tlet snap_point_tolerance_squared = crate::consts::SNAP_POINT_TOLERANCE.powi(2);\n\t\t\tif transformed_distance_between_squared < snap_point_tolerance_squared {\n\t\t\t\tend = Some(id);\n\t\t\t}\n\t\t}\n\t\tlet close_subpath = end.is_some();\n\n\t\t// Generate new point if not closing\n\t\tlet end = end.unwrap_or_else(|| {\n\t\t\tlet end = PointId::generate();\n\t\t\tlet modification_type = VectorModificationType::InsertPoint { id: end, position: next_point };\n\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\n\t\t\tend\n\t\t});\n\n\t\t// Store the segment\n\t\tlet id = SegmentId::generate();\n\t\tif self.path_closed\n\t\t\t&& let Some((handles, handle1_pos)) = match self.get_opposite_handle_type(TargetHandle::PreviewInHandle, &vector) {\n\t\t\t\tTargetHandle::PriorOutHandle(segment) => {\n\t\t\t\t\tlet handles = [HandleId::end(id), HandleId::primary(segment)];\n\t\t\t\t\tlet handle1_pos = handles[1].to_manipulator_point().get_position(&vector);\n\t\t\t\t\thandle1_pos.map(|pos| (handles, pos))\n\t\t\t\t}\n\t\t\t\tTargetHandle::PriorInHandle(segment) => {\n\t\t\t\t\tlet handles = [HandleId::end(id), HandleId::end(segment)];\n\t\t\t\t\tlet handle1_pos = handles[1].to_manipulator_point().get_position(&vector);\n\t\t\t\t\thandle1_pos.map(|pos| (handles, pos))\n\t\t\t\t}\n\t\t\t\t_ => None,\n\t\t\t} {\n\t\t\tlet angle = (handle_end - next_point).angle_to(handle1_pos - next_point);\n\t\t\tlet pi = std::f64::consts::PI;\n\t\t\tlet colinear = (angle - pi).abs() < 1e-6 || (angle + pi).abs() < 1e-6;\n\t\t\tresponses.add(GraphOperationMessage::Vector {\n\t\t\t\tlayer,\n\t\t\t\tmodification_type: VectorModificationType::SetG1Continuous { handles, enabled: colinear },\n\t\t\t});\n\t\t\tself.cleanup(responses);\n\t\t}\n\n\t\tself.prior_segment = Some(id);\n\n\t\tlet points = [start, end];\n\t\tlet modification_type = VectorModificationType::InsertSegment { id, points, handles };\n\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\n\t\t// Mirror\n\t\tif let Some((last_segment, last_point)) = self.latest_point().and_then(|point| point.in_segment).zip(self.latest_point()) {\n\t\t\tlet end = vector.segment_end_from_id(last_segment) == Some(last_point.id);\n\t\t\tlet handles = if end {\n\t\t\t\t[HandleId::end(last_segment), HandleId::primary(id)]\n\t\t\t} else {\n\t\t\t\t[HandleId::primary(last_segment), HandleId::primary(id)]\n\t\t\t};\n\n\t\t\tif let Some(h1) = handles[0].to_manipulator_point().get_position(&vector) {\n\t\t\t\tlet angle = (h1 - last_point.pos).angle_to(last_point.handle_start - last_point.pos);\n\t\t\t\tlet pi = std::f64::consts::PI;\n\t\t\t\tlet colinear = (angle - pi).abs() < 1e-6 || (angle + pi).abs() < 1e-6;\n\t\t\t\tresponses.add(GraphOperationMessage::Vector {\n\t\t\t\t\tlayer,\n\t\t\t\t\tmodification_type: VectorModificationType::SetG1Continuous { handles, enabled: colinear },\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tif !close_subpath {\n\t\t\tself.add_point(LastPoint {\n\t\t\t\tid: end,\n\t\t\t\tpos: next_point,\n\t\t\t\tin_segment: self.g1_continuous.then_some(id),\n\t\t\t\thandle_start: next_handle_start,\n\t\t\t});\n\t\t}\n\t\tself.path_closed = false;\n\t\tself.prior_segment_endpoint = None;\n\t\tresponses.add(DocumentMessage::EndTransaction);\n\t\tSome(if close_subpath { PenToolFsmState::Ready } else { PenToolFsmState::PlacingAnchor })\n\t}\n\n\t#[allow(clippy::too_many_arguments)]\n\t/// Calculates snap position delta while moving anchor and its handles.\n\tfn space_anchor_handle_snap(\n\t\t&mut self,\n\t\tviewport_to_document: &DAffine2,\n\t\ttransform: &DAffine2,\n\t\tsnap_data: &SnapData<'_>,\n\t\tmouse: &DVec2,\n\t\tvector: &Vector,\n\t\tinput: &InputPreprocessorMessageHandler,\n\t\tviewport: &ViewportMessageHandler,\n\t) -> Option<DVec2> {\n\t\tlet reference_handle = if self.path_closed { TargetHandle::PreviewInHandle } else { TargetHandle::FuturePreviewOutHandle };\n\t\tlet end_handle = self.get_opposite_handle_type(reference_handle, vector);\n\t\tlet end_handle_pos = self.target_handle_position(end_handle, vector);\n\t\tlet ref_pos = self.target_handle_position(reference_handle, vector)?;\n\t\tlet snap = &mut self.snap_manager;\n\t\tlet snap_data = SnapData::new_snap_cache(snap_data.document, input, viewport, &self.snap_cache);\n\n\t\tlet handle_start_offset = self.handle_start_offset.unwrap_or(DVec2::ZERO);\n\t\tlet document_pos = viewport_to_document.transform_point2(*mouse + handle_start_offset);\n\n\t\tlet anchor_offset = transform.transform_point2(self.next_point - ref_pos);\n\n\t\tlet handle_start = SnapCandidatePoint::handle(document_pos);\n\t\tlet anchor = SnapCandidatePoint::handle(document_pos + anchor_offset);\n\n\t\tlet snapped_near_handle_start = snap.free_snap(&snap_data, &handle_start, SnapTypeConfiguration::default());\n\t\tlet snapped_anchor = snap.free_snap(&snap_data, &anchor, SnapTypeConfiguration::default());\n\n\t\tlet handle_snap_option = end_handle_pos.and_then(|handle| match end_handle {\n\t\t\tTargetHandle::None => None,\n\t\t\tTargetHandle::FuturePreviewOutHandle => None,\n\t\t\t_ => {\n\t\t\t\tlet handle_offset = transform.transform_point2(handle - ref_pos);\n\t\t\t\tlet handle_snap = SnapCandidatePoint::handle(document_pos + handle_offset);\n\t\t\t\tSome((handle, handle_snap))\n\t\t\t}\n\t\t});\n\n\t\tlet mut delta: DVec2;\n\t\tlet best_snapped = if snapped_near_handle_start.other_snap_better(&snapped_anchor) {\n\t\t\tdelta = snapped_anchor.snapped_point_document - transform.transform_point2(self.next_point);\n\t\t\tsnapped_anchor\n\t\t} else {\n\t\t\tdelta = snapped_near_handle_start.snapped_point_document - transform.transform_point2(ref_pos);\n\t\t\tsnapped_near_handle_start\n\t\t};\n\n\t\tlet Some((handle, handle_snap)) = handle_snap_option else {\n\t\t\tsnap.update_indicator(best_snapped);\n\t\t\treturn Some(transform.inverse().transform_vector2(delta));\n\t\t};\n\n\t\tlet snapped_handle = snap.free_snap(&snap_data, &handle_snap, SnapTypeConfiguration::default());\n\n\t\tif best_snapped.other_snap_better(&snapped_handle) {\n\t\t\tdelta = snapped_handle.snapped_point_document - transform.transform_point2(handle);\n\t\t\tsnap.update_indicator(snapped_handle);\n\t\t} else {\n\t\t\tsnap.update_indicator(best_snapped);\n\t\t}\n\n\t\t// Transform delta back to original coordinate space\n\t\tSome(transform.inverse().transform_vector2(delta))\n\t}\n\n\t/// Calculates the offset from the mouse when swapping handles, and swaps the handles.\n\tfn swap_handles(\n\t\t&mut self,\n\t\tlayer: Option<LayerNodeIdentifier>,\n\t\tdocument: &DocumentMessageHandler,\n\t\tshape_editor: &mut ShapeState,\n\t\tinput: &InputPreprocessorMessageHandler,\n\t\tresponses: &mut VecDeque<Message>,\n\t) {\n\t\t// Validate necessary data exists\n\t\tlet Some(vector) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)) else {\n\t\t\treturn;\n\t\t};\n\n\t\tlet Some(viewport) = layer.map(|layer| document.metadata().transform_to_viewport(layer)) else {\n\t\t\treturn;\n\t\t};\n\n\t\t// Determine if we need to swap to opposite handle\n\t\tlet should_swap_to_opposite = self.path_closed && matches!(self.handle_type, TargetHandle::PreviewInHandle | TargetHandle::PriorOutHandle(..) | TargetHandle::PriorInHandle(..))\n\t\t\t|| !self.path_closed && matches!(self.handle_type, TargetHandle::FuturePreviewOutHandle);\n\n\t\t// Determine if we need to swap to start handle\n\t\tlet should_swap_to_start = !self.path_closed && !matches!(self.handle_type, TargetHandle::None | TargetHandle::FuturePreviewOutHandle);\n\n\t\tif should_swap_to_opposite {\n\t\t\tlet opposite_type = self.get_opposite_handle_type(self.handle_type, &vector);\n\t\t\t// Update offset\n\t\t\tlet Some(handle_pos) = self.target_handle_position(opposite_type, &vector) else {\n\t\t\t\tself.handle_swapped = false;\n\t\t\t\treturn;\n\t\t\t};\n\t\t\tif (handle_pos - self.next_point).length() < 1e-6 {\n\t\t\t\tself.handle_swapped = false;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tself.handle_end_offset = Some(viewport.transform_point2(handle_pos) - input.mouse.position);\n\n\t\t\t// Update selections if in closed path mode\n\t\t\tif self.path_closed {\n\t\t\t\tself.cleanup_target_selections(shape_editor, layer, document, responses);\n\t\t\t}\n\t\t\tself.update_handle_type(opposite_type);\n\t\t\tself.add_target_selections(shape_editor, layer);\n\t\t} else if should_swap_to_start {\n\t\t\tself.cleanup_target_selections(shape_editor, layer, document, responses);\n\n\t\t\t// Calculate offset from mouse position to next handle start\n\t\t\tif let Some(layer_id) = layer {\n\t\t\t\tlet transform = document.metadata().transform_to_viewport(layer_id);\n\t\t\t\tself.handle_start_offset = Some(transform.transform_point2(self.next_handle_start) - input.mouse.position);\n\t\t\t}\n\n\t\t\tself.update_handle_type(TargetHandle::FuturePreviewOutHandle);\n\t\t}\n\n\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::None });\n\t}\n\n\t/// Handles moving the initially created point\n\tfn handle_single_point_path_drag(&mut self, delta: DVec2, layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {\n\t\tself.next_handle_start += delta;\n\t\tself.next_point += delta;\n\n\t\tlet Some(latest) = self.latest_point_mut() else {\n\t\t\treturn Some(PenToolFsmState::DraggingHandle(self.handle_mode));\n\t\t};\n\n\t\tlatest.pos += delta;\n\n\t\tlet modification_type = VectorModificationType::ApplyPointDelta { point: latest.id, delta };\n\n\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\n\t\tresponses.add(OverlaysMessage::Draw);\n\t\tSome(PenToolFsmState::DraggingHandle(self.handle_mode))\n\t}\n\n\tfn move_anchor_and_handles(&mut self, delta: DVec2, layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>, vector: &Vector) {\n\t\tif self.handle_end.is_none()\n\t\t\t&& let Some(latest_pt) = self.latest_point_mut()\n\t\t{\n\t\t\tlatest_pt.pos += delta;\n\t\t}\n\n\t\tlet Some(end_point) = self.prior_segment_endpoint else { return };\n\n\t\tlet modification_type_anchor = VectorModificationType::ApplyPointDelta { point: end_point, delta };\n\t\tresponses.add(GraphOperationMessage::Vector {\n\t\t\tlayer,\n\t\t\tmodification_type: modification_type_anchor,\n\t\t});\n\n\t\tlet reference_handle = if self.path_closed { TargetHandle::PreviewInHandle } else { TargetHandle::FuturePreviewOutHandle };\n\n\t\t// Move the end handle\n\t\tlet end_handle_type = self.get_opposite_handle_type(reference_handle, vector);\n\t\tmatch end_handle_type {\n\t\t\tTargetHandle::PriorInHandle(..) | TargetHandle::PriorOutHandle(..) => {\n\t\t\t\tlet Some(handle_pos) = self.target_handle_position(end_handle_type, vector) else { return };\n\t\t\t\tself.update_target_handle_pos(end_handle_type, self.next_point, responses, handle_pos + delta, layer);\n\t\t\t}\n\t\t\t_ => {}\n\t\t}\n\t}\n\n\t#[allow(clippy::too_many_arguments)]\n\tfn drag_handle(\n\t\t&mut self,\n\t\tsnap_data: SnapData,\n\t\ttransform: DAffine2,\n\t\tmouse: DVec2,\n\t\tresponses: &mut VecDeque<Message>,\n\t\tlayer: Option<LayerNodeIdentifier>,\n\t\tinput: &InputPreprocessorMessageHandler,\n\t\tviewport: &ViewportMessageHandler,\n\t) -> Option<PenToolFsmState> {\n\t\tlet colinear = (self.handle_mode == HandleMode::ColinearEquidistant && self.modifiers.break_handle) || (self.handle_mode == HandleMode::ColinearLocked && !self.modifiers.break_handle);\n\t\tlet document = snap_data.document;\n\t\tlet Some(layer) = layer else { return Some(PenToolFsmState::DraggingHandle(self.handle_mode)) };\n\t\tlet vector = document.network_interface.compute_modified_vector(layer)?;\n\t\tlet viewport_to_document = document.metadata().document_to_viewport.inverse();\n\t\tlet mut mouse_pos = mouse;\n\n\t\t// Handles pressing Space to drag anchor and its handles\n\t\tif self.modifiers.move_anchor_with_handles {\n\t\t\tlet Some(delta) = self.space_anchor_handle_snap(&viewport_to_document, &transform, &snap_data, &mouse, &vector, input, viewport) else {\n\t\t\t\treturn Some(PenToolFsmState::DraggingHandle(self.handle_mode));\n\t\t\t};\n\n\t\t\tif self.moving_start_point() {\n\t\t\t\treturn self.handle_single_point_path_drag(delta, layer, responses);\n\t\t\t}\n\n\t\t\tself.next_handle_start += delta;\n\t\t\tself.next_point += delta;\n\n\t\t\tif let Some(handle) = self.handle_end.as_mut() {\n\t\t\t\t*handle += delta;\n\t\t\t\tif !self.path_closed {\n\t\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\t\treturn Some(PenToolFsmState::DraggingHandle(self.handle_mode));\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tself.move_anchor_and_handles(delta, layer, responses, &vector);\n\n\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\treturn Some(PenToolFsmState::DraggingHandle(self.handle_mode));\n\t\t}\n\n\t\tmatch self.handle_type {\n\t\t\tTargetHandle::FuturePreviewOutHandle => {\n\t\t\t\tlet offset = self.handle_start_offset.unwrap_or(DVec2::ZERO);\n\t\t\t\tmouse_pos += offset;\n\t\t\t\tself.next_handle_start = self.compute_snapped_angle(snap_data.clone(), transform, colinear, mouse_pos, Some(self.next_point), false);\n\t\t\t}\n\t\t\t_ => {\n\t\t\t\tmouse_pos += self.handle_end_offset.unwrap_or(DVec2::ZERO);\n\t\t\t\tlet mouse_pos = self.compute_snapped_angle(snap_data.clone(), transform, colinear, mouse_pos, Some(self.next_point), false);\n\t\t\t\tself.update_target_handle_pos(self.handle_type, self.next_point, responses, mouse_pos, layer);\n\t\t\t}\n\t\t}\n\n\t\tlet mouse_pos = viewport_to_document.transform_point2(mouse_pos);\n\t\tlet anchor = transform.transform_point2(self.next_point);\n\t\tlet distance = (mouse_pos - anchor).length();\n\n\t\tif self.switch_to_free_on_ctrl_release && !self.modifiers.lock_angle {\n\t\t\tself.switch_to_free_on_ctrl_release = false;\n\t\t\tself.handle_mode = HandleMode::Free;\n\t\t}\n\n\t\tif distance > 20. && self.handle_mode == HandleMode::Free && self.modifiers.lock_angle && !self.angle_locked {\n\t\t\tself.angle_locked = true\n\t\t}\n\n\t\tmatch self.handle_mode {\n\t\t\tHandleMode::ColinearLocked | HandleMode::ColinearEquidistant => {\n\t\t\t\tself.g1_continuous = true;\n\t\t\t\tself.apply_colinear_constraint(responses, layer, self.next_point, &vector);\n\t\t\t\tself.adjust_handle_length(responses, layer, &vector);\n\t\t\t}\n\t\t\tHandleMode::Free => {\n\t\t\t\tself.g1_continuous = false;\n\t\t\t}\n\t\t}\n\n\t\tif distance < 20. && self.handle_mode == HandleMode::Free && self.modifiers.lock_angle && !self.angle_locked {\n\t\t\tlet Some(endpoint) = self.prior_segment_endpoint else {\n\t\t\t\treturn Some(PenToolFsmState::DraggingHandle(self.handle_mode));\n\t\t\t};\n\t\t\tself.set_lock_angle(&vector, endpoint, self.prior_segment);\n\t\t\tself.switch_to_free_on_ctrl_release = true;\n\t\t\tlet last_segment = self.prior_segment;\n\t\t\tif let Some(latest) = self.latest_point_mut() {\n\t\t\t\tlatest.in_segment = last_segment;\n\t\t\t}\n\t\t}\n\n\t\tresponses.add(OverlaysMessage::Draw);\n\n\t\tSome(PenToolFsmState::DraggingHandle(self.handle_mode))\n\t}\n\n\t/// Makes the opposite handle equidistant or locks its length.\n\tfn adjust_handle_length(&mut self, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, vector: &Vector) {\n\t\tlet opposite_handle_type = self.get_opposite_handle_type(self.handle_type, vector);\n\t\tmatch self.handle_mode {\n\t\t\tHandleMode::ColinearEquidistant => {\n\t\t\t\tif self.modifiers.break_handle {\n\t\t\t\t\t// Store handle for later restoration only when Alt is first pressed\n\t\t\t\t\tif !self.alt_pressed {\n\t\t\t\t\t\tself.previous_handle_end_pos = self.target_handle_position(opposite_handle_type, vector);\n\t\t\t\t\t\tself.alt_pressed = true;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Set handle to opposite position of the other handle\n\t\t\t\t\tlet Some(new_position) = self.target_handle_position(self.handle_type, vector).map(|handle| self.next_point * 2. - handle) else {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t};\n\t\t\t\t\tself.update_target_handle_pos(opposite_handle_type, self.next_point, responses, new_position, layer);\n\t\t\t\t} else if self.alt_pressed {\n\t\t\t\t\t// Restore the previous handle position when Alt is released\n\t\t\t\t\tif let Some(previous_handle) = self.previous_handle_end_pos {\n\t\t\t\t\t\tself.update_target_handle_pos(opposite_handle_type, self.next_point, responses, previous_handle, layer);\n\t\t\t\t\t}\n\t\t\t\t\tself.alt_pressed = false;\n\t\t\t\t\tself.previous_handle_end_pos = None;\n\t\t\t\t}\n\t\t\t}\n\t\t\tHandleMode::ColinearLocked => {\n\t\t\t\tif !self.modifiers.break_handle {\n\t\t\t\t\tlet Some(new_position) = self.target_handle_position(self.handle_type, vector).map(|handle| self.next_point * 2. - handle) else {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t};\n\t\t\t\t\tself.update_target_handle_pos(opposite_handle_type, self.next_point, responses, new_position, layer);\n\t\t\t\t}\n\t\t\t}\n\t\t\tHandleMode::Free => {}\n\t\t}\n\t}\n\n\tfn apply_colinear_constraint(&mut self, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, anchor_pos: DVec2, vector: &Vector) {\n\t\tlet Some(handle) = self.target_handle_position(self.handle_type, vector) else { return };\n\n\t\tif (anchor_pos - handle).length() < 1e-6 && self.modifiers.lock_angle {\n\t\t\treturn;\n\t\t}\n\n\t\tlet Some(direction) = (anchor_pos - handle).try_normalize() else { return };\n\n\t\tlet opposite_handle = self.get_opposite_handle_type(self.handle_type, vector);\n\n\t\tlet Some(handle_offset) = self.target_handle_position(opposite_handle, vector).map(|handle| (handle - anchor_pos).length()) else {\n\t\t\treturn;\n\t\t};\n\n\t\tlet new_handle_position = anchor_pos + handle_offset * direction;\n\n\t\tself.update_target_handle_pos(opposite_handle, self.next_point, responses, new_handle_position, layer);\n\t}\n\n\tfn place_anchor(&mut self, snap_data: SnapData, transform: DAffine2, mouse: DVec2, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {\n\t\tlet document = snap_data.document;\n\n\t\tlet relative = if self.path_closed { None } else { self.latest_point().map(|point| point.pos) };\n\t\tself.next_point = self.compute_snapped_angle(snap_data, transform, false, mouse, relative, true);\n\n\t\tlet selected_nodes = document.network_interface.selected_nodes();\n\t\tlet mut selected_layers = selected_nodes.selected_layers(document.metadata());\n\t\tlet layer = selected_layers.next().filter(|_| selected_layers.next().is_none()).or(self.current_layer)?;\n\t\tlet vector = document.network_interface.compute_modified_vector(layer)?;\n\t\tlet transform = document.metadata().document_to_viewport * transform;\n\t\tfor point in vector.anchor_points() {\n\t\t\tlet Some(pos) = vector.point_domain.position_from_id(point) else { continue };\n\t\t\tlet transformed_distance_between_squared = transform.transform_point2(pos).distance_squared(transform.transform_point2(self.next_point));\n\t\t\tlet snap_point_tolerance_squared = crate::consts::SNAP_POINT_TOLERANCE.powi(2);\n\t\t\tif transformed_distance_between_squared < snap_point_tolerance_squared {\n\t\t\t\tself.next_point = pos;\n\t\t\t}\n\t\t}\n\t\tif let Some(handle_end) = self.handle_end.as_mut() {\n\t\t\t*handle_end = self.next_point;\n\t\t\tself.next_handle_start = self.next_point;\n\t\t}\n\t\tresponses.add(OverlaysMessage::Draw);\n\n\t\tSome(PenToolFsmState::PlacingAnchor)\n\t}\n\n\t/// Snap the angle of the line from relative to position if the key is pressed.\n\tfn compute_snapped_angle(&mut self, snap_data: SnapData, transform: DAffine2, colinear: bool, mouse: DVec2, relative: Option<DVec2>, neighbor: bool) -> DVec2 {\n\t\tlet ModifierState { snap_angle, lock_angle, .. } = self.modifiers;\n\t\tlet document = snap_data.document;\n\t\tlet mut document_pos = document.metadata().document_to_viewport.inverse().transform_point2(mouse);\n\t\tlet snap = &mut self.snap_manager;\n\n\t\tlet neighbors = relative.filter(|_| neighbor).map_or(Vec::new(), |neighbor| vec![neighbor]);\n\n\t\tlet config = SnapTypeConfiguration::default();\n\t\tif let Some(relative) = relative\n\t\t\t.map(|layer| transform.transform_point2(layer))\n\t\t\t.filter(|&relative| (snap_angle || lock_angle) && (relative - document_pos).length_squared() > f64::EPSILON * 100.)\n\t\t{\n\t\t\tlet resolution = LINE_ROTATE_SNAP_ANGLE.to_radians();\n\n\t\t\tlet angle = if lock_angle {\n\t\t\t\tself.angle\n\t\t\t} else if (relative - document_pos) != DVec2::ZERO && !lock_angle {\n\t\t\t\t(-(relative - document_pos).angle_to(DVec2::X) / resolution).round() * resolution\n\t\t\t} else {\n\t\t\t\tself.angle\n\t\t\t};\n\t\t\tdocument_pos = relative - (relative - document_pos).project_onto(DVec2::new(angle.cos(), angle.sin()));\n\n\t\t\tlet constraint = SnapConstraint::Line {\n\t\t\t\torigin: relative,\n\t\t\t\tdirection: document_pos - relative,\n\t\t\t};\n\t\t\tlet near_point = SnapCandidatePoint::handle_neighbors(document_pos, neighbors.clone());\n\t\t\tlet far_point = SnapCandidatePoint::handle_neighbors(2. * relative - document_pos, neighbors);\n\t\t\tif colinear {\n\t\t\t\tlet snapped = snap.constrained_snap(&snap_data, &near_point, constraint, config);\n\t\t\t\tlet snapped_far = snap.constrained_snap(&snap_data, &far_point, constraint, config);\n\t\t\t\tdocument_pos = if snapped_far.other_snap_better(&snapped) {\n\t\t\t\t\tsnapped.snapped_point_document\n\t\t\t\t} else {\n\t\t\t\t\t2. * relative - snapped_far.snapped_point_document\n\t\t\t\t};\n\t\t\t\tsnap.update_indicator(if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far });\n\t\t\t} else {\n\t\t\t\tlet snapped = snap.constrained_snap(&snap_data, &near_point, constraint, config);\n\t\t\t\tdocument_pos = snapped.snapped_point_document;\n\t\t\t\tsnap.update_indicator(snapped);\n\t\t\t}\n\t\t} else if let Some(relative) = relative.map(|layer| transform.transform_point2(layer)).filter(|_| colinear) {\n\t\t\tlet snapped = snap.free_snap(&snap_data, &SnapCandidatePoint::handle_neighbors(document_pos, neighbors.clone()), config);\n\t\t\tlet snapped_far = snap.free_snap(&snap_data, &SnapCandidatePoint::handle_neighbors(2. * relative - document_pos, neighbors), config);\n\t\t\tdocument_pos = if snapped_far.other_snap_better(&snapped) {\n\t\t\t\tsnapped.snapped_point_document\n\t\t\t} else {\n\t\t\t\t2. * relative - snapped_far.snapped_point_document\n\t\t\t};\n\t\t\tsnap.update_indicator(if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far });\n\t\t} else {\n\t\t\tlet snapped = snap.free_snap(&snap_data, &SnapCandidatePoint::handle_neighbors(document_pos, neighbors), config);\n\t\t\tdocument_pos = snapped.snapped_point_document;\n\t\t\tsnap.update_indicator(snapped);\n\t\t}\n\n\t\tif let Some(relative) = relative.map(|layer| transform.transform_point2(layer))\n\t\t\t&& (relative - document_pos) != DVec2::ZERO\n\t\t\t&& (relative - document_pos).length_squared() > f64::EPSILON * 100.\n\t\t{\n\t\t\tself.angle = -(relative - document_pos).angle_to(DVec2::X)\n\t\t}\n\n\t\ttransform.inverse().transform_point2(document_pos)\n\t}\n\n\t#[allow(clippy::too_many_arguments)]\n\tfn create_initial_point(\n\t\t&mut self,\n\t\tdocument: &DocumentMessageHandler,\n\t\tinput: &InputPreprocessorMessageHandler,\n\t\tviewport: &ViewportMessageHandler,\n\t\tresponses: &mut VecDeque<Message>,\n\t\ttool_options: &PenOptions,\n\t\tappend: bool,\n\t\tshape_editor: &mut ShapeState,\n\t) {\n\t\tlet point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position));\n\t\tlet snapped = self.snap_manager.free_snap(&SnapData::new(document, input, viewport), &point, SnapTypeConfiguration::default());\n\t\tlet viewport_vec = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document);\n\t\tself.handle_type = TargetHandle::FuturePreviewOutHandle;\n\n\t\tlet selected_nodes = document.network_interface.selected_nodes();\n\t\tself.handle_end = None;\n\n\t\tlet tolerance = crate::consts::SNAP_POINT_TOLERANCE;\n\t\tlet extension_choice = should_extend(document, viewport_vec, tolerance, selected_nodes.selected_layers(document.metadata()));\n\t\tif let Some((layer, point, position)) = extension_choice {\n\t\t\tself.current_layer = Some(layer);\n\t\t\tself.extend_existing_path(document, layer, point, position);\n\t\t\treturn;\n\t\t} else if let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, viewport_vec, tolerance) {\n\t\t\tlet (point, segments) = closest_segment.adjusted_insert(responses);\n\t\t\tlet layer = closest_segment.layer();\n\t\t\tlet position = closest_segment.closest_point_document();\n\n\t\t\t// Setting any one of the new segments created as the previous segment\n\t\t\tself.prior_segment_endpoint = Some(point);\n\t\t\tself.prior_segment_layer = Some(layer);\n\t\t\tself.prior_segments = Some(segments.to_vec());\n\n\t\t\tself.extend_existing_path(document, layer, point, position);\n\t\t\treturn;\n\t\t}\n\n\t\tif append {\n\t\t\tif let Some((layer, point, _)) = closest_point(document, viewport_vec, tolerance, document.metadata().all_layers(), |_| false) {\n\t\t\t\tlet vector = document.network_interface.compute_modified_vector(layer).unwrap();\n\t\t\t\tlet segment = vector.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment);\n\n\t\t\t\tif self.modifiers.lock_angle {\n\t\t\t\t\tself.set_lock_angle(&vector, point, segment);\n\t\t\t\t\tself.switch_to_free_on_ctrl_release = true;\n\t\t\t\t}\n\t\t\t}\n\t\t\tlet mut selected_layers_except_artboards = selected_nodes.selected_layers_except_artboards(&document.network_interface);\n\t\t\tlet existing_layer = selected_layers_except_artboards.next().filter(|_| selected_layers_except_artboards.next().is_none());\n\t\t\tif let Some(layer) = existing_layer {\n\t\t\t\t// Add point to existing layer\n\t\t\t\tresponses.add(PenToolMessage::AddPointLayerPosition { layer, viewport: viewport_vec });\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tif let Some((layer, point, _position)) = closest_point(document, viewport_vec, tolerance, document.metadata().all_layers(), |_| false) {\n\t\t\tlet vector = document.network_interface.compute_modified_vector(layer).unwrap();\n\t\t\tlet segment = vector.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment);\n\t\t\tself.handle_mode = HandleMode::Free;\n\t\t\tif self.modifiers.lock_angle {\n\t\t\t\tself.set_lock_angle(&vector, point, segment);\n\t\t\t\tself.switch_to_free_on_ctrl_release = true;\n\t\t\t}\n\t\t}\n\n\t\t// New path layer\n\t\tlet node_type = resolve_network_node_type(\"Path\").expect(\"Path node does not exist\");\n\t\tlet nodes = vec![(NodeId(0), node_type.default_node_template())];\n\n\t\tlet parent = document.new_layer_bounding_artboard(input, viewport);\n\t\tlet layer = graph_modification_utils::new_custom(NodeId::new(), nodes, parent, responses);\n\t\tself.current_layer = Some(layer);\n\t\ttool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses);\n\t\ttool_options.fill.apply_fill(layer, responses);\n\t\tself.prior_segment = None;\n\t\tself.prior_segments = None;\n\t\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] });\n\n\t\t// Set up the first point at local origin (0,0) and position the layer at the viewport location via Transform\n\t\tlet id = PointId::generate();\n\t\tself.add_point(LastPoint {\n\t\t\tid,\n\t\t\tpos: DVec2::ZERO,\n\t\t\tin_segment: None,\n\t\t\thandle_start: DVec2::ZERO,\n\t\t});\n\t\tself.next_point = DVec2::ZERO;\n\t\tself.next_handle_start = DVec2::ZERO;\n\t\tself.handle_end = None;\n\n\t\t// Defer the transform setup and point insertion until after the layer is created\n\t\tresponses.add(DeferMessage::AfterGraphRun {\n\t\t\tmessages: vec![\n\t\t\t\tGraphOperationMessage::TransformSet {\n\t\t\t\t\tlayer,\n\t\t\t\t\ttransform: DAffine2::from_translation(viewport_vec),\n\t\t\t\t\ttransform_in: TransformIn::Viewport,\n\t\t\t\t\tskip_rerender: false,\n\t\t\t\t}\n\t\t\t\t.into(),\n\t\t\t\tGraphOperationMessage::Vector {\n\t\t\t\t\tlayer,\n\t\t\t\t\tmodification_type: VectorModificationType::InsertPoint { id, position: DVec2::ZERO },\n\t\t\t\t}\n\t\t\t\t.into(),\n\t\t\t],\n\t\t});\n\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t}\n\n\t/// Perform extension of an existing path\n\tfn extend_existing_path(&mut self, document: &DocumentMessageHandler, layer: LayerNodeIdentifier, point: PointId, position: DVec2) {\n\t\tlet vector = document.network_interface.compute_modified_vector(layer);\n\t\tlet (handle_start, in_segment) = if let Some(vector) = &vector {\n\t\t\tvector\n\t\t\t\t.segment_iter()\n\t\t\t\t.find_map(|(segment_id, bezier, start, end)| {\n\t\t\t\t\tlet is_end = point == end;\n\t\t\t\t\tlet is_start = point == start;\n\t\t\t\t\tif !is_end && !is_start {\n\t\t\t\t\t\treturn None;\n\t\t\t\t\t}\n\n\t\t\t\t\tlet points = pathseg_points(bezier);\n\t\t\t\t\tlet handle = match (points.p1, points.p2) {\n\t\t\t\t\t\t(Some(p1), Some(_)) if is_start => p1,\n\t\t\t\t\t\t(Some(_), Some(p2)) if !is_start => p2,\n\t\t\t\t\t\t(Some(p1), None) | (None, Some(p1)) => p1,\n\t\t\t\t\t\t_ => return None,\n\t\t\t\t\t};\n\t\t\t\t\tSome((segment_id, is_end, handle))\n\t\t\t\t})\n\t\t\t\t.map(|(segment_id, is_end, handle)| {\n\t\t\t\t\tlet mirrored_handle = position * 2. - handle;\n\t\t\t\t\tlet in_segment = if is_end { Some(segment_id) } else { None };\n\t\t\t\t\t(mirrored_handle, in_segment)\n\t\t\t\t})\n\t\t\t\t.unwrap_or_else(|| (position, None))\n\t\t} else {\n\t\t\t(position, None)\n\t\t};\n\n\t\tlet in_segment = if self.modifiers.lock_angle { self.prior_segment } else { in_segment };\n\n\t\tself.add_point(LastPoint {\n\t\t\tid: point,\n\t\t\tpos: position,\n\t\t\tin_segment,\n\t\t\thandle_start,\n\t\t});\n\n\t\tself.next_point = position;\n\t\tself.next_handle_start = handle_start;\n\t\tlet vector = document.network_interface.compute_modified_vector(layer).unwrap();\n\t\tlet segment = vector.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment);\n\t\tself.handle_mode = HandleMode::Free;\n\n\t\tif self.modifiers.lock_angle {\n\t\t\tself.set_lock_angle(&vector, point, segment);\n\t\t\tself.switch_to_free_on_ctrl_release = true;\n\t\t}\n\t}\n\n\t// Stores the segment and point ID of the clicked endpoint\n\tfn store_clicked_endpoint(&mut self, document: &DocumentMessageHandler, transform: &DAffine2, input: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler) {\n\t\tlet mut manipulators = HashMap::with_hasher(NoHashBuilder);\n\t\tlet mut unselected = Vec::new();\n\t\tlet mut layer_manipulators = HashSet::with_hasher(NoHashBuilder);\n\n\t\tlet point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position));\n\n\t\tlet snapped = self.snap_manager.free_snap(&SnapData::new(document, input, viewport), &point, SnapTypeConfiguration::default());\n\t\tlet viewport = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document);\n\n\t\tlet tolerance = crate::consts::SNAP_POINT_TOLERANCE;\n\t\tself.prior_segment = None;\n\t\tself.prior_segment_endpoint = None;\n\t\tself.prior_segment_layer = None;\n\t\tself.prior_segments = None;\n\n\t\tif let Some((layer, point, _position)) = closest_point(document, viewport, tolerance, document.metadata().all_layers(), |_| false) {\n\t\t\tself.prior_segment_endpoint = Some(point);\n\t\t\tself.prior_segment_layer = Some(layer);\n\t\t\tlet vector = document.network_interface.compute_modified_vector(layer).unwrap();\n\t\t\tlet segment = vector.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment);\n\t\t\tself.prior_segment = segment;\n\t\t\tlayer_manipulators.insert(point);\n\t\t\tfor (&id, &position) in vector.point_domain.ids().iter().zip(vector.point_domain.positions()) {\n\t\t\t\tif id == point {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tunselected.push(SnapCandidatePoint::handle(transform.transform_point2(position)))\n\t\t\t}\n\t\t\tmanipulators.insert(layer, layer_manipulators);\n\t\t\tself.snap_cache = SnapCache { manipulators, unselected }\n\t\t}\n\t}\n\n\tfn set_lock_angle(&mut self, vector: &Vector, anchor: PointId, segment: Option<SegmentId>) {\n\t\tlet anchor_position = vector.point_domain.position_from_id(anchor);\n\n\t\tlet Some((anchor_position, segment)) = anchor_position.zip(segment) else {\n\t\t\tself.handle_mode = HandleMode::Free;\n\t\t\treturn;\n\t\t};\n\n\t\tmatch (self.handle_type, self.path_closed) {\n\t\t\t(TargetHandle::FuturePreviewOutHandle, _) | (TargetHandle::PreviewInHandle, true) => {\n\t\t\t\tif let Some(required_handle) = calculate_segment_angle(anchor, segment, vector, true) {\n\t\t\t\t\tself.angle = required_handle;\n\t\t\t\t\tself.handle_mode = HandleMode::ColinearEquidistant;\n\t\t\t\t}\n\t\t\t}\n\t\t\t(TargetHandle::PriorInHandle(..) | TargetHandle::PriorOutHandle(..), true) => {\n\t\t\t\tself.angle = -(self.handle_end.unwrap() - anchor_position).angle_to(DVec2::X);\n\t\t\t\tself.handle_mode = HandleMode::ColinearEquidistant;\n\t\t\t}\n\t\t\t_ => {\n\t\t\t\tself.angle = -(self.next_handle_start - anchor_position).angle_to(DVec2::X);\n\t\t\t\tself.handle_mode = HandleMode::ColinearEquidistant;\n\t\t\t}\n\t\t}\n\t}\n\n\tfn add_point_layer_position(&mut self, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, viewport: DVec2) {\n\t\t// Add the first point\n\t\tlet id = PointId::generate();\n\t\tlet pos = document.metadata().transform_to_viewport(layer).inverse().transform_point2(viewport);\n\t\tlet modification_type = VectorModificationType::InsertPoint { id, position: pos };\n\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\tself.add_point(LastPoint {\n\t\t\tid,\n\t\t\tpos,\n\t\t\tin_segment: None,\n\t\t\thandle_start: pos,\n\t\t});\n\t\tself.next_point = pos;\n\t\tself.next_handle_start = pos;\n\t\tself.handle_end = None;\n\t}\n}\n\nimpl Fsm for PenToolFsmState {\n\ttype ToolData = PenToolData;\n\ttype ToolOptions = PenOptions;\n\n\tfn transition(\n\t\tself,\n\t\tevent: ToolMessage,\n\t\ttool_data: &mut Self::ToolData,\n\t\ttool_action_data: &mut ToolActionMessageContext,\n\t\ttool_options: &Self::ToolOptions,\n\t\tresponses: &mut VecDeque<Message>,\n\t) -> Self {\n\t\tlet ToolActionMessageContext {\n\t\t\tdocument,\n\t\t\tglobal_tool_data,\n\t\t\tinput,\n\t\t\tshape_editor,\n\t\t\tviewport,\n\t\t\t..\n\t\t} = tool_action_data;\n\t\tlet selected_nodes = document.network_interface.selected_nodes();\n\t\tlet mut selected_layers = selected_nodes.selected_layers(document.metadata());\n\t\tlet layer = selected_layers.next().filter(|_| selected_layers.next().is_none()).or(tool_data.current_layer);\n\n\t\tlet mut transform = layer.map(|layer| document.metadata().transform_to_document(layer)).unwrap_or_default();\n\n\t\tif !transform.inverse().is_finite() {\n\t\t\tlet parent_transform = layer.and_then(|layer| layer.parent(document.metadata())).map(|layer| document.metadata().transform_to_document(layer));\n\n\t\t\ttransform = parent_transform.unwrap_or(DAffine2::IDENTITY);\n\t\t}\n\n\t\tif !transform.inverse().is_finite() {\n\t\t\ttransform = DAffine2::IDENTITY;\n\t\t}\n\n\t\tlet ToolMessage::Pen(event) = event else { return self };\n\t\tmatch (self, event) {\n\t\t\t(PenToolFsmState::PlacingAnchor | PenToolFsmState::GRSHandle, PenToolMessage::GRS { grab, rotate, scale }) => {\n\t\t\t\tlet Some(layer) = layer else { return PenToolFsmState::PlacingAnchor };\n\n\t\t\t\tlet Some(latest) = tool_data.latest_point() else { return PenToolFsmState::PlacingAnchor };\n\t\t\t\tif latest.handle_start == latest.pos {\n\t\t\t\t\treturn PenToolFsmState::PlacingAnchor;\n\t\t\t\t}\n\n\t\t\t\tlet latest_pos = latest.pos;\n\t\t\t\tlet latest_handle_start = latest.handle_start;\n\n\t\t\t\tlet viewport = document.metadata().transform_to_viewport(layer);\n\t\t\t\tlet last_point = viewport.transform_point2(latest.pos);\n\t\t\t\tlet handle = viewport.transform_point2(latest.handle_start);\n\n\t\t\t\tif input.keyboard.key(grab) {\n\t\t\t\t\tresponses.add(TransformLayerMessage::BeginGrabPen { last_point, handle });\n\t\t\t\t} else if input.keyboard.key(rotate) {\n\t\t\t\t\tresponses.add(TransformLayerMessage::BeginRotatePen { last_point, handle });\n\t\t\t\t} else if input.keyboard.key(scale) {\n\t\t\t\t\tresponses.add(TransformLayerMessage::BeginScalePen { last_point, handle });\n\t\t\t\t}\n\n\t\t\t\tlet vector = document.network_interface.compute_modified_vector(layer).unwrap();\n\t\t\t\ttool_data.previous_handle_start_pos = latest.handle_start;\n\t\t\t\tlet opposite_handle = tool_data.check_grs_end_handle(&vector);\n\t\t\t\ttool_data.previous_handle_end_pos = tool_data.target_handle_position(opposite_handle, &vector);\n\t\t\t\tlet handle1 = latest_handle_start - latest_pos;\n\t\t\t\tlet Some(opposite_handle_pos) = tool_data.target_handle_position(opposite_handle, &vector) else {\n\t\t\t\t\treturn PenToolFsmState::GRSHandle;\n\t\t\t\t};\n\t\t\t\tlet handle2 = opposite_handle_pos - latest_pos;\n\t\t\t\tlet pi = std::f64::consts::PI;\n\t\t\t\tlet angle = handle1.angle_to(handle2);\n\t\t\t\ttool_data.colinear = (angle - pi).abs() < 1e-6 || (angle + pi).abs() < 1e-6;\n\t\t\t\tPenToolFsmState::GRSHandle\n\t\t\t}\n\t\t\t(PenToolFsmState::GRSHandle, PenToolMessage::FinalPosition { final_position }) => {\n\t\t\t\tlet Some(layer) = layer else { return PenToolFsmState::GRSHandle };\n\t\t\t\tlet vector = document.network_interface.compute_modified_vector(layer);\n\t\t\t\tlet Some(vector) = vector else { return PenToolFsmState::GRSHandle };\n\n\t\t\t\tif let Some(latest_pt) = tool_data.latest_point_mut() {\n\t\t\t\t\tlet layer_space_to_viewport = document.metadata().transform_to_viewport(layer);\n\t\t\t\t\tlet final_pos = layer_space_to_viewport.inverse().transform_point2(final_position);\n\t\t\t\t\tlatest_pt.handle_start = final_pos;\n\t\t\t\t}\n\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\tlet Some(latest) = tool_data.latest_point() else {\n\t\t\t\t\treturn PenToolFsmState::GRSHandle;\n\t\t\t\t};\n\t\t\t\tlet opposite_handle = tool_data.check_grs_end_handle(&vector);\n\t\t\t\tlet Some(opposite_handle_pos) = tool_data.target_handle_position(opposite_handle, &vector) else {\n\t\t\t\t\treturn PenToolFsmState::GRSHandle;\n\t\t\t\t};\n\n\t\t\t\tif tool_data.colinear {\n\t\t\t\t\tlet Some(direction) = (latest.pos - latest.handle_start).try_normalize() else {\n\t\t\t\t\t\treturn PenToolFsmState::GRSHandle;\n\t\t\t\t\t};\n\n\t\t\t\t\tif (latest.pos - latest.handle_start).length_squared() < f64::EPSILON {\n\t\t\t\t\t\treturn PenToolFsmState::GRSHandle;\n\t\t\t\t\t}\n\t\t\t\t\tlet relative_distance = (opposite_handle_pos - latest.pos).length();\n\t\t\t\t\tlet relative_position = relative_distance * direction + latest.pos;\n\t\t\t\t\ttool_data.update_target_handle_pos(opposite_handle, latest.pos, responses, relative_position, layer);\n\t\t\t\t}\n\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\n\t\t\t\tPenToolFsmState::GRSHandle\n\t\t\t}\n\t\t\t(PenToolFsmState::GRSHandle, PenToolMessage::Confirm) => {\n\t\t\t\ttool_data.next_point = input.mouse.position;\n\t\t\t\ttool_data.next_handle_start = input.mouse.position;\n\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\tresponses.add(PenToolMessage::PointerMove {\n\t\t\t\t\tsnap_angle: Key::Control,\n\t\t\t\t\tbreak_handle: Key::Alt,\n\t\t\t\t\tlock_angle: Key::Shift,\n\t\t\t\t\tcolinear: Key::KeyC,\n\t\t\t\t\tmove_anchor_with_handles: Key::Space,\n\t\t\t\t});\n\n\t\t\t\tPenToolFsmState::PlacingAnchor\n\t\t\t}\n\t\t\t(PenToolFsmState::GRSHandle, PenToolMessage::Abort) => {\n\t\t\t\ttool_data.next_point = input.mouse.position;\n\t\t\t\ttool_data.next_handle_start = input.mouse.position;\n\n\t\t\t\tlet Some(layer) = layer else { return PenToolFsmState::GRSHandle };\n\t\t\t\tlet vector = document.network_interface.compute_modified_vector(layer).unwrap();\n\t\t\t\tlet opposite_handle = tool_data.check_grs_end_handle(&vector);\n\n\t\t\t\tlet previous = tool_data.previous_handle_start_pos;\n\t\t\t\tif let Some(latest) = tool_data.latest_point_mut() {\n\t\t\t\t\tlatest.handle_start = previous;\n\t\t\t\t} else {\n\t\t\t\t\treturn PenToolFsmState::PlacingAnchor;\n\t\t\t\t}\n\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\tresponses.add(PenToolMessage::PointerMove {\n\t\t\t\t\tsnap_angle: Key::Control,\n\t\t\t\t\tbreak_handle: Key::Alt,\n\t\t\t\t\tlock_angle: Key::Shift,\n\t\t\t\t\tcolinear: Key::KeyC,\n\t\t\t\t\tmove_anchor_with_handles: Key::Space,\n\t\t\t\t});\n\n\t\t\t\tlet Some((previous_pos, latest)) = tool_data.previous_handle_end_pos.zip(tool_data.latest_point()) else {\n\t\t\t\t\treturn PenToolFsmState::PlacingAnchor;\n\t\t\t\t};\n\t\t\t\ttool_data.update_target_handle_pos(opposite_handle, latest.pos, responses, previous_pos, layer);\n\n\t\t\t\tPenToolFsmState::PlacingAnchor\n\t\t\t}\n\t\t\t(_, PenToolMessage::SelectionChanged) => {\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\tself\n\t\t\t}\n\t\t\t(PenToolFsmState::Ready, PenToolMessage::Overlays { context: mut overlay_context }) => {\n\t\t\t\tmatch tool_options.pen_overlay_mode {\n\t\t\t\t\tPenOverlayMode::AllHandles => {\n\t\t\t\t\t\tpath_overlays(document, DrawHandles::All, shape_editor, &mut overlay_context);\n\t\t\t\t\t}\n\t\t\t\t\tPenOverlayMode::FrontierHandles => {\n\t\t\t\t\t\tpath_overlays(document, DrawHandles::None, shape_editor, &mut overlay_context);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Check if there is an anchor within threshold\n\t\t\t\t// If not check if there is a closest segment within threshold, if yes then draw overlay\n\t\t\t\tlet tolerance = crate::consts::SNAP_POINT_TOLERANCE;\n\t\t\t\tlet point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position));\n\t\t\t\tlet snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input, viewport), &point, SnapTypeConfiguration::default());\n\t\t\t\tlet viewport_vec = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document);\n\n\t\t\t\tlet close_to_point = closest_point(document, viewport_vec, tolerance, document.metadata().all_layers(), |_| false).is_some();\n\t\t\t\tif !close_to_point && let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, viewport_vec, tolerance) {\n\t\t\t\t\tlet pos = closest_segment.closest_point_to_viewport();\n\t\t\t\t\tlet perp = closest_segment.calculate_perp(document);\n\t\t\t\t\toverlay_context.manipulator_anchor(pos, true, None);\n\t\t\t\t\toverlay_context.line(pos - perp * SEGMENT_OVERLAY_SIZE, pos + perp * SEGMENT_OVERLAY_SIZE, Some(COLOR_OVERLAY_BLUE), None);\n\t\t\t\t}\n\t\t\t\ttool_data.snap_manager.draw_overlays(SnapData::new(document, input, viewport), &mut overlay_context);\n\t\t\t\tself\n\t\t\t}\n\t\t\t(_, PenToolMessage::Overlays { context: mut overlay_context }) => {\n\t\t\t\tlet display_anchors = overlay_context.visibility_settings.anchors();\n\t\t\t\tlet display_handles = overlay_context.visibility_settings.handles();\n\n\t\t\t\tlet valid = |point: DVec2, handle: DVec2| point.distance_squared(handle) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE;\n\n\t\t\t\tlet transform = document.metadata().document_to_viewport * transform;\n\n\t\t\t\t// The currently-being-placed anchor\n\t\t\t\tlet next_anchor = transform.transform_point2(tool_data.next_point);\n\t\t\t\t// The currently-being-placed anchor's outgoing handle (the one currently being dragged out)\n\t\t\t\tlet next_handle_start = transform.transform_point2(tool_data.next_handle_start);\n\n\t\t\t\t// The most recently placed anchor\n\t\t\t\tlet anchor_start = tool_data.latest_point().map(|point| transform.transform_point2(point.pos));\n\t\t\t\t// The most recently placed anchor's incoming handle (opposite the one currently being dragged out)\n\t\t\t\tlet handle_end = tool_data.handle_end.map(|point| transform.transform_point2(point));\n\t\t\t\t// The most recently placed anchor's outgoing handle (which is currently influencing the currently-being-placed segment)\n\t\t\t\tlet handle_start = tool_data.latest_point().map(|point| transform.transform_point2(point.handle_start));\n\n\t\t\t\tif let (Some((start, handle_start)), Some(handle_end)) = (tool_data.latest_point().map(|point| (point.pos, point.handle_start)), tool_data.handle_end) {\n\t\t\t\t\tlet end = tool_data.next_point;\n\t\t\t\t\tlet bezier = PathSeg::Cubic(CubicBez::new(dvec2_to_point(start), dvec2_to_point(handle_start), dvec2_to_point(handle_end), dvec2_to_point(end)));\n\t\t\t\t\tif (end - start).length_squared() > f64::EPSILON {\n\t\t\t\t\t\t// Draw the curve for the currently-being-placed segment\n\t\t\t\t\t\toverlay_context.outline_bezier(bezier, transform);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif display_handles {\n\t\t\t\t\t// Draw the line between the currently-being-placed anchor and its currently-being-dragged-out outgoing handle (opposite the one currently being dragged out)\n\t\t\t\t\toverlay_context.line(next_anchor, next_handle_start, None, None);\n\t\t\t\t}\n\n\t\t\t\tmatch tool_options.pen_overlay_mode {\n\t\t\t\t\tPenOverlayMode::AllHandles => {\n\t\t\t\t\t\tpath_overlays(document, DrawHandles::All, shape_editor, &mut overlay_context);\n\t\t\t\t\t}\n\t\t\t\t\tPenOverlayMode::FrontierHandles => {\n\t\t\t\t\t\tif let Some(layer) = tool_data.current_layer {\n\t\t\t\t\t\t\tif let Some(latest_segment) = tool_data.prior_segment {\n\t\t\t\t\t\t\t\tlet selected_anchors_data = HashMap::from([(layer, vec![latest_segment])]);\n\t\t\t\t\t\t\t\tpath_overlays(document, DrawHandles::SelectedAnchors(selected_anchors_data), shape_editor, &mut overlay_context);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// If a vector mesh then there can be more than one prior segments\n\t\t\t\t\t\t\telse if let Some(segments) = tool_data.prior_segments.clone() {\n\t\t\t\t\t\t\t\tlet selected_anchors_data = HashMap::from([(layer, segments)]);\n\t\t\t\t\t\t\t\tpath_overlays(document, DrawHandles::SelectedAnchors(selected_anchors_data), shape_editor, &mut overlay_context);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tpath_overlays(document, DrawHandles::None, shape_editor, &mut overlay_context);\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif let (Some(anchor_start), Some(handle_start), Some(handle_end)) = (anchor_start, handle_start, handle_end) {\n\t\t\t\t\tif display_handles {\n\t\t\t\t\t\t// Draw the line between the most recently placed anchor and its outgoing handle (which is currently influencing the currently-being-placed segment)\n\t\t\t\t\t\toverlay_context.line(anchor_start, handle_start, None, None);\n\n\t\t\t\t\t\t// Draw the line between the currently-being-placed anchor and its incoming handle (opposite the one currently being dragged out)\n\t\t\t\t\t\toverlay_context.line(next_anchor, handle_end, None, None);\n\t\t\t\t\t}\n\n\t\t\t\t\tif self == PenToolFsmState::PlacingAnchor && anchor_start != handle_start && tool_data.modifiers.lock_angle {\n\t\t\t\t\t\t// Draw the line between the currently-being-placed anchor and last-placed point (lock angle bent overlays)\n\t\t\t\t\t\toverlay_context.dashed_line(anchor_start, next_anchor, None, None, Some(4.), Some(4.), Some(0.5));\n\t\t\t\t\t}\n\n\t\t\t\t\t// Draw the line between the currently-being-placed anchor and last-placed point (snap angle bent overlays)\n\t\t\t\t\tif self == PenToolFsmState::PlacingAnchor && anchor_start != handle_start && tool_data.modifiers.snap_angle {\n\t\t\t\t\t\toverlay_context.dashed_line(anchor_start, next_anchor, None, None, Some(4.), Some(4.), Some(0.5));\n\t\t\t\t\t}\n\n\t\t\t\t\tif self == PenToolFsmState::DraggingHandle(tool_data.handle_mode) && valid(next_anchor, handle_end) && display_handles {\n\t\t\t\t\t\t// Draw the handle circle for the currently-being-dragged-out incoming handle (opposite the one currently being dragged out)\n\t\t\t\t\t\tlet selected = tool_data.handle_type == TargetHandle::PreviewInHandle;\n\t\t\t\t\t\tif display_handles {\n\t\t\t\t\t\t\toverlay_context.manipulator_handle(handle_end, selected, None);\n\t\t\t\t\t\t\toverlay_context.manipulator_handle(handle_end, selected, None);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif valid(anchor_start, handle_start) && display_handles {\n\t\t\t\t\t\t// Draw the handle circle for the most recently placed anchor's outgoing handle (which is currently influencing the currently-being-placed segment)\n\t\t\t\t\t\toverlay_context.manipulator_handle(handle_start, false, None);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Draw the whole path and its manipulators when the user is clicking-and-dragging out from the most recently placed anchor to set its outgoing handle, during which it would otherwise not have its overlays drawn\n\t\t\t\t\tmatch tool_options.pen_overlay_mode {\n\t\t\t\t\t\tPenOverlayMode::AllHandles => {\n\t\t\t\t\t\t\tpath_overlays(document, DrawHandles::All, shape_editor, &mut overlay_context);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tPenOverlayMode::FrontierHandles => {\n\t\t\t\t\t\t\tpath_overlays(document, DrawHandles::None, shape_editor, &mut overlay_context);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif self == PenToolFsmState::DraggingHandle(tool_data.handle_mode) && valid(next_anchor, next_handle_start) && display_handles {\n\t\t\t\t\t// Draw the handle circle for the currently-being-dragged-out outgoing handle (the one currently being dragged out, under the user's cursor)\n\t\t\t\t\tlet selected = tool_data.handle_type == TargetHandle::FuturePreviewOutHandle;\n\t\t\t\t\toverlay_context.manipulator_handle(next_handle_start, selected, None);\n\t\t\t\t}\n\n\t\t\t\tif self == PenToolFsmState::DraggingHandle(tool_data.handle_mode) && display_anchors {\n\t\t\t\t\t// Draw the anchor square for the most recently placed anchor\n\t\t\t\t\toverlay_context.manipulator_anchor(next_anchor, false, None);\n\t\t\t\t}\n\n\t\t\t\tif self == PenToolFsmState::PlacingAnchor {\n\t\t\t\t\tlet tolerance = crate::consts::SNAP_POINT_TOLERANCE;\n\t\t\t\t\tlet point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position));\n\t\t\t\t\tlet snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input, viewport), &point, SnapTypeConfiguration::default());\n\t\t\t\t\tlet viewport = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document);\n\t\t\t\t\tlet close_to_point = closest_point(document, viewport, tolerance, document.metadata().all_layers(), |_| false).is_some();\n\t\t\t\t\tif !close_to_point && let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, viewport, tolerance) {\n\t\t\t\t\t\tlet pos = closest_segment.closest_point_to_viewport();\n\t\t\t\t\t\tlet perp = closest_segment.calculate_perp(document);\n\t\t\t\t\t\toverlay_context.manipulator_anchor(pos, true, None);\n\t\t\t\t\t\toverlay_context.line(pos - perp * SEGMENT_OVERLAY_SIZE, pos + perp * SEGMENT_OVERLAY_SIZE, Some(COLOR_OVERLAY_BLUE), None);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Display a filled overlay of the shape if the new point closes the path\n\t\t\t\tif let Some(latest_point) = tool_data.latest_point() {\n\t\t\t\t\tlet handle_start = latest_point.handle_start;\n\t\t\t\t\tlet handle_end = tool_data.handle_end.unwrap_or(tool_data.next_handle_start);\n\t\t\t\t\tlet next_point = tool_data.next_point;\n\t\t\t\t\tlet start = latest_point.id;\n\n\t\t\t\t\tif let Some(layer) = layer\n\t\t\t\t\t\t&& let Some(mut vector) = document.network_interface.compute_modified_vector(layer)\n\t\t\t\t\t{\n\t\t\t\t\t\tlet closest_point = vector.anchor_points().filter(|&id| id != start).find(|&id| {\n\t\t\t\t\t\t\tvector.point_domain.position_from_id(id).is_some_and(|pos| {\n\t\t\t\t\t\t\t\tlet dist_sq = transform.transform_point2(pos).distance_squared(transform.transform_point2(next_point));\n\t\t\t\t\t\t\t\tdist_sq < crate::consts::SNAP_POINT_TOLERANCE.powi(2)\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// We have the point. Join the 2 vertices and check if any path is closed.\n\t\t\t\t\t\tif let Some(end) = closest_point {\n\t\t\t\t\t\t\tlet segment_id = SegmentId::generate();\n\t\t\t\t\t\t\tvector.push(segment_id, start, end, (Some(handle_start), Some(handle_end)), StrokeId::ZERO);\n\n\t\t\t\t\t\t\tlet grouped_segments = vector.auto_join_paths();\n\t\t\t\t\t\t\tlet closed_paths = grouped_segments.iter().filter(|path| path.is_closed() && path.contains(segment_id));\n\n\t\t\t\t\t\t\tlet subpaths: Vec<_> = closed_paths\n\t\t\t\t\t\t\t\t.filter_map(|path| {\n\t\t\t\t\t\t\t\t\tlet segments = path.edges.iter().filter_map(|edge| {\n\t\t\t\t\t\t\t\t\t\tvector\n\t\t\t\t\t\t\t\t\t\t\t.segment_domain\n\t\t\t\t\t\t\t\t\t\t\t.iter()\n\t\t\t\t\t\t\t\t\t\t\t.find(|(id, _, _, _)| id == &edge.id)\n\t\t\t\t\t\t\t\t\t\t\t.map(|(_, start, end, bezier)| if start == edge.start { (bezier, start, end) } else { (bezier.reversed(), end, start) })\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\tvector.subpath_from_segments_ignore_discontinuities(segments)\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t.collect();\n\n\t\t\t\t\t\t\tlet fill_color = COLOR_OVERLAY_BLUE_05;\n\t\t\t\t\t\t\toverlay_context.fill_path(subpaths.iter(), transform, fill_color);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Draw the overlays that visualize current snapping\n\t\t\t\ttool_data.snap_manager.draw_overlays(SnapData::new(document, input, viewport), &mut overlay_context);\n\n\t\t\t\tself\n\t\t\t}\n\t\t\t(_, PenToolMessage::WorkingColorChanged) => {\n\t\t\t\tresponses.add(PenToolMessage::UpdateOptions {\n\t\t\t\t\toptions: PenOptionsUpdate::WorkingColors(Some(global_tool_data.primary_color), Some(global_tool_data.secondary_color)),\n\t\t\t\t});\n\t\t\t\tself\n\t\t\t}\n\t\t\t(PenToolFsmState::Ready, PenToolMessage::DragStart { append_to_selected }) => {\n\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\t\t\t\ttool_data.handle_mode = HandleMode::Free;\n\n\t\t\t\t// Get the closest point and the segment it is on\n\t\t\t\tlet append = input.keyboard.key(append_to_selected);\n\n\t\t\t\ttool_data.store_clicked_endpoint(document, &transform, input, viewport);\n\t\t\t\ttool_data.create_initial_point(document, input, viewport, responses, tool_options, append, shape_editor);\n\n\t\t\t\t// Enter the dragging handle state while the mouse is held down, allowing the user to move the mouse and position the handle\n\t\t\t\tPenToolFsmState::DraggingHandle(tool_data.handle_mode)\n\t\t\t}\n\t\t\t(_, PenToolMessage::AddPointLayerPosition { layer, viewport }) => {\n\t\t\t\ttool_data.add_point_layer_position(document, responses, layer, viewport);\n\n\t\t\t\tself\n\t\t\t}\n\t\t\t(state, PenToolMessage::RecalculateLatestPointsPosition) => {\n\t\t\t\ttool_data.recalculate_latest_points_position(document);\n\t\t\t\tstate\n\t\t\t}\n\t\t\t(PenToolFsmState::PlacingAnchor, PenToolMessage::DragStart { append_to_selected }) => {\n\t\t\t\tlet point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position));\n\t\t\t\tlet snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input, viewport), &point, SnapTypeConfiguration::default());\n\t\t\t\tlet viewport_vec = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document);\n\n\t\t\t\t// Early return if the buffer was started and this message is being run again after the buffer (so that place_anchor updates the state with the newly merged vector)\n\t\t\t\tif tool_data.buffering_merged_vector {\n\t\t\t\t\tif let Some(layer) = layer {\n\t\t\t\t\t\ttool_data.buffering_merged_vector = false;\n\t\t\t\t\t\ttool_data.handle_mode = HandleMode::ColinearLocked;\n\t\t\t\t\t\ttool_data.bend_from_previous_point(SnapData::new(document, input, viewport), transform, layer, shape_editor, responses);\n\t\t\t\t\t\ttool_data.place_anchor(SnapData::new(document, input, viewport), transform, input.mouse.position, responses);\n\t\t\t\t\t}\n\t\t\t\t\ttool_data.buffering_merged_vector = false;\n\t\t\t\t\tPenToolFsmState::DraggingHandle(tool_data.handle_mode)\n\t\t\t\t} else {\n\t\t\t\t\tif tool_data.handle_end.is_some() {\n\t\t\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\t\t\t\t\t}\n\t\t\t\t\t// Merge two layers if the point is connected to the end point of another path\n\n\t\t\t\t\t// This might not be the correct solution to artboards being included as the other layer,\n\t\t\t\t\t// which occurs due to the `compute_modified_vector` call in `should_extend` using the click targets for a layer instead of vector.\n\t\t\t\t\tlet layers = LayerNodeIdentifier::ROOT_PARENT\n\t\t\t\t\t\t.descendants(document.metadata())\n\t\t\t\t\t\t.filter(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]));\n\t\t\t\t\tif let Some((other_layer, _, _)) = should_extend(document, viewport_vec, crate::consts::SNAP_POINT_TOLERANCE, layers) {\n\t\t\t\t\t\tlet selected_nodes = document.network_interface.selected_nodes();\n\t\t\t\t\t\tlet mut selected_layers = selected_nodes.selected_layers(document.metadata());\n\t\t\t\t\t\tif let Some(current_layer) = selected_layers\n\t\t\t\t\t\t\t.next()\n\t\t\t\t\t\t\t.filter(|current_layer| selected_layers.next().is_none() && *current_layer != other_layer)\n\t\t\t\t\t\t\t.or(tool_data.current_layer.filter(|layer| *layer != other_layer))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tmerge_layers(document, current_layer, other_layer, responses);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Even if no buffer was started, the message still has to be run again in order to call bend_from_previous_point\n\t\t\t\t\ttool_data.buffering_merged_vector = true;\n\t\t\t\t\tresponses.add(PenToolMessage::DragStart { append_to_selected });\n\t\t\t\t\tPenToolFsmState::PlacingAnchor\n\t\t\t\t}\n\t\t\t}\n\t\t\t(PenToolFsmState::PlacingAnchor, PenToolMessage::RemovePreviousHandle) => {\n\t\t\t\tif let Some(last_point) = tool_data.latest_points.last_mut() {\n\t\t\t\t\tlast_point.handle_start = last_point.pos;\n\t\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\t} else {\n\t\t\t\t\tlog::trace!(\"No latest point available to modify handle_start.\");\n\t\t\t\t}\n\t\t\t\tself\n\t\t\t}\n\t\t\t(PenToolFsmState::DraggingHandle(_), PenToolMessage::DragStop) => {\n\t\t\t\ttool_data.cleanup_target_selections(shape_editor, layer, document, responses);\n\t\t\t\ttool_data\n\t\t\t\t\t.finish_placing_handle(SnapData::new(document, input, viewport), transform, responses)\n\t\t\t\t\t.unwrap_or(PenToolFsmState::PlacingAnchor)\n\t\t\t}\n\t\t\t(\n\t\t\t\tPenToolFsmState::DraggingHandle(_),\n\t\t\t\tPenToolMessage::PointerMove {\n\t\t\t\t\tsnap_angle,\n\t\t\t\t\tbreak_handle,\n\t\t\t\t\tlock_angle,\n\t\t\t\t\tcolinear,\n\t\t\t\t\tmove_anchor_with_handles,\n\t\t\t\t},\n\t\t\t) => {\n\t\t\t\ttool_data.modifiers = ModifierState {\n\t\t\t\t\tsnap_angle: input.keyboard.key(snap_angle),\n\t\t\t\t\tlock_angle: input.keyboard.key(lock_angle),\n\t\t\t\t\tbreak_handle: input.keyboard.key(break_handle),\n\t\t\t\t\tcolinear: input.keyboard.key(colinear),\n\t\t\t\t\tmove_anchor_with_handles: input.keyboard.key(move_anchor_with_handles),\n\t\t\t\t};\n\n\t\t\t\tlet snap_data = SnapData::new(document, input, viewport);\n\t\t\t\tif tool_data.modifiers.colinear && !tool_data.toggle_colinear_debounce {\n\t\t\t\t\ttool_data.handle_mode = match tool_data.handle_mode {\n\t\t\t\t\t\tHandleMode::Free => {\n\t\t\t\t\t\t\tlet last_segment = tool_data.prior_segment;\n\t\t\t\t\t\t\tif let Some(latest) = tool_data.latest_point_mut() {\n\t\t\t\t\t\t\t\tlatest.in_segment = last_segment;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tHandleMode::ColinearEquidistant\n\t\t\t\t\t\t}\n\t\t\t\t\t\tHandleMode::ColinearEquidistant | HandleMode::ColinearLocked => HandleMode::Free,\n\t\t\t\t\t};\n\t\t\t\t\ttool_data.toggle_colinear_debounce = true;\n\t\t\t\t}\n\n\t\t\t\tlet Some(vector) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)) else {\n\t\t\t\t\treturn self;\n\t\t\t\t};\n\n\t\t\t\tif tool_data.modifiers.move_anchor_with_handles && !tool_data.space_pressed {\n\t\t\t\t\tlet reference_handle = if tool_data.path_closed {\n\t\t\t\t\t\tTargetHandle::PreviewInHandle\n\t\t\t\t\t} else {\n\t\t\t\t\t\tTargetHandle::FuturePreviewOutHandle\n\t\t\t\t\t};\n\t\t\t\t\tlet handle_start = layer.map(|layer| {\n\t\t\t\t\t\tdocument\n\t\t\t\t\t\t\t.metadata()\n\t\t\t\t\t\t\t.transform_to_viewport(layer)\n\t\t\t\t\t\t\t.transform_point2(tool_data.target_handle_position(reference_handle, &vector).unwrap())\n\t\t\t\t\t});\n\t\t\t\t\ttool_data.handle_start_offset = handle_start.map(|start| start - input.mouse.position);\n\t\t\t\t\ttool_data.space_pressed = true;\n\t\t\t\t}\n\n\t\t\t\tif !tool_data.modifiers.move_anchor_with_handles {\n\t\t\t\t\ttool_data.space_pressed = false;\n\t\t\t\t}\n\n\t\t\t\tif !tool_data.modifiers.colinear {\n\t\t\t\t\ttool_data.toggle_colinear_debounce = false;\n\t\t\t\t}\n\n\t\t\t\tif !tool_data.modifiers.lock_angle {\n\t\t\t\t\ttool_data.angle_locked = false;\n\t\t\t\t}\n\n\t\t\t\tlet state = tool_data\n\t\t\t\t\t.drag_handle(snap_data, transform, input.mouse.position, responses, layer, input, viewport)\n\t\t\t\t\t.unwrap_or(PenToolFsmState::Ready);\n\n\t\t\t\tif tool_data.handle_swapped {\n\t\t\t\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::None });\n\t\t\t\t}\n\n\t\t\t\t// Auto-panning\n\t\t\t\tlet messages = [\n\t\t\t\t\tPenToolMessage::PointerOutsideViewport {\n\t\t\t\t\t\tsnap_angle,\n\t\t\t\t\t\tbreak_handle,\n\t\t\t\t\t\tlock_angle,\n\t\t\t\t\t\tcolinear,\n\t\t\t\t\t\tmove_anchor_with_handles,\n\t\t\t\t\t}\n\t\t\t\t\t.into(),\n\t\t\t\t\tPenToolMessage::PointerMove {\n\t\t\t\t\t\tsnap_angle,\n\t\t\t\t\t\tbreak_handle,\n\t\t\t\t\t\tlock_angle,\n\t\t\t\t\t\tcolinear,\n\t\t\t\t\t\tmove_anchor_with_handles,\n\t\t\t\t\t}\n\t\t\t\t\t.into(),\n\t\t\t\t];\n\t\t\t\ttool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses);\n\n\t\t\t\tstate\n\t\t\t}\n\t\t\t(\n\t\t\t\tPenToolFsmState::PlacingAnchor,\n\t\t\t\tPenToolMessage::PointerMove {\n\t\t\t\t\tsnap_angle,\n\t\t\t\t\tbreak_handle,\n\t\t\t\t\tlock_angle,\n\t\t\t\t\tcolinear,\n\t\t\t\t\tmove_anchor_with_handles,\n\t\t\t\t},\n\t\t\t) => {\n\t\t\t\ttool_data.switch_to_free_on_ctrl_release = false;\n\t\t\t\ttool_data.alt_pressed = false;\n\t\t\t\ttool_data.modifiers = ModifierState {\n\t\t\t\t\tsnap_angle: input.keyboard.key(snap_angle),\n\t\t\t\t\tlock_angle: input.keyboard.key(lock_angle),\n\t\t\t\t\tbreak_handle: input.keyboard.key(break_handle),\n\t\t\t\t\tcolinear: input.keyboard.key(colinear),\n\t\t\t\t\tmove_anchor_with_handles: input.keyboard.key(move_anchor_with_handles),\n\t\t\t\t};\n\t\t\t\tlet state = tool_data\n\t\t\t\t\t.place_anchor(SnapData::new(document, input, viewport), transform, input.mouse.position, responses)\n\t\t\t\t\t.unwrap_or(PenToolFsmState::Ready);\n\n\t\t\t\t// Auto-panning\n\t\t\t\tlet messages = [\n\t\t\t\t\tPenToolMessage::PointerOutsideViewport {\n\t\t\t\t\t\tsnap_angle,\n\t\t\t\t\t\tbreak_handle,\n\t\t\t\t\t\tlock_angle,\n\t\t\t\t\t\tcolinear,\n\t\t\t\t\t\tmove_anchor_with_handles,\n\t\t\t\t\t}\n\t\t\t\t\t.into(),\n\t\t\t\t\tPenToolMessage::PointerMove {\n\t\t\t\t\t\tsnap_angle,\n\t\t\t\t\t\tbreak_handle,\n\t\t\t\t\t\tlock_angle,\n\t\t\t\t\t\tcolinear,\n\t\t\t\t\t\tmove_anchor_with_handles,\n\t\t\t\t\t}\n\t\t\t\t\t.into(),\n\t\t\t\t];\n\t\t\t\ttool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses);\n\n\t\t\t\tstate\n\t\t\t}\n\t\t\t(PenToolFsmState::DraggingHandle(_), PenToolMessage::SwapHandles) => {\n\t\t\t\tif !tool_data.handle_swapped {\n\t\t\t\t\ttool_data.handle_swapped = true\n\t\t\t\t}\n\t\t\t\ttool_data.swap_handles(layer, document, shape_editor, input, responses);\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\tself\n\t\t\t}\n\t\t\t(\n\t\t\t\tPenToolFsmState::Ready,\n\t\t\t\tPenToolMessage::PointerMove {\n\t\t\t\t\tsnap_angle,\n\t\t\t\t\tbreak_handle,\n\t\t\t\t\tlock_angle,\n\t\t\t\t\tcolinear,\n\t\t\t\t\tmove_anchor_with_handles,\n\t\t\t\t},\n\t\t\t) => {\n\t\t\t\ttool_data.modifiers = ModifierState {\n\t\t\t\t\tsnap_angle: input.keyboard.key(snap_angle),\n\t\t\t\t\tlock_angle: input.keyboard.key(lock_angle),\n\t\t\t\t\tbreak_handle: input.keyboard.key(break_handle),\n\t\t\t\t\tcolinear: input.keyboard.key(colinear),\n\t\t\t\t\tmove_anchor_with_handles: input.keyboard.key(move_anchor_with_handles),\n\t\t\t\t};\n\t\t\t\ttool_data.snap_manager.preview_draw(&SnapData::new(document, input, viewport), input.mouse.position);\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\tself\n\t\t\t}\n\t\t\t(PenToolFsmState::DraggingHandle(mode), PenToolMessage::PointerOutsideViewport { .. }) => {\n\t\t\t\t// Auto-panning\n\t\t\t\tlet _ = tool_data.auto_panning.shift_viewport(input, viewport, responses);\n\n\t\t\t\tPenToolFsmState::DraggingHandle(mode)\n\t\t\t}\n\t\t\t(PenToolFsmState::PlacingAnchor, PenToolMessage::PointerOutsideViewport { .. }) => {\n\t\t\t\tif !input.mouse.mouse_keys.contains(MouseKeys::LEFT) {\n\t\t\t\t\treturn self;\n\t\t\t\t}\n\t\t\t\t// Auto-panning\n\t\t\t\tlet _ = tool_data.auto_panning.shift_viewport(input, viewport, responses);\n\n\t\t\t\tPenToolFsmState::PlacingAnchor\n\t\t\t}\n\t\t\t(\n\t\t\t\tstate,\n\t\t\t\tPenToolMessage::PointerOutsideViewport {\n\t\t\t\t\tsnap_angle,\n\t\t\t\t\tbreak_handle,\n\t\t\t\t\tlock_angle,\n\t\t\t\t\tcolinear,\n\t\t\t\t\tmove_anchor_with_handles,\n\t\t\t\t},\n\t\t\t) => {\n\t\t\t\t// Auto-panning\n\t\t\t\tlet messages = [\n\t\t\t\t\tPenToolMessage::PointerOutsideViewport {\n\t\t\t\t\t\tsnap_angle,\n\t\t\t\t\t\tbreak_handle,\n\t\t\t\t\t\tlock_angle,\n\t\t\t\t\t\tcolinear,\n\t\t\t\t\t\tmove_anchor_with_handles,\n\t\t\t\t\t}\n\t\t\t\t\t.into(),\n\t\t\t\t\tPenToolMessage::PointerMove {\n\t\t\t\t\t\tsnap_angle,\n\t\t\t\t\t\tbreak_handle,\n\t\t\t\t\t\tlock_angle,\n\t\t\t\t\t\tcolinear,\n\t\t\t\t\t\tmove_anchor_with_handles,\n\t\t\t\t\t}\n\t\t\t\t\t.into(),\n\t\t\t\t];\n\t\t\t\ttool_data.auto_panning.stop(&messages, responses);\n\n\t\t\t\tstate\n\t\t\t}\n\t\t\t(PenToolFsmState::DraggingHandle(..), PenToolMessage::Confirm) => {\n\t\t\t\t// Confirm to end path\n\t\t\t\tif let Some((vector, layer)) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)).zip(layer) {\n\t\t\t\t\tlet single_point_in_layer = vector.point_domain.ids().len() == 1;\n\t\t\t\t\ttool_data.finish_placing_handle(SnapData::new(document, input, viewport), transform, responses);\n\t\t\t\t\tlet latest_points = tool_data.latest_points.len() == 1;\n\n\t\t\t\t\tif latest_points && single_point_in_layer {\n\t\t\t\t\t\tresponses.add(NodeGraphMessage::DeleteNodes {\n\t\t\t\t\t\t\tnode_ids: vec![layer.to_node()],\n\t\t\t\t\t\t\tdelete_children: true,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\t\t} else if (latest_points && tool_data.prior_segment_endpoint.is_none())\n\t\t\t\t\t\t|| (tool_data.prior_segment_endpoint.is_some() && tool_data.prior_segment_layer != Some(layer) && latest_points)\n\t\t\t\t\t{\n\t\t\t\t\t\tlet vector_modification = VectorModificationType::RemovePoint {\n\t\t\t\t\t\t\tid: tool_data.latest_point().unwrap().id,\n\t\t\t\t\t\t};\n\t\t\t\t\t\tresponses.add(GraphOperationMessage::Vector {\n\t\t\t\t\t\t\tlayer,\n\t\t\t\t\t\t\tmodification_type: vector_modification,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tresponses.add(PenToolMessage::Abort);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresponses.add(DocumentMessage::EndTransaction);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ttool_data.cleanup(responses);\n\t\t\t\ttool_data.cleanup_target_selections(shape_editor, layer, document, responses);\n\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\n\t\t\t\tPenToolFsmState::Ready\n\t\t\t}\n\t\t\t(PenToolFsmState::PlacingAnchor, PenToolMessage::Confirm) => {\n\t\t\t\tresponses.add(DocumentMessage::EndTransaction);\n\t\t\t\ttool_data.cleanup(responses);\n\t\t\t\ttool_data.cleanup_target_selections(shape_editor, layer, document, responses);\n\n\t\t\t\tPenToolFsmState::Ready\n\t\t\t}\n\t\t\t(PenToolFsmState::DraggingHandle(..), PenToolMessage::Abort) => {\n\t\t\t\tresponses.add(DocumentMessage::AbortTransaction);\n\t\t\t\tif tool_data.handle_end.is_none() {\n\t\t\t\t\ttool_data.cleanup(responses);\n\t\t\t\t\ttool_data.cleanup_target_selections(shape_editor, layer, document, responses);\n\n\t\t\t\t\tPenToolFsmState::Ready\n\t\t\t\t} else {\n\t\t\t\t\ttool_data\n\t\t\t\t\t\t.place_anchor(SnapData::new(document, input, viewport), transform, input.mouse.position, responses)\n\t\t\t\t\t\t.unwrap_or(PenToolFsmState::Ready)\n\t\t\t\t}\n\t\t\t}\n\t\t\t(PenToolFsmState::PlacingAnchor, PenToolMessage::Abort) => {\n\t\t\t\tlet should_delete_layer = if let Some(vector) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)) {\n\t\t\t\t\tvector.point_domain.ids().len() == 1\n\t\t\t\t} else {\n\t\t\t\t\tfalse\n\t\t\t\t};\n\n\t\t\t\tresponses.add(DocumentMessage::AbortTransaction);\n\t\t\t\ttool_data.cleanup(responses);\n\t\t\t\ttool_data.cleanup_target_selections(shape_editor, layer, document, responses);\n\n\t\t\t\tif should_delete_layer {\n\t\t\t\t\tresponses.add(NodeGraphMessage::DeleteNodes {\n\t\t\t\t\t\tnode_ids: vec![layer.unwrap().to_node()],\n\t\t\t\t\t\tdelete_children: true,\n\t\t\t\t\t});\n\t\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\t}\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\n\t\t\t\tPenToolFsmState::Ready\n\t\t\t}\n\t\t\t(_, PenToolMessage::Abort) => PenToolFsmState::Ready,\n\t\t\t(PenToolFsmState::DraggingHandle(..) | PenToolFsmState::PlacingAnchor, PenToolMessage::Undo) => {\n\t\t\t\tif tool_data.point_index > 0 {\n\t\t\t\t\ttool_data.point_index -= 1;\n\t\t\t\t\ttool_data\n\t\t\t\t\t\t.place_anchor(SnapData::new(document, input, viewport), transform, input.mouse.position, responses)\n\t\t\t\t\t\t.unwrap_or(PenToolFsmState::PlacingAnchor)\n\t\t\t\t} else {\n\t\t\t\t\tresponses.add(PenToolMessage::Abort);\n\t\t\t\t\tself\n\t\t\t\t}\n\t\t\t}\n\t\t\t(_, PenToolMessage::Redo) => {\n\t\t\t\ttool_data.point_index = (tool_data.point_index + 1).min(tool_data.latest_points.len().saturating_sub(1));\n\t\t\t\ttool_data.place_anchor(SnapData::new(document, input, viewport), transform, input.mouse.position, responses);\n\t\t\t\tmatch tool_data.point_index {\n\t\t\t\t\t0 => PenToolFsmState::Ready,\n\t\t\t\t\t_ => PenToolFsmState::PlacingAnchor,\n\t\t\t\t}\n\t\t\t}\n\t\t\t_ => self,\n\t\t}\n\t}\n\n\tfn update_hints(&self, responses: &mut VecDeque<Message>) {\n\t\tlet hint_data = match self {\n\t\t\tPenToolFsmState::Ready | PenToolFsmState::GRSHandle => HintData(vec![HintGroup(vec![\n\t\t\t\tHintInfo::mouse(MouseMotion::Lmb, \"Draw Path\"),\n\t\t\t\t// TODO: Only show this if a single layer is selected and it's of a valid type (e.g. a vector path but not raster or artboard)\n\t\t\t\tHintInfo::keys([Key::Shift], \"Append to Selected Layer\").prepend_plus(),\n\t\t\t])]),\n\t\t\tPenToolFsmState::PlacingAnchor => HintData(vec![\n\t\t\t\tHintGroup(vec![\n\t\t\t\t\tHintInfo::mouse(MouseMotion::Rmb, \"\"),\n\t\t\t\t\tHintInfo::keys([Key::Escape], \"\").prepend_slash(),\n\t\t\t\t\tHintInfo::keys([Key::Enter], \"End Path\").prepend_slash(),\n\t\t\t\t]),\n\t\t\t\tHintGroup(vec![HintInfo::keys([Key::Shift], \"15° Increments\"), HintInfo::keys([Key::Control], \"Lock Angle\")]),\n\t\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, \"Add Sharp Point\"), HintInfo::mouse(MouseMotion::LmbDrag, \"Add Smooth Point\")]),\n\t\t\t\tHintGroup(vec![\n\t\t\t\t\tHintInfo::mouse(MouseMotion::Lmb, \"\"),\n\t\t\t\t\tHintInfo::mouse(MouseMotion::LmbDrag, \"Bend Prev. Point\").prepend_slash(),\n\t\t\t\t\tHintInfo::keys([Key::Control], \"Lock Angle\").prepend_plus(),\n\t\t\t\t]),\n\t\t\t]),\n\t\t\tPenToolFsmState::DraggingHandle(mode) => {\n\t\t\t\tlet mut dragging_hint_data = HintData(Vec::new());\n\t\t\t\tdragging_hint_data.0.push(HintGroup(vec![\n\t\t\t\t\tHintInfo::mouse(MouseMotion::Rmb, \"\"),\n\t\t\t\t\tHintInfo::keys([Key::Escape], \"Cancel Segment\").prepend_slash(),\n\t\t\t\t\tHintInfo::keys([Key::Enter], \"End Path\"),\n\t\t\t\t]));\n\n\t\t\t\tlet mut toggle_group = match mode {\n\t\t\t\t\tHandleMode::Free => {\n\t\t\t\t\t\tvec![HintInfo::keys([Key::KeyC], \"Make Handles Colinear\")]\n\t\t\t\t\t}\n\t\t\t\t\tHandleMode::ColinearLocked | HandleMode::ColinearEquidistant => {\n\t\t\t\t\t\tvec![HintInfo::keys([Key::KeyC], \"Break Colinear Handles\")]\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\ttoggle_group.push(HintInfo::keys([Key::Tab], \"Swap Dragged Handle\"));\n\n\t\t\t\tlet mut common_hints = vec![HintInfo::keys([Key::Shift], \"15° Increments\"), HintInfo::keys([Key::Control], \"Lock Angle\")];\n\t\t\t\tlet mut hold_group = match mode {\n\t\t\t\t\tHandleMode::Free => common_hints,\n\t\t\t\t\tHandleMode::ColinearLocked => {\n\t\t\t\t\t\tcommon_hints.push(HintInfo::keys([Key::Alt], \"Non-Equidistant Handles\"));\n\t\t\t\t\t\tcommon_hints\n\t\t\t\t\t}\n\t\t\t\t\tHandleMode::ColinearEquidistant => {\n\t\t\t\t\t\tcommon_hints.push(HintInfo::keys([Key::Alt], \"Equidistant Handles\"));\n\t\t\t\t\t\tcommon_hints\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\thold_group.push(HintInfo::keys([Key::Space], \"Drag Anchor\"));\n\n\t\t\t\tdragging_hint_data.0.push(HintGroup(toggle_group));\n\t\t\t\tdragging_hint_data.0.push(HintGroup(hold_group));\n\t\t\t\tdragging_hint_data\n\t\t\t}\n\t\t};\n\n\t\thint_data.send_layout(responses);\n\t}\n\n\tfn update_cursor(&self, responses: &mut VecDeque<Message>) {\n\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/tool_messages/select_tool.rs",
    "content": "#![allow(clippy::too_many_arguments)]\n\nuse super::tool_prelude::*;\nuse crate::consts::*;\nuse crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;\nuse crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;\nuse crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;\nuse crate::messages::portfolio::document::overlays::utility_types::OverlayContext;\nuse crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};\nuse crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis, GroupFolderType};\nuse crate::messages::portfolio::document::utility_types::network_interface::{FlowType, NodeNetworkInterface, NodeTemplate};\nuse crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;\nuse crate::messages::preferences::SelectionMode;\nuse crate::messages::tool::common_functionality::auto_panning::AutoPanning;\nuse crate::messages::tool::common_functionality::compass_rose::{Axis, CompassRose};\nuse crate::messages::tool::common_functionality::graph_modification_utils;\nuse crate::messages::tool::common_functionality::graph_modification_utils::is_layer_fed_by_node_of_name;\nuse crate::messages::tool::common_functionality::measure;\nuse crate::messages::tool::common_functionality::pivot::{PivotGizmo, PivotGizmoType, PivotToolSource, pin_pivot_widget, pivot_gizmo_type_widget, pivot_reference_point_widget};\nuse crate::messages::tool::common_functionality::shape_editor::SelectionShapeType;\nuse crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapManager};\nuse crate::messages::tool::common_functionality::transformation_cage::*;\nuse crate::messages::tool::common_functionality::utility_functions::{resize_bounds, rotate_bounds, skew_bounds, text_bounding_box, transforming_transform_cage};\nuse glam::DMat2;\nuse graph_craft::document::NodeId;\nuse graphene_std::renderer::Quad;\nuse graphene_std::renderer::Rect;\nuse graphene_std::subpath::Subpath;\nuse graphene_std::transform::ReferencePoint;\nuse graphene_std::vector::misc::BooleanOperation;\nuse std::fmt;\n\n#[derive(Default, ExtractField)]\npub struct SelectTool {\n\tfsm_state: SelectToolFsmState,\n\ttool_data: SelectToolData,\n}\n\n#[allow(dead_code)]\n#[derive(Default)]\npub struct SelectOptions {\n\tnested_selection_behavior: NestedSelectionBehavior,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)]\npub enum SelectOptionsUpdate {\n\tNestedSelectionBehavior(NestedSelectionBehavior),\n\tPivotGizmoType(PivotGizmoType),\n\tSetPivotGizmoEnabled(bool),\n\tTogglePivotPinned,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize)]\npub enum NestedSelectionBehavior {\n\t#[default]\n\tShallowest,\n\tDeepest,\n}\n\nimpl fmt::Display for NestedSelectionBehavior {\n\tfn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n\t\tmatch self {\n\t\t\tNestedSelectionBehavior::Deepest => write!(f, \"Deep Select\"),\n\t\t\tNestedSelectionBehavior::Shallowest => write!(f, \"Shallow Select\"),\n\t\t}\n\t}\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub struct SelectToolPointerKeys {\n\tpub axis_align: Key,\n\tpub snap_angle: Key,\n\tpub center: Key,\n\tpub duplicate: Key,\n}\n\n#[impl_message(Message, ToolMessage, Select)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum SelectToolMessage {\n\t// Standard messages\n\tAbort,\n\tOverlays {\n\t\tcontext: OverlayContext,\n\t},\n\n\t// Tool-specific messages\n\tDragStart {\n\t\textend_selection: Key,\n\t\tremove_from_selection: Key,\n\t\tselect_deepest: Key,\n\t\tlasso_select: Key,\n\t\tskew: Key,\n\t},\n\tDragStop {\n\t\tremove_from_selection: Key,\n\t},\n\tEditLayer,\n\tEditLayerExec,\n\tEnter,\n\tPointerMove {\n\t\tmodifier_keys: SelectToolPointerKeys,\n\t},\n\tPointerOutsideViewport {\n\t\tmodifier_keys: SelectToolPointerKeys,\n\t},\n\tSelectOptions {\n\t\toptions: SelectOptionsUpdate,\n\t},\n\tSetPivot {\n\t\tposition: ReferencePoint,\n\t},\n\tSyncHistory,\n\tShiftSelectedNodes {\n\t\toffset: DVec2,\n\t},\n\tPivotShift {\n\t\toffset: Option<DVec2>,\n\t\tflush: bool,\n\t},\n}\n\nimpl ToolMetadata for SelectTool {\n\tfn icon_name(&self) -> String {\n\t\t\"GeneralSelectTool\".into()\n\t}\n\tfn tooltip_label(&self) -> String {\n\t\t\"Select Tool\".into()\n\t}\n\tfn tool_type(&self) -> crate::messages::tool::utility_types::ToolType {\n\t\tToolType::Select\n\t}\n}\n\nimpl SelectTool {\n\tfn deep_selection_widget(&self) -> WidgetInstance {\n\t\tlet layer_selection_behavior_entries = [NestedSelectionBehavior::Shallowest, NestedSelectionBehavior::Deepest]\n\t\t\t.iter()\n\t\t\t.map(|mode| {\n\t\t\t\tMenuListEntry::new(format!(\"{mode:?}\")).label(mode.to_string()).on_commit(move |_| {\n\t\t\t\t\tSelectToolMessage::SelectOptions {\n\t\t\t\t\t\toptions: SelectOptionsUpdate::NestedSelectionBehavior(*mode),\n\t\t\t\t\t}\n\t\t\t\t\t.into()\n\t\t\t\t})\n\t\t\t})\n\t\t\t.collect();\n\n\t\tDropdownInput::new(vec![layer_selection_behavior_entries])\n\t\t\t.selected_index(Some((self.tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest) as u32))\n\t\t\t.tooltip_label(\"Selection Mode\")\n\t\t\t.tooltip_description(\n\t\t\t\t\"Shallow Select: clicks initially select the least-nested layers and double clicks drill deeper into the folder hierarchy.\\n\\\n\t\t\t\tDeep Select: clicks directly select the most-nested layers in the folder hierarchy.\",\n\t\t\t)\n\t\t\t.widget_instance()\n\t}\n\n\tfn alignment_widgets(&self, disabled: bool) -> impl Iterator<Item = WidgetInstance> + use<> {\n\t\t[AlignAxis::X, AlignAxis::Y]\n\t\t\t.into_iter()\n\t\t\t.flat_map(|axis| [(axis, AlignAggregate::Min), (axis, AlignAggregate::Center), (axis, AlignAggregate::Max)])\n\t\t\t.map(move |(axis, aggregate)| {\n\t\t\t\tlet (icon, label) = match (axis, aggregate) {\n\t\t\t\t\t(AlignAxis::X, AlignAggregate::Min) => (\"AlignLeft\", \"Align Left\"),\n\t\t\t\t\t(AlignAxis::X, AlignAggregate::Center) => (\"AlignHorizontalCenter\", \"Align Horizontal Center\"),\n\t\t\t\t\t(AlignAxis::X, AlignAggregate::Max) => (\"AlignRight\", \"Align Right\"),\n\t\t\t\t\t(AlignAxis::Y, AlignAggregate::Min) => (\"AlignTop\", \"Align Top\"),\n\t\t\t\t\t(AlignAxis::Y, AlignAggregate::Center) => (\"AlignVerticalCenter\", \"Align Vertical Center\"),\n\t\t\t\t\t(AlignAxis::Y, AlignAggregate::Max) => (\"AlignBottom\", \"Align Bottom\"),\n\t\t\t\t};\n\t\t\t\tIconButton::new(icon, 24)\n\t\t\t\t\t.tooltip_label(label)\n\t\t\t\t\t.on_update(move |_| DocumentMessage::AlignSelectedLayers { axis, aggregate }.into())\n\t\t\t\t\t.disabled(disabled)\n\t\t\t\t\t.widget_instance()\n\t\t\t})\n\t}\n\n\tfn flip_widgets(&self, disabled: bool) -> impl Iterator<Item = WidgetInstance> + use<> {\n\t\t[(FlipAxis::X, \"FlipHorizontal\", \"Flip Horizontal\"), (FlipAxis::Y, \"FlipVertical\", \"Flip Vertical\")]\n\t\t\t.into_iter()\n\t\t\t.map(move |(flip_axis, icon, label)| {\n\t\t\t\tIconButton::new(icon, 24)\n\t\t\t\t\t.tooltip_label(label)\n\t\t\t\t\t.on_update(move |_| DocumentMessage::FlipSelectedLayers { flip_axis }.into())\n\t\t\t\t\t.disabled(disabled)\n\t\t\t\t\t.widget_instance()\n\t\t\t})\n\t}\n\n\tfn turn_widgets(&self, disabled: bool) -> impl Iterator<Item = WidgetInstance> + use<> {\n\t\t[(-90., \"TurnNegative90\", \"Turn -90°\"), (90., \"TurnPositive90\", \"Turn 90°\")]\n\t\t\t.into_iter()\n\t\t\t.map(move |(degrees, icon, label)| {\n\t\t\t\tIconButton::new(icon, 24)\n\t\t\t\t\t.tooltip_label(label)\n\t\t\t\t\t.on_update(move |_| DocumentMessage::RotateSelectedLayers { degrees }.into())\n\t\t\t\t\t.disabled(disabled)\n\t\t\t\t\t.widget_instance()\n\t\t\t})\n\t}\n\n\tfn boolean_widgets(&self, selected_count: usize) -> impl Iterator<Item = WidgetInstance> + use<> {\n\t\tlet list = <BooleanOperation as graphene_std::choice_type::ChoiceTypeStatic>::list();\n\t\tlist.iter().flat_map(|i| i.iter()).map(move |(operation, info)| {\n\t\t\tIconButton::new(info.icon.unwrap(), 24)\n\t\t\t\t.tooltip_label(info.label)\n\t\t\t\t.tooltip_description(info.description.unwrap_or_default())\n\t\t\t\t.disabled(selected_count == 0)\n\t\t\t\t.on_update(move |_| {\n\t\t\t\t\tlet group_folder_type = GroupFolderType::BooleanOperation(*operation);\n\t\t\t\t\tDocumentMessage::GroupSelectedLayers { group_folder_type }.into()\n\t\t\t\t})\n\t\t\t\t.widget_instance()\n\t\t})\n\t}\n}\n\nimpl LayoutHolder for SelectTool {\n\tfn layout(&self) -> Layout {\n\t\tlet mut widgets = Vec::new();\n\n\t\t// Select mode (Deep/Shallow)\n\t\twidgets.push(self.deep_selection_widget());\n\n\t\t// Pivot gizmo type (checkbox + dropdown for pivot/origin)\n\t\twidgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\t\twidgets.extend(pivot_gizmo_type_widget(self.tool_data.pivot_gizmo.state, PivotToolSource::Select));\n\n\t\tif self.tool_data.pivot_gizmo.state.is_pivot() {\n\t\t\t// Nine-position reference point widget\n\t\t\twidgets.push(Separator::new(SeparatorStyle::Related).widget_instance());\n\t\t\twidgets.push(pivot_reference_point_widget(\n\t\t\t\tself.tool_data.selected_layers_count == 0 || !self.tool_data.pivot_gizmo.state.is_pivot(),\n\t\t\t\tself.tool_data.pivot_gizmo.pivot.to_pivot_position(),\n\t\t\t\tPivotToolSource::Select,\n\t\t\t));\n\n\t\t\t// Pivot pin button\n\t\t\twidgets.push(Separator::new(SeparatorStyle::Related).widget_instance());\n\n\t\t\tlet pin_active = self.tool_data.pivot_gizmo.pin_active();\n\t\t\tlet pin_enabled = self.tool_data.pivot_gizmo.pivot.old_pivot_position == ReferencePoint::None && self.tool_data.pivot_gizmo.state.enabled;\n\n\t\t\tif pin_active || pin_enabled {\n\t\t\t\twidgets.push(pin_pivot_widget(pin_active, pin_enabled, PivotToolSource::Select));\n\t\t\t}\n\t\t}\n\n\t\t// Align\n\t\tlet disabled = self.tool_data.selected_layers_count < 2;\n\t\twidgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\t\twidgets.extend(self.alignment_widgets(disabled));\n\n\t\t// Flip\n\t\tlet disabled = self.tool_data.selected_layers_count == 0;\n\t\twidgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\t\twidgets.extend(self.flip_widgets(disabled));\n\n\t\t// Turn\n\t\twidgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\t\twidgets.extend(self.turn_widgets(disabled));\n\n\t\t// Boolean\n\t\twidgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\t\twidgets.extend(self.boolean_widgets(self.tool_data.selected_layers_count));\n\n\t\tLayout(vec![LayoutGroup::row(widgets)])\n\t}\n}\n\n#[message_handler_data]\nimpl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for SelectTool {\n\tfn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {\n\t\tlet mut redraw_reference_pivot = false;\n\n\t\tif let ToolMessage::Select(SelectToolMessage::SelectOptions { options: ref option_update }) = message {\n\t\t\tmatch *option_update {\n\t\t\t\tSelectOptionsUpdate::NestedSelectionBehavior(nested_selection_behavior) => {\n\t\t\t\t\tself.tool_data.nested_selection_behavior = nested_selection_behavior;\n\t\t\t\t\tresponses.add(ToolMessage::UpdateHints);\n\t\t\t\t}\n\t\t\t\tSelectOptionsUpdate::PivotGizmoType(gizmo_type) => {\n\t\t\t\t\tif self.tool_data.pivot_gizmo.state.enabled {\n\t\t\t\t\t\tself.tool_data.pivot_gizmo.state.gizmo_type = gizmo_type;\n\t\t\t\t\t\tresponses.add(ToolMessage::UpdateHints);\n\t\t\t\t\t\tlet pivot_gizmo = self.tool_data.pivot_gizmo();\n\t\t\t\t\t\tresponses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });\n\t\t\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\t\t\tredraw_reference_pivot = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tSelectOptionsUpdate::SetPivotGizmoEnabled(enabled) => {\n\t\t\t\t\tself.tool_data.pivot_gizmo.state.enabled = enabled;\n\t\t\t\t\tresponses.add(ToolMessage::UpdateHints);\n\t\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\t\tredraw_reference_pivot = true;\n\t\t\t\t}\n\n\t\t\t\tSelectOptionsUpdate::TogglePivotPinned => {\n\t\t\t\t\tself.tool_data.pivot_gizmo.pivot.pinned = !self.tool_data.pivot_gizmo.pivot.pinned;\n\t\t\t\t\tresponses.add(ToolMessage::UpdateHints);\n\t\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\t\tredraw_reference_pivot = true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tself.fsm_state.process_event(message, &mut self.tool_data, context, &(), responses, false);\n\n\t\tif self.tool_data.pivot_gizmo.pivot.should_refresh_pivot_position() || self.tool_data.selected_layers_changed || redraw_reference_pivot {\n\t\t\t// Send the layout containing the updated pivot position (a bit ugly to do it here not in the fsm but that doesn't have SelectTool)\n\t\t\tself.send_layout(responses, LayoutTarget::ToolOptions);\n\t\t\tself.tool_data.selected_layers_changed = false;\n\t\t}\n\t}\n\n\tfn actions(&self) -> ActionList {\n\t\tlet mut common = actions!(SelectToolMessageDiscriminant;\n\t\t\tPointerMove,\n\t\t\tAbort,\n\t\t\tEditLayer,\n\t\t\tEditLayerExec,\n\t\t\tEnter,\n\t\t);\n\n\t\tlet additional = match self.fsm_state {\n\t\t\tSelectToolFsmState::Ready { .. } => actions!(SelectToolMessageDiscriminant; DragStart),\n\t\t\t_ => actions!(SelectToolMessageDiscriminant; DragStop),\n\t\t};\n\t\tcommon.extend(additional);\n\n\t\tcommon\n\t}\n}\n\nimpl ToolTransition for SelectTool {\n\tfn event_to_message_map(&self) -> EventToMessageMap {\n\t\tEventToMessageMap {\n\t\t\ttool_abort: Some(SelectToolMessage::Abort.into()),\n\t\t\toverlay_provider: Some(|context| SelectToolMessage::Overlays { context }.into()),\n\t\t\t..Default::default()\n\t\t}\n\t}\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]\nenum SelectToolFsmState {\n\tReady {\n\t\tselection: NestedSelectionBehavior,\n\t},\n\tDrawing {\n\t\tselection_shape: SelectionShapeType,\n\t\thas_drawn: bool,\n\t},\n\tDragging {\n\t\taxis: Axis,\n\t\tusing_compass: bool,\n\t\thas_dragged: bool,\n\t\tdeepest: bool,\n\t\tremove: bool,\n\t},\n\tResizingBounds,\n\tSkewingBounds {\n\t\tskew: Key,\n\t},\n\tRotatingBounds,\n\tDraggingPivot,\n}\n\nimpl Default for SelectToolFsmState {\n\tfn default() -> Self {\n\t\tlet selection = NestedSelectionBehavior::Deepest;\n\t\tSelectToolFsmState::Ready { selection }\n\t}\n}\n\n#[derive(Clone, Debug, Default)]\nstruct SelectToolData {\n\tdrag_start: ViewportPosition,\n\tdrag_current: ViewportPosition,\n\tlasso_polygon: Vec<ViewportPosition>,\n\tselection_mode: Option<SelectionMode>,\n\tlayers_dragging: Vec<LayerNodeIdentifier>, // Unordered, often used as temporary buffer\n\tordered_layers: Vec<LayerNodeIdentifier>,  // Ordered list of layers\n\tlayer_selected_on_start: Option<LayerNodeIdentifier>,\n\tselect_single_layer: Option<LayerNodeIdentifier>,\n\taxis_align: bool,\n\tnon_duplicated_layers: Option<Vec<LayerNodeIdentifier>>,\n\tbounding_box_manager: Option<BoundingBoxManager>,\n\tsnap_manager: SnapManager,\n\tcursor: MouseCursorIcon,\n\tpivot_gizmo: PivotGizmo,\n\tpivot_gizmo_start: Option<DVec2>,\n\tpivot_gizmo_shift: Option<DVec2>,\n\tcompass_rose: CompassRose,\n\tline_center: DVec2,\n\tskew_edge: EdgeBool,\n\tnested_selection_behavior: NestedSelectionBehavior,\n\tselected_layers_count: usize,\n\tselected_layers_changed: bool,\n\tsnap_candidates: Vec<SnapCandidatePoint>,\n\tauto_panning: AutoPanning,\n\tdrag_start_center: ViewportPosition,\n}\n\nimpl SelectToolData {\n\tfn get_snap_candidates(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler) {\n\t\tself.snap_candidates.clear();\n\t\tfor &layer in &self.layers_dragging {\n\t\t\tif (self.snap_candidates.len() as f64) < document.snapping_state.tolerance {\n\t\t\t\tsnapping::get_layer_snap_points(layer, &SnapData::new(document, input, viewport), &mut self.snap_candidates);\n\t\t\t}\n\t\t\tif let Some(bounds) = document.metadata().bounding_box_with_transform(layer, DAffine2::IDENTITY) {\n\t\t\t\tlet quad = document.metadata().transform_to_document(layer) * Quad::from_box(bounds);\n\t\t\t\tsnapping::get_bbox_points(quad, &mut self.snap_candidates, snapping::BBoxSnapValues::BOUNDING_BOX, document);\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn selection_quad(&self) -> Quad {\n\t\tlet bbox = self.selection_box();\n\t\tQuad::from_box(bbox)\n\t}\n\n\tpub fn calculate_selection_mode_from_direction(&mut self) -> SelectionMode {\n\t\tlet bbox: [DVec2; 2] = self.selection_box();\n\t\tlet above_threshold = bbox[1].distance_squared(bbox[0]) > DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD.powi(2);\n\n\t\tif self.selection_mode.is_none() && above_threshold {\n\t\t\tlet mode = if bbox[1].x < bbox[0].x {\n\t\t\t\tSelectionMode::Touched\n\t\t\t} else {\n\t\t\t\t// This also covers the case where they're equal: the area is zero, so we use `Enclosed` to ensure the selection ends up empty, as nothing will be enclosed by an empty area\n\t\t\t\tSelectionMode::Enclosed\n\t\t\t};\n\t\t\tself.selection_mode = Some(mode);\n\t\t}\n\n\t\tself.selection_mode.unwrap_or(SelectionMode::Touched)\n\t}\n\n\tpub fn selection_box(&self) -> [DVec2; 2] {\n\t\tif self.drag_current == self.drag_start {\n\t\t\tlet tolerance = DVec2::splat(SELECTION_TOLERANCE);\n\t\t\t[self.drag_start - tolerance, self.drag_start + tolerance]\n\t\t} else {\n\t\t\t[self.drag_start, self.drag_current]\n\t\t}\n\t}\n\n\tpub fn intersect_lasso_no_artboards(&self, document: &DocumentMessageHandler, viewport: &ViewportMessageHandler) -> Vec<LayerNodeIdentifier> {\n\t\tif self.lasso_polygon.len() < 2 {\n\t\t\treturn Vec::new();\n\t\t}\n\t\tlet polygon = Subpath::from_anchors(self.lasso_polygon.clone(), true);\n\t\tdocument.intersect_polygon_no_artboards(polygon, viewport).collect()\n\t}\n\n\tpub fn is_layer_inside_lasso_polygon(&self, layer: &LayerNodeIdentifier, document: &DocumentMessageHandler, viewport: &ViewportMessageHandler) -> bool {\n\t\tif self.lasso_polygon.len() < 2 {\n\t\t\treturn false;\n\t\t}\n\t\tlet polygon = Subpath::from_anchors(self.lasso_polygon.clone(), true);\n\t\tdocument.is_layer_fully_inside_polygon(layer, viewport, polygon)\n\t}\n\n\t/// Duplicates the currently dragging layers. Called when Alt is pressed and the layers have not yet been duplicated.\n\tfn start_duplicates(&mut self, document: &mut DocumentMessageHandler, responses: &mut VecDeque<Message>) {\n\t\tself.non_duplicated_layers = Some(self.layers_dragging.clone());\n\t\tlet mut new_dragging = Vec::new();\n\n\t\t// Get the shallowest unique layers and sort by their index relative to parent for ordered processing\n\t\tlet mut layers = document.network_interface.shallowest_unique_layers(&[]).collect::<Vec<_>>();\n\n\t\tlayers.sort_by_key(|layer| {\n\t\t\tlet Some(parent) = layer.parent(document.metadata()) else { return usize::MAX };\n\t\t\tDocumentMessageHandler::get_calculated_insert_index(document.metadata(), &SelectedNodes(vec![layer.to_node()]), parent)\n\t\t});\n\n\t\tfor layer in layers.into_iter().rev() {\n\t\t\tlet Some(parent) = layer.parent(document.metadata()) else { continue };\n\n\t\t\t// Moves the layer back to its starting position.\n\t\t\tresponses.add(GraphOperationMessage::TransformChange {\n\t\t\t\tlayer,\n\t\t\t\ttransform: DAffine2::from_translation(self.drag_start - self.drag_current),\n\t\t\t\ttransform_in: TransformIn::Viewport,\n\t\t\t\tskip_rerender: true,\n\t\t\t});\n\n\t\t\t// Copy the layer\n\t\t\tlet mut copy_ids = HashMap::new();\n\t\t\tlet node_id = layer.to_node();\n\t\t\tcopy_ids.insert(node_id, NodeId(0));\n\n\t\t\tdocument\n\t\t\t\t.network_interface\n\t\t\t\t.upstream_flow_back_from_nodes(vec![layer.to_node()], &[], FlowType::LayerChildrenUpstreamFlow)\n\t\t\t\t.enumerate()\n\t\t\t\t.for_each(|(index, node_id)| {\n\t\t\t\t\tcopy_ids.insert(node_id, NodeId((index + 1) as u64));\n\t\t\t\t});\n\n\t\t\tlet nodes = document.network_interface.copy_nodes(&copy_ids, &[]).collect::<Vec<(NodeId, NodeTemplate)>>();\n\n\t\t\tlet insert_index = DocumentMessageHandler::get_calculated_insert_index(document.metadata(), &SelectedNodes(vec![layer.to_node()]), parent);\n\n\t\t\tlet new_ids: HashMap<_, _> = nodes.iter().map(|(id, _)| (*id, NodeId::new())).collect();\n\n\t\t\tlet layer_id = *new_ids.get(&NodeId(0)).expect(\"Node Id 0 should be a layer\");\n\t\t\tlet layer = LayerNodeIdentifier::new_unchecked(layer_id);\n\t\t\tnew_dragging.push(layer);\n\t\t\tresponses.add(NodeGraphMessage::AddNodes { nodes, new_ids });\n\t\t\tresponses.add(NodeGraphMessage::MoveLayerToStack { layer, parent, insert_index });\n\t\t}\n\t\tlet nodes = new_dragging.iter().map(|layer| layer.to_node()).collect();\n\t\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes });\n\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\tself.layers_dragging = new_dragging;\n\t}\n\n\t/// Removes the duplicated layers. Called when Alt is released and the layers have previously been duplicated.\n\tfn stop_duplicates(&mut self, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {\n\t\tlet Some(original) = self.non_duplicated_layers.take() else {\n\t\t\treturn;\n\t\t};\n\n\t\t// Delete the duplicated layers\n\t\tfor layer in document.network_interface.shallowest_unique_layers(&[]) {\n\t\t\tresponses.add(NodeGraphMessage::DeleteNodes {\n\t\t\t\tnode_ids: vec![layer.to_node()],\n\t\t\t\tdelete_children: true,\n\t\t\t});\n\t\t}\n\n\t\tfor &layer in &original {\n\t\t\tresponses.add(GraphOperationMessage::TransformChange {\n\t\t\t\tlayer,\n\t\t\t\ttransform: DAffine2::from_translation(self.drag_current - self.drag_start),\n\t\t\t\ttransform_in: TransformIn::Viewport,\n\t\t\t\tskip_rerender: true,\n\t\t\t});\n\t\t}\n\t\tlet nodes = original\n\t\t\t.iter()\n\t\t\t.filter_map(|layer| {\n\t\t\t\tif *layer != LayerNodeIdentifier::ROOT_PARENT {\n\t\t\t\t\tSome(layer.to_node())\n\t\t\t\t} else {\n\t\t\t\t\tlog::error!(\"ROOT_PARENT cannot be part of non_duplicated_layers\");\n\t\t\t\t\tNone\n\t\t\t\t}\n\t\t\t})\n\t\t\t.collect();\n\t\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes });\n\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\tresponses.add(NodeGraphMessage::SelectedNodesUpdated);\n\t\tresponses.add(NodeGraphMessage::SendGraph);\n\t\tself.layers_dragging = original;\n\t}\n\n\tfn state_from_pivot_gizmo(&self, mouse: DVec2) -> Option<SelectToolFsmState> {\n\t\tmatch self.pivot_gizmo.state.gizmo_type {\n\t\t\tPivotGizmoType::Pivot if self.pivot_gizmo.state.is_pivot() => self.pivot_gizmo.pivot.is_over(mouse).then_some(SelectToolFsmState::DraggingPivot),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tfn pivot_gizmo(&self) -> PivotGizmo {\n\t\tself.pivot_gizmo.clone()\n\t}\n\n\tfn sync_history(&mut self, document: &DocumentMessageHandler) {\n\t\tlet layers: Vec<_> = document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface).collect();\n\t\tself.ordered_layers.retain(|layer| layers.contains(layer));\n\t\tself.ordered_layers.extend(layers.iter().find(|&layer| !self.ordered_layers.contains(layer)));\n\t\tself.pivot_gizmo.layer = self.ordered_layers.last().copied()\n\t}\n}\n\n/// Bounding boxes are unfortunately not axis aligned. The bounding boxes are found after a transformation is applied to all of the layers.\n/// This uses some rather confusing logic to determine what transform that should be.\npub fn create_bounding_box_transform(document: &DocumentMessageHandler) -> DAffine2 {\n\t// Update bounds\n\tdocument\n\t\t.network_interface\n\t\t.selected_nodes()\n\t\t.selected_visible_and_unlocked_layers(&document.network_interface)\n\t\t.find(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]))\n\t\t.map(|layer| document.metadata().transform_to_viewport_with_first_transform_node_if_group(layer, &document.network_interface))\n\t\t.unwrap_or_default()\n}\n\nimpl Fsm for SelectToolFsmState {\n\ttype ToolData = SelectToolData;\n\ttype ToolOptions = ();\n\n\tfn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionMessageContext, _tool_options: &(), responses: &mut VecDeque<Message>) -> Self {\n\t\tlet ToolActionMessageContext {\n\t\t\tdocument,\n\t\t\tinput,\n\t\t\tviewport,\n\t\t\tpersistent_data,\n\t\t\t..\n\t\t} = tool_action_data;\n\n\t\tlet ToolMessage::Select(event) = event else { return self };\n\t\tmatch (self, event) {\n\t\t\t(_, SelectToolMessage::Overlays { context: mut overlay_context }) => {\n\t\t\t\ttool_data.snap_manager.draw_overlays(SnapData::new(document, input, viewport), &mut overlay_context);\n\n\t\t\t\tcrate::messages::tool::common_functionality::layer_origin_cross::draw_for_selected_layers(&mut overlay_context, document);\n\n\t\t\t\tlet selected_layers_count = document.network_interface.selected_nodes().selected_unlocked_layers(&document.network_interface).count();\n\t\t\t\ttool_data.selected_layers_changed = selected_layers_count != tool_data.selected_layers_count;\n\t\t\t\ttool_data.selected_layers_count = selected_layers_count;\n\n\t\t\t\t// Outline selected layers, but not artboards\n\t\t\t\tif overlay_context.visibility_settings.selection_outline() {\n\t\t\t\t\tfor layer in document\n\t\t\t\t\t\t.network_interface\n\t\t\t\t\t\t.selected_nodes()\n\t\t\t\t\t\t.selected_visible_and_unlocked_layers(&document.network_interface)\n\t\t\t\t\t\t.filter(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]))\n\t\t\t\t\t{\n\t\t\t\t\t\tlet layer_to_viewport = document.metadata().transform_to_viewport(layer);\n\t\t\t\t\t\toverlay_context.outline(document.metadata().layer_with_free_points_outline(layer), layer_to_viewport, None);\n\n\t\t\t\t\t\tif is_layer_fed_by_node_of_name(layer, &document.network_interface, &DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER)) {\n\t\t\t\t\t\t\tlet transformed_quad = layer_to_viewport * text_bounding_box(layer, document, &persistent_data.font_cache);\n\t\t\t\t\t\t\toverlay_context.dashed_quad(transformed_quad, None, None, Some(7.), Some(5.), None);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tlet mut transform = create_bounding_box_transform(document);\n\n\t\t\t\t// Check if the matrix is not invertible\n\t\t\t\tlet mut transform_tampered = false;\n\t\t\t\tif transform.matrix2.determinant() == 0. {\n\t\t\t\t\ttransform.matrix2 += DMat2::IDENTITY * 1e-4; // TODO: Is this the cleanest way to handle this?\n\t\t\t\t\ttransform_tampered = true;\n\t\t\t\t}\n\n\t\t\t\tlet bounds = document\n\t\t\t\t\t.network_interface\n\t\t\t\t\t.selected_nodes()\n\t\t\t\t\t.selected_visible_and_unlocked_layers(&document.network_interface)\n\t\t\t\t\t.filter(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]))\n\t\t\t\t\t.filter_map(|layer| {\n\t\t\t\t\t\tdocument\n\t\t\t\t\t\t\t.metadata()\n\t\t\t\t\t\t\t.bounding_box_with_transform(layer, transform.inverse() * document.metadata().transform_to_viewport(layer))\n\t\t\t\t\t})\n\t\t\t\t\t.reduce(graphene_std::renderer::Quad::combine_bounds);\n\n\t\t\t\t// When not in Drawing State\n\t\t\t\t// Only highlight layers if the viewport is not being panned (middle mouse button is pressed)\n\t\t\t\t// TODO: Don't use `Key::MouseMiddle` directly, instead take it as a variable from the input mappings list like in all other places; or find a better way than checking the key state\n\t\t\t\tif !matches!(self, Self::Drawing { .. }) && !input.keyboard.get(Key::MouseMiddle as usize) {\n\t\t\t\t\t// Get the layer the user is hovering over\n\t\t\t\t\t// Artboards are included since they're needed for quick measurement, but will be filtered out for selection later on\n\t\t\t\t\tlet click = document.click_list_with_artboards(input, viewport).last();\n\t\t\t\t\tlet not_selected_click = click.filter(|&hovered_layer| !document.network_interface.selected_nodes().selected_layers_contains(hovered_layer, document.metadata()));\n\t\t\t\t\tif let Some(layer) = not_selected_click {\n\t\t\t\t\t\tif overlay_context.visibility_settings.hover_outline() && !document.network_interface.is_artboard(&layer.to_node(), &[]) {\n\t\t\t\t\t\t\tlet layer_to_viewport = document.metadata().transform_to_viewport(layer);\n\t\t\t\t\t\t\tlet mut hover_overlay_draw = |layer: LayerNodeIdentifier, color: Option<&str>| {\n\t\t\t\t\t\t\t\tif layer.has_children(document.metadata()) {\n\t\t\t\t\t\t\t\t\tif let Some(bounds) = document.metadata().bounding_box_viewport(layer) {\n\t\t\t\t\t\t\t\t\t\toverlay_context.quad(Quad::from_box(bounds), color, None);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\toverlay_context.outline(document.metadata().layer_with_free_points_outline(layer), layer_to_viewport, color);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tlet layer = match tool_data.nested_selection_behavior {\n\t\t\t\t\t\t\t\tNestedSelectionBehavior::Deepest => document.find_deepest(&[layer]),\n\t\t\t\t\t\t\t\tNestedSelectionBehavior::Shallowest => layer_selected_shallowest(layer, document),\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t.unwrap_or(layer);\n\t\t\t\t\t\t\thover_overlay_draw(layer, None);\n\t\t\t\t\t\t\tif matches!(tool_data.nested_selection_behavior, NestedSelectionBehavior::Shallowest) {\n\t\t\t\t\t\t\t\tlet mut selected = document.network_interface.selected_nodes();\n\t\t\t\t\t\t\t\tselected.add_selected_nodes(vec![layer.to_node()]);\n\t\t\t\t\t\t\t\tif let Some(new_selected) = click.unwrap().ancestors(document.metadata()).filter(not_artboard(document)).find(|ancestor| {\n\t\t\t\t\t\t\t\t\tancestor\n\t\t\t\t\t\t\t\t\t\t.parent(document.metadata())\n\t\t\t\t\t\t\t\t\t\t.is_some_and(|parent| selected.selected_layers_contains(parent, document.metadata()))\n\t\t\t\t\t\t\t\t}) {\n\t\t\t\t\t\t\t\t\thover_overlay_draw(new_selected, Some(COLOR_OVERLAY_BLUE_50));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Measure with Alt held down\n\t\t\t\t\t\t// TODO: Don't use `Key::Alt` directly, instead take it as a variable from the input mappings list like in all other places\n\t\t\t\t\t\tif overlay_context.visibility_settings.quick_measurement() && !matches!(self, Self::ResizingBounds { .. }) && input.keyboard.get(Key::Alt as usize) {\n\t\t\t\t\t\t\t// Compute document-space bounding box (AABB) of all selected visible & unlocked layers\n\t\t\t\t\t\t\tlet selected_bounds_doc_space = document\n\t\t\t\t\t\t\t\t.network_interface\n\t\t\t\t\t\t\t\t.selected_nodes()\n\t\t\t\t\t\t\t\t.selected_visible_and_unlocked_layers(&document.network_interface)\n\t\t\t\t\t\t\t\t// Exclude layers that are artboards from the selection bounding box\n\t\t\t\t\t\t\t\t.filter(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]))\n\t\t\t\t\t\t\t\t// For each remaining layer, try to get its document-space bounding box and convert it to a Rect\n\t\t\t\t\t\t\t\t.filter_map(|layer| document.metadata().bounding_box_document(layer).map(Rect::from_box))\n\t\t\t\t\t\t\t\t// Combine all individual bounding boxes into one overall bounding box that contains all selected layers\n\t\t\t\t\t\t\t\t.reduce(Rect::combine_bounds);\n\n\t\t\t\t\t\t\t// Compute document-space bounding box (AABB) of the currently hovered layer\n\t\t\t\t\t\t\tlet hovered_bounds_doc_space = document.metadata().bounding_box_document(layer);\n\n\t\t\t\t\t\t\t// If both selected and hovered bounds exist, overlay measurement lines\n\t\t\t\t\t\t\tif let (Some(selected_bounds), Some(hovered_bounds)) = (selected_bounds_doc_space, hovered_bounds_doc_space.map(Rect::from_box)) {\n\t\t\t\t\t\t\t\t// Both `selected_bounds` and `hovered_bounds` are in document space.\n\t\t\t\t\t\t\t\t// To correctly render overlay lines in the UI (which is in viewport space), we need to transform both rectangles from document to viewport space.\n\t\t\t\t\t\t\t\t// Therefore, we pass `document_to_viewport` as both the `transform` and `document_to_viewport` parameters.\n\t\t\t\t\t\t\t\tlet document_to_viewport = document.metadata().document_to_viewport;\n\t\t\t\t\t\t\t\tmeasure::overlay(selected_bounds, hovered_bounds, document_to_viewport, document_to_viewport, &mut overlay_context);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif let Some(bounds) = bounds {\n\t\t\t\t\tlet bounding_box_manager = tool_data.bounding_box_manager.get_or_insert(BoundingBoxManager::default());\n\n\t\t\t\t\t// TODO: Don't perform bounding box calculations here because the user can disable overlays which breaks bbox-based resizing\n\t\t\t\t\tbounding_box_manager.bounds = bounds;\n\t\t\t\t\tbounding_box_manager.transform = transform;\n\t\t\t\t\tbounding_box_manager.transform_tampered = transform_tampered;\n\t\t\t\t\tif overlay_context.visibility_settings.transform_cage() {\n\t\t\t\t\t\tbounding_box_manager.render_overlays(&mut overlay_context, true);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\ttool_data.bounding_box_manager.take();\n\t\t\t\t}\n\n\t\t\t\tlet angle = bounds\n\t\t\t\t\t.map(|bounds| transform * Quad::from_box(bounds))\n\t\t\t\t\t.map_or(0., |quad| (quad.top_left() - quad.top_right()).to_angle());\n\n\t\t\t\tlet mouse_position = input.mouse.position;\n\t\t\t\tlet compass_rose_state = tool_data.compass_rose.compass_rose_state(mouse_position, angle);\n\n\t\t\t\tlet show_hover_ring = if let SelectToolFsmState::Dragging { axis, using_compass, .. } = self {\n\t\t\t\t\tusing_compass && !axis.is_constraint()\n\t\t\t\t} else {\n\t\t\t\t\tcompass_rose_state.is_ring()\n\t\t\t\t};\n\n\t\t\t\tlet dragging_bounds = tool_data\n\t\t\t\t\t.bounding_box_manager\n\t\t\t\t\t.as_mut()\n\t\t\t\t\t.and_then(|bounding_box| bounding_box.check_selected_edges(input.mouse.position))\n\t\t\t\t\t.is_some();\n\n\t\t\t\tlet rotating_bounds = tool_data\n\t\t\t\t\t.bounding_box_manager\n\t\t\t\t\t.as_ref()\n\t\t\t\t\t.map(|bounding_box| bounding_box.check_rotate(input.mouse.position))\n\t\t\t\t\t.unwrap_or_default();\n\n\t\t\t\tlet is_resizing_or_rotating = matches!(self, SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds { .. } | SelectToolFsmState::RotatingBounds);\n\n\t\t\t\tif overlay_context.visibility_settings.transform_cage()\n\t\t\t\t\t&& let Some(bounds) = tool_data.bounding_box_manager.as_mut()\n\t\t\t\t{\n\t\t\t\t\tlet edges = bounds.check_selected_edges(input.mouse.position);\n\t\t\t\t\tlet is_skewing = matches!(self, SelectToolFsmState::SkewingBounds { .. });\n\t\t\t\t\tlet is_near_square = edges.is_some_and(|hover_edge| bounds.over_extended_edge_midpoint(input.mouse.position, hover_edge));\n\t\t\t\t\tif is_skewing || (dragging_bounds && is_near_square && !is_resizing_or_rotating) {\n\t\t\t\t\t\tbounds.render_skew_gizmos(&mut overlay_context, tool_data.skew_edge);\n\t\t\t\t\t}\n\t\t\t\t\tif !is_skewing\n\t\t\t\t\t\t&& dragging_bounds && let Some(edges) = edges\n\t\t\t\t\t{\n\t\t\t\t\t\ttool_data.skew_edge = bounds.get_closest_edge(edges, input.mouse.position);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tlet might_resize_or_rotate = dragging_bounds || rotating_bounds;\n\t\t\t\tlet can_get_into_other_states = might_resize_or_rotate && !matches!(self, SelectToolFsmState::Dragging { .. });\n\n\t\t\t\tlet show_compass = !(can_get_into_other_states || is_resizing_or_rotating);\n\t\t\t\tlet show_compass_with_ring = bounds.map(|bounds| transform * Quad::from_box(bounds)).and_then(|quad| {\n\t\t\t\t\tconst MIN_ARROWS_TO_RESIZE_HANDLE_DISTANCE: f64 = 4.;\n\t\t\t\t\t(show_compass && quad.all_sides_at_least_width(COMPASS_ROSE_HOVER_RING_DIAMETER + RESIZE_HANDLE_SIZE + MIN_ARROWS_TO_RESIZE_HANDLE_DISTANCE))\n\t\t\t\t\t\t.then_some(\n\t\t\t\t\t\t\tmatches!(self, SelectToolFsmState::Dragging { .. })\n\t\t\t\t\t\t\t\t.then_some(show_hover_ring)\n\t\t\t\t\t\t\t\t.or((quad.contains(mouse_position)).then_some(show_hover_ring)),\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.flatten()\n\t\t\t\t});\n\n\t\t\t\tlet mut active_origin = None;\n\t\t\t\tlet mut origin_angle = 0.;\n\t\t\t\tif overlay_context.visibility_settings.origin() && !tool_data.pivot_gizmo.state.is_pivot_type() {\n\t\t\t\t\tlet get_angle = |layer: LayerNodeIdentifier| -> f64 {\n\t\t\t\t\t\tlet quad = Quad::from_box([DVec2::ZERO, DVec2::ONE]);\n\t\t\t\t\t\tlet bounds = document.metadata().transform_to_viewport_with_first_transform_node_if_group(layer, &document.network_interface) * quad;\n\t\t\t\t\t\t(bounds.top_left() - bounds.top_right()).to_angle()\n\t\t\t\t\t};\n\t\t\t\t\tif tool_data.pivot_gizmo.state.gizmo_type == PivotGizmoType::Average {\n\t\t\t\t\t\tlet mut count = 0_usize;\n\n\t\t\t\t\t\tlet sum: f64 = document\n\t\t\t\t\t\t\t.network_interface\n\t\t\t\t\t\t\t.selected_nodes()\n\t\t\t\t\t\t\t.selected_visible_and_unlocked_layers(&document.network_interface)\n\t\t\t\t\t\t\t.map(get_angle)\n\t\t\t\t\t\t\t.inspect(|_| count += 1)\n\t\t\t\t\t\t\t.sum();\n\t\t\t\t\t\tif count > 0 {\n\t\t\t\t\t\t\torigin_angle = sum / count as f64;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if tool_data.pivot_gizmo.state.gizmo_type == PivotGizmoType::Active {\n\t\t\t\t\t\torigin_angle = document\n\t\t\t\t\t\t\t.network_interface\n\t\t\t\t\t\t\t.selected_nodes()\n\t\t\t\t\t\t\t.selected_visible_and_unlocked_layers(&document.network_interface)\n\t\t\t\t\t\t\t.find(|&layer| Some(layer) == tool_data.pivot_gizmo.layer)\n\t\t\t\t\t\t\t.iter()\n\t\t\t\t\t\t\t.map(|&layer| get_angle(layer))\n\t\t\t\t\t\t\t.sum();\n\t\t\t\t\t}\n\n\t\t\t\t\tfor layer in document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface) {\n\t\t\t\t\t\tlet origin = graph_modification_utils::get_viewport_origin(layer, &document.network_interface);\n\t\t\t\t\t\tif Some(layer) == tool_data.pivot_gizmo.layer {\n\t\t\t\t\t\t\tactive_origin = Some(origin);\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\toverlay_context.dowel_pin(origin, origin_angle, None);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif let Some(origin) = active_origin {\n\t\t\t\t\toverlay_context.dowel_pin(origin, origin_angle, Some(COLOR_OVERLAY_YELLOW));\n\t\t\t\t}\n\n\t\t\t\tlet has_layers = document.network_interface.selected_nodes().has_selected_nodes();\n\t\t\t\tlet draw_pivot = tool_data.pivot_gizmo.state.is_pivot() && overlay_context.visibility_settings.pivot() && has_layers;\n\t\t\t\ttool_data.pivot_gizmo.pivot.recalculate_pivot(document);\n\t\t\t\tlet pivot = draw_pivot.then_some(tool_data.pivot_gizmo.pivot.pivot).flatten();\n\t\t\t\tif let Some(pivot) = pivot {\n\t\t\t\t\tlet offset = tool_data\n\t\t\t\t\t\t.pivot_gizmo_start\n\t\t\t\t\t\t.map(|offset| {\n\t\t\t\t\t\t\tif tool_data.pivot_gizmo.pivot_disconnected() {\n\t\t\t\t\t\t\t\ttool_data.drag_current - offset\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tDefault::default()\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.unwrap_or_default();\n\t\t\t\t\tlet shift = tool_data.pivot_gizmo_shift.unwrap_or_default();\n\t\t\t\t\toverlay_context.pivot(pivot + offset + shift, angle);\n\t\t\t\t}\n\n\t\t\t\t// Update compass rose\n\t\t\t\tif overlay_context.visibility_settings.compass_rose() {\n\t\t\t\t\ttool_data.compass_rose.refresh_position(document);\n\t\t\t\t\tlet compass_center = tool_data.compass_rose.compass_rose_position();\n\t\t\t\t\tif !matches!(self, Self::Dragging { .. }) {\n\t\t\t\t\t\ttool_data.line_center = compass_center;\n\t\t\t\t\t}\n\n\t\t\t\t\toverlay_context.compass_rose(compass_center, angle, show_compass_with_ring);\n\n\t\t\t\t\tlet axis_state = if let SelectToolFsmState::Dragging { axis, .. } = self {\n\t\t\t\t\t\tSome((axis, false))\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcompass_rose_state.axis_type().and_then(|axis| axis.is_constraint().then_some((axis, true)))\n\t\t\t\t\t};\n\n\t\t\t\t\tif show_compass_with_ring.is_some()\n\t\t\t\t\t\t&& let Some((axis, hover)) = axis_state\n\t\t\t\t\t\t&& axis.is_constraint()\n\t\t\t\t\t{\n\t\t\t\t\t\tlet e0 = tool_data\n\t\t\t\t\t\t\t.bounding_box_manager\n\t\t\t\t\t\t\t.as_ref()\n\t\t\t\t\t\t\t.map(|bounding_box_manager| bounding_box_manager.transform * Quad::from_box(bounding_box_manager.bounds))\n\t\t\t\t\t\t\t.map_or(DVec2::X, |quad| (quad.top_left() - quad.top_right()).normalize_or(DVec2::X));\n\n\t\t\t\t\t\tlet (direction, color, color_faded) = match axis {\n\t\t\t\t\t\t\tAxis::X => (e0, COLOR_OVERLAY_RED, COLOR_OVERLAY_RED_25),\n\t\t\t\t\t\t\tAxis::Y => (e0.perp(), COLOR_OVERLAY_GREEN, COLOR_OVERLAY_GREEN_25),\n\t\t\t\t\t\t\t_ => unreachable!(),\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tlet viewport_diagonal = viewport.size().into_dvec2().length();\n\n\t\t\t\t\t\tlet color = if !hover { color } else { color_faded };\n\t\t\t\t\t\tlet line_center = tool_data.line_center;\n\t\t\t\t\t\toverlay_context.line(line_center - direction * viewport_diagonal, line_center + direction * viewport_diagonal, Some(color), None);\n\t\t\t\t\t}\n\n\t\t\t\t\tif axis_state.is_none_or(|(axis, _)| !axis.is_constraint()) && tool_data.axis_align {\n\t\t\t\t\t\tlet mouse_position = mouse_position - tool_data.drag_start;\n\t\t\t\t\t\tlet snap_resolution = SELECTION_DRAG_ANGLE.to_radians();\n\t\t\t\t\t\tlet angle = -mouse_position.angle_to(DVec2::X);\n\t\t\t\t\t\tlet snapped_angle = (angle / snap_resolution).round() * snap_resolution;\n\n\t\t\t\t\t\tlet origin = tool_data.drag_start_center;\n\t\t\t\t\t\tlet viewport_diagonal = viewport.size().into_dvec2().length();\n\n\t\t\t\t\t\tlet edge = DVec2::from_angle(snapped_angle).normalize_or(DVec2::X);\n\t\t\t\t\t\tlet perp = edge.perp();\n\n\t\t\t\t\t\tlet (edge_color, perp_color) = if edge.x.abs() > edge.y.abs() {\n\t\t\t\t\t\t\t(COLOR_OVERLAY_RED, COLOR_OVERLAY_GREEN_25)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t(COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED_25)\n\t\t\t\t\t\t};\n\t\t\t\t\t\toverlay_context.line(origin - edge * viewport_diagonal, origin + edge * viewport_diagonal, Some(edge_color), None);\n\t\t\t\t\t\toverlay_context.line(origin - perp * viewport_diagonal, origin + perp * viewport_diagonal, Some(perp_color), None);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Check if the tool is in selection mode\n\t\t\t\tif let Self::Drawing { selection_shape, .. } = self {\n\t\t\t\t\t// Get the updated selection box bounds\n\t\t\t\t\tlet quad = Quad::from_box([tool_data.drag_start, tool_data.drag_current]);\n\n\t\t\t\t\tlet current_selection_mode = match tool_action_data.preferences.get_selection_mode() {\n\t\t\t\t\t\tSelectionMode::Directional => tool_data.calculate_selection_mode_from_direction(),\n\t\t\t\t\t\tSelectionMode::Touched => SelectionMode::Touched,\n\t\t\t\t\t\tSelectionMode::Enclosed => SelectionMode::Enclosed,\n\t\t\t\t\t};\n\n\t\t\t\t\t// Draw outline visualizations on the layers to be selected\n\t\t\t\t\tlet intersected_layers = match selection_shape {\n\t\t\t\t\t\tSelectionShapeType::Box => document.intersect_quad_no_artboards(quad, viewport).collect(),\n\t\t\t\t\t\tSelectionShapeType::Lasso => tool_data.intersect_lasso_no_artboards(document, viewport),\n\t\t\t\t\t};\n\t\t\t\t\tlet layers_to_outline = intersected_layers.into_iter().filter(|layer| match current_selection_mode {\n\t\t\t\t\t\tSelectionMode::Enclosed => match selection_shape {\n\t\t\t\t\t\t\tSelectionShapeType::Box => document.is_layer_fully_inside(layer, quad),\n\t\t\t\t\t\t\tSelectionShapeType::Lasso => tool_data.is_layer_inside_lasso_polygon(layer, document, viewport),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tSelectionMode::Touched => match tool_data.nested_selection_behavior {\n\t\t\t\t\t\t\tNestedSelectionBehavior::Deepest => !layer.has_children(document.metadata()),\n\t\t\t\t\t\t\tNestedSelectionBehavior::Shallowest => true,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tSelectionMode::Directional => unreachable!(),\n\t\t\t\t\t});\n\n\t\t\t\t\tif overlay_context.visibility_settings.selection_outline() {\n\t\t\t\t\t\t// Draws a temporary outline on the layers that will be selected by the current box/lasso area\n\t\t\t\t\t\tfor layer in layers_to_outline {\n\t\t\t\t\t\t\tlet layer_to_viewport = document.metadata().transform_to_viewport(layer);\n\t\t\t\t\t\t\toverlay_context.outline(document.metadata().layer_with_free_points_outline(layer), layer_to_viewport, None);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Update the selection box\n\t\t\t\t\tlet fill_color = Some(COLOR_OVERLAY_BLUE_05);\n\n\t\t\t\t\tlet polygon = &tool_data.lasso_polygon;\n\n\t\t\t\t\tmatch (selection_shape, current_selection_mode) {\n\t\t\t\t\t\t(SelectionShapeType::Box, SelectionMode::Enclosed) => overlay_context.dashed_quad(quad, None, fill_color, Some(4.), Some(4.), Some(0.5)),\n\t\t\t\t\t\t(SelectionShapeType::Lasso, SelectionMode::Enclosed) => overlay_context.dashed_polygon(polygon, None, fill_color, Some(4.), Some(4.), Some(0.5)),\n\t\t\t\t\t\t(SelectionShapeType::Box, _) => overlay_context.quad(quad, None, fill_color),\n\t\t\t\t\t\t(SelectionShapeType::Lasso, _) => overlay_context.polygon(polygon, None, fill_color),\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif let Self::Dragging { .. } = self {\n\t\t\t\t\tlet quad = Quad::from_box([tool_data.drag_start, tool_data.drag_current]);\n\t\t\t\t\tlet document_start = document.metadata().document_to_viewport.inverse().transform_point2(quad.top_left());\n\t\t\t\t\tlet document_current = document.metadata().document_to_viewport.inverse().transform_point2(quad.bottom_right());\n\n\t\t\t\t\toverlay_context.translation_box(document_current - document_start, quad, None);\n\t\t\t\t}\n\n\t\t\t\tself\n\t\t\t}\n\t\t\t(_, SelectToolMessage::EditLayer) => {\n\t\t\t\tresponses.add(DeferMessage::AfterGraphRun {\n\t\t\t\t\tmessages: vec![SelectToolMessage::EditLayerExec.into()],\n\t\t\t\t});\n\n\t\t\t\tself\n\t\t\t}\n\t\t\t(_, SelectToolMessage::EditLayerExec) => {\n\t\t\t\tif let Some(intersect) = document.click(input, viewport) {\n\t\t\t\t\tmatch tool_data.nested_selection_behavior {\n\t\t\t\t\t\tNestedSelectionBehavior::Shallowest => edit_layer_shallowest_manipulation(document, intersect, responses),\n\t\t\t\t\t\tNestedSelectionBehavior::Deepest => edit_layer_deepest_manipulation(intersect, &document.network_interface, responses),\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tself\n\t\t\t}\n\t\t\t(\n\t\t\t\tSelectToolFsmState::Ready { .. },\n\t\t\t\tSelectToolMessage::DragStart {\n\t\t\t\t\textend_selection,\n\t\t\t\t\tremove_from_selection,\n\t\t\t\t\tselect_deepest,\n\t\t\t\t\tlasso_select,\n\t\t\t\t\t..\n\t\t\t\t},\n\t\t\t) => {\n\t\t\t\ttool_data.drag_start = input.mouse.position;\n\t\t\t\ttool_data.drag_current = input.mouse.position;\n\t\t\t\ttool_data.selection_mode = None;\n\n\t\t\t\tlet mut selected: Vec<_> = document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface).collect();\n\t\t\t\tlet intersection_list = document.click_list(input, viewport).collect::<Vec<_>>();\n\t\t\t\tlet intersection = document.find_deepest(&intersection_list);\n\n\t\t\t\tlet position = tool_data.pivot_gizmo().position(document);\n\t\t\t\tlet (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging, Some(position));\n\n\t\t\t\ttool_data.drag_start_center = position;\n\n\t\t\t\t// If the user is dragging the bounding box bounds, go into ResizingBounds mode.\n\t\t\t\t// If the user is dragging the rotate trigger, go into RotatingBounds mode.\n\t\t\t\t// If the user clicks on a layer that is in their current selection, go into the dragging mode.\n\t\t\t\t// If the user clicks on new shape, make that layer their new selection.\n\t\t\t\t// Otherwise enter the box select mode\n\t\t\t\tlet bounds = tool_data\n\t\t\t\t\t.bounding_box_manager\n\t\t\t\t\t.as_ref()\n\t\t\t\t\t.map(|bounding_box_manager| bounding_box_manager.transform * Quad::from_box(bounding_box_manager.bounds));\n\n\t\t\t\tlet angle = bounds.map_or(0., |quad| (quad.top_left() - quad.top_right()).to_angle());\n\t\t\t\tlet mouse_position = input.mouse.position;\n\t\t\t\tlet compass_rose_state = tool_data.compass_rose.compass_rose_state(mouse_position, angle);\n\n\t\t\t\tlet show_compass = bounds.is_some_and(|quad| quad.all_sides_at_least_width(COMPASS_ROSE_HOVER_RING_DIAMETER) && quad.contains(mouse_position));\n\t\t\t\tlet can_grab_compass_rose = compass_rose_state.can_grab() && (show_compass || bounds.is_none());\n\n\t\t\t\tlet state = if let Some(state) = tool_data.state_from_pivot_gizmo(input.mouse.position) {\n\t\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\n\t\t\t\t\t// tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true);\n\t\t\t\t\t// tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);\n\n\t\t\t\t\tstate\n\t\t\t\t}\n\t\t\t\t// Dragging one (or two, forming a corner) of the transform cage bounding box edges\n\t\t\t\telse if resize {\n\t\t\t\t\ttool_data.get_snap_candidates(document, input, viewport);\n\t\t\t\t\tSelectToolFsmState::ResizingBounds\n\t\t\t\t} else if skew {\n\t\t\t\t\ttool_data.get_snap_candidates(document, input, viewport);\n\t\t\t\t\tSelectToolFsmState::SkewingBounds { skew: Key::Control }\n\t\t\t\t}\n\t\t\t\t// Dragging the selected layers around to transform them\n\t\t\t\telse if can_grab_compass_rose || intersection.is_some_and(|intersection| selected.iter().any(|selected_layer| intersection.starts_with(*selected_layer, document.metadata()))) {\n\t\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\n\t\t\t\t\tif input.keyboard.key(select_deepest) || tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest {\n\t\t\t\t\t\ttool_data.select_single_layer = intersection;\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttool_data.select_single_layer = intersection.and_then(|intersection| intersection.ancestors(document.metadata()).find(|ancestor| selected.contains(ancestor)));\n\t\t\t\t\t}\n\n\t\t\t\t\ttool_data.layers_dragging = selected;\n\t\t\t\t\ttool_data.get_snap_candidates(document, input, viewport);\n\t\t\t\t\tlet (axis, using_compass) = {\n\t\t\t\t\t\tlet axis_state = compass_rose_state.axis_type().filter(|_| can_grab_compass_rose);\n\t\t\t\t\t\t(axis_state.unwrap_or_default(), axis_state.is_some())\n\t\t\t\t\t};\n\n\t\t\t\t\ttool_data.pivot_gizmo_start = Some(tool_data.drag_current);\n\n\t\t\t\t\tSelectToolFsmState::Dragging {\n\t\t\t\t\t\taxis,\n\t\t\t\t\t\tusing_compass,\n\t\t\t\t\t\thas_dragged: false,\n\t\t\t\t\t\tdeepest: input.keyboard.key(select_deepest),\n\t\t\t\t\t\tremove: input.keyboard.key(extend_selection),\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Dragging near the transform cage bounding box to rotate it\n\t\t\t\telse if rotate {\n\t\t\t\t\tSelectToolFsmState::RotatingBounds\n\t\t\t\t}\n\t\t\t\t// Dragging a selection box\n\t\t\t\telse {\n\t\t\t\t\ttool_data.layers_dragging = selected;\n\t\t\t\t\tlet extend = input.keyboard.key(extend_selection);\n\t\t\t\t\tif !extend && !input.keyboard.key(remove_from_selection) {\n\t\t\t\t\t\tresponses.add(DocumentMessage::DeselectAllLayers);\n\n\t\t\t\t\t\tif !tool_data.pivot_gizmo.pivot.pinned {\n\t\t\t\t\t\t\tlet position = tool_data.pivot_gizmo.pivot.last_non_none_reference_point;\n\t\t\t\t\t\t\tresponses.add(SelectToolMessage::SetPivot { position });\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\ttool_data.layers_dragging.clear();\n\t\t\t\t\t}\n\n\t\t\t\t\tif let Some(intersection) = intersection {\n\t\t\t\t\t\ttool_data.layer_selected_on_start = Some(intersection);\n\t\t\t\t\t\tselected = intersection_list;\n\n\t\t\t\t\t\tmatch tool_data.nested_selection_behavior {\n\t\t\t\t\t\t\tNestedSelectionBehavior::Shallowest if !input.keyboard.key(select_deepest) => drag_shallowest_manipulation(responses, selected, tool_data, document, false, extend),\n\t\t\t\t\t\t\t_ => drag_deepest_manipulation(responses, selected, tool_data, document, false),\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttool_data.get_snap_candidates(document, input, viewport);\n\n\t\t\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\n\t\t\t\t\t\ttool_data.pivot_gizmo_start = Some(tool_data.drag_current);\n\n\t\t\t\t\t\tSelectToolFsmState::Dragging {\n\t\t\t\t\t\t\taxis: Axis::None,\n\t\t\t\t\t\t\tusing_compass: false,\n\t\t\t\t\t\t\thas_dragged: false,\n\t\t\t\t\t\t\tdeepest: input.keyboard.key(select_deepest),\n\t\t\t\t\t\t\tremove: input.keyboard.key(extend_selection),\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlet selection_shape = if input.keyboard.key(lasso_select) { SelectionShapeType::Lasso } else { SelectionShapeType::Box };\n\t\t\t\t\t\tSelectToolFsmState::Drawing { selection_shape, has_drawn: false }\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\ttool_data.non_duplicated_layers = None;\n\n\t\t\t\tstate\n\t\t\t}\n\t\t\t(SelectToolFsmState::DraggingPivot, SelectToolMessage::Abort) => {\n\t\t\t\tresponses.add(DocumentMessage::AbortTransaction);\n\n\t\t\t\tlet selection = tool_data.nested_selection_behavior;\n\t\t\t\tSelectToolFsmState::Ready { selection }\n\t\t\t}\n\t\t\t(\n\t\t\t\tSelectToolFsmState::Dragging {\n\t\t\t\t\taxis,\n\t\t\t\t\tusing_compass,\n\t\t\t\t\thas_dragged,\n\t\t\t\t\tdeepest,\n\t\t\t\t\tremove,\n\t\t\t\t},\n\t\t\t\tSelectToolMessage::PointerMove { modifier_keys },\n\t\t\t) => {\n\t\t\t\tif !has_dragged {\n\t\t\t\t\tresponses.add(ToolMessage::UpdateHints);\n\t\t\t\t}\n\t\t\t\tif input.keyboard.key(modifier_keys.duplicate) && tool_data.non_duplicated_layers.is_none() {\n\t\t\t\t\ttool_data.start_duplicates(document, responses);\n\t\t\t\t} else if !input.keyboard.key(modifier_keys.duplicate) && tool_data.non_duplicated_layers.is_some() {\n\t\t\t\t\ttool_data.stop_duplicates(document, responses);\n\t\t\t\t}\n\n\t\t\t\ttool_data.axis_align = input.keyboard.key(modifier_keys.axis_align);\n\n\t\t\t\t// Ignore the non duplicated layers if the current layers have not spawned yet.\n\t\t\t\tlet layers_exist = tool_data.layers_dragging.iter().all(|&layer| document.metadata().click_targets(layer).is_some());\n\t\t\t\tlet ignore = tool_data.non_duplicated_layers.as_ref().filter(|_| !layers_exist).unwrap_or(&tool_data.layers_dragging);\n\n\t\t\t\tlet snap_data = SnapData::ignore(document, input, viewport, ignore);\n\t\t\t\tlet (start, current) = (tool_data.drag_start, tool_data.drag_current);\n\t\t\t\tlet e0 = tool_data\n\t\t\t\t\t.bounding_box_manager\n\t\t\t\t\t.as_ref()\n\t\t\t\t\t.map(|bounding_box_manager| bounding_box_manager.transform * Quad::from_box(bounding_box_manager.bounds))\n\t\t\t\t\t.map_or(DVec2::X, |quad| (quad.top_left() - quad.top_right()).normalize_or(DVec2::X));\n\n\t\t\t\tlet mouse_delta = snap_drag(start, current, tool_data.axis_align, axis, snap_data, &mut tool_data.snap_manager, &tool_data.snap_candidates);\n\t\t\t\tlet mouse_delta = match axis {\n\t\t\t\t\tAxis::X => mouse_delta.project_onto(e0),\n\t\t\t\t\tAxis::Y => mouse_delta.project_onto(e0.perp()),\n\t\t\t\t\tAxis::None => mouse_delta,\n\t\t\t\t};\n\n\t\t\t\t// TODO: Cache the result of `shallowest_unique_layers` to avoid this heavy computation every frame of movement, see https://github.com/GraphiteEditor/Graphite/pull/481\n\t\t\t\tfor layer in document.network_interface.shallowest_unique_layers(&[]) {\n\t\t\t\t\tresponses.add_front(GraphOperationMessage::TransformChange {\n\t\t\t\t\t\tlayer,\n\t\t\t\t\t\ttransform: DAffine2::from_translation(mouse_delta),\n\t\t\t\t\t\ttransform_in: TransformIn::Viewport,\n\t\t\t\t\t\tskip_rerender: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\ttool_data.drag_current += mouse_delta;\n\n\t\t\t\t// Auto-panning\n\t\t\t\tlet messages = [\n\t\t\t\t\tSelectToolMessage::PointerOutsideViewport { modifier_keys: modifier_keys.clone() }.into(),\n\t\t\t\t\tSelectToolMessage::PointerMove { modifier_keys }.into(),\n\t\t\t\t];\n\t\t\t\ttool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses);\n\n\t\t\t\tSelectToolFsmState::Dragging {\n\t\t\t\t\taxis,\n\t\t\t\t\tusing_compass,\n\t\t\t\t\thas_dragged: true,\n\t\t\t\t\tdeepest,\n\t\t\t\t\tremove,\n\t\t\t\t}\n\t\t\t}\n\t\t\t(SelectToolFsmState::ResizingBounds, SelectToolMessage::PointerMove { modifier_keys }) => {\n\t\t\t\tif let Some(bounds) = &mut tool_data.bounding_box_manager {\n\t\t\t\t\tresize_bounds(\n\t\t\t\t\t\tdocument,\n\t\t\t\t\t\tresponses,\n\t\t\t\t\t\tbounds,\n\t\t\t\t\t\t&mut tool_data.layers_dragging,\n\t\t\t\t\t\t&mut tool_data.snap_manager,\n\t\t\t\t\t\t&mut tool_data.snap_candidates,\n\t\t\t\t\t\tinput,\n\t\t\t\t\t\tviewport,\n\t\t\t\t\t\tinput.keyboard.key(modifier_keys.center),\n\t\t\t\t\t\tinput.keyboard.key(modifier_keys.axis_align),\n\t\t\t\t\t\tToolType::Select,\n\t\t\t\t\t);\n\t\t\t\t\tlet messages = [\n\t\t\t\t\t\tSelectToolMessage::PointerOutsideViewport { modifier_keys: modifier_keys.clone() }.into(),\n\t\t\t\t\t\tSelectToolMessage::PointerMove { modifier_keys }.into(),\n\t\t\t\t\t];\n\t\t\t\t\ttool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses);\n\t\t\t\t}\n\t\t\t\tSelectToolFsmState::ResizingBounds\n\t\t\t}\n\t\t\t(SelectToolFsmState::SkewingBounds { skew }, SelectToolMessage::PointerMove { .. }) => {\n\t\t\t\tif let Some(bounds) = &mut tool_data.bounding_box_manager {\n\t\t\t\t\tskew_bounds(\n\t\t\t\t\t\tdocument,\n\t\t\t\t\t\tresponses,\n\t\t\t\t\t\tbounds,\n\t\t\t\t\t\tinput.keyboard.key(skew),\n\t\t\t\t\t\t&mut tool_data.layers_dragging,\n\t\t\t\t\t\tinput.mouse.position,\n\t\t\t\t\t\tToolType::Select,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tSelectToolFsmState::SkewingBounds { skew }\n\t\t\t}\n\t\t\t(SelectToolFsmState::RotatingBounds, SelectToolMessage::PointerMove { .. }) => {\n\t\t\t\tif let Some(bounds) = &mut tool_data.bounding_box_manager {\n\t\t\t\t\trotate_bounds(\n\t\t\t\t\t\tdocument,\n\t\t\t\t\t\tresponses,\n\t\t\t\t\t\tbounds,\n\t\t\t\t\t\t&mut tool_data.layers_dragging,\n\t\t\t\t\t\ttool_data.drag_start,\n\t\t\t\t\t\tinput.mouse.position,\n\t\t\t\t\t\tinput.keyboard.key(Key::Shift),\n\t\t\t\t\t\tToolType::Select,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tSelectToolFsmState::RotatingBounds\n\t\t\t}\n\t\t\t(SelectToolFsmState::DraggingPivot, SelectToolMessage::PointerMove { modifier_keys }) => {\n\t\t\t\tlet mouse_position = input.mouse.position;\n\t\t\t\tlet snapped_mouse_position = mouse_position;\n\n\t\t\t\ttool_data.pivot_gizmo.pivot.set_viewport_position(snapped_mouse_position);\n\n\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\n\t\t\t\t// Auto-panning\n\t\t\t\tlet messages = [\n\t\t\t\t\tSelectToolMessage::PointerOutsideViewport { modifier_keys: modifier_keys.clone() }.into(),\n\t\t\t\t\tSelectToolMessage::PointerMove { modifier_keys }.into(),\n\t\t\t\t];\n\t\t\t\ttool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses);\n\n\t\t\t\tSelectToolFsmState::DraggingPivot\n\t\t\t}\n\t\t\t(SelectToolFsmState::Drawing { selection_shape, has_drawn }, SelectToolMessage::PointerMove { modifier_keys }) => {\n\t\t\t\tif !has_drawn {\n\t\t\t\t\tresponses.add(ToolMessage::UpdateHints);\n\t\t\t\t}\n\n\t\t\t\ttool_data.drag_current = input.mouse.position;\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\n\t\t\t\tif selection_shape == SelectionShapeType::Lasso {\n\t\t\t\t\textend_lasso(&mut tool_data.lasso_polygon, tool_data.drag_current);\n\t\t\t\t}\n\n\t\t\t\t// Auto-panning\n\t\t\t\tlet messages = [\n\t\t\t\t\tSelectToolMessage::PointerOutsideViewport { modifier_keys: modifier_keys.clone() }.into(),\n\t\t\t\t\tSelectToolMessage::PointerMove { modifier_keys }.into(),\n\t\t\t\t];\n\t\t\t\ttool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses);\n\n\t\t\t\tSelectToolFsmState::Drawing { selection_shape, has_drawn: true }\n\t\t\t}\n\t\t\t(SelectToolFsmState::Ready { .. }, SelectToolMessage::PointerMove { .. }) => {\n\t\t\t\tlet dragging_bounds = tool_data\n\t\t\t\t\t.bounding_box_manager\n\t\t\t\t\t.as_mut()\n\t\t\t\t\t.and_then(|bounding_box| bounding_box.check_selected_edges(input.mouse.position))\n\t\t\t\t\t.is_some();\n\n\t\t\t\tlet mut cursor = tool_data\n\t\t\t\t\t.bounding_box_manager\n\t\t\t\t\t.as_ref()\n\t\t\t\t\t.map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, true, dragging_bounds, Some(tool_data.skew_edge)));\n\n\t\t\t\t// Dragging the pivot overrules the other operations\n\t\t\t\tif tool_data.state_from_pivot_gizmo(input.mouse.position).is_some() {\n\t\t\t\t\tcursor = MouseCursorIcon::Move;\n\t\t\t\t}\n\n\t\t\t\t// Generate the hover outline\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\n\t\t\t\tif tool_data.cursor != cursor {\n\t\t\t\t\ttool_data.cursor = cursor;\n\t\t\t\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor });\n\t\t\t\t}\n\n\t\t\t\tlet selection = tool_data.nested_selection_behavior;\n\t\t\t\tSelectToolFsmState::Ready { selection }\n\t\t\t}\n\t\t\t(\n\t\t\t\tSelectToolFsmState::Dragging {\n\t\t\t\t\taxis,\n\t\t\t\t\tusing_compass,\n\t\t\t\t\thas_dragged,\n\t\t\t\t\tdeepest,\n\t\t\t\t\tremove,\n\t\t\t\t},\n\t\t\t\tSelectToolMessage::PointerOutsideViewport { .. },\n\t\t\t) => {\n\t\t\t\t// Auto-panning\n\t\t\t\tif let Some(shift) = tool_data.auto_panning.shift_viewport(input, viewport, responses) {\n\t\t\t\t\ttool_data.drag_current += shift;\n\t\t\t\t\ttool_data.drag_start += shift;\n\t\t\t\t}\n\n\t\t\t\tSelectToolFsmState::Dragging {\n\t\t\t\t\taxis,\n\t\t\t\t\tusing_compass,\n\t\t\t\t\thas_dragged,\n\t\t\t\t\tdeepest,\n\t\t\t\t\tremove,\n\t\t\t\t}\n\t\t\t}\n\t\t\t(SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds { .. }, SelectToolMessage::PointerOutsideViewport { .. }) => {\n\t\t\t\t// Auto-panning\n\t\t\t\tif let Some(shift) = tool_data.auto_panning.shift_viewport(input, viewport, responses)\n\t\t\t\t\t&& let Some(bounds) = &mut tool_data.bounding_box_manager\n\t\t\t\t{\n\t\t\t\t\tbounds.center_of_transformation += shift;\n\t\t\t\t\tbounds.original_bound_transform.translation += shift;\n\t\t\t\t}\n\n\t\t\t\tself\n\t\t\t}\n\t\t\t(SelectToolFsmState::DraggingPivot, SelectToolMessage::PointerOutsideViewport { .. }) => {\n\t\t\t\t// Auto-panning\n\t\t\t\tlet _ = tool_data.auto_panning.shift_viewport(input, viewport, responses);\n\n\t\t\t\tself\n\t\t\t}\n\t\t\t(SelectToolFsmState::Drawing { .. }, SelectToolMessage::PointerOutsideViewport { .. }) => {\n\t\t\t\t// Auto-panning\n\t\t\t\tif let Some(shift) = tool_data.auto_panning.shift_viewport(input, viewport, responses) {\n\t\t\t\t\ttool_data.drag_start += shift;\n\t\t\t\t}\n\n\t\t\t\tself\n\t\t\t}\n\t\t\t(state, SelectToolMessage::PointerOutsideViewport { modifier_keys }) => {\n\t\t\t\t// Auto-panning\n\t\t\t\tlet messages = [\n\t\t\t\t\tSelectToolMessage::PointerOutsideViewport { modifier_keys: modifier_keys.clone() }.into(),\n\t\t\t\t\tSelectToolMessage::PointerMove { modifier_keys }.into(),\n\t\t\t\t];\n\t\t\t\ttool_data.auto_panning.stop(&messages, responses);\n\n\t\t\t\tstate\n\t\t\t}\n\t\t\t(SelectToolFsmState::Dragging { has_dragged, remove, deepest, .. }, SelectToolMessage::DragStop { remove_from_selection }) => {\n\t\t\t\t// Deselect layer if not snap dragging\n\t\t\t\tresponses.add(DocumentMessage::EndTransaction);\n\t\t\t\ttool_data.axis_align = false;\n\n\t\t\t\tif !has_dragged && input.keyboard.key(remove_from_selection) && tool_data.layer_selected_on_start.is_none() {\n\t\t\t\t\t// When you click on the layer with remove from selection key (shift) pressed, we deselect all nodes that are children.\n\t\t\t\t\tlet quad = tool_data.selection_quad();\n\t\t\t\t\tlet intersection = document.intersect_quad_no_artboards(quad, viewport);\n\n\t\t\t\t\tif let Some(path) = intersection.last() {\n\t\t\t\t\t\tlet replacement_selected_layers: Vec<_> = document\n\t\t\t\t\t\t\t.network_interface\n\t\t\t\t\t\t\t.selected_nodes()\n\t\t\t\t\t\t\t.selected_layers(document.metadata())\n\t\t\t\t\t\t\t.filter(|&layer| !path.starts_with(layer, document.metadata()))\n\t\t\t\t\t\t\t.collect();\n\n\t\t\t\t\t\ttool_data.layers_dragging.clear();\n\t\t\t\t\t\ttool_data.layers_dragging.extend(replacement_selected_layers.iter());\n\n\t\t\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesSet {\n\t\t\t\t\t\t\tnodes: replacement_selected_layers\n\t\t\t\t\t\t\t\t.iter()\n\t\t\t\t\t\t\t\t.filter_map(|layer| {\n\t\t\t\t\t\t\t\t\tif *layer != LayerNodeIdentifier::ROOT_PARENT {\n\t\t\t\t\t\t\t\t\t\tSome(layer.to_node())\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tlog::error!(\"ROOT_PARENT cannot be part of replacement_selected_layers\");\n\t\t\t\t\t\t\t\t\t\tNone\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t.collect(),\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t} else if tool_data.select_single_layer.take().is_some() {\n\t\t\t\t\t// Previously, we may have had many layers selected. If the user clicks without dragging, we should just select the one layer that has been clicked.\n\t\t\t\t\tif !has_dragged {\n\t\t\t\t\t\tlet selected = document.click_list(input, viewport).collect::<Vec<_>>();\n\t\t\t\t\t\tlet intersection = document.find_deepest(&selected);\n\t\t\t\t\t\tif let Some(intersection) = intersection {\n\t\t\t\t\t\t\ttool_data.layer_selected_on_start = Some(intersection);\n\n\t\t\t\t\t\t\tmatch tool_data.nested_selection_behavior {\n\t\t\t\t\t\t\t\tNestedSelectionBehavior::Shallowest if remove && !deepest => drag_shallowest_manipulation(responses, selected, tool_data, document, true, true),\n\t\t\t\t\t\t\t\tNestedSelectionBehavior::Deepest if remove => drag_deepest_manipulation(responses, selected, tool_data, document, true),\n\t\t\t\t\t\t\t\tNestedSelectionBehavior::Shallowest if !deepest => drag_shallowest_manipulation(responses, selected, tool_data, document, false, true),\n\t\t\t\t\t\t\t\t_ => {\n\t\t\t\t\t\t\t\t\tresponses.add(DocumentMessage::DeselectAllLayers);\n\t\t\t\t\t\t\t\t\ttool_data.layers_dragging.clear();\n\t\t\t\t\t\t\t\t\tdrag_deepest_manipulation(responses, selected, tool_data, document, false)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\ttool_data.get_snap_candidates(document, input, viewport);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\ttool_data.layer_selected_on_start = None;\n\n\t\t\t\ttool_data.snap_manager.cleanup(responses);\n\t\t\t\ttool_data.select_single_layer = None;\n\n\t\t\t\tif let Some(start) = tool_data.pivot_gizmo_start {\n\t\t\t\t\tlet offset = if tool_data.pivot_gizmo.pivot_disconnected() {\n\t\t\t\t\t\ttool_data.drag_current - start\n\t\t\t\t\t} else {\n\t\t\t\t\t\tDefault::default()\n\t\t\t\t\t};\n\t\t\t\t\tif let Some(v) = tool_data.pivot_gizmo.pivot.pivot.as_mut() {\n\t\t\t\t\t\t*v += offset;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ttool_data.pivot_gizmo_start = None;\n\n\t\t\t\tlet pivot_gizmo = tool_data.pivot_gizmo();\n\t\t\t\tresponses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });\n\n\t\t\t\tlet selection = tool_data.nested_selection_behavior;\n\t\t\t\tSelectToolFsmState::Ready { selection }\n\t\t\t}\n\t\t\t(\n\t\t\t\tSelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds { .. } | SelectToolFsmState::RotatingBounds | SelectToolFsmState::DraggingPivot,\n\t\t\t\tSelectToolMessage::DragStop { .. } | SelectToolMessage::Enter,\n\t\t\t) => {\n\t\t\t\tlet drag_too_small = input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON;\n\t\t\t\tlet response = if drag_too_small { DocumentMessage::AbortTransaction } else { DocumentMessage::EndTransaction };\n\n\t\t\t\tlet pivot_gizmo = tool_data.pivot_gizmo();\n\t\t\t\tresponses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });\n\n\t\t\t\tresponses.add(response);\n\n\t\t\t\ttool_data.axis_align = false;\n\t\t\t\ttool_data.snap_manager.cleanup(responses);\n\n\t\t\t\tif !matches!(self, SelectToolFsmState::DraggingPivot)\n\t\t\t\t\t&& let Some(bounds) = &mut tool_data.bounding_box_manager\n\t\t\t\t{\n\t\t\t\t\tbounds.original_transforms.clear();\n\t\t\t\t}\n\n\t\t\t\tlet selection = tool_data.nested_selection_behavior;\n\t\t\t\tSelectToolFsmState::Ready { selection }\n\t\t\t}\n\t\t\t(SelectToolFsmState::Drawing { selection_shape, .. }, SelectToolMessage::DragStop { remove_from_selection }) => {\n\t\t\t\tlet quad = tool_data.selection_quad();\n\n\t\t\t\tlet selection_mode = match tool_action_data.preferences.get_selection_mode() {\n\t\t\t\t\tSelectionMode::Directional => tool_data.calculate_selection_mode_from_direction(),\n\t\t\t\t\tselection_mode => selection_mode,\n\t\t\t\t};\n\n\t\t\t\tlet intersection: Vec<LayerNodeIdentifier> = match selection_shape {\n\t\t\t\t\tSelectionShapeType::Box => document.intersect_quad_no_artboards(quad, viewport).collect(),\n\t\t\t\t\tSelectionShapeType::Lasso => tool_data.intersect_lasso_no_artboards(document, viewport),\n\t\t\t\t};\n\t\t\t\tlet new_selected: HashSet<_> = if selection_mode == SelectionMode::Enclosed {\n\t\t\t\t\tlet is_inside = |layer: &LayerNodeIdentifier| match selection_shape {\n\t\t\t\t\t\tSelectionShapeType::Box => document.is_layer_fully_inside(layer, quad),\n\t\t\t\t\t\tSelectionShapeType::Lasso => tool_data.is_layer_inside_lasso_polygon(layer, document, viewport),\n\t\t\t\t\t};\n\t\t\t\t\tintersection.into_iter().filter(is_inside).collect()\n\t\t\t\t} else {\n\t\t\t\t\tintersection.into_iter().collect()\n\t\t\t\t};\n\n\t\t\t\tlet current_selected: HashSet<_> = document.network_interface.selected_nodes().selected_layers(document.metadata()).collect();\n\t\t\t\tlet negative_selection = input.keyboard.key(remove_from_selection);\n\t\t\t\tlet selection_modified = new_selected != current_selected;\n\n\t\t\t\t// Negative selection when both Shift and Ctrl are pressed\n\t\t\t\tif negative_selection {\n\t\t\t\t\tlet updated_selection = current_selected\n\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t.filter(|layer| !new_selected.iter().any(|selected| layer.starts_with(*selected, document.metadata())))\n\t\t\t\t\t\t.collect();\n\t\t\t\t\ttool_data.layers_dragging = updated_selection;\n\t\t\t\t} else if selection_modified {\n\t\t\t\t\tmatch tool_data.nested_selection_behavior {\n\t\t\t\t\t\tNestedSelectionBehavior::Deepest => {\n\t\t\t\t\t\t\tlet filtered_selections = filter_nested_selection(document.metadata(), &new_selected);\n\t\t\t\t\t\t\ttool_data.layers_dragging.extend(filtered_selections);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tNestedSelectionBehavior::Shallowest => {\n\t\t\t\t\t\t\t// Find each new_selected's parent node\n\t\t\t\t\t\t\tlet parent_selected: HashSet<_> = new_selected\n\t\t\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t\t\t.map(|layer| layer.ancestors(document.metadata()).filter(not_artboard(document)).last().unwrap_or(layer))\n\t\t\t\t\t\t\t\t.collect();\n\t\t\t\t\t\t\ttool_data.layers_dragging.extend(parent_selected.iter().copied());\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif negative_selection || selection_modified {\n\t\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesSet {\n\t\t\t\t\t\tnodes: tool_data\n\t\t\t\t\t\t\t.layers_dragging\n\t\t\t\t\t\t\t.iter()\n\t\t\t\t\t\t\t.filter_map(|layer| {\n\t\t\t\t\t\t\t\tif *layer != LayerNodeIdentifier::ROOT_PARENT {\n\t\t\t\t\t\t\t\t\tSome(layer.to_node())\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tlog::error!(\"ROOT_PARENT cannot be part of tool_data.layers_dragging\");\n\t\t\t\t\t\t\t\t\tNone\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.collect(),\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\ttool_data.lasso_polygon.clear();\n\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\n\t\t\t\tlet selection = tool_data.nested_selection_behavior;\n\t\t\t\tSelectToolFsmState::Ready { selection }\n\t\t\t}\n\t\t\t(SelectToolFsmState::Ready { .. }, SelectToolMessage::Enter) => {\n\t\t\t\tlet selected_nodes = document.network_interface.selected_nodes();\n\t\t\t\tlet mut selected_layers = selected_nodes.selected_layers(document.metadata());\n\n\t\t\t\tif let Some(layer) = selected_layers.next() {\n\t\t\t\t\t// Check that only one layer is selected\n\t\t\t\t\tif selected_layers.next().is_none() && is_layer_fed_by_node_of_name(layer, &document.network_interface, &DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER)) {\n\t\t\t\t\t\tresponses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Text });\n\t\t\t\t\t\tresponses.add(TextToolMessage::EditSelected);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tlet selection = tool_data.nested_selection_behavior;\n\t\t\t\tSelectToolFsmState::Ready { selection }\n\t\t\t}\n\t\t\t(SelectToolFsmState::Dragging { .. }, SelectToolMessage::Abort) => {\n\t\t\t\tresponses.add(DocumentMessage::AbortTransaction);\n\t\t\t\ttool_data.snap_manager.cleanup(responses);\n\t\t\t\ttool_data.axis_align = false;\n\t\t\t\ttool_data.lasso_polygon.clear();\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\n\t\t\t\tlet selection = tool_data.nested_selection_behavior;\n\t\t\t\tSelectToolFsmState::Ready { selection }\n\t\t\t}\n\t\t\t(_, SelectToolMessage::Abort) => {\n\t\t\t\ttool_data.layers_dragging.retain(|layer| {\n\t\t\t\t\tif *layer != LayerNodeIdentifier::ROOT_PARENT {\n\t\t\t\t\t\tdocument.network_interface.document_network().nodes.contains_key(&layer.to_node())\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfalse\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tif let Some(bounds) = &mut tool_data.bounding_box_manager {\n\t\t\t\t\tbounds.original_transforms.clear();\n\t\t\t\t}\n\n\t\t\t\tresponses.add(DocumentMessage::AbortTransaction);\n\t\t\t\ttool_data.snap_manager.cleanup(responses);\n\t\t\t\ttool_data.lasso_polygon.clear();\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\n\t\t\t\tlet selection = tool_data.nested_selection_behavior;\n\t\t\t\tSelectToolFsmState::Ready { selection }\n\t\t\t}\n\t\t\t(_, SelectToolMessage::SetPivot { position }) => {\n\t\t\t\ttool_data.pivot_gizmo.pivot.last_non_none_reference_point = position;\n\t\t\t\ttool_data.pivot_gizmo.pivot.pinned = false;\n\n\t\t\t\tlet pos: Option<DVec2> = position.into();\n\n\t\t\t\ttool_data.pivot_gizmo.pivot.set_normalized_position(pos.unwrap());\n\n\t\t\t\tlet pivot_gizmo = tool_data.pivot_gizmo();\n\t\t\t\tresponses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });\n\n\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\n\t\t\t\tself\n\t\t\t}\n\t\t\t(_, SelectToolMessage::SyncHistory) => {\n\t\t\t\ttool_data.sync_history(document);\n\n\t\t\t\tself\n\t\t\t}\n\t\t\t(_, SelectToolMessage::ShiftSelectedNodes { offset }) => {\n\t\t\t\tlet offset = document.metadata().document_to_viewport.transform_vector2(offset);\n\t\t\t\tif tool_data.pivot_gizmo.pivot_disconnected() {\n\t\t\t\t\tif let Some(v) = tool_data.pivot_gizmo.pivot.pivot.as_mut() {\n\t\t\t\t\t\t*v += offset;\n\t\t\t\t\t}\n\n\t\t\t\t\tlet pivot_gizmo = tool_data.pivot_gizmo();\n\t\t\t\t\tresponses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });\n\t\t\t\t}\n\n\t\t\t\tself\n\t\t\t}\n\t\t\t(_, SelectToolMessage::PivotShift { offset, flush }) => {\n\t\t\t\tif flush {\n\t\t\t\t\tif let Some(v) = tool_data.pivot_gizmo.pivot.pivot.as_mut() {\n\t\t\t\t\t\t*v += tool_data.pivot_gizmo_shift.take().unwrap_or_default();\n\t\t\t\t\t}\n\t\t\t\t\tlet pivot_gizmo = tool_data.pivot_gizmo();\n\t\t\t\t\tresponses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });\n\t\t\t\t\treturn self;\n\t\t\t\t}\n\t\t\t\tif tool_data.pivot_gizmo.pivot_disconnected() {\n\t\t\t\t\ttool_data.pivot_gizmo_shift = offset;\n\t\t\t\t}\n\n\t\t\t\tself\n\t\t\t}\n\t\t\t_ => self,\n\t\t}\n\t}\n\n\tfn standard_tool_messages(&self, message: &ToolMessage, responses: &mut VecDeque<Message>) -> bool {\n\t\t// Check for standard hits or cursor events\n\t\tmatch message {\n\t\t\tToolMessage::UpdateHints => {\n\t\t\t\tself.update_hints(responses);\n\t\t\t\ttrue\n\t\t\t}\n\t\t\tToolMessage::UpdateCursor => {\n\t\t\t\tself.update_cursor(responses);\n\t\t\t\ttrue\n\t\t\t}\n\t\t\t_ => false,\n\t\t}\n\t}\n\n\tfn update_hints(&self, responses: &mut VecDeque<Message>) {\n\t\tmatch self {\n\t\t\tSelectToolFsmState::Ready { selection } => {\n\t\t\t\tlet hint_data = HintData(vec![\n\t\t\t\t\tHintGroup({\n\t\t\t\t\t\tlet mut hints = vec![HintInfo::mouse(MouseMotion::Lmb, \"Select Object\"), HintInfo::keys([Key::Shift], \"Extend\").prepend_plus()];\n\t\t\t\t\t\tif *selection == NestedSelectionBehavior::Shallowest {\n\t\t\t\t\t\t\thints.extend([HintInfo::keys([Key::Accel], \"Deepest\").prepend_plus(), HintInfo::mouse(MouseMotion::LmbDouble, \"Deepen\")]);\n\t\t\t\t\t\t}\n\t\t\t\t\t\thints\n\t\t\t\t\t}),\n\t\t\t\t\tHintGroup(vec![\n\t\t\t\t\t\tHintInfo::mouse(MouseMotion::LmbDrag, \"Select Area\"),\n\t\t\t\t\t\tHintInfo::keys([Key::Shift], \"Extend\").prepend_plus(),\n\t\t\t\t\t\tHintInfo::keys([Key::Alt], \"Subtract\").prepend_plus(),\n\t\t\t\t\t\tHintInfo::keys([Key::Control], \"Lasso\").prepend_plus(),\n\t\t\t\t\t]),\n\t\t\t\t\t// TODO: Make all the following hints only appear if there is at least one selected layer\n\t\t\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, \"Drag Selected\")]),\n\t\t\t\t\tHintGroup(vec![HintInfo::multi_keys([[Key::KeyG], [Key::KeyR], [Key::KeyS]], \"Grab/Rotate/Scale Selected\")]),\n\t\t\t\t\tHintGroup(vec![\n\t\t\t\t\t\tHintInfo::arrow_keys(\"Nudge Selected\"),\n\t\t\t\t\t\tHintInfo::keys([Key::Shift], \"10x\").prepend_plus(),\n\t\t\t\t\t\tHintInfo::keys([Key::Alt], \"Resize Corner\").prepend_plus(),\n\t\t\t\t\t\tHintInfo::keys([Key::Control], \"Other Corner\").prepend_plus(),\n\t\t\t\t\t]),\n\t\t\t\t\tHintGroup(vec![\n\t\t\t\t\t\tHintInfo::keys_and_mouse([Key::Alt], MouseMotion::LmbDrag, \"Move Duplicate\"),\n\t\t\t\t\t\tHintInfo::keys([Key::Control, Key::KeyD], \"Duplicate\").add_mac_keys([Key::Command, Key::KeyD]),\n\t\t\t\t\t]),\n\t\t\t\t]);\n\t\t\t\thint_data.send_layout(responses);\n\t\t\t}\n\t\t\tSelectToolFsmState::Dragging { axis, using_compass, has_dragged, .. } if *has_dragged => {\n\t\t\t\tlet mut hint_data = vec![\n\t\t\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"\"), HintInfo::keys([Key::Escape], \"Cancel\").prepend_slash()]),\n\t\t\t\t\tHintGroup(vec![\n\t\t\t\t\t\tHintInfo::keys([Key::Alt], \"Move Duplicate\"),\n\t\t\t\t\t\tHintInfo::keys([Key::Control, Key::KeyD], \"Place Duplicate\").add_mac_keys([Key::Command, Key::KeyD]),\n\t\t\t\t\t]),\n\t\t\t\t];\n\n\t\t\t\tif !(*using_compass && axis.is_constraint()) {\n\t\t\t\t\thint_data.push(HintGroup(vec![HintInfo::keys([Key::Shift], \"Constrain to Axis\")]));\n\t\t\t\t};\n\t\t\t\tlet hint_data = HintData(hint_data);\n\t\t\t\thint_data.send_layout(responses);\n\t\t\t}\n\t\t\tSelectToolFsmState::Drawing { has_drawn, .. } if *has_drawn => {\n\t\t\t\tlet hint_data = HintData(vec![\n\t\t\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"\"), HintInfo::keys([Key::Escape], \"Cancel\").prepend_slash()]),\n\t\t\t\t\tHintGroup(vec![HintInfo::keys([Key::Shift], \"Extend\"), HintInfo::keys([Key::Alt], \"Subtract\")]),\n\t\t\t\t\t// TODO: Re-select deselected layers during drag when Shift is pressed, and re-deselect if Shift is released before drag ends.\n\t\t\t\t\t// TODO: (See https://discord.com/channels/731730685944922173/1216976541947531264/1321360311298818048)\n\t\t\t\t\t// HintGroup(vec![HintInfo::keys([Key::Shift], \"Extend\")])\n\t\t\t\t]);\n\t\t\t\thint_data.send_layout(responses);\n\t\t\t}\n\t\t\tSelectToolFsmState::Drawing { .. } | SelectToolFsmState::Dragging { .. } => {}\n\t\t\tSelectToolFsmState::ResizingBounds => {\n\t\t\t\tlet hint_data = HintData(vec![\n\t\t\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"\"), HintInfo::keys([Key::Escape], \"Cancel\").prepend_slash()]),\n\t\t\t\t\tHintGroup(vec![HintInfo::keys([Key::Alt], \"From Pivot\"), HintInfo::keys([Key::Shift], \"Preserve Aspect Ratio\")]),\n\t\t\t\t]);\n\t\t\t\thint_data.send_layout(responses);\n\t\t\t}\n\t\t\tSelectToolFsmState::RotatingBounds => {\n\t\t\t\tlet hint_data = HintData(vec![\n\t\t\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"\"), HintInfo::keys([Key::Escape], \"Cancel\").prepend_slash()]),\n\t\t\t\t\tHintGroup(vec![HintInfo::keys([Key::Shift], \"15° Increments\")]),\n\t\t\t\t]);\n\t\t\t\thint_data.send_layout(responses);\n\t\t\t}\n\t\t\tSelectToolFsmState::SkewingBounds { .. } => {\n\t\t\t\tlet hint_data = HintData(vec![\n\t\t\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"\"), HintInfo::keys([Key::Escape], \"Cancel\").prepend_slash()]),\n\t\t\t\t\tHintGroup(vec![HintInfo::keys([Key::Control], \"Unlock Slide\")]),\n\t\t\t\t]);\n\t\t\t\thint_data.send_layout(responses);\n\t\t\t}\n\t\t\tSelectToolFsmState::DraggingPivot => {\n\t\t\t\tlet hint_data = HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"\"), HintInfo::keys([Key::Escape], \"Cancel\").prepend_slash()])]);\n\t\t\t\thint_data.send_layout(responses);\n\t\t\t}\n\t\t}\n\t}\n\n\tfn update_cursor(&self, responses: &mut VecDeque<Message>) {\n\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });\n\t}\n}\n\nfn not_artboard(document: &DocumentMessageHandler) -> impl Fn(&LayerNodeIdentifier) -> bool + '_ {\n\t|&layer| layer != LayerNodeIdentifier::ROOT_PARENT && !document.network_interface.is_artboard(&layer.to_node(), &[])\n}\n\nfn drag_shallowest_manipulation(responses: &mut VecDeque<Message>, selected: Vec<LayerNodeIdentifier>, tool_data: &mut SelectToolData, document: &DocumentMessageHandler, remove: bool, exists: bool) {\n\tif selected.is_empty() {\n\t\treturn;\n\t}\n\n\tlet clicked_layer = document.find_deepest(&selected).unwrap_or_else(|| {\n\t\tLayerNodeIdentifier::ROOT_PARENT\n\t\t\t.children(document.metadata())\n\t\t\t.next()\n\t\t\t.expect(\"ROOT_PARENT should have at least one layer when clicking\")\n\t});\n\n\tlet metadata = document.metadata();\n\n\tlet selected_layers = document.network_interface.selected_nodes().selected_layers(document.metadata()).collect::<Vec<_>>();\n\tlet final_selection: Option<LayerNodeIdentifier> = (!selected_layers.is_empty() && selected_layers != vec![LayerNodeIdentifier::ROOT_PARENT]).then_some(()).and_then(|_| {\n\t\tlet mut relevant_layers = document.network_interface.selected_nodes().selected_layers(document.metadata()).collect::<Vec<_>>();\n\t\tif !relevant_layers.contains(&clicked_layer) {\n\t\t\trelevant_layers.push(clicked_layer);\n\t\t}\n\t\tclicked_layer\n\t\t\t.ancestors(metadata)\n\t\t\t.filter(not_artboard(document))\n\t\t\t.find(|&ancestor| relevant_layers.iter().all(|layer| *layer == ancestor || ancestor.is_ancestor_of(metadata, layer)))\n\t\t\t.and_then(|least_common_ancestor| {\n\t\t\t\tlet common_siblings: Vec<_> = least_common_ancestor.children(metadata).collect();\n\t\t\t\t(clicked_layer == least_common_ancestor)\n\t\t\t\t\t.then_some(least_common_ancestor)\n\t\t\t\t\t.or_else(|| common_siblings.iter().find(|&&child| clicked_layer == child || child.is_ancestor_of(metadata, &clicked_layer)).copied())\n\t\t\t})\n\t});\n\n\tif final_selection.is_some_and(|layer| selected_layers.iter().any(|selected| layer.is_child_of(metadata, selected))) {\n\t\tif exists && remove && selected_layers.len() == 1 {\n\t\t\tresponses.add(DocumentMessage::DeselectAllLayers);\n\t\t\ttool_data.layers_dragging.clear();\n\t\t}\n\t\treturn;\n\t}\n\n\tif !exists && !remove {\n\t\tresponses.add(DocumentMessage::DeselectAllLayers);\n\t\ttool_data.layers_dragging.clear();\n\t}\n\n\tlet new_selected = final_selection.unwrap_or_else(|| clicked_layer.ancestors(document.metadata()).filter(not_artboard(document)).last().unwrap_or(clicked_layer));\n\ttool_data.layers_dragging.extend(vec![new_selected]);\n\ttool_data.layers_dragging.retain(|&selected_layer| !selected_layer.is_child_of(metadata, &new_selected));\n\tif remove {\n\t\ttool_data.layers_dragging.retain(|&selected_layer| clicked_layer != selected_layer);\n\t\tif selected_layers.contains(&new_selected) {\n\t\t\ttool_data.layers_dragging.retain(|&selected_layer| new_selected != selected_layer);\n\t\t}\n\t}\n\n\tresponses.add(NodeGraphMessage::SelectedNodesSet {\n\t\tnodes: tool_data\n\t\t\t.layers_dragging\n\t\t\t.iter()\n\t\t\t.filter_map(|layer| {\n\t\t\t\tif *layer != LayerNodeIdentifier::ROOT_PARENT {\n\t\t\t\t\tSome(layer.to_node())\n\t\t\t\t} else {\n\t\t\t\t\tlog::error!(\"ROOT_PARENT cannot be part of tool_data.layers_dragging\");\n\t\t\t\t\tNone\n\t\t\t\t}\n\t\t\t})\n\t\t\t.collect(),\n\t});\n}\n\nfn layer_selected_shallowest(clicked_layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Option<LayerNodeIdentifier> {\n\tlet metadata = document.metadata();\n\tlet selected_layers = document.network_interface.selected_nodes().selected_layers(document.metadata()).collect::<Vec<_>>();\n\tlet final_selection: Option<LayerNodeIdentifier> = (!selected_layers.is_empty() && selected_layers != vec![LayerNodeIdentifier::ROOT_PARENT]).then_some(()).and_then(|_| {\n\t\tlet mut relevant_layers = document.network_interface.selected_nodes().selected_layers(document.metadata()).collect::<Vec<_>>();\n\t\tif !relevant_layers.contains(&clicked_layer) {\n\t\t\trelevant_layers.push(clicked_layer);\n\t\t}\n\t\tclicked_layer\n\t\t\t.ancestors(metadata)\n\t\t\t.filter(not_artboard(document))\n\t\t\t.find(|&ancestor| relevant_layers.iter().all(|layer| *layer == ancestor || ancestor.is_ancestor_of(metadata, layer)))\n\t\t\t.and_then(|least_common_ancestor| {\n\t\t\t\tlet common_siblings: Vec<_> = least_common_ancestor.children(metadata).collect();\n\t\t\t\t(clicked_layer == least_common_ancestor)\n\t\t\t\t\t.then_some(least_common_ancestor)\n\t\t\t\t\t.or_else(|| common_siblings.iter().find(|&&child| clicked_layer == child || child.is_ancestor_of(metadata, &clicked_layer)).copied())\n\t\t\t})\n\t});\n\n\tif final_selection.is_some_and(|layer| selected_layers.iter().any(|selected| layer.is_child_of(metadata, selected))) {\n\t\treturn None;\n\t}\n\n\tlet new_selected = final_selection.unwrap_or_else(|| clicked_layer.ancestors(document.metadata()).filter(not_artboard(document)).last().unwrap_or(clicked_layer));\n\tSome(new_selected)\n}\n\nfn drag_deepest_manipulation(responses: &mut VecDeque<Message>, selected: Vec<LayerNodeIdentifier>, tool_data: &mut SelectToolData, document: &DocumentMessageHandler, remove: bool) {\n\tlet layer = document.find_deepest(&selected).unwrap_or(\n\t\tLayerNodeIdentifier::ROOT_PARENT\n\t\t\t.children(document.metadata())\n\t\t\t.next()\n\t\t\t.expect(\"ROOT_PARENT should have a layer child when clicking\"),\n\t);\n\n\tif !remove {\n\t\ttool_data.layers_dragging.extend(vec![layer]);\n\t} else {\n\t\ttool_data.layers_dragging.retain(|&selected_layer| layer != selected_layer);\n\t}\n\tresponses.add(NodeGraphMessage::SelectedNodesSet {\n\t\tnodes: tool_data\n\t\t\t.layers_dragging\n\t\t\t.iter()\n\t\t\t.filter_map(|layer| {\n\t\t\t\tif *layer != LayerNodeIdentifier::ROOT_PARENT {\n\t\t\t\t\tSome(layer.to_node())\n\t\t\t\t} else {\n\t\t\t\t\tlog::error!(\"ROOT_PARENT cannot be part of tool_data.layers_dragging\");\n\t\t\t\t\tNone\n\t\t\t\t}\n\t\t\t})\n\t\t\t.collect(),\n\t});\n}\n\n/// Called when you double click on the layer of the shallowest layer.\n/// If possible, the direct sibling of an old selected layer is the new selected layer.\n/// Otherwise, the first non-parent ancestor is selected.\nfn edit_layer_shallowest_manipulation(document: &DocumentMessageHandler, layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>) {\n\tlet Some(new_selected) = layer.ancestors(document.metadata()).filter(not_artboard(document)).find(|ancestor| {\n\t\tancestor\n\t\t\t.parent(document.metadata())\n\t\t\t.is_some_and(|parent| document.network_interface.selected_nodes().selected_layers_contains(parent, document.metadata()))\n\t}) else {\n\t\treturn;\n\t};\n\n\tif new_selected == LayerNodeIdentifier::ROOT_PARENT {\n\t\tlog::error!(\"new_selected cannot be ROOT_PARENT\");\n\t\treturn;\n\t}\n\n\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![new_selected.to_node()] });\n}\n\n/// Called when a double click on a layer in deep select mode.\n/// If the layer is text, the text tool is selected.\nfn edit_layer_deepest_manipulation(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface, responses: &mut VecDeque<Message>) {\n\tif is_layer_fed_by_node_of_name(layer, network_interface, &DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER)) {\n\t\tresponses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Text });\n\t\tresponses.add(TextToolMessage::EditSelected);\n\t}\n}\n\npub fn extend_lasso(lasso_polygon: &mut Vec<DVec2>, point: DVec2) {\n\tif lasso_polygon.len() < 2 {\n\t\tlasso_polygon.push(point);\n\t} else {\n\t\tlet last_points = lasso_polygon.last_chunk::<2>().unwrap();\n\t\tlet distance = last_points[0].distance_squared(last_points[1]);\n\n\t\tif distance < SELECTION_TOLERANCE.powi(2) {\n\t\t\tlasso_polygon.pop();\n\t\t}\n\t\tlasso_polygon.push(point);\n\t}\n}\n\npub fn filter_nested_selection(metadata: &DocumentMetadata, new_selected: &HashSet<LayerNodeIdentifier>) -> HashSet<LayerNodeIdentifier> {\n\t// First collect childless layers\n\tlet mut filtered_selection: HashSet<_> = new_selected.iter().copied().filter(|layer| !layer.has_children(metadata)).collect();\n\n\t// Then process parents with all children selected\n\tfor &layer in new_selected {\n\t\t// Skip if the layer is not a parent\n\t\tif !layer.has_children(metadata) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// If any ancestor is already present in the filtered selection, don't include its child\n\t\tif layer.ancestors(metadata).any(|ancestor| filtered_selection.contains(&ancestor)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Skip if any of the children are not selected\n\t\tif !layer.descendants(metadata).all(|descendant| new_selected.contains(&descendant)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Remove all descendants of the parent\n\t\tfor child in layer.descendants(metadata) {\n\t\t\tfiltered_selection.remove(&child);\n\t\t}\n\t\t// Add the parent\n\t\tfiltered_selection.insert(layer);\n\t}\n\n\tfiltered_selection\n}\n"
  },
  {
    "path": "editor/src/messages/tool/tool_messages/shape_tool.rs",
    "content": "use super::tool_prelude::*;\nuse crate::consts::{BOUNDS_SELECT_THRESHOLD, DEFAULT_STROKE_WIDTH, SNAP_POINT_TOLERANCE};\nuse crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;\nuse crate::messages::portfolio::document::overlays::utility_types::OverlayContext;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::tool::common_functionality::auto_panning::AutoPanning;\nuse crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};\nuse crate::messages::tool::common_functionality::gizmos::gizmo_manager::GizmoManager;\nuse crate::messages::tool::common_functionality::graph_modification_utils;\nuse crate::messages::tool::common_functionality::resize::Resize;\nuse crate::messages::tool::common_functionality::shapes::arc_shape::Arc;\nuse crate::messages::tool::common_functionality::shapes::arrow_shape::Arrow;\nuse crate::messages::tool::common_functionality::shapes::circle_shape::Circle;\nuse crate::messages::tool::common_functionality::shapes::grid_shape::Grid;\nuse crate::messages::tool::common_functionality::shapes::line_shape::LineToolData;\nuse crate::messages::tool::common_functionality::shapes::polygon_shape::Polygon;\nuse crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeToolModifierKey, ShapeType, anchor_overlays, clicked_on_shape_endpoints, transform_cage_overlays};\nuse crate::messages::tool::common_functionality::shapes::spiral_shape::Spiral;\nuse crate::messages::tool::common_functionality::shapes::star_shape::Star;\nuse crate::messages::tool::common_functionality::shapes::{Ellipse, Line, Rectangle};\nuse crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapTypeConfiguration};\nuse crate::messages::tool::common_functionality::transformation_cage::{BoundingBoxManager, EdgeBool};\nuse crate::messages::tool::common_functionality::utility_functions::{closest_point, resize_bounds, rotate_bounds, skew_bounds, transforming_transform_cage};\nuse graph_craft::document::NodeId;\nuse graphene_std::Color;\nuse graphene_std::renderer::Quad;\nuse graphene_std::vector::misc::{ArcType, GridType, SpiralType};\nuse std::vec;\n\n#[derive(Default, ExtractField)]\npub struct ShapeTool {\n\tfsm_state: ShapeToolFsmState,\n\ttool_data: ShapeToolData,\n\toptions: ShapeToolOptions,\n}\n\npub struct ShapeToolOptions {\n\tline_weight: f64,\n\tfill: ToolColorOptions,\n\tstroke: ToolColorOptions,\n\tvertices: u32,\n\tshape_type: ShapeType,\n\tarc_type: ArcType,\n\tgrid_type: GridType,\n\tspiral_type: SpiralType,\n\tturns: f64,\n\tarrow_shaft_width: f64,\n\tarrow_head_width: f64,\n\tarrow_head_length: f64,\n}\n\nimpl Default for ShapeToolOptions {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tline_weight: DEFAULT_STROKE_WIDTH,\n\t\t\tfill: ToolColorOptions::new_secondary(),\n\t\t\tstroke: ToolColorOptions::new_primary(),\n\t\t\tvertices: 5,\n\t\t\tshape_type: ShapeType::Polygon,\n\t\t\tarc_type: ArcType::Open,\n\t\t\tspiral_type: SpiralType::Archimedean,\n\t\t\tturns: 5.,\n\t\t\tgrid_type: GridType::Rectangular,\n\t\t\tarrow_shaft_width: 14.,\n\t\t\tarrow_head_width: 32.,\n\t\t\tarrow_head_length: 28.,\n\t\t}\n\t}\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum ShapeOptionsUpdate {\n\tFillColor(Option<Color>),\n\tFillColorType(ToolColorType),\n\tLineWeight(f64),\n\tStrokeColor(Option<Color>),\n\tStrokeColorType(ToolColorType),\n\tWorkingColors(Option<Color>, Option<Color>),\n\tVertices(u32),\n\tShapeType(ShapeType),\n\tArcType(ArcType),\n\tSpiralType(SpiralType),\n\tTurns(f64),\n\tGridType(GridType),\n\tArrowShaftWidth(f64),\n\tArrowHeadWidth(f64),\n\tArrowHeadLength(f64),\n}\n\n#[impl_message(Message, ToolMessage, Shape)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum ShapeToolMessage {\n\t// Standard messages\n\tOverlays { context: OverlayContext },\n\tAbort,\n\tWorkingColorChanged,\n\n\t// Tool-specific messages\n\tDragStart,\n\tDragStop,\n\tHideShapeTypeWidget { hide: bool },\n\tPointerMove { modifier: ShapeToolModifierKey },\n\tPointerOutsideViewport { modifier: ShapeToolModifierKey },\n\tUpdateOptions { options: ShapeOptionsUpdate },\n\tSetShape { shape: ShapeType },\n\tSyncShapeWithOptions,\n\n\tIncreaseSides,\n\tDecreaseSides,\n\n\tNudgeSelectedLayers { delta_x: f64, delta_y: f64, resize: Key, resize_opposite_corner: Key },\n}\n\nfn create_sides_widget(vertices: u32) -> WidgetInstance {\n\tNumberInput::new(Some(vertices as f64))\n\t\t.label(\"Sides\")\n\t\t.int()\n\t\t.min(3.)\n\t\t.max(1000.)\n\t\t.mode(NumberInputMode::Increment)\n\t\t.on_update(|number_input: &NumberInput| {\n\t\t\tShapeToolMessage::UpdateOptions {\n\t\t\t\toptions: ShapeOptionsUpdate::Vertices(number_input.value.unwrap() as u32),\n\t\t\t}\n\t\t\t.into()\n\t\t})\n\t\t.widget_instance()\n}\n\nfn create_turns_widget(turns: f64) -> WidgetInstance {\n\tNumberInput::new(Some(turns))\n\t\t.label(\"Turns\")\n\t\t.min(0.5)\n\t\t.mode(NumberInputMode::Increment)\n\t\t.on_update(|number_input: &NumberInput| {\n\t\t\tShapeToolMessage::UpdateOptions {\n\t\t\t\toptions: ShapeOptionsUpdate::Turns(number_input.value.unwrap()),\n\t\t\t}\n\t\t\t.into()\n\t\t})\n\t\t.widget_instance()\n}\n\nfn create_shape_option_widget(shape_type: ShapeType) -> WidgetInstance {\n\tlet entries = vec![vec![\n\t\tMenuListEntry::new(\"Polygon\").label(\"Polygon\").on_commit(move |_| {\n\t\t\tShapeToolMessage::UpdateOptions {\n\t\t\t\toptions: ShapeOptionsUpdate::ShapeType(ShapeType::Polygon),\n\t\t\t}\n\t\t\t.into()\n\t\t}),\n\t\tMenuListEntry::new(\"Star\").label(\"Star\").on_commit(move |_| {\n\t\t\tShapeToolMessage::UpdateOptions {\n\t\t\t\toptions: ShapeOptionsUpdate::ShapeType(ShapeType::Star),\n\t\t\t}\n\t\t\t.into()\n\t\t}),\n\t\tMenuListEntry::new(\"Circle\").label(\"Circle\").on_commit(move |_| {\n\t\t\tShapeToolMessage::UpdateOptions {\n\t\t\t\toptions: ShapeOptionsUpdate::ShapeType(ShapeType::Circle),\n\t\t\t}\n\t\t\t.into()\n\t\t}),\n\t\tMenuListEntry::new(\"Arc\").label(\"Arc\").on_commit(move |_| {\n\t\t\tShapeToolMessage::UpdateOptions {\n\t\t\t\toptions: ShapeOptionsUpdate::ShapeType(ShapeType::Arc),\n\t\t\t}\n\t\t\t.into()\n\t\t}),\n\t\tMenuListEntry::new(\"Spiral\").label(\"Spiral\").on_commit(move |_| {\n\t\t\tShapeToolMessage::UpdateOptions {\n\t\t\t\toptions: ShapeOptionsUpdate::ShapeType(ShapeType::Spiral),\n\t\t\t}\n\t\t\t.into()\n\t\t}),\n\t\tMenuListEntry::new(\"Grid\").label(\"Grid\").on_commit(move |_| {\n\t\t\tShapeToolMessage::UpdateOptions {\n\t\t\t\toptions: ShapeOptionsUpdate::ShapeType(ShapeType::Grid),\n\t\t\t}\n\t\t\t.into()\n\t\t}),\n\t\tMenuListEntry::new(\"Arrow\").label(\"Arrow\").on_commit(move |_| {\n\t\t\tShapeToolMessage::UpdateOptions {\n\t\t\t\toptions: ShapeOptionsUpdate::ShapeType(ShapeType::Arrow),\n\t\t\t}\n\t\t\t.into()\n\t\t}),\n\t]];\n\tDropdownInput::new(entries).selected_index(Some(shape_type as u32)).widget_instance()\n}\n\nfn create_arc_type_widget(arc_type: ArcType) -> WidgetInstance {\n\tlet entries = vec![\n\t\tRadioEntryData::new(\"Open\").label(\"Open\").on_update(move |_| {\n\t\t\tShapeToolMessage::UpdateOptions {\n\t\t\t\toptions: ShapeOptionsUpdate::ArcType(ArcType::Open),\n\t\t\t}\n\t\t\t.into()\n\t\t}),\n\t\tRadioEntryData::new(\"Closed\").label(\"Closed\").on_update(move |_| {\n\t\t\tShapeToolMessage::UpdateOptions {\n\t\t\t\toptions: ShapeOptionsUpdate::ArcType(ArcType::Closed),\n\t\t\t}\n\t\t\t.into()\n\t\t}),\n\t\tRadioEntryData::new(\"Pie\").label(\"Pie\").on_update(move |_| {\n\t\t\tShapeToolMessage::UpdateOptions {\n\t\t\t\toptions: ShapeOptionsUpdate::ArcType(ArcType::PieSlice),\n\t\t\t}\n\t\t\t.into()\n\t\t}),\n\t];\n\tRadioInput::new(entries).selected_index(Some(arc_type as u32)).widget_instance()\n}\n\nfn create_weight_widget(line_weight: f64) -> WidgetInstance {\n\tNumberInput::new(Some(line_weight))\n\t\t.unit(\" px\")\n\t\t.label(\"Weight\")\n\t\t.min(0.)\n\t\t.max((1_u64 << f64::MANTISSA_DIGITS) as f64)\n\t\t.on_update(|number_input: &NumberInput| {\n\t\t\tShapeToolMessage::UpdateOptions {\n\t\t\t\toptions: ShapeOptionsUpdate::LineWeight(number_input.value.unwrap()),\n\t\t\t}\n\t\t\t.into()\n\t\t})\n\t\t.widget_instance()\n}\n\nfn create_arrow_shaft_width_widget(shaft_width: f64) -> WidgetInstance {\n\tNumberInput::new(Some(shaft_width))\n\t\t.unit(\" px\")\n\t\t.label(\"Shaft\")\n\t\t.min(0.1)\n\t\t.max(1000.)\n\t\t.on_update(|number_input: &NumberInput| {\n\t\t\tShapeToolMessage::UpdateOptions {\n\t\t\t\toptions: ShapeOptionsUpdate::ArrowShaftWidth(number_input.value.unwrap()),\n\t\t\t}\n\t\t\t.into()\n\t\t})\n\t\t.widget_instance()\n}\n\nfn create_arrow_head_width_widget(head_width: f64) -> WidgetInstance {\n\tNumberInput::new(Some(head_width))\n\t\t.unit(\" px\")\n\t\t.label(\"Head W\")\n\t\t.min(0.1)\n\t\t.max(1000.)\n\t\t.on_update(|number_input: &NumberInput| {\n\t\t\tShapeToolMessage::UpdateOptions {\n\t\t\t\toptions: ShapeOptionsUpdate::ArrowHeadWidth(number_input.value.unwrap()),\n\t\t\t}\n\t\t\t.into()\n\t\t})\n\t\t.widget_instance()\n}\n\nfn create_arrow_head_length_widget(head_length: f64) -> WidgetInstance {\n\tNumberInput::new(Some(head_length))\n\t\t.unit(\" px\")\n\t\t.label(\"Head L\")\n\t\t.min(0.1)\n\t\t.max(1000.)\n\t\t.on_update(|number_input: &NumberInput| {\n\t\t\tShapeToolMessage::UpdateOptions {\n\t\t\t\toptions: ShapeOptionsUpdate::ArrowHeadLength(number_input.value.unwrap()),\n\t\t\t}\n\t\t\t.into()\n\t\t})\n\t\t.widget_instance()\n}\n\nfn create_spiral_type_widget(spiral_type: SpiralType) -> WidgetInstance {\n\tlet entries = vec![vec![\n\t\tMenuListEntry::new(\"Archimedean\").label(\"Archimedean\").on_commit(move |_| {\n\t\t\tShapeToolMessage::UpdateOptions {\n\t\t\t\toptions: ShapeOptionsUpdate::SpiralType(SpiralType::Archimedean),\n\t\t\t}\n\t\t\t.into()\n\t\t}),\n\t\tMenuListEntry::new(\"Logarithmic\").label(\"Logarithmic\").on_commit(move |_| {\n\t\t\tShapeToolMessage::UpdateOptions {\n\t\t\t\toptions: ShapeOptionsUpdate::SpiralType(SpiralType::Logarithmic),\n\t\t\t}\n\t\t\t.into()\n\t\t}),\n\t]];\n\tDropdownInput::new(entries).selected_index(Some(spiral_type as u32)).widget_instance()\n}\n\nfn create_grid_type_widget(grid_type: GridType) -> WidgetInstance {\n\tlet entries = vec![\n\t\tRadioEntryData::new(\"Rectangular\").label(\"Rectangular\").on_update(move |_| {\n\t\t\tShapeToolMessage::UpdateOptions {\n\t\t\t\toptions: ShapeOptionsUpdate::GridType(GridType::Rectangular),\n\t\t\t}\n\t\t\t.into()\n\t\t}),\n\t\tRadioEntryData::new(\"Isometric\").label(\"Isometric\").on_update(move |_| {\n\t\t\tShapeToolMessage::UpdateOptions {\n\t\t\t\toptions: ShapeOptionsUpdate::GridType(GridType::Isometric),\n\t\t\t}\n\t\t\t.into()\n\t\t}),\n\t];\n\tRadioInput::new(entries).selected_index(Some(grid_type as u32)).widget_instance()\n}\n\nimpl LayoutHolder for ShapeTool {\n\tfn layout(&self) -> Layout {\n\t\tlet mut widgets = vec![];\n\n\t\tif !self.tool_data.hide_shape_option_widget {\n\t\t\twidgets.push(create_shape_option_widget(self.options.shape_type));\n\t\t\twidgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\n\t\t\tif self.options.shape_type == ShapeType::Polygon || self.options.shape_type == ShapeType::Star {\n\t\t\t\twidgets.push(create_sides_widget(self.options.vertices));\n\t\t\t\twidgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\t\t\t}\n\n\t\t\tif self.options.shape_type == ShapeType::Arc {\n\t\t\t\twidgets.push(create_arc_type_widget(self.options.arc_type));\n\t\t\t\twidgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\t\t\t}\n\t\t}\n\n\t\tif self.options.shape_type == ShapeType::Spiral {\n\t\t\twidgets.push(create_spiral_type_widget(self.options.spiral_type));\n\t\t\twidgets.push(Separator::new(SeparatorStyle::Related).widget_instance());\n\n\t\t\twidgets.push(create_turns_widget(self.options.turns));\n\t\t\twidgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\t\t}\n\n\t\tif self.options.shape_type == ShapeType::Grid {\n\t\t\twidgets.push(create_grid_type_widget(self.options.grid_type));\n\t\t\twidgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\t\t}\n\n\t\tif self.options.shape_type == ShapeType::Arrow {\n\t\t\twidgets.push(create_arrow_shaft_width_widget(self.options.arrow_shaft_width));\n\t\t\twidgets.push(Separator::new(SeparatorStyle::Related).widget_instance());\n\t\t\twidgets.push(create_arrow_head_width_widget(self.options.arrow_head_width));\n\t\t\twidgets.push(Separator::new(SeparatorStyle::Related).widget_instance());\n\t\t\twidgets.push(create_arrow_head_length_widget(self.options.arrow_head_length));\n\t\t\twidgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\t\t}\n\n\t\tif self.options.shape_type != ShapeType::Line {\n\t\t\twidgets.append(&mut self.options.fill.create_widgets(\n\t\t\t\t\"Fill\",\n\t\t\t\ttrue,\n\t\t\t\t|_| {\n\t\t\t\t\tShapeToolMessage::UpdateOptions {\n\t\t\t\t\t\toptions: ShapeOptionsUpdate::FillColor(None),\n\t\t\t\t\t}\n\t\t\t\t\t.into()\n\t\t\t\t},\n\t\t\t\t|color_type: ToolColorType| {\n\t\t\t\t\tWidgetCallback::new(move |_| {\n\t\t\t\t\t\tShapeToolMessage::UpdateOptions {\n\t\t\t\t\t\t\toptions: ShapeOptionsUpdate::FillColorType(color_type.clone()),\n\t\t\t\t\t\t}\n\t\t\t\t\t\t.into()\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\t|color: &ColorInput| {\n\t\t\t\t\tShapeToolMessage::UpdateOptions {\n\t\t\t\t\t\toptions: ShapeOptionsUpdate::FillColor(color.value.as_solid().map(|color| color.to_linear_srgb())),\n\t\t\t\t\t}\n\t\t\t\t\t.into()\n\t\t\t\t},\n\t\t\t));\n\n\t\t\twidgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\t\t}\n\n\t\twidgets.append(&mut self.options.stroke.create_widgets(\n\t\t\t\"Stroke\",\n\t\t\ttrue,\n\t\t\t|_| {\n\t\t\t\tShapeToolMessage::UpdateOptions {\n\t\t\t\t\toptions: ShapeOptionsUpdate::StrokeColor(None),\n\t\t\t\t}\n\t\t\t\t.into()\n\t\t\t},\n\t\t\t|color_type: ToolColorType| {\n\t\t\t\tWidgetCallback::new(move |_| {\n\t\t\t\t\tShapeToolMessage::UpdateOptions {\n\t\t\t\t\t\toptions: ShapeOptionsUpdate::StrokeColorType(color_type.clone()),\n\t\t\t\t\t}\n\t\t\t\t\t.into()\n\t\t\t\t})\n\t\t\t},\n\t\t\t|color: &ColorInput| {\n\t\t\t\tShapeToolMessage::UpdateOptions {\n\t\t\t\t\toptions: ShapeOptionsUpdate::StrokeColor(color.value.as_solid().map(|color| color.to_linear_srgb())),\n\t\t\t\t}\n\t\t\t\t.into()\n\t\t\t},\n\t\t));\n\t\twidgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\t\twidgets.push(create_weight_widget(self.options.line_weight));\n\n\t\tLayout(vec![LayoutGroup::row(widgets)])\n\t}\n}\n\n#[message_handler_data]\nimpl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for ShapeTool {\n\tfn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {\n\t\tlet ToolMessage::Shape(ShapeToolMessage::UpdateOptions { options }) = message else {\n\t\t\tself.fsm_state.process_event(message, &mut self.tool_data, context, &self.options, responses, true);\n\t\t\treturn;\n\t\t};\n\t\tmatch options {\n\t\t\tShapeOptionsUpdate::FillColor(color) => {\n\t\t\t\tself.options.fill.custom_color = color;\n\t\t\t\tself.options.fill.color_type = ToolColorType::Custom;\n\t\t\t}\n\t\t\tShapeOptionsUpdate::FillColorType(color_type) => {\n\t\t\t\tself.options.fill.color_type = color_type;\n\t\t\t}\n\t\t\tShapeOptionsUpdate::LineWeight(line_weight) => {\n\t\t\t\tself.options.line_weight = line_weight;\n\t\t\t}\n\t\t\tShapeOptionsUpdate::StrokeColor(color) => {\n\t\t\t\tself.options.stroke.custom_color = color;\n\t\t\t\tself.options.stroke.color_type = ToolColorType::Custom;\n\t\t\t}\n\t\t\tShapeOptionsUpdate::StrokeColorType(color_type) => {\n\t\t\t\tself.options.stroke.color_type = color_type;\n\t\t\t}\n\t\t\tShapeOptionsUpdate::WorkingColors(primary, secondary) => {\n\t\t\t\tself.options.stroke.primary_working_color = primary;\n\t\t\t\tself.options.stroke.secondary_working_color = secondary;\n\t\t\t\tself.options.fill.primary_working_color = primary;\n\t\t\t\tself.options.fill.secondary_working_color = secondary;\n\t\t\t}\n\t\t\tShapeOptionsUpdate::ShapeType(shape) => {\n\t\t\t\tself.options.shape_type = shape;\n\t\t\t\tself.tool_data.current_shape = shape;\n\t\t\t}\n\t\t\tShapeOptionsUpdate::Vertices(vertices) => {\n\t\t\t\tself.options.vertices = vertices;\n\t\t\t}\n\t\t\tShapeOptionsUpdate::ArcType(arc_type) => {\n\t\t\t\tself.options.arc_type = arc_type;\n\t\t\t}\n\t\t\tShapeOptionsUpdate::SpiralType(spiral_type) => {\n\t\t\t\tself.options.spiral_type = spiral_type;\n\t\t\t}\n\t\t\tShapeOptionsUpdate::Turns(turns) => {\n\t\t\t\tself.options.turns = turns;\n\t\t\t}\n\t\t\tShapeOptionsUpdate::GridType(grid_type) => {\n\t\t\t\tself.options.grid_type = grid_type;\n\t\t\t}\n\t\t\tShapeOptionsUpdate::ArrowShaftWidth(shaft_width) => {\n\t\t\t\tself.options.arrow_shaft_width = shaft_width;\n\t\t\t}\n\t\t\tShapeOptionsUpdate::ArrowHeadWidth(head_width) => {\n\t\t\t\tself.options.arrow_head_width = head_width;\n\t\t\t}\n\t\t\tShapeOptionsUpdate::ArrowHeadLength(head_length) => {\n\t\t\t\tself.options.arrow_head_length = head_length;\n\t\t\t}\n\t\t}\n\n\t\tupdate_dynamic_hints(&self.fsm_state, responses, &self.tool_data);\n\t\tself.send_layout(responses, LayoutTarget::ToolOptions);\n\t}\n\n\tfn actions(&self) -> ActionList {\n\t\tmatch self.fsm_state {\n\t\t\tShapeToolFsmState::Ready(_) => actions!(ShapeToolMessageDiscriminant;\n\t\t\t\tDragStart,\n\t\t\t\tPointerMove,\n\t\t\t\tSetShape,\n\t\t\t\tAbort,\n\t\t\t\tHideShapeTypeWidget,\n\t\t\t\tIncreaseSides,\n\t\t\t\tDecreaseSides,\n\t\t\t\tNudgeSelectedLayers,\n\t\t\t),\n\t\t\tShapeToolFsmState::Drawing(_)\n\t\t\t| ShapeToolFsmState::ResizingBounds\n\t\t\t| ShapeToolFsmState::DraggingLineEndpoints\n\t\t\t| ShapeToolFsmState::RotatingBounds\n\t\t\t| ShapeToolFsmState::ModifyingGizmo\n\t\t\t| ShapeToolFsmState::SkewingBounds { .. } => {\n\t\t\t\tactions!(ShapeToolMessageDiscriminant;\n\t\t\t\t\tDragStop,\n\t\t\t\t\tAbort,\n\t\t\t\t\tPointerMove,\n\t\t\t\t\tSetShape,\n\t\t\t\t\tHideShapeTypeWidget,\n\t\t\t\t\tIncreaseSides,\n\t\t\t\t\tDecreaseSides,\n\t\t\t\t\tNudgeSelectedLayers,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n}\n\nimpl ToolMetadata for ShapeTool {\n\tfn icon_name(&self) -> String {\n\t\t\"VectorPolygonTool\".into()\n\t}\n\tfn tooltip_label(&self) -> String {\n\t\t\"Shape Tool\".into()\n\t}\n\tfn tool_type(&self) -> ToolType {\n\t\tToolType::Shape\n\t}\n}\n\nimpl ToolTransition for ShapeTool {\n\tfn event_to_message_map(&self) -> EventToMessageMap {\n\t\tEventToMessageMap {\n\t\t\toverlay_provider: Some(|context| ShapeToolMessage::Overlays { context }.into()),\n\t\t\ttool_abort: Some(ShapeToolMessage::Abort.into()),\n\t\t\tworking_color_changed: Some(ShapeToolMessage::WorkingColorChanged.into()),\n\t\t\t..Default::default()\n\t\t}\n\t}\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub enum ShapeToolFsmState {\n\tReady(ShapeType),\n\tDrawing(ShapeType),\n\n\t// Gizmos\n\tDraggingLineEndpoints,\n\tModifyingGizmo,\n\n\t// Transform cage\n\tResizingBounds,\n\tRotatingBounds,\n\tSkewingBounds { skew: Key },\n}\n\nimpl Default for ShapeToolFsmState {\n\tfn default() -> Self {\n\t\tShapeToolFsmState::Ready(ShapeType::default())\n\t}\n}\n\n#[derive(Clone, Debug, Default)]\npub struct ShapeToolData {\n\tpub data: Resize,\n\tauto_panning: AutoPanning,\n\n\t// In viewport space\n\tpub last_mouse_position: DVec2,\n\n\t// Hide the dropdown menu when using Line, Rectangle, or Ellipse aliases\n\tpub hide_shape_option_widget: bool,\n\n\t// Shape-specific data\n\tpub line_data: LineToolData,\n\n\t// Used for by transform cage\n\tpub bounding_box_manager: Option<BoundingBoxManager>,\n\tlayers_dragging: Vec<LayerNodeIdentifier>,\n\tsnap_candidates: Vec<SnapCandidatePoint>,\n\tskew_edge: EdgeBool,\n\tcursor: MouseCursorIcon,\n\n\t// Current shape which is being drawn\n\tcurrent_shape: ShapeType,\n\n\t// Gizmos\n\tgizmo_manager: GizmoManager,\n}\n\nimpl ShapeToolData {\n\tfn get_snap_candidates(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler) {\n\t\tself.snap_candidates.clear();\n\t\tfor &layer in &self.layers_dragging {\n\t\t\tif (self.snap_candidates.len() as f64) < document.snapping_state.tolerance {\n\t\t\t\tsnapping::get_layer_snap_points(layer, &SnapData::new(document, input, viewport), &mut self.snap_candidates);\n\t\t\t}\n\t\t\tif let Some(bounds) = document.metadata().bounding_box_with_transform(layer, DAffine2::IDENTITY) {\n\t\t\t\tlet quad = document.metadata().transform_to_document(layer) * Quad::from_box(bounds);\n\t\t\t\tsnapping::get_bbox_points(quad, &mut self.snap_candidates, snapping::BBoxSnapValues::BOUNDING_BOX, document);\n\t\t\t}\n\t\t}\n\t}\n\n\tfn transform_cage_mouse_icon(&mut self, input: &InputPreprocessorMessageHandler) -> MouseCursorIcon {\n\t\tlet dragging_bounds = self\n\t\t\t.bounding_box_manager\n\t\t\t.as_mut()\n\t\t\t.and_then(|bounding_box| bounding_box.check_selected_edges(input.mouse.position))\n\t\t\t.is_some();\n\n\t\tself.bounding_box_manager.as_ref().map_or(MouseCursorIcon::Crosshair, |bounds| {\n\t\t\tlet cursor_icon = bounds.get_cursor(input, true, dragging_bounds, Some(self.skew_edge));\n\t\t\tif cursor_icon == MouseCursorIcon::Default { MouseCursorIcon::Crosshair } else { cursor_icon }\n\t\t})\n\t}\n\n\tfn shape_tool_modifier_keys() -> [Key; 3] {\n\t\t[Key::Alt, Key::Shift, Key::Control]\n\t}\n\n\tfn decrease_or_increase_sides(&self, document: &DocumentMessageHandler, shape_type: ShapeType, responses: &mut VecDeque<Message>, decrease: bool) {\n\t\tif let Some(layer) = self.data.layer {\n\t\t\tmatch shape_type {\n\t\t\t\tShapeType::Star | ShapeType::Polygon => Polygon::decrease_or_increase_sides(decrease, layer, document, responses),\n\t\t\t\tShapeType::Spiral => Spiral::update_turns(decrease, layer, document, responses),\n\t\t\t\t_ => {}\n\t\t\t}\n\t\t}\n\n\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t}\n}\n\nimpl Fsm for ShapeToolFsmState {\n\ttype ToolData = ShapeToolData;\n\ttype ToolOptions = ShapeToolOptions;\n\n\tfn transition(\n\t\tself,\n\t\tevent: ToolMessage,\n\t\ttool_data: &mut Self::ToolData,\n\t\tToolActionMessageContext {\n\t\t\tdocument,\n\t\t\tglobal_tool_data,\n\t\t\tinput,\n\t\t\tshape_editor,\n\t\t\tviewport,\n\t\t\t..\n\t\t}: &mut ToolActionMessageContext,\n\t\ttool_options: &Self::ToolOptions,\n\t\tresponses: &mut VecDeque<Message>,\n\t) -> Self {\n\t\tlet all_selected_layers_line_or_arrow = document\n\t\t\t.network_interface\n\t\t\t.selected_nodes()\n\t\t\t.selected_visible_and_unlocked_layers(&document.network_interface)\n\t\t\t.all(|layer| graph_modification_utils::get_line_id(layer, &document.network_interface).is_some() || graph_modification_utils::get_arrow_id(layer, &document.network_interface).is_some());\n\n\t\tlet ToolMessage::Shape(event) = event else { return self };\n\n\t\tmatch (self, event) {\n\t\t\t(_, ShapeToolMessage::Overlays { context: mut overlay_context }) => {\n\t\t\t\tlet mouse_position = tool_data\n\t\t\t\t\t.data\n\t\t\t\t\t.snap_manager\n\t\t\t\t\t.indicator_pos()\n\t\t\t\t\t.map(|pos| document.metadata().document_to_viewport.transform_point2(pos))\n\t\t\t\t\t.unwrap_or(input.mouse.position);\n\n\t\t\t\tif matches!(self, Self::Ready(_)) && !input.keyboard.key(Key::Control) {\n\t\t\t\t\ttool_data.gizmo_manager.handle_actions(mouse_position, document, responses);\n\t\t\t\t\ttool_data.gizmo_manager.overlays(document, input, shape_editor, mouse_position, &mut overlay_context);\n\t\t\t\t}\n\n\t\t\t\tif matches!(self, ShapeToolFsmState::ModifyingGizmo) && !input.keyboard.key(Key::Control) {\n\t\t\t\t\ttool_data.gizmo_manager.dragging_overlays(document, input, shape_editor, mouse_position, &mut overlay_context);\n\t\t\t\t\tlet cursor = tool_data.gizmo_manager.mouse_cursor_icon().unwrap_or(MouseCursorIcon::Crosshair);\n\t\t\t\t\ttool_data.cursor = cursor;\n\t\t\t\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor });\n\t\t\t\t}\n\n\t\t\t\tlet modifying_transform_cage = matches!(self, ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::RotatingBounds | ShapeToolFsmState::SkewingBounds { .. });\n\t\t\t\tlet hovering_over_gizmo = tool_data.gizmo_manager.hovering_over_gizmo();\n\n\t\t\t\t// Check if hovering over a line/arrow endpoint (using data from previous overlay pass)\n\t\t\t\tlet hovering_over_endpoint = tool_data.line_data.selected_layers_with_position.iter().any(|(layer, endpoints)| {\n\t\t\t\t\tlet transform = document.metadata().transform_to_viewport(*layer);\n\t\t\t\t\tendpoints\n\t\t\t\t\t\t.iter()\n\t\t\t\t\t\t.any(|&local_pos| (transform.transform_point2(local_pos) - input.mouse.position).length_squared() < BOUNDS_SELECT_THRESHOLD.powi(2))\n\t\t\t\t});\n\n\t\t\t\tif !matches!(self, ShapeToolFsmState::ModifyingGizmo) && !modifying_transform_cage && !hovering_over_gizmo && !hovering_over_endpoint {\n\t\t\t\t\ttool_data.data.snap_manager.draw_overlays(SnapData::new(document, input, viewport), &mut overlay_context);\n\t\t\t\t}\n\n\t\t\t\tif modifying_transform_cage && !matches!(self, ShapeToolFsmState::ModifyingGizmo) {\n\t\t\t\t\ttransform_cage_overlays(document, tool_data, &mut overlay_context);\n\t\t\t\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor: tool_data.cursor });\n\t\t\t\t}\n\n\t\t\t\tif input.keyboard.key(Key::Control) && matches!(self, ShapeToolFsmState::Ready(_)) {\n\t\t\t\t\tanchor_overlays(document, &mut overlay_context);\n\t\t\t\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair });\n\t\t\t\t} else if matches!(self, ShapeToolFsmState::Ready(_)) {\n\t\t\t\t\tLine::overlays(document, tool_data, input.mouse.position, &mut overlay_context);\n\t\t\t\t\tArrow::overlays(document, tool_data, input.mouse.position, &mut overlay_context);\n\n\t\t\t\t\tif all_selected_layers_line_or_arrow {\n\t\t\t\t\t\tlet cursor = if hovering_over_endpoint { MouseCursorIcon::Default } else { MouseCursorIcon::Crosshair };\n\t\t\t\t\t\ttool_data.cursor = cursor;\n\t\t\t\t\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor });\n\t\t\t\t\t\treturn self;\n\t\t\t\t\t}\n\n\t\t\t\t\tif !hovering_over_gizmo {\n\t\t\t\t\t\ttransform_cage_overlays(document, tool_data, &mut overlay_context);\n\t\t\t\t\t}\n\n\t\t\t\t\tlet dragging_bounds = tool_data\n\t\t\t\t\t\t.bounding_box_manager\n\t\t\t\t\t\t.as_mut()\n\t\t\t\t\t\t.and_then(|bounding_box| bounding_box.check_selected_edges(input.mouse.position))\n\t\t\t\t\t\t.is_some();\n\n\t\t\t\t\tif let Some(bounds) = tool_data.bounding_box_manager.as_mut() {\n\t\t\t\t\t\tlet edges = bounds.check_selected_edges(input.mouse.position);\n\t\t\t\t\t\tlet is_skewing = matches!(self, ShapeToolFsmState::SkewingBounds { .. });\n\t\t\t\t\t\tlet is_near_square = edges.is_some_and(|hover_edge| bounds.over_extended_edge_midpoint(input.mouse.position, hover_edge));\n\t\t\t\t\t\tif is_skewing || (dragging_bounds && is_near_square && !hovering_over_gizmo) {\n\t\t\t\t\t\t\tbounds.render_skew_gizmos(&mut overlay_context, tool_data.skew_edge);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif dragging_bounds\n\t\t\t\t\t\t\t&& !is_skewing && !hovering_over_gizmo\n\t\t\t\t\t\t\t&& let Some(edges) = edges\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttool_data.skew_edge = bounds.get_closest_edge(edges, input.mouse.position);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tlet cursor = tool_data\n\t\t\t\t\t\t.gizmo_manager\n\t\t\t\t\t\t.mouse_cursor_icon()\n\t\t\t\t\t\t.or_else(|| hovering_over_endpoint.then_some(MouseCursorIcon::Default))\n\t\t\t\t\t\t.unwrap_or_else(|| tool_data.transform_cage_mouse_icon(input));\n\n\t\t\t\t\ttool_data.cursor = cursor;\n\t\t\t\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor });\n\t\t\t\t}\n\n\t\t\t\tif matches!(self, ShapeToolFsmState::Drawing(_) | ShapeToolFsmState::DraggingLineEndpoints) {\n\t\t\t\t\tLine::overlays(document, tool_data, input.mouse.position, &mut overlay_context);\n\t\t\t\t\tArrow::overlays(document, tool_data, input.mouse.position, &mut overlay_context);\n\n\t\t\t\t\tif tool_options.shape_type == ShapeType::Circle {\n\t\t\t\t\t\ttool_data.gizmo_manager.overlays(document, input, shape_editor, mouse_position, &mut overlay_context);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tself\n\t\t\t}\n\t\t\t(ShapeToolFsmState::Ready(_), ShapeToolMessage::IncreaseSides) => {\n\t\t\t\tif matches!(tool_options.shape_type, ShapeType::Star | ShapeType::Polygon) {\n\t\t\t\t\tresponses.add(ShapeToolMessage::UpdateOptions {\n\t\t\t\t\t\toptions: ShapeOptionsUpdate::Vertices(tool_options.vertices + 1),\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tif matches!(tool_options.shape_type, ShapeType::Spiral) {\n\t\t\t\t\tresponses.add(ShapeToolMessage::UpdateOptions {\n\t\t\t\t\t\toptions: ShapeOptionsUpdate::Turns(tool_options.turns + 1.),\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tself\n\t\t\t}\n\t\t\t(ShapeToolFsmState::Ready(_), ShapeToolMessage::DecreaseSides) => {\n\t\t\t\tif matches!(tool_options.shape_type, ShapeType::Star | ShapeType::Polygon) {\n\t\t\t\t\tresponses.add(ShapeToolMessage::UpdateOptions {\n\t\t\t\t\t\toptions: ShapeOptionsUpdate::Vertices((tool_options.vertices - 1).max(3)),\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tif matches!(tool_options.shape_type, ShapeType::Spiral) {\n\t\t\t\t\tresponses.add(ShapeToolMessage::UpdateOptions {\n\t\t\t\t\t\toptions: ShapeOptionsUpdate::Turns((tool_options.turns - 1.).max(1.)),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tself\n\t\t\t}\n\t\t\t(\n\t\t\t\tShapeToolFsmState::Ready(_),\n\t\t\t\tShapeToolMessage::NudgeSelectedLayers {\n\t\t\t\t\tdelta_x,\n\t\t\t\t\tdelta_y,\n\t\t\t\t\tresize,\n\t\t\t\t\tresize_opposite_corner,\n\t\t\t\t},\n\t\t\t) => {\n\t\t\t\tresponses.add(DocumentMessage::NudgeSelectedLayers {\n\t\t\t\t\tdelta_x,\n\t\t\t\t\tdelta_y,\n\t\t\t\t\tresize,\n\t\t\t\t\tresize_opposite_corner,\n\t\t\t\t});\n\n\t\t\t\tself\n\t\t\t}\n\t\t\t(ShapeToolFsmState::Drawing(_), ShapeToolMessage::NudgeSelectedLayers { .. }) => {\n\t\t\t\tlet increase = input.keyboard.key(Key::ArrowUp);\n\t\t\t\tlet decrease = input.keyboard.key(Key::ArrowDown);\n\n\t\t\t\tif increase {\n\t\t\t\t\tresponses.add(ShapeToolMessage::IncreaseSides);\n\t\t\t\t\treturn self;\n\t\t\t\t}\n\n\t\t\t\tif decrease {\n\t\t\t\t\tresponses.add(ShapeToolMessage::DecreaseSides);\n\t\t\t\t\treturn self;\n\t\t\t\t}\n\t\t\t\tself\n\t\t\t}\n\t\t\t(ShapeToolFsmState::Drawing(_), ShapeToolMessage::IncreaseSides) => {\n\t\t\t\ttool_data.decrease_or_increase_sides(document, tool_options.shape_type, responses, false);\n\t\t\t\tself\n\t\t\t}\n\t\t\t(ShapeToolFsmState::Drawing(_), ShapeToolMessage::DecreaseSides) => {\n\t\t\t\ttool_data.decrease_or_increase_sides(document, tool_options.shape_type, responses, true);\n\t\t\t\tself\n\t\t\t}\n\t\t\t(ShapeToolFsmState::Ready(_), ShapeToolMessage::DragStart) => {\n\t\t\t\ttool_data.line_data.drag_start = input.mouse.position;\n\n\t\t\t\t// Snapped position in viewport space\n\t\t\t\tlet mouse_pos = tool_data\n\t\t\t\t\t.data\n\t\t\t\t\t.snap_manager\n\t\t\t\t\t.indicator_pos()\n\t\t\t\t\t.map(|pos| document.metadata().document_to_viewport.transform_point2(pos))\n\t\t\t\t\t.unwrap_or(input.mouse.position);\n\n\t\t\t\ttool_data.line_data.drag_current = mouse_pos;\n\n\t\t\t\tif tool_data.gizmo_manager.handle_click() && !input.keyboard.key(Key::Accel) {\n\t\t\t\t\ttool_data.data.drag_start = document.metadata().document_to_viewport.inverse().transform_point2(mouse_pos);\n\t\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\n\t\t\t\t\tlet cursor = tool_data.gizmo_manager.mouse_cursor_icon().unwrap_or(MouseCursorIcon::Crosshair);\n\t\t\t\t\ttool_data.cursor = cursor;\n\t\t\t\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor });\n\t\t\t\t\t// Send a PointerMove message to refresh the cursor icon\n\t\t\t\t\tresponses.add(ShapeToolMessage::PointerMove {\n\t\t\t\t\t\tmodifier: ShapeToolData::shape_tool_modifier_keys(),\n\t\t\t\t\t});\n\n\t\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\n\t\t\t\t\treturn ShapeToolFsmState::ModifyingGizmo;\n\t\t\t\t}\n\n\t\t\t\t// If clicked on endpoints of a selected line or arrow, drag its endpoints\n\t\t\t\tif let Some((layer, _, _)) = closest_point(\n\t\t\t\t\tdocument,\n\t\t\t\t\tmouse_pos,\n\t\t\t\t\tSNAP_POINT_TOLERANCE,\n\t\t\t\t\tdocument.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface),\n\t\t\t\t\t|_| false,\n\t\t\t\t) && clicked_on_shape_endpoints(layer, document, input, tool_data)\n\t\t\t\t\t&& !input.keyboard.key(Key::Control)\n\t\t\t\t{\n\t\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\t\t\t\t\treturn ShapeToolFsmState::DraggingLineEndpoints;\n\t\t\t\t}\n\n\t\t\t\tlet (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging, None);\n\n\t\t\t\tif !input.keyboard.key(Key::Control) {\n\t\t\t\t\t// Helper function to update cursor and send pointer move message\n\t\t\t\t\tlet update_cursor_and_pointer = |tool_data: &mut ShapeToolData, responses: &mut VecDeque<Message>| {\n\t\t\t\t\t\tlet cursor = tool_data.transform_cage_mouse_icon(input);\n\t\t\t\t\t\ttool_data.cursor = cursor;\n\t\t\t\t\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor });\n\t\t\t\t\t\tresponses.add(ShapeToolMessage::PointerMove {\n\t\t\t\t\t\t\tmodifier: ShapeToolData::shape_tool_modifier_keys(),\n\t\t\t\t\t\t});\n\t\t\t\t\t};\n\n\t\t\t\t\tmatch (resize, rotate, skew) {\n\t\t\t\t\t\t(true, false, false) => {\n\t\t\t\t\t\t\ttool_data.get_snap_candidates(document, input, viewport);\n\t\t\t\t\t\t\tupdate_cursor_and_pointer(tool_data, responses);\n\n\t\t\t\t\t\t\treturn ShapeToolFsmState::ResizingBounds;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t(false, true, false) => {\n\t\t\t\t\t\t\ttool_data.data.drag_start = mouse_pos;\n\t\t\t\t\t\t\tupdate_cursor_and_pointer(tool_data, responses);\n\n\t\t\t\t\t\t\treturn ShapeToolFsmState::RotatingBounds;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t(false, false, true) => {\n\t\t\t\t\t\t\ttool_data.get_snap_candidates(document, input, viewport);\n\t\t\t\t\t\t\tupdate_cursor_and_pointer(tool_data, responses);\n\n\t\t\t\t\t\t\treturn ShapeToolFsmState::SkewingBounds { skew: Key::Control };\n\t\t\t\t\t\t}\n\t\t\t\t\t\t_ => {}\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tmatch tool_data.current_shape {\n\t\t\t\t\tShapeType::Polygon | ShapeType::Star | ShapeType::Circle | ShapeType::Arc | ShapeType::Spiral | ShapeType::Grid | ShapeType::Rectangle | ShapeType::Ellipse => {\n\t\t\t\t\t\ttool_data.data.start(document, input, viewport);\n\t\t\t\t\t}\n\t\t\t\t\tShapeType::Arrow | ShapeType::Line => {\n\t\t\t\t\t\tlet point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position));\n\t\t\t\t\t\tlet snapped = tool_data\n\t\t\t\t\t\t\t.data\n\t\t\t\t\t\t\t.snap_manager\n\t\t\t\t\t\t\t.free_snap(&SnapData::new(document, input, viewport), &point, SnapTypeConfiguration::default());\n\t\t\t\t\t\ttool_data.data.drag_start = snapped.snapped_point_document;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\n\t\t\t\tlet node = match tool_data.current_shape {\n\t\t\t\t\tShapeType::Polygon => Polygon::create_node(tool_options.vertices),\n\t\t\t\t\tShapeType::Star => Star::create_node(tool_options.vertices),\n\t\t\t\t\tShapeType::Circle => Circle::create_node(),\n\t\t\t\t\tShapeType::Arc => Arc::create_node(tool_options.arc_type),\n\t\t\t\t\tShapeType::Spiral => Spiral::create_node(tool_options.spiral_type, tool_options.turns),\n\t\t\t\t\tShapeType::Grid => Grid::create_node(tool_options.grid_type),\n\t\t\t\t\tShapeType::Arrow => Arrow::create_node(tool_options.arrow_shaft_width, tool_options.arrow_head_width, tool_options.arrow_head_length),\n\t\t\t\t\tShapeType::Line => Line::create_node(),\n\t\t\t\t\tShapeType::Rectangle => Rectangle::create_node(),\n\t\t\t\t\tShapeType::Ellipse => Ellipse::create_node(),\n\t\t\t\t};\n\n\t\t\t\tlet nodes = vec![(NodeId(0), node)];\n\t\t\t\tlet layer = graph_modification_utils::new_custom(NodeId::new(), nodes, document.new_layer_bounding_artboard(input, viewport), responses);\n\n\t\t\t\tlet defered_responses = &mut VecDeque::new();\n\n\t\t\t\tmatch tool_data.current_shape {\n\t\t\t\t\tShapeType::Polygon | ShapeType::Star | ShapeType::Circle | ShapeType::Arc | ShapeType::Spiral | ShapeType::Grid | ShapeType::Rectangle | ShapeType::Ellipse => {\n\t\t\t\t\t\tdefered_responses.add(GraphOperationMessage::TransformSet {\n\t\t\t\t\t\t\tlayer,\n\t\t\t\t\t\t\ttransform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position),\n\t\t\t\t\t\t\ttransform_in: TransformIn::Viewport,\n\t\t\t\t\t\t\tskip_rerender: false,\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\ttool_options.stroke.apply_stroke(tool_options.line_weight, layer, defered_responses);\n\t\t\t\t\t\ttool_options.fill.apply_fill(layer, defered_responses);\n\t\t\t\t\t}\n\t\t\t\t\tShapeType::Arrow => {\n\t\t\t\t\t\tlet viewport_drag_start = tool_data.data.viewport_drag_start(document);\n\t\t\t\t\t\tdefered_responses.add(GraphOperationMessage::TransformSet {\n\t\t\t\t\t\t\tlayer,\n\t\t\t\t\t\t\ttransform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., viewport_drag_start),\n\t\t\t\t\t\t\ttransform_in: TransformIn::Viewport,\n\t\t\t\t\t\t\tskip_rerender: false,\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\ttool_data.line_data.weight = tool_options.line_weight;\n\t\t\t\t\t\ttool_data.line_data.editing_layer = Some(layer);\n\t\t\t\t\t\ttool_options.stroke.apply_stroke(tool_options.line_weight, layer, defered_responses);\n\t\t\t\t\t\ttool_options.fill.apply_fill(layer, defered_responses);\n\t\t\t\t\t}\n\t\t\t\t\tShapeType::Line => {\n\t\t\t\t\t\tlet viewport_drag_start = tool_data.data.viewport_drag_start(document);\n\t\t\t\t\t\tdefered_responses.add(GraphOperationMessage::TransformSet {\n\t\t\t\t\t\t\tlayer,\n\t\t\t\t\t\t\ttransform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., viewport_drag_start),\n\t\t\t\t\t\t\ttransform_in: TransformIn::Viewport,\n\t\t\t\t\t\t\tskip_rerender: false,\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\ttool_data.line_data.weight = tool_options.line_weight;\n\t\t\t\t\t\ttool_data.line_data.editing_layer = Some(layer);\n\t\t\t\t\t\ttool_options.stroke.apply_stroke(tool_options.line_weight, layer, defered_responses);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\ttool_data.data.layer = Some(layer);\n\n\t\t\t\tresponses.add(DeferMessage::AfterGraphRun {\n\t\t\t\t\tmessages: defered_responses.drain(..).collect(),\n\t\t\t\t});\n\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\n\t\t\t\tShapeToolFsmState::Drawing(tool_data.current_shape)\n\t\t\t}\n\t\t\t(ShapeToolFsmState::Drawing(shape), ShapeToolMessage::PointerMove { modifier }) => {\n\t\t\t\tlet Some(layer) = tool_data.data.layer else {\n\t\t\t\t\treturn ShapeToolFsmState::Ready(shape);\n\t\t\t\t};\n\n\t\t\t\tmatch tool_data.current_shape {\n\t\t\t\t\tShapeType::Polygon => Polygon::update_shape(document, input, viewport, layer, tool_data, modifier, responses),\n\t\t\t\t\tShapeType::Star => Star::update_shape(document, input, viewport, layer, tool_data, modifier, responses),\n\t\t\t\t\tShapeType::Circle => Circle::update_shape(document, input, viewport, layer, tool_data, modifier, responses),\n\t\t\t\t\tShapeType::Arc => Arc::update_shape(document, input, viewport, layer, tool_data, modifier, responses),\n\t\t\t\t\tShapeType::Spiral => Spiral::update_shape(document, input, viewport, layer, tool_data, responses),\n\t\t\t\t\tShapeType::Grid => Grid::update_shape(document, input, layer, tool_options.grid_type, tool_data, modifier, responses),\n\t\t\t\t\tShapeType::Arrow => Arrow::update_shape(document, input, viewport, layer, tool_data, modifier, responses),\n\t\t\t\t\tShapeType::Line => Line::update_shape(document, input, viewport, layer, tool_data, modifier, responses),\n\t\t\t\t\tShapeType::Rectangle => Rectangle::update_shape(document, input, viewport, layer, tool_data, modifier, responses),\n\t\t\t\t\tShapeType::Ellipse => Ellipse::update_shape(document, input, viewport, layer, tool_data, modifier, responses),\n\t\t\t\t}\n\n\t\t\t\t// Auto-panning\n\t\t\t\tlet messages = [ShapeToolMessage::PointerOutsideViewport { modifier }.into(), ShapeToolMessage::PointerMove { modifier }.into()];\n\t\t\t\ttool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses);\n\n\t\t\t\tself\n\t\t\t}\n\t\t\t(ShapeToolFsmState::DraggingLineEndpoints, ShapeToolMessage::PointerMove { modifier }) => {\n\t\t\t\tlet Some(layer) = tool_data.line_data.editing_layer else {\n\t\t\t\t\treturn ShapeToolFsmState::Ready(tool_data.current_shape);\n\t\t\t\t};\n\n\t\t\t\tif graph_modification_utils::get_arrow_id(layer, &document.network_interface).is_some() {\n\t\t\t\t\tArrow::update_shape(document, input, viewport, layer, tool_data, modifier, responses);\n\t\t\t\t} else {\n\t\t\t\t\tLine::update_shape(document, input, viewport, layer, tool_data, modifier, responses);\n\t\t\t\t}\n\n\t\t\t\t// Auto-panning\n\t\t\t\tlet messages = [ShapeToolMessage::PointerOutsideViewport { modifier }.into(), ShapeToolMessage::PointerMove { modifier }.into()];\n\t\t\t\ttool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses);\n\n\t\t\t\tself\n\t\t\t}\n\t\t\t(ShapeToolFsmState::ModifyingGizmo, ShapeToolMessage::PointerMove { .. }) => {\n\t\t\t\ttool_data.gizmo_manager.handle_update(tool_data.data.viewport_drag_start(document), document, input, responses);\n\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\n\t\t\t\tShapeToolFsmState::ModifyingGizmo\n\t\t\t}\n\t\t\t(ShapeToolFsmState::ResizingBounds, ShapeToolMessage::PointerMove { modifier }) => {\n\t\t\t\tif let Some(bounds) = &mut tool_data.bounding_box_manager {\n\t\t\t\t\tlet messages = [ShapeToolMessage::PointerOutsideViewport { modifier }.into(), ShapeToolMessage::PointerMove { modifier }.into()];\n\t\t\t\t\tresize_bounds(\n\t\t\t\t\t\tdocument,\n\t\t\t\t\t\tresponses,\n\t\t\t\t\t\tbounds,\n\t\t\t\t\t\t&mut tool_data.layers_dragging,\n\t\t\t\t\t\t&mut tool_data.data.snap_manager,\n\t\t\t\t\t\t&mut tool_data.snap_candidates,\n\t\t\t\t\t\tinput,\n\t\t\t\t\t\tviewport,\n\t\t\t\t\t\tinput.keyboard.key(modifier[0]),\n\t\t\t\t\t\tinput.keyboard.key(modifier[1]),\n\t\t\t\t\t\tToolType::Shape,\n\t\t\t\t\t);\n\t\t\t\t\ttool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses);\n\t\t\t\t}\n\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\tShapeToolFsmState::ResizingBounds\n\t\t\t}\n\t\t\t(ShapeToolFsmState::RotatingBounds, ShapeToolMessage::PointerMove { modifier }) => {\n\t\t\t\tif let Some(bounds) = &mut tool_data.bounding_box_manager {\n\t\t\t\t\trotate_bounds(\n\t\t\t\t\t\tdocument,\n\t\t\t\t\t\tresponses,\n\t\t\t\t\t\tbounds,\n\t\t\t\t\t\t&mut tool_data.layers_dragging,\n\t\t\t\t\t\ttool_data.data.drag_start,\n\t\t\t\t\t\tinput.mouse.position,\n\t\t\t\t\t\tinput.keyboard.key(modifier[1]),\n\t\t\t\t\t\tToolType::Shape,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tShapeToolFsmState::RotatingBounds\n\t\t\t}\n\t\t\t(ShapeToolFsmState::SkewingBounds { skew }, ShapeToolMessage::PointerMove { .. }) => {\n\t\t\t\tif let Some(bounds) = &mut tool_data.bounding_box_manager {\n\t\t\t\t\tskew_bounds(\n\t\t\t\t\t\tdocument,\n\t\t\t\t\t\tresponses,\n\t\t\t\t\t\tbounds,\n\t\t\t\t\t\tinput.keyboard.key(skew),\n\t\t\t\t\t\t&mut tool_data.layers_dragging,\n\t\t\t\t\t\tinput.mouse.position,\n\t\t\t\t\t\tToolType::Shape,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tShapeToolFsmState::SkewingBounds { skew }\n\t\t\t}\n\n\t\t\t(_, ShapeToolMessage::PointerMove { .. }) => {\n\t\t\t\tlet dragging_bounds = tool_data\n\t\t\t\t\t.bounding_box_manager\n\t\t\t\t\t.as_mut()\n\t\t\t\t\t.and_then(|bounding_box| bounding_box.check_selected_edges(input.mouse.position))\n\t\t\t\t\t.is_some();\n\n\t\t\t\tlet cursor = tool_data.bounding_box_manager.as_ref().map_or(MouseCursorIcon::Crosshair, |bounds| {\n\t\t\t\t\tlet cursor = bounds.get_cursor(input, true, dragging_bounds, Some(tool_data.skew_edge));\n\t\t\t\t\tif cursor == MouseCursorIcon::Default { MouseCursorIcon::Crosshair } else { cursor }\n\t\t\t\t});\n\n\t\t\t\tif tool_data.cursor != cursor {\n\t\t\t\t\ttool_data.cursor = cursor;\n\t\t\t\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor });\n\t\t\t\t}\n\n\t\t\t\ttool_data.data.snap_manager.preview_draw(&SnapData::new(document, input, viewport), input.mouse.position);\n\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\tself\n\t\t\t}\n\t\t\t(ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::SkewingBounds { .. }, ShapeToolMessage::PointerOutsideViewport { .. }) => {\n\t\t\t\t// Auto-panning\n\t\t\t\tif let Some(shift) = tool_data.auto_panning.shift_viewport(input, viewport, responses)\n\t\t\t\t\t&& let Some(bounds) = &mut tool_data.bounding_box_manager\n\t\t\t\t{\n\t\t\t\t\tbounds.center_of_transformation += shift;\n\t\t\t\t\tbounds.original_bound_transform.translation += shift;\n\t\t\t\t}\n\n\t\t\t\tself\n\t\t\t}\n\t\t\t(ShapeToolFsmState::Ready(_), ShapeToolMessage::PointerOutsideViewport { .. }) => self,\n\t\t\t(_, ShapeToolMessage::PointerOutsideViewport { .. }) => {\n\t\t\t\t// Auto-panning\n\t\t\t\tlet _ = tool_data.auto_panning.shift_viewport(input, viewport, responses);\n\t\t\t\tself\n\t\t\t}\n\t\t\t(\n\t\t\t\tShapeToolFsmState::Drawing(_)\n\t\t\t\t| ShapeToolFsmState::DraggingLineEndpoints\n\t\t\t\t| ShapeToolFsmState::ResizingBounds\n\t\t\t\t| ShapeToolFsmState::RotatingBounds\n\t\t\t\t| ShapeToolFsmState::SkewingBounds { .. }\n\t\t\t\t| ShapeToolFsmState::ModifyingGizmo,\n\t\t\t\tShapeToolMessage::DragStop,\n\t\t\t) => {\n\t\t\t\tinput.mouse.finish_transaction(tool_data.data.drag_start, responses);\n\t\t\t\ttool_data.data.cleanup(responses);\n\n\t\t\t\ttool_data.gizmo_manager.handle_cleanup();\n\n\t\t\t\tif let Some(bounds) = &mut tool_data.bounding_box_manager {\n\t\t\t\t\tbounds.original_transforms.clear();\n\t\t\t\t}\n\n\t\t\t\ttool_data.line_data.dragging_endpoint = None;\n\t\t\t\ttool_data.line_data.editing_layer = None;\n\n\t\t\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair });\n\n\t\t\t\tShapeToolFsmState::Ready(tool_data.current_shape)\n\t\t\t}\n\t\t\t(\n\t\t\t\tShapeToolFsmState::Drawing(_)\n\t\t\t\t| ShapeToolFsmState::DraggingLineEndpoints\n\t\t\t\t| ShapeToolFsmState::ResizingBounds\n\t\t\t\t| ShapeToolFsmState::RotatingBounds\n\t\t\t\t| ShapeToolFsmState::SkewingBounds { .. }\n\t\t\t\t| ShapeToolFsmState::ModifyingGizmo,\n\t\t\t\tShapeToolMessage::Abort,\n\t\t\t) => {\n\t\t\t\tresponses.add(DocumentMessage::AbortTransaction);\n\t\t\t\ttool_data.data.cleanup(responses);\n\t\t\t\ttool_data.line_data.dragging_endpoint = None;\n\t\t\t\ttool_data.line_data.editing_layer = None;\n\n\t\t\t\ttool_data.gizmo_manager.handle_cleanup();\n\n\t\t\t\tif let Some(bounds) = &mut tool_data.bounding_box_manager {\n\t\t\t\t\tbounds.original_transforms.clear();\n\t\t\t\t}\n\n\t\t\t\ttool_data.cursor = MouseCursorIcon::Crosshair;\n\t\t\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair });\n\n\t\t\t\tShapeToolFsmState::Ready(tool_data.current_shape)\n\t\t\t}\n\t\t\t(_, ShapeToolMessage::WorkingColorChanged) => {\n\t\t\t\tresponses.add(ShapeToolMessage::UpdateOptions {\n\t\t\t\t\toptions: ShapeOptionsUpdate::WorkingColors(Some(global_tool_data.primary_color), Some(global_tool_data.secondary_color)),\n\t\t\t\t});\n\t\t\t\tself\n\t\t\t}\n\t\t\t(_, ShapeToolMessage::SetShape { shape }) => {\n\t\t\t\tresponses.add(DocumentMessage::AbortTransaction);\n\t\t\t\ttool_data.data.cleanup(responses);\n\t\t\t\ttool_data.current_shape = shape;\n\t\t\t\t// Update hints for the new shape (without updating options.shape_type)\n\t\t\t\tupdate_dynamic_hints(&ShapeToolFsmState::Ready(shape), responses, tool_data);\n\t\t\t\tShapeToolFsmState::Ready(shape)\n\t\t\t}\n\t\t\t(_, ShapeToolMessage::SyncShapeWithOptions) => {\n\t\t\t\t// Sync current_shape with the dropdown selection when returning from alias tools\n\t\t\t\ttool_data.current_shape = tool_options.shape_type;\n\t\t\t\tupdate_dynamic_hints(&ShapeToolFsmState::Ready(tool_options.shape_type), responses, tool_data);\n\t\t\t\tShapeToolFsmState::Ready(tool_options.shape_type)\n\t\t\t}\n\t\t\t(_, ShapeToolMessage::HideShapeTypeWidget { hide }) => {\n\t\t\t\ttool_data.hide_shape_option_widget = hide;\n\t\t\t\tresponses.add(ToolMessage::RefreshToolOptions);\n\t\t\t\tself\n\t\t\t}\n\t\t\t_ => self,\n\t\t}\n\t}\n\n\tfn update_hints(&self, _responses: &mut VecDeque<Message>) {\n\t\t// Moved logic to update_dynamic_hints\n\t}\n\n\tfn update_cursor(&self, responses: &mut VecDeque<Message>) {\n\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair });\n\t}\n}\n\nfn update_dynamic_hints(state: &ShapeToolFsmState, responses: &mut VecDeque<Message>, tool_data: &ShapeToolData) {\n\tlet hint_data = match state {\n\t\tShapeToolFsmState::Ready(_) => {\n\t\t\tlet hint_groups = match tool_data.current_shape {\n\t\t\t\tShapeType::Polygon | ShapeType::Star => vec![\n\t\t\t\t\tHintGroup(vec![\n\t\t\t\t\t\tHintInfo::mouse(MouseMotion::LmbDrag, \"Draw Polygon\"),\n\t\t\t\t\t\tHintInfo::keys([Key::Shift], \"Constrain Regular\").prepend_plus(),\n\t\t\t\t\t\tHintInfo::keys([Key::Alt], \"From Center\").prepend_plus(),\n\t\t\t\t\t]),\n\t\t\t\t\tHintGroup(vec![HintInfo::multi_keys([[Key::BracketLeft], [Key::BracketRight]], \"Decrease/Increase Sides\")]),\n\t\t\t\t],\n\t\t\t\tShapeType::Circle => vec![HintGroup(vec![\n\t\t\t\t\tHintInfo::mouse(MouseMotion::LmbDrag, \"Draw Circle\"),\n\t\t\t\t\tHintInfo::keys([Key::Alt], \"From Center\").prepend_plus(),\n\t\t\t\t])],\n\t\t\t\tShapeType::Arc => vec![HintGroup(vec![\n\t\t\t\t\tHintInfo::mouse(MouseMotion::LmbDrag, \"Draw Arc\"),\n\t\t\t\t\tHintInfo::keys([Key::Shift], \"Constrain Arc\").prepend_plus(),\n\t\t\t\t\tHintInfo::keys([Key::Alt], \"From Center\").prepend_plus(),\n\t\t\t\t])],\n\t\t\t\tShapeType::Spiral => vec![\n\t\t\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, \"Draw Spiral\")]),\n\t\t\t\t\tHintGroup(vec![HintInfo::multi_keys([[Key::BracketLeft], [Key::BracketRight]], \"Decrease/Increase Turns\")]),\n\t\t\t\t],\n\t\t\t\tShapeType::Grid => vec![HintGroup(vec![\n\t\t\t\t\tHintInfo::mouse(MouseMotion::LmbDrag, \"Draw Grid\"),\n\t\t\t\t\tHintInfo::keys([Key::Shift], \"Constrain Regular\").prepend_plus(),\n\t\t\t\t\tHintInfo::keys([Key::Alt], \"From Center\").prepend_plus(),\n\t\t\t\t])],\n\t\t\t\tShapeType::Arrow => vec![HintGroup(vec![\n\t\t\t\t\tHintInfo::mouse(MouseMotion::LmbDrag, \"Draw Arrow\"),\n\t\t\t\t\tHintInfo::keys([Key::Shift], \"15° Increments\").prepend_plus(),\n\t\t\t\t\tHintInfo::keys([Key::Alt], \"From Center\").prepend_plus(),\n\t\t\t\t\tHintInfo::keys([Key::Control], \"Lock Angle\").prepend_plus(),\n\t\t\t\t])],\n\t\t\t\tShapeType::Line => vec![HintGroup(vec![\n\t\t\t\t\tHintInfo::mouse(MouseMotion::LmbDrag, \"Draw Line\"),\n\t\t\t\t\tHintInfo::keys([Key::Shift], \"15° Increments\").prepend_plus(),\n\t\t\t\t\tHintInfo::keys([Key::Alt], \"From Center\").prepend_plus(),\n\t\t\t\t\tHintInfo::keys([Key::Control], \"Lock Angle\").prepend_plus(),\n\t\t\t\t])],\n\t\t\t\tShapeType::Rectangle => vec![HintGroup(vec![\n\t\t\t\t\tHintInfo::mouse(MouseMotion::LmbDrag, \"Draw Rectangle\"),\n\t\t\t\t\tHintInfo::keys([Key::Shift], \"Constrain Square\").prepend_plus(),\n\t\t\t\t\tHintInfo::keys([Key::Alt], \"From Center\").prepend_plus(),\n\t\t\t\t])],\n\t\t\t\tShapeType::Ellipse => vec![HintGroup(vec![\n\t\t\t\t\tHintInfo::mouse(MouseMotion::LmbDrag, \"Draw Ellipse\"),\n\t\t\t\t\tHintInfo::keys([Key::Shift], \"Constrain Circular\").prepend_plus(),\n\t\t\t\t\tHintInfo::keys([Key::Alt], \"From Center\").prepend_plus(),\n\t\t\t\t])],\n\t\t\t};\n\t\t\tHintData(hint_groups)\n\t\t}\n\t\tShapeToolFsmState::Drawing(shape) => {\n\t\t\tlet mut common_hint_group = vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"\"), HintInfo::keys([Key::Escape], \"Cancel\").prepend_slash()])];\n\t\t\tlet tool_hint_group = match shape {\n\t\t\t\tShapeType::Polygon | ShapeType::Star | ShapeType::Arc => HintGroup(vec![HintInfo::keys([Key::Shift], \"Constrain Regular\"), HintInfo::keys([Key::Alt], \"From Center\")]),\n\t\t\t\tShapeType::Circle => HintGroup(vec![HintInfo::keys([Key::Alt], \"From Center\")]),\n\t\t\t\tShapeType::Spiral => HintGroup(vec![]),\n\t\t\t\tShapeType::Grid => HintGroup(vec![HintInfo::keys([Key::Shift], \"Constrain Regular\"), HintInfo::keys([Key::Alt], \"From Center\")]),\n\t\t\t\tShapeType::Arrow => HintGroup(vec![\n\t\t\t\t\tHintInfo::keys([Key::Shift], \"15° Increments\"),\n\t\t\t\t\tHintInfo::keys([Key::Alt], \"From Center\"),\n\t\t\t\t\tHintInfo::keys([Key::Control], \"Lock Angle\"),\n\t\t\t\t]),\n\t\t\t\tShapeType::Line => HintGroup(vec![\n\t\t\t\t\tHintInfo::keys([Key::Shift], \"15° Increments\"),\n\t\t\t\t\tHintInfo::keys([Key::Alt], \"From Center\"),\n\t\t\t\t\tHintInfo::keys([Key::Control], \"Lock Angle\"),\n\t\t\t\t]),\n\t\t\t\tShapeType::Rectangle => HintGroup(vec![HintInfo::keys([Key::Shift], \"Constrain Square\"), HintInfo::keys([Key::Alt], \"From Center\")]),\n\t\t\t\tShapeType::Ellipse => HintGroup(vec![HintInfo::keys([Key::Shift], \"Constrain Circular\"), HintInfo::keys([Key::Alt], \"From Center\")]),\n\t\t\t};\n\n\t\t\tif !tool_hint_group.0.is_empty() {\n\t\t\t\tcommon_hint_group.push(tool_hint_group);\n\t\t\t}\n\n\t\t\tif matches!(shape, ShapeType::Polygon | ShapeType::Star) {\n\t\t\t\tcommon_hint_group.push(HintGroup(vec![HintInfo::multi_keys([[Key::BracketLeft], [Key::BracketRight]], \"Decrease/Increase Sides\")]));\n\t\t\t}\n\n\t\t\tif matches!(shape, ShapeType::Spiral) {\n\t\t\t\tcommon_hint_group.push(HintGroup(vec![HintInfo::multi_keys([[Key::BracketLeft], [Key::BracketRight]], \"Decrease/Increase Turns\")]));\n\t\t\t}\n\n\t\t\tHintData(common_hint_group)\n\t\t}\n\t\tShapeToolFsmState::DraggingLineEndpoints => HintData(vec![\n\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"\"), HintInfo::keys([Key::Escape], \"Cancel\").prepend_slash()]),\n\t\t\tHintGroup(vec![\n\t\t\t\tHintInfo::keys([Key::Shift], \"15° Increments\"),\n\t\t\t\tHintInfo::keys([Key::Alt], \"From Center\"),\n\t\t\t\tHintInfo::keys([Key::Control], \"Lock Angle\"),\n\t\t\t]),\n\t\t]),\n\t\tShapeToolFsmState::ResizingBounds => HintData(vec![\n\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"\"), HintInfo::keys([Key::Escape], \"Cancel\").prepend_slash()]),\n\t\t\tHintGroup(vec![HintInfo::keys([Key::Alt], \"From Pivot\"), HintInfo::keys([Key::Shift], \"Preserve Aspect Ratio\")]),\n\t\t]),\n\t\tShapeToolFsmState::RotatingBounds => HintData(vec![\n\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"\"), HintInfo::keys([Key::Escape], \"Cancel\").prepend_slash()]),\n\t\t\tHintGroup(vec![HintInfo::keys([Key::Shift], \"15° Increments\")]),\n\t\t]),\n\t\tShapeToolFsmState::SkewingBounds { .. } => HintData(vec![\n\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"\"), HintInfo::keys([Key::Escape], \"Cancel\").prepend_slash()]),\n\t\t\tHintGroup(vec![HintInfo::keys([Key::Control], \"Unlock Slide\")]),\n\t\t]),\n\t\tShapeToolFsmState::ModifyingGizmo => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"\"), HintInfo::keys([Key::Escape], \"Cancel\").prepend_slash()])]),\n\t};\n\thint_data.send_layout(responses);\n}\n"
  },
  {
    "path": "editor/src/messages/tool/tool_messages/spline_tool.rs",
    "content": "use super::tool_prelude::*;\nuse crate::consts::{DEFAULT_STROKE_WIDTH, DRAG_THRESHOLD, PATH_JOIN_THRESHOLD, SNAP_POINT_TOLERANCE};\nuse crate::messages::input_mapper::utility_types::input_mouse::MouseKeys;\nuse crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;\nuse crate::messages::portfolio::document::node_graph::document_node_definitions::{resolve_network_node_type, resolve_proto_node_type};\nuse crate::messages::portfolio::document::overlays::utility_functions::path_endpoint_overlays;\nuse crate::messages::portfolio::document::overlays::utility_types::OverlayContext;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::tool::common_functionality::auto_panning::AutoPanning;\nuse crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};\nuse crate::messages::tool::common_functionality::graph_modification_utils::{self, find_spline, merge_layers, merge_points};\nuse crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapData, SnapManager, SnapTypeConfiguration, SnappedPoint};\nuse crate::messages::tool::common_functionality::utility_functions::{closest_point, should_extend};\nuse graph_craft::document::{NodeId, NodeInput};\nuse graphene_std::Color;\nuse graphene_std::vector::{PointId, SegmentId, VectorModificationType};\n\n#[derive(Default, ExtractField)]\npub struct SplineTool {\n\tfsm_state: SplineToolFsmState,\n\ttool_data: SplineToolData,\n\toptions: SplineOptions,\n}\n\npub struct SplineOptions {\n\tline_weight: f64,\n\tfill: ToolColorOptions,\n\tstroke: ToolColorOptions,\n}\n\nimpl Default for SplineOptions {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tline_weight: DEFAULT_STROKE_WIDTH,\n\t\t\tfill: ToolColorOptions::new_none(),\n\t\t\tstroke: ToolColorOptions::new_primary(),\n\t\t}\n\t}\n}\n\n#[impl_message(Message, ToolMessage, Spline)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum SplineToolMessage {\n\t// Standard messages\n\tOverlays { context: OverlayContext },\n\tCanvasTransformed,\n\tAbort,\n\tWorkingColorChanged,\n\n\t// Tool-specific messages\n\tConfirm,\n\tDragStart { append_to_selected: Key },\n\tDragStop,\n\tMergeEndpoints,\n\tPointerMove,\n\tPointerOutsideViewport,\n\tUndo,\n\tUpdateOptions { options: SplineOptionsUpdate },\n}\n\n#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]\nenum SplineToolFsmState {\n\t#[default]\n\tReady,\n\tDrawing,\n\tMergingEndpoints,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum SplineOptionsUpdate {\n\tFillColor(Option<Color>),\n\tFillColorType(ToolColorType),\n\tLineWeight(f64),\n\tStrokeColor(Option<Color>),\n\tStrokeColorType(ToolColorType),\n\tWorkingColors(Option<Color>, Option<Color>),\n}\n\nimpl ToolMetadata for SplineTool {\n\tfn icon_name(&self) -> String {\n\t\t\"VectorSplineTool\".into()\n\t}\n\tfn tooltip_label(&self) -> String {\n\t\t\"Spline Tool\".into()\n\t}\n\tfn tool_type(&self) -> crate::messages::tool::utility_types::ToolType {\n\t\tToolType::Spline\n\t}\n}\n\nfn create_weight_widget(line_weight: f64) -> WidgetInstance {\n\tNumberInput::new(Some(line_weight))\n\t\t.unit(\" px\")\n\t\t.label(\"Weight\")\n\t\t.min(0.)\n\t\t.max((1_u64 << f64::MANTISSA_DIGITS) as f64)\n\t\t.on_update(|number_input: &NumberInput| {\n\t\t\tSplineToolMessage::UpdateOptions {\n\t\t\t\toptions: SplineOptionsUpdate::LineWeight(number_input.value.unwrap()),\n\t\t\t}\n\t\t\t.into()\n\t\t})\n\t\t.widget_instance()\n}\n\nimpl LayoutHolder for SplineTool {\n\tfn layout(&self) -> Layout {\n\t\tlet mut widgets = self.options.fill.create_widgets(\n\t\t\t\"Fill\",\n\t\t\ttrue,\n\t\t\t|_| {\n\t\t\t\tSplineToolMessage::UpdateOptions {\n\t\t\t\t\toptions: SplineOptionsUpdate::FillColor(None),\n\t\t\t\t}\n\t\t\t\t.into()\n\t\t\t},\n\t\t\t|color_type: ToolColorType| {\n\t\t\t\tWidgetCallback::new(move |_| {\n\t\t\t\t\tSplineToolMessage::UpdateOptions {\n\t\t\t\t\t\toptions: SplineOptionsUpdate::FillColorType(color_type.clone()),\n\t\t\t\t\t}\n\t\t\t\t\t.into()\n\t\t\t\t})\n\t\t\t},\n\t\t\t|color: &ColorInput| {\n\t\t\t\tSplineToolMessage::UpdateOptions {\n\t\t\t\t\toptions: SplineOptionsUpdate::FillColor(color.value.as_solid().map(|color| color.to_linear_srgb())),\n\t\t\t\t}\n\t\t\t\t.into()\n\t\t\t},\n\t\t);\n\n\t\twidgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\n\t\twidgets.append(&mut self.options.stroke.create_widgets(\n\t\t\t\"Stroke\",\n\t\t\ttrue,\n\t\t\t|_| {\n\t\t\t\tSplineToolMessage::UpdateOptions {\n\t\t\t\t\toptions: SplineOptionsUpdate::StrokeColor(None),\n\t\t\t\t}\n\t\t\t\t.into()\n\t\t\t},\n\t\t\t|color_type: ToolColorType| {\n\t\t\t\tWidgetCallback::new(move |_| {\n\t\t\t\t\tSplineToolMessage::UpdateOptions {\n\t\t\t\t\t\toptions: SplineOptionsUpdate::StrokeColorType(color_type.clone()),\n\t\t\t\t\t}\n\t\t\t\t\t.into()\n\t\t\t\t})\n\t\t\t},\n\t\t\t|color: &ColorInput| {\n\t\t\t\tSplineToolMessage::UpdateOptions {\n\t\t\t\t\toptions: SplineOptionsUpdate::StrokeColor(color.value.as_solid().map(|color| color.to_linear_srgb())),\n\t\t\t\t}\n\t\t\t\t.into()\n\t\t\t},\n\t\t));\n\t\twidgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\t\twidgets.push(create_weight_widget(self.options.line_weight));\n\n\t\tLayout(vec![LayoutGroup::row(widgets)])\n\t}\n}\n\n#[message_handler_data]\nimpl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for SplineTool {\n\tfn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {\n\t\tlet ToolMessage::Spline(SplineToolMessage::UpdateOptions { options }) = message else {\n\t\t\tself.fsm_state.process_event(message, &mut self.tool_data, context, &self.options, responses, true);\n\t\t\treturn;\n\t\t};\n\t\tmatch options {\n\t\t\tSplineOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,\n\t\t\tSplineOptionsUpdate::FillColor(color) => {\n\t\t\t\tself.options.fill.custom_color = color;\n\t\t\t\tself.options.fill.color_type = ToolColorType::Custom;\n\t\t\t}\n\t\t\tSplineOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,\n\t\t\tSplineOptionsUpdate::StrokeColor(color) => {\n\t\t\t\tself.options.stroke.custom_color = color;\n\t\t\t\tself.options.stroke.color_type = ToolColorType::Custom;\n\t\t\t}\n\t\t\tSplineOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,\n\t\t\tSplineOptionsUpdate::WorkingColors(primary, secondary) => {\n\t\t\t\tself.options.stroke.primary_working_color = primary;\n\t\t\t\tself.options.stroke.secondary_working_color = secondary;\n\t\t\t\tself.options.fill.primary_working_color = primary;\n\t\t\t\tself.options.fill.secondary_working_color = secondary;\n\t\t\t}\n\t\t}\n\n\t\tself.send_layout(responses, LayoutTarget::ToolOptions);\n\t}\n\n\tfn actions(&self) -> ActionList {\n\t\tmatch self.fsm_state {\n\t\t\tSplineToolFsmState::Ready => actions!(SplineToolMessageDiscriminant;\n\t\t\t\tUndo,\n\t\t\t\tDragStart,\n\t\t\t\tDragStop,\n\t\t\t\tPointerMove,\n\t\t\t\tConfirm,\n\t\t\t\tAbort,\n\t\t\t),\n\t\t\tSplineToolFsmState::Drawing => actions!(SplineToolMessageDiscriminant;\n\t\t\t\tDragStop,\n\t\t\t\tPointerMove,\n\t\t\t\tConfirm,\n\t\t\t\tAbort,\n\t\t\t),\n\t\t\tSplineToolFsmState::MergingEndpoints => actions!(SplineToolMessageDiscriminant;\n\t\t\t\tMergeEndpoints,\n\t\t\t),\n\t\t}\n\t}\n}\n\nimpl ToolTransition for SplineTool {\n\tfn event_to_message_map(&self) -> EventToMessageMap {\n\t\tEventToMessageMap {\n\t\t\toverlay_provider: Some(|context: OverlayContext| SplineToolMessage::Overlays { context }.into()),\n\t\t\tcanvas_transformed: Some(SplineToolMessage::CanvasTransformed.into()),\n\t\t\ttool_abort: Some(SplineToolMessage::Abort.into()),\n\t\t\tworking_color_changed: Some(SplineToolMessage::WorkingColorChanged.into()),\n\t\t\t..Default::default()\n\t\t}\n\t}\n}\n\n#[derive(Clone, Debug)]\nenum EndpointPosition {\n\tStart,\n\tEnd,\n}\n\n#[derive(Clone, Debug, Default)]\nstruct SplineToolData {\n\t/// List of points inserted.\n\tpoints: Vec<(PointId, DVec2)>,\n\t/// Point to be inserted.\n\tnext_point: DVec2,\n\t/// Point that was inserted temporarily to show preview.\n\tpreview_point: Option<PointId>,\n\t/// Segment that was inserted temporarily to show preview.\n\tpreview_segment: Option<SegmentId>,\n\textend: bool,\n\tweight: f64,\n\t/// The layer we are editing.\n\tcurrent_layer: Option<LayerNodeIdentifier>,\n\t/// The layers to merge to the current layer before we merge endpoints in merge_endpoint field.\n\tmerge_layers: HashSet<LayerNodeIdentifier>,\n\t/// The endpoint IDs to merge with the spline's start/end endpoint after spline drawing is finished.\n\tmerge_endpoints: Vec<(EndpointPosition, PointId)>,\n\tsnap_manager: SnapManager,\n\tauto_panning: AutoPanning,\n\t/// Viewport-space start position for newly created layers, used to compute local-space\n\t/// positions before the deferred TransformSet has been reflected in metadata.\n\tnew_layer_viewport_start: Option<DVec2>,\n}\n\nimpl SplineToolData {\n\tfn cleanup(&mut self) {\n\t\tself.current_layer = None;\n\t\tself.new_layer_viewport_start = None;\n\t\tself.merge_layers = HashSet::new();\n\t\tself.merge_endpoints = Vec::new();\n\t\tself.preview_point = None;\n\t\tself.preview_segment = None;\n\t\tself.extend = false;\n\t\tself.points = Vec::new();\n\t}\n\n\t/// Get the snapped point while ignoring current layer\n\tfn snapped_point(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler) -> SnappedPoint {\n\t\tlet point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position));\n\t\tlet ignore = if let Some(layer) = self.current_layer { vec![layer] } else { vec![] };\n\t\tlet snap_data = SnapData::ignore(document, input, viewport, &ignore);\n\t\tself.snap_manager.free_snap(&snap_data, &point, SnapTypeConfiguration::default())\n\t}\n}\n\nimpl Fsm for SplineToolFsmState {\n\ttype ToolData = SplineToolData;\n\ttype ToolOptions = SplineOptions;\n\n\tfn transition(\n\t\tself,\n\t\tevent: ToolMessage,\n\t\ttool_data: &mut Self::ToolData,\n\t\ttool_action_data: &mut ToolActionMessageContext,\n\t\ttool_options: &Self::ToolOptions,\n\t\tresponses: &mut VecDeque<Message>,\n\t) -> Self {\n\t\tlet ToolActionMessageContext {\n\t\t\tdocument,\n\t\t\tglobal_tool_data,\n\t\t\tinput,\n\t\t\tshape_editor,\n\t\t\tviewport,\n\t\t\t..\n\t\t} = tool_action_data;\n\n\t\tlet ToolMessage::Spline(event) = event else { return self };\n\t\tmatch (self, event) {\n\t\t\t(_, SplineToolMessage::CanvasTransformed) => self,\n\t\t\t(_, SplineToolMessage::Overlays { context: mut overlay_context }) => {\n\t\t\t\tpath_endpoint_overlays(document, shape_editor, &mut overlay_context);\n\t\t\t\ttool_data.snap_manager.draw_overlays(SnapData::new(document, input, viewport), &mut overlay_context);\n\t\t\t\tself\n\t\t\t}\n\t\t\t(SplineToolFsmState::MergingEndpoints, SplineToolMessage::MergeEndpoints) => {\n\t\t\t\tlet Some(current_layer) = tool_data.current_layer else { return SplineToolFsmState::Ready };\n\n\t\t\t\tif let Some(&layer) = tool_data.merge_layers.iter().last() {\n\t\t\t\t\tmerge_layers(document, current_layer, layer, responses);\n\t\t\t\t\ttool_data.merge_layers.remove(&layer);\n\n\t\t\t\t\tresponses.add(SplineToolMessage::MergeEndpoints);\n\t\t\t\t\treturn SplineToolFsmState::MergingEndpoints;\n\t\t\t\t}\n\n\t\t\t\tlet Some((start_endpoint, _)) = tool_data.points.first() else { return SplineToolFsmState::Ready };\n\t\t\t\tlet Some((last_endpoint, _)) = tool_data.points.last() else { return SplineToolFsmState::Ready };\n\n\t\t\t\tif let Some((position, second_endpoint)) = tool_data.merge_endpoints.pop() {\n\t\t\t\t\tlet first_endpoint = match position {\n\t\t\t\t\t\tEndpointPosition::Start => *start_endpoint,\n\t\t\t\t\t\tEndpointPosition::End => *last_endpoint,\n\t\t\t\t\t};\n\t\t\t\t\tmerge_points(document, current_layer, first_endpoint, second_endpoint, responses);\n\n\t\t\t\t\tresponses.add(SplineToolMessage::MergeEndpoints);\n\t\t\t\t\treturn SplineToolFsmState::MergingEndpoints;\n\t\t\t\t}\n\n\t\t\t\tresponses.add(DocumentMessage::EndTransaction);\n\t\t\t\tSplineToolFsmState::Ready\n\t\t\t}\n\t\t\t(SplineToolFsmState::Ready, SplineToolMessage::DragStart { append_to_selected }) => {\n\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\n\t\t\t\ttool_data.snap_manager.cleanup(responses);\n\t\t\t\ttool_data.cleanup();\n\t\t\t\ttool_data.weight = tool_options.line_weight;\n\n\t\t\t\tlet point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position));\n\t\t\t\tlet snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input, viewport), &point, SnapTypeConfiguration::default());\n\t\t\t\tlet viewport_vec = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document);\n\n\t\t\t\tlet layers = LayerNodeIdentifier::ROOT_PARENT\n\t\t\t\t\t.descendants(document.metadata())\n\t\t\t\t\t.filter(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]));\n\n\t\t\t\t// Extend an endpoint of the selected path\n\t\t\t\tif let Some((layer, point, position)) = should_extend(document, viewport_vec, SNAP_POINT_TOLERANCE, layers) {\n\t\t\t\t\tif find_spline(document, layer).is_some() {\n\t\t\t\t\t\t// If the point is the part of Spline then we extend it.\n\t\t\t\t\t\ttool_data.current_layer = Some(layer);\n\t\t\t\t\t\ttool_data.points.push((point, position));\n\t\t\t\t\t\ttool_data.next_point = position;\n\t\t\t\t\t\ttool_data.extend = true;\n\n\t\t\t\t\t\textend_spline(tool_data, true, responses);\n\n\t\t\t\t\t\treturn SplineToolFsmState::Drawing;\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttool_data.merge_layers.insert(layer);\n\t\t\t\t\t\ttool_data.merge_endpoints.push((EndpointPosition::Start, point));\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tlet selected_nodes = document.network_interface.selected_nodes();\n\t\t\t\tlet mut selected_layers_except_artboards = selected_nodes.selected_layers_except_artboards(&document.network_interface);\n\t\t\t\tlet selected_layer = selected_layers_except_artboards.next().filter(|_| selected_layers_except_artboards.next().is_none());\n\n\t\t\t\tlet append_to_selected_layer = input.keyboard.key(append_to_selected);\n\n\t\t\t\t// Create new path in the selected layer when shift is down\n\t\t\t\tif let (Some(layer), true) = (selected_layer, append_to_selected_layer) {\n\t\t\t\t\ttool_data.current_layer = Some(layer);\n\n\t\t\t\t\tlet transform = document.metadata().transform_to_viewport(layer);\n\t\t\t\t\tlet position = transform.inverse().transform_point2(input.mouse.position);\n\t\t\t\t\ttool_data.next_point = position;\n\n\t\t\t\t\treturn SplineToolFsmState::Drawing;\n\t\t\t\t}\n\n\t\t\t\tresponses.add(DocumentMessage::DeselectAllLayers);\n\n\t\t\t\tlet parent = document.new_layer_bounding_artboard(input, viewport);\n\n\t\t\t\tlet path_node_type = resolve_network_node_type(\"Path\").expect(\"Path node does not exist\");\n\t\t\t\tlet path_node = path_node_type.default_node_template();\n\t\t\t\tlet spline_node_type = resolve_proto_node_type(graphene_std::vector::spline::IDENTIFIER).expect(\"Spline node does not exist\");\n\t\t\t\tlet spline_node = spline_node_type.node_template_input_override([Some(NodeInput::node(NodeId(1), 0))]);\n\t\t\t\tlet nodes = vec![(NodeId(1), path_node), (NodeId(0), spline_node)];\n\n\t\t\t\tlet layer = graph_modification_utils::new_custom(NodeId::new(), nodes, parent, responses);\n\t\t\t\ttool_options.stroke.apply_stroke(tool_data.weight, layer, responses);\n\t\t\t\ttool_options.fill.apply_fill(layer, responses);\n\t\t\t\ttool_data.current_layer = Some(layer);\n\t\t\t\ttool_data.new_layer_viewport_start = Some(viewport_vec);\n\n\t\t\t\t// Position the layer at the initial mouse position via Transform\n\t\t\t\tresponses.add(DeferMessage::AfterGraphRun {\n\t\t\t\t\tmessages: vec![\n\t\t\t\t\t\tGraphOperationMessage::TransformSet {\n\t\t\t\t\t\t\tlayer,\n\t\t\t\t\t\t\ttransform: DAffine2::from_translation(viewport_vec),\n\t\t\t\t\t\t\ttransform_in: TransformIn::Viewport,\n\t\t\t\t\t\t\tskip_rerender: false,\n\t\t\t\t\t\t}\n\t\t\t\t\t\t.into(),\n\t\t\t\t\t\tNodeGraphMessage::RunDocumentGraph.into(),\n\t\t\t\t\t],\n\t\t\t\t});\n\n\t\t\t\tSplineToolFsmState::Drawing\n\t\t\t}\n\t\t\t(SplineToolFsmState::Drawing, SplineToolMessage::DragStop) => {\n\t\t\t\t// The first DragStop event will be ignored to prevent insertion of new point.\n\t\t\t\tif tool_data.extend {\n\t\t\t\t\ttool_data.extend = false;\n\t\t\t\t\treturn SplineToolFsmState::Drawing;\n\t\t\t\t}\n\t\t\t\tlet Some(layer) = tool_data.current_layer else {\n\t\t\t\t\treturn SplineToolFsmState::Ready;\n\t\t\t\t};\n\n\t\t\t\t// Convert snapped document-space position to layer-local space\n\t\t\t\tlet snapped_document = tool_data.snapped_point(document, input, viewport).snapped_point_document;\n\t\t\t\tlet document_to_viewport = document.metadata().document_to_viewport;\n\t\t\t\tlet viewport_pos = document_to_viewport.transform_point2(snapped_document);\n\n\t\t\t\t// For newly created layers, the deferred TransformSet may not yet be reflected\n\t\t\t\t// in the metadata, so compute local position from the known viewport start.\n\t\t\t\ttool_data.next_point = if let Some(start) = tool_data.new_layer_viewport_start {\n\t\t\t\t\tviewport_pos - start\n\t\t\t\t} else {\n\t\t\t\t\tlet transform = document.metadata().transform_to_viewport(layer);\n\t\t\t\t\ttransform.inverse().transform_point2(viewport_pos)\n\t\t\t\t};\n\t\t\t\ttool_data.new_layer_viewport_start = None;\n\n\t\t\t\tif tool_data.points.last().is_none_or(|last_pos| last_pos.1.distance(tool_data.next_point) > DRAG_THRESHOLD) {\n\t\t\t\t\tlet preview_point = tool_data.preview_point;\n\t\t\t\t\textend_spline(tool_data, false, responses);\n\t\t\t\t\ttool_data.preview_point = preview_point;\n\n\t\t\t\t\tif try_merging_lastest_endpoint(document, tool_data).is_some() {\n\t\t\t\t\t\tresponses.add(SplineToolMessage::Confirm);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tSplineToolFsmState::Drawing\n\t\t\t}\n\t\t\t(SplineToolFsmState::Drawing, SplineToolMessage::PointerMove) => {\n\t\t\t\tlet Some(layer) = tool_data.current_layer else { return SplineToolFsmState::Ready };\n\t\t\t\tlet ignore = |cp: PointId| tool_data.preview_point.is_some_and(|pp| pp == cp) || tool_data.points.last().is_some_and(|(ep, _)| *ep == cp);\n\t\t\t\tlet join_point = closest_point(document, input.mouse.position, PATH_JOIN_THRESHOLD, vec![layer].into_iter(), ignore);\n\n\t\t\t\t// Endpoints snapping - closest_point returns local-space positions\n\t\t\t\tif let Some((_, _, point)) = join_point {\n\t\t\t\t\ttool_data.next_point = point;\n\t\t\t\t\ttool_data.snap_manager.clear_indicator();\n\t\t\t\t} else {\n\t\t\t\t\t// Convert snapped document-space position to layer-local space\n\t\t\t\t\tlet snapped_point = tool_data.snapped_point(document, input, viewport);\n\t\t\t\t\tlet document_to_viewport = document.metadata().document_to_viewport;\n\t\t\t\t\tlet viewport_pos = document_to_viewport.transform_point2(snapped_point.snapped_point_document);\n\n\t\t\t\t\t// For newly created layers, the deferred TransformSet may not yet be reflected\n\t\t\t\t\t// in the metadata, so compute local position from the known viewport start.\n\t\t\t\t\ttool_data.next_point = if let Some(start) = tool_data.new_layer_viewport_start {\n\t\t\t\t\t\tviewport_pos - start\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlet transform = document.metadata().transform_to_viewport(layer);\n\t\t\t\t\t\ttransform.inverse().transform_point2(viewport_pos)\n\t\t\t\t\t};\n\t\t\t\t\ttool_data.snap_manager.update_indicator(snapped_point);\n\t\t\t\t}\n\n\t\t\t\textend_spline(tool_data, true, responses);\n\n\t\t\t\t// Auto-panning\n\t\t\t\tlet messages = [SplineToolMessage::PointerOutsideViewport.into(), SplineToolMessage::PointerMove.into()];\n\t\t\t\ttool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses);\n\n\t\t\t\tSplineToolFsmState::Drawing\n\t\t\t}\n\t\t\t(_, SplineToolMessage::PointerMove) => {\n\t\t\t\ttool_data.snap_manager.preview_draw(&SnapData::new(document, input, viewport), input.mouse.position);\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\tself\n\t\t\t}\n\t\t\t(SplineToolFsmState::Drawing, SplineToolMessage::PointerOutsideViewport) => {\n\t\t\t\tif !input.mouse.mouse_keys.contains(MouseKeys::LEFT) {\n\t\t\t\t\treturn self;\n\t\t\t\t}\n\t\t\t\t// Auto-panning\n\t\t\t\tlet _ = tool_data.auto_panning.shift_viewport(input, viewport, responses);\n\n\t\t\t\tSplineToolFsmState::Drawing\n\t\t\t}\n\t\t\t(state, SplineToolMessage::PointerOutsideViewport) => {\n\t\t\t\t// Auto-panning\n\t\t\t\tlet messages = [SplineToolMessage::PointerOutsideViewport.into(), SplineToolMessage::PointerMove.into()];\n\t\t\t\ttool_data.auto_panning.stop(&messages, responses);\n\n\t\t\t\tstate\n\t\t\t}\n\t\t\t(SplineToolFsmState::Drawing, SplineToolMessage::Confirm) => {\n\t\t\t\tif tool_data.points.len() <= 1 {\n\t\t\t\t\tresponses.add(DocumentMessage::AbortTransaction);\n\t\t\t\t\treturn SplineToolFsmState::Ready;\n\t\t\t\t}\n\n\t\t\t\tdelete_preview(tool_data, responses);\n\n\t\t\t\tresponses.add(SplineToolMessage::MergeEndpoints);\n\t\t\t\tSplineToolFsmState::MergingEndpoints\n\t\t\t}\n\t\t\t(SplineToolFsmState::Drawing, SplineToolMessage::Abort) => {\n\t\t\t\tresponses.add(DocumentMessage::AbortTransaction);\n\t\t\t\tSplineToolFsmState::Ready\n\t\t\t}\n\t\t\t(_, SplineToolMessage::WorkingColorChanged) => {\n\t\t\t\tresponses.add(SplineToolMessage::UpdateOptions {\n\t\t\t\t\toptions: SplineOptionsUpdate::WorkingColors(Some(global_tool_data.primary_color), Some(global_tool_data.secondary_color)),\n\t\t\t\t});\n\t\t\t\tself\n\t\t\t}\n\t\t\t_ => self,\n\t\t}\n\t}\n\n\tfn update_hints(&self, responses: &mut VecDeque<Message>) {\n\t\tlet hint_data = match self {\n\t\t\tSplineToolFsmState::Ready => HintData(vec![HintGroup(vec![\n\t\t\t\tHintInfo::mouse(MouseMotion::Lmb, \"Draw Spline\"),\n\t\t\t\tHintInfo::keys([Key::Shift], \"Append to Selected Layer\").prepend_plus(),\n\t\t\t])]),\n\t\t\tSplineToolFsmState::Drawing => HintData(vec![\n\t\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"\"), HintInfo::keys([Key::Escape], \"Cancel\").prepend_slash()]),\n\t\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, \"Extend Spline\")]),\n\t\t\t\tHintGroup(vec![HintInfo::keys([Key::Enter], \"End Spline\")]),\n\t\t\t]),\n\t\t\tSplineToolFsmState::MergingEndpoints => HintData(vec![]),\n\t\t};\n\n\t\thint_data.send_layout(responses);\n\t}\n\n\tfn update_cursor(&self, responses: &mut VecDeque<Message>) {\n\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });\n\t}\n}\n\nfn try_merging_lastest_endpoint(document: &DocumentMessageHandler, tool_data: &mut SplineToolData) -> Option<()> {\n\tif tool_data.points.len() < 2 {\n\t\treturn None;\n\t};\n\tlet (last_endpoint, last_endpoint_position) = tool_data.points.last()?;\n\tlet preview_point = tool_data.preview_point;\n\tlet current_layer = tool_data.current_layer?;\n\n\tlet layers = LayerNodeIdentifier::ROOT_PARENT\n\t\t.descendants(document.metadata())\n\t\t.filter(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]));\n\n\tlet exclude = |p: PointId| preview_point.is_some_and(|pp| pp == p) || *last_endpoint == p;\n\tlet position = document.metadata().transform_to_viewport(current_layer).transform_point2(*last_endpoint_position);\n\n\tlet (layer, endpoint, _) = closest_point(document, position, PATH_JOIN_THRESHOLD, layers, exclude)?;\n\ttool_data.merge_layers.insert(layer);\n\ttool_data.merge_endpoints.push((EndpointPosition::End, endpoint));\n\n\tSome(())\n}\n\nfn extend_spline(tool_data: &mut SplineToolData, show_preview: bool, responses: &mut VecDeque<Message>) {\n\tdelete_preview(tool_data, responses);\n\n\tlet Some(layer) = tool_data.current_layer else { return };\n\n\tlet next_point_pos = tool_data.next_point;\n\tlet next_point_id = PointId::generate();\n\tlet modification_type = VectorModificationType::InsertPoint {\n\t\tid: next_point_id,\n\t\tposition: next_point_pos,\n\t};\n\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\n\tif let Some((last_point_id, _)) = tool_data.points.last() {\n\t\tlet points = [*last_point_id, next_point_id];\n\t\tlet id = SegmentId::generate();\n\t\tlet modification_type = VectorModificationType::InsertSegment { id, points, handles: [None, None] };\n\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\n\t\tif show_preview {\n\t\t\ttool_data.preview_segment = Some(id);\n\t\t}\n\t}\n\n\tif show_preview {\n\t\ttool_data.preview_point = Some(next_point_id);\n\t} else {\n\t\ttool_data.points.push((next_point_id, next_point_pos));\n\t}\n}\n\nfn delete_preview(tool_data: &mut SplineToolData, responses: &mut VecDeque<Message>) {\n\tlet Some(layer) = tool_data.current_layer else { return };\n\n\tif let Some(id) = tool_data.preview_point {\n\t\tlet modification_type = VectorModificationType::RemovePoint { id };\n\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t}\n\tif let Some(id) = tool_data.preview_segment {\n\t\tlet modification_type = VectorModificationType::RemoveSegment { id };\n\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t}\n\n\ttool_data.preview_point = None;\n\ttool_data.preview_segment = None;\n}\n\n#[cfg(test)]\nmod test_spline_tool {\n\tuse crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;\n\tuse crate::messages::tool::tool_messages::spline_tool::find_spline;\n\tuse crate::test_utils::test_prelude::*;\n\tuse glam::DAffine2;\n\tuse graphene_std::vector::PointId;\n\tuse graphene_std::vector::Vector;\n\n\tfn assert_point_positions(vector: &Vector, layer_to_viewport: DAffine2, expected_points: &[DVec2], epsilon: f64) {\n\t\tlet points_in_viewport: Vec<DVec2> = vector\n\t\t\t.point_domain\n\t\t\t.ids()\n\t\t\t.iter()\n\t\t\t.filter_map(|&point_id| {\n\t\t\t\tlet position = vector.point_domain.position_from_id(point_id)?;\n\t\t\t\tSome(layer_to_viewport.transform_point2(position))\n\t\t\t})\n\t\t\t.collect();\n\n\t\t// Verify each point position is close to the expected position\n\t\tfor (i, expected_point) in expected_points.iter().enumerate() {\n\t\t\tlet actual_point = points_in_viewport[i];\n\t\t\tlet distance = (actual_point - *expected_point).length();\n\n\t\t\tassert!(\n\t\t\t\tdistance < epsilon,\n\t\t\t\t\"Point {i} position mismatch: expected {expected_point:?}, got {actual_point:?} (distance: {distance})\"\n\t\t\t);\n\t\t}\n\t}\n\n\t#[tokio::test]\n\tasync fn test_continue_drawing_from_existing_spline() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\n\t\tlet initial_points = [DVec2::new(100., 100.), DVec2::new(200., 150.), DVec2::new(300., 100.)];\n\n\t\teditor.select_tool(ToolType::Spline).await;\n\n\t\tfor &point in &initial_points {\n\t\t\teditor.click_tool(ToolType::Spline, MouseKeys::LEFT, point, ModifierKeys::empty()).await;\n\t\t}\n\n\t\teditor.press(Key::Enter, ModifierKeys::empty()).await;\n\n\t\tlet document = editor.active_document();\n\t\tlet spline_layer = document\n\t\t\t.metadata()\n\t\t\t.all_layers()\n\t\t\t.find(|layer| find_spline(document, *layer).is_some())\n\t\t\t.expect(\"Failed to find a layer with a spline node\");\n\n\t\tlet first_spline_node = find_spline(document, spline_layer).expect(\"Spline node not found in the layer\");\n\n\t\tlet first_vector = document.network_interface.compute_modified_vector(spline_layer).expect(\"Vector not found for the spline layer\");\n\n\t\t// Verify initial spline has correct number of points and segments\n\t\tlet initial_point_count = first_vector.point_domain.ids().len();\n\t\tlet initial_segment_count = first_vector.segment_domain.ids().len();\n\t\tassert_eq!(initial_point_count, 3, \"Expected 3 points in initial spline, found {initial_point_count}\");\n\t\tassert_eq!(initial_segment_count, 2, \"Expected 2 segments in initial spline, found {initial_segment_count}\");\n\n\t\tlet layer_to_viewport = document.metadata().transform_to_viewport(spline_layer);\n\n\t\tlet endpoints: Vec<(PointId, DVec2)> = first_vector\n\t\t\t.anchor_endpoints()\n\t\t\t.filter_map(|point_id| first_vector.point_domain.position_from_id(point_id).map(|pos| (point_id, layer_to_viewport.transform_point2(pos))))\n\t\t\t.collect();\n\n\t\tassert_eq!(endpoints.len(), 2, \"Expected 2 endpoints in the initial spline\");\n\n\t\tlet (_, endpoint_position) = endpoints.first().expect(\"No endpoints found in spline\");\n\n\t\teditor.select_tool(ToolType::Spline).await;\n\t\teditor.click_tool(ToolType::Spline, MouseKeys::LEFT, *endpoint_position, ModifierKeys::empty()).await;\n\n\t\tlet continuation_points = [DVec2::new(400., 150.), DVec2::new(500., 100.)];\n\n\t\tfor &point in &continuation_points {\n\t\t\teditor.click_tool(ToolType::Spline, MouseKeys::LEFT, point, ModifierKeys::empty()).await;\n\t\t}\n\n\t\teditor.press(Key::Enter, ModifierKeys::empty()).await;\n\n\t\tlet document = editor.active_document();\n\t\tlet extended_vector = document\n\t\t\t.network_interface\n\t\t\t.compute_modified_vector(spline_layer)\n\t\t\t.expect(\"Vector not found for the extended spline layer\");\n\n\t\t// Verify extended spline has correct number of points and segments\n\t\tlet extended_point_count = extended_vector.point_domain.ids().len();\n\t\tlet extended_segment_count = extended_vector.segment_domain.ids().len();\n\n\t\tassert_eq!(extended_point_count, 5, \"Expected 5 points in extended spline, found {extended_point_count}\");\n\t\tassert_eq!(extended_segment_count, 4, \"Expected 4 segments in extended spline, found {extended_segment_count}\");\n\n\t\t// Verify the spline node is still the same\n\t\tlet extended_spline_node = find_spline(document, spline_layer).expect(\"Spline node not found after extension\");\n\t\tassert_eq!(first_spline_node, extended_spline_node, \"Spline node changed after extension\");\n\n\t\t// Verify the positions of all points in the extended spline\n\t\tlet layer_to_viewport = document.metadata().transform_to_viewport(spline_layer);\n\n\t\tlet all_expected_points = [initial_points[0], initial_points[1], initial_points[2], continuation_points[0], continuation_points[1]];\n\n\t\tassert_point_positions(&extended_vector, layer_to_viewport, &all_expected_points, 1e-10);\n\t}\n\n\t#[tokio::test]\n\tasync fn test_spline_with_zoomed_view() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\n\t\t// Zooming the viewport\n\t\teditor.handle_message(NavigationMessage::CanvasZoomSet { zoom_factor: 2. }).await;\n\n\t\t// Selecting the spline tool\n\t\teditor.select_tool(ToolType::Spline).await;\n\n\t\t// Adding points by clicking at different positions\n\t\teditor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(50., 50.), ModifierKeys::empty()).await;\n\t\teditor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(100., 50.), ModifierKeys::empty()).await;\n\t\teditor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(150., 100.), ModifierKeys::empty()).await;\n\n\t\t// Finish the spline\n\t\teditor.handle_message(SplineToolMessage::Confirm).await;\n\n\t\t// Evaluate the graph to ensure everything is processed\n\t\tif let Err(e) = editor.eval_graph().await {\n\t\t\tpanic!(\"Graph evaluation failed: {e}\");\n\t\t}\n\n\t\t// Get the layer and vector data\n\t\tlet document = editor.active_document();\n\t\tlet network_interface = &document.network_interface;\n\t\tlet layer = network_interface\n\t\t\t.selected_nodes()\n\t\t\t.selected_visible_and_unlocked_layers(network_interface)\n\t\t\t.next()\n\t\t\t.expect(\"Should have a selected layer\");\n\t\tlet vector = network_interface.compute_modified_vector(layer).expect(\"Should have vector data\");\n\t\tlet layer_to_viewport = document.metadata().transform_to_viewport(layer);\n\n\t\t// Expected points in viewport coordinates\n\t\tlet expected_points = vec![DVec2::new(50., 50.), DVec2::new(100., 50.), DVec2::new(150., 100.)];\n\n\t\t// Assert all points are correctly positioned\n\t\tassert_point_positions(&vector, layer_to_viewport, &expected_points, 1e-10);\n\t}\n\n\t#[tokio::test]\n\tasync fn test_spline_with_panned_view() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\n\t\tlet pan_amount = DVec2::new(200., 150.);\n\t\teditor.handle_message(NavigationMessage::CanvasPan { delta: pan_amount }).await;\n\n\t\teditor.select_tool(ToolType::Spline).await;\n\n\t\t// Add points by clicking at different positions\n\t\teditor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(50., 50.), ModifierKeys::empty()).await;\n\t\teditor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(100., 50.), ModifierKeys::empty()).await;\n\t\teditor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(150., 100.), ModifierKeys::empty()).await;\n\n\t\teditor.handle_message(SplineToolMessage::Confirm).await;\n\n\t\t// Evaluating the graph to ensure everything is processed\n\t\tif let Err(e) = editor.eval_graph().await {\n\t\t\tpanic!(\"Graph evaluation failed: {e}\");\n\t\t}\n\n\t\t// Get the layer and vector data\n\t\tlet document = editor.active_document();\n\t\tlet network_interface = &document.network_interface;\n\t\tlet layer = network_interface\n\t\t\t.selected_nodes()\n\t\t\t.selected_visible_and_unlocked_layers(network_interface)\n\t\t\t.next()\n\t\t\t.expect(\"Should have a selected layer\");\n\t\tlet vector = network_interface.compute_modified_vector(layer).expect(\"Should have vector data\");\n\t\tlet layer_to_viewport = document.metadata().transform_to_viewport(layer);\n\n\t\t// Expected points in viewport coordinates\n\t\tlet expected_points = vec![DVec2::new(50., 50.), DVec2::new(100., 50.), DVec2::new(150., 100.)];\n\n\t\t// Assert all points are correctly positioned\n\t\tassert_point_positions(&vector, layer_to_viewport, &expected_points, 1e-10);\n\t}\n\n\t#[tokio::test]\n\tasync fn test_spline_with_tilted_view() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\n\t\t// Tilt/rotate the viewport (45 degrees)\n\t\teditor.handle_message(NavigationMessage::CanvasTiltSet { angle_radians: 45_f64.to_radians() }).await;\n\t\teditor.select_tool(ToolType::Spline).await;\n\n\t\teditor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(50., 50.), ModifierKeys::empty()).await;\n\t\teditor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(100., 50.), ModifierKeys::empty()).await;\n\t\teditor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(150., 100.), ModifierKeys::empty()).await;\n\n\t\teditor.handle_message(SplineToolMessage::Confirm).await;\n\n\t\t// Evaluating the graph to ensure everything is processed\n\t\tif let Err(e) = editor.eval_graph().await {\n\t\t\tpanic!(\"Graph evaluation failed: {e}\");\n\t\t}\n\n\t\t// Get the layer and vector data\n\t\tlet document = editor.active_document();\n\t\tlet network_interface = &document.network_interface;\n\t\tlet layer = network_interface\n\t\t\t.selected_nodes()\n\t\t\t.selected_visible_and_unlocked_layers(network_interface)\n\t\t\t.next()\n\t\t\t.expect(\"Should have a selected layer\");\n\t\tlet vector = network_interface.compute_modified_vector(layer).expect(\"Should have vector data\");\n\t\tlet layer_to_viewport = document.metadata().transform_to_viewport(layer);\n\n\t\t// Expected points in viewport coordinates\n\t\tlet expected_points = vec![DVec2::new(50., 50.), DVec2::new(100., 50.), DVec2::new(150., 100.)];\n\n\t\t// Assert all points are correctly positioned\n\t\tassert_point_positions(&vector, layer_to_viewport, &expected_points, 1e-10);\n\t}\n\n\t#[tokio::test]\n\tasync fn test_spline_with_combined_transformations() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\n\t\t// Applying multiple transformations\n\t\teditor.handle_message(NavigationMessage::CanvasZoomSet { zoom_factor: 1.5 }).await;\n\t\teditor.handle_message(NavigationMessage::CanvasPan { delta: DVec2::new(100., 75.) }).await;\n\t\teditor.handle_message(NavigationMessage::CanvasTiltSet { angle_radians: 30_f64.to_radians() }).await;\n\n\t\teditor.select_tool(ToolType::Spline).await;\n\n\t\teditor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(50., 50.), ModifierKeys::empty()).await;\n\t\teditor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(100., 50.), ModifierKeys::empty()).await;\n\t\teditor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(150., 100.), ModifierKeys::empty()).await;\n\n\t\teditor.handle_message(SplineToolMessage::Confirm).await;\n\t\tif let Err(e) = editor.eval_graph().await {\n\t\t\tpanic!(\"Graph evaluation failed: {e}\");\n\t\t}\n\n\t\t// Get the layer and vector data\n\t\tlet document = editor.active_document();\n\t\tlet network_interface = &document.network_interface;\n\t\tlet layer = network_interface\n\t\t\t.selected_nodes()\n\t\t\t.selected_visible_and_unlocked_layers(network_interface)\n\t\t\t.next()\n\t\t\t.expect(\"Should have a selected layer\");\n\t\tlet vector = network_interface.compute_modified_vector(layer).expect(\"Should have vector data\");\n\t\tlet layer_to_viewport = document.metadata().transform_to_viewport(layer);\n\n\t\t// Expected points in viewport coordinates\n\t\tlet expected_points = vec![DVec2::new(50., 50.), DVec2::new(100., 50.), DVec2::new(150., 100.)];\n\n\t\t// Assert all points are correctly positioned\n\t\tassert_point_positions(&vector, layer_to_viewport, &expected_points, 1e-10);\n\t}\n\n\t#[tokio::test]\n\tasync fn test_spline_tool_with_transformed_artboard() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\n\t\teditor.drag_tool(ToolType::Artboard, 0., 0., 500., 500., ModifierKeys::empty()).await;\n\t\tlet document = editor.active_document();\n\t\tlet artboard_layer = document.network_interface.selected_nodes().selected_layers(document.metadata()).next().unwrap();\n\n\t\teditor\n\t\t\t.handle_message(GraphOperationMessage::TransformSet {\n\t\t\t\tlayer: artboard_layer,\n\t\t\t\ttransform: DAffine2::from_scale_angle_translation(DVec2::new(1.5, 1.2), 30_f64.to_radians(), DVec2::new(50., 25.)),\n\t\t\t\ttransform_in: TransformIn::Local,\n\t\t\t\tskip_rerender: false,\n\t\t\t})\n\t\t\t.await;\n\n\t\tlet spline_points = [DVec2::new(100., 100.), DVec2::new(200., 150.), DVec2::new(300., 100.)];\n\n\t\teditor.draw_spline(&spline_points).await;\n\n\t\tlet document = editor.active_document();\n\n\t\tlet mut layers = document.metadata().all_layers();\n\t\tlayers.next();\n\n\t\tlet spline_layer = layers.next().expect(\"Failed to find the spline layer\");\n\t\tassert!(find_spline(document, spline_layer).is_some(), \"Spline node not found in the layer\");\n\n\t\tlet vector = document.network_interface.compute_modified_vector(spline_layer).expect(\"Vector not found for the spline layer\");\n\n\t\t// Verify we have the correct number of points and segments\n\t\tlet point_count = vector.point_domain.ids().len();\n\t\tlet segment_count = vector.segment_domain.ids().len();\n\n\t\tassert_eq!(point_count, 3, \"Expected 3 points in the spline, found {point_count}\");\n\t\tassert_eq!(segment_count, 2, \"Expected 2 segments in the spline, found {segment_count}\");\n\n\t\tlet layer_to_viewport = document.metadata().transform_to_viewport(spline_layer);\n\n\t\tassert_point_positions(&vector, layer_to_viewport, &spline_points, 1e-10);\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/tool_messages/text_tool.rs",
    "content": "#![allow(clippy::too_many_arguments)]\n\nuse super::tool_prelude::*;\nuse crate::consts::{COLOR_OVERLAY_BLUE_05, COLOR_OVERLAY_RED, DRAG_THRESHOLD};\nuse crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;\nuse crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;\nuse crate::messages::portfolio::document::overlays::utility_types::OverlayContext;\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::portfolio::document::utility_types::network_interface::InputConnector;\nuse crate::messages::portfolio::utility_types::{FontCatalog, FontCatalogStyle, PersistentData};\nuse crate::messages::tool::common_functionality::auto_panning::AutoPanning;\nuse crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};\nuse crate::messages::tool::common_functionality::graph_modification_utils::{self, is_layer_fed_by_node_of_name};\nuse crate::messages::tool::common_functionality::resize::Resize;\nuse crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData};\nuse crate::messages::tool::common_functionality::transformation_cage::*;\nuse crate::messages::tool::common_functionality::utility_functions::text_bounding_box;\nuse crate::messages::tool::utility_types::ToolRefreshOptions;\nuse graph_craft::document::value::TaggedValue;\nuse graph_craft::document::{NodeId, NodeInput};\nuse graphene_std::renderer::Quad;\nuse graphene_std::text::{Font, FontCache, TextAlign, TypesettingConfig, lines_clipping};\nuse graphene_std::vector::style::Fill;\nuse graphene_std::{Color, NodeInputDecleration};\n\n#[derive(Default, ExtractField)]\npub struct TextTool {\n\tfsm_state: TextToolFsmState,\n\ttool_data: TextToolData,\n\toptions: TextOptions,\n}\n\npub struct TextOptions {\n\tfont_size: f64,\n\tline_height_ratio: f64,\n\tcharacter_spacing: f64,\n\tfont: Font,\n\tfill: ToolColorOptions,\n\ttilt: f64,\n\talign: TextAlign,\n}\n\nimpl Default for TextOptions {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tfont_size: 24.,\n\t\t\tline_height_ratio: 1.2,\n\t\t\tcharacter_spacing: 0.,\n\t\t\tfont: Font::new(graphene_std::consts::DEFAULT_FONT_FAMILY.into(), graphene_std::consts::DEFAULT_FONT_STYLE.into()),\n\t\t\tfill: ToolColorOptions::new_primary(),\n\t\t\ttilt: 0.,\n\t\t\talign: TextAlign::default(),\n\t\t}\n\t}\n}\n\n#[impl_message(Message, ToolMessage, Text)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum TextToolMessage {\n\t// Standard messages\n\tAbort,\n\tWorkingColorChanged,\n\tOverlays { context: OverlayContext },\n\n\t// Tool-specific messages\n\tDragStart,\n\tDragStop,\n\tEditSelected,\n\tInteract,\n\tPointerMove { center: Key, lock_ratio: Key },\n\tPointerOutsideViewport { center: Key, lock_ratio: Key },\n\tTextChange { new_text: String, is_left_or_right_click: bool },\n\tUpdateBounds { new_text: String },\n\tUpdateOptions { options: TextOptionsUpdate },\n\tRefreshEditingFontData,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum TextOptionsUpdate {\n\tFillColor(Option<Color>),\n\tFillColorType(ToolColorType),\n\tFont { font: Font },\n\tFontSize(f64),\n\tLineHeightRatio(f64),\n\tAlign(TextAlign),\n\tWorkingColors(Option<Color>, Option<Color>),\n}\n\nimpl ToolMetadata for TextTool {\n\tfn icon_name(&self) -> String {\n\t\t\"VectorTextTool\".into()\n\t}\n\tfn tooltip_label(&self) -> String {\n\t\t\"Text Tool\".into()\n\t}\n\tfn tool_type(&self) -> crate::messages::tool::utility_types::ToolType {\n\t\tToolType::Text\n\t}\n}\n\nfn create_text_widgets(tool: &TextTool, font_catalog: &FontCatalog) -> Vec<WidgetInstance> {\n\tfn update_options(font: Font, commit_style: Option<String>) -> impl Fn(&()) -> Message + Clone {\n\t\tlet mut font = font;\n\t\tif let Some(style) = commit_style {\n\t\t\tfont.font_style = style;\n\t\t}\n\n\t\tmove |_| {\n\t\t\tTextToolMessage::UpdateOptions {\n\t\t\t\toptions: TextOptionsUpdate::Font { font: font.clone() },\n\t\t\t}\n\t\t\t.into()\n\t\t}\n\t}\n\n\tlet font = DropdownInput::new(vec![\n\t\tfont_catalog\n\t\t\t.0\n\t\t\t.iter()\n\t\t\t.map(|family| {\n\t\t\t\tlet font = Font::new(family.name.clone(), tool.options.font.font_style.clone());\n\t\t\t\tlet commit_style = font_catalog.find_font_style_in_catalog(&tool.options.font).map(|style| style.to_named_style());\n\t\t\t\tlet update = update_options(font.clone(), None);\n\t\t\t\tlet commit = update_options(font, commit_style);\n\n\t\t\t\tMenuListEntry::new(family.name.clone())\n\t\t\t\t\t.label(family.name.clone())\n\t\t\t\t\t.font(family.closest_style(400, false).preview_url(&family.name))\n\t\t\t\t\t.on_update(update)\n\t\t\t\t\t.on_commit(commit)\n\t\t\t})\n\t\t\t.collect::<Vec<_>>(),\n\t])\n\t.selected_index(font_catalog.0.iter().position(|family| family.name == tool.options.font.font_family).map(|i| i as u32))\n\t.virtual_scrolling(true)\n\t.widget_instance();\n\n\tlet style = DropdownInput::new({\n\t\tfont_catalog\n\t\t\t.0\n\t\t\t.iter()\n\t\t\t.find(|family| family.name == tool.options.font.font_family)\n\t\t\t.map(|family| {\n\t\t\t\tlet build_entry = |style: &FontCatalogStyle| {\n\t\t\t\t\tlet font_style = style.to_named_style();\n\n\t\t\t\t\tlet font = Font::new(tool.options.font.font_family.clone(), font_style.clone());\n\t\t\t\t\tlet commit_style = font_catalog.find_font_style_in_catalog(&tool.options.font).map(|style| style.to_named_style());\n\t\t\t\t\tlet update = update_options(font.clone(), None);\n\t\t\t\t\tlet commit = update_options(font, commit_style);\n\n\t\t\t\t\tMenuListEntry::new(font_style.clone()).on_update(update).on_commit(commit).label(font_style)\n\t\t\t\t};\n\n\t\t\t\tvec![\n\t\t\t\t\tfamily.styles.iter().filter(|style| !style.italic).map(build_entry).collect::<Vec<_>>(),\n\t\t\t\t\tfamily.styles.iter().filter(|style| style.italic).map(build_entry).collect::<Vec<_>>(),\n\t\t\t\t]\n\t\t\t})\n\t\t\t.filter(|styles| !styles.is_empty())\n\t\t\t.unwrap_or_default()\n\t})\n\t.selected_index(\n\t\tfont_catalog\n\t\t\t.0\n\t\t\t.iter()\n\t\t\t.find(|family| family.name == tool.options.font.font_family)\n\t\t\t.and_then(|family| {\n\t\t\t\tlet not_italic = family.styles.iter().filter(|style| !style.italic);\n\t\t\t\tlet italic = family.styles.iter().filter(|style| style.italic);\n\t\t\t\tnot_italic\n\t\t\t\t\t.chain(italic)\n\t\t\t\t\t.position(|style| Some(style) == font_catalog.find_font_style_in_catalog(&tool.options.font).as_ref())\n\t\t\t})\n\t\t\t.map(|i| i as u32),\n\t)\n\t.widget_instance();\n\n\tlet size = NumberInput::new(Some(tool.options.font_size))\n\t\t.unit(\" px\")\n\t\t.label(\"Size\")\n\t\t.int()\n\t\t.min(1.)\n\t\t.max((1_u64 << f64::MANTISSA_DIGITS) as f64)\n\t\t.on_update(|number_input: &NumberInput| {\n\t\t\tTextToolMessage::UpdateOptions {\n\t\t\t\toptions: TextOptionsUpdate::FontSize(number_input.value.unwrap()),\n\t\t\t}\n\t\t\t.into()\n\t\t})\n\t\t.widget_instance();\n\tlet line_height_ratio = NumberInput::new(Some(tool.options.line_height_ratio))\n\t\t.label(\"Line Height\")\n\t\t.int()\n\t\t.min(0.)\n\t\t.max((1_u64 << f64::MANTISSA_DIGITS) as f64)\n\t\t.step(0.1)\n\t\t.on_update(|number_input: &NumberInput| {\n\t\t\tTextToolMessage::UpdateOptions {\n\t\t\t\toptions: TextOptionsUpdate::LineHeightRatio(number_input.value.unwrap()),\n\t\t\t}\n\t\t\t.into()\n\t\t})\n\t\t.widget_instance();\n\tlet align_entries: Vec<_> = [TextAlign::Left, TextAlign::Center, TextAlign::Right, TextAlign::JustifyLeft]\n\t\t.into_iter()\n\t\t.map(|align| {\n\t\t\tRadioEntryData::new(format!(\"{align:?}\")).label(align.to_string()).on_update(move |_| {\n\t\t\t\tTextToolMessage::UpdateOptions {\n\t\t\t\t\toptions: TextOptionsUpdate::Align(align),\n\t\t\t\t}\n\t\t\t\t.into()\n\t\t\t})\n\t\t})\n\t\t.collect();\n\tlet align = RadioInput::new(align_entries).selected_index(Some(tool.options.align as u32)).widget_instance();\n\tvec![\n\t\tfont,\n\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\tstyle,\n\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\tsize,\n\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\tline_height_ratio,\n\t\tSeparator::new(SeparatorStyle::Related).widget_instance(),\n\t\talign,\n\t]\n}\n\nimpl ToolRefreshOptions for TextTool {\n\tfn refresh_options(&self, responses: &mut VecDeque<Message>, persistent_data: &PersistentData) {\n\t\tself.send_layout(responses, LayoutTarget::ToolOptions, &persistent_data.font_catalog);\n\t}\n}\n\nimpl TextTool {\n\tfn send_layout(&self, responses: &mut VecDeque<Message>, layout_target: LayoutTarget, font_catalog: &FontCatalog) {\n\t\tresponses.add(LayoutMessage::SendLayout {\n\t\t\tlayout: self.layout(font_catalog),\n\t\t\tlayout_target,\n\t\t});\n\t}\n\n\tfn layout(&self, font_catalog: &FontCatalog) -> Layout {\n\t\tlet mut widgets = create_text_widgets(self, font_catalog);\n\n\t\twidgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());\n\n\t\twidgets.append(&mut self.options.fill.create_widgets(\n\t\t\t\"Fill\",\n\t\t\ttrue,\n\t\t\t|_| {\n\t\t\t\tTextToolMessage::UpdateOptions {\n\t\t\t\t\toptions: TextOptionsUpdate::FillColor(None),\n\t\t\t\t}\n\t\t\t\t.into()\n\t\t\t},\n\t\t\t|color_type: ToolColorType| {\n\t\t\t\tWidgetCallback::new(move |_| {\n\t\t\t\t\tTextToolMessage::UpdateOptions {\n\t\t\t\t\t\toptions: TextOptionsUpdate::FillColorType(color_type.clone()),\n\t\t\t\t\t}\n\t\t\t\t\t.into()\n\t\t\t\t})\n\t\t\t},\n\t\t\t|color: &ColorInput| {\n\t\t\t\tTextToolMessage::UpdateOptions {\n\t\t\t\t\toptions: TextOptionsUpdate::FillColor(color.value.as_solid().map(|color| color.to_linear_srgb())),\n\t\t\t\t}\n\t\t\t\t.into()\n\t\t\t},\n\t\t));\n\n\t\tLayout(vec![LayoutGroup::row(widgets)])\n\t}\n}\n\n#[message_handler_data]\nimpl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for TextTool {\n\tfn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {\n\t\tlet ToolMessage::Text(TextToolMessage::UpdateOptions { options }) = message else {\n\t\t\tself.fsm_state.process_event(message, &mut self.tool_data, context, &self.options, responses, true);\n\t\t\treturn;\n\t\t};\n\t\tmatch options {\n\t\t\tTextOptionsUpdate::Font { font } => {\n\t\t\t\tself.options.font = font;\n\t\t\t}\n\t\t\tTextOptionsUpdate::FontSize(font_size) => self.options.font_size = font_size,\n\t\t\tTextOptionsUpdate::LineHeightRatio(line_height_ratio) => self.options.line_height_ratio = line_height_ratio,\n\t\t\tTextOptionsUpdate::Align(align) => self.options.align = align,\n\t\t\tTextOptionsUpdate::FillColor(color) => {\n\t\t\t\tself.options.fill.custom_color = color;\n\t\t\t\tself.options.fill.color_type = ToolColorType::Custom;\n\t\t\t}\n\t\t\tTextOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,\n\t\t\tTextOptionsUpdate::WorkingColors(primary, secondary) => {\n\t\t\t\tself.options.fill.primary_working_color = primary;\n\t\t\t\tself.options.fill.secondary_working_color = secondary;\n\t\t\t}\n\t\t}\n\n\t\tself.send_layout(responses, LayoutTarget::ToolOptions, &context.persistent_data.font_catalog);\n\t}\n\n\tfn actions(&self) -> ActionList {\n\t\tmatch self.fsm_state {\n\t\t\tTextToolFsmState::Ready => actions!(TextToolMessageDiscriminant;\n\t\t\t\tDragStart,\n\t\t\t\tPointerOutsideViewport,\n\t\t\t\tPointerMove,\n\t\t\t),\n\t\t\tTextToolFsmState::Editing => actions!(TextToolMessageDiscriminant;\n\t\t\t\tDragStart,\n\t\t\t\tAbort,\n\t\t\t),\n\t\t\tTextToolFsmState::Placing | TextToolFsmState::Dragging => actions!(TextToolMessageDiscriminant;\n\t\t\t\tDragStop,\n\t\t\t\tAbort,\n\t\t\t\tPointerMove,\n\t\t\t\tPointerOutsideViewport,\n\t\t\t),\n\t\t\tTextToolFsmState::ResizingBounds => actions!(TextToolMessageDiscriminant;\n\t\t\t\tDragStop,\n\t\t\t\tAbort,\n\t\t\t\tPointerMove,\n\t\t\t\tPointerOutsideViewport,\n\t\t\t),\n\t\t}\n\t}\n}\n\nimpl ToolTransition for TextTool {\n\tfn event_to_message_map(&self) -> EventToMessageMap {\n\t\tEventToMessageMap {\n\t\t\tcanvas_transformed: None,\n\t\t\ttool_abort: Some(TextToolMessage::Abort.into()),\n\t\t\tworking_color_changed: Some(TextToolMessage::WorkingColorChanged.into()),\n\t\t\toverlay_provider: Some(|context| TextToolMessage::Overlays { context }.into()),\n\t\t\t..Default::default()\n\t\t}\n\t}\n}\n\n#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]\nenum TextToolFsmState {\n\t/// The tool is ready to place or edit text.\n\t#[default]\n\tReady,\n\t/// The user is typing in the interactive viewport text area.\n\tEditing,\n\t/// The user is dragging to create a new text area.\n\tPlacing,\n\t/// The user is dragging an existing text layer to move it.\n\tDragging,\n\t/// The user is dragging to resize the text area.\n\tResizingBounds,\n}\n\n#[derive(Clone, Debug)]\npub struct EditingText {\n\ttext: String,\n\tfont: Font,\n\ttypesetting: TypesettingConfig,\n\tcolor: Option<Color>,\n\ttransform: DAffine2,\n}\n\n#[derive(Clone, Debug, Copy)]\nstruct ResizingLayer {\n\tid: LayerNodeIdentifier,\n\t/// The transform of the text layer in document space at the start of the transformation.\n\toriginal_transform: DAffine2,\n}\n\n#[derive(Clone, Debug, Default)]\nstruct TextToolData {\n\tlayer: LayerNodeIdentifier,\n\tediting_text: Option<EditingText>,\n\tnew_text: String,\n\tdrag_start: DVec2,\n\tdrag_current: DVec2,\n\tresize: Resize,\n\tauto_panning: AutoPanning,\n\t// Since the overlays must be drawn without knowledge of the inputs\n\tcached_resize_bounds: [DVec2; 2],\n\tbounding_box_manager: Option<BoundingBoxManager>,\n\tsnap_candidates: Vec<SnapCandidatePoint>,\n\t// TODO: Handle multiple layers in the future\n\tlayer_dragging: Option<ResizingLayer>,\n}\n\nimpl TextToolData {\n\tfn delete_empty_layer(&mut self, font_cache: &FontCache, responses: &mut VecDeque<Message>) -> TextToolFsmState {\n\t\t// Remove the editable textbox UI first\n\t\tself.set_editing(false, font_cache, responses);\n\n\t\t// Delete the empty text layer and update the graph\n\t\tresponses.add(NodeGraphMessage::DeleteNodes {\n\t\t\tnode_ids: vec![self.layer.to_node()],\n\t\t\tdelete_children: true,\n\t\t});\n\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\n\t\tTextToolFsmState::Ready\n\t}\n\n\t/// Set the editing state of the currently modifying layer\n\tfn set_editing(&self, editable: bool, font_cache: &FontCache, responses: &mut VecDeque<Message>) {\n\t\tif let Some(editing_text) = self.editing_text.as_ref().filter(|_| editable) {\n\t\t\tresponses.add(FrontendMessage::DisplayEditableTextbox {\n\t\t\t\ttext: editing_text.text.clone(),\n\t\t\t\tline_height_ratio: editing_text.typesetting.line_height_ratio,\n\t\t\t\tfont_size: editing_text.typesetting.font_size,\n\t\t\t\tcolor: editing_text.color.map_or(\"#000000\".to_string(), |color| format!(\"#{}\", color.to_rgba_hex_srgb())),\n\t\t\t\tfont_data: font_cache.get(&editing_text.font).map(|(data, _)| data.clone()).unwrap_or_default().into(),\n\t\t\t\ttransform: editing_text.transform.to_cols_array(),\n\t\t\t\tmax_width: editing_text.typesetting.max_width,\n\t\t\t\tmax_height: editing_text.typesetting.max_height,\n\t\t\t\talign: editing_text.typesetting.align,\n\t\t\t});\n\t\t} else {\n\t\t\t// Check if DisplayRemoveEditableTextbox is already in the responses queue\n\t\t\tlet has_remove_textbox = responses.iter().any(|msg| matches!(msg, Message::Frontend(FrontendMessage::DisplayRemoveEditableTextbox)));\n\t\t\tresponses.add(FrontendMessage::DisplayRemoveEditableTextbox);\n\n\t\t\tif has_remove_textbox {\n\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes: Vec::new() });\n\t\t\t}\n\t\t}\n\t}\n\n\tfn load_layer_text_node(&mut self, document: &DocumentMessageHandler) -> Option<()> {\n\t\tlet transform = document.metadata().transform_to_viewport(self.layer);\n\t\tlet color = graph_modification_utils::get_fill_color(self.layer, &document.network_interface).unwrap_or(Color::BLACK);\n\t\tlet (text, font, typesetting, _) = graph_modification_utils::get_text(self.layer, &document.network_interface)?;\n\t\tself.editing_text = Some(EditingText {\n\t\t\ttext: text.clone(),\n\t\t\tfont: font.clone(),\n\t\t\ttypesetting,\n\t\t\tcolor: Some(color),\n\t\t\ttransform,\n\t\t});\n\t\tself.new_text.clone_from(text);\n\t\tSome(())\n\t}\n\n\tfn start_editing_layer(&mut self, layer: LayerNodeIdentifier, tool_state: TextToolFsmState, document: &DocumentMessageHandler, font_cache: &FontCache, responses: &mut VecDeque<Message>) {\n\t\tif layer == LayerNodeIdentifier::ROOT_PARENT {\n\t\t\tlog::error!(\"Cannot edit ROOT_PARENT in TextTooLData\")\n\t\t}\n\n\t\tif tool_state == TextToolFsmState::Editing {\n\t\t\tself.set_editing(false, font_cache, responses);\n\t\t}\n\n\t\tself.layer = layer;\n\t\tif self.load_layer_text_node(document).is_some() {\n\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\n\t\t\tself.set_editing(true, font_cache, responses);\n\n\t\t\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![self.layer.to_node()] });\n\t\t\t// Make the rendered text invisible while editing\n\t\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\t\tinput_connector: InputConnector::node(graph_modification_utils::get_text_id(self.layer, &document.network_interface).unwrap(), 1),\n\t\t\t\tinput: NodeInput::value(TaggedValue::String(\"\".to_string()), false),\n\t\t\t});\n\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t};\n\t}\n\n\tfn new_text(&mut self, document: &DocumentMessageHandler, editing_text: EditingText, font_cache: &FontCache, responses: &mut VecDeque<Message>) {\n\t\t// Create new text\n\t\tself.new_text = String::new();\n\t\tresponses.add(DocumentMessage::AddTransaction);\n\n\t\tself.layer = LayerNodeIdentifier::new_unchecked(NodeId::new());\n\n\t\tresponses.add(PortfolioMessage::LoadFontData { font: editing_text.font.clone() });\n\t\tresponses.add(GraphOperationMessage::NewTextLayer {\n\t\t\tid: self.layer.to_node(),\n\t\t\ttext: String::new(),\n\t\t\tfont: editing_text.font.clone(),\n\t\t\ttypesetting: editing_text.typesetting,\n\t\t\tparent: document.new_layer_parent(true),\n\t\t\tinsert_index: 0,\n\t\t});\n\t\tresponses.add(GraphOperationMessage::FillSet {\n\t\t\tlayer: self.layer,\n\t\t\tfill: if let Some(color) = editing_text.color { Fill::Solid(color.to_gamma_srgb()) } else { Fill::None },\n\t\t});\n\t\tlet transform = editing_text.transform;\n\t\tself.editing_text = Some(editing_text);\n\n\t\tself.set_editing(true, font_cache, responses);\n\n\t\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![self.layer.to_node()] });\n\n\t\t// Defer TransformSet until after the graph has run so that downstream_transform_to_viewport\n\t\t// has correct metadata for the new layer (needed for proper placement in transformed parents).\n\t\tlet layer = self.layer;\n\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\tresponses.add(DeferMessage::AfterGraphRun {\n\t\t\tmessages: vec![\n\t\t\t\tGraphOperationMessage::TransformSet {\n\t\t\t\t\tlayer,\n\t\t\t\t\ttransform,\n\t\t\t\t\ttransform_in: TransformIn::Viewport,\n\t\t\t\t\tskip_rerender: false,\n\t\t\t\t}\n\t\t\t\t.into(),\n\t\t\t\tNodeGraphMessage::RunDocumentGraph.into(),\n\t\t\t],\n\t\t});\n\t}\n\n\tfn check_click(document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, font_cache: &FontCache) -> Option<LayerNodeIdentifier> {\n\t\tdocument\n\t\t\t.metadata()\n\t\t\t.all_layers()\n\t\t\t.filter(|&layer| is_layer_fed_by_node_of_name(layer, &document.network_interface, &DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER)))\n\t\t\t.find(|&layer| {\n\t\t\t\tlet transformed_quad = document.metadata().transform_to_viewport(layer) * text_bounding_box(layer, document, font_cache);\n\t\t\t\tlet mouse = DVec2::new(input.mouse.position.x, input.mouse.position.y);\n\n\t\t\t\ttransformed_quad.contains(mouse)\n\t\t\t})\n\t}\n\n\tfn get_snap_candidates(&mut self, document: &DocumentMessageHandler, font_cache: &FontCache) {\n\t\tself.snap_candidates.clear();\n\n\t\tif let Some(ResizingLayer { id, .. }) = self.layer_dragging {\n\t\t\tlet quad = document.metadata().transform_to_document(id) * text_bounding_box(id, document, font_cache);\n\t\t\tsnapping::get_bbox_points(quad, &mut self.snap_candidates, snapping::BBoxSnapValues::BOUNDING_BOX, document);\n\t\t}\n\t}\n}\n\nfn can_edit_selected(document: &DocumentMessageHandler) -> Option<LayerNodeIdentifier> {\n\tlet selected_nodes = document.network_interface.selected_nodes();\n\tlet mut selected_layers = selected_nodes.selected_layers(document.metadata());\n\tlet layer = selected_layers.next()?;\n\n\t// Check that only one layer is selected\n\tif selected_layers.next().is_some() {\n\t\treturn None;\n\t}\n\n\tif !is_layer_fed_by_node_of_name(layer, &document.network_interface, &DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER)) {\n\t\treturn None;\n\t}\n\n\tSome(layer)\n}\n\nimpl Fsm for TextToolFsmState {\n\ttype ToolData = TextToolData;\n\ttype ToolOptions = TextOptions;\n\n\tfn transition(\n\t\tself,\n\t\tevent: ToolMessage,\n\t\ttool_data: &mut Self::ToolData,\n\t\ttransition_data: &mut ToolActionMessageContext,\n\t\ttool_options: &Self::ToolOptions,\n\t\tresponses: &mut VecDeque<Message>,\n\t) -> Self {\n\t\tlet ToolActionMessageContext {\n\t\t\tdocument,\n\t\t\tglobal_tool_data,\n\t\t\tinput,\n\t\t\tpersistent_data,\n\t\t\tviewport,\n\t\t\t..\n\t\t} = transition_data;\n\t\tlet font_cache = &persistent_data.font_cache;\n\t\tlet fill_color = COLOR_OVERLAY_BLUE_05;\n\n\t\tlet ToolMessage::Text(event) = event else { return self };\n\t\tmatch (self, event) {\n\t\t\t(TextToolFsmState::Editing, TextToolMessage::Overlays { context: mut overlay_context }) => {\n\t\t\t\tlet transform = document.metadata().transform_to_viewport(tool_data.layer).to_cols_array();\n\t\t\t\tresponses.add(FrontendMessage::DisplayEditableTextboxTransform { transform });\n\t\t\t\tif let Some(editing_text) = tool_data.editing_text.as_mut() {\n\t\t\t\t\tlet far = graphene_std::text::bounding_box(&tool_data.new_text, &editing_text.font, font_cache, editing_text.typesetting, false);\n\t\t\t\t\tif far.x != 0. && far.y != 0. {\n\t\t\t\t\t\tlet quad = Quad::from_box([DVec2::ZERO, far]);\n\t\t\t\t\t\tlet transformed_quad = document.metadata().transform_to_viewport(tool_data.layer) * quad;\n\t\t\t\t\t\toverlay_context.quad(transformed_quad, None, Some(fill_color));\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tTextToolFsmState::Editing\n\t\t\t}\n\t\t\t(_, TextToolMessage::Overlays { context: mut overlay_context }) => {\n\t\t\t\tif matches!(self, Self::Placing) {\n\t\t\t\t\t// Get the updated selection box bounds\n\t\t\t\t\tlet quad = Quad::from_box(tool_data.cached_resize_bounds);\n\n\t\t\t\t\t// Draw a bounding box on the layers to be selected\n\t\t\t\t\tfor layer in document.intersect_quad_no_artboards(quad, viewport) {\n\t\t\t\t\t\toverlay_context.quad(Quad::from_box(document.metadata().bounding_box_viewport(layer).unwrap_or([DVec2::ZERO; 2])), None, Some(fill_color));\n\t\t\t\t\t}\n\n\t\t\t\t\toverlay_context.quad(quad, None, Some(fill_color));\n\t\t\t\t}\n\n\t\t\t\t// TODO: implement bounding box for multiple layers\n\t\t\t\tlet selected = document.network_interface.selected_nodes();\n\t\t\t\tlet mut all_layers = selected.selected_visible_and_unlocked_layers(&document.network_interface);\n\t\t\t\tlet layer = all_layers.find(|layer| is_layer_fed_by_node_of_name(*layer, &document.network_interface, &DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER)));\n\t\t\t\tlet bounds = layer.map(|layer| text_bounding_box(layer, document, font_cache));\n\t\t\t\tlet layer_transform = layer.map(|layer| document.metadata().transform_to_viewport(layer)).unwrap_or(DAffine2::IDENTITY);\n\n\t\t\t\tif layer.is_none() || bounds.is_none() || layer_transform.matrix2.determinant() == 0. {\n\t\t\t\t\treturn self;\n\t\t\t\t}\n\n\t\t\t\tif overlay_context.visibility_settings.transform_cage() {\n\t\t\t\t\tif let Some(bounds) = bounds {\n\t\t\t\t\t\tlet bounding_box_manager = tool_data.bounding_box_manager.get_or_insert(BoundingBoxManager::default());\n\t\t\t\t\t\tbounding_box_manager.bounds = [bounds.0[0], bounds.0[2]];\n\t\t\t\t\t\tbounding_box_manager.transform = layer_transform;\n\n\t\t\t\t\t\tbounding_box_manager.render_quad(&mut overlay_context);\n\t\t\t\t\t\t// Draw red overlay if text is clipped\n\t\t\t\t\t\tlet transformed_quad = layer_transform * bounds;\n\t\t\t\t\t\tif let Some((text, font, typesetting, _)) = graph_modification_utils::get_text(layer.unwrap(), &document.network_interface)\n\t\t\t\t\t\t\t&& lines_clipping(text.as_str(), font, font_cache, typesetting)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\toverlay_context.line(transformed_quad.0[2], transformed_quad.0[3], Some(COLOR_OVERLAY_RED), Some(3.));\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbounding_box_manager.render_overlays(&mut overlay_context, false);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\ttool_data.bounding_box_manager.take();\n\t\t\t\t}\n\n\t\t\t\ttool_data.resize.snap_manager.draw_overlays(SnapData::new(document, input, viewport), &mut overlay_context);\n\n\t\t\t\tself\n\t\t\t}\n\t\t\t(state, TextToolMessage::EditSelected) => {\n\t\t\t\tif let Some(layer) = can_edit_selected(document) {\n\t\t\t\t\ttool_data.start_editing_layer(layer, state, document, font_cache, responses);\n\t\t\t\t\treturn TextToolFsmState::Editing;\n\t\t\t\t}\n\n\t\t\t\tstate\n\t\t\t}\n\t\t\t(TextToolFsmState::Ready, TextToolMessage::DragStart) => {\n\t\t\t\ttool_data.resize.start(document, input, viewport);\n\t\t\t\ttool_data.cached_resize_bounds = [tool_data.resize.viewport_drag_start(document); 2];\n\t\t\t\ttool_data.drag_start = input.mouse.position;\n\t\t\t\ttool_data.drag_current = input.mouse.position;\n\n\t\t\t\tlet dragging_bounds = tool_data.bounding_box_manager.as_mut().and_then(|bounding_box| {\n\t\t\t\t\tlet edges = bounding_box.check_selected_edges(input.mouse.position);\n\n\t\t\t\t\tbounding_box.selected_edges = edges.map(|(top, bottom, left, right)| {\n\t\t\t\t\t\tlet selected_edges = SelectedEdges::new(top, bottom, left, right, bounding_box.bounds);\n\t\t\t\t\t\tbounding_box.opposite_pivot = selected_edges.calculate_pivot();\n\t\t\t\t\t\tselected_edges\n\t\t\t\t\t});\n\n\t\t\t\t\tedges\n\t\t\t\t});\n\n\t\t\t\tlet selected = document.network_interface.selected_nodes();\n\t\t\t\tlet mut all_selected = selected.selected_visible_and_unlocked_layers(&document.network_interface);\n\t\t\t\tlet selected = all_selected.find(|layer| is_layer_fed_by_node_of_name(*layer, &document.network_interface, &DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER)));\n\n\t\t\t\tif dragging_bounds.is_some() {\n\t\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\n\t\t\t\t\t// Set the original transform\n\t\t\t\t\tif let Some(id) = selected {\n\t\t\t\t\t\tlet original_transform = document.metadata().transform_to_document(id);\n\t\t\t\t\t\ttool_data.layer_dragging = Some(ResizingLayer { id, original_transform });\n\t\t\t\t\t}\n\n\t\t\t\t\tif let Some(bounds) = &mut tool_data.bounding_box_manager {\n\t\t\t\t\t\tbounds.original_bound_transform = bounds.transform;\n\t\t\t\t\t\tbounds.center_of_transformation = bounds.transform.transform_point2((bounds.bounds[0] + bounds.bounds[1]) / 2.);\n\t\t\t\t\t}\n\t\t\t\t\ttool_data.get_snap_candidates(document, font_cache);\n\n\t\t\t\t\treturn TextToolFsmState::ResizingBounds;\n\t\t\t\t} else if let Some(clicked_layer) = TextToolData::check_click(document, input, font_cache) {\n\t\t\t\t\tresponses.add(DocumentMessage::StartTransaction);\n\n\t\t\t\t\tif selected != Some(clicked_layer) {\n\t\t\t\t\t\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![clicked_layer.to_node()] });\n\t\t\t\t\t}\n\n\t\t\t\t\tlet original_transform = document.metadata().transform_to_document(clicked_layer);\n\t\t\t\t\ttool_data.layer_dragging = Some(ResizingLayer {\n\t\t\t\t\t\tid: clicked_layer,\n\t\t\t\t\t\toriginal_transform,\n\t\t\t\t\t});\n\t\t\t\t\ttool_data.get_snap_candidates(document, font_cache);\n\t\t\t\t\treturn TextToolFsmState::Dragging;\n\t\t\t\t}\n\t\t\t\tTextToolFsmState::Placing\n\t\t\t}\n\t\t\t(TextToolFsmState::Ready, TextToolMessage::PointerMove { .. }) => {\n\t\t\t\t// This ensures the cursor only changes if a layer is selected\n\t\t\t\tlet selected = document.network_interface.selected_nodes();\n\t\t\t\tlet mut all_selected = selected.selected_visible_and_unlocked_layers(&document.network_interface);\n\t\t\t\tlet layer = all_selected.find(|&layer| is_layer_fed_by_node_of_name(layer, &document.network_interface, &DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER)));\n\n\t\t\t\tlet mut cursor = tool_data\n\t\t\t\t\t.bounding_box_manager\n\t\t\t\t\t.as_ref()\n\t\t\t\t\t.map_or(MouseCursorIcon::Text, |bounds| bounds.get_cursor(input, false, false, None));\n\t\t\t\tif layer.is_none() || cursor == MouseCursorIcon::Default {\n\t\t\t\t\tcursor = MouseCursorIcon::Text;\n\t\t\t\t}\n\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor });\n\n\t\t\t\tTextToolFsmState::Ready\n\t\t\t}\n\t\t\t(TextToolFsmState::Placing, TextToolMessage::PointerMove { center, lock_ratio }) => {\n\t\t\t\ttool_data.cached_resize_bounds = tool_data.resize.calculate_points_ignore_layer(document, input, viewport, center, lock_ratio, false);\n\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\n\t\t\t\t// Auto-panning\n\t\t\t\tlet messages = [\n\t\t\t\t\tTextToolMessage::PointerOutsideViewport { center, lock_ratio }.into(),\n\t\t\t\t\tTextToolMessage::PointerMove { center, lock_ratio }.into(),\n\t\t\t\t];\n\t\t\t\ttool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses);\n\n\t\t\t\tTextToolFsmState::Placing\n\t\t\t}\n\t\t\t(TextToolFsmState::Dragging, TextToolMessage::PointerMove { center, lock_ratio }) => {\n\t\t\t\tif let Some(dragging_layer) = &tool_data.layer_dragging {\n\t\t\t\t\tlet delta = input.mouse.position - tool_data.drag_current;\n\t\t\t\t\ttool_data.drag_current = input.mouse.position;\n\n\t\t\t\t\tresponses.add(GraphOperationMessage::TransformChange {\n\t\t\t\t\t\tlayer: dragging_layer.id,\n\t\t\t\t\t\ttransform: DAffine2::from_translation(delta),\n\t\t\t\t\t\ttransform_in: TransformIn::Viewport,\n\t\t\t\t\t\tskip_rerender: false,\n\t\t\t\t\t});\n\n\t\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\n\t\t\t\t\t// Auto-panning\n\t\t\t\t\tlet messages = [\n\t\t\t\t\t\tTextToolMessage::PointerOutsideViewport { center, lock_ratio }.into(),\n\t\t\t\t\t\tTextToolMessage::PointerMove { center, lock_ratio }.into(),\n\t\t\t\t\t];\n\t\t\t\t\ttool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses);\n\t\t\t\t}\n\n\t\t\t\tTextToolFsmState::Dragging\n\t\t\t}\n\t\t\t(TextToolFsmState::ResizingBounds, TextToolMessage::PointerMove { center, lock_ratio }) => {\n\t\t\t\tif let Some(bounds) = &mut tool_data.bounding_box_manager\n\t\t\t\t\t&& let Some(movement) = &mut bounds.selected_edges\n\t\t\t\t{\n\t\t\t\t\tlet (centered, constrain) = (input.keyboard.key(center), input.keyboard.key(lock_ratio));\n\t\t\t\t\tlet center_position = centered.then_some(bounds.center_of_transformation);\n\n\t\t\t\t\tlet Some(dragging_layer) = tool_data.layer_dragging else { return TextToolFsmState::Ready };\n\t\t\t\t\tlet Some(node_id) = graph_modification_utils::get_text_id(dragging_layer.id, &document.network_interface) else {\n\t\t\t\t\t\twarn!(\"Cannot get text node id\");\n\t\t\t\t\t\ttool_data.layer_dragging.take();\n\t\t\t\t\t\treturn TextToolFsmState::Ready;\n\t\t\t\t\t};\n\n\t\t\t\t\tlet selected = vec![dragging_layer.id];\n\t\t\t\t\tlet snap = Some(SizeSnapData {\n\t\t\t\t\t\tmanager: &mut tool_data.resize.snap_manager,\n\t\t\t\t\t\tpoints: &mut tool_data.snap_candidates,\n\t\t\t\t\t\tsnap_data: SnapData::ignore(document, input, viewport, &selected),\n\t\t\t\t\t});\n\n\t\t\t\t\tlet (position, size) = movement.new_size(input.mouse.position, bounds.original_bound_transform, center_position, constrain, snap);\n\t\t\t\t\t// Normalize so the size is always positive\n\t\t\t\t\tlet (position, size) = (position.min(position + size), size.abs());\n\n\t\t\t\t\t// Compute the offset needed for the top left in bounds space\n\t\t\t\t\tlet original_position = movement.bounds[0].min(movement.bounds[1]);\n\t\t\t\t\tlet translation_bounds_space = position - original_position;\n\n\t\t\t\t\t// Compute a transformation from bounds->viewport->layer\n\t\t\t\t\tlet transform_to_layer = document.metadata().transform_to_viewport(dragging_layer.id).inverse() * bounds.original_bound_transform;\n\t\t\t\t\tlet size_layer = transform_to_layer.transform_vector2(size);\n\n\t\t\t\t\t// Find the translation necessary from the original position in viewport space\n\t\t\t\t\tlet translation_viewport = bounds.original_bound_transform.transform_vector2(translation_bounds_space);\n\n\t\t\t\t\t// TODO: Don't set both max_width and max_height to true at the same time, only do one based on which edge is being dragged (or both if a corner is being dragged)\n\t\t\t\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\t\t\t\tinput_connector: InputConnector::node(node_id, graphene_std::text::text::HasMaxWidthInput::INDEX),\n\t\t\t\t\t\tinput: NodeInput::value(TaggedValue::Bool(true), false),\n\t\t\t\t\t});\n\t\t\t\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\t\t\t\tinput_connector: InputConnector::node(node_id, graphene_std::text::text::MaxWidthInput::INDEX),\n\t\t\t\t\t\tinput: NodeInput::value(TaggedValue::F64(size_layer.x), false),\n\t\t\t\t\t});\n\t\t\t\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\t\t\t\tinput_connector: InputConnector::node(node_id, graphene_std::text::text::HasMaxHeightInput::INDEX),\n\t\t\t\t\t\tinput: NodeInput::value(TaggedValue::Bool(true), false),\n\t\t\t\t\t});\n\t\t\t\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\t\t\t\tinput_connector: InputConnector::node(node_id, graphene_std::text::text::MaxHeightInput::INDEX),\n\t\t\t\t\t\tinput: NodeInput::value(TaggedValue::F64(size_layer.y), false),\n\t\t\t\t\t});\n\t\t\t\t\tresponses.add(GraphOperationMessage::TransformSet {\n\t\t\t\t\t\tlayer: dragging_layer.id,\n\t\t\t\t\t\ttransform: DAffine2::from_translation(translation_viewport) * document.metadata().document_to_viewport * dragging_layer.original_transform,\n\t\t\t\t\t\ttransform_in: TransformIn::Viewport,\n\t\t\t\t\t\tskip_rerender: false,\n\t\t\t\t\t});\n\t\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\n\t\t\t\t\t// Auto-panning\n\t\t\t\t\tlet messages = [\n\t\t\t\t\t\tTextToolMessage::PointerOutsideViewport { center, lock_ratio }.into(),\n\t\t\t\t\t\tTextToolMessage::PointerMove { center, lock_ratio }.into(),\n\t\t\t\t\t];\n\t\t\t\t\ttool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses);\n\t\t\t\t}\n\t\t\t\tTextToolFsmState::ResizingBounds\n\t\t\t}\n\t\t\t(_, TextToolMessage::PointerMove { .. }) => {\n\t\t\t\ttool_data.resize.snap_manager.preview_draw(&SnapData::new(document, input, viewport), input.mouse.position);\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\n\t\t\t\tself\n\t\t\t}\n\t\t\t(TextToolFsmState::Placing, TextToolMessage::PointerOutsideViewport { .. }) => {\n\t\t\t\t// Auto-panning setup\n\t\t\t\tlet _ = tool_data.auto_panning.shift_viewport(input, viewport, responses);\n\n\t\t\t\tTextToolFsmState::Placing\n\t\t\t}\n\t\t\t(TextToolFsmState::ResizingBounds | TextToolFsmState::Dragging, TextToolMessage::PointerOutsideViewport { .. }) => {\n\t\t\t\t// Auto-panning\n\t\t\t\tif let Some(shift) = tool_data.auto_panning.shift_viewport(input, viewport, responses)\n\t\t\t\t\t&& let Some(bounds) = &mut tool_data.bounding_box_manager\n\t\t\t\t{\n\t\t\t\t\tbounds.center_of_transformation += shift;\n\t\t\t\t\tbounds.original_bound_transform.translation += shift;\n\t\t\t\t}\n\n\t\t\t\tself\n\t\t\t}\n\t\t\t(state, TextToolMessage::PointerOutsideViewport { center, lock_ratio }) => {\n\t\t\t\t// Auto-panning stop\n\t\t\t\tlet messages = [\n\t\t\t\t\tTextToolMessage::PointerOutsideViewport { center, lock_ratio }.into(),\n\t\t\t\t\tTextToolMessage::PointerMove { center, lock_ratio }.into(),\n\t\t\t\t];\n\t\t\t\ttool_data.auto_panning.stop(&messages, responses);\n\n\t\t\t\tstate\n\t\t\t}\n\t\t\t(TextToolFsmState::ResizingBounds, TextToolMessage::DragStop) => {\n\t\t\t\tlet drag_too_small = input.mouse.position.distance(tool_data.resize.viewport_drag_start(document)) < 10. * f64::EPSILON;\n\t\t\t\tlet response = if drag_too_small { DocumentMessage::AbortTransaction } else { DocumentMessage::EndTransaction };\n\t\t\t\tresponses.add(response);\n\n\t\t\t\ttool_data.resize.snap_manager.cleanup(responses);\n\n\t\t\t\tif let Some(bounds) = &mut tool_data.bounding_box_manager {\n\t\t\t\t\tbounds.original_transforms.clear();\n\t\t\t\t}\n\n\t\t\t\tTextToolFsmState::Ready\n\t\t\t}\n\t\t\t(TextToolFsmState::Placing, TextToolMessage::DragStop) => {\n\t\t\t\tlet [start, end] = tool_data.cached_resize_bounds;\n\t\t\t\tlet has_dragged = (start - end).length_squared() > DRAG_THRESHOLD * DRAG_THRESHOLD;\n\n\t\t\t\t// Check if the user has clicked (no dragging) on some existing text\n\t\t\t\tif !has_dragged && let Some(clicked_text_layer_path) = TextToolData::check_click(document, input, font_cache) {\n\t\t\t\t\ttool_data.start_editing_layer(clicked_text_layer_path, self, document, font_cache, responses);\n\t\t\t\t\treturn TextToolFsmState::Editing;\n\t\t\t\t}\n\n\t\t\t\t// Otherwise create some new text\n\t\t\t\tlet constraint_size = has_dragged.then_some((start - end).abs());\n\t\t\t\tlet editing_text = EditingText {\n\t\t\t\t\ttext: String::new(),\n\t\t\t\t\ttransform: DAffine2::from_translation(start),\n\t\t\t\t\ttypesetting: TypesettingConfig {\n\t\t\t\t\t\tfont_size: tool_options.font_size,\n\t\t\t\t\t\tline_height_ratio: tool_options.line_height_ratio,\n\t\t\t\t\t\tmax_width: constraint_size.map(|size| size.x),\n\t\t\t\t\t\tcharacter_spacing: tool_options.character_spacing,\n\t\t\t\t\t\tmax_height: constraint_size.map(|size| size.y),\n\t\t\t\t\t\ttilt: tool_options.tilt,\n\t\t\t\t\t\talign: tool_options.align,\n\t\t\t\t\t},\n\t\t\t\t\tfont: Font::new(tool_options.font.font_family.clone(), tool_options.font.font_style.clone()),\n\t\t\t\t\tcolor: tool_options.fill.active_color(),\n\t\t\t\t};\n\t\t\t\ttool_data.new_text(document, editing_text, font_cache, responses);\n\t\t\t\tTextToolFsmState::Editing\n\t\t\t}\n\t\t\t(TextToolFsmState::Dragging, TextToolMessage::DragStop) => {\n\t\t\t\tlet drag_too_small = input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON;\n\t\t\t\tlet response = if drag_too_small { DocumentMessage::AbortTransaction } else { DocumentMessage::EndTransaction };\n\t\t\t\tresponses.add(response);\n\n\t\t\t\ttool_data.resize.snap_manager.cleanup(responses);\n\n\t\t\t\tif let Some(bounds) = &mut tool_data.bounding_box_manager {\n\t\t\t\t\tbounds.original_transforms.clear();\n\t\t\t\t}\n\n\t\t\t\tif drag_too_small && let Some(layer_info) = &tool_data.layer_dragging {\n\t\t\t\t\ttool_data.start_editing_layer(layer_info.id, self, document, font_cache, responses);\n\t\t\t\t\treturn TextToolFsmState::Editing;\n\t\t\t\t}\n\t\t\t\ttool_data.layer_dragging.take();\n\n\t\t\t\tTextToolFsmState::Ready\n\t\t\t}\n\t\t\t(TextToolFsmState::Editing, TextToolMessage::RefreshEditingFontData) => {\n\t\t\t\tlet font = Font::new(tool_options.font.font_family.clone(), tool_options.font.font_style.clone());\n\t\t\t\tresponses.add(FrontendMessage::DisplayEditableTextboxUpdateFontData {\n\t\t\t\t\tfont_data: font_cache.get(&font).map(|(data, _)| data.clone()).unwrap_or_default().into(),\n\t\t\t\t});\n\n\t\t\t\tTextToolFsmState::Editing\n\t\t\t}\n\t\t\t(TextToolFsmState::Editing, TextToolMessage::TextChange { new_text, is_left_or_right_click }) => {\n\t\t\t\ttool_data.new_text = new_text;\n\n\t\t\t\tif !is_left_or_right_click {\n\t\t\t\t\ttool_data.set_editing(false, font_cache, responses);\n\n\t\t\t\t\tresponses.add(NodeGraphMessage::SetInput {\n\t\t\t\t\t\tinput_connector: InputConnector::node(graph_modification_utils::get_text_id(tool_data.layer, &document.network_interface).unwrap(), 1),\n\t\t\t\t\t\tinput: NodeInput::value(TaggedValue::String(tool_data.new_text.clone()), false),\n\t\t\t\t\t});\n\t\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\n\t\t\t\t\tTextToolFsmState::Ready\n\t\t\t\t} else {\n\t\t\t\t\tif tool_data.new_text.is_empty() {\n\t\t\t\t\t\treturn tool_data.delete_empty_layer(font_cache, responses);\n\t\t\t\t\t}\n\n\t\t\t\t\tresponses.add(FrontendMessage::TriggerTextCommit);\n\n\t\t\t\t\tTextToolFsmState::Editing\n\t\t\t\t}\n\t\t\t}\n\t\t\t(TextToolFsmState::Editing, TextToolMessage::UpdateBounds { new_text }) => {\n\t\t\t\ttool_data.new_text = new_text;\n\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\t\t\t\tTextToolFsmState::Editing\n\t\t\t}\n\t\t\t(_, TextToolMessage::WorkingColorChanged) => {\n\t\t\t\tresponses.add(TextToolMessage::UpdateOptions {\n\t\t\t\t\toptions: TextOptionsUpdate::WorkingColors(Some(global_tool_data.primary_color), Some(global_tool_data.secondary_color)),\n\t\t\t\t});\n\t\t\t\tself\n\t\t\t}\n\t\t\t(TextToolFsmState::Editing, TextToolMessage::Abort) => {\n\t\t\t\tif tool_data.new_text.is_empty() {\n\t\t\t\t\treturn tool_data.delete_empty_layer(font_cache, responses);\n\t\t\t\t}\n\n\t\t\t\tresponses.add(FrontendMessage::TriggerTextCommit);\n\t\t\t\tTextToolFsmState::Editing\n\t\t\t}\n\t\t\t(state, TextToolMessage::Abort) => {\n\t\t\t\tif matches!(state, TextToolFsmState::ResizingBounds | TextToolFsmState::Dragging) {\n\t\t\t\t\tresponses.add(DocumentMessage::AbortTransaction);\n\t\t\t\t\tif let Some(bounds) = &mut tool_data.bounding_box_manager {\n\t\t\t\t\t\tbounds.original_transforms.clear();\n\t\t\t\t\t}\n\t\t\t\t\tif matches!(state, TextToolFsmState::Dragging) {\n\t\t\t\t\t\ttool_data.layer_dragging.take();\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tinput.mouse.finish_transaction(tool_data.resize.viewport_drag_start(document), responses);\n\t\t\t\t}\n\t\t\t\ttool_data.resize.cleanup(responses);\n\n\t\t\t\tTextToolFsmState::Ready\n\t\t\t}\n\t\t\t_ => self,\n\t\t}\n\t}\n\n\tfn update_hints(&self, responses: &mut VecDeque<Message>) {\n\t\tlet hint_data = match self {\n\t\t\tTextToolFsmState::Ready => HintData(vec![\n\t\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, \"Place Text\")]),\n\t\t\t\tHintGroup(vec![\n\t\t\t\t\tHintInfo::mouse(MouseMotion::LmbDrag, \"Place Text Box\"),\n\t\t\t\t\tHintInfo::keys([Key::Shift], \"Constrain Square\").prepend_plus(),\n\t\t\t\t\tHintInfo::keys([Key::Alt], \"From Center\").prepend_plus(),\n\t\t\t\t]),\n\t\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, \"Edit Text\")]),\n\t\t\t]),\n\t\t\tTextToolFsmState::Editing => HintData(vec![HintGroup(vec![\n\t\t\t\tHintInfo::keys([Key::Control, Key::Enter], \"\").add_mac_keys([Key::Command, Key::Enter]),\n\t\t\t\tHintInfo::keys([Key::Escape], \"Commit Changes\").prepend_slash(),\n\t\t\t])]),\n\t\t\tTextToolFsmState::Placing => HintData(vec![\n\t\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"\"), HintInfo::keys([Key::Escape], \"Cancel\").prepend_slash()]),\n\t\t\t\tHintGroup(vec![HintInfo::keys([Key::Shift], \"Constrain Square\"), HintInfo::keys([Key::Alt], \"From Center\")]),\n\t\t\t]),\n\t\t\tTextToolFsmState::Dragging => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"\"), HintInfo::keys([Key::Escape], \"Cancel\").prepend_slash()])]),\n\t\t\tTextToolFsmState::ResizingBounds => HintData(vec![\n\t\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"\"), HintInfo::keys([Key::Escape], \"Cancel\").prepend_slash()]),\n\t\t\t\tHintGroup(vec![HintInfo::keys([Key::Shift], \"Lock Aspect Ratio\"), HintInfo::keys([Key::Alt], \"From Center\")]),\n\t\t\t]),\n\t\t};\n\n\t\thint_data.send_layout(responses);\n\t}\n\n\tfn update_cursor(&self, responses: &mut VecDeque<Message>) {\n\t\tlet cursor = match self {\n\t\t\tTextToolFsmState::Placing => MouseCursorIcon::Crosshair,\n\t\t\t_ => MouseCursorIcon::Text,\n\t\t};\n\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor });\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/transform_layer/mod.rs",
    "content": "//! Handles Blender inspired layer transformation with the <kbd>G</kbd>, <kbd>R</kbd>, and <kbd>S</kbd> keys for grabbing, rotating, and scaling.\n//!\n//! Other features include\n//! - Typing a number for a precise transformation\n//! - <kbd>Shift</kbd> to slow transformation\n//! - <kbd>Ctrl</kbd> to snap angles to 15°\n//! - Escape or right click to cancel\n\nmod transform_layer_message;\npub mod transform_layer_message_handler;\n\n#[doc(inline)]\npub use transform_layer_message::{TransformLayerMessage, TransformLayerMessageDiscriminant};\n#[doc(inline)]\npub use transform_layer_message_handler::TransformLayerMessageHandler;\n"
  },
  {
    "path": "editor/src/messages/tool/transform_layer/transform_layer_message.rs",
    "content": "use crate::messages::input_mapper::utility_types::input_keyboard::Key;\nuse crate::messages::portfolio::document::overlays::utility_types::OverlayContext;\nuse crate::messages::portfolio::document::utility_types::transformation::TransformType;\nuse crate::messages::prelude::*;\nuse crate::messages::tool::common_functionality::pivot::PivotGizmo;\nuse glam::DVec2;\n\n#[impl_message(Message, ToolMessage, TransformLayer)]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum TransformLayerMessage {\n\t// Messages\n\tOverlays { context: OverlayContext },\n\tApplyTransformOperation { final_transform: bool },\n\tBeginTransformOperation { operation: TransformType },\n\tBeginGrab,\n\tBeginRotate,\n\tBeginScale,\n\tBeginGRS { operation: TransformType },\n\tBeginGrabPen { last_point: DVec2, handle: DVec2 },\n\tBeginRotatePen { last_point: DVec2, handle: DVec2 },\n\tBeginScalePen { last_point: DVec2, handle: DVec2 },\n\tCancelTransformOperation,\n\tConstrainX,\n\tConstrainY,\n\tPointerMove { slow_key: Key, increments_key: Key },\n\tSelectionChanged,\n\tTypeBackspace,\n\tTypeDecimalPoint,\n\tTypeDigit { digit: u8 },\n\tTypeNegate,\n\tSetPivotGizmo { pivot_gizmo: PivotGizmo },\n}\n"
  },
  {
    "path": "editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs",
    "content": "use crate::consts::{ANGLE_MEASURE_RADIUS_FACTOR, ARC_MEASURE_RADIUS_FACTOR_RANGE, COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GRAY, SLOWING_DIVISOR};\nuse crate::messages::input_mapper::utility_types::input_mouse::{DocumentPosition, ViewportPosition};\nuse crate::messages::portfolio::document::overlays::utility_functions::text_width;\nuse crate::messages::portfolio::document::overlays::utility_types::{OverlayProvider, Pivot};\nuse crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse crate::messages::portfolio::document::utility_types::misc::PTZ;\nuse crate::messages::portfolio::document::utility_types::transformation::{Axis, OriginalTransforms, Selected, TransformOperation, TransformType, Typing};\nuse crate::messages::prelude::*;\nuse crate::messages::tool::common_functionality::pivot::{PivotGizmo, PivotGizmoType};\nuse crate::messages::tool::common_functionality::shape_editor::ShapeState;\nuse crate::messages::tool::tool_messages::select_tool;\nuse crate::messages::tool::tool_messages::tool_prelude::Key;\nuse crate::messages::tool::utility_types::{ToolData, ToolType};\nuse glam::{DAffine2, DVec2};\nuse graphene_std::renderer::Quad;\nuse graphene_std::vector::click_target::ClickTargetType;\nuse graphene_std::vector::misc::ManipulatorPointId;\nuse graphene_std::vector::{Vector, VectorModificationType};\nuse std::f64::consts::{PI, TAU};\n\nconst TRANSFORM_GRS_OVERLAY_PROVIDER: OverlayProvider = |context| TransformLayerMessage::Overlays { context }.into();\n\n// TODO: Get these from the input mapper\nconst SLOW_KEY: Key = Key::Shift;\nconst INCREMENTS_KEY: Key = Key::Control;\n\n#[derive(ExtractField)]\npub struct TransformLayerMessageContext<'a> {\n\tpub document: &'a DocumentMessageHandler,\n\tpub input: &'a InputPreprocessorMessageHandler,\n\tpub tool_data: &'a ToolData,\n\tpub shape_editor: &'a mut ShapeState,\n\tpub viewport: &'a ViewportMessageHandler,\n}\n\n#[derive(Debug, Clone, Default, ExtractField)]\npub struct TransformationState {\n\tpub is_rounded_to_intervals: bool,\n\tpub is_transforming_in_local_space: bool,\n\tpub local_transform_axes: [DVec2; 2],\n\tpub document_space_pivot: DocumentPosition,\n}\n\nimpl TransformationState {\n\tpub fn pivot_viewport(&self, document: &DocumentMessageHandler) -> DVec2 {\n\t\tdocument.metadata().document_to_viewport.transform_point2(self.document_space_pivot)\n\t}\n\n\tpub fn constraint_axis(&self, axis_constraint: Axis) -> Option<DVec2> {\n\t\tmatch axis_constraint {\n\t\t\tAxis::X => Some(if self.is_transforming_in_local_space { self.local_transform_axes[0] } else { DVec2::X }),\n\t\t\tAxis::Y => Some(if self.is_transforming_in_local_space { self.local_transform_axes[1] } else { DVec2::Y }),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tpub fn project_onto_constrained(&self, vector: DVec2, axis_constraint: Axis) -> DVec2 {\n\t\tself.constraint_axis(axis_constraint).map_or(vector, |direction| vector.project_onto_normalized(direction))\n\t}\n\n\tpub fn local_to_viewport_transform(&self) -> DAffine2 {\n\t\tif self.is_transforming_in_local_space {\n\t\t\tDAffine2::from_cols(self.local_transform_axes[0], self.local_transform_axes[1], DVec2::ZERO)\n\t\t} else {\n\t\t\tDAffine2::IDENTITY\n\t\t}\n\t}\n}\n\n#[derive(Debug, Clone, Default, ExtractField)]\npub struct TransformLayerMessageHandler {\n\tpub transform_operation: TransformOperation,\n\tstate: TransformationState,\n\tslow: bool,\n\tlayer_bounding_box: Quad,\n\ttyping: Typing,\n\tmouse_position: ViewportPosition,\n\tstart_mouse: ViewportPosition,\n\toriginal_transforms: OriginalTransforms,\n\tpivot_gizmo: PivotGizmo,\n\tpivot: ViewportPosition,\n\tpath_bounds: Option<[DVec2; 2]>,\n\tlocal_mouse_start: DocumentPosition,\n\tgrab_target: DocumentPosition,\n\tptz: PTZ,\n\tinitial_transform: DAffine2,\n\toperation_count: usize,\n\twas_grabbing: bool,\n\n\t// Pen tool (outgoing handle GRS manipulation)\n\thandle: DVec2,\n\tlast_point: DVec2,\n\tgrs_pen_handle: bool,\n\n\t// Path tool (ghost outlines showing pre-transform geometry)\n\tghost_outline: Vec<(Vec<ClickTargetType>, DAffine2)>,\n}\n\n#[message_handler_data]\nimpl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for TransformLayerMessageHandler {\n\tfn process_message(&mut self, message: TransformLayerMessage, responses: &mut VecDeque<Message>, context: TransformLayerMessageContext) {\n\t\tlet TransformLayerMessageContext {\n\t\t\tdocument,\n\t\t\tinput,\n\t\t\ttool_data,\n\t\t\tshape_editor,\n\t\t\tviewport,\n\t\t} = context;\n\n\t\tlet using_path_tool = tool_data.active_tool_type == ToolType::Path;\n\t\tlet using_select_tool = tool_data.active_tool_type == ToolType::Select;\n\t\tlet using_pen_tool = tool_data.active_tool_type == ToolType::Pen;\n\t\tlet using_shape_tool = tool_data.active_tool_type == ToolType::Shape;\n\n\t\t// TODO: Add support for transforming layer not in the document network\n\t\tlet selected_layers = document\n\t\t\t.network_interface\n\t\t\t.selected_nodes()\n\t\t\t.selected_layers(document.metadata())\n\t\t\t.filter(|&layer| document.network_interface.is_visible(&layer.to_node(), &[]) && !document.network_interface.is_locked(&layer.to_node(), &[]))\n\t\t\t.collect::<Vec<_>>();\n\n\t\tlet mut selected = Selected::new(\n\t\t\t&mut self.original_transforms,\n\t\t\t&mut self.pivot,\n\t\t\t&selected_layers,\n\t\t\tresponses,\n\t\t\t&document.network_interface,\n\t\t\tSome(shape_editor),\n\t\t\t&tool_data.active_tool_type,\n\t\t\tSome(&mut self.handle),\n\t\t);\n\n\t\tlet document_to_viewport = document.metadata().document_to_viewport;\n\t\tlet mut begin_operation = |operation: TransformOperation, typing: &mut Typing, mouse_position: &mut DVec2, start_mouse: &mut DVec2, transform: &mut DAffine2| {\n\t\t\tif operation != TransformOperation::None {\n\t\t\t\tselected.revert_operation();\n\t\t\t\ttyping.clear();\n\t\t\t}\n\n\t\t\tif using_pen_tool {\n\t\t\t\tselected.responses.add(PenToolMessage::GRS {\n\t\t\t\t\tgrab: Key::KeyG,\n\t\t\t\t\trotate: Key::KeyR,\n\t\t\t\t\tscale: Key::KeyS,\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif !using_path_tool {\n\t\t\t\tself.pivot_gizmo.recalculate_transform(document);\n\t\t\t\t*selected.pivot = self.pivot_gizmo.position(document);\n\t\t\t\tself.state.document_space_pivot = document.metadata().document_to_viewport.inverse().transform_point2(*selected.pivot);\n\t\t\t\tself.grab_target = self.state.document_space_pivot;\n\t\t\t}\n\t\t\t// TODO: Here vector data from all layers is not considered which can be a problem in pivot calculation\n\t\t\telse if let Some(vector) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) {\n\t\t\t\t*selected.original_transforms = OriginalTransforms::default();\n\n\t\t\t\tlet viewspace = document.metadata().transform_to_viewport(selected_layers[0]);\n\t\t\t\tlet selected_segments = shape_editor.selected_segments().collect::<HashSet<_>>();\n\t\t\t\tlet mut affected_points = shape_editor.selected_points().copied().collect::<Vec<_>>();\n\n\t\t\t\tfor (segment_id, _, start, end) in vector.segment_bezier_iter() {\n\t\t\t\t\tif selected_segments.contains(&segment_id) {\n\t\t\t\t\t\taffected_points.push(ManipulatorPointId::Anchor(start));\n\t\t\t\t\t\taffected_points.push(ManipulatorPointId::Anchor(end));\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tlet affected_point_refs = affected_points.iter().collect();\n\n\t\t\t\tlet get_location = |point: &&ManipulatorPointId| point.get_position(&vector).map(|position| viewspace.transform_point2(position));\n\t\t\t\tif let (Some((new_pivot, grab_target)), bounds) = calculate_pivot(\n\t\t\t\t\tdocument,\n\t\t\t\t\t&affected_point_refs,\n\t\t\t\t\t&vector,\n\t\t\t\t\tviewspace,\n\t\t\t\t\t|point: &ManipulatorPointId| get_location(&point),\n\t\t\t\t\t&mut self.pivot_gizmo,\n\t\t\t\t) {\n\t\t\t\t\t*selected.pivot = new_pivot;\n\t\t\t\t\tself.path_bounds = bounds;\n\n\t\t\t\t\tself.state.document_space_pivot = document_to_viewport.inverse().transform_point2(*selected.pivot);\n\t\t\t\t\tself.grab_target = document_to_viewport.inverse().transform_point2(grab_target);\n\t\t\t\t} else {\n\t\t\t\t\tlog::warn!(\"Failed to calculate pivot.\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t*mouse_position = input.mouse.position;\n\t\t\t*start_mouse = input.mouse.position;\n\t\t\t*transform = document_to_viewport;\n\t\t\tself.local_mouse_start = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position);\n\n\t\t\tselected.original_transforms.clear();\n\n\t\t\tselected.responses.add(DocumentMessage::StartTransaction);\n\t\t};\n\n\t\tmatch message {\n\t\t\t// Overlays\n\t\t\tTransformLayerMessage::Overlays { context: mut overlay_context } => {\n\t\t\t\tif !overlay_context.visibility_settings.transform_measurement() {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif using_path_tool {\n\t\t\t\t\tfor (outline, transform) in &self.ghost_outline {\n\t\t\t\t\t\toverlay_context.outline(outline.iter(), *transform, Some(COLOR_OVERLAY_GRAY));\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tlet viewport_box = viewport.size().into_dvec2();\n\t\t\t\tlet axis_constraint = self.transform_operation.axis_constraint();\n\n\t\t\t\tlet format_rounded = |value: f64, precision: usize| {\n\t\t\t\t\tif self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() {\n\t\t\t\t\t\tformat!(\"{value:.precision$}\").trim_end_matches('0').trim_end_matches('.').to_string()\n\t\t\t\t\t} else {\n\t\t\t\t\t\tself.typing.string.clone()\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\t// TODO: Ensure removing this and adding this doesn't change the position of layers under PTZ ops\n\t\t\t\t// responses.add(TransformLayerMessage::PointerMove {\n\t\t\t\t// \tslow_key: SLOW_KEY,\n\t\t\t\t// \tincrements_key: INCREMENTS_KEY,\n\t\t\t\t// });\n\n\t\t\t\tmatch self.transform_operation {\n\t\t\t\t\tTransformOperation::None => (),\n\t\t\t\t\tTransformOperation::Grabbing(translation) => {\n\t\t\t\t\t\tlet translation_viewport = self.state.local_to_viewport_transform().matrix2 * translation.to_dvec(&self.state, document);\n\t\t\t\t\t\tlet pivot = document_to_viewport.transform_point2(self.grab_target);\n\t\t\t\t\t\tlet quad = Quad::from_box([pivot, pivot + translation_viewport]);\n\n\t\t\t\t\t\tresponses.add(SelectToolMessage::PivotShift {\n\t\t\t\t\t\t\toffset: Some(translation_viewport),\n\t\t\t\t\t\t\tflush: false,\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tlet typed_string = (!self.typing.digits.is_empty() && self.transform_operation.can_begin_typing()).then(|| self.typing.string.clone());\n\t\t\t\t\t\toverlay_context.translation_box(translation_viewport / document_to_viewport.matrix2.y_axis.length(), quad, typed_string);\n\t\t\t\t\t}\n\t\t\t\t\tTransformOperation::Scaling(scale) => {\n\t\t\t\t\t\tlet scale = scale.to_f64(self.state.is_rounded_to_intervals);\n\t\t\t\t\t\tlet text = format!(\"{}x\", format_rounded(scale, 3));\n\t\t\t\t\t\tlet start_mouse = document_to_viewport.transform_point2(self.local_mouse_start);\n\t\t\t\t\t\tlet local_edge = start_mouse - self.state.pivot_viewport(document);\n\t\t\t\t\t\tlet local_edge = self.state.project_onto_constrained(local_edge, axis_constraint);\n\t\t\t\t\t\tlet boundary_point = self.state.pivot_viewport(document) + local_edge * scale.min(1.);\n\t\t\t\t\t\tlet end_point = self.state.pivot_viewport(document) + local_edge * scale.max(1.);\n\n\t\t\t\t\t\tif scale > 0. {\n\t\t\t\t\t\t\toverlay_context.dashed_line(self.state.pivot_viewport(document), boundary_point, None, None, Some(2.), Some(2.), Some(0.5));\n\t\t\t\t\t\t}\n\t\t\t\t\t\toverlay_context.line(boundary_point, end_point, None, None);\n\n\t\t\t\t\t\tlet transform = DAffine2::from_translation(\n\t\t\t\t\t\t\tboundary_point.midpoint(self.state.pivot_viewport(document)) + local_edge.perp().normalize_or(DVec2::X) * local_edge.element_product().signum() * 24.,\n\t\t\t\t\t\t);\n\t\t\t\t\t\toverlay_context.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);\n\t\t\t\t\t}\n\t\t\t\t\tTransformOperation::Rotating(rotation) => {\n\t\t\t\t\t\tlet angle = rotation.to_f64(self.state.is_rounded_to_intervals);\n\t\t\t\t\t\tlet start_mouse = document_to_viewport.transform_point2(self.local_mouse_start);\n\t\t\t\t\t\tlet offset_angle = if self.grs_pen_handle {\n\t\t\t\t\t\t\tself.handle - self.last_point\n\t\t\t\t\t\t} else if using_path_tool {\n\t\t\t\t\t\t\tstart_mouse - self.state.pivot_viewport(document)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// TODO: This is always zero breaking the `.to_angle()` below?\n\t\t\t\t\t\t\tself.layer_bounding_box.top_right() - self.layer_bounding_box.top_right()\n\t\t\t\t\t\t};\n\t\t\t\t\t\tlet tilt_offset = document.document_ptz.unmodified_tilt();\n\t\t\t\t\t\tlet offset_angle = offset_angle.to_angle() + tilt_offset;\n\t\t\t\t\t\tlet width = viewport_box.max_element();\n\t\t\t\t\t\tlet radius = start_mouse.distance(self.state.pivot_viewport(document));\n\t\t\t\t\t\tlet arc_radius = ANGLE_MEASURE_RADIUS_FACTOR * width;\n\t\t\t\t\t\tlet radius = radius.clamp(ARC_MEASURE_RADIUS_FACTOR_RANGE.0 * width, ARC_MEASURE_RADIUS_FACTOR_RANGE.1 * width);\n\t\t\t\t\t\tlet angle_in_degrees = angle.to_degrees();\n\t\t\t\t\t\tlet display_angle = if angle_in_degrees.is_sign_positive() {\n\t\t\t\t\t\t\tangle_in_degrees - (angle_in_degrees / 360.).floor() * 360.\n\t\t\t\t\t\t} else if angle_in_degrees.is_sign_negative() {\n\t\t\t\t\t\t\tangle_in_degrees - ((angle_in_degrees / 360.).floor() + 1.) * 360.\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tangle_in_degrees\n\t\t\t\t\t\t};\n\t\t\t\t\t\tlet text = format!(\"{}°\", format_rounded(display_angle, 2));\n\t\t\t\t\t\tconst FONT_SIZE: f64 = 12.;\n\n\t\t\t\t\t\tlet text_texture_width = text_width(&text, FONT_SIZE) / 2.;\n\t\t\t\t\t\tlet text_texture_height = 12.;\n\t\t\t\t\t\tlet text_angle_on_unit_circle = DVec2::from_angle((angle % TAU) / 2. + offset_angle);\n\t\t\t\t\t\tlet text_texture_position = DVec2::new(\n\t\t\t\t\t\t\t(arc_radius + 4. + text_texture_width) * text_angle_on_unit_circle.x,\n\t\t\t\t\t\t\t(arc_radius + text_texture_height) * text_angle_on_unit_circle.y,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tlet transform = DAffine2::from_translation(text_texture_position + self.state.pivot_viewport(document));\n\t\t\t\t\t\toverlay_context.draw_angle(self.state.pivot_viewport(document), radius, arc_radius, offset_angle, angle);\n\t\t\t\t\t\toverlay_context.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Messages\n\t\t\tTransformLayerMessage::ApplyTransformOperation { final_transform } => {\n\t\t\t\tselected.original_transforms.clear();\n\t\t\t\tself.typing.clear();\n\t\t\t\tif final_transform {\n\t\t\t\t\tself.transform_operation = TransformOperation::None;\n\t\t\t\t\tself.operation_count = 0;\n\t\t\t\t}\n\n\t\t\t\tif using_pen_tool {\n\t\t\t\t\tself.last_point = DVec2::ZERO;\n\t\t\t\t\tself.grs_pen_handle = false;\n\n\t\t\t\t\tselected.pen_handle = None;\n\t\t\t\t\tselected.responses.add(PenToolMessage::Confirm);\n\t\t\t\t} else {\n\t\t\t\t\tupdate_colinear_handles(&selected_layers, document, responses);\n\t\t\t\t\tresponses.add(DocumentMessage::EndTransaction);\n\t\t\t\t\tresponses.add(ToolMessage::UpdateHints);\n\t\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\t}\n\n\t\t\t\tif using_path_tool {\n\t\t\t\t\tself.ghost_outline.clear();\n\t\t\t\t}\n\n\t\t\t\tresponses.add(SelectToolMessage::PivotShift { offset: None, flush: true });\n\n\t\t\t\tif final_transform {\n\t\t\t\t\tself.was_grabbing = false;\n\t\t\t\t\tresponses.add(OverlaysMessage::RemoveProvider {\n\t\t\t\t\t\tprovider: TRANSFORM_GRS_OVERLAY_PROVIDER,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\tTransformLayerMessage::BeginTransformOperation { operation } => {\n\t\t\t\tbegin_operation(self.transform_operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse, &mut self.initial_transform);\n\t\t\t\tself.transform_operation = match operation {\n\t\t\t\t\tTransformType::Grab => TransformOperation::Grabbing(Default::default()),\n\t\t\t\t\tTransformType::Rotate => TransformOperation::Rotating(Default::default()),\n\t\t\t\t\tTransformType::Scale => TransformOperation::Scaling(Default::default()),\n\t\t\t\t};\n\t\t\t\tself.layer_bounding_box = selected.bounding_box();\n\t\t\t\tlet bounding_box = select_tool::create_bounding_box_transform(document);\n\t\t\t\tself.state.local_transform_axes = [bounding_box.x_axis, bounding_box.y_axis].map(|axis| axis.normalize_or_zero());\n\t\t\t}\n\t\t\tTransformLayerMessage::BeginGrabPen { last_point, handle } | TransformLayerMessage::BeginRotatePen { last_point, handle } | TransformLayerMessage::BeginScalePen { last_point, handle } => {\n\t\t\t\tself.typing.clear();\n\n\t\t\t\tself.last_point = last_point;\n\t\t\t\tself.handle = handle;\n\t\t\t\tself.grs_pen_handle = true;\n\t\t\t\tself.mouse_position = input.mouse.position;\n\t\t\t\tself.start_mouse = input.mouse.position;\n\n\t\t\t\tlet top_left = DVec2::new(last_point.x, handle.y);\n\t\t\t\tlet bottom_right = DVec2::new(handle.x, last_point.y);\n\t\t\t\tself.state.is_transforming_in_local_space = false;\n\t\t\t\tself.layer_bounding_box = Quad::from_box([top_left, bottom_right]);\n\t\t\t\tlet normalized_along = (handle - last_point).normalize_or_zero();\n\t\t\t\tself.state.local_transform_axes = [normalized_along, normalized_along.perp()];\n\t\t\t\tself.grab_target = document.metadata().document_to_viewport.inverse().transform_point2(handle);\n\t\t\t\tlet pivot = last_point;\n\t\t\t\tself.state.document_space_pivot = document.metadata().document_to_viewport.inverse().transform_point2(pivot);\n\t\t\t\tself.local_mouse_start = document.metadata().document_to_viewport.inverse().transform_point2(self.start_mouse);\n\t\t\t\tself.handle = handle;\n\n\t\t\t\t// Operation-specific logic\n\t\t\t\tself.transform_operation = match message {\n\t\t\t\t\tTransformLayerMessage::BeginGrabPen { .. } => TransformOperation::Grabbing(Default::default()),\n\t\t\t\t\tTransformLayerMessage::BeginRotatePen { .. } => TransformOperation::Rotating(Default::default()),\n\t\t\t\t\tTransformLayerMessage::BeginScalePen { .. } => TransformOperation::Scaling(Default::default()),\n\t\t\t\t\t_ => unreachable!(), // Safe because the match arms are exhaustive\n\t\t\t\t};\n\n\t\t\t\tresponses.add(OverlaysMessage::AddProvider {\n\t\t\t\t\tprovider: TRANSFORM_GRS_OVERLAY_PROVIDER,\n\t\t\t\t});\n\t\t\t\t// Find a way better than this hack\n\t\t\t\tresponses.add(TransformLayerMessage::PointerMove {\n\t\t\t\t\tslow_key: SLOW_KEY,\n\t\t\t\t\tincrements_key: INCREMENTS_KEY,\n\t\t\t\t});\n\t\t\t}\n\t\t\tTransformLayerMessage::BeginGRS { operation: transform_type } => {\n\t\t\t\tlet selected_points: Vec<&ManipulatorPointId> = shape_editor.selected_points().collect();\n\t\t\t\tlet selected_segments = shape_editor.selected_segments().collect::<Vec<_>>();\n\n\t\t\t\tif using_path_tool {\n\t\t\t\t\tSelf::set_ghost_outline(&mut self.ghost_outline, shape_editor, document);\n\t\t\t\t\tif (selected_points.is_empty() && selected_segments.is_empty())\n\t\t\t\t\t\t|| (!using_path_tool && !using_select_tool && !using_pen_tool && !using_shape_tool)\n\t\t\t\t\t\t|| selected_layers.is_empty()\n\t\t\t\t\t\t|| (transform_type.equivalent_to(self.transform_operation) && !self.was_grabbing)\n\t\t\t\t\t{\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif using_path_tool && transform_type == TransformType::Grab {\n\t\t\t\t\t\t// Check if a single point is selected and it's a colinear point\n\t\t\t\t\t\tlet single_anchor_selected = shape_editor.selected_points().count() == 1 && shape_editor.selected_points().any(|point| point.as_anchor().is_some());\n\n\t\t\t\t\t\tif single_anchor_selected && transform_type.equivalent_to(self.transform_operation) && self.was_grabbing {\n\t\t\t\t\t\t\tlet selected_nodes = &document.network_interface.selected_nodes();\n\n\t\t\t\t\t\t\tlet Some(layer) = selected_nodes.selected_layers(document.metadata()).next() else { return };\n\t\t\t\t\t\t\tlet Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { return };\n\t\t\t\t\t\t\tlet Some(anchor) = shape_editor.selected_points().next() else { return };\n\n\t\t\t\t\t\t\tif vector_data.colinear(*anchor) {\n\t\t\t\t\t\t\t\tresponses.add(TransformLayerMessage::CancelTransformOperation);\n\n\t\t\t\t\t\t\t\t// Start sliding point\n\t\t\t\t\t\t\t\tresponses.add(DeferMessage::AfterGraphRun {\n\t\t\t\t\t\t\t\t\tmessages: vec![PathToolMessage::StartSlidingPoint.into()],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif transform_type.equivalent_to(self.transform_operation) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tself.was_grabbing = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif let Some(vector) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer))\n\t\t\t\t\t&& let [point] = selected_points.as_slice()\n\t\t\t\t{\n\t\t\t\t\tif matches!(point, ManipulatorPointId::Anchor(_)) {\n\t\t\t\t\t\tif let Some([handle1, handle2]) = point.get_handle_pair(&vector) {\n\t\t\t\t\t\t\tlet handle1_length = handle1.length(&vector);\n\t\t\t\t\t\t\tlet handle2_length = handle2.length(&vector);\n\n\t\t\t\t\t\t\tif (handle1_length == 0. && handle2_length == 0. && !using_select_tool) || (handle1_length == f64::MAX && handle2_length == f64::MAX && !using_select_tool) {\n\t\t\t\t\t\t\t\t// G should work for this point but not R and S\n\t\t\t\t\t\t\t\tif matches!(transform_type, TransformType::Rotate | TransformType::Scale) {\n\t\t\t\t\t\t\t\t\tselected.original_transforms.clear();\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlet handle_length = point.as_handle().map(|handle| handle.length(&vector));\n\n\t\t\t\t\t\tif handle_length == Some(0.) {\n\t\t\t\t\t\t\tselected.original_transforms.clear();\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tself.state.is_transforming_in_local_space = false;\n\t\t\t\tself.operation_count += 1;\n\n\t\t\t\tlet chain_operation = self.transform_operation != TransformOperation::None;\n\t\t\t\tif chain_operation {\n\t\t\t\t\tresponses.add(TransformLayerMessage::ApplyTransformOperation { final_transform: false });\n\t\t\t\t} else {\n\t\t\t\t\tresponses.add(OverlaysMessage::AddProvider {\n\t\t\t\t\t\tprovider: TRANSFORM_GRS_OVERLAY_PROVIDER,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tresponses.add(TransformLayerMessage::BeginTransformOperation { operation: transform_type });\n\t\t\t\tresponses.add(TransformLayerMessage::PointerMove {\n\t\t\t\t\tslow_key: SLOW_KEY,\n\t\t\t\t\tincrements_key: INCREMENTS_KEY,\n\t\t\t\t});\n\t\t\t}\n\t\t\tTransformLayerMessage::BeginGrab => responses.add_front(TransformLayerMessage::BeginGRS { operation: TransformType::Grab }),\n\t\t\tTransformLayerMessage::BeginRotate => responses.add_front(TransformLayerMessage::BeginGRS { operation: TransformType::Rotate }),\n\t\t\tTransformLayerMessage::BeginScale => responses.add_front(TransformLayerMessage::BeginGRS { operation: TransformType::Scale }),\n\t\t\tTransformLayerMessage::CancelTransformOperation => {\n\t\t\t\tif using_path_tool {\n\t\t\t\t\tself.ghost_outline.clear();\n\t\t\t\t\tself.was_grabbing = false;\n\t\t\t\t}\n\n\t\t\t\tif using_pen_tool {\n\t\t\t\t\tself.typing.clear();\n\n\t\t\t\t\tself.last_point = DVec2::ZERO;\n\t\t\t\t\tself.transform_operation = TransformOperation::None;\n\t\t\t\t\tself.handle = DVec2::ZERO;\n\n\t\t\t\t\tresponses.add(PenToolMessage::Abort);\n\t\t\t\t\tresponses.add(ToolMessage::UpdateHints);\n\t\t\t\t} else {\n\t\t\t\t\tselected.original_transforms.clear();\n\t\t\t\t\tself.typing.clear();\n\t\t\t\t\tself.transform_operation = TransformOperation::None;\n\n\t\t\t\t\tresponses.add(DocumentMessage::RepeatedAbortTransaction { undo_count: self.operation_count });\n\t\t\t\t\tself.operation_count = 0;\n\t\t\t\t\tresponses.add(ToolMessage::UpdateHints);\n\t\t\t\t}\n\n\t\t\t\tresponses.add(SelectToolMessage::PivotShift { offset: None, flush: false });\n\t\t\t\tresponses.add(OverlaysMessage::RemoveProvider {\n\t\t\t\t\tprovider: TRANSFORM_GRS_OVERLAY_PROVIDER,\n\t\t\t\t});\n\t\t\t}\n\t\t\tTransformLayerMessage::ConstrainX => {\n\t\t\t\tself.state.is_transforming_in_local_space = self.transform_operation.constrain_axis(Axis::X, &mut selected, &self.state, document);\n\t\t\t\tself.transform_operation.grs_typed(self.typing.evaluate(), &mut selected, &self.state, document);\n\t\t\t}\n\t\t\tTransformLayerMessage::ConstrainY => {\n\t\t\t\tself.state.is_transforming_in_local_space = self.transform_operation.constrain_axis(Axis::Y, &mut selected, &self.state, document);\n\t\t\t\tself.transform_operation.grs_typed(self.typing.evaluate(), &mut selected, &self.state, document);\n\t\t\t}\n\t\t\tTransformLayerMessage::PointerMove { slow_key, increments_key } => {\n\t\t\t\tself.slow = input.keyboard.get(slow_key as usize);\n\t\t\t\tlet old_ptz = self.ptz;\n\t\t\t\tself.ptz = document.document_ptz;\n\t\t\t\tif old_ptz != self.ptz {\n\t\t\t\t\tself.mouse_position = input.mouse.position;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet new_increments = input.keyboard.get(increments_key as usize);\n\t\t\t\tif new_increments != self.state.is_rounded_to_intervals {\n\t\t\t\t\tself.state.is_rounded_to_intervals = new_increments;\n\t\t\t\t\tself.transform_operation.apply_transform_operation(&mut selected, &self.state, document);\n\t\t\t\t}\n\n\t\t\t\tif self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() {\n\t\t\t\t\tmatch self.transform_operation {\n\t\t\t\t\t\tTransformOperation::None => {}\n\t\t\t\t\t\tTransformOperation::Grabbing(translation) => {\n\t\t\t\t\t\t\tlet delta_pos = input.mouse.position - self.mouse_position;\n\t\t\t\t\t\t\tlet delta_pos = (self.initial_transform * document_to_viewport.inverse()).transform_vector2(delta_pos);\n\t\t\t\t\t\t\tlet delta_viewport = if self.slow { delta_pos / SLOWING_DIVISOR } else { delta_pos };\n\t\t\t\t\t\t\tlet delta_scaled = delta_viewport / document_to_viewport.y_axis.length(); // Values are local to the viewport but scaled so values are relative to the current scale.\n\t\t\t\t\t\t\tself.transform_operation = TransformOperation::Grabbing(translation.increment_amount(delta_scaled));\n\t\t\t\t\t\t\tself.transform_operation.apply_transform_operation(&mut selected, &self.state, document);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tTransformOperation::Rotating(rotation) => {\n\t\t\t\t\t\t\tlet start_offset = self.state.pivot_viewport(document) - self.mouse_position;\n\t\t\t\t\t\t\tlet end_offset = self.state.pivot_viewport(document) - input.mouse.position;\n\t\t\t\t\t\t\tlet angle = start_offset.angle_to(end_offset);\n\n\t\t\t\t\t\t\tlet change = if self.slow { angle / SLOWING_DIVISOR } else { angle };\n\n\t\t\t\t\t\t\tself.transform_operation = TransformOperation::Rotating(rotation.increment_amount(change));\n\t\t\t\t\t\t\tself.transform_operation.apply_transform_operation(&mut selected, &self.state, document);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tTransformOperation::Scaling(mut scale) => {\n\t\t\t\t\t\t\tlet axis_constraint = scale.constraint;\n\t\t\t\t\t\t\tlet to_mouse_final = self.mouse_position - self.state.pivot_viewport(document);\n\t\t\t\t\t\t\tlet to_mouse_final_old = input.mouse.position - self.state.pivot_viewport(document);\n\t\t\t\t\t\t\tlet to_mouse_start = self.start_mouse - self.state.pivot_viewport(document);\n\n\t\t\t\t\t\t\tlet to_mouse_final = self.state.project_onto_constrained(to_mouse_final, axis_constraint);\n\t\t\t\t\t\t\tlet to_mouse_final_old = self.state.project_onto_constrained(to_mouse_final_old, axis_constraint);\n\t\t\t\t\t\t\tlet to_mouse_start = self.state.project_onto_constrained(to_mouse_start, axis_constraint);\n\n\t\t\t\t\t\t\tlet change = {\n\t\t\t\t\t\t\t\tlet previous_frame_dist = to_mouse_final.dot(to_mouse_start);\n\t\t\t\t\t\t\t\tlet current_frame_dist = to_mouse_final_old.dot(to_mouse_start);\n\t\t\t\t\t\t\t\tlet start_transform_dist = to_mouse_start.length_squared();\n\n\t\t\t\t\t\t\t\t(current_frame_dist - previous_frame_dist) / start_transform_dist\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tlet change = if self.slow { change / SLOWING_DIVISOR } else { change };\n\n\t\t\t\t\t\t\tscale = scale.increment_amount(change);\n\t\t\t\t\t\t\tself.transform_operation = TransformOperation::Scaling(scale);\n\t\t\t\t\t\t\tself.transform_operation.apply_transform_operation(&mut selected, &self.state, document);\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tself.mouse_position = input.mouse.position;\n\t\t\t}\n\t\t\tTransformLayerMessage::SelectionChanged => {\n\t\t\t\tlet target_layers = document.network_interface.selected_nodes().selected_layers(document.metadata()).collect();\n\t\t\t\tshape_editor.set_selected_layers(target_layers);\n\t\t\t}\n\t\t\tTransformLayerMessage::TypeBackspace => {\n\t\t\t\tif self.typing.digits.is_empty() && self.typing.negative {\n\t\t\t\t\tself.transform_operation.negate(&mut selected, &self.state, document);\n\t\t\t\t\tself.typing.type_negate();\n\t\t\t\t}\n\t\t\t\tself.transform_operation.grs_typed(self.typing.type_backspace(), &mut selected, &self.state, document);\n\t\t\t}\n\t\t\tTransformLayerMessage::TypeDecimalPoint => {\n\t\t\t\tif self.transform_operation.can_begin_typing() {\n\t\t\t\t\tself.transform_operation.grs_typed(self.typing.type_decimal_point(), &mut selected, &self.state, document)\n\t\t\t\t}\n\t\t\t}\n\t\t\tTransformLayerMessage::TypeDigit { digit } => {\n\t\t\t\tif self.transform_operation.can_begin_typing() {\n\t\t\t\t\tself.transform_operation.grs_typed(self.typing.type_number(digit), &mut selected, &self.state, document)\n\t\t\t\t}\n\t\t\t}\n\t\t\tTransformLayerMessage::TypeNegate => {\n\t\t\t\tif self.typing.digits.is_empty() {\n\t\t\t\t\tself.transform_operation.negate(&mut selected, &self.state, document);\n\t\t\t\t}\n\t\t\t\tself.transform_operation.grs_typed(self.typing.type_negate(), &mut selected, &self.state, document)\n\t\t\t}\n\t\t\tTransformLayerMessage::SetPivotGizmo { pivot_gizmo } => {\n\t\t\t\tself.pivot_gizmo = pivot_gizmo;\n\t\t\t}\n\t\t}\n\t}\n\n\tfn actions(&self) -> ActionList {\n\t\tlet mut common = actions!(TransformLayerMessageDiscriminant;\n\t\t\tBeginGrab,\n\t\t\tBeginRotate,\n\t\t\tBeginScale,\n\t\t);\n\n\t\tif self.transform_operation != TransformOperation::None {\n\t\t\tlet active = actions!(TransformLayerMessageDiscriminant;\n\t\t\t\tPointerMove,\n\t\t\t\tCancelTransformOperation,\n\t\t\t\tApplyTransformOperation,\n\t\t\t\tTypeDigit,\n\t\t\t\tTypeBackspace,\n\t\t\t\tTypeDecimalPoint,\n\t\t\t\tTypeNegate,\n\t\t\t\tConstrainX,\n\t\t\t\tConstrainY,\n\t\t\t);\n\t\t\tcommon.extend(active);\n\t\t}\n\n\t\tcommon\n\t}\n}\n\nimpl TransformLayerMessageHandler {\n\tpub fn is_transforming(&self) -> bool {\n\t\tself.transform_operation != TransformOperation::None\n\t}\n\n\tpub fn hints(&self, responses: &mut VecDeque<Message>) {\n\t\tself.transform_operation.hints(responses, self.state.is_transforming_in_local_space);\n\t}\n\n\tfn set_ghost_outline(ghost_outline: &mut Vec<(Vec<ClickTargetType>, DAffine2)>, shape_editor: &ShapeState, document: &DocumentMessageHandler) {\n\t\tghost_outline.clear();\n\t\tfor &layer in shape_editor.selected_shape_state.keys() {\n\t\t\t// We probably need to collect here\n\t\t\tlet outline = document.metadata().layer_with_free_points_outline(layer).cloned().collect();\n\t\t\tlet transform = document.metadata().transform_to_viewport(layer);\n\t\t\tghost_outline.push((outline, transform));\n\t\t}\n\t}\n}\n\nfn calculate_pivot(\n\tdocument: &DocumentMessageHandler,\n\tselected_points: &Vec<&ManipulatorPointId>,\n\tvector: &Vector,\n\tviewspace: DAffine2,\n\tget_location: impl Fn(&ManipulatorPointId) -> Option<DVec2>,\n\tgizmo: &mut PivotGizmo,\n) -> (Option<(DVec2, DVec2)>, Option<[DVec2; 2]>) {\n\tlet average_position = || {\n\t\tlet mut point_count = 0_usize;\n\t\tselected_points.iter().filter_map(|p| get_location(p)).inspect(|_| point_count += 1).sum::<DVec2>() / point_count as f64\n\t};\n\tlet bounds = selected_points.iter().filter_map(|p| get_location(p)).fold(None, |acc: Option<[DVec2; 2]>, point| {\n\t\tif let Some([mut min, mut max]) = acc {\n\t\t\tmin.x = min.x.min(point.x);\n\t\t\tmin.y = min.y.min(point.y);\n\t\t\tmax.x = max.x.max(point.x);\n\t\t\tmax.y = max.y.max(point.y);\n\t\t\tSome([min, max])\n\t\t} else {\n\t\t\tSome([point, point])\n\t\t}\n\t});\n\tgizmo.pivot.recalculate_pivot_for_layer(document, bounds);\n\tlet position = || {\n\t\t(if gizmo.state.enabled {\n\t\t\tmatch gizmo.state.gizmo_type {\n\t\t\t\tPivotGizmoType::Average => None,\n\t\t\t\tPivotGizmoType::Active => gizmo.point.and_then(|p| get_location(&p)),\n\t\t\t\tPivotGizmoType::Pivot => gizmo.pivot.pivot,\n\t\t\t}\n\t\t} else {\n\t\t\tNone\n\t\t})\n\t\t.unwrap_or_else(average_position)\n\t};\n\tlet [point] = selected_points.as_slice() else {\n\t\t// Handle the case where there are multiple points\n\t\tlet position = position();\n\t\treturn (Some((position, position)), bounds);\n\t};\n\n\tmatch point {\n\t\tManipulatorPointId::PrimaryHandle(_) | ManipulatorPointId::EndHandle(_) => {\n\t\t\t// Get the anchor position and transform it to the pivot\n\t\t\tlet (Some(pivot_position), Some(position)) = (\n\t\t\t\tpoint.get_anchor_position(vector).map(|anchor_position| viewspace.transform_point2(anchor_position)),\n\t\t\t\tpoint.get_position(vector),\n\t\t\t) else {\n\t\t\t\treturn (None, None);\n\t\t\t};\n\t\t\tlet target = viewspace.transform_point2(position);\n\t\t\t(Some((pivot_position, target)), None)\n\t\t}\n\t\t_ => {\n\t\t\t// Calculate the average position of all selected points\n\t\t\tlet position = position();\n\t\t\t(Some((position, position)), bounds)\n\t\t}\n\t}\n}\n\nfn update_colinear_handles(selected_layers: &[LayerNodeIdentifier], document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {\n\tfor &layer in selected_layers {\n\t\tlet Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };\n\n\t\tfor [handle1, handle2] in &vector.colinear_manipulators {\n\t\t\tlet manipulator1 = handle1.to_manipulator_point();\n\t\t\tlet manipulator2 = handle2.to_manipulator_point();\n\n\t\t\tlet Some(anchor) = manipulator1.get_anchor_position(&vector) else { continue };\n\t\t\tlet Some(pos1) = manipulator1.get_position(&vector).map(|pos| pos - anchor) else { continue };\n\t\t\tlet Some(pos2) = manipulator2.get_position(&vector).map(|pos| pos - anchor) else { continue };\n\n\t\t\tlet angle = pos1.angle_to(pos2);\n\n\t\t\t// Check if handles are not colinear (not approximately equal to +/- PI)\n\t\t\tif (angle - PI).abs() > 1e-6 && (angle + PI).abs() > 1e-6 {\n\t\t\t\tlet modification_type = VectorModificationType::SetG1Continuous {\n\t\t\t\t\thandles: [*handle1, *handle2],\n\t\t\t\t\tenabled: false,\n\t\t\t\t};\n\n\t\t\t\tresponses.add(GraphOperationMessage::Vector { layer, modification_type });\n\t\t\t}\n\t\t}\n\t}\n}\n\n#[cfg(test)]\nmod test_transform_layer {\n\tuse crate::messages::portfolio::document::graph_operation::transform_utils;\n\tuse crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext;\n\tuse crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;\n\tuse crate::messages::portfolio::document::utility_types::misc::GroupFolderType;\n\tuse crate::messages::prelude::Message;\n\tuse crate::messages::tool::transform_layer::transform_layer_message_handler::VectorModificationType;\n\tuse crate::test_utils::test_prelude::*;\n\tuse glam::DAffine2;\n\tuse graphene_std::vector::PointId;\n\tuse std::collections::VecDeque;\n\n\tasync fn get_layer_transform(editor: &mut EditorTestUtils, layer: LayerNodeIdentifier) -> Option<DAffine2> {\n\t\tlet document = editor.active_document();\n\t\tlet network_interface = &document.network_interface;\n\t\tlet _responses: VecDeque<Message> = VecDeque::new();\n\t\tlet transform_node_id = ModifyInputsContext::locate_node_in_layer_chain(&DefinitionIdentifier::Network(\"Transform\".into()), layer, network_interface)?;\n\t\tlet document_node = network_interface.document_network().nodes.get(&transform_node_id)?;\n\t\tSome(transform_utils::get_current_transform(&document_node.inputs))\n\t}\n\n\t#[tokio::test]\n\tasync fn test_grab_apply() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\n\t\teditor.drag_tool(ToolType::Rectangle, 0., 0., 100., 100., ModifierKeys::empty()).await;\n\n\t\tlet document = editor.active_document();\n\t\tlet layer = document.metadata().all_layers().next().unwrap();\n\n\t\tlet original_transform = get_layer_transform(&mut editor, layer).await.unwrap();\n\n\t\teditor.handle_message(TransformLayerMessage::BeginGrab).await;\n\n\t\tlet translation = DVec2::new(50., 50.);\n\t\teditor.move_mouse(translation.x, translation.y, ModifierKeys::empty(), MouseKeys::NONE).await;\n\n\t\teditor\n\t\t\t.handle_message(TransformLayerMessage::PointerMove {\n\t\t\t\tslow_key: Key::Shift,\n\t\t\t\tincrements_key: Key::Control,\n\t\t\t})\n\t\t\t.await;\n\n\t\teditor.handle_message(TransformLayerMessage::ApplyTransformOperation { final_transform: true }).await;\n\n\t\tlet final_transform = get_layer_transform(&mut editor, layer).await.unwrap();\n\n\t\tlet translation_diff = (final_transform.translation - original_transform.translation).length();\n\t\tassert!(translation_diff > 10., \"Transform should have changed after applying transformation. Diff: {translation_diff}\");\n\t}\n\n\t#[tokio::test]\n\tasync fn test_grab_cancel() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor.drag_tool(ToolType::Rectangle, 0., 0., 100., 100., ModifierKeys::empty()).await;\n\n\t\tlet document = editor.active_document();\n\t\tlet layer = document.metadata().all_layers().next().unwrap();\n\t\tlet original_transform = get_layer_transform(&mut editor, layer).await.expect(\"Should be able to get the layer transform\");\n\n\t\teditor.handle_message(TransformLayerMessage::BeginGrab).await;\n\t\teditor.move_mouse(50., 50., ModifierKeys::empty(), MouseKeys::NONE).await;\n\t\teditor\n\t\t\t.handle_message(TransformLayerMessage::PointerMove {\n\t\t\t\tslow_key: Key::Shift,\n\t\t\t\tincrements_key: Key::Control,\n\t\t\t})\n\t\t\t.await;\n\n\t\tlet during_transform = get_layer_transform(&mut editor, layer).await.expect(\"Should be able to get the layer transform during operation\");\n\n\t\tassert!(original_transform != during_transform, \"Transform should change during operation\");\n\n\t\teditor.handle_message(TransformLayerMessage::CancelTransformOperation).await;\n\n\t\tlet final_transform = get_layer_transform(&mut editor, layer).await.expect(\"Should be able to get the final transform\");\n\t\tlet final_translation = final_transform.translation;\n\t\tlet original_translation = original_transform.translation;\n\n\t\t// Verify transform is either restored to original OR reset to identity\n\t\tassert!(\n\t\t\t(final_translation - original_translation).length() < 5. || final_translation.length() < 0.001,\n\t\t\t\"Transform neither restored to original nor reset to identity. Original: {original_translation:?}, Final: {final_translation:?}\"\n\t\t);\n\t}\n\n\t#[tokio::test]\n\tasync fn test_rotate_apply() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor.drag_tool(ToolType::Rectangle, 0., 0., 100., 100., ModifierKeys::empty()).await;\n\n\t\tlet document = editor.active_document();\n\t\tlet layer = document.metadata().all_layers().next().unwrap();\n\n\t\tlet original_transform = get_layer_transform(&mut editor, layer).await.unwrap();\n\n\t\teditor.handle_message(TransformLayerMessage::BeginRotate).await;\n\n\t\teditor.move_mouse(150., 50., ModifierKeys::empty(), MouseKeys::NONE).await;\n\n\t\teditor\n\t\t\t.handle_message(TransformLayerMessage::PointerMove {\n\t\t\t\tslow_key: Key::Shift,\n\t\t\t\tincrements_key: Key::Control,\n\t\t\t})\n\t\t\t.await;\n\n\t\teditor.handle_message(TransformLayerMessage::ApplyTransformOperation { final_transform: true }).await;\n\n\t\tlet final_transform = get_layer_transform(&mut editor, layer).await.unwrap();\n\t\tprintln!(\"Final transform: {final_transform:?}\");\n\n\t\t// Check matrix components have changed (rotation affects matrix2)\n\t\tlet matrix_diff = (final_transform.matrix2.x_axis - original_transform.matrix2.x_axis).length();\n\t\tassert!(matrix_diff > 0.1, \"Rotation should have changed the transform matrix. Diff: {matrix_diff}\");\n\t}\n\n\t#[tokio::test]\n\tasync fn test_rotate_cancel() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor.drag_tool(ToolType::Rectangle, 0., 0., 100., 100., ModifierKeys::empty()).await;\n\n\t\tlet document = editor.active_document();\n\t\tlet layer = document.metadata().all_layers().next().unwrap();\n\t\tlet original_transform = get_layer_transform(&mut editor, layer).await.unwrap();\n\n\t\teditor.handle_message(TransformLayerMessage::BeginRotate).await;\n\t\teditor.handle_message(TransformLayerMessage::CancelTransformOperation).await;\n\n\t\tlet after_cancel = get_layer_transform(&mut editor, layer).await.unwrap();\n\n\t\tassert!(!after_cancel.translation.x.is_nan(), \"Transform is NaN after cancel\");\n\t\tassert!(!after_cancel.translation.y.is_nan(), \"Transform is NaN after cancel\");\n\n\t\tlet translation_diff = (after_cancel.translation - original_transform.translation).length();\n\t\tassert!(translation_diff < 1., \"Translation component changed too much: {translation_diff}\");\n\t}\n\n\t#[tokio::test]\n\tasync fn test_scale_apply() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor.drag_tool(ToolType::Rectangle, 0., 0., 100., 100., ModifierKeys::empty()).await;\n\n\t\tlet document = editor.active_document();\n\t\tlet layer = document.metadata().all_layers().next().unwrap();\n\n\t\tlet original_transform = get_layer_transform(&mut editor, layer).await.unwrap();\n\n\t\teditor.handle_message(TransformLayerMessage::BeginScale).await;\n\n\t\teditor.move_mouse(150., 150., ModifierKeys::empty(), MouseKeys::NONE).await;\n\n\t\teditor\n\t\t\t.handle_message(TransformLayerMessage::PointerMove {\n\t\t\t\tslow_key: Key::Shift,\n\t\t\t\tincrements_key: Key::Control,\n\t\t\t})\n\t\t\t.await;\n\n\t\teditor.handle_message(TransformLayerMessage::ApplyTransformOperation { final_transform: true }).await;\n\n\t\tlet final_transform = get_layer_transform(&mut editor, layer).await.unwrap();\n\n\t\t// Check scaling components have changed\n\t\tlet scale_diff_x = (final_transform.matrix2.x_axis.x - original_transform.matrix2.x_axis.x).abs();\n\t\tlet scale_diff_y = (final_transform.matrix2.y_axis.y - original_transform.matrix2.y_axis.y).abs();\n\n\t\tassert!(\n\t\t\tscale_diff_x > 0.1 || scale_diff_y > 0.1,\n\t\t\t\"Scaling should have changed the transform matrix. Diffs: x={scale_diff_x}, y={scale_diff_y}\"\n\t\t);\n\t}\n\n\t#[tokio::test]\n\tasync fn test_scale_cancel() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor.drag_tool(ToolType::Rectangle, 0., 0., 100., 100., ModifierKeys::empty()).await;\n\n\t\tlet document = editor.active_document();\n\t\tlet layer = document.metadata().all_layers().next().unwrap();\n\t\tlet original_transform = get_layer_transform(&mut editor, layer).await.unwrap();\n\n\t\teditor.handle_message(TransformLayerMessage::BeginScale).await;\n\n\t\t// Cancel immediately without moving to ensure proper reset\n\t\teditor.handle_message(TransformLayerMessage::CancelTransformOperation).await;\n\n\t\tlet after_cancel = get_layer_transform(&mut editor, layer).await.unwrap();\n\n\t\t// The scale factor is represented in the matrix2 part, so check those components\n\t\tassert!(\n\t\t\t(after_cancel.matrix2.x_axis.x - original_transform.matrix2.x_axis.x).abs() < 0.1 && (after_cancel.matrix2.y_axis.y - original_transform.matrix2.y_axis.y).abs() < 0.1,\n\t\t\t\"Matrix scale components should be restored after cancellation\"\n\t\t);\n\n\t\t// Also check translation component is similar\n\t\tlet translation_diff = (after_cancel.translation - original_transform.translation).length();\n\t\tassert!(translation_diff < 1., \"Translation component changed too much: {translation_diff}\");\n\t}\n\n\t#[tokio::test]\n\tasync fn test_grab_rotate_scale_chained() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor.drag_tool(ToolType::Rectangle, 0., 0., 100., 100., ModifierKeys::empty()).await;\n\t\tlet document = editor.active_document();\n\t\tlet layer = document.metadata().all_layers().next().unwrap();\n\t\teditor.handle_message(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] }).await;\n\t\tlet original_transform = get_layer_transform(&mut editor, layer).await.unwrap();\n\n\t\teditor.handle_message(TransformLayerMessage::BeginGrab).await;\n\t\teditor.move_mouse(150., 130., ModifierKeys::empty(), MouseKeys::NONE).await;\n\t\teditor\n\t\t\t.handle_message(TransformLayerMessage::PointerMove {\n\t\t\t\tslow_key: Key::Shift,\n\t\t\t\tincrements_key: Key::Control,\n\t\t\t})\n\t\t\t.await;\n\n\t\tlet after_grab_transform = get_layer_transform(&mut editor, layer).await.unwrap();\n\t\tlet expected_translation = DVec2::new(50., 30.);\n\t\tlet actual_translation = after_grab_transform.translation - original_transform.translation;\n\t\tassert!(\n\t\t\t(actual_translation - expected_translation).length() < 1e-5,\n\t\t\t\"Expected translation of {expected_translation:?}, got {actual_translation:?}\"\n\t\t);\n\n\t\t// 2. Chain to rotation - from current position to create ~45 degree rotation\n\t\teditor.handle_message(TransformLayerMessage::BeginRotate).await;\n\t\teditor.move_mouse(190., 90., ModifierKeys::empty(), MouseKeys::NONE).await;\n\t\teditor\n\t\t\t.handle_message(TransformLayerMessage::PointerMove {\n\t\t\t\tslow_key: Key::Shift,\n\t\t\t\tincrements_key: Key::Control,\n\t\t\t})\n\t\t\t.await;\n\t\tlet after_rotate_transform = get_layer_transform(&mut editor, layer).await.unwrap();\n\t\t// Checking for off-diagonal elements close to 0.707, which corresponds to cos(45°) and sin(45°)\n\t\tassert!(\n\t\t\t!after_rotate_transform.matrix2.abs_diff_eq(after_grab_transform.matrix2, 1e-5) &&\n\t\t\t(after_rotate_transform.matrix2.x_axis.y.abs() - 0.707).abs() < 0.1 &&  // Check for off-diagonal elements close to 0.707\n\t\t\t(after_rotate_transform.matrix2.y_axis.x.abs() - 0.707).abs() < 0.1, // that would indicate ~45° rotation\n\t\t\t\"Rotation should change matrix components with approximately 45° rotation\"\n\t\t);\n\n\t\t// 3. Chain to scaling - scale(area) up by 2x\n\t\teditor.handle_message(TransformLayerMessage::BeginScale).await;\n\t\teditor.move_mouse(250., 200., ModifierKeys::empty(), MouseKeys::NONE).await;\n\t\teditor\n\t\t\t.handle_message(TransformLayerMessage::PointerMove {\n\t\t\t\tslow_key: Key::Shift,\n\t\t\t\tincrements_key: Key::Control,\n\t\t\t})\n\t\t\t.await;\n\n\t\tlet after_scale_transform = get_layer_transform(&mut editor, layer).await.unwrap();\n\t\tlet before_scale_det = after_rotate_transform.matrix2.determinant();\n\t\tlet after_scale_det = after_scale_transform.matrix2.determinant();\n\t\tassert!(\n\t\t\tafter_scale_det >= 2. * before_scale_det,\n\t\t\t\"Scale should increase the determinant of the matrix (before: {before_scale_det}, after: {after_scale_det})\"\n\t\t);\n\n\t\teditor.handle_message(TransformLayerMessage::ApplyTransformOperation { final_transform: true }).await;\n\t\tlet final_transform = get_layer_transform(&mut editor, layer).await.unwrap();\n\n\t\tassert!(final_transform.abs_diff_eq(after_scale_transform, 1e-5), \"Final transform should match the transform before committing\");\n\t\tassert!(!final_transform.abs_diff_eq(original_transform, 1e-5), \"Final transform should be different from original transform\");\n\t}\n\n\t#[tokio::test]\n\tasync fn test_scale_with_panned_view() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor.drag_tool(ToolType::Rectangle, 0., 0., 100., 100., ModifierKeys::empty()).await;\n\t\tlet document = editor.active_document();\n\t\tlet layer = document.metadata().all_layers().next().unwrap();\n\n\t\tlet original_transform = get_layer_transform(&mut editor, layer).await.unwrap();\n\n\t\tlet pan_amount = DVec2::new(200., 150.);\n\t\teditor.handle_message(NavigationMessage::CanvasPan { delta: pan_amount }).await;\n\n\t\teditor.handle_message(TransformLayerMessage::BeginScale).await;\n\t\teditor.handle_message(TransformLayerMessage::TypeDigit { digit: 2 }).await;\n\t\teditor.handle_message(TransformLayerMessage::ApplyTransformOperation { final_transform: true }).await;\n\n\t\tlet final_transform = get_layer_transform(&mut editor, layer).await.unwrap();\n\n\t\tlet scale_x = final_transform.matrix2.x_axis.length() / original_transform.matrix2.x_axis.length();\n\t\tlet scale_y = final_transform.matrix2.y_axis.length() / original_transform.matrix2.y_axis.length();\n\n\t\tassert!((scale_x - 2.).abs() < 0.1, \"Expected scale factor X of 2, got: {scale_x}\");\n\t\tassert!((scale_y - 2.).abs() < 0.1, \"Expected scale factor Y of 2, got: {scale_y}\");\n\t}\n\n\t#[tokio::test]\n\tasync fn test_scale_with_zoomed_view() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor.drag_tool(ToolType::Rectangle, 0., 0., 100., 100., ModifierKeys::empty()).await;\n\t\tlet document = editor.active_document();\n\t\tlet layer = document.metadata().all_layers().next().unwrap();\n\n\t\tlet original_transform = get_layer_transform(&mut editor, layer).await.unwrap();\n\n\t\teditor.handle_message(NavigationMessage::CanvasZoomIncrease { center_on_mouse: false }).await;\n\t\teditor.handle_message(NavigationMessage::CanvasZoomIncrease { center_on_mouse: false }).await;\n\n\t\teditor.handle_message(TransformLayerMessage::BeginScale).await;\n\t\teditor.handle_message(TransformLayerMessage::TypeDigit { digit: 2 }).await;\n\t\teditor.handle_message(TransformLayerMessage::ApplyTransformOperation { final_transform: true }).await;\n\n\t\tlet final_transform = get_layer_transform(&mut editor, layer).await.unwrap();\n\n\t\tlet scale_x = final_transform.matrix2.x_axis.length() / original_transform.matrix2.x_axis.length();\n\t\tlet scale_y = final_transform.matrix2.y_axis.length() / original_transform.matrix2.y_axis.length();\n\n\t\tassert!((scale_x - 2.).abs() < 0.1, \"Expected scale factor X of 2, got: {scale_x}\");\n\t\tassert!((scale_y - 2.).abs() < 0.1, \"Expected scale factor Y of 2, got: {scale_y}\");\n\t}\n\n\t#[tokio::test]\n\tasync fn test_rotate_with_rotated_view() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor.drag_tool(ToolType::Rectangle, 0., 0., 100., 100., ModifierKeys::empty()).await;\n\t\tlet document = editor.active_document();\n\t\tlet layer = document.metadata().all_layers().next().unwrap();\n\n\t\tlet original_transform = get_layer_transform(&mut editor, layer).await.unwrap();\n\n\t\t// Rotate the document view (45 degrees)\n\t\teditor.handle_message(NavigationMessage::BeginCanvasTilt { was_dispatched_from_menu: false }).await;\n\t\teditor.handle_message(NavigationMessage::CanvasTiltSet { angle_radians: 45_f64.to_radians() }).await;\n\t\teditor.handle_message(TransformLayerMessage::BeginRotate).await;\n\n\t\teditor.handle_message(TransformLayerMessage::TypeDigit { digit: 9 }).await;\n\t\teditor.handle_message(TransformLayerMessage::TypeDigit { digit: 0 }).await;\n\t\teditor.handle_message(TransformLayerMessage::ApplyTransformOperation { final_transform: true }).await;\n\n\t\tlet final_transform = get_layer_transform(&mut editor, layer).await.unwrap();\n\n\t\tlet original_angle = original_transform.to_scale_angle_translation().1;\n\t\tlet final_angle = final_transform.to_scale_angle_translation().1;\n\t\tlet angle_change = (final_angle - original_angle).to_degrees();\n\n\t\t// Normalize angle between 0 and 360\n\t\tlet angle_change = ((angle_change % 360.) + 360.) % 360.;\n\t\tassert!((angle_change - 90.).abs() < 0.1, \"Expected rotation of 90 degrees, got: {angle_change}\");\n\t}\n\n\t#[tokio::test]\n\tasync fn test_grs_single_anchor() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor.handle_message(DocumentMessage::CreateEmptyFolder).await;\n\t\tlet document = editor.active_document();\n\t\tlet layer = document.metadata().all_layers().next().unwrap();\n\n\t\tlet point_id = PointId::generate();\n\t\tlet modification_type = VectorModificationType::InsertPoint {\n\t\t\tid: point_id,\n\t\t\tposition: DVec2::new(100., 100.),\n\t\t};\n\t\teditor.handle_message(GraphOperationMessage::Vector { layer, modification_type }).await;\n\t\teditor.handle_message(ToolMessage::ActivateTool { tool_type: ToolType::Select }).await;\n\n\t\t// Testing grab operation - just checking that it doesn't crash.\n\t\teditor.handle_message(TransformLayerMessage::BeginGrab).await;\n\t\teditor.move_mouse(150., 150., ModifierKeys::empty(), MouseKeys::NONE).await;\n\t\teditor\n\t\t\t.handle_message(TransformLayerMessage::PointerMove {\n\t\t\t\tslow_key: Key::Shift,\n\t\t\t\tincrements_key: Key::Control,\n\t\t\t})\n\t\t\t.await;\n\t\teditor.handle_message(TransformLayerMessage::ApplyTransformOperation { final_transform: true }).await;\n\n\t\tlet final_transform = get_layer_transform(&mut editor, layer).await;\n\t\tassert!(final_transform.is_some(), \"Transform node should exist after grab operation\");\n\t}\n\t#[tokio::test]\n\tasync fn test_scale_to_zero_then_rescale() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor.drag_tool(ToolType::Rectangle, 0., 0., 100., 100., ModifierKeys::empty()).await;\n\t\tlet document = editor.active_document();\n\t\tlet layer = document.metadata().all_layers().next().unwrap();\n\n\t\t// First scale to near-zero\n\t\teditor.handle_message(TransformLayerMessage::BeginScale).await;\n\t\teditor.handle_message(TransformLayerMessage::TypeDigit { digit: 0 }).await;\n\t\teditor.handle_message(TransformLayerMessage::TypeDecimalPoint).await;\n\t\teditor.handle_message(TransformLayerMessage::TypeDigit { digit: 0 }).await;\n\t\teditor.handle_message(TransformLayerMessage::TypeDigit { digit: 0 }).await;\n\t\teditor.handle_message(TransformLayerMessage::TypeDigit { digit: 0 }).await;\n\t\teditor.handle_message(TransformLayerMessage::TypeDigit { digit: 1 }).await;\n\t\teditor.handle_message(TransformLayerMessage::ApplyTransformOperation { final_transform: true }).await;\n\n\t\tlet near_zero_transform = get_layer_transform(&mut editor, layer).await.unwrap();\n\t\t// Verify scale is near zero.\n\t\tlet scale_x = near_zero_transform.matrix2.x_axis.length();\n\t\tlet scale_y = near_zero_transform.matrix2.y_axis.length();\n\t\tassert!(scale_x < 0.001, \"Scale factor X should be near zero, got: {scale_x}\");\n\t\tassert!(scale_y < 0.001, \"Scale factor Y should be near zero, got: {scale_y}\");\n\t\tassert!(scale_x > 0., \"Scale factor X should not be exactly zero\");\n\t\tassert!(scale_y > 0., \"Scale factor Y should not be exactly zero\");\n\n\t\teditor.handle_message(TransformLayerMessage::BeginScale).await;\n\t\teditor.handle_message(TransformLayerMessage::TypeDigit { digit: 2 }).await;\n\t\teditor.handle_message(TransformLayerMessage::ApplyTransformOperation { final_transform: true }).await;\n\n\t\tlet final_transform = get_layer_transform(&mut editor, layer).await.unwrap();\n\t\tassert!(final_transform.is_finite(), \"Transform should be finite after rescaling\");\n\n\t\tlet new_scale_x = final_transform.matrix2.x_axis.length();\n\t\tlet new_scale_y = final_transform.matrix2.y_axis.length();\n\t\tassert!(new_scale_x > 0., \"After rescaling, scale factor X should be non-zero\");\n\t\tassert!(new_scale_y > 0., \"After rescaling, scale factor Y should be non-zero\");\n\t}\n\n\t#[tokio::test]\n\tasync fn test_transform_with_different_selections() {\n\t\tlet mut editor = EditorTestUtils::create();\n\t\teditor.new_document().await;\n\t\teditor.draw_rect(0., 0., 100., 100.).await;\n\t\teditor.draw_rect(150., 0., 250., 100.).await;\n\t\teditor.draw_rect(0., 150., 100., 250.).await;\n\t\teditor.draw_rect(150., 150., 250., 250.).await;\n\t\tlet document = editor.active_document();\n\t\tlet layers: Vec<LayerNodeIdentifier> = document.metadata().all_layers().collect();\n\n\t\tassert!(layers.len() == 4);\n\n\t\t// Creating a group with two rectangles\n\t\teditor\n\t\t\t.handle_message(NodeGraphMessage::SelectedNodesSet {\n\t\t\t\tnodes: vec![layers[2].to_node(), layers[3].to_node()],\n\t\t\t})\n\t\t\t.await;\n\t\teditor\n\t\t\t.handle_message(DocumentMessage::GroupSelectedLayers {\n\t\t\t\tgroup_folder_type: GroupFolderType::Layer,\n\t\t\t})\n\t\t\t.await;\n\n\t\t// Get the group layer (should be the newest layer)\n\t\tlet document = editor.active_document();\n\t\tlet group_layer = document.metadata().all_layers().next().unwrap();\n\n\t\t// Test 1: Transform single layer\n\t\teditor.handle_message(NodeGraphMessage::SelectedNodesSet { nodes: vec![layers[0].to_node()] }).await;\n\t\tlet original_transform = get_layer_transform(&mut editor, layers[0]).await.unwrap();\n\t\teditor.handle_message(TransformLayerMessage::BeginGrab).await;\n\t\teditor.move_mouse(50., 50., ModifierKeys::empty(), MouseKeys::NONE).await;\n\t\teditor\n\t\t\t.handle_message(TransformLayerMessage::PointerMove {\n\t\t\t\tslow_key: Key::Shift,\n\t\t\t\tincrements_key: Key::Control,\n\t\t\t})\n\t\t\t.await;\n\t\teditor.handle_message(TransformLayerMessage::ApplyTransformOperation { final_transform: true }).await;\n\t\tlet final_transform = get_layer_transform(&mut editor, layers[0]).await.unwrap();\n\t\tassert!(!final_transform.abs_diff_eq(original_transform, 1e-5), \"Transform should change for single layer\");\n\n\t\t// Test 2: Transform multiple layers\n\t\teditor\n\t\t\t.handle_message(NodeGraphMessage::SelectedNodesSet {\n\t\t\t\tnodes: vec![layers[0].to_node(), layers[1].to_node()],\n\t\t\t})\n\t\t\t.await;\n\t\tlet original_transform_1 = get_layer_transform(&mut editor, layers[0]).await.unwrap();\n\t\tlet original_transform_2 = get_layer_transform(&mut editor, layers[1]).await.unwrap();\n\t\teditor.handle_message(TransformLayerMessage::BeginRotate).await;\n\t\teditor.move_mouse(200., 50., ModifierKeys::empty(), MouseKeys::NONE).await;\n\t\teditor\n\t\t\t.handle_message(TransformLayerMessage::PointerMove {\n\t\t\t\tslow_key: Key::Shift,\n\t\t\t\tincrements_key: Key::Control,\n\t\t\t})\n\t\t\t.await;\n\t\teditor.handle_message(TransformLayerMessage::ApplyTransformOperation { final_transform: true }).await;\n\t\tlet final_transform_1 = get_layer_transform(&mut editor, layers[0]).await.unwrap();\n\t\tlet final_transform_2 = get_layer_transform(&mut editor, layers[1]).await.unwrap();\n\t\tassert!(!final_transform_1.abs_diff_eq(original_transform_1, 1e-5), \"Transform should change for first layer in multi-selection\");\n\t\tassert!(\n\t\t\t!final_transform_2.abs_diff_eq(original_transform_2, 1e-5),\n\t\t\t\"Transform should change for second layer in multi-selection\"\n\t\t);\n\n\t\t// Test 3: Transform group\n\t\teditor.handle_message(NodeGraphMessage::SelectedNodesSet { nodes: vec![group_layer.to_node()] }).await;\n\t\tlet original_group_transform = get_layer_transform(&mut editor, group_layer).await.unwrap();\n\t\teditor.handle_message(TransformLayerMessage::BeginScale).await;\n\t\teditor.handle_message(TransformLayerMessage::TypeDigit { digit: 2 }).await;\n\t\teditor.handle_message(TransformLayerMessage::ApplyTransformOperation { final_transform: true }).await;\n\t\tlet final_group_transform = get_layer_transform(&mut editor, group_layer).await.unwrap();\n\t\tassert!(!final_group_transform.abs_diff_eq(original_group_transform, 1e-5), \"Transform should change for group\");\n\n\t\t// Test 4: Transform layers inside transformed group\n\t\tlet child_layer_id = {\n\t\t\tlet document = editor.active_document_mut();\n\t\t\tlet group_children = document.network_interface.downstream_layers(&group_layer.to_node(), &[]);\n\t\t\tif !group_children.is_empty() {\n\t\t\t\tSome(LayerNodeIdentifier::new(group_children[0], &document.network_interface))\n\t\t\t} else {\n\t\t\t\tNone\n\t\t\t}\n\t\t};\n\t\tassert!(child_layer_id.is_some(), \"Group should have child layers\");\n\t\tlet child_layer_id = child_layer_id.unwrap();\n\t\teditor\n\t\t\t.handle_message(NodeGraphMessage::SelectedNodesSet {\n\t\t\t\tnodes: vec![child_layer_id.to_node()],\n\t\t\t})\n\t\t\t.await;\n\t\tlet original_child_transform = get_layer_transform(&mut editor, child_layer_id).await.unwrap();\n\t\teditor.handle_message(TransformLayerMessage::BeginGrab).await;\n\t\teditor.move_mouse(30., 30., ModifierKeys::empty(), MouseKeys::NONE).await;\n\t\teditor\n\t\t\t.handle_message(TransformLayerMessage::PointerMove {\n\t\t\t\tslow_key: Key::Shift,\n\t\t\t\tincrements_key: Key::Control,\n\t\t\t})\n\t\t\t.await;\n\t\teditor.handle_message(TransformLayerMessage::ApplyTransformOperation { final_transform: true }).await;\n\t\tlet final_child_transform = get_layer_transform(&mut editor, child_layer_id).await.unwrap();\n\t\tassert!(!final_child_transform.abs_diff_eq(original_child_transform, 1e-5), \"Child layer inside transformed group should change\");\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/tool/utility_types.rs",
    "content": "#![allow(clippy::too_many_arguments)]\n\nuse super::common_functionality::shape_editor::ShapeState;\nuse super::tool_messages::*;\nuse crate::messages::broadcast::BroadcastMessage;\nuse crate::messages::broadcast::event::EventMessage;\nuse crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, LabeledKeyOrMouseMotion, LabeledShortcut, MouseMotion};\nuse crate::messages::input_mapper::utility_types::macros::action_shortcut;\nuse crate::messages::input_mapper::utility_types::misc::ActionShortcut;\nuse crate::messages::layout::utility_types::widget_prelude::*;\nuse crate::messages::portfolio::document::overlays::utility_types::OverlayProvider;\nuse crate::messages::portfolio::utility_types::PersistentData;\nuse crate::messages::preferences::PreferencesMessageHandler;\nuse crate::messages::prelude::*;\nuse crate::messages::tool::common_functionality::shapes::shape_utility::ShapeType;\nuse crate::node_graph_executor::NodeGraphExecutor;\nuse graphene_std::raster::color::Color;\nuse std::borrow::Cow;\nuse std::fmt::{self, Debug};\n\n#[derive(ExtractField)]\npub struct ToolActionMessageContext<'a> {\n\tpub document: &'a mut DocumentMessageHandler,\n\tpub document_id: DocumentId,\n\tpub global_tool_data: &'a DocumentToolData,\n\tpub input: &'a InputPreprocessorMessageHandler,\n\tpub persistent_data: &'a PersistentData,\n\tpub shape_editor: &'a mut ShapeState,\n\tpub node_graph: &'a NodeGraphExecutor,\n\tpub preferences: &'a PreferencesMessageHandler,\n\tpub viewport: &'a ViewportMessageHandler,\n}\n\npub trait ToolCommon: for<'a, 'b> MessageHandler<ToolMessage, &'b mut ToolActionMessageContext<'a>> + ToolRefreshOptions + ToolTransition + ToolMetadata {}\nimpl<T> ToolCommon for T where T: for<'a, 'b> MessageHandler<ToolMessage, &'b mut ToolActionMessageContext<'a>> + ToolRefreshOptions + ToolTransition + ToolMetadata {}\n\ntype Tool = dyn ToolCommon + Send + Sync;\n\npub trait ToolRefreshOptions {\n\tfn refresh_options(&self, responses: &mut VecDeque<Message>, _persistent_data: &PersistentData);\n}\n\nimpl<T: LayoutHolder> ToolRefreshOptions for T {\n\tfn refresh_options(&self, responses: &mut VecDeque<Message>, _persistent_data: &PersistentData) {\n\t\tself.send_layout(responses, LayoutTarget::ToolOptions);\n\t}\n}\n\n/// The FSM (finite state machine) is a flowchart between different operating states that a specific tool might be in.\n/// It is the central \"core\" logic area of each tool which is in charge of maintaining the state of the tool and responding to events coming from outside (like user input).\n/// For example, a tool might be `Ready` or `Drawing` depending on if the user is idle or actively drawing with the mouse held down.\n/// The FSM keeps track of what the tool is doing and allows the tool to take action when events are directed at the FSM.\n/// Every tool, which implements this trait, must implement the `transition()` function.\n/// That is where new events are sent, and where the flowchart transition logic occurs to respond to events and end in a new state.\npub trait Fsm {\n\t/// The implementing tool must set this to a struct designed to store the internal values stored in the tool.\n\t/// For example, it might be used to store the starting location of a point when a drag began so the displacement distance can be calculated.\n\ttype ToolData;\n\t/// The implementing tool must set this to a struct (or `()` if none) designed to store the values of the tool options set by the user in the tool controls portion on the left of the control bar.\n\ttype ToolOptions;\n\n\t/// Implementing this mandatory trait function lets a specific tool react accordingly (and potentially change its state or internal variables) upon receiving an event to do something.\n\t/// Based on its current state, and what the event is, the FSM (finite state machine) should direct the tool to an appropriate outcome.\n\t/// For example, if the tool's FSM is in a `Ready` state and receives a `DragStart` message as its event, it may decide to send some messages,\n\t/// update some internal tool variables, and end by transitioning to a `Drawing` state.\n\t#[must_use]\n\tfn transition(self, message: ToolMessage, tool_data: &mut Self::ToolData, transition_data: &mut ToolActionMessageContext, options: &Self::ToolOptions, responses: &mut VecDeque<Message>) -> Self;\n\n\t/// Implementing this trait function lets a specific tool provide a list of hints (user input actions presently available) to draw in the footer bar.\n\tfn update_hints(&self, responses: &mut VecDeque<Message>);\n\t/// Implementing this trait function lets a specific tool set the current mouse cursor icon.\n\tfn update_cursor(&self, responses: &mut VecDeque<Message>);\n\n\t/// If this message is a standard tool message, process it and return true. Standard tool messages are those which are common across every tool.\n\tfn standard_tool_messages(&self, message: &ToolMessage, responses: &mut VecDeque<Message>) -> bool {\n\t\t// Check for standard hits or cursor events\n\t\tmatch message {\n\t\t\tToolMessage::UpdateHints => {\n\t\t\t\tself.update_hints(responses);\n\t\t\t\ttrue\n\t\t\t}\n\t\t\tToolMessage::UpdateCursor => {\n\t\t\t\tself.update_cursor(responses);\n\t\t\t\ttrue\n\t\t\t}\n\t\t\t_ => false,\n\t\t}\n\t}\n\n\t/// When an event makes the tool change or do something, it is processed here to perform a step (transition) on the tool's finite state machine (FSM).\n\t/// This function is called by the specific tool's message handler when the dispatcher routes a message to the active tool.\n\tfn process_event(\n\t\t&mut self,\n\t\tmessage: ToolMessage,\n\t\ttool_data: &mut Self::ToolData,\n\t\ttransition_data: &mut ToolActionMessageContext,\n\t\toptions: &Self::ToolOptions,\n\t\tresponses: &mut VecDeque<Message>,\n\t\tupdate_cursor_on_transition: bool,\n\t) where\n\t\tSelf: PartialEq + Sized + Copy,\n\t{\n\t\t// If this message is one of the standard tool messages, process it and exit early\n\t\tif self.standard_tool_messages(&message, responses) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Transition the tool\n\t\tlet new_state = self.transition(message, tool_data, transition_data, options, responses);\n\n\t\t// Update state\n\t\tif *self != new_state {\n\t\t\t*self = new_state;\n\t\t\tself.update_hints(responses);\n\t\t\tif update_cursor_on_transition {\n\t\t\t\tself.update_cursor(responses);\n\t\t\t}\n\t\t}\n\t}\n}\n\n#[derive(Debug, Clone)]\npub struct DocumentToolData {\n\tpub primary_color: Color,\n\tpub secondary_color: Color,\n}\n\nimpl DocumentToolData {\n\tpub fn update_working_colors(&self, responses: &mut VecDeque<Message>) {\n\t\tlet layout = Layout(vec![\n\t\t\tLayoutGroup::row(vec![\n\t\t\t\tWorkingColorsInput::new(self.primary_color.to_gamma_srgb(), self.secondary_color.to_gamma_srgb()).widget_instance(),\n\t\t\t]),\n\t\t\tLayoutGroup::row(vec![\n\t\t\t\tIconButton::new(\"SwapVertical\", 16)\n\t\t\t\t\t.tooltip_label(\"Swap Working Colors\")\n\t\t\t\t\t.tooltip_shortcut(action_shortcut!(ToolMessageDiscriminant::SwapColors))\n\t\t\t\t\t.on_update(|_| ToolMessage::SwapColors.into())\n\t\t\t\t\t.widget_instance(),\n\t\t\t\tIconButton::new(\"WorkingColors\", 16)\n\t\t\t\t\t.tooltip_label(\"Reset Working Colors\")\n\t\t\t\t\t.tooltip_shortcut(action_shortcut!(ToolMessageDiscriminant::ResetColors))\n\t\t\t\t\t.on_update(|_| ToolMessage::ResetColors.into())\n\t\t\t\t\t.widget_instance(),\n\t\t\t]),\n\t\t]);\n\n\t\tresponses.add(LayoutMessage::SendLayout {\n\t\t\tlayout,\n\t\t\tlayout_target: LayoutTarget::WorkingColors,\n\t\t});\n\n\t\tresponses.add(BroadcastMessage::TriggerEvent(EventMessage::WorkingColorChanged));\n\t}\n}\n\n#[derive(Clone, Debug, Default)]\npub struct EventToMessageMap {\n\tpub canvas_transformed: Option<ToolMessage>,\n\tpub selection_changed: Option<ToolMessage>,\n\tpub tool_abort: Option<ToolMessage>,\n\tpub working_color_changed: Option<ToolMessage>,\n\tpub overlay_provider: Option<OverlayProvider>,\n}\n\npub trait ToolTransition {\n\tfn event_to_message_map(&self) -> EventToMessageMap;\n\n\tfn activate(&self, responses: &mut VecDeque<Message>) {\n\t\tlet mut subscribe_message = |broadcast_to_tool_mapping: Option<ToolMessage>, event: EventMessage| {\n\t\t\tif let Some(mapping) = broadcast_to_tool_mapping {\n\t\t\t\tresponses.add(BroadcastMessage::SubscribeEvent {\n\t\t\t\t\ton: event,\n\t\t\t\t\tsend: Box::new(mapping.into()),\n\t\t\t\t});\n\t\t\t}\n\t\t};\n\n\t\tlet event_to_tool_map = self.event_to_message_map();\n\t\tsubscribe_message(event_to_tool_map.canvas_transformed, EventMessage::CanvasTransformed);\n\t\tsubscribe_message(event_to_tool_map.tool_abort, EventMessage::ToolAbort);\n\t\tsubscribe_message(event_to_tool_map.selection_changed, EventMessage::SelectionChanged);\n\t\tsubscribe_message(event_to_tool_map.working_color_changed, EventMessage::WorkingColorChanged);\n\t\tif let Some(overlay_provider) = event_to_tool_map.overlay_provider {\n\t\t\tresponses.add(OverlaysMessage::AddProvider { provider: overlay_provider });\n\t\t}\n\t}\n\n\tfn deactivate(&self, responses: &mut VecDeque<Message>) {\n\t\tlet mut unsubscribe_message = |broadcast_to_tool_mapping: Option<ToolMessage>, event: EventMessage| {\n\t\t\tif let Some(mapping) = broadcast_to_tool_mapping {\n\t\t\t\tresponses.add(BroadcastMessage::UnsubscribeEvent {\n\t\t\t\t\ton: event,\n\t\t\t\t\tsend: Box::new(mapping.into()),\n\t\t\t\t});\n\t\t\t}\n\t\t};\n\n\t\tlet event_to_tool_map = self.event_to_message_map();\n\t\tunsubscribe_message(event_to_tool_map.canvas_transformed, EventMessage::CanvasTransformed);\n\t\tunsubscribe_message(event_to_tool_map.tool_abort, EventMessage::ToolAbort);\n\t\tunsubscribe_message(event_to_tool_map.selection_changed, EventMessage::SelectionChanged);\n\t\tunsubscribe_message(event_to_tool_map.working_color_changed, EventMessage::WorkingColorChanged);\n\t\tif let Some(overlay_provider) = event_to_tool_map.overlay_provider {\n\t\t\tresponses.add(OverlaysMessage::RemoveProvider { provider: overlay_provider });\n\t\t}\n\t}\n}\n\npub trait ToolMetadata {\n\tfn icon_name(&self) -> String;\n\tfn tooltip_label(&self) -> String;\n\tfn tooltip_description(&self) -> String {\n\t\t// TODO: Remove this to make tool descriptions mandatory once we've written them all\n\t\tString::new()\n\t}\n\tfn tool_type(&self) -> ToolType;\n}\n\npub struct ToolData {\n\tpub active_tool_type: ToolType,\n\tpub active_shape_type: Option<ToolType>,\n\tpub tools: HashMap<ToolType, Box<Tool>>,\n}\n\nimpl fmt::Debug for ToolData {\n\tfn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n\t\tf.debug_struct(\"ToolData\").field(\"active_tool_type\", &self.active_tool_type).field(\"tool_options\", &\"[…]\").finish()\n\t}\n}\n\nimpl ToolData {\n\tpub fn active_tool_mut(&mut self) -> &mut Box<Tool> {\n\t\tself.tools.get_mut(&self.active_tool_type).expect(\"The active tool is not initialized\")\n\t}\n\n\tpub fn active_tool(&self) -> &Tool {\n\t\tself.tools.get(&self.active_tool_type).map(|x| x.as_ref()).expect(\"The active tool is not initialized\")\n\t}\n}\n\nimpl ToolData {\n\tpub fn send_layout(&self, responses: &mut VecDeque<Message>, layout_target: LayoutTarget, brush_tool: bool) {\n\t\tresponses.add(LayoutMessage::SendLayout {\n\t\t\tlayout: self.layout(brush_tool),\n\t\t\tlayout_target,\n\t\t});\n\t}\n\n\tfn layout(&self, brush_tool: bool) -> Layout {\n\t\tlet active_tool = self.active_shape_type.unwrap_or(self.active_tool_type);\n\n\t\tlet tool_groups_layout = list_tools_in_groups()\n\t\t\t.iter()\n\t\t\t.map(|tool_group|\n\t\t\t\ttool_group\n\t\t\t\t\t.iter()\n\t\t\t\t\t.filter_map(|tool_availability| {\n\t\t\t\t\t\tif !brush_tool && let ToolRole::Normal(tool) = tool_availability && tool.tool_type() == ToolType::Brush {\n\t\t\t\t\t\t\treturn None;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tSome(match tool_availability {\n\t\t\t\t\t\t\tToolRole::Normal(tool) =>\n\t\t\t\t\t\t\t\tToolEntry::new(tool.tool_type(), tool.icon_name())\n\t\t\t\t\t\t\t\t\t.tooltip_label(tool.tooltip_label())\n\t\t\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(tool_type_to_activate_tool_message(tool.tool_type()))),\n\t\t\t\t\t\t\tToolRole::Shape(shape) =>\n\t\t\t\t\t\t\t\tToolEntry::new(shape.tool_type(), shape.icon_name())\n\t\t\t\t\t\t\t\t\t.tooltip_label(shape.tooltip_label())\n\t\t\t\t\t\t\t\t\t.tooltip_description(shape.tooltip_description())\n\t\t\t\t\t\t\t\t\t.tooltip_shortcut(action_shortcut!(tool_type_to_activate_tool_message(shape.tool_type()))),\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t\t\t.collect::<Vec<_>>()\n\t\t\t)\n\t\t\t.filter(|group| !group.is_empty())\n\t\t\t.flat_map(|group| {\n\t\t\t\tlet separator = std::iter::once(Separator::new(SeparatorStyle::Section).direction(SeparatorDirection::Vertical).widget_instance());\n\t\t\t\tlet buttons = group.into_iter().map(|ToolEntry { tooltip_label, tooltip_description, tooltip_shortcut, tool_type, icon_name }| {\n\t\t\t\t\tIconButton::new(icon_name, 32)\n\t\t\t\t\t\t.disabled(false)\n\t\t\t\t\t\t.emphasized(match tool_type {\n\t\t\t\t\t\t\tToolType::Line | ToolType::Ellipse | ToolType::Rectangle => { self.active_shape_type.is_some() && active_tool == tool_type }\n\t\t\t\t\t\t\t_ => active_tool == tool_type,\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.tooltip_label(tooltip_label.clone())\n\t\t\t\t\t\t.tooltip_description(tooltip_description)\n\t\t\t\t\t\t.tooltip_shortcut(tooltip_shortcut)\n\t\t\t\t\t\t.on_update(move |_| {\n\t\t\t\t\t\t\tmatch tool_type {\n\t\t\t\t\t\t\t\tToolType::Line => ToolMessage::ActivateToolShapeLine.into(),\n\t\t\t\t\t\t\t\tToolType::Rectangle => ToolMessage::ActivateToolShapeRectangle.into(),\n\t\t\t\t\t\t\t\tToolType::Ellipse => ToolMessage::ActivateToolShapeEllipse.into(),\n\t\t\t\t\t\t\t\tToolType::Shape => ToolMessage::ActivateToolShape.into(),\n\t\t\t\t\t\t\t\t_ => ToolMessage::ActivateTool { tool_type }.into(),\n\t\t\t\t\t\t\t\t// _ => if !tooltip_description.contains(\"Coming soon.\") { ToolMessage::ActivateTool { tool_type }.into() } else { Message::NoOp },\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.widget_instance()\n\t\t\t\t});\n\n\t\t\t\tseparator.chain(buttons)\n\t\t\t})\n\t\t\t// Skip the initial separator\n\t\t\t.skip(1)\n\t\t\t.collect();\n\n\t\tLayout(vec![LayoutGroup::row(tool_groups_layout)])\n\t}\n}\n\n#[derive(Debug, Clone, Default, WidgetBuilder)]\n#[widget_builder(not_widget_instance)]\npub struct ToolEntry {\n\t#[widget_builder(constructor)]\n\tpub tool_type: ToolType,\n\t#[widget_builder(constructor)]\n\tpub icon_name: String,\n\tpub tooltip_label: String,\n\tpub tooltip_description: String,\n\tpub tooltip_shortcut: Option<ActionShortcut>,\n}\n\n#[derive(Debug)]\npub struct ToolFsmState {\n\tpub document_tool_data: DocumentToolData,\n\tpub tool_data: ToolData,\n}\n\nimpl Default for ToolFsmState {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\ttool_data: ToolData {\n\t\t\t\tactive_tool_type: ToolType::Select,\n\t\t\t\tactive_shape_type: None,\n\t\t\t\ttools: list_tools_in_groups()\n\t\t\t\t\t.into_iter()\n\t\t\t\t\t.flatten()\n\t\t\t\t\t.filter_map(|tool| match tool {\n\t\t\t\t\t\tToolRole::Normal(tool) => Some((tool.tool_type(), tool)),\n\t\t\t\t\t\tToolRole::Shape(_) => None,\n\t\t\t\t\t})\n\t\t\t\t\t.collect(),\n\t\t\t},\n\t\t\tdocument_tool_data: DocumentToolData {\n\t\t\t\tprimary_color: Color::BLACK,\n\t\t\t\tsecondary_color: Color::WHITE,\n\t\t\t},\n\t\t}\n\t}\n}\n\nimpl ToolFsmState {\n\tpub fn new() -> Self {\n\t\tSelf::default()\n\t}\n}\n\n#[repr(usize)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, Default)]\npub enum ToolType {\n\t// General tool group\n\t#[default]\n\tSelect,\n\tArtboard,\n\tNavigate,\n\tEyedropper,\n\tFill,\n\tGradient,\n\n\t// Vector tool group\n\tPath,\n\tPen,\n\tFreehand,\n\tSpline,\n\tShape,\n\tLine,      // Shape tool alias\n\tRectangle, // Shape tool alias\n\tEllipse,   // Shape tool alias\n\tText,\n\n\t// Raster tool group\n\tBrush,\n\tHeal,\n\tClone,\n\tPatch,\n\tDetail,\n\tRelight,\n}\n\nimpl ToolType {\n\tpub fn get_shape(&self) -> Option<Self> {\n\t\tmatch self {\n\t\t\tSelf::Rectangle | Self::Line | Self::Ellipse => Some(*self),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tpub fn get_tool(self) -> Self {\n\t\tif self.get_shape().is_some() { ToolType::Shape } else { self }\n\t}\n}\n\nenum ToolRole {\n\tNormal(Box<Tool>),\n\tShape(ShapeType),\n}\n\n/// List of all the tools in their conventional ordering and grouping.\nfn list_tools_in_groups() -> Vec<Vec<ToolRole>> {\n\tvec![\n\t\tvec![\n\t\t\t// General tool group\n\t\t\tToolRole::Normal(Box::<select_tool::SelectTool>::default()),\n\t\t\tToolRole::Normal(Box::<artboard_tool::ArtboardTool>::default()),\n\t\t\tToolRole::Normal(Box::<navigate_tool::NavigateTool>::default()),\n\t\t\tToolRole::Normal(Box::<eyedropper_tool::EyedropperTool>::default()),\n\t\t\tToolRole::Normal(Box::<fill_tool::FillTool>::default()),\n\t\t\tToolRole::Normal(Box::<gradient_tool::GradientTool>::default()),\n\t\t],\n\t\tvec![\n\t\t\t// Vector tool group\n\t\t\tToolRole::Normal(Box::<path_tool::PathTool>::default()),\n\t\t\tToolRole::Normal(Box::<pen_tool::PenTool>::default()),\n\t\t\tToolRole::Normal(Box::<freehand_tool::FreehandTool>::default()),\n\t\t\tToolRole::Normal(Box::<spline_tool::SplineTool>::default()),\n\t\t\tToolRole::Shape(ShapeType::Line),\n\t\t\tToolRole::Shape(ShapeType::Rectangle),\n\t\t\tToolRole::Shape(ShapeType::Ellipse),\n\t\t\tToolRole::Normal(Box::<shape_tool::ShapeTool>::default()),\n\t\t\tToolRole::Normal(Box::<text_tool::TextTool>::default()),\n\t\t],\n\t\tvec![\n\t\t\t// Raster tool group\n\t\t\tToolRole::Normal(Box::<brush_tool::BrushTool>::default()),\n\t\t\t// ToolRole::Normal(\n\t\t\t// \tToolEntry::new(ToolType::Heal, \"RasterHealTool\")\n\t\t\t// \t\t.tooltip_label(\"Heal Tool\")\n\t\t\t// \t\t.tooltip_shortcut(action_shortcut_manual!(Key::KeyJ)),\n\t\t\t// ),\n\t\t\t// ToolRole::Normal(\n\t\t\t// \tToolEntry::new(ToolType::Clone, \"RasterCloneTool\")\n\t\t\t// \t\t.tooltip_label(\"Clone Tool\")\n\t\t\t// \t\t.tooltip_shortcut(action_shortcut_manual!(Key::KeyC)),\n\t\t\t// ),\n\t\t\t// ToolRole::Normal(ToolEntry::new(ToolType::Patch, \"RasterPatchTool\")\n\t\t\t// \t\t.tooltip_label(\"Patch Tool\"),\n\t\t\t// ),\n\t\t\t// ToolRole::Normal(\n\t\t\t// \tToolEntry::new(ToolType::Detail, \"RasterDetailTool\")\n\t\t\t// \t\t.tooltip_label(\"Detail Tool\")\n\t\t\t// \t\t.tooltip_shortcut(action_shortcut_manual!(Key::KeyD)),\n\t\t\t// ),\n\t\t\t// ToolRole::Normal(\n\t\t\t// \tToolEntry::new(ToolType::Relight, \"RasterRelightTool\")\n\t\t\t// \t\t.tooltip_label(\"Relight Tool\")\n\t\t\t// \t\t.tooltip_shortcut(action_shortcut_manual!(Key::KeyO)),\n\t\t\t// ),\n\t\t],\n\t]\n}\n\npub fn tool_message_to_tool_type(tool_message: &ToolMessage) -> ToolType {\n\tmatch tool_message {\n\t\t// General tool group\n\t\tToolMessage::Select(_) => ToolType::Select,\n\t\tToolMessage::Artboard(_) => ToolType::Artboard,\n\t\tToolMessage::Navigate(_) => ToolType::Navigate,\n\t\tToolMessage::Eyedropper(_) => ToolType::Eyedropper,\n\t\tToolMessage::Fill(_) => ToolType::Fill,\n\t\tToolMessage::Gradient(_) => ToolType::Gradient,\n\n\t\t// Vector tool group\n\t\tToolMessage::Path(_) => ToolType::Path,\n\t\tToolMessage::Pen(_) => ToolType::Pen,\n\t\tToolMessage::Freehand(_) => ToolType::Freehand,\n\t\tToolMessage::Spline(_) => ToolType::Spline,\n\t\tToolMessage::Shape(_) => ToolType::Shape, // Includes the Line, Rectangle, and Ellipse aliases\n\t\tToolMessage::Text(_) => ToolType::Text,\n\n\t\t// Raster tool group\n\t\tToolMessage::Brush(_) => ToolType::Brush,\n\t\t// ToolMessage::Heal(_) => ToolType::Heal,\n\t\t// ToolMessage::Clone(_) => ToolType::Clone,\n\t\t// ToolMessage::Patch(_) => ToolType::Patch,\n\t\t// ToolMessage::Detail(_) => ToolType::Detail,\n\t\t// ToolMessage::Relight(_) => ToolType::Relight,\n\t\t_ => panic!(\"Conversion from ToolMessage to ToolType impossible because the given ToolMessage does not have a matching ToolType. Got: {tool_message:?}\"),\n\t}\n}\n\npub fn tool_type_to_activate_tool_message(tool_type: ToolType) -> ToolMessageDiscriminant {\n\tmatch tool_type {\n\t\t// General tool group\n\t\tToolType::Select => ToolMessageDiscriminant::ActivateToolSelect,\n\t\tToolType::Artboard => ToolMessageDiscriminant::ActivateToolArtboard,\n\t\tToolType::Navigate => ToolMessageDiscriminant::ActivateToolNavigate,\n\t\tToolType::Eyedropper => ToolMessageDiscriminant::ActivateToolEyedropper,\n\t\tToolType::Fill => ToolMessageDiscriminant::ActivateToolFill,\n\t\tToolType::Gradient => ToolMessageDiscriminant::ActivateToolGradient,\n\n\t\t// Vector tool group\n\t\tToolType::Path => ToolMessageDiscriminant::ActivateToolPath,\n\t\tToolType::Pen => ToolMessageDiscriminant::ActivateToolPen,\n\t\tToolType::Freehand => ToolMessageDiscriminant::ActivateToolFreehand,\n\t\tToolType::Spline => ToolMessageDiscriminant::ActivateToolSpline,\n\t\tToolType::Line => ToolMessageDiscriminant::ActivateToolShapeLine,           // Shape tool alias\n\t\tToolType::Rectangle => ToolMessageDiscriminant::ActivateToolShapeRectangle, // Shape tool alias\n\t\tToolType::Ellipse => ToolMessageDiscriminant::ActivateToolShapeEllipse,     // Shape tool alias\n\t\tToolType::Shape => ToolMessageDiscriminant::ActivateToolShape,\n\t\tToolType::Text => ToolMessageDiscriminant::ActivateToolText,\n\n\t\t// Raster tool group\n\t\tToolType::Brush => ToolMessageDiscriminant::ActivateToolBrush,\n\t\t// ToolType::Heal => ToolMessageDiscriminant::ActivateToolHeal,\n\t\t// ToolType::Clone => ToolMessageDiscriminant::ActivateToolClone,\n\t\t// ToolType::Patch => ToolMessageDiscriminant::ActivateToolPatch,\n\t\t// ToolType::Detail => ToolMessageDiscriminant::ActivateToolDetail,\n\t\t// ToolType::Relight => ToolMessageDiscriminant::ActivateToolRelight,\n\t\t_ => panic!(\"Conversion from ToolType to ToolMessage impossible because the given ToolType does not have a matching ToolMessage. Got: {tool_type:?}\"),\n\t}\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]\npub struct HintData(pub Vec<HintGroup>);\n\nimpl HintData {\n\tpub fn to_layout(&self) -> Layout {\n\t\tlet mut widgets = Vec::new();\n\n\t\tfor (index, hint_group) in self.0.iter().enumerate() {\n\t\t\tif index > 0 {\n\t\t\t\twidgets.push(Separator::new(SeparatorStyle::Section).widget_instance());\n\t\t\t}\n\t\t\tfor hint in &hint_group.0 {\n\t\t\t\tif hint.plus {\n\t\t\t\t\twidgets.push(TextLabel::new(\"+\").bold(true).widget_instance());\n\t\t\t\t}\n\t\t\t\tif hint.slash {\n\t\t\t\t\twidgets.push(TextLabel::new(\"/\").bold(true).widget_instance());\n\t\t\t\t}\n\n\t\t\t\tfor shortcut in &hint.key_groups {\n\t\t\t\t\twidgets.push(ShortcutLabel::new(Some(ActionShortcut::Shortcut(shortcut.clone()))).widget_instance());\n\t\t\t\t}\n\t\t\t\tif let Some(mouse_movement) = hint.mouse {\n\t\t\t\t\tlet mouse_movement = LabeledShortcut(vec![LabeledKeyOrMouseMotion::MouseMotion(mouse_movement)]);\n\t\t\t\t\tlet shortcut = ActionShortcut::Shortcut(mouse_movement);\n\t\t\t\t\twidgets.push(ShortcutLabel::new(Some(shortcut)).widget_instance());\n\t\t\t\t}\n\n\t\t\t\tif !hint.label.is_empty() {\n\t\t\t\t\twidgets.push(TextLabel::new(hint.label.clone()).widget_instance());\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tLayout(vec![LayoutGroup::row(widgets)])\n\t}\n\n\tpub fn send_layout(&self, responses: &mut VecDeque<Message>) {\n\t\tresponses.add(LayoutMessage::SendLayout {\n\t\t\tlayout: self.to_layout(),\n\t\t\tlayout_target: LayoutTarget::StatusBarHints,\n\t\t});\n\t}\n\n\tpub fn clear_layout(responses: &mut VecDeque<Message>) {\n\t\tresponses.add(LayoutMessage::SendLayout {\n\t\t\tlayout: Layout::default(),\n\t\t\tlayout_target: LayoutTarget::StatusBarHints,\n\t\t});\n\t}\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]\npub struct HintGroup(pub Vec<HintInfo>);\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]\npub struct HintInfo {\n\t/// A `KeysGroup` specifies all the keys pressed simultaneously to perform an action (like \"Ctrl C\" to copy).\n\t/// Usually at most one is given, but less commonly, multiple can be used to describe additional hotkeys not used simultaneously (like the four different arrow keys to nudge a layer).\n\t#[serde(rename = \"keyGroups\")]\n\tpub key_groups: Vec<LabeledShortcut>,\n\t/// `None` means that the regular `key_groups` should be used for all platforms, `Some` is an override for a Mac-only input hint.\n\t#[serde(rename = \"keyGroupsMac\")]\n\tpub key_groups_mac: Option<Vec<LabeledShortcut>>,\n\t/// An optional `MouseMotion` that can indicate the mouse action, like which mouse button is used and whether a drag occurs.\n\t/// No such icon is shown if `None` is given, and it can be combined with `key_groups` if desired.\n\tpub mouse: Option<MouseMotion>,\n\t/// The text describing what occurs with this input combination.\n\tpub label: Cow<'static, str>,\n\t/// Draws a prepended \"+\" symbol which indicates that this is a refinement upon a previous hint in the group.\n\tpub plus: bool,\n\t/// Draws a prepended \"/\" symbol which indicates that this is an alternative to a previous hint in the group.\n\tpub slash: bool,\n}\n\nimpl HintInfo {\n\t/// Used for a hint where a single key or key stroke is used to perform one action.\n\t/// Examples:\n\t/// - The Escape key can be used to cancel an action\n\t/// - The Ctrl+C key stroke can be used to copy\n\tpub fn keys(keys: impl IntoIterator<Item = Key>, label: impl Into<Cow<'static, str>>) -> Self {\n\t\tlet keys = keys.into_iter().collect();\n\t\tSelf {\n\t\t\tkey_groups: vec![KeysGroup(keys).into()],\n\t\t\tkey_groups_mac: None,\n\t\t\tmouse: None,\n\t\t\tlabel: label.into(),\n\t\t\tplus: false,\n\t\t\tslash: false,\n\t\t}\n\t}\n\n\t/// Used for a hint where multiple different individual keys can be used to perform variations of the same action. These keys are represented with a slight separation between them compared to [`Self::keys`].\n\t/// Examples:\n\t/// - The four arrow keys can be used to nudge a layer in different directions\n\t/// - The G, R, and S keys can be used to enter GRS transformation mode\n\tpub fn multi_keys(multi_keys: impl IntoIterator<Item = impl IntoIterator<Item = Key>>, label: impl Into<Cow<'static, str>>) -> Self {\n\t\tlet key_groups = multi_keys.into_iter().map(|keys| KeysGroup(keys.into_iter().collect()).into()).collect();\n\t\tSelf {\n\t\t\tkey_groups,\n\t\t\tkey_groups_mac: None,\n\t\t\tmouse: None,\n\t\t\tlabel: label.into(),\n\t\t\tplus: false,\n\t\t\tslash: false,\n\t\t}\n\t}\n\n\tpub fn mouse(mouse_motion: MouseMotion, label: impl Into<Cow<'static, str>>) -> Self {\n\t\tSelf {\n\t\t\tkey_groups: vec![],\n\t\t\tkey_groups_mac: None,\n\t\t\tmouse: Some(mouse_motion),\n\t\t\tlabel: label.into(),\n\t\t\tplus: false,\n\t\t\tslash: false,\n\t\t}\n\t}\n\n\tpub fn label(label: impl Into<Cow<'static, str>>) -> Self {\n\t\tSelf {\n\t\t\tkey_groups: vec![],\n\t\t\tkey_groups_mac: None,\n\t\t\tmouse: None,\n\t\t\tlabel: label.into(),\n\t\t\tplus: false,\n\t\t\tslash: false,\n\t\t}\n\t}\n\n\tpub fn keys_and_mouse(keys: impl IntoIterator<Item = Key>, mouse_motion: MouseMotion, label: impl Into<Cow<'static, str>>) -> Self {\n\t\tlet keys = keys.into_iter().collect();\n\t\tSelf {\n\t\t\tkey_groups: vec![KeysGroup(keys).into()],\n\t\t\tkey_groups_mac: None,\n\t\t\tmouse: Some(mouse_motion),\n\t\t\tlabel: label.into(),\n\t\t\tplus: false,\n\t\t\tslash: false,\n\t\t}\n\t}\n\n\tpub fn multi_keys_and_mouse(multi_keys: impl IntoIterator<Item = impl IntoIterator<Item = Key>>, mouse_motion: MouseMotion, label: impl Into<Cow<'static, str>>) -> Self {\n\t\tlet key_groups = multi_keys.into_iter().map(|keys| KeysGroup(keys.into_iter().collect()).into()).collect();\n\t\tSelf {\n\t\t\tkey_groups,\n\t\t\tkey_groups_mac: None,\n\t\t\tmouse: Some(mouse_motion),\n\t\t\tlabel: label.into(),\n\t\t\tplus: false,\n\t\t\tslash: false,\n\t\t}\n\t}\n\n\tpub fn arrow_keys(label: impl Into<Cow<'static, str>>) -> Self {\n\t\tlet multi_keys = [[Key::ArrowUp], [Key::ArrowRight], [Key::ArrowDown], [Key::ArrowLeft]];\n\t\tSelf::multi_keys(multi_keys, label)\n\t}\n\n\tpub fn prepend_plus(mut self) -> Self {\n\t\tself.plus = true;\n\t\tself\n\t}\n\n\tpub fn prepend_slash(mut self) -> Self {\n\t\tself.slash = true;\n\t\tself\n\t}\n\n\tpub fn add_mac_keys(mut self, keys: impl IntoIterator<Item = Key>) -> Self {\n\t\tlet mac_keys = keys.into_iter().collect();\n\t\tself.key_groups_mac = Some(vec![KeysGroup(mac_keys).into()]);\n\t\tself\n\t}\n}\n"
  },
  {
    "path": "editor/src/messages/viewport/mod.rs",
    "content": "mod viewport_message;\nmod viewport_message_handler;\n\n#[doc(inline)]\npub use viewport_message::{ViewportMessage, ViewportMessageDiscriminant};\n#[doc(inline)]\npub use viewport_message_handler::*;\n"
  },
  {
    "path": "editor/src/messages/viewport/viewport_message.rs",
    "content": "use crate::messages::prelude::*;\n\n#[impl_message(Message, Viewport)]\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum ViewportMessage {\n\tUpdate { x: f64, y: f64, width: f64, height: f64, scale: f64 },\n\tRepropagateUpdate,\n}\n"
  },
  {
    "path": "editor/src/messages/viewport/viewport_message_handler.rs",
    "content": "use std::ops::{Add, Div, Mul, Sub};\n\nuse crate::messages::prelude::*;\nuse crate::messages::tool::tool_messages::tool_prelude::DVec2;\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, ExtractField)]\npub struct ViewportMessageHandler {\n\tbounds: Bounds,\n\t// Ratio of logical pixels to physical pixels\n\tscale: f64,\n}\nimpl Default for ViewportMessageHandler {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tbounds: Bounds {\n\t\t\t\toffset: Point { x: 0., y: 0. },\n\t\t\t\tsize: Point { x: 0., y: 0. },\n\t\t\t},\n\t\t\tscale: 1.0,\n\t\t}\n\t}\n}\n\n#[message_handler_data]\nimpl MessageHandler<ViewportMessage, ()> for ViewportMessageHandler {\n\tfn process_message(&mut self, message: ViewportMessage, responses: &mut VecDeque<Message>, _: ()) {\n\t\tmatch message {\n\t\t\tViewportMessage::Update { x, y, width, height, scale } => {\n\t\t\t\tassert!(scale > 0., \"Viewport scale must be greater than zero\");\n\t\t\t\tself.scale = scale;\n\n\t\t\t\tself.bounds = Bounds {\n\t\t\t\t\toffset: Point { x, y },\n\t\t\t\t\tsize: Point { x: width, y: height },\n\t\t\t\t};\n\t\t\t\tresponses.add(NodeGraphMessage::UpdateNodeGraphWidth);\n\t\t\t}\n\t\t\tViewportMessage::RepropagateUpdate => {}\n\t\t}\n\n\t\t#[cfg(not(target_family = \"wasm\"))]\n\t\t{\n\t\t\tlet physical_bounds = self.bounds().to_physical();\n\t\t\tresponses.add(FrontendMessage::UpdateViewportPhysicalBounds {\n\t\t\t\tx: physical_bounds.x(),\n\t\t\t\ty: physical_bounds.y(),\n\t\t\t\twidth: physical_bounds.width(),\n\t\t\t\theight: physical_bounds.height(),\n\t\t\t});\n\t\t}\n\n\t\tresponses.add(NavigationMessage::CanvasPan { delta: DVec2::ZERO });\n\n\t\tif self.is_valid() {\n\t\t\tresponses.add(DeferMessage::AfterGraphRun {\n\t\t\t\tmessages: vec![\n\t\t\t\t\tDeferMessage::AfterGraphRun {\n\t\t\t\t\t\tmessages: vec![DeferMessage::TriggerNavigationReady.into()],\n\t\t\t\t\t}\n\t\t\t\t\t.into(),\n\t\t\t\t],\n\t\t\t});\n\t\t}\n\t}\n\n\tadvertise_actions!(ViewportMessageDiscriminant;);\n}\n\nimpl ViewportMessageHandler {\n\tpub fn scale(&self) -> f64 {\n\t\tself.scale\n\t}\n\n\tpub fn bounds(&self) -> LogicalBounds {\n\t\tself.bounds.into_scaled(self.scale)\n\t}\n\n\tpub fn offset(&self) -> LogicalPoint {\n\t\tself.bounds.offset.into_scaled(self.scale)\n\t}\n\n\tpub fn size(&self) -> LogicalPoint {\n\t\tself.bounds.size().into_scaled(self.scale)\n\t}\n\n\t#[expect(private_bounds)]\n\tpub fn logical<T: Into<Point>>(&self, point: T) -> LogicalPoint {\n\t\tpoint.into().convert_to_logical(self.scale)\n\t}\n\n\t#[expect(private_bounds)]\n\tpub fn physical<T: Into<Point>>(&self, point: T) -> PhysicalPoint {\n\t\tpoint.into().convert_to_physical(self.scale)\n\t}\n\n\tpub fn center_in_viewport_space(&self) -> LogicalPoint {\n\t\tlet size = self.size();\n\t\tLogicalPoint {\n\t\t\tinner: Point { x: size.x() / 2., y: size.y() / 2. },\n\t\t\tscale: size.scale,\n\t\t}\n\t}\n\n\tpub fn center_in_window_space(&self) -> LogicalPoint {\n\t\tlet size = self.size();\n\t\tlet offset = self.offset();\n\t\tLogicalPoint {\n\t\t\tinner: Point {\n\t\t\t\tx: (size.x() / 2.) + offset.x(),\n\t\t\t\ty: (size.y() / 2.) + offset.y(),\n\t\t\t},\n\t\t\tscale: size.scale,\n\t\t}\n\t}\n\n\tpub fn is_valid(&self) -> bool {\n\t\tself.scale > 0. && self.bounds.size.x() > 0. && self.bounds.size.y() > 0. && self.bounds.offset.x() >= 0. && self.bounds.offset.y() >= 0.\n\t}\n\n\tpub(crate) fn is_in_bounds(&self, point: LogicalPoint) -> bool {\n\t\tpoint.x() >= self.bounds.x() && point.y() >= self.bounds.y() && point.x() <= self.bounds.x() + self.bounds.width() && point.y() <= self.bounds.y() + self.bounds.height()\n\t}\n}\n\npub trait ToLogical<L: ToPhysical<Self> + ?Sized> {\n\tfn to_logical(self) -> L;\n}\npub trait ToPhysical<P: ToLogical<Self> + ?Sized> {\n\tfn to_physical(self) -> P;\n}\n\ntrait IntoScaled<T: Scaled>: Sized {\n\tfn into_scaled(self, scale: f64) -> T;\n}\ntrait FromWithScale<T>: Sized {\n\tfn from_with_scale(value: T, scale: f64) -> Self;\n}\nimpl<T, U: Scaled> IntoScaled<U> for T\nwhere\n\tU: FromWithScale<T>,\n{\n\tfn into_scaled(self, scale: f64) -> U {\n\t\tU::from_with_scale(self, scale)\n\t}\n}\n\ntrait AsPoint {\n\tfn as_point(&self) -> Point;\n}\n\ntrait Scaled {\n\tfn scale(&self) -> f64;\n}\n\npub trait Position {\n\tfn x(&self) -> f64;\n\tfn y(&self) -> f64;\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize)]\nstruct Point {\n\tx: f64,\n\ty: f64,\n}\nimpl Point {\n\tfn convert_to_logical(&self, scale: f64) -> LogicalPoint {\n\t\tPoint { x: self.x(), y: self.y() }.into_scaled(scale)\n\t}\n\tfn convert_to_physical(&self, scale: f64) -> PhysicalPoint {\n\t\tPoint {\n\t\t\tx: self.x() / scale,\n\t\t\ty: self.y() / scale,\n\t\t}\n\t\t.into_scaled(scale)\n\t}\n}\nimpl Position for Point {\n\tfn x(&self) -> f64 {\n\t\tself.x\n\t}\n\tfn y(&self) -> f64 {\n\t\tself.y\n\t}\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize)]\npub struct LogicalPoint {\n\tinner: Point,\n\tscale: f64,\n}\nimpl AsPoint for LogicalPoint {\n\tfn as_point(&self) -> Point {\n\t\tself.inner\n\t}\n}\nimpl Scaled for LogicalPoint {\n\tfn scale(&self) -> f64 {\n\t\tself.scale\n\t}\n}\nimpl Position for LogicalPoint {\n\tfn x(&self) -> f64 {\n\t\tself.inner.x()\n\t}\n\tfn y(&self) -> f64 {\n\t\tself.inner.y()\n\t}\n}\nimpl ToPhysical<PhysicalPoint> for LogicalPoint {\n\tfn to_physical(self) -> PhysicalPoint {\n\t\tPhysicalPoint { inner: self.inner, scale: self.scale }\n\t}\n}\nimpl FromWithScale<Point> for LogicalPoint {\n\tfn from_with_scale(value: Point, scale: f64) -> Self {\n\t\tSelf { inner: value, scale }\n\t}\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize)]\npub struct PhysicalPoint {\n\tinner: Point,\n\tscale: f64,\n}\nimpl AsPoint for PhysicalPoint {\n\tfn as_point(&self) -> Point {\n\t\tself.inner\n\t}\n}\nimpl Scaled for PhysicalPoint {\n\tfn scale(&self) -> f64 {\n\t\tself.scale\n\t}\n}\nimpl Position for PhysicalPoint {\n\tfn x(&self) -> f64 {\n\t\tself.inner.x() * self.scale\n\t}\n\tfn y(&self) -> f64 {\n\t\tself.inner.y() * self.scale\n\t}\n}\nimpl ToLogical<LogicalPoint> for PhysicalPoint {\n\tfn to_logical(self) -> LogicalPoint {\n\t\tLogicalPoint { inner: self.inner, scale: self.scale }\n\t}\n}\nimpl FromWithScale<Point> for PhysicalPoint {\n\tfn from_with_scale(value: Point, scale: f64) -> Self {\n\t\tSelf { inner: value, scale }\n\t}\n}\n\npub trait Rect<P: Position>: Position {\n\tfn offset(&self) -> P;\n\tfn size(&self) -> P;\n\tfn width(&self) -> f64;\n\tfn height(&self) -> f64;\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, ExtractField)]\nstruct Bounds {\n\toffset: Point,\n\tsize: Point,\n}\nimpl Position for Bounds {\n\tfn x(&self) -> f64 {\n\t\tself.offset.x()\n\t}\n\tfn y(&self) -> f64 {\n\t\tself.offset.y()\n\t}\n}\nimpl Rect<Point> for Bounds {\n\tfn offset(&self) -> Point {\n\t\tself.offset\n\t}\n\tfn size(&self) -> Point {\n\t\tself.size\n\t}\n\tfn width(&self) -> f64 {\n\t\tself.size.x()\n\t}\n\tfn height(&self) -> f64 {\n\t\tself.size.y()\n\t}\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize)]\npub struct LogicalBounds {\n\toffset: Point,\n\tsize: Point,\n\tscale: f64,\n}\nimpl Scaled for LogicalBounds {\n\tfn scale(&self) -> f64 {\n\t\tself.scale\n\t}\n}\nimpl Position for LogicalBounds {\n\tfn x(&self) -> f64 {\n\t\tself.offset.x()\n\t}\n\tfn y(&self) -> f64 {\n\t\tself.offset.y()\n\t}\n}\nimpl Rect<LogicalPoint> for LogicalBounds {\n\tfn offset(&self) -> LogicalPoint {\n\t\tself.offset.into_scaled(self.scale)\n\t}\n\tfn size(&self) -> LogicalPoint {\n\t\tself.size.into_scaled(self.scale)\n\t}\n\tfn width(&self) -> f64 {\n\t\tself.size.x()\n\t}\n\tfn height(&self) -> f64 {\n\t\tself.size.y()\n\t}\n}\nimpl ToPhysical<PhysicalBounds> for LogicalBounds {\n\tfn to_physical(self) -> PhysicalBounds {\n\t\tPhysicalBounds {\n\t\t\toffset: self.offset,\n\t\t\tsize: self.size,\n\t\t\tscale: self.scale,\n\t\t}\n\t}\n}\nimpl FromWithScale<Bounds> for LogicalBounds {\n\tfn from_with_scale(value: Bounds, scale: f64) -> Self {\n\t\tSelf {\n\t\t\toffset: value.offset(),\n\t\t\tsize: value.size(),\n\t\t\tscale,\n\t\t}\n\t}\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize)]\npub struct PhysicalBounds {\n\toffset: Point,\n\tsize: Point,\n\tscale: f64,\n}\nimpl Scaled for PhysicalBounds {\n\tfn scale(&self) -> f64 {\n\t\tself.scale\n\t}\n}\nimpl Position for PhysicalBounds {\n\tfn x(&self) -> f64 {\n\t\tself.offset.x() * self.scale\n\t}\n\tfn y(&self) -> f64 {\n\t\tself.offset.y() * self.scale\n\t}\n}\nimpl Rect<PhysicalPoint> for PhysicalBounds {\n\tfn offset(&self) -> PhysicalPoint {\n\t\tself.offset.into_scaled(self.scale)\n\t}\n\tfn size(&self) -> PhysicalPoint {\n\t\tself.size.into_scaled(self.scale)\n\t}\n\tfn width(&self) -> f64 {\n\t\tself.size.x() * self.scale\n\t}\n\tfn height(&self) -> f64 {\n\t\tself.size.y() * self.scale\n\t}\n}\nimpl ToLogical<LogicalBounds> for PhysicalBounds {\n\tfn to_logical(self) -> LogicalBounds {\n\t\tLogicalBounds {\n\t\t\toffset: self.offset,\n\t\t\tsize: self.size,\n\t\t\tscale: self.scale,\n\t\t}\n\t}\n}\nimpl FromWithScale<Bounds> for PhysicalBounds {\n\tfn from_with_scale(value: Bounds, scale: f64) -> Self {\n\t\tSelf {\n\t\t\toffset: value.offset(),\n\t\t\tsize: value.size(),\n\t\t\tscale,\n\t\t}\n\t}\n}\n\nimpl Mul<f64> for Point {\n\ttype Output = Point;\n\tfn mul(self, rhs: f64) -> Self::Output {\n\t\tassert_ne!(rhs, 0.0, \"Cannot multiply point by zero\");\n\t\tPoint { x: self.x * rhs, y: self.y * rhs }\n\t}\n}\nimpl Div<f64> for Point {\n\ttype Output = Point;\n\tfn div(self, rhs: f64) -> Self::Output {\n\t\tassert_ne!(rhs, 0.0, \"Cannot divide point by zero\");\n\t\tPoint { x: self.x / rhs, y: self.y / rhs }\n\t}\n}\nimpl Add<f64> for Point {\n\ttype Output = Point;\n\tfn add(self, rhs: f64) -> Self::Output {\n\t\tPoint { x: self.x + rhs, y: self.y + rhs }\n\t}\n}\nimpl Sub<f64> for Point {\n\ttype Output = Point;\n\tfn sub(self, rhs: f64) -> Self::Output {\n\t\tPoint { x: self.x - rhs, y: self.y - rhs }\n\t}\n}\nimpl Mul<Point> for Point {\n\ttype Output = Point;\n\tfn mul(self, rhs: Point) -> Self::Output {\n\t\tassert_ne!(rhs.x, 0.0, \"Cannot multiply point by zero\");\n\t\tassert_ne!(rhs.y, 0.0, \"Cannot multiply point by zero\");\n\t\tPoint { x: self.x * rhs.x, y: self.y * rhs.y }\n\t}\n}\nimpl Div<Point> for Point {\n\ttype Output = Point;\n\tfn div(self, rhs: Point) -> Self::Output {\n\t\tassert_ne!(rhs.x, 0.0, \"Cannot multiply point by zero\");\n\t\tassert_ne!(rhs.y, 0.0, \"Cannot multiply point by zero\");\n\t\tPoint { x: self.x / rhs.x, y: self.y / rhs.y }\n\t}\n}\nimpl Add<Point> for Point {\n\ttype Output = Point;\n\tfn add(self, rhs: Point) -> Self::Output {\n\t\tPoint { x: self.x + rhs.x, y: self.y + rhs.y }\n\t}\n}\nimpl Sub<Point> for Point {\n\ttype Output = Point;\n\tfn sub(self, rhs: Point) -> Self::Output {\n\t\tPoint { x: self.x - rhs.x, y: self.y - rhs.y }\n\t}\n}\n\nimpl Mul<f64> for Bounds {\n\ttype Output = Bounds;\n\tfn mul(self, rhs: f64) -> Self::Output {\n\t\tassert_ne!(rhs, 0.0, \"Cannot multiply bounds by zero\");\n\t\tBounds {\n\t\t\toffset: self.offset * rhs,\n\t\t\tsize: self.size * rhs,\n\t\t}\n\t}\n}\nimpl Div<f64> for Bounds {\n\ttype Output = Bounds;\n\tfn div(self, rhs: f64) -> Self::Output {\n\t\tassert_ne!(rhs, 0.0, \"Cannot divide bounds by zero\");\n\t\tBounds {\n\t\t\toffset: self.offset / rhs,\n\t\t\tsize: self.size / rhs,\n\t\t}\n\t}\n}\n\nimpl Mul<LogicalPoint> for LogicalPoint {\n\ttype Output = LogicalPoint;\n\tfn mul(self, rhs: LogicalPoint) -> Self::Output {\n\t\tassert_scale(&self, &rhs);\n\t\t(self.as_point() * rhs.as_point()).into_scaled(self.scale())\n\t}\n}\nimpl Div<LogicalPoint> for LogicalPoint {\n\ttype Output = LogicalPoint;\n\tfn div(self, rhs: LogicalPoint) -> Self::Output {\n\t\tassert_scale(&self, &rhs);\n\t\t(self.as_point() / rhs.as_point()).into_scaled(self.scale())\n\t}\n}\nimpl Add<LogicalPoint> for LogicalPoint {\n\ttype Output = LogicalPoint;\n\tfn add(self, rhs: LogicalPoint) -> Self::Output {\n\t\tassert_scale(&self, &rhs);\n\t\t(self.as_point() + rhs.as_point()).into_scaled(self.scale())\n\t}\n}\nimpl Sub<LogicalPoint> for LogicalPoint {\n\ttype Output = LogicalPoint;\n\tfn sub(self, rhs: LogicalPoint) -> Self::Output {\n\t\tassert_scale(&self, &rhs);\n\t\t(self.as_point() - rhs.as_point()).into_scaled(self.scale())\n\t}\n}\nimpl Mul<PhysicalPoint> for PhysicalPoint {\n\ttype Output = PhysicalPoint;\n\tfn mul(self, rhs: PhysicalPoint) -> Self::Output {\n\t\tassert_scale(&self, &rhs);\n\t\t(self.as_point() * rhs.as_point()).into_scaled(self.scale())\n\t}\n}\nimpl Div<PhysicalPoint> for PhysicalPoint {\n\ttype Output = PhysicalPoint;\n\tfn div(self, rhs: PhysicalPoint) -> Self::Output {\n\t\tassert_scale(&self, &rhs);\n\t\t(self.as_point() / rhs.as_point()).into_scaled(self.scale())\n\t}\n}\nimpl Add<PhysicalPoint> for PhysicalPoint {\n\ttype Output = PhysicalPoint;\n\tfn add(self, rhs: PhysicalPoint) -> Self::Output {\n\t\tassert_scale(&self, &rhs);\n\t\t(self.as_point() + rhs.as_point()).into_scaled(self.scale())\n\t}\n}\nimpl Sub<PhysicalPoint> for PhysicalPoint {\n\ttype Output = PhysicalPoint;\n\tfn sub(self, rhs: PhysicalPoint) -> Self::Output {\n\t\tassert_scale(&self, &rhs);\n\t\t(self.as_point() - rhs.as_point()).into_scaled(self.scale())\n\t}\n}\nfn assert_scale<T: Scaled>(a: &T, b: &T) {\n\tassert_eq!(a.scale(), b.scale(), \"Cannot multiply with diffent scale\");\n}\n\nimpl From<(f64, f64)> for Point {\n\tfn from((x, y): (f64, f64)) -> Self {\n\t\tSelf { x, y }\n\t}\n}\nimpl From<(f64, f64, f64, f64)> for Bounds {\n\tfn from((x, y, width, height): (f64, f64, f64, f64)) -> Self {\n\t\tSelf {\n\t\t\toffset: Point { x, y },\n\t\t\tsize: Point { x: width, y: height },\n\t\t}\n\t}\n}\n\nimpl From<LogicalPoint> for (f64, f64) {\n\tfn from(point: LogicalPoint) -> Self {\n\t\t(point.x(), point.y())\n\t}\n}\nimpl From<PhysicalPoint> for (f64, f64) {\n\tfn from(point: PhysicalPoint) -> Self {\n\t\t(point.x(), point.y())\n\t}\n}\nimpl From<LogicalBounds> for (f64, f64, f64, f64) {\n\tfn from(bounds: LogicalBounds) -> Self {\n\t\t(bounds.x(), bounds.y(), bounds.width(), bounds.height())\n\t}\n}\nimpl From<PhysicalBounds> for (f64, f64, f64, f64) {\n\tfn from(bounds: PhysicalBounds) -> Self {\n\t\t(bounds.x(), bounds.y(), bounds.width(), bounds.height())\n\t}\n}\n\nimpl From<glam::DVec2> for Point {\n\tfn from(vec: glam::DVec2) -> Self {\n\t\tSelf { x: vec.x, y: vec.y }\n\t}\n}\nimpl From<LogicalPoint> for glam::DVec2 {\n\tfn from(val: LogicalPoint) -> Self {\n\t\tglam::DVec2::new(val.x(), val.y())\n\t}\n}\nimpl From<PhysicalPoint> for glam::DVec2 {\n\tfn from(val: PhysicalPoint) -> Self {\n\t\tglam::DVec2::new(val.x(), val.y())\n\t}\n}\n\nimpl From<[glam::DVec2; 2]> for Bounds {\n\tfn from(bounds: [glam::DVec2; 2]) -> Self {\n\t\tSelf {\n\t\t\toffset: bounds[0].into(),\n\t\t\tsize: Point {\n\t\t\t\tx: bounds[1].x - bounds[0].x,\n\t\t\t\ty: bounds[1].y - bounds[0].y,\n\t\t\t},\n\t\t}\n\t}\n}\nimpl From<LogicalBounds> for [glam::DVec2; 2] {\n\tfn from(bounds: LogicalBounds) -> Self {\n\t\t[glam::DVec2::new(bounds.x(), bounds.y()), glam::DVec2::new(bounds.x() + bounds.width(), bounds.y() + bounds.height())]\n\t}\n}\nimpl From<PhysicalBounds> for [glam::DVec2; 2] {\n\tfn from(bounds: PhysicalBounds) -> Self {\n\t\t[glam::DVec2::new(bounds.x(), bounds.y()), glam::DVec2::new(bounds.x() + bounds.width(), bounds.y() + bounds.height())]\n\t}\n}\n\nimpl LogicalPoint {\n\tpub fn into_dvec2(self) -> DVec2 {\n\t\tDVec2::new(self.x(), self.y())\n\t}\n}\nimpl PhysicalPoint {\n\tpub fn into_dvec2(self) -> DVec2 {\n\t\tDVec2::new(self.x(), self.y())\n\t}\n}\n"
  },
  {
    "path": "editor/src/node_graph_executor/runtime.rs",
    "content": "use super::*;\nuse crate::messages::frontend::utility_types::{ExportBounds, FileType};\nuse glam::{DAffine2, UVec2};\nuse graph_craft::document::value::TaggedValue;\nuse graph_craft::document::{NodeId, NodeNetwork};\nuse graph_craft::graphene_compiler::Compiler;\nuse graph_craft::proto::GraphErrors;\nuse graph_craft::wasm_application_io::EditorPreferences;\nuse graph_craft::{ProtoNodeIdentifier, concrete};\nuse graphene_std::application_io::{ApplicationIo, ExportFormat, ImageTexture, NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig};\nuse graphene_std::bounds::RenderBoundingBox;\nuse graphene_std::memo::IORecord;\nuse graphene_std::ops::Convert;\nuse graphene_std::raster_types::Raster;\nuse graphene_std::renderer::{Render, RenderParams, SvgRender};\nuse graphene_std::renderer::{RenderSvgSegmentList, SvgSegment};\nuse graphene_std::table::{Table, TableRow};\nuse graphene_std::text::FontCache;\nuse graphene_std::transform::RenderQuality;\nuse graphene_std::vector::Vector;\nuse graphene_std::vector::style::RenderMode;\nuse graphene_std::wasm_application_io::{RenderOutputType, WasmApplicationIo, WasmEditorApi};\nuse graphene_std::{Artboard, Context, Graphic};\nuse interpreted_executor::dynamic_executor::{DynamicExecutor, IntrospectError, ResolvedDocumentNodeTypesDelta};\nuse interpreted_executor::util::wrap_network_in_scope;\nuse spin::Mutex;\nuse std::sync::Arc;\nuse std::sync::mpsc::{Receiver, Sender};\n\n/// Persistent data between graph executions. It's updated via message passing from the editor thread with [`GraphRuntimeRequest`]`.\n/// Some of these fields are put into a [`WasmEditorApi`] which is passed to the final compiled graph network upon each execution.\n/// Once the implementation is finished, this will live in a separate thread. Right now it's part of the main JS thread, but its own separate JS stack frame independent from the editor.\npub struct NodeRuntime {\n\t#[cfg(test)]\n\tpub(super) executor: DynamicExecutor,\n\t#[cfg(not(test))]\n\texecutor: DynamicExecutor,\n\treceiver: Receiver<GraphRuntimeRequest>,\n\tsender: InternalNodeGraphUpdateSender,\n\teditor_preferences: EditorPreferences,\n\told_graph: Option<NodeNetwork>,\n\tupdate_thumbnails: bool,\n\n\teditor_api: Arc<WasmEditorApi>,\n\tnode_graph_errors: GraphErrors,\n\tmonitor_nodes: Vec<Vec<NodeId>>,\n\n\t/// Which node is inspected and which monitor node is used (if any) for the current execution.\n\tinspect_state: Option<InspectState>,\n\n\t/// Mapping of the fully-qualified node paths to their preprocessor substitutions.\n\tsubstitutions: HashMap<ProtoNodeIdentifier, DocumentNode>,\n\n\t// TODO: Remove, it doesn't need to be persisted anymore\n\t/// The current renders of the thumbnails for layer nodes.\n\tthumbnail_renders: HashMap<NodeId, Vec<SvgSegment>>,\n\tvector_modify: HashMap<NodeId, Vector>,\n\n\t/// Cached surface for Wasm viewport rendering (reused across frames)\n\t#[cfg(all(target_family = \"wasm\", feature = \"gpu\"))]\n\twasm_viewport_surface: Option<wgpu_executor::WgpuSurface>,\n\t/// Currently displayed texture, the runtime keeps a reference to it to avoid the texture getting destroyed while it is still in use.\n\t#[cfg(all(target_family = \"wasm\", feature = \"gpu\"))]\n\tcurrent_viewport_texture: Option<ImageTexture>,\n}\n\n/// Messages passed from the editor thread to the node runtime thread.\n#[derive(Debug, serde::Serialize, serde::Deserialize)]\npub enum GraphRuntimeRequest {\n\tGraphUpdate(GraphUpdate),\n\tExecutionRequest(ExecutionRequest),\n\tFontCacheUpdate(FontCache),\n\tEditorPreferencesUpdate(EditorPreferences),\n}\n\n#[derive(Debug, serde::Serialize, serde::Deserialize)]\npub struct GraphUpdate {\n\tpub(super) network: NodeNetwork,\n\t/// The node that should be temporary inspected during execution\n\tpub(super) node_to_inspect: Option<NodeId>,\n}\n\n#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]\npub struct ExportConfig {\n\tpub name: String,\n\tpub file_type: FileType,\n\tpub scale_factor: f64,\n\tpub bounds: ExportBounds,\n\tpub transparent_background: bool,\n\tpub size: UVec2,\n\tpub artboard_name: Option<String>,\n\tpub artboard_count: usize,\n}\n\n#[derive(Clone)]\nstruct InternalNodeGraphUpdateSender(Sender<NodeGraphUpdate>);\n\nimpl InternalNodeGraphUpdateSender {\n\tfn send_generation_response(&self, response: CompilationResponse) {\n\t\tself.0.send(NodeGraphUpdate::CompilationResponse(response)).expect(\"Failed to send response\")\n\t}\n\n\tfn send_execution_response(&self, response: ExecutionResponse) {\n\t\tself.0.send(NodeGraphUpdate::ExecutionResponse(response)).expect(\"Failed to send response\")\n\t}\n\n\tfn send_eyedropper_preview(&self, raster: Raster<CPU>) {\n\t\tself.0.send(NodeGraphUpdate::EyedropperPreview(raster)).expect(\"Failed to send response\")\n\t}\n}\n\nimpl NodeGraphUpdateSender for InternalNodeGraphUpdateSender {\n\tfn send(&self, message: NodeGraphUpdateMessage) {\n\t\tself.0.send(NodeGraphUpdate::NodeGraphUpdateMessage(message)).expect(\"Failed to send response\")\n\t}\n}\n\n// TODO: Replace with `core::cell::LazyCell` (<https://doc.rust-lang.org/core/cell/struct.LazyCell.html>) or similar\npub static NODE_RUNTIME: once_cell::sync::Lazy<Mutex<Option<NodeRuntime>>> = once_cell::sync::Lazy::new(|| Mutex::new(None));\n\nimpl NodeRuntime {\n\tpub fn new(receiver: Receiver<GraphRuntimeRequest>, sender: Sender<NodeGraphUpdate>) -> Self {\n\t\tSelf {\n\t\t\texecutor: DynamicExecutor::default(),\n\t\t\treceiver,\n\t\t\tsender: InternalNodeGraphUpdateSender(sender.clone()),\n\t\t\teditor_preferences: EditorPreferences::default(),\n\t\t\told_graph: None,\n\t\t\tupdate_thumbnails: true,\n\n\t\t\teditor_api: WasmEditorApi {\n\t\t\t\tfont_cache: FontCache::default(),\n\t\t\t\teditor_preferences: Box::new(EditorPreferences::default()),\n\t\t\t\tnode_graph_message_sender: Box::new(InternalNodeGraphUpdateSender(sender)),\n\n\t\t\t\tapplication_io: None,\n\t\t\t}\n\t\t\t.into(),\n\n\t\t\tnode_graph_errors: Vec::new(),\n\t\t\tmonitor_nodes: Vec::new(),\n\n\t\t\tsubstitutions: preprocessor::generate_node_substitutions(),\n\n\t\t\tthumbnail_renders: Default::default(),\n\t\t\tvector_modify: Default::default(),\n\t\t\tinspect_state: None,\n\t\t\t#[cfg(all(target_family = \"wasm\", feature = \"gpu\"))]\n\t\t\twasm_viewport_surface: None,\n\t\t\t#[cfg(all(target_family = \"wasm\", feature = \"gpu\"))]\n\t\t\tcurrent_viewport_texture: None,\n\t\t}\n\t}\n\n\tpub async fn run(&mut self) -> Option<ImageTexture> {\n\t\tif self.editor_api.application_io.is_none() {\n\t\t\tself.editor_api = WasmEditorApi {\n\t\t\t\t#[cfg(all(not(test), target_family = \"wasm\"))]\n\t\t\t\tapplication_io: Some(WasmApplicationIo::new().await.into()),\n\t\t\t\t#[cfg(any(test, not(target_family = \"wasm\")))]\n\t\t\t\tapplication_io: Some(WasmApplicationIo::new_offscreen().await.into()),\n\t\t\t\tfont_cache: self.editor_api.font_cache.clone(),\n\t\t\t\tnode_graph_message_sender: Box::new(self.sender.clone()),\n\t\t\t\teditor_preferences: Box::new(self.editor_preferences.clone()),\n\t\t\t}\n\t\t\t.into();\n\t\t}\n\n\t\tlet mut font = None;\n\t\tlet mut preferences = None;\n\t\tlet mut graph = None;\n\t\tlet mut eyedropper = None;\n\t\tlet mut execution = None;\n\t\tfor request in self.receiver.try_iter() {\n\t\t\tmatch request {\n\t\t\t\tGraphRuntimeRequest::GraphUpdate(_) => graph = Some(request),\n\t\t\t\tGraphRuntimeRequest::ExecutionRequest(ref execution_request) => {\n\t\t\t\t\tif execution_request.render_config.for_eyedropper {\n\t\t\t\t\t\teyedropper = Some(request);\n\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tlet for_export = execution_request.render_config.for_export;\n\n\t\t\t\t\texecution = Some(request);\n\n\t\t\t\t\t// If we get an export request we always execute it immedeatly otherwise it could get deduplicated\n\t\t\t\t\tif for_export {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tGraphRuntimeRequest::FontCacheUpdate(_) => font = Some(request),\n\t\t\t\tGraphRuntimeRequest::EditorPreferencesUpdate(_) => preferences = Some(request),\n\t\t\t}\n\t\t}\n\n\t\t// Eydropper should use the same time and pointer to not invalidate the cache\n\t\tif let Some(GraphRuntimeRequest::ExecutionRequest(eyedropper)) = &mut eyedropper\n\t\t\t&& let Some(GraphRuntimeRequest::ExecutionRequest(execution)) = &execution\n\t\t{\n\t\t\teyedropper.render_config.time = execution.render_config.time;\n\t\t\teyedropper.render_config.pointer = execution.render_config.pointer;\n\t\t}\n\n\t\tlet requests = [font, preferences, graph, eyedropper, execution].into_iter().flatten();\n\n\t\tfor request in requests {\n\t\t\tmatch request {\n\t\t\t\tGraphRuntimeRequest::FontCacheUpdate(font_cache) => {\n\t\t\t\t\tself.editor_api = WasmEditorApi {\n\t\t\t\t\t\tfont_cache,\n\t\t\t\t\t\tapplication_io: self.editor_api.application_io.clone(),\n\t\t\t\t\t\tnode_graph_message_sender: Box::new(self.sender.clone()),\n\t\t\t\t\t\teditor_preferences: Box::new(self.editor_preferences.clone()),\n\t\t\t\t\t}\n\t\t\t\t\t.into();\n\t\t\t\t\tif let Some(graph) = self.old_graph.clone() {\n\t\t\t\t\t\t// We ignore this result as compilation errors should have been reported in an earlier iteration\n\t\t\t\t\t\tlet _ = self.update_network(graph).await;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tGraphRuntimeRequest::EditorPreferencesUpdate(preferences) => {\n\t\t\t\t\tself.editor_preferences = preferences.clone();\n\t\t\t\t\tself.editor_api = WasmEditorApi {\n\t\t\t\t\t\tfont_cache: self.editor_api.font_cache.clone(),\n\t\t\t\t\t\tapplication_io: self.editor_api.application_io.clone(),\n\t\t\t\t\t\tnode_graph_message_sender: Box::new(self.sender.clone()),\n\t\t\t\t\t\teditor_preferences: Box::new(preferences),\n\t\t\t\t\t}\n\t\t\t\t\t.into();\n\t\t\t\t\tif let Some(graph) = self.old_graph.clone() {\n\t\t\t\t\t\t// We ignore this result as compilation errors should have been reported in an earlier iteration\n\t\t\t\t\t\tlet _ = self.update_network(graph).await;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tGraphRuntimeRequest::GraphUpdate(GraphUpdate { mut network, node_to_inspect }) => {\n\t\t\t\t\t// Insert the monitor node to manage the inspection\n\t\t\t\t\tself.inspect_state = node_to_inspect.map(|inspect| InspectState::monitor_inspect_node(&mut network, inspect));\n\n\t\t\t\t\tself.old_graph = Some(network.clone());\n\n\t\t\t\t\tself.node_graph_errors.clear();\n\t\t\t\t\tlet result = self.update_network(network).await;\n\t\t\t\t\tlet node_graph_errors = self.node_graph_errors.clone();\n\n\t\t\t\t\tself.update_thumbnails = true;\n\n\t\t\t\t\tself.sender.send_generation_response(CompilationResponse { result, node_graph_errors });\n\t\t\t\t}\n\t\t\t\tGraphRuntimeRequest::ExecutionRequest(ExecutionRequest { execution_id, mut render_config, .. }) => {\n\t\t\t\t\t// We may want to render via the SVG pipeline even though raster was requested, if SVG Preview render mode is active or WebGPU/Vello is unavailable\n\t\t\t\t\tif render_config.export_format == ExportFormat::Raster\n\t\t\t\t\t\t&& (render_config.render_mode == RenderMode::SvgPreview || self.editor_api.application_io.as_ref().unwrap().gpu_executor().is_none())\n\t\t\t\t\t{\n\t\t\t\t\t\trender_config.export_format = ExportFormat::Svg;\n\t\t\t\t\t}\n\n\t\t\t\t\tlet result = self.execute_network(render_config).await;\n\t\t\t\t\tlet mut responses = VecDeque::new();\n\t\t\t\t\t// TODO: Only process monitor nodes if the graph has changed, not when only the Footprint changes\n\t\t\t\t\tif !render_config.for_eyedropper {\n\t\t\t\t\t\tself.process_monitor_nodes(&mut responses, self.update_thumbnails);\n\t\t\t\t\t}\n\t\t\t\t\tself.update_thumbnails = false;\n\n\t\t\t\t\t// Resolve the result from the inspection by accessing the monitor node\n\t\t\t\t\tlet inspect_result = self.inspect_state.and_then(|state| state.access(&self.executor));\n\n\t\t\t\t\tlet (result, texture) = match result {\n\t\t\t\t\t\tOk(TaggedValue::RenderOutput(RenderOutput {\n\t\t\t\t\t\t\tdata: RenderOutputType::Texture(image_texture),\n\t\t\t\t\t\t\tmetadata,\n\t\t\t\t\t\t})) if render_config.for_export => {\n\t\t\t\t\t\t\tlet executor = self\n\t\t\t\t\t\t\t\t.editor_api\n\t\t\t\t\t\t\t\t.application_io\n\t\t\t\t\t\t\t\t.as_ref()\n\t\t\t\t\t\t\t\t.unwrap()\n\t\t\t\t\t\t\t\t.gpu_executor()\n\t\t\t\t\t\t\t\t.expect(\"GPU executor should be available when we receive a texture\");\n\n\t\t\t\t\t\t\tlet raster_cpu = Raster::new_gpu(image_texture.texture.as_ref().clone()).convert(Footprint::BOUNDLESS, executor).await;\n\n\t\t\t\t\t\t\tlet (data, width, height) = raster_cpu.to_flat_u8();\n\n\t\t\t\t\t\t\t(\n\t\t\t\t\t\t\t\tOk(TaggedValue::RenderOutput(RenderOutput {\n\t\t\t\t\t\t\t\t\tdata: RenderOutputType::Buffer { data, width, height },\n\t\t\t\t\t\t\t\t\tmetadata,\n\t\t\t\t\t\t\t\t})),\n\t\t\t\t\t\t\t\tNone,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tOk(TaggedValue::RenderOutput(RenderOutput {\n\t\t\t\t\t\t\tdata: RenderOutputType::Texture(image_texture),\n\t\t\t\t\t\t\tmetadata: _,\n\t\t\t\t\t\t})) if render_config.for_eyedropper => {\n\t\t\t\t\t\t\tlet executor = self\n\t\t\t\t\t\t\t\t.editor_api\n\t\t\t\t\t\t\t\t.application_io\n\t\t\t\t\t\t\t\t.as_ref()\n\t\t\t\t\t\t\t\t.unwrap()\n\t\t\t\t\t\t\t\t.gpu_executor()\n\t\t\t\t\t\t\t\t.expect(\"GPU executor should be available when we receive a texture\");\n\n\t\t\t\t\t\t\tlet raster_cpu = Raster::new_gpu(image_texture.texture.as_ref().clone()).convert(Footprint::BOUNDLESS, executor).await;\n\n\t\t\t\t\t\t\tself.sender.send_eyedropper_preview(raster_cpu);\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Eyedropper render that didn't produce a texture (e.g., SVG fallback when GPU is unavailable); discard it\n\t\t\t\t\t\t_ if render_config.for_eyedropper => {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t#[cfg(all(target_family = \"wasm\", feature = \"gpu\"))]\n\t\t\t\t\t\tOk(TaggedValue::RenderOutput(RenderOutput {\n\t\t\t\t\t\t\tdata: RenderOutputType::Texture(image_texture),\n\t\t\t\t\t\t\tmetadata,\n\t\t\t\t\t\t})) if !render_config.for_export => {\n\t\t\t\t\t\t\t// On Wasm, for viewport rendering, blit the texture to a surface and return a CanvasFrame\n\t\t\t\t\t\t\tlet app_io = self.editor_api.application_io.as_ref().unwrap();\n\t\t\t\t\t\t\tlet executor = app_io.gpu_executor().expect(\"GPU executor should be available when we receive a texture\");\n\n\t\t\t\t\t\t\t// Get or create the cached surface\n\t\t\t\t\t\t\tif self.wasm_viewport_surface.is_none() {\n\t\t\t\t\t\t\t\tlet surface_handle = app_io.create_window();\n\t\t\t\t\t\t\t\tlet wasm_surface = executor\n\t\t\t\t\t\t\t\t\t.create_surface(graphene_std::wasm_application_io::WasmSurfaceHandle {\n\t\t\t\t\t\t\t\t\t\tsurface: surface_handle.surface.clone(),\n\t\t\t\t\t\t\t\t\t\twindow_id: surface_handle.window_id,\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t.expect(\"Failed to create surface\");\n\t\t\t\t\t\t\t\tself.wasm_viewport_surface = Some(Arc::new(wasm_surface));\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tlet surface = self.wasm_viewport_surface.as_ref().unwrap();\n\n\t\t\t\t\t\t\t// Use logical resolution for CSS sizing, physical resolution for the actual surface/texture\n\t\t\t\t\t\t\tlet physical_resolution = render_config.viewport.resolution;\n\t\t\t\t\t\t\tlet logical_resolution = physical_resolution.as_dvec2() / render_config.scale;\n\n\t\t\t\t\t\t\t// Blit the texture to the surface\n\t\t\t\t\t\t\tlet mut encoder = executor.context.device.create_command_encoder(&vello::wgpu::CommandEncoderDescriptor {\n\t\t\t\t\t\t\t\tlabel: Some(\"Texture to Surface Blit\"),\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t// Configure the surface at physical resolution (for HiDPI displays)\n\t\t\t\t\t\t\tlet surface_inner = &surface.surface.inner;\n\t\t\t\t\t\t\tlet surface_caps = surface_inner.get_capabilities(&executor.context.adapter);\n\t\t\t\t\t\t\tsurface_inner.configure(\n\t\t\t\t\t\t\t\t&executor.context.device,\n\t\t\t\t\t\t\t\t&vello::wgpu::SurfaceConfiguration {\n\t\t\t\t\t\t\t\t\tusage: vello::wgpu::TextureUsages::RENDER_ATTACHMENT | vello::wgpu::TextureUsages::COPY_DST,\n\t\t\t\t\t\t\t\t\tformat: vello::wgpu::TextureFormat::Rgba8Unorm,\n\t\t\t\t\t\t\t\t\twidth: physical_resolution.x,\n\t\t\t\t\t\t\t\t\theight: physical_resolution.y,\n\t\t\t\t\t\t\t\t\tpresent_mode: surface_caps.present_modes[0],\n\t\t\t\t\t\t\t\t\talpha_mode: vello::wgpu::CompositeAlphaMode::PreMultiplied,\n\t\t\t\t\t\t\t\t\tview_formats: vec![],\n\t\t\t\t\t\t\t\t\tdesired_maximum_frame_latency: 2,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\tlet surface_texture = surface_inner.get_current_texture().expect(\"Failed to get surface texture\");\n\t\t\t\t\t\t\tself.current_viewport_texture = Some(image_texture.clone());\n\n\t\t\t\t\t\t\tencoder.copy_texture_to_texture(\n\t\t\t\t\t\t\t\tvello::wgpu::TexelCopyTextureInfoBase {\n\t\t\t\t\t\t\t\t\ttexture: image_texture.texture.as_ref(),\n\t\t\t\t\t\t\t\t\tmip_level: 0,\n\t\t\t\t\t\t\t\t\torigin: Default::default(),\n\t\t\t\t\t\t\t\t\taspect: Default::default(),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tvello::wgpu::TexelCopyTextureInfoBase {\n\t\t\t\t\t\t\t\t\ttexture: &surface_texture.texture,\n\t\t\t\t\t\t\t\t\tmip_level: 0,\n\t\t\t\t\t\t\t\t\torigin: Default::default(),\n\t\t\t\t\t\t\t\t\taspect: Default::default(),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\timage_texture.texture.size(),\n\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\texecutor.context.queue.submit([encoder.finish()]);\n\t\t\t\t\t\t\tsurface_texture.present();\n\n\t\t\t\t\t\t\t// TODO: Figure out if we can explicityl destroy the wgpu texture here to reduce the allocation pressure. We might also be able to use a texture allocation pool\n\n\t\t\t\t\t\t\tlet frame = graphene_std::application_io::SurfaceFrame {\n\t\t\t\t\t\t\t\tsurface_id: surface.window_id,\n\t\t\t\t\t\t\t\tresolution: logical_resolution,\n\t\t\t\t\t\t\t\ttransform: glam::DAffine2::IDENTITY,\n\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\t(\n\t\t\t\t\t\t\t\tOk(TaggedValue::RenderOutput(RenderOutput {\n\t\t\t\t\t\t\t\t\tdata: RenderOutputType::CanvasFrame(frame),\n\t\t\t\t\t\t\t\t\tmetadata,\n\t\t\t\t\t\t\t\t})),\n\t\t\t\t\t\t\t\tNone,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tOk(TaggedValue::RenderOutput(RenderOutput {\n\t\t\t\t\t\t\tdata: RenderOutputType::Texture(texture),\n\t\t\t\t\t\t\tmetadata,\n\t\t\t\t\t\t})) => (\n\t\t\t\t\t\t\tOk(TaggedValue::RenderOutput(RenderOutput {\n\t\t\t\t\t\t\t\tdata: RenderOutputType::Texture(texture.clone()),\n\t\t\t\t\t\t\t\tmetadata,\n\t\t\t\t\t\t\t})),\n\t\t\t\t\t\t\tSome(texture),\n\t\t\t\t\t\t),\n\t\t\t\t\t\tr => (r, None),\n\t\t\t\t\t};\n\n\t\t\t\t\tself.sender.send_execution_response(ExecutionResponse {\n\t\t\t\t\t\texecution_id,\n\t\t\t\t\t\tresult,\n\t\t\t\t\t\tresponses,\n\t\t\t\t\t\tvector_modify: self.vector_modify.clone(),\n\t\t\t\t\t\tinspect_result,\n\t\t\t\t\t});\n\t\t\t\t\treturn texture;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tNone\n\t}\n\n\tasync fn update_network(&mut self, mut graph: NodeNetwork) -> Result<ResolvedDocumentNodeTypesDelta, (ResolvedDocumentNodeTypesDelta, String)> {\n\t\tpreprocessor::expand_network(&mut graph, &self.substitutions);\n\n\t\tlet scoped_network = wrap_network_in_scope(graph, self.editor_api.clone());\n\n\t\t// We assume only one output\n\t\tassert_eq!(scoped_network.exports.len(), 1, \"Graph with multiple outputs not yet handled\");\n\n\t\tlet c = Compiler {};\n\t\tlet proto_network = match c.compile_single(scoped_network) {\n\t\t\tOk(network) => network,\n\t\t\tErr(e) => return Err((ResolvedDocumentNodeTypesDelta::default(), e)),\n\t\t};\n\t\tself.monitor_nodes = proto_network\n\t\t\t.nodes\n\t\t\t.iter()\n\t\t\t.filter(|(_, node)| node.identifier == graphene_std::memo::monitor::IDENTIFIER)\n\t\t\t.map(|(_, node)| node.original_location.path.clone().unwrap_or_default())\n\t\t\t.collect::<Vec<_>>();\n\n\t\tassert_ne!(proto_network.nodes.len(), 0, \"No proto nodes exist?\");\n\t\tself.executor.update(proto_network).await.map_err(|(types, e)| {\n\t\t\tself.node_graph_errors.clone_from(&e);\n\t\t\t(types, format!(\"{e:?}\"))\n\t\t})\n\t}\n\n\tasync fn execute_network(&mut self, render_config: RenderConfig) -> Result<TaggedValue, String> {\n\t\tuse graph_craft::graphene_compiler::Executor;\n\n\t\tmatch self.executor.input_type() {\n\t\t\tSome(t) if t == concrete!(RenderConfig) => (&self.executor).execute(render_config).await.map_err(|e| e.to_string()),\n\t\t\tSome(t) if t == concrete!(()) => (&self.executor).execute(()).await.map_err(|e| e.to_string()),\n\t\t\tSome(t) => Err(format!(\"Invalid input type {t:?}\")),\n\t\t\t_ => Err(format!(\"No input type:\\n{:?}\", self.node_graph_errors)),\n\t\t}\n\t}\n\n\t/// Updates state data\n\tpub fn process_monitor_nodes(&mut self, responses: &mut VecDeque<FrontendMessage>, update_thumbnails: bool) {\n\t\t// TODO: Consider optimizing this since it's currently O(m*n^2), with a sort it could be made O(m * n*log(n))\n\t\tself.thumbnail_renders.retain(|id, _| self.monitor_nodes.iter().any(|monitor_node_path| monitor_node_path.contains(id)));\n\n\t\tfor monitor_node_path in &self.monitor_nodes {\n\t\t\t// Skip the inspect monitor node\n\t\t\tif self.inspect_state.is_some_and(|inspect_state| monitor_node_path.last().copied() == Some(inspect_state.monitor_node)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// The monitor nodes are located within a document node, and are thus children in that network, so this gets the parent document node's ID\n\t\t\tlet Some(parent_network_node_id) = monitor_node_path.len().checked_sub(2).and_then(|index| monitor_node_path.get(index)).copied() else {\n\t\t\t\twarn!(\"Monitor node has invalid node id\");\n\t\t\t\tcontinue;\n\t\t\t};\n\n\t\t\t// Extract the monitor node's stored `Graphic` data\n\t\t\tlet Ok(introspected_data) = self.executor.introspect(monitor_node_path) else {\n\t\t\t\t// TODO: Fix the root of the issue causing the spam of this warning (this at least temporarily disables it in release builds)\n\t\t\t\t#[cfg(debug_assertions)]\n\t\t\t\twarn!(\"Failed to introspect monitor node {}\", self.executor.introspect(monitor_node_path).unwrap_err());\n\t\t\t\tcontinue;\n\t\t\t};\n\n\t\t\t// Graphic table: thumbnail\n\t\t\tif let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Graphic>>>() {\n\t\t\t\tif update_thumbnails {\n\t\t\t\t\tSelf::render_thumbnail(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses)\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Artboard table: thumbnail\n\t\t\telse if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Artboard>>>() {\n\t\t\t\tif update_thumbnails {\n\t\t\t\t\tSelf::render_thumbnail(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses)\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Vector table: vector modifications\n\t\t\telse if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Vector>>>() {\n\t\t\t\t// Insert the vector modify\n\t\t\t\tlet default = TableRow::default();\n\t\t\t\tself.vector_modify\n\t\t\t\t\t.insert(parent_network_node_id, io.output.iter().next().unwrap_or_else(|| default.as_ref()).element.clone());\n\t\t\t}\n\t\t\t// Other\n\t\t\telse {\n\t\t\t\tlog::warn!(\"Failed to downcast monitor node output {parent_network_node_id:?}\");\n\t\t\t}\n\t\t}\n\t}\n\n\t/// If this is `Graphic` data, regenerate click targets and thumbnails for the layers in the graph, modifying the state and updating the UI.\n\tfn render_thumbnail(thumbnail_renders: &mut HashMap<NodeId, Vec<SvgSegment>>, parent_network_node_id: NodeId, graphic: &impl Render, responses: &mut VecDeque<FrontendMessage>) {\n\t\t// Skip thumbnails if the layer is too complex (for performance)\n\t\tif graphic.render_complexity() > 1000 {\n\t\t\tlet old = thumbnail_renders.insert(parent_network_node_id, Vec::new());\n\t\t\tif old.is_none_or(|v| !v.is_empty()) {\n\t\t\t\tresponses.push_back(FrontendMessage::UpdateNodeThumbnail {\n\t\t\t\t\tid: parent_network_node_id,\n\t\t\t\t\tvalue: \"<svg viewBox=\\\"0 0 10 10\\\" data-tooltip-description=\\\"Dense thumbnail omitted for performance.\\\"><line x1=\\\"0\\\" y1=\\\"10\\\" x2=\\\"10\\\" y2=\\\"0\\\" stroke=\\\"red\\\" /></svg>\"\n\t\t\t\t\t\t.to_string(),\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tlet bounds = match graphic.bounding_box(DAffine2::IDENTITY, true) {\n\t\t\tRenderBoundingBox::None => None,\n\t\t\tRenderBoundingBox::Infinite => Some([DVec2::ZERO, DVec2::new(300., 200.)]),\n\t\t\tRenderBoundingBox::Rectangle(bounds) => Some(bounds),\n\t\t};\n\t\tlet new_thumbnail_svg = if let Some(bounds) = bounds {\n\t\t\tlet footprint = Footprint {\n\t\t\t\ttransform: DAffine2::from_translation(DVec2::new(bounds[0].x, bounds[0].y)),\n\t\t\t\tresolution: UVec2::new((bounds[1].x - bounds[0].x).abs() as u32, (bounds[1].y - bounds[0].y).abs() as u32),\n\t\t\t\tquality: RenderQuality::Full,\n\t\t\t};\n\n\t\t\t// Render the thumbnail from a `Graphic` into an SVG string\n\t\t\tlet render_params = RenderParams {\n\t\t\t\tfootprint,\n\t\t\t\tthumbnail: true,\n\t\t\t\t..Default::default()\n\t\t\t};\n\t\t\tlet mut render = SvgRender::new();\n\t\t\tgraphic.render_svg(&mut render, &render_params);\n\n\t\t\t// And give the SVG a viewbox and outer <svg>...</svg> wrapper tag\n\t\t\trender.format_svg(bounds[0], bounds[1]);\n\n\t\t\trender.svg\n\t\t} else {\n\t\t\tVec::new()\n\t\t};\n\n\t\t// Update frontend thumbnail\n\t\tlet old_thumbnail_svg = thumbnail_renders.entry(parent_network_node_id).or_default();\n\t\tif old_thumbnail_svg != &new_thumbnail_svg {\n\t\t\tresponses.push_back(FrontendMessage::UpdateNodeThumbnail {\n\t\t\t\tid: parent_network_node_id,\n\t\t\t\tvalue: new_thumbnail_svg.to_svg_string(),\n\t\t\t});\n\t\t\t*old_thumbnail_svg = new_thumbnail_svg;\n\t\t}\n\t}\n}\n\npub async fn introspect_node(path: &[NodeId]) -> Result<Arc<dyn std::any::Any + Send + Sync + 'static>, IntrospectError> {\n\tlet runtime = NODE_RUNTIME.lock();\n\tif let Some(ref mut runtime) = runtime.as_ref() {\n\t\treturn runtime.executor.introspect(path);\n\t}\n\tErr(IntrospectError::RuntimeNotReady)\n}\n\npub async fn run_node_graph() -> (bool, Option<ImageTexture>) {\n\tlet Some(mut runtime) = NODE_RUNTIME.try_lock() else { return (false, None) };\n\tif let Some(ref mut runtime) = runtime.as_mut() {\n\t\treturn (true, runtime.run().await);\n\t}\n\t(false, None)\n}\n\npub async fn replace_node_runtime(runtime: NodeRuntime) -> Option<NodeRuntime> {\n\tlet mut node_runtime = NODE_RUNTIME.lock();\n\tnode_runtime.replace(runtime)\n}\npub async fn replace_application_io(application_io: WasmApplicationIo) {\n\tlet mut node_runtime = NODE_RUNTIME.lock();\n\tif let Some(node_runtime) = &mut *node_runtime {\n\t\tnode_runtime.editor_api = WasmEditorApi {\n\t\t\tfont_cache: node_runtime.editor_api.font_cache.clone(),\n\t\t\tapplication_io: Some(application_io.into()),\n\t\t\tnode_graph_message_sender: Box::new(node_runtime.sender.clone()),\n\t\t\teditor_preferences: Box::new(node_runtime.editor_preferences.clone()),\n\t\t}\n\t\t.into();\n\t}\n}\n\n/// Which node is inspected and which monitor node is used (if any) for the current execution\n#[derive(Debug, Clone, Copy)]\nstruct InspectState {\n\tinspect_node: NodeId,\n\tmonitor_node: NodeId,\n}\n/// The resulting value from the temporary inspected during execution\n#[derive(Clone, Debug, Default)]\npub struct InspectResult {\n\tintrospected_data: Option<Arc<dyn std::any::Any + Send + Sync + 'static>>,\n\tpub inspect_node: NodeId,\n}\n\nimpl InspectResult {\n\tpub fn take_data(&mut self) -> Option<Arc<dyn std::any::Any + Send + Sync + 'static>> {\n\t\tself.introspected_data.clone()\n\t}\n}\n\n// This is very ugly but is required to be inside a message\nimpl PartialEq for InspectResult {\n\tfn eq(&self, other: &Self) -> bool {\n\t\tself.inspect_node == other.inspect_node\n\t}\n}\n\nimpl InspectState {\n\t/// Insert the monitor node to manage the inspection\n\tpub fn monitor_inspect_node(network: &mut NodeNetwork, inspect_node: NodeId) -> Self {\n\t\tlet monitor_id = NodeId::new();\n\n\t\t// It is necessary to replace the inputs before inserting the monitor node to avoid changing the input of the new monitor node\n\t\tfor input in network.nodes.values_mut().flat_map(|node| node.inputs.iter_mut()).chain(&mut network.exports) {\n\t\t\tlet NodeInput::Node { node_id, output_index, .. } = input else { continue };\n\t\t\t// We only care about the primary output of our inspect node\n\t\t\tif *output_index != 0 || *node_id != inspect_node {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t*node_id = monitor_id;\n\t\t}\n\n\t\tlet monitor_node = DocumentNode {\n\t\t\tinputs: vec![NodeInput::node(inspect_node, 0)], // Connect to the primary output of the inspect node\n\t\t\timplementation: DocumentNodeImplementation::ProtoNode(graphene_std::memo::monitor::IDENTIFIER),\n\t\t\tcall_argument: graph_craft::generic!(T),\n\t\t\tskip_deduplication: true,\n\t\t\t..Default::default()\n\t\t};\n\t\tnetwork.nodes.insert(monitor_id, monitor_node);\n\n\t\tSelf {\n\t\t\tinspect_node,\n\t\t\tmonitor_node: monitor_id,\n\t\t}\n\t}\n\t/// Resolve the result from the inspection by accessing the monitor node\n\tfn access(&self, executor: &DynamicExecutor) -> Option<InspectResult> {\n\t\tlet introspected_data = executor.introspect(&[self.monitor_node]).inspect_err(|e| warn!(\"Failed to introspect monitor node {e}\")).ok();\n\t\t// TODO: Consider displaying the error instead of ignoring it\n\n\t\tSome(InspectResult {\n\t\t\tinspect_node: self.inspect_node,\n\t\t\tintrospected_data,\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "editor/src/node_graph_executor/runtime_io.rs",
    "content": "use super::*;\nuse std::sync::mpsc::{Receiver, Sender};\n\n/// Handles communication with the NodeRuntime\n#[derive(Debug)]\npub struct NodeRuntimeIO {\n\t// Send to\n\tsender: Sender<GraphRuntimeRequest>,\n\treceiver: Receiver<NodeGraphUpdate>,\n}\n\nimpl Default for NodeRuntimeIO {\n\tfn default() -> Self {\n\t\tSelf::new()\n\t}\n}\n\nimpl NodeRuntimeIO {\n\t/// Creates a new NodeRuntimeIO instance\n\tpub fn new() -> Self {\n\t\tlet (response_sender, response_receiver) = std::sync::mpsc::channel();\n\t\tlet (request_sender, request_receiver) = std::sync::mpsc::channel();\n\t\tfutures::executor::block_on(replace_node_runtime(NodeRuntime::new(request_receiver, response_sender)));\n\n\t\tSelf {\n\t\t\tsender: request_sender,\n\t\t\treceiver: response_receiver,\n\t\t}\n\t}\n\t#[cfg(test)]\n\tpub fn with_channels(sender: Sender<GraphRuntimeRequest>, receiver: Receiver<NodeGraphUpdate>) -> Self {\n\t\tSelf { sender, receiver }\n\t}\n\n\t/// Sends a message to the NodeRuntime\n\tpub fn send(&self, message: GraphRuntimeRequest) -> Result<(), String> {\n\t\tself.sender.send(message).map_err(|e| e.to_string())\n\t}\n\n\t/// Receives any pending updates from the NodeRuntime\n\tpub fn receive(&self) -> impl Iterator<Item = NodeGraphUpdate> + use<'_> {\n\t\tself.receiver.try_iter()\n\t}\n}\n"
  },
  {
    "path": "editor/src/node_graph_executor.rs",
    "content": "use crate::messages::frontend::utility_types::{ExportBounds, FileType};\nuse crate::messages::prelude::*;\nuse glam::{DAffine2, DVec2, UVec2};\nuse graph_craft::document::value::{RenderOutput, TaggedValue};\nuse graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput};\nuse graph_craft::proto::GraphErrors;\nuse graph_craft::wasm_application_io::EditorPreferences;\nuse graphene_std::application_io::{NodeGraphUpdateMessage, RenderConfig, TimingInformation};\nuse graphene_std::raster::{CPU, Raster};\nuse graphene_std::renderer::{RenderMetadata, format_transform_matrix};\nuse graphene_std::text::FontCache;\nuse graphene_std::transform::Footprint;\nuse graphene_std::vector::Vector;\nuse graphene_std::wasm_application_io::RenderOutputType;\nuse interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypesDelta;\n\nmod runtime_io;\npub use runtime_io::NodeRuntimeIO;\n\nmod runtime;\npub use runtime::*;\n\n#[derive(Debug, serde::Serialize, serde::Deserialize)]\npub struct ExecutionRequest {\n\texecution_id: u64,\n\trender_config: RenderConfig,\n}\n\npub struct ExecutionResponse {\n\texecution_id: u64,\n\tresult: Result<TaggedValue, String>,\n\tresponses: VecDeque<FrontendMessage>,\n\tvector_modify: HashMap<NodeId, Vector>,\n\t/// The resulting value from the temporary inspected during execution\n\tinspect_result: Option<InspectResult>,\n}\n\n#[derive(serde::Serialize, serde::Deserialize)]\npub struct CompilationResponse {\n\tresult: Result<ResolvedDocumentNodeTypesDelta, (ResolvedDocumentNodeTypesDelta, String)>,\n\tnode_graph_errors: GraphErrors,\n}\n\npub enum NodeGraphUpdate {\n\tExecutionResponse(ExecutionResponse),\n\tCompilationResponse(CompilationResponse),\n\tEyedropperPreview(Raster<CPU>),\n\tNodeGraphUpdateMessage(NodeGraphUpdateMessage),\n}\n\n#[derive(Debug, Default)]\npub struct NodeGraphExecutor {\n\truntime_io: NodeRuntimeIO,\n\tcurrent_execution_id: u64,\n\tfutures: VecDeque<(u64, ExecutionContext)>,\n\tnode_graph_hash: u64,\n\tprevious_node_to_inspect: Option<NodeId>,\n}\n\n#[derive(Debug, Clone)]\nstruct ExecutionContext {\n\texport_config: Option<ExportConfig>,\n\tdocument_id: DocumentId,\n}\n\nimpl NodeGraphExecutor {\n\t/// A local runtime is useful on threads since having global state causes flakes\n\t#[cfg(test)]\n\tpub(crate) fn new_with_local_runtime() -> (NodeRuntime, Self) {\n\t\tlet (request_sender, request_receiver) = std::sync::mpsc::channel();\n\t\tlet (response_sender, response_receiver) = std::sync::mpsc::channel();\n\t\tlet node_runtime = NodeRuntime::new(request_receiver, response_sender);\n\n\t\tlet node_executor = Self {\n\t\t\tfutures: Default::default(),\n\t\t\truntime_io: NodeRuntimeIO::with_channels(request_sender, response_receiver),\n\t\t\tnode_graph_hash: 0,\n\t\t\tcurrent_execution_id: 0,\n\t\t\tprevious_node_to_inspect: None,\n\t\t};\n\t\t(node_runtime, node_executor)\n\t}\n\n\t/// Execute the network by flattening it and creating a borrow stack.\n\tfn queue_execution(&mut self, render_config: RenderConfig) -> u64 {\n\t\tlet execution_id = self.current_execution_id;\n\t\tself.current_execution_id += 1;\n\t\tlet request = ExecutionRequest { execution_id, render_config };\n\t\tself.runtime_io.send(GraphRuntimeRequest::ExecutionRequest(request)).expect(\"Failed to send generation request\");\n\n\t\texecution_id\n\t}\n\n\tpub fn update_font_cache(&self, font_cache: FontCache) {\n\t\tself.runtime_io.send(GraphRuntimeRequest::FontCacheUpdate(font_cache)).expect(\"Failed to send font cache update\");\n\t}\n\n\tpub fn update_editor_preferences(&self, editor_preferences: EditorPreferences) {\n\t\tself.runtime_io\n\t\t\t.send(GraphRuntimeRequest::EditorPreferencesUpdate(editor_preferences))\n\t\t\t.expect(\"Failed to send editor preferences\");\n\t}\n\n\t/// Updates the network to monitor all inputs. Useful for the testing.\n\t#[cfg(test)]\n\tpub(crate) fn update_node_graph_instrumented(&mut self, document: &mut DocumentMessageHandler) -> Result<Instrumented, String> {\n\t\t// We should always invalidate the cache.\n\t\tself.node_graph_hash = crate::application::generate_uuid();\n\t\tlet mut network = document.network_interface.document_network().clone();\n\t\tlet instrumented = Instrumented::new(&mut network);\n\n\t\tself.runtime_io\n\t\t\t.send(GraphRuntimeRequest::GraphUpdate(GraphUpdate { network, node_to_inspect: None }))\n\t\t\t.map_err(|e| e.to_string())?;\n\t\tOk(instrumented)\n\t}\n\n\t/// Update the cached network if necessary.\n\tfn update_node_graph(&mut self, document: &mut DocumentMessageHandler, node_to_inspect: Option<NodeId>, ignore_hash: bool) -> Result<(), String> {\n\t\tlet network_hash = document.network_interface.network_hash();\n\t\t// Refresh the graph when it changes or the inspect node changes\n\t\tif network_hash != self.node_graph_hash || self.previous_node_to_inspect != node_to_inspect || ignore_hash {\n\t\t\tlet network = document.network_interface.document_network().clone();\n\t\t\tself.previous_node_to_inspect = node_to_inspect;\n\t\t\tself.node_graph_hash = network_hash;\n\n\t\t\tself.runtime_io\n\t\t\t\t.send(GraphRuntimeRequest::GraphUpdate(GraphUpdate { network, node_to_inspect }))\n\t\t\t\t.map_err(|e| e.to_string())?;\n\t\t}\n\n\t\tOk(())\n\t}\n\n\t/// Adds an evaluate request for whatever current network is cached.\n\tpub(crate) fn submit_current_node_graph_evaluation(\n\t\t&mut self,\n\t\tdocument: &mut DocumentMessageHandler,\n\t\tdocument_id: DocumentId,\n\t\tviewport_resolution: UVec2,\n\t\tviewport_scale: f64,\n\t\ttime: TimingInformation,\n\t\tpointer: DVec2,\n\t) -> Result<Message, String> {\n\t\tlet viewport = Footprint {\n\t\t\ttransform: document.metadata().document_to_viewport,\n\t\t\tresolution: viewport_resolution,\n\t\t\t..Default::default()\n\t\t};\n\t\tlet render_config = RenderConfig {\n\t\t\tviewport,\n\t\t\tscale: viewport_scale,\n\t\t\ttime,\n\t\t\tpointer,\n\t\t\texport_format: graphene_std::application_io::ExportFormat::Raster,\n\t\t\trender_mode: document.render_mode,\n\t\t\thide_artboards: false,\n\t\t\tfor_export: false,\n\t\t\tfor_eyedropper: false,\n\t\t};\n\n\t\t// Execute the node graph\n\t\tlet execution_id = self.queue_execution(render_config);\n\n\t\tself.futures.push_back((execution_id, ExecutionContext { export_config: None, document_id }));\n\n\t\tOk(DeferMessage::SetGraphSubmissionIndex { execution_id }.into())\n\t}\n\n\t/// Evaluates a node graph, computing the entire graph\n\t#[allow(clippy::too_many_arguments)]\n\tpub fn submit_node_graph_evaluation(\n\t\t&mut self,\n\t\tdocument: &mut DocumentMessageHandler,\n\t\tdocument_id: DocumentId,\n\t\tviewport_resolution: UVec2,\n\t\tviewport_scale: f64,\n\t\ttime: TimingInformation,\n\t\tnode_to_inspect: Option<NodeId>,\n\t\tignore_hash: bool,\n\t\tpointer: DVec2,\n\t) -> Result<Message, String> {\n\t\tself.update_node_graph(document, node_to_inspect, ignore_hash)?;\n\t\tself.submit_current_node_graph_evaluation(document, document_id, viewport_resolution, viewport_scale, time, pointer)\n\t}\n\n\t#[allow(clippy::too_many_arguments)]\n\tpub(crate) fn submit_eyedropper_preview(\n\t\t&mut self,\n\t\tdocument: &DocumentMessageHandler,\n\t\tdocument_id: DocumentId,\n\t\ttransform: DAffine2,\n\t\tpointer: DVec2,\n\t\tviewport_resolution: UVec2,\n\t\tviewport_scale: f64,\n\t\ttime: TimingInformation,\n\t) -> Result<Message, String> {\n\t\tlet viewport = Footprint {\n\t\t\ttransform,\n\t\t\tresolution: viewport_resolution,\n\t\t\t..Default::default()\n\t\t};\n\n\t\t// TODO: On desktop, SVG Preview mode cannot work with the Eyedropper tool until <https://github.com/GraphiteEditor/Graphite/issues/3796> is implemented.\n\t\t// TODO: So for now, we fall back to the Eyedropper using Normal mode (Vello) rendering, which looks similar enough to SVG Preview.\n\t\t#[cfg(not(target_family = \"wasm\"))]\n\t\tlet render_mode = match document.render_mode {\n\t\t\tgraphene_std::vector::style::RenderMode::SvgPreview => graphene_std::vector::style::RenderMode::Normal,\n\t\t\tother => other,\n\t\t};\n\t\t// On web, SVG Preview is handled by the frontend's SVG rasterization path instead, producing the correct result, so we keep it enabled.\n\t\t#[cfg(target_family = \"wasm\")]\n\t\tlet render_mode = document.render_mode;\n\n\t\tlet render_config = RenderConfig {\n\t\t\tviewport,\n\t\t\tscale: viewport_scale,\n\t\t\ttime,\n\t\t\tpointer,\n\t\t\texport_format: graphene_std::application_io::ExportFormat::Raster,\n\t\t\trender_mode,\n\t\t\thide_artboards: false,\n\t\t\tfor_export: false,\n\t\t\tfor_eyedropper: true,\n\t\t};\n\n\t\t// Execute the node graph\n\t\tlet execution_id = self.queue_execution(render_config);\n\n\t\tself.futures.push_back((execution_id, ExecutionContext { export_config: None, document_id }));\n\n\t\tOk(DeferMessage::SetGraphSubmissionIndex { execution_id }.into())\n\t}\n\n\t/// Evaluates a node graph for export\n\tpub fn submit_document_export(&mut self, document: &mut DocumentMessageHandler, document_id: DocumentId, mut export_config: ExportConfig) -> Result<(), String> {\n\t\tlet network = document.network_interface.document_network().clone();\n\n\t\tlet export_format = if export_config.file_type == FileType::Svg {\n\t\t\tgraphene_std::application_io::ExportFormat::Svg\n\t\t} else {\n\t\t\tgraphene_std::application_io::ExportFormat::Raster\n\t\t};\n\n\t\t// Calculate the bounding box of the region to be exported\n\t\tlet bounds = match export_config.bounds {\n\t\t\tExportBounds::AllArtwork => document.network_interface.document_bounds_document_space(!export_config.transparent_background),\n\t\t\tExportBounds::Selection => document.network_interface.selected_bounds_document_space(!export_config.transparent_background, &[]),\n\t\t\tExportBounds::Artboard(id) => document.metadata().bounding_box_document(id),\n\t\t}\n\t\t.ok_or_else(|| \"No bounding box\".to_string())?;\n\n\t\tlet resolution_in_document_space = bounds[1] - bounds[0];\n\t\tlet export_resolution = resolution_in_document_space * export_config.scale_factor;\n\t\tlet resolution = export_resolution.round().as_uvec2();\n\t\tlet transform = DAffine2::from_translation(bounds[0]).inverse();\n\t\tlet viewport = Footprint {\n\t\t\tresolution,\n\t\t\ttransform,\n\t\t\t..Default::default()\n\t\t};\n\n\t\tlet render_config = RenderConfig {\n\t\t\tviewport,\n\t\t\tscale: export_config.scale_factor,\n\t\t\ttime: Default::default(),\n\t\t\tpointer: DVec2::ZERO,\n\t\t\texport_format,\n\t\t\trender_mode: document.render_mode,\n\t\t\thide_artboards: export_config.transparent_background,\n\t\t\tfor_export: true,\n\t\t\tfor_eyedropper: false,\n\t\t};\n\t\texport_config.size = resolution;\n\n\t\t// Execute the node graph\n\t\tself.runtime_io\n\t\t\t.send(GraphRuntimeRequest::GraphUpdate(GraphUpdate { network, node_to_inspect: None }))\n\t\t\t.map_err(|e| e.to_string())?;\n\t\tlet execution_id = self.queue_execution(render_config);\n\t\tself.futures.push_back((\n\t\t\texecution_id,\n\t\t\tExecutionContext {\n\t\t\t\texport_config: Some(export_config),\n\t\t\t\tdocument_id,\n\t\t\t},\n\t\t));\n\n\t\tOk(())\n\t}\n\n\tpub fn poll_node_graph_evaluation(&mut self, document: &mut DocumentMessageHandler, responses: &mut VecDeque<Message>) -> Result<(), String> {\n\t\tlet results = self.runtime_io.receive().collect::<Vec<_>>();\n\t\tfor response in results {\n\t\t\tmatch response {\n\t\t\t\tNodeGraphUpdate::ExecutionResponse(execution_response) => {\n\t\t\t\t\tlet ExecutionResponse {\n\t\t\t\t\t\texecution_id,\n\t\t\t\t\t\tresult,\n\t\t\t\t\t\tresponses: existing_responses,\n\t\t\t\t\t\tvector_modify,\n\t\t\t\t\t\tinspect_result,\n\t\t\t\t\t} = execution_response;\n\n\t\t\t\t\tresponses.add(OverlaysMessage::Draw);\n\n\t\t\t\t\tlet node_graph_output = match result {\n\t\t\t\t\t\tOk(output) => output,\n\t\t\t\t\t\tErr(e) => {\n\t\t\t\t\t\t\t// Clear the click targets while the graph is in an un-renderable state\n\t\t\t\t\t\t\tdocument.network_interface.update_click_targets(HashMap::new());\n\t\t\t\t\t\t\tdocument.network_interface.update_vector_modify(HashMap::new());\n\t\t\t\t\t\t\treturn Err(format!(\"Node graph evaluation failed:\\n{e}\"));\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\tresponses.extend(existing_responses.into_iter().map(Into::into));\n\t\t\t\t\tdocument.network_interface.update_vector_modify(vector_modify);\n\n\t\t\t\t\twhile let Some(&(fid, _)) = self.futures.front() {\n\t\t\t\t\t\tif fid < execution_id {\n\t\t\t\t\t\t\tself.futures.pop_front();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tlet Some((fid, execution_context)) = self.futures.pop_front() else {\n\t\t\t\t\t\tpanic!(\"InvalidGenerationId\")\n\t\t\t\t\t};\n\t\t\t\t\tassert_eq!(fid, execution_id, \"Missmatch in execution id\");\n\n\t\t\t\t\tif let Some(export_config) = execution_context.export_config {\n\t\t\t\t\t\t// Special handling for exporting the artwork\n\t\t\t\t\t\tself.process_export(node_graph_output, export_config, responses)?;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tself.process_node_graph_output(node_graph_output, responses)?;\n\t\t\t\t\t}\n\t\t\t\t\tresponses.add(DeferMessage::TriggerGraphRun {\n\t\t\t\t\t\texecution_id,\n\t\t\t\t\t\tdocument_id: execution_context.document_id,\n\t\t\t\t\t});\n\n\t\t\t\t\t// Update the Data panel on the frontend using the value of the inspect result.\n\t\t\t\t\tif let Some(inspect_result) = (self.previous_node_to_inspect.is_some()).then_some(inspect_result).flatten() {\n\t\t\t\t\t\tresponses.add(DataPanelMessage::UpdateLayout { inspect_result });\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresponses.add(DataPanelMessage::ClearLayout);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tNodeGraphUpdate::CompilationResponse(execution_response) => {\n\t\t\t\t\tlet CompilationResponse { node_graph_errors, result } = execution_response;\n\t\t\t\t\tlet type_delta = match result {\n\t\t\t\t\t\tErr((incomplete_delta, e)) => {\n\t\t\t\t\t\t\t// Clear the click targets while the graph is in an un-renderable state\n\n\t\t\t\t\t\t\tdocument.network_interface.update_click_targets(HashMap::new());\n\t\t\t\t\t\t\tdocument.network_interface.update_vector_modify(HashMap::new());\n\n\t\t\t\t\t\t\tlog::trace!(\"{e}\");\n\n\t\t\t\t\t\t\tresponses.add(NodeGraphMessage::UpdateTypes {\n\t\t\t\t\t\t\t\tresolved_types: incomplete_delta,\n\t\t\t\t\t\t\t\tnode_graph_errors,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tresponses.add(NodeGraphMessage::SendGraph);\n\n\t\t\t\t\t\t\treturn Err(format!(\"Node graph evaluation failed:\\n{e}\"));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tOk(result) => result,\n\t\t\t\t\t};\n\n\t\t\t\t\tresponses.add(NodeGraphMessage::UpdateTypes {\n\t\t\t\t\t\tresolved_types: type_delta,\n\t\t\t\t\t\tnode_graph_errors,\n\t\t\t\t\t});\n\t\t\t\t\tresponses.add(NodeGraphMessage::SendGraph);\n\t\t\t\t}\n\t\t\t\tNodeGraphUpdate::EyedropperPreview(raster) => {\n\t\t\t\t\tlet (data, width, height) = raster.to_flat_u8();\n\t\t\t\t\tresponses.add(EyedropperToolMessage::PreviewImage { data, width, height });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tOk(())\n\t}\n\n\tfn process_node_graph_output(&mut self, node_graph_output: TaggedValue, responses: &mut VecDeque<Message>) -> Result<(), String> {\n\t\tlet TaggedValue::RenderOutput(render_output) = node_graph_output else {\n\t\t\treturn Err(format!(\"Invalid node graph output type: {node_graph_output:#?}\"));\n\t\t};\n\n\t\tmatch render_output.data {\n\t\t\tRenderOutputType::Svg { svg, image_data } => {\n\t\t\t\t// Send to frontend\n\t\t\t\tresponses.add(FrontendMessage::UpdateImageData { image_data });\n\t\t\t\tresponses.add(FrontendMessage::UpdateDocumentArtwork { svg });\n\t\t\t}\n\t\t\tRenderOutputType::CanvasFrame(frame) => {\n\t\t\t\tlet matrix = format_transform_matrix(frame.transform);\n\t\t\t\tlet transform = if matrix.is_empty() { String::new() } else { format!(\" transform=\\\"{matrix}\\\"\") };\n\t\t\t\tlet svg = format!(\n\t\t\t\t\tr#\"<svg><foreignObject width=\"{}\" height=\"{}\"{transform}><div data-canvas-placeholder=\"{}\" data-is-viewport=\"true\"></div></foreignObject></svg>\"#,\n\t\t\t\t\tframe.resolution.x, frame.resolution.y, frame.surface_id.0,\n\t\t\t\t);\n\t\t\t\tresponses.add(FrontendMessage::UpdateDocumentArtwork { svg });\n\t\t\t}\n\t\t\tRenderOutputType::Texture { .. } => {}\n\t\t\t_ => return Err(format!(\"Invalid node graph output type: {:#?}\", render_output.data)),\n\t\t}\n\n\t\tlet RenderMetadata {\n\t\t\tupstream_footprints,\n\t\t\tlocal_transforms,\n\t\t\tfirst_element_source_id,\n\t\t\tclick_targets,\n\t\t\tclip_targets,\n\t\t\tvector_data,\n\t\t} = render_output.metadata;\n\n\t\t// Run these update state messages immediately\n\t\tresponses.add(DocumentMessage::UpdateUpstreamTransforms {\n\t\t\tupstream_footprints,\n\t\t\tlocal_transforms,\n\t\t\tfirst_element_source_id,\n\t\t});\n\t\tresponses.add(DocumentMessage::UpdateClickTargets { click_targets });\n\t\tresponses.add(DocumentMessage::UpdateClipTargets { clip_targets });\n\t\tresponses.add(DocumentMessage::UpdateVectorData { vector_data });\n\t\tresponses.add(DocumentMessage::RenderScrollbars);\n\t\tresponses.add(DocumentMessage::RenderRulers);\n\t\tresponses.add(OverlaysMessage::Draw);\n\n\t\tOk(())\n\t}\n\n\tfn process_export(&self, node_graph_output: TaggedValue, export_config: ExportConfig, responses: &mut VecDeque<Message>) -> Result<(), String> {\n\t\tlet ExportConfig {\n\t\t\tfile_type,\n\t\t\tname,\n\t\t\tsize,\n\t\t\t#[cfg(feature = \"gpu\")]\n\t\t\ttransparent_background,\n\t\t\tartboard_name,\n\t\t\tartboard_count,\n\t\t\t..\n\t\t} = export_config;\n\n\t\tlet file_extension = match file_type {\n\t\t\tFileType::Svg => \"svg\",\n\t\t\tFileType::Png => \"png\",\n\t\t\tFileType::Jpg => \"jpg\",\n\t\t};\n\t\tlet base_name = match (artboard_name, artboard_count) {\n\t\t\t(Some(artboard_name), count) if count > 1 => format!(\"{name} - {artboard_name}\"),\n\t\t\t_ => name,\n\t\t};\n\t\tlet name = format!(\"{base_name}.{file_extension}\");\n\n\t\tmatch node_graph_output {\n\t\t\tTaggedValue::RenderOutput(RenderOutput {\n\t\t\t\tdata: RenderOutputType::Svg { svg, .. },\n\t\t\t\t..\n\t\t\t}) => {\n\t\t\t\tif file_type == FileType::Svg {\n\t\t\t\t\tresponses.add(FrontendMessage::TriggerSaveFile {\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tcontent: svg.into_bytes().into(),\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tlet mime = file_type.to_mime().to_string();\n\t\t\t\t\tlet size = size.as_dvec2().into();\n\t\t\t\t\tresponses.add(FrontendMessage::TriggerExportImage { svg, name, mime, size });\n\t\t\t\t}\n\t\t\t}\n\t\t\t#[cfg(feature = \"gpu\")]\n\t\t\tTaggedValue::RenderOutput(RenderOutput {\n\t\t\t\tdata: RenderOutputType::Buffer { data, width, height },\n\t\t\t\t..\n\t\t\t}) if file_type != FileType::Svg => {\n\t\t\t\tuse image::buffer::ConvertBuffer;\n\t\t\t\tuse image::{ImageFormat, RgbImage, RgbaImage};\n\n\t\t\t\tlet Some(image) = RgbaImage::from_raw(width, height, data) else {\n\t\t\t\t\treturn Err(\"Failed to create image buffer for export\".to_string());\n\t\t\t\t};\n\n\t\t\t\tlet mut encoded = Vec::new();\n\t\t\t\tlet mut cursor = std::io::Cursor::new(&mut encoded);\n\n\t\t\t\tmatch file_type {\n\t\t\t\t\tFileType::Png => {\n\t\t\t\t\t\tlet result = if transparent_background {\n\t\t\t\t\t\t\timage.write_to(&mut cursor, ImageFormat::Png)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tlet image: RgbImage = image.convert();\n\t\t\t\t\t\t\timage.write_to(&mut cursor, ImageFormat::Png)\n\t\t\t\t\t\t};\n\t\t\t\t\t\tif let Err(err) = result {\n\t\t\t\t\t\t\treturn Err(format!(\"Failed to encode PNG: {err}\"));\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tFileType::Jpg => {\n\t\t\t\t\t\tlet image: RgbImage = image.convert();\n\t\t\t\t\t\tlet result = image.write_to(&mut cursor, ImageFormat::Jpeg);\n\t\t\t\t\t\tif let Err(err) = result {\n\t\t\t\t\t\t\treturn Err(format!(\"Failed to encode JPG: {err}\"));\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tFileType::Svg => {\n\t\t\t\t\t\treturn Err(\"SVG cannot be exported from an image buffer\".to_string());\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tresponses.add(FrontendMessage::TriggerSaveFile { name, content: encoded.into() });\n\t\t\t}\n\t\t\t_ => {\n\t\t\t\treturn Err(format!(\"Incorrect render type for exporting to an SVG ({file_type:?}, {node_graph_output})\"));\n\t\t\t}\n\t\t};\n\n\t\tOk(())\n\t}\n}\n\n// Re-export for usage by tests in other modules\n#[cfg(test)]\npub use test::Instrumented;\n\n#[cfg(test)]\nmod test {\n\tuse std::sync::Arc;\n\n\tuse super::*;\n\tuse crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;\n\tuse crate::test_utils::test_prelude::{self, NodeGraphLayer};\n\tuse graph_craft::ProtoNodeIdentifier;\n\tuse graph_craft::document::NodeNetwork;\n\tuse graphene_std::Context;\n\tuse graphene_std::NodeInputDecleration;\n\tuse graphene_std::memo::IORecord;\n\tuse test_prelude::LayerNodeIdentifier;\n\n\t/// Stores all of the monitor nodes that have been attached to a graph\n\t#[derive(Default)]\n\tpub struct Instrumented {\n\t\tprotonodes_by_name: HashMap<ProtoNodeIdentifier, Vec<Vec<Vec<NodeId>>>>,\n\t\tprotonodes_by_path: HashMap<Vec<NodeId>, Vec<Vec<NodeId>>>,\n\t}\n\n\timpl Instrumented {\n\t\t/// Adds montior nodes to the network\n\t\tfn add(&mut self, network: &mut NodeNetwork, path: &mut Vec<NodeId>) {\n\t\t\t// Required to do seperately to satiate the borrow checker.\n\t\t\tlet mut monitor_nodes = Vec::new();\n\t\t\tfor (id, node) in network.nodes.iter_mut() {\n\t\t\t\t// Recursively instrument\n\t\t\t\tif let DocumentNodeImplementation::Network(nested) = &mut node.implementation {\n\t\t\t\t\tpath.push(*id);\n\t\t\t\t\tself.add(nested, path);\n\t\t\t\t\tpath.pop();\n\t\t\t\t}\n\t\t\t\tlet mut monitor_node_ids = Vec::with_capacity(node.inputs.len());\n\t\t\t\tfor input in &mut node.inputs {\n\t\t\t\t\tlet node_id = NodeId::new();\n\t\t\t\t\tlet old_input = std::mem::replace(input, NodeInput::node(node_id, 0));\n\t\t\t\t\tmonitor_nodes.push((old_input, node_id));\n\t\t\t\t\tpath.push(node_id);\n\t\t\t\t\tmonitor_node_ids.push(path.clone());\n\t\t\t\t\tpath.pop();\n\t\t\t\t}\n\t\t\t\tif let DocumentNodeImplementation::ProtoNode(identifier) = &mut node.implementation {\n\t\t\t\t\tpath.push(*id);\n\t\t\t\t\tself.protonodes_by_name.entry(identifier.clone()).or_default().push(monitor_node_ids.clone());\n\t\t\t\t\tself.protonodes_by_path.insert(path.clone(), monitor_node_ids);\n\t\t\t\t\tpath.pop();\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (input, monitor_id) in monitor_nodes {\n\t\t\t\tlet monitor_node = DocumentNode {\n\t\t\t\t\tinputs: vec![input],\n\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(graphene_std::memo::monitor::IDENTIFIER),\n\t\t\t\t\tcall_argument: graph_craft::generic!(T),\n\t\t\t\t\tskip_deduplication: true,\n\t\t\t\t\t..Default::default()\n\t\t\t\t};\n\t\t\t\tnetwork.nodes.insert(monitor_id, monitor_node);\n\t\t\t}\n\t\t}\n\n\t\t/// Instrument a graph and return a new [Instrumented] state.\n\t\tpub fn new(network: &mut NodeNetwork) -> Self {\n\t\t\tlet mut instrumented = Self::default();\n\t\t\tinstrumented.add(network, &mut Vec::new());\n\t\t\tinstrumented\n\t\t}\n\n\t\tfn downcast<Input: NodeInputDecleration>(dynamic: Arc<dyn std::any::Any + Send + Sync>) -> Option<Input::Result>\n\t\twhere\n\t\t\tInput::Result: Send + Sync + Clone + 'static,\n\t\t{\n\t\t\t// This is quite inflexible since it only allows the footprint as inputs.\n\t\t\tif let Some(x) = dynamic.downcast_ref::<IORecord<(), Input::Result>>() {\n\t\t\t\tSome(x.output.clone())\n\t\t\t} else if let Some(x) = dynamic.downcast_ref::<IORecord<Footprint, Input::Result>>() {\n\t\t\t\tSome(x.output.clone())\n\t\t\t} else if let Some(x) = dynamic.downcast_ref::<IORecord<Context, Input::Result>>() {\n\t\t\t\tSome(x.output.clone())\n\t\t\t} else {\n\t\t\t\twarn!(\"cannot downcast type for introspection\");\n\t\t\t\tNone\n\t\t\t}\n\t\t}\n\n\t\t/// Grab all of the values of the input every time it occurs in the graph.\n\t\tpub fn grab_all_input<'a, Input: NodeInputDecleration + 'a>(&'a self, runtime: &'a NodeRuntime) -> impl Iterator<Item = Input::Result> + 'a\n\t\twhere\n\t\t\tInput::Result: Send + Sync + Clone + 'static,\n\t\t{\n\t\t\tself.protonodes_by_name\n\t\t\t\t.get(&Input::identifier())\n\t\t\t\t.map_or([].as_slice(), |x| x.as_slice())\n\t\t\t\t.iter()\n\t\t\t\t.filter_map(|inputs| inputs.get(Input::INDEX))\n\t\t\t\t.filter_map(|input_monitor_node| runtime.executor.introspect(input_monitor_node).ok())\n\t\t\t\t.filter_map(Instrumented::downcast::<Input>) // Some might not resolve (e.g. generics that don't work properly)\n\t\t}\n\n\t\tpub fn grab_protonode_input<Input: NodeInputDecleration>(&self, path: &Vec<NodeId>, runtime: &NodeRuntime) -> Option<Input::Result>\n\t\twhere\n\t\t\tInput::Result: Send + Sync + Clone + 'static,\n\t\t{\n\t\t\tlet input_monitor_node = self.protonodes_by_path.get(path)?.get(Input::INDEX)?;\n\n\t\t\tlet dynamic = runtime.executor.introspect(input_monitor_node).ok()?;\n\n\t\t\tSelf::downcast::<Input>(dynamic)\n\t\t}\n\n\t\tpub fn grab_input_from_layer<Input: NodeInputDecleration>(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface, runtime: &NodeRuntime) -> Option<Input::Result>\n\t\twhere\n\t\t\tInput::Result: Send + Sync + Clone + 'static,\n\t\t{\n\t\t\tlet node_graph_layer = NodeGraphLayer::new(layer, network_interface);\n\t\t\tlet node = node_graph_layer.upstream_node_id_from_protonode(Input::identifier())?;\n\t\t\tself.grab_protonode_input::<Input>(&vec![node], runtime)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "editor/src/test_utils.rs",
    "content": "use crate::application::Editor;\nuse crate::messages::input_mapper::utility_types::input_keyboard::ModifierKeys;\nuse crate::messages::input_mapper::utility_types::input_mouse::{EditorMouseState, MouseKeys, ScrollDelta, ViewportPosition};\nuse crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;\nuse crate::messages::prelude::*;\nuse crate::messages::tool::tool_messages::tool_prelude::Key;\nuse crate::messages::tool::utility_types::ToolType;\nuse crate::node_graph_executor::Instrumented;\nuse crate::node_graph_executor::NodeRuntime;\nuse crate::test_utils::test_prelude::LayerNodeIdentifier;\nuse glam::{DVec2, UVec2};\nuse graph_craft::document::DocumentNode;\nuse graphene_std::InputAccessor;\nuse graphene_std::raster::color::Color;\nuse graphene_std::uuid::NodeId;\n\n/// A set of utility functions to make the writing of editor test more declarative\npub struct EditorTestUtils {\n\tpub editor: Editor,\n\tpub runtime: NodeRuntime,\n}\n\nimpl EditorTestUtils {\n\tpub fn create() -> Self {\n\t\tlet _ = env_logger::builder().is_test(true).try_init();\n\n\t\tlet (mut editor, runtime) = Editor::new_local_executor();\n\n\t\teditor.handle_message(PortfolioMessage::Init);\n\n\t\tSelf { editor, runtime }\n\t}\n\n\tpub fn eval_graph<'a>(&'a mut self) -> impl std::future::Future<Output = Result<Instrumented, String>> + 'a {\n\t\t// An inner function is required since async functions in traits are a bit weird\n\t\tasync fn run<'a>(editor: &'a mut Editor, runtime: &'a mut NodeRuntime) -> Result<Instrumented, String> {\n\t\t\tlet portfolio = &mut editor.dispatcher.message_handlers.portfolio_message_handler;\n\t\t\tlet document_id = portfolio.active_document_id.unwrap();\n\t\t\tlet exector = &mut portfolio.executor;\n\t\t\tlet document = portfolio.documents.get_mut(&document_id).unwrap();\n\n\t\t\tlet instrumented = match exector.update_node_graph_instrumented(document) {\n\t\t\t\tOk(instrumented) => instrumented,\n\t\t\t\tErr(e) => return Err(format!(\"update_node_graph_instrumented failed\\n\\n{e}\")),\n\t\t\t};\n\n\t\t\tif let Err(e) = exector.submit_current_node_graph_evaluation(document, document_id, UVec2::ONE, 1., Default::default(), DVec2::ZERO) {\n\t\t\t\treturn Err(format!(\"submit_current_node_graph_evaluation failed\\n\\n{e}\"));\n\t\t\t}\n\t\t\truntime.run().await;\n\n\t\t\tlet mut messages = VecDeque::new();\n\t\t\tif let Err(e) = editor.poll_node_graph_evaluation(&mut messages) {\n\t\t\t\treturn Err(format!(\"Graph should render\\n\\n{e}\"));\n\t\t\t}\n\t\t\tlet frontend_messages = messages.into_iter().flat_map(|message| editor.handle_message(message));\n\n\t\t\tfor message in frontend_messages {\n\t\t\t\tmessage.check_node_graph_error();\n\t\t\t}\n\n\t\t\tOk(instrumented)\n\t\t}\n\n\t\trun(&mut self.editor, &mut self.runtime)\n\t}\n\n\tpub async fn handle_message(&mut self, message: impl Into<Message>) -> Vec<FrontendMessage> {\n\t\tlet frontend_messages_from_msg = self.editor.handle_message(message);\n\n\t\t// Required to process any buffered messages\n\t\tif let Err(e) = self.eval_graph().await {\n\t\t\tpanic!(\"Failed to evaluate graph: {e}\");\n\t\t}\n\n\t\tfrontend_messages_from_msg\n\t}\n\n\tpub async fn new_document(&mut self) {\n\t\tself.handle_message(Message::Portfolio(PortfolioMessage::NewDocumentWithName { name: String::from(\"Test document\") }))\n\t\t\t.await;\n\t}\n\n\tpub async fn draw_rect(&mut self, x1: f64, y1: f64, x2: f64, y2: f64) {\n\t\tself.drag_tool(ToolType::Rectangle, x1, y1, x2, y2, ModifierKeys::default()).await;\n\t}\n\n\tpub async fn draw_polygon(&mut self, x1: f64, y1: f64, x2: f64, y2: f64) {\n\t\tself.drag_tool(ToolType::Shape, x1, y1, x2, y2, ModifierKeys::default()).await;\n\t}\n\n\tpub async fn draw_ellipse(&mut self, x1: f64, y1: f64, x2: f64, y2: f64) {\n\t\tself.drag_tool(ToolType::Ellipse, x1, y1, x2, y2, ModifierKeys::default()).await;\n\t}\n\n\tpub async fn click_tool(&mut self, typ: ToolType, button: MouseKeys, position: DVec2, modifier_keys: ModifierKeys) {\n\t\tself.select_tool(typ).await;\n\n\t\tself.move_mouse(position.x, position.y, modifier_keys, MouseKeys::empty()).await;\n\n\t\tself.mousedown(\n\t\t\tEditorMouseState {\n\t\t\t\teditor_position: position,\n\t\t\t\tmouse_keys: button,\n\t\t\t\t..Default::default()\n\t\t\t},\n\t\t\tmodifier_keys,\n\t\t)\n\t\t.await;\n\n\t\tself.mouseup(\n\t\t\tEditorMouseState {\n\t\t\t\teditor_position: position,\n\t\t\t\t..Default::default()\n\t\t\t},\n\t\t\tmodifier_keys,\n\t\t)\n\t\t.await;\n\t}\n\n\tpub async fn drag_tool(&mut self, typ: ToolType, x1: f64, y1: f64, x2: f64, y2: f64, modifier_keys: ModifierKeys) {\n\t\tself.select_tool(typ).await;\n\n\t\tself.move_mouse(x1, y1, modifier_keys, MouseKeys::empty()).await;\n\n\t\tself.left_mousedown(x1, y1, modifier_keys).await;\n\n\t\tself.move_mouse(x2, y2, modifier_keys, MouseKeys::LEFT).await;\n\n\t\tself.mouseup(\n\t\t\tEditorMouseState {\n\t\t\t\teditor_position: (x2, y2).into(),\n\t\t\t\tmouse_keys: MouseKeys::empty(),\n\t\t\t\tscroll_delta: ScrollDelta::default(),\n\t\t\t},\n\t\t\tmodifier_keys,\n\t\t)\n\t\t.await;\n\t}\n\n\tpub async fn drag_tool_cancel_rmb(&mut self, typ: ToolType) {\n\t\tself.select_tool(typ).await;\n\n\t\tself.move_mouse(50., 50., ModifierKeys::default(), MouseKeys::empty()).await;\n\n\t\tself.left_mousedown(50., 50., ModifierKeys::default()).await;\n\n\t\tself.move_mouse(100., 100., ModifierKeys::default(), MouseKeys::LEFT).await;\n\n\t\tself.mousedown(\n\t\t\tEditorMouseState {\n\t\t\t\teditor_position: (100., 100.).into(),\n\t\t\t\tmouse_keys: MouseKeys::LEFT | MouseKeys::RIGHT,\n\t\t\t\tscroll_delta: ScrollDelta::default(),\n\t\t\t},\n\t\t\tModifierKeys::default(),\n\t\t)\n\t\t.await;\n\t}\n\n\tpub fn active_document(&self) -> &DocumentMessageHandler {\n\t\tself.editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap()\n\t}\n\n\tpub fn active_document_mut(&mut self) -> &mut DocumentMessageHandler {\n\t\tself.editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut().unwrap()\n\t}\n\n\tpub fn get_node<'a, T: InputAccessor<'a, DocumentNode>>(&'a self) -> impl Iterator<Item = T> + 'a {\n\t\tself.active_document()\n\t\t\t.network_interface\n\t\t\t.document_network()\n\t\t\t.recursive_nodes()\n\t\t\t.inspect(|(_, node, _)| println!(\"{:#?}\", node.implementation))\n\t\t\t.filter_map(move |(_, document, _)| T::new_with_source(document))\n\t}\n\n\tpub async fn move_mouse(&mut self, x: f64, y: f64, modifier_keys: ModifierKeys, mouse_keys: MouseKeys) {\n\t\tlet editor_mouse_state = EditorMouseState {\n\t\t\teditor_position: ViewportPosition::new(x, y),\n\t\t\tmouse_keys,\n\t\t\t..Default::default()\n\t\t};\n\t\tself.input(InputPreprocessorMessage::PointerMove { editor_mouse_state, modifier_keys }).await;\n\t}\n\n\tpub async fn mousedown(&mut self, editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys) {\n\t\tself.input(InputPreprocessorMessage::PointerDown { editor_mouse_state, modifier_keys }).await;\n\t}\n\n\tpub async fn mouseup(&mut self, editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys) {\n\t\tself.handle_message(InputPreprocessorMessage::PointerUp { editor_mouse_state, modifier_keys }).await;\n\t}\n\n\tpub async fn press(&mut self, key: Key, modifier_keys: ModifierKeys) {\n\t\tlet key_repeat = false;\n\n\t\tself.handle_message(InputPreprocessorMessage::KeyDown { key, modifier_keys, key_repeat }).await;\n\t\tself.handle_message(InputPreprocessorMessage::KeyUp { key, modifier_keys, key_repeat }).await;\n\t}\n\n\tpub async fn left_mousedown(&mut self, x: f64, y: f64, modifier_keys: ModifierKeys) {\n\t\tself.mousedown(\n\t\t\tEditorMouseState {\n\t\t\t\teditor_position: (x, y).into(),\n\t\t\t\tmouse_keys: MouseKeys::LEFT,\n\t\t\t\tscroll_delta: ScrollDelta::default(),\n\t\t\t},\n\t\t\tmodifier_keys,\n\t\t)\n\t\t.await;\n\t}\n\n\tpub async fn left_mouseup(&mut self, x: f64, y: f64, modifier_keys: ModifierKeys) {\n\t\tself.mouseup(\n\t\t\tEditorMouseState {\n\t\t\t\teditor_position: (x, y).into(),\n\t\t\t\tmouse_keys: MouseKeys::empty(),\n\t\t\t\tscroll_delta: ScrollDelta::default(),\n\t\t\t},\n\t\t\tmodifier_keys,\n\t\t)\n\t\t.await;\n\t}\n\n\tpub async fn input(&mut self, message: InputPreprocessorMessage) {\n\t\tself.handle_message(Message::InputPreprocessor(message)).await;\n\t}\n\n\tpub async fn select_tool(&mut self, tool_type: ToolType) {\n\t\tmatch tool_type {\n\t\t\tToolType::Line => self.handle_message(Message::Tool(ToolMessage::ActivateToolShapeLine)).await,\n\t\t\tToolType::Rectangle => self.handle_message(Message::Tool(ToolMessage::ActivateToolShapeRectangle)).await,\n\t\t\tToolType::Ellipse => self.handle_message(Message::Tool(ToolMessage::ActivateToolShapeEllipse)).await,\n\t\t\t_ => self.handle_message(Message::Tool(ToolMessage::ActivateTool { tool_type })).await,\n\t\t};\n\t}\n\n\tpub async fn select_primary_color(&mut self, color: Color) {\n\t\tself.handle_message(Message::Tool(ToolMessage::SelectWorkingColor { color, primary: true })).await;\n\t}\n\n\tpub async fn select_secondary_color(&mut self, color: Color) {\n\t\tself.handle_message(Message::Tool(ToolMessage::SelectWorkingColor { color, primary: false })).await;\n\t}\n\n\tpub async fn create_raster_image(&mut self, image: graphene_std::raster::Image<Color>, mouse: Option<(f64, f64)>) {\n\t\tself.handle_message(PortfolioMessage::PasteImage {\n\t\t\tname: None,\n\t\t\timage,\n\t\t\tmouse,\n\t\t\tparent_and_insert_index: None,\n\t\t})\n\t\t.await;\n\t}\n\tpub async fn draw_spline(&mut self, points: &[DVec2]) {\n\t\tself.select_tool(ToolType::Spline).await;\n\n\t\tfor &point in points {\n\t\t\tself.click_tool(ToolType::Spline, MouseKeys::LEFT, point, ModifierKeys::empty()).await;\n\t\t}\n\n\t\tself.press(Key::Enter, ModifierKeys::empty()).await;\n\t}\n\n\tpub async fn get_selected_layer(&mut self) -> Option<LayerNodeIdentifier> {\n\t\tself.active_document().network_interface.selected_nodes().selected_layers(self.active_document().metadata()).next()\n\t}\n\n\tpub async fn double_click(&mut self, position: DVec2) {\n\t\tself.handle_message(InputPreprocessorMessage::DoubleClick {\n\t\t\teditor_mouse_state: EditorMouseState {\n\t\t\t\teditor_position: position,\n\t\t\t\tmouse_keys: MouseKeys::LEFT,\n\t\t\t\tscroll_delta: ScrollDelta::default(),\n\t\t\t},\n\t\t\tmodifier_keys: ModifierKeys::empty(),\n\t\t})\n\t\t.await;\n\t}\n\n\tpub async fn drag_path(&mut self, points: &[DVec2], modifier_keys: ModifierKeys) {\n\t\tif points.is_empty() {\n\t\t\treturn;\n\t\t}\n\n\t\tlet first_point = points[0];\n\t\tself.move_mouse(first_point.x, first_point.y, modifier_keys, MouseKeys::empty()).await;\n\t\tself.left_mousedown(first_point.x, first_point.y, modifier_keys).await;\n\n\t\tfor &point in &points[1..] {\n\t\t\tself.move_mouse(point.x, point.y, modifier_keys, MouseKeys::LEFT).await;\n\t\t}\n\n\t\tself.mouseup(\n\t\t\tEditorMouseState {\n\t\t\t\teditor_position: points[points.len() - 1],\n\t\t\t\tmouse_keys: MouseKeys::empty(),\n\t\t\t\tscroll_delta: ScrollDelta::default(),\n\t\t\t},\n\t\t\tmodifier_keys,\n\t\t)\n\t\t.await;\n\t}\n\n\t/// Necessary for doing snapping since snaps outside of the viewport are discarded\n\tpub async fn set_viewport_size(&mut self, top_left: DVec2, bottom_right: DVec2) {\n\t\tself.handle_message(ViewportMessage::Update {\n\t\t\tx: top_left.x,\n\t\t\ty: top_left.y,\n\t\t\twidth: bottom_right.x - top_left.x,\n\t\t\theight: bottom_right.y - top_left.y,\n\t\t\tscale: 1.,\n\t\t})\n\t\t.await;\n\t}\n\n\tpub async fn create_node_by_name(&mut self, node_type: DefinitionIdentifier) -> NodeId {\n\t\tlet node_id = NodeId::new();\n\t\tself.handle_message(NodeGraphMessage::CreateNodeFromContextMenu {\n\t\t\tnode_id: Some(node_id),\n\t\t\tnode_type,\n\t\t\txy: None,\n\t\t\tadd_transaction: true,\n\t\t})\n\t\t.await;\n\t\tnode_id\n\t}\n}\n\npub trait FrontendMessageTestUtils {\n\tfn check_node_graph_error(&self);\n}\n\nimpl FrontendMessageTestUtils for FrontendMessage {\n\tfn check_node_graph_error(&self) {\n\t\tlet FrontendMessage::UpdateNodeGraphErrorDiagnostic { error } = self else { return };\n\t\tif let Some(error) = error {\n\t\t\tpanic!(\"error: {:?}\", error);\n\t\t}\n\t}\n}\n\n#[cfg(test)]\npub mod test_prelude {\n\tpub use super::FrontendMessageTestUtils;\n\tpub use crate::application::Editor;\n\tpub use crate::float_eq;\n\tpub use crate::messages::input_mapper::utility_types::input_keyboard::{Key, ModifierKeys};\n\tpub use crate::messages::input_mapper::utility_types::input_mouse::MouseKeys;\n\tpub use crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;\n\tpub use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;\n\tpub use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\n\tpub use crate::messages::prelude::*;\n\tpub use crate::messages::tool::common_functionality::graph_modification_utils::{NodeGraphLayer, is_layer_fed_by_node_of_name};\n\tpub use crate::messages::tool::utility_types::ToolType;\n\tpub use crate::node_graph_executor::NodeRuntime;\n\tpub use crate::test_utils::EditorTestUtils;\n\tpub use core::f64;\n\tpub use glam::{DVec2, IVec2};\n\tpub use graph_craft::document::DocumentNode;\n\tpub use graphene_std::raster::{Color, Image};\n\tpub use graphene_std::transform::Footprint;\n\tpub use graphene_std::{InputAccessor, InputAccessorSource};\n\n\t#[macro_export]\n\tmacro_rules! float_eq {\n\t\t($left:expr, $right:expr $(,)?) => {\n\t\t\tmatch (&$left, &$right) {\n\t\t\t\t(left_val, right_val) => {\n\t\t\t\t\tif (*left_val - *right_val).abs() > 1e-10 {\n\t\t\t\t\t\tpanic!(\"assertion `left == right` failed\\n  left: {}\\n right: {}\", *left_val, *right_val)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n}\n"
  },
  {
    "path": "editor/src/utility_traits.rs",
    "content": "pub use crate::dispatcher::*;\nuse crate::messages::prelude::*;\n\n/// Implements a message handler struct for a separate message struct.\n/// - The first type argument (`M`) is that message struct type, representing a message enum variant to be matched and handled in `process_message()`.\n/// - The second type argument (`C`) is the type of the context struct that can be passed along by the caller to `process_message()`.\npub trait MessageHandler<M: ToDiscriminant, C>\nwhere\n\tM::Discriminant: AsMessage,\n\t<M::Discriminant as TransitiveChild>::TopParent: TransitiveChild<Parent = <M::Discriminant as TransitiveChild>::TopParent, TopParent = <M::Discriminant as TransitiveChild>::TopParent> + AsMessage,\n{\n\tfn process_message(&mut self, message: M, responses: &mut VecDeque<Message>, context: C);\n\n\tfn actions(&self) -> ActionList;\n}\n\npub type ActionList = Vec<Vec<MessageDiscriminant>>;\n\npub trait AsMessage: TransitiveChild\nwhere\n\tSelf::TopParent: TransitiveChild<Parent = Self::TopParent, TopParent = Self::TopParent> + AsMessage,\n{\n\tfn local_name(self) -> String;\n\tfn global_name(self) -> String {\n\t\t<Self as Into<Self::TopParent>>::into(self).local_name()\n\t}\n}\n\n// TODO: Add Send + Sync requirement\n// Use something like rw locks for synchronization\npub trait MessageHandlerData {}\n\npub trait ToDiscriminant {\n\ttype Discriminant;\n\n\tfn to_discriminant(&self) -> Self::Discriminant;\n}\n\npub trait TransitiveChild: Into<Self::Parent> + Into<Self::TopParent> {\n\ttype TopParent;\n\ttype Parent;\n}\n\npub trait Hint {\n\tfn hints(&self) -> HashMap<String, String>;\n}\n\npub trait HierarchicalTree {\n\tfn build_message_tree() -> DebugMessageTree;\n\n\tfn message_handler_data_str() -> MessageData {\n\t\tMessageData::new(String::new(), Vec::new(), \"\", 0)\n\t}\n\n\tfn message_handler_str() -> MessageData {\n\t\tMessageData::new(String::new(), Vec::new(), \"\", 0)\n\t}\n\n\tfn path() -> &'static str {\n\t\t\"\"\n\t}\n}\n\npub trait ExtractField {\n\tfn field_types() -> Vec<(String, usize)>;\n\tfn path() -> &'static str;\n\tfn line_number() -> usize;\n\tfn print_field_types();\n}\n"
  },
  {
    "path": "editor/src/utility_types.rs",
    "content": "#[derive(Debug)]\npub struct MessageData {\n\tname: String,\n\tfields: Vec<(String, usize)>,\n\tpath: &'static str,\n\tline_number: usize,\n}\n\nimpl MessageData {\n\tpub fn new(name: String, fields: Vec<(String, usize)>, path: &'static str, line_number: usize) -> MessageData {\n\t\tMessageData { name, fields, path, line_number }\n\t}\n\n\tpub fn name(&self) -> &str {\n\t\t&self.name\n\t}\n\n\tpub fn fields(&self) -> &Vec<(String, usize)> {\n\t\t&self.fields\n\t}\n\n\tpub fn path(&self) -> &'static str {\n\t\tself.path\n\t}\n\n\tpub fn line_number(&self) -> usize {\n\t\tself.line_number\n\t}\n}\n\n#[derive(Debug)]\npub struct DebugMessageTree {\n\tname: String,\n\tfields: Option<Vec<String>>,\n\tvariants: Option<Vec<DebugMessageTree>>,\n\tmessage_handler: Option<MessageData>,\n\tmessage_handler_data: Option<MessageData>,\n\tpath: &'static str,\n\tline_number: usize,\n}\n\nimpl DebugMessageTree {\n\tpub fn new(name: &str) -> DebugMessageTree {\n\t\tDebugMessageTree {\n\t\t\tname: name.to_string(),\n\t\t\tfields: None,\n\t\t\tvariants: None,\n\t\t\tmessage_handler: None,\n\t\t\tmessage_handler_data: None,\n\t\t\tpath: \"\",\n\t\t\tline_number: 0,\n\t\t}\n\t}\n\n\tpub fn add_fields(&mut self, fields: Vec<String>) {\n\t\tself.fields = Some(fields);\n\t}\n\n\tpub fn set_path(&mut self, path: &'static str) {\n\t\tself.path = path;\n\t}\n\n\tpub fn set_line_number(&mut self, line_number: usize) {\n\t\tself.line_number = line_number\n\t}\n\n\tpub fn add_variant(&mut self, variant: DebugMessageTree) {\n\t\tif let Some(variants) = &mut self.variants {\n\t\t\tvariants.push(variant);\n\t\t} else {\n\t\t\tself.variants = Some(vec![variant]);\n\t\t}\n\t}\n\n\tpub fn add_message_handler_data_field(&mut self, message_handler_data: MessageData) {\n\t\tself.message_handler_data = Some(message_handler_data);\n\t}\n\n\tpub fn add_message_handler_field(&mut self, message_handler: MessageData) {\n\t\tself.message_handler = Some(message_handler);\n\t}\n\n\tpub fn name(&self) -> &str {\n\t\t&self.name\n\t}\n\n\tpub fn fields(&self) -> Option<&Vec<String>> {\n\t\tself.fields.as_ref()\n\t}\n\n\tpub fn path(&self) -> &'static str {\n\t\tself.path\n\t}\n\n\tpub fn line_number(&self) -> usize {\n\t\tself.line_number\n\t}\n\n\tpub fn variants(&self) -> Option<&Vec<DebugMessageTree>> {\n\t\tself.variants.as_ref()\n\t}\n\n\tpub fn message_handler_data_fields(&self) -> Option<&MessageData> {\n\t\tself.message_handler_data.as_ref()\n\t}\n\n\tpub fn message_handler_fields(&self) -> Option<&MessageData> {\n\t\tself.message_handler.as_ref()\n\t}\n}\n"
  },
  {
    "path": "flake.nix",
    "content": "# This is a helper file for people using NixOS as their operating system.\n# If you don't know what this file does, you can safely ignore it.\n# This file defines the reproducible development environment for the project.\n#\n# Development Environment:\n# - Provides all necessary tools for Rust/Wasm development\n# - Includes dependencies for desktop app development\n# - Sets up profiling and debugging tools\n# - Configures mold as the default linker for faster builds\n#\n# Usage:\n# - Development shell: `nix develop` from the project root\n# - Run in dev shell with direnv: add `use flake` to .envrc\n{\n  inputs = {\n    nixpkgs.url = \"github:nixos/nixpkgs/nixos-unstable\";\n    rust-overlay = {\n      url = \"github:oxalica/rust-overlay\";\n      inputs.nixpkgs.follows = \"nixpkgs\";\n    };\n    crane.url = \"github:ipetkov/crane\";\n  };\n\n  outputs = inputs: import ./.nix inputs;\n}\n"
  },
  {
    "path": "frontend/.gitignore",
    "content": "node_modules/\nwasm/pkg/\npublic/build/\ndist/\n"
  },
  {
    "path": "frontend/.prettierrc",
    "content": "{\n\t\"singleQuote\": false,\n\t\"useTabs\": true,\n\t\"tabWidth\": 4,\n\t\"printWidth\": 200,\n\t\"plugins\": [\n\t\t\"prettier-plugin-svelte\"\n\t],\n\t\"overrides\": [\n\t\t{\n\t\t\t\"files\": [\n\t\t\t\t\"*.svelte\"\n\t\t\t],\n\t\t\t\"options\": {\n\t\t\t\t\"parser\": \"svelte\"\n\t\t\t}\n\t\t}\n\t]\n}\n"
  },
  {
    "path": "frontend/README.md",
    "content": "# Overview of `/frontend/`\n\nThe Graphite frontend is a web app that provides the presentation for the editor. It displays the GUI based on state from the backend and provides users with interactive widgets that send updates to the backend, which is the source of truth for state information. The frontend is built out of reactive components using the [Svelte](https://svelte.dev/) framework. The backend is written in Rust and compiled to WebAssembly (Wasm) to be run in the browser alongside the JS code.\n\n## Bundled assets: `assets/`\n\nImages that are used in components and embedded into the application bundle by the build system.\n\n## Svelte/TypeScript source: `src/`\n\nSource code for the web app in the form of Svelte components and [TypeScript](https://www.typescriptlang.org/) files.\n\n## WebAssembly wrapper: `wasm/`\n\nWraps the editor backend codebase (`/editor`) and provides a JS-centric API for the web app to use as an entry point, unburdened by Rust's complex data types that are incompatible with JS data types. Bindings (JS functions that call into the Wasm module) are provided by [wasm-bindgen](https://rustwasm.github.io/docs/wasm-bindgen/) in concert with [wasm-pack](https://github.com/rustwasm/wasm-pack).\n\n## ESLint configuration: `eslint.config.js`\n\nWhen you use `npm run check`, [ESLint](https://eslint.org/) checks the code in the frontend project for code quality. (The command also reports TS and Svelte errors.) The tool enforces style rules on the JS, TS, and Svelte (including its HTML and SCSS) files in our frontend codebase. As it is set up in this config file, ESLint will complain about bad practices and often help reformat code automatically when the file is saved in VS Code, or manually when `npm run fix` is executed. (If you don't use VS Code, remember to run this command before committing!) This config file for ESLint sets our style preferences and configures our usage of extensions/plugins for Svelte support and [Prettier](https://prettier.io/)'s role as a code formatter.\n\n## Svelte configuration: `svelte.config.js`\n\nConfigures the Svelte compiler, including the preprocessor setup for SCSS and TypeScript support, and compiler warning filters.\n\n## TypeScript configuration: `tsconfig.json`\n\nBasic configuration options for the TypeScript build tool to do its job in our repository.\n\n## Vite configuration: `vite.config.ts`\n\nWe use the [Vite](https://vitejs.dev/) bundler/build system. This file is where we configure Vite to set up plugins (like the third-party license checker/generator). Part of the license checker plugin setup includes some functions to format web package licenses, as well as Rust package licenses provided by [cargo-about](https://github.com/EmbarkStudios/cargo-about), into a text file that's distributed with the application to provide license notices for third-party code.\n\n## npm ecosystem packages: `package.json`\n\nWhile we don't use Node.js as a JS-based server, we do rely on its ecosystem of packages for our build system toolchain. Our project's philosophy on third-party packages is to keep our dependency tree as light as possible, so adding anything new to our `package.json` should have overwhelming justification. Most of the packages are just development tooling (TypeScript, Vite, ESLint, Prettier, Sass, etc.) that run in your terminal during the build process.\n\n## npm package installed versions: `package-lock.json`\n\nSpecifies the exact versions of packages installed in the npm dependency tree. While `package.json` specifies which packages to install and their minimum/maximum acceptable version numbers, `package-lock.json` represents the exact versions of each dependency and sub-dependency. Running `npm ci` will grab these exact versions to ensure you are using the same packages as everyone else working on Graphite. `npm update` will modify `package-lock.json` to specify newer versions of any updated (sub-)dependencies and download those, as long as they don't exceed the maximum version allowed in `package.json`. To check for newer versions that exceed the max version, run `npm outdated` to see a list. Unless you know why you are doing it, try to avoid committing updates to `package-lock.json` by mistake if your code changes don't pertain to package updates. And never manually modify the file.\n\n"
  },
  {
    "path": "frontend/branding-installer.js",
    "content": "/* eslint-disable no-console */\n\nimport crypto from \"crypto\";\nimport fs from \"fs\";\nimport http from \"http\";\nimport https from \"https\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\nimport zlib from \"zlib\";\n\nimport * as tar from \"tar\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nconst BRANDING_INFO_FILE = path.join(__dirname, \"../.branding\");\nconst BRANDING_DIR = path.join(__dirname, \"../branding\");\nconst INSTALLED_BRANDING_INFO_FILE = path.join(BRANDING_DIR, \".branding\");\nconst TEMP_FILE = path.join(__dirname, \"branding_download.tar.gz\");\n\nfunction downloadFile(url, dest) {\n\treturn new Promise((resolve, reject) => {\n\t\tconst file = fs.createWriteStream(dest);\n\t\tconst protocol = url.startsWith(\"https\") ? https : http;\n\n\t\tconst request = protocol.get(url, (response) => {\n\t\t\tif (response.statusCode === 301 || response.statusCode === 302 || response.statusCode === 307) {\n\t\t\t\tfile.close();\n\t\t\t\tfs.unlink(dest, () => {});\n\t\t\t\tif (response.headers.location) {\n\t\t\t\t\tdownloadFile(response.headers.location, dest).then(resolve).catch(reject);\n\t\t\t\t} else {\n\t\t\t\t\treject(new Error(\"Redirect location missing\"));\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (response.statusCode !== 200) {\n\t\t\t\tfile.close();\n\t\t\t\tfs.unlink(dest, () => {});\n\t\t\t\treject(new Error(`Failed to download: ${response.statusCode}`));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tresponse.pipe(file);\n\t\t\tfile.on(\"finish\", () => {\n\t\t\t\tfile.close(resolve);\n\t\t\t});\n\t\t});\n\n\t\trequest.on(\"error\", (err) => {\n\t\t\tfs.unlink(dest, () => {});\n\t\t\treject(err);\n\t\t});\n\t});\n}\n\nasync function main() {\n\tif (!fs.existsSync(BRANDING_INFO_FILE)) {\n\t\tconsole.error(`Branding info file not found at ${BRANDING_INFO_FILE}`);\n\t\tprocess.exit(1);\n\t}\n\n\tconst content = fs.readFileSync(BRANDING_INFO_FILE, \"utf8\");\n\n\tif (fs.existsSync(INSTALLED_BRANDING_INFO_FILE)) {\n\t\tconst installedContent = fs.readFileSync(INSTALLED_BRANDING_INFO_FILE, \"utf8\");\n\t\tif (content === installedContent) {\n\t\t\tconsole.log(\"Branding assets are up to date.\");\n\t\t\treturn;\n\t\t}\n\t}\n\n\tconst lines = content\n\t\t.split(\"\\n\")\n\t\t.map((l) => l.trim())\n\t\t.filter((l) => l.length > 0);\n\n\tif (lines.length < 2) {\n\t\tconsole.error(\"Branding file must contain at least two lines: URL and Hash\");\n\t\tprocess.exit(1);\n\t}\n\n\tconst url = lines[0];\n\tconst expectedHash = lines[1];\n\n\tconsole.log(`Downloading branding assets from <${url}>...`);\n\n\ttry {\n\t\tawait downloadFile(url, TEMP_FILE);\n\t} catch (err) {\n\t\tconsole.error(\"Download failed:\", err);\n\t\tprocess.exit(1);\n\t}\n\n\tconsole.log(\"Download complete. Verifying hash...\");\n\n\tconst fileBuffer = fs.readFileSync(TEMP_FILE);\n\tconst hashSum = crypto.createHash(\"sha256\");\n\thashSum.update(fileBuffer);\n\tconst hex = hashSum.digest(\"hex\");\n\n\tif (hex !== expectedHash) {\n\t\tconsole.error(\"Hash mismatch!\");\n\t\tconsole.error(`Expected: ${expectedHash}`);\n\t\tconsole.error(`Actual:   ${hex}`);\n\t\tif (fs.existsSync(TEMP_FILE)) fs.unlinkSync(TEMP_FILE);\n\t\tprocess.exit(1);\n\t}\n\n\tconsole.log(\"Hash verified. Extracting...\");\n\n\tif (fs.existsSync(BRANDING_DIR)) {\n\t\tfs.rmSync(BRANDING_DIR, { recursive: true, force: true });\n\t}\n\tfs.mkdirSync(BRANDING_DIR, { recursive: true });\n\n\ttry {\n\t\t// Extract the tar.gz file\n\t\tawait new Promise((resolve, reject) => {\n\t\t\tfs.createReadStream(TEMP_FILE)\n\t\t\t\t.pipe(zlib.createGunzip())\n\t\t\t\t.pipe(\n\t\t\t\t\ttar.x({\n\t\t\t\t\t\tcwd: BRANDING_DIR,\n\t\t\t\t\t\tstrip: 1,\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t\t.on(\"error\", reject)\n\t\t\t\t.on(\"finish\", resolve);\n\t\t});\n\t\tfs.copyFileSync(BRANDING_INFO_FILE, INSTALLED_BRANDING_INFO_FILE);\n\t\tconsole.log(\"Extraction complete.\");\n\t} catch (error) {\n\t\tconsole.error(\"Failed to extract archive:\", error);\n\t} finally {\n\t\tif (fs.existsSync(TEMP_FILE)) {\n\t\t\tfs.unlinkSync(TEMP_FILE);\n\t\t}\n\t}\n}\n\nmain()\n\t.then(() => process.exit(0))\n\t.catch((err) => {\n\t\tconsole.error(\"An error occurred:\", err);\n\t\tprocess.exit(1);\n\t});\n"
  },
  {
    "path": "frontend/eslint.config.js",
    "content": "import js from \"@eslint/js\";\nimport { defineConfig, globalIgnores } from \"eslint/config\";\nimport * as pluginImport from \"eslint-plugin-import\";\nimport pluginPrettier from \"eslint-plugin-prettier\";\nimport pluginSvelte from \"eslint-plugin-svelte\";\nimport globals from \"globals\";\nimport ts from \"typescript-eslint\";\n\nexport default defineConfig([\n\tjs.configs.recommended,\n\tts.configs.recommended,\n\tpluginImport.flatConfigs.recommended,\n\tpluginImport.flatConfigs.typescript,\n\tpluginSvelte.configs[\"flat/recommended\"],\n\tpluginSvelte.configs[\"flat/prettier\"],\n\tglobalIgnores([\n\t\t// Ignore generated directories\n\t\t\"**/node_modules/\",\n\t\t\"**/dist/\",\n\t\t\"**/pkg/\",\n\t\t\"wasm/pkg/\",\n\t\t// Don't ignore JS and TS dotfiles in this folder\n\t\t\"!**/.*.js\",\n\t\t\"!**/.*.ts\",\n\t]),\n\t{\n\t\tplugins: {\n\t\t\tprettier: pluginPrettier,\n\t\t},\n\t\tsettings: {\n\t\t\t\"import/parsers\": { \"@typescript-eslint/parser\": [\".ts\"] },\n\t\t\t\"import/resolver\": { typescript: true, node: true },\n\t\t},\n\t\tlanguageOptions: {\n\t\t\tglobals: {\n\t\t\t\t...globals.browser,\n\t\t\t\t...globals.node,\n\t\t\t},\n\t\t},\n\t\trules: {\n\t\t\t// Standard ESLint config (for ordinary JS syntax linting)\n\t\t\tindent: \"off\",\n\t\t\tquotes: [\"error\", \"double\", { allowTemplateLiterals: true }],\n\t\t\tcamelcase: [\"error\", { properties: \"always\" }],\n\t\t\t\"linebreak-style\": [\"error\", \"unix\"],\n\t\t\t\"eol-last\": [\"error\", \"always\"],\n\t\t\t\"max-len\": [\"error\", { code: 200, tabWidth: 4, ignorePattern: `d=\"([\\\\s\\\\S]*?)\"` }],\n\t\t\t\"prefer-destructuring\": \"off\",\n\t\t\t\"no-console\": \"warn\",\n\t\t\t\"no-debugger\": \"warn\",\n\t\t\t\"no-param-reassign\": [\"error\", { props: false }],\n\t\t\t\"no-bitwise\": \"off\",\n\t\t\t\"no-shadow\": \"off\",\n\t\t\t\"no-use-before-define\": \"off\",\n\t\t\t\"no-restricted-imports\": [\"error\", { patterns: [\".*\", \"!@graphite/*\"] }],\n\n\t\t\t// TypeScript plugin config (for TS-specific linting)\n\t\t\t\"@typescript-eslint/indent\": \"off\",\n\t\t\t\"@typescript-eslint/camelcase\": \"off\",\n\t\t\t\"@typescript-eslint/no-use-before-define\": \"off\",\n\t\t\t\"@typescript-eslint/no-unused-vars\": [\n\t\t\t\t\"error\",\n\t\t\t\t{\n\t\t\t\t\targs: \"all\",\n\t\t\t\t\targsIgnorePattern: \"^_\",\n\t\t\t\t\tcaughtErrors: \"all\",\n\t\t\t\t\tcaughtErrorsIgnorePattern: \"^_\",\n\t\t\t\t\tdestructuredArrayIgnorePattern: \"^_\",\n\t\t\t\t\tvarsIgnorePattern: \"^_\",\n\t\t\t\t\tignoreRestSiblings: true,\n\t\t\t\t},\n\t\t\t],\n\t\t\t\"@typescript-eslint/no-non-null-assertion\": \"error\",\n\t\t\t\"@typescript-eslint/consistent-type-imports\": \"error\",\n\t\t\t\"@typescript-eslint/consistent-type-definitions\": [\"error\", \"type\"],\n\t\t\t\"@typescript-eslint/consistent-type-assertions\": [\"error\", { assertionStyle: \"never\" }],\n\t\t\t\"@typescript-eslint/consistent-indexed-object-style\": [\"error\", \"record\"],\n\t\t\t\"@typescript-eslint/consistent-generic-constructors\": [\"error\", \"constructor\"],\n\t\t\t\"@typescript-eslint/no-restricted-types\": [\"error\", { types: { null: \"Use `undefined` instead.\" } }],\n\n\t\t\t// Prettier plugin config (for validating and fixing formatting)\n\t\t\t\"prettier/prettier\": \"error\",\n\n\t\t\t// Svelte plugin config (for validating Svelte-specific syntax)\n\t\t\t\"svelte/no-at-html-tags\": \"off\",\n\t\t\t\"svelte/no-useless-mustaches\": \"off\",\n\t\t\t\"svelte/valid-compile\": [\"error\", { ignoreWarnings: true }],\n\t\t\t\"svelte/require-each-key\": \"off\", // TODO: Remove this rule and fix the places where it's violated\n\n\t\t\t// Import plugin config (for intelligently validating module import statements)\n\t\t\t\"import/consistent-type-specifier-style\": [\"error\", \"prefer-top-level\"],\n\t\t\t\"import/no-unresolved\": \"error\",\n\t\t\t// `no-duplicates` disabled due to <https://github.com/import-js/eslint-plugin-import/issues/1479#issuecomment-1789527447>. Reenable if that issue gets fixed.\n\t\t\t\"import/no-duplicates\": \"off\",\n\t\t\t\"import/prefer-default-export\": \"off\",\n\t\t\t\"import/no-relative-packages\": \"error\",\n\t\t\t\"import/no-named-as-default-member\": \"off\",\n\t\t\t\"import/order\": [\n\t\t\t\t\"error\",\n\t\t\t\t{\n\t\t\t\t\talphabetize: { order: \"asc\", caseInsensitive: true },\n\t\t\t\t\tpathGroups: [{ pattern: \"**/*.svelte\", group: \"unknown\", position: \"after\" }],\n\t\t\t\t\t\"newlines-between\": \"always-and-inside-groups\",\n\t\t\t\t\twarnOnUnassignedImports: true,\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t},\n\t{\n\t\tfiles: [\"**/*.svelte\"],\n\t\tlanguageOptions: {\n\t\t\t// Parse the `<script>` in `.svelte` as TypeScript by adding the following configuration.\n\t\t\tparserOptions: {\n\t\t\t\tprojectService: true,\n\t\t\t\tparser: ts.parser,\n\t\t\t\textraFileExtensions: [\".svelte\"],\n\t\t\t},\n\t\t},\n\t},\n\t{\n\t\tfiles: [\"**/*.js\"],\n\t\textends: [ts.configs.disableTypeChecked],\n\t},\n]);\n"
  },
  {
    "path": "frontend/index.html",
    "content": "<!doctype html>\n<html>\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<title>Graphite</title>\n\t\t<link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"apple-touch-icon.png\" />\n\t\t<link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"favicon-32x32.png\" />\n\t\t<link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"favicon-16x16.png\" />\n\t\t<link rel=\"manifest\" href=\"site.webmanifest\" />\n\t\t<link rel=\"mask-icon\" href=\"safari-pinned-tab.svg\" color=\"#473a3a\" />\n\t\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5\" />\n\t\t<meta name=\"apple-mobile-web-app-title\" content=\"Graphite\" />\n\t\t<meta name=\"application-name\" content=\"Graphite\" />\n\t\t<meta name=\"msapplication-TileColor\" content=\"#ffffff\" />\n\t\t<meta name=\"theme-color\" content=\"#ffffff\" />\n\t\t<meta name=\"color-scheme\" content=\"dark only\" />\n\t\t<meta name=\"darkreader-lock\" />\n\t\t<!-- INDEX_HTML_HEAD_REPLACEMENT -->\n\t</head>\n\t<body>\n\t\t<noscript>JavaScript is required</noscript>\n\t\t<style>\n\t\t\tbody {\n\t\t\t\theight: 100%;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\tbody::before {\n\t\t\t\tcontent: \"\";\n\t\t\t\tposition: absolute;\n\t\t\t\tinset: 0;\n\t\t\t\tbackground: #222;\n\t\t\t}\n\n\t\t\tbody::after {\n\t\t\t\tcontent: \"\";\n\t\t\t\tdisplay: block;\n\t\t\t\tposition: absolute;\n\t\t\t\tleft: 50%;\n\t\t\t\ttop: 50%;\n\t\t\t\twidth: 60px;\n\t\t\t\theight: 60px;\n\t\t\t\tborder-radius: 50%;\n\t\t\t\tborder: 4px solid #eee;\n\t\t\t\tborder-color: #eee transparent #eee transparent;\n\t\t\t\tanimation: spinning-loading-indicator 1s linear infinite;\n\n\t\t\t\t@media (prefers-reduced-motion) {\n\t\t\t\t\tborder: none;\n\t\t\t\t\tanimation: none;\n\t\t\t\t\tcontent: \"Loading…\";\n\t\t\t\t\tfont-family: Arial, sans-serif;\n\t\t\t\t\tfont-size: 24px;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t@keyframes spinning-loading-indicator {\n\t\t\t\t0% {\n\t\t\t\t\ttransform: translate(-30px, -30px) rotate(0deg);\n\t\t\t\t}\n\t\t\t\t100% {\n\t\t\t\t\ttransform: translate(-30px, -30px) rotate(360deg);\n\t\t\t\t}\n\t\t\t}\n\t\t</style>\n\t\t<script type=\"module\" src=\"src/main.ts\"></script>\n\t</body>\n</html>\n"
  },
  {
    "path": "frontend/package-installer.js",
    "content": "// This script automatically installs the npm packages listed in package-lock.json and runs before `npm start`.\n// It skips the installation if this has already run and neither package.json nor package-lock.json has been modified since.\n\nimport { execSync } from \"child_process\";\nimport { existsSync, statSync, writeFileSync } from \"fs\";\n\nconst INSTALL_TIMESTAMP_FILE = \"node_modules/.install-timestamp\";\n\n// Checks if the install is needed by comparing modification times\nconst isInstallNeeded = () => {\n\tif (!existsSync(INSTALL_TIMESTAMP_FILE)) return true;\n\n\tconst timestamp = statSync(INSTALL_TIMESTAMP_FILE).mtime;\n\treturn [\"package.json\", \"package-lock.json\"].some((file) => {\n\t\treturn existsSync(file) && statSync(file).mtime > timestamp;\n\t});\n};\n\n// Run `npm ci` if needed and update the install timestamp\nif (isInstallNeeded()) {\n\ttry {\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.log(\"Installing npm packages...\");\n\n\t\t// Check if packages are up to date, doing so quickly by using `npm ci`, preferring local cached packages, and skipping the package audit and other checks\n\t\texecSync(\"npm ci --prefer-offline --no-audit --no-fund\", { stdio: \"inherit\" });\n\n\t\t// Touch the install timestamp file\n\t\twriteFileSync(INSTALL_TIMESTAMP_FILE, \"\");\n\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.log(\"Finished installing npm packages.\");\n\t} catch (_) {\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.error(\n\t\t\t\"\\n\\n\" +\n\t\t\t\t\"------------------------------------------------------------> \" +\n\t\t\t\t\"Failed to install npm packages. Please delete the `node_modules` folder and run `npm install` from the `/frontend` directory.\",\n\t\t);\n\t\tprocess.exit(1);\n\t}\n} else {\n\t// eslint-disable-next-line no-console\n\tconsole.log(\"All npm packages are up-to-date.\");\n}\n"
  },
  {
    "path": "frontend/package.json",
    "content": "{\n\t\"name\": \"graphite-web-frontend\",\n\t\"private\": true,\n\t\"description\": \"Graphite's web app frontend.\",\n\t\"author\": \"Graphite Authors <contact@graphite.art>\",\n\t\"browserslist\": \"> 1.5%, last 2 versions, not dead, not ie 11, not op_mini all, not ios_saf < 13\",\n\t\"type\": \"module\",\n\t\"scripts\": {\n\t\t\"---------- DEV SERVER ----------\": \"\",\n\t\t\"start\": \"npm run setup && npm run wasm:build-dev && concurrently -k -n \\\"VITE,RUST\\\" \\\"vite\\\" \\\"npm run wasm:watch-dev\\\"\",\n\t\t\"production\": \"npm run setup && npm run wasm:build-production && concurrently -k -n \\\"VITE,RUST\\\" \\\"vite\\\" \\\"npm run wasm:watch-production\\\"\",\n\t\t\"---------- BUILDS ----------\": \"\",\n\t\t\"build\": \"npm run setup && npm run wasm:build-production && vite build\",\n\t\t\"build-dev\": \"npm run setup && npm run wasm:build-dev && vite build --mode dev\",\n\t\t\"build-native\": \"npm run setup && npm run native:build-production\",\n\t\t\"build-native-dev\": \"npm run setup && npm run native:build-dev\",\n\t\t\"---------- UTILITIES ----------\": \"\",\n\t\t\"check\": \"svelte-check --fail-on-warnings && eslint\",\n\t\t\"fix\": \"eslint --fix\",\n\t\t\"---------- INTERNAL ----------\": \"\",\n\t\t\"setup\": \"node package-installer.js && node branding-installer.js\",\n\t\t\"native:build-dev\": \"wasm-pack build ./wasm --dev --target=web --no-default-features --features native && vite build --mode native\",\n\t\t\"native:build-production\": \"wasm-pack build ./wasm --release --target=web --no-default-features --features native && vite build --mode native\",\n\t\t\"wasm:build-dev\": \"wasm-pack build ./wasm --dev --target=web\",\n\t\t\"wasm:build-production\": \"wasm-pack build ./wasm --release --target=web\",\n\t\t\"wasm:watch-dev\": \"cargo watch --postpone --watch-when-idle --workdir=wasm --shell \\\"wasm-pack build . --dev --target=web -- --color=always\\\"\",\n\t\t\"wasm:watch-production\": \"cargo watch --postpone --watch-when-idle --workdir=wasm --shell \\\"wasm-pack build . --release --target=web -- --color=always\\\"\"\n\t},\n\t\"//\": \"NOTE: `source-sans-pro` is never to be upgraded to 3.x because that renders a pixel above its intended position.\",\n\t\"dependencies\": {\n\t\t\"idb-keyval\": \"^6.2.2\",\n\t\t\"source-code-pro\": \"2.42.0\",\n\t\t\"source-sans-pro\": \"2.45.0\"\n\t},\n\t\"devDependencies\": {\n\t\t\"@eslint/compat\": \"^2.0.1\",\n\t\t\"@eslint/eslintrc\": \"^3.3.3\",\n\t\t\"@eslint/js\": \"^9.39.2\",\n\t\t\"@sveltejs/vite-plugin-svelte\": \"^6.2.4\",\n\t\t\"@types/node\": \"^25.0.9\",\n\t\t\"buffer\": \"^6.0.3\",\n\t\t\"concurrently\": \"^9.2.1\",\n\t\t\"eslint-import-resolver-typescript\": \"^4.4.4\",\n\t\t\"eslint-plugin-import\": \"^2.32.0\",\n\t\t\"eslint-plugin-prettier\": \"^5.5.5\",\n\t\t\"eslint-plugin-svelte\": \"^3.14.0\",\n\t\t\"globals\": \"^17.0.0\",\n\t\t\"svelte-check\": \"^4.4.4\",\n\t\t\"license-checker-rseidelsohn\": \"^4.4.2\",\n\t\t\"postcss\": \"^8.5.6\",\n\t\t\"prettier\": \"^3.8.0\",\n\t\t\"prettier-plugin-svelte\": \"^3.4.1\",\n\t\t\"process\": \"^0.11.10\",\n\t\t\"sass\": \"^1.97.2\",\n\t\t\"svelte\": \"5.47.1\",\n\t\t\"svelte-preprocess\": \"^6.0.3\",\n\t\t\"tar\": \"^7.5.4\",\n\t\t\"ts-node\": \"^10.9.2\",\n\t\t\"typescript\": \"^5.9.3\",\n\t\t\"typescript-eslint\": \"^8.53.1\",\n\t\t\"vite\": \"^7.3.1\",\n\t\t\"vite-multiple-assets\": \"2.2.6\"\n\t},\n\t\"homepage\": \"https://graphite.art\",\n\t\"license\": \"Apache-2.0\",\n\t\"repository\": {\n\t\t\"type\": \"git\",\n\t\t\"url\": \"git+https://github.com/GraphiteEditor/Graphite.git\"\n\t}\n}\n"
  },
  {
    "path": "frontend/src/App.svelte",
    "content": "<script lang=\"ts\">\n\timport { onMount, onDestroy } from \"svelte\";\n\n\timport { initWasm, createEditor } from \"@graphite/editor\";\n\timport type { Editor as GraphiteEditor } from \"@graphite/editor\";\n\n\timport Editor from \"@graphite/components/Editor.svelte\";\n\n\tlet editor: GraphiteEditor | undefined = undefined;\n\n\tonMount(async () => {\n\t\tawait initWasm();\n\n\t\teditor = createEditor();\n\t});\n\n\tonDestroy(() => {\n\t\teditor?.destroy();\n\t});\n</script>\n\n{#if editor !== undefined}\n\t<Editor {editor} />\n{/if}\n"
  },
  {
    "path": "frontend/src/README.md",
    "content": "# Overview of `/frontend/src/`\n\n## Svelte components: `components/`\n\nSvelte components that build the Graphite editor GUI. These each contain a TypeScript section, a Svelte-templated HTML template section, and an SCSS stylesheet section. The aim is to avoid implementing much editor business logic here, just enough to make things interactive and communicate to the backend where the real business logic should occur.\n\n## Managers: `managers/`\n\nTypeScript files which manage the input/output of browser APIs and link this functionality with the editor backend. These files subscribe to backend messages to execute JS APIs, and in response to these APIs or user interactions, they may call functions into the backend (defined in `/frontend/wasm/editor_api.rs`).\n\nEach manager module exports a factory function (e.g. `createClipboardManager(editor)`) that sets up message subscriptions and returns a `{ destroy }` object. In `Editor.svelte`, each manager is created at startup and its `destroy()` method is called on unmount to clean up subscriptions and side-effects (e.g. event listeners). Managers use self-accepting HMR to tear down and re-create with updated code during development.\n\n## Stores: `stores/`\n\nTypeScript files which provide reactive state to Svelte components. Each module persists a Svelte writable store at module level (surviving HMR via `import.meta.hot.data`) and exports a factory function (e.g. `createDialogStore(editor)`) that sets up backend message subscriptions and returns an object containing the store's `subscribe` method, any action methods for components to call, and a `destroy` method.\n\nIn `Editor.svelte`, each store is created and passed to Svelte's `setContext()`. Components access stores via `getContext<DialogStore>(\"dialog\")` and use the `subscribe` method for reactive state and action methods (like `createCrashDialog()`) to trigger state changes.\n\n## *Managers vs. stores*\n\n*Both managers and stores subscribe to backend messages and may interact with browser APIs. The difference is that stores expose reactive state to components via `setContext()`/`getContext()`, while managers are self-contained systems that operate for the lifetime of the application and aren't accessed by Svelte components.*\n\n## Utility functions: `utility-functions/`\n\nTypeScript files which define and `export` individual helper functions for use elsewhere in the codebase. These files should not persist state outside each function.\n\n## Wasm editor: `editor.ts`\n\nInstantiates the Wasm and editor backend instances. The function `initWasm()` asynchronously constructs and initializes an instance of the Wasm bindings JS module provided by wasm-bindgen/wasm-pack. The function `createEditor()` constructs an instance of the editor backend. In theory there could be multiple editor instances sharing the same Wasm module instance. The function returns an object where `raw` is the Wasm memory, `handle` provides access to callable backend functions, and `subscriptions` is the subscription router (described below).\n\n`initWasm()` occurs in `main.ts` right before the Svelte application is mounted, then `createEditor()` is run in `Editor.svelte` during the Svelte app's creation. Similarly to the stores described above, the editor is given via `setContext()` so other components can get it via `getContext` and call functions on `editor.handle` or `editor.subscriptions`.\n\n## Subscription router: `subscription-router.ts`\n\nAssociates messages from the backend with subscribers in the frontend, and routes messages to subscriber callbacks. This module provides a `subscribeFrontendMessage(messageType, callback)` function which JS code throughout the frontend can call to be registered as the exclusive handler for a chosen message type. The router's other function, `handleFrontendMessage(messageType, messageData)`, is called via the callback passed to `EditorHandle.create()` in `editor.ts` when the backend sends a `FrontendMessage`. When this occurs, the subscription router delivers the message to the subscriber by executing its registered `callback` function.\n\n## Svelte app entry point: `App.svelte`\n\nThe entry point for the Svelte application.\n\n## Editor base instance: `Editor.svelte`\n\nThis is where we define global CSS style rules, construct all stores and managers with the editor instance, set store contexts for component access, and clean up all `destroy()` methods on unmount.\n\n## Global type augmentations: `global.d.ts`\n\nExtends built-in browser type definitions using TypeScript's interface merging. This includes Graphite's custom properties on the `window` object, custom events like `pointerlockmove`, and experimental browser APIs not yet in TypeScript's standard library. New custom events or non-standard browser APIs used by the frontend should be declared here.\n\n## JS bundle entry point: `main.ts`\n\nThe entry point for the entire project's code bundle. Here we simply mount the Svelte application with `export default mount(App, { target: document.body });`.\n"
  },
  {
    "path": "frontend/src/components/Editor.svelte",
    "content": "<script lang=\"ts\">\n\timport { onMount, onDestroy, setContext } from \"svelte\";\n\n\timport type { Editor } from \"@graphite/editor\";\n\timport { createClipboardManager, destroyClipboardManager } from \"@graphite/managers/clipboard\";\n\timport { createFontsManager, destroyFontsManager } from \"@graphite/managers/fonts\";\n\timport { createHyperlinkManager, destroyHyperlinkManager } from \"@graphite/managers/hyperlink\";\n\timport { createInputManager, destroyInputManager } from \"@graphite/managers/input\";\n\timport { createLocalizationManager, destroyLocalizationManager } from \"@graphite/managers/localization\";\n\timport { createPanicManager, destroyPanicManager } from \"@graphite/managers/panic\";\n\timport { createPersistenceManager, destroyPersistenceManager } from \"@graphite/managers/persistence\";\n\timport { createAppWindowStore, destroyAppWindowStore } from \"@graphite/stores/app-window\";\n\timport { createDialogStore, destroyDialogStore } from \"@graphite/stores/dialog\";\n\timport { createDocumentStore, destroyDocumentStore } from \"@graphite/stores/document\";\n\timport { createFullscreenStore, destroyFullscreenStore } from \"@graphite/stores/fullscreen\";\n\timport { createNodeGraphStore, destroyNodeGraphStore } from \"@graphite/stores/node-graph\";\n\timport { createPortfolioStore, destroyPortfolioStore } from \"@graphite/stores/portfolio\";\n\timport { createTooltipStore, destroyTooltipStore } from \"@graphite/stores/tooltip\";\n\n\timport MainWindow from \"@graphite/components/window/MainWindow.svelte\";\n\n\t// Graphite Wasm editor\n\texport let editor: Editor;\n\tsetContext(\"editor\", editor);\n\n\tconst stores = {\n\t\tdialog: createDialogStore(editor),\n\t\ttooltip: createTooltipStore(editor),\n\t\tdocument: createDocumentStore(editor),\n\t\tfullscreen: createFullscreenStore(editor),\n\t\tnodeGraph: createNodeGraphStore(editor),\n\t\tportfolio: createPortfolioStore(editor),\n\t\tappWindow: createAppWindowStore(editor),\n\t};\n\tObject.entries(stores).forEach(([key, store]) => setContext(key, store));\n\n\tonMount(() => {\n\t\tcreateClipboardManager(editor);\n\t\tcreateHyperlinkManager(editor);\n\t\tcreateLocalizationManager(editor);\n\t\tcreatePanicManager(editor);\n\t\tcreatePersistenceManager(editor, stores.portfolio);\n\t\tcreateFontsManager(editor);\n\t\tcreateInputManager(editor, stores.dialog, stores.portfolio, stores.document);\n\n\t\t// Initialize certain setup tasks required by the editor backend to be ready for the user now that the frontend is ready.\n\t\t// The backend handles idempotency, so this is safe to call again during HMR re-mounts.\n\t\teditor.handle.initAfterFrontendReady();\n\n\t\t// Re-send all UI layouts from Rust so the frontend has them after an HMR re-mount\n\t\teditor.handle.resendAllLayouts();\n\t});\n\n\tonDestroy(() => {\n\t\t// Stores\n\t\tdestroyDialogStore();\n\t\tdestroyTooltipStore();\n\t\tdestroyDocumentStore();\n\t\tdestroyFullscreenStore();\n\t\tdestroyNodeGraphStore();\n\t\tdestroyPortfolioStore();\n\t\tdestroyAppWindowStore();\n\n\t\t// Managers\n\t\tdestroyClipboardManager();\n\t\tdestroyHyperlinkManager();\n\t\tdestroyLocalizationManager();\n\t\tdestroyPanicManager();\n\t\tdestroyPersistenceManager();\n\t\tdestroyFontsManager();\n\t\tdestroyInputManager();\n\t});\n</script>\n\n<MainWindow />\n\n<style lang=\"scss\" global>\n\t// Disable the spinning loading indicator\n\tbody::before,\n\tbody::after {\n\t\tcontent: none !important;\n\t}\n\n\t:root {\n\t\t// Replace usage of `-rgb` variants with CSS color() function to calculate alpha when browsers support it\n\t\t// See https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color() and https://caniuse.com/css-color-function\n\t\t// Specifically, support for the relative syntax is needed: `color(from var(--color-0-black) srgb r g b / 0.5)` to convert black to 50% alpha\n\t\t--color-0-black: #000;\n\t\t--color-0-black-rgb: 0, 0, 0;\n\t\t--color-1-nearblack: #111;\n\t\t--color-1-nearblack-rgb: 17, 17, 17;\n\t\t--color-2-mildblack: #222;\n\t\t--color-2-mildblack-rgb: 34, 34, 34;\n\t\t--color-3-darkgray: #333;\n\t\t--color-3-darkgray-rgb: 51, 51, 51;\n\t\t--color-4-dimgray: #444;\n\t\t--color-4-dimgray-rgb: 68, 68, 68;\n\t\t--color-5-dullgray: #555;\n\t\t--color-5-dullgray-rgb: 85, 85, 85;\n\t\t--color-6-lowergray: #666;\n\t\t--color-6-lowergray-rgb: 102, 102, 102;\n\t\t--color-7-middlegray: #777;\n\t\t--color-7-middlegray-rgb: 109, 109, 109;\n\t\t--color-8-uppergray: #888;\n\t\t--color-8-uppergray-rgb: 136, 136, 136;\n\t\t--color-9-palegray: #999;\n\t\t--color-9-palegray-rgb: 153, 153, 153;\n\t\t--color-a-softgray: #aaa;\n\t\t--color-a-softgray-rgb: 170, 170, 170;\n\t\t--color-b-lightgray: #bbb;\n\t\t--color-b-lightgray-rgb: 187, 187, 187;\n\t\t--color-c-brightgray: #ccc;\n\t\t--color-c-brightgray-rgb: 204, 204, 204;\n\t\t--color-d-mildwhite: #ddd;\n\t\t--color-d-mildwhite-rgb: 221, 221, 221;\n\t\t--color-e-nearwhite: #eee;\n\t\t--color-e-nearwhite-rgb: 238, 238, 238;\n\t\t--color-f-white: #fff;\n\t\t--color-f-white-rgb: 255, 255, 255;\n\t\t--color-error-red: #d6536e;\n\t\t--color-error-red-rgb: 214, 83, 110;\n\t\t--color-warning-yellow: #d5aa43;\n\t\t--color-warning-yellow-rgb: 213, 170, 67;\n\n\t\t--color-data-general: #cfcfcf;\n\t\t--color-data-general-dim: #8a8a8a;\n\t\t--color-data-number: #c9a699;\n\t\t--color-data-number-dim: #886b60;\n\t\t--color-data-artboard: #fbf9eb;\n\t\t--color-data-artboard-dim: #b9b9a9;\n\t\t--color-data-graphic: #68c587;\n\t\t--color-data-graphic-dim: #37754c;\n\t\t--color-data-raster: #e4bb72;\n\t\t--color-data-raster-dim: #9a7b43;\n\t\t--color-data-vector: #65bbe5;\n\t\t--color-data-vector-dim: #417892;\n\t\t--color-data-color: #ce6ea7;\n\t\t--color-data-color-dim: #924071;\n\t\t--color-data-gradient: #af81eb;\n\t\t--color-data-gradient-dim: #6c489b;\n\t\t--color-data-typography: #eea7a7;\n\t\t--color-data-typography-dim: #955252;\n\t\t--color-data-invalid: #d6536e; // Same as --color-error-red\n\t\t--color-data-invalid-dim: #a7324a;\n\n\t\t--color-none: white;\n\t\t--color-none-repeat: no-repeat;\n\t\t--color-none-position: center center;\n\t\t// 24px tall, 48px wide\n\t\t--color-none-size-24px: 60px 24px;\n\t\t// Red diagonal slash (24px tall)\n\t\t--color-none-image-24px: url('data:image/svg+xml;utf8,\\\n\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 60 24\"><line stroke=\"red\" stroke-width=\"4px\" x1=\"0\" y1=\"27\" x2=\"60\" y2=\"-3\" /></svg>\\\n\t\t\t');\n\t\t// 32px tall, 64px wide\n\t\t--color-none-size-32px: 80px 32px;\n\t\t// Red diagonal slash (32px tall)\n\t\t--color-none-image-32px: url('data:image/svg+xml;utf8,\\\n\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 80 32\"><line stroke=\"red\" stroke-width=\"4px\" x1=\"0\" y1=\"36\" x2=\"80\" y2=\"-4\" /></svg>\\\n\t\t\t');\n\n\t\t--color-transparent-checkered-background:\n\t\t\tlinear-gradient(45deg, #cccccc 25%, transparent 25%, transparent 75%, #cccccc 75%), linear-gradient(45deg, #cccccc 25%, transparent 25%, transparent 75%, #cccccc 75%),\n\t\t\tlinear-gradient(#ffffff, #ffffff);\n\t\t--color-transparent-checkered-background-size: 16px 16px, 16px 16px, 16px 16px;\n\t\t--color-transparent-checkered-background-position: 0 0, 8px 8px, 8px 8px;\n\t\t--color-transparent-checkered-background-position-plus-one: 1px 1px, 9px 9px, 9px 9px;\n\t\t--color-transparent-checkered-background-size-mini: 8px 8px, 8px 8px, 8px 8px;\n\t\t--color-transparent-checkered-background-position-mini: 0 0, 4px 4px, 4px 4px;\n\t\t--color-transparent-checkered-background-repeat: repeat, repeat, repeat;\n\n\t\t--inheritance-stripes-background: repeating-linear-gradient(\n\t\t\t-45deg,\n\t\t\ttransparent 0px,\n\t\t\ttransparent calc((3px * sqrt(2) / 2) - 0.5px),\n\t\t\tvar(--color-5-dullgray) calc((3px * sqrt(2) / 2) - 0.5px),\n\t\t\tvar(--color-5-dullgray) calc((3px * sqrt(2) / 2) + 0.5px),\n\t\t\ttransparent calc((3px * sqrt(2) / 2) + 0.5px),\n\t\t\ttransparent calc(6px * sqrt(2) / 2)\n\t\t);\n\t\t--inheritance-dots-background-4-dimgray: url('data:image/svg+xml;utf8,\\\n\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 4 4\" width=\"4px\" height=\"4px\" fill=\"%23444\"><rect width=\"1\" height=\"1\" /><rect x=\"2\" y=\"2\" width=\"1\" height=\"1\" /></svg>\\\n\t\t\t');\n\t\t--inheritance-dots-background-6-lowergray: url('data:image/svg+xml;utf8,\\\n\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 4 4\" width=\"4px\" height=\"4px\" fill=\"%23666\"><rect width=\"1\" height=\"1\" /><rect x=\"2\" y=\"2\" width=\"1\" height=\"1\" /></svg>\\\n\t\t\t');\n\n\t\t// Array of 2x3 dots (fill: --color-e-nearwhite)\n\t\t--icon-drag-grip: url('data:image/svg+xml;utf8,\\\n\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 8 24\" fill=\"%23eee\">\\\n\t\t\t\t<circle cx=\"0.5\" cy=\"1.5\" r=\"0.5\" /><circle cx=\"3.5\" cy=\"1.5\" r=\"0.5\" />\\\n\t\t\t\t<circle cx=\"0.5\" cy=\"4.5\" r=\"0.5\" /><circle cx=\"3.5\" cy=\"4.5\" r=\"0.5\" />\\\n\t\t\t\t<circle cx=\"0.5\" cy=\"7.5\" r=\"0.5\" /><circle cx=\"3.5\" cy=\"7.5\" r=\"0.5\" />\\\n\t\t\t</svg>\\\n\t\t\t');\n\t\t// Array of 2x3 dots (fill: --color-f-white)\n\t\t--icon-drag-grip-hover: url('data:image/svg+xml;utf8,\\\n\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 8 24\" fill=\"%23fff\">\\\n\t\t\t\t<circle cx=\"0.5\" cy=\"1.5\" r=\"0.5\" /><circle cx=\"3.5\" cy=\"1.5\" r=\"0.5\" />\\\n\t\t\t\t<circle cx=\"0.5\" cy=\"4.5\" r=\"0.5\" /><circle cx=\"3.5\" cy=\"4.5\" r=\"0.5\" />\\\n\t\t\t\t<circle cx=\"0.5\" cy=\"7.5\" r=\"0.5\" /><circle cx=\"3.5\" cy=\"7.5\" r=\"0.5\" />\\\n\t\t\t</svg>\\\n\t\t\t');\n\t\t// Array of 2x3 dots (fill: --color-8-uppergray)\n\t\t--icon-drag-grip-disabled: url('data:image/svg+xml;utf8,\\\n\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 8 24\" fill=\"%23888\">\\\n\t\t\t\t<circle cx=\"0.5\" cy=\"1.5\" r=\"0.5\" /><circle cx=\"3.5\" cy=\"1.5\" r=\"0.5\" />\\\n\t\t\t\t<circle cx=\"0.5\" cy=\"4.5\" r=\"0.5\" /><circle cx=\"3.5\" cy=\"4.5\" r=\"0.5\" />\\\n\t\t\t\t<circle cx=\"0.5\" cy=\"7.5\" r=\"0.5\" /><circle cx=\"3.5\" cy=\"7.5\" r=\"0.5\" />\\\n\t\t\t</svg>\\\n\t\t\t');\n\n\t\t// Arrow triangle (fill: --color-e-nearwhite)\n\t\t--icon-expand-collapse-arrow: url('data:image/svg+xml;utf8,\\\n\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 8 8\"><polygon fill=\"%23eee\" points=\"3,0 1,0 5,4 1,8 3,8 7,4\" /></svg>\\\n\t\t\t');\n\t\t// Arrow triangle (fill: --color-f-white)\n\t\t--icon-expand-collapse-arrow-hover: url('data:image/svg+xml;utf8,\\\n\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 8 8\"><polygon fill=\"%23fff\" points=\"3,0 1,0 5,4 1,8 3,8 7,4\" /></svg>\\\n\t\t\t');\n\t\t// Arrow triangle (fill: --color-8-uppergray)\n\t\t--icon-expand-collapse-arrow-disabled: url('data:image/svg+xml;utf8,\\\n\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 8 8\"><polygon fill=\"%23888\" points=\"3,0 1,0 5,4 1,8 3,8 7,4\" /></svg>\\\n\t\t\t');\n\t}\n\n\thtml,\n\tbody {\n\t\tmargin: 0;\n\t\theight: 100%;\n\t\tbackground: var(--color-2-mildblack);\n\t\toverscroll-behavior: none;\n\t\t-webkit-user-select: none; // Still required by Safari as of 2025\n\t\tuser-select: none;\n\t}\n\n\tbody.cursor-hidden * {\n\t\tcursor: none !important;\n\t}\n\n\t// Needed for the viewport hole punch on desktop\n\thtml:has(body > .viewport-hole-punch),\n\tbody:has(> .viewport-hole-punch) {\n\t\tbackground: none;\n\t}\n\n\t// The default value of `auto` from the CSS spec is a footgun with flexbox layouts:\n\t// https://stackoverflow.com/questions/36247140/why-dont-flex-items-shrink-past-content-size\n\t* {\n\t\tmin-width: 0;\n\t\tmin-height: 0;\n\t}\n\n\thtml,\n\tbody,\n\tinput,\n\ttextarea,\n\tbutton {\n\t\tfont-family: \"Source Sans Pro\", Arial, sans-serif;\n\t\tfont-weight: 400;\n\t\tfont-size: 14px;\n\t\tline-height: 1;\n\t\tcolor: var(--color-e-nearwhite);\n\t}\n\n\tsvg,\n\timg {\n\t\tdisplay: block;\n\t}\n\n\t.layout-row,\n\t.layout-col {\n\t\t.scrollable-x,\n\t\t.scrollable-y {\n\t\t\toverflow: hidden;\n\t\t\tscrollbar-width: thin;\n\t\t\tscrollbar-color: var(--color-4-dimgray) transparent;\n\t\t}\n\n\t\t.scrollable-x.scrollable-y {\n\t\t\toverflow: auto;\n\t\t}\n\n\t\t.scrollable-x:not(.scrollable-y) {\n\t\t\tscrollbar-width: none;\n\t\t\toverflow: auto hidden;\n\t\t}\n\n\t\t.scrollable-y:not(.scrollable-x) {\n\t\t\toverflow: hidden auto;\n\t\t}\n\t}\n\n\t// List of all elements that should show an outline when focused by tabbing or by clicking the element\n\t.dropdown-input .dropdown-box,\n\t.font-input .dropdown-box {\n\t\t&:focus {\n\t\t\toutline: 1px dashed var(--color-e-nearwhite);\n\t\t\toutline-offset: -1px;\n\t\t}\n\t}\n\n\t// List of all elements that should show an outline when focused by tabbing, but not by clicking the element\n\t.icon-button,\n\t.text-button,\n\t.popover-button,\n\t.color-button > button,\n\t.color-picker .preset-color,\n\t.working-colors-input .swatch > button,\n\t.radio-input button,\n\t.menu-list,\n\t.menu-list-button .entry,\n\t.layer-tree .expand-arrow,\n\t.widget-section .header {\n\t\t&:focus-visible {\n\t\t\toutline: 1px dashed var(--color-e-nearwhite);\n\t\t\toutline-offset: -1px;\n\t\t}\n\n\t\t// Variant: dark outline over light colors\n\t\t&.preset-color.white,\n\t\t&.text-button.emphasized {\n\t\t\t&:focus-visible {\n\t\t\t\toutline: 1px dashed var(--color-2-mildblack);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Checkbox needs to apply the focus outline to its sibling label\n\t.optional-input input:focus-visible + label,\n\t.checkbox-input input:focus-visible + label {\n\t\toutline: 1px dashed var(--color-e-nearwhite);\n\t\toutline-offset: -1px;\n\t}\n\n\t// Variant: dark outline over light colors (when the checkbox is checked)\n\t:not(.optional-input) > .checkbox-input input:focus-visible + label.checked {\n\t\toutline: 1px dashed var(--color-2-mildblack);\n\t}\n\n\t@font-face {\n\t\tfont-family: \"Source Sans Pro\";\n\t\tfont-weight: 400;\n\t\tfont-style: normal;\n\t\tfont-stretch: normal;\n\t\tsrc: url(\"@graphite/../node_modules/source-sans-pro/WOFF2/TTF/SourceSansPro-Regular.ttf.woff2\") format(\"woff2\");\n\t}\n\n\t@font-face {\n\t\tfont-family: \"Source Sans Pro\";\n\t\tfont-weight: 400;\n\t\tfont-style: italic;\n\t\tfont-stretch: normal;\n\t\tsrc: url(\"@graphite/../node_modules/source-sans-pro/WOFF2/TTF/SourceSansPro-It.ttf.woff2\") format(\"woff2\");\n\t}\n\n\t@font-face {\n\t\tfont-family: \"Source Sans Pro\";\n\t\tfont-weight: 700;\n\t\tfont-style: normal;\n\t\tfont-stretch: normal;\n\t\tsrc: url(\"@graphite/../node_modules/source-sans-pro/WOFF2/TTF/SourceSansPro-Bold.ttf.woff2\") format(\"woff2\");\n\t}\n\n\t@font-face {\n\t\tfont-family: \"Source Sans Pro\";\n\t\tfont-weight: 700;\n\t\tfont-style: italic;\n\t\tfont-stretch: normal;\n\t\tsrc: url(\"@graphite/../node_modules/source-sans-pro/WOFF2/TTF/SourceSansPro-BoldIt.ttf.woff2\") format(\"woff2\");\n\t}\n\n\t@font-face {\n\t\tfont-family: \"Source Code Pro\";\n\t\tfont-weight: 400;\n\t\tfont-style: normal;\n\t\tfont-stretch: normal;\n\t\tsrc: url(\"@graphite/../node_modules/source-code-pro/WOFF2/TTF/SourceCodePro-Regular.ttf.woff2\") format(\"woff2\");\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/README.md",
    "content": "# Overview of `/frontend/src/components/`\n\nEach component represents a (usually reusable) part of the Graphite editor GUI.\n\n## Floating Menus: `floating-menus/`\n\nThe temporary UI areas with dark backgrounds which hover over the top of the editor window content. Examples include menu lists, popovers, and dialogs.\n\n## Layout: `layout/`\n\nUseful containers that control the flow of content held within.\n\n## Panels: `panels/`\n\nThe dockable tabbed regions like the Document, Properties, Layers, Data, and Welcome panels.\n\n## Views: `views/`\n\nContent views rendered within panels, such as the node graph.\n\n## Widgets: `widgets/`\n\nThe interactive input items used to display information and provide user control.\n\n## Window: `window/`\n\nThe building blocks for the Title Bar, Workspace, and Status Bar within an editor application window.\n\n# Svelte tips and tricks\n\nThis section contains a growing list of quick reference information for helpful Svelte solutions and best practices. Feel free to add to this to help contributors learn things, or yourself remember tricks you'll likely forget in a few months.\n\n## Bi-directional props\n\nThe component declares this:\n\n```ts\n// The dispatcher that sends the changed value as a custom event to the parent\nconst dispatch = createEventDispatcher<{ theBidirectionalProperty: number }>();\n\n// The prop\nexport let theBidirectionalProperty: number;\n\n// Called only when `theBidirectionalProperty` is changed from outside this component via its props\n$: console.log(theBidirectionalProperty);\n\n// Example of a method that would update the value\nfunction doSomething() {\n\tdispatch(\"theBidirectionalProperty\", SOME_NEW_VALUE);\n},\n```\n\nUsers of the component do this for `theCorrespondingDataEntry` to be a two-way binding:\n\n```ts\nlet theCorrespondingDataEntry = 42;\n```\n\n```svelte\n<DropdownInput\n\ttheBidirectionalProperty={theCorrespondingDataEntry}\n\ton:theBidirectionalProperty={({ detail }) => { theCorrespondingDataEntry = detail; }}\n/>\n```\n"
  },
  {
    "path": "frontend/src/components/floating-menus/ColorPicker.svelte",
    "content": "<script lang=\"ts\">\n\timport { getContext, onDestroy, createEventDispatcher, tick } from \"svelte\";\n\n\timport { isPlatformNative } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\timport type { FillChoice, MenuDirection, Color } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\timport type { TooltipStore } from \"@graphite/stores/tooltip\";\n\timport {\n\t\tcontrastingOutlineFactor,\n\t\tfillChoiceColor,\n\t\tfillChoiceGradientStops,\n\t\tcreateColor,\n\t\tcreateColorFromHSVA,\n\t\tcolorFromCSS,\n\t\tcolorToRgb255,\n\t\tcolorToHSV,\n\t\tcolorToHexOptionalAlpha,\n\t\tcolorToHexNoAlpha,\n\t\tcolorToRgbCSS,\n\t\tcolorContrastingColor,\n\t\tcolorOpaque,\n\t\tcolorEquals,\n\t\tgradientFirstColor,\n\t} from \"@graphite/utility-functions/colors\";\n\timport type { HSV, RGB } from \"@graphite/utility-functions/colors\";\n\n\timport FloatingMenu, { preventEscapeClosingParentFloatingMenu } from \"@graphite/components/layout/FloatingMenu.svelte\";\n\timport LayoutCol from \"@graphite/components/layout/LayoutCol.svelte\";\n\timport LayoutRow from \"@graphite/components/layout/LayoutRow.svelte\";\n\timport IconButton from \"@graphite/components/widgets/buttons/IconButton.svelte\";\n\timport NumberInput from \"@graphite/components/widgets/inputs/NumberInput.svelte\";\n\timport SpectrumInput, { MAX_MIDPOINT, MIN_MIDPOINT } from \"@graphite/components/widgets/inputs/SpectrumInput.svelte\";\n\timport TextInput from \"@graphite/components/widgets/inputs/TextInput.svelte\";\n\timport Separator from \"@graphite/components/widgets/labels/Separator.svelte\";\n\timport TextLabel from \"@graphite/components/widgets/labels/TextLabel.svelte\";\n\n\ttype PresetColors = \"None\" | \"Black\" | \"White\" | \"Red\" | \"Yellow\" | \"Green\" | \"Cyan\" | \"Blue\" | \"Magenta\";\n\n\tconst PURE_COLORS: Record<PresetColors, [number, number, number]> = {\n\t\tNone: [0, 0, 0],\n\t\tBlack: [0, 0, 0],\n\t\tWhite: [1, 1, 1],\n\t\tRed: [1, 0, 0],\n\t\tYellow: [1, 1, 0],\n\t\tGreen: [0, 1, 0],\n\t\tCyan: [0, 1, 1],\n\t\tBlue: [0, 0, 1],\n\t\tMagenta: [1, 0, 1],\n\t};\n\tconst PURE_COLORS_GRAYABLE: [PresetColors, string, string][] = [\n\t\t[\"Red\", \"#ff0000\", \"#4c4c4c\"],\n\t\t[\"Yellow\", \"#ffff00\", \"#e3e3e3\"],\n\t\t[\"Green\", \"#00ff00\", \"#969696\"],\n\t\t[\"Cyan\", \"#00ffff\", \"#b2b2b2\"],\n\t\t[\"Blue\", \"#0000ff\", \"#1c1c1c\"],\n\t\t[\"Magenta\", \"#ff00ff\", \"#696969\"],\n\t];\n\n\tconst dispatch = createEventDispatcher<{ colorOrGradient: FillChoice; startHistoryTransaction: undefined; commitHistoryTransaction: undefined }>();\n\tconst tooltip = getContext<TooltipStore>(\"tooltip\");\n\n\texport let colorOrGradient: FillChoice;\n\texport let allowNone = false;\n\t// export let allowTransparency = false; // TODO: Implement\n\texport let disabled = false;\n\texport let direction: MenuDirection = \"Bottom\";\n\t// TODO: See if this should be made to follow the pattern of DropdownInput.svelte so this could be removed\n\texport let open: boolean;\n\n\tconst initSolidColor = fillChoiceColor(colorOrGradient);\n\tconst initGradientStops = fillChoiceGradientStops(colorOrGradient);\n\tconst colorForHSVA = initSolidColor || (initGradientStops ? gradientFirstColor(initGradientStops) : undefined);\n\tconst hsvOrNone = colorForHSVA ? colorToHSV(colorForHSVA) : undefined;\n\tconst hsv = hsvOrNone || { h: 0, s: 0, v: 0 };\n\n\t// Gradient color stops\n\t$: gradient = fillChoiceGradientStops(colorOrGradient);\n\tlet activeIndex: number | undefined = 0;\n\tlet activeIndexIsMidpoint = false;\n\t$: selectedGradientColor = (activeIndex !== undefined && gradient?.color[activeIndex]) || colorFromCSS(\"black\") || createColor(0, 0, 0, 1);\n\t// Currently viewed color\n\t$: color = fillChoiceColor(colorOrGradient) || selectedGradientColor;\n\t// New color components\n\tlet hue = hsv.h;\n\tlet saturation = hsv.s;\n\tlet value = hsv.v;\n\tlet alpha = colorForHSVA ? colorForHSVA.alpha : 1;\n\tlet isNone = hsvOrNone === undefined;\n\t// Old color components\n\tlet oldHue = hsv.h;\n\tlet oldSaturation = hsv.s;\n\tlet oldValue = hsv.v;\n\tlet oldAlpha = colorForHSVA ? colorForHSVA.alpha : 1;\n\tlet oldIsNone = hsvOrNone === undefined;\n\t// Transient state\n\tlet draggingPickerTrack: HTMLDivElement | undefined = undefined;\n\tlet strayCloses = true;\n\tlet gradientSpectrumDragging = false;\n\tlet shiftPressed = false;\n\tlet alignedAxis: \"saturation\" | \"value\" | undefined = undefined;\n\tlet hueBeforeDrag = 0;\n\tlet saturationBeforeDrag = 0;\n\tlet valueBeforeDrag = 0;\n\tlet alphaBeforeDrag = 0;\n\tlet saturationStartOfAxisAlign: number | undefined = undefined;\n\tlet valueStartOfAxisAlign: number | undefined = undefined;\n\tlet saturationRestoreWhenShiftReleased: number | undefined = undefined;\n\tlet valueRestoreWhenShiftReleased: number | undefined = undefined;\n\n\tlet self: FloatingMenu | undefined;\n\tlet hexCodeInputWidget: TextInput | undefined;\n\tlet gradientSpectrumInputWidget: SpectrumInput | undefined;\n\n\t$: watchOpen(open);\n\t$: watchColor(color);\n\n\t$: oldColor = oldIsNone ? undefined : createColorFromHSVA(oldHue, oldSaturation, oldValue, oldAlpha);\n\t$: newColor = isNone ? undefined : createColorFromHSVA(hue, saturation, value, alpha);\n\t$: rgbChannels = ((): [keyof RGB, number | undefined][] => {\n\t\tconst rgb = newColor ? colorToRgb255(newColor) : undefined;\n\t\treturn [\n\t\t\t[\"r\", rgb?.r],\n\t\t\t[\"g\", rgb?.g],\n\t\t\t[\"b\", rgb?.b],\n\t\t];\n\t})();\n\t$: hsvChannels = ((): [keyof HSV, number | undefined][] => {\n\t\treturn [\n\t\t\t[\"h\", isNone ? undefined : hue * 360],\n\t\t\t[\"s\", isNone ? undefined : saturation * 100],\n\t\t\t[\"v\", isNone ? undefined : value * 100],\n\t\t];\n\t})();\n\t$: opaqueHueColor = createColorFromHSVA(hue, 1, 1, 1);\n\t$: outlineFactor = Math.max(\n\t\tcontrastingOutlineFactor(newColor ? { Solid: newColor } : (\"None\" as const), \"--color-2-mildblack\", 0.01),\n\t\tcontrastingOutlineFactor(oldColor ? { Solid: oldColor } : (\"None\" as const), \"--color-2-mildblack\", 0.01),\n\t);\n\t$: outlined = outlineFactor > 0.0001;\n\t$: transparency = (newColor?.alpha ?? 1) < 1 || (oldColor?.alpha ?? 1) < 1;\n\n\tasync function watchOpen(open: boolean) {\n\t\tif (open) {\n\t\t\tsetTimeout(() => hexCodeInputWidget?.focus(), 0);\n\n\t\t\tawait tick();\n\t\t\tsetOldHSVA(hue, saturation, value, alpha, isNone);\n\t\t}\n\t}\n\n\tfunction watchColor(color: Color) {\n\t\tconst hsv = colorToHSV(color);\n\n\t\t// Update the hue, but only if it is necessary so we don't:\n\t\t// - ...jump the user's hue from 360° (top) to the equivalent 0° (bottom)\n\t\t// - ...reset the hue to 0° if the color is fully desaturated, where all hues are equivalent\n\t\t// - ...reset the hue to 0° if the color's value is black, where all hues are equivalent\n\t\tif (!(hsv.h === 0 && hue === 1) && hsv.s > 0 && hsv.v > 0) hue = hsv.h;\n\t\t// Update the saturation, but only if it is necessary so we don't:\n\t\t// - ...reset the saturation to the left if the color's value is black along the bottom edge, where all saturations are equivalent\n\t\tif (hsv.v !== 0) saturation = hsv.s;\n\t\t// Update the value\n\t\tvalue = hsv.v;\n\t\t// Update the alpha\n\t\talpha = color.alpha;\n\t\t// Update the status of this not being a color\n\t\tisNone = false;\n\t}\n\n\tfunction onPointerDown(e: PointerEvent) {\n\t\tif (disabled) return;\n\n\t\tconst target = e.target instanceof HTMLElement ? e.target : undefined;\n\t\tdraggingPickerTrack = target?.closest(\"[data-saturation-value-picker], [data-hue-picker], [data-alpha-picker]\") || undefined;\n\n\t\thueBeforeDrag = hue;\n\t\tsaturationBeforeDrag = saturation;\n\t\tvalueBeforeDrag = value;\n\t\talphaBeforeDrag = alpha;\n\n\t\tsaturationStartOfAxisAlign = undefined;\n\t\tvalueStartOfAxisAlign = undefined;\n\n\t\taddEvents();\n\n\t\tonPointerMove(e);\n\t}\n\n\tfunction onPointerMove(e: PointerEvent) {\n\t\t// Just in case the mouseup event is lost\n\t\tif (e.buttons === 0) removeEvents();\n\n\t\tif (draggingPickerTrack?.hasAttribute(\"data-saturation-value-picker\")) {\n\t\t\tconst rectangle = draggingPickerTrack.getBoundingClientRect();\n\n\t\t\tsaturation = clamp((e.clientX - rectangle.left) / rectangle.width, 0, 1);\n\t\t\tvalue = clamp(1 - (e.clientY - rectangle.top) / rectangle.height, 0, 1);\n\t\t\tstrayCloses = false;\n\n\t\t\tif (shiftPressed) updateAxisLock();\n\t\t} else if (draggingPickerTrack?.hasAttribute(\"data-hue-picker\")) {\n\t\t\tconst rectangle = draggingPickerTrack.getBoundingClientRect();\n\n\t\t\thue = clamp(1 - (e.clientY - rectangle.top) / rectangle.height, 0, 1);\n\t\t\tstrayCloses = false;\n\t\t} else if (draggingPickerTrack?.hasAttribute(\"data-alpha-picker\")) {\n\t\t\tconst rectangle = draggingPickerTrack.getBoundingClientRect();\n\n\t\t\talpha = clamp(1 - (e.clientY - rectangle.top) / rectangle.height, 0, 1);\n\t\t\tstrayCloses = false;\n\t\t}\n\n\t\tconst color = createColorFromHSVA(hue, saturation, value, alpha);\n\t\tsetColor(color);\n\n\t\tif (!e.shiftKey) {\n\t\t\tshiftPressed = false;\n\t\t\talignedAxis = undefined;\n\t\t} else if (!shiftPressed && draggingPickerTrack) {\n\t\t\tshiftPressed = true;\n\t\t\tsaturationStartOfAxisAlign = saturationBeforeDrag;\n\t\t\tvalueStartOfAxisAlign = valueBeforeDrag;\n\t\t}\n\t}\n\n\tfunction onPointerUp() {\n\t\tif (draggingPickerTrack) dispatch(\"commitHistoryTransaction\");\n\t\tremoveEvents();\n\t}\n\n\tfunction onMouseDown(e: MouseEvent) {\n\t\tconst BUTTONS_RIGHT = 0b0000_0010;\n\t\tif (e.buttons & BUTTONS_RIGHT) abortDrag();\n\t}\n\n\tfunction onKeyDown(e: KeyboardEvent) {\n\t\tif (e.key === \"Escape\") {\n\t\t\tconst element = self?.div();\n\t\t\tif (element) preventEscapeClosingParentFloatingMenu(element);\n\n\t\t\tabortDrag();\n\t\t}\n\t}\n\n\tfunction onKeyUp(e: KeyboardEvent) {\n\t\tif (e.key === \"Shift\") {\n\t\t\tshiftPressed = false;\n\t\t\talignedAxis = undefined;\n\n\t\t\tif (saturationRestoreWhenShiftReleased !== undefined && valueRestoreWhenShiftReleased !== undefined) {\n\t\t\t\tsaturation = saturationRestoreWhenShiftReleased;\n\t\t\t\tvalue = valueRestoreWhenShiftReleased;\n\n\t\t\t\tconst color = createColorFromHSVA(hue, saturation, value, alpha);\n\t\t\t\tsetColor(color);\n\t\t\t}\n\t\t}\n\t}\n\n\tfunction addEvents() {\n\t\tdocument.addEventListener(\"pointermove\", onPointerMove);\n\t\tdocument.addEventListener(\"pointerup\", onPointerUp);\n\t\tdocument.addEventListener(\"mousedown\", onMouseDown);\n\t\tdocument.addEventListener(\"keydown\", onKeyDown);\n\t\tdocument.addEventListener(\"keyup\", onKeyUp);\n\n\t\tdispatch(\"startHistoryTransaction\");\n\t}\n\n\tfunction removeEvents() {\n\t\tdraggingPickerTrack = undefined;\n\t\t// The setTimeout is necessary to prevent the FloatingMenu's `escapeCloses` from becoming true immediately upon pressing the Escape key, and thus closing\n\t\tsetTimeout(() => (strayCloses = true), 0);\n\t\tshiftPressed = false;\n\t\talignedAxis = undefined;\n\n\t\tdocument.removeEventListener(\"pointermove\", onPointerMove);\n\t\tdocument.removeEventListener(\"pointerup\", onPointerUp);\n\t\tdocument.removeEventListener(\"mousedown\", onMouseDown);\n\t\tdocument.removeEventListener(\"keydown\", onKeyDown);\n\t\tdocument.removeEventListener(\"keyup\", onKeyUp);\n\t}\n\n\tfunction updateAxisLock() {\n\t\tif (!saturationStartOfAxisAlign || !valueStartOfAxisAlign) return;\n\n\t\tconst deltaSaturation = saturation - saturationStartOfAxisAlign;\n\t\tconst deltaValue = value - valueStartOfAxisAlign;\n\n\t\tsaturationRestoreWhenShiftReleased = saturation;\n\t\tvalueRestoreWhenShiftReleased = value;\n\n\t\tif (Math.abs(deltaSaturation) < Math.abs(deltaValue)) {\n\t\t\talignedAxis = \"saturation\";\n\t\t\tsaturation = saturationStartOfAxisAlign;\n\t\t} else {\n\t\t\talignedAxis = \"value\";\n\t\t\tvalue = valueStartOfAxisAlign;\n\t\t}\n\t}\n\n\tfunction abortDrag() {\n\t\tremoveEvents();\n\n\t\thue = hueBeforeDrag;\n\t\tsaturation = saturationBeforeDrag;\n\t\tvalue = valueBeforeDrag;\n\t\talpha = alphaBeforeDrag;\n\n\t\tconst color = createColorFromHSVA(hue, saturation, value, alpha);\n\t\tsetColor(color);\n\t}\n\n\tfunction setColor(color?: Color | \"None\") {\n\t\tif (color === \"None\") {\n\t\t\tdispatch(\"colorOrGradient\", \"None\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst colorToEmit = color || createColorFromHSVA(hue, saturation, value, alpha);\n\n\t\tif (gradientSpectrumInputWidget && activeIndex !== undefined && gradient && gradient.position[activeIndex] !== undefined) {\n\t\t\tconst gradientStops = fillChoiceGradientStops(colorOrGradient);\n\t\t\tif (gradientStops) gradientStops.color[activeIndex] = colorToEmit;\n\t\t}\n\n\t\tdispatch(\"colorOrGradient\", gradient ? { Gradient: gradient } : { Solid: colorToEmit });\n\t}\n\n\tfunction swapNewWithOld() {\n\t\tconst old = oldColor;\n\n\t\tconst tempHue = hue;\n\t\tconst tempSaturation = saturation;\n\t\tconst tempValue = value;\n\t\tconst tempAlpha = alpha;\n\t\tconst tempIsNone = isNone;\n\n\t\tsetNewHSVA(oldHue, oldSaturation, oldValue, oldAlpha, oldIsNone);\n\t\tsetOldHSVA(tempHue, tempSaturation, tempValue, tempAlpha, tempIsNone);\n\n\t\tsetColor(old || \"None\");\n\t}\n\n\tfunction setColorCode(colorCode: string) {\n\t\tconst color = colorFromCSS(colorCode);\n\t\tif (color) setColor(color);\n\t}\n\n\tfunction setColorRGB(channel: keyof RGB, strength: number | undefined) {\n\t\t// Do nothing if the given value is undefined\n\t\tif (strength === undefined || !newColor) return undefined;\n\t\t// Set the specified channel to the given value\n\t\telse if (channel === \"r\") setColor(createColor(strength / 255, newColor.green, newColor.blue, newColor.alpha));\n\t\telse if (channel === \"g\") setColor(createColor(newColor.red, strength / 255, newColor.blue, newColor.alpha));\n\t\telse if (channel === \"b\") setColor(createColor(newColor.red, newColor.green, strength / 255, newColor.alpha));\n\t}\n\n\tfunction setColorHSV(channel: keyof HSV, strength: number | undefined) {\n\t\t// Do nothing if the given value is undefined\n\t\tif (strength === undefined) return undefined;\n\t\t// Set the specified channel to the given value\n\t\telse if (channel === \"h\") hue = strength / 360;\n\t\telse if (channel === \"s\") saturation = strength / 100;\n\t\telse if (channel === \"v\") value = strength / 100;\n\n\t\tsetColor();\n\t}\n\n\tfunction setColorAlphaPercent(strength: number | undefined) {\n\t\tif (strength !== undefined) alpha = strength / 100;\n\t\tsetColor();\n\t}\n\n\tfunction setColorPreset(preset: PresetColors) {\n\t\tdispatch(\"startHistoryTransaction\");\n\n\t\tif (preset === \"None\") {\n\t\t\tsetNewHSVA(0, 0, 0, 1, true);\n\t\t\tsetColor(\"None\");\n\t\t} else {\n\t\t\tconst presetColor = createColor(...PURE_COLORS[preset], 1);\n\t\t\tconst hsv = colorToHSV(presetColor);\n\t\t\tif (!hsv) return;\n\n\t\t\tsetNewHSVA(hsv.h, hsv.s, hsv.v, presetColor.alpha, false);\n\t\t\tsetColor(presetColor);\n\t\t}\n\t}\n\n\tfunction setNewHSVA(h: number, s: number, v: number, a: number, none: boolean) {\n\t\thue = h;\n\t\tsaturation = s;\n\t\tvalue = v;\n\t\talpha = a;\n\t\tisNone = none;\n\t}\n\n\tfunction setOldHSVA(h: number, s: number, v: number, a: number, none: boolean) {\n\t\toldHue = h;\n\t\toldSaturation = s;\n\t\toldValue = v;\n\t\toldAlpha = a;\n\t\toldIsNone = none;\n\t}\n\n\t// TODO: Replace this temporary usage of the browser eyedropper API, that only works in Chromium-based browsers, with the custom color sampler system used by the Eyedropper tool\n\tfunction eyedropperSupported(): boolean {\n\t\t// TODO: Implement support in the desktop app for OS-level color picking\n\t\tif (isPlatformNative()) return false;\n\n\t\treturn window.EyeDropper !== undefined;\n\t}\n\n\tasync function activateEyedropperSample() {\n\t\tif (!eyedropperSupported()) return;\n\n\t\ttry {\n\t\t\tconst result = await new EyeDropper().open();\n\t\t\tdispatch(\"startHistoryTransaction\");\n\t\t\tsetColorCode(result.sRGBHex);\n\t\t} catch {\n\t\t\t// Do nothing\n\t\t}\n\t}\n\n\tfunction gradientActiveMarkerIndexChange({ detail: { activeMarkerIndex, activeMarkerIsMidpoint } }: CustomEvent<{ activeMarkerIndex: number | undefined; activeMarkerIsMidpoint: boolean }>) {\n\t\tactiveIndex = activeMarkerIndex;\n\t\tactiveIndexIsMidpoint = activeMarkerIsMidpoint;\n\n\t\tconst color = activeMarkerIndex === undefined ? undefined : gradient?.color[activeMarkerIndex];\n\t\tconst hsv = color ? colorToHSV(color) : undefined;\n\t\tif (!color || !hsv) return;\n\n\t\tsetColor(color);\n\n\t\tsetNewHSVA(hsv.h, hsv.s, hsv.v, color.alpha, false);\n\t\tsetOldHSVA(hsv.h, hsv.s, hsv.v, color.alpha, false);\n\t}\n\n\tfunction clamp(value: number, min = 0, max = 1): number {\n\t\treturn Math.max(min, Math.min(value, max));\n\t}\n\n\texport function div(): HTMLDivElement | undefined {\n\t\treturn self?.div();\n\t}\n\n\tonDestroy(() => {\n\t\tremoveEvents();\n\t});\n</script>\n\n<FloatingMenu class=\"color-picker\" classes={{ disabled }} {open} on:open {strayCloses} escapeCloses={strayCloses && !gradientSpectrumDragging} {direction} type=\"Popover\" bind:this={self}>\n\t<LayoutRow\n\t\tstyles={{\n\t\t\t\"--new-color\": newColor ? colorToHexOptionalAlpha(newColor) : undefined,\n\t\t\t\"--new-color-contrasting\": colorContrastingColor(newColor),\n\t\t\t\"--old-color\": oldColor ? colorToHexOptionalAlpha(oldColor) : undefined,\n\t\t\t\"--old-color-contrasting\": colorContrastingColor(oldColor),\n\t\t\t\"--hue-color\": colorToRgbCSS(opaqueHueColor),\n\t\t\t\"--hue-color-contrasting\": colorContrastingColor(opaqueHueColor),\n\t\t\t\"--opaque-color\": colorToHexNoAlpha(newColor ? colorOpaque(newColor) : createColor(0, 0, 0, 1)),\n\t\t\t\"--opaque-color-contrasting\": colorContrastingColor(newColor ? colorOpaque(newColor) : createColor(0, 0, 0, 1)),\n\t\t}}\n\t>\n\t\t{@const hueDescription = \"The shade along the spectrum of the rainbow.\"}\n\t\t{@const saturationDescription = \"The vividness from grayscale to full color.\"}\n\t\t{@const valueDescription = \"The brightness from black to full color.\"}\n\t\t<LayoutCol class=\"pickers-and-gradient\">\n\t\t\t<LayoutRow class=\"pickers\">\n\t\t\t\t<LayoutCol\n\t\t\t\t\tclass=\"saturation-value-picker\"\n\t\t\t\t\tdata-tooltip-label=\"Saturation and Value\"\n\t\t\t\t\tdata-tooltip-description={`To move only along the saturation (X) or value (Y) axis, perform the shortcut shown.${disabled ? \"\\n\\nDisabled (read-only).\" : \"\"}`}\n\t\t\t\t\tdata-tooltip-shortcut={$tooltip.shiftClickShortcut?.shortcut ? JSON.stringify($tooltip.shiftClickShortcut.shortcut) : undefined}\n\t\t\t\t\ton:pointerdown={onPointerDown}\n\t\t\t\t\tdata-saturation-value-picker\n\t\t\t\t>\n\t\t\t\t\t{#if alignedAxis}\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclass=\"selection-circle-axis-snap-line\"\n\t\t\t\t\t\t\tstyle:width={alignedAxis === \"value\" ? \"100%\" : undefined}\n\t\t\t\t\t\t\tstyle:height={alignedAxis === \"saturation\" ? \"100%\" : undefined}\n\t\t\t\t\t\t\tstyle:top={alignedAxis === \"value\" ? `${(1 - value) * 100}%` : undefined}\n\t\t\t\t\t\t\tstyle:left={alignedAxis === \"saturation\" ? `${saturation * 100}%` : undefined}\n\t\t\t\t\t\t></div>\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclass=\"selection-circle-axis-snap-line\"\n\t\t\t\t\t\t\tstyle:width={alignedAxis === \"saturation\" ? \"100%\" : undefined}\n\t\t\t\t\t\t\tstyle:height={alignedAxis === \"value\" ? \"100%\" : undefined}\n\t\t\t\t\t\t\tstyle:top={alignedAxis === \"saturation\" ? `${(1 - valueBeforeDrag) * 100}%` : undefined}\n\t\t\t\t\t\t\tstyle:left={alignedAxis === \"value\" ? `${saturationBeforeDrag * 100}%` : undefined}\n\t\t\t\t\t\t></div>\n\t\t\t\t\t{/if}\n\t\t\t\t\t{#if !isNone}\n\t\t\t\t\t\t<div class=\"selection-circle\" style:top={`${(1 - value) * 100}%`} style:left={`${saturation * 100}%`}></div>\n\t\t\t\t\t{/if}\n\t\t\t\t</LayoutCol>\n\t\t\t\t<LayoutCol\n\t\t\t\t\tclass=\"hue-picker\"\n\t\t\t\t\tdata-tooltip-label=\"Hue\"\n\t\t\t\t\tdata-tooltip-description={`${hueDescription}${disabled ? \"\\n\\nDisabled (read-only).\" : \"\"}`}\n\t\t\t\t\ton:pointerdown={onPointerDown}\n\t\t\t\t\tdata-hue-picker\n\t\t\t\t>\n\t\t\t\t\t{#if !isNone}\n\t\t\t\t\t\t<div class=\"selection-needle\" style:top={`${(1 - hue) * 100}%`}></div>\n\t\t\t\t\t{/if}\n\t\t\t\t</LayoutCol>\n\t\t\t\t<LayoutCol\n\t\t\t\t\tclass=\"alpha-picker\"\n\t\t\t\t\tdata-tooltip-label=\"Alpha\"\n\t\t\t\t\tdata-tooltip-description={`The level of translucency.${disabled ? \"\\n\\nDisabled (read-only).\" : \"\"}`}\n\t\t\t\t\ton:pointerdown={onPointerDown}\n\t\t\t\t\tdata-alpha-picker\n\t\t\t\t>\n\t\t\t\t\t{#if !isNone}\n\t\t\t\t\t\t<div class=\"selection-needle\" style:top={`${(1 - alpha) * 100}%`}></div>\n\t\t\t\t\t{/if}\n\t\t\t\t</LayoutCol>\n\t\t\t</LayoutRow>\n\t\t\t{#if gradient}\n\t\t\t\t<LayoutRow class=\"gradient\">\n\t\t\t\t\t<SpectrumInput\n\t\t\t\t\t\t{gradient}\n\t\t\t\t\t\t{disabled}\n\t\t\t\t\t\ton:gradient={() => dispatch(\"colorOrGradient\", gradient ? { Gradient: gradient } : \"None\")}\n\t\t\t\t\t\ton:activeMarkerIndexChange={gradientActiveMarkerIndexChange}\n\t\t\t\t\t\tactiveMarkerIndex={activeIndex}\n\t\t\t\t\t\tactiveMarkerIsMidpoint={activeIndexIsMidpoint}\n\t\t\t\t\t\ton:dragging={({ detail }) => (gradientSpectrumDragging = detail)}\n\t\t\t\t\t\tbind:this={gradientSpectrumInputWidget}\n\t\t\t\t\t/>\n\t\t\t\t\t{#if gradientSpectrumInputWidget && activeIndex !== undefined}\n\t\t\t\t\t\t<NumberInput\n\t\t\t\t\t\t\tvalue={(activeIndexIsMidpoint ? gradient.midpoint[activeIndex] : gradient.position[activeIndex] || 0) * 100}\n\t\t\t\t\t\t\t{disabled}\n\t\t\t\t\t\t\ton:value={({ detail: position }) => {\n\t\t\t\t\t\t\t\tif (gradientSpectrumInputWidget && activeIndex !== undefined && position !== undefined) {\n\t\t\t\t\t\t\t\t\tgradientSpectrumInputWidget.setPosition(activeIndex, position / 100, activeIndexIsMidpoint);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tdisplayDecimalPlaces={0}\n\t\t\t\t\t\t\tmin={activeIndexIsMidpoint ? MIN_MIDPOINT * 100 : 0}\n\t\t\t\t\t\t\tmax={activeIndexIsMidpoint ? MAX_MIDPOINT * 100 : 100}\n\t\t\t\t\t\t\tunit=\"%\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t{/if}\n\t\t\t\t</LayoutRow>\n\t\t\t{/if}\n\t\t</LayoutCol>\n\t\t<LayoutCol class=\"details\">\n\t\t\t<LayoutRow\n\t\t\t\tclass=\"choice-preview\"\n\t\t\t\tclasses={{ outlined, transparency }}\n\t\t\t\tstyles={{ \"--outline-amount\": outlineFactor }}\n\t\t\t\ttooltipDescription={!colorEquals(newColor, oldColor) ? \"Comparison between the present color choice (left) and the color before it was changed (right).\" : \"The present color choice.\"}\n\t\t\t>\n\t\t\t\t{#if !colorEquals(newColor, oldColor) && !disabled}\n\t\t\t\t\t<div class=\"swap-button-background\"></div>\n\t\t\t\t\t<IconButton class=\"swap-button\" icon=\"SwapHorizontal\" size={16} action={swapNewWithOld} tooltipLabel=\"Swap\" />\n\t\t\t\t{/if}\n\t\t\t\t<LayoutCol class=\"new-color\" classes={{ none: isNone }}>\n\t\t\t\t\t{#if !colorEquals(newColor, oldColor)}\n\t\t\t\t\t\t<TextLabel>New</TextLabel>\n\t\t\t\t\t{/if}\n\t\t\t\t</LayoutCol>\n\t\t\t\t{#if !colorEquals(newColor, oldColor)}\n\t\t\t\t\t<LayoutCol class=\"old-color\" classes={{ none: oldIsNone }}>\n\t\t\t\t\t\t<TextLabel>Old</TextLabel>\n\t\t\t\t\t</LayoutCol>\n\t\t\t\t{/if}\n\t\t\t</LayoutRow>\n\t\t\t<!-- <DropdownInput entries={[[{ label: \"sRGB\" }]]} selectedIndex={0} disabled={true} tooltipDescription=\"Color model, color space, and HDR (coming soon).\" /> -->\n\t\t\t<LayoutRow>\n\t\t\t\t{@const hexDescription = \"Color code in hexadecimal format. 6 digits if opaque, 8 with alpha. Accepts input of CSS color values including named colors.\"}\n\t\t\t\t<TextLabel tooltipLabel=\"Hex Color Code\" tooltipDescription={hexDescription}>Hex</TextLabel>\n\t\t\t\t<Separator style=\"Related\" />\n\t\t\t\t<LayoutRow>\n\t\t\t\t\t<TextInput\n\t\t\t\t\t\tvalue={newColor ? colorToHexOptionalAlpha(newColor) : \"-\"}\n\t\t\t\t\t\t{disabled}\n\t\t\t\t\t\ton:commitText={({ detail }) => {\n\t\t\t\t\t\t\tdispatch(\"startHistoryTransaction\");\n\t\t\t\t\t\t\tsetColorCode(detail);\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tcentered={true}\n\t\t\t\t\t\ttooltipLabel=\"Hex Color Code\"\n\t\t\t\t\t\ttooltipDescription={hexDescription}\n\t\t\t\t\t\tbind:this={hexCodeInputWidget}\n\t\t\t\t\t/>\n\t\t\t\t</LayoutRow>\n\t\t\t</LayoutRow>\n\t\t\t<LayoutRow>\n\t\t\t\t<TextLabel tooltipLabel=\"Red/Green/Blue\" tooltipDescription=\"Integers 0–255.\">RGB</TextLabel>\n\t\t\t\t<Separator style=\"Related\" />\n\t\t\t\t<LayoutRow>\n\t\t\t\t\t{#each rgbChannels as [channel, strength], index}\n\t\t\t\t\t\t{#if index > 0}\n\t\t\t\t\t\t\t<Separator style=\"Related\" />\n\t\t\t\t\t\t{/if}\n\t\t\t\t\t\t<NumberInput\n\t\t\t\t\t\t\tvalue={strength}\n\t\t\t\t\t\t\t{disabled}\n\t\t\t\t\t\t\ton:value={({ detail }) => {\n\t\t\t\t\t\t\t\tstrength = detail;\n\t\t\t\t\t\t\t\tsetColorRGB(channel, detail);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\ton:startHistoryTransaction={() => {\n\t\t\t\t\t\t\t\tdispatch(\"startHistoryTransaction\");\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tmin={0}\n\t\t\t\t\t\t\tmax={255}\n\t\t\t\t\t\t\tminWidth={1}\n\t\t\t\t\t\t\tdisplayDecimalPlaces={0}\n\t\t\t\t\t\t\ttooltipLabel={{ r: \"Red Channel\", g: \"Green Channel\", b: \"Blue Channel\" }[channel]}\n\t\t\t\t\t\t\ttooltipDescription=\"Integers 0–255.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t{/each}\n\t\t\t\t</LayoutRow>\n\t\t\t</LayoutRow>\n\t\t\t<LayoutRow>\n\t\t\t\t<TextLabel\n\t\t\t\t\ttooltipLabel=\"Hue/Saturation/Value\"\n\t\t\t\t\ttooltipDescription=\"Also known as Hue/Saturation/Brightness (HSB). Not to be confused with Hue/Saturation/Lightness (HSL), a different color model.\">HSV</TextLabel\n\t\t\t\t>\n\t\t\t\t<Separator style=\"Related\" />\n\t\t\t\t<LayoutRow>\n\t\t\t\t\t{#each hsvChannels as [channel, strength], index}\n\t\t\t\t\t\t{#if index > 0}\n\t\t\t\t\t\t\t<Separator style=\"Related\" />\n\t\t\t\t\t\t{/if}\n\t\t\t\t\t\t<NumberInput\n\t\t\t\t\t\t\tvalue={strength}\n\t\t\t\t\t\t\t{disabled}\n\t\t\t\t\t\t\ton:value={({ detail }) => {\n\t\t\t\t\t\t\t\tstrength = detail;\n\t\t\t\t\t\t\t\tsetColorHSV(channel, detail);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\ton:startHistoryTransaction={() => {\n\t\t\t\t\t\t\t\tdispatch(\"startHistoryTransaction\");\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tmin={0}\n\t\t\t\t\t\t\tmax={channel === \"h\" ? 360 : 100}\n\t\t\t\t\t\t\tunit={channel === \"h\" ? \"°\" : \"%\"}\n\t\t\t\t\t\t\tminWidth={1}\n\t\t\t\t\t\t\tdisplayDecimalPlaces={1}\n\t\t\t\t\t\t\ttooltipLabel={{\n\t\t\t\t\t\t\t\th: \"Hue Component\",\n\t\t\t\t\t\t\t\ts: \"Saturation Component\",\n\t\t\t\t\t\t\t\tv: \"Value Component\",\n\t\t\t\t\t\t\t}[channel]}\n\t\t\t\t\t\t\ttooltipDescription={{\n\t\t\t\t\t\t\t\th: hueDescription,\n\t\t\t\t\t\t\t\ts: saturationDescription,\n\t\t\t\t\t\t\t\tv: valueDescription,\n\t\t\t\t\t\t\t}[channel]}\n\t\t\t\t\t\t/>\n\t\t\t\t\t{/each}\n\t\t\t\t</LayoutRow>\n\t\t\t</LayoutRow>\n\t\t\t<LayoutRow>\n\t\t\t\t{@const alphaDescription = \"The level of translucency, from transparent (0%) to opaque (100%).\"}\n\t\t\t\t<TextLabel tooltipLabel=\"Alpha\" tooltipDescription={alphaDescription}>Alpha</TextLabel>\n\t\t\t\t<Separator style=\"Related\" />\n\t\t\t\t<NumberInput\n\t\t\t\t\tvalue={!isNone ? alpha * 100 : undefined}\n\t\t\t\t\t{disabled}\n\t\t\t\t\ton:value={({ detail }) => {\n\t\t\t\t\t\tif (detail !== undefined) alpha = detail / 100;\n\t\t\t\t\t\tsetColorAlphaPercent(detail);\n\t\t\t\t\t}}\n\t\t\t\t\ton:startHistoryTransaction={() => {\n\t\t\t\t\t\tdispatch(\"startHistoryTransaction\");\n\t\t\t\t\t}}\n\t\t\t\t\tmin={0}\n\t\t\t\t\tmax={100}\n\t\t\t\t\trangeMin={0}\n\t\t\t\t\trangeMax={100}\n\t\t\t\t\tunit=\"%\"\n\t\t\t\t\tmode=\"Range\"\n\t\t\t\t\tdisplayDecimalPlaces={1}\n\t\t\t\t\ttooltipLabel=\"Alpha\"\n\t\t\t\t\ttooltipDescription={alphaDescription}\n\t\t\t\t/>\n\t\t\t</LayoutRow>\n\t\t\t<LayoutRow class=\"leftover-space\" />\n\t\t\t<LayoutRow>\n\t\t\t\t{#if allowNone && !gradient}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclass=\"preset-color none\"\n\t\t\t\t\t\t{disabled}\n\t\t\t\t\t\ton:click={() => setColorPreset(\"None\")}\n\t\t\t\t\t\tdata-tooltip-label=\"Set to No Color\"\n\t\t\t\t\t\tdata-tooltip-description={disabled ? \"Disabled (read-only).\" : \"\"}\n\t\t\t\t\t\ttabindex=\"0\"\n\t\t\t\t\t></button>\n\t\t\t\t\t<Separator style=\"Related\" />\n\t\t\t\t{/if}\n\t\t\t\t<button\n\t\t\t\t\tclass=\"preset-color black\"\n\t\t\t\t\t{disabled}\n\t\t\t\t\ton:click={() => setColorPreset(\"Black\")}\n\t\t\t\t\tdata-tooltip-label=\"Set to Black\"\n\t\t\t\t\tdata-tooltip-description={disabled ? \"Disabled (read-only).\" : \"\"}\n\t\t\t\t\ttabindex=\"0\"\n\t\t\t\t></button>\n\t\t\t\t<Separator style=\"Related\" />\n\t\t\t\t<button\n\t\t\t\t\tclass=\"preset-color white\"\n\t\t\t\t\t{disabled}\n\t\t\t\t\ton:click={() => setColorPreset(\"White\")}\n\t\t\t\t\tdata-tooltip-label=\"Set to White\"\n\t\t\t\t\tdata-tooltip-description={disabled ? \"Disabled (read-only).\" : \"\"}\n\t\t\t\t\ttabindex=\"0\"\n\t\t\t\t></button>\n\t\t\t\t<Separator style=\"Related\" />\n\t\t\t\t<button class=\"preset-color pure\" {disabled} tabindex=\"-1\">\n\t\t\t\t\t{#each PURE_COLORS_GRAYABLE as [preset, color, gray]}\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\ton:click={() => setColorPreset(preset)}\n\t\t\t\t\t\t\tstyle:--pure-color={color}\n\t\t\t\t\t\t\tstyle:--pure-color-gray={gray}\n\t\t\t\t\t\t\tdata-tooltip-label={`Set to ${preset}`}\n\t\t\t\t\t\t\tdata-tooltip-description={disabled ? \"Disabled (read-only).\" : \"\"}\n\t\t\t\t\t\t></div>\n\t\t\t\t\t{/each}\n\t\t\t\t</button>\n\t\t\t\t{#if eyedropperSupported()}\n\t\t\t\t\t<Separator style=\"Related\" />\n\t\t\t\t\t<IconButton icon=\"Eyedropper\" size={24} {disabled} action={activateEyedropperSample} tooltipLabel=\"Eyedropper\" tooltipDescription=\"Sample a pixel color from the document.\" />\n\t\t\t\t{/if}\n\t\t\t</LayoutRow>\n\t\t</LayoutCol>\n\t</LayoutRow>\n</FloatingMenu>\n\n<style lang=\"scss\" global>\n\t.color-picker {\n\t\t--widget-height: 24px;\n\t\t--picker-size: 256px;\n\t\t--picker-circle-radius: 6px;\n\n\t\t.pickers-and-gradient {\n\t\t\t.pickers {\n\t\t\t\t.saturation-value-picker {\n\t\t\t\t\twidth: var(--picker-size);\n\t\t\t\t\tbackground-blend-mode: multiply;\n\t\t\t\t\tbackground: linear-gradient(to bottom, #ffffff, #000000), linear-gradient(to right, #ffffff, var(--hue-color));\n\t\t\t\t\tposition: relative;\n\t\t\t\t}\n\n\t\t\t\t.saturation-value-picker,\n\t\t\t\t.hue-picker,\n\t\t\t\t.alpha-picker {\n\t\t\t\t\theight: var(--picker-size);\n\t\t\t\t\tborder-radius: 2px;\n\t\t\t\t\tposition: relative;\n\t\t\t\t\toverflow: hidden;\n\t\t\t\t}\n\n\t\t\t\t.hue-picker,\n\t\t\t\t.alpha-picker {\n\t\t\t\t\twidth: 24px;\n\t\t\t\t\tmargin-left: 8px;\n\t\t\t\t\tposition: relative;\n\t\t\t\t}\n\n\t\t\t\t.hue-picker {\n\t\t\t\t\t--selection-needle-color: var(--hue-color-contrasting);\n\t\t\t\t\tbackground-blend-mode: screen;\n\t\t\t\t\tbackground:\n\t\t\t\t\t\t// Reds\n\t\t\t\t\t\tlinear-gradient(to top, #ff0000ff calc(100% / 6), #ff000000 calc(200% / 6), #ff000000 calc(400% / 6), #ff0000ff calc(500% / 6)),\n\t\t\t\t\t\t// Greens\n\t\t\t\t\t\tlinear-gradient(to top, #00ff0000 0%, #00ff00ff calc(100% / 6), #00ff00ff 50%, #00ff0000 calc(400% / 6)),\n\t\t\t\t\t\t// Blues\n\t\t\t\t\t\tlinear-gradient(to top, #0000ff00 calc(200% / 6), #0000ffff 50%, #0000ffff calc(500% / 6), #0000ff00 100%);\n\t\t\t\t}\n\n\t\t\t\t.alpha-picker {\n\t\t\t\t\t--selection-needle-color: var(--new-color-contrasting);\n\t\t\t\t\tbackground-image: linear-gradient(to bottom, var(--opaque-color), transparent), var(--color-transparent-checkered-background);\n\t\t\t\t\tbackground-size:\n\t\t\t\t\t\t100% 100%,\n\t\t\t\t\t\tvar(--color-transparent-checkered-background-size);\n\t\t\t\t\tbackground-position:\n\t\t\t\t\t\t0 0,\n\t\t\t\t\t\tvar(--color-transparent-checkered-background-position);\n\t\t\t\t\tbackground-repeat: no-repeat, var(--color-transparent-checkered-background-repeat);\n\t\t\t\t}\n\n\t\t\t\t.selection-circle {\n\t\t\t\t\tpointer-events: none;\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\tleft: 0;\n\t\t\t\t\ttop: 0;\n\t\t\t\t\twidth: 0;\n\t\t\t\t\theight: 0;\n\n\t\t\t\t\t&::after {\n\t\t\t\t\t\tcontent: \"\";\n\t\t\t\t\t\tdisplay: block;\n\t\t\t\t\t\tposition: relative;\n\t\t\t\t\t\tleft: calc(-1 * var(--picker-circle-radius));\n\t\t\t\t\t\ttop: calc(-1 * var(--picker-circle-radius));\n\t\t\t\t\t\twidth: calc(var(--picker-circle-radius) * 2 + 1px);\n\t\t\t\t\t\theight: calc(var(--picker-circle-radius) * 2 + 1px);\n\t\t\t\t\t\tborder-radius: 50%;\n\t\t\t\t\t\tborder: 2px solid var(--opaque-color-contrasting);\n\t\t\t\t\t\tbackground: var(--opaque-color);\n\t\t\t\t\t\tbox-sizing: border-box;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t.selection-circle-axis-snap-line {\n\t\t\t\t\tpointer-events: none;\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\twidth: 1px;\n\t\t\t\t\theight: 1px;\n\t\t\t\t\ttop: 0;\n\t\t\t\t\tleft: 0;\n\t\t\t\t\tbackground: var(--opaque-color-contrasting);\n\n\t\t\t\t\t+ .selection-circle-axis-snap-line {\n\t\t\t\t\t\topacity: 0.25;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t.selection-needle {\n\t\t\t\t\tpointer-events: none;\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\ttop: 0;\n\t\t\t\t\twidth: 100%;\n\t\t\t\t\theight: 0;\n\n\t\t\t\t\t&::before {\n\t\t\t\t\t\tcontent: \"\";\n\t\t\t\t\t\tposition: absolute;\n\t\t\t\t\t\ttop: -4px;\n\t\t\t\t\t\tleft: 0;\n\t\t\t\t\t\tborder-style: solid;\n\t\t\t\t\t\tborder-width: 4px 0 4px 4px;\n\t\t\t\t\t\tborder-color: transparent transparent transparent var(--selection-needle-color);\n\t\t\t\t\t}\n\n\t\t\t\t\t&::after {\n\t\t\t\t\t\tcontent: \"\";\n\t\t\t\t\t\tposition: absolute;\n\t\t\t\t\t\ttop: -4px;\n\t\t\t\t\t\tright: 0;\n\t\t\t\t\t\tborder-style: solid;\n\t\t\t\t\t\tborder-width: 4px 4px 4px 0;\n\t\t\t\t\t\tborder-color: transparent var(--selection-needle-color) transparent transparent;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.gradient {\n\t\t\t\tmargin-top: 16px;\n\n\t\t\t\t.spectrum-input {\n\t\t\t\t\tflex: 1 1 100%;\n\t\t\t\t}\n\n\t\t\t\t.number-input {\n\t\t\t\t\tmargin-left: 8px;\n\t\t\t\t\tmin-width: 0;\n\t\t\t\t\twidth: calc(24px + 8px + 24px);\n\t\t\t\t\tflex: 0 0 auto;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t.details {\n\t\t\tmargin-left: 16px;\n\t\t\twidth: 200px;\n\t\t\tgap: 8px;\n\n\t\t\t> .layout-row {\n\t\t\t\theight: 24px;\n\t\t\t\tflex: 0 0 auto;\n\n\t\t\t\t> .text-label {\n\t\t\t\t\t// TODO: Use a table or grid layout for this width to match the widest label. Hard-coding it won't work when we add translation/localization.\n\t\t\t\t\tflex: 0 0 34px;\n\t\t\t\t\tline-height: 24px;\n\t\t\t\t}\n\n\t\t\t\t&.leftover-space {\n\t\t\t\t\tflex: 1 1 100%;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.choice-preview {\n\t\t\t\tflex: 0 0 auto;\n\t\t\t\twidth: 100%;\n\t\t\t\theight: 32px;\n\t\t\t\tborder-radius: 2px;\n\t\t\t\tbox-sizing: border-box;\n\t\t\t\toverflow: hidden;\n\t\t\t\tposition: relative;\n\n\t\t\t\t&.outlined::after {\n\t\t\t\t\tcontent: \"\";\n\t\t\t\t\tpointer-events: none;\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\ttop: 0;\n\t\t\t\t\tbottom: 0;\n\t\t\t\t\tleft: 0;\n\t\t\t\t\tright: 0;\n\t\t\t\t\tbox-shadow: inset 0 0 0 1px rgba(var(--color-0-black-rgb), var(--outline-amount));\n\t\t\t\t}\n\n\t\t\t\t&.transparency {\n\t\t\t\t\tbackground-image: var(--color-transparent-checkered-background);\n\t\t\t\t\tbackground-size: var(--color-transparent-checkered-background-size);\n\t\t\t\t\tbackground-position: var(--color-transparent-checkered-background-position);\n\t\t\t\t\tbackground-repeat: var(--color-transparent-checkered-background-repeat);\n\t\t\t\t}\n\n\t\t\t\t.swap-button-background {\n\t\t\t\t\toverflow: hidden;\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\tmix-blend-mode: multiply;\n\t\t\t\t\topacity: 0.25;\n\t\t\t\t\tborder-radius: 2px;\n\t\t\t\t\twidth: 16px;\n\t\t\t\t\theight: 16px;\n\t\t\t\t\ttop: 50%;\n\t\t\t\t\tleft: 50%;\n\t\t\t\t\ttransform: translate(-50%, -50%);\n\n\t\t\t\t\t&::before,\n\t\t\t\t\t&::after {\n\t\t\t\t\t\tcontent: \"\";\n\t\t\t\t\t\tposition: absolute;\n\t\t\t\t\t\twidth: 50%;\n\t\t\t\t\t\theight: 100%;\n\t\t\t\t\t}\n\n\t\t\t\t\t&::before {\n\t\t\t\t\t\tleft: 0;\n\t\t\t\t\t\tbackground: var(--new-color-contrasting);\n\t\t\t\t\t}\n\n\t\t\t\t\t&::after {\n\t\t\t\t\t\tright: 0;\n\t\t\t\t\t\tbackground: var(--old-color-contrasting);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t.swap-button {\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\ttransform: translate(-50%, -50%);\n\t\t\t\t\ttop: 50%;\n\t\t\t\t\tleft: 50%;\n\t\t\t\t}\n\n\t\t\t\t.new-color {\n\t\t\t\t\tbackground: var(--new-color);\n\n\t\t\t\t\t.text-label {\n\t\t\t\t\t\ttext-align: left;\n\t\t\t\t\t\tmargin: 2px 8px;\n\t\t\t\t\t\tcolor: var(--new-color-contrasting);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t.old-color {\n\t\t\t\t\tbackground: var(--old-color);\n\n\t\t\t\t\t.text-label {\n\t\t\t\t\t\ttext-align: right;\n\t\t\t\t\t\tmargin: 2px 8px;\n\t\t\t\t\t\tcolor: var(--old-color-contrasting);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t.new-color,\n\t\t\t\t.old-color {\n\t\t\t\t\twidth: 50%;\n\t\t\t\t\theight: 100%;\n\n\t\t\t\t\t&.none {\n\t\t\t\t\t\tbackground: var(--color-none);\n\t\t\t\t\t\tbackground-repeat: var(--color-none-repeat);\n\t\t\t\t\t\tbackground-position: var(--color-none-position);\n\t\t\t\t\t\tbackground-size: var(--color-none-size-32px);\n\t\t\t\t\t\tbackground-image: var(--color-none-image-32px);\n\n\t\t\t\t\t\t.text-label {\n\t\t\t\t\t\t\t// Many stacked white shadows helps to increase the opacity and approximate shadow spread which does not exist for text shadows\n\t\t\t\t\t\t\ttext-shadow:\n\t\t\t\t\t\t\t\t0 0 4px white,\n\t\t\t\t\t\t\t\t0 0 4px white,\n\t\t\t\t\t\t\t\t0 0 4px white,\n\t\t\t\t\t\t\t\t0 0 4px white,\n\t\t\t\t\t\t\t\t0 0 4px white,\n\t\t\t\t\t\t\t\t0 0 4px white,\n\t\t\t\t\t\t\t\t0 0 4px white,\n\t\t\t\t\t\t\t\t0 0 4px white,\n\t\t\t\t\t\t\t\t0 0 4px white,\n\t\t\t\t\t\t\t\t0 0 4px white;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.preset-color {\n\t\t\t\tborder: none;\n\t\t\t\tmargin: 0;\n\t\t\t\tpadding: 0;\n\t\t\t\tborder-radius: 2px;\n\t\t\t\theight: 24px;\n\t\t\t\tflex: 1 1 100%;\n\n\t\t\t\t&.none {\n\t\t\t\t\tbackground: var(--color-none);\n\t\t\t\t\tbackground-repeat: var(--color-none-repeat);\n\t\t\t\t\tbackground-position: var(--color-none-position);\n\t\t\t\t\tbackground-size: var(--color-none-size-24px);\n\t\t\t\t\tbackground-image: var(--color-none-image-24px);\n\n\t\t\t\t\t&,\n\t\t\t\t\t& ~ .black,\n\t\t\t\t\t& ~ .white {\n\t\t\t\t\t\twidth: 48px;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t&.black {\n\t\t\t\t\tbackground: black;\n\t\t\t\t}\n\n\t\t\t\t&.white {\n\t\t\t\t\tbackground: white;\n\t\t\t\t}\n\n\t\t\t\t&.pure {\n\t\t\t\t\twidth: 24px;\n\t\t\t\t\tfont-size: 0;\n\t\t\t\t\toverflow: hidden;\n\t\t\t\t\tflex: 0 0 auto;\n\n\t\t\t\t\tdiv {\n\t\t\t\t\t\tdisplay: inline-block;\n\t\t\t\t\t\twidth: calc(100% / 3);\n\t\t\t\t\t\theight: 50%;\n\t\t\t\t\t\t// For the least jarring luminance conversion, these colors are derived by placing a black layer with the \"desaturate\" blend mode over the colors.\n\t\t\t\t\t\t// We don't use the CSS `filter: grayscale(1);` property because it produces overly dark tones for bright colors with a noticeable jump on hover.\n\t\t\t\t\t\tbackground: var(--pure-color-gray);\n\t\t\t\t\t\ttransition: background-color 0.1s;\n\t\t\t\t\t}\n\n\t\t\t\t\t&:hover div {\n\t\t\t\t\t\tbackground: var(--pure-color);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t&.disabled .pickers-and-gradient .pickers :is(.saturation-value-picker, .hue-picker, .alpha-picker),\n\t\t&.disabled .details .preset-color,\n\t\t&.disabled .details .choice-preview {\n\t\t\ttransition: opacity 0.1s;\n\n\t\t\t&:hover {\n\t\t\t\topacity: 0.5;\n\t\t\t}\n\t\t}\n\n\t\t&.disabled .details .preset-color.pure:hover div {\n\t\t\tbackground: var(--pure-color-gray);\n\t\t}\n\t}\n\n\t// paddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpadding\n</style>\n"
  },
  {
    "path": "frontend/src/components/floating-menus/Dialog.svelte",
    "content": "<script lang=\"ts\">\n\timport { getContext, onMount } from \"svelte\";\n\n\timport { wipeDocuments } from \"@graphite/managers/persistence\";\n\timport type { DialogStore } from \"@graphite/stores/dialog\";\n\timport { crashReportUrl } from \"@graphite/utility-functions/crash-report\";\n\n\timport FloatingMenu from \"@graphite/components/layout/FloatingMenu.svelte\";\n\timport LayoutCol from \"@graphite/components/layout/LayoutCol.svelte\";\n\timport LayoutRow from \"@graphite/components/layout/LayoutRow.svelte\";\n\timport TextButton from \"@graphite/components/widgets/buttons/TextButton.svelte\";\n\timport IconLabel from \"@graphite/components/widgets/labels/IconLabel.svelte\";\n\timport TextLabel from \"@graphite/components/widgets/labels/TextLabel.svelte\";\n\timport WidgetLayout from \"@graphite/components/widgets/WidgetLayout.svelte\";\n\n\tconst dialog = getContext<DialogStore>(\"dialog\");\n\n\tlet self: FloatingMenu | undefined;\n\n\tonMount(() => {\n\t\t// Focus the button which is marked as emphasized, or otherwise the first button, in the popup\n\t\tconst button = self?.div?.()?.querySelector(\"[data-emphasized]\") || self?.div?.()?.querySelector(\"[data-text-button]\");\n\t\tconst emphasizedOrFirstButton = button instanceof HTMLButtonElement ? button : undefined;\n\t\temphasizedOrFirstButton?.focus();\n\t});\n</script>\n\n<!-- TODO: Use https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog for improved accessibility -->\n<FloatingMenu open={true} class=\"dialog\" type=\"Dialog\" direction=\"Center\" bind:this={self} data-dialog>\n\t<LayoutRow class=\"header-area\">\n\t\t{#if $dialog.icon}\n\t\t\t<IconLabel icon={$dialog.icon} />\n\t\t{/if}\n\t\t<TextLabel>{$dialog.title}</TextLabel>\n\t</LayoutRow>\n\t<LayoutRow class={`content ${$dialog.title === \"Demo Artwork\" ? \"center\" : \"\" /* TODO: Replace this with a less hacky approach that's compatible with localization/translation */}`}>\n\t\t<LayoutCol class=\"column-1\">\n\t\t\t{#if $dialog.column1.length > 0}\n\t\t\t\t<WidgetLayout layout={$dialog.column1} layoutTarget=\"DialogColumn1\" class=\"details\" />\n\t\t\t{/if}\n\t\t\t{#if $dialog.panicDetails}\n\t\t\t\t<div class=\"widget-layout details\">\n\t\t\t\t\t<div class=\"widget-span row\"><TextLabel bold={true}>The editor crashed — sorry about that</TextLabel></div>\n\t\t\t\t\t<div class=\"widget-span row\"><TextLabel>Please report this by filing an issue on GitHub:</TextLabel></div>\n\t\t\t\t\t<div class=\"widget-span row\"><TextButton label=\"Report Bug\" icon=\"Warning\" flush={true} action={() => window.open(crashReportUrl($dialog.panicDetails), \"_blank\")} /></div>\n\t\t\t\t\t<div class=\"widget-span row\"><TextLabel multiline={true}>Reload the editor to continue. If this occurs<br />immediately on repeated reloads, clear storage:</TextLabel></div>\n\t\t\t\t\t<div class=\"widget-span row\">\n\t\t\t\t\t\t<TextButton\n\t\t\t\t\t\t\tlabel=\"Clear Saved Documents\"\n\t\t\t\t\t\t\ticon=\"Trash\"\n\t\t\t\t\t\t\tflush={true}\n\t\t\t\t\t\t\taction={async () => {\n\t\t\t\t\t\t\t\tawait wipeDocuments();\n\t\t\t\t\t\t\t\twindow.location.reload();\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t{/if}\n\t\t</LayoutCol>\n\t\t{#if $dialog.column2.length > 0}\n\t\t\t<LayoutCol class=\"column-2\">\n\t\t\t\t<WidgetLayout layout={$dialog.column2} layoutTarget=\"DialogColumn2\" class=\"details\" />\n\t\t\t</LayoutCol>\n\t\t{/if}\n\t</LayoutRow>\n\t<LayoutRow class=\"footer-area\">\n\t\t{#if $dialog.buttons.length > 0}\n\t\t\t<WidgetLayout layout={$dialog.buttons} layoutTarget=\"DialogButtons\" class=\"details\" />\n\t\t{/if}\n\t\t{#if $dialog.panicDetails}\n\t\t\t<TextButton label=\"Copy Error Log\" action={() => navigator.clipboard.writeText($dialog.panicDetails)} />\n\t\t\t<TextButton label=\"Reload\" emphasized={true} action={() => window.location.reload()} />\n\t\t{/if}\n\t</LayoutRow>\n</FloatingMenu>\n\n<style lang=\"scss\" global>\n\t.dialog {\n\t\tposition: absolute;\n\t\tpointer-events: none;\n\t\twidth: 100%;\n\t\theight: 100%;\n\n\t\t> .floating-menu-container > .floating-menu-content {\n\t\t\tpointer-events: auto;\n\t\t\tpadding: 0;\n\t\t}\n\n\t\t.header-area,\n\t\t.footer-area {\n\t\t\tbackground: var(--color-1-nearblack);\n\t\t\tflex: 0 0 auto;\n\t\t}\n\n\t\t.header-area,\n\t\t.footer-area,\n\t\t.content {\n\t\t\tpadding: 16px 24px;\n\t\t}\n\n\t\t.header-area {\n\t\t\tborder-radius: 4px 4px 0 0;\n\n\t\t\t.icon-label {\n\t\t\t\twidth: 24px;\n\t\t\t\theight: 24px;\n\n\t\t\t\t+ .text-label {\n\t\t\t\t\tmargin-left: 12px;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.text-label {\n\t\t\t\tline-height: 24px;\n\t\t\t}\n\t\t}\n\n\t\t.content {\n\t\t\tmargin: -4px 0;\n\t\t\tpadding-right: calc(24px + 1px * var(--even-integer-subpixel-expansion-x));\n\t\t\tpadding-bottom: calc(16px + 1px * var(--even-integer-subpixel-expansion-y));\n\n\t\t\t&.center .row {\n\t\t\t\tjustify-content: center;\n\t\t\t}\n\n\t\t\t.column-1 + .column-2 {\n\t\t\t\tmargin-left: 48px;\n\n\t\t\t\t.text-button {\n\t\t\t\t\tjustify-content: left;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.radio-input button {\n\t\t\t\tflex-grow: 1;\n\t\t\t}\n\n\t\t\t.text-label.multiline {\n\t\t\t\t-webkit-user-select: text; // Still required by Safari as of 2026\n\t\t\t\tuser-select: text;\n\t\t\t}\n\n\t\t\t// Used by the \"Third-Party Software License Notices\" dialog\n\t\t\t.details:has(.text-label.multiline.monospace) {\n\t\t\t\tmax-height: 60vh;\n\t\t\t\tmax-width: 80vw;\n\t\t\t\toverflow: auto;\n\t\t\t}\n\n\t\t\t// Used by the \"Open Demo Artwork\" dialog\n\t\t\t.image-label {\n\t\t\t\tborder-radius: 2px;\n\t\t\t}\n\t\t}\n\n\t\t.footer-area {\n\t\t\tborder-radius: 0 0 4px 4px;\n\t\t\tjustify-content: right;\n\n\t\t\t.text-button {\n\t\t\t\tmin-width: 96px;\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/floating-menus/EyedropperPreview.svelte",
    "content": "<script lang=\"ts\" context=\"module\">\n\t// Should be equal to the width and height of the zoom preview canvas in the CSS\n\tconst ZOOM_WINDOW_DIMENSIONS_EXPANDED = 110;\n\t// Should be equal to the width and height of the `.pixel-outline` div in the CSS, and should be evenly divisible into the number above\n\tconst UPSCALE_FACTOR = 10;\n\n\texport const ZOOM_WINDOW_DIMENSIONS = ZOOM_WINDOW_DIMENSIONS_EXPANDED / UPSCALE_FACTOR;\n</script>\n\n<script lang=\"ts\">\n\timport { onMount } from \"svelte\";\n\n\timport FloatingMenu from \"@graphite/components/layout/FloatingMenu.svelte\";\n\n\tlet temporaryCanvas: HTMLCanvasElement | undefined;\n\tlet zoomPreviewCanvas: HTMLCanvasElement | undefined;\n\n\texport let imageData: ImageData | undefined = undefined;\n\texport let colorChoice: string;\n\texport let primaryColor: string;\n\texport let secondaryColor: string;\n\texport let x: number;\n\texport let y: number;\n\n\t$: displayImageDataPreview(imageData);\n\n\tfunction displayImageDataPreview(imageData: ImageData | undefined) {\n\t\tif (!zoomPreviewCanvas) return;\n\t\tconst context = zoomPreviewCanvas.getContext(\"2d\");\n\n\t\tif (!temporaryCanvas) {\n\t\t\ttemporaryCanvas = document.createElement(\"canvas\");\n\t\t\ttemporaryCanvas.width = ZOOM_WINDOW_DIMENSIONS;\n\t\t\ttemporaryCanvas.height = ZOOM_WINDOW_DIMENSIONS;\n\t\t}\n\t\tconst temporaryContext = temporaryCanvas.getContext(\"2d\");\n\n\t\tif (!imageData || !context || !temporaryContext) return;\n\n\t\ttemporaryContext.putImageData(imageData, 0, 0, 0, 0, ZOOM_WINDOW_DIMENSIONS, ZOOM_WINDOW_DIMENSIONS);\n\n\t\tcontext.fillStyle = \"black\";\n\t\tcontext.fillRect(0, 0, ZOOM_WINDOW_DIMENSIONS, ZOOM_WINDOW_DIMENSIONS);\n\n\t\tcontext.drawImage(temporaryCanvas, 0, 0);\n\t}\n\n\tonMount(() => {\n\t\tdisplayImageDataPreview(imageData);\n\t});\n</script>\n\n<FloatingMenu\n\topen={true}\n\tclass=\"eyedropper-preview\"\n\ttype=\"Cursor\"\n\tstyles={{ \"--ring-color-primary\": primaryColor, \"--ring-color-secondary\": secondaryColor, \"--ring-color-choice\": colorChoice, left: x + \"px\", top: y + \"px\" }}\n>\n\t<div class=\"ring\">\n\t\t<div class=\"canvas-container\">\n\t\t\t<canvas width={ZOOM_WINDOW_DIMENSIONS} height={ZOOM_WINDOW_DIMENSIONS} bind:this={zoomPreviewCanvas}></canvas>\n\t\t\t<div class=\"pixel-outline\"></div>\n\t\t</div>\n\t</div>\n</FloatingMenu>\n\n<style lang=\"scss\" global>\n\t.eyedropper-preview {\n\t\tpointer-events: none;\n\n\t\t.floating-menu-content.floating-menu-content {\n\t\t\tborder: none;\n\t\t\tmargin-left: 0;\n\t\t}\n\n\t\t.ring {\n\t\t\ttransform: translate(0, -50%) rotate(45deg);\n\t\t\tposition: relative;\n\t\t\tbackground: var(--ring-color-choice);\n\t\t\tpadding: 16px;\n\t\t\tborder: 8px solid;\n\t\t\tborder-radius: 50%;\n\t\t\tborder-top-color: var(--ring-color-primary);\n\t\t\tborder-left-color: var(--ring-color-primary);\n\t\t\tborder-bottom-color: var(--ring-color-secondary);\n\t\t\tborder-right-color: var(--ring-color-secondary);\n\n\t\t\t&::after {\n\t\t\t\tcontent: \"\";\n\t\t\t\tposition: absolute;\n\t\t\t\twidth: 100%;\n\t\t\t\theight: 100%;\n\t\t\t\ttop: -8px;\n\t\t\t\tleft: -8px;\n\t\t\t\tpadding: 8px;\n\t\t\t\tborder-radius: 50%;\n\t\t\t\tbox-shadow:\n\t\t\t\t\t0 0 0 1px rgba(255, 255, 255, 0.5),\n\t\t\t\t\t0 0 8px rgba(0, 0, 0, 0.25);\n\t\t\t}\n\n\t\t\t.canvas-container {\n\t\t\t\ttransform: rotate(-45deg);\n\n\t\t\t\tcanvas {\n\t\t\t\t\tdisplay: block;\n\t\t\t\t\twidth: 110px;\n\t\t\t\t\theight: 110px;\n\t\t\t\t\tborder-radius: 50%;\n\t\t\t\t\timage-rendering: pixelated;\n\t\t\t\t}\n\n\t\t\t\t&::after {\n\t\t\t\t\tcontent: \"\";\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\ttop: 0;\n\t\t\t\t\tleft: 0;\n\t\t\t\t\twidth: 100%;\n\t\t\t\t\theight: 100%;\n\t\t\t\t\tborder-radius: 50%;\n\t\t\t\t\tbox-shadow:\n\t\t\t\t\t\tinset 0 0 0 1px rgba(255, 255, 255, 0.5),\n\t\t\t\t\t\tinset 0 0 8px rgba(0, 0, 0, 0.25);\n\t\t\t\t}\n\n\t\t\t\t.pixel-outline {\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\tleft: 50%;\n\t\t\t\t\ttop: 50%;\n\t\t\t\t\ttransform: translate(-50%, -50%);\n\t\t\t\t\t--outline-width: 2;\n\t\t\t\t\tmargin-top: calc(-1px * (var(--outline-width) / 2));\n\t\t\t\t\twidth: calc(10px - (var(--outline-width) * 1px));\n\t\t\t\t\theight: calc(10px - var(--outline-width) * 1px);\n\t\t\t\t\tborder: calc(var(--outline-width) * 1px) solid var(--color-0-black);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/floating-menus/MenuList.svelte",
    "content": "<svelte:options accessors={true} />\n\n<script lang=\"ts\">\n\timport { createEventDispatcher, tick, onDestroy, onMount } from \"svelte\";\n\n\timport type { MenuListEntry, MenuDirection } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\n\timport MenuList from \"@graphite/components/floating-menus/MenuList.svelte\";\n\timport FloatingMenu from \"@graphite/components/layout/FloatingMenu.svelte\";\n\timport LayoutCol from \"@graphite/components/layout/LayoutCol.svelte\";\n\timport LayoutRow from \"@graphite/components/layout/LayoutRow.svelte\";\n\timport TextInput from \"@graphite/components/widgets/inputs/TextInput.svelte\";\n\timport IconLabel from \"@graphite/components/widgets/labels/IconLabel.svelte\";\n\timport Separator from \"@graphite/components/widgets/labels/Separator.svelte\";\n\timport ShortcutLabel from \"@graphite/components/widgets/labels/ShortcutLabel.svelte\";\n\timport TextLabel from \"@graphite/components/widgets/labels/TextLabel.svelte\";\n\n\tlet self: FloatingMenu | undefined;\n\tlet scroller: LayoutCol | undefined;\n\tlet searchTextInput: TextInput | undefined;\n\n\tconst dispatch = createEventDispatcher<{\n\t\topen: boolean;\n\t\tactiveEntry: MenuListEntry;\n\t\tselectedEntryValuePath: string[];\n\t\thoverInEntry: MenuListEntry;\n\t\thoverOutEntry: undefined;\n\t\tnaturalWidth: number;\n\t}>();\n\n\texport let parentsValuePath: string[] = [];\n\texport let entries: MenuListEntry[][];\n\texport let entriesHash: bigint;\n\texport let activeEntry: MenuListEntry | undefined = undefined;\n\texport let open: boolean;\n\texport let direction: MenuDirection = \"Bottom\";\n\texport let minWidth = 0;\n\texport let drawIcon = false;\n\texport let interactive = false;\n\texport let scrollableY = false;\n\texport let virtualScrolling = false;\n\n\t// Keep the child references outside of the entries array so as to avoid infinite recursion.\n\tlet childReferences: MenuList[][] = [];\n\tlet openChildValue: string | undefined = undefined;\n\tlet search = \"\";\n\tlet reactiveEntries = entries;\n\tlet highlighted: MenuListEntry | undefined = activeEntry;\n\tlet virtualScrollingEntriesStart = 0;\n\tlet keydownListenerAdded = false;\n\tlet destroyed = false;\n\n\t// `watchOpen` is called only when `open` is changed from outside this component\n\t$: watchOpen(open);\n\t$: watchEntries(entries);\n\t$: watchEntriesHash(entriesHash);\n\t$: watchRemeasureWidth(filteredEntries, drawIcon);\n\t$: watchHighlightedWithSearch(filteredEntries, open);\n\n\t$: virtualScrollingEntryHeight = virtualScrolling ? 20 : 0;\n\t$: filteredEntries = reactiveEntries.map((section) => section.filter((entry) => inSearch(search, entry)));\n\t$: startIndex = virtualScrollingEntryHeight ? virtualScrollingStartIndex : 0;\n\t// Virtual scrolling calculations\n\t$: virtualScrollingTotalHeight = filteredEntries.length === 0 ? 0 : filteredEntries[0].length * virtualScrollingEntryHeight;\n\t$: virtualScrollingStartIndex = filteredEntries.length === 0 ? 0 : Math.floor(virtualScrollingEntriesStart / virtualScrollingEntryHeight) || 0;\n\t$: virtualScrollingEndIndex = filteredEntries.length === 0 ? 0 : Math.min(filteredEntries[0].length, virtualScrollingStartIndex + 1 + 400 / virtualScrollingEntryHeight);\n\n\t// TODO: Move keyboard input handling entirely to the unified system in `input.ts`.\n\t// TODO: The current approach is hacky and blocks the allowances for shortcuts like the key to open the browser's dev tools.\n\tonMount(async () => {\n\t\tawait tick();\n\t\tif (!destroyed && open && !inNestedMenuList() && !keydownListenerAdded) {\n\t\t\taddEventListener(\"keydown\", keydown);\n\t\t\tkeydownListenerAdded = true;\n\t\t}\n\t});\n\tonDestroy(() => {\n\t\tremoveEventListener(\"keydown\", keydown);\n\t\t// Set the destroyed status in the closure kept by the awaited `tick()` in `onMount` in case that delayed run occurs after the component is destroyed\n\t\tdestroyed = true;\n\t});\n\n\tfunction inNestedMenuList(): boolean {\n\t\tconst div = self?.div();\n\t\tif (!(div instanceof HTMLDivElement)) return false;\n\t\treturn Boolean(div.closest(\"[data-floating-menu-content]\"));\n\t}\n\n\t// Required to keep the highlighted item centered and to find a new highlighted item if necessary\n\tasync function watchHighlightedWithSearch(filteredEntries: MenuListEntry[][], open: boolean) {\n\t\tif (highlighted && open) {\n\t\t\t// Allows the scrollable area to expand if necessary\n\t\t\tawait tick();\n\n\t\t\tconst flattened = filteredEntries.flat();\n\t\t\tconst highlightedFound = highlighted?.label && flattened.map((entry) => entry.label).includes(highlighted.label);\n\t\t\tconst newHighlighted = highlightedFound ? highlighted : flattened[0];\n\t\t\tsetHighlighted(newHighlighted);\n\t\t}\n\t}\n\n\t// Detect when the user types, which creates a search box\n\tasync function startSearch(e: KeyboardEvent) {\n\t\t// Only accept single-character symbol inputs other than space\n\t\tif (e.key.length !== 1 || e.key === \" \") return;\n\n\t\t// Stop shortcuts being activated\n\t\te.stopPropagation();\n\t\te.preventDefault();\n\n\t\t// Forward the input's first character to the search box, which after that point the user will continue typing into directly\n\t\tsearch = e.key;\n\n\t\t// Must wait until the DOM elements have been created (after the if condition becomes true) before the search box exists\n\t\tawait tick();\n\n\t\t// Get the search box element\n\t\tconst searchElement = searchTextInput?.element();\n\t\tif (!searchTextInput || !searchElement) return;\n\n\t\t// Focus the search box and move the cursor to the end\n\t\tsearchTextInput.focus();\n\t\tsearchElement.setSelectionRange(search.length, search.length);\n\n\t\t// Continue listening for keyboard navigation even when the search box is focused\n\t\t// searchElement.onkeydown = (e) => {\n\t\t// \tif ([\"Enter\", \"Escape\", \"ArrowUp\", \"ArrowDown\", \"ArrowLeft\", \"ArrowRight\"].includes(e.key)) {\n\t\t// \t\tkeydown(e, false);\n\t\t// \t}\n\t\t// };\n\t}\n\n\tfunction inSearch(search: string, entry: MenuListEntry): boolean {\n\t\treturn !search || entry.label.toLowerCase().includes(search.toLowerCase());\n\t}\n\n\tfunction watchOpen(open: boolean) {\n\t\tif (open && !inNestedMenuList() && !keydownListenerAdded) {\n\t\t\taddEventListener(\"keydown\", keydown);\n\t\t\tkeydownListenerAdded = true;\n\t\t} else if (!open && !inNestedMenuList() && keydownListenerAdded) {\n\t\t\tremoveEventListener(\"keydown\", keydown);\n\t\t\tkeydownListenerAdded = false;\n\t\t}\n\n\t\thighlighted = activeEntry;\n\t\tdispatch(\"open\", open);\n\n\t\tsearch = \"\";\n\t}\n\n\tfunction watchEntries(entries: MenuListEntry[][]) {\n\t\tentries.forEach((_, index) => {\n\t\t\tif (!childReferences[index]) childReferences[index] = [];\n\t\t});\n\t}\n\n\tfunction watchEntriesHash(_entriesHash: bigint) {\n\t\treactiveEntries = entries;\n\t}\n\n\tfunction watchRemeasureWidth(_: MenuListEntry[][], __: boolean) {\n\t\tself?.measureAndEmitNaturalWidth();\n\t}\n\n\tfunction onScroll(e: Event) {\n\t\tif (!virtualScrollingEntryHeight) return;\n\t\tvirtualScrollingEntriesStart = e.target instanceof HTMLElement ? e.target.scrollTop : 0;\n\t}\n\n\tfunction getChildReference(menuListEntry: MenuListEntry): MenuList | undefined {\n\t\tconst index = filteredEntries.flat().findIndex((entry) => entry.value === menuListEntry.value);\n\n\t\tif (index !== -1) {\n\t\t\treturn childReferences.flat().filter((x) => x)[index];\n\t\t} else {\n\t\t\t// eslint-disable-next-line no-console\n\t\t\tconsole.error(\"MenuListEntry not found in filteredEntries:\", menuListEntry);\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\tfunction onEntryClick(menuListEntry: MenuListEntry) {\n\t\t// Close the containing menu\n\t\tlet childReference = getChildReference(menuListEntry);\n\t\tif (childReference) {\n\t\t\topenChildValue = undefined;\n\t\t\treactiveEntries = reactiveEntries;\n\t\t}\n\t\tdispatch(\"open\", false);\n\t\topen = false;\n\t\treactiveEntries = reactiveEntries;\n\n\t\t// Notify the parent about the clicked entry as the new active entry\n\t\tdispatch(\"activeEntry\", menuListEntry);\n\t\tdispatch(\"selectedEntryValuePath\", [...parentsValuePath, menuListEntry.value]);\n\t}\n\n\tfunction onEntryPointerEnter(menuListEntry: MenuListEntry) {\n\t\tif (!menuListEntry.children?.length) {\n\t\t\tdispatch(\"hoverInEntry\", menuListEntry);\n\t\t\treturn;\n\t\t}\n\n\t\tlet childReference = getChildReference(menuListEntry);\n\t\tif (childReference) {\n\t\t\topenChildValue = menuListEntry.value;\n\t\t\treactiveEntries = reactiveEntries;\n\t\t} else {\n\t\t\tdispatch(\"open\", true);\n\t\t}\n\t}\n\n\tfunction onEntryPointerLeave(menuListEntry: MenuListEntry) {\n\t\tif (!menuListEntry.children?.length) {\n\t\t\tdispatch(\"hoverOutEntry\");\n\t\t\treturn;\n\t\t}\n\n\t\tlet childReference = getChildReference(menuListEntry);\n\t\tif (childReference) {\n\t\t\topenChildValue = undefined;\n\t\t\treactiveEntries = reactiveEntries;\n\t\t} else {\n\t\t\tdispatch(\"open\", false);\n\t\t}\n\t}\n\n\tfunction includeSeparator(entries: MenuListEntry[][], section: MenuListEntry[], sectionIndex: number, search: string): boolean {\n\t\tconst elementsBeforeCurrentSection = entries\n\t\t\t.slice(0, sectionIndex)\n\t\t\t.flat()\n\t\t\t.filter((entry) => inSearch(search, entry));\n\t\tconst entriesInCurrentSection = section.filter((entry) => inSearch(search, entry));\n\n\t\treturn elementsBeforeCurrentSection.length > 0 && entriesInCurrentSection.length > 0;\n\t}\n\n\tfunction currentEntries(section: MenuListEntry[], virtualScrollingEntryHeight: number, virtualScrollingStartIndex: number, virtualScrollingEndIndex: number, search: string) {\n\t\tif (!virtualScrollingEntryHeight) {\n\t\t\treturn section.filter((entry) => inSearch(search, entry));\n\t\t}\n\t\treturn section.filter((entry) => inSearch(search, entry)).slice(virtualScrollingStartIndex, virtualScrollingEndIndex);\n\t}\n\n\tfunction openSubmenu(highlightedEntry: MenuListEntry): boolean {\n\t\tlet childReference = getChildReference(highlightedEntry);\n\t\t// No submenu to open\n\t\tif (!childReference || !highlightedEntry.children?.length) return false;\n\n\t\topenChildValue = highlightedEntry.value;\n\t\t// The reason we bother taking `highlightdEntry` as an argument is because, when this function is called, it can ensure `highlightedEntry` is not undefined.\n\t\t// But here we still have to set `highlighted` to itself so Svelte knows to reactively update it after we set its `childReference.open` property.\n\t\thighlighted = highlighted;\n\n\t\t// Highlight first item\n\t\tchildReference.setHighlighted(highlightedEntry.children[0][0]);\n\n\t\t// Submenu was opened\n\t\treturn true;\n\t}\n\n\t/// Handles keyboard navigation for the menu.\n\t// Returns a boolean indicating whether the entire menu stack should be dismissed.\n\texport function keydown(e: KeyboardEvent, submenu = false): boolean {\n\t\tconst menuOpen = open;\n\t\tconst flatEntries = filteredEntries.flat().filter((entry) => !entry.disabled);\n\t\tconst openChild = (openChildValue !== undefined && flatEntries.findIndex((entry) => entry.value === openChildValue)) || -1;\n\n\t\t// Allow opening menu with space or enter\n\t\tif (!menuOpen && (e.key === \" \" || e.key === \"Enter\")) {\n\t\t\topen = true;\n\t\t\thighlighted = activeEntry;\n\n\t\t\t// Keep the menu stack open\n\t\t\treturn false;\n\t\t}\n\n\t\t// If a submenu is open, have it handle this instead\n\t\tif (menuOpen && openChild >= 0) {\n\t\t\tconst childMenuListEntry = flatEntries[openChild];\n\t\t\tconst childMenu = getChildReference(childMenuListEntry);\n\n\t\t\t// Redirect the keyboard navigation to a submenu if one is open\n\t\t\tconst shouldCloseStack = childMenu?.keydown(e, true) || false;\n\n\t\t\t// Highlight the menu item in the parent list that corresponds with the open submenu\n\t\t\tif (highlighted && e.key !== \"Escape\") setHighlighted(childMenuListEntry);\n\n\t\t\t// Handle the child closing the entire menu stack\n\t\t\tif (shouldCloseStack) open = false;\n\n\t\t\t// Keep the menu stack open\n\t\t\treturn shouldCloseStack;\n\t\t}\n\n\t\t// Navigate to the next and previous entries with arrow keys\n\t\tif ((menuOpen || interactive) && (e.key === \"ArrowUp\" || e.key === \"ArrowDown\")) {\n\t\t\tlet newIndex = e.key === \"ArrowUp\" ? flatEntries.length - 1 : 0;\n\t\t\tif (highlighted) {\n\t\t\t\tconst index = highlighted ? flatEntries.map((entry) => entry.label).indexOf(highlighted.label) : 0;\n\t\t\t\tnewIndex = index + (e.key === \"ArrowUp\" ? -1 : 1);\n\n\t\t\t\t// Interactive dropdowns should lock at the end whereas other dropdowns should loop\n\t\t\t\tif (interactive) newIndex = Math.min(flatEntries.length - 1, Math.max(0, newIndex));\n\t\t\t\telse newIndex = (newIndex + flatEntries.length) % flatEntries.length;\n\t\t\t}\n\n\t\t\tconst newEntry = flatEntries[newIndex];\n\t\t\tsetHighlighted(newEntry);\n\n\t\t\te.preventDefault();\n\n\t\t\t// Keep the menu stack open\n\t\t\treturn false;\n\t\t}\n\n\t\t// Close menu with escape key\n\t\tif (menuOpen && e.key === \"Escape\") {\n\t\t\topen = false;\n\n\t\t\t// Reset active to before open\n\t\t\tsetHighlighted(activeEntry);\n\n\t\t\t// Keep the menu stack open\n\t\t\treturn false;\n\t\t}\n\n\t\t// Click on a highlighted entry with the enter key\n\t\tif (menuOpen && highlighted && e.key === \"Enter\") {\n\t\t\t// Handle clicking on an option if enter is pressed\n\t\t\tif (!highlighted.children?.length) onEntryClick(highlighted);\n\t\t\telse openSubmenu(highlighted);\n\n\t\t\t// Stop the event from triggering a press on a new dialog\n\t\t\te.preventDefault();\n\n\t\t\t// Enter should close the entire menu stack\n\t\t\treturn true;\n\t\t}\n\n\t\t// Open a submenu with the right arrow key, space, or enter\n\t\tif (menuOpen && highlighted && (e.key === \"ArrowRight\" || e.key === \" \" || e.key === \"Enter\")) {\n\t\t\t// Right arrow opens a submenu\n\t\t\tconst openable = openSubmenu(highlighted);\n\n\t\t\t// Prevent the right arrow from moving the search text cursor if we are opening a submenu\n\t\t\tif (openable) e.preventDefault();\n\n\t\t\t// Keep the menu stack open\n\t\t\treturn false;\n\t\t}\n\n\t\t// Close a submenu with the left arrow key\n\t\tif (menuOpen && e.key === \"ArrowLeft\") {\n\t\t\t// Left arrow closes a submenu\n\t\t\tif (submenu) {\n\t\t\t\topen = false;\n\n\t\t\t\te.preventDefault();\n\t\t\t}\n\n\t\t\t// Keep the menu stack open\n\t\t\treturn false;\n\t\t}\n\n\t\t// Start a search with any other key\n\t\tif (menuOpen && search === \"\") {\n\t\t\tstartSearch(e);\n\n\t\t\t// Keep the menu stack open\n\t\t\treturn false;\n\t\t}\n\n\t\t// If nothing happened, keep the menu stack open\n\t\treturn false;\n\t}\n\n\texport function setHighlighted(newHighlight: MenuListEntry | undefined) {\n\t\thighlighted = newHighlight;\n\n\t\t// Interactive menus should keep the active entry the same as the highlighted one\n\t\t// if (interactive && newHighlight?.value !== activeEntry?.value && newHighlight) {\n\t\t// \tdispatch(\"activeEntry\", newHighlight);\n\t\t// }\n\n\t\t// Scroll into view\n\t\tlet container = scroller?.div?.();\n\t\tif (!container || !highlighted) return;\n\t\tlet containerBoundingRect = container.getBoundingClientRect();\n\t\tlet highlightedIndex = filteredEntries.flat().findIndex((entry) => entry.value === highlighted?.value);\n\n\t\tlet selectedBoundingRect = new DOMRect();\n\t\tif (virtualScrollingEntryHeight) {\n\t\t\t// Special case for virtual scrolling\n\t\t\tselectedBoundingRect.y = highlightedIndex * virtualScrollingEntryHeight - container.scrollTop + containerBoundingRect.y;\n\t\t\tselectedBoundingRect.height = virtualScrollingEntryHeight;\n\t\t} else {\n\t\t\tlet entries = Array.from(container.children).filter((element) => element.classList.contains(\"row\"));\n\t\t\tlet element = entries[highlightedIndex - startIndex];\n\t\t\tif (!element) return;\n\t\t\tcontainerBoundingRect = element.getBoundingClientRect();\n\t\t}\n\n\t\tif (containerBoundingRect.y > selectedBoundingRect.y) {\n\t\t\tcontainer.scrollBy(0, selectedBoundingRect.y - containerBoundingRect.y);\n\t\t}\n\t\tif (containerBoundingRect.y + containerBoundingRect.height < selectedBoundingRect.y + selectedBoundingRect.height) {\n\t\t\tcontainer.scrollBy(0, selectedBoundingRect.y - (containerBoundingRect.y + containerBoundingRect.height) + selectedBoundingRect.height);\n\t\t}\n\t}\n</script>\n\n<FloatingMenu\n\tclass=\"menu-list\"\n\t{open}\n\ton:open={({ detail }) => (open = detail)}\n\ton:naturalWidth\n\ttype=\"Dropdown\"\n\twindowEdgeMargin={0}\n\tescapeCloses={false}\n\t{direction}\n\t{minWidth}\n\tscrollableY={scrollableY && virtualScrollingEntryHeight === 0}\n\tbind:this={self}\n>\n\t{#if search.length > 0}\n\t\t<TextInput class=\"search\" value={search} on:value={({ detail }) => (search = detail)} bind:this={searchTextInput}></TextInput>\n\t{/if}\n\t<!-- If we put the scrollableY on the layoutcol for non-font dropdowns then for some reason it always creates a tiny scrollbar.\n\tHowever when we are using the virtual scrolling then we need the layoutcol to be scrolling so we can bind the events without using `self`. -->\n\t<LayoutCol\n\t\tbind:this={scroller}\n\t\tscrollableY={scrollableY && virtualScrollingEntryHeight !== 0}\n\t\ton:scroll={onScroll}\n\t\tstyles={{ \"min-width\": virtualScrollingEntryHeight ? `${minWidth}px` : `inherit` }}\n\t>\n\t\t{#if virtualScrollingEntryHeight}\n\t\t\t<LayoutRow class=\"scroll-spacer\" styles={{ height: `${virtualScrollingStartIndex * virtualScrollingEntryHeight}px` }} />\n\t\t{/if}\n\t\t{#each reactiveEntries as section, sectionIndex (sectionIndex)}\n\t\t\t{#if includeSeparator(reactiveEntries, section, sectionIndex, search)}\n\t\t\t\t<Separator style=\"Section\" direction=\"Vertical\" />\n\t\t\t{/if}\n\t\t\t{#each currentEntries(section, virtualScrollingEntryHeight, virtualScrollingStartIndex, virtualScrollingEndIndex, search) as entry, entryIndex (entryIndex + startIndex)}\n\t\t\t\t<LayoutRow\n\t\t\t\t\tclass=\"row\"\n\t\t\t\t\tclasses={{ open: openChildValue === entry.value, active: entry.label === highlighted?.label, disabled: Boolean(entry.disabled) }}\n\t\t\t\t\tstyles={{ height: virtualScrollingEntryHeight || \"20px\" }}\n\t\t\t\t\ttooltipLabel={entry.tooltipLabel}\n\t\t\t\t\ttooltipDescription={entry.tooltipDescription}\n\t\t\t\t\ttooltipShortcut={entry.tooltipShortcut}\n\t\t\t\t\ton:click={() => !entry.disabled && onEntryClick(entry)}\n\t\t\t\t\ton:pointerenter={() => !entry.disabled && onEntryPointerEnter(entry)}\n\t\t\t\t\ton:pointerleave={() => !entry.disabled && onEntryPointerLeave(entry)}\n\t\t\t\t>\n\t\t\t\t\t{#if entry.icon && drawIcon}\n\t\t\t\t\t\t<IconLabel icon={entry.icon} iconSizeOverride={16} class=\"entry-icon\" />\n\t\t\t\t\t{:else if drawIcon}\n\t\t\t\t\t\t<div class=\"no-icon\"></div>\n\t\t\t\t\t{/if}\n\n\t\t\t\t\t{#if entry.font}\n\t\t\t\t\t\t<link rel=\"stylesheet\" href={entry.font} />\n\t\t\t\t\t{/if}\n\n\t\t\t\t\t<TextLabel class=\"entry-label\" styles={entry.font ? { \"font-family\": entry.value } : {}}>{entry.label}</TextLabel>\n\n\t\t\t\t\t{#if entry.tooltipShortcut?.shortcut.length}\n\t\t\t\t\t\t<ShortcutLabel shortcut={entry.tooltipShortcut} />\n\t\t\t\t\t{/if}\n\n\t\t\t\t\t{#if entry.children?.length}\n\t\t\t\t\t\t<IconLabel class=\"submenu-arrow\" icon=\"DropdownArrow\" />\n\t\t\t\t\t{:else}\n\t\t\t\t\t\t<div class=\"no-submenu-arrow\"></div>\n\t\t\t\t\t{/if}\n\n\t\t\t\t\t{#if entry.children}\n\t\t\t\t\t\t<MenuList\n\t\t\t\t\t\t\ton:naturalWidth={({ detail }) => {\n\t\t\t\t\t\t\t\t// We do a manual dispatch here instead of just `on:naturalWidth` as a workaround for the <script> tag\n\t\t\t\t\t\t\t\t// at the top of this file displaying a \"'render' implicitly has return type 'any' because...\" error.\n\t\t\t\t\t\t\t\t// See explanation at <https://github.com/sveltejs/language-tools/issues/452#issuecomment-723148184>.\n\t\t\t\t\t\t\t\tdispatch(\"naturalWidth\", detail);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\ton:selectedEntryValuePath={({ detail }) => dispatch(\"selectedEntryValuePath\", detail)}\n\t\t\t\t\t\t\tparentsValuePath={[...parentsValuePath, entry.value]}\n\t\t\t\t\t\t\topen={openChildValue === entry.value}\n\t\t\t\t\t\t\tdirection=\"TopRight\"\n\t\t\t\t\t\t\tentries={entry.children}\n\t\t\t\t\t\t\tentriesHash={entry.childrenHash || 0n}\n\t\t\t\t\t\t\t{minWidth}\n\t\t\t\t\t\t\t{drawIcon}\n\t\t\t\t\t\t\t{scrollableY}\n\t\t\t\t\t\t\tbind:this={childReferences[sectionIndex][entryIndex + startIndex]}\n\t\t\t\t\t\t/>\n\t\t\t\t\t{/if}\n\t\t\t\t</LayoutRow>\n\t\t\t{/each}\n\t\t{/each}\n\t\t{#if virtualScrollingEntryHeight}\n\t\t\t<LayoutRow class=\"scroll-spacer\" styles={{ height: `${virtualScrollingTotalHeight - virtualScrollingEndIndex * virtualScrollingEntryHeight}px` }} />\n\t\t{/if}\n\t</LayoutCol>\n</FloatingMenu>\n\n<style lang=\"scss\" global>\n\t.menu-list {\n\t\t.search {\n\t\t\tmargin: 4px;\n\t\t\tmargin-top: 0;\n\t\t}\n\n\t\t.floating-menu-container .floating-menu-content.floating-menu-content {\n\t\t\tpadding: 4px 0;\n\n\t\t\t.separator {\n\t\t\t\tmargin: 4px 0;\n\n\t\t\t\tdiv {\n\t\t\t\t\tbackground: var(--color-3-darkgray);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.scroll-spacer {\n\t\t\t\tflex: 0 0 auto;\n\t\t\t}\n\n\t\t\t.row {\n\t\t\t\theight: 20px;\n\t\t\t\talign-items: center;\n\t\t\t\twhite-space: nowrap;\n\t\t\t\tposition: relative;\n\t\t\t\tflex: 0 0 auto;\n\t\t\t\tborder-radius: 2px;\n\t\t\t\tmargin: 0 4px;\n\n\t\t\t\t> * {\n\t\t\t\t\tflex: 0 0 auto;\n\t\t\t\t}\n\n\t\t\t\t.no-icon {\n\t\t\t\t\twidth: 16px;\n\t\t\t\t\theight: 16px;\n\t\t\t\t}\n\n\t\t\t\t.entry-label {\n\t\t\t\t\tflex: 1 1 100%;\n\t\t\t\t\tmargin: 0 4px;\n\t\t\t\t}\n\n\t\t\t\t.entry-icon,\n\t\t\t\t.no-icon {\n\t\t\t\t\tmargin: 0 4px;\n\t\t\t\t}\n\n\t\t\t\t.shortcut-label {\n\t\t\t\t\tmargin-left: 12px;\n\t\t\t\t}\n\n\t\t\t\t.submenu-arrow {\n\t\t\t\t\ttransform: rotate(270deg);\n\t\t\t\t}\n\n\t\t\t\t.no-submenu-arrow {\n\t\t\t\t\twidth: 12px;\n\t\t\t\t\theight: 12px;\n\t\t\t\t}\n\n\t\t\t\t// Extend the submenu to the right by the width of the margin outside the row, since we want the submenu to line up with the edge of the menu\n\t\t\t\t&.open {\n\t\t\t\t\t// Offset by the margin distance\n\t\t\t\t\t> .menu-list {\n\t\t\t\t\t\tmargin-right: -4px;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Extend the click target by the margin distance so the user can hover to the right of the row, within the margin area, and still have the submenu open\n\t\t\t\t\t&::after {\n\t\t\t\t\t\tcontent: \"\";\n\t\t\t\t\t\tposition: absolute;\n\t\t\t\t\t\ttop: 0;\n\t\t\t\t\t\tright: -4px;\n\t\t\t\t\t\twidth: 4px;\n\t\t\t\t\t\theight: 100%;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t&:hover,\n\t\t\t\t&.open {\n\t\t\t\t\tbackground: var(--color-4-dimgray);\n\t\t\t\t}\n\n\t\t\t\t&.active {\n\t\t\t\t\tbackground: var(--color-e-nearwhite);\n\t\t\t\t\tcolor: var(--color-2-mildblack);\n\n\t\t\t\t\t> .icon-label {\n\t\t\t\t\t\tfill: var(--color-2-mildblack);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t&.disabled {\n\t\t\t\t\tcolor: var(--color-8-uppergray);\n\n\t\t\t\t\t&:hover {\n\t\t\t\t\t\tbackground: none;\n\t\t\t\t\t}\n\n\t\t\t\t\tsvg {\n\t\t\t\t\t\tfill: var(--color-8-uppergray);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// paddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpadding\n</style>\n"
  },
  {
    "path": "frontend/src/components/floating-menus/NodeCatalog.svelte",
    "content": "<script lang=\"ts\">\n\timport { createEventDispatcher, getContext, onMount } from \"svelte\";\n\timport { SvelteMap } from \"svelte/reactivity\";\n\n\timport type { FrontendNodeType } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\timport type { NodeGraphStore } from \"@graphite/stores/node-graph\";\n\n\timport LayoutCol from \"@graphite/components/layout/LayoutCol.svelte\";\n\timport TextButton from \"@graphite/components/widgets/buttons/TextButton.svelte\";\n\timport TextInput from \"@graphite/components/widgets/inputs/TextInput.svelte\";\n\timport TextLabel from \"@graphite/components/widgets/labels/TextLabel.svelte\";\n\n\tconst dispatch = createEventDispatcher<{ selectNodeType: string }>();\n\tconst nodeGraph = getContext<NodeGraphStore>(\"nodeGraph\");\n\n\t// Content\n\texport let disabled = false;\n\t// Behavior\n\texport let initialSearchTerm = \"\";\n\n\tlet nodeSearchInput: TextInput | undefined = undefined;\n\tlet searchTerm = initialSearchTerm;\n\n\t$: nodeCategories = buildNodeCategories(searchTerm);\n\n\ttype NodeCategoryDetails = {\n\t\tnodes: FrontendNodeType[];\n\t\topen: boolean;\n\t};\n\n\tfunction buildNodeCategories(searchTerm: string): [string, NodeCategoryDetails][] {\n\t\tconst nodeTypes = $nodeGraph.nodeTypes;\n\t\tconst categories = new SvelteMap<string, NodeCategoryDetails>();\n\t\tconst isTypeSearch = searchTerm.toLowerCase().startsWith(\"type:\");\n\t\tlet typeSearchTerm = \"\";\n\t\tlet remainingSearchTerms = [searchTerm.toLowerCase()];\n\n\t\tif (isTypeSearch) {\n\t\t\t// Extract the first word after \"type:\" as the type search\n\t\t\tconst searchParts = searchTerm.substring(5).trim().split(/\\s+/);\n\t\t\ttypeSearchTerm = searchParts[0].toLowerCase();\n\n\t\t\tremainingSearchTerms = searchParts.slice(1).map((term) => term.toLowerCase());\n\t\t}\n\n\t\tnodeTypes.forEach((node) => {\n\t\t\tlet matchesTypeSearch = true;\n\t\t\tlet matchesRemainingTerms = true;\n\n\t\t\tif (isTypeSearch && typeSearchTerm) {\n\t\t\t\tmatchesTypeSearch = node.inputTypes?.some((inputType) => inputType.toLowerCase().includes(typeSearchTerm)) || false;\n\t\t\t}\n\n\t\t\tif (remainingSearchTerms.length > 0) {\n\t\t\t\tmatchesRemainingTerms = remainingSearchTerms.every((term) => {\n\t\t\t\t\tconst nameMatch = node.name.toLowerCase().includes(term);\n\t\t\t\t\tconst categoryMatch = node.category.toLowerCase().includes(term);\n\n\t\t\t\t\t// Quick and dirty hack to alias \"Layer\" to \"Merge\" in the search\n\t\t\t\t\tconst layerAliasMatch = node.name === \"Merge\" && \"layer\".includes(term);\n\n\t\t\t\t\treturn nameMatch || categoryMatch || layerAliasMatch;\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Node matches if it passes both type search and remaining terms filters\n\t\t\tconst includesSearchTerm = matchesTypeSearch && matchesRemainingTerms;\n\n\t\t\tif (searchTerm.length > 0 && !includesSearchTerm) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst category = categories.get(node.category);\n\t\t\tlet open = includesSearchTerm;\n\t\t\tif (searchTerm.length === 0) {\n\t\t\t\topen = false;\n\t\t\t}\n\n\t\t\tif (category) {\n\t\t\t\tcategory.open = category.open || open;\n\t\t\t\tcategory.nodes.push(node);\n\t\t\t} else {\n\t\t\t\tcategories.set(node.category, {\n\t\t\t\t\topen,\n\t\t\t\t\tnodes: [node],\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\n\t\tconst START_CATEGORIES_ORDER = [\"UNCATEGORIZED\", \"General\", \"Value\", \"Math\", \"Style\"];\n\t\tconst END_CATEGORIES_ORDER = [\"Debug\"];\n\t\treturn Array.from(categories)\n\t\t\t.sort((a, b) => a[0].localeCompare(b[0]))\n\t\t\t.sort((a, b) => {\n\t\t\t\tconst aIndex = START_CATEGORIES_ORDER.findIndex((x) => a[0].startsWith(x));\n\t\t\t\tconst bIndex = START_CATEGORIES_ORDER.findIndex((x) => b[0].startsWith(x));\n\t\t\t\tif (aIndex !== -1 && bIndex !== -1) return aIndex - bIndex;\n\t\t\t\tif (aIndex !== -1) return -1;\n\t\t\t\tif (bIndex !== -1) return 1;\n\t\t\t\treturn 0;\n\t\t\t})\n\t\t\t.sort((a, b) => {\n\t\t\t\tconst aIndex = END_CATEGORIES_ORDER.findIndex((x) => a[0].startsWith(x));\n\t\t\t\tconst bIndex = END_CATEGORIES_ORDER.findIndex((x) => b[0].startsWith(x));\n\t\t\t\tif (aIndex !== -1 && bIndex !== -1) return aIndex - bIndex;\n\t\t\t\tif (aIndex !== -1) return 1;\n\t\t\t\tif (bIndex !== -1) return -1;\n\t\t\t\treturn 0;\n\t\t\t});\n\t}\n\n\tonMount(() => {\n\t\tsetTimeout(() => nodeSearchInput?.focus(), 0);\n\t});\n</script>\n\n<LayoutCol class=\"node-catalog\">\n\t<TextInput placeholder=\"Search Nodes…\" value={searchTerm} on:value={({ detail }) => (searchTerm = detail)} bind:this={nodeSearchInput} />\n\t<div class=\"list-results\" on:wheel|passive|stopPropagation>\n\t\t{#each nodeCategories as nodeCategory}\n\t\t\t<details open={nodeCategory[1].open}>\n\t\t\t\t<summary>\n\t\t\t\t\t<TextLabel>{nodeCategory[0]}</TextLabel>\n\t\t\t\t</summary>\n\t\t\t\t{#each nodeCategory[1].nodes as nodeType}\n\t\t\t\t\t<TextButton\n\t\t\t\t\t\t{disabled}\n\t\t\t\t\t\tlabel={nodeType.name}\n\t\t\t\t\t\ttooltipLabel={nodeType.name}\n\t\t\t\t\t\ttooltipDescription={nodeType.identifier ? $nodeGraph.nodeDescriptions.get(nodeType.identifier) : undefined}\n\t\t\t\t\t\taction={() => dispatch(\"selectNodeType\", nodeType.identifier)}\n\t\t\t\t\t/>\n\t\t\t\t{/each}\n\t\t\t</details>\n\t\t{:else}\n\t\t\t<TextLabel>No search results</TextLabel>\n\t\t{/each}\n\t</div>\n</LayoutCol>\n\n<style lang=\"scss\" global>\n\t.node-catalog {\n\t\tmax-height: 30vh;\n\t\tmin-width: 250px;\n\n\t\t.text-input {\n\t\t\tflex: 0 0 auto;\n\t\t\tmargin-bottom: 4px;\n\t\t}\n\n\t\t.list-results {\n\t\t\toverflow-y: auto;\n\t\t\tflex: 1 1 auto;\n\n\t\t\tdetails {\n\t\t\t\tcursor: pointer;\n\t\t\t\tposition: relative;\n\t\t\t\tmargin-right: 4px;\n\n\t\t\t\t&[open] summary .text-label::before {\n\t\t\t\t\ttransform: rotate(90deg);\n\t\t\t\t}\n\n\t\t\t\tsummary {\n\t\t\t\t\tdisplay: flex;\n\n\t\t\t\t\t.text-label {\n\t\t\t\t\t\tpadding-left: 16px;\n\t\t\t\t\t\tposition: relative;\n\t\t\t\t\t\tpointer-events: none;\n\n\t\t\t\t\t\t&::before {\n\t\t\t\t\t\t\tcontent: \"\";\n\t\t\t\t\t\t\tposition: absolute;\n\t\t\t\t\t\t\tmargin: auto;\n\t\t\t\t\t\t\ttop: 0;\n\t\t\t\t\t\t\tbottom: 0;\n\t\t\t\t\t\t\tleft: 0;\n\t\t\t\t\t\t\twidth: 8px;\n\t\t\t\t\t\t\theight: 8px;\n\t\t\t\t\t\t\tbackground: var(--icon-expand-collapse-arrow);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t.text-button {\n\t\t\t\t\twidth: 100%;\n\t\t\t\t\tmargin: 4px 0;\n\t\t\t\t\ttext-align: left;\n\t\t\t\t}\n\n\t\t\t\t&:last-child .text-button {\n\t\t\t\t\tmargin-bottom: 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/floating-menus/Tooltip.svelte",
    "content": "<script lang=\"ts\">\n\timport { getContext } from \"svelte\";\n\n\timport type { LabeledShortcut } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\timport type { Editor } from \"@graphite/editor\";\n\timport type { TooltipStore } from \"@graphite/stores/tooltip\";\n\n\timport FloatingMenu from \"@graphite/components/layout/FloatingMenu.svelte\";\n\timport LayoutRow from \"@graphite/components/layout/LayoutRow.svelte\";\n\timport ShortcutLabel from \"@graphite/components/widgets/labels/ShortcutLabel.svelte\";\n\timport TextLabel from \"@graphite/components/widgets/labels/TextLabel.svelte\";\n\n\tconst tooltip = getContext<TooltipStore>(\"tooltip\");\n\tconst editor = getContext<Editor>(\"editor\");\n\n\tlet self: FloatingMenu | undefined;\n\n\t$: label = parseMarkdown(filterTodo($tooltip.element?.getAttribute(\"data-tooltip-label\")?.trim()));\n\t$: description = parseMarkdown(filterTodo($tooltip.element?.getAttribute(\"data-tooltip-description\")?.trim()));\n\t$: shortcutJSON = $tooltip.element?.getAttribute(\"data-tooltip-shortcut\")?.trim();\n\t$: shortcut = ((shortcutJSON) => {\n\t\tif (!shortcutJSON) return undefined;\n\t\ttry {\n\t\t\tconst parsed: LabeledShortcut = JSON.parse(shortcutJSON);\n\t\t\tif (!Array.isArray(parsed)) return undefined;\n\n\t\t\treturn parsed;\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t})(shortcutJSON);\n\n\t// TODO: Once all TODOs are replaced with real text, remove this function\n\tfunction filterTodo(text: string | undefined): string | undefined {\n\t\tif (text?.trim().toUpperCase() === \"TODO\" && !editor.handle.inDevelopmentMode()) return \"\";\n\t\treturn text;\n\t}\n\n\tfunction parseMarkdown(markdown: string | undefined): string | undefined {\n\t\tif (!markdown) return undefined;\n\n\t\tlet text = markdown.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\").replace(/\"/g, \"&quot;\").replace(/'/g, \"&apos;\");\n\n\t\treturn (\n\t\t\ttext\n\t\t\t\t// .split(\"\\n\")\n\t\t\t\t// .map((line) => line.trim())\n\t\t\t\t// .join(\"\\n\")\n\t\t\t\t// .split(\"\\n\\n\")\n\t\t\t\t// .map((paragraph) => paragraph.replaceAll(\"\\n\", \" \"))\n\t\t\t\t// .join(\"\\n\\n\")\n\t\t\t\t// Bold\n\t\t\t\t.replace(/\\*\\*((?:(?!\\*\\*).)+)\\*\\*/g, \"<strong>$1</strong>\")\n\t\t\t\t// Italic\n\t\t\t\t.replace(/\\*([^*]+)\\*/g, \"<em>$1</em>\")\n\t\t\t\t// Backticks\n\t\t\t\t.replace(/`([^`]+)`/g, \"<code>$1</code>\")\n\t\t);\n\t}\n</script>\n\n{#if label || description}\n\t<div class=\"tooltip\" style:top={`${$tooltip.position.y}px`} style:left={`${$tooltip.position.x}px`}>\n\t\t<FloatingMenu open={true} type=\"Tooltip\" direction=\"Bottom\" bind:this={self}>\n\t\t\t{#if label || shortcut}\n\t\t\t\t<LayoutRow class=\"tooltip-header\">\n\t\t\t\t\t{#if label}\n\t\t\t\t\t\t<TextLabel class=\"tooltip-label\">{@html label}</TextLabel>\n\t\t\t\t\t{/if}\n\t\t\t\t\t{#if shortcut}\n\t\t\t\t\t\t<ShortcutLabel shortcut={{ shortcut }} />\n\t\t\t\t\t{/if}\n\t\t\t\t</LayoutRow>\n\t\t\t{/if}\n\t\t\t{#if description}\n\t\t\t\t<TextLabel class=\"tooltip-description\">{@html description}</TextLabel>\n\t\t\t{/if}\n\t\t</FloatingMenu>\n\t</div>\n{/if}\n\n<style lang=\"scss\" global>\n\t.tooltip {\n\t\tposition: absolute;\n\t\tpointer-events: none;\n\t\twidth: 0;\n\t\theight: 0;\n\n\t\t.floating-menu-content {\n\t\t\tmax-width: Min(400px, 50vw);\n\n\t\t\t.tooltip-header + .tooltip-description {\n\t\t\t\tmargin-top: 4px;\n\t\t\t}\n\n\t\t\t.text-label {\n\t\t\t\twhite-space: pre-wrap;\n\t\t\t}\n\n\t\t\t.text-label + .shortcut-label {\n\t\t\t\tmargin-left: 8px;\n\t\t\t}\n\n\t\t\t.tooltip-description {\n\t\t\t\tcolor: var(--color-b-lightgray);\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/layout/ConditionalWrapper.svelte",
    "content": "<script lang=\"ts\">\n\texport let condition: boolean;\n\texport let wrapperClass = \"\";\n</script>\n\n{#if condition}\n\t<div class={wrapperClass}>\n\t\t<slot />\n\t</div>\n{:else}\n\t<slot />\n{/if}\n\n<style lang=\"scss\" global></style>\n"
  },
  {
    "path": "frontend/src/components/layout/FloatingMenu.svelte",
    "content": "<script lang=\"ts\" context=\"module\">\n\t/// Prevents the escape key from closing the parent floating menu of the given element.\n\t/// This works by momentarily setting the `data-escape-does-not-close` attribute on the parent floating menu element.\n\t/// After checking for the Escape key, it checks (in one `setTimeout`) for the attribute and ignores the key if it's present.\n\t/// Then after two calls of `setTimeout`, we can safely remove the attribute here.\n\texport function preventEscapeClosingParentFloatingMenu(element: HTMLElement) {\n\t\tconst floatingMenuParent = element.closest(\"[data-floating-menu-content]\") || undefined;\n\t\tif (floatingMenuParent instanceof HTMLElement) {\n\t\t\tfloatingMenuParent.setAttribute(\"data-escape-does-not-close\", \"\");\n\t\t\tsetTimeout(() => {\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tfloatingMenuParent.removeAttribute(\"data-escape-does-not-close\");\n\t\t\t\t}, 0);\n\t\t\t}, 0);\n\t\t}\n\t}\n</script>\n\n<script lang=\"ts\">\n\timport { onMount, onDestroy, afterUpdate, createEventDispatcher, tick } from \"svelte\";\n\n\timport type { MenuDirection } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\timport { browserVersion } from \"@graphite/utility-functions/platform\";\n\n\timport LayoutCol from \"@graphite/components/layout/LayoutCol.svelte\";\n\n\tconst BUTTON_LEFT = 0;\n\tconst POINTER_STRAY_DISTANCE = 100;\n\n\tconst dispatch = createEventDispatcher<{ open: boolean; naturalWidth: number }>();\n\n\tlet className = \"\";\n\texport { className as class };\n\texport let classes: Record<string, boolean> = {};\n\tlet styleName = \"\";\n\texport { styleName as style };\n\texport let styles: Record<string, string | number | undefined> = {};\n\texport let open: boolean;\n\texport let type: \"Popover\" | \"Tooltip\" | \"Dropdown\" | \"Dialog\" | \"Cursor\";\n\texport let direction: MenuDirection = \"Bottom\";\n\texport let windowEdgeMargin = 6;\n\texport let scrollableY = false;\n\texport let minWidth = 0;\n\texport let escapeCloses = true;\n\texport let strayCloses = true;\n\n\tlet tail: HTMLDivElement | undefined;\n\tlet self: HTMLDivElement | undefined;\n\tlet floatingMenuContainer: HTMLDivElement | undefined;\n\tlet floatingMenuContent: LayoutCol | undefined;\n\n\t// The resize observer is attached to the floating menu container, which is the zero-height div of the width of the parent element's floating menu spawner.\n\t// Since CSS doesn't let us make the floating menu (with `position: fixed`) have a 100% width of this container, we need to use JS to observe its size and\n\t// tell the floating menu content to use it as a min-width so the floating menu is at least the width of the parent element's floating menu spawner.\n\t// This is the opposite concern of the natural width measurement system, which gets the natural width of the floating menu content in order for the\n\t// spawner widget to optionally set its min-size to the floating menu's natural width.\n\tconst containerResizeObserver = new ResizeObserver((entries: ResizeObserverEntry[]) => {\n\t\tresizeObserverCallback(entries);\n\t});\n\n\tlet dialogResizeObserver: ResizeObserver | undefined;\n\tlet wasOpen = open;\n\tlet measuringOngoing = false;\n\tlet measuringOngoingGuard = false;\n\tlet minWidthParentWidth = 0;\n\tlet pointerStillDown = false;\n\tlet floatingMenuBounds = new DOMRect();\n\tlet floatingMenuContentBounds = new DOMRect();\n\n\t$: watchOpenChange(open);\n\n\t$: minWidthStyleValue = measuringOngoing ? \"0\" : `${Math.max(minWidth, minWidthParentWidth)}px`;\n\t$: displayTail = open && type === \"Popover\";\n\t$: displayContainer = open || measuringOngoing;\n\t$: extraClasses = Object.entries(classes)\n\t\t.flatMap(([className, stateName]) => (stateName ? [className] : []))\n\t\t.join(\" \");\n\t$: extraStyles = Object.entries(styles)\n\t\t.flatMap((styleAndValue) => (styleAndValue[1] !== undefined ? [`${styleAndValue[0]}: ${styleAndValue[1]};`] : []))\n\t\t.join(\" \");\n\n\t// Called only when `open` is changed from outside this component\n\tasync function watchOpenChange(isOpen: boolean) {\n\t\tsetSafariScrollableOverflow(isOpen);\n\n\t\t// Switching from closed to open\n\t\tif (isOpen && !wasOpen) {\n\t\t\t// TODO: Close any other floating menus that may already be open, which can happen using tab navigation and Enter/Space Bar to open\n\n\t\t\t// Close floating menu if pointer strays far enough away\n\t\t\twindow.addEventListener(\"pointermove\", pointerMoveHandler);\n\t\t\t// Close floating menu if esc is pressed\n\t\t\twindow.addEventListener(\"keydown\", keyDownHandler);\n\t\t\t// Close floating menu if pointer is outside (but within stray distance)\n\t\t\twindow.addEventListener(\"pointerdown\", pointerDownHandler);\n\t\t\t// Cancel the subsequent click event to prevent the floating menu from reopening if the floating menu's button is the click event target\n\t\t\twindow.addEventListener(\"pointerup\", pointerUpHandler);\n\n\t\t\t// Floating menu min-width resize observer\n\n\t\t\tawait tick();\n\n\t\t\t// Start a new observation of the now-open floating menu\n\t\t\tif (floatingMenuContainer) {\n\t\t\t\tcontainerResizeObserver.disconnect();\n\t\t\t\tcontainerResizeObserver.observe(floatingMenuContainer);\n\t\t\t}\n\t\t}\n\n\t\t// Switching from open to closed\n\t\tif (!isOpen && wasOpen) {\n\t\t\t// Clean up observation of the now-closed floating menu\n\t\t\tcontainerResizeObserver.disconnect();\n\n\t\t\twindow.removeEventListener(\"pointermove\", pointerMoveHandler);\n\t\t\twindow.removeEventListener(\"keydown\", keyDownHandler);\n\t\t\twindow.removeEventListener(\"pointerdown\", pointerDownHandler);\n\t\t\t// The `pointerup` event is removed in `pointerMoveHandler()` and `pointerDownHandler()`\n\t\t}\n\n\t\t// Now that we're done reading the old state, update it to the current state for next time\n\t\twasOpen = isOpen;\n\t}\n\n\t// Mitigate a Safari rendering bug which clips the floating menu extending beyond a scrollable container. The bug is possibly related to\n\t// <https://bugs.webkit.org/show_bug.cgi?id=160953>, but in our case it happens when `overflow` of a parent is `auto` rather than `hidden`.\n\t// The issue exists when the container is set to `overflow: auto` but fine when `overflow: hidden`. So this workaround temporarily sets\n\t// the scrollable container to `overflow: hidden`, thus removing the scrollbars and ability to scroll until the floating menu is closed.\n\tfunction setSafariScrollableOverflow(hidden: boolean) {\n\t\tif (!browserVersion().toLowerCase().includes(\"safari\")) return;\n\t\tconst scrollable = self?.closest(\"[data-scrollable-x], [data-scrollable-y]\");\n\t\tif (scrollable instanceof HTMLElement) scrollable.style.overflow = hidden ? \"hidden\" : \"\";\n\t}\n\n\tonMount(() => {\n\t\t// Measure the content and round up its width and height to the nearest even integer.\n\t\t// This solves antialiasing issues when the content isn't cleanly divisible by 2 and gets translated by (-50%, -50%) causing all its content to be blurry.\n\t\tconst floatingMenuContentDiv = floatingMenuContent?.div?.();\n\t\tif (type === \"Dialog\" && floatingMenuContentDiv) {\n\t\t\tdialogResizeObserver = new ResizeObserver((entries) => {\n\t\t\t\tentries.forEach((entry) => {\n\t\t\t\t\tconst existingWidth = Number(floatingMenuContentDiv.style.getPropertyValue(\"--even-integer-subpixel-expansion-x\"));\n\t\t\t\t\tconst existingHeight = Number(floatingMenuContentDiv.style.getPropertyValue(\"--even-integer-subpixel-expansion-y\"));\n\n\t\t\t\t\tlet { width, height } = entry.contentRect;\n\t\t\t\t\twidth -= existingWidth;\n\t\t\t\t\theight -= existingHeight;\n\n\t\t\t\t\tlet targetWidth = Math.ceil(width);\n\t\t\t\t\tif (targetWidth % 2 === 1) targetWidth += 1;\n\t\t\t\t\tlet targetHeight = Math.ceil(height);\n\t\t\t\t\tif (targetHeight % 2 === 1) targetHeight += 1;\n\n\t\t\t\t\t// We have to set the style properties directly because attempting to do it through a Svelte bound property results in `afterUpdate()` being triggered\n\t\t\t\t\tfloatingMenuContentDiv.style.setProperty(\"--even-integer-subpixel-expansion-x\", `${targetWidth - width}`);\n\t\t\t\t\tfloatingMenuContentDiv.style.setProperty(\"--even-integer-subpixel-expansion-y\", `${targetHeight - height}`);\n\t\t\t\t});\n\t\t\t});\n\t\t\tdialogResizeObserver.observe(floatingMenuContentDiv);\n\t\t}\n\t});\n\n\tonDestroy(() => {\n\t\tcontainerResizeObserver.disconnect();\n\t\tdialogResizeObserver?.disconnect();\n\t\twindow.removeEventListener(\"pointermove\", pointerMoveHandler);\n\t\twindow.removeEventListener(\"keydown\", keyDownHandler);\n\t\twindow.removeEventListener(\"pointerdown\", pointerDownHandler);\n\t\twindow.removeEventListener(\"pointerup\", pointerUpHandler);\n\t\twindow.removeEventListener(\"click\", clickHandlerCapture, true);\n\n\t\t// Revert Safari overflow workaround if the menu was open when destroyed\n\t\tif (open) setSafariScrollableOverflow(false);\n\t});\n\n\tafterUpdate(() => {\n\t\t// Gets the client bounds of the elements and apply relevant styles to them.\n\t\t// TODO: Use DOM attribute bindings more whilst not causing recursive updates. Turning measuring on and off both causes the component to change,\n\t\t// TODO: which causes the `afterUpdate()` Svelte event to fire extraneous times (hurting performance and sometimes causing an infinite loop).\n\t\tif (!measuringOngoingGuard) positionAndStyleFloatingMenu();\n\t});\n\n\tfunction resizeObserverCallback(entries: ResizeObserverEntry[]) {\n\t\tminWidthParentWidth = entries[0].contentRect.width;\n\t}\n\n\tfunction positionAndStyleFloatingMenu() {\n\t\tif (type === \"Cursor\") return;\n\n\t\tconst floatingMenuContentDiv = floatingMenuContent?.div?.();\n\t\tif (!self || !floatingMenuContainer || !floatingMenuContent || !floatingMenuContentDiv) return;\n\n\t\tconst windowBounds = document.documentElement.getBoundingClientRect();\n\t\tfloatingMenuBounds = self.getBoundingClientRect();\n\t\tconst floatingMenuContainerBounds = floatingMenuContainer.getBoundingClientRect();\n\t\tfloatingMenuContentBounds = floatingMenuContentDiv.getBoundingClientRect();\n\n\t\tconst overflowingLeft = floatingMenuContentBounds.left - windowEdgeMargin <= windowBounds.left;\n\t\tconst overflowingRight = floatingMenuContentBounds.right + windowEdgeMargin >= windowBounds.right;\n\t\tconst overflowingTop = floatingMenuContentBounds.top - windowEdgeMargin <= windowBounds.top;\n\t\tconst overflowingBottom = floatingMenuContentBounds.bottom + windowEdgeMargin >= windowBounds.bottom;\n\n\t\t// TODO: Make this work for all types. This is currently limited to tooltips because they're inherently small and transient.\n\t\t// TODO: But on popovers and dropdowns, it's a bit harder to do this right. First we check if it's overflowing and flip the direction to avoid the overflow.\n\t\t// TODO: But once it's flipped, if the position moves and the menu would no longer be overflowing, we're still flipped and thus unable to automatically notice the need to flip back.\n\t\t// TODO: So as a result, once flipped, it stays flipped forever even if the menu spawner element is moved back away from the edge of the window.\n\t\tif (type === \"Tooltip\") {\n\t\t\t// Flip direction if overflowing the edge of the window\n\t\t\tif (direction === \"Top\" && overflowingTop) direction = \"Bottom\";\n\t\t\telse if (direction === \"Bottom\" && overflowingBottom) direction = \"Top\";\n\t\t\telse if (direction === \"Left\" && overflowingLeft) direction = \"Right\";\n\t\t\telse if (direction === \"Right\" && overflowingRight) direction = \"Left\";\n\t\t}\n\n\t\tconst inParentFloatingMenu = Boolean(floatingMenuContainer.closest(\"[data-floating-menu-content]\"));\n\t\tconst noPosition = Boolean(floatingMenuContainer.closest(\"[data-floating-menu-no-position]\"));\n\t\tif (!inParentFloatingMenu && !noPosition) {\n\t\t\t// Required to correctly position content when scrolled (it has a `position: fixed` to prevent clipping)\n\t\t\t// We use `.style` on a div (instead of a style DOM attribute binding) because the binding causes the `afterUpdate()` hook to call the function we're in recursively forever\n\t\t\tlet tailOffset = 0;\n\t\t\tif (type === \"Popover\") tailOffset = 10;\n\t\t\tif (type === \"Tooltip\") tailOffset = direction === \"Bottom\" ? 20 : 10;\n\n\t\t\tif (direction === \"Bottom\") floatingMenuContentDiv.style.top = `${tailOffset + floatingMenuBounds.y}px`;\n\t\t\tif (direction === \"Top\") floatingMenuContentDiv.style.bottom = `${tailOffset + (windowBounds.height - floatingMenuBounds.y)}px`;\n\t\t\tif (direction === \"Right\") floatingMenuContentDiv.style.left = `${tailOffset + floatingMenuBounds.x}px`;\n\t\t\tif (direction === \"Left\") floatingMenuContentDiv.style.right = `${tailOffset + (windowBounds.width - floatingMenuBounds.x)}px`;\n\n\t\t\t// Required to correctly position tail when scrolled (it has a `position: fixed` to prevent clipping)\n\t\t\t// We use `.style` on a div (instead of a style DOM attribute binding) because the binding causes the `afterUpdate()` hook to call the function we're in recursively forever\n\t\t\tif (tail && direction === \"Bottom\") tail.style.top = `${floatingMenuBounds.y}px`;\n\t\t\tif (tail && direction === \"Top\") tail.style.bottom = `${windowBounds.height - floatingMenuBounds.y}px`;\n\t\t\tif (tail && direction === \"Right\") tail.style.left = `${floatingMenuBounds.x}px`;\n\t\t\tif (tail && direction === \"Left\") tail.style.right = `${windowBounds.width - floatingMenuBounds.x}px`;\n\t\t}\n\n\t\ttype Edge = \"Top\" | \"Bottom\" | \"Left\" | \"Right\";\n\t\tlet zeroedBorderVertical: Edge | undefined;\n\t\tlet zeroedBorderHorizontal: Edge | undefined;\n\n\t\tif (direction === \"Top\" || direction === \"Bottom\") {\n\t\t\tzeroedBorderVertical = direction === \"Top\" ? \"Bottom\" : \"Top\";\n\n\t\t\t// We use `.style` on a div (instead of a style DOM attribute binding) because the binding causes the `afterUpdate()` hook to call the function we're in recursively forever\n\t\t\tif (overflowingLeft) {\n\t\t\t\tfloatingMenuContentDiv.style.left = `${windowEdgeMargin}px`;\n\t\t\t\tif (windowBounds.left + floatingMenuContainerBounds.left === 12) zeroedBorderHorizontal = \"Left\";\n\t\t\t}\n\t\t\tif (overflowingRight) {\n\t\t\t\tfloatingMenuContentDiv.style.right = `${windowEdgeMargin}px`;\n\t\t\t\tif (windowBounds.right - floatingMenuContainerBounds.right === 12) zeroedBorderHorizontal = \"Right\";\n\t\t\t}\n\t\t}\n\t\tif (direction === \"Left\" || direction === \"Right\") {\n\t\t\tzeroedBorderHorizontal = direction === \"Left\" ? \"Right\" : \"Left\";\n\n\t\t\t// We use `.style` on a div (instead of a style DOM attribute binding) because the binding causes the `afterUpdate()` hook to call the function we're in recursively forever\n\t\t\tif (overflowingTop) {\n\t\t\t\tfloatingMenuContentDiv.style.top = `${windowEdgeMargin}px`;\n\t\t\t\tif (windowBounds.top + floatingMenuContainerBounds.top === 12) zeroedBorderVertical = \"Top\";\n\t\t\t}\n\t\t\tif (overflowingBottom) {\n\t\t\t\tfloatingMenuContentDiv.style.bottom = `${windowEdgeMargin}px`;\n\t\t\t\tif (windowBounds.bottom - floatingMenuContainerBounds.bottom === 12) zeroedBorderVertical = \"Bottom\";\n\t\t\t}\n\t\t}\n\n\t\t// Remove the rounded corner from the content where the tail perfectly meets the corner\n\t\tif (displayTail && windowEdgeMargin === 6 && zeroedBorderVertical && zeroedBorderHorizontal) {\n\t\t\t// We use `.style` on a div (instead of a style DOM attribute binding) because the binding causes the `afterUpdate()` hook to call the function we're in recursively forever\n\t\t\tswitch (`${zeroedBorderVertical}${zeroedBorderHorizontal}`) {\n\t\t\t\tcase \"TopLeft\":\n\t\t\t\t\tfloatingMenuContentDiv.style.borderTopLeftRadius = \"0\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"TopRight\":\n\t\t\t\t\tfloatingMenuContentDiv.style.borderTopRightRadius = \"0\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"BottomLeft\":\n\t\t\t\t\tfloatingMenuContentDiv.style.borderBottomLeftRadius = \"0\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"BottomRight\":\n\t\t\t\t\tfloatingMenuContentDiv.style.borderBottomRightRadius = \"0\";\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\texport function div(): HTMLDivElement | undefined {\n\t\treturn self;\n\t}\n\n\t// To be called by the parent component. Measures the actual width of the floating menu content element and returns it in a promise.\n\texport async function measureAndEmitNaturalWidth() {\n\t\tif (!measuringOngoingGuard) return;\n\n\t\t// Wait for the changed content which fired the `afterUpdate()` Svelte event to be put into the DOM\n\t\tawait tick();\n\n\t\t// Wait until all fonts have been loaded and rendered so measurements of content involving text are accurate\n\t\tawait document.fonts.ready;\n\n\t\t// Make the component show itself with 0 min-width so it can be measured, and wait until the values have been updated to the DOM\n\t\tmeasuringOngoing = true;\n\t\tmeasuringOngoingGuard = true;\n\t\tawait tick();\n\n\t\t// Measure the width of the floating menu content element, if it's currently visible\n\t\t// The result will be `undefined` if the menu is invisible, perhaps because an ancestor component is hidden with a falsy Svelte template if condition\n\t\tconst naturalWidth: number | undefined = floatingMenuContent?.div?.()?.clientWidth;\n\n\t\t// Turn off measuring mode for the component, which triggers another call to the `afterUpdate()` Svelte event, so we can turn off the protection after that has happened\n\t\tmeasuringOngoing = false;\n\t\tawait tick();\n\t\tmeasuringOngoingGuard = false;\n\n\t\t// Notify the parent about the measured natural width\n\t\tif (naturalWidth !== undefined && naturalWidth >= 0) {\n\t\t\tdispatch(\"naturalWidth\", naturalWidth);\n\t\t}\n\t}\n\n\tfunction pointerMoveHandler(e: PointerEvent) {\n\t\t// This element and the element being hovered over\n\t\tconst target = e.target instanceof HTMLElement ? e.target : undefined;\n\n\t\t// Get the spawner element (that which is clicked to spawn this floating menu)\n\t\t// Assumes the spawner is a sibling of this FloatingMenu component\n\t\tconst ownSpawner: HTMLElement | undefined = self?.parentElement?.querySelector(\":scope > [data-floating-menu-spawner]\") || undefined;\n\t\t// Get the spawner element containing whatever element the user is hovering over now, if there is one\n\t\tconst targetSpawner: HTMLElement | undefined = target?.closest?.(\"[data-floating-menu-spawner]\") || undefined;\n\n\t\t// HOVER TRANSFER\n\t\t// Transfer from this open floating menu to a sibling floating menu if the pointer hovers to a valid neighboring floating menu spawner\n\t\thoverTransfer(self, ownSpawner, targetSpawner);\n\n\t\t// POINTER STRAY\n\t\t// Close the floating menu if the pointer has strayed far enough from its bounds (and it's not hovering over its own spawner)\n\t\tconst notHoveringOverOwnSpawner = ownSpawner !== targetSpawner;\n\t\tif (strayCloses && notHoveringOverOwnSpawner && isPointerEventOutsideFloatingMenu(e, POINTER_STRAY_DISTANCE)) {\n\t\t\t// TODO: Extend this rectangle bounds check to all submenu bounds up the DOM tree since currently submenus disappear\n\t\t\t// TODO: with zero stray distance if the cursor is further than the stray distance from only the top-level menu\n\t\t\tdispatch(\"open\", false);\n\t\t}\n\n\t\t// Clean up any messes from lost pointerup events\n\t\tconst BUTTONS_LEFT = 0b0000_0001;\n\t\tconst eventIncludesLmb = Boolean(e.buttons & BUTTONS_LEFT);\n\t\tif (!open && !eventIncludesLmb) {\n\t\t\tpointerStillDown = false;\n\t\t\twindow.removeEventListener(\"pointerup\", pointerUpHandler);\n\t\t}\n\t}\n\n\tfunction hoverTransfer(self: HTMLDivElement | undefined, ownSpawner: HTMLElement | undefined, targetSpawner: HTMLElement | undefined) {\n\t\t// Algorithm pseudo-code to detect and transfer to hover-transferrable floating menu spawners\n\t\t// Accompanying diagram: <https://files.keavon.com/-/SpringgreenKnownXantus/capture.png>\n\t\t//\n\t\t// Check our own parent for descendant spawners\n\t\t// Filter out ourself and our children\n\t\t// Filter out all with a different distance than our own distance from the currently-being-checked parent\n\t\t// How many left?\n\t\t//     None -> go up a level and repeat\n\t\t//     Some -> is one of them the target?\n\t\t//         Yes -> click it and terminate\n\t\t//         No -> do nothing and terminate\n\n\t\t// Helper function that gets used below\n\t\tconst getDepthFromAncestor = (item: Element, ancestor: Element): number | undefined => {\n\t\t\tlet depth = 1;\n\n\t\t\tlet parent = item.parentElement || undefined;\n\t\t\twhile (parent) {\n\t\t\t\tif (parent === ancestor) return depth;\n\n\t\t\t\tparent = parent.parentElement || undefined;\n\t\t\t\tdepth += 1;\n\t\t\t}\n\n\t\t\treturn undefined;\n\t\t};\n\n\t\t// A list of all the descendant spawners: the spawner for this floating menu plus any spawners belonging to widgets inside this floating menu\n\t\tconst ownDescendantMenuSpawners = Array.from(self?.parentElement?.querySelectorAll(\"[data-floating-menu-spawner]\") || []);\n\n\t\t// Start with the parent of the spawner for this floating menu and keep widening the search for any other valid spawners that are hover-transferrable\n\t\tlet currentAncestor = (targetSpawner && ownSpawner?.parentElement) || undefined;\n\t\twhile (currentAncestor) {\n\t\t\t// If the current ancestor blocks hover transfer, stop searching\n\t\t\tif (currentAncestor.hasAttribute(\"data-block-hover-transfer\")) break;\n\n\t\t\tconst ownSpawnerDepthFromCurrentAncestor = ownSpawner && getDepthFromAncestor(ownSpawner, currentAncestor);\n\t\t\tconst currentAncestor2 = currentAncestor; // This duplicate variable avoids an ESLint warning\n\n\t\t\t// Get the list of descendant spawners and filter out invalid possibilities for spawners that are hover-transferrable\n\t\t\tconst listOfDescendantSpawners = Array.from(currentAncestor?.querySelectorAll(\"[data-floating-menu-spawner]\") || []);\n\t\t\tconst filteredListOfDescendantSpawners = listOfDescendantSpawners.filter((item: Element): boolean => {\n\t\t\t\t// Filter away ourself and our descendants\n\t\t\t\tconst notOurself = !ownDescendantMenuSpawners.includes(item);\n\t\t\t\t// And filter away unequal depths from the current ancestor\n\t\t\t\tconst notUnequalDepths = notOurself && getDepthFromAncestor(item, currentAncestor2) === ownSpawnerDepthFromCurrentAncestor;\n\t\t\t\t// And filter away descendants that explicitly disable hover transfer\n\t\t\t\treturn notUnequalDepths && !(item instanceof HTMLElement && item.hasAttribute(\"data-block-hover-transfer\"));\n\t\t\t});\n\n\t\t\t// If none were found, widen the search by a level and keep trying (or stop looping if the root was reached)\n\t\t\tif (filteredListOfDescendantSpawners.length === 0) {\n\t\t\t\tcurrentAncestor = currentAncestor?.parentElement || undefined;\n\t\t\t}\n\t\t\t// Stop after the first non-empty set was found\n\t\t\telse {\n\t\t\t\tconst foundTarget = filteredListOfDescendantSpawners.find((item: Element): boolean => item === targetSpawner);\n\t\t\t\t// If the currently hovered spawner is one of the found valid hover-transferrable spawners, swap to it by clicking on it\n\t\t\t\tif (foundTarget instanceof HTMLElement) {\n\t\t\t\t\tdispatch(\"open\", false);\n\t\t\t\t\tfoundTarget.click();\n\t\t\t\t}\n\n\t\t\t\t// In either case, we are done searching\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tfunction keyDownHandler(e: KeyboardEvent) {\n\t\tif (escapeCloses && e.key === \"Escape\") {\n\t\t\tsetTimeout(() => {\n\t\t\t\tif (!floatingMenuContainer?.querySelector(\"[data-floating-menu-content][data-escape-does-not-close]\")) {\n\t\t\t\t\tdispatch(\"open\", false);\n\t\t\t\t}\n\t\t\t}, 0);\n\n\t\t\t// Find the parent floating menu and prevent it from also closing with the escape key when this floating menu does\n\t\t\tif (self) preventEscapeClosingParentFloatingMenu(self);\n\t\t}\n\t}\n\n\tfunction pointerDownHandler(e: PointerEvent) {\n\t\t// Close the floating menu if the pointer clicked outside the floating menu (but within stray distance)\n\t\tif (isPointerEventOutsideFloatingMenu(e)) {\n\t\t\tdispatch(\"open\", false);\n\n\t\t\t// Track if the left pointer button is now down so its later click event can be canceled\n\t\t\tconst eventIsForLmb = e.button === BUTTON_LEFT;\n\t\t\tif (eventIsForLmb) pointerStillDown = true;\n\t\t}\n\t}\n\n\tfunction pointerUpHandler(e: PointerEvent) {\n\t\tconst eventIsForLmb = e.button === BUTTON_LEFT;\n\t\tif (pointerStillDown && eventIsForLmb) {\n\t\t\t// Clean up self\n\t\t\tpointerStillDown = false;\n\t\t\twindow.removeEventListener(\"pointerup\", pointerUpHandler);\n\t\t\t// Prevent the click event from firing, which would normally occur right after this pointerup event\n\t\t\twindow.addEventListener(\"click\", clickHandlerCapture, true);\n\t\t}\n\t}\n\n\tfunction clickHandlerCapture(e: MouseEvent) {\n\t\t// Stop the click event from reopening this floating menu if the click event targets the floating menu's button\n\t\te.stopPropagation();\n\t\t// Clean up self\n\t\twindow.removeEventListener(\"click\", clickHandlerCapture, true);\n\t}\n\n\tfunction isPointerEventOutsideFloatingMenu(e: PointerEvent, extraDistanceAllowed = 0): boolean {\n\t\t// Consider all child menus as well as the top-level one\n\t\tconst allContainedFloatingMenus = [...(self?.querySelectorAll(\"[data-floating-menu-content]\") || [])];\n\n\t\treturn !allContainedFloatingMenus.find((element) => !isPointerEventOutsideMenuElement(e, element, extraDistanceAllowed));\n\t}\n\n\tfunction isPointerEventOutsideMenuElement(e: PointerEvent, element: Element, extraDistanceAllowed = 0): boolean {\n\t\tconst floatingMenuBounds = element.getBoundingClientRect();\n\n\t\tif (floatingMenuBounds.left - e.clientX >= extraDistanceAllowed) return true;\n\t\tif (e.clientX - floatingMenuBounds.right >= extraDistanceAllowed) return true;\n\t\tif (floatingMenuBounds.top - e.clientY >= extraDistanceAllowed) return true;\n\t\tif (e.clientY - floatingMenuBounds.bottom >= extraDistanceAllowed) return true;\n\n\t\treturn false;\n\t}\n</script>\n\n<div\n\tclass={`floating-menu ${direction.toLowerCase()} ${type.toLowerCase()} ${className} ${extraClasses}`.trim()}\n\tstyle={`${styleName} ${extraStyles}`.trim() || undefined}\n\tbind:this={self}\n\t{...$$restProps}\n>\n\t{#if displayTail}\n\t\t<div class=\"tail\" bind:this={tail}></div>\n\t{/if}\n\t{#if displayContainer}\n\t\t<div class=\"floating-menu-container\" bind:this={floatingMenuContainer}>\n\t\t\t<LayoutCol class=\"floating-menu-content\" styles={{ \"min-width\": minWidthStyleValue }} {scrollableY} bind:this={floatingMenuContent} data-floating-menu-content>\n\t\t\t\t<slot />\n\t\t\t</LayoutCol>\n\t\t</div>\n\t{/if}\n</div>\n\n<style lang=\"scss\" global>\n\t.floating-menu {\n\t\tposition: absolute;\n\t\twidth: 0;\n\t\theight: 0;\n\t\tdisplay: flex;\n\t\t// Floating menus begin at a z-index of 1000\n\t\tz-index: 1000;\n\t\t--floating-menu-content-offset: 0;\n\n\t\t.tail {\n\t\t\t// Put the tail above the floating menu's shadow\n\t\t\tz-index: 10;\n\t\t\t// Draw over the application without being clipped by the containing panel's `overflow: hidden`\n\t\t\tposition: fixed;\n\n\t\t\t&,\n\t\t\t&::before {\n\t\t\t\twidth: 0;\n\t\t\t\theight: 0;\n\t\t\t\tborder-style: solid;\n\t\t\t}\n\n\t\t\t&::before {\n\t\t\t\tcontent: \"\";\n\t\t\t\tposition: absolute;\n\t\t\t}\n\t\t}\n\n\t\t.floating-menu-container {\n\t\t\tdisplay: flex;\n\n\t\t\t.floating-menu-content {\n\t\t\t\tbackground: var(--color-2-mildblack);\n\t\t\t\tbox-shadow: rgba(var(--color-0-black-rgb), 0.5) 0 2px 4px;\n\t\t\t\tborder: 1px solid var(--color-3-darkgray);\n\t\t\t\tborder-radius: 4px;\n\t\t\t\tcolor: var(--color-e-nearwhite);\n\t\t\t\tfont-size: inherit;\n\t\t\t\tpadding: 8px;\n\t\t\t\tz-index: 0;\n\t\t\t\t// Draw over the application without being clipped by the containing panel's `overflow: hidden`\n\t\t\t\tposition: fixed;\n\t\t\t\t// Counteract the rightward shift caused by the border\n\t\t\t\tmargin-left: -1px;\n\t\t\t}\n\t\t}\n\n\t\t&.dropdown {\n\t\t\t&.top {\n\t\t\t\twidth: 100%;\n\t\t\t\tleft: 0;\n\t\t\t\ttop: 0;\n\t\t\t}\n\n\t\t\t&.bottom {\n\t\t\t\twidth: 100%;\n\t\t\t\tleft: 0;\n\t\t\t\tbottom: 0;\n\t\t\t}\n\n\t\t\t&.left {\n\t\t\t\theight: 100%;\n\t\t\t\ttop: 0;\n\t\t\t\tleft: 0;\n\t\t\t}\n\n\t\t\t&.right {\n\t\t\t\theight: 100%;\n\t\t\t\ttop: 0;\n\t\t\t\tright: 0;\n\t\t\t}\n\n\t\t\t&.topleft {\n\t\t\t\ttop: 0;\n\t\t\t\tleft: 0;\n\t\t\t\tmargin-top: -4px;\n\t\t\t}\n\n\t\t\t&.topright {\n\t\t\t\ttop: 0;\n\t\t\t\tright: 0;\n\t\t\t\tmargin-top: -4px;\n\t\t\t}\n\n\t\t\t&.topleft {\n\t\t\t\tbottom: 0;\n\t\t\t\tleft: 0;\n\t\t\t\tmargin-bottom: -4px;\n\t\t\t}\n\n\t\t\t&.topright {\n\t\t\t\tbottom: 0;\n\t\t\t\tright: 0;\n\t\t\t\tmargin-bottom: -4px;\n\t\t\t}\n\t\t}\n\n\t\t&.top.dropdown .floating-menu-container,\n\t\t&.bottom.dropdown .floating-menu-container {\n\t\t\tjustify-content: left;\n\t\t}\n\n\t\t&.popover {\n\t\t\t--floating-menu-content-offset: 10px;\n\t\t}\n\n\t\t&.cursor .floating-menu-container .floating-menu-content {\n\t\t\tbackground: none;\n\t\t\tbox-shadow: none;\n\t\t\tborder-radius: 0;\n\t\t\tpadding: 0;\n\t\t}\n\n\t\t&.center {\n\t\t\tjustify-content: center;\n\t\t\talign-items: center;\n\n\t\t\t> .floating-menu-container > .floating-menu-content {\n\t\t\t\ttransform: translate(-50%, -50%);\n\t\t\t}\n\t\t}\n\n\t\t&.top,\n\t\t&.bottom {\n\t\t\tflex-direction: column;\n\t\t}\n\n\t\t&.top .tail,\n\t\t&.topleft .tail,\n\t\t&.topright .tail {\n\t\t\tborder-color: var(--color-3-darkgray) transparent transparent transparent;\n\n\t\t\t&::before {\n\t\t\t\tborder-color: var(--color-2-mildblack) transparent transparent transparent;\n\t\t\t\tbottom: 0;\n\t\t\t}\n\n\t\t\t&,\n\t\t\t&::before {\n\t\t\t\tborder-width: 8px 6px 0 6px;\n\t\t\t\tmargin-left: -6px;\n\t\t\t\tmargin-bottom: 2px;\n\t\t\t}\n\t\t}\n\n\t\t&.bottom .tail,\n\t\t&.bottomleft .tail,\n\t\t&.bottomright .tail {\n\t\t\tborder-color: transparent transparent var(--color-3-darkgray) transparent;\n\n\t\t\t&::before {\n\t\t\t\tborder-color: transparent transparent var(--color-2-mildblack) transparent;\n\t\t\t\ttop: 0;\n\t\t\t}\n\n\t\t\t&,\n\t\t\t&::before {\n\t\t\t\tborder-width: 0 6px 8px 6px;\n\t\t\t\tmargin-left: -6px;\n\t\t\t\tmargin-top: 2px;\n\t\t\t}\n\t\t}\n\n\t\t&.left .tail {\n\t\t\tborder-color: transparent transparent transparent var(--color-3-darkgray);\n\n\t\t\t&::before {\n\t\t\t\tborder-color: transparent transparent transparent var(--color-2-mildblack);\n\t\t\t\tright: 0;\n\t\t\t}\n\n\t\t\t&,\n\t\t\t&::before {\n\t\t\t\tborder-width: 6px 0 6px 8px;\n\t\t\t\tmargin-top: -6px;\n\t\t\t\tmargin-right: 2px;\n\t\t\t}\n\t\t}\n\n\t\t&.right .tail {\n\t\t\tborder-color: transparent var(--color-3-darkgray) transparent transparent;\n\n\t\t\t&::before {\n\t\t\t\tborder-color: transparent var(--color-2-mildblack) transparent transparent;\n\t\t\t\tleft: 0;\n\t\t\t}\n\n\t\t\t&,\n\t\t\t&::before {\n\t\t\t\tborder-width: 6px 8px 6px 0;\n\t\t\t\tmargin-top: -6px;\n\t\t\t\tmargin-left: 2px;\n\t\t\t}\n\t\t}\n\n\t\t&.top .floating-menu-container {\n\t\t\tjustify-content: center;\n\t\t\tmargin-bottom: var(--floating-menu-content-offset);\n\t\t}\n\n\t\t&.bottom .floating-menu-container {\n\t\t\tjustify-content: center;\n\t\t\tmargin-top: var(--floating-menu-content-offset);\n\t\t}\n\n\t\t&.left .floating-menu-container {\n\t\t\talign-items: center;\n\t\t\tmargin-right: var(--floating-menu-content-offset);\n\t\t}\n\n\t\t&.right .floating-menu-container {\n\t\t\talign-items: center;\n\t\t\tmargin-left: var(--floating-menu-content-offset);\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/layout/LayoutCol.svelte",
    "content": "<script lang=\"ts\">\n\timport type { ActionShortcut } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\n\tlet className = \"\";\n\texport { className as class };\n\texport let classes: Record<string, boolean> = {};\n\tlet styleName = \"\";\n\texport { styleName as style };\n\texport let styles: Record<string, string | number | undefined> = {};\n\texport let tooltipLabel: string | undefined = undefined;\n\texport let tooltipDescription: string | undefined = undefined;\n\texport let tooltipShortcut: ActionShortcut | undefined = undefined;\n\t// TODO: Add middle-click drag scrolling\n\texport let scrollableX = false;\n\texport let scrollableY = false;\n\n\tlet self: HTMLDivElement | undefined;\n\n\t$: extraClasses = Object.entries(classes)\n\t\t.flatMap(([className, stateName]) => (stateName ? [className] : []))\n\t\t.join(\" \");\n\t$: extraStyles = Object.entries(styles)\n\t\t.flatMap((styleAndValue) => (styleAndValue[1] !== undefined ? [`${styleAndValue[0]}: ${styleAndValue[1]};`] : []))\n\t\t.join(\" \");\n\n\texport function div(): HTMLDivElement | undefined {\n\t\treturn self;\n\t}\n</script>\n\n<!-- Excluded events because these require `|passive` or `|nonpassive` modifiers. Use a <div> for these instead: `on:wheel`, `on:touchmove`, `on:touchstart` -->\n<div\n\tdata-tooltip-label={tooltipLabel}\n\tdata-tooltip-description={tooltipDescription}\n\tdata-tooltip-shortcut={tooltipShortcut?.shortcut ? JSON.stringify(tooltipShortcut.shortcut) : undefined}\n\tdata-scrollable-x={scrollableX ? \"\" : undefined}\n\tdata-scrollable-y={scrollableY ? \"\" : undefined}\n\tclass={`layout-col ${className} ${extraClasses}`.trim()}\n\tclass:scrollable-x={scrollableX}\n\tclass:scrollable-y={scrollableY}\n\tstyle={`${styleName} ${extraStyles}`.trim() || undefined}\n\tbind:this={self}\n\ton:auxclick\n\ton:blur\n\ton:click\n\ton:dblclick\n\ton:dragend\n\ton:dragleave\n\ton:dragover\n\ton:dragstart\n\ton:drop\n\ton:mouseup\n\ton:pointerdown\n\ton:pointerenter\n\ton:pointerleave\n\ton:scroll\n\t{...$$restProps}\n>\n\t<slot />\n</div>\n\n<!-- Unused (each impacts performance, see <https://github.com/GraphiteEditor/Graphite/issues/1877>):\non:contextmenu\non:copy\non:cut\non:drag\non:dragenter\non:focus\non:fullscreenchange\non:fullscreenerror\non:gotpointercapture\non:keydown\non:keypress\non:keyup\non:lostpointercapture\non:mousedown\non:mouseenter\non:mouseleave\non:mousemove\non:mouseout\non:mouseover\non:paste\non:pointercancel\non:pointermove\non:pointerout\non:pointerover\non:pointerup\non:select\non:touchcancel\non:touchend\n-->\n\n<style lang=\"scss\" global>\n\t.layout-col {\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tflex-grow: 1;\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/layout/LayoutRow.svelte",
    "content": "<script lang=\"ts\">\n\timport type { ActionShortcut } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\n\tlet className = \"\";\n\texport { className as class };\n\texport let classes: Record<string, boolean> = {};\n\tlet styleName = \"\";\n\texport { styleName as style };\n\texport let styles: Record<string, string | number | undefined> = {};\n\texport let tooltipLabel: string | undefined = undefined;\n\texport let tooltipDescription: string | undefined = undefined;\n\texport let tooltipShortcut: ActionShortcut | undefined = undefined;\n\t// TODO: Add middle-click drag scrolling\n\texport let scrollableX = false;\n\texport let scrollableY = false;\n\n\tlet self: HTMLDivElement | undefined;\n\n\t$: extraClasses = Object.entries(classes)\n\t\t.flatMap(([className, stateName]) => (stateName ? [className] : []))\n\t\t.join(\" \");\n\t$: extraStyles = Object.entries(styles)\n\t\t.flatMap((styleAndValue) => (styleAndValue[1] !== undefined ? [`${styleAndValue[0]}: ${styleAndValue[1]};`] : []))\n\t\t.join(\" \");\n\n\texport function div(): HTMLDivElement | undefined {\n\t\treturn self;\n\t}\n</script>\n\n<!-- Excluded events because these require `|passive` or `|nonpassive` modifiers. Use a <div> for these instead: `on:wheel`, `on:touchmove`, `on:touchstart` -->\n<div\n\tdata-tooltip-label={tooltipLabel}\n\tdata-tooltip-description={tooltipDescription}\n\tdata-tooltip-shortcut={tooltipShortcut?.shortcut ? JSON.stringify(tooltipShortcut.shortcut) : undefined}\n\tdata-scrollable-x={scrollableX ? \"\" : undefined}\n\tdata-scrollable-y={scrollableY ? \"\" : undefined}\n\tclass={`layout-row ${className} ${extraClasses}`.trim()}\n\tclass:scrollable-x={scrollableX}\n\tclass:scrollable-y={scrollableY}\n\tstyle={`${styleName} ${extraStyles}`.trim() || undefined}\n\tbind:this={self}\n\ton:auxclick\n\ton:blur\n\ton:click\n\ton:dblclick\n\ton:dragend\n\ton:dragleave\n\ton:dragover\n\ton:dragstart\n\ton:drop\n\ton:mousedown\n\ton:mouseup\n\ton:pointerdown\n\ton:pointerenter\n\ton:pointerleave\n\ton:scroll\n\t{...$$restProps}\n>\n\t<slot />\n</div>\n\n<!-- Unused (each impacts performance, see <https://github.com/GraphiteEditor/Graphite/issues/1877>):\non:contextmenu\non:copy\non:cut\non:drag\non:dragenter\non:focus\non:fullscreenchange\non:fullscreenerror\non:gotpointercapture\non:keydown\non:keypress\non:keyup\non:lostpointercapture\non:mouseenter\non:mouseleave\non:mousemove\non:mouseout\non:mouseover\non:paste\non:pointercancel\non:pointermove\non:pointerout\non:pointerover\non:pointerup\non:select\non:touchcancel\non:touchend\n-->\n\n<style lang=\"scss\" global>\n\t.layout-row {\n\t\tdisplay: flex;\n\t\tflex-direction: row;\n\t\tflex-grow: 1;\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/panels/Data.svelte",
    "content": "<script lang=\"ts\">\n\timport { getContext, onMount, onDestroy } from \"svelte\";\n\n\timport type { Layout } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\timport type { Editor } from \"@graphite/editor\";\n\timport { patchLayout } from \"@graphite/utility-functions/widgets\";\n\n\timport LayoutCol from \"@graphite/components/layout/LayoutCol.svelte\";\n\timport WidgetLayout from \"@graphite/components/widgets/WidgetLayout.svelte\";\n\n\tconst editor = getContext<Editor>(\"editor\");\n\n\tlet dataPanelLayout: Layout = [];\n\n\tonMount(() => {\n\t\teditor.subscriptions.subscribeLayoutUpdate(\"DataPanel\", (data) => {\n\t\t\tpatchLayout(dataPanelLayout, data);\n\t\t\tdataPanelLayout = dataPanelLayout;\n\t\t});\n\t});\n\n\tonDestroy(() => {\n\t\teditor.subscriptions.unsubscribeLayoutUpdate(\"DataPanel\");\n\t});\n</script>\n\n<LayoutCol class=\"data-panel\">\n\t<LayoutCol class=\"body\" scrollableY={true}>\n\t\t<WidgetLayout layout={dataPanelLayout} layoutTarget=\"DataPanel\" />\n\t</LayoutCol>\n</LayoutCol>\n\n<style lang=\"scss\" global>\n\t.data-panel {\n\t\tflex-grow: 1;\n\t\tpadding: 4px;\n\n\t\ttable {\n\t\t\tmargin: -4px;\n\t\t\twidth: calc(100% + 2 * 4px);\n\n\t\t\t.text-label {\n\t\t\t\twhite-space: wrap;\n\t\t\t}\n\n\t\t\t&:not(:first-child) {\n\t\t\t\tmargin-top: 0;\n\t\t\t}\n\n\t\t\ttr:first-child:has(td:first-child label:empty) ~ tr td:first-child {\n\t\t\t\twidth: 0;\n\t\t\t}\n\t\t}\n\n\t\t.widget-span:has(.text-area-input) {\n\t\t\tflex: 1 1 100%;\n\n\t\t\t.text-area-input textarea {\n\t\t\t\theight: 100%;\n\t\t\t\tmargin-top: 0;\n\t\t\t\tmargin-bottom: 0;\n\t\t\t\tresize: none;\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/panels/Document.svelte",
    "content": "<script lang=\"ts\">\n\timport { getContext, onMount, onDestroy, tick } from \"svelte\";\n\n\timport type { Color, MenuDirection, MouseCursorIcon } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\timport type { Editor } from \"@graphite/editor\";\n\timport type { AppWindowStore } from \"@graphite/stores/app-window\";\n\timport type { DocumentStore } from \"@graphite/stores/document\";\n\timport type { MessageBody } from \"@graphite/subscription-router\";\n\timport { fillChoiceColor, createColor } from \"@graphite/utility-functions/colors\";\n\timport { pasteFile } from \"@graphite/utility-functions/files\";\n\timport { textInputCleanup } from \"@graphite/utility-functions/keyboard-entry\";\n\timport { rasterizeSVGCanvas } from \"@graphite/utility-functions/rasterization\";\n\timport { setupViewportResizeObserver } from \"@graphite/utility-functions/viewports\";\n\n\timport ColorPicker from \"@graphite/components/floating-menus/ColorPicker.svelte\";\n\timport EyedropperPreview, { ZOOM_WINDOW_DIMENSIONS } from \"@graphite/components/floating-menus/EyedropperPreview.svelte\";\n\timport LayoutCol from \"@graphite/components/layout/LayoutCol.svelte\";\n\timport LayoutRow from \"@graphite/components/layout/LayoutRow.svelte\";\n\timport Graph from \"@graphite/components/views/Graph.svelte\";\n\timport RulerInput from \"@graphite/components/widgets/inputs/RulerInput.svelte\";\n\timport ScrollbarInput from \"@graphite/components/widgets/inputs/ScrollbarInput.svelte\";\n\timport WidgetLayout from \"@graphite/components/widgets/WidgetLayout.svelte\";\n\n\tlet rulerHorizontal: RulerInput | undefined;\n\tlet rulerVertical: RulerInput | undefined;\n\tlet viewport: HTMLDivElement | undefined;\n\tlet gradientStopPicker: ColorPicker | undefined;\n\n\tconst editor = getContext<Editor>(\"editor\");\n\tconst appWindow = getContext<AppWindowStore>(\"appWindow\");\n\tconst document = getContext<DocumentStore>(\"document\");\n\n\t// Interactive text editing\n\tlet textInput: undefined | HTMLDivElement = undefined;\n\tlet showTextInput: boolean;\n\tlet textInputMatrix: [number, number, number, number, number, number];\n\n\t// Scrollbars\n\tlet scrollbarPos = { x: 0.5, y: 0.5 };\n\tlet scrollbarSize = { x: 0.5, y: 0.5 };\n\tlet scrollbarMultiplier = { x: 0, y: 0 };\n\n\t// Rulers\n\tlet rulerOrigin = { x: 0, y: 0 };\n\tlet rulerSpacing = 100;\n\tlet rulerInterval = 100;\n\tlet rulersVisible = true;\n\n\t// Rendered SVG viewport data\n\tlet artworkSvg = \"\";\n\n\t// Rasterized SVG viewport data, or none if it's not up-to-date\n\tlet rasterizedCanvas: HTMLCanvasElement | undefined = undefined;\n\tlet rasterizedContext: CanvasRenderingContext2D | undefined = undefined;\n\n\t// Cursor icon to display while hovering over the canvas\n\tlet canvasCursor = \"default\";\n\n\t// Cursor position for cursor floating menus like the Eyedropper tool zoom\n\tlet cursorLeft = 0;\n\tlet cursorTop = 0;\n\tlet cursorEyedropper = false;\n\tlet cursorEyedropperPreviewImageData: ImageData | undefined = undefined;\n\tlet cursorEyedropperPreviewColorChoice = \"\";\n\tlet cursorEyedropperPreviewColorPrimary = \"\";\n\tlet cursorEyedropperPreviewColorSecondary = \"\";\n\n\t// Gradient stop color picker\n\tlet gradientStopPickerColor: Color | undefined = undefined;\n\tlet gradientStopPickerPosition: { x: number; y: number } | undefined = undefined;\n\n\t// Canvas dimensions\n\tlet canvasWidth: number | undefined = undefined;\n\tlet canvasHeight: number | undefined = undefined;\n\n\tlet devicePixelRatio: number | undefined;\n\tlet removeUpdatePixelRatio: (() => void) | undefined;\n\tlet viewportResizeObserver: ResizeObserver | undefined;\n\tlet cleanupViewportResizeObserver: (() => void) | undefined;\n\tlet addedFontFaces: FontFace[] = [];\n\n\t// Dimension is rounded up to the nearest even number because resizing is centered, and dividing an odd number by 2 for centering causes antialiasing\n\t$: canvasWidthRoundedToEven = canvasWidth && (canvasWidth % 2 === 1 ? canvasWidth + 1 : canvasWidth);\n\t$: canvasHeightRoundedToEven = canvasHeight && (canvasHeight % 2 === 1 ? canvasHeight + 1 : canvasHeight);\n\t// Used to set the canvas element size on the page.\n\t// The value above in pixels, or if undefined, we fall back to 100% as a non-pixel-perfect backup that's hopefully short-lived\n\t$: canvasWidthCSS = canvasWidthRoundedToEven ? `${canvasWidthRoundedToEven}px` : \"100%\";\n\t$: canvasHeightCSS = canvasHeightRoundedToEven ? `${canvasHeightRoundedToEven}px` : \"100%\";\n\n\t$: canvasWidthScaled = canvasWidth && devicePixelRatio && Math.floor(canvasWidth * devicePixelRatio);\n\t$: canvasHeightScaled = canvasHeight && devicePixelRatio && Math.floor(canvasHeight * devicePixelRatio);\n\n\t// Used to set the canvas rendering dimensions.\n\t$: canvasWidthScaledRoundedToEven = canvasWidthScaled && (canvasWidthScaled % 2 === 1 ? canvasWidthScaled + 1 : canvasWidthScaled);\n\t$: canvasHeightScaledRoundedToEven = canvasHeightScaled && (canvasHeightScaled % 2 === 1 ? canvasHeightScaled + 1 : canvasHeightScaled);\n\n\t$: toolShelfTotalToolsAndSeparators = ((layoutGroup) => {\n\t\tif (!layoutGroup || !(\"Row\" in layoutGroup)) return undefined;\n\n\t\tlet totalSeparators = 0;\n\t\tlet totalToolRowsFor1Columns = 0;\n\t\tlet totalToolRowsFor2Columns = 0;\n\t\tlet totalToolRowsFor3Columns = 0;\n\n\t\tconst tally = () => {\n\t\t\ttotalToolRowsFor1Columns += toolsInCurrentGroup;\n\t\t\ttotalToolRowsFor2Columns += Math.ceil(toolsInCurrentGroup / 2);\n\t\t\ttotalToolRowsFor3Columns += Math.ceil(toolsInCurrentGroup / 3);\n\t\t\ttoolsInCurrentGroup = 0;\n\t\t};\n\n\t\tlet toolsInCurrentGroup = 0;\n\t\tlayoutGroup.Row.rowWidgets.forEach((widget) => {\n\t\t\tif (\"Separator\" in widget.widget) {\n\t\t\t\ttotalSeparators += 1;\n\t\t\t\ttally();\n\t\t\t} else {\n\t\t\t\ttoolsInCurrentGroup += 1;\n\t\t\t}\n\t\t});\n\t\ttally();\n\n\t\treturn {\n\t\t\ttotalSeparators,\n\t\t\ttotalToolRowsFor1Columns,\n\t\t\ttotalToolRowsFor2Columns,\n\t\t\ttotalToolRowsFor3Columns,\n\t\t};\n\t})($document.toolShelfLayout[0]);\n\n\tfunction dropFile(e: DragEvent) {\n\t\tif (!e.dataTransfer) return;\n\n\t\tlet mouse: [number, number] | undefined = undefined;\n\t\tif (e.target instanceof Element && e.target.closest(\"[data-viewport]\")) mouse = [e.clientX, e.clientY];\n\n\t\te.preventDefault();\n\n\t\tArray.from(e.dataTransfer.items).forEach(async (item) => await pasteFile(item, editor, mouse));\n\t}\n\n\tfunction panCanvasX(newValue: number) {\n\t\tconst delta = newValue - scrollbarPos.x;\n\t\tscrollbarPos.x = newValue;\n\t\teditor.handle.panCanvas(-delta * scrollbarMultiplier.x, 0);\n\t}\n\n\tfunction panCanvasY(newValue: number) {\n\t\tconst delta = newValue - scrollbarPos.y;\n\t\tscrollbarPos.y = newValue;\n\t\teditor.handle.panCanvas(0, -delta * scrollbarMultiplier.y);\n\t}\n\n\tfunction canvasPointerDown(e: PointerEvent) {\n\t\tconst onEditbox = e.target instanceof HTMLDivElement && e.target.contentEditable;\n\n\t\tif (!onEditbox) viewport?.setPointerCapture(e.pointerId);\n\t\tif (window.document.activeElement instanceof HTMLElement) {\n\t\t\twindow.document.activeElement.blur();\n\t\t}\n\t}\n\n\t// Update rendered SVGs\n\texport async function updateDocumentArtwork(svg: string) {\n\t\t// TODO: Sort this out so we're either sending only the SVG inner contents from the backend or not setting the width/height attributes here\n\t\t// TODO: (but preserving the rounding-up-to-the-next-even-number to prevent antialiasing).\n\t\tartworkSvg = svg\n\t\t\t.trim()\n\t\t\t.replace(/<svg[^>]*>/, \"\")\n\t\t\t.slice(0, -\"</svg>\".length);\n\t\trasterizedCanvas = undefined;\n\n\t\tawait tick();\n\n\t\tconst placeholders = window.document.querySelectorAll(\"[data-viewport] [data-canvas-placeholder]\");\n\t\t// Replace the placeholders with the actual canvas elements\n\t\tArray.from(placeholders).forEach((placeholder) => {\n\t\t\tconst canvasName = placeholder.getAttribute(\"data-canvas-placeholder\");\n\t\t\tif (!canvasName) return;\n\t\t\t// Get the canvas element from the global storage\n\t\t\tlet canvas = window.imageCanvases[canvasName];\n\n\t\t\t// Get logical dimensions from foreignObject parent (set by backend)\n\t\t\tconst foreignObject = placeholder.parentElement;\n\t\t\tif (!foreignObject) return;\n\t\t\tconst logicalWidth = parseFloat(foreignObject.getAttribute(\"width\") || \"0\");\n\t\t\tconst logicalHeight = parseFloat(foreignObject.getAttribute(\"height\") || \"0\");\n\n\t\t\t// Viewport canvas is marked with data-is-viewport and should never be cloned.\n\t\t\t// If it's already mounted in the viewport, skip the DOM replacement since it's already showing the rendered content.\n\t\t\t// We check `canvas.isConnected` to ensure it's in the live DOM, not a detached tree from a destroyed component.\n\t\t\tconst isViewport = placeholder.hasAttribute(\"data-is-viewport\");\n\t\t\tif (isViewport && canvas.isConnected && canvas.parentElement?.closest(\"[data-viewport]\")) return;\n\n\t\t\t// Clone canvas for repeated instances (layers that appear multiple times)\n\t\t\tif (!isViewport && canvas.parentElement) {\n\t\t\t\tconst newCanvas = window.document.createElement(\"canvas\");\n\t\t\t\tconst context = newCanvas.getContext(\"2d\");\n\n\t\t\t\tnewCanvas.width = canvas.width;\n\t\t\t\tnewCanvas.height = canvas.height;\n\n\t\t\t\tcontext?.drawImage(canvas, 0, 0);\n\n\t\t\t\tcanvas = newCanvas;\n\t\t\t}\n\n\t\t\t// Set CSS size to logical resolution (for correct display size)\n\t\t\tcanvas.style.width = `${logicalWidth}px`;\n\t\t\tcanvas.style.height = `${logicalHeight}px`;\n\n\t\t\tplaceholder.replaceWith(canvas);\n\t\t});\n\t}\n\n\texport async function updateEyedropperSamplingState(\n\t\t// `image` is currently only used for Vello renders\n\t\timage: ImageData | undefined,\n\t\tmousePosition: [number, number] | undefined,\n\t\tcolorPrimary: string,\n\t\tcolorSecondary: string,\n\t): Promise<[number, number, number] | undefined> {\n\t\tif (mousePosition === undefined) {\n\t\t\tcursorEyedropper = false;\n\t\t\treturn undefined;\n\t\t}\n\t\tcursorEyedropper = true;\n\n\t\tif (canvasWidth === undefined || canvasHeight === undefined) return undefined;\n\n\t\tcursorLeft = mousePosition[0];\n\t\tcursorTop = mousePosition[1];\n\n\t\tlet preview = image;\n\t\tif (!preview) {\n\t\t\t// This works nearly perfectly, but sometimes at odd DPI scale factors like 1.25, the anti-aliasing color can yield slightly incorrect colors (potential room for future improvement)\n\t\t\tconst dpiFactor = window.devicePixelRatio;\n\t\t\tconst [width, height] = [canvasWidth, canvasHeight];\n\n\t\t\tconst outsideArtboardsColor = getComputedStyle(window.document.documentElement).getPropertyValue(\"--color-2-mildblack\");\n\t\t\tconst outsideArtboards = `<rect x=\"0\" y=\"0\" width=\"100%\" height=\"100%\" fill=\"${outsideArtboardsColor}\" />`;\n\n\t\t\tconst svg = `\n\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:graphite=\"https://graphite.art\" width=\"${width}\" height=\"${height}\">${outsideArtboards}${artworkSvg}</svg>\n\t\t\t\t`.trim();\n\n\t\t\tif (!rasterizedCanvas) {\n\t\t\t\trasterizedCanvas = await rasterizeSVGCanvas(svg, width * dpiFactor, height * dpiFactor);\n\t\t\t\trasterizedContext = rasterizedCanvas.getContext(\"2d\", { willReadFrequently: true }) || undefined;\n\t\t\t}\n\t\t\tif (!rasterizedContext) return undefined;\n\n\t\t\tpreview = rasterizedContext.getImageData(\n\t\t\t\tmousePosition[0] * dpiFactor - (ZOOM_WINDOW_DIMENSIONS - 1) / 2,\n\t\t\t\tmousePosition[1] * dpiFactor - (ZOOM_WINDOW_DIMENSIONS - 1) / 2,\n\t\t\t\tZOOM_WINDOW_DIMENSIONS,\n\t\t\t\tZOOM_WINDOW_DIMENSIONS,\n\t\t\t);\n\t\t\tif (!preview) return undefined;\n\t\t}\n\n\t\tconst centerPixel = (() => {\n\t\t\tconst { width, height, data } = preview;\n\t\t\tconst x = Math.floor(width / 2);\n\t\t\tconst y = Math.floor(height / 2);\n\t\t\tconst index = (y * width + x) * 4;\n\t\t\treturn {\n\t\t\t\tr: data[index],\n\t\t\t\tg: data[index + 1],\n\t\t\t\tb: data[index + 2],\n\t\t\t};\n\t\t})();\n\t\tconst hex = [centerPixel.r, centerPixel.g, centerPixel.b].map((x) => x.toString(16).padStart(2, \"0\")).join(\"\");\n\t\tconst rgb: [number, number, number] = [centerPixel.r / 255, centerPixel.g / 255, centerPixel.b / 255];\n\n\t\tcursorEyedropperPreviewColorChoice = \"#\" + hex;\n\t\tcursorEyedropperPreviewColorPrimary = colorPrimary;\n\t\tcursorEyedropperPreviewColorSecondary = colorSecondary;\n\t\tcursorEyedropperPreviewImageData = preview;\n\n\t\treturn rgb;\n\t}\n\n\t// Update scrollbars and rulers\n\texport function updateDocumentScrollbars(position: [number, number], size: [number, number], multiplier: [number, number]) {\n\t\tscrollbarPos = { x: position[0], y: position[1] };\n\t\tscrollbarSize = { x: size[0], y: size[1] };\n\t\tscrollbarMultiplier = { x: multiplier[0], y: multiplier[1] };\n\t}\n\n\texport function updateDocumentRulers(origin: [number, number], spacing: number, interval: number, visible: boolean) {\n\t\trulerOrigin = { x: origin[0], y: origin[1] };\n\t\trulerSpacing = spacing;\n\t\trulerInterval = interval;\n\t\trulersVisible = visible;\n\t}\n\n\t// Update mouse cursor icon\n\texport function updateMouseCursor(cursor: MouseCursorIcon) {\n\t\tconst mouseCursorIconCSSNames: Record<MouseCursorIcon, string> = {\n\t\t\tDefault: \"default\",\n\t\t\tNone: \"none\",\n\t\t\tZoomIn: \"zoom-in\",\n\t\t\tZoomOut: \"zoom-out\",\n\t\t\tGrabbing: \"grabbing\",\n\t\t\tCrosshair: \"crosshair\",\n\t\t\tText: \"text\",\n\t\t\tMove: \"move\",\n\t\t\tNSResize: \"ns-resize\",\n\t\t\tEWResize: \"ew-resize\",\n\t\t\tNESWResize: \"nesw-resize\",\n\t\t\tNWSEResize: \"nwse-resize\",\n\t\t\tRotate: \"custom-rotate\",\n\t\t};\n\t\tlet cursorString = mouseCursorIconCSSNames[cursor] || \"alias\";\n\n\t\t// This isn't very clean but it's good enough for now until we need more icons, then we can build something more robust (consider blob URLs)\n\t\tif (cursor === \"Rotate\") {\n\t\t\tconst svg = `\n\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 20 20\" width=\"20\" height=\"20\">\n\t\t\t\t\t<path fill=\"none\" stroke=\"black\" stroke-width=\"2\" d=\"M10,15.8c-3.2,0-5.8-2.6-5.8-5.8S6.8,4.2,10,4.2c0.999,0,1.999,0.273,2.877,0.771L11.7,7h5.8l-2.9-5l-1.013,1.746C12.5,3.125,11.271,2.8,10,2.8C6,2.8,2.8,6,2.8,10S6,17.2,10,17.2s7.2-3.2,7.2-7.2h-1.4C15.8,13.2,13.2,15.8,10,15.8z\" />\n\t\t\t\t\t<path fill=\"white\" d=\"M10,15.8c-3.2,0-5.8-2.6-5.8-5.8S6.8,4.2,10,4.2c0.999,0,1.999,0.273,2.877,0.771L11.7,7h5.8l-2.9-5l-1.013,1.746C12.5,3.125,11.271,2.8,10,2.8C6,2.8,2.8,6,2.8,10S6,17.2,10,17.2s7.2-3.2,7.2-7.2h-1.4C15.8,13.2,13.2,15.8,10,15.8z\" />\n\t\t\t\t</svg>\n\t\t\t\t`\n\t\t\t\t.split(\"\\n\")\n\t\t\t\t.map((line) => line.trim())\n\t\t\t\t.join(\"\");\n\n\t\t\tcursorString = `url('data:image/svg+xml;utf8,${svg}') 8 8, alias`;\n\t\t}\n\n\t\tcanvasCursor = cursorString;\n\t}\n\n\tfunction preventTextEditingScroll(e: Event) {\n\t\tif (!(e.target instanceof HTMLElement)) return;\n\t\te.target.scrollTop = 0;\n\t\te.target.scrollLeft = 0;\n\t}\n\n\t// Text entry\n\texport function triggerTextCommit() {\n\t\tif (!textInput) return;\n\t\tconst textCleaned = textInputCleanup(textInput.innerText);\n\t\teditor.handle.onChangeText(textCleaned, false);\n\t}\n\n\texport async function displayEditableTextbox(data: MessageBody<\"DisplayEditableTextbox\">) {\n\t\tshowTextInput = true;\n\n\t\tawait tick();\n\n\t\tif (!textInput) return;\n\n\t\t// eslint-disable-next-line svelte/no-dom-manipulating\n\t\tif (data.text === \"\") textInput.textContent = \"\";\n\t\t// eslint-disable-next-line svelte/no-dom-manipulating\n\t\telse textInput.textContent = `${data.text}\\n`;\n\n\t\t// Make it so `maxHeight` is a multiple of `lineHeight`\n\t\tconst lineHeight = data.lineHeightRatio * data.fontSize;\n\t\tlet height = data.maxHeight === undefined ? \"auto\" : `${Math.floor(data.maxHeight / lineHeight) * lineHeight}px`;\n\n\t\ttextInput.contentEditable = \"true\";\n\t\ttextInput.style.transformOrigin = \"0 0\";\n\t\ttextInput.style.width = data.maxWidth ? `${data.maxWidth}px` : \"max-content\";\n\t\ttextInput.style.height = height;\n\t\ttextInput.style.lineHeight = `${data.lineHeightRatio}`;\n\t\ttextInput.style.fontSize = `${data.fontSize}px`;\n\t\ttextInput.style.color = data.color;\n\t\ttextInput.style.textAlign = data.align;\n\n\t\ttextInput.oninput = () => {\n\t\t\tif (!textInput) return;\n\t\t\teditor.handle.updateBounds(textInputCleanup(textInput.innerText));\n\t\t};\n\n\t\ttextInputMatrix = data.transform;\n\n\t\tif (data.fontData.length > 0 && data.fontData.buffer instanceof ArrayBuffer) {\n\t\t\tconst fontView = new Uint8Array(data.fontData.buffer, data.fontData.byteOffset, data.fontData.byteLength);\n\t\t\tconst face = new FontFace(\"text-font\", fontView);\n\t\t\twindow.document.fonts.add(face);\n\t\t\taddedFontFaces.push(face);\n\t\t\ttextInput.style.fontFamily = \"text-font\";\n\t\t}\n\n\t\t// Necessary to select contenteditable: https://stackoverflow.com/questions/6139107/programmatically-select-text-in-a-contenteditable-html-element/6150060#6150060\n\n\t\tconst range = window.document.createRange();\n\t\trange.selectNodeContents(textInput);\n\n\t\tconst selection = window.getSelection();\n\t\tif (selection) {\n\t\t\tselection.removeAllRanges();\n\t\t\tselection.addRange(range);\n\t\t}\n\n\t\ttextInput.focus();\n\t\ttextInput.click();\n\n\t\t// Sends the text input element used for interactively editing with the text tool in a custom event\n\t\twindow.dispatchEvent(new CustomEvent(\"modifyinputfield\", { detail: textInput }));\n\t}\n\n\texport function displayRemoveEditableTextbox() {\n\t\twindow.dispatchEvent(new CustomEvent(\"modifyinputfield\", { detail: undefined }));\n\t\tshowTextInput = false;\n\t}\n\n\tfunction updateViewportInfo() {\n\t\tif (!viewport) return;\n\t\t// Resize the canvas\n\t\tcanvasWidth = Math.ceil(parseFloat(getComputedStyle(viewport).width));\n\t\tcanvasHeight = Math.ceil(parseFloat(getComputedStyle(viewport).height));\n\n\t\tdevicePixelRatio = window.devicePixelRatio || 1;\n\n\t\t// Resize the rulers\n\t\trulerHorizontal?.resize();\n\t\trulerVertical?.resize();\n\n\t\t// Note: Viewport bounds are now sent to the backend by the ResizeObserver in viewports.ts\n\t\t// which provides pixel-perfect physical dimensions via devicePixelContentBoxSize\n\t}\n\n\tfunction gradientStopPickerDirection(position: { x: number; y: number } | undefined, viewport: HTMLDivElement | undefined): MenuDirection {\n\t\tconst element = gradientStopPicker?.div()?.querySelector(\"[data-floating-menu-content]\");\n\t\tconst picker = element instanceof HTMLElement ? element : undefined;\n\t\tif (!picker || !position || !viewport) return \"Bottom\";\n\n\t\tconst roomRight = position.x + picker.offsetWidth - viewport.clientWidth;\n\t\tconst roomBelow = position.y + picker.offsetHeight - viewport.clientHeight;\n\n\t\t// Prefer bottom if there's room\n\t\tif (roomBelow <= 0) return \"Bottom\";\n\t\t// Otherwise choose the direction with more room\n\t\treturn roomRight > roomBelow ? \"Bottom\" : \"Right\";\n\t}\n\n\tonMount(() => {\n\t\t// Not compatible with Safari:\n\t\t// <https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio#browser_compatibility>\n\t\t// <https://bugs.webkit.org/show_bug.cgi?id=124862>\n\t\tconst updatePixelRatio = () => {\n\t\t\tremoveUpdatePixelRatio?.();\n\t\t\tconst mediaQueryList = matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);\n\t\t\t// The event is one-time use, so we have to set up a new listener and remove the old one every time\n\t\t\tmediaQueryList.addEventListener(\"change\", updatePixelRatio);\n\t\t\tremoveUpdatePixelRatio = () => mediaQueryList.removeEventListener(\"change\", updatePixelRatio);\n\n\t\t\tupdateViewportInfo();\n\t\t};\n\t\tupdatePixelRatio();\n\n\t\t// Update rendered SVGs\n\t\teditor.subscriptions.subscribeFrontendMessage(\"UpdateDocumentArtwork\", async (data) => {\n\t\t\tawait tick();\n\n\t\t\tupdateDocumentArtwork(data.svg);\n\t\t});\n\t\teditor.subscriptions.subscribeFrontendMessage(\"UpdateEyedropperSamplingState\", async (data) => {\n\t\t\tawait tick();\n\n\t\t\tconst { image, mousePosition, primaryColor, secondaryColor, setColorChoice } = data;\n\t\t\tconst imageData = image !== undefined ? new ImageData(new Uint8ClampedArray(image.data), image.width, image.height) : undefined;\n\t\t\tconst rgb = await updateEyedropperSamplingState(imageData, mousePosition, primaryColor, secondaryColor);\n\n\t\t\tif (setColorChoice && rgb) {\n\t\t\t\tif (setColorChoice === \"Primary\") editor.handle.updatePrimaryColor(...rgb, 1);\n\t\t\t\tif (setColorChoice === \"Secondary\") editor.handle.updateSecondaryColor(...rgb, 1);\n\t\t\t}\n\t\t});\n\n\t\t// Gradient stop color picker\n\t\teditor.subscriptions.subscribeFrontendMessage(\"UpdateGradientStopColorPickerPosition\", (data) => {\n\t\t\tgradientStopPickerColor = data.color;\n\t\t\tgradientStopPickerPosition = { x: data.position[0], y: data.position[1] };\n\t\t});\n\n\t\t// Update scrollbars and rulers\n\t\teditor.subscriptions.subscribeFrontendMessage(\"UpdateDocumentScrollbars\", async (data) => {\n\t\t\tawait tick();\n\n\t\t\tconst { position, size, multiplier } = data;\n\t\t\tupdateDocumentScrollbars(position, size, multiplier);\n\t\t});\n\t\teditor.subscriptions.subscribeFrontendMessage(\"UpdateDocumentRulers\", async (data) => {\n\t\t\tawait tick();\n\n\t\t\tconst { origin, spacing, interval, visible } = data;\n\t\t\tupdateDocumentRulers(origin, spacing, interval, visible);\n\t\t});\n\n\t\t// Update mouse cursor icon\n\t\teditor.subscriptions.subscribeFrontendMessage(\"UpdateMouseCursor\", async (data) => {\n\t\t\tawait tick();\n\n\t\t\tupdateMouseCursor(data.cursor);\n\t\t});\n\n\t\t// Text entry\n\t\teditor.subscriptions.subscribeFrontendMessage(\"TriggerTextCommit\", async () => {\n\t\t\tawait tick();\n\n\t\t\ttriggerTextCommit();\n\t\t});\n\t\teditor.subscriptions.subscribeFrontendMessage(\"DisplayEditableTextbox\", async (data) => {\n\t\t\tawait tick();\n\n\t\t\tdisplayEditableTextbox(data);\n\t\t});\n\t\teditor.subscriptions.subscribeFrontendMessage(\"DisplayEditableTextboxUpdateFontData\", async (data) => {\n\t\t\tawait tick();\n\n\t\t\tif (textInput && data.fontData.length > 0 && data.fontData.buffer instanceof ArrayBuffer) {\n\t\t\t\tconst fontView = new Uint8Array(data.fontData.buffer, data.fontData.byteOffset, data.fontData.byteLength);\n\t\t\t\tconst face = new FontFace(\"text-font\", fontView);\n\t\t\t\twindow.document.fonts.add(face);\n\t\t\t\taddedFontFaces.push(face);\n\t\t\t\ttextInput.style.fontFamily = \"text-font\";\n\t\t\t}\n\t\t});\n\t\teditor.subscriptions.subscribeFrontendMessage(\"DisplayEditableTextboxTransform\", async (data) => {\n\t\t\ttextInputMatrix = data.transform;\n\t\t});\n\t\teditor.subscriptions.subscribeFrontendMessage(\"DisplayRemoveEditableTextbox\", async () => {\n\t\t\tawait tick();\n\n\t\t\tdisplayRemoveEditableTextbox();\n\t\t});\n\n\t\t// Setup ResizeObserver for pixel-perfect viewport tracking with physical dimensions\n\t\t// This must happen in onMount to ensure the viewport container element exists\n\t\tcleanupViewportResizeObserver = setupViewportResizeObserver(editor);\n\n\t\t// Also observe the inner viewport for canvas sizing and ruler updates\n\t\tviewportResizeObserver = new ResizeObserver(() => {\n\t\t\tupdateViewportInfo();\n\t\t});\n\t\tif (viewport) viewportResizeObserver.observe(viewport);\n\t});\n\n\tonDestroy(() => {\n\t\tcleanupViewportResizeObserver?.();\n\t\tviewportResizeObserver?.disconnect();\n\t\tremoveUpdatePixelRatio?.();\n\t\taddedFontFaces.forEach((face) => window.document.fonts.delete(face));\n\n\t\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdateDocumentArtwork\");\n\t\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdateEyedropperSamplingState\");\n\t\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdateGradientStopColorPickerPosition\");\n\t\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdateDocumentScrollbars\");\n\t\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdateDocumentRulers\");\n\t\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdateMouseCursor\");\n\t\teditor.subscriptions.unsubscribeFrontendMessage(\"TriggerTextCommit\");\n\t\teditor.subscriptions.unsubscribeFrontendMessage(\"DisplayEditableTextbox\");\n\t\teditor.subscriptions.unsubscribeFrontendMessage(\"DisplayEditableTextboxUpdateFontData\");\n\t\teditor.subscriptions.unsubscribeFrontendMessage(\"DisplayEditableTextboxTransform\");\n\t\teditor.subscriptions.unsubscribeFrontendMessage(\"DisplayRemoveEditableTextbox\");\n\t});\n</script>\n\n<LayoutCol class=\"document\" on:dragover={(e) => e.preventDefault()} on:drop={dropFile}>\n\t<LayoutRow class=\"control-bar\" classes={{ \"for-graph\": $document.graphViewOverlayOpen }} scrollableX={true}>\n\t\t{#if !$document.graphViewOverlayOpen}\n\t\t\t<WidgetLayout layout={$document.toolOptionsLayout} layoutTarget=\"ToolOptions\" />\n\t\t\t<LayoutRow class=\"spacer\" />\n\t\t\t<WidgetLayout layout={$document.documentBarLayout} layoutTarget=\"DocumentBar\" />\n\t\t{:else}\n\t\t\t<WidgetLayout layout={$document.nodeGraphControlBarLayout} layoutTarget=\"NodeGraphControlBar\" />\n\t\t{/if}\n\t</LayoutRow>\n\t<LayoutRow\n\t\tclass=\"tool-shelf-and-viewport-area\"\n\t\tstyles={toolShelfTotalToolsAndSeparators && {\n\t\t\t\"--total-separators\": toolShelfTotalToolsAndSeparators.totalSeparators,\n\t\t\t\"--total-tool-rows-for-1-columns\": toolShelfTotalToolsAndSeparators.totalToolRowsFor1Columns,\n\t\t\t\"--total-tool-rows-for-2-columns\": toolShelfTotalToolsAndSeparators.totalToolRowsFor2Columns,\n\t\t\t\"--total-tool-rows-for-3-columns\": toolShelfTotalToolsAndSeparators.totalToolRowsFor3Columns,\n\t\t}}\n\t>\n\t\t<LayoutCol class=\"tool-shelf\">\n\t\t\t{#if !$document.graphViewOverlayOpen}\n\t\t\t\t<LayoutCol class=\"tools\" scrollableY={true}>\n\t\t\t\t\t<WidgetLayout layout={$document.toolShelfLayout} layoutTarget=\"ToolShelf\" />\n\t\t\t\t</LayoutCol>\n\t\t\t{:else}\n\t\t\t\t<LayoutRow class=\"spacer\" />\n\t\t\t{/if}\n\t\t\t<LayoutCol class=\"tool-shelf-bottom-widgets\">\n\t\t\t\t<WidgetLayout class=\"working-colors-input-area\" layout={$document.workingColorsLayout} layoutTarget=\"WorkingColors\" />\n\t\t\t</LayoutCol>\n\t\t</LayoutCol>\n\t\t<LayoutCol class=\"viewport-container\">\n\t\t\t{#if rulersVisible}\n\t\t\t\t<LayoutRow class=\"ruler-or-scrollbar top-ruler\">\n\t\t\t\t\t<LayoutCol class=\"ruler-corner\"></LayoutCol>\n\t\t\t\t\t<RulerInput origin={rulerOrigin.x} majorMarkSpacing={rulerSpacing} numberInterval={rulerInterval} direction=\"Horizontal\" bind:this={rulerHorizontal} />\n\t\t\t\t</LayoutRow>\n\t\t\t{/if}\n\t\t\t<LayoutRow class=\"viewport-container-inner-1\">\n\t\t\t\t{#if rulersVisible}\n\t\t\t\t\t<LayoutCol class=\"ruler-or-scrollbar\">\n\t\t\t\t\t\t<RulerInput origin={rulerOrigin.y} majorMarkSpacing={rulerSpacing} numberInterval={rulerInterval} direction=\"Vertical\" bind:this={rulerVertical} />\n\t\t\t\t\t</LayoutCol>\n\t\t\t\t{/if}\n\t\t\t\t<LayoutCol class=\"viewport-container-inner-2\" styles={{ cursor: canvasCursor }} data-viewport-container>\n\t\t\t\t\t{#if cursorEyedropper}\n\t\t\t\t\t\t<EyedropperPreview\n\t\t\t\t\t\t\tcolorChoice={cursorEyedropperPreviewColorChoice}\n\t\t\t\t\t\t\tprimaryColor={cursorEyedropperPreviewColorPrimary}\n\t\t\t\t\t\t\tsecondaryColor={cursorEyedropperPreviewColorSecondary}\n\t\t\t\t\t\t\timageData={cursorEyedropperPreviewImageData}\n\t\t\t\t\t\t\tx={cursorLeft}\n\t\t\t\t\t\t\ty={cursorTop}\n\t\t\t\t\t\t/>\n\t\t\t\t\t{/if}\n\t\t\t\t\t<div\n\t\t\t\t\t\tstyle:left={gradientStopPickerPosition ? `${gradientStopPickerPosition?.x}px` : undefined}\n\t\t\t\t\t\tstyle:top={gradientStopPickerPosition ? `${gradientStopPickerPosition?.y}px` : undefined}\n\t\t\t\t\t\tstyle:position=\"absolute\"\n\t\t\t\t\t\tdata-floating-menu-no-position\n\t\t\t\t\t>\n\t\t\t\t\t\t<div data-floating-menu-spawner></div>\n\t\t\t\t\t\t<ColorPicker\n\t\t\t\t\t\t\tdirection={gradientStopPickerDirection(gradientStopPickerPosition, viewport)}\n\t\t\t\t\t\t\topen={Boolean(gradientStopPickerPosition && gradientStopPickerColor)}\n\t\t\t\t\t\t\ton:open={({ detail }) => {\n\t\t\t\t\t\t\t\tif (!detail) {\n\t\t\t\t\t\t\t\t\teditor.handle.closeGradientStopColorPicker();\n\t\t\t\t\t\t\t\t\tgradientStopPickerPosition = undefined;\n\t\t\t\t\t\t\t\t\tgradientStopPickerColor = undefined;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tcolorOrGradient={{ Solid: gradientStopPickerColor || createColor(0, 0, 0, 1) }}\n\t\t\t\t\t\t\ton:colorOrGradient={({ detail }) => {\n\t\t\t\t\t\t\t\tconst color = fillChoiceColor(detail);\n\t\t\t\t\t\t\t\tif (color) editor.handle.updateGradientStopColor(color.red, color.green, color.blue, color.alpha);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\ton:startHistoryTransaction={() => editor.handle.startGradientStopColorTransaction()}\n\t\t\t\t\t\t\ton:commitHistoryTransaction={() => editor.handle.commitGradientStopColorTransaction()}\n\t\t\t\t\t\t\tbind:this={gradientStopPicker}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclass:viewport={!$appWindow.viewportHolePunch}\n\t\t\t\t\t\tclass:viewport-transparent={$appWindow.viewportHolePunch}\n\t\t\t\t\t\ton:pointerdown={(e) => canvasPointerDown(e)}\n\t\t\t\t\t\tbind:this={viewport}\n\t\t\t\t\t\tdata-viewport\n\t\t\t\t\t>\n\t\t\t\t\t\t{#if !$appWindow.viewportHolePunch}\n\t\t\t\t\t\t\t<svg class=\"artboards\" style:width={canvasWidthCSS} style:height={canvasHeightCSS}>\n\t\t\t\t\t\t\t\t{@html artworkSvg}\n\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t{/if}\n\t\t\t\t\t\t<div class=\"text-input\" style:width={canvasWidthCSS} style:height={canvasHeightCSS} style:pointer-events={showTextInput ? \"auto\" : \"\"}>\n\t\t\t\t\t\t\t{#if showTextInput}\n\t\t\t\t\t\t\t\t<div bind:this={textInput} style:transform=\"matrix({textInputMatrix})\" on:scroll={preventTextEditingScroll}></div>\n\t\t\t\t\t\t\t{/if}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t{#if !$appWindow.viewportHolePunch}\n\t\t\t\t\t\t\t<canvas\n\t\t\t\t\t\t\t\tclass=\"overlays\"\n\t\t\t\t\t\t\t\twidth={canvasWidthScaledRoundedToEven}\n\t\t\t\t\t\t\t\theight={canvasHeightScaledRoundedToEven}\n\t\t\t\t\t\t\t\tstyle:width={canvasWidthCSS}\n\t\t\t\t\t\t\t\tstyle:height={canvasHeightCSS}\n\t\t\t\t\t\t\t\tdata-overlays-canvas\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t</canvas>\n\t\t\t\t\t\t{/if}\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<div class=\"graph-view\" class:open={$document.graphViewOverlayOpen} style:--fade-artwork={`${$document.fadeArtwork}%`} data-graph>\n\t\t\t\t\t\t<Graph />\n\t\t\t\t\t</div>\n\t\t\t\t</LayoutCol>\n\t\t\t\t<LayoutCol class=\"ruler-or-scrollbar right-scrollbar\">\n\t\t\t\t\t<ScrollbarInput\n\t\t\t\t\t\tdirection=\"Vertical\"\n\t\t\t\t\t\tthumbLength={scrollbarSize.y}\n\t\t\t\t\t\tthumbPosition={scrollbarPos.y}\n\t\t\t\t\t\ton:trackShift={({ detail }) => editor.handle.panCanvasByFraction(0, detail)}\n\t\t\t\t\t\ton:thumbPosition={({ detail }) => panCanvasY(detail)}\n\t\t\t\t\t\ton:thumbDragStart={() => editor.handle.panCanvasAbortPrepare(false)}\n\t\t\t\t\t\ton:thumbDragAbort={() => editor.handle.panCanvasAbort(false)}\n\t\t\t\t\t/>\n\t\t\t\t</LayoutCol>\n\t\t\t</LayoutRow>\n\t\t\t<LayoutRow class=\"ruler-or-scrollbar bottom-scrollbar\">\n\t\t\t\t<ScrollbarInput\n\t\t\t\t\tdirection=\"Horizontal\"\n\t\t\t\t\tthumbLength={scrollbarSize.x}\n\t\t\t\t\tthumbPosition={scrollbarPos.x}\n\t\t\t\t\ton:trackShift={({ detail }) => editor.handle.panCanvasByFraction(detail, 0)}\n\t\t\t\t\ton:thumbPosition={({ detail }) => panCanvasX(detail)}\n\t\t\t\t\ton:thumbDragStart={() => editor.handle.panCanvasAbortPrepare(true)}\n\t\t\t\t\ton:thumbDragAbort={() => editor.handle.panCanvasAbort(true)}\n\t\t\t\t/>\n\t\t\t</LayoutRow>\n\t\t</LayoutCol>\n\t</LayoutRow>\n</LayoutCol>\n\n<style lang=\"scss\" global>\n\t.document {\n\t\theight: 100%;\n\n\t\t&.document.document {\n\t\t\tpadding-bottom: 0;\n\t\t}\n\n\t\t.control-bar {\n\t\t\theight: 32px;\n\t\t\tflex: 0 0 auto;\n\t\t\tpadding: 0 4px; // Padding (instead of margin) is needed for the viewport hole punch on desktop\n\t\t\tbackground: var(--color-3-darkgray); // Needed for the viewport hole punch on desktop\n\n\t\t\t.spacer {\n\t\t\t\tmin-width: 40px;\n\t\t\t}\n\n\t\t\t&.for-graph {\n\t\t\t\tjustify-content: space-between;\n\t\t\t}\n\t\t}\n\n\t\t.tool-shelf-and-viewport-area {\n\t\t\t// Enables usage of the `100cqh` unit to reference the height of this container element.\n\t\t\tcontainer-type: size;\n\n\t\t\t// Update this if the tool icons change width in the future.\n\t\t\t--tool-width: 32;\n\t\t\t// Update this if the items below the tools (i.e. the working colors) change height in the future.\n\t\t\t--height-of-elements-below-tools: 72px;\n\t\t\t// Update this if the height changes as set in `Separator.svelte`.\n\t\t\t--height-of-separator: calc(12px + 1px + 12px);\n\n\t\t\t// Target height for the tools within the container above the lower elements.\n\t\t\t--available-height: calc(100cqh - var(--height-of-elements-below-tools));\n\t\t\t// The least height required to fit all the tools in 1 column and 2 columns, which the available space must exceed in order for the fewest needed columns to be used.\n\t\t\t--1-col-required-height: calc(var(--total-tool-rows-for-1-columns) * calc(var(--tool-width) * 1px) + var(--total-separators) * var(--height-of-separator));\n\t\t\t--2-col-required-height: calc(var(--total-tool-rows-for-2-columns) * calc(var(--tool-width) * 1px) + var(--total-separators) * var(--height-of-separator));\n\n\t\t\t// These evaluate to 0px (if false) or 1px (if true). (We multiply by 1000000 to force the result to be a discrete integer 0 or 1 and not interpolate values in-between.)\n\t\t\t--needs-at-least-1-column: 1px; // Always true\n\t\t\t--needs-at-least-2-columns: calc(1px - clamp(0px, calc((var(--available-height) - Min(var(--available-height), var(--1-col-required-height))) * 1000000), 1px));\n\t\t\t--needs-at-least-3-columns: calc(1px - clamp(0px, calc((var(--available-height) - Min(var(--available-height), var(--2-col-required-height))) * 1000000), 1px));\n\t\t\t--columns: calc(var(--needs-at-least-1-column) + var(--needs-at-least-2-columns) + var(--needs-at-least-3-columns));\n\t\t\t--columns-width: calc(var(--columns) * var(--tool-width));\n\t\t\t--columns-width-max: calc(3px * var(--tool-width));\n\n\t\t\t.tool-shelf {\n\t\t\t\tflex: 0 0 auto;\n\t\t\t\tjustify-content: space-between;\n\t\t\t\tbackground: var(--color-3-darkgray); // Needed for the viewport hole punch on desktop\n\n\t\t\t\t.tools {\n\t\t\t\t\tflex: 0 1 auto;\n\n\t\t\t\t\t// Disabled because Firefox appears to have switched to using overlay scrollbars which float atop the content and don't affect the layout (as of FF 135 on Windows).\n\t\t\t\t\t// We'll keep this here in case it's needed in the future.\n\t\t\t\t\t//\n\t\t\t\t\t// Firefox-specific workaround for this bug causing the scrollbar to cover up the toolbar instead of widening to accommodate the scrollbar:\n\t\t\t\t\t// <https://bugzilla.mozilla.org/show_bug.cgi?id=764076>\n\t\t\t\t\t// <https://stackoverflow.com/questions/63278303/firefox-does-not-take-vertical-scrollbar-width-into-account-when-calculating-par>\n\t\t\t\t\t// Remove this when the Firefox bug is fixed.\n\t\t\t\t\t// @-moz-document url-prefix() {\n\t\t\t\t\t// \t--available-height-plus-1: calc(var(--available-height) + 1px);\n\t\t\t\t\t// \t--3-col-required-height: calc(var(--total-tool-rows-for-3-columns) * calc(var(--tool-width) * 1px) + var(--total-separators) * var(--height-of-separator));\n\t\t\t\t\t// \t--overflows-with-3-columns: calc(1px - clamp(0px, calc((var(--available-height-plus-1) - Min(var(--available-height-plus-1), var(--3-col-required-height))) * 1000000), 1px));\n\t\t\t\t\t// \t--firefox-scrollbar-width-space-occupied: 2; // Might change someday, or on different platforms, but this is the value in FF 120 on Windows\n\t\t\t\t\t// \tpadding-right: calc(var(--firefox-scrollbar-width-space-occupied) * var(--overflows-with-3-columns));\n\t\t\t\t\t// }\n\n\t\t\t\t\t.widget-span {\n\t\t\t\t\t\tflex-wrap: wrap;\n\t\t\t\t\t\twidth: var(--columns-width);\n\n\t\t\t\t\t\t.icon-button {\n\t\t\t\t\t\t\tmargin: 0;\n\n\t\t\t\t\t\t\t// &[data-tooltip-description^=\"Coming soon.\"] {\n\t\t\t\t\t\t\t// \topacity: 0.25;\n\t\t\t\t\t\t\t// \ttransition: opacity 0.1s;\n\n\t\t\t\t\t\t\t// \t&:hover {\n\t\t\t\t\t\t\t// \t\topacity: 1;\n\t\t\t\t\t\t\t// \t}\n\t\t\t\t\t\t\t// }\n\n\t\t\t\t\t\t\t&:not(.emphasized) {\n\t\t\t\t\t\t\t\t.color-general {\n\t\t\t\t\t\t\t\t\tfill: var(--color-data-general);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t.color-vector {\n\t\t\t\t\t\t\t\t\tfill: var(--color-data-vector);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t.color-raster {\n\t\t\t\t\t\t\t\t\tfill: var(--color-data-raster);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t.separator {\n\t\t\t\t\t\t\tmin-height: 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t.tool-shelf-bottom-widgets {\n\t\t\t\t\tflex: 0 0 auto;\n\t\t\t\t\talign-items: center;\n\n\t\t\t\t\t.working-colors-input-area {\n\t\t\t\t\t\theight: auto;\n\t\t\t\t\t\tmargin: 0;\n\t\t\t\t\t\tmin-height: 0;\n\n\t\t\t\t\t\t.working-colors-input {\n\t\t\t\t\t\t\tmargin: 0;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t.icon-button {\n\t\t\t\t\t\t\t--widget-height: 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.viewport-container {\n\t\t\t\tflex: 1 1 100%;\n\n\t\t\t\t.ruler-or-scrollbar {\n\t\t\t\t\tflex: 0 0 auto;\n\t\t\t\t\tbackground: var(--color-3-darkgray); // Needed for the viewport hole punch on desktop\n\t\t\t\t}\n\n\t\t\t\t.ruler-corner {\n\t\t\t\t\tbackground: var(--color-2-mildblack);\n\t\t\t\t\twidth: 16px;\n\t\t\t\t\tposition: relative;\n\n\t\t\t\t\t&::after {\n\t\t\t\t\t\tcontent: \"\";\n\t\t\t\t\t\tbackground: var(--color-5-dullgray);\n\t\t\t\t\t\tposition: absolute;\n\t\t\t\t\t\twidth: 1px;\n\t\t\t\t\t\theight: 1px;\n\t\t\t\t\t\tright: 0;\n\t\t\t\t\t\tbottom: 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t.top-ruler .ruler-input {\n\t\t\t\t\tmargin-right: 16px;\n\t\t\t\t}\n\n\t\t\t\t&:has(.top-ruler) .right-scrollbar .scrollbar-input {\n\t\t\t\t\tmargin-top: -16px;\n\t\t\t\t}\n\n\t\t\t\t.bottom-scrollbar .scrollbar-input {\n\t\t\t\t\tmargin-right: 16px;\n\t\t\t\t}\n\n\t\t\t\t.viewport-container-inner-1,\n\t\t\t\t.viewport-container-inner-2 {\n\t\t\t\t\tflex: 1 1 100%;\n\t\t\t\t\tposition: relative;\n\n\t\t\t\t\t.viewport {\n\t\t\t\t\t\tbackground: var(--color-2-mildblack);\n\t\t\t\t\t}\n\n\t\t\t\t\t.viewport,\n\t\t\t\t\t.viewport-transparent {\n\t\t\t\t\t\twidth: 100%;\n\t\t\t\t\t\theight: 100%;\n\t\t\t\t\t\t// Allows the SVG to be placed at explicit integer values of width and height to prevent non-pixel-perfect SVG scaling\n\t\t\t\t\t\tposition: relative;\n\t\t\t\t\t\toverflow: hidden;\n\n\t\t\t\t\t\t.artwork,\n\t\t\t\t\t\t.text-input,\n\t\t\t\t\t\t.overlays {\n\t\t\t\t\t\t\tposition: absolute;\n\t\t\t\t\t\t\ttop: 0;\n\t\t\t\t\t\t\t// Fallback values if JS hasn't set these to integers yet\n\t\t\t\t\t\t\twidth: 100%;\n\t\t\t\t\t\t\theight: 100%;\n\t\t\t\t\t\t\t// Allows dev tools to select the artwork without being blocked by the SVG containers\n\t\t\t\t\t\t\tpointer-events: none;\n\n\t\t\t\t\t\t\t// Prevent inheritance from reaching the child elements\n\t\t\t\t\t\t\t> * {\n\t\t\t\t\t\t\t\tpointer-events: auto;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t.text-input {\n\t\t\t\t\t\t\tword-break: break-all;\n\t\t\t\t\t\t\tunicode-bidi: plaintext;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t.text-input div {\n\t\t\t\t\t\t\tcursor: text;\n\t\t\t\t\t\t\tbackground: none;\n\t\t\t\t\t\t\tborder: none;\n\t\t\t\t\t\t\tmargin: 0;\n\t\t\t\t\t\t\tpadding: 0;\n\t\t\t\t\t\t\toverflow-x: visible;\n\t\t\t\t\t\t\toverflow-y: hidden;\n\t\t\t\t\t\t\toverflow-wrap: anywhere;\n\t\t\t\t\t\t\twhite-space: pre-wrap;\n\t\t\t\t\t\t\tword-break: normal;\n\t\t\t\t\t\t\tunicode-bidi: plaintext;\n\t\t\t\t\t\t\tdisplay: inline-block;\n\t\t\t\t\t\t\t// Workaround to force Chrome to display the flashing text entry cursor when text is empty\n\t\t\t\t\t\t\tpadding-left: 1px;\n\t\t\t\t\t\t\tmargin-left: -1px;\n\n\t\t\t\t\t\t\t&:focus {\n\t\t\t\t\t\t\t\tborder: none;\n\t\t\t\t\t\t\t\toutline: none; // Ok for contenteditable element\n\t\t\t\t\t\t\t\tmargin: -1px;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t.graph-view {\n\t\t\t\t\t\tpointer-events: none;\n\t\t\t\t\t\ttransition: opacity 0.2s;\n\t\t\t\t\t\topacity: 0;\n\n\t\t\t\t\t\t&.open {\n\t\t\t\t\t\t\tcursor: auto;\n\t\t\t\t\t\t\tpointer-events: auto;\n\t\t\t\t\t\t\topacity: 1;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t&::before {\n\t\t\t\t\t\t\tcontent: \"\";\n\t\t\t\t\t\t\tposition: absolute;\n\t\t\t\t\t\t\ttop: 0;\n\t\t\t\t\t\t\tleft: 0;\n\t\t\t\t\t\t\twidth: 100%;\n\t\t\t\t\t\t\theight: 100%;\n\t\t\t\t\t\t\tbackground: var(--color-2-mildblack);\n\t\t\t\t\t\t\topacity: var(--fade-artwork);\n\t\t\t\t\t\t\tpointer-events: none;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t.fade-artwork,\n\t\t\t\t\t.graph {\n\t\t\t\t\t\tposition: absolute;\n\t\t\t\t\t\ttop: 0;\n\t\t\t\t\t\tleft: 0;\n\t\t\t\t\t\twidth: 100%;\n\t\t\t\t\t\theight: 100%;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/panels/Layers.svelte",
    "content": "<script lang=\"ts\">\n\timport { getContext, onMount, onDestroy, tick } from \"svelte\";\n\timport { SvelteMap } from \"svelte/reactivity\";\n\n\timport type { LayerPanelEntry, LayerStructureEntry, Layout } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\timport type { Editor } from \"@graphite/editor\";\n\timport type { NodeGraphStore } from \"@graphite/stores/node-graph\";\n\timport type { TooltipStore } from \"@graphite/stores/tooltip\";\n\timport { pasteFile } from \"@graphite/utility-functions/files\";\n\timport { operatingSystem } from \"@graphite/utility-functions/platform\";\n\timport { patchLayout } from \"@graphite/utility-functions/widgets\";\n\n\timport LayoutCol from \"@graphite/components/layout/LayoutCol.svelte\";\n\timport LayoutRow from \"@graphite/components/layout/LayoutRow.svelte\";\n\timport IconButton from \"@graphite/components/widgets/buttons/IconButton.svelte\";\n\timport IconLabel from \"@graphite/components/widgets/labels/IconLabel.svelte\";\n\timport Separator from \"@graphite/components/widgets/labels/Separator.svelte\";\n\timport WidgetLayout from \"@graphite/components/widgets/WidgetLayout.svelte\";\n\n\ttype LayerListingInfo = {\n\t\tfolderIndex: number;\n\t\tbottomLayer: boolean;\n\t\teditingName: boolean;\n\t\tentry: LayerPanelEntry;\n\t};\n\n\ttype DraggingData = {\n\t\tselect?: () => void;\n\t\tinsertParentId: bigint | undefined;\n\t\tinsertDepth: number;\n\t\tinsertIndex: number | undefined;\n\t\thighlightFolder: boolean;\n\t\tmarkerHeight: number;\n\t};\n\n\ttype InternalDragState = {\n\t\tactive: boolean;\n\t\tlayerId: bigint;\n\t\tlisting: LayerListingInfo;\n\t\tstartX: number;\n\t\tstartY: number;\n\t};\n\n\tconst editor = getContext<Editor>(\"editor\");\n\tconst nodeGraph = getContext<NodeGraphStore>(\"nodeGraph\");\n\tconst tooltip = getContext<TooltipStore>(\"tooltip\");\n\n\tlet list: LayoutCol | undefined;\n\n\t// Layer data\n\tlet layerCache = new SvelteMap<string, LayerPanelEntry>(); // TODO: replace with BigUint64Array as index\n\tlet layers: LayerListingInfo[] = [];\n\n\t// Interactive dragging\n\tlet draggable = true;\n\tlet draggingData: undefined | DraggingData = undefined;\n\tlet internalDragState: InternalDragState | undefined = undefined;\n\tlet fakeHighlightOfNotYetSelectedLayerBeingDragged: undefined | bigint = undefined;\n\tlet justFinishedDrag = false; // Used to prevent click events after a drag\n\tlet dragInPanel = false;\n\n\t// Interactive clipping\n\tlet layerToClipUponClick: LayerListingInfo | undefined = undefined;\n\tlet layerToClipAltKeyPressed = false;\n\n\t// Layouts\n\tlet layersPanelControlBarLeftLayout: Layout = [];\n\tlet layersPanelControlBarRightLayout: Layout = [];\n\tlet layersPanelBottomBarLayout: Layout = [];\n\n\tonMount(() => {\n\t\teditor.subscriptions.subscribeLayoutUpdate(\"LayersPanelControlLeftBar\", (data) => {\n\t\t\tpatchLayout(layersPanelControlBarLeftLayout, data);\n\t\t\tlayersPanelControlBarLeftLayout = layersPanelControlBarLeftLayout;\n\t\t});\n\n\t\teditor.subscriptions.subscribeLayoutUpdate(\"LayersPanelControlRightBar\", (data) => {\n\t\t\tpatchLayout(layersPanelControlBarRightLayout, data);\n\t\t\tlayersPanelControlBarRightLayout = layersPanelControlBarRightLayout;\n\t\t});\n\n\t\teditor.subscriptions.subscribeLayoutUpdate(\"LayersPanelBottomBar\", (data) => {\n\t\t\tpatchLayout(layersPanelBottomBarLayout, data);\n\t\t\tlayersPanelBottomBarLayout = layersPanelBottomBarLayout;\n\t\t});\n\n\t\teditor.subscriptions.subscribeFrontendMessage(\"UpdateDocumentLayerStructure\", (data) => {\n\t\t\trebuildLayerHierarchy(data.layerStructure);\n\t\t});\n\n\t\teditor.subscriptions.subscribeFrontendMessage(\"UpdateDocumentLayerDetails\", (data) => {\n\t\t\tconst targetLayer = data.data;\n\t\t\tconst targetId = targetLayer.id;\n\n\t\t\tupdateLayerInTree(targetId, targetLayer);\n\t\t});\n\n\t\taddEventListener(\"pointerup\", draggingPointerUp);\n\t\taddEventListener(\"pointermove\", draggingPointerMove);\n\t\taddEventListener(\"mousedown\", draggingMouseDown);\n\t\taddEventListener(\"keydown\", draggingKeyDown);\n\t\taddEventListener(\"keydown\", handleLayerPanelKeyDown);\n\n\t\taddEventListener(\"pointermove\", clippingHover);\n\t\taddEventListener(\"keydown\", clippingKeyPress);\n\t\taddEventListener(\"keyup\", clippingKeyPress);\n\t});\n\n\tonDestroy(() => {\n\t\teditor.subscriptions.unsubscribeLayoutUpdate(\"LayersPanelControlLeftBar\");\n\t\teditor.subscriptions.unsubscribeLayoutUpdate(\"LayersPanelControlRightBar\");\n\t\teditor.subscriptions.unsubscribeLayoutUpdate(\"LayersPanelBottomBar\");\n\t\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdateDocumentLayerStructure\");\n\t\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdateDocumentLayerDetails\");\n\n\t\tremoveEventListener(\"pointerup\", draggingPointerUp);\n\t\tremoveEventListener(\"pointermove\", draggingPointerMove);\n\t\tremoveEventListener(\"mousedown\", draggingMouseDown);\n\t\tremoveEventListener(\"keydown\", draggingKeyDown);\n\t\tremoveEventListener(\"keydown\", handleLayerPanelKeyDown);\n\n\t\tremoveEventListener(\"pointermove\", clippingHover);\n\t\tremoveEventListener(\"keydown\", clippingKeyPress);\n\t\tremoveEventListener(\"keyup\", clippingKeyPress);\n\t});\n\n\tfunction toggleNodeVisibilityLayerPanel(id: bigint) {\n\t\teditor.handle.toggleNodeVisibilityLayerPanel(id);\n\t}\n\n\tfunction toggleLayerLock(id: bigint) {\n\t\teditor.handle.toggleLayerLock(id);\n\t}\n\n\tfunction handleExpandArrowClickWithModifiers(e: MouseEvent, id: bigint) {\n\t\tconst accel = operatingSystem() === \"Mac\" ? e.metaKey : e.ctrlKey;\n\t\tconst collapseRecursive = e.altKey || accel;\n\t\teditor.handle.toggleLayerExpansion(id, collapseRecursive);\n\t\te.stopPropagation();\n\t}\n\n\tasync function onEditLayerName(listing: LayerListingInfo) {\n\t\tif (listing.editingName) return;\n\n\t\tdraggable = false;\n\t\tlisting.editingName = true;\n\t\tlayers = layers;\n\n\t\tawait tick();\n\n\t\tconst query = list?.div?.()?.querySelector(\"[data-text-input]:not([disabled])\");\n\t\tconst textInput = (query instanceof HTMLInputElement && query) || undefined;\n\t\ttextInput?.select();\n\t}\n\n\tfunction onEditLayerNameChange(listing: LayerListingInfo, e: Event) {\n\t\t// Eliminate duplicate events\n\t\tif (!listing.editingName) return;\n\n\t\tdraggable = true;\n\t\tlisting.editingName = false;\n\t\tlayers = layers;\n\n\t\tconst name = (e.target instanceof HTMLInputElement && e.target.value) || \"\";\n\t\teditor.handle.setLayerName(listing.entry.id, name);\n\t\tlisting.entry.alias = name;\n\t}\n\n\tasync function onEditLayerNameDeselect(listing: LayerListingInfo) {\n\t\tdraggable = true;\n\t\tlisting.editingName = false;\n\t\tlayers = layers;\n\n\t\t// Set it back to the original name if the user didn't enter a new name\n\t\tif (document.activeElement instanceof HTMLInputElement) document.activeElement.value = listing.entry.alias;\n\n\t\t// Deselect the text so it doesn't appear selected while the input field becomes disabled and styled to look like regular text\n\t\twindow.getSelection()?.removeAllRanges();\n\t}\n\n\tfunction selectLayerWithModifiers(e: MouseEvent, listing: LayerListingInfo) {\n\t\tif (justFinishedDrag) {\n\t\t\tjustFinishedDrag = false;\n\t\t\t// Prevent bubbling to deselectAllLayers\n\t\t\te.stopPropagation();\n\t\t\treturn;\n\t\t}\n\n\t\t// Get the pressed state of the modifier keys\n\t\tconst [ctrl, meta, shift, alt] = [e.ctrlKey, e.metaKey, e.shiftKey, e.altKey];\n\t\t// Get the state of the platform's accel key and its opposite platform's accel key\n\t\tconst [accel, oppositeAccel] = operatingSystem() === \"Mac\" ? [meta, ctrl] : [ctrl, meta];\n\n\t\t// Alt-clicking to make a clipping mask\n\t\tif (layerToClipAltKeyPressed && layerToClipUponClick && layerToClipUponClick.entry.clippable) clipLayer(layerToClipUponClick);\n\t\t// Select the layer only if the accel and/or shift keys are pressed\n\t\telse if (!oppositeAccel && !alt) selectLayer(listing, accel, shift);\n\n\t\te.stopPropagation();\n\t}\n\n\tfunction clipLayer(listing: LayerListingInfo) {\n\t\teditor.handle.clipLayer(listing.entry.id);\n\t}\n\n\tfunction clippingKeyPress(e: KeyboardEvent) {\n\t\tlayerToClipAltKeyPressed = e.altKey;\n\t}\n\n\tfunction clippingHover(e: PointerEvent) {\n\t\t// Don't do anything if the user is dragging to rearrange layers\n\t\tif (dragInPanel) return;\n\n\t\t// Get the layer below the cursor\n\t\tconst target = (e.target instanceof HTMLElement && e.target.closest(\"[data-layer]\")) || undefined;\n\t\tif (!target) {\n\t\t\tlayerToClipUponClick = undefined;\n\t\t\treturn;\n\t\t}\n\n\t\t// Check if the cursor is near the border between two layers\n\t\tconst DISTANCE = 6;\n\t\tconst distanceFromTop = e.clientY - target.getBoundingClientRect().top;\n\t\tconst distanceFromBottom = target.getBoundingClientRect().bottom - e.clientY;\n\n\t\tconst nearTop = distanceFromTop < DISTANCE;\n\t\tconst nearBottom = distanceFromBottom < DISTANCE;\n\n\t\t// If we are not near the border, we don't want to clip\n\t\tif (!nearTop && !nearBottom) {\n\t\t\tlayerToClipUponClick = undefined;\n\t\t\treturn;\n\t\t}\n\n\t\t// If we are near the border, we want to clip the layer above the border\n\t\tconst indexAttribute = target?.getAttribute(\"data-index\") ?? undefined;\n\t\tconst index = indexAttribute ? Number(indexAttribute) : undefined;\n\t\tconst layer = index !== undefined && layers[nearTop ? index - 1 : index];\n\t\tif (!layer) return;\n\n\t\t// Update the state used to show the clipping action\n\t\tlayerToClipUponClick = layer;\n\t\tlayerToClipAltKeyPressed = e.altKey;\n\t}\n\n\tfunction selectLayer(listing: LayerListingInfo, accel: boolean, shift: boolean) {\n\t\t// Don't select while we are entering text to rename the layer\n\t\tif (listing.editingName) return;\n\n\t\teditor.handle.selectLayer(listing.entry.id, accel, shift);\n\t}\n\n\tasync function deselectAllLayers() {\n\t\tif (justFinishedDrag) {\n\t\t\tjustFinishedDrag = false;\n\t\t\treturn;\n\t\t}\n\n\t\teditor.handle.deselectAllLayers();\n\t}\n\n\tfunction calculateDragIndex(tree: LayoutCol, clientY: number, select?: () => void): DraggingData {\n\t\tconst treeChildren = tree.div()?.children;\n\t\tconst treeOffset = tree.div()?.getBoundingClientRect().top;\n\n\t\t// Folder to insert into\n\t\tlet insertParentId: bigint | undefined = undefined;\n\t\tlet insertDepth = 0;\n\n\t\t// Insert index (starts at the end, essentially infinity)\n\t\tlet insertIndex = undefined;\n\n\t\t// Whether you are inserting into a folder and should show the folder outline\n\t\tlet highlightFolder = false;\n\n\t\tlet markerHeight = 0;\n\t\tconst layerPanel = document.querySelector(\"[data-layer-panel]\"); // Selects the element with the data-layer-panel attribute\n\t\tif (layerPanel !== null && treeChildren !== undefined && treeOffset !== undefined) {\n\t\t\tlet layerPanelTop = layerPanel.getBoundingClientRect().top;\n\t\t\tArray.from(treeChildren).forEach((treeChild) => {\n\t\t\t\tconst indexAttribute = treeChild.getAttribute(\"data-index\");\n\t\t\t\tif (!indexAttribute) return;\n\t\t\t\tconst { folderIndex, entry: layer } = layers[parseInt(indexAttribute, 10)];\n\n\t\t\t\tconst rect = treeChild.getBoundingClientRect();\n\t\t\t\tif (rect.top > clientY || rect.bottom < clientY) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst pointerPercentage = (clientY - rect.top) / rect.height;\n\t\t\t\tif (layer.childrenAllowed) {\n\t\t\t\t\tif (pointerPercentage < 0.25) {\n\t\t\t\t\t\tinsertParentId = layer.parentId;\n\t\t\t\t\t\tinsertDepth = layer.depth - 1;\n\t\t\t\t\t\tinsertIndex = folderIndex;\n\t\t\t\t\t\tmarkerHeight = rect.top - layerPanelTop;\n\t\t\t\t\t} else if (pointerPercentage < 0.75 || (layer.childrenPresent && layer.expanded)) {\n\t\t\t\t\t\tinsertParentId = layer.id;\n\t\t\t\t\t\tinsertDepth = layer.depth;\n\t\t\t\t\t\tinsertIndex = 0;\n\t\t\t\t\t\thighlightFolder = true;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tinsertParentId = layer.parentId;\n\t\t\t\t\t\tinsertDepth = layer.depth - 1;\n\t\t\t\t\t\tinsertIndex = folderIndex + 1;\n\t\t\t\t\t\tmarkerHeight = rect.bottom - layerPanelTop;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif (pointerPercentage < 0.5) {\n\t\t\t\t\t\tinsertParentId = layer.parentId;\n\t\t\t\t\t\tinsertDepth = layer.depth - 1;\n\t\t\t\t\t\tinsertIndex = folderIndex;\n\t\t\t\t\t\tmarkerHeight = rect.top - layerPanelTop;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tinsertParentId = layer.parentId;\n\t\t\t\t\t\tinsertDepth = layer.depth - 1;\n\t\t\t\t\t\tinsertIndex = folderIndex + 1;\n\t\t\t\t\t\tmarkerHeight = rect.bottom - layerPanelTop;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t\t// Dragging to the empty space below all layers\n\t\t\tlet lastLayer = treeChildren[treeChildren.length - 1];\n\t\t\tif (lastLayer.getBoundingClientRect().bottom < clientY) {\n\t\t\t\tconst numberRootLayers = layers.filter((layer) => layer.entry.depth === 1).length;\n\t\t\t\tinsertParentId = undefined;\n\t\t\t\tinsertDepth = 0;\n\t\t\t\tinsertIndex = numberRootLayers;\n\t\t\t\tmarkerHeight = lastLayer.getBoundingClientRect().bottom - layerPanelTop;\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tselect,\n\t\t\tinsertParentId,\n\t\t\tinsertDepth,\n\t\t\tinsertIndex,\n\t\t\thighlightFolder,\n\t\t\tmarkerHeight,\n\t\t};\n\t}\n\n\tfunction layerPointerDown(e: PointerEvent, listing: LayerListingInfo) {\n\t\t// Only left click drags\n\t\tif (e.button !== 0 || !draggable) return;\n\n\t\tinternalDragState = {\n\t\t\tactive: false,\n\t\t\tlayerId: listing.entry.id,\n\t\t\tlisting: listing,\n\t\t\tstartX: e.clientX,\n\t\t\tstartY: e.clientY,\n\t\t};\n\t}\n\n\tfunction draggingPointerMove(e: PointerEvent) {\n\t\tif (!internalDragState || !list) return;\n\n\t\t// Calculate distance moved\n\t\tif (!internalDragState.active) {\n\t\t\tconst distance = Math.hypot(e.clientX - internalDragState.startX, e.clientY - internalDragState.startY);\n\t\t\tconst DRAG_THRESHOLD = 5;\n\n\t\t\tif (distance > DRAG_THRESHOLD) {\n\t\t\t\tinternalDragState.active = true;\n\t\t\t\tdragInPanel = true;\n\n\t\t\t\tconst layer = internalDragState.listing.entry;\n\t\t\t\tif (!$nodeGraph.selected.includes(layer.id)) {\n\t\t\t\t\tfakeHighlightOfNotYetSelectedLayerBeingDragged = layer.id;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Perform drag calculations if a drag is occurring\n\t\tif (internalDragState.active) {\n\t\t\tconst select = () => {\n\t\t\t\tif (internalDragState && !$nodeGraph.selected.includes(internalDragState.layerId)) {\n\t\t\t\t\tselectLayer(internalDragState.listing, false, false);\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tdraggingData = calculateDragIndex(list, e.clientY, select);\n\t\t}\n\t}\n\n\tfunction draggingPointerUp() {\n\t\tif (internalDragState?.active && draggingData) {\n\t\t\tconst { select, insertParentId, insertIndex } = draggingData;\n\n\t\t\t// Commit the move\n\t\t\tselect?.();\n\t\t\teditor.handle.moveLayerInTree(insertParentId, insertIndex);\n\n\t\t\t// Prevent the subsequent click event from processing\n\t\t\tjustFinishedDrag = true;\n\t\t} else if (justFinishedDrag) {\n\t\t\t// Avoid right-click abort getting stuck with `justFinishedDrag` set and blocking the first subsequent click to select a layer\n\t\t\tsetTimeout(() => {\n\t\t\t\tjustFinishedDrag = false;\n\t\t\t}, 0);\n\t\t}\n\n\t\t// Reset state\n\t\tabortDrag();\n\t}\n\n\tfunction abortDrag() {\n\t\tinternalDragState = undefined;\n\t\tdraggingData = undefined;\n\t\tfakeHighlightOfNotYetSelectedLayerBeingDragged = undefined;\n\t\tdragInPanel = false;\n\t}\n\n\tfunction draggingMouseDown(e: MouseEvent) {\n\t\t// Abort if a drag is active and the user presses the right mouse button (button 2)\n\t\tif (e.button === 2 && internalDragState?.active) {\n\t\t\tjustFinishedDrag = true;\n\t\t\tabortDrag();\n\t\t}\n\t}\n\n\tfunction draggingKeyDown(e: KeyboardEvent) {\n\t\tif (e.key === \"Escape\" && internalDragState?.active) {\n\t\t\tjustFinishedDrag = true;\n\t\t\tabortDrag();\n\t\t}\n\t}\n\n\tfunction handleLayerPanelKeyDown(e: KeyboardEvent) {\n\t\t// TODO: Handle this F2 shortcut detection in the backend, not frontend, so it uses the standard key binding system\n\n\t\t// Only handle F2 if not currently editing a layer name\n\t\tif (e.key === \"F2\" && !layers.some((layer) => layer.editingName)) {\n\t\t\t// Find the first selected layer\n\t\t\tconst selectedLayer = layers.find((layer) => layer.entry.selected);\n\t\t\tif (selectedLayer) {\n\t\t\t\te.preventDefault();\n\t\t\t\tonEditLayerName(selectedLayer);\n\t\t\t}\n\t\t}\n\t}\n\n\tasync function navigateToLayer(currentListing: LayerListingInfo, direction: \"Up\" | \"Down\") {\n\t\t// Save the current layer name\n\t\tconst inputElement = document.activeElement;\n\t\tif (inputElement instanceof HTMLInputElement) {\n\t\t\tconst name = inputElement.value || \"\";\n\t\t\teditor.handle.setLayerName(currentListing.entry.id, name);\n\t\t\tcurrentListing.entry.alias = name;\n\t\t}\n\n\t\t// Find current layer index\n\t\tconst currentIndex = layers.findIndex((layer) => layer.entry.id === currentListing.entry.id);\n\t\tif (currentIndex === -1) return;\n\n\t\t// Calculate target index based on direction\n\t\tconst targetIndex = direction === \"Down\" ? currentIndex + 1 : currentIndex - 1;\n\t\tif (targetIndex >= layers.length || targetIndex < 0) return;\n\n\t\tconst targetListing = layers[targetIndex];\n\t\tif (!targetListing) return;\n\n\t\t// Exit edit mode on current layer\n\t\tcurrentListing.editingName = false;\n\t\tdraggable = true;\n\t\tlayers = layers;\n\n\t\t// Start edit mode on target layer\n\t\tawait onEditLayerName(targetListing);\n\t}\n\n\tfunction fileDragOver(e: DragEvent) {\n\t\tif (!draggable || !e.dataTransfer || !e.dataTransfer.types.includes(\"Files\")) return;\n\n\t\t// Stop the drag from being shown as cancelled\n\t\te.preventDefault();\n\t\tdragInPanel = true;\n\n\t\tif (list) draggingData = calculateDragIndex(list, e.clientY);\n\t}\n\n\tfunction fileDrop(e: DragEvent) {\n\t\tif (!draggingData || !e.dataTransfer || !e.dataTransfer.types.includes(\"Files\")) return;\n\n\t\tconst { insertParentId, insertIndex } = draggingData;\n\n\t\te.preventDefault();\n\n\t\tArray.from(e.dataTransfer.items).forEach(async (item) => await pasteFile(item, editor, undefined, insertParentId, insertIndex));\n\n\t\tdraggingData = undefined;\n\t\tfakeHighlightOfNotYetSelectedLayerBeingDragged = undefined;\n\t\tdragInPanel = false;\n\t}\n\n\tfunction rebuildLayerHierarchy(layerStructure: LayerStructureEntry[]) {\n\t\tconst layerWithNameBeingEdited = layers.find((layer: LayerListingInfo) => layer.editingName);\n\t\tconst layerIdWithNameBeingEdited = layerWithNameBeingEdited?.entry.id;\n\n\t\t// Clear the layer hierarchy before rebuilding it\n\t\tlayers = [];\n\n\t\t// Build the new layer hierarchy\n\t\tconst recurse = (children: LayerStructureEntry[]) => {\n\t\t\tchildren.forEach((item, index) => {\n\t\t\t\tconst mapping = layerCache.get(String(item.layerId));\n\t\t\t\tif (mapping) {\n\t\t\t\t\tmapping.id = item.layerId;\n\t\t\t\t\tlayers.push({\n\t\t\t\t\t\tfolderIndex: index,\n\t\t\t\t\t\tbottomLayer: index === children.length - 1,\n\t\t\t\t\t\tentry: mapping,\n\t\t\t\t\t\teditingName: layerIdWithNameBeingEdited === item.layerId,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\t// Call self recursively if there are any children\n\t\t\t\tif (item.children.length >= 1) recurse(item.children);\n\t\t\t});\n\t\t};\n\t\trecurse(layerStructure);\n\t\tlayers = layers;\n\t}\n\n\tfunction updateLayerInTree(targetId: bigint, targetLayer: LayerPanelEntry) {\n\t\tlayerCache.set(String(targetId), targetLayer);\n\n\t\tconst layer = layers.find((layer: LayerListingInfo) => layer.entry.id === targetId);\n\t\tif (layer) {\n\t\t\tlayer.entry = targetLayer;\n\t\t\tlayers = layers;\n\t\t}\n\t}\n</script>\n\n<LayoutCol class=\"layers\" on:dragleave={() => (dragInPanel = false)}>\n\t<LayoutRow class=\"control-bar\" scrollableX={true}>\n\t\t<WidgetLayout layout={layersPanelControlBarLeftLayout} layoutTarget=\"LayersPanelControlLeftBar\" />\n\t\t{#if layersPanelControlBarLeftLayout?.length > 0 && layersPanelControlBarRightLayout?.length > 0}\n\t\t\t<Separator />\n\t\t{/if}\n\t\t<WidgetLayout layout={layersPanelControlBarRightLayout} layoutTarget=\"LayersPanelControlRightBar\" />\n\t</LayoutRow>\n\t<LayoutRow class=\"list-area\" classes={{ \"drag-ongoing\": Boolean(internalDragState?.active && draggingData) }} scrollableY={true}>\n\t\t<LayoutCol\n\t\t\tclass=\"list\"\n\t\t\tstyles={{ cursor: layerToClipUponClick && layerToClipAltKeyPressed && layerToClipUponClick.entry.clippable ? \"alias\" : \"auto\" }}\n\t\t\tdata-layer-panel\n\t\t\tbind:this={list}\n\t\t\ton:click={() => deselectAllLayers()}\n\t\t\ton:dragover={fileDragOver}\n\t\t\ton:drop={fileDrop}\n\t\t>\n\t\t\t{#each layers as listing, index}\n\t\t\t\t{@const selected = fakeHighlightOfNotYetSelectedLayerBeingDragged !== undefined ? fakeHighlightOfNotYetSelectedLayerBeingDragged === listing.entry.id : listing.entry.selected}\n\t\t\t\t<LayoutRow\n\t\t\t\t\tclass=\"layer\"\n\t\t\t\t\tclasses={{\n\t\t\t\t\t\tselected,\n\t\t\t\t\t\t\"ancestor-of-selected\": listing.entry.ancestorOfSelected,\n\t\t\t\t\t\t\"descendant-of-selected\": listing.entry.descendantOfSelected,\n\t\t\t\t\t\t\"selected-but-not-in-selected-network\": selected && !listing.entry.inSelectedNetwork,\n\t\t\t\t\t\t\"insert-folder\": (draggingData?.highlightFolder || false) && draggingData?.insertParentId === listing.entry.id,\n\t\t\t\t\t}}\n\t\t\t\t\tstyles={{ \"--layer-indent-levels\": `${listing.entry.depth - 1}` }}\n\t\t\t\t\tdata-layer\n\t\t\t\t\tdata-index={index}\n\t\t\t\t\ton:pointerdown={(e) => layerPointerDown(e, listing)}\n\t\t\t\t\ton:click={(e) => selectLayerWithModifiers(e, listing)}\n\t\t\t\t>\n\t\t\t\t\t{#if listing.entry.childrenAllowed}\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclass=\"expand-arrow\"\n\t\t\t\t\t\t\tclass:expanded={listing.entry.expanded}\n\t\t\t\t\t\t\tdisabled={!listing.entry.childrenPresent}\n\t\t\t\t\t\t\tdata-tooltip-label={listing.entry.expanded ? \"Collapse (All)\" : \"Expand (All)\"}\n\t\t\t\t\t\t\tdata-tooltip-description={(listing.entry.expanded\n\t\t\t\t\t\t\t\t? \"Hide the layers nested within. (To affect all open descendants, perform the shortcut shown.)\"\n\t\t\t\t\t\t\t\t: \"Show the layers nested within. (To affect all closed descendants, perform the shortcut shown.)\") +\n\t\t\t\t\t\t\t\t(listing.entry.ancestorOfSelected && !listing.entry.expanded ? \"\\n\\nA selected layer is currently contained within.\\n\" : \"\")}\n\t\t\t\t\t\t\tdata-tooltip-shortcut={$tooltip.altClickShortcut?.shortcut ? JSON.stringify($tooltip.altClickShortcut.shortcut) : undefined}\n\t\t\t\t\t\t\ton:click={(e) => handleExpandArrowClickWithModifiers(e, listing.entry.id)}\n\t\t\t\t\t\t\ttabindex=\"0\"\n\t\t\t\t\t\t></button>\n\t\t\t\t\t{:else}\n\t\t\t\t\t\t<div class=\"expand-arrow-none\"></div>\n\t\t\t\t\t{/if}\n\t\t\t\t\t{#if listing.entry.clipped}\n\t\t\t\t\t\t<IconLabel\n\t\t\t\t\t\t\ticon=\"Clipped\"\n\t\t\t\t\t\t\tclass=\"clipped-arrow\"\n\t\t\t\t\t\t\ttooltipLabel=\"Layer Clipped\"\n\t\t\t\t\t\t\ttooltipDescription=\"Clipping mask is active. To release it, target the bottom border of the layer and perform the shortcut shown.\"\n\t\t\t\t\t\t\ttooltipShortcut={$tooltip.altClickShortcut}\n\t\t\t\t\t\t/>\n\t\t\t\t\t{/if}\n\t\t\t\t\t<div class=\"thumbnail\">\n\t\t\t\t\t\t{#if $nodeGraph.thumbnails.has(listing.entry.id)}\n\t\t\t\t\t\t\t{@html $nodeGraph.thumbnails.get(listing.entry.id)}\n\t\t\t\t\t\t{/if}\n\t\t\t\t\t</div>\n\t\t\t\t\t{#if listing.entry.iconName}\n\t\t\t\t\t\t<IconLabel icon={listing.entry.iconName} class=\"layer-type-icon\" tooltipLabel=\"Artboard\" />\n\t\t\t\t\t{/if}\n\t\t\t\t\t<LayoutRow class=\"layer-name\" on:dblclick={() => onEditLayerName(listing)}>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tdata-text-input\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tvalue={listing.entry.alias}\n\t\t\t\t\t\t\tplaceholder={listing.entry.implementationName}\n\t\t\t\t\t\t\tdisabled={!listing.editingName}\n\t\t\t\t\t\t\ton:blur={() => onEditLayerNameDeselect(listing)}\n\t\t\t\t\t\t\ton:keydown={(e) => {\n\t\t\t\t\t\t\t\tif (e.key === \"Escape\") {\n\t\t\t\t\t\t\t\t\tonEditLayerNameDeselect(listing);\n\t\t\t\t\t\t\t\t} else if (e.key === \"Enter\") {\n\t\t\t\t\t\t\t\t\tonEditLayerNameChange(listing, e);\n\t\t\t\t\t\t\t\t} else if (e.key === \"Tab\") {\n\t\t\t\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\t\t\t\tnavigateToLayer(listing, e.shiftKey ? \"Up\" : \"Down\");\n\t\t\t\t\t\t\t\t} else if (e.key === \"ArrowUp\") {\n\t\t\t\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\t\t\t\tnavigateToLayer(listing, \"Up\");\n\t\t\t\t\t\t\t\t} else if (e.key === \"ArrowDown\") {\n\t\t\t\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\t\t\t\tnavigateToLayer(listing, \"Down\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\ton:change={(e) => onEditLayerNameChange(listing, e)}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</LayoutRow>\n\t\t\t\t\t{#if !listing.entry.unlocked || !listing.entry.parentsUnlocked}\n\t\t\t\t\t\t<IconButton\n\t\t\t\t\t\t\tclass=\"status-toggle\"\n\t\t\t\t\t\t\tclasses={{ inherited: !listing.entry.parentsUnlocked }}\n\t\t\t\t\t\t\taction={(e) => (toggleLayerLock(listing.entry.id), e?.stopPropagation())}\n\t\t\t\t\t\t\tsize={24}\n\t\t\t\t\t\t\ticon={listing.entry.unlocked ? \"PadlockUnlocked\" : \"PadlockLocked\"}\n\t\t\t\t\t\t\thoverIcon={listing.entry.unlocked ? \"PadlockLocked\" : \"PadlockUnlocked\"}\n\t\t\t\t\t\t\ttooltipLabel={listing.entry.unlocked ? \"Lock\" : \"Unlock\"}\n\t\t\t\t\t\t\ttooltipDescription={!listing.entry.parentsUnlocked ? \"A parent of this layer is locked and that status is being inherited.\" : \"\"}\n\t\t\t\t\t\t/>\n\t\t\t\t\t{/if}\n\t\t\t\t\t<IconButton\n\t\t\t\t\t\tclass=\"status-toggle\"\n\t\t\t\t\t\tclasses={{ inherited: !listing.entry.parentsVisible }}\n\t\t\t\t\t\taction={(e) => (toggleNodeVisibilityLayerPanel(listing.entry.id), e?.stopPropagation())}\n\t\t\t\t\t\tsize={24}\n\t\t\t\t\t\ticon={listing.entry.visible ? \"EyeVisible\" : \"EyeHidden\"}\n\t\t\t\t\t\thoverIcon={listing.entry.visible ? \"EyeHide\" : \"EyeShow\"}\n\t\t\t\t\t\ttooltipLabel={listing.entry.visible ? \"Hide\" : \"Show\"}\n\t\t\t\t\t\ttooltipDescription={!listing.entry.parentsVisible ? \"A parent of this layer is hidden and that status is being inherited.\" : \"\"}\n\t\t\t\t\t/>\n\t\t\t\t</LayoutRow>\n\t\t\t{/each}\n\t\t</LayoutCol>\n\t\t{#if draggingData && !draggingData.highlightFolder && dragInPanel}\n\t\t\t<div class=\"insert-mark\" style:left={`${4 + draggingData.insertDepth * 16}px`} style:top={`${draggingData.markerHeight}px`}></div>\n\t\t{/if}\n\t</LayoutRow>\n\t<LayoutRow class=\"bottom-bar\" scrollableX={true}>\n\t\t<WidgetLayout layout={layersPanelBottomBarLayout} layoutTarget=\"LayersPanelBottomBar\" />\n\t</LayoutRow>\n</LayoutCol>\n\n<style lang=\"scss\" global>\n\t.layers {\n\t\t// Control bar\n\t\t.control-bar {\n\t\t\theight: 32px;\n\t\t\tflex: 0 0 auto;\n\t\t\tmargin: 0 4px;\n\t\t\tborder-bottom: 1px solid var(--color-2-mildblack);\n\t\t\tjustify-content: space-between;\n\n\t\t\t.widget-span:first-child {\n\t\t\t\tflex: 1 1 auto;\n\t\t\t}\n\n\t\t\t&:not(:has(*)) {\n\t\t\t\tdisplay: none;\n\t\t\t}\n\t\t}\n\n\t\t// Bottom bar\n\t\t.bottom-bar {\n\t\t\theight: 24px;\n\t\t\tpadding-top: 4px;\n\t\t\tflex: 0 0 auto;\n\t\t\tmargin: 0 4px;\n\t\t\tjustify-content: flex-end;\n\t\t\tborder-top: 1px solid var(--color-2-mildblack);\n\n\t\t\t.widget-span > * {\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t&:not(:has(*)) {\n\t\t\t\tdisplay: none;\n\t\t\t}\n\t\t}\n\n\t\t// Layer hierarchy\n\t\t.list-area {\n\t\t\tposition: relative;\n\t\t\tpadding-top: 4px;\n\t\t\t// Combine with the bottom bar to avoid a double border\n\t\t\tmargin-bottom: -1px;\n\n\t\t\t&.drag-ongoing .layer {\n\t\t\t\tpointer-events: none;\n\t\t\t}\n\n\t\t\t.layer {\n\t\t\t\tflex: 0 0 auto;\n\t\t\t\talign-items: center;\n\t\t\t\tposition: relative;\n\t\t\t\tborder-bottom: 1px solid var(--color-2-mildblack);\n\t\t\t\tborder-radius: 2px;\n\t\t\t\theight: 32px;\n\t\t\t\tmargin: 0 4px;\n\t\t\t\tpadding-left: calc(var(--layer-indent-levels) * 16px);\n\n\t\t\t\t// Dimming\n\t\t\t\t&.selected {\n\t\t\t\t\tbackground: var(--color-4-dimgray);\n\t\t\t\t}\n\n\t\t\t\t&.ancestor-of-selected .expand-arrow:not(.expanded) {\n\t\t\t\t\tbackground-image: var(--inheritance-dots-background-6-lowergray);\n\t\t\t\t}\n\n\t\t\t\t&.descendant-of-selected {\n\t\t\t\t\tbackground-image: var(--inheritance-dots-background-4-dimgray);\n\t\t\t\t}\n\n\t\t\t\t&.selected-but-not-in-selected-network {\n\t\t\t\t\tbackground: rgba(var(--color-4-dimgray-rgb), 0.5);\n\t\t\t\t}\n\n\t\t\t\t&.insert-folder {\n\t\t\t\t\toutline: 3px solid var(--color-e-nearwhite);\n\t\t\t\t\toutline-offset: -3px;\n\t\t\t\t}\n\n\t\t\t\t.expand-arrow {\n\t\t\t\t\tpadding: 0;\n\t\t\t\t\tmargin: 0;\n\t\t\t\t\tmargin-right: 4px;\n\t\t\t\t\twidth: 16px;\n\t\t\t\t\theight: 100%;\n\t\t\t\t\tborder: none;\n\t\t\t\t\tposition: relative;\n\t\t\t\t\tbackground: none;\n\t\t\t\t\tflex: 0 0 auto;\n\t\t\t\t\tdisplay: flex;\n\t\t\t\t\talign-items: center;\n\t\t\t\t\tjustify-content: center;\n\t\t\t\t\tborder-radius: 2px;\n\n\t\t\t\t\t&::after {\n\t\t\t\t\t\tcontent: \"\";\n\t\t\t\t\t\tposition: absolute;\n\t\t\t\t\t\twidth: 8px;\n\t\t\t\t\t\theight: 8px;\n\t\t\t\t\t\tbackground: var(--icon-expand-collapse-arrow);\n\t\t\t\t\t}\n\n\t\t\t\t\t&[disabled]::after {\n\t\t\t\t\t\tbackground: var(--icon-expand-collapse-arrow-disabled);\n\t\t\t\t\t}\n\n\t\t\t\t\t&:hover:not([disabled]) {\n\t\t\t\t\t\tbackground: var(--color-5-dullgray);\n\n\t\t\t\t\t\t&::after {\n\t\t\t\t\t\t\tbackground: var(--icon-expand-collapse-arrow-hover);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t&.expanded::after {\n\t\t\t\t\t\ttransform: rotate(90deg);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t.expand-arrow-none {\n\t\t\t\t\tflex: 0 0 16px;\n\t\t\t\t\tmargin-right: 4px;\n\t\t\t\t}\n\n\t\t\t\t.clipped-arrow {\n\t\t\t\t\tmargin-left: 2px;\n\t\t\t\t\tmargin-right: 2px;\n\t\t\t\t}\n\n\t\t\t\t.thumbnail {\n\t\t\t\t\twidth: 36px;\n\t\t\t\t\theight: 24px;\n\t\t\t\t\tborder-radius: 2px;\n\t\t\t\t\toverflow: hidden;\n\t\t\t\t\tflex: 0 0 auto;\n\t\t\t\t\tbackground-image: var(--color-transparent-checkered-background);\n\t\t\t\t\tbackground-size: var(--color-transparent-checkered-background-size-mini);\n\t\t\t\t\tbackground-position: var(--color-transparent-checkered-background-position-mini);\n\t\t\t\t\tbackground-repeat: var(--color-transparent-checkered-background-repeat);\n\n\t\t\t\t\tsvg {\n\t\t\t\t\t\twidth: 100%;\n\t\t\t\t\t\theight: 100%;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t.layer-type-icon {\n\t\t\t\t\tmargin-left: 8px;\n\t\t\t\t\tmargin-right: -4px;\n\t\t\t\t}\n\n\t\t\t\t.layer-name {\n\t\t\t\t\tflex: 1 1 100%;\n\t\t\t\t\tmargin: 0 8px;\n\n\t\t\t\t\tinput {\n\t\t\t\t\t\tcolor: inherit;\n\t\t\t\t\t\tbackground: none;\n\t\t\t\t\t\tborder: none;\n\t\t\t\t\t\toutline: none; // Ok for input element\n\t\t\t\t\t\tmargin: 0;\n\t\t\t\t\t\tpadding: 0;\n\t\t\t\t\t\ttext-overflow: ellipsis;\n\t\t\t\t\t\twhite-space: nowrap;\n\t\t\t\t\t\toverflow: hidden;\n\t\t\t\t\t\tborder-radius: 2px;\n\t\t\t\t\t\theight: 24px;\n\t\t\t\t\t\twidth: 100%;\n\n\t\t\t\t\t\t&:disabled {\n\t\t\t\t\t\t\t-webkit-user-select: none; // Still required by Safari as of 2025\n\t\t\t\t\t\t\tuser-select: none;\n\t\t\t\t\t\t\t// Workaround for `user-select: none` not working on <input> elements\n\t\t\t\t\t\t\tpointer-events: none;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t&:focus {\n\t\t\t\t\t\t\tbackground: var(--color-1-nearblack);\n\t\t\t\t\t\t\tpadding: 0 4px;\n\n\t\t\t\t\t\t\t&::placeholder {\n\t\t\t\t\t\t\t\topacity: 0.5;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t&::placeholder {\n\t\t\t\t\t\t\topacity: 1;\n\t\t\t\t\t\t\tcolor: inherit;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t.status-toggle {\n\t\t\t\t\tflex: 0 0 auto;\n\t\t\t\t\talign-items: center;\n\t\t\t\t\theight: 100%;\n\n\t\t\t\t\t&.inherited {\n\t\t\t\t\t\tbackground-image: var(--inheritance-stripes-background);\n\t\t\t\t\t}\n\n\t\t\t\t\t.icon-button {\n\t\t\t\t\t\theight: 100%;\n\t\t\t\t\t\twidth: calc(24px + 2 * 4px);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.insert-mark {\n\t\t\t\tposition: absolute;\n\t\t\t\tleft: 4px;\n\t\t\t\tright: 4px;\n\t\t\t\tbackground: var(--color-e-nearwhite);\n\t\t\t\tmargin-top: 1px;\n\t\t\t\theight: 5px;\n\t\t\t\tz-index: 1;\n\t\t\t\tpointer-events: none;\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/panels/Properties.svelte",
    "content": "<script lang=\"ts\">\n\timport { getContext, onMount, onDestroy } from \"svelte\";\n\n\timport type { Layout } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\timport type { Editor } from \"@graphite/editor\";\n\timport { patchLayout } from \"@graphite/utility-functions/widgets\";\n\n\timport LayoutCol from \"@graphite/components/layout/LayoutCol.svelte\";\n\timport WidgetLayout from \"@graphite/components/widgets/WidgetLayout.svelte\";\n\n\tconst editor = getContext<Editor>(\"editor\");\n\n\tlet propertiesPanelLayout: Layout = [];\n\n\tonMount(() => {\n\t\teditor.subscriptions.subscribeLayoutUpdate(\"PropertiesPanel\", (data) => {\n\t\t\tpatchLayout(propertiesPanelLayout, data);\n\t\t\tpropertiesPanelLayout = propertiesPanelLayout;\n\t\t});\n\t});\n\n\tonDestroy(() => {\n\t\teditor.subscriptions.unsubscribeLayoutUpdate(\"PropertiesPanel\");\n\t});\n</script>\n\n<LayoutCol class=\"properties\">\n\t<LayoutCol class=\"sections\" scrollableY={true}>\n\t\t<WidgetLayout layout={propertiesPanelLayout} layoutTarget=\"PropertiesPanel\" />\n\t</LayoutCol>\n</LayoutCol>\n\n<style lang=\"scss\" global>\n\t.properties {\n\t\theight: 100%;\n\t\tflex: 1 1 100%;\n\n\t\t.sections {\n\t\t\tflex: 1 1 100%;\n\n\t\t\t// Used as a placeholder for empty assist widgets\n\t\t\t.separator.section.horizontal {\n\t\t\t\tmargin: 0;\n\t\t\t\tmargin-left: 24px;\n\n\t\t\t\tdiv {\n\t\t\t\t\twidth: 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t.text-button {\n\t\t\tflex-basis: 0;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/panels/Welcome.svelte",
    "content": "<script lang=\"ts\">\n\timport { getContext, onMount, onDestroy } from \"svelte\";\n\n\timport { isPlatformNative } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\timport type { Layout } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\timport type { Editor } from \"@graphite/editor\";\n\timport { pasteFile } from \"@graphite/utility-functions/files\";\n\timport { patchLayout } from \"@graphite/utility-functions/widgets\";\n\n\timport LayoutCol from \"@graphite/components/layout/LayoutCol.svelte\";\n\timport LayoutRow from \"@graphite/components/layout/LayoutRow.svelte\";\n\timport IconLabel from \"@graphite/components/widgets/labels/IconLabel.svelte\";\n\timport TextLabel from \"@graphite/components/widgets/labels/TextLabel.svelte\";\n\timport WidgetLayout from \"@graphite/components/widgets/WidgetLayout.svelte\";\n\n\tconst editor = getContext<Editor>(\"editor\");\n\n\tlet welcomePanelButtonsLayout: Layout = [];\n\n\tonMount(() => {\n\t\teditor.subscriptions.subscribeLayoutUpdate(\"WelcomeScreenButtons\", (data) => {\n\t\t\tpatchLayout(welcomePanelButtonsLayout, data);\n\t\t\twelcomePanelButtonsLayout = welcomePanelButtonsLayout;\n\t\t});\n\t});\n\n\tonDestroy(() => {\n\t\teditor.subscriptions.unsubscribeLayoutUpdate(\"WelcomeScreenButtons\");\n\t});\n\n\tfunction dropFile(e: DragEvent) {\n\t\tif (!e.dataTransfer) return;\n\n\t\te.preventDefault();\n\n\t\tArray.from(e.dataTransfer.items).forEach(async (item) => await pasteFile(item, editor));\n\t}\n</script>\n\n<LayoutCol class=\"welcome-panel\" on:dragover={(e) => e.preventDefault()} on:drop={dropFile}>\n\t<LayoutCol class=\"top-spacer\"></LayoutCol>\n\t<LayoutCol class=\"content-container\">\n\t\t<LayoutCol class=\"content\">\n\t\t\t<LayoutRow class=\"logotype\">\n\t\t\t\t<IconLabel icon=\"GraphiteLogotypeSolid\" />\n\t\t\t</LayoutRow>\n\t\t\t<LayoutRow class=\"actions\">\n\t\t\t\t<WidgetLayout layout={welcomePanelButtonsLayout} layoutTarget=\"WelcomeScreenButtons\" />\n\t\t\t</LayoutRow>\n\t\t</LayoutCol>\n\t</LayoutCol>\n\t<LayoutCol class=\"bottom-message\">\n\t\t<TextLabel italic={true} disabled={true}>\n\t\t\t{#if isPlatformNative()}\n\t\t\t\tYou are testing Release Candidate 4 of the 1.0 desktop release. Please regularly check Discord for the next testing build and report issues you encounter.\n\t\t\t{/if}\n\t\t</TextLabel>\n\t</LayoutCol>\n</LayoutCol>\n\n<style lang=\"scss\" global>\n\t.welcome-panel {\n\t\tbackground: var(--color-2-mildblack);\n\t\tmargin: 4px;\n\t\tborder-radius: 2px;\n\t\tjustify-content: space-between;\n\n\t\t.content-container {\n\t\t\tflex: 0 0 auto;\n\t\t\tjustify-content: center;\n\n\t\t\t.content {\n\t\t\t\tflex: 0 0 auto;\n\t\t\t\talign-items: center;\n\n\t\t\t\t.logotype {\n\t\t\t\t\tmargin-top: 8px;\n\t\t\t\t\tmargin-bottom: 40px;\n\n\t\t\t\t\tsvg {\n\t\t\t\t\t\twidth: auto;\n\t\t\t\t\t\theight: 120px;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t.actions {\n\t\t\t\t\tmargin-bottom: 8px;\n\n\t\t\t\t\ttable {\n\t\t\t\t\t\tborder-spacing: 8px;\n\t\t\t\t\t\tmargin: -8px;\n\n\t\t\t\t\t\ttd {\n\t\t\t\t\t\t\tpadding: 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t.top-spacer {\n\t\t\tflex: 0 1 48px;\n\t\t}\n\n\t\t.bottom-message {\n\t\t\tflex: 0 0 48px;\n\t\t\talign-items: center;\n\t\t\tjustify-content: end;\n\n\t\t\t.text-label {\n\t\t\t\twhite-space: wrap;\n\t\t\t\tmargin: 0 1em;\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/views/Graph.svelte",
    "content": "<script lang=\"ts\">\n\timport { getContext } from \"svelte\";\n\timport { cubicInOut } from \"svelte/easing\";\n\timport { fade } from \"svelte/transition\";\n\n\timport type { FrontendGraphInput, FrontendGraphOutput, FrontendNode } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\timport type { Editor } from \"@graphite/editor\";\n\timport type { DocumentStore } from \"@graphite/stores/document\";\n\timport { closeContextMenu } from \"@graphite/stores/node-graph\";\n\timport type { NodeGraphStore } from \"@graphite/stores/node-graph\";\n\n\timport NodeCatalog from \"@graphite/components/floating-menus/NodeCatalog.svelte\";\n\timport FloatingMenu from \"@graphite/components/layout/FloatingMenu.svelte\";\n\timport LayoutCol from \"@graphite/components/layout/LayoutCol.svelte\";\n\timport IconButton from \"@graphite/components/widgets/buttons/IconButton.svelte\";\n\timport TextButton from \"@graphite/components/widgets/buttons/TextButton.svelte\";\n\timport TextLabel from \"@graphite/components/widgets/labels/TextLabel.svelte\";\n\n\tconst GRID_COLLAPSE_SPACING = 10;\n\tconst GRID_SIZE = 24;\n\tconst FADE_TRANSITION = { duration: 200, easing: cubicInOut };\n\n\tconst editor = getContext<Editor>(\"editor\");\n\tconst nodeGraph = getContext<NodeGraphStore>(\"nodeGraph\");\n\tconst documentState = getContext<DocumentStore>(\"document\");\n\n\tlet graph: HTMLDivElement | undefined;\n\n\t$: gridSpacing = calculateGridSpacing($nodeGraph.transform.scale);\n\t$: gridDotRadius = 1 + Math.floor($nodeGraph.transform.scale - 0.5 + 0.001) / 2;\n\n\t// Close the context menu when the graph view overlay is closed\n\t$: if (!$documentState.graphViewOverlayOpen) closeContextMenu();\n\n\tlet inputElement: HTMLInputElement;\n\tlet hoveringImportIndex: number | undefined = undefined;\n\tlet hoveringExportIndex: number | undefined = undefined;\n\n\tlet editingNameImportIndex: number | undefined = undefined;\n\tlet editingNameExportIndex: number | undefined = undefined;\n\tlet editingNameText = \"\";\n\n\tfunction exportsToEdgeTextInputWidth() {\n\t\tlet exportTextDivs = document.querySelectorAll(`[data-export-text-edge]`);\n\t\tlet exportTextDiv = Array.from(exportTextDivs).find((div) => {\n\t\t\treturn div.getAttribute(\"data-index\") === String(editingNameExportIndex);\n\t\t});\n\t\tif (!graph || !exportTextDiv) return \"50px\";\n\t\tlet distance = graph.getBoundingClientRect().right - exportTextDiv.getBoundingClientRect().right;\n\t\treturn distance - 15 + \"px\";\n\t}\n\n\tfunction importsToEdgeTextInputWidth() {\n\t\tlet importTextDivs = document.querySelectorAll(`[data-import-text-edge]`);\n\t\tlet importTextDiv = Array.from(importTextDivs).find((div) => {\n\t\t\treturn div.getAttribute(\"data-index\") === String(editingNameImportIndex);\n\t\t});\n\t\tif (!graph || !importTextDiv) return \"50px\";\n\t\tlet distance = importTextDiv.getBoundingClientRect().left - graph.getBoundingClientRect().left;\n\t\treturn distance - 15 + \"px\";\n\t}\n\n\tfunction setEditingImportNameIndex(index: number, currentName: string) {\n\t\tfocusInput(currentName);\n\t\teditingNameImportIndex = index;\n\t}\n\n\tfunction setEditingExportNameIndex(index: number, currentName: string) {\n\t\tfocusInput(currentName);\n\t\teditingNameExportIndex = index;\n\t}\n\n\tfunction focusInput(currentName: string) {\n\t\teditingNameText = currentName;\n\t\tsetTimeout(() => {\n\t\t\tif (inputElement) {\n\t\t\t\tinputElement.focus();\n\t\t\t}\n\t\t}, 0);\n\t}\n\n\tfunction setEditingImportName(event: Event) {\n\t\tif (editingNameImportIndex !== undefined) {\n\t\t\tif (!(event.target instanceof HTMLInputElement)) return;\n\t\t\tlet text = event.target.value;\n\t\t\teditor.handle.setImportName(editingNameImportIndex, text);\n\t\t\teditingNameImportIndex = undefined;\n\t\t}\n\t}\n\n\tfunction setEditingExportName(event: Event) {\n\t\tif (editingNameExportIndex !== undefined) {\n\t\t\tif (!(event.target instanceof HTMLInputElement)) return;\n\t\t\tlet text = event.target.value;\n\t\t\teditor.handle.setExportName(editingNameExportIndex, text);\n\t\t\teditingNameExportIndex = undefined;\n\t\t}\n\t}\n\n\tfunction calculateGridSpacing(scale: number): number {\n\t\tconst dense = scale * GRID_SIZE;\n\t\tlet sparse = dense;\n\n\t\twhile (sparse > 0 && sparse < GRID_COLLAPSE_SPACING) {\n\t\t\tsparse *= 2;\n\t\t}\n\n\t\treturn sparse;\n\t}\n\n\tfunction createNode(identifier: string) {\n\t\tif ($nodeGraph.contextMenuInformation === undefined) return;\n\n\t\teditor.handle.createNode(identifier, $nodeGraph.contextMenuInformation.contextMenuCoordinates[0], $nodeGraph.contextMenuInformation.contextMenuCoordinates[1]);\n\t}\n\n\tfunction nodeBorderMask(nodeWidth: number, primaryInputExists: boolean, exposedSecondaryInputs: number, primaryOutputExists: boolean, exposedSecondaryOutputs: number): string {\n\t\tconst nodeHeight = Math.max(1 + exposedSecondaryInputs, 1 + exposedSecondaryOutputs) * 24;\n\n\t\tconst boxes: { x: number; y: number; width: number; height: number }[] = [];\n\n\t\t// Primary input\n\t\tif (primaryInputExists) boxes.push({ x: -8, y: 4, width: 16, height: 16 });\n\t\t// Secondary inputs\n\t\tfor (let i = 0; i < exposedSecondaryInputs; i++) boxes.push({ x: -8, y: 4 + (i + 1) * 24, width: 16, height: 16 });\n\n\t\t// Primary output\n\t\tif (primaryOutputExists) boxes.push({ x: nodeWidth - 8, y: 4, width: 16, height: 16 });\n\t\t// Exposed outputs\n\t\tfor (let i = 0; i < exposedSecondaryOutputs; i++) boxes.push({ x: nodeWidth - 8, y: 4 + (i + 1) * 24, width: 16, height: 16 });\n\n\t\treturn borderMask(boxes, nodeWidth, nodeHeight);\n\t}\n\n\tfunction layerBorderMask(nodeWidthFromThumbnail: number, nodeChainAreaLeftExtension: number, hasLeftInputWire: boolean): string {\n\t\tconst NODE_HEIGHT = 2 * 24;\n\t\tconst THUMBNAIL_WIDTH = 72 + 8 * 2;\n\t\tconst FUDGE_HEIGHT_BEYOND_LAYER_HEIGHT = 2;\n\n\t\tconst nodeWidth = nodeWidthFromThumbnail + nodeChainAreaLeftExtension;\n\n\t\tconst boxes: { x: number; y: number; width: number; height: number }[] = [];\n\n\t\t// Left input\n\t\tif (hasLeftInputWire && nodeChainAreaLeftExtension > 0) {\n\t\t\tboxes.push({ x: -8, y: 16, width: 16, height: 16 });\n\t\t}\n\n\t\t// Thumbnail\n\t\tboxes.push({ x: nodeChainAreaLeftExtension - 8, y: -FUDGE_HEIGHT_BEYOND_LAYER_HEIGHT, width: THUMBNAIL_WIDTH, height: NODE_HEIGHT + FUDGE_HEIGHT_BEYOND_LAYER_HEIGHT * 2 });\n\n\t\t// Right visibility button\n\t\tboxes.push({ x: nodeWidth - 12, y: (NODE_HEIGHT - 24) / 2, width: 24, height: 24 });\n\n\t\treturn borderMask(boxes, nodeWidth, NODE_HEIGHT);\n\t}\n\n\tfunction borderMask(boxes: { x: number; y: number; width: number; height: number }[], nodeWidth: number, nodeHeight: number): string {\n\t\tconst rectangles = boxes.map((box) => `M${box.x},${box.y} L${box.x + box.width},${box.y} L${box.x + box.width},${box.y + box.height} L${box.x},${box.y + box.height}z`);\n\t\treturn `M-2,-2 L${nodeWidth + 2},-2 L${nodeWidth + 2},${nodeHeight + 2} L-2,${nodeHeight + 2}z ${rectangles.join(\" \")}`;\n\t}\n\n\tfunction dataTypeTooltipLabel(value: FrontendGraphInput | FrontendGraphOutput): string {\n\t\treturn `Data Type: ${value.resolvedType}`;\n\t}\n\n\tfunction nodeNameTooltipLabel(node: FrontendNode): string {\n\t\treturn node.displayName === node.implementationName ? node.displayName : `${node.displayName} (${node.implementationName})`;\n\t}\n\n\tfunction validTypesText(value: FrontendGraphInput): string {\n\t\tconst validTypes = value.validTypes.length > 0 ? value.validTypes.map((x) => `• ${x}`).join(\"\\n\") : \"None\";\n\t\treturn `Valid Types:\\n${validTypes}`;\n\t}\n\n\tfunction outputConnectedToText(output: FrontendGraphOutput): string {\n\t\treturn editor.handle.inDevelopmentMode() ? output.connectedTo.join(\"\\n\") : \"\";\n\t}\n\n\tfunction inputConnectedToText(input: FrontendGraphInput): string {\n\t\treturn editor.handle.inDevelopmentMode() ? input.connectedTo : \"\";\n\t}\n\n\tfunction zipWithUndefined(arr1: FrontendGraphInput[], arr2: FrontendGraphOutput[]) {\n\t\tconst maxLength = Math.max(arr1.length, arr2.length);\n\t\tconst result = [];\n\t\tfor (let i = 0; i < maxLength; i++) {\n\t\t\tresult.push([arr1[i], arr2[i]]);\n\t\t}\n\t\treturn result;\n\t}\n</script>\n\n<div\n\tclass=\"graph\"\n\tbind:this={graph}\n\tstyle:--grid-spacing={`${gridSpacing}px`}\n\tstyle:--grid-offset-x={`${$nodeGraph.transform.x}px`}\n\tstyle:--grid-offset-y={`${$nodeGraph.transform.y}px`}\n\tstyle:--grid-dot-radius={`${gridDotRadius}px`}\n\tdata-node-graph\n>\n\t<!-- Right click menu for adding nodes -->\n\t{#if $nodeGraph.contextMenuInformation}\n\t\t<FloatingMenu\n\t\t\tclass=\"context-menu\"\n\t\t\tdata-context-menu\n\t\t\tstyles={{\n\t\t\t\tleft: `${$nodeGraph.contextMenuInformation.contextMenuCoordinates[0] * $nodeGraph.transform.scale + $nodeGraph.transform.x}px`,\n\t\t\t\ttop: `${$nodeGraph.contextMenuInformation.contextMenuCoordinates[1] * $nodeGraph.transform.scale + $nodeGraph.transform.y}px`,\n\t\t\t}}\n\t\t\topen={true}\n\t\t\ttype=\"Popover\"\n\t\t\tdirection=\"BottomLeft\"\n\t\t>\n\t\t\t{#if $nodeGraph.contextMenuInformation.contextMenuData.type === \"CreateNode\"}\n\t\t\t\t<NodeCatalog initialSearchTerm={$nodeGraph.contextMenuInformation.contextMenuData.data.compatibleType || \"\"} on:selectNodeType={(e) => createNode(e.detail)} />\n\t\t\t{:else if $nodeGraph.contextMenuInformation.contextMenuData.type === \"ModifyNode\"}\n\t\t\t\t<LayoutCol class=\"modify-node-menu\">\n\t\t\t\t\t<TextButton\n\t\t\t\t\t\tlabel=\"Merge Selected Nodes\"\n\t\t\t\t\t\taction={() => {\n\t\t\t\t\t\t\teditor.handle.mergeSelectedNodes();\n\t\t\t\t\t\t\tcloseContextMenu();\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tflush={true}\n\t\t\t\t\t/>\n\t\t\t\t\t{@const currentlyIsNode = $nodeGraph.contextMenuInformation.contextMenuData.data.currentlyIsNode}\n\t\t\t\t\t<TextButton\n\t\t\t\t\t\tlabel={currentlyIsNode ? \"Display as Layer\" : \"Display as Node\"}\n\t\t\t\t\t\taction={() => {\n\t\t\t\t\t\t\tif ($nodeGraph.contextMenuInformation?.contextMenuData.type === \"ModifyNode\") {\n\t\t\t\t\t\t\t\teditor.handle.setToNodeOrLayer($nodeGraph.contextMenuInformation.contextMenuData.data.nodeId, currentlyIsNode);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcloseContextMenu();\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tdisabled={!$nodeGraph.contextMenuInformation.contextMenuData.data.canBeLayer}\n\t\t\t\t\t\tflush={true}\n\t\t\t\t\t/>\n\t\t\t\t\t{#if $nodeGraph.contextMenuInformation.contextMenuData.data.hasSelectedLayers}\n\t\t\t\t\t\t{@const allLocked = $nodeGraph.contextMenuInformation.contextMenuData.data.allSelectedLayersLocked}\n\t\t\t\t\t\t{@const nodeId = $nodeGraph.contextMenuInformation.contextMenuData.data.nodeId}\n\t\t\t\t\t\t<TextButton\n\t\t\t\t\t\t\tlabel={allLocked ? \"Unlock\" : \"Lock\"}\n\t\t\t\t\t\t\taction={() => {\n\t\t\t\t\t\t\t\tif ($nodeGraph.selected.includes(nodeId)) {\n\t\t\t\t\t\t\t\t\teditor.handle.toggleSelectedLocked();\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\teditor.handle.toggleLayerLock(nodeId);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcloseContextMenu();\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tflush={true}\n\t\t\t\t\t\t/>\n\t\t\t\t\t{/if}\n\t\t\t\t</LayoutCol>\n\t\t\t{/if}\n\t\t</FloatingMenu>\n\t{/if}\n\n\t{#if $nodeGraph.error}\n\t\t<div class=\"node-error-container\" style:transform-origin=\"0 0\" style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}>\n\t\t\t<span class=\"node-error faded\" style:left={`${$nodeGraph.error.position[0]}px`} style:top={`${$nodeGraph.error.position[1]}px`} transition:fade={FADE_TRANSITION}>\n\t\t\t\t{$nodeGraph.error.error}\n\t\t\t</span>\n\t\t\t<span class=\"node-error hover\" style:left={`${$nodeGraph.error.position[0]}px`} style:top={`${$nodeGraph.error.position[1]}px`} transition:fade={FADE_TRANSITION}>\n\t\t\t\t{$nodeGraph.error.error}\n\t\t\t</span>\n\t\t</div>\n\t{/if}\n\n\t<!-- Click target debug visualizations -->\n\t{#if $nodeGraph.clickTargets}\n\t\t<div class=\"click-targets\" style:transform-origin=\"0 0\" style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}>\n\t\t\t<svg>\n\t\t\t\t{#each $nodeGraph.clickTargets.nodeClickTargets as pathString}\n\t\t\t\t\t<path class=\"node\" d={pathString} />\n\t\t\t\t{/each}\n\t\t\t\t{#each $nodeGraph.clickTargets.layerClickTargets as pathString}\n\t\t\t\t\t<path class=\"layer\" d={pathString} />\n\t\t\t\t{/each}\n\t\t\t\t{#each $nodeGraph.clickTargets.connectorClickTargets as pathString}\n\t\t\t\t\t<path class=\"connector\" d={pathString} />\n\t\t\t\t{/each}\n\t\t\t\t{#each $nodeGraph.clickTargets.iconClickTargets as pathString}\n\t\t\t\t\t<path class=\"visibility\" d={pathString} />\n\t\t\t\t{/each}\n\t\t\t\t<path class=\"all-nodes-bounding-box\" d={$nodeGraph.clickTargets.allNodesBoundingBox} />\n\t\t\t\t{#each $nodeGraph.clickTargets.modifyImportExport as pathString}\n\t\t\t\t\t<path class=\"modify-import-export\" d={pathString} />\n\t\t\t\t{/each}\n\t\t\t</svg>\n\t\t</div>\n\t{/if}\n\n\t<!-- Thick vertical layer connection wires -->\n\t<div class=\"wires\" style:transform-origin=\"0 0\" style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}>\n\t\t<svg>\n\t\t\t{#each $nodeGraph.wires.values() as map}\n\t\t\t\t{#each map.values() as { pathString, dataType, thick, dashed }}\n\t\t\t\t\t{#if thick}\n\t\t\t\t\t\t<path\n\t\t\t\t\t\t\td={pathString}\n\t\t\t\t\t\t\tstyle:--data-line-width=\"8px\"\n\t\t\t\t\t\t\tstyle:--data-color={`var(--color-data-${dataType.toLowerCase()})`}\n\t\t\t\t\t\t\tstyle:--data-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`}\n\t\t\t\t\t\t\tstyle:--data-dasharray={`3,${dashed ? 2 : 0}`}\n\t\t\t\t\t\t/>\n\t\t\t\t\t{/if}\n\t\t\t\t{/each}\n\t\t\t{/each}\n\t\t</svg>\n\t</div>\n\n\t<!-- Import and Export connectors -->\n\t<div class=\"imports-and-exports\" style:transform-origin=\"0 0\" style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}>\n\t\t{#if $nodeGraph.updateImportsExports}\n\t\t\t{#each $nodeGraph.updateImportsExports.imports as frontendOutput, index}\n\t\t\t\t{#if frontendOutput}\n\t\t\t\t\t<svg\n\t\t\t\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\t\t\t\tviewBox=\"0 0 8 8\"\n\t\t\t\t\t\tclass=\"connector\"\n\t\t\t\t\t\tdata-connector=\"output\"\n\t\t\t\t\t\tdata-tooltip-label={dataTypeTooltipLabel(frontendOutput)}\n\t\t\t\t\t\tdata-tooltip-description={outputConnectedToText(frontendOutput)}\n\t\t\t\t\t\tdata-datatype={frontendOutput.dataType}\n\t\t\t\t\t\tstyle:--data-color={`var(--color-data-${frontendOutput.dataType.toLowerCase()})`}\n\t\t\t\t\t\tstyle:--data-color-dim={`var(--color-data-${frontendOutput.dataType.toLowerCase()}-dim)`}\n\t\t\t\t\t\tstyle:--offset-left={($nodeGraph.updateImportsExports.importPosition[0] - 8) / 24}\n\t\t\t\t\t\tstyle:--offset-top={($nodeGraph.updateImportsExports.importPosition[1] - 8) / 24 + index}\n\t\t\t\t\t>\n\t\t\t\t\t\t{#if frontendOutput.connectedTo.length > 0}\n\t\t\t\t\t\t\t<path d=\"M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z\" fill=\"var(--data-color)\" />\n\t\t\t\t\t\t{:else}\n\t\t\t\t\t\t\t<path d=\"M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z\" fill=\"var(--data-color-dim)\" />\n\t\t\t\t\t\t{/if}\n\t\t\t\t\t</svg>\n\n\t\t\t\t\t<div\n\t\t\t\t\t\ton:pointerenter={() => (hoveringImportIndex = index)}\n\t\t\t\t\t\ton:pointerleave={() => (hoveringImportIndex = undefined)}\n\t\t\t\t\t\tclass=\"edit-import-export import\"\n\t\t\t\t\t\tclass:separator-bottom={index === 0 && $nodeGraph.updateImportsExports.addImportExport}\n\t\t\t\t\t\tclass:separator-top={index === 1 && $nodeGraph.updateImportsExports.addImportExport}\n\t\t\t\t\t\tstyle:--offset-left={($nodeGraph.updateImportsExports.importPosition[0] - 8) / 24}\n\t\t\t\t\t\tstyle:--offset-top={($nodeGraph.updateImportsExports.importPosition[1] - 8) / 24 + index}\n\t\t\t\t\t>\n\t\t\t\t\t\t{#if editingNameImportIndex === index}\n\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\tclass=\"import-text-input\"\n\t\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\t\tstyle:width={importsToEdgeTextInputWidth()}\n\t\t\t\t\t\t\t\tbind:this={inputElement}\n\t\t\t\t\t\t\t\tbind:value={editingNameText}\n\t\t\t\t\t\t\t\ton:blur={setEditingImportName}\n\t\t\t\t\t\t\t\ton:keydown={(e) => e.key === \"Enter\" && setEditingImportName(e)}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t{:else}\n\t\t\t\t\t\t\t<p class=\"import-text\" on:dblclick={() => setEditingImportNameIndex(index, frontendOutput.name)}>\n\t\t\t\t\t\t\t\t{frontendOutput.name}\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t{/if}\n\t\t\t\t\t\t{#if (hoveringImportIndex === index || editingNameImportIndex === index) && $nodeGraph.updateImportsExports.addImportExport}\n\t\t\t\t\t\t\t<IconButton\n\t\t\t\t\t\t\t\tsize={16}\n\t\t\t\t\t\t\t\ticon=\"Remove\"\n\t\t\t\t\t\t\t\tclass=\"remove-button-import\"\n\t\t\t\t\t\t\t\tdata-index={index}\n\t\t\t\t\t\t\t\tdata-import-text-edge\n\t\t\t\t\t\t\t\taction={() => {\n\t\t\t\t\t\t\t\t\t/* Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown */\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t{#if index > 0}\n\t\t\t\t\t\t\t\t<div class=\"reorder-drag-grip\" data-tooltip-description=\"Reorder this export\"></div>\n\t\t\t\t\t\t\t{/if}\n\t\t\t\t\t\t{/if}\n\t\t\t\t\t</div>\n\t\t\t\t{:else}\n\t\t\t\t\t<div\n\t\t\t\t\t\tclass=\"plus\"\n\t\t\t\t\t\tstyle:--offset-top={($nodeGraph.updateImportsExports.importPosition[1] - 12) / 24}\n\t\t\t\t\t\tstyle:--offset-left={($nodeGraph.updateImportsExports.importPosition[0] - 12) / 24}\n\t\t\t\t\t>\n\t\t\t\t\t\t<IconButton size={24} icon=\"Add\" action={() => editor.handle.addPrimaryImport()} />\n\t\t\t\t\t</div>\n\t\t\t\t{/if}\n\t\t\t{/each}\n\n\t\t\t{#each $nodeGraph.updateImportsExports.exports as frontendInput, index}\n\t\t\t\t{#if frontendInput}\n\t\t\t\t\t<svg\n\t\t\t\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\t\t\t\tviewBox=\"0 0 8 8\"\n\t\t\t\t\t\tclass=\"connector\"\n\t\t\t\t\t\tdata-connector=\"input\"\n\t\t\t\t\t\tdata-tooltip-label={dataTypeTooltipLabel(frontendInput)}\n\t\t\t\t\t\tdata-tooltip-description={inputConnectedToText(frontendInput)}\n\t\t\t\t\t\tdata-datatype={frontendInput.dataType}\n\t\t\t\t\t\tstyle:--data-color={`var(--color-data-${frontendInput.dataType.toLowerCase()})`}\n\t\t\t\t\t\tstyle:--data-color-dim={`var(--color-data-${frontendInput.dataType.toLowerCase()}-dim)`}\n\t\t\t\t\t\tstyle:--offset-left={($nodeGraph.updateImportsExports.exportPosition[0] - 8) / 24}\n\t\t\t\t\t\tstyle:--offset-top={($nodeGraph.updateImportsExports.exportPosition[1] - 8) / 24 + index}\n\t\t\t\t\t>\n\t\t\t\t\t\t{#if frontendInput.connectedTo !== \"Connected to nothing.\"}\n\t\t\t\t\t\t\t<path d=\"M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z\" fill=\"var(--data-color)\" />\n\t\t\t\t\t\t{:else}\n\t\t\t\t\t\t\t<path d=\"M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z\" fill=\"var(--data-color-dim)\" />\n\t\t\t\t\t\t{/if}\n\t\t\t\t\t</svg>\n\t\t\t\t\t<div\n\t\t\t\t\t\ton:pointerenter={() => (hoveringExportIndex = index)}\n\t\t\t\t\t\ton:pointerleave={() => (hoveringExportIndex = undefined)}\n\t\t\t\t\t\tclass=\"edit-import-export export\"\n\t\t\t\t\t\tclass:separator-bottom={index === 0 && $nodeGraph.updateImportsExports.addImportExport}\n\t\t\t\t\t\tclass:separator-top={index === 1 && $nodeGraph.updateImportsExports.addImportExport}\n\t\t\t\t\t\tstyle:--offset-left={($nodeGraph.updateImportsExports.exportPosition[0] - 8) / 24}\n\t\t\t\t\t\tstyle:--offset-top={($nodeGraph.updateImportsExports.exportPosition[1] - 8) / 24 + index}\n\t\t\t\t\t>\n\t\t\t\t\t\t{#if (hoveringExportIndex === index || editingNameExportIndex === index) && $nodeGraph.updateImportsExports.addImportExport}\n\t\t\t\t\t\t\t{#if index > 0}\n\t\t\t\t\t\t\t\t<div class=\"reorder-drag-grip\" data-tooltip-description=\"Reorder this export\"></div>\n\t\t\t\t\t\t\t{/if}\n\t\t\t\t\t\t\t<IconButton\n\t\t\t\t\t\t\t\tsize={16}\n\t\t\t\t\t\t\t\ticon=\"Remove\"\n\t\t\t\t\t\t\t\tclass=\"remove-button-export\"\n\t\t\t\t\t\t\t\tdata-index={index}\n\t\t\t\t\t\t\t\tdata-export-text-edge\n\t\t\t\t\t\t\t\taction={() => {\n\t\t\t\t\t\t\t\t\t/* Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown */\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t{/if}\n\t\t\t\t\t\t{#if editingNameExportIndex === index}\n\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\t\tstyle:width={exportsToEdgeTextInputWidth()}\n\t\t\t\t\t\t\t\tbind:this={inputElement}\n\t\t\t\t\t\t\t\tbind:value={editingNameText}\n\t\t\t\t\t\t\t\ton:blur={setEditingExportName}\n\t\t\t\t\t\t\t\ton:keydown={(e) => e.key === \"Enter\" && setEditingExportName(e)}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t{:else}\n\t\t\t\t\t\t\t<p class=\"export-text\" on:dblclick={() => setEditingExportNameIndex(index, frontendInput.name)}>\n\t\t\t\t\t\t\t\t{frontendInput.name}\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t{/if}\n\t\t\t\t\t</div>\n\t\t\t\t{:else}\n\t\t\t\t\t<div\n\t\t\t\t\t\tclass=\"plus\"\n\t\t\t\t\t\tstyle:--offset-left={($nodeGraph.updateImportsExports.exportPosition[0] - 12) / 24}\n\t\t\t\t\t\tstyle:--offset-top={($nodeGraph.updateImportsExports.exportPosition[1] - 12) / 24}\n\t\t\t\t\t>\n\t\t\t\t\t\t<IconButton size={24} icon=\"Add\" action={() => editor.handle.addPrimaryExport()} />\n\t\t\t\t\t</div>\n\t\t\t\t{/if}\n\t\t\t{/each}\n\n\t\t\t{#if $nodeGraph.updateImportsExports.addImportExport}\n\t\t\t\t<div\n\t\t\t\t\tclass=\"plus\"\n\t\t\t\t\tstyle:--offset-left={($nodeGraph.updateImportsExports.importPosition[0] - 12) / 24}\n\t\t\t\t\tstyle:--offset-top={($nodeGraph.updateImportsExports.importPosition[1] - 12) / 24 + $nodeGraph.updateImportsExports.imports.length}\n\t\t\t\t>\n\t\t\t\t\t<IconButton size={24} icon=\"Add\" action={() => editor.handle.addSecondaryImport()} />\n\t\t\t\t</div>\n\t\t\t\t<div\n\t\t\t\t\tclass=\"plus\"\n\t\t\t\t\tstyle:--offset-left={($nodeGraph.updateImportsExports.exportPosition[0] - 12) / 24}\n\t\t\t\t\tstyle:--offset-top={($nodeGraph.updateImportsExports.exportPosition[1] - 12) / 24 + $nodeGraph.updateImportsExports.exports.length}\n\t\t\t\t>\n\t\t\t\t\t<IconButton size={24} icon=\"Add\" action={() => editor.handle.addSecondaryExport()} />\n\t\t\t\t</div>\n\t\t\t{/if}\n\n\t\t\t{#if $nodeGraph.reorderImportIndex !== undefined}\n\t\t\t\t{@const position = {\n\t\t\t\t\tx: Number($nodeGraph.updateImportsExports.importPosition[0]),\n\t\t\t\t\ty: Number($nodeGraph.updateImportsExports.importPosition[1]) + Number($nodeGraph.reorderImportIndex) * 24,\n\t\t\t\t}}\n\t\t\t\t<div class=\"reorder-bar\" style:--offset-left={(position.x - 48) / 24} style:--offset-top={(position.y - 12) / 24}></div>\n\t\t\t{/if}\n\n\t\t\t{#if $nodeGraph.reorderExportIndex !== undefined}\n\t\t\t\t{@const position = {\n\t\t\t\t\tx: Number($nodeGraph.updateImportsExports.exportPosition[0]),\n\t\t\t\t\ty: Number($nodeGraph.updateImportsExports.exportPosition[1]) + Number($nodeGraph.reorderExportIndex) * 24,\n\t\t\t\t}}\n\t\t\t\t<div class=\"reorder-bar\" style:--offset-left={position.x / 24} style:--offset-top={(position.y - 12) / 24}></div>\n\t\t\t{/if}\n\t\t{/if}\n\t</div>\n\n\t<!-- Layers and nodes -->\n\t<div class=\"layers-and-nodes\" style:transform-origin=\"0 0\" style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}>\n\t\t<!-- Layers -->\n\t\t{#each Array.from($nodeGraph.nodes)\n\t\t\t.filter(([nodeId, node]) => node.isLayer && $nodeGraph.visibleNodes.has(nodeId))\n\t\t\t.map(([_, node], nodeIndex) => ({ node, nodeIndex })) as { node, nodeIndex } (nodeIndex)}\n\t\t\t{@const clipPathId = String(Math.random()).substring(2)}\n\t\t\t{@const stackDataInput = node.exposedInputs[0]}\n\t\t\t{@const layerAreaWidth = $nodeGraph.layerWidths.get(node.id) || 8}\n\t\t\t{@const layerChainWidth = $nodeGraph.chainWidths.get(node.id) || 0}\n\t\t\t{@const hasLeftInputWire = $nodeGraph.hasLeftInputWire.get(node.id) || false}\n\t\t\t{@const description = node.reference ? $nodeGraph.nodeDescriptions.get(node.reference) : undefined}\n\t\t\t<div\n\t\t\t\tclass=\"layer\"\n\t\t\t\tclass:selected={$nodeGraph.selected.includes(node.id)}\n\t\t\t\tclass:in-selected-network={$nodeGraph.inSelectedNetwork}\n\t\t\t\tclass:previewed={node.previewed}\n\t\t\t\tclass:disabled={!node.visible}\n\t\t\t\tclass:locked={node.locked}\n\t\t\t\tstyle:--offset-left={node.position?.[0] || 0}\n\t\t\t\tstyle:--offset-top={node.position?.[1] || 0}\n\t\t\t\tstyle:--clip-path-id={`url(#${clipPathId})`}\n\t\t\t\tstyle:--data-color={`var(--color-data-${(node.primaryOutput?.dataType || \"General\").toLowerCase()})`}\n\t\t\t\tstyle:--data-color-dim={`var(--color-data-${(node.primaryOutput?.dataType || \"General\").toLowerCase()}-dim)`}\n\t\t\t\tstyle:--layer-area-width={layerAreaWidth}\n\t\t\t\tstyle:--node-chain-area-left-extension={layerChainWidth !== 0 ? layerChainWidth + 0.5 : 0}\n\t\t\t\tdata-tooltip-label={nodeNameTooltipLabel(node)}\n\t\t\t\tdata-tooltip-description={`\n\t\t\t\t\t${(description || \"\").trim()}${editor.handle.inDevelopmentMode() ? `\\n\\nID: ${node.id}. Position: (${node.position[0]}, ${node.position[1]}).` : \"\"}\n\t\t\t\t\t`.trim()}\n\t\t\t\tdata-node={node.id}\n\t\t\t>\n\t\t\t\t<div class=\"thumbnail\">\n\t\t\t\t\t{#if $nodeGraph.thumbnails.has(node.id)}\n\t\t\t\t\t\t{@html $nodeGraph.thumbnails.get(node.id)}\n\t\t\t\t\t{/if}\n\t\t\t\t\t<!-- Layer stacking top output -->\n\t\t\t\t\t{#if node.primaryOutput}\n\t\t\t\t\t\t<svg\n\t\t\t\t\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\t\t\t\t\tviewBox=\"0 0 8 12\"\n\t\t\t\t\t\t\tclass=\"connector top\"\n\t\t\t\t\t\t\tdata-connector=\"output\"\n\t\t\t\t\t\t\tdata-tooltip-label={dataTypeTooltipLabel(node.primaryOutput)}\n\t\t\t\t\t\t\tdata-tooltip-description={outputConnectedToText(node.primaryOutput)}\n\t\t\t\t\t\t\tdata-datatype={node.primaryOutput.dataType}\n\t\t\t\t\t\t\tstyle:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`}\n\t\t\t\t\t\t\tstyle:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{#if node.primaryOutput.connectedTo.length > 0}\n\t\t\t\t\t\t\t\t<path d=\"M0,6.953l2.521,-1.694a2.649,2.649,0,0,1,2.959,0l2.52,1.694v5.047h-8z\" fill=\"var(--data-color)\" />\n\t\t\t\t\t\t\t\t{#if node.primaryOutputConnectedToLayer}\n\t\t\t\t\t\t\t\t\t<path d=\"M0,-3.5h8v8l-2.521,-1.681a2.666,2.666,0,0,0,-2.959,0l-2.52,1.681z\" fill=\"var(--data-color-dim)\" />\n\t\t\t\t\t\t\t\t{/if}\n\t\t\t\t\t\t\t{:else}\n\t\t\t\t\t\t\t\t<path d=\"M0,6.953l2.521,-1.694a2.649,2.649,0,0,1,2.959,0l2.52,1.694v5.047h-8z\" fill=\"var(--data-color-dim)\" />\n\t\t\t\t\t\t\t{/if}\n\t\t\t\t\t\t</svg>\n\t\t\t\t\t{/if}\n\t\t\t\t\t<!-- Layer stacking bottom input -->\n\t\t\t\t\t<svg\n\t\t\t\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\t\t\t\tviewBox=\"0 0 8 12\"\n\t\t\t\t\t\tclass=\"connector bottom\"\n\t\t\t\t\t\tdata-connector=\"input\"\n\t\t\t\t\t\tdata-tooltip-label={node.primaryInput ? dataTypeTooltipLabel(node.primaryInput) : \"\"}\n\t\t\t\t\t\tdata-tooltip-description={node.primaryInput ? `${validTypesText(node.primaryInput).trim()}\\n\\n${inputConnectedToText(node.primaryInput)}` : \"\"}\n\t\t\t\t\t\tdata-datatype={node.primaryInput?.dataType}\n\t\t\t\t\t\tstyle:--data-color={`var(--color-data-${(node.primaryInput?.dataType || \"General\").toLowerCase()})`}\n\t\t\t\t\t\tstyle:--data-color-dim={`var(--color-data-${(node.primaryInput?.dataType || \"General\").toLowerCase()}-dim)`}\n\t\t\t\t\t>\n\t\t\t\t\t\t{#if node.primaryInput?.connectedTo !== \"Connected to nothing.\"}\n\t\t\t\t\t\t\t<path d=\"M0,0H8V8L5.479,6.319a2.666,2.666,0,0,0-2.959,0L0,8Z\" fill=\"var(--data-color)\" />\n\t\t\t\t\t\t\t{#if node.primaryInputConnectedToLayer}\n\t\t\t\t\t\t\t\t<path d=\"M0,10.95l2.52,-1.69c0.89,-0.6,2.06,-0.6,2.96,0l2.52,1.69v5.05h-8v-5.05z\" fill=\"var(--data-color-dim)\" />\n\t\t\t\t\t\t\t{/if}\n\t\t\t\t\t\t{:else}\n\t\t\t\t\t\t\t<path d=\"M0,0H8V8L5.479,6.319a2.666,2.666,0,0,0-2.959,0L0,8Z\" fill=\"var(--data-color-dim)\" />\n\t\t\t\t\t\t{/if}\n\t\t\t\t\t</svg>\n\t\t\t\t</div>\n\t\t\t\t<!-- Layer input connector (from left) -->\n\t\t\t\t{#if node.exposedInputs.length > 0}\n\t\t\t\t\t<div class=\"input connectors\">\n\t\t\t\t\t\t<svg\n\t\t\t\t\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\t\t\t\t\tviewBox=\"0 0 8 8\"\n\t\t\t\t\t\t\tclass=\"connector\"\n\t\t\t\t\t\t\tdata-tooltip-label={dataTypeTooltipLabel(stackDataInput)}\n\t\t\t\t\t\t\tdata-tooltip-description={`${validTypesText(stackDataInput).trim()}\\n\\n${inputConnectedToText(stackDataInput)}`}\n\t\t\t\t\t\t\tdata-connector=\"input\"\n\t\t\t\t\t\t\tdata-datatype={stackDataInput.dataType}\n\t\t\t\t\t\t\tstyle:--data-color={`var(--color-data-${stackDataInput.dataType.toLowerCase()})`}\n\t\t\t\t\t\t\tstyle:--data-color-dim={`var(--color-data-${stackDataInput.dataType.toLowerCase()}-dim)`}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{#if stackDataInput.connectedTo !== undefined}\n\t\t\t\t\t\t\t\t<path d=\"M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z\" fill=\"var(--data-color)\" />\n\t\t\t\t\t\t\t{:else}\n\t\t\t\t\t\t\t\t<path d=\"M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z\" fill=\"var(--data-color-dim)\" />\n\t\t\t\t\t\t\t{/if}\n\t\t\t\t\t\t</svg>\n\t\t\t\t\t</div>\n\t\t\t\t{/if}\n\t\t\t\t<div class=\"details\">\n\t\t\t\t\t<!-- TODO: Allow the user to edit the name, just like in the Layers panel -->\n\t\t\t\t\t<TextLabel>{node.displayName}</TextLabel>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"solo-drag-grip\" data-tooltip-description=\"Drag only this layer without pushing others outside the stack\"></div>\n\t\t\t\t{#if node.locked}\n\t\t\t\t\t<IconButton\n\t\t\t\t\t\tclass=\"lock\"\n\t\t\t\t\t\tdata-lock-button\n\t\t\t\t\t\tsize={24}\n\t\t\t\t\t\ticon=\"PadlockLocked\"\n\t\t\t\t\t\thoverIcon=\"PadlockUnlocked\"\n\t\t\t\t\t\taction={() => {\n\t\t\t\t\t\t\t/* Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown */\n\t\t\t\t\t\t}}\n\t\t\t\t\t\ttooltipLabel=\"Unlock\"\n\t\t\t\t\t/>\n\t\t\t\t{/if}\n\t\t\t\t<IconButton\n\t\t\t\t\tclass=\"visibility\"\n\t\t\t\t\tdata-visibility-button\n\t\t\t\t\tsize={24}\n\t\t\t\t\ticon={node.visible ? \"EyeVisible\" : \"EyeHidden\"}\n\t\t\t\t\thoverIcon={node.visible ? \"EyeHide\" : \"EyeShow\"}\n\t\t\t\t\taction={() => {\n\t\t\t\t\t\t/* Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown */\n\t\t\t\t\t}}\n\t\t\t\t\ttooltipLabel={node.visible ? \"Hide\" : \"Show\"}\n\t\t\t\t/>\n\n\t\t\t\t<svg class=\"border-mask\" width=\"0\" height=\"0\">\n\t\t\t\t\t<defs>\n\t\t\t\t\t\t<clipPath id={clipPathId}>\n\t\t\t\t\t\t\t<!-- Keep this equation in sync with the equivalent one in the CSS rule for `.layer { width: ... }` below -->\n\t\t\t\t\t\t\t<path clip-rule=\"evenodd\" d={layerBorderMask(24 * layerAreaWidth - 12, layerChainWidth ? (0.5 + layerChainWidth) * 24 : 0, hasLeftInputWire)} />\n\t\t\t\t\t\t</clipPath>\n\t\t\t\t\t</defs>\n\t\t\t\t</svg>\n\t\t\t</div>\n\t\t{/each}\n\n\t\t<!-- Node connection wires -->\n\t\t<div class=\"wires\">\n\t\t\t<svg>\n\t\t\t\t{#each $nodeGraph.wires.values() as map}\n\t\t\t\t\t{#each map.values() as { pathString, dataType, thick, dashed }}\n\t\t\t\t\t\t{#if !thick}\n\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\td={pathString}\n\t\t\t\t\t\t\t\tstyle:--data-line-width=\"2px\"\n\t\t\t\t\t\t\t\tstyle:--data-color={`var(--color-data-${dataType.toLowerCase()})`}\n\t\t\t\t\t\t\t\tstyle:--data-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`}\n\t\t\t\t\t\t\t\tstyle:--data-dasharray={`3,${dashed ? 2 : 0}`}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t{/if}\n\t\t\t\t\t{/each}\n\t\t\t\t{/each}\n\t\t\t\t{#if $nodeGraph.wirePathInProgress}\n\t\t\t\t\t<path\n\t\t\t\t\t\td={$nodeGraph.wirePathInProgress?.pathString}\n\t\t\t\t\t\tstyle:--data-line-width={`${$nodeGraph.wirePathInProgress.thick ? 8 : 2}px`}\n\t\t\t\t\t\tstyle:--data-color={`var(--color-data-${$nodeGraph.wirePathInProgress.dataType.toLowerCase()})`}\n\t\t\t\t\t\tstyle:--data-color-dim={`var(--color-data-${$nodeGraph.wirePathInProgress.dataType.toLowerCase()}-dim)`}\n\t\t\t\t\t\tstyle:--data-dasharray={`3,${$nodeGraph.wirePathInProgress.dashed ? 2 : 0}`}\n\t\t\t\t\t/>\n\t\t\t\t{/if}\n\t\t\t</svg>\n\t\t</div>\n\n\t\t<!-- Nodes -->\n\t\t{#each Array.from($nodeGraph.nodes)\n\t\t\t.filter(([nodeId, node]) => !node.isLayer && $nodeGraph.visibleNodes.has(nodeId))\n\t\t\t.map(([_, node], nodeIndex) => ({ node, nodeIndex })) as { node, nodeIndex } (nodeIndex)}\n\t\t\t{@const exposedInputsOutputs = zipWithUndefined(node.exposedInputs, node.exposedOutputs)}\n\t\t\t{@const clipPathId = String(Math.random()).substring(2)}\n\t\t\t{@const description = node.reference ? $nodeGraph.nodeDescriptions.get(node.reference) : undefined}\n\t\t\t<div\n\t\t\t\tclass=\"node\"\n\t\t\t\tclass:selected={$nodeGraph.selected.includes(node.id)}\n\t\t\t\tclass:previewed={node.previewed}\n\t\t\t\tclass:disabled={!node.visible}\n\t\t\t\tstyle:--offset-left={node.position?.[0] || 0}\n\t\t\t\tstyle:--offset-top={node.position?.[1] || 0}\n\t\t\t\tstyle:--clip-path-id={`url(#${clipPathId})`}\n\t\t\t\tstyle:--data-color={`var(--color-data-${(node.primaryOutput?.dataType || \"General\").toLowerCase()})`}\n\t\t\t\tstyle:--data-color-dim={`var(--color-data-${(node.primaryOutput?.dataType || \"General\").toLowerCase()}-dim)`}\n\t\t\t\tdata-tooltip-label={nodeNameTooltipLabel(node)}\n\t\t\t\tdata-tooltip-description={`\n\t\t\t\t\t${(description || \"\").trim()}${editor.handle.inDevelopmentMode() ? `\\n\\nID: ${node.id}. Position: (${node.position[0]}, ${node.position[1]}).` : \"\"}\n\t\t\t\t\t`.trim()}\n\t\t\t\tdata-node={node.id}\n\t\t\t>\n\t\t\t\t<!-- Primary row -->\n\t\t\t\t<div class=\"primary\" class:in-selected-network={$nodeGraph.inSelectedNetwork} class:no-secondary-section={exposedInputsOutputs.length === 0}>\n\t\t\t\t\t<!-- TODO: Allow the user to edit the name, just like in the Layers panel -->\n\t\t\t\t\t<TextLabel>{node.displayName}</TextLabel>\n\t\t\t\t</div>\n\t\t\t\t<!-- Secondary rows -->\n\t\t\t\t{#if exposedInputsOutputs.length > 0}\n\t\t\t\t\t<div class=\"secondary\" class:in-selected-network={$nodeGraph.inSelectedNetwork}>\n\t\t\t\t\t\t{#each exposedInputsOutputs as [input, output]}\n\t\t\t\t\t\t\t<div class={`secondary-row expanded ${input !== undefined ? \"input\" : \"output\"}`}>\n\t\t\t\t\t\t\t\t<TextLabel tooltipLabel={input !== undefined ? input.name : output.name} tooltipDescription={input !== undefined ? input.description : output.description}>\n\t\t\t\t\t\t\t\t\t{input !== undefined ? input.name : output.name}\n\t\t\t\t\t\t\t\t</TextLabel>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t{/each}\n\t\t\t\t\t</div>\n\t\t\t\t{/if}\n\t\t\t\t<!-- Input connectors -->\n\t\t\t\t<div class=\"input connectors\">\n\t\t\t\t\t{#if node.primaryInput?.dataType}\n\t\t\t\t\t\t<svg\n\t\t\t\t\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\t\t\t\t\tviewBox=\"0 0 8 8\"\n\t\t\t\t\t\t\tclass=\"connector primary-connector\"\n\t\t\t\t\t\t\tdata-connector=\"input\"\n\t\t\t\t\t\t\tdata-tooltip-label={dataTypeTooltipLabel(node.primaryInput)}\n\t\t\t\t\t\t\tdata-tooltip-description={`${validTypesText(node.primaryInput).trim()}\\n\\n${inputConnectedToText(node.primaryInput)}`}\n\t\t\t\t\t\t\tdata-datatype={node.primaryInput?.dataType}\n\t\t\t\t\t\t\tstyle:--data-color={`var(--color-data-${node.primaryInput.dataType.toLowerCase()})`}\n\t\t\t\t\t\t\tstyle:--data-color-dim={`var(--color-data-${node.primaryInput.dataType.toLowerCase()}-dim)`}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{#if node.primaryInput.connectedTo !== undefined}\n\t\t\t\t\t\t\t\t<path d=\"M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z\" fill=\"var(--data-color)\" />\n\t\t\t\t\t\t\t{:else}\n\t\t\t\t\t\t\t\t<path d=\"M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z\" fill=\"var(--data-color-dim)\" />\n\t\t\t\t\t\t\t{/if}\n\t\t\t\t\t\t</svg>\n\t\t\t\t\t{/if}\n\t\t\t\t\t{#each node.exposedInputs as secondary, index}\n\t\t\t\t\t\t{#if index < node.exposedInputs.length}\n\t\t\t\t\t\t\t<svg\n\t\t\t\t\t\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\t\t\t\t\t\tviewBox=\"0 0 8 8\"\n\t\t\t\t\t\t\t\tclass=\"connector\"\n\t\t\t\t\t\t\t\tdata-connector=\"input\"\n\t\t\t\t\t\t\t\tdata-tooltip-label={dataTypeTooltipLabel(secondary)}\n\t\t\t\t\t\t\t\tdata-tooltip-description={`${validTypesText(secondary).trim()}\\n\\n${inputConnectedToText(secondary)}`}\n\t\t\t\t\t\t\t\tdata-datatype={secondary.dataType}\n\t\t\t\t\t\t\t\tstyle:--data-color={`var(--color-data-${secondary.dataType.toLowerCase()})`}\n\t\t\t\t\t\t\t\tstyle:--data-color-dim={`var(--color-data-${secondary.dataType.toLowerCase()}-dim)`}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{#if secondary.connectedTo !== undefined}\n\t\t\t\t\t\t\t\t\t<path d=\"M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z\" fill=\"var(--data-color)\" />\n\t\t\t\t\t\t\t\t{:else}\n\t\t\t\t\t\t\t\t\t<path d=\"M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z\" fill=\"var(--data-color-dim)\" />\n\t\t\t\t\t\t\t\t{/if}\n\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t{/if}\n\t\t\t\t\t{/each}\n\t\t\t\t</div>\n\t\t\t\t<!-- Output connectors -->\n\t\t\t\t<div class=\"output connectors\">\n\t\t\t\t\t{#if node.primaryOutput}\n\t\t\t\t\t\t<svg\n\t\t\t\t\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\t\t\t\t\tviewBox=\"0 0 8 8\"\n\t\t\t\t\t\t\tclass=\"connector primary-connector\"\n\t\t\t\t\t\t\tdata-connector=\"output\"\n\t\t\t\t\t\t\tdata-tooltip-label={dataTypeTooltipLabel(node.primaryOutput)}\n\t\t\t\t\t\t\tdata-tooltip-description={`${outputConnectedToText(node.primaryOutput)}`}\n\t\t\t\t\t\t\tdata-datatype={node.primaryOutput.dataType}\n\t\t\t\t\t\t\tstyle:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`}\n\t\t\t\t\t\t\tstyle:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{#if node.primaryOutput.connectedTo !== undefined}\n\t\t\t\t\t\t\t\t<path d=\"M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z\" fill=\"var(--data-color)\" />\n\t\t\t\t\t\t\t{:else}\n\t\t\t\t\t\t\t\t<path d=\"M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z\" fill=\"var(--data-color-dim)\" />\n\t\t\t\t\t\t\t{/if}\n\t\t\t\t\t\t</svg>\n\t\t\t\t\t{/if}\n\t\t\t\t\t{#each node.exposedOutputs as secondary}\n\t\t\t\t\t\t<svg\n\t\t\t\t\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\t\t\t\t\tviewBox=\"0 0 8 8\"\n\t\t\t\t\t\t\tclass=\"connector\"\n\t\t\t\t\t\t\tdata-connector=\"output\"\n\t\t\t\t\t\t\tdata-tooltip-label={dataTypeTooltipLabel(secondary)}\n\t\t\t\t\t\t\tdata-tooltip-description={`${outputConnectedToText(secondary)}`}\n\t\t\t\t\t\t\tdata-datatype={secondary.dataType}\n\t\t\t\t\t\t\tstyle:--data-color={`var(--color-data-${secondary.dataType.toLowerCase()})`}\n\t\t\t\t\t\t\tstyle:--data-color-dim={`var(--color-data-${secondary.dataType.toLowerCase()}-dim)`}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{#if secondary.connectedTo !== undefined}\n\t\t\t\t\t\t\t\t<path d=\"M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z\" fill=\"var(--data-color)\" />\n\t\t\t\t\t\t\t{:else}\n\t\t\t\t\t\t\t\t<path d=\"M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z\" fill=\"var(--data-color-dim)\" />\n\t\t\t\t\t\t\t{/if}\n\t\t\t\t\t\t</svg>\n\t\t\t\t\t{/each}\n\t\t\t\t</div>\n\t\t\t\t<svg class=\"border-mask\" width=\"0\" height=\"0\">\n\t\t\t\t\t<defs>\n\t\t\t\t\t\t<clipPath id={clipPathId}>\n\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\tclip-rule=\"evenodd\"\n\t\t\t\t\t\t\t\td={nodeBorderMask(120, node.primaryInput?.dataType !== undefined, node.exposedInputs.length, node.primaryOutput !== undefined, node.exposedOutputs.length)}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</clipPath>\n\t\t\t\t\t</defs>\n\t\t\t\t</svg>\n\t\t\t</div>\n\t\t{/each}\n\t</div>\n</div>\n\n<!-- Box selection widget -->\n{#if $nodeGraph.box}\n\t<div\n\t\tclass=\"box-selection\"\n\t\tstyle:left={`${Math.min($nodeGraph.box.startX, $nodeGraph.box.endX)}px`}\n\t\tstyle:top={`${Math.min($nodeGraph.box.startY, $nodeGraph.box.endY)}px`}\n\t\tstyle:width={`${Math.abs($nodeGraph.box.startX - $nodeGraph.box.endX)}px`}\n\t\tstyle:height={`${Math.abs($nodeGraph.box.startY - $nodeGraph.box.endY)}px`}\n\t></div>\n{/if}\n\n<style lang=\"scss\" global>\n\t.graph {\n\t\tposition: relative;\n\t\toverflow: hidden;\n\t\tdisplay: flex;\n\t\tflex-direction: row;\n\t\tflex-grow: 1;\n\n\t\t// We're displaying the dotted grid in a pseudo-element because `image-rendering` is an inherited property and we don't want it to apply to child elements\n\t\t&::before {\n\t\t\tcontent: \"\";\n\t\t\tposition: absolute;\n\t\t\twidth: 100%;\n\t\t\theight: 100%;\n\t\t\tbackground-size: var(--grid-spacing) var(--grid-spacing);\n\t\t\tbackground-position: calc(var(--grid-offset-x) - var(--grid-dot-radius)) calc(var(--grid-offset-y) - var(--grid-dot-radius));\n\t\t\tbackground-image: radial-gradient(circle at var(--grid-dot-radius) var(--grid-dot-radius), var(--color-3-darkgray) var(--grid-dot-radius), transparent 0);\n\t\t\tbackground-repeat: repeat;\n\t\t\timage-rendering: pixelated;\n\t\t\tmix-blend-mode: screen;\n\t\t}\n\n\t\t> img {\n\t\t\tposition: absolute;\n\t\t\tbottom: 0;\n\t\t}\n\n\t\t.breadcrumb-trail-buttons {\n\t\t\tmargin-top: 8px;\n\t\t\tmargin-left: 8px;\n\t\t}\n\n\t\t.context-menu {\n\t\t\twidth: max-content;\n\n\t\t\t.modify-node-menu {\n\t\t\t\tmargin: -4px;\n\n\t\t\t\t.text-button {\n\t\t\t\t\tjustify-content: left;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.tail {\n\t\t\t\tdisplay: none;\n\t\t\t}\n\t\t}\n\n\t\t.node-error-container {\n\t\t\tposition: absolute;\n\t\t\tz-index: 1;\n\n\t\t\t.node-error {\n\t\t\t\tposition: absolute;\n\t\t\t\twidth: max-content;\n\t\t\t\twhite-space: pre-wrap;\n\t\t\t\tmax-width: 600px;\n\t\t\t\tline-height: 18px;\n\t\t\t\tcolor: var(--color-2-mildblack);\n\t\t\t\tbackground: var(--color-error-red);\n\t\t\t\tpadding: 8px;\n\t\t\t\tborder-radius: 4px;\n\t\t\t\ttransition: opacity 0.2s;\n\t\t\t\topacity: 0.5;\n\t\t\t\ttransform: translateY(-100%);\n\n\t\t\t\t// Tail\n\t\t\t\t&::after {\n\t\t\t\t\tcontent: \"\";\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\tleft: 6px;\n\t\t\t\t\tbottom: -8px;\n\t\t\t\t\twidth: 0;\n\t\t\t\t\theight: 0;\n\t\t\t\t\tborder-style: solid;\n\t\t\t\t\tborder-width: 8px 6px 0 6px;\n\t\t\t\t\tborder-color: var(--color-error-red) transparent transparent transparent;\n\t\t\t\t}\n\n\t\t\t\t&.hover {\n\t\t\t\t\topacity: 0;\n\t\t\t\t\tz-index: 1;\n\t\t\t\t\tpointer-events: none;\n\t\t\t\t}\n\n\t\t\t\t&.faded:hover + .hover {\n\t\t\t\t\topacity: 1;\n\t\t\t\t}\n\n\t\t\t\t&.faded:hover {\n\t\t\t\t\tz-index: 2;\n\t\t\t\t\topacity: 1;\n\t\t\t\t\t-webkit-user-select: text;\n\t\t\t\t\tuser-select: text;\n\t\t\t\t\ttransition:\n\t\t\t\t\t\topacity 0.2s,\n\t\t\t\t\t\tz-index 0s 0.2s;\n\n\t\t\t\t\t&::selection {\n\t\t\t\t\t\tbackground-color: var(--color-e-nearwhite);\n\n\t\t\t\t\t\t// Target only Safari\n\t\t\t\t\t\t@supports (background: -webkit-named-image(i)) {\n\t\t\t\t\t\t\t& {\n\t\t\t\t\t\t\t\t// Setting an alpha value opts out of Safari's \"fancy\" (but not visible on dark backgrounds) selection highlight rendering\n\t\t\t\t\t\t\t\t// https://stackoverflow.com/a/71753552/775283\n\t\t\t\t\t\t\t\tbackground-color: rgba(var(--color-e-nearwhite-rgb), calc(254 / 255));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t.click-targets {\n\t\t\tposition: absolute;\n\t\t\tpointer-events: none;\n\t\t\twidth: 100%;\n\t\t\theight: 100%;\n\t\t\tz-index: 10;\n\n\t\t\tsvg {\n\t\t\t\toverflow: visible;\n\t\t\t\twidth: 100%;\n\t\t\t\theight: 100%;\n\t\t\t\tstroke-width: 1;\n\t\t\t\tfill: none;\n\n\t\t\t\t.layer {\n\t\t\t\t\tstroke: yellow;\n\t\t\t\t}\n\n\t\t\t\t.node {\n\t\t\t\t\tstroke: blue;\n\t\t\t\t}\n\n\t\t\t\t.connector {\n\t\t\t\t\tstroke: green;\n\t\t\t\t}\n\n\t\t\t\t.visibility {\n\t\t\t\t\tstroke: red;\n\t\t\t\t}\n\n\t\t\t\t.all-nodes-bounding-box {\n\t\t\t\t\tstroke: purple;\n\t\t\t\t}\n\n\t\t\t\t.modify-import-export {\n\t\t\t\t\tstroke: orange;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t.wires {\n\t\t\tpointer-events: none;\n\t\t\tposition: absolute;\n\t\t\twidth: 100%;\n\t\t\theight: 100%;\n\n\t\t\tsvg {\n\t\t\t\twidth: 100%;\n\t\t\t\theight: 100%;\n\t\t\t\toverflow: visible;\n\n\t\t\t\tpath {\n\t\t\t\t\tfill: none;\n\t\t\t\t\tstroke: var(--data-color-dim);\n\t\t\t\t\tstroke-width: var(--data-line-width);\n\t\t\t\t\tstroke-dasharray: var(--data-dasharray);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t.imports-and-exports {\n\t\t\twidth: 100%;\n\t\t\theight: 100%;\n\t\t\tposition: absolute;\n\t\t\tpointer-events: none;\n\t\t\t// Keeps the connectors above the wires\n\t\t\tz-index: 1;\n\n\t\t\t// Zero specificity with `:where()` to allow other rules to override `pointer-events`\n\t\t\t:where(.graph-view.open & > *) {\n\t\t\t\tpointer-events: auto;\n\t\t\t}\n\n\t\t\t.connector {\n\t\t\t\tposition: absolute;\n\t\t\t\twidth: 8px;\n\t\t\t\theight: 8px;\n\t\t\t\tmargin-top: 4px;\n\t\t\t\tmargin-left: 5px;\n\t\t\t\ttop: calc(var(--offset-top) * 24px);\n\t\t\t\tleft: calc(var(--offset-left) * 24px);\n\t\t\t}\n\n\t\t\t.reorder-bar {\n\t\t\t\tposition: absolute;\n\t\t\t\ttop: calc(var(--offset-top) * 24px);\n\t\t\t\tleft: calc(var(--offset-left) * 24px);\n\t\t\t\twidth: 50px;\n\t\t\t\theight: 2px;\n\t\t\t\tbackground: white;\n\t\t\t}\n\n\t\t\t.plus {\n\t\t\t\tposition: absolute;\n\t\t\t\ttop: calc(var(--offset-top) * 24px);\n\t\t\t\tleft: calc(var(--offset-left) * 24px);\n\t\t\t}\n\n\t\t\t.edit-import-export {\n\t\t\t\tposition: absolute;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\ttop: calc(var(--offset-top) * 24px);\n\t\t\t\tmargin-top: -5px;\n\t\t\t\theight: 24px;\n\n\t\t\t\t&.separator-bottom::after,\n\t\t\t\t&.separator-top::before {\n\t\t\t\t\tcontent: \"\";\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\tbackground: var(--color-8-uppergray);\n\t\t\t\t\theight: 1px;\n\t\t\t\t\tleft: -4px;\n\t\t\t\t\tright: -4px;\n\t\t\t\t}\n\n\t\t\t\t&.separator-bottom::after {\n\t\t\t\t\tbottom: -1px;\n\t\t\t\t}\n\n\t\t\t\t&.separator-top::before {\n\t\t\t\t\ttop: 0;\n\t\t\t\t}\n\n\t\t\t\t&.import {\n\t\t\t\t\tright: calc(100% - var(--offset-left) * 24px);\n\t\t\t\t}\n\n\t\t\t\t&.export {\n\t\t\t\t\tleft: calc(var(--offset-left) * 24px + 17px);\n\t\t\t\t}\n\n\t\t\t\t.import-text {\n\t\t\t\t\ttext-align: right;\n\t\t\t\t\ttext-wrap: nowrap;\n\t\t\t\t}\n\n\t\t\t\t.export-text {\n\t\t\t\t\ttext-wrap: nowrap;\n\t\t\t\t}\n\n\t\t\t\t.import-text-input {\n\t\t\t\t\ttext-align: right;\n\t\t\t\t}\n\n\t\t\t\t.remove-button-import {\n\t\t\t\t\tmargin-left: 3px;\n\t\t\t\t}\n\n\t\t\t\t.remove-button-export {\n\t\t\t\t\tmargin-right: 3px;\n\t\t\t\t}\n\n\t\t\t\t.reorder-drag-grip {\n\t\t\t\t\twidth: 8px;\n\t\t\t\t\theight: 24px;\n\t\t\t\t\tbackground-position: 2px 8px;\n\t\t\t\t\tborder-radius: 2px;\n\t\t\t\t\tmargin: -6px 0;\n\t\t\t\t\tbackground-image: var(--icon-drag-grip-hover);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t.layers-and-nodes {\n\t\t\tposition: absolute;\n\t\t\tpointer-events: none;\n\t\t\twidth: 100%;\n\t\t\theight: 100%;\n\n\t\t\t// Zero specificity with `:where()` to allow other rules to override `pointer-events`\n\t\t\t:where(.graph-view.open & > *) {\n\t\t\t\tpointer-events: auto;\n\t\t\t}\n\t\t}\n\n\t\t.layer,\n\t\t.node {\n\t\t\tposition: absolute;\n\t\t\tdisplay: flex;\n\t\t\tleft: calc(var(--offset-left) * 24px);\n\t\t\ttop: calc(var(--offset-top) * 24px);\n\t\t\t// TODO: Reenable the `transition` property below after dealing with all edge cases where the wires need to be updated until the transition is complete\n\t\t\t// transition: top 0.1s cubic-bezier(0, 0, 0.2, 1), left 0.1s cubic-bezier(0, 0, 0.2, 1); // Update `DRAG_SMOOTHING_TIME` in the JS above.\n\t\t\t// TODO: Reenable the `backdrop-filter` property once a solution can be found for the black whole-page flickering problems it causes in Chrome.\n\t\t\t// TODO: Additionally, find a solution for this having no effect in Firefox due to a browser bug caused when the two\n\t\t\t// ancestor elements, `.graph` and `.panel`, each have the simultaneous pairing of `overflow: hidden` and `border-radius`.\n\t\t\t// See: https://stackoverflow.com/questions/75137879/bug-with-backdrop-filter-in-firefox\n\t\t\t// backdrop-filter: blur(4px);\n\t\t\tbackground: rgba(var(--color-0-black-rgb), 0.33);\n\n\t\t\t&::after {\n\t\t\t\tcontent: \"\";\n\t\t\t\tposition: absolute;\n\t\t\t\tbox-sizing: border-box;\n\t\t\t\ttop: 0;\n\t\t\t\tleft: 0;\n\t\t\t\twidth: 100%;\n\t\t\t\theight: 100%;\n\t\t\t\tpointer-events: none;\n\t\t\t\tclip-path: var(--clip-path-id);\n\t\t\t}\n\n\t\t\t.border-mask {\n\t\t\t\tposition: absolute;\n\t\t\t\ttop: 0;\n\t\t\t}\n\n\t\t\t&.disabled {\n\t\t\t\tbackground: rgba(var(--color-4-dimgray-rgb), 0.33);\n\t\t\t\tcolor: var(--color-a-softgray);\n\n\t\t\t\t.icon-label {\n\t\t\t\t\tfill: var(--color-a-softgray);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t&.previewed::after {\n\t\t\t\tborder: 1px dashed var(--data-color);\n\t\t\t}\n\n\t\t\t.connectors {\n\t\t\t\tposition: absolute;\n\t\t\t\t// Keeps the connectors above the wires\n\t\t\t\tz-index: 1;\n\n\t\t\t\t&.input {\n\t\t\t\t\tleft: -3px;\n\t\t\t\t}\n\n\t\t\t\t&.output {\n\t\t\t\t\tright: -5px;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.connector {\n\t\t\t\t// Double the intended value because of margin collapsing, but for the first and last we divide it by two as intended\n\t\t\t\tmargin: calc(24px - 8px) 0;\n\t\t\t\twidth: 8px;\n\t\t\t\theight: 8px;\n\t\t\t}\n\n\t\t\t.text-label {\n\t\t\t\toverflow: hidden;\n\t\t\t\ttext-overflow: ellipsis;\n\t\t\t}\n\t\t}\n\n\t\t.layer {\n\t\t\tborder-radius: 8px;\n\t\t\t--extra-width-to-reach-grid-multiple: 8px;\n\t\t\t--node-chain-area-left-extension: 0;\n\t\t\t// Keep this equation in sync with the equivalent one in the Svelte template `<clipPath><path d=\"layerBorderMask(...)\" /></clipPath>` above, as well as the `left` connector offset CSS rule above in `.connectors.input` above.\n\t\t\twidth: calc((var(--layer-area-width) - 0.5) * 24px);\n\t\t\tpadding-left: calc(var(--node-chain-area-left-extension) * 24px);\n\t\t\tmargin-left: calc((0.5 - var(--node-chain-area-left-extension)) * 24px);\n\n\t\t\t&::after {\n\t\t\t\tborder: 1px solid var(--color-5-dullgray);\n\t\t\t\tborder-radius: 8px;\n\t\t\t}\n\n\t\t\t&.selected {\n\t\t\t\tbackground: rgba(var(--color-5-dullgray-rgb), 0.33);\n\n\t\t\t\t&.in-selected-network {\n\t\t\t\t\tbackground: rgba(var(--color-6-lowergray-rgb), 0.33);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.thumbnail {\n\t\t\t\tbackground: var(--color-2-mildblack);\n\t\t\t\tborder: 1px solid var(--data-color-dim);\n\t\t\t\tborder-radius: 2px;\n\t\t\t\tposition: relative;\n\t\t\t\tbox-sizing: border-box;\n\t\t\t\theight: 48px;\n\t\t\t\t// We shorten the width by 1px on the left and right so the inner thumbnail graphic maintains a perfect 3:2 aspect ratio\n\t\t\t\twidth: calc(72px - 2px);\n\t\t\t\tmargin: 0 1px;\n\n\t\t\t\t&::before {\n\t\t\t\t\tcontent: \"\";\n\t\t\t\t\tbackground-image: var(--color-transparent-checkered-background);\n\t\t\t\t\tbackground-size: var(--color-transparent-checkered-background-size);\n\t\t\t\t\tbackground-position: var(--color-transparent-checkered-background-position);\n\t\t\t\t\tbackground-repeat: var(--color-transparent-checkered-background-repeat);\n\t\t\t\t}\n\n\t\t\t\t&::before,\n\t\t\t\tsvg:not(.connector) {\n\t\t\t\t\tpointer-events: none;\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\tmargin: auto;\n\t\t\t\t\ttop: 1px;\n\t\t\t\t\tleft: 1px;\n\t\t\t\t\twidth: calc(100% - 2px);\n\t\t\t\t\theight: calc(100% - 2px);\n\t\t\t\t}\n\n\t\t\t\t.connector {\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\tmargin: 0 auto;\n\t\t\t\t\tleft: 0;\n\t\t\t\t\tright: 0;\n\t\t\t\t\theight: 12px;\n\n\t\t\t\t\t&.top {\n\t\t\t\t\t\ttop: -13px;\n\t\t\t\t\t}\n\n\t\t\t\t\t&.bottom {\n\t\t\t\t\t\tbottom: -13px;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.details {\n\t\t\t\tmargin: 0 8px;\n\n\t\t\t\t.text-label {\n\t\t\t\t\twhite-space: nowrap;\n\t\t\t\t\tline-height: 48px;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.solo-drag-grip {\n\t\t\t\twidth: 8px;\n\t\t\t\theight: 24px;\n\t\t\t\tbackground-position: 2px 8px;\n\t\t\t\tright: calc(-12px + 24px);\n\t\t\t\tborder-radius: 2px;\n\t\t\t}\n\n\t\t\t&.locked .solo-drag-grip {\n\t\t\t\tright: calc(-12px + 24px + 24px);\n\t\t\t}\n\n\t\t\t.solo-drag-grip:hover,\n\t\t\t&.selected .solo-drag-grip {\n\t\t\t\tbackground-image: var(--icon-drag-grip);\n\n\t\t\t\t&:hover {\n\t\t\t\t\tbackground-image: var(--icon-drag-grip-hover);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.visibility {\n\t\t\t\tright: -12px;\n\t\t\t}\n\n\t\t\t.lock {\n\t\t\t\tright: 12px;\n\t\t\t}\n\n\t\t\t.input.connectors {\n\t\t\t\tleft: calc(-3px + var(--node-chain-area-left-extension) * 24px - 36px);\n\t\t\t}\n\n\t\t\t.solo-drag-grip,\n\t\t\t.lock,\n\t\t\t.visibility,\n\t\t\t.input.connectors,\n\t\t\t.input.connectors .connector {\n\t\t\t\tposition: absolute;\n\t\t\t\tmargin: auto 0;\n\t\t\t\ttop: 0;\n\t\t\t\tbottom: 0;\n\t\t\t}\n\n\t\t\t.input.connectors .connector {\n\t\t\t\tleft: 24px;\n\t\t\t}\n\t\t}\n\n\t\t.node {\n\t\t\tflex-direction: column;\n\t\t\tborder-radius: 2px;\n\t\t\twidth: 120px;\n\t\t\ttop: calc((var(--offset-top) + 0.5) * 24px);\n\n\t\t\t&::after {\n\t\t\t\tborder: 1px solid var(--data-color-dim);\n\t\t\t\tborder-radius: 2px;\n\t\t\t}\n\n\t\t\t&.selected {\n\t\t\t\t.primary {\n\t\t\t\t\tbackground: rgba(var(--color-f-white-rgb), 0.15);\n\n\t\t\t\t\t&.in-selected-network {\n\t\t\t\t\t\tbackground: rgba(var(--color-f-white-rgb), 0.2);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t.secondary {\n\t\t\t\t\tbackground: rgba(var(--color-f-white-rgb), 0.1);\n\n\t\t\t\t\t&.in-selected-network {\n\t\t\t\t\t\tbackground: rgba(var(--color-f-white-rgb), 0.15);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.connector {\n\t\t\t\t&:first-of-type {\n\t\t\t\t\tmargin-top: calc((24px - 8px) / 2);\n\n\t\t\t\t\t&:not(.primary-connector) {\n\t\t\t\t\t\tmargin-top: calc((24px - 8px) / 2 + 24px);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t&:last-of-type {\n\t\t\t\t\tmargin-bottom: calc((24px - 8px) / 2);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.primary {\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tposition: relative;\n\t\t\t\twidth: 100%;\n\t\t\t\theight: 24px;\n\t\t\t\tborder-radius: 2px 2px 0 0;\n\t\t\t\tbackground: rgba(var(--color-f-white-rgb), 0.05);\n\n\t\t\t\t&.no-secondary-section {\n\t\t\t\t\tborder-radius: 2px;\n\t\t\t\t}\n\n\t\t\t\t.icon-label {\n\t\t\t\t\tdisplay: none; // Remove after we have unique icons for the nodes\n\t\t\t\t\tmargin: 0 8px;\n\t\t\t\t}\n\n\t\t\t\t.text-label {\n\t\t\t\t\t// margin-right: 4px; // Restore after reenabling icon-label\n\t\t\t\t\tmargin: 0 8px;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.secondary {\n\t\t\t\tdisplay: flex;\n\t\t\t\tflex-direction: column;\n\t\t\t\twidth: 100%;\n\t\t\t\tposition: relative;\n\n\t\t\t\t.secondary-row {\n\t\t\t\t\tposition: relative;\n\t\t\t\t\tdisplay: flex;\n\t\t\t\t\talign-items: center;\n\t\t\t\t\tmargin: 0 8px;\n\t\t\t\t\twidth: calc(100% - 8px - 8px);\n\t\t\t\t\theight: 24px;\n\n\t\t\t\t\t&:last-of-type {\n\t\t\t\t\t\tborder-radius: 0 0 2px 2px;\n\t\t\t\t\t}\n\n\t\t\t\t\t.text-label {\n\t\t\t\t\t\twidth: 100%;\n\t\t\t\t\t}\n\n\t\t\t\t\t&.output {\n\t\t\t\t\t\tflex-direction: row-reverse;\n\t\t\t\t\t\ttext-align: right;\n\n\t\t\t\t\t\tsvg {\n\t\t\t\t\t\t\twidth: 30px;\n\t\t\t\t\t\t\theight: 20px;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t&::before {\n\t\t\t\t\tleft: 0;\n\t\t\t\t}\n\n\t\t\t\t&::after {\n\t\t\t\t\tright: 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t.box-selection {\n\t\tposition: absolute;\n\t\tpointer-events: none;\n\t\tz-index: 2;\n\t\t// TODO: This will be removed after box selection, and all of graph rendering, is moved to the backend and this whole file\n\t\t// is removed, but for now this color needs to stay in sync with `COLOR_OVERLAY_BLUE` set in consts.rs of the editor backend.\n\t\tbackground: rgba(0, 168, 255, 0.05);\n\t\tborder: 1px solid #00a8ff;\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/widgets/WidgetLayout.svelte",
    "content": "<script lang=\"ts\">\n\timport type { Layout, LayoutTarget } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\n\timport WidgetSection from \"@graphite/components/widgets/WidgetSection.svelte\";\n\timport WidgetSpan from \"@graphite/components/widgets/WidgetSpan.svelte\";\n\timport WidgetTable from \"@graphite/components/widgets/WidgetTable.svelte\";\n\n\texport let layout: Layout;\n\texport let layoutTarget: LayoutTarget;\n\tlet className = \"\";\n\texport { className as class };\n\texport let classes: Record<string, boolean> = {};\n</script>\n\n{#each layout as layoutGroup}\n\t{#if \"Row\" in layoutGroup}\n\t\t<WidgetSpan direction=\"row\" widgets={layoutGroup.Row.rowWidgets} {layoutTarget} class={className} {classes} />\n\t{:else if \"Column\" in layoutGroup}\n\t\t<WidgetSpan direction=\"column\" widgets={layoutGroup.Column.columnWidgets} {layoutTarget} class={className} {classes} />\n\t{:else if \"Section\" in layoutGroup}\n\t\t<WidgetSection widgetData={layoutGroup.Section} {layoutTarget} class={className} {classes} />\n\t{:else if \"Table\" in layoutGroup}\n\t\t<WidgetTable widgetData={layoutGroup.Table} {layoutTarget} />\n\t{/if}\n{/each}\n\n<style lang=\"scss\" global></style>\n"
  },
  {
    "path": "frontend/src/components/widgets/WidgetSection.svelte",
    "content": "<script lang=\"ts\">\n\timport { getContext } from \"svelte\";\n\n\timport type { LayoutTarget, WidgetSection as WidgetSectionData } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\timport type { Editor } from \"@graphite/editor\";\n\n\timport LayoutCol from \"@graphite/components/layout/LayoutCol.svelte\";\n\timport IconButton from \"@graphite/components/widgets/buttons/IconButton.svelte\";\n\timport TextLabel from \"@graphite/components/widgets/labels/TextLabel.svelte\";\n\timport WidgetSpan from \"@graphite/components/widgets/WidgetSpan.svelte\";\n\n\texport let widgetData: WidgetSectionData;\n\texport let layoutTarget: LayoutTarget;\n\n\tlet className = \"\";\n\texport { className as class };\n\texport let classes: Record<string, boolean> = {};\n\n\tlet expanded = true;\n\n\tconst editor = getContext<Editor>(\"editor\");\n</script>\n\n<!-- TODO: Implement collapsable sections with properties system -->\n<LayoutCol class={`widget-section ${className}`.trim()} {classes}>\n\t<button class=\"header\" class:expanded on:click|stopPropagation={() => (expanded = !expanded)} tabindex=\"0\">\n\t\t<div class=\"expand-arrow\"></div>\n\t\t<TextLabel tooltipLabel={widgetData.name} tooltipDescription={widgetData.description} bold={true}>{widgetData.name}</TextLabel>\n\t\t<IconButton\n\t\t\ticon={widgetData.pinned ? \"PinActive\" : \"PinInactive\"}\n\t\t\ttooltipDescription={widgetData.pinned ? \"Unpin this node so it's no longer shown here when nothing is selected.\" : \"Pin this node so it's shown here when nothing is selected.\"}\n\t\t\tsize={24}\n\t\t\taction={(e) => {\n\t\t\t\teditor.handle.setNodePinned(widgetData.id, !widgetData.pinned);\n\t\t\t\te?.stopPropagation();\n\t\t\t}}\n\t\t\tclass=\"show-only-on-hover\"\n\t\t/>\n\t\t<IconButton\n\t\t\ticon=\"Trash\"\n\t\t\ttooltipDescription=\"Delete this node from the layer chain.\"\n\t\t\tsize={24}\n\t\t\taction={(e) => {\n\t\t\t\teditor.handle.deleteNode(widgetData.id);\n\t\t\t\te?.stopPropagation();\n\t\t\t}}\n\t\t\tclass=\"show-only-on-hover\"\n\t\t/>\n\t\t<IconButton\n\t\t\ticon={widgetData.visible ? \"EyeVisible\" : \"EyeHidden\"}\n\t\t\thoverIcon={widgetData.visible ? \"EyeHide\" : \"EyeShow\"}\n\t\t\ttooltipDescription={widgetData.visible ? \"Hide this node.\" : \"Show this node.\"}\n\t\t\tsize={24}\n\t\t\taction={(e) => {\n\t\t\t\teditor.handle.toggleNodeVisibilityLayerPanel(widgetData.id);\n\t\t\t\te?.stopPropagation();\n\t\t\t}}\n\t\t\tclass={widgetData.visible ? \"show-only-on-hover\" : \"\"}\n\t\t/>\n\t</button>\n\t{#if expanded}\n\t\t<LayoutCol class=\"body\" data-block-hover-transfer>\n\t\t\t{#each widgetData.layout as layoutGroup}\n\t\t\t\t{#if \"Row\" in layoutGroup}\n\t\t\t\t\t<WidgetSpan direction=\"row\" widgets={layoutGroup.Row.rowWidgets} {layoutTarget} />\n\t\t\t\t{:else if \"Section\" in layoutGroup}\n\t\t\t\t\t<svelte:self widgetData={layoutGroup.Section} {layoutTarget} />\n\t\t\t\t{/if}\n\t\t\t{/each}\n\t\t</LayoutCol>\n\t{/if}\n</LayoutCol>\n\n<style lang=\"scss\" global>\n\t.widget-section {\n\t\tflex: 0 0 auto;\n\t\tmargin: 0 4px;\n\t\tmargin-top: 4px;\n\n\t\t.header {\n\t\t\ttext-align: left;\n\t\t\talign-items: center;\n\t\t\tdisplay: flex;\n\t\t\tflex: 0 0 24px;\n\t\t\tpadding-left: 8px;\n\t\t\tpadding-right: 0;\n\t\t\tmargin-bottom: 4px;\n\t\t\tborder: 0;\n\t\t\tborder-radius: 4px;\n\t\t\tbackground: var(--color-2-mildblack);\n\n\t\t\t&.expanded {\n\t\t\t\tborder-radius: 4px 4px 0 0;\n\t\t\t\tmargin-bottom: 0;\n\n\t\t\t\t.expand-arrow::after {\n\t\t\t\t\ttransform: rotate(90deg);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t&:hover {\n\t\t\t\tbackground: var(--color-4-dimgray);\n\n\t\t\t\t.expand-arrow::after {\n\t\t\t\t\tbackground: var(--icon-expand-collapse-arrow-hover);\n\t\t\t\t}\n\n\t\t\t\t+ .body {\n\t\t\t\t\tborder: 1px solid var(--color-4-dimgray);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.expand-arrow {\n\t\t\t\twidth: 8px;\n\t\t\t\theight: 8px;\n\t\t\t\tmargin: 0;\n\t\t\t\tpadding: 0;\n\t\t\t\tposition: relative;\n\t\t\t\tflex: 0 0 auto;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tjustify-content: center;\n\n\t\t\t\t&::after {\n\t\t\t\t\tcontent: \"\";\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\twidth: 8px;\n\t\t\t\t\theight: 8px;\n\t\t\t\t\tbackground: var(--icon-expand-collapse-arrow);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.text-label {\n\t\t\t\theight: 18px;\n\t\t\t\tmargin-left: 8px;\n\t\t\t\tflex: 1 1 100%;\n\t\t\t}\n\t\t}\n\n\t\t&:not(:hover) .header .show-only-on-hover {\n\t\t\tdisplay: none;\n\t\t}\n\n\t\t.body {\n\t\t\tpadding: 0 7px;\n\t\t\tpadding-top: 1px;\n\t\t\tmargin-top: -1px;\n\t\t\tbackground: var(--color-3-darkgray);\n\t\t\tborder: 1px solid var(--color-2-mildblack);\n\t\t\tborder-radius: 0 0 4px 4px;\n\t\t\toverflow: hidden;\n\n\t\t\t.widget-span.row {\n\t\t\t\t&:first-child {\n\t\t\t\t\tmargin-top: calc(4px - 1px);\n\t\t\t\t}\n\n\t\t\t\t&:last-child {\n\t\t\t\t\tmargin-bottom: calc(4px - 1px);\n\t\t\t\t}\n\n\t\t\t\t> .text-button:first-child {\n\t\t\t\t\tmargin-left: 16px;\n\t\t\t\t}\n\n\t\t\t\t> .text-label:first-of-type {\n\t\t\t\t\tflex: 0 0 25%;\n\t\t\t\t\tmargin-left: 16px;\n\t\t\t\t}\n\n\t\t\t\t> .parameter-expose-button + .text-label:first-of-type {\n\t\t\t\t\tmargin-left: 8px;\n\t\t\t\t}\n\n\t\t\t\t> .text-button {\n\t\t\t\t\tflex-grow: 1;\n\t\t\t\t}\n\n\t\t\t\t> .radio-input button {\n\t\t\t\t\tflex: 1 1 100%;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/widgets/WidgetSpan.svelte",
    "content": "<script lang=\"ts\">\n\timport { getContext } from \"svelte\";\n\n\timport type { LayoutTarget, Widget, WidgetInstance } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\timport type { Editor } from \"@graphite/editor\";\n\timport { parseFillChoice } from \"@graphite/utility-functions/colors\";\n\n\timport NodeCatalog from \"@graphite/components/floating-menus/NodeCatalog.svelte\";\n\timport BreadcrumbTrailButtons from \"@graphite/components/widgets/buttons/BreadcrumbTrailButtons.svelte\";\n\timport IconButton from \"@graphite/components/widgets/buttons/IconButton.svelte\";\n\timport ImageButton from \"@graphite/components/widgets/buttons/ImageButton.svelte\";\n\timport ParameterExposeButton from \"@graphite/components/widgets/buttons/ParameterExposeButton.svelte\";\n\timport PopoverButton from \"@graphite/components/widgets/buttons/PopoverButton.svelte\";\n\timport TextButton from \"@graphite/components/widgets/buttons/TextButton.svelte\";\n\timport CheckboxInput from \"@graphite/components/widgets/inputs/CheckboxInput.svelte\";\n\timport ColorInput from \"@graphite/components/widgets/inputs/ColorInput.svelte\";\n\timport CurveInput from \"@graphite/components/widgets/inputs/CurveInput.svelte\";\n\timport DropdownInput from \"@graphite/components/widgets/inputs/DropdownInput.svelte\";\n\timport NumberInput from \"@graphite/components/widgets/inputs/NumberInput.svelte\";\n\timport RadioInput from \"@graphite/components/widgets/inputs/RadioInput.svelte\";\n\timport ReferencePointInput from \"@graphite/components/widgets/inputs/ReferencePointInput.svelte\";\n\timport TextAreaInput from \"@graphite/components/widgets/inputs/TextAreaInput.svelte\";\n\timport TextInput from \"@graphite/components/widgets/inputs/TextInput.svelte\";\n\timport WorkingColorsInput from \"@graphite/components/widgets/inputs/WorkingColorsInput.svelte\";\n\timport IconLabel from \"@graphite/components/widgets/labels/IconLabel.svelte\";\n\timport ImageLabel from \"@graphite/components/widgets/labels/ImageLabel.svelte\";\n\timport Separator from \"@graphite/components/widgets/labels/Separator.svelte\";\n\timport ShortcutLabel from \"@graphite/components/widgets/labels/ShortcutLabel.svelte\";\n\timport TextLabel from \"@graphite/components/widgets/labels/TextLabel.svelte\";\n\n\t// Extract the discriminant key names from the Widget tagged enum union (e.g. \"TextButton\" | \"CheckboxInput\" | ...)\n\ttype WidgetKind = Widget extends infer T ? (T extends Record<infer K, unknown> ? K & string : never) : never;\n\t// Extract the props type for a specific widget kind (e.g. WidgetProps<\"TextButton\"> gives the Wasm-generated TextButton interface)\n\ttype WidgetProps<K extends WidgetKind> = Extract<Widget, Record<K, unknown>>[K];\n\t// A Widget tagged enum unwrapped into a correlated [kind, props] tuple\n\ttype UnwrappedWidget = { [K in WidgetKind]: [kind: K, props: WidgetProps<K>] }[WidgetKind];\n\n\tconst editor = getContext<Editor>(\"editor\");\n\n\texport let widgets: WidgetInstance[];\n\texport let direction: \"row\" | \"column\";\n\texport let layoutTarget: LayoutTarget;\n\n\tlet className = \"\";\n\texport { className as class };\n\texport let classes: Record<string, boolean> = {};\n\n\texport let narrow = false;\n\n\t$: extraClasses = Object.entries(classes)\n\t\t.flatMap(([className, stateName]) => (stateName ? [className] : []))\n\t\t.join(\" \");\n\n\tfunction widgetValueCommit(widgetIndex: number, value: unknown) {\n\t\teditor.handle.widgetValueCommit(layoutTarget, widgets[widgetIndex].widgetId, value);\n\t}\n\n\tfunction widgetValueUpdate(widgetIndex: number, value: unknown, resendWidget: boolean) {\n\t\teditor.handle.widgetValueUpdate(layoutTarget, widgets[widgetIndex].widgetId, value, resendWidget);\n\t}\n\n\tfunction widgetValueCommitAndUpdate(widgetIndex: number, value: unknown, resendWidget: boolean) {\n\t\teditor.handle.widgetValueCommitAndUpdate(layoutTarget, widgets[widgetIndex].widgetId, value, resendWidget);\n\t}\n\n\t// Extracts the kind and props from a Widget tagged enum, validated against the widget registry.\n\t// The overload declares the precise correlated return type while the implementation uses broader types.\n\tfunction unwrapWidget(widgetInstance: WidgetInstance): UnwrappedWidget | undefined;\n\tfunction unwrapWidget(widgetInstance: WidgetInstance) {\n\t\tconst entry = Object.entries(widgetInstance.widget)[0];\n\t\tif (!entry || !(entry[0] in widgetResolvers)) return undefined;\n\t\treturn entry;\n\t}\n\n\t// Resolves the unwrapped widget through the registry to get its Svelte component and computed props.\n\tfunction resolveWidget([kind, widgetProps]: UnwrappedWidget, widgetIndex: number) {\n\t\tconst config = widgetResolvers[kind];\n\t\treturn {\n\t\t\tcomponent: config.component,\n\t\t\tprops: config.getProps(widgetProps, widgetIndex),\n\t\t\tslot: config.getSlotContent?.(widgetProps),\n\t\t};\n\t}\n\n\t// Svelte has no variance-safe base type for component constructors\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\ttype SvelteComponentAny = any;\n\n\ttype WidgetConfig<K extends WidgetKind> = {\n\t\tcomponent: SvelteComponentAny;\n\t\tgetProps(props: WidgetProps<K>, widgetIndex: number): Record<string, unknown> | undefined;\n\t\tgetSlotContent?(props: WidgetProps<K>): string;\n\t};\n\n\t// The union of all individual widget props types (distributed across each WidgetKind member)\n\ttype AnyWidgetProps = { [K in WidgetKind]: WidgetProps<K> }[WidgetKind];\n\n\t// Uniform view for runtime lookup — widens the per-kind config types to a single type that\n\t// accepts any widget props, avoiding the correlated unions problem at the call site\n\ttype WidgetResolver = {\n\t\tcomponent: SvelteComponentAny;\n\t\tgetProps(props: AnyWidgetProps, widgetIndex: number): Record<string, unknown> | undefined;\n\t\tgetSlotContent?(props: AnyWidgetProps): string;\n\t};\n\n\t// Overload: callers provide the precise mapped type (preserving per-entry type inference).\n\t// Implementation: receives/returns the widened uniform type (no cast needed).\n\t// Method syntax bivariance makes WidgetConfig<K> assignable to WidgetResolver in the overload check.\n\tfunction createWidgetResolvers(registry: { [K in WidgetKind]: WidgetConfig<K> }): Record<WidgetKind, WidgetResolver>;\n\tfunction createWidgetResolvers(registry: Record<WidgetKind, WidgetResolver>): Record<WidgetKind, WidgetResolver> {\n\t\treturn registry;\n\t}\n\n\tconst widgetResolvers = createWidgetResolvers({\n\t\tCheckboxInput: {\n\t\t\tcomponent: CheckboxInput,\n\t\t\tgetProps: (props, index) => ({\n\t\t\t\t...props,\n\t\t\t\t$$events: { checked: (e: CustomEvent) => widgetValueCommitAndUpdate(index, e.detail, true) },\n\t\t\t}),\n\t\t},\n\t\tColorInput: {\n\t\t\tcomponent: ColorInput,\n\t\t\tgetProps: (props, index) => ({\n\t\t\t\t...props,\n\t\t\t\tvalue: parseFillChoice(props.value),\n\t\t\t\t$$events: {\n\t\t\t\t\tvalue: (e: CustomEvent) => widgetValueUpdate(index, e.detail, false),\n\t\t\t\t\tstartHistoryTransaction: () => widgetValueCommit(index, props.value),\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t\tCurveInput: {\n\t\t\t// TODO: CurvesInput is currently unused\n\t\t\tcomponent: CurveInput,\n\t\t\tgetProps: (props, index) => ({\n\t\t\t\t...props,\n\t\t\t\t$$events: {\n\t\t\t\t\tvalue: (e: CustomEvent) => widgetValueCommitAndUpdate(index, e.detail, false),\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t\tDropdownInput: {\n\t\t\tcomponent: DropdownInput,\n\t\t\tgetProps: (props, index) => ({\n\t\t\t\t...props,\n\t\t\t\t$$events: {\n\t\t\t\t\thoverInEntry: (e: CustomEvent) => widgetValueUpdate(index, e.detail, false),\n\t\t\t\t\thoverOutEntry: (e: CustomEvent) => widgetValueUpdate(index, e.detail, false),\n\t\t\t\t\tselectedIndex: (e: CustomEvent) => widgetValueCommitAndUpdate(index, e.detail, true),\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t\tParameterExposeButton: {\n\t\t\tcomponent: ParameterExposeButton,\n\t\t\tgetProps: (props, index) => ({\n\t\t\t\t...props,\n\t\t\t\taction: () => widgetValueCommitAndUpdate(index, undefined, true),\n\t\t\t}),\n\t\t},\n\t\tIconButton: {\n\t\t\tcomponent: IconButton,\n\t\t\tgetProps: (props, index) => ({\n\t\t\t\t...props,\n\t\t\t\taction: () => widgetValueCommitAndUpdate(index, undefined, true),\n\t\t\t}),\n\t\t},\n\t\tIconLabel: {\n\t\t\tcomponent: IconLabel,\n\t\t\tgetProps: (props) => ({ ...props }),\n\t\t},\n\t\tShortcutLabel: {\n\t\t\tcomponent: ShortcutLabel,\n\t\t\tgetProps: (props) => {\n\t\t\t\tif (!props.shortcut) return undefined;\n\t\t\t\treturn { ...props };\n\t\t\t},\n\t\t},\n\t\tImageLabel: {\n\t\t\tcomponent: ImageLabel,\n\t\t\tgetProps: (props) => ({ ...props }),\n\t\t},\n\t\tImageButton: {\n\t\t\tcomponent: ImageButton,\n\t\t\tgetProps: (props, index) => ({\n\t\t\t\t...props,\n\t\t\t\taction: () => widgetValueCommitAndUpdate(index, undefined, true),\n\t\t\t}),\n\t\t},\n\t\tNodeCatalog: {\n\t\t\tcomponent: NodeCatalog,\n\t\t\tgetProps: (props, index) => ({\n\t\t\t\t...props,\n\t\t\t\t$$events: { selectNodeType: (e: CustomEvent) => widgetValueCommitAndUpdate(index, e.detail, false) },\n\t\t\t}),\n\t\t},\n\t\tNumberInput: {\n\t\t\tcomponent: NumberInput,\n\t\t\tgetProps: (props, index) => ({\n\t\t\t\t...props,\n\t\t\t\tincrementCallbackIncrease: () => widgetValueCommitAndUpdate(index, \"Increment\", false),\n\t\t\t\tincrementCallbackDecrease: () => widgetValueCommitAndUpdate(index, \"Decrement\", false),\n\t\t\t\t$$events: {\n\t\t\t\t\tvalue: (e: CustomEvent) => widgetValueUpdate(index, e.detail, true),\n\t\t\t\t\tstartHistoryTransaction: () => widgetValueCommit(index, props.value),\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t\tReferencePointInput: {\n\t\t\tcomponent: ReferencePointInput,\n\t\t\tgetProps: (props, index) => ({\n\t\t\t\t...props,\n\t\t\t\t$$events: { value: (e: CustomEvent) => widgetValueCommitAndUpdate(index, e.detail, true) },\n\t\t\t}),\n\t\t},\n\t\tPopoverButton: {\n\t\t\tcomponent: PopoverButton,\n\t\t\tgetProps: (props) => ({\n\t\t\t\t...props,\n\t\t\t\tlayoutTarget,\n\t\t\t}),\n\t\t},\n\t\tRadioInput: {\n\t\t\tcomponent: RadioInput,\n\t\t\tgetProps: (props, index) => ({\n\t\t\t\t...props,\n\t\t\t\t$$events: { selectedIndex: (e: CustomEvent) => widgetValueCommitAndUpdate(index, e.detail, true) },\n\t\t\t}),\n\t\t},\n\t\tSeparator: {\n\t\t\tcomponent: Separator,\n\t\t\tgetProps: (props) => ({ ...props }),\n\t\t},\n\t\tWorkingColorsInput: {\n\t\t\tcomponent: WorkingColorsInput,\n\t\t\tgetProps: (props) => ({ ...props }),\n\t\t},\n\t\tTextAreaInput: {\n\t\t\tcomponent: TextAreaInput,\n\t\t\tgetProps: (props, index) => ({\n\t\t\t\t...props,\n\t\t\t\t$$events: { commitText: (e: CustomEvent) => widgetValueCommitAndUpdate(index, e.detail, false) },\n\t\t\t}),\n\t\t},\n\t\tTextButton: {\n\t\t\tcomponent: TextButton,\n\t\t\tgetProps: (props, index) => ({\n\t\t\t\t...props,\n\t\t\t\taction: () => widgetValueCommitAndUpdate(index, [], true),\n\t\t\t\t$$events: { selectedEntryValuePath: (e: CustomEvent) => widgetValueCommitAndUpdate(index, e.detail, false) },\n\t\t\t}),\n\t\t},\n\t\tBreadcrumbTrailButtons: {\n\t\t\tcomponent: BreadcrumbTrailButtons,\n\t\t\tgetProps: (props, index) => ({\n\t\t\t\t...props,\n\t\t\t\taction: (breadcrumbIndex: number) => widgetValueCommitAndUpdate(index, breadcrumbIndex, true),\n\t\t\t}),\n\t\t},\n\t\tTextInput: {\n\t\t\tcomponent: TextInput,\n\t\t\tgetProps: (props, index) => ({\n\t\t\t\t...props,\n\t\t\t\t$$events: { commitText: (e: CustomEvent) => widgetValueCommitAndUpdate(index, e.detail, true) },\n\t\t\t}),\n\t\t},\n\t\tTextLabel: {\n\t\t\tcomponent: TextLabel,\n\t\t\tgetProps: ({ value: _, ...rest }) => rest,\n\t\t\tgetSlotContent: (props) => props.value,\n\t\t},\n\t});\n</script>\n\n<div class={`widget-span ${className} ${extraClasses}`.trim()} class:narrow class:row={direction === \"row\"} class:column={direction === \"column\"}>\n\t{#each widgets as widget, widgetIndex}\n\t\t{@const unwrapped = unwrapWidget(widget)}\n\t\t{#if unwrapped}\n\t\t\t{@const { component, props, slot } = resolveWidget(unwrapped, widgetIndex)}\n\t\t\t{#if props !== undefined && slot !== undefined}\n\t\t\t\t<svelte:component this={component} {...props}>{slot}</svelte:component>\n\t\t\t{:else if props !== undefined}\n\t\t\t\t<svelte:component this={component} {...props} />\n\t\t\t{/if}\n\t\t{/if}\n\t{/each}\n</div>\n\n<style lang=\"scss\" global>\n\t.widget-span.column {\n\t\tflex: 0 0 auto;\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t}\n\n\t.widget-span.row {\n\t\tflex: 0 0 auto;\n\t\tdisplay: flex;\n\t\tmin-height: var(--row-height);\n\t\t--row-height: 32px;\n\n\t\t&.narrow {\n\t\t\t--row-height: 24px;\n\t\t}\n\n\t\t> * {\n\t\t\t--widget-height: 24px;\n\t\t\t// Vertically center the widget within the row\n\t\t\tmargin: calc((var(--row-height) - var(--widget-height)) / 2) 0;\n\t\t\tmin-height: var(--widget-height);\n\n\t\t\t&:not(.multiline) {\n\t\t\t\tline-height: var(--widget-height);\n\t\t\t}\n\n\t\t\t&.icon-label.size-12 {\n\t\t\t\t--widget-height: 12px;\n\t\t\t}\n\n\t\t\t&.icon-label.size-16 {\n\t\t\t\t--widget-height: 16px;\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/widgets/WidgetTable.svelte",
    "content": "<script lang=\"ts\">\n\timport type { LayoutTarget, WidgetTable } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\n\timport WidgetSpan from \"@graphite/components/widgets/WidgetSpan.svelte\";\n\n\texport let widgetData: WidgetTable;\n\texport let layoutTarget: LayoutTarget;\n\n\t$: columns = widgetData.tableWidgets.length > 0 ? widgetData.tableWidgets[0].length : 0;\n</script>\n\n<table class:unstyled={widgetData.unstyled}>\n\t<tbody>\n\t\t{#each widgetData.tableWidgets as row}\n\t\t\t<tr>\n\t\t\t\t{#each row as cell}\n\t\t\t\t\t<td colspan={row.length < columns ? columns - row.length + 1 : undefined}>\n\t\t\t\t\t\t<WidgetSpan direction=\"row\" widgets={[cell]} {layoutTarget} narrow={true} />\n\t\t\t\t\t</td>\n\t\t\t\t{/each}\n\t\t\t</tr>\n\t\t{/each}\n\t</tbody>\n</table>\n\n<style lang=\"scss\" global>\n\ttable:not(.unstyled) {\n\t\tbackground: var(--color-3-darkgray);\n\t\tborder: none;\n\t\tborder-spacing: 4px;\n\t\tborder-radius: 2px;\n\n\t\ttd {\n\t\t\tbackground: var(--color-2-mildblack);\n\t\t\tvertical-align: top;\n\t\t\tborder: none;\n\t\t\tborder-radius: 2px;\n\t\t\tpadding: 4px 8px;\n\t\t}\n\n\t\ttr:first-child td {\n\t\t\tbackground-image: var(--inheritance-dots-background-4-dimgray);\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/widgets/buttons/BreadcrumbTrailButtons.svelte",
    "content": "<script lang=\"ts\">\n\timport type { ActionShortcut } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\n\timport LayoutRow from \"@graphite/components/layout/LayoutRow.svelte\";\n\timport TextButton from \"@graphite/components/widgets/buttons/TextButton.svelte\";\n\n\t// Content\n\texport let labels: string[];\n\texport let disabled = false;\n\t// Tooltips\n\texport let tooltipLabel: string | undefined = undefined;\n\texport let tooltipDescription: string | undefined = undefined;\n\texport let tooltipShortcut: ActionShortcut | undefined = undefined;\n\t// Callbacks\n\texport let action: (index: number) => void;\n</script>\n\n<LayoutRow class=\"breadcrumb-trail-buttons\" {tooltipLabel} {tooltipDescription} {tooltipShortcut}>\n\t{#each labels as label, index}\n\t\t<TextButton {label} emphasized={index === labels.length - 1} {disabled} action={() => !disabled && index !== labels.length - 1 && action(index)} />\n\t{/each}\n</LayoutRow>\n\n<style lang=\"scss\" global>\n\t.breadcrumb-trail-buttons {\n\t\t.text-button {\n\t\t\tposition: relative;\n\n\t\t\t&:not(:first-of-type) {\n\t\t\t\tborder-top-left-radius: 0;\n\t\t\t\tborder-bottom-left-radius: 0;\n\n\t\t\t\t&::before {\n\t\t\t\t\tcontent: \"\";\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\tleft: -4px;\n\t\t\t\t\twidth: 8px;\n\t\t\t\t\theight: 100%;\n\t\t\t\t\tbackground: var(--button-background-color);\n\t\t\t\t\tclip-path: polygon(8px -1px, 0 -1px, 4px 12px, 0 25px, 8px 25px);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t&:not(:last-of-type) {\n\t\t\t\tborder-top-right-radius: 0;\n\t\t\t\tborder-bottom-right-radius: 0;\n\n\t\t\t\t&::after {\n\t\t\t\t\tcontent: \"\";\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\tright: -4px;\n\t\t\t\t\twidth: 8px;\n\t\t\t\t\theight: 100%;\n\t\t\t\t\tbackground: var(--button-background-color);\n\t\t\t\t\tclip-path: polygon(0 -1px, 4px -1px, 8px 12px, 4px 25px, 0 25px);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t&:last-of-type {\n\t\t\t\t// Make this non-functional button not change color on hover\n\t\t\t\tpointer-events: none;\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/widgets/buttons/IconButton.svelte",
    "content": "<script lang=\"ts\">\n\timport type { ActionShortcut } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\timport type { IconName, IconSize } from \"@graphite/icons\";\n\n\timport IconLabel from \"@graphite/components/widgets/labels/IconLabel.svelte\";\n\n\t// Content\n\texport let icon: IconName;\n\texport let hoverIcon: IconName | undefined = undefined;\n\texport let size: IconSize;\n\texport let disabled = false;\n\t// Styling\n\texport let emphasized = false;\n\t// Tooltips\n\texport let tooltipLabel: string | undefined = undefined;\n\texport let tooltipDescription: string | undefined = undefined;\n\texport let tooltipShortcut: ActionShortcut | undefined = undefined;\n\t// Callbacks\n\texport let action: (e?: MouseEvent) => void;\n\n\tlet className = \"\";\n\texport { className as class };\n\texport let classes: Record<string, boolean> = {};\n\n\t$: extraClasses = Object.entries(classes)\n\t\t.flatMap(([className, stateName]) => (stateName ? [className] : []))\n\t\t.join(\" \");\n</script>\n\n<button\n\tclass={`icon-button size-${size} ${className} ${extraClasses}`.trim()}\n\tclass:hover-icon={hoverIcon && !disabled}\n\tclass:disabled\n\tclass:emphasized\n\ton:click={action}\n\t{disabled}\n\tdata-tooltip-label={tooltipLabel}\n\tdata-tooltip-description={tooltipDescription}\n\tdata-tooltip-shortcut={tooltipShortcut?.shortcut ? JSON.stringify(tooltipShortcut.shortcut) : undefined}\n\ttabindex={emphasized ? -1 : 0}\n\t{...$$restProps}\n>\n\t<IconLabel {icon} />\n\t{#if hoverIcon && !disabled}\n\t\t<IconLabel icon={hoverIcon} />\n\t{/if}\n</button>\n\n<style lang=\"scss\" global>\n\t.icon-button {\n\t\tdisplay: flex;\n\t\tjustify-content: center;\n\t\talign-items: center;\n\t\tflex: 0 0 auto;\n\t\tmargin: 0;\n\t\tpadding: 0;\n\t\tborder: none;\n\t\tborder-radius: 2px;\n\t\tbackground: none;\n\n\t\tsvg {\n\t\t\tfill: var(--color-e-nearwhite);\n\t\t}\n\n\t\t// The `where` pseudo-class does not contribtue to specificity\n\t\t& + :where(.icon-button) {\n\t\t\tmargin-left: 0;\n\t\t}\n\n\t\t&:hover {\n\t\t\tbackground: var(--color-5-dullgray);\n\t\t}\n\n\t\t&.hover-icon {\n\t\t\t&:not(:hover) .icon-label:nth-of-type(2) {\n\t\t\t\tdisplay: none;\n\t\t\t}\n\n\t\t\t&:hover .icon-label:nth-of-type(1) {\n\t\t\t\tdisplay: none;\n\t\t\t}\n\t\t}\n\n\t\t&.disabled {\n\t\t\tbackground: none;\n\n\t\t\tsvg {\n\t\t\t\tfill: var(--color-8-uppergray);\n\t\t\t}\n\t\t}\n\n\t\t&.emphasized {\n\t\t\tbackground: var(--color-e-nearwhite);\n\n\t\t\tsvg {\n\t\t\t\tfill: var(--color-2-mildblack);\n\t\t\t}\n\t\t}\n\n\t\t&.size-12 {\n\t\t\twidth: 12px;\n\t\t\theight: 12px;\n\t\t}\n\n\t\t&.size-16 {\n\t\t\twidth: 16px;\n\t\t\theight: 16px;\n\t\t}\n\n\t\t&.size-24 {\n\t\t\twidth: 24px;\n\t\t\theight: 24px;\n\t\t}\n\n\t\t&.size-32 {\n\t\t\twidth: 32px;\n\t\t\theight: 32px;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/widgets/buttons/ImageButton.svelte",
    "content": "<script lang=\"ts\">\n\timport type { ActionShortcut } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\timport { IMAGE_BASE64_STRINGS } from \"@graphite/utility-functions/images\";\n\n\tlet className = \"\";\n\texport { className as class };\n\texport let classes: Record<string, boolean> = {};\n\n\t// Content\n\texport let image: string;\n\texport let width: string | undefined;\n\texport let height: string | undefined;\n\t// Tooltips\n\texport let tooltipLabel: string | undefined = undefined;\n\texport let tooltipDescription: string | undefined = undefined;\n\texport let tooltipShortcut: ActionShortcut | undefined = undefined;\n\t// Callbacks\n\texport let action: (e?: MouseEvent) => void;\n\n\t$: extraClasses = Object.entries(classes)\n\t\t.flatMap(([className, stateName]) => (stateName ? [className] : []))\n\t\t.join(\" \");\n</script>\n\n<img\n\tsrc={IMAGE_BASE64_STRINGS[image]}\n\tstyle:width\n\tstyle:height\n\tclass={`image-button ${className} ${extraClasses}`.trim()}\n\tdata-tooltip-label={tooltipLabel}\n\tdata-tooltip-description={tooltipDescription}\n\tdata-tooltip-shortcut={tooltipShortcut?.shortcut ? JSON.stringify(tooltipShortcut.shortcut) : undefined}\n\talt=\"\"\n\ton:click={action}\n/>\n\n<style lang=\"scss\" global>\n\t.image-button {\n\t\twidth: auto;\n\t\theight: auto;\n\t\tborder-radius: 2px;\n\n\t\t+ .image-button.image-button {\n\t\t\tmargin-left: 8px;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/widgets/buttons/ParameterExposeButton.svelte",
    "content": "<script lang=\"ts\">\n\timport type { FrontendGraphDataType, ActionShortcut } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\n\timport LayoutRow from \"@graphite/components/layout/LayoutRow.svelte\";\n\n\t// Content\n\texport let exposed: boolean;\n\texport let dataType: FrontendGraphDataType;\n\t// Tooltips\n\texport let tooltipLabel: string | undefined = undefined;\n\texport let tooltipDescription: string | undefined = undefined;\n\texport let tooltipShortcut: ActionShortcut | undefined = undefined;\n\t// Callbacks\n\texport let action: (e?: MouseEvent) => void;\n</script>\n\n<LayoutRow class=\"parameter-expose-button\">\n\t<button\n\t\tclass:exposed\n\t\tstyle:--data-type-color={`var(--color-data-${dataType.toLowerCase()})`}\n\t\tstyle:--data-type-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`}\n\t\ton:click={action}\n\t\tdata-tooltip-label={tooltipLabel}\n\t\tdata-tooltip-description={tooltipDescription}\n\t\tdata-tooltip-shortcut={tooltipShortcut?.shortcut ? JSON.stringify(tooltipShortcut.shortcut) : undefined}\n\t\ttabindex=\"-1\"\n\t>\n\t\t{#if !exposed}\n\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 8 8\">\n\t\t\t\t<circle class=\"interior\" r=\"3\" cx=\"4\" cy=\"4\" />\n\t\t\t\t<path\n\t\t\t\t\tclass=\"outline\"\n\t\t\t\t\td=\"M4,1C5.656,1 7,2.344 7,4C7,5.656 5.656,7 4,7C2.344,7 1,5.656 1,4C1,2.344 2.344,1 4,1ZM4,2C2.896,2 2,2.896 2,4C2,5.104 2.896,6 4,6C5.104,6 6,5.104 6,4C6,2.896 5.104,2 4,2z\"\n\t\t\t\t/>\n\t\t\t</svg>\n\t\t{:else}\n\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 8 8\">\n\t\t\t\t<path class=\"interior\" d=\"M0,6.306L0,1.694C0,0.228 1.06,-0.41 2.356,0.276L7.028,2.752C8.324,3.438 8.324,4.562 7.028,5.248L2.356,7.723C1.06,8.41 0,7.771 0,6.306z\" />\n\t\t\t\t<path\n\t\t\t\t\tclass=\"outline\"\n\t\t\t\t\td=\"M1.364,8C0.56,8 0,7.372 0,6.306L0,1.694C0,0.228 1.06,-0.41 2.356,0.276L7.028,2.752C8.324,3.439 8.324,4.561 7.028,5.248L2.356,7.724C2.002,7.911 1.666,8 1.364,8ZM1.364,7.2C1.542,7.2 1.756,7.137 1.981,7.017L6.653,4.541C7.031,4.341 7.2,4.125 7.2,4C7.2,3.875 7.031,3.659 6.653,3.459L1.982,0.983C1.756,0.863 1.542,0.8 1.364,0.8C0.873,0.8 0.8,1.36 0.8,1.694L0.8,6.306C0.8,6.64 0.873,7.2 1.364,7.2z\"\n\t\t\t\t/>\n\t\t\t</svg>\n\t\t{/if}\n\t</button>\n</LayoutRow>\n\n<style lang=\"scss\" global>\n\t.parameter-expose-button {\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tflex: 0 0 auto;\n\t\tmax-height: 24px;\n\n\t\tbutton {\n\t\t\tflex: 0 0 auto;\n\t\t\twidth: 8px;\n\t\t\theight: 8px;\n\t\t\tmargin: 0;\n\t\t\tpadding: 0;\n\t\t\tborder: none;\n\t\t\tbackground: none;\n\t\t\tfill: none;\n\t\t\tstroke: none;\n\n\t\t\t.outline {\n\t\t\t\tfill: none;\n\t\t\t}\n\n\t\t\t.interior {\n\t\t\t\tfill: var(--data-type-color);\n\t\t\t}\n\n\t\t\t&:hover {\n\t\t\t\t.outline {\n\t\t\t\t\tfill: var(--data-type-color);\n\t\t\t\t}\n\n\t\t\t\t.interior {\n\t\t\t\t\tfill: var(--data-type-color-dim);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/widgets/buttons/PopoverButton.svelte",
    "content": "<script lang=\"ts\">\n\timport type { MenuDirection, ActionShortcut, Layout, LayoutTarget } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\timport type { IconName, PopoverButtonStyle } from \"@graphite/icons\";\n\n\timport FloatingMenu from \"@graphite/components/layout/FloatingMenu.svelte\";\n\timport LayoutRow from \"@graphite/components/layout/LayoutRow.svelte\";\n\timport IconButton from \"@graphite/components/widgets/buttons/IconButton.svelte\";\n\timport IconLabel from \"@graphite/components/widgets/labels/IconLabel.svelte\";\n\timport WidgetLayout from \"@graphite/components/widgets/WidgetLayout.svelte\";\n\n\texport let layoutTarget: LayoutTarget;\n\n\t// Content\n\texport let style: PopoverButtonStyle = \"DropdownArrow\";\n\texport let icon: IconName | undefined = undefined;\n\texport let disabled = false;\n\t// Children\n\texport let popoverLayout: Layout;\n\texport let popoverMinWidth = 1;\n\texport let menuDirection: MenuDirection = \"Bottom\";\n\t// Tooltips\n\texport let tooltipLabel: string | undefined = undefined;\n\texport let tooltipDescription: string | undefined = undefined;\n\texport let tooltipShortcut: ActionShortcut | undefined = undefined;\n\t// Callbacks\n\texport let action: (() => void) | undefined = undefined;\n\n\tlet open = false;\n\n\tfunction onClick() {\n\t\topen = true;\n\t\taction?.();\n\t}\n</script>\n\n<LayoutRow class=\"popover-button\" classes={{ \"has-icon\": icon !== undefined, \"direction-top\": menuDirection === \"Top\" }}>\n\t<IconButton\n\t\tclass=\"dropdown-icon\"\n\t\tclasses={{ open }}\n\t\t{disabled}\n\t\taction={() => onClick()}\n\t\ticon={style || \"DropdownArrow\"}\n\t\tsize={16}\n\t\t{tooltipLabel}\n\t\t{tooltipDescription}\n\t\t{tooltipShortcut}\n\t\tdata-floating-menu-spawner\n\t/>\n\t{#if icon !== undefined}\n\t\t<IconLabel class=\"descriptive-icon\" classes={{ open }} {disabled} {icon} {tooltipLabel} {tooltipDescription} {tooltipShortcut} />\n\t{/if}\n\n\t<FloatingMenu {open} on:open={({ detail }) => (open = detail)} minWidth={popoverMinWidth} type=\"Popover\" direction={menuDirection || \"Bottom\"}>\n\t\t<WidgetLayout layout={popoverLayout} {layoutTarget} />\n\t</FloatingMenu>\n</LayoutRow>\n\n<style lang=\"scss\" global>\n\t.popover-button {\n\t\tposition: relative;\n\t\twidth: 16px;\n\t\theight: 24px;\n\t\tflex: 0 0 auto;\n\n\t\t&.has-icon {\n\t\t\twidth: 36px;\n\n\t\t\t.dropdown-icon {\n\t\t\t\tpadding-left: calc(36px - 16px);\n\t\t\t\tbox-sizing: content-box;\n\t\t\t}\n\n\t\t\t&.direction-top .dropdown-icon .icon-label {\n\t\t\t\ttransform: rotate(180deg);\n\t\t\t}\n\t\t}\n\n\t\t.dropdown-icon {\n\t\t\twidth: 16px;\n\t\t\theight: 100%;\n\t\t\tpadding: 0;\n\t\t\tborder: none;\n\t\t\tborder-radius: 2px;\n\t\t\tfill: var(--color-e-nearwhite);\n\n\t\t\t&:hover:not(.disabled),\n\t\t\t&.open:not(.disabled) {\n\t\t\t\tbackground: var(--color-5-dullgray);\n\t\t\t}\n\t\t}\n\n\t\t.descriptive-icon {\n\t\t\twidth: 16px;\n\t\t\theight: 16px;\n\t\t\tmargin: auto 0;\n\t\t\tmargin-left: calc(-16px - 16px);\n\t\t\tpointer-events: none;\n\t\t}\n\n\t\t.floating-menu {\n\t\t\tleft: 50%;\n\t\t\tbottom: 0;\n\n\t\t\t.floating-menu-content > :first-child:not(:has(:not(.text-label))),\n\t\t\t.floating-menu-content > :first-child:not(:has(:not(.checkbox-input))) {\n\t\t\t\tmargin-top: -8px;\n\t\t\t}\n\n\t\t\t.floating-menu-content > :last-child:not(:has(:not(.text-label))),\n\t\t\t.floating-menu-content > :last-child:not(:has(:not(.checkbox-input))) {\n\t\t\t\tmargin-bottom: -8px;\n\t\t\t}\n\t\t}\n\n\t\t&.direction-top .floating-menu {\n\t\t\tbottom: 100%;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/widgets/buttons/TextButton.svelte",
    "content": "<script lang=\"ts\">\n\timport { createEventDispatcher } from \"svelte\";\n\n\timport type { MenuListEntry, ActionShortcut } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\timport type { IconName } from \"@graphite/icons\";\n\n\timport MenuList from \"@graphite/components/floating-menus/MenuList.svelte\";\n\timport ConditionalWrapper from \"@graphite/components/layout/ConditionalWrapper.svelte\";\n\timport IconLabel from \"@graphite/components/widgets/labels/IconLabel.svelte\";\n\timport TextLabel from \"@graphite/components/widgets/labels/TextLabel.svelte\";\n\n\tconst dispatch = createEventDispatcher<{ selectedEntryValuePath: string[] }>();\n\n\tlet self: MenuList;\n\tlet open = false;\n\n\t// Note: IconButton should instead be used if only an icon, but no label, is desired.\n\t// However, if multiple TextButton widgets are used in a group with only some having no label, this component is able to accommodate that.\n\n\t// Content\n\texport let label: string;\n\texport let icon: IconName | undefined = undefined;\n\texport let hoverIcon: IconName | undefined = undefined;\n\texport let disabled = false;\n\t// Children\n\texport let menuListChildren: MenuListEntry[][] | undefined = undefined;\n\texport let menuListChildrenHash: bigint | undefined = undefined;\n\t// Styling\n\texport let emphasized = false;\n\texport let flush = false;\n\texport let narrow = false;\n\t// Sizing\n\texport let minWidth = 0;\n\t// Tooltips\n\texport let tooltipLabel: string | undefined = undefined;\n\texport let tooltipDescription: string | undefined = undefined;\n\texport let tooltipShortcut: ActionShortcut | undefined = undefined;\n\t// Callbacks\n\t// TODO: Replace this with an event binding (and on other components that do this)\n\texport let action: (() => void) | undefined;\n\n\t$: menuListChildrenExists = (menuListChildren?.length ?? 0) > 0;\n\n\t// Handles either a button click or, if applicable, the opening of the menu list floating menu\n\tfunction onClick(e: MouseEvent) {\n\t\t// If there's no menu to open, trigger the action\n\t\tif ((menuListChildren?.length ?? 0) === 0) {\n\t\t\t// Call the action\n\t\t\tif (!disabled) action?.();\n\n\t\t\t// Exit early so we don't continue on and try to open the menu\n\t\t\treturn;\n\t\t}\n\n\t\t// Focus the target so that keyboard inputs are sent to the dropdown\n\t\tif (e.target instanceof HTMLElement) e.target.focus();\n\n\t\t// Open the menu list floating menu\n\t\tif (self) self.open = true;\n\t\telse throw new Error(\"The menu bar floating menu has no reference to `self`\");\n\t}\n</script>\n\n<ConditionalWrapper condition={menuListChildrenExists} wrapperClass=\"text-button-container\">\n\t<button\n\t\tclass=\"text-button\"\n\t\tclass:open={self?.open}\n\t\tclass:hover-icon={hoverIcon && !disabled}\n\t\tclass:emphasized\n\t\tclass:disabled\n\t\tclass:narrow\n\t\tclass:flush\n\t\tstyle:min-width={minWidth > 0 ? `${minWidth}px` : undefined}\n\t\tdata-tooltip-label={tooltipLabel}\n\t\tdata-tooltip-description={tooltipDescription}\n\t\tdata-tooltip-shortcut={tooltipShortcut?.shortcut ? JSON.stringify(tooltipShortcut.shortcut) : undefined}\n\t\tdata-emphasized={emphasized || undefined}\n\t\tdata-disabled={disabled || undefined}\n\t\tdata-text-button\n\t\ttabindex={disabled ? -1 : 0}\n\t\tdata-floating-menu-spawner\n\t\tdata-block-hover-transfer={menuListChildrenExists ? undefined : \"\"}\n\t\ton:click={onClick}\n\t>\n\t\t{#if icon}\n\t\t\t<IconLabel {icon} />\n\t\t\t{#if hoverIcon && !disabled}\n\t\t\t\t<IconLabel icon={hoverIcon} />\n\t\t\t{/if}\n\t\t{/if}\n\t\t{#if label}\n\t\t\t<TextLabel>{label}</TextLabel>\n\t\t{/if}\n\t</button>\n\t{#if menuListChildrenExists}\n\t\t<MenuList\n\t\t\ton:selectedEntryValuePath={({ detail }) => dispatch(\"selectedEntryValuePath\", detail)}\n\t\t\ton:open={({ detail }) => (open = detail)}\n\t\t\t{open}\n\t\t\tentries={menuListChildren || []}\n\t\t\tentriesHash={menuListChildrenHash || 0n}\n\t\t\tdirection=\"Bottom\"\n\t\t\tminWidth={240}\n\t\t\tdrawIcon={true}\n\t\t\tbind:this={self}\n\t\t/>\n\t{/if}\n</ConditionalWrapper>\n\n<style lang=\"scss\" global>\n\t.text-button-container {\n\t\tdisplay: flex;\n\t\tposition: relative;\n\t}\n\n\t.text-button {\n\t\tdisplay: flex;\n\t\tjustify-content: center;\n\t\talign-items: center;\n\t\tflex: 0 0 auto;\n\t\twhite-space: nowrap;\n\t\theight: var(--widget-height);\n\t\tmargin: 0;\n\t\tpadding: 0 8px;\n\t\tbox-sizing: border-box;\n\t\tborder: none;\n\t\tborder-radius: 2px;\n\t\tbackground: var(--button-background-color);\n\t\tcolor: var(--button-text-color);\n\t\t--button-background-color: var(--color-4-dimgray);\n\t\t--button-text-color: var(--color-e-nearwhite);\n\t\t--widget-height: 24px;\n\n\t\t&.narrow.narrow {\n\t\t\t--widget-height: 20px;\n\t\t}\n\n\t\t&:hover,\n\t\t&.open {\n\t\t\t--button-background-color: var(--color-6-lowergray);\n\t\t\t--button-text-color: var(--color-f-white);\n\t\t}\n\n\t\t&.hover-icon {\n\t\t\t&:not(:hover) .icon-label:nth-of-type(2) {\n\t\t\t\tdisplay: none;\n\t\t\t}\n\n\t\t\t&:hover .icon-label:nth-of-type(1) {\n\t\t\t\tdisplay: none;\n\t\t\t}\n\t\t}\n\n\t\t&.disabled {\n\t\t\t--button-background-color: var(--color-4-dimgray);\n\t\t\t--button-text-color: var(--color-8-uppergray);\n\t\t}\n\n\t\t&.emphasized {\n\t\t\t--button-background-color: var(--color-e-nearwhite);\n\t\t\t--button-text-color: var(--color-2-mildblack);\n\n\t\t\t&:hover,\n\t\t\t&.open {\n\t\t\t\t--button-background-color: var(--color-f-white);\n\t\t\t}\n\n\t\t\t&.disabled {\n\t\t\t\t--button-background-color: var(--color-8-uppergray);\n\t\t\t}\n\t\t}\n\n\t\t&.flush {\n\t\t\t--button-background-color: none;\n\t\t\t--button-text-color: var(--color-e-nearwhite);\n\n\t\t\t&:hover,\n\t\t\t&.open {\n\t\t\t\t--button-background-color: var(--color-5-dullgray);\n\t\t\t}\n\n\t\t\t&.disabled {\n\t\t\t\t--button-text-color: var(--color-8-uppergray);\n\t\t\t\t--button-background-color: none;\n\t\t\t}\n\t\t}\n\n\t\t.icon-label {\n\t\t\tfill: var(--button-text-color);\n\n\t\t\t+ .text-label {\n\t\t\t\tmargin-left: 8px;\n\t\t\t}\n\t\t}\n\n\t\t.text-label {\n\t\t\toverflow: hidden;\n\t\t}\n\n\t\t// Custom styling for when multiple TextButton widgets are used next to one another in a row or column\n\t\t.widget-span.row > & + .text-button,\n\t\t.layout-row > & + .text-button {\n\t\t\tmargin-left: 8px;\n\t\t}\n\t\t.widget-span.column > & + .text-button,\n\t\t.layout-column > & + .text-button {\n\t\t\tmargin-top: 8px;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/widgets/inputs/CheckboxInput.svelte",
    "content": "<script lang=\"ts\">\n\timport { createEventDispatcher } from \"svelte\";\n\n\timport type { ActionShortcut } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\timport type { IconName } from \"@graphite/icons\";\n\n\timport LayoutRow from \"@graphite/components/layout/LayoutRow.svelte\";\n\timport IconLabel from \"@graphite/components/widgets/labels/IconLabel.svelte\";\n\n\tconst dispatch = createEventDispatcher<{ checked: boolean }>();\n\tconst backupId = String(Math.random()).substring(2);\n\n\t// Content\n\texport let checked = false;\n\texport let icon: IconName | undefined = undefined;\n\texport let forLabel: bigint | undefined = undefined;\n\texport let disabled = false;\n\t// Tooltips\n\texport let tooltipLabel: string | undefined = undefined;\n\texport let tooltipDescription: string | undefined = undefined;\n\texport let tooltipShortcut: ActionShortcut | undefined = undefined;\n\n\tlet inputElement: HTMLInputElement | undefined;\n\n\t$: id = forLabel !== undefined ? String(forLabel) : backupId;\n\t$: displayIcon = !checked && (!icon || icon === \"Checkmark\") ? \"Empty12px\" : icon || \"Checkmark\";\n\n\texport function isChecked() {\n\t\treturn checked;\n\t}\n\n\texport function input(): HTMLInputElement | undefined {\n\t\treturn inputElement;\n\t}\n\n\tfunction toggleCheckboxFromLabel(e: KeyboardEvent) {\n\t\tconst target = e.target instanceof HTMLLabelElement ? e.target : undefined;\n\t\tconst previousSibling = target?.previousSibling instanceof HTMLInputElement ? target.previousSibling : undefined;\n\t\tpreviousSibling?.click();\n\t}\n</script>\n\n<LayoutRow class=\"checkbox-input\">\n\t<input\n\t\ttype=\"checkbox\"\n\t\tid={`checkbox-input-${id}`}\n\t\tbind:checked\n\t\ton:change={(_) => dispatch(\"checked\", inputElement?.checked || false)}\n\t\t{disabled}\n\t\ttabindex={disabled ? -1 : 0}\n\t\tbind:this={inputElement}\n\t/>\n\t<label\n\t\tclass:disabled\n\t\tclass:checked\n\t\tfor={`checkbox-input-${id}`}\n\t\ton:keydown={(e) => e.key === \"Enter\" && toggleCheckboxFromLabel(e)}\n\t\tdata-tooltip-label={tooltipLabel}\n\t\tdata-tooltip-description={tooltipDescription}\n\t\tdata-tooltip-shortcut={tooltipShortcut?.shortcut ? JSON.stringify(tooltipShortcut.shortcut) : undefined}\n\t>\n\t\t<LayoutRow class=\"checkbox-box\">\n\t\t\t<IconLabel icon={displayIcon} />\n\t\t</LayoutRow>\n\t</label>\n</LayoutRow>\n\n<style lang=\"scss\" global>\n\t.checkbox-input {\n\t\tflex: 0 0 auto;\n\t\talign-items: center;\n\n\t\tinput {\n\t\t\t// We can't use `display: none` because it must be visible to work as a tabbale input that accepts a space bar actuation\n\t\t\twidth: 0;\n\t\t\theight: 0;\n\t\t\tmargin: 0;\n\t\t\topacity: 0;\n\t\t\t// Firefox weirdly applies a 2px border which causes this element to take up a 4x4 square of space, so this removes it from the flow to prevent it from offsetting the label\n\t\t\tposition: absolute;\n\t\t}\n\n\t\t// Unchecked\n\t\tlabel {\n\t\t\tdisplay: flex;\n\t\t\theight: 16px;\n\t\t\t// Provides rounded corners for the :focus outline\n\t\t\tborder-radius: 2px;\n\n\t\t\t.checkbox-box {\n\t\t\t\tflex: 0 0 auto;\n\t\t\t\tbackground: var(--color-5-dullgray);\n\t\t\t\tpadding: 2px;\n\t\t\t\tborder-radius: 2px;\n\n\t\t\t\t.icon-label {\n\t\t\t\t\tfill: var(--color-8-uppergray);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Hovered while unchecked\n\t\t\t&:hover .checkbox-box,\n\t\t\t&.label-is-hovered .checkbox-box {\n\t\t\t\tbackground: var(--color-6-lowergray);\n\t\t\t}\n\n\t\t\t// Disabled while unchecked\n\t\t\t&.disabled .checkbox-box {\n\t\t\t\tbackground: var(--color-4-dimgray);\n\t\t\t}\n\t\t}\n\n\t\t// Checked\n\t\tinput:checked + label {\n\t\t\t.checkbox-box {\n\t\t\t\tbackground: var(--color-e-nearwhite);\n\n\t\t\t\t.icon-label {\n\t\t\t\t\tfill: var(--color-2-mildblack);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Hovered while checked\n\t\t\t&:hover .checkbox-box,\n\t\t\t&.label-is-hovered .checkbox-box {\n\t\t\t\tbackground: var(--color-f-white);\n\t\t\t}\n\n\t\t\t// Disabled while checked\n\t\t\t&.disabled .checkbox-box {\n\t\t\t\tbackground: var(--color-8-uppergray);\n\t\t\t}\n\t\t}\n\n\t\t+ .text-label.text-label {\n\t\t\tmargin-left: 8px;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/widgets/inputs/ColorInput.svelte",
    "content": "<script lang=\"ts\">\n\timport { createEventDispatcher } from \"svelte\";\n\n\timport type { FillChoice, MenuDirection, ActionShortcut } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\timport { contrastingOutlineFactor, fillChoiceColor, fillChoiceGradientStops, colorToHexOptionalAlpha, gradientToLinearGradientCSS } from \"@graphite/utility-functions/colors\";\n\n\timport ColorPicker from \"@graphite/components/floating-menus/ColorPicker.svelte\";\n\timport LayoutCol from \"@graphite/components/layout/LayoutCol.svelte\";\n\n\tconst dispatch = createEventDispatcher<{ value: FillChoice; startHistoryTransaction: undefined }>();\n\n\t// Content\n\texport let value: FillChoice;\n\texport let allowNone = false;\n\t// export let allowTransparency = false; // TODO: Implement\n\texport let menuDirection: MenuDirection = \"Bottom\";\n\texport let disabled = false;\n\t// Styling\n\texport let narrow = false;\n\t// Tooltips\n\texport let tooltipLabel: string | undefined = undefined;\n\texport let tooltipDescription: string | undefined = undefined;\n\texport let tooltipShortcut: ActionShortcut | undefined = undefined;\n\n\tlet open = false;\n\n\t$: outlineFactor = contrastingOutlineFactor(value, [\"--color-1-nearblack\", \"--color-3-darkgray\"], 0.01);\n\t$: outlined = outlineFactor > 0.0001;\n\t$: gradientStops = fillChoiceGradientStops(value);\n\t$: solidColor = fillChoiceColor(value);\n\t$: chosenGradient = gradientStops\n\t\t? gradientToLinearGradientCSS(gradientStops)\n\t\t: solidColor\n\t\t\t? `linear-gradient(${colorToHexOptionalAlpha(solidColor)}, ${colorToHexOptionalAlpha(solidColor)})`\n\t\t\t: undefined;\n\t$: none = value === \"None\";\n\t$: transparency = gradientStops ? gradientStops.color.some((color) => color.alpha < 1) : solidColor ? solidColor.alpha < 1 : false;\n</script>\n\n<LayoutCol class=\"color-button\" classes={{ open, disabled, narrow, none, transparency, outlined, \"direction-top\": menuDirection === \"Top\" }} {tooltipLabel} {tooltipDescription} {tooltipShortcut}>\n\t<button style:--chosen-gradient={chosenGradient} style:--outline-amount={outlineFactor} on:click={() => (open = true)} tabindex=\"0\" data-floating-menu-spawner></button>\n\t<ColorPicker\n\t\t{open}\n\t\t{disabled}\n\t\tcolorOrGradient={value}\n\t\tdirection={menuDirection || \"Bottom\"}\n\t\ton:open={({ detail }) => (open = detail)}\n\t\ton:colorOrGradient={({ detail }) => {\n\t\t\tvalue = detail;\n\t\t\tdispatch(\"value\", detail);\n\t\t}}\n\t\ton:startHistoryTransaction={() => {\n\t\t\t// This event is sent to the backend so it knows to start a transaction for the history system. See discussion for some explanation:\n\t\t\t// <https://github.com/GraphiteEditor/Graphite/pull/1584#discussion_r1477592483>\n\t\t\tdispatch(\"startHistoryTransaction\");\n\t\t}}\n\t\t{allowNone}\n\t/>\n</LayoutCol>\n\n<style lang=\"scss\" global>\n\t.color-button {\n\t\tposition: relative;\n\t\tmin-width: 80px;\n\n\t\t&.narrow.narrow {\n\t\t\t--widget-height: 20px;\n\t\t}\n\n\t\t> button {\n\t\t\tborder: none;\n\t\t\tborder-radius: 2px;\n\t\t\tpadding: 0;\n\t\t\tmargin: 0;\n\t\t\twidth: 100%;\n\t\t\theight: 100%;\n\t\t\toverflow: hidden;\n\t\t\tposition: relative;\n\n\t\t\t&::before {\n\t\t\t\tcontent: \"\";\n\t\t\t\tposition: absolute;\n\t\t\t\ttop: 0;\n\t\t\t\tbottom: 0;\n\t\t\t\tleft: 0;\n\t\t\t\tright: 0;\n\t\t\t\tbackground: var(--chosen-gradient);\n\t\t\t}\n\n\t\t\t.text-label {\n\t\t\t\tbackground: var(--color-5-dullgray);\n\t\t\t\tfont-size: 10px;\n\t\t\t\tline-height: 12px;\n\t\t\t\theight: 12px;\n\t\t\t\tborder-radius: 0 0 0 2px;\n\t\t\t\tpadding-right: 4px;\n\t\t\t\tpadding-left: 4px;\n\t\t\t\tposition: absolute;\n\t\t\t\tright: 0;\n\t\t\t\ttop: 0;\n\t\t\t}\n\t\t}\n\n\t\t&.outlined > button::after {\n\t\t\tcontent: \"\";\n\t\t\tposition: absolute;\n\t\t\ttop: 0;\n\t\t\tbottom: 0;\n\t\t\tleft: 0;\n\t\t\tright: 0;\n\t\t\tbox-shadow: inset 0 0 0 1px rgba(var(--color-5-dullgray-rgb), var(--outline-amount));\n\t\t}\n\n\t\t&.transparency > button {\n\t\t\tbackground-image: var(--color-transparent-checkered-background);\n\t\t\tbackground-size: var(--color-transparent-checkered-background-size);\n\t\t\tbackground-position: var(--color-transparent-checkered-background-position);\n\t\t\tbackground-repeat: var(--color-transparent-checkered-background-repeat);\n\t\t}\n\n\t\t&:not(.disabled).none > button {\n\t\t\tbackground: var(--color-none);\n\t\t\tbackground-repeat: var(--color-none-repeat);\n\t\t\tbackground-position: var(--color-none-position);\n\t\t\tbackground-size: var(--color-none-size-24px);\n\t\t\tbackground-image: var(--color-none-image-24px);\n\t\t}\n\n\t\t&.disabled.none > button::after {\n\t\t\tcontent: \"\";\n\t\t\tposition: absolute;\n\t\t\ttop: 0;\n\t\t\tbottom: 0;\n\t\t\tleft: 0;\n\t\t\tright: 0;\n\t\t\tbackground: var(--color-4-dimgray);\n\t\t}\n\n\t\t&:not(.disabled):hover > button .text-label,\n\t\t&:not(.disabled).open > button .text-label {\n\t\t\tbackground: var(--color-6-lowergray);\n\t\t\tcolor: var(--color-f-white);\n\t\t}\n\n\t\t&.disabled > button .text-label {\n\t\t\tbackground: var(--color-4-dimgray);\n\t\t\tcolor: var(--color-8-uppergray);\n\t\t}\n\n\t\t> .floating-menu {\n\t\t\tleft: 50%;\n\t\t\tbottom: 0;\n\t\t}\n\n\t\t&.direction-top > .floating-menu {\n\t\t\tbottom: 100%;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/widgets/inputs/CurveInput.svelte",
    "content": "<script lang=\"ts\">\n\timport { createEventDispatcher } from \"svelte\";\n\n\timport type { Curve, CurveManipulatorGroup, ActionShortcut } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\n\timport LayoutRow from \"@graphite/components/layout/LayoutRow.svelte\";\n\n\tconst dispatch = createEventDispatcher<{\n\t\tvalue: Curve;\n\t}>();\n\n\tconst GRID_SIZE = 4;\n\n\t// Content\n\texport let value: Curve;\n\t// Tooltips\n\texport let tooltipLabel: string | undefined = undefined;\n\texport let tooltipDescription: string | undefined = undefined;\n\texport let tooltipShortcut: ActionShortcut | undefined = undefined;\n\n\tlet manipulatorsList: CurveManipulatorGroup[] = [\n\t\t{\n\t\t\tanchor: [0, 0],\n\t\t\thandles: [\n\t\t\t\t[-1, -1],\n\t\t\t\t[0.25, 0.25],\n\t\t\t],\n\t\t},\n\t\t{\n\t\t\tanchor: [0.5, 0.5],\n\t\t\thandles: [\n\t\t\t\t[0.25, 0.25],\n\t\t\t\t[0.75, 0.75],\n\t\t\t],\n\t\t},\n\t\t{\n\t\t\tanchor: [1, 1],\n\t\t\thandles: [\n\t\t\t\t[0.75, 0.75],\n\t\t\t\t[2, 2],\n\t\t\t],\n\t\t},\n\t];\n\tlet selectedNodeIndex: number | undefined = undefined;\n\tlet draggedNodeIndex: number | undefined = undefined;\n\tlet dAttribute = recalculateSvgPath();\n\n\t$: {\n\t\tmanipulatorsList = [manipulatorsList[0]].concat(value.manipulatorGroups).concat([manipulatorsList[manipulatorsList.length - 1]]);\n\t\tmanipulatorsList[0].handles[1] = value.firstHandle;\n\t\tmanipulatorsList[manipulatorsList.length - 1].handles[0] = value.lastHandle;\n\t\tdAttribute = recalculateSvgPath();\n\t}\n\n\tfunction updateCurve() {\n\t\tdispatch(\"value\", {\n\t\t\tmanipulatorGroups: manipulatorsList.slice(1, manipulatorsList.length - 1),\n\t\t\tfirstHandle: manipulatorsList[0].handles[1],\n\t\t\tlastHandle: manipulatorsList[manipulatorsList.length - 1].handles[0],\n\t\t});\n\t}\n\n\tfunction recalculateSvgPath() {\n\t\tlet dAttribute = \"\";\n\t\tlet anchor = manipulatorsList[0].anchor;\n\t\tlet handle = manipulatorsList[0].handles[1];\n\n\t\tmanipulatorsList.slice(1).forEach((m) => {\n\t\t\tdAttribute += `M${anchor[0]} ${1 - anchor[1]} C${handle[0]} ${1 - handle[1]}, ${m.handles[0][0]} ${1 - m.handles[0][1]}, ${m.anchor[0]} ${1 - m.anchor[1]} `;\n\t\t\tanchor = m.anchor;\n\t\t\thandle = m.handles[1];\n\t\t});\n\n\t\treturn dAttribute;\n\t}\n\n\tfunction handleManipulatorPointerDown(e: PointerEvent, i: number) {\n\t\t// Delete an anchor with right click or middle click\n\t\tif (e.button > 0 && i > 0 && i < manipulatorsList.length - 1) {\n\t\t\tdraggedNodeIndex = undefined;\n\t\t\tselectedNodeIndex = undefined;\n\n\t\t\tmanipulatorsList.splice(i, 1);\n\t\t\tmanipulatorsList = manipulatorsList;\n\n\t\t\tdAttribute = recalculateSvgPath();\n\n\t\t\tupdateCurve();\n\n\t\t\treturn;\n\t\t}\n\n\t\tdraggedNodeIndex = i;\n\t\tif (i >= 0) selectedNodeIndex = i;\n\t}\n\n\tfunction getSvgPositionFromPointerEvent(e: PointerEvent): [number, number] | undefined {\n\t\tif (!(e.target instanceof SVGElement)) return undefined;\n\n\t\tconst target = e.target?.closest(\"svg\") || undefined;\n\t\tif (!target) return undefined;\n\n\t\tconst rect = target.getBoundingClientRect();\n\t\tconst x = (e.x - rect.x) / rect.width;\n\t\tconst y = 1 - (e.y - rect.y) / rect.height;\n\t\treturn [clamp(x), clamp(y)];\n\t}\n\n\tfunction clampHandles() {\n\t\tfor (let i = 0; i < manipulatorsList.length - 1; i++) {\n\t\t\tconst [min, max] = [manipulatorsList[i].anchor[0], manipulatorsList[i + 1].anchor[0]];\n\n\t\t\tfor (let j = 0; j < 2; j++) {\n\t\t\t\tmanipulatorsList[i + j].handles[1 - j][0] = clamp(manipulatorsList[i + j].handles[1 - j][0], min, max);\n\t\t\t\tmanipulatorsList[i + j].handles[1 - j][1] = clamp(manipulatorsList[i + j].handles[1 - j][1]);\n\t\t\t}\n\t\t}\n\t}\n\n\tfunction handlePointerUp(e: PointerEvent) {\n\t\tif (draggedNodeIndex !== undefined) {\n\t\t\tdraggedNodeIndex = undefined;\n\t\t\treturn;\n\t\t}\n\t\tif (e.button !== 0) return;\n\t\tconst anchor = getSvgPositionFromPointerEvent(e);\n\t\tif (!anchor) return;\n\n\t\tlet nodeIndex = manipulatorsList.findIndex((manipulators) => manipulators.anchor[0] > anchor[0]);\n\t\tif (nodeIndex === -1) nodeIndex = manipulatorsList.length;\n\n\t\tmanipulatorsList.splice(nodeIndex, 0, {\n\t\t\tanchor: anchor,\n\t\t\thandles: [\n\t\t\t\t[anchor[0] - 0.05, anchor[1]],\n\t\t\t\t[anchor[0] + 0.05, anchor[1]],\n\t\t\t],\n\t\t});\n\t\tselectedNodeIndex = nodeIndex;\n\t\tclampHandles();\n\t\tdAttribute = recalculateSvgPath();\n\t\tupdateCurve();\n\t}\n\n\tfunction setHandlePosition(anchorIndex: number, handleIndex: number, position: [number, number]) {\n\t\tconst { anchor, handles } = manipulatorsList[anchorIndex];\n\t\tconst otherHandle = handles[1 - handleIndex];\n\n\t\tconst handleVector = [anchor[0] - position[0], anchor[1] - position[1]];\n\t\tconst handleVectorLength = Math.hypot(...handleVector);\n\t\tconst handleVectorNormalized = [handleVector[0] / handleVectorLength, handleVector[1] / handleVectorLength];\n\t\tconst otherHandleVectorLength = Math.hypot(anchor[0] - otherHandle[0], anchor[1] - otherHandle[1]);\n\n\t\thandles[handleIndex] = position;\n\t\thandles[1 - handleIndex] = [anchor[0] + handleVectorNormalized[0] * otherHandleVectorLength, anchor[1] + handleVectorNormalized[1] * otherHandleVectorLength];\n\t}\n\n\tfunction handlePointerMove(e: PointerEvent) {\n\t\tif (draggedNodeIndex === undefined || draggedNodeIndex === 0 || draggedNodeIndex === manipulatorsList.length - 1) return;\n\t\tconst position = getSvgPositionFromPointerEvent(e);\n\t\tif (!position) return;\n\n\t\tif (draggedNodeIndex > 0) {\n\t\t\tposition[0] = clamp(position[0], manipulatorsList[draggedNodeIndex - 1].anchor[0], manipulatorsList[draggedNodeIndex + 1].anchor[0]);\n\n\t\t\tconst manipulators = manipulatorsList[draggedNodeIndex];\n\t\t\tmanipulators.handles = [\n\t\t\t\t[manipulators.handles[0][0] + position[0] - manipulators.anchor[0], manipulators.handles[0][1] + position[1] - manipulators.anchor[1]],\n\t\t\t\t[manipulators.handles[1][0] + position[0] - manipulators.anchor[0], manipulators.handles[1][1] + position[1] - manipulators.anchor[1]],\n\t\t\t];\n\t\t\tmanipulators.anchor = position;\n\t\t} else {\n\t\t\tif (selectedNodeIndex === undefined) return;\n\t\t\tsetHandlePosition(selectedNodeIndex, -draggedNodeIndex - 1, position);\n\n\t\t\tconst manipulators = manipulatorsList[selectedNodeIndex];\n\t\t\tif (manipulators.handles[0][0] > manipulators.anchor[0]) {\n\t\t\t\tmanipulators.handles = [manipulators.handles[1], manipulators.handles[0]];\n\t\t\t\tdraggedNodeIndex = -3 - draggedNodeIndex;\n\t\t\t}\n\t\t}\n\n\t\tclampHandles();\n\t\tdAttribute = recalculateSvgPath();\n\t\tupdateCurve();\n\t}\n\n\tfunction clamp(value: number, min = 0, max = 1): number {\n\t\treturn Math.max(min, Math.min(value, max));\n\t}\n</script>\n\n<LayoutRow class=\"curve-input\" {tooltipLabel} {tooltipDescription} {tooltipShortcut}>\n\t<svg viewBox=\"0 0 1 1\" on:pointermove={handlePointerMove} on:pointerup={handlePointerUp}>\n\t\t{#each { length: GRID_SIZE - 1 } as _, i}\n\t\t\t<path class=\"grid\" d={`M 0 ${(i + 1) / GRID_SIZE} L 1 ${(i + 1) / GRID_SIZE}`} />\n\t\t\t<path class=\"grid\" d={`M ${(i + 1) / GRID_SIZE} 0 L ${(i + 1) / GRID_SIZE} 1`} />\n\t\t{/each}\n\t\t<path class=\"curve\" d={dAttribute} />\n\t\t{#if selectedNodeIndex !== undefined}\n\t\t\t{@const m = manipulatorsList[selectedNodeIndex]}\n\t\t\t{#each [0, 1] as i}\n\t\t\t\t<path d={`M ${m.anchor[0]} ${1 - m.anchor[1]} L ${m.handles[i][0]} ${1 - m.handles[i][1]}`} class=\"handle-line\" />\n\t\t\t\t<circle cx={m.handles[i][0]} cy={1 - m.handles[i][1]} class=\"manipulator handle\" r=\"0.02\" on:pointerdown={(e) => handleManipulatorPointerDown(e, -i - 1)} />\n\t\t\t{/each}\n\t\t{/if}\n\t\t{#each manipulatorsList as manipulators, i}\n\t\t\t<circle cx={manipulators.anchor[0]} cy={1 - manipulators.anchor[1]} class=\"manipulator\" r=\"0.02\" on:pointerdown={(e) => handleManipulatorPointerDown(e, i)} />\n\t\t{/each}\n\t</svg>\n\t<slot />\n</LayoutRow>\n\n<style lang=\"scss\" global>\n\t.curve-input {\n\t\tbackground: var(--color-1-nearblack);\n\t\tdisplay: flex;\n\t\tposition: relative;\n\t\tmin-width: calc(2 * var(--widget-height));\n\t\tmax-width: calc(8 * var(--widget-height));\n\n\t\t.grid {\n\t\t\tstroke: var(--color-5-dullgray);\n\t\t\tstroke-width: 0.005;\n\t\t\tpointer-events: none;\n\t\t}\n\n\t\t.curve {\n\t\t\tfill: none;\n\t\t\tstroke: var(--color-e-nearwhite);\n\t\t\tstroke-width: 0.01;\n\t\t}\n\n\t\t.manipulator {\n\t\t\tfill: var(--color-1-nearblack);\n\t\t\tstroke: var(--color-e-nearwhite);\n\t\t\tstroke-width: 0.01;\n\n\t\t\t&:hover {\n\t\t\t\tfill: var(--color-f-white);\n\t\t\t\tstroke: var(--color-f-white);\n\t\t\t}\n\n\t\t\t&.handle {\n\t\t\t\tfill: var(--color-1-nearblack);\n\t\t\t\tstroke: var(--color-c-brightgray);\n\n\t\t\t\t&:hover {\n\t\t\t\t\tfill: var(--color-a-softgray);\n\t\t\t\t\tstroke: var(--color-a-softgray);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t.handle-line {\n\t\t\tstroke: var(--color-5-dullgray);\n\t\t\tstroke-width: 0.005;\n\t\t\tpointer-events: none;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/widgets/inputs/DropdownInput.svelte",
    "content": "<script lang=\"ts\">\n\timport { createEventDispatcher } from \"svelte\";\n\n\timport type { MenuListEntry, ActionShortcut } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\n\timport MenuList from \"@graphite/components/floating-menus/MenuList.svelte\";\n\timport LayoutRow from \"@graphite/components/layout/LayoutRow.svelte\";\n\timport IconLabel from \"@graphite/components/widgets/labels/IconLabel.svelte\";\n\timport TextLabel from \"@graphite/components/widgets/labels/TextLabel.svelte\";\n\n\tconst DASH_ENTRY: MenuListEntry = {\n\t\tvalue: \"\",\n\t\tlabel: \"-\",\n\t\ticon: undefined,\n\t\tdisabled: false,\n\t\tchildren: [],\n\t\tchildrenHash: 0n,\n\t\tfont: undefined,\n\t\ttooltipLabel: \"\",\n\t\ttooltipDescription: \"\",\n\t\ttooltipShortcut: undefined,\n\t};\n\n\tconst dispatch = createEventDispatcher<{ selectedIndex: number; hoverInEntry: number; hoverOutEntry: number }>();\n\n\tlet self: LayoutRow | undefined;\n\n\t// Content\n\texport let selectedIndex: number | undefined = undefined; // When not provided, a dash is displayed\n\texport let drawIcon = false;\n\texport let disabled = false;\n\t// Children\n\texport let entries: MenuListEntry[][];\n\texport let entriesHash: bigint | undefined = undefined;\n\t// Styling\n\texport let narrow = false;\n\t// Behavior\n\texport let virtualScrolling = false;\n\texport let interactive = true;\n\t// Sizing\n\texport let minWidth = 0;\n\texport let maxWidth = 0;\n\t// Tooltips\n\texport let tooltipLabel: string | undefined = undefined;\n\texport let tooltipDescription: string | undefined = undefined;\n\texport let tooltipShortcut: ActionShortcut | undefined = undefined;\n\n\tlet activeEntry = makeActiveEntry();\n\tlet activeEntrySkipWatcher = false;\n\tlet initialSelectedIndex: number | undefined = undefined;\n\tlet open = false;\n\n\t$: watchSelectedIndex(selectedIndex);\n\t$: watchEntries(entries);\n\t$: watchActiveEntry(activeEntry);\n\t$: watchOpen(open);\n\n\tfunction watchOpen(open: boolean) {\n\t\tinitialSelectedIndex = open ? selectedIndex : undefined;\n\t}\n\n\t// Called only when `selectedIndex` is changed from outside this component\n\tfunction watchSelectedIndex(_: typeof selectedIndex) {\n\t\tactiveEntrySkipWatcher = true;\n\t\tactiveEntry = makeActiveEntry();\n\t}\n\n\t// Called only when `entries` is changed from outside this component\n\tfunction watchEntries(_: typeof entries) {\n\t\tactiveEntrySkipWatcher = true;\n\t\tactiveEntry = makeActiveEntry();\n\t}\n\n\t// Called when the `activeEntry` two-way binding on this component's MenuList component is changed, or by the `watchSelectedIndex()` watcher above (but we want to skip that case)\n\tfunction watchActiveEntry(activeEntry: MenuListEntry) {\n\t\tif (activeEntrySkipWatcher) {\n\t\t\tactiveEntrySkipWatcher = false;\n\t\t} else if (activeEntry !== DASH_ENTRY) {\n\t\t\t// We need to set to the initial value first to track a right history step, as if we hover in initial selection.\n\t\t\tif (initialSelectedIndex !== undefined) dispatch(\"hoverInEntry\", initialSelectedIndex);\n\t\t\tconst index = entries.flat().findIndex((entry) => entry.value === activeEntry.value);\n\t\t\tif (index !== -1) {\n\t\t\t\tdispatch(\"selectedIndex\", index);\n\t\t\t} else {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error(\"Selected index not found in entries:\", activeEntry);\n\t\t\t}\n\t\t}\n\t}\n\n\tfunction dispatchHoverInEntry(hoveredEntry: MenuListEntry) {\n\t\tconst index = entries.flat().findIndex((entry) => entry.value === hoveredEntry.value);\n\n\t\tif (index !== -1) {\n\t\t\tdispatch(\"hoverInEntry\", index);\n\t\t} else {\n\t\t\t// eslint-disable-next-line no-console\n\t\t\tconsole.error(\"Hovered entry not found in entries:\", hoveredEntry);\n\t\t}\n\t}\n\n\tfunction dispatchHoverOutEntry() {\n\t\tif (initialSelectedIndex !== undefined) dispatch(\"hoverOutEntry\", initialSelectedIndex);\n\t}\n\n\tfunction makeActiveEntry(): MenuListEntry {\n\t\tconst allEntries = entries.flat();\n\n\t\tif (selectedIndex !== undefined && selectedIndex >= 0 && selectedIndex < allEntries.length) {\n\t\t\treturn allEntries[selectedIndex];\n\t\t}\n\t\treturn DASH_ENTRY;\n\t}\n\n\tfunction unFocusDropdownBox(e: FocusEvent) {\n\t\tconst blurTarget = (e.target instanceof Element ? e.target.closest(\"[data-dropdown-input]\") : undefined) || undefined;\n\t\tif (blurTarget !== self?.div?.()) open = false;\n\t}\n</script>\n\n<LayoutRow\n\tclass=\"dropdown-input\"\n\tclasses={{ narrow }}\n\tstyles={{\n\t\t...(minWidth > 0 ? { \"min-width\": `${minWidth}px` } : {}),\n\t\t...(maxWidth > 0 ? { \"max-width\": `${maxWidth}px` } : {}),\n\t}}\n\tbind:this={self}\n\tdata-dropdown-input\n>\n\t<LayoutRow\n\t\tclass=\"dropdown-box\"\n\t\tclasses={{ disabled, open }}\n\t\t{tooltipLabel}\n\t\t{tooltipDescription}\n\t\t{tooltipShortcut}\n\t\ton:click={() => !disabled && (open = true)}\n\t\ton:blur={unFocusDropdownBox}\n\t\ttabindex={disabled ? -1 : 0}\n\t\tdata-floating-menu-spawner\n\t>\n\t\t{#if activeEntry.icon}\n\t\t\t<IconLabel class=\"dropdown-icon\" icon={activeEntry.icon} />\n\t\t{/if}\n\t\t<TextLabel class=\"dropdown-label\">{activeEntry.label}</TextLabel>\n\t\t<IconLabel class=\"dropdown-arrow\" icon=\"DropdownArrow\" />\n\t</LayoutRow>\n\t<MenuList\n\t\ton:naturalWidth={({ detail }) => (minWidth = detail)}\n\t\ton:activeEntry={({ detail }) => (activeEntry = detail)}\n\t\ton:hoverInEntry={({ detail }) => dispatchHoverInEntry(detail)}\n\t\ton:hoverOutEntry={() => dispatchHoverOutEntry()}\n\t\ton:open={({ detail }) => (open = detail)}\n\t\t{open}\n\t\t{activeEntry}\n\t\t{entries}\n\t\tentriesHash={entriesHash || 0n}\n\t\t{drawIcon}\n\t\t{interactive}\n\t\t{virtualScrolling}\n\t\tdirection=\"Bottom\"\n\t\tscrollableY={true}\n\t/>\n</LayoutRow>\n\n<style lang=\"scss\" global>\n\t.dropdown-input {\n\t\tposition: relative;\n\t\t--widget-height: 24px;\n\n\t\t&.narrow.narrow {\n\t\t\t--widget-height: 20px;\n\t\t}\n\n\t\t.dropdown-box {\n\t\t\talign-items: center;\n\t\t\twhite-space: nowrap;\n\t\t\tborder-radius: 2px;\n\t\t\tbackground: var(--color-1-nearblack);\n\t\t\theight: var(--widget-height);\n\n\t\t\t.dropdown-label {\n\t\t\t\tmargin: 0;\n\t\t\t\tmargin-left: 8px;\n\t\t\t\tflex: 1 1 100%;\n\t\t\t}\n\n\t\t\t.dropdown-icon {\n\t\t\t\tmargin: 4px 8px;\n\t\t\t\tflex: 0 0 auto;\n\n\t\t\t\t& + .dropdown-label {\n\t\t\t\t\tmargin-left: 0;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.dropdown-arrow {\n\t\t\t\tmargin: 4px;\n\t\t\t\tmargin-right: 2px;\n\t\t\t\tflex: 0 0 auto;\n\t\t\t}\n\n\t\t\t&:hover,\n\t\t\t&.open {\n\t\t\t\tbackground: var(--color-4-dimgray);\n\t\t\t}\n\n\t\t\t&.disabled {\n\t\t\t\tbackground: var(--color-2-mildblack);\n\n\t\t\t\t.text-label {\n\t\t\t\t\tcolor: var(--color-8-uppergray);\n\t\t\t\t}\n\n\t\t\t\tsvg {\n\t\t\t\t\tfill: var(--color-8-uppergray);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t.menu-list .floating-menu-container .floating-menu-content {\n\t\t\tmax-height: 400px;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/widgets/inputs/FieldInput.svelte",
    "content": "<script lang=\"ts\">\n\timport { createEventDispatcher } from \"svelte\";\n\n\timport type { ActionShortcut } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\timport { operatingSystem } from \"@graphite/utility-functions/platform\";\n\n\timport { preventEscapeClosingParentFloatingMenu } from \"@graphite/components/layout/FloatingMenu.svelte\";\n\timport LayoutRow from \"@graphite/components/layout/LayoutRow.svelte\";\n\n\tconst dispatch = createEventDispatcher<{\n\t\tvalue: string;\n\t\ttextFocused: undefined;\n\t\ttextChanged: undefined;\n\t\ttextChangeCanceled: undefined;\n\t}>();\n\n\tlet className = \"\";\n\texport { className as class };\n\texport let classes: Record<string, boolean> = {};\n\tlet styleName = \"\";\n\texport { styleName as style };\n\texport let styles: Record<string, string | number | undefined> = {};\n\texport let value: string;\n\texport let label: string | undefined = undefined;\n\texport let spellcheck = false;\n\texport let disabled = false;\n\texport let narrow = false;\n\texport let textarea = false;\n\texport let tooltipLabel: string | undefined = undefined;\n\texport let tooltipDescription: string | undefined = undefined;\n\texport let tooltipShortcut: ActionShortcut | undefined = undefined;\n\texport let placeholder: string | undefined = undefined;\n\texport let hideContextMenu = false;\n\n\tlet inputOrTextarea: HTMLInputElement | HTMLTextAreaElement | undefined;\n\tlet id = String(Math.random()).substring(2);\n\tlet macKeyboardLayout = operatingSystem() === \"Mac\";\n\n\t$: dispatch(\"value\", value);\n\n\t// Select (highlight) all the text. For technical reasons, it is necessary to pass the current text.\n\texport function selectAllText(currentText: string) {\n\t\tif (!inputOrTextarea) return;\n\n\t\t// Setting the value directly is required to make the following `select()` call work\n\t\tinputOrTextarea.value = currentText;\n\t\tinputOrTextarea.select();\n\t}\n\n\texport function focus() {\n\t\tinputOrTextarea?.focus();\n\t}\n\n\texport function unFocus() {\n\t\tinputOrTextarea?.blur();\n\t}\n\n\texport function getValue(): string {\n\t\treturn inputOrTextarea?.value || \"\";\n\t}\n\n\texport function setInputElementValue(value: string) {\n\t\tif (!inputOrTextarea) return;\n\n\t\tinputOrTextarea.value = value;\n\t}\n\n\texport function element(): HTMLInputElement | HTMLTextAreaElement | undefined {\n\t\treturn inputOrTextarea;\n\t}\n\n\tfunction cancel() {\n\t\tdispatch(\"textChangeCanceled\");\n\n\t\tif (inputOrTextarea) preventEscapeClosingParentFloatingMenu(inputOrTextarea);\n\t}\n</script>\n\n<!-- This is a base component, extended by others like NumberInput and TextInput. It should not be used directly. -->\n<LayoutRow class={`field-input ${className}`} classes={{ disabled, narrow, ...classes }} style={styleName} {styles} {tooltipLabel} {tooltipDescription} {tooltipShortcut}>\n\t{#if !textarea}\n\t\t<input\n\t\t\ttype=\"text\"\n\t\t\tclass:has-label={label}\n\t\t\tid={`field-input-${id}`}\n\t\t\t{spellcheck}\n\t\t\t{disabled}\n\t\t\t{placeholder}\n\t\t\tbind:this={inputOrTextarea}\n\t\t\tbind:value\n\t\t\ton:focus={() => dispatch(\"textFocused\")}\n\t\t\ton:blur={() => dispatch(\"textChanged\")}\n\t\t\ton:change={() => dispatch(\"textChanged\")}\n\t\t\ton:keydown={(e) => e.key === \"Enter\" && dispatch(\"textChanged\")}\n\t\t\ton:keydown={(e) => e.key === \"Escape\" && cancel()}\n\t\t\ton:pointerdown\n\t\t\ton:contextmenu={(e) => hideContextMenu && e.preventDefault()}\n\t\t\tdata-input-element\n\t\t/>\n\t{:else}\n\t\t<textarea\n\t\t\tclass:has-label={label}\n\t\t\tid={`field-input-${id}`}\n\t\t\tclass=\"scrollable-y\"\n\t\t\tdata-scrollable-y\n\t\t\t{spellcheck}\n\t\t\t{disabled}\n\t\t\tbind:this={inputOrTextarea}\n\t\t\tbind:value\n\t\t\ton:focus={() => dispatch(\"textFocused\")}\n\t\t\ton:blur={() => dispatch(\"textChanged\")}\n\t\t\ton:change={() => dispatch(\"textChanged\")}\n\t\t\ton:keydown={(e) => (macKeyboardLayout ? e.metaKey : e.ctrlKey) && e.key === \"Enter\" && dispatch(\"textChanged\")}\n\t\t\ton:keydown={(e) => e.key === \"Escape\" && cancel()}\n\t\t\ton:pointerdown\n\t\t\ton:contextmenu={(e) => hideContextMenu && e.preventDefault()}\n\t\t></textarea>\n\t{/if}\n\t{#if label}\n\t\t<label for={`field-input-${id}`} on:pointerdown>{label}</label>\n\t{/if}\n\t<slot />\n</LayoutRow>\n\n<style lang=\"scss\" global>\n\t.field-input {\n\t\tmin-width: 80px;\n\t\theight: auto;\n\t\tposition: relative;\n\t\tborder-radius: 2px;\n\t\tbackground: var(--color-1-nearblack);\n\t\tflex-direction: row-reverse;\n\n\t\t&.narrow.narrow {\n\t\t\t--widget-height: 20px;\n\t\t}\n\n\t\tlabel {\n\t\t\tflex: 0 0 auto;\n\t\t\tline-height: calc(var(--widget-height) - 6px);\n\t\t\tpadding: 3px 0;\n\t\t\tpadding-right: 4px;\n\t\t\tmargin-left: 8px;\n\t\t\toverflow: hidden;\n\t\t\ttext-overflow: ellipsis;\n\t\t\twhite-space: nowrap;\n\t\t}\n\n\t\t&:not(.disabled) label {\n\t\t\tcursor: text;\n\t\t}\n\n\t\tinput,\n\t\ttextarea {\n\t\t\tflex: 1 1 100%;\n\t\t\twidth: 0;\n\t\t\tmin-width: 30px;\n\t\t\theight: calc(var(--widget-height) - 6px);\n\t\t\tline-height: calc(var(--widget-height) - 6px);\n\t\t\tmargin: 0 8px;\n\t\t\tpadding: 3px 0;\n\t\t\toutline: none; // Ok for input/textarea element\n\t\t\tborder: none;\n\t\t\tbackground: none;\n\t\t\tcolor: var(--color-e-nearwhite);\n\t\t\tcaret-color: var(--color-e-nearwhite);\n\t\t\tunicode-bidi: plaintext;\n\n\t\t\t&::selection {\n\t\t\t\tbackground-color: var(--color-4-dimgray);\n\n\t\t\t\t// Target only Safari\n\t\t\t\t@supports (background: -webkit-named-image(i)) {\n\t\t\t\t\t& {\n\t\t\t\t\t\t// Setting an alpha value opts out of Safari's \"fancy\" (but not visible on dark backgrounds) selection highlight rendering\n\t\t\t\t\t\t// https://stackoverflow.com/a/71753552/775283\n\t\t\t\t\t\tbackground-color: rgba(var(--color-4-dimgray-rgb), calc(254 / 255));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tinput {\n\t\t\t&:not(:focus).has-label {\n\t\t\t\ttext-align: right;\n\t\t\t\tmargin-left: 0;\n\t\t\t\tmargin-right: 8px;\n\t\t\t}\n\n\t\t\t&:focus {\n\t\t\t\ttext-align: left;\n\n\t\t\t\t& + label {\n\t\t\t\t\tdisplay: none;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\ttextarea {\n\t\t\tmin-height: calc((var(--widget-height) - 6px) * 3);\n\t\t\tmargin: 3px;\n\t\t\tpadding: 0 5px;\n\t\t\tbox-sizing: border-box;\n\t\t\tresize: vertical;\n\t\t}\n\n\t\t&.disabled {\n\t\t\tbackground: var(--color-2-mildblack);\n\n\t\t\tlabel,\n\t\t\tinput,\n\t\t\ttextarea {\n\t\t\t\tcolor: var(--color-8-uppergray);\n\t\t\t}\n\n\t\t\tinput {\n\t\t\t\t// Disables drag-selecting the text, since `user-select: none` doesn't work for input elements\n\t\t\t\tpointer-events: none;\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/widgets/inputs/NumberInput.svelte",
    "content": "<script lang=\"ts\">\n\timport { createEventDispatcher, onMount, onDestroy, getContext } from \"svelte\";\n\n\timport { evaluateMathExpression, isPlatformNative } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\timport type { NumberInputMode, NumberInputIncrementBehavior, ActionShortcut } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\timport type { Editor } from \"@graphite/editor\";\n\timport { PRESS_REPEAT_DELAY_MS, PRESS_REPEAT_INTERVAL_MS } from \"@graphite/managers/input\";\n\timport { browserVersion } from \"@graphite/utility-functions/platform\";\n\n\timport { preventEscapeClosingParentFloatingMenu } from \"@graphite/components/layout/FloatingMenu.svelte\";\n\timport FieldInput from \"@graphite/components/widgets/inputs/FieldInput.svelte\";\n\n\tconst BUTTONS_LEFT = 0b0000_0001;\n\tconst BUTTONS_RIGHT = 0b0000_0010;\n\tconst BUTTON_LEFT = 0;\n\tconst BUTTON_RIGHT = 2;\n\n\tconst dispatch = createEventDispatcher<{ value: number | undefined; startHistoryTransaction: undefined }>();\n\n\tconst editor = getContext<Editor>(\"editor\");\n\n\t// Content\n\t/// When `value` is not provided (i.e. it's `undefined`), a dash is displayed.\n\texport let value: number | undefined = undefined;\n\texport let label: string | undefined = undefined;\n\texport let disabled = false;\n\t// Styling\n\texport let narrow = false;\n\t// Behavior\n\t/// Mode behavior\n\t/// \"Increment\" shows arrows and allows dragging left/right to change the value.\n\t/// \"Range\" shows a range slider between some minimum and maximum value.\n\texport let mode: NumberInputMode = \"Increment\";\n\texport let min: number | undefined = undefined;\n\texport let max: number | undefined = undefined;\n\t/// `rangeMin` and `rangeMax` are only applicable with a `mode` of \"Range\".\n\t/// They set the lower and upper values of the slider to drag between.\n\texport let rangeMin = 0;\n\texport let rangeMax = 1;\n\t/// When `mode` is \"Increment\", `step` is the multiplier or addend used with `incrementBehavior`.\n\t/// When `mode` is \"Range\", `step` is the range slider's snapping increment if `isInteger` is `true`.\n\texport let step = 1;\n\texport let isInteger = false;\n\t/// `incrementBehavior` is only applicable with a `mode` of \"Increment\".\n\t/// \"Add\"/\"Multiply\": The value is added or multiplied by `step`.\n\t/// \"Callback\": the functions `incrementCallbackIncrease` and `incrementCallbackDecrease` call custom behavior.\n\t/// \"None\": the increment arrows are not shown.\n\texport let incrementBehavior: NumberInputIncrementBehavior = \"Add\";\n\texport let displayDecimalPlaces = 2;\n\texport let unit = \"\";\n\texport let unitIsHiddenWhenEditing = true;\n\n\t// Sizing\n\texport let minWidth = 0;\n\texport let maxWidth = 0;\n\t// Tooltips\n\texport let tooltipLabel: string | undefined = undefined;\n\texport let tooltipDescription: string | undefined = undefined;\n\texport let tooltipShortcut: ActionShortcut | undefined = undefined;\n\t// Callbacks\n\texport let incrementCallbackIncrease: (() => void) | undefined = undefined;\n\texport let incrementCallbackDecrease: (() => void) | undefined = undefined;\n\n\tlet self: FieldInput | undefined;\n\tlet inputRangeElement: HTMLInputElement | undefined;\n\tlet text = displayText(value, unit);\n\tlet editing = false;\n\tlet isDragging = false;\n\tlet pressingArrow = false;\n\tlet repeatTimeout: ReturnType<typeof setTimeout> | undefined = undefined;\n\t// Stays in sync with a binding to the actual input range slider element.\n\tlet rangeSliderValue = value !== undefined ? value : 0;\n\t// Value used to render the position of the fake slider when applicable, and length of the progress colored region to the slider's left.\n\t// This is the same as `rangeSliderValue` except in the \"Deciding\" state, when it has the previous location before the user's mousedown.\n\tlet rangeSliderValueAsRendered = value !== undefined ? value : 0;\n\t// Keeps track of the state of the slider drag as the user transitions through steps of the input process.\n\t// - \"Ready\": no interaction is happening.\n\t// - \"Deciding\": the user has pressed down the mouse and might next decide to either drag left/right or release without dragging.\n\t// - \"Dragging\": the user is dragging the slider left/right.\n\t// - \"Aborted\": the user has right clicked or pressed Escape to abort the drag, but hasn't yet released all mouse buttons.\n\tlet rangeSliderClickDragState: \"Ready\" | \"Deciding\" | \"Dragging\" | \"Aborted\" = \"Ready\";\n\t// Stores the initial value upon beginning to drag so it can be restored upon aborting. Set to `undefined` when not dragging.\n\tlet initialValueBeforeDragging: number | undefined = undefined;\n\t// Stores the total value change during the process of dragging the slider. Set to 0 when not dragging.\n\tlet cumulativeDragDelta = 0;\n\t// Track whether the Shift key is currently held down.\n\tlet shiftKeyDown = false;\n\t// Track whether the Ctrl key is currently held down.\n\tlet ctrlKeyDown = false;\n\t// Cleanup function for active drag interactions, called on destroy to prevent leaked listeners\n\tlet activeDragCleanup: (() => void) | undefined;\n\t// Track the slider abort state for cleanup on destroy\n\tlet sliderResetAbortHandler: (() => void) | undefined;\n\tlet sliderAbortTimeout1: ReturnType<typeof setTimeout> | undefined;\n\tlet sliderAbortTimeout2: ReturnType<typeof setTimeout> | undefined;\n\n\t$: watchValue(value, unit);\n\t$: sliderStepValue = isInteger ? (step === undefined ? 1 : step) : \"any\";\n\t$: styles = {\n\t\t...(minWidth > 0 ? { \"min-width\": `${minWidth}px` } : {}),\n\t\t...(maxWidth > 0 ? { \"max-width\": `${maxWidth}px` } : {}),\n\t\t...(mode === \"Range\" ? { \"--progress-factor\": Math.min(Math.max((rangeSliderValueAsRendered - rangeMin) / (rangeMax - rangeMin), 0), 1) } : {}),\n\t};\n\n\t// Keep track of the Shift and Ctrl key being held down.\n\tconst trackShiftAndCtrl = (e: KeyboardEvent | MouseEvent) => {\n\t\tshiftKeyDown = e.shiftKey;\n\t\tctrlKeyDown = e.ctrlKey;\n\t};\n\tonMount(() => {\n\t\taddEventListener(\"keydown\", trackShiftAndCtrl);\n\t\taddEventListener(\"keyup\", trackShiftAndCtrl);\n\t\taddEventListener(\"mousemove\", trackShiftAndCtrl);\n\t});\n\tonDestroy(() => {\n\t\tclearTimeout(repeatTimeout);\n\t\tclearTimeout(sliderAbortTimeout1);\n\t\tclearTimeout(sliderAbortTimeout2);\n\n\t\tactiveDragCleanup?.();\n\n\t\t// Exit pointer lock if active (non-Safari path)\n\t\tif (document.pointerLockElement) document.exitPointerLock();\n\n\t\t// Remove Safari cursor-hidden workaround class if present\n\t\tconst isSafari = browserVersion().toLowerCase().includes(\"safari\");\n\t\tif (isSafari) document.body.classList.remove(\"cursor-hidden\");\n\n\t\t// Clean up any listeners related to tracking the Shift and Ctrl keys\n\t\tremoveEventListener(\"keydown\", trackShiftAndCtrl);\n\t\tremoveEventListener(\"keyup\", trackShiftAndCtrl);\n\t\tremoveEventListener(\"mousemove\", trackShiftAndCtrl);\n\n\t\t// Clean up any slider-related listeners that may be active\n\t\tremoveEventListener(\"mousedown\", sliderAbortFromMousedown);\n\t\tremoveEventListener(\"keydown\", sliderAbortFromMousedown);\n\t\tremoveEventListener(\"pointermove\", sliderAbortFromDragging);\n\t\tremoveEventListener(\"keydown\", sliderAbortFromDragging);\n\t\tremoveEventListener(\"keydown\", incrementPressAbort);\n\t\tif (sliderResetAbortHandler) removeEventListener(\"pointerup\", sliderResetAbortHandler);\n\t});\n\n\t// ===============================\n\t// TRACKING AND UPDATING THE VALUE\n\t// ===============================\n\n\t// Called only when `value` is changed from outside this component.\n\tfunction watchValue(value: number | undefined, unit: string) {\n\t\t// Don't update if the slider is currently being dragged (we don't want the backend fighting with the user's drag)\n\t\tif (rangeSliderClickDragState === \"Dragging\") return;\n\n\t\t// Draw a dash if the value is undefined\n\t\tif (value === undefined) {\n\t\t\ttext = \"-\";\n\t\t\treturn;\n\t\t}\n\n\t\t// Update the range slider with the new value\n\t\trangeSliderValue = value;\n\t\trangeSliderValueAsRendered = value;\n\n\t\t// The simple `clamp()` function can't be used here since `undefined` values need to be boundless\n\t\tlet sanitized = value;\n\t\tif (typeof min === \"number\") sanitized = Math.max(sanitized, min);\n\t\tif (typeof max === \"number\") sanitized = Math.min(sanitized, max);\n\n\t\ttext = displayText(sanitized, unit);\n\t}\n\n\t// Called internally to update the value indirectly by informing the parent component of the new value,\n\t// so it can update the prop for this component, finally yielding the value change.\n\tfunction updateValue(newValue: number | undefined): number | undefined {\n\t\t// Check if the new value is valid, otherwise we use the old value (rounded if it's an integer)\n\t\tconst oldValue = value !== undefined && isInteger ? Math.round(value) : value;\n\t\tlet newValueValidated = newValue !== undefined ? newValue : oldValue;\n\n\t\tif (newValueValidated !== undefined) {\n\t\t\tif (typeof min === \"number\" && !Number.isNaN(min)) newValueValidated = Math.max(newValueValidated, min);\n\t\t\tif (typeof max === \"number\" && !Number.isNaN(max)) newValueValidated = Math.min(newValueValidated, max);\n\n\t\t\tif (isInteger) newValueValidated = Math.round(newValueValidated);\n\n\t\t\trangeSliderValue = newValueValidated;\n\t\t\trangeSliderValueAsRendered = newValueValidated;\n\t\t}\n\n\t\ttext = displayText(newValueValidated, unit);\n\n\t\tif (newValue !== undefined) dispatch(\"value\", newValueValidated);\n\n\t\t// For any caller that needs to know what the value was changed to, we return it here\n\t\treturn newValueValidated;\n\t}\n\n\t// ================\n\t// HELPER FUNCTIONS\n\t// ================\n\n\t// Calculates the string to display when the field is not being edited.\n\tfunction displayText(displayValue: number | undefined, unit: string): string {\n\t\tif (displayValue === undefined) return \"-\";\n\n\t\tconst roundingPower = 10 ** Math.max(displayDecimalPlaces, 0);\n\n\t\tconst unitlessDisplayValue = Math.round(displayValue * roundingPower) / roundingPower;\n\t\treturn `${unitlessDisplayValue}${unPluralize(unit, displayValue)}`;\n\t}\n\n\t// Removes the trailing \"s\" from a unit if the quantity is 1.\n\tfunction unPluralize(unit: string, quantity: number): string {\n\t\tif (quantity !== 1 || !unit.endsWith(\"s\")) return unit;\n\t\treturn unit.slice(0, -1);\n\t}\n\n\t// ===========================\n\t// ALL MODES: TEXT VALUE ENTRY\n\t// ===========================\n\n\tfunction onTextFocused() {\n\t\t// The degree of precision allowed in the number that's shown when editing the number field, where additional precision is removed to round out floating point errors.\n\t\tconst MAX_PRECISION = 12;\n\t\tconst noFloatingImprecisionValue = value === undefined ? undefined : Number(value.toPrecision(MAX_PRECISION));\n\n\t\tif (value === undefined) text = \"\";\n\t\telse if (unitIsHiddenWhenEditing) text = `${noFloatingImprecisionValue}`;\n\t\telse text = `${noFloatingImprecisionValue}${unPluralize(unit, value)}`;\n\n\t\tediting = true;\n\n\t\tself?.selectAllText(text);\n\t\t// Workaround for weird behavior in Firefox: <https://github.com/GraphiteEditor/Graphite/issues/2215>\n\t\tif (isDragging) self?.unFocus();\n\t}\n\n\t// Called only when `value` is changed from the <input> element via user input and committed, either with the\n\t// enter key (via the `change` event) or when the <input> element is unfocused (with the `blur` event binding).\n\tfunction onTextChanged() {\n\t\t// The `unFocus()` call at the bottom of this function and in `onTextChangeCanceled()` causes this function to be run again, so this check skips a second run.\n\t\tif (!editing) return;\n\n\t\t// Insert a leading zero before all decimal points lacking a preceding digit, since the library doesn't realize that \"point\" means \"zero point\".\n\t\tconst textWithLeadingZeroes = text.replaceAll(/(?<=^|[^0-9])\\./g, \"0.\"); // Match any \".\" that is preceded by the start of the string (^) or a non-digit character ([^0-9])\n\n\t\tlet newValue = evaluateMathExpression(textWithLeadingZeroes);\n\t\tif (newValue !== undefined && isNaN(newValue)) newValue = undefined; // Rejects `sqrt(-1)`\n\n\t\tif (newValue !== undefined) {\n\t\t\tconst oldValue = value !== undefined && isInteger ? Math.round(value) : value;\n\t\t\tif (newValue !== oldValue) dispatch(\"startHistoryTransaction\");\n\t\t}\n\t\tupdateValue(newValue);\n\n\t\tediting = false;\n\t\tself?.unFocus();\n\t}\n\n\tfunction onTextChangeCanceled() {\n\t\tupdateValue(undefined);\n\n\t\tconst valueOrZero = value !== undefined ? value : 0;\n\t\trangeSliderValue = valueOrZero;\n\t\trangeSliderValueAsRendered = valueOrZero;\n\n\t\tediting = false;\n\n\t\tself?.unFocus();\n\t}\n\n\t// =============================\n\t// INCREMENT MODE: ARROW BUTTONS\n\t// =============================\n\n\tfunction onIncrementPointerDown(e: PointerEvent, direction: \"Decrease\" | \"Increase\") {\n\t\tif (value === undefined || e.button !== BUTTON_LEFT) return;\n\n\t\tconst actions: Record<NumberInputIncrementBehavior, () => void> = {\n\t\t\tAdd: () => {\n\t\t\t\tconst directionAddend = direction === \"Increase\" ? step : -step;\n\t\t\t\tconst newValue = value !== undefined ? value + directionAddend : undefined;\n\t\t\t\tupdateValue(newValue);\n\t\t\t},\n\t\t\tMultiply: () => {\n\t\t\t\tconst directionMultiplier = direction === \"Increase\" ? step : 1 / step;\n\t\t\t\tconst newValue = value !== undefined ? value * directionMultiplier : undefined;\n\t\t\t\tupdateValue(newValue);\n\t\t\t},\n\t\t\tCallback: () => {\n\t\t\t\tif (direction === \"Increase\") incrementCallbackIncrease?.();\n\t\t\t\tif (direction === \"Decrease\") incrementCallbackDecrease?.();\n\t\t\t},\n\t\t\tNone: () => {},\n\t\t};\n\n\t\tconst sendAction = () => {\n\t\t\tif (!pressingArrow) return;\n\n\t\t\tactions[incrementBehavior]();\n\n\t\t\tif (afterInitialDelay) repeatTimeout = setTimeout(sendAction, PRESS_REPEAT_INTERVAL_MS);\n\t\t\tafterInitialDelay = true;\n\t\t};\n\n\t\tpressingArrow = true;\n\t\tinitialValueBeforeDragging = value;\n\t\tlet afterInitialDelay = false;\n\t\tsendAction();\n\t\trepeatTimeout = setTimeout(sendAction, PRESS_REPEAT_DELAY_MS);\n\t\taddEventListener(\"keydown\", incrementPressAbort);\n\t}\n\n\tfunction onIncrementPointerUp() {\n\t\tpressingArrow = false;\n\t\tclearTimeout(repeatTimeout);\n\t}\n\n\tfunction incrementPressAbort(e: KeyboardEvent | MouseEvent) {\n\t\t// Only abort if the user right clicks or presses Escape\n\t\tif (e instanceof KeyboardEvent && e.key !== \"Escape\") return;\n\t\tif (e instanceof MouseEvent && e.button !== BUTTON_RIGHT) return;\n\n\t\tconst element = self?.element() || undefined;\n\t\tif (element) preventEscapeClosingParentFloatingMenu(element);\n\n\t\tpressingArrow = false;\n\t\tclearTimeout(repeatTimeout);\n\t\tupdateValue(initialValueBeforeDragging);\n\t\tremoveEventListener(\"keydown\", incrementPressAbort);\n\t}\n\n\t// =======================================\n\t// INCREMENT MODE: DRAGGING LEFT AND RIGHT\n\t// =======================================\n\n\t// TODO: Prevent right clicking the input field from focusing it (i.e. entering its text editing state).\n\t// TODO: `preventDefault()` doesn't work. Relevant StackOverflow question without any working answers:\n\t// TODO: <https://stackoverflow.com/questions/60746390/react-prevent-right-click-from-focusing-an-otherwise-focusable-element>\n\t// TODO: Another potential solution is to somehow track if the user right clicked, then use the \"focus\" event handler to immediately return\n\t// TODO: focus to the previously focused element with `e.relatedTarget.focus();`. But a \"FocusEvent\" doesn't include mouse button click info.\n\t// TODO: Alternatively, we could stick an element in front of the input field that blocks clicks on the underlying input field. Then it could\n\t// TODO: call `.focus()` on the input field when left clicked and then hide itself so it doesn't block the input field while being edited.\n\n\tfunction onDragPointerDown(e: PointerEvent) {\n\t\t// Only drag the number with left click (and when it's valid to do so)\n\t\tif (e.button !== BUTTON_LEFT || mode !== \"Increment\" || value === undefined || disabled || editing) return;\n\n\t\t// Remove the text entry cursor from any other selected text field\n\t\tif (document.activeElement instanceof HTMLElement) document.activeElement.blur();\n\n\t\t// Don't drag the text value from its input element\n\t\te.preventDefault();\n\n\t\t// Now we need to wait and see if the user follows this up with a mousemove or mouseup...\n\n\t\t// For some reason, both events can get fired before their event listeners are removed, so we need to guard against both running.\n\t\tlet alreadyActedGuard = false;\n\n\t\t// If it's a mousemove, we'll enter the dragging state and begin dragging.\n\t\tconst onMove = () => {\n\t\t\tif (alreadyActedGuard) return;\n\t\t\talreadyActedGuard = true;\n\n\t\t\tisDragging = true;\n\n\t\t\tactiveDragCleanup?.();\n\t\t\tbeginDrag(e);\n\t\t};\n\t\t// If it's a mouseup, we'll begin editing the text field.\n\t\tconst onUp = () => {\n\t\t\tif (alreadyActedGuard) return;\n\t\t\talreadyActedGuard = true;\n\n\t\t\tisDragging = false;\n\t\t\tself?.focus();\n\n\t\t\tactiveDragCleanup?.();\n\t\t};\n\t\taddEventListener(\"pointermove\", onMove);\n\t\taddEventListener(\"pointerup\", onUp);\n\t\tactiveDragCleanup = () => {\n\t\t\tremoveEventListener(\"pointermove\", onMove);\n\t\t\tremoveEventListener(\"pointerup\", onUp);\n\t\t\tactiveDragCleanup = undefined;\n\t\t};\n\t}\n\n\tfunction beginDrag(e: PointerEvent) {\n\t\t// Get the click target\n\t\tconst target = e.target || undefined;\n\t\tif (!(target instanceof HTMLElement)) return;\n\n\t\t// Default to using pointer lock except on unsupported platforms (Safari and the native desktop app).\n\t\t// Even though Safari supposedly supports the PointerLock API, it implements an old (2016) version of the spec that requires an \"engagement\n\t\t// gesture\" (<https://www.w3.org/TR/2016/REC-pointerlock-20161027/#glossary>) to initiate pointer lock, which the newer spec doesn't require.\n\t\t// Because \"mousemove\" (and similarly, the \"pointermove\" event we use) is defined as not being a user-initiated \"engagement gesture\" event,\n\t\t// Safari never lets us to enter pointer lock while the mouse button is held down and we are awaiting movement to begin dragging the slider.\n\t\tconst isSafari = browserVersion().toLowerCase().includes(\"safari\");\n\t\tconst usePointerLock = !isSafari && !isPlatformNative();\n\n\t\t// On Safari, we use a workaround involving an alternative strategy where we hide the cursor while it's within the web page\n\t\t// (but we can't hide it when it ventures outside the page), taking advantage of a separate (helpful) Safari bug where it\n\t\t// keeps reporting deltas to the pointer position even as it pushes up against edges of the screen. Like the PointerLock API,\n\t\t// this allows infinite movement in each direction, but the downside is that the cursor remains visible outside the page and\n\t\t// it ends up in that location when the drag ends. It isn't possible to take advantage of the PointerCapture API (yes, that's\n\t\t// a different API than PointerLock) which is supposed to allow the CSS cursor style (such as `cursor: none`) to persist even\n\t\t// while dragging it outside the browser window, because Safari has another bug where the cursor icon is unaffected by that API.\n\t\tif (isSafari) document.body.classList.add(\"cursor-hidden\");\n\n\t\t// Enter dragging state\n\t\tif (usePointerLock) target.requestPointerLock();\n\t\tif (isPlatformNative()) {\n\t\t\teditor.handle.appWindowPointerLock();\n\t\t}\n\t\tinitialValueBeforeDragging = value;\n\t\tcumulativeDragDelta = 0;\n\n\t\t// Tell the backend that we are beginning a transaction for the history system\n\t\tstartDragging();\n\n\t\t// We ignore the first event invocation's `e.movementX` value because it's unreliable.\n\t\t// In both Chrome and Firefox (tested on Windows 10), the first `e.movementX` value is occasionally a very large number\n\t\t// (around positive 1000, even if movement was in the negative direction). This seems to happen more often if the movement is rapid.\n\t\t// TODO: On rarer occasions, it isn't sufficient to ignore just the first event, so this solution is imperfect.\n\t\t// TODO: Using a counter to ignore more frames helps progressively decrease—but not eliminate—the issue, but it makes drag initiation feel delayed so we don't do that.\n\t\t// TODO: A better solution will need to discard outlier movement values across multiple frames by basically implementing a time-series data analysis filtering algorithm.\n\t\tlet ignoredFirstMovement = false;\n\n\t\tconst pointerUp = () => {\n\t\t\t// Confirm on release by setting the reset value to the current value, so once the pointer lock ends,\n\t\t\t// the value is set to itself instead of the initial (abort) value in the \"pointerlockchange\" event handler function.\n\t\t\tinitialValueBeforeDragging = value;\n\t\t\tcumulativeDragDelta = 0;\n\n\t\t\tif (usePointerLock) document.exitPointerLock();\n\t\t\telse pointerLockChange();\n\t\t};\n\t\tconst pointerMove = (e: PointerEvent) => {\n\t\t\t// TODO: Display a fake cursor over the top of the page which wraps around the edges of the editor.\n\n\t\t\t// Abort the drag if right click is down. This works here because a \"pointermove\" event is fired when right clicking even if the cursor didn't move.\n\t\t\tif (e.buttons & BUTTONS_RIGHT) {\n\t\t\t\tif (usePointerLock) document.exitPointerLock();\n\t\t\t\telse pointerLockChange();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// If no buttons are down, that means we are stuck in the drag state after having released the mouse, so we should exit.\n\t\t\t// For some reason on Firefox in Wayland, `e.buttons` can be 0 while `e.button` is -1, but we don't want to exit in that state.\n\t\t\tif (e.buttons === 0 && e.button !== -1) {\n\t\t\t\tif (usePointerLock) document.exitPointerLock();\n\t\t\t\telse pointerLockChange();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Calculate and then update the dragged value offset, slowed down by 10x when Shift is held.\n\t\t\tif (ignoredFirstMovement && initialValueBeforeDragging !== undefined) {\n\t\t\t\tpointerLockMoveUpdate(e.movementX, e.shiftKey, e.ctrlKey, initialValueBeforeDragging);\n\t\t\t}\n\t\t\tignoredFirstMovement = true;\n\t\t};\n\t\t// On desktop we don't get `pointermove` events while in pointer lock (CEF doesn't support pointer lock).\n\t\t// We have to listen for our custom `pointerlockmove` events instead.\n\t\tconst pointerLockMove = ({ detail }: WindowEventMap[\"pointerlockmove\"]) => {\n\t\t\tif (ignoredFirstMovement && initialValueBeforeDragging !== undefined) {\n\t\t\t\tconst delta = detail.x;\n\t\t\t\tpointerLockMoveUpdate(delta, shiftKeyDown, ctrlKeyDown, initialValueBeforeDragging);\n\t\t\t}\n\t\t\tignoredFirstMovement = true;\n\t\t};\n\t\tconst pointerLockChange = () => {\n\t\t\t// Do nothing if we just entered, rather than exited, pointer lock.\n\t\t\tif (usePointerLock && document.pointerLockElement) return;\n\n\t\t\t// Un-hide the cursor if we're using the Safari workaround.\n\t\t\tif (isSafari) document.body.classList.remove(\"cursor-hidden\");\n\n\t\t\t// Reset the value to the initial value if the drag was aborted, or to the current value if it was just confirmed by changing the initial value to the current value.\n\t\t\tupdateValue(initialValueBeforeDragging);\n\t\t\tinitialValueBeforeDragging = undefined;\n\t\t\tcumulativeDragDelta = 0;\n\n\t\t\t// Clean up the event listeners.\n\t\t\tactiveDragCleanup?.();\n\t\t};\n\n\t\taddEventListener(\"pointerup\", pointerUp);\n\t\taddEventListener(\"pointermove\", pointerMove);\n\t\taddEventListener(\"pointerlockmove\", pointerLockMove);\n\t\tif (usePointerLock) document.addEventListener(\"pointerlockchange\", pointerLockChange);\n\t\tactiveDragCleanup = () => {\n\t\t\tremoveEventListener(\"pointerup\", pointerUp);\n\t\t\tremoveEventListener(\"pointermove\", pointerMove);\n\t\t\tremoveEventListener(\"pointerlockmove\", pointerLockMove);\n\t\t\tif (usePointerLock) document.removeEventListener(\"pointerlockchange\", pointerLockChange);\n\t\t\tactiveDragCleanup = undefined;\n\t\t};\n\t}\n\n\tfunction pointerLockMoveUpdate(delta: number, slow: boolean, snapping: boolean, initialValue: number) {\n\t\tconst CHANGE_PER_DRAG_PX = 0.1;\n\t\tconst CHANGE_PER_DRAG_PX_SLOW = CHANGE_PER_DRAG_PX / 10;\n\n\t\tconst dragDelta = delta * (slow ? CHANGE_PER_DRAG_PX_SLOW : CHANGE_PER_DRAG_PX);\n\t\tcumulativeDragDelta += dragDelta;\n\n\t\tconst combined = initialValue + cumulativeDragDelta;\n\t\tconst combineSnapped = snapping || isInteger ? Math.round(combined) : combined;\n\n\t\tconst newValue = updateValue(combineSnapped);\n\n\t\t// If the value was altered within the `updateValue()` call, we need to rectify the cumulative drag delta to account for the change.\n\t\tif (newValue !== undefined) cumulativeDragDelta -= combineSnapped - newValue;\n\t}\n\n\t// ===============================\n\t// RANGE MODE: DRAGGING THE SLIDER\n\t// ===============================\n\n\t// Called by the range slider's \"input\" event which fires continuously while the user is dragging the slider.\n\t// It also fires once when the user clicks the slider, causing its position to jump to the clicked X position.\n\t// Firefox also likes to fire this event more liberally, and it likes to make this event happen after most others,\n\t// which is why the logic for this feature has to be pretty complicated to ensure robustness even across unexpected event ordering.\n\t//\n\t// Summary:\n\t// - Do nothing if the user is still dragging with left click after aborting with right click or escape\n\t// - If this is the first \"input\" event upon mousedown, manage the state so we end up waiting for the user to decide on a subsequent action:\n\t//     - Right clicking or pressing Escape means we abort from the \"Deciding\" state and wait for the user to release all mouse buttons before respecting any further input\n\t//     - Releasing the click without dragging means we focus the text input element to edit the number field\n\t//     - Dragging the slider means we commit to dragging the slider, so we begin watching for an abort from that state (and continue onto the next bullet point below)\n\t// - If the user has committed to dragging the slider, so we update this widget's value.\n\tfunction onSliderInput() {\n\t\t// Exit early if the slider is disabled by having been aborted by the user.\n\t\tif (rangeSliderClickDragState === \"Aborted\") {\n\t\t\t// If we've just aborted the drag by right clicking, but the user hasn't yet released the left mouse button, Firefox treats\n\t\t\t// some subsequent interactions with the slider (like that right mouse button release, or maybe mouse movement in some cases)\n\t\t\t// as input changes to the slider position. Thus, until we leave the \"Aborted\" state by releasing all mouse buttons, we have\n\t\t\t// to set the slider position back to the currently intended value to fight against Firefox's attempts to let the user move it.\n\t\t\tupdateValue(rangeSliderValueAsRendered);\n\n\t\t\t// Now we exit early because we're ignoring further user input until the user releases all mouse buttons, which gets us back to the \"Ready\" state.\n\t\t\treturn;\n\t\t}\n\n\t\t// Keep only 4 digits after the decimal point.\n\t\tconst ROUNDING_EXPONENT = 4;\n\t\tconst ROUNDING_MAGNITUDE = 10 ** ROUNDING_EXPONENT;\n\t\tconst roundedValue = Math.round(rangeSliderValue * ROUNDING_MAGNITUDE) / ROUNDING_MAGNITUDE;\n\n\t\t// Exit if this is an extraneous event invocation that occurred after mouseup, which happens in Firefox.\n\t\tif (value !== undefined && Math.abs(value - roundedValue) < 1 / ROUNDING_MAGNITUDE) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Snap the slider value to the nearest integer if the Ctrl key is held, or the widget is set to integer mode.\n\t\tconst snappedValue = ctrlKeyDown || isInteger ? Math.round(roundedValue) : roundedValue;\n\n\t\t// The first \"input\" event upon mousedown means we transition to a \"Deciding\" state, allowing us to wait for the\n\t\t// next event to determine if the user is dragging (to slide the slider) or releasing (to edit the numerical text field).\n\t\tif (rangeSliderClickDragState === \"Ready\") {\n\t\t\t// We're in the \"Deciding\" state now, which means we're waiting for the user to either drag or release the slider.\n\t\t\trangeSliderClickDragState = \"Deciding\";\n\n\t\t\t// We want to render the fake slider thumb at the old position, which is still the number held by `value`.\n\t\t\trangeSliderValueAsRendered = value || 0;\n\n\t\t\t// We also store this initial value so we can restore it if the user aborts the drag.\n\t\t\tinitialValueBeforeDragging = value;\n\n\t\t\t// We want to allow the user to right click to abort from this \"Deciding\" state so the slider isn't stuck waiting for either a drag or release.\n\t\t\taddEventListener(\"mousedown\", sliderAbortFromMousedown);\n\t\t\taddEventListener(\"keydown\", sliderAbortFromMousedown);\n\n\t\t\t// Exit early because we don't want to use the value set by where on the track the user pressed.\n\t\t\treturn;\n\t\t}\n\n\t\t// Now that we've past the point of entering the \"Deciding\" state in this subsequent invocation, we know that the user has\n\t\t// either dragged or released the mouse so we can stop watching for a right click to abort from that short point in the process.\n\t\tremoveEventListener(\"mousedown\", sliderAbortFromMousedown);\n\t\tremoveEventListener(\"keydown\", sliderAbortFromMousedown);\n\n\t\t// If the subsequent event upon entering the \"Deciding\" state is this slider \"input\" event caused by the user dragging it, that means the user has\n\t\t// committed to dragging the slider (instead of alternatively deciding on releasing it to edit the text field, or aborting with Escape/right click).\n\t\tif (rangeSliderClickDragState === \"Deciding\") {\n\t\t\t// We're dragging now, so that's the new state.\n\t\t\trangeSliderClickDragState = \"Dragging\";\n\n\t\t\t// Tell the backend that we are beginning a transaction for the history system\n\t\t\tstartDragging();\n\n\t\t\t// We want to begin watching for an abort while dragging the slider.\n\t\t\taddEventListener(\"pointermove\", sliderAbortFromDragging);\n\t\t\taddEventListener(\"keydown\", sliderAbortFromDragging);\n\n\t\t\t// Since we've committed to dragging the slider, we want to use the new slider value. Continue to the logic below.\n\t\t}\n\n\t\t// If we're in a dragging state, we want to use the new slider value.\n\t\trangeSliderValueAsRendered = snappedValue;\n\t\tupdateValue(snappedValue);\n\t}\n\n\t// This handles the user releasing all mouse buttons after clicking (and potentially dragging) the slider.\n\t// If the slider wasn't dragged, we focus the text input element to begin editing the number field.\n\t// Then, regardless of the above, we clean up the state and event listeners.\n\t// This is called by the range slider's \"pointerup\" event bound in the HTML template.\n\tfunction onSliderPointerUp() {\n\t\t// User clicked but didn't drag, so we focus the text input element.\n\t\tif (rangeSliderClickDragState === \"Deciding\") {\n\t\t\tconst inputElement = self?.element();\n\t\t\tif (!inputElement) return;\n\n\t\t\t// Set the slider position back to the original position to undo the user moving it.\n\t\t\trangeSliderValue = rangeSliderValueAsRendered;\n\n\t\t\t// Begin editing the number text field.\n\t\t\tinputElement.focus();\n\n\t\t\t// In the next step, we'll switch back to the neutral state so that after the user is done editing the text field, the process can begin anew.\n\t\t}\n\n\t\t// Since the user decided to release the slider, we reset to the neutral state so the user can begin the process anew.\n\t\t// But if the slider was aborted, we don't want to reset the state because we're still waiting for the user to release all mouse buttons.\n\t\tif (rangeSliderClickDragState !== \"Aborted\") {\n\t\t\trangeSliderClickDragState = \"Ready\";\n\t\t}\n\n\t\t// Clean up the event listeners that were for tracking an abort while dragging the slider, now that we're no longer dragging it.\n\t\tremoveEventListener(\"mousedown\", sliderAbortFromMousedown);\n\t\tremoveEventListener(\"keydown\", sliderAbortFromMousedown);\n\t\tremoveEventListener(\"pointermove\", sliderAbortFromDragging);\n\t\tremoveEventListener(\"keydown\", sliderAbortFromDragging);\n\t}\n\n\tfunction startDragging() {\n\t\t// This event is sent to the backend so it knows to start a transaction for the history system. See discussion for some explanation:\n\t\t// <https://github.com/GraphiteEditor/Graphite/pull/1584#discussion_r1477592483>\n\t\tdispatch(\"startHistoryTransaction\");\n\t}\n\n\t// We want to let the user abort while dragging the slider by right clicking or pressing Escape.\n\t// This function also helps recover and clean up if the window loses focus while dragging the slider.\n\t// Since we reuse the function for both the \"pointermove\" and \"keydown\" events, it is split into parts that only run for a `PointerEvent` or `KeyboardEvent`.\n\tfunction sliderAbortFromDragging(e: PointerEvent | KeyboardEvent) {\n\t\t// Logic for aborting from pressing Escape.\n\t\tif (e instanceof KeyboardEvent) {\n\t\t\t// Detect if the user pressed Escape and abort the slider drag.\n\t\t\tif (e.key === \"Escape\") sliderAbort(true);\n\t\t}\n\n\t\t// Logic for aborting from a right click.\n\t\t// Detect if a right click has occurred and abort the slider drag.\n\t\t// This handler's \"pointermove\" event will be fired upon right click even if the cursor didn't move, which is why it's okay to check in this event handler.\n\t\tif (e instanceof PointerEvent && e.buttons & BUTTONS_RIGHT) {\n\t\t\t// Call the abort helper function\n\t\t\tsliderAbort(false);\n\t\t}\n\n\t\t// Recovery from the window losing focus while dragging the slider.\n\t\t// If somehow the user moved the pointer while not left click-dragging the slider, we know that we were stuck in the \"Deciding\" state, so we recover and clean up.\n\t\t// This could happen while dragging the slider and using a hotkey to tab away to another window or browser tab, then returning to the stuck state.\n\t\tif (e instanceof PointerEvent && !(e.target === inputRangeElement && e.buttons & BUTTONS_LEFT)) {\n\t\t\t// Switch back to the neutral state.\n\t\t\trangeSliderClickDragState = \"Ready\";\n\n\t\t\t// Remove the \"pointermove\" and \"keydown\" event listeners that are for tracking an abort while\n\t\t\t// dragging the slider, now that we're no longer dragging it due to the loss of window focus.\n\t\t\tremoveEventListener(\"pointermove\", sliderAbortFromDragging);\n\t\t\tremoveEventListener(\"keydown\", sliderAbortFromDragging);\n\t\t}\n\t}\n\n\t// We want to let the user abort immediately after clicking the slider, but not yet deciding to drag or release.\n\t// During this momentary step, the slider hasn't moved yet but we want to allow aborting from this limbo state.\n\tfunction sliderAbortFromMousedown(e: MouseEvent | KeyboardEvent) {\n\t\t// Logic for aborting from a right click or pressing Escape.\n\t\tconst abortWithEscape = e instanceof KeyboardEvent && e.key === \"Escape\";\n\t\tconst abortWithRightClick = e instanceof MouseEvent && e.button === BUTTON_RIGHT;\n\n\t\t// Call the abort helper function\n\t\tif (abortWithEscape || abortWithRightClick) sliderAbort(abortWithEscape);\n\n\t\t// Clean up these event listeners because they were for getting us into this function and now we're done with them.\n\t\tremoveEventListener(\"mousedown\", sliderAbortFromMousedown);\n\t\tremoveEventListener(\"keydown\", sliderAbortFromMousedown);\n\t}\n\n\t// Helper function that performs the state management and cleanup for aborting the slider drag.\n\tfunction sliderAbort(abortWithEscape: boolean) {\n\t\tconst element = self?.element() || undefined;\n\t\tif (abortWithEscape && element) preventEscapeClosingParentFloatingMenu(element);\n\n\t\t// End the user's drag by instantaneously disabling and re-enabling the range input element\n\t\tif (inputRangeElement) inputRangeElement.disabled = true;\n\t\tsliderAbortTimeout1 = setTimeout(() => {\n\t\t\tif (inputRangeElement) inputRangeElement.disabled = false;\n\t\t}, 0);\n\n\t\t// Set the value back to the original value before the user began dragging.\n\t\tif (initialValueBeforeDragging !== undefined) {\n\t\t\trangeSliderValueAsRendered = initialValueBeforeDragging;\n\t\t\tupdateValue(initialValueBeforeDragging);\n\t\t}\n\n\t\t// Set the state to \"Aborted\" so we can ignore further user input until the user releases all mouse buttons.\n\t\trangeSliderClickDragState = \"Aborted\";\n\n\t\t// Detect when all mouse buttons are released so we can exit the \"Aborted\" state and return to the \"Ready\" state.\n\t\t// (The \"pointerup\" event is defined as firing only upon all mouse buttons being released, which is what we need here.)\n\t\tconst sliderResetAbort = () => {\n\t\t\t// Switch back to the neutral state so the user can begin the process anew.\n\t\t\t// We do this inside setTimeout() to delay this until after Firefox has fired its extraneous \"input\" event after this \"pointerup\" event.\n\t\t\t//\n\t\t\t// A delay of 0 seems to be sufficient, but if the bug persists, we can try increasing the delay. The bug is reproduced in Firefox by\n\t\t\t// dragging the slider, hitting Escape, then releasing the mouse button. This results in being transferred by `onSliderInput()` to the\n\t\t\t// \"Deciding\" state when we should remain in the \"Ready\" state as set here. (For debugging, this can be visualized in CSS by\n\t\t\t// recoloring the fake slider handle, which is shown in the \"Deciding\" state.)\n\t\t\tsliderAbortTimeout2 = setTimeout(() => (rangeSliderClickDragState = \"Ready\"), 0);\n\n\t\t\t// Clean up the event listener that was used to call this function.\n\t\t\tremoveEventListener(\"pointerup\", sliderResetAbort);\n\t\t\tsliderResetAbortHandler = undefined;\n\t\t};\n\t\tsliderResetAbortHandler = sliderResetAbort;\n\t\taddEventListener(\"pointerup\", sliderResetAbort);\n\n\t\t// Clean up the event listeners that were for tracking an abort while dragging the slider, now that we're no longer dragging it.\n\t\tremoveEventListener(\"pointermove\", sliderAbortFromDragging);\n\t\tremoveEventListener(\"keydown\", sliderAbortFromDragging);\n\t}\n</script>\n\n<FieldInput\n\tclass=\"number-input\"\n\tclasses={{\n\t\tnarrow,\n\t\tincrement: mode === \"Increment\",\n\t\trange: mode === \"Range\",\n\t}}\n\tvalue={text}\n\ton:value={({ detail }) => (text = detail)}\n\ton:textFocused={onTextFocused}\n\ton:textChanged={onTextChanged}\n\ton:textChangeCanceled={onTextChangeCanceled}\n\ton:pointerdown={onDragPointerDown}\n\t{label}\n\t{disabled}\n\t{narrow}\n\t{tooltipLabel}\n\t{tooltipDescription}\n\t{tooltipShortcut}\n\t{styles}\n\thideContextMenu={true}\n\tspellcheck={false}\n\tbind:this={self}\n>\n\t{#if value !== undefined}\n\t\t{#if mode === \"Increment\" && incrementBehavior !== \"None\"}\n\t\t\t<button\n\t\t\t\tclass=\"arrow left\"\n\t\t\t\ton:pointerdown={(e) => onIncrementPointerDown(e, \"Decrease\")}\n\t\t\t\ton:mousedown={incrementPressAbort}\n\t\t\t\ton:pointerup={onIncrementPointerUp}\n\t\t\t\ton:pointerleave={onIncrementPointerUp}\n\t\t\t\ttabindex=\"-1\"\n\t\t\t></button>\n\t\t\t<button\n\t\t\t\tclass=\"arrow right\"\n\t\t\t\ton:pointerdown={(e) => onIncrementPointerDown(e, \"Increase\")}\n\t\t\t\ton:mousedown={incrementPressAbort}\n\t\t\t\ton:pointerup={onIncrementPointerUp}\n\t\t\t\ton:pointerleave={onIncrementPointerUp}\n\t\t\t\ttabindex=\"-1\"\n\t\t\t></button>\n\t\t{/if}\n\t\t{#if mode === \"Range\"}\n\t\t\t<input\n\t\t\t\ttype=\"range\"\n\t\t\t\ttabindex=\"-1\"\n\t\t\t\tclass=\"slider\"\n\t\t\t\tclass:hidden={rangeSliderClickDragState === \"Deciding\"}\n\t\t\t\t{disabled}\n\t\t\t\tmin={rangeMin}\n\t\t\t\tmax={rangeMax}\n\t\t\t\tstep={sliderStepValue}\n\t\t\t\tbind:value={rangeSliderValue}\n\t\t\t\ton:input={onSliderInput}\n\t\t\t\ton:pointerup={onSliderPointerUp}\n\t\t\t\ton:contextmenu|preventDefault\n\t\t\t\ton:wheel={(e) => /* Stops slider eating the scroll event in Firefox */ e.target instanceof HTMLInputElement && e.target.blur()}\n\t\t\t\tbind:this={inputRangeElement}\n\t\t\t/>\n\t\t\t{#if rangeSliderClickDragState === \"Deciding\"}\n\t\t\t\t<div class=\"fake-slider-thumb\"></div>\n\t\t\t{/if}\n\t\t\t<div class=\"slider-progress\"></div>\n\t\t{/if}\n\t{/if}\n</FieldInput>\n\n<style lang=\"scss\" global>\n\t.number-input {\n\t\t&.narrow {\n\t\t\t--widget-height: 20px;\n\t\t}\n\n\t\t&.increment {\n\t\t\t--arrow-radius: 3px;\n\n\t\t\t// Widen the label and input margins from the edges by an extra 8px to make room for the increment arrows\n\t\t\tlabel {\n\t\t\t\tmargin-left: 8px;\n\t\t\t}\n\n\t\t\t// Keep the right-aligned input element from overlapping the increment arrow on the right\n\t\t\tinput[type=\"text\"]:not(:focus).has-label {\n\t\t\t\tmargin-right: 8px;\n\t\t\t}\n\n\t\t\t// Hide the increment arrows when entering text, disabled, or not hovered\n\t\t\tinput[type=\"text\"]:focus ~ .arrow,\n\t\t\t&.disabled .arrow,\n\t\t\t&:not(:hover) .arrow {\n\t\t\t\tdisplay: none;\n\t\t\t}\n\n\t\t\t// Show the left-right arrow cursor when hovered over the draggable area\n\t\t\t&:not(.disabled) input[type=\"text\"]:not(:focus),\n\t\t\t&:not(.disabled) label {\n\t\t\t\tcursor: ew-resize;\n\t\t\t}\n\n\t\t\t// Style the decrement/increment arrows\n\t\t\t.arrow {\n\t\t\t\tposition: absolute;\n\t\t\t\ttop: 0;\n\t\t\t\tmargin: 0;\n\t\t\t\tpadding: calc(var(--widget-height) / 2 - var(--arrow-radius)) 0;\n\t\t\t\tborder: none;\n\t\t\t\tborder-radius: 2px;\n\t\t\t\tbackground: rgba(var(--color-1-nearblack-rgb), 0.5);\n\t\t\t\t// An outline can appear when pressing the arrow button with left click then hitting Escape, so this stops that from showing\n\t\t\t\toutline: none;\n\t\t\t\t// TODO: This is a quick, imperfect way to make the arrow buttons appear like they're behind the text (without messing with the element click targets if we used z-index).\n\t\t\t\t// TODO: But it doesn't preserve the exact hover color due to the blending. Improve this by using a separate element for displaying the arrow and listening for pointer events.\n\t\t\t\tmix-blend-mode: screen;\n\n\t\t\t\t&.right {\n\t\t\t\t\tright: 0;\n\t\t\t\t\tpadding-left: 7px;\n\t\t\t\t\tpadding-right: 6px;\n\n\t\t\t\t\t&::before {\n\t\t\t\t\t\tcontent: \"\";\n\t\t\t\t\t\tdisplay: block;\n\t\t\t\t\t\twidth: 0;\n\t\t\t\t\t\theight: 0;\n\t\t\t\t\t\tborder-style: solid;\n\t\t\t\t\t\tborder-width: var(--arrow-radius) 0 var(--arrow-radius) var(--arrow-radius);\n\t\t\t\t\t\tborder-color: transparent transparent transparent var(--color-e-nearwhite);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t&.left {\n\t\t\t\t\tleft: 0;\n\t\t\t\t\tpadding-left: 6px;\n\t\t\t\t\tpadding-right: 7px;\n\n\t\t\t\t\t&::after {\n\t\t\t\t\t\tcontent: \"\";\n\t\t\t\t\t\tdisplay: block;\n\t\t\t\t\t\twidth: 0;\n\t\t\t\t\t\theight: 0;\n\t\t\t\t\t\tborder-style: solid;\n\t\t\t\t\t\tborder-width: var(--arrow-radius) var(--arrow-radius) var(--arrow-radius) 0;\n\t\t\t\t\t\tborder-color: transparent var(--color-e-nearwhite) transparent transparent;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t&:hover {\n\t\t\t\t\tbackground: var(--color-4-dimgray);\n\n\t\t\t\t\t&::before {\n\t\t\t\t\t\tborder-color: transparent transparent transparent var(--color-f-white);\n\t\t\t\t\t}\n\n\t\t\t\t\t&::after {\n\t\t\t\t\t\tborder-color: transparent var(--color-f-white) transparent transparent;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t&.range {\n\t\t\tposition: relative;\n\n\t\t\tinput[type=\"text\"],\n\t\t\tlabel {\n\t\t\t\tz-index: 1;\n\t\t\t}\n\n\t\t\tinput[type=\"text\"]:focus ~ .slider,\n\t\t\tinput[type=\"text\"]:focus ~ .fake-slider-thumb,\n\t\t\tinput[type=\"text\"]:focus ~ .slider-progress {\n\t\t\t\tdisplay: none;\n\t\t\t}\n\n\t\t\t.slider {\n\t\t\t\tposition: absolute;\n\t\t\t\tleft: 0;\n\t\t\t\ttop: 0;\n\t\t\t\twidth: 100%;\n\t\t\t\theight: 100%;\n\t\t\t\tpadding: 0;\n\t\t\t\tmargin: 0;\n\t\t\t\t-webkit-appearance: none; // Required until Safari 15.4 (Graphite supports 15.0+)\n\t\t\t\tappearance: none;\n\t\t\t\tbackground: none;\n\t\t\t\tcursor: default;\n\t\t\t\t// Except when disabled, the range slider goes above the label and input so it's interactable.\n\t\t\t\t// Then we use the blend mode to make it appear behind which works since the text is almost white and background almost black.\n\t\t\t\t// When disabled, the blend mode trick doesn't work with the grayer colors. But we don't need it to be interactable, so it can actually go behind properly.\n\t\t\t\tz-index: 2;\n\t\t\t\tmix-blend-mode: screen;\n\n\t\t\t\t&.hidden {\n\t\t\t\t\topacity: 0;\n\t\t\t\t}\n\n\t\t\t\t&:disabled {\n\t\t\t\t\tmix-blend-mode: normal;\n\t\t\t\t\tz-index: 0;\n\t\t\t\t}\n\n\t\t\t\t&:hover ~ .slider-progress::before {\n\t\t\t\t\tbackground: var(--color-3-darkgray);\n\t\t\t\t}\n\n\t\t\t\t// Chromium and Safari\n\t\t\t\t&::-webkit-slider-thumb {\n\t\t\t\t\t-webkit-appearance: none; // Required until Safari 15.4 (Graphite supports 15.0+)\n\t\t\t\t\tappearance: none;\n\t\t\t\t\tborder-radius: 2px;\n\t\t\t\t\twidth: 4px;\n\t\t\t\t\theight: calc(var(--widget-height) - 2px);\n\t\t\t\t\tbackground: #494949; // Becomes var(--color-5-dullgray) with screen blend mode over var(--color-1-nearblack) background\n\t\t\t\t}\n\n\t\t\t\t&:hover::-webkit-slider-thumb {\n\t\t\t\t\tbackground: #5b5b5b; // Becomes var(--color-6-lowergray) with screen blend mode over var(--color-1-nearblack) background\n\t\t\t\t}\n\n\t\t\t\t&:disabled::-webkit-slider-thumb {\n\t\t\t\t\tbackground: var(--color-4-dimgray);\n\t\t\t\t}\n\n\t\t\t\t// Firefox\n\t\t\t\t&::-moz-range-thumb {\n\t\t\t\t\tborder: none;\n\t\t\t\t\tborder-radius: 2px;\n\t\t\t\t\twidth: 4px;\n\t\t\t\t\theight: calc(var(--widget-height) - 2px);\n\t\t\t\t\tbackground: #494949; // Becomes var(--color-5-dullgray) with screen blend mode over var(--color-1-nearblack) background\n\t\t\t\t}\n\n\t\t\t\t&:hover::-moz-range-thumb {\n\t\t\t\t\tbackground: #5b5b5b; // Becomes var(--color-6-lowergray) with screen blend mode over var(--color-1-nearblack) background\n\t\t\t\t}\n\n\t\t\t\t&:disabled::-moz-range-thumb {\n\t\t\t\t\tbackground: var(--color-4-dimgray);\n\t\t\t\t}\n\n\t\t\t\t&::-moz-range-track {\n\t\t\t\t\theight: 0;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// This fake slider thumb stays in the location of the real thumb while we have to hide the real slider between mousedown and mouseup or mousemove.\n\t\t\t// That's because the range input element moves to the pressed location immediately upon mousedown, but we don't want to show that yet.\n\t\t\t// Instead, we want to wait until the user does something:\n\t\t\t// - Releasing the mouse means we reset the slider to its previous location, thus canceling the slider move. In that case, we focus the text entry.\n\t\t\t// - Moving the mouse left/right means we have begun dragging, so then we hide this fake one and continue showing the actual drag of the real slider.\n\t\t\t.fake-slider-thumb {\n\t\t\t\tposition: absolute;\n\t\t\t\tleft: 2px;\n\t\t\t\tright: 2px;\n\t\t\t\ttop: 0;\n\t\t\t\tbottom: 0;\n\t\t\t\tz-index: 2;\n\t\t\t\tmix-blend-mode: screen;\n\t\t\t\tpointer-events: none;\n\n\t\t\t\t&::before {\n\t\t\t\t\tcontent: \"\";\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\tborder-radius: 2px;\n\t\t\t\t\tmargin-left: -2px;\n\t\t\t\t\twidth: 4px;\n\t\t\t\t\theight: calc(var(--widget-height) - 2px);\n\t\t\t\t\ttop: 1px;\n\t\t\t\t\tleft: calc(var(--progress-factor) * 100%);\n\t\t\t\t\tbackground: #5b5b5b; // Becomes var(--color-6-lowergray) with screen blend mode over var(--color-1-nearblack) background\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.slider-progress {\n\t\t\t\tposition: absolute;\n\t\t\t\ttop: 2px;\n\t\t\t\tbottom: 2px;\n\t\t\t\tleft: 2px;\n\t\t\t\tright: 2px;\n\t\t\t\tpointer-events: none;\n\n\t\t\t\t&::before {\n\t\t\t\t\tcontent: \"\";\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\ttop: 0;\n\t\t\t\t\tleft: 0;\n\t\t\t\t\twidth: calc(var(--progress-factor) * 100% - 2px);\n\t\t\t\t\theight: 100%;\n\t\t\t\t\tbackground: var(--color-2-mildblack);\n\t\t\t\t\tborder-radius: 1px 0 0 1px;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tinput {\n\t\t\ttext-align: center;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/widgets/inputs/RadioInput.svelte",
    "content": "<script lang=\"ts\">\n\timport { createEventDispatcher } from \"svelte\";\n\n\timport type { RadioEntryData } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\n\timport LayoutRow from \"@graphite/components/layout/LayoutRow.svelte\";\n\timport IconLabel from \"@graphite/components/widgets/labels/IconLabel.svelte\";\n\timport TextLabel from \"@graphite/components/widgets/labels/TextLabel.svelte\";\n\n\tconst dispatch = createEventDispatcher<{ selectedIndex: number }>();\n\n\t// Content\n\texport let selectedIndex: number | undefined = undefined;\n\texport let disabled = false;\n\t// Children\n\texport let entries: RadioEntryData[];\n\t// Styling\n\texport let narrow = false;\n\t// Sizing\n\texport let minWidth = 0;\n\n\t$: mixed = selectedIndex === undefined && !disabled;\n\n\tfunction handleEntryClick(radioEntryData: RadioEntryData) {\n\t\tconst index = entries.indexOf(radioEntryData);\n\t\tdispatch(\"selectedIndex\", index);\n\t}\n</script>\n\n<LayoutRow class=\"radio-input\" classes={{ disabled, narrow, mixed }} styles={{ ...(minWidth > 0 ? { \"min-width\": `${minWidth}px` } : {}) }}>\n\t{#each entries as entry, index}\n\t\t<button\n\t\t\tclass:active={!mixed ? index === selectedIndex : undefined}\n\t\t\ton:click={() => handleEntryClick(entry)}\n\t\t\tdata-tooltip-label={entry.tooltipLabel}\n\t\t\tdata-tooltip-description={entry.tooltipDescription}\n\t\t\tdata-tooltip-shortcut={entry.tooltipShortcut?.shortcut ? JSON.stringify(entry.tooltipShortcut.shortcut) : undefined}\n\t\t\ttabindex={index === selectedIndex ? -1 : 0}\n\t\t\t{disabled}\n\t\t>\n\t\t\t{#if entry.icon}\n\t\t\t\t<IconLabel icon={entry.icon} />\n\t\t\t{/if}\n\t\t\t{#if entry.label}\n\t\t\t\t<TextLabel>{entry.label}</TextLabel>\n\t\t\t{/if}\n\t\t</button>\n\t{/each}\n</LayoutRow>\n\n<style lang=\"scss\" global>\n\t.radio-input {\n\t\tbackground: var(--color-4-dimgray);\n\t\tborder-radius: 2px;\n\t\t--widget-height: 24px;\n\t\theight: var(--widget-height);\n\n\t\tbutton {\n\t\t\tbackground: var(--color-4-dimgray);\n\t\t\tfill: var(--color-e-nearwhite);\n\t\t\tborder-radius: 2px;\n\t\t\theight: 20px;\n\t\t\tpadding: 0;\n\t\t\tmargin: 2px 1px;\n\t\t\tborder: none;\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\t\t\t// `min-width: fit-content` and `flex: 1 1 0` together allow us to occupy space such that we're always at least the content width,\n\t\t\t// but if the container is set wider, we distribute the space evenly (so buttons with short and long labels would have equal widths).\n\t\t\tmin-width: fit-content;\n\t\t\tflex: 1 1 0;\n\n\t\t\t&:first-of-type {\n\t\t\t\tmargin-left: 2px;\n\t\t\t}\n\n\t\t\t&:last-of-type {\n\t\t\t\tmargin-right: 2px;\n\t\t\t}\n\n\t\t\t&.active {\n\t\t\t\tbackground: var(--color-e-nearwhite);\n\t\t\t\tcolor: var(--color-2-mildblack);\n\n\t\t\t\tsvg {\n\t\t\t\t\tfill: var(--color-2-mildblack);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.icon-label {\n\t\t\t\tmargin: 2px;\n\n\t\t\t\t+ .text-label {\n\t\t\t\t\tmargin-left: 0;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.text-label {\n\t\t\t\tmargin: 0 8px;\n\t\t\t\toverflow: hidden;\n\t\t\t\tflex: 0 0 auto;\n\t\t\t}\n\t\t}\n\n\t\t&:not(.disabled) button:not(.active):hover {\n\t\t\tbackground: var(--color-6-lowergray);\n\t\t\tcolor: var(--color-f-white);\n\n\t\t\tsvg {\n\t\t\t\tfill: var(--color-f-white);\n\t\t\t}\n\t\t}\n\n\t\t&.disabled button {\n\t\t\tcolor: var(--color-8-uppergray);\n\n\t\t\tsvg {\n\t\t\t\tfill: var(--color-8-uppergray);\n\t\t\t}\n\n\t\t\t&.active {\n\t\t\t\tbackground: var(--color-8-uppergray);\n\t\t\t\tcolor: var(--color-2-mildblack);\n\n\t\t\t\tsvg {\n\t\t\t\t\tfill: var(--color-2-mildblack);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t&.narrow.narrow {\n\t\t\t--widget-height: 20px;\n\t\t\theight: var(--widget-height);\n\n\t\t\tbutton {\n\t\t\t\theight: 16px;\n\t\t\t}\n\t\t}\n\n\t\t&.mixed {\n\t\t\tbutton:not(:hover),\n\t\t\t&.disabled button:hover {\n\t\t\t\tbackground: var(--color-5-dullgray);\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/widgets/inputs/ReferencePointInput.svelte",
    "content": "<script lang=\"ts\">\n\timport { createEventDispatcher } from \"svelte\";\n\n\timport type { ReferencePoint, ActionShortcut } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\n\tconst dispatch = createEventDispatcher<{ value: ReferencePoint }>();\n\n\t// Content\n\texport let value: string;\n\texport let disabled = false;\n\t// Tooltips\n\texport let tooltipLabel: string | undefined = undefined;\n\texport let tooltipDescription: string | undefined = undefined;\n\texport let tooltipShortcut: ActionShortcut | undefined = undefined;\n\n\tfunction setValue(newValue: ReferencePoint) {\n\t\tdispatch(\"value\", newValue);\n\t}\n</script>\n\n<div\n\tclass=\"reference-point-input\"\n\tclass:disabled\n\tdata-tooltip-label={tooltipLabel}\n\tdata-tooltip-description={tooltipDescription}\n\tdata-tooltip-shortcut={tooltipShortcut?.shortcut ? JSON.stringify(tooltipShortcut.shortcut) : undefined}\n>\n\t<button on:click={() => setValue(\"TopLeft\")} class=\"row-1 col-1\" class:active={value === \"TopLeft\"} tabindex=\"-1\" {disabled}><div></div></button>\n\t<button on:click={() => setValue(\"TopCenter\")} class=\"row-1 col-2\" class:active={value === \"TopCenter\"} tabindex=\"-1\" {disabled}><div></div></button>\n\t<button on:click={() => setValue(\"TopRight\")} class=\"row-1 col-3\" class:active={value === \"TopRight\"} tabindex=\"-1\" {disabled}><div></div></button>\n\t<button on:click={() => setValue(\"CenterLeft\")} class=\"row-2 col-1\" class:active={value === \"CenterLeft\"} tabindex=\"-1\" {disabled}><div></div></button>\n\t<button on:click={() => setValue(\"Center\")} class=\"row-2 col-2\" class:active={value === \"Center\"} tabindex=\"-1\" {disabled}><div></div></button>\n\t<button on:click={() => setValue(\"CenterRight\")} class=\"row-2 col-3\" class:active={value === \"CenterRight\"} tabindex=\"-1\" {disabled}><div></div></button>\n\t<button on:click={() => setValue(\"BottomLeft\")} class=\"row-3 col-1\" class:active={value === \"BottomLeft\"} tabindex=\"-1\" {disabled}><div></div></button>\n\t<button on:click={() => setValue(\"BottomCenter\")} class=\"row-3 col-2\" class:active={value === \"BottomCenter\"} tabindex=\"-1\" {disabled}><div></div></button>\n\t<button on:click={() => setValue(\"BottomRight\")} class=\"row-3 col-3\" class:active={value === \"BottomRight\"} tabindex=\"-1\" {disabled}><div></div></button>\n</div>\n\n<style lang=\"scss\" global>\n\t.reference-point-input {\n\t\tposition: relative;\n\t\tflex: 0 0 auto;\n\t\twidth: 24px;\n\t\theight: 24px;\n\t\t--reference-point-border-color: var(--color-5-dullgray);\n\t\t--reference-point-fill-active: var(--color-e-nearwhite);\n\n\t\tbutton {\n\t\t\tposition: absolute;\n\t\t\twidth: 5px;\n\t\t\theight: 5px;\n\t\t\tmargin: 0;\n\t\t\tpadding: 0;\n\t\t\tbackground: var(--color-1-nearblack);\n\t\t\tborder: 1px solid var(--reference-point-border-color);\n\n\t\t\t&.active {\n\t\t\t\tborder-color: transparent;\n\t\t\t\tbackground: var(--reference-point-fill-active);\n\t\t\t}\n\n\t\t\t&.col-1::before,\n\t\t\t&.col-2::before {\n\t\t\t\tcontent: \"\";\n\t\t\t\tpointer-events: none;\n\t\t\t\twidth: 2px;\n\t\t\t\theight: 0;\n\t\t\t\tborder-top: 1px solid var(--reference-point-border-color);\n\t\t\t\tposition: absolute;\n\t\t\t\ttop: 1px;\n\t\t\t\tright: -3px;\n\t\t\t}\n\n\t\t\t&.row-1::after,\n\t\t\t&.row-2::after {\n\t\t\t\tcontent: \"\";\n\t\t\t\tpointer-events: none;\n\t\t\t\twidth: 0;\n\t\t\t\theight: 2px;\n\t\t\t\tborder-left: 1px solid var(--reference-point-border-color);\n\t\t\t\tposition: absolute;\n\t\t\t\tbottom: -3px;\n\t\t\t\tright: 1px;\n\t\t\t}\n\n\t\t\t&.row-1 {\n\t\t\t\ttop: 3px;\n\t\t\t}\n\t\t\t&.col-1 {\n\t\t\t\tleft: 3px;\n\t\t\t}\n\n\t\t\t&.row-2 {\n\t\t\t\ttop: 10px;\n\t\t\t}\n\t\t\t&.col-2 {\n\t\t\t\tleft: 10px;\n\t\t\t}\n\n\t\t\t&.row-3 {\n\t\t\t\ttop: 17px;\n\t\t\t}\n\t\t\t&.col-3 {\n\t\t\t\tleft: 17px;\n\t\t\t}\n\n\t\t\t// Click targets that extend 1px beyond the borders of each square\n\t\t\tdiv {\n\t\t\t\twidth: 100%;\n\t\t\t\theight: 100%;\n\t\t\t\tpadding: 2px;\n\t\t\t\tmargin: -2px;\n\t\t\t}\n\t\t}\n\n\t\t&:not(.disabled) button:not(.active):hover {\n\t\t\tborder-color: transparent;\n\t\t\tbackground: var(--color-6-lowergray);\n\t\t}\n\n\t\t&.disabled button {\n\t\t\t--reference-point-border-color: var(--color-4-dimgray);\n\t\t\t--reference-point-fill-active: var(--color-8-uppergray);\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/widgets/inputs/RulerInput.svelte",
    "content": "<script lang=\"ts\">\n\timport { onMount } from \"svelte\";\n\n\tconst RULER_THICKNESS = 16;\n\tconst MAJOR_MARK_THICKNESS = 16;\n\tconst MINOR_MARK_THICKNESS = 6;\n\tconst MICRO_MARK_THICKNESS = 3;\n\n\ttype RulerDirection = \"Horizontal\" | \"Vertical\";\n\n\texport let direction: RulerDirection = \"Vertical\";\n\texport let origin: number;\n\texport let numberInterval: number;\n\texport let majorMarkSpacing: number;\n\texport let minorDivisions = 5;\n\texport let microDivisions = 2;\n\n\tlet rulerInput: HTMLDivElement | undefined;\n\tlet rulerLength = 0;\n\tlet svgBounds = { width: \"0px\", height: \"0px\" };\n\n\t$: svgPath = computeSvgPath(direction, origin, majorMarkSpacing, minorDivisions, microDivisions, rulerLength);\n\t$: svgTexts = computeSvgTexts(direction, origin, majorMarkSpacing, numberInterval, rulerLength);\n\n\tfunction computeSvgPath(direction: RulerDirection, origin: number, majorMarkSpacing: number, minorDivisions: number, microDivisions: number, rulerLength: number): string {\n\t\tconst isVertical = direction === \"Vertical\";\n\t\tconst lineDirection = isVertical ? \"H\" : \"V\";\n\n\t\tconst offsetStart = mod(origin, majorMarkSpacing);\n\t\tconst shiftedOffsetStart = offsetStart - majorMarkSpacing;\n\n\t\tconst divisions = majorMarkSpacing / minorDivisions / microDivisions;\n\t\tconst majorMarksFrequency = minorDivisions * microDivisions;\n\n\t\tlet dPathAttribute = \"\";\n\t\tlet i = 0;\n\t\tfor (let location = shiftedOffsetStart; location < rulerLength; location += divisions) {\n\t\t\tlet length;\n\t\t\tif (i % majorMarksFrequency === 0) length = MAJOR_MARK_THICKNESS;\n\t\t\telse if (i % microDivisions === 0) length = MINOR_MARK_THICKNESS;\n\t\t\telse length = MICRO_MARK_THICKNESS;\n\t\t\ti += 1;\n\n\t\t\tconst destination = Math.round(location) + 0.5;\n\t\t\tconst startPoint = isVertical ? `${RULER_THICKNESS - length},${destination}` : `${destination},${RULER_THICKNESS - length}`;\n\t\t\tdPathAttribute += `M${startPoint}${lineDirection}${RULER_THICKNESS} `;\n\t\t}\n\n\t\treturn dPathAttribute;\n\t}\n\n\tfunction computeSvgTexts(direction: RulerDirection, origin: number, majorMarkSpacing: number, numberInterval: number, rulerLength: number): { transform: string; text: string }[] {\n\t\tconst isVertical = direction === \"Vertical\";\n\n\t\tconst offsetStart = mod(origin, majorMarkSpacing);\n\t\tconst shiftedOffsetStart = offsetStart - majorMarkSpacing;\n\n\t\tconst svgTextCoordinates = [];\n\n\t\tlet labelNumber = (Math.ceil(-origin / majorMarkSpacing) - 1) * numberInterval;\n\n\t\tfor (let location = shiftedOffsetStart; location < rulerLength; location += majorMarkSpacing) {\n\t\t\tconst destination = Math.round(location);\n\t\t\tconst x = isVertical ? 9 : destination + 2;\n\t\t\tconst y = isVertical ? destination + 1 : 9;\n\n\t\t\tlet transform = `translate(${x} ${y})`;\n\t\t\tif (isVertical) transform += \" rotate(270)\";\n\n\t\t\tconst text = numberInterval >= 1 ? `${labelNumber}` : labelNumber.toFixed(Math.abs(Math.log10(numberInterval))).replace(/\\.0+$/, \"\");\n\n\t\t\tsvgTextCoordinates.push({ transform, text });\n\n\t\t\tlabelNumber += numberInterval;\n\t\t}\n\n\t\treturn svgTextCoordinates;\n\t}\n\n\texport function resize() {\n\t\tif (!rulerInput) return;\n\n\t\tconst isVertical = direction === \"Vertical\";\n\n\t\tconst newLength = isVertical ? rulerInput.clientHeight : rulerInput.clientWidth;\n\t\tconst roundedUp = (Math.floor(newLength / majorMarkSpacing) + 1) * majorMarkSpacing;\n\n\t\tif (roundedUp !== rulerLength) {\n\t\t\trulerLength = roundedUp;\n\t\t\tconst thickness = `${RULER_THICKNESS}px`;\n\t\t\tconst length = `${roundedUp}px`;\n\t\t\tsvgBounds = isVertical ? { width: thickness, height: length } : { width: length, height: thickness };\n\t\t}\n\t}\n\n\t// Modulo function that works for negative numbers, unlike the JS `%` operator\n\tfunction mod(n: number, m: number): number {\n\t\tconst remainder = n % m;\n\t\treturn Math.floor(remainder >= 0 ? remainder : remainder + m);\n\t}\n\n\tonMount(resize);\n</script>\n\n<div class={`ruler-input ${direction.toLowerCase()}`} bind:this={rulerInput}>\n\t<svg style:width={svgBounds.width} style:height={svgBounds.height}>\n\t\t<path d={svgPath} />\n\t\t{#each svgTexts as svgText}\n\t\t\t<text transform={svgText.transform}>{svgText.text}</text>\n\t\t{/each}\n\t</svg>\n</div>\n\n<style lang=\"scss\" global>\n\t.ruler-input {\n\t\tflex: 1 1 100%;\n\t\tbackground: var(--color-2-mildblack);\n\t\toverflow: hidden;\n\t\tposition: relative;\n\t\tbox-sizing: border-box;\n\n\t\t&.horizontal {\n\t\t\theight: 16px;\n\t\t\tborder-bottom: 1px solid var(--color-5-dullgray);\n\t\t}\n\n\t\t&.vertical {\n\t\t\twidth: 16px;\n\t\t\tborder-right: 1px solid var(--color-5-dullgray);\n\n\t\t\tsvg text {\n\t\t\t\ttext-anchor: end;\n\t\t\t}\n\t\t}\n\n\t\tsvg {\n\t\t\tposition: absolute;\n\n\t\t\tpath {\n\t\t\t\tstroke-width: 1px;\n\t\t\t\tstroke: var(--color-5-dullgray);\n\t\t\t}\n\n\t\t\ttext {\n\t\t\t\tfont-size: 12px;\n\t\t\t\tfill: var(--color-8-uppergray);\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/widgets/inputs/ScrollbarInput.svelte",
    "content": "<script lang=\"ts\">\n\timport { createEventDispatcher, onDestroy } from \"svelte\";\n\n\timport { PRESS_REPEAT_DELAY_MS, PRESS_REPEAT_INTERVAL_MS, PRESS_REPEAT_INTERVAL_RAPID_MS } from \"@graphite/managers/input\";\n\n\tconst ARROW_CLICK_DISTANCE = 0.05;\n\tconst ARROW_REPEAT_DISTANCE = 0.01;\n\n\t// Convert the position of the thumb (0-1) to the position on the track (0-1).\n\t// This includes the 1/2 thumb length gap of the possible thumb position each side so the end of the thumb doesn't go off the track.\n\tconst lerp = (a: number, b: number, t: number): number => a * (1 - t) + b * t;\n\tconst thumbToTrack = (thumbLength: number, thumbPosition: number): number => lerp(thumbLength / 2, 1 - thumbLength / 2, thumbPosition);\n\n\tconst pointerPosition = (e: PointerEvent): number => (direction === \"Vertical\" ? e.clientY : e.clientX);\n\n\tconst clamp01 = (value: number): number => Math.min(Math.max(value, 0), 1);\n\n\tconst dispatch = createEventDispatcher<{ trackShift: number; thumbPosition: number; thumbDragStart: undefined; thumbDragEnd: undefined; thumbDragAbort: undefined }>();\n\n\texport let direction: \"Horizontal\" | \"Vertical\" = \"Vertical\";\n\texport let thumbPosition = 0.5;\n\texport let thumbLength = 0.5;\n\n\tlet scrollTrack: HTMLDivElement | undefined;\n\tlet dragging = false;\n\tlet pressingTrack = false;\n\tlet pressingArrow = false;\n\tlet repeatTimeout: ReturnType<typeof setTimeout> | undefined = undefined;\n\tlet pointerPositionLastFrame = 0;\n\tlet thumbTop: string | undefined = undefined;\n\tlet thumbBottom: string | undefined = undefined;\n\tlet thumbLeft: string | undefined = undefined;\n\tlet thumbRight: string | undefined = undefined;\n\n\t$: start = thumbToTrack(thumbLength, thumbPosition) - thumbLength / 2;\n\t$: end = 1 - thumbToTrack(thumbLength, thumbPosition) - thumbLength / 2;\n\t$: [thumbTop, thumbBottom, thumbLeft, thumbRight] = direction === \"Vertical\" ? [`${start * 100}%`, `${end * 100}%`, \"0%\", \"0%\"] : [\"0%\", \"0%\", `${start * 100}%`, `${end * 100}%`];\n\n\tfunction trackLength(): number | undefined {\n\t\tif (scrollTrack === undefined) return undefined;\n\t\treturn direction === \"Vertical\" ? scrollTrack.clientHeight - thumbLength : scrollTrack.clientWidth;\n\t}\n\n\tfunction trackOffset(): number | undefined {\n\t\tif (scrollTrack === undefined) return undefined;\n\t\treturn direction === \"Vertical\" ? scrollTrack.getBoundingClientRect().top : scrollTrack.getBoundingClientRect().left;\n\t}\n\n\tfunction dragThumb(e: PointerEvent) {\n\t\tif (dragging) return;\n\n\t\tdragging = true;\n\t\tdispatch(\"thumbDragStart\");\n\t\tpointerPositionLastFrame = pointerPosition(e);\n\n\t\taddEvents();\n\t}\n\n\tfunction pressArrow(direction: number) {\n\t\tconst sendMove = () => {\n\t\t\tif (!pressingArrow) return;\n\n\t\t\tconst distance = afterInitialDelay ? ARROW_REPEAT_DISTANCE : ARROW_CLICK_DISTANCE;\n\t\t\tdispatch(\"trackShift\", -direction * distance);\n\n\t\t\tif (afterInitialDelay) repeatTimeout = setTimeout(sendMove, PRESS_REPEAT_INTERVAL_RAPID_MS);\n\t\t\tafterInitialDelay = true;\n\t\t};\n\n\t\tpressingArrow = true;\n\t\tdispatch(\"thumbDragStart\");\n\t\tlet afterInitialDelay = false;\n\t\tsendMove();\n\t\trepeatTimeout = setTimeout(sendMove, PRESS_REPEAT_DELAY_MS);\n\n\t\taddEvents();\n\t}\n\n\tfunction pressTrack(e: PointerEvent) {\n\t\tif (dragging) return;\n\n\t\tconst length = trackLength();\n\t\tconst offset = trackOffset();\n\t\tif (length === undefined || offset === undefined) return;\n\n\t\tconst sendMove = () => {\n\t\t\tif (!pressingTrack) return;\n\n\t\t\tconst oldPointer = thumbToTrack(thumbLength, thumbPosition) * length + offset;\n\t\t\tconst newPointer = pointerPosition(e);\n\n\t\t\t// Check if the thumb has reached the cursor position\n\t\t\tconst proposedThumbPosition = (newPointer - offset) / length;\n\t\t\tif (proposedThumbPosition >= start && proposedThumbPosition <= 1 - end) {\n\t\t\t\t// End pressing the track\n\t\t\t\tpressingTrack = false;\n\t\t\t\tclearTimeout(repeatTimeout);\n\n\t\t\t\t// Begin dragging the thumb\n\t\t\t\tdragging = true;\n\t\t\t\tpointerPositionLastFrame = newPointer;\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst move = newPointer - oldPointer < 0 ? 1 : -1;\n\t\t\tdispatch(\"trackShift\", move);\n\n\t\t\tif (afterInitialDelay) repeatTimeout = setTimeout(sendMove, PRESS_REPEAT_INTERVAL_MS);\n\t\t\tafterInitialDelay = true;\n\t\t};\n\n\t\tdispatch(\"thumbDragStart\");\n\t\tpressingTrack = true;\n\t\tlet afterInitialDelay = false;\n\t\tsendMove();\n\t\trepeatTimeout = setTimeout(sendMove, PRESS_REPEAT_DELAY_MS);\n\n\t\taddEvents();\n\t}\n\n\tfunction abortInteraction() {\n\t\tif (pressingTrack || pressingArrow) {\n\t\t\tpressingTrack = false;\n\t\t\tpressingArrow = false;\n\t\t\tclearTimeout(repeatTimeout);\n\t\t\tdispatch(\"thumbDragAbort\");\n\t\t}\n\n\t\tif (dragging) {\n\t\t\tdragging = false;\n\t\t\tdispatch(\"thumbDragAbort\");\n\t\t}\n\t}\n\n\tfunction onPointerUp() {\n\t\tif (dragging) dispatch(\"thumbDragEnd\");\n\n\t\tdragging = false;\n\t\tpressingTrack = false;\n\t\tpressingArrow = false;\n\t\tclearTimeout(repeatTimeout);\n\t\tremoveEvents();\n\t}\n\n\tfunction onPointerMove(e: PointerEvent) {\n\t\tif (pressingTrack) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (pressingArrow) {\n\t\t\tconst target = e.target || undefined;\n\t\t\tif (!target || !(target instanceof Element)) return;\n\t\t\tif (!target?.closest?.(\"[data-scrollbar-arrow]\")) {\n\t\t\t\tpressingArrow = false;\n\t\t\t\tclearTimeout(repeatTimeout);\n\t\t\t\tremoveEvents();\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tif (dragging) {\n\t\t\tconst length = trackLength();\n\t\t\tif (length === undefined) return;\n\n\t\t\tconst positionPositionThisFrame = pointerPosition(e);\n\t\t\tconst dragDelta = positionPositionThisFrame - pointerPositionLastFrame;\n\t\t\tconst movement = dragDelta / (length * (1 - thumbLength));\n\t\t\tconst newThumbPosition = clamp01(thumbPosition + movement);\n\t\t\tdispatch(\"thumbPosition\", newThumbPosition);\n\n\t\t\tpointerPositionLastFrame = positionPositionThisFrame;\n\n\t\t\treturn;\n\t\t}\n\n\t\tremoveEvents();\n\t}\n\n\tfunction onMouseDown(e: MouseEvent) {\n\t\tconst BUTTONS_RIGHT = 0b0000_0010;\n\t\tif (e.buttons & BUTTONS_RIGHT) abortInteraction();\n\t}\n\n\tfunction onKeyDown(e: KeyboardEvent) {\n\t\tif (e.key === \"Escape\") abortInteraction();\n\t}\n\n\tonDestroy(() => {\n\t\tremoveEvents();\n\t});\n\n\tfunction addEvents() {\n\t\twindow.addEventListener(\"pointerup\", onPointerUp);\n\t\twindow.addEventListener(\"pointermove\", onPointerMove);\n\t\twindow.addEventListener(\"mousedown\", onMouseDown);\n\t\twindow.addEventListener(\"keydown\", onKeyDown);\n\t}\n\n\tfunction removeEvents() {\n\t\twindow.removeEventListener(\"pointerup\", onPointerUp);\n\t\twindow.removeEventListener(\"pointermove\", onPointerMove);\n\t\twindow.removeEventListener(\"mousedown\", onMouseDown);\n\t\twindow.removeEventListener(\"keydown\", onKeyDown);\n\t\tclearTimeout(repeatTimeout);\n\t}\n</script>\n\n<div class={`scrollbar-input ${direction.toLowerCase()}`}>\n\t<button class=\"arrow decrease\" on:pointerdown={() => pressArrow(-1)} tabindex=\"-1\" data-scrollbar-arrow></button>\n\t<div class=\"scroll-track\" on:pointerdown={pressTrack} bind:this={scrollTrack}>\n\t\t<div class=\"scroll-thumb\" on:pointerdown={dragThumb} class:dragging style:top={thumbTop} style:bottom={thumbBottom} style:left={thumbLeft} style:right={thumbRight}></div>\n\t</div>\n\t<button class=\"arrow increase\" on:pointerdown={() => pressArrow(1)} tabindex=\"-1\" data-scrollbar-arrow></button>\n</div>\n\n<style lang=\"scss\" global>\n\t.scrollbar-input {\n\t\tdisplay: flex;\n\t\tflex: 1 1 100%;\n\n\t\t&.vertical {\n\t\t\tflex-direction: column;\n\t\t}\n\n\t\t&.horizontal {\n\t\t\tflex-direction: row;\n\t\t}\n\n\t\t.arrow {\n\t\t\t--arrow-color: var(--color-5-dullgray);\n\t\t\tflex: 0 0 auto;\n\t\t\tbackground: none;\n\t\t\tborder: none;\n\t\t\tmargin: 0;\n\t\t\tpadding: 0;\n\t\t\twidth: 16px;\n\t\t\theight: 16px;\n\n\t\t\t&:hover {\n\t\t\t\t--arrow-color: var(--color-6-lowergray);\n\t\t\t}\n\n\t\t\t&:hover:active {\n\t\t\t\t--arrow-color: var(--color-c-brightgray);\n\t\t\t}\n\n\t\t\t&::after {\n\t\t\t\tcontent: \"\";\n\t\t\t\tdisplay: block;\n\t\t\t\tborder-style: solid;\n\t\t\t}\n\t\t}\n\n\t\t&.vertical .arrow.decrease::after {\n\t\t\tmargin: 4px 3px;\n\t\t\tborder-width: 0 5px 8px 5px;\n\t\t\tborder-color: transparent transparent var(--arrow-color) transparent;\n\t\t}\n\n\t\t&.vertical .arrow.increase::after {\n\t\t\tmargin: 4px 3px;\n\t\t\tborder-width: 8px 5px 0 5px;\n\t\t\tborder-color: var(--arrow-color) transparent transparent transparent;\n\t\t}\n\n\t\t&.horizontal .arrow.decrease::after {\n\t\t\tmargin: 3px 4px;\n\t\t\tborder-width: 5px 8px 5px 0;\n\t\t\tborder-color: transparent var(--arrow-color) transparent transparent;\n\t\t}\n\n\t\t&.horizontal .arrow.increase::after {\n\t\t\tmargin: 3px 4px;\n\t\t\tborder-width: 5px 0 5px 8px;\n\t\t\tborder-color: transparent transparent transparent var(--arrow-color);\n\t\t}\n\n\t\t.scroll-track {\n\t\t\tposition: relative;\n\t\t\tflex: 1 1 100%;\n\n\t\t\t.scroll-thumb {\n\t\t\t\tposition: absolute;\n\t\t\t\tborder-radius: 4px;\n\t\t\t\tbackground: var(--color-4-dimgray);\n\n\t\t\t\t&:hover,\n\t\t\t\t&.dragging {\n\t\t\t\t\tbackground: var(--color-6-lowergray);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/widgets/inputs/SpectrumInput.svelte",
    "content": "<script lang=\"ts\" context=\"module\">\n\texport const MIN_MIDPOINT = 0.01;\n\texport const MAX_MIDPOINT = 0.99;\n</script>\n\n<script lang=\"ts\">\n\timport { createEventDispatcher, onMount, onDestroy } from \"svelte\";\n\n\timport { evaluateGradientAtPosition } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\timport type { Color, GradientStops } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\timport { colorToHexOptionalAlpha, colorToRgbCSS, gradientFirstColor, gradientLastColor, gradientToLinearGradientCSS } from \"@graphite/utility-functions/colors\";\n\n\timport { preventEscapeClosingParentFloatingMenu } from \"@graphite/components/layout/FloatingMenu.svelte\";\n\timport LayoutCol from \"@graphite/components/layout/LayoutCol.svelte\";\n\timport LayoutRow from \"@graphite/components/layout/LayoutRow.svelte\";\n\n\tconst BUTTON_LEFT = 0;\n\tconst BUTTON_RIGHT = 2;\n\n\tconst dispatch = createEventDispatcher<{ activeMarkerIndexChange: { activeMarkerIndex: number | undefined; activeMarkerIsMidpoint: boolean }; gradient: GradientStops; dragging: boolean }>();\n\n\texport let gradient: GradientStops;\n\texport let disabled = false;\n\texport let activeMarkerIndex: number | undefined = 0;\n\texport let activeMarkerIsMidpoint = false;\n\t// export let disabled = false;\n\t// export let tooltipLabel: string | undefined = undefined;\n\t// export let tooltipDescription: string | undefined = undefined;\n\t// export let tooltipShortcut: ActionShortcut | undefined = undefined;\n\n\t/// Reference to the marker track element so we can access its div.\n\tlet markerTrack: LayoutRow | undefined = undefined;\n\t/// When dragging, stores the original value of the marker or midpoint being dragged, so we can restore it if the drag is cancelled.\n\tlet dragRestore: number | undefined = undefined;\n\t/// When dragging, indicates whether this maker was inserted during the drag, so we know whether to remove it again if the drag is cancelled.\n\tlet deletionRestore: boolean | undefined = undefined;\n\t/// When dragging, stores the previous active marker (or its midpoint) index, so we can restore active selection to that one if the drag is cancelled on a different marker.\n\tlet activeMarkerIndexRestore: number | undefined = undefined;\n\t/// When dragging, stores whether the previously active drag item was a midpoint (matching the index kept in `activeMarkerIndexRestore`), so we can restore its active selection if cancelled.\n\tlet activeMarkerIsMidpointRestore = false;\n\t/// When dragging a midpoint, tracks whether the midpoint has actually moved by at least a pixel, to tell between a click-then-click-and-drag (just a drag) or a double-click (reset the midpoint).\n\tlet midpointDragged = false;\n\n\tfunction markerPointerDown(e: PointerEvent, index: number) {\n\t\tif (disabled) return;\n\n\t\t// Left-click to select and begin potentially dragging\n\t\tif (e.button === BUTTON_LEFT) {\n\t\t\t// Set restore values at this time, so later the user can cancel the drag and restore to these values\n\t\t\tactiveMarkerIndexRestore = activeMarkerIndex;\n\t\t\tactiveMarkerIsMidpointRestore = activeMarkerIsMidpoint;\n\n\t\t\t// Update the parent component with the newly activated marker or midpoint drag item\n\t\t\tactiveMarkerIndex = index;\n\t\t\tactiveMarkerIsMidpoint = false;\n\t\t\tdispatch(\"activeMarkerIndexChange\", { activeMarkerIndex, activeMarkerIsMidpoint });\n\n\t\t\taddEvents();\n\t\t\treturn;\n\t\t}\n\n\t\t// Right-click to delete\n\t\tif (e.button === BUTTON_RIGHT && deletionRestore === undefined) {\n\t\t\tdeleteStopByIndex(index);\n\t\t\treturn;\n\t\t}\n\t}\n\n\tfunction markerPosition(e: MouseEvent): number | undefined {\n\t\tconst markerTrackRect = markerTrack?.div()?.getBoundingClientRect();\n\t\tif (!markerTrackRect) return;\n\n\t\tconst ratio = (e.clientX - markerTrackRect.left) / markerTrackRect.width;\n\n\t\treturn Math.max(0, Math.min(1, ratio));\n\t}\n\n\tfunction midpointPointerDown(e: PointerEvent, index: number) {\n\t\tif (disabled) return;\n\t\tif (e.button !== BUTTON_LEFT) return;\n\n\t\t// Since we just pressed the mouse button down, the midpoint has not been dragged by any distance\n\t\tmidpointDragged = false;\n\n\t\t// Set restore values at this time, so later the user can cancel the drag and restore to these values\n\t\tactiveMarkerIndexRestore = activeMarkerIndex;\n\t\tactiveMarkerIsMidpointRestore = activeMarkerIsMidpoint;\n\n\t\t// Update the parent component with the newly activated marker or midpoint drag item\n\t\tactiveMarkerIndex = index;\n\t\tactiveMarkerIsMidpoint = true;\n\t\tdispatch(\"activeMarkerIndexChange\", { activeMarkerIndex, activeMarkerIsMidpoint });\n\n\t\taddEvents();\n\t}\n\n\tfunction resetMidpoint(index: number) {\n\t\tif (disabled || midpointDragged) return;\n\n\t\tgradient.midpoint[index] = 0.5;\n\t\tdispatch(\"gradient\", gradient);\n\t}\n\n\tfunction insertStop(e: MouseEvent) {\n\t\tif (disabled) return;\n\t\tif (e.button !== BUTTON_LEFT) return;\n\n\t\t// Determine the position along the gradient (0-1) based on the click position in the marker track\n\t\tlet position = markerPosition(e);\n\t\tif (position === undefined) return;\n\n\t\t// Determine which index the new stop should be inserted at based on its position\n\t\tlet index = gradient.position.findIndex((item) => item > position);\n\t\tif (index === -1) index = gradient.position.length;\n\n\t\t// Determine the color of the new stop by evaluating the gradient at the position of the new stop\n\t\tconst color: Color = evaluateGradientAtPosition(position, new Float64Array(gradient.position), new Float64Array(gradient.midpoint), gradient.color);\n\n\t\t// Insert the new stop into the gradient\n\t\tgradient.position.splice(index, 0, position);\n\t\t// Duplicate the midpoint ratio position of the interval we're inserting into, so both new intervals have the same midpoint position ratio\n\t\tgradient.midpoint.splice(index, 0, gradient.midpoint[index - 1] || 0.5);\n\t\tgradient.color.splice(index, 0, color);\n\t\tdispatch(\"gradient\", gradient);\n\n\t\t// Set restore values at this time, so later the user can cancel the drag and restore to these values\n\t\tactiveMarkerIndexRestore = activeMarkerIndex;\n\t\tactiveMarkerIsMidpointRestore = activeMarkerIsMidpoint;\n\n\t\t// Update the parent component with the newly activated marker or midpoint drag item\n\t\tactiveMarkerIndex = index;\n\t\tactiveMarkerIsMidpoint = false;\n\t\tdispatch(\"activeMarkerIndexChange\", { activeMarkerIndex, activeMarkerIsMidpoint });\n\n\t\t// Since this stop insertion can happen as part of the beginning of a drag, we set this to indicate that it should be removed again if the drag is cancelled\n\t\tdeletionRestore = true;\n\n\t\taddEvents();\n\t}\n\n\tfunction deleteStop(e: KeyboardEvent) {\n\t\tif (disabled) return;\n\n\t\tif (e.key !== \"Delete\" && e.key !== \"Backspace\") return;\n\t\tif (activeMarkerIndex === undefined) return;\n\t\tif (gradient.position.length <= 2 && !activeMarkerIsMidpoint) return;\n\n\t\t// Stop dragging the marker or midpoint\n\t\tstopDrag();\n\n\t\t// Either reset the midpoint to 50% or delete the marker, based on which type is currently active\n\t\tif (activeMarkerIsMidpoint) resetMidpoint(activeMarkerIndex);\n\t\telse deleteStopByIndex(activeMarkerIndex);\n\t}\n\n\tfunction deleteStopByIndex(index: number) {\n\t\tif (disabled) return;\n\n\t\tif (gradient.position.length <= 2) return;\n\n\t\tgradient.position.splice(index, 1);\n\t\tgradient.midpoint.splice(index, 1);\n\t\tgradient.color.splice(index, 1);\n\t\tdispatch(\"gradient\", gradient);\n\n\t\tdeletionRestore = undefined;\n\n\t\tif (gradient.position.length === 0) {\n\t\t\tactiveMarkerIndex = undefined;\n\t\t} else {\n\t\t\tactiveMarkerIndex = Math.max(0, Math.min(gradient.position.length - 1, index));\n\t\t}\n\t\tactiveMarkerIsMidpoint = false;\n\t\tdispatch(\"activeMarkerIndexChange\", { activeMarkerIndex, activeMarkerIsMidpoint });\n\t}\n\n\tfunction moveMarker(e: PointerEvent, index: number) {\n\t\tif (disabled) return;\n\n\t\t// Just in case the mouseup event is lost\n\t\tif (e.buttons === 0) stopDrag();\n\n\t\tlet position = markerPosition(e);\n\t\tif (position === undefined) return;\n\n\t\tif (dragRestore === undefined) dragRestore = position;\n\t\tif (deletionRestore === undefined) {\n\t\t\tdeletionRestore = false;\n\n\t\t\tdispatch(\"dragging\", true);\n\t\t}\n\n\t\tsetPosition(index, position, false);\n\t}\n\n\tfunction moveMidpoint(e: PointerEvent, index: number) {\n\t\tif (disabled) return;\n\n\t\t// Guard in case the mouseup event is lost\n\t\tif (e.buttons === 0) {\n\t\t\tstopDrag();\n\t\t\treturn;\n\t\t}\n\n\t\tlet position = markerPosition(e);\n\t\tif (position === undefined) return;\n\n\t\tif (dragRestore === undefined) {\n\t\t\tdragRestore = gradient.midpoint[index];\n\t\t\tmidpointDragged = true;\n\t\t\tdispatch(\"dragging\", true);\n\t\t}\n\n\t\tconst leftStop = gradient.position[index];\n\t\tconst rightStop = gradient.position[index + 1];\n\t\tconst range = rightStop - leftStop;\n\t\tif (range <= 0) return;\n\n\t\tgradient.midpoint[index] = Math.max(MIN_MIDPOINT, Math.min(MAX_MIDPOINT, (position - leftStop) / range));\n\t\tdispatch(\"gradient\", gradient);\n\t}\n\n\texport function setPosition(index: number, position: number, isMidpoint: boolean) {\n\t\tif (disabled) return;\n\n\t\tconst markers = toMarkers(gradient);\n\t\tconst active = markers[index];\n\n\t\tif (isMidpoint) active.midpoint = position;\n\t\telse active.position = position;\n\n\t\tmarkers.sort((a, b) => a.position - b.position);\n\t\tif (markers.indexOf(active) !== activeMarkerIndex) {\n\t\t\tactiveMarkerIndex = markers.indexOf(active);\n\t\t\tdispatch(\"activeMarkerIndexChange\", { activeMarkerIndex, activeMarkerIsMidpoint });\n\t\t}\n\n\t\tgradient.position = markers.map((stop) => stop.position);\n\t\tgradient.midpoint = markers.map((stop) => stop.midpoint);\n\t\tgradient.color = markers.map((stop) => stop.color);\n\t\tdispatch(\"gradient\", gradient);\n\t}\n\n\tfunction toMarkers(gradient: GradientStops): { position: number; midpoint: number; color: Color }[] {\n\t\treturn gradient.position.map((position, i) => ({\n\t\t\tposition,\n\t\t\tmidpoint: gradient.midpoint[i],\n\t\t\tcolor: gradient.color[i],\n\t\t}));\n\t}\n\n\tfunction toMidpoints(gradient: GradientStops): number[] {\n\t\tif (gradient.position.length < 2) return [];\n\n\t\treturn gradient.midpoint.slice(0, -1).map((midpoint, i) => {\n\t\t\tconst leftMarker = gradient.position[i];\n\t\t\tconst rightMarker = gradient.position[i + 1];\n\t\t\treturn leftMarker + midpoint * (rightMarker - leftMarker);\n\t\t});\n\t}\n\n\tfunction abortDrag() {\n\t\tif (disabled) return;\n\n\t\tif (activeMarkerIndex !== undefined) {\n\t\t\tif (activeMarkerIsMidpoint && dragRestore !== undefined) {\n\t\t\t\tgradient.midpoint[activeMarkerIndex] = dragRestore;\n\t\t\t\tdispatch(\"gradient\", gradient);\n\t\t\t} else {\n\t\t\t\tif (deletionRestore) deleteStopByIndex(activeMarkerIndex);\n\t\t\t\telse if (dragRestore !== undefined) setPosition(activeMarkerIndex, dragRestore, false);\n\t\t\t}\n\t\t}\n\n\t\tactiveMarkerIndex = activeMarkerIndexRestore;\n\t\tactiveMarkerIsMidpoint = activeMarkerIsMidpointRestore;\n\t\tdispatch(\"activeMarkerIndexChange\", { activeMarkerIndex, activeMarkerIsMidpoint });\n\n\t\tstopDrag();\n\t}\n\n\tfunction stopDrag() {\n\t\tif (disabled) return;\n\n\t\tremoveEvents();\n\n\t\tdragRestore = undefined;\n\t\tdeletionRestore = undefined;\n\t\tactiveMarkerIndexRestore = undefined;\n\t\tactiveMarkerIsMidpointRestore = false;\n\t\tmidpointDragged = false;\n\n\t\tdispatch(\"dragging\", false);\n\t}\n\n\tfunction onPointerMove(e: PointerEvent) {\n\t\tif (disabled) return;\n\n\t\tif (activeMarkerIsMidpoint && activeMarkerIndex !== undefined) moveMidpoint(e, activeMarkerIndex);\n\t\telse if (activeMarkerIndex !== undefined) moveMarker(e, activeMarkerIndex);\n\t}\n\n\tfunction onPointerUp() {\n\t\tif (disabled) return;\n\n\t\tstopDrag();\n\t}\n\n\tfunction onMouseDown(e: MouseEvent) {\n\t\tif (disabled) return;\n\n\t\tconst BUTTONS_RIGHT = 0b0000_0010;\n\t\tif (e.buttons & BUTTONS_RIGHT) abortDrag();\n\t}\n\n\tfunction onKeyDown(e: KeyboardEvent) {\n\t\tif (disabled) return;\n\n\t\tif (e.key === \"Escape\") {\n\t\t\tconst element = markerTrack?.div();\n\t\t\tif (element) preventEscapeClosingParentFloatingMenu(element);\n\n\t\t\tabortDrag();\n\t\t}\n\t}\n\n\tfunction addEvents() {\n\t\tdocument.addEventListener(\"pointermove\", onPointerMove);\n\t\tdocument.addEventListener(\"pointerup\", onPointerUp);\n\t\tdocument.addEventListener(\"mousedown\", onMouseDown);\n\t\tdocument.addEventListener(\"keydown\", onKeyDown);\n\t}\n\n\tfunction removeEvents() {\n\t\tdocument.removeEventListener(\"pointermove\", onPointerMove);\n\t\tdocument.removeEventListener(\"pointerup\", onPointerUp);\n\t\tdocument.removeEventListener(\"mousedown\", onMouseDown);\n\t\tdocument.removeEventListener(\"keydown\", onKeyDown);\n\t}\n\n\tonMount(() => {\n\t\tdocument.addEventListener(\"keydown\", deleteStop);\n\t});\n\tonDestroy(() => {\n\t\tremoveEvents();\n\t\tdocument.removeEventListener(\"keydown\", deleteStop);\n\t});\n\n\t// Future design notes:\n\t//\n\t// # Backend -> Frontend\n\t// Populate(gradient, { position, color }[], active) // The only way indexes get changed. Frontend drops marker if it's being dragged.\n\t// UpdateGradient(gradient)\n\t// UpdateMarkers({ index, position, color }[])\n\t//\n\t// # Frontend -> Backend\n\t// SendNewActive(index)\n\t// SendPositions({ index, position }[])\n\t// AddMarker(position)\n\t// RemoveMarkers(index[])\n\t// ResetMarkerToDefault(index)\n\t//\n\t// We need a way to encode constraints on some markers, like locking them in place or preventing reordering\n\t// We need a way to encode the allowability of adding new markers between certain markers, or preventing the deletion of certain markers\n\t// We need the ability to multi-select markers and move them all at once\n</script>\n\n<LayoutCol\n\tclass=\"spectrum-input\"\n\tclasses={{ disabled }}\n\tstyles={{\n\t\t\"--gradient-start\": ((color) => (color ? colorToHexOptionalAlpha(color) : \"black\"))(gradientFirstColor(gradient)),\n\t\t\"--gradient-end\": ((color) => (color ? colorToHexOptionalAlpha(color) : \"black\"))(gradientLastColor(gradient)),\n\t\t\"--gradient-stops\": gradientToLinearGradientCSS(gradient),\n\t}}\n>\n\t<LayoutRow class=\"gradient-strip\" on:pointerdown={insertStop}></LayoutRow>\n\t<LayoutRow class=\"midpoint-track\">\n\t\t{#each toMidpoints(gradient) as midpoint, index}\n\t\t\t<svg\n\t\t\t\tclass=\"midpoint\"\n\t\t\t\tclass:active={index === activeMarkerIndex && activeMarkerIsMidpoint}\n\t\t\t\tstyle:--midpoint-position={midpoint}\n\t\t\t\ton:pointerdown={(e) => midpointPointerDown(e, index)}\n\t\t\t\ton:dblclick={() => resetMidpoint(index)}\n\t\t\t\tdata-gradient-midpoint\n\t\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\t\tviewBox=\"0 0 8 8\"\n\t\t\t>\n\t\t\t\t<polygon points=\"0,4 4,0 8,4 4,8\" />\n\t\t\t</svg>\n\t\t{/each}\n\t</LayoutRow>\n\t<LayoutRow class=\"marker-track\" bind:this={markerTrack}>\n\t\t{#each toMarkers(gradient) as marker, index}\n\t\t\t<svg\n\t\t\t\tclass=\"marker\"\n\t\t\t\tclass:active={index === activeMarkerIndex && !activeMarkerIsMidpoint}\n\t\t\t\tstyle:--marker-position={marker.position}\n\t\t\t\tstyle:--marker-color={colorToRgbCSS(marker.color)}\n\t\t\t\ton:pointerdown={(e) => markerPointerDown(e, index)}\n\t\t\t\tdata-gradient-marker\n\t\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\t\tviewBox=\"0 0 12 12\"\n\t\t\t>\n\t\t\t\t<path class=\"inner-fill\" d=\"M10,11.5H2c-0.8,0-1.5-0.7-1.5-1.5V6.8c0-0.4,0.2-0.8,0.4-1.1L6,0.7l5.1,5.1c0.3,0.3,0.4,0.7,0.4,1.1V10C11.5,10.8,10.8,11.5,10,11.5z\" />\n\t\t\t\t{#if disabled}\n\t\t\t\t\t<path class=\"disabled-fill\" d=\"M10,11.5H2c-0.8,0-1.5-0.7-1.5-1.5V6.8c0-0.4,0.2-0.8,0.4-1.1L6,0.7l5.1,5.1c0.3,0.3,0.4,0.7,0.4,1.1V10C11.5,10.8,10.8,11.5,10,11.5z\" />\n\t\t\t\t{/if}\n\t\t\t\t<path\n\t\t\t\t\tclass=\"outer-border\"\n\t\t\t\t\td=\"M6,1.4L1.3,6.1C1.1,6.3,1,6.6,1,6.8V10c0,0.6,0.4,1,1,1h8c0.6,0,1-0.4,1-1V6.8c0-0.3-0.1-0.5-0.3-0.7L6,1.4M6,0l5.4,5.4C11.8,5.8,12,6.3,12,6.8V10c0,1.1-0.9,2-2,2H2c-1.1,0-2-0.9-2-2V6.8c0-0.5,0.2-1,0.6-1.4L6,0z\"\n\t\t\t\t/>\n\t\t\t</svg>\n\t\t{/each}\n\t</LayoutRow>\n</LayoutCol>\n\n<style lang=\"scss\" global>\n\t.spectrum-input {\n\t\tposition: relative;\n\t\t--marker-half-width: 6px;\n\n\t\t.gradient-strip {\n\t\t\tflex: 0 0 auto;\n\t\t\theight: 16px;\n\t\t\tbackground-image:\n\t\t\t\tvar(--gradient-stops),\n\t\t\t\t// Solid start/end colors on either side so the gradient begins at the center of a marker\n\t\t\t\tlinear-gradient(var(--gradient-start), var(--gradient-start)),\n\t\t\t\tlinear-gradient(var(--gradient-end), var(--gradient-end)),\n\t\t\t\tvar(--color-transparent-checkered-background);\n\t\t\tbackground-size:\n\t\t\t\tcalc(100% - 2 * var(--marker-half-width)) 100%,\n\t\t\t\t// TODO: Find a solution that avoids visual artifacts where these end colors meet the gradient that appear when viewing with a non-integer zoom or display scaling factor\n\t\t\t\tvar(--marker-half-width) 100%,\n\t\t\t\tvar(--marker-half-width) 100%,\n\t\t\t\tvar(--color-transparent-checkered-background-size);\n\t\t\tbackground-position:\n\t\t\t\tvar(--marker-half-width) 0,\n\t\t\t\tleft 0,\n\t\t\t\tright 0,\n\t\t\t\tvar(--color-transparent-checkered-background-position);\n\t\t\tbackground-repeat: no-repeat, no-repeat, no-repeat, var(--color-transparent-checkered-background-repeat);\n\t\t\tborder-radius: 2px;\n\t\t}\n\n\t\t&.disabled .gradient-strip {\n\t\t\ttransition: opacity 0.1s;\n\n\t\t\t&:hover {\n\t\t\t\topacity: 0.5;\n\t\t\t}\n\t\t}\n\n\t\t.midpoint-track {\n\t\t\tposition: absolute;\n\t\t\ttop: 0;\n\t\t\tleft: var(--marker-half-width);\n\t\t\tright: var(--marker-half-width);\n\n\t\t\t.midpoint {\n\t\t\t\tposition: absolute;\n\t\t\t\tmargin-left: -4px;\n\t\t\t\twidth: 8px;\n\t\t\t\theight: 8px;\n\t\t\t\tbottom: 0;\n\t\t\t\tleft: calc(var(--midpoint-position) * 100%);\n\n\t\t\t\tpolygon {\n\t\t\t\t\tstroke: var(--color-e-nearwhite);\n\t\t\t\t\tfill: var(--color-2-mildblack);\n\t\t\t\t}\n\n\t\t\t\t&.active {\n\t\t\t\t\tz-index: 1;\n\n\t\t\t\t\tpolygon {\n\t\t\t\t\t\tfill: var(--color-e-nearwhite);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t&.disabled .midpoint-track .midpoint polygon {\n\t\t\tstroke: var(--color-4-dimgray);\n\t\t}\n\n\t\t.marker-track {\n\t\t\tmargin-top: calc(24px - 16px - 12px);\n\t\t\tmargin-left: var(--marker-half-width);\n\t\t\twidth: calc(100% - 2 * var(--marker-half-width));\n\t\t\tposition: relative;\n\t\t\tpointer-events: none;\n\n\t\t\t.marker {\n\t\t\t\tposition: absolute;\n\t\t\t\ttransform: translateX(-50%);\n\t\t\t\tleft: calc(var(--marker-position) * 100%);\n\t\t\t\twidth: 12px;\n\t\t\t\theight: 12px;\n\t\t\t\tpointer-events: auto;\n\t\t\t\toverflow: visible;\n\t\t\t\tpadding-top: 12px;\n\t\t\t\tmargin-top: -12px;\n\n\t\t\t\t.inner-fill {\n\t\t\t\t\tfill: var(--marker-color);\n\t\t\t\t}\n\n\t\t\t\t.outer-border {\n\t\t\t\t\tfill: var(--color-5-dullgray);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t&.disabled .marker-track .marker {\n\t\t\t.disabled-fill {\n\t\t\t\topacity: 0.5;\n\t\t\t}\n\n\t\t\t.outer-border {\n\t\t\t\tfill: var(--color-4-dimgray);\n\t\t\t}\n\t\t}\n\n\t\t&:not(.disabled) .marker-track .marker {\n\t\t\t&:not(.active) {\n\t\t\t\t.inner-fill:hover + .outer-border,\n\t\t\t\t.outer-border:hover {\n\t\t\t\t\tfill: var(--color-6-lowergray);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t&.active {\n\t\t\t\tz-index: 1;\n\n\t\t\t\t.inner-fill {\n\t\t\t\t\tfilter: drop-shadow(0 0 1px var(--color-2-mildblack)) drop-shadow(0 0 1px var(--color-2-mildblack));\n\t\t\t\t}\n\n\t\t\t\t// Outer border when active\n\t\t\t\t.outer-border {\n\t\t\t\t\tfill: var(--color-e-nearwhite);\n\t\t\t\t}\n\n\t\t\t\t.inner-fill:hover + .outer-border,\n\t\t\t\t.outer-border:hover {\n\t\t\t\t\tfill: var(--color-f-white);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/widgets/inputs/TextAreaInput.svelte",
    "content": "<script lang=\"ts\">\n\timport { createEventDispatcher } from \"svelte\";\n\n\timport type { ActionShortcut } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\n\timport FieldInput from \"@graphite/components/widgets/inputs/FieldInput.svelte\";\n\n\tconst dispatch = createEventDispatcher<{ commitText: string }>();\n\n\texport let value: string;\n\texport let label: string | undefined = undefined;\n\texport let tooltipLabel: string | undefined = undefined;\n\texport let tooltipDescription: string | undefined = undefined;\n\texport let tooltipShortcut: ActionShortcut | undefined = undefined;\n\texport let disabled = false;\n\n\tlet self: FieldInput | undefined;\n\tlet editing = false;\n\n\tfunction onTextFocused() {\n\t\tediting = true;\n\t}\n\n\t// Called only when `value` is changed from the <textarea> element via user input and committed, either\n\t// via the `change` event or when the <input> element is unfocused (with the `blur` event binding)\n\tfunction onTextChanged() {\n\t\t// The `unFocus()` call in `onTextChangeCanceled()` causes itself to be run again, so this if statement skips a second run\n\t\tif (!editing) return;\n\n\t\tonTextChangeCanceled();\n\n\t\t// TODO: Find a less hacky way to do this\n\t\tif (self) dispatch(\"commitText\", self.getValue());\n\n\t\t// Required if value is not changed by the parent component upon update:value event\n\t\tself?.setInputElementValue(self.getValue());\n\t}\n\n\tfunction onTextChangeCanceled() {\n\t\tediting = false;\n\n\t\tself?.unFocus();\n\t}\n\n\texport function focus() {\n\t\tself?.focus();\n\t}\n</script>\n\n<FieldInput\n\tclass=\"text-area-input\"\n\tclasses={{ \"has-label\": Boolean(label) }}\n\t{value}\n\ton:value\n\ton:textFocused={onTextFocused}\n\ton:textChanged={onTextChanged}\n\ton:textChangeCanceled={onTextChangeCanceled}\n\ttextarea={true}\n\tspellcheck={true}\n\t{label}\n\t{disabled}\n\t{tooltipLabel}\n\t{tooltipDescription}\n\t{tooltipShortcut}\n\tbind:this={self}\n/>\n\n<style lang=\"scss\" global>\n</style>\n"
  },
  {
    "path": "frontend/src/components/widgets/inputs/TextInput.svelte",
    "content": "<script lang=\"ts\">\n\timport { createEventDispatcher } from \"svelte\";\n\n\timport type { ActionShortcut } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\n\timport FieldInput from \"@graphite/components/widgets/inputs/FieldInput.svelte\";\n\n\tconst dispatch = createEventDispatcher<{ commitText: string }>();\n\n\t// Content\n\texport let value: string;\n\texport let label: string | undefined = undefined;\n\texport let placeholder: string | undefined = undefined;\n\texport let disabled = false;\n\t// Styling\n\texport let narrow = false;\n\texport let centered = false;\n\t// Sizing\n\texport let minWidth = 0;\n\texport let maxWidth = 0;\n\t// Tooltips\n\texport let tooltipLabel: string | undefined = undefined;\n\texport let tooltipDescription: string | undefined = undefined;\n\texport let tooltipShortcut: ActionShortcut | undefined = undefined;\n\n\tlet className = \"\";\n\texport { className as class };\n\texport let classes: Record<string, boolean> = {};\n\n\tlet self: FieldInput | undefined;\n\tlet editing = false;\n\n\tfunction onTextFocused() {\n\t\tediting = true;\n\n\t\tself?.selectAllText(value);\n\t}\n\n\t// Called only when `value` is changed from the <input> element via user input and committed, either with the\n\t// enter key (via the `change` event) or when the <input> element is unfocused (with the `blur` event binding)\n\tfunction onTextChanged() {\n\t\t// The `unFocus()` call in `onTextChangeCanceled()` causes itself to be run again, so this if statement skips a second run\n\t\tif (!editing) return;\n\n\t\tonTextChangeCanceled();\n\n\t\t// TODO: Find a less hacky way to do this\n\t\tif (self) dispatch(\"commitText\", self.getValue());\n\n\t\t// Required if value is not changed by the parent component upon update:value event\n\t\tself?.setInputElementValue(self.getValue());\n\t}\n\n\tfunction onTextChangeCanceled() {\n\t\tediting = false;\n\n\t\tself?.unFocus();\n\t}\n\n\texport function focus() {\n\t\tself?.focus();\n\t}\n\n\texport function element(): HTMLInputElement | HTMLTextAreaElement | undefined {\n\t\treturn self?.element();\n\t}\n</script>\n\n<FieldInput\n\tclass={`text-input ${className}`.trim()}\n\tclasses={{ centered, ...classes }}\n\tstyles={{\n\t\t...(minWidth > 0 ? { \"min-width\": `${minWidth}px` } : {}),\n\t\t...(maxWidth > 0 ? { \"max-width\": `${maxWidth}px` } : {}),\n\t}}\n\t{value}\n\ton:value\n\ton:textFocused={onTextFocused}\n\ton:textChanged={onTextChanged}\n\ton:textChangeCanceled={onTextChangeCanceled}\n\tspellcheck={true}\n\t{label}\n\t{disabled}\n\t{narrow}\n\t{tooltipLabel}\n\t{tooltipDescription}\n\t{tooltipShortcut}\n\t{placeholder}\n\tbind:this={self}\n/>\n\n<style lang=\"scss\" global>\n\t.text-input {\n\t\tflex-shrink: 0;\n\n\t\tinput {\n\t\t\ttext-align: left;\n\t\t}\n\n\t\t&.centered {\n\t\t\tinput:not(:focus) {\n\t\t\t\ttext-align: center;\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/widgets/inputs/WorkingColorsInput.svelte",
    "content": "<script lang=\"ts\">\n\timport { getContext } from \"svelte\";\n\n\timport type { Color } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\timport type { Editor } from \"@graphite/editor\";\n\timport { fillChoiceColor, colorToRgbaCSS } from \"@graphite/utility-functions/colors\";\n\n\timport ColorPicker from \"@graphite/components/floating-menus/ColorPicker.svelte\";\n\timport LayoutCol from \"@graphite/components/layout/LayoutCol.svelte\";\n\timport LayoutRow from \"@graphite/components/layout/LayoutRow.svelte\";\n\n\tconst editor = getContext<Editor>(\"editor\");\n\n\t// Content\n\texport let primary: Color;\n\texport let secondary: Color;\n\n\tlet primaryOpen = false;\n\tlet secondaryOpen = false;\n\n\tfunction clickPrimarySwatch() {\n\t\tprimaryOpen = true;\n\t\tsecondaryOpen = false;\n\t}\n\n\tfunction clickSecondarySwatch() {\n\t\tprimaryOpen = false;\n\t\tsecondaryOpen = true;\n\t}\n\n\tfunction primaryColorChanged(color: Color) {\n\t\teditor.handle.updatePrimaryColor(color.red, color.green, color.blue, color.alpha);\n\t}\n\n\tfunction secondaryColorChanged(color: Color) {\n\t\teditor.handle.updateSecondaryColor(color.red, color.green, color.blue, color.alpha);\n\t}\n</script>\n\n<LayoutCol class=\"working-colors-button\">\n\t<LayoutRow class=\"primary swatch\">\n\t\t<button on:click={clickPrimarySwatch} class:open={primaryOpen} style:--swatch-color={colorToRgbaCSS(primary)} data-floating-menu-spawner data-block-hover-transfer tabindex=\"0\"></button>\n\t\t<ColorPicker\n\t\t\topen={primaryOpen}\n\t\t\ton:open={({ detail }) => (primaryOpen = detail)}\n\t\t\tcolorOrGradient={{ Solid: primary }}\n\t\t\ton:colorOrGradient={({ detail }) => {\n\t\t\t\tconst color = fillChoiceColor(detail);\n\t\t\t\tif (color) primaryColorChanged(color);\n\t\t\t}}\n\t\t\tdirection=\"Right\"\n\t\t/>\n\t</LayoutRow>\n\t<LayoutRow class=\"secondary swatch\">\n\t\t<button on:click={clickSecondarySwatch} class:open={secondaryOpen} style:--swatch-color={colorToRgbaCSS(secondary)} data-floating-menu-spawner data-block-hover-transfer tabindex=\"0\"></button>\n\t\t<ColorPicker\n\t\t\topen={secondaryOpen}\n\t\t\ton:open={({ detail }) => (secondaryOpen = detail)}\n\t\t\tcolorOrGradient={{ Solid: secondary }}\n\t\t\ton:colorOrGradient={({ detail }) => {\n\t\t\t\tconst color = fillChoiceColor(detail);\n\t\t\t\tif (color) secondaryColorChanged(color);\n\t\t\t}}\n\t\t\tdirection=\"Right\"\n\t\t/>\n\t</LayoutRow>\n</LayoutCol>\n\n<style lang=\"scss\" global>\n\t.working-colors-button {\n\t\tflex: 0 0 auto;\n\n\t\t.swatch {\n\t\t\twidth: 28px;\n\t\t\theight: 28px;\n\t\t\tmargin: 0 2px;\n\t\t\tposition: relative;\n\n\t\t\t> button {\n\t\t\t\t--swatch-color: #ffffff;\n\t\t\t\t--ring-color: var(--color-5-dullgray);\n\t\t\t\twidth: 100%;\n\t\t\t\theight: 100%;\n\t\t\t\tmargin: 0;\n\t\t\t\tpadding: 0;\n\t\t\t\tborder: none;\n\t\t\t\toutline: none;\n\t\t\t\tborder-radius: 50%;\n\t\t\t\tbox-sizing: border-box;\n\t\t\t\toverflow: hidden;\n\t\t\t\tposition: relative;\n\t\t\t\t// Color of the panel background, used to extend outside the ring and appear to cut out a crescent from the lower circle (by covering it up with the panel background color)\n\t\t\t\tbox-shadow: 0 0 0 2px var(--color-3-darkgray);\n\t\t\t\tbackground: var(--color-3-darkgray);\n\n\t\t\t\t// Main color and checked transparency pattern (inset by 1px to begin inside/below the ring to avoid antialiasing artifacts)\n\t\t\t\t&::before {\n\t\t\t\t\tcontent: \"\";\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\ttop: 1px;\n\t\t\t\t\tbottom: 1px;\n\t\t\t\t\tleft: 1px;\n\t\t\t\t\tright: 1px;\n\t\t\t\t\tborder-radius: 50%;\n\t\t\t\t\tbackground: linear-gradient(var(--swatch-color), var(--swatch-color)), var(--color-transparent-checkered-background);\n\t\t\t\t\tbackground-size:\n\t\t\t\t\t\t100% 100%,\n\t\t\t\t\t\tvar(--color-transparent-checkered-background-size);\n\t\t\t\t\tbackground-position:\n\t\t\t\t\t\t0 0,\n\t\t\t\t\t\tvar(--color-transparent-checkered-background-position-plus-one);\n\t\t\t\t\tbackground-repeat: no-repeat, var(--color-transparent-checkered-background-repeat);\n\t\t\t\t}\n\n\t\t\t\t// Gray ring outline\n\t\t\t\t&::after {\n\t\t\t\t\tcontent: \"\";\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\ttop: 0;\n\t\t\t\t\tbottom: 0;\n\t\t\t\t\tleft: 0;\n\t\t\t\t\tright: 0;\n\t\t\t\t\tborder-radius: 50%;\n\t\t\t\t\tbox-shadow: inset 0 0 0 2px var(--ring-color);\n\t\t\t\t}\n\n\t\t\t\t&:hover,\n\t\t\t\t&.open {\n\t\t\t\t\t--ring-color: var(--color-6-lowergray);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.floating-menu {\n\t\t\t\ttop: 50%;\n\t\t\t\tright: -2px;\n\t\t\t}\n\n\t\t\t&.primary {\n\t\t\t\tmargin-bottom: -8px;\n\t\t\t\tz-index: 1;\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/widgets/labels/IconLabel.svelte",
    "content": "<script lang=\"ts\">\n\timport type { ActionShortcut } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\timport { ICONS, ICON_SVG_STRINGS } from \"@graphite/icons\";\n\timport type { IconName } from \"@graphite/icons\";\n\n\timport LayoutRow from \"@graphite/components/layout/LayoutRow.svelte\";\n\n\tlet className = \"\";\n\texport { className as class };\n\texport let classes: Record<string, boolean> = {};\n\n\texport let iconSizeOverride: number | undefined = undefined;\n\n\t// Content\n\texport let icon: IconName;\n\texport let disabled = false;\n\t// Tooltips\n\texport let tooltipLabel: string | undefined = undefined;\n\texport let tooltipDescription: string | undefined = undefined;\n\texport let tooltipShortcut: ActionShortcut | undefined = undefined;\n\n\t$: iconSizeClass = ((icon: IconName) => {\n\t\tconst iconData = ICONS[icon];\n\t\tif (!iconData) {\n\t\t\t// eslint-disable-next-line no-console\n\t\t\tconsole.warn(`Icon \"${icon}\" does not exist.`);\n\t\t\treturn \"size-24\";\n\t\t}\n\t\tif (iconData.size === undefined) return \"\";\n\t\treturn `size-${iconSizeOverride || iconData.size}`;\n\t})(icon);\n\t$: extraClasses = Object.entries(classes)\n\t\t.flatMap(([className, stateName]) => (stateName ? [className] : []))\n\t\t.join(\" \");\n</script>\n\n<LayoutRow class={`icon-label ${iconSizeClass} ${className} ${extraClasses}`.trim()} classes={{ disabled }} {tooltipLabel} {tooltipDescription} {tooltipShortcut}>\n\t{@html ICON_SVG_STRINGS[icon] || \"�\"}\n</LayoutRow>\n\n<style lang=\"scss\" global>\n\t.icon-label {\n\t\tflex: 0 0 auto;\n\t\tfill: var(--color-e-nearwhite);\n\n\t\t&.disabled {\n\t\t\tfill: var(--color-8-uppergray);\n\t\t}\n\n\t\t&.size-12 {\n\t\t\twidth: 12px;\n\t\t\theight: 12px;\n\t\t}\n\n\t\t&.size-16 {\n\t\t\twidth: 16px;\n\t\t\theight: 16px;\n\t\t}\n\n\t\t&.size-24 {\n\t\t\twidth: 24px;\n\t\t\theight: 24px;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/widgets/labels/ImageLabel.svelte",
    "content": "<script lang=\"ts\">\n\timport type { ActionShortcut } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\n\tlet className = \"\";\n\texport { className as class };\n\texport let classes: Record<string, boolean> = {};\n\n\t// Content\n\texport let url: string;\n\texport let width: string | undefined;\n\texport let height: string | undefined;\n\t// Tooltips\n\texport let tooltipLabel: string | undefined = undefined;\n\texport let tooltipDescription: string | undefined = undefined;\n\texport let tooltipShortcut: ActionShortcut | undefined = undefined;\n\n\t$: extraClasses = Object.entries(classes)\n\t\t.flatMap(([className, stateName]) => (stateName ? [className] : []))\n\t\t.join(\" \");\n</script>\n\n<img\n\tsrc={url}\n\tstyle:width\n\tstyle:height\n\tclass={`image-label ${className} ${extraClasses}`.trim()}\n\tdata-tooltip-label={tooltipLabel}\n\tdata-tooltip-description={tooltipDescription}\n\tdata-tooltip-shortcut={tooltipShortcut?.shortcut ? JSON.stringify(tooltipShortcut.shortcut) : undefined}\n\talt=\"\"\n/>\n\n<style lang=\"scss\" global>\n\t.image-label {\n\t\twidth: auto;\n\t\theight: auto;\n\t\tborder-radius: 2px;\n\t\tbackground-image: var(--color-transparent-checkered-background);\n\t\tbackground-size: var(--color-transparent-checkered-background-size);\n\t\tbackground-position: var(--color-transparent-checkered-background-position);\n\t\tbackground-repeat: var(--color-transparent-checkered-background-repeat);\n\n\t\t+ .image-label.image-label {\n\t\t\tmargin-left: 8px;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/widgets/labels/Separator.svelte",
    "content": "<script lang=\"ts\">\n\timport type { SeparatorDirection, SeparatorStyle } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\n\t// Content\n\texport let direction: SeparatorDirection = \"Horizontal\";\n\texport let style: SeparatorStyle = \"Unrelated\";\n</script>\n\n<div class={`separator ${direction.toLowerCase()} ${style.toLowerCase()}`}>\n\t{#if style === \"Section\"}\n\t\t<div></div>\n\t{/if}\n</div>\n\n<style lang=\"scss\" global>\n\t.separator {\n\t\t&.vertical {\n\t\t\tflex: 0 0 auto;\n\n\t\t\t&.related {\n\t\t\t\theight: 4px;\n\t\t\t}\n\n\t\t\t&.unrelated {\n\t\t\t\theight: 16px;\n\t\t\t}\n\n\t\t\t&.section {\n\t\t\t\t// If changing this, update `--height-of-separator` in `Document.svelte`\n\t\t\t\tmargin: 12px 0;\n\t\t\t\twidth: 100%;\n\n\t\t\t\tdiv {\n\t\t\t\t\tmargin: 0 4px;\n\t\t\t\t\theight: 1px;\n\t\t\t\t\twidth: calc(100% - 8px);\n\t\t\t\t\tbackground: var(--color-5-dullgray);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t&.horizontal {\n\t\t\tflex: 0 0 auto;\n\n\t\t\t&.related {\n\t\t\t\twidth: 4px;\n\t\t\t}\n\n\t\t\t&.unrelated {\n\t\t\t\twidth: 16px;\n\t\t\t}\n\n\t\t\t&.section {\n\t\t\t\tmargin: 0 12px;\n\t\t\t\theight: 100%;\n\n\t\t\t\tdiv {\n\t\t\t\t\tmargin: 4px 0;\n\t\t\t\t\theight: calc(100% - 8px);\n\t\t\t\t\twidth: 1px;\n\t\t\t\t\tbackground: var(--color-5-dullgray);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/widgets/labels/ShortcutLabel.svelte",
    "content": "<script lang=\"ts\">\n\timport type { ActionShortcut, Key, LabeledShortcut, MouseMotion } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\timport type { IconName } from \"@graphite/icons\";\n\timport { operatingSystem } from \"@graphite/utility-functions/platform\";\n\n\timport LayoutRow from \"@graphite/components/layout/LayoutRow.svelte\";\n\timport IconLabel from \"@graphite/components/widgets/labels/IconLabel.svelte\";\n\timport TextLabel from \"@graphite/components/widgets/labels/TextLabel.svelte\";\n\n\t// Content\n\texport let shortcut: ActionShortcut;\n\n\tfunction keyTextOrIconList(keyGroup: LabeledShortcut): ({ label?: string; icon?: IconName }[] | { mouseMotion?: MouseMotion }[])[] {\n\t\tconst list = keyGroup.map((labeledKeyOrMouseMotion): { label?: string; icon?: IconName; mouseMotion?: MouseMotion } => {\n\t\t\t// Use a mouse icon if it's a mouse motion instead of a key\n\t\t\tif (typeof labeledKeyOrMouseMotion === \"string\") return { mouseMotion: labeledKeyOrMouseMotion };\n\n\t\t\t// `key` is the name of the `Key` enum in Rust, while `label` is the localized string to display (if it doesn't become an icon)\n\t\t\tlet key: Key | \"Option\" = labeledKeyOrMouseMotion.key;\n\t\t\tconst label = labeledKeyOrMouseMotion.label;\n\n\t\t\t// Replace Alt and Accel keys with their Mac-specific equivalents\n\t\t\tif (operatingSystem() === \"Mac\") {\n\t\t\t\tif (key === \"Alt\") key = \"Option\";\n\t\t\t\tif (key === \"Accel\") key = \"Command\";\n\t\t\t}\n\n\t\t\t// Either display an icon...\n\t\t\tconst icon = keyboardHintIcon(key);\n\t\t\tif (icon) return { icon };\n\n\t\t\t// ...or display text\n\t\t\treturn { label };\n\t\t});\n\n\t\t// Consolidate consecutive labels into a concatenated single label\n\t\tconst consolidatedList: ReturnType<typeof keyTextOrIconList> = [];\n\t\tlist.forEach((currentItem) => {\n\t\t\tconst lastGroup = consolidatedList.length > 0 ? consolidatedList[consolidatedList.length - 1] : undefined;\n\t\t\tconst lastItem = lastGroup !== undefined ? lastGroup[lastGroup.length - 1] : undefined;\n\n\t\t\t// If current and last are both labels, concatenate both within their existing label\n\t\t\tif (currentItem.label && lastItem && \"label\" in lastItem && lastItem.label) {\n\t\t\t\tlastItem.label += \" \" + currentItem.label;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// If current and last are both of the same group type (both icons/labels, or both mouseMotion), join them within their existing\n\t\t\tif (lastItem && (((currentItem.label || currentItem.icon) && (\"label\" in lastItem || \"icon\" in lastItem)) || (currentItem.mouseMotion && \"mouseMotion\" in lastItem))) {\n\t\t\t\tlastGroup?.push(currentItem);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Otherwise, start a new group with the first item of its group type\n\t\t\tconsolidatedList.push([currentItem]);\n\t\t});\n\t\treturn consolidatedList;\n\t}\n\n\tfunction keyboardHintIcon(input: Key | \"Option\"): IconName | undefined {\n\t\tswitch (input) {\n\t\t\tcase \"ArrowDown\":\n\t\t\t\treturn \"KeyboardArrowDown\";\n\t\t\tcase \"ArrowLeft\":\n\t\t\t\treturn \"KeyboardArrowLeft\";\n\t\t\tcase \"ArrowRight\":\n\t\t\t\treturn \"KeyboardArrowRight\";\n\t\t\tcase \"ArrowUp\":\n\t\t\t\treturn \"KeyboardArrowUp\";\n\t\t\tcase \"Backspace\":\n\t\t\t\treturn \"KeyboardBackspace\";\n\t\t\tcase \"Enter\":\n\t\t\t\treturn \"KeyboardEnter\";\n\t\t\tcase \"Space\":\n\t\t\t\treturn \"KeyboardSpace\";\n\t\t\tcase \"Tab\":\n\t\t\t\treturn \"KeyboardTab\";\n\t\t\tcase \"Command\":\n\t\t\t\treturn operatingSystem() === \"Mac\" ? \"KeyboardCommand\" : undefined;\n\t\t\tcase \"Control\":\n\t\t\t\treturn operatingSystem() === \"Mac\" ? \"KeyboardControl\" : undefined;\n\t\t\tcase \"Option\":\n\t\t\t\treturn operatingSystem() === \"Mac\" ? \"KeyboardOption\" : undefined;\n\t\t\tcase \"Shift\":\n\t\t\t\treturn operatingSystem() === \"Mac\" ? \"KeyboardShift\" : undefined;\n\t\t\tdefault:\n\t\t\t\treturn undefined;\n\t\t}\n\t}\n\n\tfunction mouseHintIcon(input: MouseMotion): IconName {\n\t\treturn {\n\t\t\tNone: \"MouseHintNone\" as const,\n\t\t\tLmb: \"MouseHintLmb\" as const,\n\t\t\tRmb: \"MouseHintRmb\" as const,\n\t\t\tMmb: \"MouseHintMmb\" as const,\n\t\t\tScrollUp: \"MouseHintScrollUp\" as const,\n\t\t\tScrollDown: \"MouseHintScrollDown\" as const,\n\t\t\tDrag: \"MouseHintDrag\" as const,\n\t\t\tLmbDouble: \"MouseHintLmbDouble\" as const,\n\t\t\tLmbDrag: \"MouseHintLmbDrag\" as const,\n\t\t\tRmbDrag: \"MouseHintRmbDrag\" as const,\n\t\t\tRmbDouble: \"MouseHintRmbDouble\" as const,\n\t\t\tMmbDrag: \"MouseHintMmbDrag\" as const,\n\t\t}[input];\n\t}\n</script>\n\n<LayoutRow class=\"shortcut-label\">\n\t{#each keyTextOrIconList(shortcut.shortcut) as group}\n\t\t{#if \"label\" in group[0] || \"icon\" in group[0]}\n\t\t\t<div class=\"key-label\">\n\t\t\t\t{#each group as item}\n\t\t\t\t\t{#if \"label\" in item && item.label}\n\t\t\t\t\t\t<TextLabel>{item.label}</TextLabel>\n\t\t\t\t\t{:else if \"icon\" in item && item.icon}\n\t\t\t\t\t\t<IconLabel icon={item.icon} />\n\t\t\t\t\t{/if}\n\t\t\t\t{/each}\n\t\t\t</div>\n\t\t{/if}\n\t\t{#if \"mouseMotion\" in group[0]}\n\t\t\t{#each group as item}\n\t\t\t\t{#if \"mouseMotion\" in item && item.mouseMotion}\n\t\t\t\t\t<div class=\"mouse-icon\">\n\t\t\t\t\t\t<IconLabel icon={mouseHintIcon(item.mouseMotion)} />\n\t\t\t\t\t</div>\n\t\t\t\t{/if}\n\t\t\t{/each}\n\t\t{/if}\n\t{/each}\n</LayoutRow>\n\n<style lang=\"scss\" global>\n\t.shortcut-label {\n\t\talign-items: center;\n\n\t\t.key-label {\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\theight: 16px;\n\t\t\tpadding: 0 4px;\n\t\t\tborder-radius: 4px;\n\t\t\tbackground: var(--color-3-darkgray);\n\t\t\tcolor: var(--color-b-lightgray);\n\t\t\tfill: var(--color-b-lightgray);\n\n\t\t\t* + * {\n\t\t\t\tmargin-left: 4px;\n\t\t\t}\n\t\t}\n\n\t\tsvg {\n\t\t\tfill: var(--color-b-lightgray);\n\n\t\t\t.dim {\n\t\t\t\tfill: var(--color-8-uppergray);\n\t\t\t}\n\t\t}\n\n\t\t.floating-menu-content .row > & {\n\t\t\t.key-label,\n\t\t\t.mouse-icon {\n\t\t\t\tcolor: var(--color-8-uppergray);\n\t\t\t\tbackground: none;\n\n\t\t\t\t&:first-child {\n\t\t\t\t\tpadding-left: 0;\n\t\t\t\t}\n\n\t\t\t\t&:last-child {\n\t\t\t\t\tpadding-right: 0;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.key-label svg {\n\t\t\t\tfill: var(--color-8-uppergray);\n\t\t\t}\n\n\t\t\t.mouse-icon svg {\n\t\t\t\t// 3 shades brighter than the 8-uppergray of key labels/icons\n\t\t\t\tfill: var(--color-b-lightgray);\n\n\t\t\t\t.dim {\n\t\t\t\t\t// 3 shades darker than the 8-uppergray of key labels/icons\n\t\t\t\t\tfill: var(--color-5-dullgray);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/widgets/labels/TextLabel.svelte",
    "content": "<script lang=\"ts\">\n\timport { onMount, onDestroy } from \"svelte\";\n\n\timport type { ActionShortcut } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\n\tlet className = \"\";\n\texport { className as class };\n\texport let classes: Record<string, boolean> = {};\n\tlet styleName = \"\";\n\texport { styleName as style };\n\texport let styles: Record<string, string | number | undefined> = {};\n\n\t// Content\n\t// `value` is passed via slot\n\texport let disabled = false;\n\texport let forCheckbox: bigint | undefined = undefined;\n\t// Styling\n\texport let narrow = false;\n\texport let bold = false;\n\texport let italic = false;\n\texport let monospace = false;\n\texport let multiline = false;\n\texport let centerAlign = false;\n\texport let tableAlign = false;\n\t// Sizing\n\texport let minWidth = 0;\n\texport let minWidthCharacters = 0;\n\t// Tooltips\n\texport let tooltipLabel: string | undefined = undefined;\n\texport let tooltipDescription: string | undefined = undefined;\n\texport let tooltipShortcut: ActionShortcut | undefined = undefined;\n\n\tlet self: HTMLLabelElement | undefined;\n\n\t$: extraClasses = Object.entries(classes)\n\t\t.flatMap(([className, stateName]) => (stateName ? [className] : []))\n\t\t.join(\" \");\n\t$: extraStyles = Object.entries(styles)\n\t\t.flatMap((styleAndValue) => (styleAndValue[1] !== undefined ? [`${styleAndValue[0]}: ${styleAndValue[1]};`] : []))\n\t\t.join(\" \");\n\n\t$: watchForCheckbox(forCheckbox);\n\n\tfunction watchForCheckbox(forCheckbox: bigint | undefined) {\n\t\tif (!self) return;\n\n\t\tself.removeEventListener(\"pointerenter\", handlePointerEnter);\n\t\tself.removeEventListener(\"pointerleave\", handlePointerLeave);\n\n\t\tif (forCheckbox !== undefined) {\n\t\t\tself.addEventListener(\"pointerenter\", handlePointerEnter);\n\t\t\tself.addEventListener(\"pointerleave\", handlePointerLeave);\n\t\t}\n\t}\n\n\tfunction handlePointerEnter() {\n\t\tdocument.querySelector(`[for=\"checkbox-input-${forCheckbox}\"]`)?.classList.add(\"label-is-hovered\");\n\t}\n\n\tfunction handlePointerLeave() {\n\t\tdocument.querySelector(`[for=\"checkbox-input-${forCheckbox}\"]`)?.classList.remove(\"label-is-hovered\");\n\t}\n\n\tonMount(() => watchForCheckbox(forCheckbox));\n\tonDestroy(() => {\n\t\thandlePointerLeave();\n\t\twatchForCheckbox(undefined);\n\t});\n</script>\n\n<label\n\tclass={`text-label ${className} ${extraClasses}`.trim()}\n\tclass:disabled\n\tclass:narrow\n\tclass:bold\n\tclass:italic\n\tclass:monospace\n\tclass:multiline\n\tclass:center-align={centerAlign}\n\tclass:table-align={tableAlign}\n\tstyle:min-width={minWidthCharacters ? `${minWidthCharacters}ch` : minWidth || undefined}\n\tstyle={`${styleName} ${extraStyles}`.trim() || undefined}\n\tdata-tooltip-label={tooltipLabel}\n\tdata-tooltip-description={tooltipDescription}\n\tdata-tooltip-shortcut={tooltipShortcut?.shortcut ? JSON.stringify(tooltipShortcut.shortcut) : undefined}\n\tfor={forCheckbox !== undefined ? `checkbox-input-${forCheckbox}` : undefined}\n\tbind:this={self}\n>\n\t<slot />\n</label>\n\n<style lang=\"scss\" global>\n\t.text-label {\n\t\tline-height: 18px;\n\t\twhite-space: nowrap;\n\t\t// Force Safari to not draw a text cursor, even though this element has `user-select: none`\n\t\tcursor: default;\n\n\t\t&.narrow.narrow {\n\t\t\t--widget-height: 20px;\n\t\t}\n\n\t\t&.disabled {\n\t\t\tcolor: var(--color-8-uppergray);\n\t\t}\n\n\t\t&.bold {\n\t\t\tfont-weight: 700;\n\t\t}\n\n\t\t&.italic {\n\t\t\tfont-style: italic;\n\t\t}\n\n\t\t&.monospace,\n\t\tcode {\n\t\t\tfont-family: \"Source Code Pro\", monospace;\n\t\t\tfont-size: 12px;\n\t\t}\n\n\t\t&.multiline {\n\t\t\twhite-space: pre-wrap;\n\t\t\tmargin: 4px 0;\n\t\t}\n\n\t\t&.center-align {\n\t\t\ttext-align: center;\n\t\t}\n\n\t\t&.table-align {\n\t\t\tflex: 0 0 30%;\n\t\t\ttext-align: right;\n\t\t}\n\n\t\ta {\n\t\t\tcolor: inherit;\n\t\t}\n\n\t\tcode {\n\t\t\tbackground: var(--color-3-darkgray);\n\t\t\tpadding: 0 2px;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/window/MainWindow.svelte",
    "content": "<script lang=\"ts\">\n\timport { getContext } from \"svelte\";\n\n\timport { isPlatformNative } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\timport type { AppWindowStore } from \"@graphite/stores/app-window\";\n\timport type { DialogStore } from \"@graphite/stores/dialog\";\n\timport type { TooltipStore } from \"@graphite/stores/tooltip\";\n\n\timport Dialog from \"@graphite/components/floating-menus/Dialog.svelte\";\n\timport Tooltip from \"@graphite/components/floating-menus/Tooltip.svelte\";\n\timport LayoutCol from \"@graphite/components/layout/LayoutCol.svelte\";\n\timport TextLabel from \"@graphite/components/widgets/labels/TextLabel.svelte\";\n\timport StatusBar from \"@graphite/components/window/StatusBar.svelte\";\n\timport TitleBar from \"@graphite/components/window/TitleBar.svelte\";\n\timport Workspace from \"@graphite/components/window/Workspace.svelte\";\n\n\tconst dialog = getContext<DialogStore>(\"dialog\");\n\tconst tooltip = getContext<TooltipStore>(\"tooltip\");\n\tconst appWindow = getContext<AppWindowStore>(\"appWindow\");\n</script>\n\n<LayoutCol class=\"main-window\" classes={{ \"viewport-hole-punch\": $appWindow.viewportHolePunch }}>\n\t{#if !($appWindow.platform == \"Mac\" && $appWindow.fullscreen)}\n\t\t<TitleBar />\n\t{/if}\n\t<Workspace />\n\t<StatusBar />\n\t{#if $dialog.visible}\n\t\t<Dialog />\n\t{/if}\n\t{#if $tooltip.visible}\n\t\t<Tooltip />\n\t{/if}\n\t{#if isPlatformNative() && new Date() > new Date(\"2026-04-30\")}\n\t\t<LayoutCol class=\"release-candidate-expiry\">\n\t\t\t<TextLabel>\n\t\t\t\t<p>\n\t\t\t\t\tThis is an outdated desktop release candidate build. Its testing<br />\n\t\t\t\t\tperiod has concluded and the next build is available for download.<br />\n\t\t\t\t\tPlease update to help us continue testing by reporting new issues.\n\t\t\t\t</p>\n\t\t\t</TextLabel>\n\t\t</LayoutCol>\n\t{/if}\n</LayoutCol>\n\n<style lang=\"scss\" global>\n\t.main-window {\n\t\theight: 100%;\n\t\toverflow: auto;\n\t\ttouch-action: none;\n\t}\n\n\t.release-candidate-expiry {\n\t\tposition: absolute;\n\t\ttop: 50%;\n\t\tleft: 50%;\n\t\ttransform: translate(-50%, -50%);\n\t\tbackground-color: var(--color-e-nearwhite);\n\t\tcolor: var(--color-2-mildblack);\n\t\topacity: 0.9;\n\t\tpointer-events: none;\n\t\tpadding: 12px 40px;\n\t\tborder-radius: 4px;\n\t\ttext-align-last: justify;\n\t\tfont-size: 18px;\n\t\tz-index: 1000;\n\n\t\t.text-label {\n\t\t\tline-height: 1.5;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/window/Panel.svelte",
    "content": "<script lang=\"ts\">\n\timport { getContext, tick } from \"svelte\";\n\n\timport type { Editor } from \"@graphite/editor\";\n\n\timport LayoutCol from \"@graphite/components/layout/LayoutCol.svelte\";\n\timport LayoutRow from \"@graphite/components/layout/LayoutRow.svelte\";\n\timport Data from \"@graphite/components/panels/Data.svelte\";\n\timport Document from \"@graphite/components/panels/Document.svelte\";\n\timport Layers from \"@graphite/components/panels/Layers.svelte\";\n\timport Properties from \"@graphite/components/panels/Properties.svelte\";\n\timport Welcome from \"@graphite/components/panels/Welcome.svelte\";\n\timport IconButton from \"@graphite/components/widgets/buttons/IconButton.svelte\";\n\timport TextLabel from \"@graphite/components/widgets/labels/TextLabel.svelte\";\n\n\ttype PanelType = keyof typeof PANEL_COMPONENTS;\n\n\tconst PANEL_COMPONENTS = {\n\t\tWelcome,\n\t\tDocument,\n\t\tLayers,\n\t\tProperties,\n\t\tData,\n\t};\n\tconst BUTTON_LEFT = 0;\n\tconst BUTTON_MIDDLE = 1;\n\n\tconst editor = getContext<Editor>(\"editor\");\n\n\texport let tabMinWidths = false;\n\texport let tabCloseButtons = false;\n\texport let tabLabels: { name: string; unsaved?: boolean; tooltipLabel?: string; tooltipDescription?: string; tooltipShortcut?: string }[];\n\texport let tabActiveIndex: number;\n\texport let panelType: PanelType | undefined = undefined;\n\texport let clickAction: ((index: number) => void) | undefined = undefined;\n\texport let closeAction: ((index: number) => void) | undefined = undefined;\n\texport let emptySpaceAction: (() => void) | undefined = undefined;\n\n\tlet className = \"\";\n\texport { className as class };\n\texport let classes: Record<string, boolean> = {};\n\tlet styleName = \"\";\n\texport { styleName as style };\n\texport let styles: Record<string, string | number | undefined> = {};\n\n\tlet tabElements: (LayoutRow | undefined)[] = [];\n\n\tfunction onEmptySpaceAction(e: MouseEvent) {\n\t\tif (e.target !== e.currentTarget) return;\n\t\tif (e.button === BUTTON_MIDDLE || (e.button === BUTTON_LEFT && e.detail === 2)) emptySpaceAction?.();\n\t}\n\n\texport async function scrollTabIntoView(newIndex: number) {\n\t\tawait tick();\n\t\ttabElements[newIndex]?.div?.()?.scrollIntoView();\n\t}\n</script>\n\n<LayoutCol on:pointerdown={() => panelType && editor.handle.setActivePanel(panelType)} class={`panel ${className}`.trim()} {classes} style={styleName} {styles}>\n\t<LayoutRow class=\"tab-bar\" classes={{ \"min-widths\": tabMinWidths }}>\n\t\t<LayoutRow class=\"tab-group\" scrollableX={true} on:click={onEmptySpaceAction} on:auxclick={onEmptySpaceAction}>\n\t\t\t{#each tabLabels as tabLabel, tabIndex}\n\t\t\t\t<LayoutRow\n\t\t\t\t\tclass=\"tab\"\n\t\t\t\t\tclasses={{ active: tabIndex === tabActiveIndex }}\n\t\t\t\t\ttooltipLabel={tabLabel.tooltipLabel}\n\t\t\t\t\ttooltipDescription={tabLabel.tooltipDescription}\n\t\t\t\t\ton:click={(e) => {\n\t\t\t\t\t\te.stopPropagation();\n\t\t\t\t\t\tclickAction?.(tabIndex);\n\t\t\t\t\t}}\n\t\t\t\t\ton:auxclick={(e) => {\n\t\t\t\t\t\t// Middle mouse button click\n\t\t\t\t\t\tif (e.button === BUTTON_MIDDLE) {\n\t\t\t\t\t\t\te.stopPropagation();\n\t\t\t\t\t\t\tcloseAction?.(tabIndex);\n\t\t\t\t\t\t}\n\t\t\t\t\t}}\n\t\t\t\t\tbind:this={tabElements[tabIndex]}\n\t\t\t\t>\n\t\t\t\t\t<LayoutRow class=\"name\">\n\t\t\t\t\t\t<TextLabel class=\"text\">{tabLabel.name}</TextLabel>\n\t\t\t\t\t\t{#if tabLabel.unsaved}\n\t\t\t\t\t\t\t<TextLabel>*</TextLabel>\n\t\t\t\t\t\t{/if}\n\t\t\t\t\t</LayoutRow>\n\t\t\t\t\t{#if tabCloseButtons}\n\t\t\t\t\t\t<IconButton\n\t\t\t\t\t\t\taction={(e) => {\n\t\t\t\t\t\t\t\te?.stopPropagation();\n\t\t\t\t\t\t\t\tcloseAction?.(tabIndex);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\ticon=\"CloseX\"\n\t\t\t\t\t\t\tsize={16}\n\t\t\t\t\t\t/>\n\t\t\t\t\t{/if}\n\t\t\t\t</LayoutRow>\n\t\t\t{/each}\n\t\t</LayoutRow>\n\t</LayoutRow>\n\t<LayoutCol class=\"panel-body\">\n\t\t{#if panelType}\n\t\t\t<svelte:component this={PANEL_COMPONENTS[panelType]} />\n\t\t{/if}\n\t</LayoutCol>\n</LayoutCol>\n\n<style lang=\"scss\" global>\n\t.panel {\n\t\tbackground: var(--color-1-nearblack);\n\t\tborder-radius: 6px;\n\t\toverflow: hidden;\n\n\t\t.tab-bar {\n\t\t\theight: 28px;\n\t\t\tmin-height: auto;\n\t\t\tbackground: var(--color-1-nearblack); // Needed for the viewport hole punch on desktop\n\t\t\tflex-shrink: 0;\n\n\t\t\t&.min-widths .tab-group .tab {\n\t\t\t\tmin-width: 120px;\n\t\t\t\tmax-width: 360px;\n\t\t\t}\n\n\t\t\t.tab-group {\n\t\t\t\tflex: 1 1 100%;\n\t\t\t\tposition: relative;\n\n\t\t\t\t// This always hangs out at the end of the last tab, providing 16px (15px plus the 1px reserved for the separator line) to the right of the tabs.\n\t\t\t\t// When the last tab is selected, its bottom rounded fillet adds 16px to the width, which stretches the scrollbar width allocation in only that situation.\n\t\t\t\t// This pseudo-element ensures we always reserve that space to prevent the scrollbar from jumping when the last tab is selected.\n\t\t\t\t// There is unfortunately no apparent way to remove that 16px gap from the end of the scroll container, since negative margin does not reduce the scrollbar allocation.\n\t\t\t\t&::after {\n\t\t\t\t\tcontent: \"\";\n\t\t\t\t\twidth: 15px;\n\t\t\t\t\tflex: 0 0 auto;\n\t\t\t\t}\n\n\t\t\t\t.tab {\n\t\t\t\t\tflex: 0 1 auto;\n\t\t\t\t\theight: 28px;\n\t\t\t\t\tpadding: 0 8px;\n\t\t\t\t\talign-items: center;\n\t\t\t\t\tposition: relative;\n\n\t\t\t\t\t&.active {\n\t\t\t\t\t\tbackground: var(--color-3-darkgray);\n\t\t\t\t\t\tborder-radius: 6px 6px 0 0;\n\t\t\t\t\t\tposition: relative;\n\n\t\t\t\t\t\t&:not(:first-child)::before,\n\t\t\t\t\t\t&::after {\n\t\t\t\t\t\t\tcontent: \"\";\n\t\t\t\t\t\t\twidth: 16px;\n\t\t\t\t\t\t\theight: 8px;\n\t\t\t\t\t\t\tposition: absolute;\n\t\t\t\t\t\t\tbottom: 0;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t&:not(:first-child)::before {\n\t\t\t\t\t\t\tleft: -16px;\n\t\t\t\t\t\t\tborder-bottom-right-radius: 8px;\n\t\t\t\t\t\t\tbox-shadow: 8px 0 0 0 var(--color-3-darkgray);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t&::after {\n\t\t\t\t\t\t\tright: -16px;\n\t\t\t\t\t\t\tborder-bottom-left-radius: 8px;\n\t\t\t\t\t\t\tbox-shadow: -8px 0 0 0 var(--color-3-darkgray);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t.name {\n\t\t\t\t\t\tflex: 1 1 100%;\n\n\t\t\t\t\t\t.text-label {\n\t\t\t\t\t\t\t// Height and line-height required because https://stackoverflow.com/a/21611191/775283\n\t\t\t\t\t\t\theight: 28px;\n\t\t\t\t\t\t\tline-height: 28px;\n\t\t\t\t\t\t\tflex: 0 0 auto;\n\n\t\t\t\t\t\t\t&.text {\n\t\t\t\t\t\t\t\toverflow-x: hidden;\n\t\t\t\t\t\t\t\twhite-space: nowrap;\n\t\t\t\t\t\t\t\ttext-overflow: ellipsis;\n\t\t\t\t\t\t\t\tflex-shrink: 1;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t.icon-button {\n\t\t\t\t\t\tmargin-left: 8px;\n\t\t\t\t\t}\n\n\t\t\t\t\t& + .tab {\n\t\t\t\t\t\tmargin-left: 1px;\n\t\t\t\t\t}\n\n\t\t\t\t\t&:not(.active) + .tab:not(.active)::before {\n\t\t\t\t\t\tcontent: \"\";\n\t\t\t\t\t\tposition: absolute;\n\t\t\t\t\t\tleft: -1px;\n\t\t\t\t\t\twidth: 1px;\n\t\t\t\t\t\theight: 16px;\n\t\t\t\t\t\tbackground: var(--color-5-dullgray);\n\t\t\t\t\t}\n\n\t\t\t\t\t&:last-of-type {\n\t\t\t\t\t\tmargin-right: 1px;\n\n\t\t\t\t\t\t&:not(.active)::after {\n\t\t\t\t\t\t\tcontent: \"\";\n\t\t\t\t\t\t\tposition: absolute;\n\t\t\t\t\t\t\tright: -1px;\n\t\t\t\t\t\t\twidth: 1px;\n\t\t\t\t\t\t\theight: 16px;\n\t\t\t\t\t\t\tbackground: var(--color-5-dullgray);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t.panel-body {\n\t\t\tbackground: var(--color-3-darkgray);\n\t\t\tflex: 1 1 100%;\n\t\t\tflex-direction: column;\n\n\t\t\t> div {\n\t\t\t\tpadding-bottom: 4px;\n\t\t\t}\n\t\t}\n\n\t\t// Needed for the viewport hole punch on desktop\n\t\t.viewport-hole-punch &.document-panel,\n\t\t.viewport-hole-punch &.document-panel .panel-body:not(:has(.welcome-panel)) {\n\t\t\tbackground: none;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/window/StatusBar.svelte",
    "content": "<script lang=\"ts\">\n\timport { getContext, onMount, onDestroy } from \"svelte\";\n\n\timport type { Layout } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\timport type { Editor } from \"@graphite/editor\";\n\timport { patchLayout } from \"@graphite/utility-functions/widgets\";\n\n\timport LayoutRow from \"@graphite/components/layout/LayoutRow.svelte\";\n\timport Separator from \"@graphite/components/widgets/labels/Separator.svelte\";\n\timport WidgetLayout from \"@graphite/components/widgets/WidgetLayout.svelte\";\n\n\tconst editor = getContext<Editor>(\"editor\");\n\n\tlet statusBarHintsLayout: Layout = [];\n\tlet statusBarInfoLayout: Layout = [];\n\n\tonMount(() => {\n\t\teditor.subscriptions.subscribeLayoutUpdate(\"StatusBarHints\", (data) => {\n\t\t\tpatchLayout(statusBarHintsLayout, data);\n\t\t\tstatusBarHintsLayout = statusBarHintsLayout;\n\t\t});\n\t\teditor.subscriptions.subscribeLayoutUpdate(\"StatusBarInfo\", (data) => {\n\t\t\tpatchLayout(statusBarInfoLayout, data);\n\t\t\tstatusBarInfoLayout = statusBarInfoLayout;\n\t\t});\n\t});\n\n\tonDestroy(() => {\n\t\teditor.subscriptions.unsubscribeLayoutUpdate(\"StatusBarHints\");\n\t\teditor.subscriptions.unsubscribeLayoutUpdate(\"StatusBarInfo\");\n\t});\n</script>\n\n<LayoutRow class=\"status-bar\">\n\t<WidgetLayout class=\"hints\" layout={statusBarHintsLayout} layoutTarget=\"StatusBarHints\" />\n\t<Separator />\n\t<WidgetLayout class=\"info\" layout={statusBarInfoLayout} layoutTarget=\"StatusBarInfo\" />\n</LayoutRow>\n\n<style lang=\"scss\" global>\n\t.status-bar {\n\t\theight: 24px;\n\t\twidth: 100%;\n\t\tflex: 0 0 auto;\n\t\tjustify-content: space-between;\n\n\t\t.hints {\n\t\t\toverflow: hidden;\n\t\t\t--row-height: 24px;\n\t\t\tmargin: 0 4px;\n\t\t\tmax-width: calc(100% - 2 * 4px);\n\t\t\tflex: 1 1 auto;\n\n\t\t\t> * {\n\t\t\t\tflex: 0 0 auto;\n\t\t\t}\n\n\t\t\t.text-label,\n\t\t\t.shortcut-label {\n\t\t\t\talign-items: center;\n\t\t\t\tflex-shrink: 0;\n\n\t\t\t\t+ .text-label,\n\t\t\t\t+ .shortcut-label {\n\t\t\t\t\tmargin-left: 4px;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.text-label:not(.bold) + .shortcut-label {\n\t\t\t\tmargin-left: 12px;\n\t\t\t}\n\n\t\t\t.text-label.bold {\n\t\t\t\tpadding: 0 4px;\n\t\t\t}\n\t\t}\n\n\t\t.hints + .separator {\n\t\t\tposition: relative;\n\n\t\t\t&::before {\n\t\t\t\tcontent: \"\";\n\t\t\t\tposition: absolute;\n\t\t\t\ttop: 0;\n\t\t\t\tbottom: 0;\n\t\t\t\tleft: -40px;\n\t\t\t\twidth: 40px;\n\t\t\t\tbackground: linear-gradient(to right, rgba(var(--color-2-mildblack-rgb), 0) 0%, rgba(var(--color-2-mildblack-rgb), 1) 100%);\n\t\t\t}\n\t\t}\n\n\t\t.info {\n\t\t\tmargin: 0 4px;\n\t\t\t--row-height: 24px;\n\t\t\tjustify-content: flex-end;\n\n\t\t\t.text-label {\n\t\t\t\talign-items: center;\n\t\t\t\tflex-shrink: 0;\n\n\t\t\t\t+ .text-label {\n\t\t\t\t\tmargin-left: 4px;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/components/window/TitleBar.svelte",
    "content": "<script lang=\"ts\">\n\timport { getContext, onMount, onDestroy } from \"svelte\";\n\n\timport { isPlatformNative } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\timport type { Layout } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\timport type { Editor } from \"@graphite/editor\";\n\timport type { AppWindowStore } from \"@graphite/stores/app-window\";\n\timport { enterFullscreen, exitFullscreen } from \"@graphite/stores/fullscreen\";\n\timport type { FullscreenStore } from \"@graphite/stores/fullscreen\";\n\timport type { TooltipStore } from \"@graphite/stores/tooltip\";\n\timport { patchLayout } from \"@graphite/utility-functions/widgets\";\n\n\timport LayoutRow from \"@graphite/components/layout/LayoutRow.svelte\";\n\timport IconLabel from \"@graphite/components/widgets/labels/IconLabel.svelte\";\n\timport WidgetLayout from \"@graphite/components/widgets/WidgetLayout.svelte\";\n\n\tconst keyboardLockApiSupported = navigator.keyboard !== undefined && \"lock\" in navigator.keyboard;\n\n\tconst appWindow = getContext<AppWindowStore>(\"appWindow\");\n\tconst editor = getContext<Editor>(\"editor\");\n\tconst fullscreen = getContext<FullscreenStore>(\"fullscreen\");\n\tconst tooltip = getContext<TooltipStore>(\"tooltip\");\n\n\tlet menuBarLayout: Layout = [];\n\n\t$: showFullscreenButton = $appWindow.platform === \"Web\" || $fullscreen.windowFullscreen || (isPlatformNative() && $appWindow.fullscreen);\n\t$: isFullscreen = isPlatformNative() ? $appWindow.fullscreen : $fullscreen.windowFullscreen;\n\t// On Mac, the menu bar height needs to be scaled by the inverse of the UI scale to fit its native window buttons\n\t$: height = $appWindow.platform === \"Mac\" ? 28 * (1 / $appWindow.uiScale) : 28;\n\n\tonMount(() => {\n\t\teditor.subscriptions.subscribeLayoutUpdate(\"MenuBar\", (data) => {\n\t\t\tpatchLayout(menuBarLayout, data);\n\t\t\tmenuBarLayout = menuBarLayout;\n\t\t});\n\t});\n\n\tonDestroy(() => {\n\t\teditor.subscriptions.unsubscribeLayoutUpdate(\"MenuBar\");\n\t});\n</script>\n\n<LayoutRow class=\"title-bar\" styles={{ height: height + \"px\" }}>\n\t<!-- Menu bar -->\n\t<LayoutRow class=\"menu-bar\">\n\t\t{#if $appWindow.platform !== \"Mac\"}\n\t\t\t<WidgetLayout layout={menuBarLayout} layoutTarget=\"MenuBar\" />\n\t\t{/if}\n\t</LayoutRow>\n\t<!-- Window frame -->\n\t<LayoutRow class=\"window-frame\" on:mousedown={() => !isFullscreen && editor.handle.appWindowDrag()} on:dblclick={() => !isFullscreen && editor.handle.appWindowMaximize()} />\n\t<!-- Window buttons -->\n\t<LayoutRow class=\"window-buttons\" classes={{ fullscreen: showFullscreenButton, windows: $appWindow.platform === \"Windows\", linux: $appWindow.platform === \"Linux\" }}>\n\t\t{#if $appWindow.platform !== \"Mac\"}\n\t\t\t{#if showFullscreenButton}\n\t\t\t\t<LayoutRow\n\t\t\t\t\ttooltipLabel={isFullscreen ? \"Exit Fullscreen\" : \"Enter Fullscreen\"}\n\t\t\t\t\ttooltipDescription={$appWindow.platform === \"Web\" && keyboardLockApiSupported\n\t\t\t\t\t\t? \"While fullscreen, keyboard shortcuts normally reserved by the browser become available.\"\n\t\t\t\t\t\t: undefined}\n\t\t\t\t\ttooltipShortcut={$tooltip.fullscreenShortcut}\n\t\t\t\t\ton:click={() => {\n\t\t\t\t\t\tif (isPlatformNative()) editor.handle.appWindowFullscreen();\n\t\t\t\t\t\telse ($fullscreen.windowFullscreen ? exitFullscreen : enterFullscreen)();\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t<IconLabel icon={isFullscreen ? \"FullscreenExit\" : \"FullscreenEnter\"} />\n\t\t\t\t</LayoutRow>\n\t\t\t{:else}\n\t\t\t\t<LayoutRow tooltipLabel=\"Minimize\" on:click={() => editor.handle.appWindowMinimize()}>\n\t\t\t\t\t<IconLabel icon=\"WindowButtonWinMinimize\" />\n\t\t\t\t</LayoutRow>\n\t\t\t\t<LayoutRow tooltipLabel={$appWindow.maximized ? ($appWindow.platform === \"Windows\" ? \"Restore Down\" : \"Unmaximize\") : \"Maximize\"} on:click={() => editor.handle.appWindowMaximize()}>\n\t\t\t\t\t<IconLabel icon={$appWindow.maximized ? \"WindowButtonWinRestoreDown\" : \"WindowButtonWinMaximize\"} />\n\t\t\t\t</LayoutRow>\n\t\t\t\t<LayoutRow tooltipLabel=\"Close\" on:click={() => editor.handle.appWindowClose()}>\n\t\t\t\t\t<IconLabel icon=\"WindowButtonWinClose\" />\n\t\t\t\t</LayoutRow>\n\t\t\t{/if}\n\t\t{/if}\n\t</LayoutRow>\n</LayoutRow>\n\n<style lang=\"scss\" global>\n\t.title-bar {\n\t\tflex: 0 0 auto;\n\n\t\t> .layout-row {\n\t\t\tflex: 0 0 auto;\n\n\t\t\t> .widget-span {\n\t\t\t\t--row-height: 28px;\n\n\t\t\t\t> * {\n\t\t\t\t\t--widget-height: 28px;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t&.window-frame {\n\t\t\t\tflex: 1 1 100%;\n\t\t\t}\n\n\t\t\t.text-button {\n\t\t\t\theight: 100%;\n\t\t\t}\n\t\t}\n\n\t\t.window-buttons {\n\t\t\t> .layout-row {\n\t\t\t\tflex: 0 0 auto;\n\t\t\t\talign-items: center;\n\n\t\t\t\tsvg {\n\t\t\t\t\tfill: var(--color-e-nearwhite);\n\t\t\t\t}\n\n\t\t\t\t&:hover {\n\t\t\t\t\tbackground: var(--color-6-lowergray);\n\n\t\t\t\t\tsvg {\n\t\t\t\t\t\tfill: var(--color-f-white);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t&.fullscreen > .layout-row {\n\t\t\t\tpadding: 0 8px;\n\t\t\t}\n\n\t\t\t&.windows:not(.fullscreen) > .layout-row {\n\t\t\t\tpadding: 0 17px;\n\n\t\t\t\t&:hover {\n\t\t\t\t\tbackground: #2d2d2d;\n\t\t\t\t}\n\n\t\t\t\t&:last-of-type:hover {\n\t\t\t\t\tbackground: #c42b1c;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t&.linux:not(.fullscreen) > .layout-row {\n\t\t\t\tpadding: 0 12px;\n\n\t\t\t\t&:hover {\n\t\t\t\t\tborder-radius: 2px;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// paddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpadding\n</style>\n"
  },
  {
    "path": "frontend/src/components/window/Workspace.svelte",
    "content": "<script lang=\"ts\">\n\timport { getContext, onDestroy } from \"svelte\";\n\n\timport type { OpenDocument } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\timport type { Editor } from \"@graphite/editor\";\n\timport type { PortfolioStore } from \"@graphite/stores/portfolio\";\n\n\timport LayoutCol from \"@graphite/components/layout/LayoutCol.svelte\";\n\timport LayoutRow from \"@graphite/components/layout/LayoutRow.svelte\";\n\timport Panel from \"@graphite/components/window/Panel.svelte\";\n\n\tconst MIN_PANEL_SIZE = 100;\n\tconst PANEL_SIZES = {\n\t\t/**/ root: 100,\n\t\t/*   ├─ */ content: 80,\n\t\t/*   │     ├─ */ document: 70,\n\t\t/*   │     └─ */ data: 30,\n\t\t/*   └─ */ details: 20,\n\t\t/*         ├─ */ properties: 45,\n\t\t/*         └─ */ layers: 55,\n\t} as const;\n\n\tlet panelSizes: Record<string, number> = PANEL_SIZES;\n\tlet documentPanel: Panel | undefined;\n\tlet gutterResizeRestore: [number, number] | undefined = undefined;\n\tlet pointerCaptureId: number | undefined = undefined;\n\tlet activeResizeCleanup: (() => void) | undefined = undefined;\n\n\tonDestroy(() => {\n\t\tactiveResizeCleanup?.();\n\t});\n\n\t$: documentPanel?.scrollTabIntoView($portfolio.activeDocumentIndex);\n\n\t$: documentTabLabels = $portfolio.documents.map((doc: OpenDocument) => {\n\t\tconst name = doc.details.name;\n\t\tconst unsaved = !doc.details.isSaved;\n\t\tif (!editor.handle.inDevelopmentMode()) return { name, unsaved };\n\n\t\tconst tooltipDescription = `Document ID: ${doc.id}`;\n\t\treturn { name, unsaved, tooltipLabel: name, tooltipDescription };\n\t});\n\n\tconst editor = getContext<Editor>(\"editor\");\n\tconst portfolio = getContext<PortfolioStore>(\"portfolio\");\n\n\tfunction resizePanel(e: PointerEvent) {\n\t\tconst gutter = e.target;\n\t\tif (!(gutter instanceof HTMLDivElement)) return;\n\n\t\tconst nextSibling = gutter.nextElementSibling;\n\t\tconst prevSibling = gutter.previousElementSibling;\n\n\t\tconst parentElement = gutter.parentElement;\n\t\tif (!(nextSibling instanceof HTMLDivElement) || !(prevSibling instanceof HTMLDivElement) || !(parentElement instanceof HTMLDivElement)) return;\n\n\t\tconst nextSiblingName = nextSibling.getAttribute(\"data-subdivision-name\") || undefined;\n\t\tconst prevSiblingName = prevSibling.getAttribute(\"data-subdivision-name\") || undefined;\n\n\t\tif (!nextSiblingName || !prevSiblingName || !(nextSiblingName in PANEL_SIZES) || !(prevSiblingName in PANEL_SIZES)) return;\n\n\t\t// Are we resizing horizontally?\n\t\tconst isHorizontal = gutter.getAttribute(\"data-gutter-horizontal\") !== null;\n\n\t\t// Get the current size in px of the panels being resized and the gutter\n\t\tconst gutterSize = isHorizontal ? gutter.getBoundingClientRect().width : gutter.getBoundingClientRect().height;\n\t\tconst nextSiblingSize = isHorizontal ? nextSibling.getBoundingClientRect().width : nextSibling.getBoundingClientRect().height;\n\t\tconst prevSiblingSize = isHorizontal ? prevSibling.getBoundingClientRect().width : prevSibling.getBoundingClientRect().height;\n\t\tconst parentElementSize = isHorizontal ? parentElement.getBoundingClientRect().width : parentElement.getBoundingClientRect().height;\n\n\t\t// Measure the resizing panels as a percentage of all sibling panels\n\t\tconst totalResizingSpaceOccupied = gutterSize + nextSiblingSize + prevSiblingSize;\n\t\tconst proportionBeingResized = totalResizingSpaceOccupied / parentElementSize;\n\n\t\t// Prevent cursor flicker as mouse temporarily leaves the gutter\n\t\tpointerCaptureId = e.pointerId;\n\t\tgutter.setPointerCapture(pointerCaptureId);\n\n\t\tconst mouseStart = isHorizontal ? e.clientX : e.clientY;\n\n\t\tconst abortResize = () => {\n\t\t\tif (pointerCaptureId) gutter.releasePointerCapture(pointerCaptureId);\n\t\t\tremoveListeners();\n\t\t\tactiveResizeCleanup = undefined;\n\n\t\t\tpointerCaptureId = e.pointerId;\n\t\t\tgutter.setPointerCapture(pointerCaptureId);\n\n\t\t\tif (gutterResizeRestore !== undefined) {\n\t\t\t\tpanelSizes[nextSiblingName] = gutterResizeRestore[0];\n\t\t\t\tpanelSizes[prevSiblingName] = gutterResizeRestore[1];\n\t\t\t\tgutterResizeRestore = undefined;\n\t\t\t}\n\t\t};\n\n\t\tconst onPointerMove = (e: PointerEvent) => {\n\t\t\tconst mouseCurrent = isHorizontal ? e.clientX : e.clientY;\n\t\t\tlet mouseDelta = mouseStart - mouseCurrent;\n\n\t\t\tmouseDelta = Math.max(nextSiblingSize + mouseDelta, MIN_PANEL_SIZE) - nextSiblingSize;\n\t\t\tmouseDelta = prevSiblingSize - Math.max(prevSiblingSize - mouseDelta, MIN_PANEL_SIZE);\n\n\t\t\tif (gutterResizeRestore === undefined) gutterResizeRestore = [panelSizes[nextSiblingName], panelSizes[prevSiblingName]];\n\n\t\t\tpanelSizes[nextSiblingName] = ((nextSiblingSize + mouseDelta) / totalResizingSpaceOccupied) * proportionBeingResized * 100;\n\t\t\tpanelSizes[prevSiblingName] = ((prevSiblingSize - mouseDelta) / totalResizingSpaceOccupied) * proportionBeingResized * 100;\n\t\t};\n\n\t\tconst onPointerUp = () => {\n\t\t\tgutterResizeRestore = undefined;\n\t\t\tif (pointerCaptureId) gutter.releasePointerCapture(pointerCaptureId);\n\t\t\tremoveListeners();\n\t\t\tactiveResizeCleanup = undefined;\n\t\t};\n\n\t\tconst onMouseDown = (e: MouseEvent) => {\n\t\t\tconst BUTTONS_RIGHT = 0b0000_0010;\n\t\t\tif (e.buttons & BUTTONS_RIGHT) abortResize();\n\t\t};\n\n\t\tconst onKeyDown = (e: KeyboardEvent) => {\n\t\t\tif (e.key === \"Escape\") abortResize();\n\t\t};\n\n\t\tconst addListeners = () => {\n\t\t\tdocument.addEventListener(\"pointermove\", onPointerMove);\n\t\t\tdocument.addEventListener(\"pointerup\", onPointerUp);\n\t\t\tdocument.addEventListener(\"mousedown\", onMouseDown);\n\t\t\tdocument.addEventListener(\"keydown\", onKeyDown);\n\t\t};\n\n\t\tconst removeListeners = () => {\n\t\t\tdocument.removeEventListener(\"pointermove\", onPointerMove);\n\t\t\tdocument.removeEventListener(\"pointerup\", onPointerUp);\n\t\t\tdocument.removeEventListener(\"mousedown\", onMouseDown);\n\t\t\tdocument.removeEventListener(\"keydown\", onKeyDown);\n\t\t};\n\n\t\taddListeners();\n\t\tactiveResizeCleanup = removeListeners;\n\t}\n</script>\n\n<LayoutRow class=\"workspace\" data-workspace>\n\t<LayoutRow class=\"workspace-grid-subdivision\" styles={{ \"flex-grow\": panelSizes[\"root\"] }} data-subdivision-name=\"root\">\n\t\t<LayoutCol class=\"workspace-grid-subdivision\" styles={{ \"flex-grow\": panelSizes[\"content\"] }} data-subdivision-name=\"content\">\n\t\t\t<LayoutRow class=\"workspace-grid-subdivision\" styles={{ \"flex-grow\": panelSizes[\"document\"] }} data-subdivision-name=\"document\">\n\t\t\t\t<Panel\n\t\t\t\t\tclass=\"document-panel\"\n\t\t\t\t\tpanelType={$portfolio.documents.length > 0 ? \"Document\" : \"Welcome\"}\n\t\t\t\t\ttabCloseButtons={true}\n\t\t\t\t\ttabMinWidths={true}\n\t\t\t\t\ttabLabels={documentTabLabels}\n\t\t\t\t\temptySpaceAction={() => editor.handle.newDocumentDialog()}\n\t\t\t\t\tclickAction={(tabIndex) => editor.handle.selectDocument($portfolio.documents[tabIndex].id)}\n\t\t\t\t\tcloseAction={(tabIndex) => editor.handle.closeDocumentWithConfirmation($portfolio.documents[tabIndex].id)}\n\t\t\t\t\ttabActiveIndex={$portfolio.activeDocumentIndex}\n\t\t\t\t\tbind:this={documentPanel}\n\t\t\t\t/>\n\t\t\t</LayoutRow>\n\t\t\t{#if $portfolio.dataPanelOpen}\n\t\t\t\t<LayoutRow class=\"workspace-grid-resize-gutter\" data-gutter-vertical on:pointerdown={(e) => resizePanel(e)} />\n\t\t\t\t<LayoutRow class=\"workspace-grid-subdivision\" styles={{ \"flex-grow\": panelSizes[\"data\"] }} data-subdivision-name=\"data\">\n\t\t\t\t\t<Panel panelType=\"Data\" tabLabels={[{ name: \"Data\" }]} tabActiveIndex={0} />\n\t\t\t\t</LayoutRow>\n\t\t\t{/if}\n\t\t</LayoutCol>\n\t\t{#if $portfolio.propertiesPanelOpen || $portfolio.layersPanelOpen}\n\t\t\t<LayoutCol class=\"workspace-grid-resize-gutter\" data-gutter-horizontal on:pointerdown={(e) => resizePanel(e)} />\n\t\t\t<LayoutCol class=\"workspace-grid-subdivision\" styles={{ \"flex-grow\": panelSizes[\"details\"] }} data-subdivision-name=\"details\">\n\t\t\t\t{#if $portfolio.propertiesPanelOpen}\n\t\t\t\t\t<LayoutRow class=\"workspace-grid-subdivision\" styles={{ \"flex-grow\": panelSizes[\"properties\"] }} data-subdivision-name=\"properties\">\n\t\t\t\t\t\t<Panel panelType=\"Properties\" tabLabels={[{ name: \"Properties\" }]} tabActiveIndex={0} />\n\t\t\t\t\t</LayoutRow>\n\t\t\t\t{/if}\n\t\t\t\t{#if $portfolio.propertiesPanelOpen && $portfolio.layersPanelOpen}\n\t\t\t\t\t<LayoutRow class=\"workspace-grid-resize-gutter\" data-gutter-vertical on:pointerdown={(e) => resizePanel(e)} />\n\t\t\t\t{/if}\n\t\t\t\t{#if $portfolio.layersPanelOpen}\n\t\t\t\t\t<LayoutRow class=\"workspace-grid-subdivision\" styles={{ \"flex-grow\": panelSizes[\"layers\"] }} data-subdivision-name=\"layers\">\n\t\t\t\t\t\t<Panel panelType=\"Layers\" tabLabels={[{ name: \"Layers\" }]} tabActiveIndex={0} />\n\t\t\t\t\t</LayoutRow>\n\t\t\t\t{/if}\n\t\t\t</LayoutCol>\n\t\t{/if}\n\t</LayoutRow>\n</LayoutRow>\n\n<style lang=\"scss\" global>\n\t.workspace {\n\t\tposition: relative;\n\t\tflex: 1 1 100%;\n\n\t\t.workspace-grid-subdivision {\n\t\t\tposition: relative;\n\t\t\tflex: 1 1 0;\n\t\t\tmin-height: 28px;\n\n\t\t\t&.folded {\n\t\t\t\tflex-grow: 0;\n\t\t\t\theight: 0;\n\t\t\t}\n\t\t}\n\n\t\t.workspace-grid-resize-gutter {\n\t\t\tflex: 0 0 4px;\n\n\t\t\t&.layout-row {\n\t\t\t\tcursor: ns-resize;\n\t\t\t}\n\n\t\t\t&.layout-col {\n\t\t\t\tcursor: ew-resize;\n\t\t\t}\n\t\t}\n\n\t\t// Needed for the viewport hole punch on desktop\n\t\t.viewport-hole-punch & .workspace-grid-subdivision:has(.panel.document-panel)::after {\n\t\t\tcontent: \"\";\n\t\t\tposition: absolute;\n\t\t\tinset: 6px;\n\t\t\tborder-radius: 6px;\n\t\t\tbox-shadow: 0 0 0 calc(100vw + 100vh) var(--color-2-mildblack);\n\t\t\tz-index: -1;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "frontend/src/editor.ts",
    "content": "// import { panicProxy } from \"@graphite/utility-functions/panic-proxy\";\n\nimport init, { EditorHandle, wasmMemory, receiveNativeMessage } from \"@graphite/../wasm/pkg/graphite_wasm\";\nimport type { FrontendMessage } from \"@graphite/../wasm/pkg/graphite_wasm\";\nimport { createSubscriptionRouter } from \"@graphite/subscription-router\";\nimport type { MessageName, SubscriptionRouter } from \"@graphite/subscription-router\";\nimport { operatingSystem } from \"@graphite/utility-functions/platform\";\n\n// TODO: Remove `raw`, split out `subscriptions`, and unwrap the remaining `handle` so `EditorHandle` can replace `Editor` and then it can also be renamed to `Editor` to fully remove `EditorHandle`.\nexport type Editor = {\n\traw: WebAssembly.Memory;\n\thandle: EditorHandle;\n\tsubscriptions: SubscriptionRouter;\n\tdestroy: () => void;\n};\n\n// `wasmImport` starts uninitialized because its initialization needs to occur asynchronously, and thus needs to occur by manually calling and awaiting `initWasm()`.\nlet wasmImport: WebAssembly.Memory | undefined;\n\n// Should be called asynchronously before `createEditor()`.\nexport async function initWasm() {\n\t// Skip if the Wasm module is already initialized\n\tif (wasmImport !== undefined) return;\n\n\t// Import the Wasm module JS bindings and wrap them in the panic proxy\n\tconst wasm = await init();\n\tfor (const [name, f] of Object.entries(wasm)) {\n\t\tif (name.startsWith(\"__node_registry\")) f();\n\t}\n\n\twasmImport = await wasmMemory();\n\twindow.imageCanvases = {};\n\twindow.receiveNativeMessage = receiveNativeMessage;\n}\n\n// Should be called after running `initWasm()` and its promise resolving.\nexport function createEditor(): Editor {\n\t// Raw: object containing several callable functions from `editor_api.rs` defined directly on the Wasm module, not the `EditorHandle` struct (generated by wasm-bindgen)\n\tif (!wasmImport) throw new Error(\"Editor Wasm backend was not initialized at application startup\");\n\tconst raw: WebAssembly.Memory = wasmImport;\n\n\t// Provide a random starter seed which must occur after initializing the Wasm module, since Wasm can't generate its own random numbers\n\tconst randomSeedFloat = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);\n\tconst randomSeed = BigInt(randomSeedFloat);\n\n\t// Handle: object containing many functions from `editor_api.rs` that are part of the `EditorHandle` struct (generated by wasm-bindgen)\n\tconst handle = EditorHandle.create(operatingSystem(), randomSeed, (messageType: MessageName, messageData: FrontendMessage) => {\n\t\t// This callback is called by Wasm when a FrontendMessage is received from the Wasm wrapper `EditorHandle`\n\t\tsubscriptions.handleFrontendMessage(messageType, messageData);\n\t});\n\n\t// Subscriptions: allows subscribing to messages in JS that are sent from the Wasm backend\n\tconst subscriptions = createSubscriptionRouter();\n\n\t// Check if the URL hash fragment has any demo artwork to be loaded\n\tconst demoArtworkAbortController = new AbortController();\n\t(async () => {\n\t\tconst demoArtwork = window.location.hash.trim().match(/#demo\\/(.*)/)?.[1];\n\t\tif (!demoArtwork) return;\n\n\t\ttry {\n\t\t\tconst url = new URL(`/demo-artwork/${demoArtwork}.${handle.fileExtension()}`, document.location.href);\n\t\t\tconst data = await fetch(url, { signal: demoArtworkAbortController.signal });\n\t\t\tif (!data.ok) throw new Error();\n\n\t\t\tconst filename = url.pathname.split(\"/\").pop() || \"Untitled\";\n\t\t\tconst content = await data.bytes();\n\t\t\thandle.openFile(`${filename}.${handle.fileExtension()}`, content);\n\n\t\t\t// Remove the hash fragment from the URL\n\t\t\thistory.replaceState(\"\", \"\", `${window.location.pathname}${window.location.search}`);\n\t\t} catch {\n\t\t\t// Do nothing\n\t\t}\n\t})();\n\n\tfunction destroy() {\n\t\thandle.free();\n\t\tdemoArtworkAbortController.abort();\n\t}\n\n\treturn { raw, handle, subscriptions, destroy };\n}\n\n// Wasm state can't be hot-replaced, so we tell Vite to do a full page reload when this module changes\nimport.meta.hot?.accept(() => location.reload());\n"
  },
  {
    "path": "frontend/src/global.d.ts",
    "content": "/* eslint-disable @typescript-eslint/consistent-type-definitions */\n\n// Graphite's custom properties added to the global `window` object\ninterface Window {\n\timageCanvases: Record<string, HTMLCanvasElement>;\n\treceiveNativeMessage?: (buffer: ArrayBuffer) => void;\n}\n\n// Graphite's custom \"pointerlockmove\" event dispatched by input.ts for pointer lock in the CEF desktop app\ninterface WindowEventMap {\n\tpointerlockmove: CustomEvent<{ x: number; y: number }>;\n}\n\n// Experimental Keyboard API: https://developer.mozilla.org/en-US/docs/Web/API/Keyboard\ninterface Navigator {\n\tkeyboard?: Keyboard;\n}\ninterface Keyboard {\n\tlock(keyCodes?: string[]): Promise<void>;\n\tunlock(): void;\n\tgetLayoutMap(): Promise<KeyboardLayoutMap>;\n}\ninterface KeyboardLayoutMap {\n\tentries(): IterableIterator<[string, string]>;\n\tget(key: string): string | undefined;\n\thas(key: string): boolean;\n\treadonly size: number;\n}\n\n// Experimental EyeDropper API: https://developer.mozilla.org/en-US/docs/Web/API/EyeDropper\ninterface Window {\n\tEyeDropper?: typeof EyeDropper;\n}\ndeclare class EyeDropper {\n\tconstructor();\n\topen(options?: { signal?: AbortSignal }): Promise<{ sRGBHex: string }>;\n}\n\n// Experimental \"clipboard-read\" Permission: https://developer.mozilla.org/en-US/docs/Web/API/Permissions\ninterface Permissions {\n\tquery(permissionDesc: { name: \"clipboard-read\" }): Promise<PermissionStatus>;\n}\n\n// Non-standard Stack Trace Limit API: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/stackTraceLimit\ninterface ErrorConstructor {\n\tstackTraceLimit?: number;\n}\n"
  },
  {
    "path": "frontend/src/icons.ts",
    "content": "// Graphics\nimport GraphiteLogotypeSolid from \"@branding/assets/graphics/graphite-logotype-solid.svg\";\n\nconst GRAPHICS = {\n\tGraphiteLogotypeSolid: { svg: GraphiteLogotypeSolid, size: undefined },\n} as const;\n\n// 12px Solid\nimport Add from \"@branding/assets/icon-12px-solid/add.svg\";\nimport Checkmark from \"@branding/assets/icon-12px-solid/checkmark.svg\";\nimport Clipped from \"@branding/assets/icon-12px-solid/clipped.svg\";\nimport CloseX from \"@branding/assets/icon-12px-solid/close-x.svg\";\nimport Delay from \"@branding/assets/icon-12px-solid/delay.svg\";\nimport DotThick from \"@branding/assets/icon-12px-solid/dot-thick.svg\";\nimport Dot from \"@branding/assets/icon-12px-solid/dot.svg\";\nimport DropdownArrow from \"@branding/assets/icon-12px-solid/dropdown-arrow.svg\";\nimport Edit12px from \"@branding/assets/icon-12px-solid/edit-12px.svg\";\nimport Empty12px from \"@branding/assets/icon-12px-solid/empty-12px.svg\";\nimport Failure from \"@branding/assets/icon-12px-solid/failure.svg\";\nimport FullscreenEnter from \"@branding/assets/icon-12px-solid/fullscreen-enter.svg\";\nimport FullscreenExit from \"@branding/assets/icon-12px-solid/fullscreen-exit.svg\";\nimport GridDotted from \"@branding/assets/icon-12px-solid/grid-dotted.svg\";\nimport Grid from \"@branding/assets/icon-12px-solid/grid.svg\";\nimport Info from \"@branding/assets/icon-12px-solid/info.svg\";\nimport KeyboardArrowDown from \"@branding/assets/icon-12px-solid/keyboard-arrow-down.svg\";\nimport KeyboardArrowLeft from \"@branding/assets/icon-12px-solid/keyboard-arrow-left.svg\";\nimport KeyboardArrowRight from \"@branding/assets/icon-12px-solid/keyboard-arrow-right.svg\";\nimport KeyboardArrowUp from \"@branding/assets/icon-12px-solid/keyboard-arrow-up.svg\";\nimport KeyboardBackspace from \"@branding/assets/icon-12px-solid/keyboard-backspace.svg\";\nimport KeyboardCommand from \"@branding/assets/icon-12px-solid/keyboard-command.svg\";\nimport KeyboardControl from \"@branding/assets/icon-12px-solid/keyboard-control.svg\";\nimport KeyboardEnter from \"@branding/assets/icon-12px-solid/keyboard-enter.svg\";\nimport KeyboardOption from \"@branding/assets/icon-12px-solid/keyboard-option.svg\";\nimport KeyboardShift from \"@branding/assets/icon-12px-solid/keyboard-shift.svg\";\nimport KeyboardSpace from \"@branding/assets/icon-12px-solid/keyboard-space.svg\";\nimport KeyboardTab from \"@branding/assets/icon-12px-solid/keyboard-tab.svg\";\nimport License12px from \"@branding/assets/icon-12px-solid/license-12px.svg\";\nimport Link from \"@branding/assets/icon-12px-solid/link.svg\";\nimport Overlays from \"@branding/assets/icon-12px-solid/overlays.svg\";\nimport Remove from \"@branding/assets/icon-12px-solid/remove.svg\";\nimport RenderModeNormal from \"@branding/assets/icon-12px-solid/render-mode-normal.svg\";\nimport RenderModeOutline from \"@branding/assets/icon-12px-solid/render-mode-outline.svg\";\nimport RenderModePixels from \"@branding/assets/icon-12px-solid/render-mode-pixels.svg\";\nimport RenderModeSvg from \"@branding/assets/icon-12px-solid/render-mode-svg.svg\";\nimport Snapping from \"@branding/assets/icon-12px-solid/snapping.svg\";\nimport SwapHorizontal from \"@branding/assets/icon-12px-solid/swap-horizontal.svg\";\nimport SwapVertical from \"@branding/assets/icon-12px-solid/swap-vertical.svg\";\nimport VerticalEllipsis from \"@branding/assets/icon-12px-solid/vertical-ellipsis.svg\";\nimport Warning from \"@branding/assets/icon-12px-solid/warning.svg\";\nimport WindowButtonWinClose from \"@branding/assets/icon-12px-solid/window-button-win-close.svg\";\nimport WindowButtonWinMaximize from \"@branding/assets/icon-12px-solid/window-button-win-maximize.svg\";\nimport WindowButtonWinMinimize from \"@branding/assets/icon-12px-solid/window-button-win-minimize.svg\";\nimport WindowButtonWinRestoreDown from \"@branding/assets/icon-12px-solid/window-button-win-restore-down.svg\";\nimport WorkingColors from \"@branding/assets/icon-12px-solid/working-colors.svg\";\n\nconst SOLID_12PX = {\n\tAdd: { svg: Add, size: 12 },\n\tCheckmark: { svg: Checkmark, size: 12 },\n\tClipped: { svg: Clipped, size: 12 },\n\tCloseX: { svg: CloseX, size: 12 },\n\tDelay: { svg: Delay, size: 12 },\n\tDot: { svg: Dot, size: 12 },\n\tDotThick: { svg: DotThick, size: 12 },\n\tDropdownArrow: { svg: DropdownArrow, size: 12 },\n\tEdit12px: { svg: Edit12px, size: 12 },\n\tEmpty12px: { svg: Empty12px, size: 12 },\n\tFailure: { svg: Failure, size: 12 },\n\tFullscreenEnter: { svg: FullscreenEnter, size: 12 },\n\tFullscreenExit: { svg: FullscreenExit, size: 12 },\n\tGrid: { svg: Grid, size: 12 },\n\tGridDotted: { svg: GridDotted, size: 12 },\n\tInfo: { svg: Info, size: 12 },\n\tKeyboardArrowDown: { svg: KeyboardArrowDown, size: 12 },\n\tKeyboardArrowLeft: { svg: KeyboardArrowLeft, size: 12 },\n\tKeyboardArrowRight: { svg: KeyboardArrowRight, size: 12 },\n\tKeyboardArrowUp: { svg: KeyboardArrowUp, size: 12 },\n\tKeyboardBackspace: { svg: KeyboardBackspace, size: 12 },\n\tKeyboardCommand: { svg: KeyboardCommand, size: 12 },\n\tKeyboardControl: { svg: KeyboardControl, size: 12 },\n\tKeyboardEnter: { svg: KeyboardEnter, size: 12 },\n\tKeyboardOption: { svg: KeyboardOption, size: 12 },\n\tKeyboardShift: { svg: KeyboardShift, size: 12 },\n\tKeyboardSpace: { svg: KeyboardSpace, size: 12 },\n\tKeyboardTab: { svg: KeyboardTab, size: 12 },\n\tLicense12px: { svg: License12px, size: 12 },\n\tLink: { svg: Link, size: 12 },\n\tOverlays: { svg: Overlays, size: 12 },\n\tRemove: { svg: Remove, size: 12 },\n\tRenderModeNormal: { svg: RenderModeNormal, size: 12 },\n\tRenderModeOutline: { svg: RenderModeOutline, size: 12 },\n\tRenderModePixels: { svg: RenderModePixels, size: 12 },\n\tRenderModeSvg: { svg: RenderModeSvg, size: 12 },\n\tSnapping: { svg: Snapping, size: 12 },\n\tSwapHorizontal: { svg: SwapHorizontal, size: 12 },\n\tSwapVertical: { svg: SwapVertical, size: 12 },\n\tVerticalEllipsis: { svg: VerticalEllipsis, size: 12 },\n\tWarning: { svg: Warning, size: 12 },\n\tWindowButtonWinClose: { svg: WindowButtonWinClose, size: 12 },\n\tWindowButtonWinMaximize: { svg: WindowButtonWinMaximize, size: 12 },\n\tWindowButtonWinMinimize: { svg: WindowButtonWinMinimize, size: 12 },\n\tWindowButtonWinRestoreDown: { svg: WindowButtonWinRestoreDown, size: 12 },\n\tWorkingColors: { svg: WorkingColors, size: 12 },\n} as const;\n\n// 16px Solid\nimport AlignBottom from \"@branding/assets/icon-16px-solid/align-bottom.svg\";\nimport AlignHorizontalCenter from \"@branding/assets/icon-16px-solid/align-horizontal-center.svg\";\nimport AlignLeft from \"@branding/assets/icon-16px-solid/align-left.svg\";\nimport AlignRight from \"@branding/assets/icon-16px-solid/align-right.svg\";\nimport AlignTop from \"@branding/assets/icon-16px-solid/align-top.svg\";\nimport AlignVerticalCenter from \"@branding/assets/icon-16px-solid/align-vertical-center.svg\";\nimport Artboard from \"@branding/assets/icon-16px-solid/artboard.svg\";\nimport BooleanDifference from \"@branding/assets/icon-16px-solid/boolean-difference.svg\";\nimport BooleanDivide from \"@branding/assets/icon-16px-solid/boolean-divide.svg\";\nimport BooleanIntersect from \"@branding/assets/icon-16px-solid/boolean-intersect.svg\";\nimport BooleanSubtractBack from \"@branding/assets/icon-16px-solid/boolean-subtract-back.svg\";\nimport BooleanSubtractFront from \"@branding/assets/icon-16px-solid/boolean-subtract-front.svg\";\nimport BooleanUnion from \"@branding/assets/icon-16px-solid/boolean-union.svg\";\nimport Bug from \"@branding/assets/icon-16px-solid/bug.svg\";\nimport CheckboxChecked from \"@branding/assets/icon-16px-solid/checkbox-checked.svg\";\nimport CheckboxUnchecked from \"@branding/assets/icon-16px-solid/checkbox-unchecked.svg\";\nimport CloseAll from \"@branding/assets/icon-16px-solid/close-all.svg\";\nimport Close from \"@branding/assets/icon-16px-solid/close.svg\";\nimport Code from \"@branding/assets/icon-16px-solid/code.svg\";\nimport Copy from \"@branding/assets/icon-16px-solid/copy.svg\";\nimport Credits from \"@branding/assets/icon-16px-solid/credits.svg\";\nimport CustomColor from \"@branding/assets/icon-16px-solid/custom-color.svg\";\nimport Cut from \"@branding/assets/icon-16px-solid/cut.svg\";\nimport DeselectAll from \"@branding/assets/icon-16px-solid/deselect-all.svg\";\nimport Edit from \"@branding/assets/icon-16px-solid/edit.svg\";\nimport Empty from \"@branding/assets/icon-16px-solid/empty.svg\";\nimport EyeHidden from \"@branding/assets/icon-16px-solid/eye-hidden.svg\";\nimport EyeHide from \"@branding/assets/icon-16px-solid/eye-hide.svg\";\nimport EyeShow from \"@branding/assets/icon-16px-solid/eye-show.svg\";\nimport EyeVisible from \"@branding/assets/icon-16px-solid/eye-visible.svg\";\nimport Eyedropper from \"@branding/assets/icon-16px-solid/eyedropper.svg\";\nimport FileExport from \"@branding/assets/icon-16px-solid/file-export.svg\";\nimport FileImport from \"@branding/assets/icon-16px-solid/file-import.svg\";\nimport File from \"@branding/assets/icon-16px-solid/file.svg\";\nimport FlipHorizontal from \"@branding/assets/icon-16px-solid/flip-horizontal.svg\";\nimport FlipVertical from \"@branding/assets/icon-16px-solid/flip-vertical.svg\";\nimport FolderOpen from \"@branding/assets/icon-16px-solid/folder-open.svg\";\nimport Folder from \"@branding/assets/icon-16px-solid/folder.svg\";\nimport FrameAll from \"@branding/assets/icon-16px-solid/frame-all.svg\";\nimport FrameSelected from \"@branding/assets/icon-16px-solid/frame-selected.svg\";\nimport GraphViewClosed from \"@branding/assets/icon-16px-solid/graph-view-closed.svg\";\nimport GraphViewOpen from \"@branding/assets/icon-16px-solid/graph-view-open.svg\";\nimport GraphiteLogo from \"@branding/assets/icon-16px-solid/graphite-logo.svg\";\nimport HandleVisibilityAll from \"@branding/assets/icon-16px-solid/handle-visibility-all.svg\";\nimport HandleVisibilityFrontier from \"@branding/assets/icon-16px-solid/handle-visibility-frontier.svg\";\nimport HandleVisibilitySelected from \"@branding/assets/icon-16px-solid/handle-visibility-selected.svg\";\nimport Heart from \"@branding/assets/icon-16px-solid/heart.svg\";\nimport HistoryRedo from \"@branding/assets/icon-16px-solid/history-redo.svg\";\nimport HistoryUndo from \"@branding/assets/icon-16px-solid/history-undo.svg\";\nimport IconsGrid from \"@branding/assets/icon-16px-solid/icons-grid.svg\";\nimport Image from \"@branding/assets/icon-16px-solid/image.svg\";\nimport Layer from \"@branding/assets/icon-16px-solid/layer.svg\";\nimport License from \"@branding/assets/icon-16px-solid/license.svg\";\nimport NewLayer from \"@branding/assets/icon-16px-solid/new-layer.svg\";\nimport NodeBlur from \"@branding/assets/icon-16px-solid/node-blur.svg\";\nimport NodeBrushwork from \"@branding/assets/icon-16px-solid/node-brushwork.svg\";\nimport NodeColorCorrection from \"@branding/assets/icon-16px-solid/node-color-correction.svg\";\nimport NodeGradient from \"@branding/assets/icon-16px-solid/node-gradient.svg\";\nimport NodeMagicWand from \"@branding/assets/icon-16px-solid/node-magic-wand.svg\";\nimport NodeMask from \"@branding/assets/icon-16px-solid/node-mask.svg\";\nimport NodeMotionBlur from \"@branding/assets/icon-16px-solid/node-motion-blur.svg\";\nimport NodeNodes from \"@branding/assets/icon-16px-solid/node-nodes.svg\";\nimport NodeOutput from \"@branding/assets/icon-16px-solid/node-output.svg\";\nimport NodeShape from \"@branding/assets/icon-16px-solid/node-shape.svg\";\nimport NodeText from \"@branding/assets/icon-16px-solid/node-text.svg\";\nimport NodeTransform from \"@branding/assets/icon-16px-solid/node-transform.svg\";\nimport Node from \"@branding/assets/icon-16px-solid/node.svg\";\nimport PadlockLocked from \"@branding/assets/icon-16px-solid/padlock-locked.svg\";\nimport PadlockUnlocked from \"@branding/assets/icon-16px-solid/padlock-unlocked.svg\";\nimport Paste from \"@branding/assets/icon-16px-solid/paste.svg\";\nimport PinActive from \"@branding/assets/icon-16px-solid/pin-active.svg\";\nimport PinInactive from \"@branding/assets/icon-16px-solid/pin-inactive.svg\";\nimport PlaybackPause from \"@branding/assets/icon-16px-solid/playback-pause.svg\";\nimport PlaybackPlay from \"@branding/assets/icon-16px-solid/playback-play.svg\";\nimport PlaybackToEnd from \"@branding/assets/icon-16px-solid/playback-to-end.svg\";\nimport PlaybackToStart from \"@branding/assets/icon-16px-solid/playback-to-start.svg\";\nimport Random from \"@branding/assets/icon-16px-solid/random.svg\";\nimport Reload from \"@branding/assets/icon-16px-solid/reload.svg\";\nimport Reset from \"@branding/assets/icon-16px-solid/reset.svg\";\nimport Resync from \"@branding/assets/icon-16px-solid/resync.svg\";\nimport ReverseRadialGradientToLeft from \"@branding/assets/icon-16px-solid/reverse-radial-gradient-to-left.svg\";\nimport ReverseRadialGradientToRight from \"@branding/assets/icon-16px-solid/reverse-radial-gradient-to-right.svg\";\nimport Reverse from \"@branding/assets/icon-16px-solid/reverse.svg\";\nimport Save from \"@branding/assets/icon-16px-solid/save.svg\";\nimport SelectAll from \"@branding/assets/icon-16px-solid/select-all.svg\";\nimport SelectParent from \"@branding/assets/icon-16px-solid/select-parent.svg\";\nimport Settings from \"@branding/assets/icon-16px-solid/settings.svg\";\nimport SmallDot from \"@branding/assets/icon-16px-solid/small-dot.svg\";\nimport StackBottom from \"@branding/assets/icon-16px-solid/stack-bottom.svg\";\nimport StackHollow from \"@branding/assets/icon-16px-solid/stack-hollow.svg\";\nimport StackLower from \"@branding/assets/icon-16px-solid/stack-lower.svg\";\nimport StackRaise from \"@branding/assets/icon-16px-solid/stack-raise.svg\";\nimport StackReverse from \"@branding/assets/icon-16px-solid/stack-reverse.svg\";\nimport Stack from \"@branding/assets/icon-16px-solid/stack.svg\";\nimport TiltReset from \"@branding/assets/icon-16px-solid/tilt-reset.svg\";\nimport Tilt from \"@branding/assets/icon-16px-solid/tilt.svg\";\nimport TransformationGrab from \"@branding/assets/icon-16px-solid/transformation-grab.svg\";\nimport TransformationRotate from \"@branding/assets/icon-16px-solid/transformation-rotate.svg\";\nimport TransformationScale from \"@branding/assets/icon-16px-solid/transformation-scale.svg\";\nimport Trash from \"@branding/assets/icon-16px-solid/trash.svg\";\nimport TurnNegative90 from \"@branding/assets/icon-16px-solid/turn-negative-90.svg\";\nimport TurnPositive90 from \"@branding/assets/icon-16px-solid/turn-positive-90.svg\";\nimport UserManual from \"@branding/assets/icon-16px-solid/user-manual.svg\";\nimport ViewportDesignMode from \"@branding/assets/icon-16px-solid/viewport-design-mode.svg\";\nimport ViewportGuideMode from \"@branding/assets/icon-16px-solid/viewport-guide-mode.svg\";\nimport ViewportSelectMode from \"@branding/assets/icon-16px-solid/viewport-select-mode.svg\";\nimport Volunteer from \"@branding/assets/icon-16px-solid/volunteer.svg\";\nimport Website from \"@branding/assets/icon-16px-solid/website.svg\";\nimport WorkingColorsPrimary from \"@branding/assets/icon-16px-solid/working-colors-primary.svg\";\nimport WorkingColorsSecondary from \"@branding/assets/icon-16px-solid/working-colors-secondary.svg\";\nimport Zoom1x from \"@branding/assets/icon-16px-solid/zoom-1x.svg\";\nimport Zoom2x from \"@branding/assets/icon-16px-solid/zoom-2x.svg\";\nimport ZoomIn from \"@branding/assets/icon-16px-solid/zoom-in.svg\";\nimport ZoomOut from \"@branding/assets/icon-16px-solid/zoom-out.svg\";\nimport ZoomReset from \"@branding/assets/icon-16px-solid/zoom-reset.svg\";\n\nconst SOLID_16PX = {\n\tAlignBottom: { svg: AlignBottom, size: 16 },\n\tAlignHorizontalCenter: { svg: AlignHorizontalCenter, size: 16 },\n\tAlignLeft: { svg: AlignLeft, size: 16 },\n\tAlignRight: { svg: AlignRight, size: 16 },\n\tAlignTop: { svg: AlignTop, size: 16 },\n\tAlignVerticalCenter: { svg: AlignVerticalCenter, size: 16 },\n\tArtboard: { svg: Artboard, size: 16 },\n\tBooleanDifference: { svg: BooleanDifference, size: 16 },\n\tBooleanDivide: { svg: BooleanDivide, size: 16 },\n\tBooleanIntersect: { svg: BooleanIntersect, size: 16 },\n\tBooleanSubtractBack: { svg: BooleanSubtractBack, size: 16 },\n\tBooleanSubtractFront: { svg: BooleanSubtractFront, size: 16 },\n\tBooleanUnion: { svg: BooleanUnion, size: 16 },\n\tBug: { svg: Bug, size: 16 },\n\tCheckboxChecked: { svg: CheckboxChecked, size: 16 },\n\tCheckboxUnchecked: { svg: CheckboxUnchecked, size: 16 },\n\tClose: { svg: Close, size: 16 },\n\tCloseAll: { svg: CloseAll, size: 16 },\n\tCode: { svg: Code, size: 16 },\n\tCopy: { svg: Copy, size: 16 },\n\tCredits: { svg: Credits, size: 16 },\n\tCustomColor: { svg: CustomColor, size: 16 },\n\tCut: { svg: Cut, size: 16 },\n\tDeselectAll: { svg: DeselectAll, size: 16 },\n\tEdit: { svg: Edit, size: 16 },\n\tEmpty: { svg: Empty, size: 16 },\n\tEyedropper: { svg: Eyedropper, size: 16 },\n\tEyeHidden: { svg: EyeHidden, size: 16 },\n\tEyeHide: { svg: EyeHide, size: 16 },\n\tEyeShow: { svg: EyeShow, size: 16 },\n\tEyeVisible: { svg: EyeVisible, size: 16 },\n\tFile: { svg: File, size: 16 },\n\tFileExport: { svg: FileExport, size: 16 },\n\tFileImport: { svg: FileImport, size: 16 },\n\tFlipHorizontal: { svg: FlipHorizontal, size: 16 },\n\tFlipVertical: { svg: FlipVertical, size: 16 },\n\tFolder: { svg: Folder, size: 16 },\n\tFolderOpen: { svg: FolderOpen, size: 16 },\n\tFrameAll: { svg: FrameAll, size: 16 },\n\tFrameSelected: { svg: FrameSelected, size: 16 },\n\tGraphiteLogo: { svg: GraphiteLogo, size: 16 },\n\tGraphViewClosed: { svg: GraphViewClosed, size: 16 },\n\tGraphViewOpen: { svg: GraphViewOpen, size: 16 },\n\tHandleVisibilityAll: { svg: HandleVisibilityAll, size: 16 },\n\tHandleVisibilityFrontier: { svg: HandleVisibilityFrontier, size: 16 },\n\tHandleVisibilitySelected: { svg: HandleVisibilitySelected, size: 16 },\n\tHeart: { svg: Heart, size: 16 },\n\tHistoryRedo: { svg: HistoryRedo, size: 16 },\n\tHistoryUndo: { svg: HistoryUndo, size: 16 },\n\tIconsGrid: { svg: IconsGrid, size: 16 },\n\tImage: { svg: Image, size: 16 },\n\tLayer: { svg: Layer, size: 16 },\n\tLicense: { svg: License, size: 16 },\n\tNewLayer: { svg: NewLayer, size: 16 },\n\tNode: { svg: Node, size: 16 },\n\tNodeBlur: { svg: NodeBlur, size: 16 },\n\tNodeBrushwork: { svg: NodeBrushwork, size: 16 },\n\tNodeColorCorrection: { svg: NodeColorCorrection, size: 16 },\n\tNodeGradient: { svg: NodeGradient, size: 16 },\n\tNodeMagicWand: { svg: NodeMagicWand, size: 16 },\n\tNodeMask: { svg: NodeMask, size: 16 },\n\tNodeMotionBlur: { svg: NodeMotionBlur, size: 16 },\n\tNodeNodes: { svg: NodeNodes, size: 16 },\n\tNodeOutput: { svg: NodeOutput, size: 16 },\n\tNodeShape: { svg: NodeShape, size: 16 },\n\tNodeText: { svg: NodeText, size: 16 },\n\tNodeTransform: { svg: NodeTransform, size: 16 },\n\tPadlockLocked: { svg: PadlockLocked, size: 16 },\n\tPadlockUnlocked: { svg: PadlockUnlocked, size: 16 },\n\tPaste: { svg: Paste, size: 16 },\n\tPinActive: { svg: PinActive, size: 16 },\n\tPinInactive: { svg: PinInactive, size: 16 },\n\tPlaybackPause: { svg: PlaybackPause, size: 16 },\n\tPlaybackPlay: { svg: PlaybackPlay, size: 16 },\n\tPlaybackToEnd: { svg: PlaybackToEnd, size: 16 },\n\tPlaybackToStart: { svg: PlaybackToStart, size: 16 },\n\tRandom: { svg: Random, size: 16 },\n\tReload: { svg: Reload, size: 16 },\n\tReset: { svg: Reset, size: 16 },\n\tResync: { svg: Resync, size: 16 },\n\tReverse: { svg: Reverse, size: 16 },\n\tReverseRadialGradientToLeft: { svg: ReverseRadialGradientToLeft, size: 16 },\n\tReverseRadialGradientToRight: { svg: ReverseRadialGradientToRight, size: 16 },\n\tSave: { svg: Save, size: 16 },\n\tSelectAll: { svg: SelectAll, size: 16 },\n\tSelectParent: { svg: SelectParent, size: 16 },\n\tSettings: { svg: Settings, size: 16 },\n\tSmallDot: { svg: SmallDot, size: 16 },\n\tStack: { svg: Stack, size: 16 },\n\tStackBottom: { svg: StackBottom, size: 16 },\n\tStackHollow: { svg: StackHollow, size: 16 },\n\tStackLower: { svg: StackLower, size: 16 },\n\tStackRaise: { svg: StackRaise, size: 16 },\n\tStackReverse: { svg: StackReverse, size: 16 },\n\tTilt: { svg: Tilt, size: 16 },\n\tTiltReset: { svg: TiltReset, size: 16 },\n\tTransformationGrab: { svg: TransformationGrab, size: 16 },\n\tTransformationRotate: { svg: TransformationRotate, size: 16 },\n\tTransformationScale: { svg: TransformationScale, size: 16 },\n\tTrash: { svg: Trash, size: 16 },\n\tTurnNegative90: { svg: TurnNegative90, size: 16 },\n\tTurnPositive90: { svg: TurnPositive90, size: 16 },\n\tUserManual: { svg: UserManual, size: 16 },\n\tViewportDesignMode: { svg: ViewportDesignMode, size: 16 },\n\tViewportGuideMode: { svg: ViewportGuideMode, size: 16 },\n\tViewportSelectMode: { svg: ViewportSelectMode, size: 16 },\n\tVolunteer: { svg: Volunteer, size: 16 },\n\tWebsite: { svg: Website, size: 16 },\n\tWorkingColorsPrimary: { svg: WorkingColorsPrimary, size: 16 },\n\tWorkingColorsSecondary: { svg: WorkingColorsSecondary, size: 16 },\n\tZoom1x: { svg: Zoom1x, size: 16 },\n\tZoom2x: { svg: Zoom2x, size: 16 },\n\tZoomIn: { svg: ZoomIn, size: 16 },\n\tZoomOut: { svg: ZoomOut, size: 16 },\n\tZoomReset: { svg: ZoomReset, size: 16 },\n} as const;\n\n// 16px Two-Tone\nimport MouseHintDrag from \"@branding/assets/icon-16px-two-tone/mouse-hint-drag.svg\";\nimport MouseHintLmbDouble from \"@branding/assets/icon-16px-two-tone/mouse-hint-lmb-double.svg\";\nimport MouseHintLmbDrag from \"@branding/assets/icon-16px-two-tone/mouse-hint-lmb-drag.svg\";\nimport MouseHintLmb from \"@branding/assets/icon-16px-two-tone/mouse-hint-lmb.svg\";\nimport MouseHintMmbDrag from \"@branding/assets/icon-16px-two-tone/mouse-hint-mmb-drag.svg\";\nimport MouseHintMmb from \"@branding/assets/icon-16px-two-tone/mouse-hint-mmb.svg\";\nimport MouseHintNone from \"@branding/assets/icon-16px-two-tone/mouse-hint-none.svg\";\nimport MouseHintRmbDouble from \"@branding/assets/icon-16px-two-tone/mouse-hint-rmb-double.svg\";\nimport MouseHintRmbDrag from \"@branding/assets/icon-16px-two-tone/mouse-hint-rmb-drag.svg\";\nimport MouseHintRmb from \"@branding/assets/icon-16px-two-tone/mouse-hint-rmb.svg\";\nimport MouseHintScrollDown from \"@branding/assets/icon-16px-two-tone/mouse-hint-scroll-down.svg\";\nimport MouseHintScrollUp from \"@branding/assets/icon-16px-two-tone/mouse-hint-scroll-up.svg\";\n\nconst TWO_TONE_16PX = {\n\tMouseHintDrag: { svg: MouseHintDrag, size: 16 },\n\tMouseHintLmb: { svg: MouseHintLmb, size: 16 },\n\tMouseHintLmbDouble: { svg: MouseHintLmbDouble, size: 16 },\n\tMouseHintLmbDrag: { svg: MouseHintLmbDrag, size: 16 },\n\tMouseHintMmb: { svg: MouseHintMmb, size: 16 },\n\tMouseHintMmbDrag: { svg: MouseHintMmbDrag, size: 16 },\n\tMouseHintNone: { svg: MouseHintNone, size: 16 },\n\tMouseHintRmb: { svg: MouseHintRmb, size: 16 },\n\tMouseHintRmbDouble: { svg: MouseHintRmbDouble, size: 16 },\n\tMouseHintRmbDrag: { svg: MouseHintRmbDrag, size: 16 },\n\tMouseHintScrollDown: { svg: MouseHintScrollDown, size: 16 },\n\tMouseHintScrollUp: { svg: MouseHintScrollUp, size: 16 },\n} as const;\n\n// 24px Two-Tone\nimport GeneralArtboardTool from \"@branding/assets/icon-24px-two-tone/general-artboard-tool.svg\";\nimport GeneralEyedropperTool from \"@branding/assets/icon-24px-two-tone/general-eyedropper-tool.svg\";\nimport GeneralFillTool from \"@branding/assets/icon-24px-two-tone/general-fill-tool.svg\";\nimport GeneralGradientTool from \"@branding/assets/icon-24px-two-tone/general-gradient-tool.svg\";\nimport GeneralNavigateTool from \"@branding/assets/icon-24px-two-tone/general-navigate-tool.svg\";\nimport GeneralSelectTool from \"@branding/assets/icon-24px-two-tone/general-select-tool.svg\";\nimport RasterBrushTool from \"@branding/assets/icon-24px-two-tone/raster-brush-tool.svg\";\nimport RasterCloneTool from \"@branding/assets/icon-24px-two-tone/raster-clone-tool.svg\";\nimport RasterDetailTool from \"@branding/assets/icon-24px-two-tone/raster-detail-tool.svg\";\nimport RasterHealTool from \"@branding/assets/icon-24px-two-tone/raster-heal-tool.svg\";\nimport RasterPatchTool from \"@branding/assets/icon-24px-two-tone/raster-patch-tool.svg\";\nimport RasterRelightTool from \"@branding/assets/icon-24px-two-tone/raster-relight-tool.svg\";\nimport VectorEllipseTool from \"@branding/assets/icon-24px-two-tone/vector-ellipse-tool.svg\";\nimport VectorFreehandTool from \"@branding/assets/icon-24px-two-tone/vector-freehand-tool.svg\";\nimport VectorLineTool from \"@branding/assets/icon-24px-two-tone/vector-line-tool.svg\";\nimport VectorPathTool from \"@branding/assets/icon-24px-two-tone/vector-path-tool.svg\";\nimport VectorPenTool from \"@branding/assets/icon-24px-two-tone/vector-pen-tool.svg\";\nimport VectorPolygonTool from \"@branding/assets/icon-24px-two-tone/vector-polygon-tool.svg\";\nimport VectorRectangleTool from \"@branding/assets/icon-24px-two-tone/vector-rectangle-tool.svg\";\nimport VectorSplineTool from \"@branding/assets/icon-24px-two-tone/vector-spline-tool.svg\";\nimport VectorTextTool from \"@branding/assets/icon-24px-two-tone/vector-text-tool.svg\";\n\nconst TWO_TONE_24PX = {\n\tGeneralArtboardTool: { svg: GeneralArtboardTool, size: 24 },\n\tGeneralEyedropperTool: { svg: GeneralEyedropperTool, size: 24 },\n\tGeneralFillTool: { svg: GeneralFillTool, size: 24 },\n\tGeneralGradientTool: { svg: GeneralGradientTool, size: 24 },\n\tGeneralNavigateTool: { svg: GeneralNavigateTool, size: 24 },\n\tGeneralSelectTool: { svg: GeneralSelectTool, size: 24 },\n\tRasterBrushTool: { svg: RasterBrushTool, size: 24 },\n\tRasterCloneTool: { svg: RasterCloneTool, size: 24 },\n\tRasterDetailTool: { svg: RasterDetailTool, size: 24 },\n\tRasterHealTool: { svg: RasterHealTool, size: 24 },\n\tRasterPatchTool: { svg: RasterPatchTool, size: 24 },\n\tRasterRelightTool: { svg: RasterRelightTool, size: 24 },\n\tVectorEllipseTool: { svg: VectorEllipseTool, size: 24 },\n\tVectorFreehandTool: { svg: VectorFreehandTool, size: 24 },\n\tVectorLineTool: { svg: VectorLineTool, size: 24 },\n\tVectorPathTool: { svg: VectorPathTool, size: 24 },\n\tVectorPenTool: { svg: VectorPenTool, size: 24 },\n\tVectorRectangleTool: { svg: VectorRectangleTool, size: 24 },\n\tVectorPolygonTool: { svg: VectorPolygonTool, size: 24 },\n\tVectorSplineTool: { svg: VectorSplineTool, size: 24 },\n\tVectorTextTool: { svg: VectorTextTool, size: 24 },\n} as const;\n\n// All icons\nconst ICON_LIST = {\n\t...GRAPHICS,\n\t...SOLID_12PX,\n\t...SOLID_16PX,\n\t...TWO_TONE_16PX,\n\t...TWO_TONE_24PX,\n} as const;\n\n// Exported icons and types\nexport const ICONS: IconDefinitionType<typeof ICON_LIST> = ICON_LIST;\nexport const ICON_SVG_STRINGS = Object.fromEntries(Object.entries(ICONS).map(([name, data]) => [name, data.svg]));\n\nexport type IconName = keyof typeof ICONS;\nexport type IconSize = undefined | 12 | 16 | 24 | 32;\nexport type PopoverButtonStyle = \"DropdownArrow\" | \"VerticalEllipsis\";\n\n// The following helper type declarations allow us to avoid manually maintaining the `IconName` type declaration as a string union paralleling the keys of the\n// icon definitions. It lets TypeScript do that for us. Our goal is to define the big key-value pair of icons by constraining its values, but inferring its keys.\n// Constraining its values means that TypeScript can make sure each icon definition has a valid size number from the union of numbers that is `IconSize`.\n// Inferring its keys means we don't have to specify a supertype like `string` or `any` for the key-value pair's keys, which would prevent us from accessing\n// the individual keys with `keyof typeof`. Absent a specified type for the keys, TypeScript falls back to inferring that the key-value pair's type is the\n// map of all its individual entries. Having the full list of entries lets us automatically set the `IconName` type to the union of strings that is the full\n// list of keys. The result is that we don't have to maintain a separate list of icon names since this scheme infers it from the keys of the icon definitions.\n// Based on https://stackoverflow.com/a/64119715/775283\ntype IconDefinition = { svg: string; size: IconSize };\ntype EvaluateType<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;\ntype IconDefinitionType<T extends Record<string, IconDefinition>> = EvaluateType<{ [key in keyof T]: IconDefinition }>;\n"
  },
  {
    "path": "frontend/src/main.ts",
    "content": "// This file is the browser's entry point for the JS bundle\n\nimport { mount, unmount } from \"svelte\";\n\nimport App from \"@graphite/App.svelte\";\n\ndocument.body.setAttribute(\"data-app-container\", \"\");\n\nconst app = mount(App, { target: document.body });\n\n// Ensure the old component tree is properly torn down during HMR so all onDestroy hooks fire (which clean up IO managers, state providers, etc.)\nimport.meta.hot?.dispose(() => unmount(app));\n\nexport default app;\n"
  },
  {
    "path": "frontend/src/managers/clipboard.ts",
    "content": "import type { Editor } from \"@graphite/editor\";\n\nlet editorRef: Editor | undefined = undefined;\n\nexport function createClipboardManager(editor: Editor) {\n\teditorRef = editor;\n\n\teditor.subscriptions.subscribeFrontendMessage(\"TriggerClipboardWrite\", (data) => {\n\t\t// If the Clipboard API is supported in the browser, copy text to the clipboard\n\t\tnavigator.clipboard?.writeText?.(data.content);\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"TriggerSelectionRead\", async (data) => {\n\t\teditor.handle.readSelection(readAtCaret(data.cut), data.cut);\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"TriggerSelectionWrite\", async (data) => {\n\t\tinsertAtCaret(data.content);\n\t});\n}\n\nexport function destroyClipboardManager() {\n\tconst editor = editorRef;\n\tif (!editor) return;\n\n\teditor.subscriptions.unsubscribeFrontendMessage(\"TriggerClipboardWrite\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"TriggerSelectionRead\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"TriggerSelectionWrite\");\n}\n\nfunction readAtCaret(cut: boolean): string | undefined {\n\tconst element = window.document.activeElement;\n\n\tif (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {\n\t\tconst start = element.selectionStart;\n\t\tconst end = element.selectionEnd;\n\n\t\tif ((!start && start !== 0) || (!end && end !== 0) || start === end) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst value = element.value;\n\t\tconst selectedText = value.slice(start, end);\n\n\t\tif (cut) {\n\t\t\telement.value = value.slice(0, start) + value.slice(end);\n\n\t\t\telement.selectionStart = element.selectionEnd = start;\n\t\t\telement.dispatchEvent(new Event(\"input\", { bubbles: true }));\n\t\t}\n\n\t\treturn selectedText;\n\t}\n\n\tconst selection = window.getSelection();\n\tif (!selection || selection.rangeCount === 0) {\n\t\treturn undefined;\n\t}\n\n\tconst selectedText = String(selection);\n\tif (!selectedText) return undefined;\n\n\tif (cut) {\n\t\tconst range = selection.getRangeAt(0);\n\t\trange.deleteContents();\n\n\t\trange.collapse(true);\n\t\tselection.removeAllRanges();\n\t\tselection.addRange(range);\n\t}\n\n\treturn selectedText;\n}\n\nfunction insertAtCaret(text: string) {\n\tconst element = window.document.activeElement;\n\n\tif (!element) return;\n\n\tif (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {\n\t\tconst start = element.selectionStart;\n\t\tconst end = element.selectionEnd;\n\n\t\tif ((!start && start !== 0) || (!end && end !== 0)) return;\n\n\t\tconst value = element.value;\n\n\t\telement.value = value.slice(0, start) + text + value.slice(end);\n\n\t\tconst newPos = start + text.length;\n\t\telement.selectionStart = element.selectionEnd = newPos;\n\t} else if (element instanceof HTMLElement && element.isContentEditable) {\n\t\tconst selection = window.getSelection();\n\t\tif (!selection || selection.rangeCount === 0) return;\n\n\t\tconst range = selection.getRangeAt(0);\n\t\trange.deleteContents();\n\n\t\tconst textNode = window.document.createTextNode(text);\n\t\trange.insertNode(textNode);\n\n\t\trange.setStartAfter(textNode);\n\t\trange.collapse(true);\n\n\t\tselection.removeAllRanges();\n\t\tselection.addRange(range);\n\t}\n\n\telement.dispatchEvent(new Event(\"input\", { bubbles: true }));\n}\n\n// Self-accepting HMR: tear down the old instance and re-create with the new module's code\nimport.meta.hot?.accept((newModule) => {\n\tdestroyClipboardManager();\n\tif (editorRef) newModule?.createClipboardManager(editorRef);\n});\n"
  },
  {
    "path": "frontend/src/managers/fonts.ts",
    "content": "import type { Editor } from \"@graphite/editor\";\n\ntype ApiResponse = { family: string; variants: string[]; files: Record<string, string> }[];\n\nconst FONT_LIST_API = \"https://api.graphite.art/font-list\";\n\nlet editorRef: Editor | undefined = undefined;\nlet abortController: AbortController | undefined = undefined;\n\nexport function createFontsManager(editor: Editor) {\n\teditorRef = editor;\n\tabortController = new AbortController();\n\n\teditor.subscriptions.subscribeFrontendMessage(\"TriggerFontCatalogLoad\", async () => {\n\t\ttry {\n\t\t\tconst response = await fetch(FONT_LIST_API, abortController ? { signal: abortController.signal } : undefined);\n\t\t\tif (!response.ok) throw new Error(`Font catalog request failed with status ${response.status}`);\n\t\t\tconst fontListResponse: { items: ApiResponse } = await response.json();\n\t\t\tconst fontListData = fontListResponse.items;\n\n\t\t\tconst catalog = fontListData.map((font) => {\n\t\t\t\tconst styles = font.variants.map((variant) => {\n\t\t\t\t\tconst weight = variant === \"regular\" || variant === \"italic\" ? 400 : parseInt(variant, 10);\n\t\t\t\t\tconst italic = variant.endsWith(\"italic\");\n\t\t\t\t\tconst url = font.files[variant].replace(\"http://\", \"https://\");\n\n\t\t\t\t\treturn { weight, italic, url };\n\t\t\t\t});\n\t\t\t\treturn { name: font.family, styles };\n\t\t\t});\n\n\t\t\teditor.handle.onFontCatalogLoad(catalog);\n\t\t} catch (error) {\n\t\t\tif (error instanceof DOMException && error.name === \"AbortError\") return;\n\t\t\tthrow error;\n\t\t}\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"TriggerFontDataLoad\", async (data) => {\n\t\tconst { fontFamily, fontStyle } = data.font;\n\n\t\ttry {\n\t\t\tif (!data.url) throw new Error(\"No URL provided for font data load\");\n\t\t\tconst response = await fetch(data.url, abortController ? { signal: abortController.signal } : undefined);\n\t\t\tif (!response.ok) throw new Error(`Font data request failed with status ${response.status}`);\n\t\t\tconst buffer = await response.arrayBuffer();\n\t\t\tconst bytes = new Uint8Array(buffer);\n\n\t\t\teditor.handle.onFontLoad(fontFamily, fontStyle, bytes);\n\t\t} catch (error) {\n\t\t\tif (error instanceof DOMException && error.name === \"AbortError\") return;\n\t\t\t// eslint-disable-next-line no-console\n\t\t\tconsole.error(\"Failed to load font:\", error);\n\t\t}\n\t});\n}\n\nexport function destroyFontsManager() {\n\tconst editor = editorRef;\n\tif (!editor) return;\n\n\tabortController?.abort();\n\teditor.subscriptions.unsubscribeFrontendMessage(\"TriggerFontCatalogLoad\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"TriggerFontDataLoad\");\n}\n\n// Self-accepting HMR: tear down the old instance and re-create with the new module's code\nimport.meta.hot?.accept((newModule) => {\n\tdestroyFontsManager();\n\tif (editorRef) newModule?.createFontsManager(editorRef);\n});\n"
  },
  {
    "path": "frontend/src/managers/hyperlink.ts",
    "content": "import type { Editor } from \"@graphite/editor\";\n\nlet editorRef: Editor | undefined = undefined;\n\nexport function createHyperlinkManager(editor: Editor) {\n\teditorRef = editor;\n\n\teditor.subscriptions.subscribeFrontendMessage(\"TriggerVisitLink\", async (data) => {\n\t\twindow.open(data.url, \"_blank\", \"noopener\");\n\t});\n}\n\nexport function destroyHyperlinkManager() {\n\tconst editor = editorRef;\n\tif (!editor) return;\n\n\teditor.subscriptions.unsubscribeFrontendMessage(\"TriggerVisitLink\");\n}\n\n// Self-accepting HMR: tear down the old instance and re-create with the new module's code\nimport.meta.hot?.accept((newModule) => {\n\tdestroyHyperlinkManager();\n\tif (editorRef) newModule?.createHyperlinkManager(editorRef);\n});\n"
  },
  {
    "path": "frontend/src/managers/input.ts",
    "content": "import { get } from \"svelte/store\";\n\nimport { isPlatformNative } from \"@graphite/../wasm/pkg/graphite_wasm\";\nimport type { Editor } from \"@graphite/editor\";\nimport type { DialogStore } from \"@graphite/stores/dialog\";\nimport type { DocumentStore } from \"@graphite/stores/document\";\nimport { fullscreenModeChanged, toggleFullscreen } from \"@graphite/stores/fullscreen\";\nimport type { PortfolioStore } from \"@graphite/stores/portfolio\";\nimport { pasteFile } from \"@graphite/utility-functions/files\";\nimport { makeKeyboardModifiersBitfield, textInputCleanup, getLocalizedScanCode } from \"@graphite/utility-functions/keyboard-entry\";\nimport { operatingSystem } from \"@graphite/utility-functions/platform\";\nimport { extractPixelData } from \"@graphite/utility-functions/rasterization\";\nimport { stripIndents } from \"@graphite/utility-functions/strip-indents\";\n\nconst BUTTON_LEFT = 0;\nconst BUTTON_MIDDLE = 1;\nconst BUTTON_RIGHT = 2;\nconst BUTTON_BACK = 3;\nconst BUTTON_FORWARD = 4;\n\nexport const PRESS_REPEAT_DELAY_MS = 400;\nexport const PRESS_REPEAT_INTERVAL_MS = 72;\nexport const PRESS_REPEAT_INTERVAL_RAPID_MS = 10;\n\ntype EventName = keyof HTMLElementEventMap | keyof WindowEventHandlersEventMap | \"modifyinputfield\" | \"pointerlockchange\" | \"pointerlockerror\";\ntype EventListenerTarget = {\n\taddEventListener: typeof window.addEventListener;\n\tremoveEventListener: typeof window.removeEventListener;\n};\n\nlet editorRef: Editor | undefined = undefined;\nlet dialogStore: DialogStore | undefined = undefined;\nlet portfolioStore: PortfolioStore | undefined = undefined;\nlet documentStore: DocumentStore | undefined = undefined;\n\nlet viewportPointerInteractionOngoing = false;\nlet textToolInteractiveInputElement: HTMLDivElement | undefined = undefined;\nlet canvasFocused = true;\nlet inPointerLock = false;\nlet lastShakeTime = 0;\nconst shakeSamples: { x: number; y: number; time: number }[] = [];\n\nconst listeners: { target: EventListenerTarget; eventName: EventName; action(event: Event): void; options?: AddEventListenerOptions }[] = [\n\t{ target: window, eventName: \"beforeunload\", action: (e: BeforeUnloadEvent) => onBeforeUnload(e) },\n\t{ target: window, eventName: \"keyup\", action: (e: KeyboardEvent) => onKeyUp(e) },\n\t{ target: window, eventName: \"keydown\", action: (e: KeyboardEvent) => onKeyDown(e) },\n\t{ target: window, eventName: \"pointermove\", action: (e: PointerEvent) => onPointerMove(e) },\n\t{ target: window, eventName: \"pointerdown\", action: (e: PointerEvent) => onPointerDown(e) },\n\t{ target: window, eventName: \"pointerup\", action: (e: PointerEvent) => onPointerUp(e) },\n\t{ target: window, eventName: \"mousedown\", action: (e: MouseEvent) => onMouseDown(e) },\n\t{ target: window, eventName: \"mouseup\", action: (e: MouseEvent) => onPotentialDoubleClick(e) },\n\t{ target: window, eventName: \"wheel\", action: (e: WheelEvent) => onWheelScroll(e), options: { passive: false } },\n\t{ target: window, eventName: \"modifyinputfield\", action: (e: CustomEvent) => onModifyInputField(e) },\n\t{ target: window, eventName: \"focusout\", action: () => (canvasFocused = false) },\n\t{ target: window.document, eventName: \"contextmenu\", action: (e: MouseEvent) => onContextMenu(e) },\n\t{ target: window.document, eventName: \"fullscreenchange\", action: () => fullscreenModeChanged() },\n\t{ target: window.document.body, eventName: \"paste\", action: (e: ClipboardEvent) => onPaste(e) },\n\t{ target: window.document, eventName: \"pointerlockchange\", action: onPointerLockChange },\n\t{ target: window.document, eventName: \"pointerlockerror\", action: onPointerLockChange },\n];\n\nexport function createInputManager(editor: Editor, dialog: DialogStore, portfolio: PortfolioStore, doc: DocumentStore) {\n\teditorRef = editor;\n\tdialogStore = dialog;\n\tportfolioStore = portfolio;\n\tdocumentStore = doc;\n\n\teditor.subscriptions.subscribeFrontendMessage(\"TriggerClipboardRead\", () => {\n\t\ttriggerClipboardRead();\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"WindowPointerLockMove\", (data) => {\n\t\t// Desktop app only: dispatch custom pointer lock movement events\n\t\tconst event = new CustomEvent(\"pointerlockmove\", { detail: { x: data.position[0], y: data.position[1] } });\n\t\twindow.dispatchEvent(event);\n\t});\n\n\t// INITIALIZATION\n\n\t// Focus the app container\n\tconst app = window.document.querySelector(\"[data-app-container]\");\n\tif (app instanceof HTMLElement) app.focus();\n\n\t// Bind the event listeners\n\tbindListeners();\n}\n\n// Return the destructor\nexport function destroyInputManager() {\n\tconst editor = editorRef;\n\tif (!editor) return;\n\n\tunbindListeners();\n\teditor.subscriptions.unsubscribeFrontendMessage(\"TriggerClipboardRead\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"WindowPointerLockMove\");\n}\n\nasync function triggerClipboardRead() {\n\tconst editor = editorRef;\n\tif (!editor) return;\n\n\t// In the try block, attempt to read from the Clipboard API, which may not have permission and may not be supported in all browsers\n\t// In the catch block, explain to the user why the paste failed and how to fix or work around the problem\n\ttry {\n\t\t// Attempt to check if the clipboard permission is denied, and throw an error if that is the case\n\t\t// In Firefox, the `clipboard-read` permission isn't supported, so attempting to query it throws an error\n\t\t// In Safari, the entire Permissions API isn't supported, so the query never occurs and this block is skipped without an error and we assume we might have permission\n\t\tconst permission = await navigator.permissions?.query({ name: \"clipboard-read\" });\n\t\tif (permission?.state === \"denied\") throw new Error(\"Permission denied\");\n\n\t\t// Read the clipboard contents if the Clipboard API is available\n\t\tconst clipboardItems = await navigator.clipboard.read();\n\t\tif (!clipboardItems) throw new Error(\"Clipboard API unsupported\");\n\n\t\t// Read any layer data or images from the clipboard\n\t\tconst success = await Promise.any(\n\t\t\tArray.from(clipboardItems).map(async (item) => {\n\t\t\t\t// Read plain text and, if it is a layer, pass it to the editor\n\t\t\t\tif (item.types.includes(\"text/plain\")) {\n\t\t\t\t\tconst blob = await item.getType(\"text/plain\");\n\t\t\t\t\tconst reader = new FileReader();\n\t\t\t\t\treader.onload = () => {\n\t\t\t\t\t\tif (typeof reader.result === \"string\") editor.handle.pasteText(reader.result);\n\t\t\t\t\t};\n\t\t\t\t\treader.readAsText(blob);\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\t// Read an image from the clipboard and pass it to the editor to be loaded\n\t\t\t\tconst imageType = item.types.find((type) => type.startsWith(\"image/\"));\n\n\t\t\t\t// Import the actual SVG content if it's an SVG\n\t\t\t\tif (imageType?.includes(\"svg\")) {\n\t\t\t\t\tconst blob = await item.getType(\"text/plain\");\n\t\t\t\t\tconst reader = new FileReader();\n\t\t\t\t\treader.onload = () => {\n\t\t\t\t\t\tif (typeof reader.result === \"string\") editor.handle.pasteSvg(undefined, reader.result);\n\t\t\t\t\t};\n\t\t\t\t\treader.readAsText(blob);\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\t// Import the bitmap image if it's an image\n\t\t\t\tif (imageType) {\n\t\t\t\t\tconst blob = await item.getType(imageType);\n\t\t\t\t\tconst reader = new FileReader();\n\t\t\t\t\treader.onload = async () => {\n\t\t\t\t\t\tif (reader.result instanceof ArrayBuffer) {\n\t\t\t\t\t\t\tconst imageData = await extractPixelData(new Blob([reader.result], { type: imageType }));\n\t\t\t\t\t\t\teditor.handle.pasteImage(undefined, new Uint8Array(imageData.data), imageData.width, imageData.height);\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t\treader.readAsArrayBuffer(blob);\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\t// The API limits what kinds of data we can access, so we can get copied images and our text encodings of copied nodes, but not files (like\n\t\t\t\t// .graphite or even image files). However, the user can paste those with Ctrl+V, which we recommend they in the error message that's shown to them.\n\t\t\t\treturn false;\n\t\t\t}),\n\t\t);\n\n\t\tif (!success) throw new Error(\"No valid clipboard data\");\n\t} catch (err) {\n\t\tconst unsupported = stripIndents`\n\t\t\tThis browser does not support reading from the clipboard.\n\t\t\tUse the standard keyboard shortcut to paste instead.\n\t\t\t`;\n\t\tconst denied = stripIndents`\n\t\t\tThe browser's clipboard permission has been denied.\n\n\t\t\tOpen the browser's website settings (usually accessible\n\t\t\tjust left of the URL bar) to allow this permission.\n\t\t\t`;\n\t\tconst nothing = stripIndents`\n\t\t\tNo valid clipboard data was found. You may have better\n\t\t\tsuccess pasting with the standard keyboard shortcut instead.\n\t\t\t`;\n\n\t\tconst matchMessage = {\n\t\t\t\"clipboard-read\": unsupported,\n\t\t\t\"Clipboard API unsupported\": unsupported,\n\t\t\t\"Permission denied\": denied,\n\t\t\t\"No valid clipboard data\": nothing,\n\t\t};\n\t\tconst message = Object.entries(matchMessage).find(([key]) => String(err).includes(key))?.[1] || String(err);\n\n\t\teditor.handle.errorDialog(\"Cannot access clipboard\", message);\n\t}\n}\n\n// Event bindings\n\nfunction bindListeners() {\n\t// Add event bindings for the lifetime of the application\n\tlisteners.forEach(({ target, eventName, action, options }) => target.addEventListener(eventName, action, options));\n}\n\nfunction unbindListeners() {\n\t// Remove event bindings after the lifetime of the application (or on hot-module replacement during development)\n\tlisteners.forEach(({ target, eventName, action, options }) => target.removeEventListener(eventName, action, options));\n}\n\n// Keyboard events\n\nasync function shouldRedirectKeyboardEventToBackend(e: KeyboardEvent): Promise<boolean> {\n\tif (!dialogStore) return false;\n\n\t// Don't redirect when a dialog is covering the workspace\n\tif (get(dialogStore).visible) return false;\n\n\tconst key = await getLocalizedScanCode(e);\n\n\t// TODO: Switch to a system where everything is sent to the backend, then the input preprocessor makes decisions and kicks some inputs back to the frontend\n\tconst accelKey = operatingSystem() === \"Mac\" ? e.metaKey : e.ctrlKey;\n\n\t// Cut, copy, and paste is handled in the backend on desktop\n\tif (isPlatformNative() && accelKey && [\"KeyX\", \"KeyC\", \"KeyV\"].includes(key)) return true;\n\t// But on web, we want to not redirect paste\n\tif (!isPlatformNative() && key === \"KeyV\" && accelKey) return false;\n\n\t// Don't redirect user input from text entry into HTML elements\n\tif (targetIsTextField(e.target || undefined) && key !== \"Escape\" && !(accelKey && [\"Enter\", \"NumpadEnter\"].includes(key))) return false;\n\n\t// Don't redirect tab or enter if not in canvas (to allow navigating elements)\n\tpotentiallyRestoreCanvasFocus(e);\n\tif (\n\t\t!canvasFocused &&\n\t\t!targetIsTextField(e.target || undefined) &&\n\t\t[\"Tab\", \"Enter\", \"NumpadEnter\", \"Space\", \"ArrowDown\", \"ArrowLeft\", \"ArrowRight\", \"ArrowUp\"].includes(key) &&\n\t\t!(e.ctrlKey || e.metaKey || e.altKey)\n\t)\n\t\treturn false;\n\n\t// Don't redirect if a MenuList is open\n\tif (window.document.querySelector(\"[data-floating-menu-content]\")) return false;\n\n\t// Web-only keyboard shortcuts\n\tif (!isPlatformNative()) {\n\t\t// Don't redirect a fullscreen request, but process it immediately instead\n\t\tif (((operatingSystem() !== \"Mac\" && key === \"F11\") || (operatingSystem() === \"Mac\" && e.ctrlKey && e.metaKey && key === \"KeyF\")) && e.type === \"keydown\" && !e.repeat) {\n\t\t\te.preventDefault();\n\t\t\ttoggleFullscreen();\n\t\t\treturn false;\n\t\t}\n\n\t\t// Don't redirect a reload request\n\t\tif (key === \"F5\") return false;\n\t\tif (key === \"KeyR\" && accelKey) return false;\n\n\t\t// Don't redirect debugging tools\n\t\tif ([\"F12\", \"F8\"].includes(key)) return false;\n\t\tif ([\"KeyC\", \"KeyI\", \"KeyJ\"].includes(key) && accelKey && e.shiftKey) return false;\n\t}\n\n\t// Redirect to the backend\n\treturn true;\n}\n\nasync function onKeyDown(e: KeyboardEvent) {\n\tconst editor = editorRef;\n\tif (!editor || !dialogStore) return;\n\n\tconst key = await getLocalizedScanCode(e);\n\n\tconst NO_KEY_REPEAT_MODIFIER_KEYS = [\"ControlLeft\", \"ControlRight\", \"ShiftLeft\", \"ShiftRight\", \"MetaLeft\", \"MetaRight\", \"AltLeft\", \"AltRight\", \"AltGraph\", \"CapsLock\", \"Fn\", \"FnLock\"];\n\tif (e.repeat && NO_KEY_REPEAT_MODIFIER_KEYS.includes(key)) return;\n\n\tif (await shouldRedirectKeyboardEventToBackend(e)) {\n\t\te.preventDefault();\n\t\tconst modifiers = makeKeyboardModifiersBitfield(e);\n\t\teditor.handle.onKeyDown(key, modifiers, e.repeat);\n\t\treturn;\n\t}\n\n\tif (get(dialogStore).visible && key === \"Escape\") {\n\t\teditor.handle.onDialogDismiss();\n\t}\n}\n\nasync function onKeyUp(e: KeyboardEvent) {\n\tconst editor = editorRef;\n\tif (!editor) return;\n\n\tconst key = await getLocalizedScanCode(e);\n\n\tif (await shouldRedirectKeyboardEventToBackend(e)) {\n\t\te.preventDefault();\n\t\tconst modifiers = makeKeyboardModifiersBitfield(e);\n\t\teditor.handle.onKeyUp(key, modifiers, e.repeat);\n\t}\n}\n\n// Pointer events\n\n// While any pointer button is already down, additional button down events are not reported, but they are sent as `pointermove` events and these are handled in the backend\nfunction onPointerMove(e: PointerEvent) {\n\tconst editor = editorRef;\n\tif (!editor || !documentStore) return;\n\n\tpotentiallyRestoreCanvasFocus(e);\n\n\tif (!e.buttons) viewportPointerInteractionOngoing = false;\n\n\t// Don't redirect pointer movement to the backend if there's no ongoing interaction and it's over a floating menu, or the graph overlay, on top of the canvas\n\t// TODO: A better approach is to pass along a boolean to the backend's input preprocessor so it can know if it's being occluded by the GUI.\n\t// TODO: This would allow it to properly decide to act on removing hover focus from something that was hovered in the canvas before moving over the GUI.\n\t// TODO: Further explanation: https://github.com/GraphiteEditor/Graphite/pull/623#discussion_r866436197\n\tconst inFloatingMenu = e.target instanceof Element && e.target.closest(\"[data-floating-menu-content]\");\n\tconst inGraphOverlay = get(documentStore).graphViewOverlayOpen;\n\tif (!viewportPointerInteractionOngoing && (inFloatingMenu || inGraphOverlay)) return;\n\n\tconst modifiers = makeKeyboardModifiersBitfield(e);\n\tif (detectShake(e)) editor.handle.onMouseShake(e.clientX, e.clientY, e.buttons, modifiers);\n\teditor.handle.onMouseMove(e.clientX, e.clientY, e.buttons, modifiers);\n}\n\nfunction onPointerDown(e: PointerEvent) {\n\tconst editor = editorRef;\n\tif (!editor || !dialogStore) return;\n\n\tpotentiallyRestoreCanvasFocus(e);\n\n\tconst inFloatingMenu = e.target instanceof Element && e.target.closest(\"[data-floating-menu-content]\");\n\tconst isTargetingCanvas = !inFloatingMenu && e.target instanceof Element && e.target.closest(\"[data-viewport], [data-viewport-container], [data-node-graph]\");\n\tconst inDialog = e.target instanceof Element && e.target.closest(\"[data-dialog] [data-floating-menu-content]\");\n\tconst inContextMenu = e.target instanceof Element && e.target.closest(\"[data-context-menu]\");\n\tconst inTextInput = e.target === textToolInteractiveInputElement;\n\n\tif (get(dialogStore).visible && !inDialog) {\n\t\teditor.handle.onDialogDismiss();\n\t\te.preventDefault();\n\t\te.stopPropagation();\n\t}\n\n\tif (!inTextInput && !inContextMenu) {\n\t\tif (textToolInteractiveInputElement) {\n\t\t\tconst isLeftOrRightClick = e.button === BUTTON_RIGHT || e.button === BUTTON_LEFT;\n\t\t\teditor.handle.onChangeText(textInputCleanup(textToolInteractiveInputElement.innerText), isLeftOrRightClick);\n\t\t} else {\n\t\t\tviewportPointerInteractionOngoing = isTargetingCanvas instanceof Element;\n\t\t}\n\t}\n\n\tif (viewportPointerInteractionOngoing && isTargetingCanvas instanceof Element) {\n\t\tconst modifiers = makeKeyboardModifiersBitfield(e);\n\t\teditor.handle.onMouseDown(e.clientX, e.clientY, e.buttons, modifiers);\n\t}\n}\n\nfunction onPointerUp(e: PointerEvent) {\n\tconst editor = editorRef;\n\tif (!editor) return;\n\n\tpotentiallyRestoreCanvasFocus(e);\n\n\t// Don't let the browser navigate back or forward when using the buttons on some mice\n\t// TODO: This works in Chrome but not in Firefox\n\t// TODO: Possible workaround: use the browser's history API to block navigation:\n\t// TODO: <https://stackoverflow.com/questions/57102502/preventing-mouse-fourth-and-fifth-buttons-from-navigating-back-forward-in-browse>\n\tif (e.button === BUTTON_BACK || e.button === BUTTON_FORWARD) e.preventDefault();\n\n\tif (!e.buttons) viewportPointerInteractionOngoing = false;\n\n\tif (textToolInteractiveInputElement) return;\n\n\tconst modifiers = makeKeyboardModifiersBitfield(e);\n\teditor.handle.onMouseUp(e.clientX, e.clientY, e.buttons, modifiers);\n}\n\n// Mouse events\n\nfunction onPotentialDoubleClick(e: MouseEvent) {\n\tconst editor = editorRef;\n\tif (!editor) return;\n\n\tif (textToolInteractiveInputElement || inPointerLock) return;\n\n\t// Allow only events within the viewport or node graph boundaries\n\tconst isTargetingCanvas = e.target instanceof Element && e.target.closest(\"[data-viewport], [data-viewport-container], [data-node-graph]\");\n\tif (!(isTargetingCanvas instanceof Element)) return;\n\n\t// Allow only repeated increments of double-clicks (not 1, 3, 5, etc.)\n\tif (e.detail % 2 == 1) return;\n\n\t// `e.buttons` is always 0 in the `mouseup` event, so we have to convert from `e.button` instead\n\tlet buttons = 1;\n\tif (e.button === BUTTON_LEFT) buttons = 1; // Left\n\tif (e.button === BUTTON_RIGHT) buttons = 2; // Right\n\tif (e.button === BUTTON_MIDDLE) buttons = 4; // Middle\n\tif (e.button === BUTTON_BACK) buttons = 8; // Back\n\tif (e.button === BUTTON_FORWARD) buttons = 16; // Forward\n\n\tconst modifiers = makeKeyboardModifiersBitfield(e);\n\teditor.handle.onDoubleClick(e.clientX, e.clientY, buttons, modifiers);\n}\n\nfunction onMouseDown(e: MouseEvent) {\n\t// Block middle mouse button auto-scroll mode (the circular gizmo that appears and allows quick scrolling by moving the cursor above or below it)\n\tif (e.button === BUTTON_MIDDLE) e.preventDefault();\n}\n\nfunction onContextMenu(e: MouseEvent) {\n\tif (!targetIsTextField(e.target || undefined) && e.target !== textToolInteractiveInputElement) {\n\t\te.preventDefault();\n\t}\n}\n\nfunction onPointerLockChange() {\n\tinPointerLock = Boolean(window.document.pointerLockElement);\n}\n\n// Wheel events\n\nfunction onWheelScroll(e: WheelEvent) {\n\tconst editor = editorRef;\n\tif (!editor) return;\n\n\tconst isTargetingCanvas = e.target instanceof Element && e.target.closest(\"[data-viewport], [data-viewport-container], [data-node-graph]\");\n\n\t// Prevent zooming the entire page when using Ctrl + scroll wheel outside of the viewport\n\tif (e.ctrlKey && !isTargetingCanvas) {\n\t\te.preventDefault();\n\t}\n\n\t// Redirect vertical scroll wheel movement into a horizontal scroll on a horizontally scrollable element\n\t// There seems to be no possible way to properly employ the browser's smooth scrolling interpolation\n\tconst horizontalScrollableElement = e.target instanceof Element && e.target.closest(\"[data-scrollable-x]\");\n\tif (horizontalScrollableElement && e.deltaY !== 0) {\n\t\thorizontalScrollableElement.scrollTo(horizontalScrollableElement.scrollLeft + e.deltaY, 0);\n\t\treturn;\n\t}\n\n\tif (isTargetingCanvas) {\n\t\te.preventDefault();\n\t\tconst modifiers = makeKeyboardModifiersBitfield(e);\n\t\teditor.handle.onWheelScroll(e.clientX, e.clientY, e.buttons, e.deltaX, e.deltaY, e.deltaZ, modifiers);\n\t}\n}\n\n// Receives a custom event dispatched when the user begins interactively editing with the text tool.\n// We keep a copy of the text input element to check against when it's active for text entry.\nfunction onModifyInputField(e: CustomEvent) {\n\ttextToolInteractiveInputElement = e.detail;\n}\n\n// Window events\n\nasync function onBeforeUnload(e: BeforeUnloadEvent) {\n\tconst editor = editorRef;\n\tif (!editor || !portfolioStore) return;\n\n\tconst activeDocument = get(portfolioStore).documents[get(portfolioStore).activeDocumentIndex];\n\tif (activeDocument && !activeDocument.details.isAutoSaved) editor.handle.triggerAutoSave(activeDocument.id);\n\n\t// Skip the message if the editor crashed, since work is already lost\n\tif (await editor.handle.hasCrashed()) return;\n\n\t// Skip the message during development, since it's annoying when testing\n\tif (await editor.handle.inDevelopmentMode()) return;\n\n\tconst allDocumentsSaved = get(portfolioStore).documents.reduce((acc, doc) => acc && doc.details.isSaved, true);\n\tif (!allDocumentsSaved) {\n\t\te.returnValue = \"Unsaved work will be lost if the web browser tab is closed. Close anyway?\";\n\t\te.preventDefault();\n\t}\n}\n\nfunction onPaste(e: ClipboardEvent) {\n\tconst editor = editorRef;\n\tif (!editor) return;\n\n\tconst dataTransfer = e.clipboardData;\n\tif (!dataTransfer || targetIsTextField(e.target || undefined)) return;\n\te.preventDefault();\n\n\tArray.from(dataTransfer.items).forEach(async (item) => {\n\t\tif (item.type === \"text/plain\") item.getAsString((text) => editor.handle.pasteText(text));\n\t\tawait pasteFile(item, editor);\n\t});\n}\n\nfunction detectShake(e: PointerEvent | MouseEvent): boolean {\n\tconst SENSITIVITY_DIRECTION_CHANGES = 3;\n\tconst SENSITIVITY_DISTANCE_TO_DISPLACEMENT_RATIO = 0.1;\n\tconst DETECTION_WINDOW_MS = 500;\n\tconst DEBOUNCE_MS = 1000;\n\n\t// Add the current mouse position and time to our list of samples\n\tconst now = Date.now();\n\tshakeSamples.push({ x: e.clientX, y: e.clientY, time: now });\n\n\t// Remove samples that are older than our time window\n\twhile (shakeSamples.length > 0 && now - shakeSamples[0].time > DETECTION_WINDOW_MS) {\n\t\tshakeSamples.shift();\n\t}\n\n\t// We can't be shaking if it's too early in terms of samples or debounce time\n\tif (shakeSamples.length <= 3 || now - lastShakeTime <= DEBOUNCE_MS) return false;\n\n\t// Calculate the total distance traveled\n\tlet totalDistanceSquared = 0;\n\tfor (let i = 1; i < shakeSamples.length; i += 1) {\n\t\tconst p1 = shakeSamples[i - 1];\n\t\tconst p2 = shakeSamples[i];\n\t\ttotalDistanceSquared += (p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2;\n\t}\n\n\t// Count the number of times the mouse changes direction significantly, and the average position of the mouse\n\tlet directionChanges = 0;\n\tconst averagePoint = { x: 0, y: 0 };\n\tlet averagePointCount = 0;\n\tfor (let i = 0; i < shakeSamples.length - 2; i += 1) {\n\t\tconst p1 = shakeSamples[i];\n\t\tconst p2 = shakeSamples[i + 1];\n\t\tconst p3 = shakeSamples[i + 2];\n\n\t\tconst vector1 = { x: p2.x - p1.x, y: p2.y - p1.y };\n\t\tconst vector2 = { x: p3.x - p2.x, y: p3.y - p2.y };\n\n\t\t// Check if the dot product is negative, which indicates the angle between vectors is > 90 degrees\n\t\tif (vector1.x * vector2.x + vector1.y * vector2.y < 0) directionChanges += 1;\n\n\t\taveragePoint.x += p2.x;\n\t\taveragePoint.y += p2.y;\n\t\taveragePointCount += 1;\n\t}\n\tif (averagePointCount > 0) {\n\t\taveragePoint.x /= averagePointCount;\n\t\taveragePoint.y /= averagePointCount;\n\t}\n\n\t// Calculate the displacement (the distance between the first and last mouse positions)\n\tconst lastPoint = shakeSamples[shakeSamples.length - 1];\n\tconst displacementSquared = (lastPoint.x - averagePoint.x) ** 2 + (lastPoint.y - averagePoint.y) ** 2;\n\n\t// A shake is detected if the mouse has traveled a lot but not moved far, and has changed direction enough times\n\tif (SENSITIVITY_DISTANCE_TO_DISPLACEMENT_RATIO * totalDistanceSquared >= displacementSquared && directionChanges >= SENSITIVITY_DIRECTION_CHANGES) {\n\t\tlastShakeTime = now;\n\t\tshakeSamples.length = 0;\n\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nfunction targetIsTextField(target: EventTarget | HTMLElement | undefined): boolean {\n\treturn target instanceof HTMLElement && (target.nodeName === \"INPUT\" || target.nodeName === \"TEXTAREA\" || target.isContentEditable);\n}\n\nfunction potentiallyRestoreCanvasFocus(e: Event) {\n\tconst appElement = window.document.querySelector(\"[data-app-container]\");\n\tconst app = appElement instanceof HTMLElement ? appElement : null;\n\n\tconst newInCanvasArea =\n\t\t(e.target instanceof Element && e.target.closest(\"[data-viewport], [data-viewport-container], [data-graph]\")) instanceof Element &&\n\t\t!targetIsTextField(window.document.activeElement || undefined);\n\tif (!canvasFocused && newInCanvasArea) {\n\t\tcanvasFocused = true;\n\t\tapp?.focus();\n\t}\n}\n\n// Self-accepting HMR: tear down the old instance and re-create with the new module's code\nimport.meta.hot?.accept((newModule) => {\n\tdestroyInputManager();\n\tif (editorRef && dialogStore && portfolioStore && documentStore) newModule?.createInputManager(editorRef, dialogStore, portfolioStore, documentStore);\n});\n"
  },
  {
    "path": "frontend/src/managers/localization.ts",
    "content": "import type { Editor } from \"@graphite/editor\";\n\nlet editorRef: Editor | undefined = undefined;\n\nexport function createLocalizationManager(editor: Editor) {\n\teditorRef = editor;\n\n\teditor.subscriptions.subscribeFrontendMessage(\"TriggerAboutGraphiteLocalizedCommitDate\", (data) => {\n\t\tconst localized = localizeTimestamp(data.commitDate);\n\t\teditor.handle.requestAboutGraphiteDialogWithLocalizedCommitDate(localized.timestamp, localized.year);\n\t});\n}\n\nexport function destroyLocalizationManager() {\n\tconst editor = editorRef;\n\tif (!editor) return;\n\n\teditor.subscriptions.unsubscribeFrontendMessage(\"TriggerAboutGraphiteLocalizedCommitDate\");\n}\n\nfunction localizeTimestamp(utc: string): { timestamp: string; year: string } {\n\t// Timestamp\n\tconst date = new Date(utc);\n\tif (Number.isNaN(date.getTime())) return { timestamp: utc, year: `${new Date().getFullYear()}` };\n\n\tconst timezoneName = Intl.DateTimeFormat(undefined, { timeZoneName: \"longGeneric\" })\n\t\t.formatToParts(new Date())\n\t\t.find((part) => part.type === \"timeZoneName\");\n\n\tconst dateString = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, \"0\")}-${String(date.getDate()).padStart(2, \"0\")}`;\n\tconst timeString = `${String(date.getHours()).padStart(2, \"0\")}:${String(date.getMinutes()).padStart(2, \"0\")}`;\n\tconst timezoneNameString = timezoneName?.value;\n\treturn { timestamp: `${dateString} ${timeString} ${timezoneNameString}`, year: String(date.getFullYear()) };\n}\n\n// Self-accepting HMR: tear down the old instance and re-create with the new module's code\nimport.meta.hot?.accept((newModule) => {\n\tdestroyLocalizationManager();\n\tif (editorRef) newModule?.createLocalizationManager(editorRef);\n});\n"
  },
  {
    "path": "frontend/src/managers/panic.ts",
    "content": "import type { Editor } from \"@graphite/editor\";\nimport { createCrashDialog } from \"@graphite/stores/dialog\";\n\nlet editorRef: Editor | undefined = undefined;\n\nexport function createPanicManager(editor: Editor) {\n\teditorRef = editor;\n\n\teditor.subscriptions.subscribeFrontendMessage(\"DisplayDialogPanic\", (data) => {\n\t\t// `Error.stackTraceLimit` is only available in V8/Chromium\n\t\tconst previousStackTraceLimit = Error.stackTraceLimit;\n\t\tError.stackTraceLimit = Infinity;\n\t\tconst stackTrace = new Error().stack || \"\";\n\t\tError.stackTraceLimit = previousStackTraceLimit;\n\n\t\tconst panicDetails = `${data.panicInfo}${stackTrace ? `\\n\\n${stackTrace}` : \"\"}`;\n\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.error(panicDetails);\n\n\t\tcreateCrashDialog(panicDetails);\n\t});\n}\n\nexport function destroyPanicManager() {\n\tconst editor = editorRef;\n\tif (!editor) return;\n\n\teditor.subscriptions.unsubscribeFrontendMessage(\"DisplayDialogPanic\");\n}\n\n// Self-accepting HMR: tear down the old instance and re-create with the new module's code\nimport.meta.hot?.accept((newModule) => {\n\tdestroyPanicManager();\n\tif (editorRef) newModule?.createPanicManager(editorRef);\n});\n"
  },
  {
    "path": "frontend/src/managers/persistence.ts",
    "content": "import * as idb from \"idb-keyval\";\nimport { get } from \"svelte/store\";\n\nimport type { Editor } from \"@graphite/editor\";\nimport type { PortfolioStore } from \"@graphite/stores/portfolio\";\nimport type { MessageBody } from \"@graphite/subscription-router\";\n\nlet editorRef: Editor | undefined = undefined;\nlet portfolioStore: PortfolioStore | undefined = undefined;\n\nexport function createPersistenceManager(editor: Editor, portfolio: PortfolioStore) {\n\teditorRef = editor;\n\tportfolioStore = portfolio;\n\n\teditor.subscriptions.subscribeFrontendMessage(\"TriggerSavePreferences\", async (data) => {\n\t\tawait saveEditorPreferences(data.preferences);\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"TriggerLoadPreferences\", async () => {\n\t\tawait loadEditorPreferences(editor);\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"TriggerPersistenceWriteDocument\", async (data) => {\n\t\tawait storeDocument(data, portfolio);\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"TriggerPersistenceRemoveDocument\", async (data) => {\n\t\tawait removeDocument(String(data.documentId), portfolio);\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"TriggerLoadFirstAutoSaveDocument\", async () => {\n\t\tawait loadFirstDocument(editor);\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"TriggerLoadRestAutoSaveDocuments\", async () => {\n\t\tawait loadRestDocuments(editor);\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"TriggerOpenLaunchDocuments\", async () => {\n\t\t// TODO: Could be used to load documents from URL params or similar on launch\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"TriggerSaveActiveDocument\", async (data) => {\n\t\tconst indexedDbStorage = idb.createStore(\"graphite\", \"store\");\n\n\t\tconst previouslySavedDocuments = await idb.get<Record<string, MessageBody<\"TriggerPersistenceWriteDocument\">>>(\"documents\", indexedDbStorage);\n\n\t\tconst documentId = String(data.documentId);\n\n\t\t// TODO: Eventually remove this document upgrade code\n\t\t// Migrate TriggerPersistenceWriteDocument.documentId from string to bigint if needed\n\t\tif (previouslySavedDocuments) {\n\t\t\tObject.values(previouslySavedDocuments).forEach((doc) => {\n\t\t\t\tif (typeof doc.documentId === \"string\") doc.documentId = BigInt(doc.documentId);\n\t\t\t});\n\t\t}\n\n\t\tif (!previouslySavedDocuments) return;\n\t\tif (documentId in previouslySavedDocuments) {\n\t\t\tawait storeCurrentDocumentId(documentId);\n\t\t}\n\t});\n}\n\nexport function destroyPersistenceManager() {\n\tconst editor = editorRef;\n\tif (!editor) return;\n\n\teditor.subscriptions.unsubscribeFrontendMessage(\"TriggerSavePreferences\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"TriggerLoadPreferences\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"TriggerPersistenceWriteDocument\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"TriggerPersistenceRemoveDocument\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"TriggerLoadFirstAutoSaveDocument\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"TriggerLoadRestAutoSaveDocuments\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"TriggerOpenLaunchDocuments\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"TriggerSaveActiveDocument\");\n}\n\nexport async function storeCurrentDocumentId(documentId: string) {\n\tconst indexedDbStorage = idb.createStore(\"graphite\", \"store\");\n\n\tawait idb.set(\"current_document_id\", String(documentId), indexedDbStorage);\n}\n\nexport async function storeDocument(autoSaveDocument: MessageBody<\"TriggerPersistenceWriteDocument\">, portfolio: PortfolioStore) {\n\tconst indexedDbStorage = idb.createStore(\"graphite\", \"store\");\n\n\tawait idb.update<Record<string, MessageBody<\"TriggerPersistenceWriteDocument\">>>(\n\t\t\"documents\",\n\t\t(old) => {\n\t\t\tconst documents = old || {};\n\t\t\tdocuments[String(autoSaveDocument.documentId)] = autoSaveDocument;\n\t\t\treturn documents;\n\t\t},\n\t\tindexedDbStorage,\n\t);\n\n\tconst documentOrder = get(portfolio).documents.map((doc) => String(doc.id));\n\tawait idb.set(\"documents_tab_order\", documentOrder, indexedDbStorage);\n\tawait storeCurrentDocumentId(String(autoSaveDocument.documentId));\n}\n\nexport async function removeDocument(id: string, portfolio: PortfolioStore) {\n\tconst indexedDbStorage = idb.createStore(\"graphite\", \"store\");\n\n\tawait idb.update<Record<string, MessageBody<\"TriggerPersistenceWriteDocument\">>>(\n\t\t\"documents\",\n\t\t(old) => {\n\t\t\tconst documents = old || {};\n\t\t\tdelete documents[id];\n\t\t\treturn documents;\n\t\t},\n\t\tindexedDbStorage,\n\t);\n\n\tawait idb.update<string[]>(\n\t\t\"documents_tab_order\",\n\t\t(old) => {\n\t\t\tconst order = old || [];\n\t\t\treturn order.filter((docId) => docId !== id);\n\t\t},\n\t\tindexedDbStorage,\n\t);\n\n\tconst documentCount = get(portfolio).documents.length;\n\tif (documentCount > 0) {\n\t\tconst documentIndex = get(portfolio).activeDocumentIndex;\n\t\tconst documentId = String(get(portfolio).documents[documentIndex].id);\n\n\t\tconst tabOrder = (await idb.get<string[]>(\"documents_tab_order\", indexedDbStorage)) || [];\n\t\tif (tabOrder.includes(documentId)) {\n\t\t\tawait storeCurrentDocumentId(documentId);\n\t\t}\n\t} else {\n\t\tawait idb.del(\"current_document_id\", indexedDbStorage);\n\t}\n}\n\nexport async function loadFirstDocument(editor: Editor) {\n\tconst indexedDbStorage = idb.createStore(\"graphite\", \"store\");\n\n\tconst previouslySavedDocuments = await idb.get<Record<string, MessageBody<\"TriggerPersistenceWriteDocument\">>>(\"documents\", indexedDbStorage);\n\n\t// TODO: Eventually remove this document upgrade code\n\t// Migrate TriggerPersistenceWriteDocument.documentId from string to bigint if the browser is storing the old format as strings\n\tif (previouslySavedDocuments) {\n\t\tObject.values(previouslySavedDocuments).forEach((doc) => {\n\t\t\tif (typeof doc.documentId === \"string\") doc.documentId = BigInt(doc.documentId);\n\t\t});\n\t}\n\n\tconst documentOrder = await idb.get<string[]>(\"documents_tab_order\", indexedDbStorage);\n\tconst currentDocumentIdString = await idb.get<string>(\"current_document_id\", indexedDbStorage);\n\tconst currentDocumentId = currentDocumentIdString ? BigInt(currentDocumentIdString) : undefined;\n\tif (!previouslySavedDocuments || !documentOrder) return;\n\n\tconst orderedSavedDocuments = documentOrder.flatMap((id) => (previouslySavedDocuments[id] ? [previouslySavedDocuments[id]] : []));\n\n\tif (currentDocumentId !== undefined && String(currentDocumentId) in previouslySavedDocuments) {\n\t\tconst doc = previouslySavedDocuments[String(currentDocumentId)];\n\t\teditor.handle.openAutoSavedDocument(doc.documentId, doc.details.name, doc.details.isSaved, doc.document, false);\n\t\teditor.handle.selectDocument(currentDocumentId);\n\t} else {\n\t\tconst len = orderedSavedDocuments.length;\n\t\tif (len > 0) {\n\t\t\tconst doc = orderedSavedDocuments[len - 1];\n\t\t\teditor.handle.openAutoSavedDocument(doc.documentId, doc.details.name, doc.details.isSaved, doc.document, false);\n\t\t\teditor.handle.selectDocument(doc.documentId);\n\t\t}\n\t}\n}\n\nexport async function loadRestDocuments(editor: Editor) {\n\tconst indexedDbStorage = idb.createStore(\"graphite\", \"store\");\n\n\tconst previouslySavedDocuments = await idb.get<Record<string, MessageBody<\"TriggerPersistenceWriteDocument\">>>(\"documents\", indexedDbStorage);\n\n\t// TODO: Eventually remove this document upgrade code\n\t// Migrate TriggerPersistenceWriteDocument.documentId from string to bigint if needed\n\tif (previouslySavedDocuments) {\n\t\tObject.values(previouslySavedDocuments).forEach((doc) => {\n\t\t\tif (typeof doc.documentId === \"string\") doc.documentId = BigInt(doc.documentId);\n\t\t});\n\t}\n\n\tconst documentOrder = await idb.get<string[]>(\"documents_tab_order\", indexedDbStorage);\n\tconst currentDocumentIdString = await idb.get<string>(\"current_document_id\", indexedDbStorage);\n\tconst currentDocumentId = currentDocumentIdString ? BigInt(currentDocumentIdString) : undefined;\n\tif (!previouslySavedDocuments || !documentOrder) return;\n\n\tconst orderedSavedDocuments = documentOrder.flatMap((id) => (previouslySavedDocuments[id] ? [previouslySavedDocuments[id]] : []));\n\n\tconst currentIndex = currentDocumentId !== undefined ? orderedSavedDocuments.findIndex((doc) => doc.documentId === currentDocumentId) : -1;\n\n\t// Open documents in order around the current document, placing earlier ones before it and later ones after\n\tif (currentIndex !== -1 && currentDocumentId !== undefined) {\n\t\tfor (let i = currentIndex - 1; i >= 0; i--) {\n\t\t\tconst { documentId, document, details } = orderedSavedDocuments[i];\n\t\t\tconst { name, isSaved } = details;\n\t\t\teditor.handle.openAutoSavedDocument(documentId, name, isSaved, document, true);\n\t\t}\n\t\tfor (let i = currentIndex + 1; i < orderedSavedDocuments.length; i++) {\n\t\t\tconst { documentId, document, details } = orderedSavedDocuments[i];\n\t\t\tconst { name, isSaved } = details;\n\t\t\teditor.handle.openAutoSavedDocument(documentId, name, isSaved, document, false);\n\t\t}\n\n\t\teditor.handle.selectDocument(currentDocumentId);\n\t}\n\t// No valid current document: open all remaining documents and select the last one\n\telse {\n\t\tconst length = orderedSavedDocuments.length;\n\n\t\tfor (let i = length - 2; i >= 0; i--) {\n\t\t\tconst { documentId, document, details } = orderedSavedDocuments[i];\n\t\t\tconst { name, isSaved } = details;\n\t\t\teditor.handle.openAutoSavedDocument(documentId, name, isSaved, document, true);\n\t\t}\n\n\t\tif (length > 0) editor.handle.selectDocument(orderedSavedDocuments[length - 1].documentId);\n\t}\n}\n\nexport async function saveEditorPreferences(preferences: unknown) {\n\tconst indexedDbStorage = idb.createStore(\"graphite\", \"store\");\n\n\tawait idb.set(\"preferences\", preferences, indexedDbStorage);\n}\n\nexport async function loadEditorPreferences(editor: Editor) {\n\tconst indexedDbStorage = idb.createStore(\"graphite\", \"store\");\n\n\tconst preferences = await idb.get<Record<string, unknown>>(\"preferences\", indexedDbStorage);\n\teditor.handle.loadPreferences(preferences ? JSON.stringify(preferences) : undefined);\n}\n\nexport async function wipeDocuments() {\n\tconst indexedDbStorage = idb.createStore(\"graphite\", \"store\");\n\n\tawait idb.del(\"documents_tab_order\", indexedDbStorage);\n\tawait idb.del(\"current_document_id\", indexedDbStorage);\n\tawait idb.del(\"documents\", indexedDbStorage);\n}\n\n// Self-accepting HMR: tear down the old instance and re-create with the new module's code\nimport.meta.hot?.accept((newModule) => {\n\tdestroyPersistenceManager();\n\tif (editorRef && portfolioStore) newModule?.createPersistenceManager(editorRef, portfolioStore);\n});\n"
  },
  {
    "path": "frontend/src/stores/app-window.ts",
    "content": "import { writable } from \"svelte/store\";\nimport type { Writable } from \"svelte/store\";\n\nimport type { AppWindowPlatform } from \"@graphite/../wasm/pkg/graphite_wasm\";\nimport type { Editor } from \"@graphite/editor\";\n\nexport type AppWindowStore = ReturnType<typeof createAppWindowStore>;\n\ntype AppWindowStoreState = {\n\tplatform: AppWindowPlatform;\n\tmaximized: boolean;\n\tfullscreen: boolean;\n\tviewportHolePunch: boolean;\n\tuiScale: number;\n};\nconst initialState: AppWindowStoreState = {\n\tplatform: \"Web\",\n\tmaximized: false,\n\tfullscreen: false,\n\tviewportHolePunch: false,\n\tuiScale: 1,\n};\n\nlet editorRef: Editor | undefined = undefined;\n\n// Store state persisted across HMR to maintain reactive subscriptions in the component tree\nconst store: Writable<AppWindowStoreState> = import.meta.hot?.data?.store || writable<AppWindowStoreState>(initialState);\nif (import.meta.hot) import.meta.hot.data.store = store;\nconst { subscribe, update } = store;\n\nexport function createAppWindowStore(editor: Editor) {\n\teditorRef = editor;\n\n\teditor.subscriptions.subscribeFrontendMessage(\"UpdatePlatform\", (data) => {\n\t\tupdate((state) => {\n\t\t\tstate.platform = data.platform;\n\t\t\treturn state;\n\t\t});\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"UpdateMaximized\", (data) => {\n\t\tupdate((state) => {\n\t\t\tstate.maximized = data.maximized;\n\t\t\treturn state;\n\t\t});\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"UpdateFullscreen\", (data) => {\n\t\tupdate((state) => {\n\t\t\tstate.fullscreen = data.fullscreen;\n\t\t\treturn state;\n\t\t});\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"UpdateViewportHolePunch\", (data) => {\n\t\tupdate((state) => {\n\t\t\tstate.viewportHolePunch = data.active;\n\t\t\treturn state;\n\t\t});\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"UpdateUIScale\", (data) => {\n\t\tupdate((state) => {\n\t\t\tstate.uiScale = data.scale;\n\t\t\treturn state;\n\t\t});\n\t});\n\n\treturn { subscribe };\n}\n\nexport function destroyAppWindowStore() {\n\tconst editor = editorRef;\n\tif (!editor) return;\n\n\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdatePlatform\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdateMaximized\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdateFullscreen\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdateViewportHolePunch\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdateUIScale\");\n}\n"
  },
  {
    "path": "frontend/src/stores/dialog.ts",
    "content": "import { tick } from \"svelte\";\nimport { writable } from \"svelte/store\";\nimport type { Writable } from \"svelte/store\";\n\nimport type { Layout } from \"@graphite/../wasm/pkg/graphite_wasm\";\nimport type { Editor } from \"@graphite/editor\";\nimport type { IconName } from \"@graphite/icons\";\nimport { patchLayout } from \"@graphite/utility-functions/widgets\";\n\nexport type DialogStore = ReturnType<typeof createDialogStore>;\n\ntype DialogStoreState = {\n\tvisible: boolean;\n\ttitle: string;\n\ticon: IconName | undefined;\n\tbuttons: Layout;\n\tcolumn1: Layout;\n\tcolumn2: Layout;\n\tpanicDetails: string;\n};\nconst initialState: DialogStoreState = {\n\tvisible: false,\n\ttitle: \"\",\n\ticon: undefined,\n\tbuttons: [],\n\tcolumn1: [],\n\tcolumn2: [],\n\t// Special case for the crash dialog because we cannot handle button widget callbacks from Rust once the editor has panicked\n\tpanicDetails: \"\",\n};\n\nlet editorRef: Editor | undefined = undefined;\n\n// Store state persisted across HMR to maintain reactive subscriptions in the component tree\nconst store: Writable<DialogStoreState> = import.meta.hot?.data?.store || writable<DialogStoreState>(initialState);\nif (import.meta.hot) import.meta.hot.data.store = store;\nconst { subscribe, update } = store;\n\nexport function createDialogStore(editor: Editor) {\n\teditorRef = editor;\n\n\teditor.subscriptions.subscribeFrontendMessage(\"DisplayDialog\", (data) => {\n\t\tupdate((state) => {\n\t\t\tstate.visible = true;\n\n\t\t\tstate.title = data.title;\n\t\t\tstate.icon = data.icon;\n\n\t\t\treturn state;\n\t\t});\n\t});\n\teditor.subscriptions.subscribeLayoutUpdate(\"DialogButtons\", async (data) => {\n\t\tawait tick();\n\n\t\tupdate((state) => {\n\t\t\tpatchLayout(state.buttons, data);\n\n\t\t\treturn state;\n\t\t});\n\t});\n\teditor.subscriptions.subscribeLayoutUpdate(\"DialogColumn1\", async (data) => {\n\t\tawait tick();\n\n\t\tupdate((state) => {\n\t\t\tpatchLayout(state.column1, data);\n\n\t\t\treturn state;\n\t\t});\n\t});\n\teditor.subscriptions.subscribeLayoutUpdate(\"DialogColumn2\", async (data) => {\n\t\tawait tick();\n\n\t\tupdate((state) => {\n\t\t\tpatchLayout(state.column2, data);\n\n\t\t\treturn state;\n\t\t});\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"DialogClose\", () => {\n\t\tupdate((state) => {\n\t\t\t// Disallow dismissing the crash dialog since it should remain as the final notification\n\t\t\tif (state.panicDetails === \"\") state.visible = false;\n\n\t\t\treturn state;\n\t\t});\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"TriggerDisplayThirdPartyLicensesDialog\", async () => {\n\t\tconst BACKUP_URL = \"https://editor.graphite.art/third-party-licenses.txt\";\n\t\tlet licenseText = `Content was not able to load. Please check your network connection and try again.\\n\\nOr visit ${BACKUP_URL} for the license notices.`;\n\n\t\ttry {\n\t\t\tconst response = await fetch(\"/third-party-licenses.txt\");\n\t\t\tif (response.ok && response.headers.get(\"Content-Type\")?.includes(\"text/plain\")) licenseText = await response.text();\n\t\t} catch {\n\t\t\t// Do nothing on network error\n\t\t}\n\n\t\teditor.handle.requestLicensesThirdPartyDialogWithLicenseText(licenseText);\n\t});\n\n\treturn { subscribe };\n}\n\nexport function destroyDialogStore() {\n\tconst editor = editorRef;\n\tif (!editor) return;\n\n\teditor.subscriptions.unsubscribeFrontendMessage(\"DisplayDialog\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"DialogClose\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"TriggerDisplayThirdPartyLicensesDialog\");\n\teditor.subscriptions.unsubscribeLayoutUpdate(\"DialogButtons\");\n\teditor.subscriptions.unsubscribeLayoutUpdate(\"DialogColumn1\");\n\teditor.subscriptions.unsubscribeLayoutUpdate(\"DialogColumn2\");\n}\n\n// Creates a crash dialog from JS once the editor has panicked.\n// Normal dialogs are created in the Rust backend, but for the crash dialog, the editor has panicked so it cannot respond to widget callbacks.\nexport function createCrashDialog(panicDetails: string) {\n\tupdate((state) => {\n\t\tstate.visible = true;\n\n\t\tstate.icon = \"Failure\";\n\t\tstate.title = \"Crash\";\n\t\tstate.panicDetails = panicDetails;\n\n\t\tstate.column1 = [];\n\t\tstate.column2 = [];\n\t\tstate.buttons = [];\n\n\t\treturn state;\n\t});\n}\n"
  },
  {
    "path": "frontend/src/stores/document.ts",
    "content": "import { tick } from \"svelte\";\nimport { writable } from \"svelte/store\";\nimport type { Writable } from \"svelte/store\";\n\nimport type { Layout } from \"@graphite/../wasm/pkg/graphite_wasm\";\nimport type { Editor } from \"@graphite/editor\";\nimport { patchLayout } from \"@graphite/utility-functions/widgets\";\n\nexport type DocumentStore = ReturnType<typeof createDocumentStore>;\n\ntype DocumentStoreState = {\n\ttoolOptionsLayout: Layout;\n\tdocumentBarLayout: Layout;\n\ttoolShelfLayout: Layout;\n\tworkingColorsLayout: Layout;\n\tnodeGraphControlBarLayout: Layout;\n\tgraphViewOverlayOpen: boolean;\n\tfadeArtwork: number;\n};\nconst initialState: DocumentStoreState = {\n\ttoolOptionsLayout: [],\n\tdocumentBarLayout: [],\n\ttoolShelfLayout: [],\n\tworkingColorsLayout: [],\n\tnodeGraphControlBarLayout: [],\n\tgraphViewOverlayOpen: false,\n\tfadeArtwork: 100,\n};\n\nlet editorRef: Editor | undefined = undefined;\n\n// Store state persisted across HMR to maintain reactive subscriptions in the component tree\nconst store: Writable<DocumentStoreState> = import.meta.hot?.data?.store || writable<DocumentStoreState>(initialState);\nif (import.meta.hot) import.meta.hot.data.store = store;\nconst { subscribe, update } = store;\n\nexport function createDocumentStore(editor: Editor) {\n\teditorRef = editor;\n\n\t// Update layouts\n\teditor.subscriptions.subscribeFrontendMessage(\"UpdateGraphFadeArtwork\", (data) => {\n\t\tupdate((state) => {\n\t\t\tstate.fadeArtwork = data.percentage;\n\t\t\treturn state;\n\t\t});\n\t});\n\teditor.subscriptions.subscribeLayoutUpdate(\"ToolOptions\", async (data) => {\n\t\tawait tick();\n\n\t\tupdate((state) => {\n\t\t\tpatchLayout(state.toolOptionsLayout, data);\n\t\t\treturn state;\n\t\t});\n\t});\n\teditor.subscriptions.subscribeLayoutUpdate(\"DocumentBar\", async (data) => {\n\t\tawait tick();\n\n\t\tupdate((state) => {\n\t\t\tpatchLayout(state.documentBarLayout, data);\n\t\t\treturn state;\n\t\t});\n\t});\n\teditor.subscriptions.subscribeLayoutUpdate(\"ToolShelf\", async (data) => {\n\t\tawait tick();\n\n\t\tupdate((state) => {\n\t\t\tpatchLayout(state.toolShelfLayout, data);\n\t\t\treturn state;\n\t\t});\n\t});\n\teditor.subscriptions.subscribeLayoutUpdate(\"WorkingColors\", async (data) => {\n\t\tawait tick();\n\n\t\tupdate((state) => {\n\t\t\tpatchLayout(state.workingColorsLayout, data);\n\t\t\treturn state;\n\t\t});\n\t});\n\teditor.subscriptions.subscribeLayoutUpdate(\"NodeGraphControlBar\", async (data) => {\n\t\tawait tick();\n\n\t\tupdate((state) => {\n\t\t\tpatchLayout(state.nodeGraphControlBarLayout, data);\n\t\t\treturn state;\n\t\t});\n\t});\n\n\t// Show or hide the graph view overlay\n\teditor.subscriptions.subscribeFrontendMessage(\"UpdateGraphViewOverlay\", (data) => {\n\t\tupdate((state) => {\n\t\t\tstate.graphViewOverlayOpen = data.open;\n\t\t\treturn state;\n\t\t});\n\t});\n\n\treturn { subscribe };\n}\n\nexport function destroyDocumentStore() {\n\tconst editor = editorRef;\n\tif (!editor) return;\n\n\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdateGraphFadeArtwork\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdateGraphViewOverlay\");\n\teditor.subscriptions.unsubscribeLayoutUpdate(\"ToolOptions\");\n\teditor.subscriptions.unsubscribeLayoutUpdate(\"DocumentBar\");\n\teditor.subscriptions.unsubscribeLayoutUpdate(\"ToolShelf\");\n\teditor.subscriptions.unsubscribeLayoutUpdate(\"WorkingColors\");\n\teditor.subscriptions.unsubscribeLayoutUpdate(\"NodeGraphControlBar\");\n}\n"
  },
  {
    "path": "frontend/src/stores/fullscreen.ts",
    "content": "import { get, writable } from \"svelte/store\";\nimport type { Writable } from \"svelte/store\";\n\nimport type { Editor } from \"@graphite/editor\";\n\nexport type FullscreenStore = ReturnType<typeof createFullscreenStore>;\n\ntype FullscreenStoreState = {\n\twindowFullscreen: boolean;\n\tkeyboardLocked: boolean;\n};\nconst initialState: FullscreenStoreState = {\n\twindowFullscreen: false,\n\tkeyboardLocked: false,\n};\n\nlet editorRef: Editor | undefined = undefined;\n\n// Store state persisted across HMR to maintain reactive subscriptions in the component tree\nconst store: Writable<FullscreenStoreState> = import.meta.hot?.data?.store || writable<FullscreenStoreState>(initialState);\nif (import.meta.hot) import.meta.hot.data.store = store;\nconst { subscribe, update } = store;\n\nexport function createFullscreenStore(editor: Editor) {\n\teditorRef = editor;\n\n\teditor.subscriptions.subscribeFrontendMessage(\"WindowFullscreen\", () => {\n\t\ttoggleFullscreen();\n\t});\n\n\treturn { subscribe };\n}\n\nexport function destroyFullscreenStore() {\n\tconst editor = editorRef;\n\tif (!editor) return;\n\n\teditor.subscriptions.unsubscribeFrontendMessage(\"WindowFullscreen\");\n}\n\nexport function fullscreenModeChanged() {\n\tupdate((state) => {\n\t\tstate.windowFullscreen = Boolean(document.fullscreenElement);\n\t\tif (!state.windowFullscreen) state.keyboardLocked = false;\n\t\treturn state;\n\t});\n}\n\nexport async function enterFullscreen() {\n\tawait document.documentElement.requestFullscreen();\n\n\tconst keyboardLockApiSupported = navigator.keyboard !== undefined && \"lock\" in navigator.keyboard;\n\n\tif (keyboardLockApiSupported && navigator.keyboard) {\n\t\tawait navigator.keyboard.lock([\"ControlLeft\", \"ControlRight\"]);\n\n\t\tupdate((state) => {\n\t\t\tstate.keyboardLocked = true;\n\t\t\treturn state;\n\t\t});\n\t}\n}\n\nexport async function exitFullscreen() {\n\tawait document.exitFullscreen();\n}\n\nexport async function toggleFullscreen() {\n\tconst state = get(store);\n\tif (state.windowFullscreen) await exitFullscreen();\n\telse await enterFullscreen();\n}\n"
  },
  {
    "path": "frontend/src/stores/node-graph.ts",
    "content": "import { writable } from \"svelte/store\";\nimport type { Writable } from \"svelte/store\";\n\nimport type { NodeGraphErrorDiagnostic, BoxSelection, FrontendClickTargets, ContextMenuInformation, FrontendNode, FrontendNodeType, WirePath } from \"@graphite/../wasm/pkg/graphite_wasm\";\nimport type { Editor } from \"@graphite/editor\";\nimport type { MessageBody } from \"@graphite/subscription-router\";\n\nexport type NodeGraphStore = ReturnType<typeof createNodeGraphStore>;\n\ntype NodeGraphStoreState = {\n\tbox: BoxSelection | undefined;\n\tclickTargets: FrontendClickTargets | undefined;\n\tcontextMenuInformation: ContextMenuInformation | undefined;\n\terror: NodeGraphErrorDiagnostic | undefined;\n\tlayerWidths: Map<bigint, number>;\n\tchainWidths: Map<bigint, number>;\n\thasLeftInputWire: Map<bigint, boolean>;\n\tupdateImportsExports: MessageBody<\"UpdateImportsExports\"> | undefined;\n\tnodes: Map<bigint, FrontendNode>;\n\tvisibleNodes: Set<bigint>;\n\t/// The index is the exposed input index. The exports have a first key value of u32::MAX.\n\twires: Map<bigint, Map<number, WirePath>>;\n\twirePathInProgress: WirePath | undefined;\n\tnodeDescriptions: Map<string, string>;\n\tnodeTypes: FrontendNodeType[];\n\tthumbnails: Map<bigint, string>;\n\tselected: bigint[];\n\ttransform: { scale: number; x: number; y: number };\n\tinSelectedNetwork: boolean;\n\treorderImportIndex: number | undefined;\n\treorderExportIndex: number | undefined;\n};\nconst initialState: NodeGraphStoreState = {\n\tbox: undefined,\n\tclickTargets: undefined,\n\tcontextMenuInformation: undefined,\n\terror: undefined,\n\tlayerWidths: new Map(),\n\tchainWidths: new Map(),\n\thasLeftInputWire: new Map(),\n\tupdateImportsExports: undefined,\n\tnodes: new Map(),\n\tvisibleNodes: new Set(),\n\twires: new Map(),\n\twirePathInProgress: undefined,\n\tnodeDescriptions: new Map(),\n\tnodeTypes: [],\n\tthumbnails: new Map(),\n\tselected: [],\n\ttransform: { scale: 1, x: 0, y: 0 },\n\tinSelectedNetwork: true,\n\treorderImportIndex: undefined,\n\treorderExportIndex: undefined,\n};\n\nlet editorRef: Editor | undefined = undefined;\n\n// Store state persisted across HMR to maintain reactive subscriptions in the component tree\nconst store: Writable<NodeGraphStoreState> = import.meta.hot?.data?.store || writable<NodeGraphStoreState>(initialState);\nif (import.meta.hot) import.meta.hot.data.store = store;\nconst { subscribe, update } = store;\n\nexport function createNodeGraphStore(editor: Editor) {\n\teditorRef = editor;\n\n\t// Set up message subscriptions on creation\n\teditor.subscriptions.subscribeFrontendMessage(\"SendUIMetadata\", (data) => {\n\t\tupdate((state) => {\n\t\t\tstate.nodeDescriptions = new Map(data.nodeDescriptions);\n\t\t\tstate.nodeTypes = data.nodeTypes;\n\t\t\treturn state;\n\t\t});\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"UpdateBox\", (data) => {\n\t\tupdate((state) => {\n\t\t\tstate.box = data.box;\n\t\t\treturn state;\n\t\t});\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"UpdateClickTargets\", (data) => {\n\t\tupdate((state) => {\n\t\t\tstate.clickTargets = data.clickTargets;\n\t\t\treturn state;\n\t\t});\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"UpdateContextMenuInformation\", (data) => {\n\t\tupdate((state) => {\n\t\t\tstate.contextMenuInformation = data.contextMenuInformation;\n\t\t\treturn state;\n\t\t});\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"UpdateImportReorderIndex\", (data) => {\n\t\tupdate((state) => {\n\t\t\tstate.reorderImportIndex = data.importIndex;\n\t\t\treturn state;\n\t\t});\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"UpdateExportReorderIndex\", (data) => {\n\t\tupdate((state) => {\n\t\t\tstate.reorderExportIndex = data.exportIndex;\n\t\t\treturn state;\n\t\t});\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"UpdateImportsExports\", (data) => {\n\t\tupdate((state) => {\n\t\t\tstate.updateImportsExports = data;\n\t\t\treturn state;\n\t\t});\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"UpdateInSelectedNetwork\", (data) => {\n\t\tupdate((state) => {\n\t\t\tstate.inSelectedNetwork = data.inSelectedNetwork;\n\t\t\treturn state;\n\t\t});\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"UpdateLayerWidths\", (data) => {\n\t\tupdate((state) => {\n\t\t\tstate.layerWidths = data.layerWidths;\n\t\t\tstate.chainWidths = data.chainWidths;\n\t\t\tstate.hasLeftInputWire = data.hasLeftInputWire;\n\t\t\treturn state;\n\t\t});\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"UpdateNodeGraphNodes\", (data) => {\n\t\tupdate((state) => {\n\t\t\tstate.nodes.clear();\n\t\t\tdata.nodes.forEach((node) => {\n\t\t\t\tstate.nodes.set(node.id, node);\n\t\t\t});\n\t\t\treturn state;\n\t\t});\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"UpdateNodeGraphErrorDiagnostic\", (data) => {\n\t\tupdate((state) => {\n\t\t\tstate.error = data.error;\n\t\t\treturn state;\n\t\t});\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"UpdateVisibleNodes\", (data) => {\n\t\tupdate((state) => {\n\t\t\tstate.visibleNodes = new Set<bigint>(data.nodes);\n\t\t\treturn state;\n\t\t});\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"UpdateNodeGraphWires\", (data) => {\n\t\tupdate((state) => {\n\t\t\tdata.wires.forEach((wireUpdate) => {\n\t\t\t\tlet inputMap = state.wires.get(wireUpdate.id);\n\t\t\t\t// If it doesn't exist, create it and set it in the outer map\n\t\t\t\tif (!inputMap) {\n\t\t\t\t\tinputMap = new Map();\n\t\t\t\t\tstate.wires.set(wireUpdate.id, inputMap);\n\t\t\t\t}\n\t\t\t\tif (wireUpdate.wirePathUpdate !== undefined) {\n\t\t\t\t\tinputMap.set(wireUpdate.inputIndex, wireUpdate.wirePathUpdate);\n\t\t\t\t} else {\n\t\t\t\t\tinputMap.delete(wireUpdate.inputIndex);\n\t\t\t\t}\n\t\t\t});\n\t\t\treturn state;\n\t\t});\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"ClearAllNodeGraphWires\", () => {\n\t\tupdate((state) => {\n\t\t\tstate.wires.clear();\n\t\t\treturn state;\n\t\t});\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"UpdateNodeGraphSelection\", (data) => {\n\t\tupdate((state) => {\n\t\t\tstate.selected = data.selected;\n\t\t\treturn state;\n\t\t});\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"UpdateNodeGraphTransform\", (data) => {\n\t\tupdate((state) => {\n\t\t\tstate.transform = { scale: data.scale, x: data.translation[0], y: data.translation[1] };\n\t\t\treturn state;\n\t\t});\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"UpdateNodeThumbnail\", (data) => {\n\t\tupdate((state) => {\n\t\t\tstate.thumbnails.set(data.id, data.value);\n\t\t\treturn state;\n\t\t});\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"UpdateWirePathInProgress\", (data) => {\n\t\tupdate((state) => {\n\t\t\tstate.wirePathInProgress = data.wirePath;\n\t\t\treturn state;\n\t\t});\n\t});\n\n\treturn { subscribe };\n}\n\nexport function destroyNodeGraphStore() {\n\tconst editor = editorRef;\n\tif (!editor) return;\n\n\teditor.subscriptions.unsubscribeFrontendMessage(\"SendUIMetadata\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdateBox\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdateClickTargets\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdateContextMenuInformation\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdateImportReorderIndex\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdateExportReorderIndex\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdateImportsExports\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdateInSelectedNetwork\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdateLayerWidths\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdateNodeGraphNodes\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdateNodeGraphErrorDiagnostic\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdateVisibleNodes\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdateNodeGraphWires\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"ClearAllNodeGraphWires\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdateNodeGraphSelection\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdateNodeGraphTransform\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdateNodeThumbnail\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdateWirePathInProgress\");\n}\n\nexport function closeContextMenu() {\n\tupdate((state) => {\n\t\tstate.contextMenuInformation = undefined;\n\t\treturn state;\n\t});\n}\n"
  },
  {
    "path": "frontend/src/stores/portfolio.ts",
    "content": "import { writable } from \"svelte/store\";\nimport type { Writable } from \"svelte/store\";\n\nimport type { OpenDocument } from \"@graphite/../wasm/pkg/graphite_wasm\";\nimport type { Editor } from \"@graphite/editor\";\nimport { downloadFile, downloadFileBlob, upload } from \"@graphite/utility-functions/files\";\nimport { rasterizeSVG } from \"@graphite/utility-functions/rasterization\";\n\nexport type PortfolioStore = ReturnType<typeof createPortfolioStore>;\n\ntype PortfolioStoreState = {\n\tunsaved: boolean;\n\tdocuments: OpenDocument[];\n\tactiveDocumentIndex: number;\n\tdataPanelOpen: boolean;\n\tpropertiesPanelOpen: boolean;\n\tlayersPanelOpen: boolean;\n};\nconst initialState: PortfolioStoreState = {\n\tunsaved: false,\n\tdocuments: [],\n\tactiveDocumentIndex: 0,\n\tdataPanelOpen: false,\n\tpropertiesPanelOpen: true,\n\tlayersPanelOpen: true,\n};\n\nlet editorRef: Editor | undefined = undefined;\n\n// Store state persisted across HMR to maintain reactive subscriptions in the component tree\nconst store: Writable<PortfolioStoreState> = import.meta.hot?.data?.store || writable<PortfolioStoreState>(initialState);\nif (import.meta.hot) import.meta.hot.data.store = store;\nconst { subscribe, update } = store;\n\nexport function createPortfolioStore(editor: Editor) {\n\teditorRef = editor;\n\n\teditor.subscriptions.subscribeFrontendMessage(\"UpdateOpenDocumentsList\", (data) => {\n\t\tupdate((state) => {\n\t\t\tstate.documents = data.openDocuments;\n\t\t\treturn state;\n\t\t});\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"UpdateActiveDocument\", (data) => {\n\t\tupdate((state) => {\n\t\t\t// Assume we receive a correct document id\n\t\t\tconst activeId = state.documents.findIndex((doc) => doc.id === data.documentId);\n\t\t\tstate.activeDocumentIndex = activeId;\n\t\t\treturn state;\n\t\t});\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"TriggerFetchAndOpenDocument\", async (data) => {\n\t\ttry {\n\t\t\tconst url = new URL(`demo-artwork/${data.filename}`, document.location.href);\n\t\t\tconst response = await fetch(url);\n\t\t\teditor.handle.openFile(data.filename, await response.bytes());\n\t\t} catch {\n\t\t\t// Needs to be delayed until the end of the current call stack so the existing demo artwork dialog can be closed first, otherwise this dialog won't show\n\t\t\tsetTimeout(() => {\n\t\t\t\teditor.handle.errorDialog(\"Failed to open document\", \"The file could not be reached over the internet. You may be offline, or it may be missing.\");\n\t\t\t}, 0);\n\t\t}\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"TriggerOpen\", async () => {\n\t\tconst data = await upload(`image/*,.${editor.handle.fileExtension()}`, \"data\");\n\t\teditor.handle.openFile(data.filename, data.content);\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"TriggerImport\", async () => {\n\t\t// TODO: Use the same `accept` string as in the `TriggerOpen` handler once importing Graphite documents as nodes is supported\n\t\tconst data = await upload(\"image/*\", \"data\");\n\t\teditor.handle.importFile(data.filename, data.content);\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"TriggerSaveDocument\", (data) => {\n\t\tdownloadFile(data.name, data.content);\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"TriggerSaveFile\", (data) => {\n\t\tdownloadFile(data.name, data.content);\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"TriggerExportImage\", async (data) => {\n\t\tconst { svg, name, mime, size } = data;\n\n\t\t// Fill the canvas with white if it'll be a JPEG (which does not support transparency and defaults to black)\n\t\tconst backgroundColor = mime.endsWith(\"jpeg\") ? \"white\" : undefined;\n\n\t\t// Rasterize the SVG to an image file\n\t\ttry {\n\t\t\tconst blob = await rasterizeSVG(svg, size[0], size[1], mime, backgroundColor);\n\n\t\t\t// Have the browser download the file to the user's disk\n\t\t\tdownloadFileBlob(name, blob);\n\t\t} catch {\n\t\t\t// Fail silently if there's an error rasterizing the SVG, such as a zero-sized image\n\t\t}\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"UpdateDataPanelState\", async (data) => {\n\t\tupdate((state) => {\n\t\t\tstate.dataPanelOpen = data.open;\n\t\t\treturn state;\n\t\t});\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"UpdatePropertiesPanelState\", async (data) => {\n\t\tupdate((state) => {\n\t\t\tstate.propertiesPanelOpen = data.open;\n\t\t\treturn state;\n\t\t});\n\t});\n\n\teditor.subscriptions.subscribeFrontendMessage(\"UpdateLayersPanelState\", async (data) => {\n\t\tupdate((state) => {\n\t\t\tstate.layersPanelOpen = data.open;\n\t\t\treturn state;\n\t\t});\n\t});\n\n\treturn { subscribe };\n}\n\nexport function destroyPortfolioStore() {\n\tconst editor = editorRef;\n\tif (!editor) return;\n\n\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdateOpenDocumentsList\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdateActiveDocument\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"TriggerFetchAndOpenDocument\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"TriggerOpen\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"TriggerImport\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"TriggerSaveDocument\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"TriggerSaveFile\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"TriggerExportImage\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdateDataPanelState\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdatePropertiesPanelState\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"UpdateLayersPanelState\");\n}\n"
  },
  {
    "path": "frontend/src/stores/tooltip.ts",
    "content": "import { writable } from \"svelte/store\";\nimport type { Writable } from \"svelte/store\";\n\nimport type { ActionShortcut } from \"@graphite/../wasm/pkg/graphite_wasm\";\nimport type { Editor } from \"@graphite/editor\";\nimport { operatingSystem } from \"@graphite/utility-functions/platform\";\n\nexport type TooltipStore = ReturnType<typeof createTooltipStore>;\n\nconst SHOW_TOOLTIP_DELAY_MS = 500;\n\nlet tooltipTimeout: ReturnType<typeof setTimeout> | undefined = undefined;\n\ntype TooltipStoreState = {\n\tvisible: boolean;\n\telement: Element | undefined;\n\tposition: { x: number; y: number };\n\tshiftClickShortcut: ActionShortcut | undefined;\n\taltClickShortcut: ActionShortcut | undefined;\n\tfullscreenShortcut: ActionShortcut | undefined;\n};\nconst initialState: TooltipStoreState = {\n\tvisible: false,\n\telement: undefined,\n\tposition: { x: 0, y: 0 },\n\tshiftClickShortcut: undefined,\n\taltClickShortcut: undefined,\n\tfullscreenShortcut: undefined,\n};\n\nlet editorRef: Editor | undefined = undefined;\n\n// Store state persisted across HMR to maintain reactive subscriptions in the component tree\nconst store: Writable<TooltipStoreState> = import.meta.hot?.data?.store || writable<TooltipStoreState>(initialState);\nif (import.meta.hot) import.meta.hot.data.store = store;\nconst { subscribe, update } = store;\n\nexport function createTooltipStore(editor: Editor) {\n\teditorRef = editor;\n\n\tdocument.addEventListener(\"mouseover\", onMouseOver);\n\tdocument.addEventListener(\"mousemove\", onMouseMove);\n\tdocument.addEventListener(\"mouseleave\", onMouseLeave);\n\tdocument.addEventListener(\"mousedown\", closeTooltip);\n\tdocument.addEventListener(\"keydown\", closeTooltip);\n\tdocument.addEventListener(\"wheel\", closeTooltip);\n\n\teditor.subscriptions.subscribeFrontendMessage(\"SendShortcutShiftClick\", async (data) => {\n\t\tupdate((state) => {\n\t\t\tstate.shiftClickShortcut = data.shortcut;\n\t\t\treturn state;\n\t\t});\n\t});\n\teditor.subscriptions.subscribeFrontendMessage(\"SendShortcutAltClick\", async (data) => {\n\t\tupdate((state) => {\n\t\t\tstate.altClickShortcut = data.shortcut;\n\t\t\treturn state;\n\t\t});\n\t});\n\teditor.subscriptions.subscribeFrontendMessage(\"SendShortcutFullscreen\", async (data) => {\n\t\tupdate((state) => {\n\t\t\tstate.fullscreenShortcut = operatingSystem() === \"Mac\" ? data.shortcutMac : data.shortcut;\n\t\t\treturn state;\n\t\t});\n\t});\n\n\treturn { subscribe };\n}\n\nexport function destroyTooltipStore() {\n\tconst editor = editorRef;\n\tif (!editor) return;\n\n\tif (tooltipTimeout) clearTimeout(tooltipTimeout);\n\n\tdocument.removeEventListener(\"mouseover\", onMouseOver);\n\tdocument.removeEventListener(\"mousemove\", onMouseMove);\n\tdocument.removeEventListener(\"mouseleave\", onMouseLeave);\n\tdocument.removeEventListener(\"mousedown\", closeTooltip);\n\tdocument.removeEventListener(\"keydown\", closeTooltip);\n\tdocument.removeEventListener(\"wheel\", closeTooltip);\n\n\teditor.subscriptions.unsubscribeFrontendMessage(\"SendShortcutShiftClick\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"SendShortcutAltClick\");\n\teditor.subscriptions.unsubscribeFrontendMessage(\"SendShortcutFullscreen\");\n}\n\n// Listen for mouse movements onto tooltip-bearing HTML elements to track the future target of a tooltip\nfunction onMouseOver(e: MouseEvent) {\n\tconst element = (e.target instanceof Element && e.target.closest(\"[data-tooltip-label], [data-tooltip-description], [data-tooltip-shortcut]\")) || undefined;\n\n\tupdate((state) => {\n\t\tstate.visible = false;\n\t\tstate.element = element;\n\t\treturn state;\n\t});\n}\n\n// Listen for mouse movements to schedule and position the tooltip, or hide it immediately upon further movement\nfunction onMouseMove(e: MouseEvent) {\n\t// Hide the tooltip now that the cursor has moved\n\tupdate((state) => {\n\t\tstate.visible = false;\n\t\treturn state;\n\t});\n\n\t// Before we schedule a new future tooltip appearance, we clear the existing one\n\tif (tooltipTimeout) clearTimeout(tooltipTimeout);\n\n\t// Don't show tooltips while mouse buttons are pressed\n\tif (e.buttons !== 0) return;\n\n\t// Schedule the tooltip to appear at this cursor position after a delay\n\ttooltipTimeout = setTimeout(() => {\n\t\tupdate((state) => {\n\t\t\tif (state.element) {\n\t\t\t\tstate.visible = true;\n\t\t\t\tstate.position = { x: e.clientX, y: e.clientY };\n\t\t\t}\n\t\t\treturn state;\n\t\t});\n\t}, SHOW_TOOLTIP_DELAY_MS);\n}\n\n// Hide tooltip and cancel any pending timeout when the mouse leaves the application window\nfunction onMouseLeave() {\n\tif (tooltipTimeout) clearTimeout(tooltipTimeout);\n\tcloseTooltip();\n}\n\n// Stop showing a tooltip if the user clicks or presses a key, and require the user to first move out of the element before it can re-appear\nfunction closeTooltip() {\n\tupdate((state) => {\n\t\tstate.visible = false;\n\t\tstate.element = undefined;\n\t\treturn state;\n\t});\n}\n"
  },
  {
    "path": "frontend/src/subscription-router.ts",
    "content": "import type { FrontendMessage, LayoutTarget, WidgetDiff } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\n// Type convert a union of messages into a map of messages\nexport type ToMessageMap<T> = {\n\t[K in T extends string ? T : T extends object ? keyof T : never]: K extends T ? Record<string, never> : T extends Record<K, infer Payload> ? Payload : never;\n};\n\nexport type MessageMap = ToMessageMap<FrontendMessage>;\nexport type MessageName = keyof MessageMap;\nexport type MessageBody<T extends MessageName> = Extract<FrontendMessage, Record<T, unknown>>[T];\n\nexport function createSubscriptionRouter() {\n\t// Callbacks are wrapped at subscription time to capture their type-specific data extraction in a closure,\n\t// so the stored function has a uniform signature and the map doesn't need per-key generic value types.\n\tconst subscriptions = new Map<MessageName, (taggedMessage: MessageMap) => void>();\n\tconst layoutCallbacks = new Map<LayoutTarget, (diffs: WidgetDiff[]) => void>();\n\n\tconst subscribeFrontendMessage = <T extends MessageName>(messageType: T, callback: (data: MessageMap[T]) => void) => {\n\t\tsubscriptions.set(messageType, (taggedMessage: MessageMap) => callback(taggedMessage[messageType]));\n\t};\n\n\tconst unsubscribeFrontendMessage = (messageType: MessageName) => {\n\t\tsubscriptions.delete(messageType);\n\t};\n\n\tconst subscribeLayoutUpdate = (target: LayoutTarget, callback: (diffs: WidgetDiff[]) => void) => {\n\t\tlayoutCallbacks.set(target, callback);\n\t};\n\n\tconst unsubscribeLayoutUpdate = (target: LayoutTarget) => {\n\t\tlayoutCallbacks.delete(target);\n\t};\n\n\tfunction normalizeMessage<T extends string | object>(message: T): ToMessageMap<T>;\n\tfunction normalizeMessage(message: string | Record<string, unknown>): Record<string, unknown> {\n\t\t// If it's a bare string, convert it to an object with an empty payload\n\t\tif (typeof message === \"string\") {\n\t\t\tconst result: Record<string, Record<string, never>> = { [message]: {} };\n\t\t\treturn result;\n\t\t}\n\n\t\t// If it's already an object, it matches the structure of our map\n\t\treturn message;\n\t}\n\n\tconst handleFrontendMessage = (messageType: MessageName, messageData: FrontendMessage) => {\n\t\t// Messages with non-empty data are provided by Serde JSON as an object with one key as the message name, like: { NameOfThisMessage: { ... } }\n\t\t// Messages with empty data are provided by Serde JSON as a string with the message name, like: \"NameOfThisMessage\"\n\t\t// Here we extract the payload object or create an empty payload object, as needed.\n\t\tconst taggedMessage = normalizeMessage(messageData);\n\n\t\t// Resolve the dispatch thunk, depending on whether this is a layout update or a regular message.\n\t\t// UpdateLayout messages are dispatched to layout-specific callbacks based on the layout target.\n\t\t// The thunk is re-evaluated on each retry because the callback may not be registered yet.\n\t\tlet getHandler: () => ((taggedMessage: MessageMap) => void) | undefined = () => subscriptions.get(messageType);\n\n\t\t// Handle layout updates specially to route them to layout-specific callbacks and extract the diffs as the data to pass\n\t\tlet target: LayoutTarget | undefined;\n\t\tif (\"UpdateLayout\" in taggedMessage) {\n\t\t\tconst { layoutTarget, diff } = taggedMessage[\"UpdateLayout\"];\n\t\t\ttarget = layoutTarget;\n\n\t\t\tgetHandler = () => {\n\t\t\t\tconst layoutCallback = layoutCallbacks.get(layoutTarget);\n\t\t\t\tif (!layoutCallback) return undefined;\n\t\t\t\treturn () => layoutCallback(diff);\n\t\t\t};\n\t\t}\n\n\t\t// Try to execute the callback. Due to message ordering, the callback may not be registered yet,\n\t\t// so we retry a few times on the next stack frame to give onMount a chance to run.\n\t\tlet retries = 0;\n\t\tconst callCallback = () => {\n\t\t\tconst handler = getHandler();\n\n\t\t\tif (handler) {\n\t\t\t\thandler(taggedMessage);\n\t\t\t}\n\t\t\t// Try again on the next stack frame, if the retry limit hasn't been exceeded yet\n\t\t\telse if (retries <= 3) {\n\t\t\t\tretries += 1;\n\t\t\t\tsetTimeout(callCallback, 0);\n\t\t\t}\n\t\t\t// Guard against this firing after a teardown during HMR, if no handlers are registered anymore\n\t\t\telse if (subscriptions.size + layoutCallbacks.size > 0) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error(`Received a frontend message of type ${messageType}${target ? ` (${target})` : \"\"} but no handler was registered for it from the client.`);\n\t\t\t}\n\t\t};\n\n\t\tcallCallback();\n\t};\n\n\treturn {\n\t\tsubscribeFrontendMessage,\n\t\tunsubscribeFrontendMessage,\n\t\tsubscribeLayoutUpdate,\n\t\tunsubscribeLayoutUpdate,\n\t\thandleFrontendMessage,\n\t};\n}\nexport type SubscriptionRouter = ReturnType<typeof createSubscriptionRouter>;\n"
  },
  {
    "path": "frontend/src/utility-functions/colors.ts",
    "content": "import { sampleInterpolatedGradient } from \"@graphite/../wasm/pkg/graphite_wasm\";\nimport type { Color, FillChoice, GradientStops } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\n// Channels can have any range (0-1, 0-255, 0-100, 0-360) in the context they are being used in, these are just containers for the numbers\nexport type HSV = { h: number; s: number; v: number };\nexport type RGB = { r: number; g: number; b: number };\n\n// COLOR FACTORY FUNCTIONS\n\nexport function createColor(red: number, green: number, blue: number, alpha: number): Color {\n\treturn { red, green, blue, alpha };\n}\n\nexport function createColorFromHSVA(h: number, s: number, v: number, a: number): Color {\n\tconst convert = (n: number): number => {\n\t\tconst k = (n + h * 6) % 6;\n\t\treturn v - v * s * Math.max(Math.min(...[k, 4 - k, 1]), 0);\n\t};\n\n\treturn { red: convert(5), green: convert(3), blue: convert(1), alpha: a };\n}\n\n// COLOR UTILITY FUNCTIONS\n\nexport function isColor(value: unknown): value is Color {\n\treturn typeof value === \"object\" && value !== null && \"red\" in value;\n}\n\nexport function colorFromCSS(colorCode: string): Color | undefined {\n\t// Allow single-digit hex value inputs\n\tlet colorValue = colorCode.trim();\n\tif (colorValue.length === 2 && colorValue.charAt(0) === \"#\" && /[0-9a-f]/i.test(colorValue.charAt(1))) {\n\t\tconst digit = colorValue.charAt(1);\n\t\tcolorValue = `#${digit}${digit}${digit}`;\n\t}\n\n\tconst canvas = document.createElement(\"canvas\");\n\tcanvas.width = 1;\n\tcanvas.height = 1;\n\tconst context = canvas.getContext(\"2d\", { willReadFrequently: true });\n\tif (!context) return undefined;\n\n\tcontext.clearRect(0, 0, 1, 1);\n\n\tcontext.fillStyle = \"black\";\n\tcontext.fillStyle = colorValue;\n\tconst comparisonA = context.fillStyle;\n\n\tcontext.fillStyle = \"white\";\n\tcontext.fillStyle = colorValue;\n\tconst comparisonB = context.fillStyle;\n\n\t// Invalid color\n\tif (comparisonA !== comparisonB) {\n\t\t// If this color code didn't start with a #, add it and try again\n\t\tif (colorValue.trim().charAt(0) !== \"#\") return colorFromCSS(`#${colorValue.trim()}`);\n\t\treturn undefined;\n\t}\n\n\tcontext.fillRect(0, 0, 1, 1);\n\n\tconst [r, g, b, a] = [...context.getImageData(0, 0, 1, 1).data];\n\treturn createColor(r / 255, g / 255, b / 255, a / 255);\n}\n\nexport function colorEquals(c1: Color | undefined, c2: Color | undefined): boolean {\n\tif (c1 === undefined && c2 === undefined) return true;\n\tif (c1 === undefined || c2 === undefined) return false;\n\treturn Math.abs(c1.red - c2.red) < 1e-6 && Math.abs(c1.green - c2.green) < 1e-6 && Math.abs(c1.blue - c2.blue) < 1e-6 && Math.abs(c1.alpha - c2.alpha) < 1e-6;\n}\n\nexport function colorToHexNoAlpha(color: Color): string {\n\tconst r = Math.round(color.red * 255)\n\t\t.toString(16)\n\t\t.padStart(2, \"0\");\n\tconst g = Math.round(color.green * 255)\n\t\t.toString(16)\n\t\t.padStart(2, \"0\");\n\tconst b = Math.round(color.blue * 255)\n\t\t.toString(16)\n\t\t.padStart(2, \"0\");\n\n\treturn `#${r}${g}${b}`;\n}\n\nexport function colorToHexOptionalAlpha(color: Color): string {\n\tconst hex = colorToHexNoAlpha(color);\n\tconst a = Math.round(color.alpha * 255)\n\t\t.toString(16)\n\t\t.padStart(2, \"0\");\n\n\treturn a === \"ff\" ? hex : `${hex}${a}`;\n}\n\nexport function colorToRgb255(color: Color): RGB {\n\treturn {\n\t\tr: Math.round(color.red * 255),\n\t\tg: Math.round(color.green * 255),\n\t\tb: Math.round(color.blue * 255),\n\t};\n}\n\nexport function colorToRgbCSS(color: Color): string {\n\tconst rgb = colorToRgb255(color);\n\n\treturn `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`;\n}\n\nexport function colorToRgbaCSS(color: Color): string {\n\tconst rgb = colorToRgb255(color);\n\n\treturn `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${color.alpha})`;\n}\n\nexport function colorToHSV(color: Color): HSV {\n\tconst { red: r, green: g, blue: b } = color;\n\n\tconst max = Math.max(r, g, b);\n\tconst min = Math.min(r, g, b);\n\n\tconst d = max - min;\n\tconst s = max === 0 ? 0 : d / max;\n\tconst v = max;\n\n\tlet h = 0;\n\tif (max !== min) {\n\t\tswitch (max) {\n\t\t\tcase r:\n\t\t\t\th = (g - b) / d + (g < b ? 6 : 0);\n\t\t\t\tbreak;\n\t\t\tcase g:\n\t\t\t\th = (b - r) / d + 2;\n\t\t\t\tbreak;\n\t\t\tcase b:\n\t\t\t\th = (r - g) / d + 4;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t}\n\t\th /= 6;\n\t}\n\n\treturn { h, s, v };\n}\n\nexport function colorOpaque(color: Color): Color {\n\treturn createColor(color.red, color.green, color.blue, 1);\n}\n\nexport function colorLuminance(color: Color): number {\n\t// Convert alpha into white\n\tconst r = color.red * color.alpha + (1 - color.alpha);\n\tconst g = color.green * color.alpha + (1 - color.alpha);\n\tconst b = color.blue * color.alpha + (1 - color.alpha);\n\n\t// https://stackoverflow.com/a/3943023/775283\n\n\tconst linearR = r <= 0.04045 ? r / 12.92 : ((r + 0.055) / 1.055) ** 2.4;\n\tconst linearG = g <= 0.04045 ? g / 12.92 : ((g + 0.055) / 1.055) ** 2.4;\n\tconst linearB = b <= 0.04045 ? b / 12.92 : ((b + 0.055) / 1.055) ** 2.4;\n\n\treturn linearR * 0.2126 + linearG * 0.7152 + linearB * 0.0722;\n}\n\nexport function colorContrastingColor(color: Color | undefined): \"black\" | \"white\" {\n\tif (!color) return \"black\";\n\n\tconst luminance = colorLuminance(color);\n\n\treturn luminance > Math.sqrt(1.05 * 0.05) - 0.05 ? \"black\" : \"white\";\n}\n\nexport function contrastingOutlineFactor(value: FillChoice, proximityColor: string | [string, string], proximityRange: number): number {\n\tconst pair = Array.isArray(proximityColor) ? [proximityColor[0], proximityColor[1]] : [proximityColor, proximityColor];\n\tconst [range1, range2] = pair.map((color) => colorFromCSS(window.getComputedStyle(document.body).getPropertyValue(color)));\n\n\tconst contrast = (color: Color | undefined): number => {\n\t\tif (!color) return 0;\n\n\t\tconst lum = colorLuminance(color);\n\t\tlet rangeLuminance1 = range1 ? colorLuminance(range1) : 0;\n\t\tlet rangeLuminance2 = range2 ? colorLuminance(range2) : 0;\n\t\t[rangeLuminance1, rangeLuminance2] = [Math.min(rangeLuminance1, rangeLuminance2), Math.max(rangeLuminance1, rangeLuminance2)];\n\n\t\tconst distance = Math.max(0, rangeLuminance1 - lum, lum - rangeLuminance2);\n\n\t\treturn (1 - Math.min(distance / proximityRange, 1)) * (1 - colorToHSV(color).s);\n\t};\n\n\tconst gradientStops = fillChoiceGradientStops(value);\n\tif (gradientStops) {\n\t\tif (gradientStops.color.length === 0) return 0;\n\n\t\tconst first = contrast(gradientStops.color[0]);\n\t\tconst last = contrast(gradientStops.color[gradientStops.color.length - 1]);\n\n\t\treturn Math.min(first, last);\n\t}\n\n\treturn contrast(fillChoiceColor(value));\n}\n\n// GRADIENT UTILITY FUNCTIONS\n\nexport function isGradientStops(value: unknown): value is GradientStops {\n\treturn typeof value === \"object\" && value !== null && \"position\" in value && \"midpoint\" in value && \"color\" in value;\n}\n\nexport function gradientToLinearGradientCSS(gradient: GradientStops): string {\n\tif (gradient.position.length === 1) {\n\t\treturn `linear-gradient(to right, ${colorToHexOptionalAlpha(gradient.color[0])} 0%, ${colorToHexOptionalAlpha(gradient.color[0])} 100%)`;\n\t}\n\n\tconst pieces = sampleInterpolatedGradient(new Float64Array(gradient.position), new Float64Array(gradient.midpoint), gradient.color, false);\n\treturn `linear-gradient(to right, ${pieces})`;\n}\n\nexport function gradientFirstColor(gradient: GradientStops): Color | undefined {\n\treturn gradient.color[0];\n}\n\nexport function gradientLastColor(gradient: GradientStops): Color | undefined {\n\treturn gradient.color[gradient.color.length - 1];\n}\n\n// FILL CHOICE UTILITY FUNCTIONS\n\nexport function fillChoiceColor(value: FillChoice): Color | undefined {\n\tif (typeof value === \"object\" && \"Solid\" in value) return value.Solid;\n\treturn undefined;\n}\n\nexport function fillChoiceGradientStops(value: FillChoice): GradientStops | undefined {\n\tif (typeof value === \"object\" && \"Gradient\" in value) return value.Gradient;\n\treturn undefined;\n}\n\nexport function parseFillChoice(value: unknown): FillChoice {\n\tif (value === \"None\" || value === undefined || value === null) return \"None\";\n\tif (typeof value === \"object\" && value !== null && \"Solid\" in value && isColor(value.Solid)) return { Solid: value.Solid };\n\tif (typeof value === \"object\" && value !== null && \"Gradient\" in value && isGradientStops(value.Gradient)) return { Gradient: value.Gradient };\n\treturn \"None\";\n}\n"
  },
  {
    "path": "frontend/src/utility-functions/crash-report.ts",
    "content": "import { browserVersion, operatingSystem } from \"@graphite/utility-functions/platform\";\nimport { stripIndents } from \"@graphite/utility-functions/strip-indents\";\n\nexport function crashReportUrl(panicDetails: string): string {\n\tconst url = new URL(\"https://github.com/GraphiteEditor/Graphite/issues/new\");\n\n\tconst buildUrl = (includeCrashReport: boolean) => {\n\t\tlet body = stripIndents`\n\t\t\t**Describe the Crash**\n\t\t\tExplain clearly what you were doing when the crash occurred.\n\n\t\t\t**Steps To Reproduce**\n\t\t\tDescribe precisely how the crash occurred, step by step, starting with a new editor window.\n\t\t\t1. Open the Graphite editor at https://dev.graphite.art — IMPORTANT! Confirm you have tested in this development version. It may have already been fixed since the last stable release.\n\t\t\t2.\n\t\t\t3.\n\t\t\t4.\n\t\t\t5.\n\n\t\t\t**Additional Details**\n\t\t\tProvide any further information or context that you think would be helpful in fixing the issue. Screenshots or video can be linked or attached to this issue.\n\n\t\t\t**Browser and OS**\n\t\t\t${browserVersion()}, ${operatingSystem()}\n\n\t\t\t**Stack Trace**\n\t\t\tCopied from the crash dialog in the Graphite editor:\n\t\t`;\n\n\t\tconst manualCopyStackTraceNotice = stripIndents`\n\t\t\tBefore submitting this bug, REPLACE THIS WITH THE LOG. Return to the editor and click \"Copy Error Log\" in the crash dialog and paste it in place of this text.\n\t\t`;\n\n\t\tbody += \"\\n\\n```\\n\";\n\t\tbody += includeCrashReport ? panicDetails.trimEnd() : manualCopyStackTraceNotice;\n\t\tbody += \"\\n```\";\n\n\t\tconst fields = {\n\t\t\ttitle: \"[Crash Report] \",\n\t\t\tbody,\n\t\t\tlabels: [\"Crash\"].join(\",\"),\n\t\t\tprojects: [].join(\",\"),\n\t\t\tmilestone: \"\",\n\t\t\tassignee: \"\",\n\t\t\ttemplate: \"\",\n\t\t};\n\n\t\tObject.entries(fields).forEach(([field, value]) => {\n\t\t\tif (value) url.searchParams.set(field, value);\n\t\t});\n\n\t\treturn String(url);\n\t};\n\n\tlet urlString = buildUrl(true);\n\tif (urlString.length >= 8192) {\n\t\t// Fall back to a shorter version if it exceeds GitHub limits of 8192 total characters\n\t\turlString = buildUrl(false);\n\t}\n\treturn urlString;\n}\n"
  },
  {
    "path": "frontend/src/utility-functions/escape.ts",
    "content": "/* eslint-disable no-useless-escape */\n/* eslint-disable quotes */\n\nexport function escapeJSON(str: string): string {\n\treturn str\n\t\t.replace(/[\\\\]/g, \"\\\\\\\\\")\n\t\t.replace(/[\\\"]/g, '\\\\\"')\n\t\t.replace(/[\\/]/g, \"\\\\/\")\n\t\t.replace(/[\\b]/g, \"\\\\b\")\n\t\t.replace(/[\\f]/g, \"\\\\f\")\n\t\t.replace(/[\\n]/g, \"\\\\n\")\n\t\t.replace(/[\\r]/g, \"\\\\r\")\n\t\t.replace(/[\\t]/g, \"\\\\t\");\n}\n"
  },
  {
    "path": "frontend/src/utility-functions/files.ts",
    "content": "import type { Editor } from \"@graphite/editor\";\nimport { extractPixelData } from \"@graphite/utility-functions/rasterization\";\n\nexport function downloadFileURL(filename: string, url: string) {\n\tconst element = document.createElement(\"a\");\n\n\telement.href = url;\n\telement.setAttribute(\"download\", filename);\n\n\telement.click();\n}\n\nexport function downloadFileBlob(filename: string, blob: Blob) {\n\tconst url = URL.createObjectURL(blob);\n\n\tdownloadFileURL(filename, url);\n\n\tURL.revokeObjectURL(url);\n}\n\nexport function downloadFile(filename: string, content: Uint8Array) {\n\tconst type = filename.endsWith(\".svg\") ? \"image/svg+xml;charset=utf-8\" : \"application/octet-stream\";\n\n\tif (content.length > 0 && content.buffer instanceof ArrayBuffer) {\n\t\tconst contentView = new Uint8Array(content.buffer, content.byteOffset, content.byteLength);\n\t\tconst blob = new Blob([contentView], { type });\n\t\tdownloadFileBlob(filename, blob);\n\t}\n}\n\n// See https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/file#accept for the `accept` string format\nexport async function upload(accept: string, textOrData: \"text\"): Promise<UploadResult<string>>;\nexport async function upload(accept: string, textOrData: \"data\"): Promise<UploadResult<Uint8Array>>;\nexport async function upload(accept: string, textOrData: \"both\"): Promise<UploadResult<{ text: string; data: Uint8Array }>>;\nexport async function upload(accept: string, textOrData: \"text\" | \"data\" | \"both\"): Promise<UploadResult<string | Uint8Array | { text: string; data: Uint8Array }>> {\n\treturn new Promise((resolve) => {\n\t\tconst element = document.createElement(\"input\");\n\t\telement.type = \"file\";\n\t\telement.accept = accept;\n\n\t\telement.addEventListener(\n\t\t\t\"change\",\n\t\t\tasync () => {\n\t\t\t\tif (element.files?.length) {\n\t\t\t\t\tconst file = element.files[0];\n\n\t\t\t\t\tconst filename = file.name;\n\t\t\t\t\tconst type = file.type;\n\t\t\t\t\tconst content =\n\t\t\t\t\t\ttextOrData === \"text\"\n\t\t\t\t\t\t\t? await file.text()\n\t\t\t\t\t\t\t: textOrData === \"data\"\n\t\t\t\t\t\t\t\t? new Uint8Array(await file.arrayBuffer())\n\t\t\t\t\t\t\t\t: { text: await file.text(), data: new Uint8Array(await file.arrayBuffer()) };\n\n\t\t\t\t\tresolve({ filename, type, content });\n\t\t\t\t}\n\t\t\t},\n\t\t\t{ capture: false, once: true },\n\t\t);\n\n\t\telement.click();\n\n\t\t// Once `element` goes out of scope, it has no references so it gets garbage collected along with its event listener, so `removeEventListener` is not needed\n\t});\n}\nexport type UploadResult<T> = { filename: string; type: string; content: T };\n\nexport async function pasteFile(item: DataTransferItem, editor: Editor, mouse?: [number, number], insertParentId?: bigint, insertIndex?: number) {\n\tconst file = item.getAsFile();\n\tif (!file) return;\n\n\tif (file.type.startsWith(\"image/svg\")) {\n\t\tconst svg = await file.text();\n\t\teditor.handle.pasteSvg(file.name, svg, mouse?.[0], mouse?.[1], insertParentId, insertIndex);\n\t} else if (file.type.startsWith(\"image/\")) {\n\t\tconst imageData = await extractPixelData(file);\n\t\teditor.handle.pasteImage(file.name, new Uint8Array(imageData.data), imageData.width, imageData.height, mouse?.[0], mouse?.[1], insertParentId, insertIndex);\n\t} else if (file.name.endsWith(\".\" + editor.handle.fileExtension())) {\n\t\t// TODO: When we eventually have sub-documents, this should be changed to import the document as a node instead of opening it in a separate tab\n\t\teditor.handle.openFile(file.name, await file.bytes());\n\t}\n}\n"
  },
  {
    "path": "frontend/src/utility-functions/images.ts",
    "content": "// Demo artwork\nimport ThumbnailChangingSeasons from \"@graphite/../assets/thumbnail-changing-seasons.png\";\nimport ThumbnailIsometricFountain from \"@graphite/../assets/thumbnail-isometric-fountain.png\";\nimport ThumbnailPaintedDreams from \"@graphite/../assets/thumbnail-painted-dreams.png\";\nimport ThumbnailParametricDunescape from \"@graphite/../assets/thumbnail-parametric-dunescape.png\";\nimport ThumbnailProceduralStringLights from \"@graphite/../assets/thumbnail-procedural-string-lights.png\";\nimport ThumbnailRedDress from \"@graphite/../assets/thumbnail-red-dress.png\";\nimport ThumbnailValleyOfSpires from \"@graphite/../assets/thumbnail-valley-of-spires.png\";\n\nconst DEMO_ARTWORK = {\n\tThumbnailChangingSeasons,\n\tThumbnailIsometricFountain,\n\tThumbnailPaintedDreams,\n\tThumbnailParametricDunescape,\n\tThumbnailProceduralStringLights,\n\tThumbnailRedDress,\n\tThumbnailValleyOfSpires,\n} as const;\n\n// All images\nconst IMAGE_LIST = {\n\t...DEMO_ARTWORK,\n} as const;\n\n// Exported images and types\nexport const IMAGES: ImageDefinitionType<typeof IMAGE_LIST> = IMAGE_LIST;\nexport const IMAGE_BASE64_STRINGS = Object.fromEntries(Object.entries(IMAGES).map(([name, data]) => [name, data]));\n\n// See `icons.ts` for explanation about how this works\ntype EvaluateType<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;\ntype ImageDefinitionType<T extends Record<string, string>> = EvaluateType<{ [key in keyof T]: string }>;\n"
  },
  {
    "path": "frontend/src/utility-functions/keyboard-entry.ts",
    "content": "export function makeKeyboardModifiersBitfield(e: WheelEvent | PointerEvent | MouseEvent | KeyboardEvent): number {\n\treturn (\n\t\t// Shift (all platforms)\n\t\t(Number(e.shiftKey) << 0) |\n\t\t// Alt (all platforms, also called Option on Mac)\n\t\t(Number(e.altKey) << 1) |\n\t\t// Control (all platforms)\n\t\t(Number(e.ctrlKey) << 2) |\n\t\t// Meta (Windows/Linux) or Command (Mac)\n\t\t(Number(e.metaKey) << 3)\n\t);\n}\n\n// Necessary because innerText puts an extra newline character at the end when the text is more than one line.\nexport function textInputCleanup(text: string): string {\n\tif (text[text.length - 1] === \"\\n\") return text.slice(0, -1);\n\treturn text;\n}\n\n// This function tries to find what scan code the user pressed, even if using a non-US keyboard.\n// Directly using `KeyboardEvent.code` scan code only works on a US QWERTY layout, because alternate layouts like\n// QWERTZ (German) or AZERTY (French) will end up reporting the wrong keys.\n// Directly using `KeyboardEvent.key` doesn't work because the results are often garbage, as the printed character\n// varies when the Shift key is pressed, or worse, when the Option (Alt) key on a Mac is pressed.\n// This function does its best to try and sort through both of those sources of information to determine the localized scan code.\n//\n// This function is an imperfect stopgap solution to allow non-US keyboards to be handled on a best-effort basis.\n// Eventually we will need a more robust system based on a giant database of keyboard layouts from all around the world.\n// We'd provide the user a choice of layout, and aim to detect a default based on the `key` and `code` values entered by the user\n// combined with `Keyboard.getLayoutMap()` where supported in Chromium-based browsers and perhaps the browser's language and IP address.\n// We are also limited by browser APIs, since the spec doesn't support what we need it to:\n// <https://github.com/WICG/keyboard-map/issues/26>\n// In the desktop version of VS Code, this is achieved with this Electron plugin:\n// <https://github.com/Microsoft/node-native-keymap>\n// We may be able to port that (it's a relatively small codebase) to Rust for use with our desktop application.\n// But on the web, just like VS Code, we're limited by the shortcomings of the spec.\n// A collection of further insights:\n// <https://docs.google.com/document/d/1p17IBbYGsZivLIMhKZOaCJFAJFokbPfKrkB37fOPXSM/edit>\n// And it's a really good idea to read the explainer on keyboard layout variations and the whole spec (it's quite digestible):\n// <https://www.w3.org/TR/uievents-code/#key-alphanumeric-writing-system>\nexport async function getLocalizedScanCode(e: KeyboardEvent): Promise<string> {\n\tconst keyText = e.key;\n\tconst scanCode = e.code;\n\n\t// Use the key code directly if it isn't one that changes per locale (i.e. isn't a writing system key or one of the other few exceptions)\n\tconst scanCodeNotLocaleSpecific = !LOCALE_SPECIFIC_KEY_CODES.includes(scanCode);\n\tif (scanCodeNotLocaleSpecific) {\n\t\treturn scanCode;\n\t}\n\n\t// Use the key directly if it's one of the exceptions that usually don't change, but sometimes do in a predictable way\n\tif (SCAN_CODES_FOR_NON_WRITING_KEYS_THAT_VARY_PER_LOCALE.includes(scanCode)) {\n\t\t// Numpad comma and period which swap in some locales as decimal and thousands separator symbols\n\t\tif (NUMPAD_DECIMAL_AND_THOUSANDS_SEPARATORS.includes(scanCode)) {\n\t\t\tswitch (scanCode) {\n\t\t\t\tcase \".\":\n\t\t\t\t\treturn \"NumpadDecimal\";\n\t\t\t\tcase \",\":\n\t\t\t\t\treturn \"NumpadComma\";\n\t\t\t\tdefault:\n\t\t\t\t\treturn scanCode;\n\t\t\t}\n\t\t}\n\n\t\t// The AltRight key changes from a key value of \"Alt\" to \"AltGraph\" on keyboards with an AltGraph key\n\t\tif (scanCode === \"AltRight\") {\n\t\t\treturn keyText === \"Alt\" ? \"AltRight\" : \"AltGraph\";\n\t\t}\n\t}\n\n\t// Use good-enough-for-now heuristics on the writing system keys, which are commonly subject to change by locale\n\n\t// Number scan codes\n\tif (/^Digit[0-9]$/.test(scanCode)) {\n\t\t// For now it's good enough to treat every digit key, regardless of locale, as just its digit from the standard US layout.\n\t\t// Even on a keyboard like the French AZERTY layout, where numbers are shifted, users still refer to those keys by their numbers.\n\t\t// This unfortunately means that any special symbols under these keys are overridden by their number, making it impossible to access some shortcuts that rely on those special symbols.\n\t\t// We'll have to deal with that for now, and find a way to upgrade or properly replace this system, or assign alternate keymaps based on locale, when people complain.\n\t\treturn scanCode;\n\t}\n\n\t// Letter scan codes\n\tif (/^Key([A-Z])$/.test(scanCode)) {\n\t\t// Get the uppercase letter, with any accents or discritics removed if possible\n\t\tconst rawLetter = keyText\n\t\t\t.normalize(\"NFD\")\n\t\t\t.replace(/\\p{Diacritic}/gu, \"\")\n\t\t\t.toUpperCase();\n\n\t\t// If the key letter is in the A-Z range, use the key code for that letter\n\t\tif (/^[A-Z]$/.test(rawLetter)) return `Key${rawLetter}`;\n\n\t\t// If the key text isn't one of the named attribute values, that means it must be the literal unicode value which we use directly\n\t\t// It is likely a weird symbol that isn't in the A-Z range even with accents removed.\n\t\t// It might be a symbol from an Option key combination on a Mac. Or it might be from a non-Latin alphabet like Cyrillic.\n\t\tif (!KEY_ATTRIBUTE_VALUES.has(keyText)) {\n\t\t\tif (navigator.keyboard && \"getLayoutMap\" in navigator.keyboard) {\n\t\t\t\tconst layout = await navigator.keyboard.getLayoutMap();\n\n\t\t\t\ttype KeyCode = string;\n\t\t\t\ttype KeySymbol = string;\n\t\t\t\t// Get all the keyboard mappings and transform the key symbols to uppercase\n\t\t\t\tconst keyboardLayoutMap: [KeyCode, KeySymbol][] = [...layout.entries()].map(([keyCode, keySymbol]) => [keyCode, keySymbol.toUpperCase()]);\n\n\t\t\t\t// If we match the uppercase version of the pressed key character, use the scan code that produces it\n\t\t\t\tconst matchedEntry = keyboardLayoutMap.find(([_, keySymbol]) => keySymbol === keyText.toUpperCase());\n\t\t\t\tif (matchedEntry) return matchedEntry[0];\n\t\t\t}\n\n\t\t\t// If the keyboard layout API is unavailable, or it didn't match anything, just return the scan code that the user typed\n\t\t\t// This isn't perfect because alternate keyboard layouts may end up having the US QWERTY key,\n\t\t\t// but it's all we can do without a giant database of keyboard layouts and Mac Option key combinations\n\t\t\treturn scanCode;\n\t\t}\n\n\t\t// If the key's named attribute value shares a name with a scan code, use that scan code\n\t\tif (KEY_CODE_NAMES.includes(keyText)) {\n\t\t\treturn scanCodeFromKeyText(keyText);\n\t\t}\n\t\tif (KEY_ATTRIBUTE_VALUES_INVOLVING_HANDEDNESS.includes(keyText)) {\n\t\t\t// Since for some reason we're in a situation where we are using the key instead of the scan code to\n\t\t\t// match one of the modifier keys which have both a Left and Right variant as part of the scan code names,\n\t\t\t// but no handedness as part of the key's named attribute values, we default to the left side as it's more common.\n\t\t\treturn `${keyText}Left`;\n\t\t}\n\n\t\t// All reasonable attempts to figure out what this key is has now failed, so we fall back on the US QWERTY layout scan code\n\t\treturn scanCode;\n\t}\n\n\t// If the key text is the unicode character for one of the standard symbols on the US keyboard, use the symbol even though it's not located on the same scan code as on a US keyboard\n\tif (WRITING_SYSTEM_SPECIAL_CHARS.includes(keyText)) {\n\t\treturn scanCodeFromKeyText(keyText);\n\t}\n\n\t// If the key is otherwise totally unrecognized, we ignore it\n\tif (keyText === \"Unidentified\" || scanCode === \"Unidentified\") return \"Unidentified\";\n\n\t// As a last resort, we just use the scan code\n\treturn scanCode;\n}\n\nfunction scanCodeFromKeyText(keyText: string): string {\n\t// There are many possible unicode symbols as well as named attribute values, but we only care about finding the equivalent scan code (based on the US keyboard) without regard for modifiers\n\n\t// Match any handed modifier keys by claiming it's the left-handed modifier\n\tif (KEY_ATTRIBUTE_VALUES_INVOLVING_HANDEDNESS.includes(keyText)) {\n\t\treturn `${keyText}Left`;\n\t}\n\n\t// Match any named attribute keys to an identical key name\n\tconst identicalName = KEY_CODE_NAMES.find((code) => code === keyText);\n\tif (identicalName) return keyText;\n\n\t// Match the space character\n\t// Order matters because the next step assumes it can safely ignore the space character\n\tif (keyText === \" \") return \"Space\";\n\n\t// Match individual characters by the scan code which produces that symbol on a US keyboard, either shifted or not\n\t// This also includes the `Digit*` and `Key*` codes\n\tconst matchedScanCode = KEY_CODES.find((info) => info.keys?.us?.includes(keyText));\n\tif (matchedScanCode) return matchedScanCode.code;\n\n\treturn \"Unidentified\";\n}\n\ntype KeyCategories = \"writing-system\" | \"functional\" | \"functional-jp-kr\" | \"control-pad\" | \"arrow-pad\" | \"numpad\" | \"function\" | \"media\" | \"unidentified\";\ntype KeyboardLocale = \"us\";\ntype ScanCodeInfo = { code: string; category: KeyCategories; keys?: Record<KeyboardLocale, string | undefined> };\nconst KEY_CODES: ScanCodeInfo[] = [\n\t// Writing system keys\n\t// Codes produce different printed characters depending on locale\n\t// https://www.w3.org/TR/uievents-code/#key-alphanumeric-writing-system\n\n\t{ code: \"Digit0\", category: \"writing-system\", keys: { us: \"0 )\" } },\n\t{ code: \"Digit1\", category: \"writing-system\", keys: { us: \"1 !\" } },\n\t{ code: \"Digit2\", category: \"writing-system\", keys: { us: \"2 @\" } },\n\t{ code: \"Digit3\", category: \"writing-system\", keys: { us: \"3 #\" } },\n\t{ code: \"Digit4\", category: \"writing-system\", keys: { us: \"4 $\" } },\n\t{ code: \"Digit5\", category: \"writing-system\", keys: { us: \"5 %\" } },\n\t{ code: \"Digit6\", category: \"writing-system\", keys: { us: \"6 ^\" } },\n\t{ code: \"Digit7\", category: \"writing-system\", keys: { us: \"7 &\" } },\n\t{ code: \"Digit8\", category: \"writing-system\", keys: { us: \"8 *\" } },\n\t{ code: \"Digit9\", category: \"writing-system\", keys: { us: \"9 (\" } },\n\n\t{ code: \"KeyA\", category: \"writing-system\", keys: { us: \"a A\" } },\n\t{ code: \"KeyB\", category: \"writing-system\", keys: { us: \"b B\" } },\n\t{ code: \"KeyC\", category: \"writing-system\", keys: { us: \"c C\" } },\n\t{ code: \"KeyD\", category: \"writing-system\", keys: { us: \"d D\" } },\n\t{ code: \"KeyE\", category: \"writing-system\", keys: { us: \"e E\" } },\n\t{ code: \"KeyF\", category: \"writing-system\", keys: { us: \"f F\" } },\n\t{ code: \"KeyG\", category: \"writing-system\", keys: { us: \"g G\" } },\n\t{ code: \"KeyH\", category: \"writing-system\", keys: { us: \"h H\" } },\n\t{ code: \"KeyI\", category: \"writing-system\", keys: { us: \"i I\" } },\n\t{ code: \"KeyJ\", category: \"writing-system\", keys: { us: \"j J\" } },\n\t{ code: \"KeyK\", category: \"writing-system\", keys: { us: \"k K\" } },\n\t{ code: \"KeyL\", category: \"writing-system\", keys: { us: \"l L\" } },\n\t{ code: \"KeyM\", category: \"writing-system\", keys: { us: \"m M\" } },\n\t{ code: \"KeyN\", category: \"writing-system\", keys: { us: \"n N\" } },\n\t{ code: \"KeyO\", category: \"writing-system\", keys: { us: \"o O\" } },\n\t{ code: \"KeyP\", category: \"writing-system\", keys: { us: \"p P\" } },\n\t{ code: \"KeyQ\", category: \"writing-system\", keys: { us: \"q Q\" } },\n\t{ code: \"KeyR\", category: \"writing-system\", keys: { us: \"r R\" } },\n\t{ code: \"KeyS\", category: \"writing-system\", keys: { us: \"s S\" } },\n\t{ code: \"KeyT\", category: \"writing-system\", keys: { us: \"t T\" } },\n\t{ code: \"KeyU\", category: \"writing-system\", keys: { us: \"u U\" } },\n\t{ code: \"KeyV\", category: \"writing-system\", keys: { us: \"v V\" } },\n\t{ code: \"KeyW\", category: \"writing-system\", keys: { us: \"w W\" } },\n\t{ code: \"KeyX\", category: \"writing-system\", keys: { us: \"x X\" } },\n\t{ code: \"KeyY\", category: \"writing-system\", keys: { us: \"y Y\" } },\n\t{ code: \"KeyZ\", category: \"writing-system\", keys: { us: \"z Z\" } },\n\n\t{ code: \"Backquote\", category: \"writing-system\", keys: { us: \"` ~\" } },\n\t{ code: \"Backslash\", category: \"writing-system\", keys: { us: \"\\\\ |\" } },\n\t{ code: \"BracketLeft\", category: \"writing-system\", keys: { us: \"[ {\" } },\n\t{ code: \"BracketRight\", category: \"writing-system\", keys: { us: \"] }\" } },\n\t{ code: \"Comma\", category: \"writing-system\", keys: { us: \", <\" } },\n\t{ code: \"Equal\", category: \"writing-system\", keys: { us: \"= +\" } },\n\t{ code: \"Minus\", category: \"writing-system\", keys: { us: \"- _\" } },\n\t{ code: \"Period\", category: \"writing-system\", keys: { us: \". >\" } },\n\t{ code: \"Quote\", category: \"writing-system\", keys: { us: \"' \\\"\" } },\n\t{ code: \"Semicolon\", category: \"writing-system\", keys: { us: \"; :\" } },\n\t{ code: \"Slash\", category: \"writing-system\", keys: { us: \"/ ?\" } },\n\n\t{ code: \"IntlBackslash\", category: \"writing-system\", keys: { us: undefined } },\n\t{ code: \"IntlRo\", category: \"writing-system\", keys: { us: undefined } },\n\t{ code: \"IntlYen\", category: \"writing-system\", keys: { us: undefined } },\n\n\t// Functional keys\n\t// https://www.w3.org/TR/uievents-code/#key-alphanumeric-functional\n\t// Codes have the same meaning regardless of locale, except for \"AltRight\"\n\t{ code: \"AltLeft\", category: \"functional\" },\n\t{ code: \"AltRight\", category: \"functional\" }, // Exception: `key` value is either \"Alt\" or \"AltGraph\" depending on locale (e.g. US vs. French, respectively)\n\t// The W3C table includes this in the Writing System Keys table instead of the Functional Keys table, but its diagrams\n\t// and text describe it as a functional key, so it has been moved here under the assumption that the table is incorrect\n\t// https://github.com/w3c/uievents-code/issues/34\n\t{ code: \"Backspace\", category: \"writing-system\" }, // Shares a name with a key attribute\n\t{ code: \"CapsLock\", category: \"functional\" }, // Shares a name with a key attribute\n\t{ code: \"ContextMenu\", category: \"functional\" }, // Shares a name with a key attribute\n\t{ code: \"ControlLeft\", category: \"functional\" }, // Shares a name with a key attribute as \"Control\"\n\t{ code: \"ControlRight\", category: \"functional\" }, // Shares a name with a key attribute as \"Control\"\n\t{ code: \"Enter\", category: \"functional\" }, // Shares a name with a key attribute\n\t{ code: \"MetaLeft\", category: \"functional\" }, // Shares a name with a key attribute as \"Meta\"\n\t{ code: \"MetaRight\", category: \"functional\" }, // Shares a name with a key attribute as \"Meta\"\n\t{ code: \"ShiftLeft\", category: \"functional\" }, // Shares a name with a key attribute as \"Shift\"\n\t{ code: \"ShiftRight\", category: \"functional\" }, // Shares a name with a key attribute as \"Shift\"\n\t{ code: \"Space\", category: \"functional\" },\n\t{ code: \"Tab\", category: \"functional\" }, // Shares a name with a key attribute\n\n\t// Functional Japanese/Korean keys\n\t{ code: \"Convert\", category: \"functional-jp-kr\" }, // Shares a name with a key attribute\n\t{ code: \"KanaMode\", category: \"functional-jp-kr\" }, // Shares a name with a key attribute\n\t{ code: \"Lang1\", category: \"functional-jp-kr\" },\n\t{ code: \"Lang2\", category: \"functional-jp-kr\" },\n\t{ code: \"Lang3\", category: \"functional-jp-kr\" },\n\t{ code: \"Lang4\", category: \"functional-jp-kr\" },\n\t{ code: \"Lang5\", category: \"functional-jp-kr\" },\n\t{ code: \"NonConvert\", category: \"functional-jp-kr\" }, // Shares a name with a key attribute\n\n\t// Control pad keys\n\t{ code: \"Delete\", category: \"control-pad\" }, // Shares a name with a key attribute\n\t{ code: \"End\", category: \"control-pad\" }, // Shares a name with a key attribute\n\t{ code: \"Help\", category: \"control-pad\" }, // Shares a name with a key attribute\n\t{ code: \"Home\", category: \"control-pad\" }, // Shares a name with a key attribute\n\t{ code: \"Insert\", category: \"control-pad\" }, // Shares a name with a key attribute\n\t{ code: \"PageDown\", category: \"control-pad\" }, // Shares a name with a key attribute\n\t{ code: \"PageUp\", category: \"control-pad\" }, // Shares a name with a key attribute\n\n\t// Arrow pad keys\n\t{ code: \"ArrowDown\", category: \"arrow-pad\" }, // Shares a name with a key attribute\n\t{ code: \"ArrowLeft\", category: \"arrow-pad\" }, // Shares a name with a key attribute\n\t{ code: \"ArrowRight\", category: \"arrow-pad\" }, // Shares a name with a key attribute\n\t{ code: \"ArrowUp\", category: \"arrow-pad\" }, // Shares a name with a key attribute\n\n\t// Numpad keys\n\t{ code: \"Numpad0\", category: \"numpad\" },\n\t{ code: \"Numpad1\", category: \"numpad\" },\n\t{ code: \"Numpad2\", category: \"numpad\" },\n\t{ code: \"Numpad3\", category: \"numpad\" },\n\t{ code: \"Numpad4\", category: \"numpad\" },\n\t{ code: \"Numpad5\", category: \"numpad\" },\n\t{ code: \"Numpad6\", category: \"numpad\" },\n\t{ code: \"Numpad7\", category: \"numpad\" },\n\t{ code: \"Numpad8\", category: \"numpad\" },\n\t{ code: \"Numpad9\", category: \"numpad\" },\n\t{ code: \"NumLock\", category: \"numpad\" }, // Shares a name with a key attribute\n\t{ code: \"NumpadAdd\", category: \"numpad\" },\n\t{ code: \"NumpadBackspace\", category: \"numpad\" },\n\t{ code: \"NumpadClear\", category: \"numpad\" },\n\t{ code: \"NumpadClearEntry\", category: \"numpad\" },\n\t{ code: \"NumpadComma\", category: \"numpad\" }, // Exception: Produces either a comma (,) or period (.) depending on locale (e.g. comma in US vs. period in Brazil)\n\t{ code: \"NumpadDecimal\", category: \"numpad\" }, // Exception: Produces either a comma (,) or period (.) depending on locale (e.g. period in US vs. decimal in Brazil)\n\t{ code: \"NumpadDivide\", category: \"numpad\" },\n\t{ code: \"NumpadEnter\", category: \"numpad\" },\n\t{ code: \"NumpadEqual\", category: \"numpad\" },\n\t{ code: \"NumpadHash\", category: \"numpad\" },\n\t{ code: \"NumpadMemoryAdd\", category: \"numpad\" },\n\t{ code: \"NumpadMemoryClear\", category: \"numpad\" },\n\t{ code: \"NumpadMemoryRecall\", category: \"numpad\" },\n\t{ code: \"NumpadMemoryStore\", category: \"numpad\" },\n\t{ code: \"NumpadMemorySubtract\", category: \"numpad\" },\n\t{ code: \"NumpadMultiply\", category: \"numpad\" },\n\t{ code: \"NumpadParenLeft\", category: \"numpad\" },\n\t{ code: \"NumpadParenRight\", category: \"numpad\" },\n\t{ code: \"NumpadStar\", category: \"numpad\" },\n\t{ code: \"NumpadSubtract\", category: \"numpad\" },\n\n\t// Function keys\n\t{ code: \"Escape\", category: \"function\" }, // Shares a name with a key attribute\n\t{ code: \"F1\", category: \"function\" }, // Shares a name with a key attribute\n\t{ code: \"F2\", category: \"function\" }, // Shares a name with a key attribute\n\t{ code: \"F3\", category: \"function\" }, // Shares a name with a key attribute\n\t{ code: \"F4\", category: \"function\" }, // Shares a name with a key attribute\n\t{ code: \"F5\", category: \"function\" }, // Shares a name with a key attribute\n\t{ code: \"F6\", category: \"function\" }, // Shares a name with a key attribute\n\t{ code: \"F7\", category: \"function\" }, // Shares a name with a key attribute\n\t{ code: \"F8\", category: \"function\" }, // Shares a name with a key attribute\n\t{ code: \"F9\", category: \"function\" }, // Shares a name with a key attribute\n\t{ code: \"F10\", category: \"function\" }, // Shares a name with a key attribute\n\t{ code: \"F11\", category: \"function\" }, // Shares a name with a key attribute\n\t{ code: \"F12\", category: \"function\" }, // Shares a name with a key attribute\n\t{ code: \"F13\", category: \"function\" }, // Shares a name with a key attribute\n\t{ code: \"F14\", category: \"function\" }, // Shares a name with a key attribute\n\t{ code: \"F15\", category: \"function\" }, // Shares a name with a key attribute\n\t{ code: \"F16\", category: \"function\" }, // Shares a name with a key attribute\n\t{ code: \"F17\", category: \"function\" }, // Shares a name with a key attribute\n\t{ code: \"F18\", category: \"function\" }, // Shares a name with a key attribute\n\t{ code: \"F19\", category: \"function\" }, // Shares a name with a key attribute\n\t{ code: \"F20\", category: \"function\" }, // Shares a name with a key attribute\n\t{ code: \"F21\", category: \"function\" }, // Shares a name with a key attribute\n\t{ code: \"F22\", category: \"function\" }, // Shares a name with a key attribute\n\t{ code: \"F23\", category: \"function\" }, // Shares a name with a key attribute\n\t{ code: \"F24\", category: \"function\" }, // Shares a name with a key attribute\n\t{ code: \"Fn\", category: \"function\" }, // Shares a name with a key attribute\n\t{ code: \"FnLock\", category: \"function\" }, // Shares a name with a key attribute\n\t{ code: \"PrintScreen\", category: \"function\" }, // Shares a name with a key attribute\n\t{ code: \"ScrollLock\", category: \"function\" }, // Shares a name with a key attribute\n\t{ code: \"Pause\", category: \"function\" }, // Shares a name with a key attribute\n\n\t// Media keys\n\t{ code: \"BrowserBack\", category: \"media\" }, // Shares a name with a key attribute\n\t{ code: \"BrowserFavorites\", category: \"media\" }, // Shares a name with a key attribute\n\t{ code: \"BrowserForward\", category: \"media\" }, // Shares a name with a key attribute\n\t{ code: \"BrowserHome\", category: \"media\" }, // Shares a name with a key attribute\n\t{ code: \"BrowserRefresh\", category: \"media\" }, // Shares a name with a key attribute\n\t{ code: \"BrowserSearch\", category: \"media\" }, // Shares a name with a key attribute\n\t{ code: \"BrowserStop\", category: \"media\" }, // Shares a name with a key attribute\n\t{ code: \"Eject\", category: \"media\" }, // Shares a name with a key attribute\n\t{ code: \"LaunchApp1\", category: \"media\" },\n\t{ code: \"LaunchApp2\", category: \"media\" },\n\t{ code: \"LaunchMail\", category: \"media\" }, // Shares a name with a key attribute\n\t{ code: \"MediaPlayPause\", category: \"media\" }, // Shares a name with a key attribute\n\t{ code: \"MediaSelect\", category: \"media\" },\n\t{ code: \"MediaStop\", category: \"media\" }, // Shares a name with a key attribute\n\t{ code: \"MediaTrackNext\", category: \"media\" }, // Shares a name with a key attribute\n\t{ code: \"MediaTrackPrevious\", category: \"media\" }, // Shares a name with a key attribute\n\t{ code: \"Power\", category: \"media\" }, // Shares a name with a key attribute\n\t{ code: \"Sleep\", category: \"media\" },\n\t{ code: \"AudioVolumeDown\", category: \"media\" }, // Shares a name with a key attribute\n\t{ code: \"AudioVolumeMute\", category: \"media\" }, // Shares a name with a key attribute\n\t{ code: \"AudioVolumeUp\", category: \"media\" }, // Shares a name with a key attribute\n\t{ code: \"WakeUp\", category: \"media\" }, // Shares a name with a key attribute\n\n\t// Unidentified keys\n\t{ code: \"Unidentified\", category: \"unidentified\" }, // Shares a name with a key attribute\n];\nconst KEY_CODE_NAMES = Object.values(KEY_CODES).map((info) => info.code);\n// const KEY_CODE_NAMES_WITHOUT_HANDEDNESS = KEY_CODE_NAMES.filter((code) => !(code.endsWith(\"Right\") && HANDED_KEY_ATTRIBUTE_VALUES.some((modifier) => code === `${modifier}Right`))).map((code) =>\n// code.endsWith(\"Left\") && HANDED_KEY_ATTRIBUTE_VALUES.some((modifier) => code === `${modifier}Left`) ? code.replace(\"Left\", \"\") : code\n// );\nconst NUMPAD_DECIMAL_AND_THOUSANDS_SEPARATORS = [\"NumpadComma\", \"NumpadDecimal\"];\nconst SCAN_CODES_FOR_NON_WRITING_KEYS_THAT_VARY_PER_LOCALE = [\"AltRight\", ...NUMPAD_DECIMAL_AND_THOUSANDS_SEPARATORS];\nconst LOCALE_SPECIFIC_KEY_CODES_INFO = KEY_CODES.filter((key) => key.category === \"writing-system\" || SCAN_CODES_FOR_NON_WRITING_KEYS_THAT_VARY_PER_LOCALE.includes(key.code));\nconst LOCALE_SPECIFIC_KEY_CODES = LOCALE_SPECIFIC_KEY_CODES_INFO.map((info) => info.code);\nconst WRITING_SYSTEM_SPECIAL_CHARS = Object.values(KEY_CODES)\n\t.filter((info) => info.category === \"writing-system\")\n\t.flatMap((info) => info.keys?.us?.split(\" \"))\n\t.filter((character): character is string => (!character ? false : !/[a-zA-Z0-9]/.test(character)));\n\nconst KEY_ATTRIBUTE_VALUES_INVOLVING_HANDEDNESS = [\"Control\", \"Meta\", \"Shift\"];\nconst KEY_ATTRIBUTE_VALUES = new Set([\n\t// Modifier\n\t\"Alt\", // Glyph modifier key\n\t\"AltGraph\", // Glyph modifier key\n\t\"CapsLock\", // Glyph modifier key\n\t\"Control\",\n\t\"Fn\",\n\t\"FnLock\",\n\t\"Meta\",\n\t\"NumLock\",\n\t\"ScrollLock\",\n\t\"Shift\",\n\t\"Symbol\",\n\t\"SymbolLock\",\n\n\t// Legacy modifier\n\t\"Hyper\",\n\t\"Super\",\n\n\t// White space\n\t\"Enter\", // Control character\n\t\"Tab\", // Control character\n\n\t// Navigation\n\t\"ArrowDown\",\n\t\"ArrowLeft\",\n\t\"ArrowRight\",\n\t\"ArrowUp\",\n\t\"End\",\n\t\"Home\",\n\t\"PageDown\",\n\t\"PageUp\",\n\n\t// Editing\n\t\"Backspace\", // Control character\n\t\"Clear\",\n\t\"Copy\",\n\t\"CrSel\",\n\t\"Cut\",\n\t\"Delete\", // Control character\n\t\"EraseEof\",\n\t\"ExSel\",\n\t\"Insert\",\n\t\"Paste\",\n\t\"Redo\",\n\t\"Undo\",\n\n\t// UI\n\t\"Accept\",\n\t\"Again\",\n\t\"Attn\",\n\t\"Cancel\",\n\t\"ContextMenu\",\n\t\"Escape\", // Control character\n\t\"Execute\",\n\t\"Find\",\n\t\"Help\",\n\t\"Pause\",\n\t\"Play\",\n\t\"Props\",\n\t\"Select\",\n\t\"ZoomIn\",\n\t\"ZoomOut\",\n\n\t// Device\n\t\"BrightnessDown\",\n\t\"BrightnessUp\",\n\t\"Eject\",\n\t\"LogOff\",\n\t\"Power\",\n\t\"PowerOff\",\n\t\"PrintScreen\",\n\t\"Hibernate\",\n\t\"Standby\",\n\t\"WakeUp\",\n\n\t// IME composition keys\n\t\"AllCandidates\",\n\t\"Alphanumeric\",\n\t\"CodeInput\",\n\t\"Compose\",\n\t\"Convert\",\n\t\"Dead\",\n\t\"FinalMode\",\n\t\"GroupFirst\",\n\t\"GroupLast\",\n\t\"GroupNext\",\n\t\"GroupPrevious\",\n\t\"ModeChange\",\n\t\"NextCandidate\",\n\t\"NonConvert\",\n\t\"PreviousCandidate\",\n\t\"Process\",\n\t\"SingleCandidate\",\n\n\t// Korean-specific\n\t\"HangulMode\",\n\t\"HanjaMode\",\n\t\"JunjaMode\",\n\n\t// Japanese-specific\n\t\"Eisu\",\n\t\"Hankaku\",\n\t\"Hiragana\",\n\t\"HiraganaKatakana\",\n\t\"KanaMode\",\n\t\"KanjiMode\",\n\t\"Katakana\",\n\t\"Romaji\",\n\t\"Zenkaku\",\n\t\"ZenkakuHankaku\",\n\n\t// Common function\n\t\"F1\",\n\t\"F2\",\n\t\"F3\",\n\t\"F4\",\n\t\"F5\",\n\t\"F6\",\n\t\"F7\",\n\t\"F8\",\n\t\"F9\",\n\t\"F10\",\n\t\"F11\",\n\t\"F12\",\n\t\"F13\",\n\t\"F14\",\n\t\"F15\",\n\t\"F16\",\n\t\"F17\",\n\t\"F18\",\n\t\"F19\",\n\t\"F20\",\n\t\"F21\",\n\t\"F22\",\n\t\"F23\",\n\t\"F24\",\n\t\"Soft1\",\n\t\"Soft2\",\n\t\"Soft3\",\n\t\"Soft4\",\n\t\"Soft5\",\n\t\"Soft6\",\n\t\"Soft7\",\n\t\"Soft8\",\n\t\"Soft9\",\n\t\"Soft10\",\n\t\"Soft11\",\n\t\"Soft12\",\n\t\"Soft13\",\n\t\"Soft14\",\n\t\"Soft15\",\n\t\"Soft16\",\n\t\"Soft17\",\n\t\"Soft18\",\n\t\"Soft19\",\n\t\"Soft20\",\n\t\"Soft21\",\n\t\"Soft22\",\n\t\"Soft23\",\n\t\"Soft24\",\n\n\t// Multimedia\n\t\"ChannelDown\",\n\t\"ChannelUp\",\n\t\"Close\",\n\t\"MailForward\",\n\t\"MailReply\",\n\t\"MailSend\",\n\t\"MediaClose\",\n\t\"MediaFastForward\",\n\t\"MediaPause\",\n\t\"MediaPlay\",\n\t\"MediaPlayPause\",\n\t\"MediaRecord\",\n\t\"MediaRewind\",\n\t\"MediaStop\",\n\t\"MediaTrackNext\",\n\t\"MediaTrackPrevious\",\n\t\"New\",\n\t\"Open\",\n\t\"Print\",\n\t\"Save\",\n\t\"SpellCheck\",\n\n\t// Multimedia numpad\n\t\"Digit11\",\n\t\"Digit12\",\n\n\t// Audio\n\t\"AudioBalanceLeft\",\n\t\"AudioBalanceRight\",\n\t\"AudioBassBoostDown\",\n\t\"AudioBassBoostToggle\",\n\t\"AudioBassBoostUp\",\n\t\"AudioFaderFront\",\n\t\"AudioFaderRear\",\n\t\"AudioSurroundModeNext\",\n\t\"AudioTrebleDown\",\n\t\"AudioTrebleUp\",\n\t\"AudioVolumeDown\",\n\t\"AudioVolumeUp\",\n\t\"AudioVolumeMute\",\n\t\"MicrophoneToggle\",\n\t\"MicrophoneVolumeDown\",\n\t\"MicrophoneVolumeUp\",\n\t\"MicrophoneVolumeMute\",\n\n\t// Speech\n\t\"SpeechCorrectionList\",\n\t\"SpeechInputToggle\",\n\n\t// Application\n\t\"LaunchApplication1\",\n\t\"LaunchApplication2\",\n\t\"LaunchCalendar\",\n\t\"LaunchContacts\",\n\t\"LaunchMail\",\n\t\"LaunchMediaPlayer\",\n\t\"LaunchMusicPlayer\",\n\t\"LaunchPhone\",\n\t\"LaunchScreenSaver\",\n\t\"LaunchSpreadsheet\",\n\t\"LaunchWebBrowser\",\n\t\"LaunchWebCam\",\n\t\"LaunchWordProcessor\",\n\n\t// Browser\n\t\"BrowserBack\",\n\t\"BrowserFavorites\",\n\t\"BrowserForward\",\n\t\"BrowserHome\",\n\t\"BrowserRefresh\",\n\t\"BrowserSearch\",\n\t\"BrowserStop\",\n\n\t// Mobile phone\n\t\"AppSwitch\",\n\t\"Call\",\n\t\"Camera\",\n\t\"CameraFocus\",\n\t\"EndCall\",\n\t\"GoBack\",\n\t\"GoHome\",\n\t\"HeadsetHook\",\n\t\"LastNumberRedial\",\n\t\"Notification\",\n\t\"MannerMode\",\n\t\"VoiceDial\",\n\n\t// TV\n\t\"TV\",\n\t\"TV3DMode\",\n\t\"TVAntennaCable\",\n\t\"TVAudioDescription\",\n\t\"TVAudioDescriptionMixDown\",\n\t\"TVAudioDescriptionMixUp\",\n\t\"TVContentsMenu\",\n\t\"TVDataService\",\n\t\"TVInput\",\n\t\"TVInputComponent1\",\n\t\"TVInputComponent2\",\n\t\"TVInputComposite1\",\n\t\"TVInputComposite2\",\n\t\"TVInputHDMI1\",\n\t\"TVInputHDMI2\",\n\t\"TVInputHDMI3\",\n\t\"TVInputHDMI4\",\n\t\"TVInputVGA1\",\n\t\"TVMediaContext\",\n\t\"TVNetwork\",\n\t\"TVNumberEntry\",\n\t\"TVPower\",\n\t\"TVRadioService\",\n\t\"TVSatellite\",\n\t\"TVSatelliteBS\",\n\t\"TVSatelliteCS\",\n\t\"TVSatelliteToggle\",\n\t\"TVTerrestrialAnalog\",\n\t\"TVTerrestrialDigital\",\n\t\"TVTimer\",\n\n\t// Media controls\n\t\"AVRInput\",\n\t\"AVRPower\",\n\t\"ColorF0Red\",\n\t\"ColorF1Green\",\n\t\"ColorF2Yellow\",\n\t\"ColorF3Blue\",\n\t\"ColorF4Grey\",\n\t\"ColorF5Brown\",\n\t\"ClosedCaptionToggle\",\n\t\"Dimmer\",\n\t\"DisplaySwap\",\n\t\"DVR\",\n\t\"Exit\",\n\t\"FavoriteClear0\",\n\t\"FavoriteClear1\",\n\t\"FavoriteClear2\",\n\t\"FavoriteClear3\",\n\t\"FavoriteRecall0\",\n\t\"FavoriteRecall1\",\n\t\"FavoriteRecall2\",\n\t\"FavoriteRecall3\",\n\t\"FavoriteStore0\",\n\t\"FavoriteStore1\",\n\t\"FavoriteStore2\",\n\t\"FavoriteStore3\",\n\t\"Guide\",\n\t\"GuideNextDay\",\n\t\"GuidePreviousDay\",\n\t\"Info\",\n\t\"InstantReplay\",\n\t\"Link\",\n\t\"ListProgram\",\n\t\"LiveContent\",\n\t\"Lock\",\n\t\"MediaApps\",\n\t\"MediaAudioTrack\",\n\t\"MediaLast\",\n\t\"MediaSkipBackward\",\n\t\"MediaSkipForward\",\n\t\"MediaStepBackward\",\n\t\"MediaStepForward\",\n\t\"MediaTopMenu\",\n\t\"NavigateIn\",\n\t\"NavigateNext\",\n\t\"NavigateOut\",\n\t\"NavigatePrevious\",\n\t\"NextFavoriteChannel\",\n\t\"NextUserProfile\",\n\t\"OnDemand\",\n\t\"Pairing\",\n\t\"PinPDown\",\n\t\"PinPMove\",\n\t\"PinPToggle\",\n\t\"PinPUp\",\n\t\"PlaySpeedDown\",\n\t\"PlaySpeedReset\",\n\t\"PlaySpeedUp\",\n\t\"RandomToggle\",\n\t\"RcLowBattery\",\n\t\"RecordSpeedNext\",\n\t\"RfBypass\",\n\t\"ScanChannelsToggle\",\n\t\"ScreenModeNext\",\n\t\"Settings\",\n\t\"SplitScreenToggle\",\n\t\"STBInput\",\n\t\"STBPower\",\n\t\"Subtitle\",\n\t\"Teletext\",\n\t\"VideoModeNext\",\n\t\"Wink\",\n\t\"ZoomToggle\",\n\n\t// Unidentified\n\t\"Unidentified\",\n]);\n"
  },
  {
    "path": "frontend/src/utility-functions/network.ts",
    "content": "export type RequestResult = { body: string; status: number };\n\n// Special implementation using the legacy XMLHttpRequest API that provides callbacks to get:\n// - Calls with the percent progress uploading the request to the server\n// - Calls when downloading the result from the server, after the server has begun streaming back the response data\n// It returns a tuple of the promise as well as the XHR which can be used to call the `.abort()` method on it.\nexport function requestWithUploadDownloadProgress(\n\turl: string,\n\tmethod: \"GET\" | \"HEAD\" | \"POST\" | \"PUT\" | \"DELETE\" | \"CONNECT\" | \"OPTIONS\" | \"TRACE\" | \"PATCH\",\n\tbody: string,\n\tuploadProgress: (progress: number) => void,\n\tdownloadOccurring: () => void,\n): [Promise<RequestResult>, XMLHttpRequest | undefined] {\n\tlet xhrValue: XMLHttpRequest | undefined;\n\tconst promise = new Promise<RequestResult>((resolve, reject) => {\n\t\tconst xhr = new XMLHttpRequest();\n\t\txhr.upload.addEventListener(\"progress\", (e) => uploadProgress(e.loaded / e.total));\n\t\txhr.addEventListener(\"progress\", () => downloadOccurring());\n\t\txhr.addEventListener(\"load\", () => resolve({ status: xhr.status, body: xhr.responseText }));\n\t\txhr.addEventListener(\"abort\", () => resolve({ status: xhr.status, body: xhr.responseText }));\n\t\txhr.addEventListener(\"error\", () => reject(new Error(\"Request error\")));\n\t\txhr.open(method, url, true);\n\t\txhr.setRequestHeader(\"accept\", \"*/*\");\n\t\txhr.setRequestHeader(\"accept-language\", \"en-US,en;q=0.9\");\n\t\txhr.setRequestHeader(\"content-type\", \"application/json\");\n\n\t\txhrValue = xhr;\n\n\t\txhr.send(body);\n\t});\n\n\treturn [promise, xhrValue];\n}\n"
  },
  {
    "path": "frontend/src/utility-functions/panic-proxy.ts",
    "content": "// This works by proxying every function call and wrapping a try-catch block to filter out redundant and confusing\n// `RuntimeError: unreachable` exceptions that would normally be printed in the browser's JS console upon a panic.\nexport function panicProxy<T extends object>(module: T): T {\n\tconst proxyHandler = {\n\t\tget(target: T, propKey: string | symbol, receiver: unknown): unknown {\n\t\t\tconst targetValue = Reflect.get(target, propKey, receiver);\n\n\t\t\t// Keep the original value being accessed if it isn't a function\n\t\t\tconst isFunction = typeof targetValue === \"function\";\n\t\t\tif (!isFunction) return targetValue;\n\n\t\t\t// Special handling to wrap the return of a constructor in the proxy\n\t\t\tconst isClass = isFunction && /^\\s*class\\s+/.test(String(targetValue));\n\t\t\tif (isClass) {\n\t\t\t\treturn function (...args: unknown[]): unknown {\n\t\t\t\t\t// All three of these comment lines are necessary to suppress errors at both compile time and while editing this file (@ts-expect-error doesn't work here while editing the file)\n\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n\t\t\t\t\t// @ts-ignore\n\t\t\t\t\tconst result = new targetValue(...args);\n\n\t\t\t\t\treturn panicProxy(result);\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Replace the original function with a wrapper function that runs the original in a try-catch block\n\t\t\treturn function (...args: unknown[]): unknown {\n\t\t\t\tlet result;\n\t\t\t\ttry {\n\t\t\t\t\t// @ts-expect-error TypeScript does not know what `this` is, since it should be able to be anything\n\t\t\t\t\tresult = targetValue.apply(this, args);\n\t\t\t\t} catch (err) {\n\t\t\t\t\t// Suppress `unreachable` WebAssembly.RuntimeError exceptions\n\t\t\t\t\tif (!String(err).startsWith(\"RuntimeError: unreachable\")) throw err;\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t\t};\n\t\t},\n\t};\n\n\treturn new Proxy<T>(module, proxyHandler);\n}\n"
  },
  {
    "path": "frontend/src/utility-functions/platform.ts",
    "content": "export function browserVersion(): string {\n\tconst agent = window.navigator.userAgent;\n\tlet match = agent.match(/(opera|chrome|safari|firefox|msie|trident(?=\\/))\\/?\\s*(\\d+)/i) || [];\n\n\tif (/trident/i.test(match[1])) {\n\t\tconst browser = /\\brv[ :]+(\\d+)/g.exec(agent) || [];\n\t\treturn `IE ${browser[1] || \"\"}`.trim();\n\t}\n\n\tif (match[1] === \"Chrome\") {\n\t\tlet browser = agent.match(/\\bEdg\\/(\\d+)/) || undefined;\n\t\tif (browser !== undefined) return `Edge (Chromium) ${browser[1]}`;\n\n\t\tbrowser = agent.match(/\\bOPR\\/(\\d+)/) || undefined;\n\t\tif (browser !== undefined) return `Opera ${browser[1]}`;\n\t}\n\n\tmatch = match[2] ? [match[1], match[2]] : [navigator.appName, navigator.appVersion, \"-?\"];\n\n\tconst browser = agent.match(/version\\/(\\d+)/i) || undefined;\n\tif (browser !== undefined) match.splice(1, 1, browser[1]);\n\n\treturn `${match[0]} ${match[1]}`;\n}\n\nexport type OperatingSystem = \"Windows\" | \"Mac\" | \"Linux\";\n\nexport function operatingSystem(): OperatingSystem {\n\tconst osTable: Record<string, OperatingSystem> = { Windows: \"Windows\", Mac: \"Mac\", Linux: \"Linux\" };\n\n\tconst userAgentOS = Object.keys(osTable).find((key) => window.navigator.userAgent.includes(key));\n\treturn osTable[userAgentOS || \"Windows\"];\n}\n"
  },
  {
    "path": "frontend/src/utility-functions/rasterization.ts",
    "content": "// Rasterize the string of an SVG document at a given width and height and return the canvas it was drawn onto during the rasterization process\nexport async function rasterizeSVGCanvas(svg: string, width: number, height: number, backgroundColor?: string): Promise<HTMLCanvasElement> {\n\t// A canvas to render our SVG to in order to get a raster image\n\tconst canvas = document.createElement(\"canvas\");\n\tcanvas.width = width;\n\tcanvas.height = height;\n\tconst context = canvas.getContext(\"2d\", { willReadFrequently: true });\n\tif (!context) throw new Error(\"Can't create 2D context from canvas during SVG rasterization\");\n\n\t// Apply a background fill color if one is given\n\tif (backgroundColor) {\n\t\tcontext.fillStyle = backgroundColor;\n\t\tcontext.fillRect(0, 0, width, height);\n\t}\n\n\t// Create a blob URL for our SVG\n\tconst svgBlob = new Blob([svg], { type: \"image/svg+xml;charset=utf-8\" });\n\tconst url = URL.createObjectURL(svgBlob);\n\n\t// Load the Image from the URL and wait until it's done\n\tconst image = new Image();\n\timage.src = url;\n\tawait new Promise<void>((resolve) => {\n\t\timage.onload = () => resolve();\n\t});\n\n\t// Draw our SVG to the canvas\n\tcontext?.drawImage(image, 0, 0, width, height);\n\n\t// Clean up the SVG blob URL (once the URL is revoked, the SVG blob data itself is garbage collected after `svgBlob` goes out of scope)\n\tURL.revokeObjectURL(url);\n\n\treturn canvas;\n}\n\n// Rasterize the string of an SVG document at a given width and height and turn it into the blob data of an image file matching the given MIME type\nexport async function rasterizeSVG(svg: string, width: number, height: number, mime: string, backgroundColor?: string): Promise<Blob> {\n\tif (!width || !height) throw new Error(\"Width and height must be nonzero when given to rasterizeSVG()\");\n\n\tconst canvas = await rasterizeSVGCanvas(svg, width, height, backgroundColor);\n\n\t// Convert the canvas to an image of the correct MIME type\n\tconst blob = await new Promise<Blob | undefined>((resolve) => {\n\t\tcanvas.toBlob((blob) => {\n\t\t\tresolve(blob || undefined);\n\t\t}, mime);\n\t});\n\n\tif (!blob) throw new Error(\"Converting canvas to blob data failed in rasterizeSVG()\");\n\n\treturn blob;\n}\n\n/// Convert an image source (e.g. PNG document) into pixel data, a width, and a height\nexport async function extractPixelData(imageData: ImageBitmapSource): Promise<ImageData> {\n\tconst canvasContext = await imageToCanvasContext(imageData);\n\tconst width = canvasContext.canvas.width;\n\tconst height = canvasContext.canvas.height;\n\n\treturn canvasContext.getImageData(0, 0, width, height);\n}\n\nexport async function imageToCanvasContext(imageData: ImageBitmapSource): Promise<CanvasRenderingContext2D> {\n\t// Special handling to rasterize an SVG file\n\tlet svgImageData;\n\tif (imageData instanceof File && imageData.type === \"image/svg+xml\") {\n\t\tconst svgSource = await imageData.text();\n\t\tconst svgElement = new DOMParser().parseFromString(svgSource, \"image/svg+xml\").querySelector(\"svg\");\n\t\tif (!svgElement) throw new Error(\"Error reading SVG file\");\n\n\t\tlet bounds = svgElement.viewBox.baseVal;\n\n\t\t// If the bounds are zero (which will happen if the `viewBox` is not provided), set bounds to the artwork's bounding box\n\t\tif (bounds.width === 0 || bounds.height === 0) {\n\t\t\t// It's necessary to measure while the element is in the DOM, otherwise the dimensions are zero\n\t\t\tconst toRemove = document.body.insertAdjacentElement(\"beforeend\", svgElement);\n\t\t\tbounds = svgElement.getBBox();\n\t\t\ttoRemove?.remove();\n\t\t}\n\n\t\tsvgImageData = await rasterizeSVGCanvas(svgSource, bounds.width, bounds.height);\n\t}\n\n\t// Decode the image file binary data\n\tconst image = await createImageBitmap(svgImageData || imageData);\n\n\tlet { width, height } = image;\n\twidth = Math.floor(width);\n\theight = Math.floor(height);\n\n\t// Render image to canvas\n\tconst canvas = document.createElement(\"canvas\");\n\tcanvas.width = width;\n\tcanvas.height = height;\n\n\tconst context = canvas.getContext(\"2d\", { willReadFrequently: true });\n\tif (!context) throw new Error(\"Could not create canvas context\");\n\tcontext.drawImage(image, 0, 0, image.width, image.height, 0, 0, width, height);\n\n\treturn context;\n}\n"
  },
  {
    "path": "frontend/src/utility-functions/strip-indents.ts",
    "content": "export function stripIndents(stringPieces: TemplateStringsArray, ...substitutions: unknown[]): string {\n\tconst interleavedSubstitutions = stringPieces.flatMap((stringPiece, index) => [stringPiece, substitutions[index] !== undefined ? substitutions[index] : \"\"]);\n\tconst stringLines = interleavedSubstitutions.join(\"\").split(\"\\n\");\n\n\tconst visibleLineTabPrefixLengths = stringLines.map((line) => (/\\S/.test(line) ? (line.match(/^(\\t*)/) || [])[1].length : Infinity));\n\tconst commonTabPrefixLength = Math.min(...visibleLineTabPrefixLengths);\n\n\tconst linesWithoutCommonTabPrefix = stringLines.map((line) => line.substring(commonTabPrefixLength));\n\tconst multiLineString = linesWithoutCommonTabPrefix.join(\"\\n\");\n\n\treturn multiLineString.trim();\n}\n"
  },
  {
    "path": "frontend/src/utility-functions/viewports.ts",
    "content": "import type { Editor } from \"@graphite/editor\";\n\nexport function setupViewportResizeObserver(editor: Editor): () => void {\n\tconst viewports = Array.from(window.document.querySelectorAll(\"[data-viewport-container]\"));\n\tif (viewports.length <= 0) return () => {};\n\n\tconst viewport = viewports[0];\n\tif (!(viewport instanceof HTMLElement)) return () => {};\n\n\tconst resizeObserver = new ResizeObserver((entries) => {\n\t\tfor (const entry of entries) {\n\t\t\tconst devicePixelRatio = window.devicePixelRatio || 1;\n\n\t\t\t// Get exact device pixel dimensions from the browser\n\t\t\t// Use devicePixelContentBoxSize for pixel-perfect rendering with fallback for Safari\n\t\t\tlet physicalWidth: number;\n\t\t\tlet physicalHeight: number;\n\n\t\t\tif (entry.devicePixelContentBoxSize && entry.devicePixelContentBoxSize.length > 0) {\n\t\t\t\t// Modern browsers (Chrome, Firefox): get exact device pixels from the browser\n\t\t\t\tphysicalWidth = entry.devicePixelContentBoxSize[0].inlineSize;\n\t\t\t\tphysicalHeight = entry.devicePixelContentBoxSize[0].blockSize;\n\t\t\t} else {\n\t\t\t\t// Fallback for Safari: calculate from contentBoxSize and devicePixelRatio\n\t\t\t\tphysicalWidth = entry.contentBoxSize[0].inlineSize * devicePixelRatio;\n\t\t\t\tphysicalHeight = entry.contentBoxSize[0].blockSize * devicePixelRatio;\n\t\t\t}\n\n\t\t\t// Compute the logical size which corresponds to the physical size\n\t\t\tconst logicalWidth = physicalWidth / devicePixelRatio;\n\t\t\tconst logicalHeight = physicalHeight / devicePixelRatio;\n\n\t\t\t// Get viewport position\n\t\t\tconst bounds = entry.target.getBoundingClientRect();\n\n\t\t\t// TODO: Consider passing physical sizes as well to eliminate pixel inaccuracies since width and height could be rounded differently\n\t\t\tconst scale = physicalWidth / logicalWidth;\n\n\t\t\tif (!scale || scale <= 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\teditor.handle.updateViewport(bounds.x, bounds.y, logicalWidth, logicalHeight, scale);\n\t\t}\n\t});\n\n\tresizeObserver.observe(viewport);\n\n\treturn () => {\n\t\tresizeObserver.disconnect();\n\t};\n}\n"
  },
  {
    "path": "frontend/src/utility-functions/widgets.ts",
    "content": "import type { Layout, LayoutGroup, WidgetDiff, WidgetInstance } from \"@graphite/../wasm/pkg/graphite_wasm\";\n\ntype UIItem = Layout | LayoutGroup | WidgetInstance[] | WidgetInstance;\n// Updates a widget layout based on a list of updates, giving the new layout by mutating the `layout` argument\nexport function patchLayout(layout: /* &mut */ Layout, diffs: WidgetDiff[]) {\n\tdiffs.forEach((update) => {\n\t\t// Extract the actual content from the DiffUpdate tagged enum\n\t\tconst { newValue } = update;\n\t\tlet newContent: Layout | LayoutGroup | WidgetInstance;\n\t\tif (\"layout\" in newValue) newContent = newValue.layout;\n\t\telse if (\"layoutGroup\" in newValue) newContent = newValue.layoutGroup;\n\t\telse if (\"widget\" in newValue) newContent = newValue.widget;\n\t\telse throw new Error(\"DiffUpdate invalid\");\n\n\t\t// Find the object where the diff applies to\n\t\tconst diffObject = update.widgetPath.reduce((targetLayout, index: bigint): UIItem | undefined => {\n\t\t\tconst i = Number(index);\n\n\t\t\tif (targetLayout && \"Column\" in targetLayout) return targetLayout.Column.columnWidgets[i];\n\t\t\tif (targetLayout && \"Row\" in targetLayout) return targetLayout.Row.rowWidgets[i];\n\t\t\tif (targetLayout && \"Table\" in targetLayout) return targetLayout.Table.tableWidgets[i];\n\t\t\tif (targetLayout && \"Section\" in targetLayout) return targetLayout.Section.layout[i];\n\t\t\tif (targetLayout && \"widget\" in targetLayout && \"widgetId\" in targetLayout) {\n\t\t\t\tif (\"PopoverButton\" in targetLayout.widget && targetLayout.widget.PopoverButton.popoverLayout) {\n\t\t\t\t\treturn targetLayout.widget.PopoverButton.popoverLayout[i];\n\t\t\t\t}\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error(\"Tried to index widget\");\n\t\t\t\treturn targetLayout;\n\t\t\t}\n\n\t\t\treturn targetLayout?.[i];\n\t\t}, layout);\n\n\t\t// Exit if we failed to produce a valid patch for the existing layout.\n\t\t// This means that the backend assumed an existing layout that doesn't exist in the frontend. This can happen, for\n\t\t// example, if a panel is destroyed in the frontend but was never cleared in the backend, so the next time the backend\n\t\t// tries to update the layout, it attempts to insert only the changes against the old layout that no longer exists.\n\t\tif (diffObject === undefined) {\n\t\t\t// eslint-disable-next-line no-console\n\t\t\tconsole.error(\"In `patchLayout`, the `diffObject` is undefined. The layout has not been updated. See the source code comment above this error for hints.\");\n\t\t\treturn;\n\t\t}\n\n\t\t// If this is a list with a length, then set the length to 0 to clear the list\n\t\tif (\"length\" in diffObject) {\n\t\t\tdiffObject.length = 0;\n\t\t}\n\t\t// Remove all of the keys from the old object\n\t\tObject.keys(diffObject).forEach((key) => Reflect.deleteProperty(diffObject, key));\n\n\t\t// Assign keys to the new object\n\t\t// `Object.assign` works but `diffObject = update.newValue;` doesn't.\n\t\t// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign\n\t\tObject.assign(diffObject, newContent);\n\t});\n}\n"
  },
  {
    "path": "frontend/src/vite-env-override.d.ts",
    "content": "// Allow `import` statements to work with image files in the eyes of the TypeScript compiler.\n// This prevents red underlines from showing and lets it know the types of imported variables for image data.\n// The actual import is performed by Vite when building, as configured in the `resolve` aliases in `vite.config.ts`.\n\ndeclare module \"*.svg\" {\n\tconst content: string;\n\texport default content;\n}\n\ndeclare module \"*.png\" {\n\tconst content: string;\n\texport default content;\n}\n\ndeclare module \"*.jpg\" {\n\tconst content: string;\n\texport default content;\n}\n"
  },
  {
    "path": "frontend/src/vite-env.d.ts",
    "content": "/// <reference types=\"svelte\" />\n/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "frontend/svelte.config.js",
    "content": "import { sveltePreprocess } from \"svelte-preprocess\";\n\nexport default {\n\tpreprocess: sveltePreprocess(),\n\tcompilerOptions: /** @type {import(\"svelte/compiler\").ModuleCompileOptions} */ ({\n\t\twarningFilter: (warning) => !warning.code.startsWith(\"a11y_\") && ![\"css_unused_selector\"].includes(warning.code),\n\t}),\n};\n"
  },
  {
    "path": "frontend/tsconfig.json",
    "content": "{\n\t\"compilerOptions\": {\n\t\t\"target\": \"ESNext\",\n\t\t\"module\": \"ESNext\",\n\t\t\"moduleResolution\": \"node\",\n\t\t\"strict\": true,\n\t\t\"importHelpers\": true,\n\t\t\"skipLibCheck\": true,\n\t\t\"esModuleInterop\": true,\n\t\t\"allowSyntheticDefaultImports\": true,\n\t\t\"verbatimModuleSyntax\": true,\n\t\t\"sourceMap\": true,\n\t\t\"types\": [\"node\", \"svelte\", \"svelte/store\", \"svelte/motion\", \"svelte/transition\", \"svelte/animate\", \"svelte/easing\"],\n\t\t\"baseUrl\": \".\",\n\t\t\"paths\": {\n\t\t\t\"/*\": [\"./*\"],\n\t\t\t\"@graphite/*\": [\"src/*\"],\n\t\t\t\"@branding/*\": [\"../branding/*\"]\n\t\t},\n\t\t\"lib\": [\"ESNext\", \"DOM\", \"DOM.Iterable\", \"ScriptHost\"]\n\t},\n\t\"include\": [\"src/**/*.ts\", \"src/**/*.svelte\", \"*.ts\"],\n\t\"ts-node\": {\n\t\t\"compilerOptions\": {\n\t\t\t\"useDefineForClassFields\": false,\n\t\t\t\"noImplicitOverride\": true\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "frontend/vite.config.ts",
    "content": "import { execSync } from \"child_process\";\nimport { readFileSync } from \"fs\";\nimport path from \"path\";\n\nimport { svelte } from \"@sveltejs/vite-plugin-svelte\";\nimport { defineConfig } from \"vite\";\nimport type { PluginOption } from \"vite\";\nimport { DynamicPublicDirectory as viteMultipleAssets } from \"vite-multiple-assets\";\n\nconst projectRootDir = path.resolve(__dirname);\n\n// https://vitejs.dev/config/\nexport default defineConfig(({ mode }) => {\n\treturn {\n\t\tplugins: plugins(mode),\n\t\tresolve: {\n\t\t\talias: [\n\t\t\t\t{ find: /@branding\\/(.*\\.svg)/, replacement: path.resolve(projectRootDir, \"../branding\", \"$1?raw\") },\n\t\t\t\t{ find: \"@graphite/../assets\", replacement: path.resolve(projectRootDir, \"assets\") },\n\t\t\t\t{ find: \"@graphite/../public\", replacement: path.resolve(projectRootDir, \"public\") },\n\t\t\t\t{ find: \"@graphite\", replacement: path.resolve(projectRootDir, \"src\") },\n\t\t\t],\n\t\t},\n\t\tserver: {\n\t\t\tport: 8080,\n\t\t\thost: \"0.0.0.0\",\n\t\t},\n\t};\n});\n\nfunction plugins(mode: string): PluginOption[] {\n\tconst plugins = [\n\t\tsvelte(),\n\t\tviteMultipleAssets(\n\t\t\t// Additional static asset directories besides `public/`\n\t\t\t[\n\t\t\t\t{ input: \"../demo-artwork/**\", output: \"demo-artwork\" },\n\t\t\t\t{ input: \"../branding/favicons/**\", output: \"\" },\n\t\t\t],\n\t\t\t// Options where we set custom MIME types\n\t\t\t{ mimeTypes: { \".graphite\": \"application/json\" } },\n\t\t),\n\t];\n\n\tif (mode !== \"native\") {\n\t\tplugins.push({\n\t\t\tname: \"third-party-licenses\",\n\t\t\tbuildStart() {\n\t\t\t\ttry {\n\t\t\t\t\texecSync(\"cargo run -p third-party-licenses\", {\n\t\t\t\t\t\tstdio: \"inherit\",\n\t\t\t\t\t});\n\t\t\t\t} catch (_e) {\n\t\t\t\t\tthis.error(\"Failed to generate third-party licenses\");\n\t\t\t\t}\n\t\t\t},\n\t\t\tgenerateBundle() {\n\t\t\t\tconst source = readFileSync(path.resolve(projectRootDir, \"third-party-licenses.txt\"), \"utf-8\");\n\t\t\t\tthis.emitFile({\n\t\t\t\t\ttype: \"asset\",\n\t\t\t\t\tfileName: \"third-party-licenses.txt\",\n\t\t\t\t\tsource,\n\t\t\t\t});\n\t\t\t},\n\t\t});\n\t}\n\n\treturn plugins;\n}\n"
  },
  {
    "path": "frontend/wasm/Cargo.toml",
    "content": "[package]\nname = \"graphite-wasm\"\npublish = false\nversion = \"0.0.0\"\nrust-version = \"1.88\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\nedition = \"2024\"\nreadme = \"../../README.md\"\nhomepage = \"https://graphite.art\"\nrepository = \"https://github.com/GraphiteEditor/Graphite\"\nlicense = \"Apache-2.0\"\n\n[features]\ndefault = [\"gpu\", \"shader-nodes\"]\ngpu = [\"editor/gpu\"]\nshader-nodes = [\"graphene-std/shader-nodes\", \"gpu\"]\nnative = [\"node-macro/disable-registration\"]\n\n[lib]\ncrate-type = [\"cdylib\", \"rlib\"]\n\n[dependencies]\n# Local dependencies\neditor = { path = \"../../editor\", package = \"graphite-editor\", features = [\n\t\"gpu\",\n] }\ngraphene-std = { workspace = true }\n\n# Workspace dependencies\ngraph-craft = { workspace = true }\nlog = { workspace = true }\nserde = { workspace = true }\nwasm-bindgen = { workspace = true }\nserde-wasm-bindgen = { workspace = true }\njs-sys = { workspace = true }\nwasm-bindgen-futures = { workspace = true }\nmath-parser = { workspace = true }\nwgpu = { workspace = true }\nweb-sys = { workspace = true }\nron = { workspace = true }\nserde_json = { workspace = true }\nnode-macro = { workspace = true }\n\n[package.metadata.wasm-pack.profile.dev]\nwasm-opt = false\n\n[package.metadata.wasm-pack.profile.dev.wasm-bindgen]\ndebug-js-glue = true\ndemangle-name-section = true\ndwarf-debug-info = false\n\n[package.metadata.wasm-pack.profile.release]\nwasm-opt = [\"-Os\", \"-g\"]\n\n[package.metadata.wasm-pack.profile.release.wasm-bindgen]\ndebug-js-glue = false\ndemangle-name-section = false\ndwarf-debug-info = false\n\n[lints.rust]\nunexpected_cfgs = { level = \"warn\", check-cfg = [\n\t'cfg(wasm_bindgen_unstable_test_coverage)',\n] }\n\n[package.metadata.cargo-shear]\nignored = [\"wgpu\"]\n"
  },
  {
    "path": "frontend/wasm/README.md",
    "content": "# Overview of `/frontend/wasm/`\n\n## Wasm wrapper API: `src/editor_api.rs`\n\nProvides bindings for JS to call functions defined in this file, and for `FrontendMessage`s to be sent from Rust back to JS in the form of a callback to the subscription router. This Wasm wrapper crate, since it's written in Rust, is able to call into the Editor crate's codebase and send `FrontendMessage`s back to JS.\n\n## Wasm wrapper helper code: `src/helpers.rs`\n\nAssorted function and struct definitions used in the Wasm wrapper.\n\n## Native communication: `src/native_communication.rs`\n\nHandles receiving serialized `FrontendMessage`s from the native desktop app via an `ArrayBuffer` and forwarding them to JS through the editor handle.\n\n## Wasm wrapper initialization: `src/lib.rs`\n\nEntry point for the Rust codebase in the Wasm environment. Sets up panic hooks and logging, and defines thread-local storage for the editor instance, editor handle, message buffer, and panic dialog callback.\n"
  },
  {
    "path": "frontend/wasm/src/editor_api.rs",
    "content": "#![allow(clippy::too_many_arguments)]\n//\n// This file is where functions are defined to be called directly from JS.\n// It serves as a thin wrapper over the editor backend API that relies\n// on the dispatcher messaging system and more complex Rust data types.\n//\nuse crate::helpers::translate_key;\nuse crate::{EDITOR_HANDLE, EDITOR_HAS_CRASHED, Error, FRONTEND_READY, MESSAGE_BUFFER, PANIC_DIALOG_MESSAGE_CALLBACK};\nuse editor::consts::FILE_EXTENSION;\nuse editor::messages::clipboard::utility_types::ClipboardContentRaw;\nuse editor::messages::input_mapper::utility_types::input_keyboard::ModifierKeys;\nuse editor::messages::input_mapper::utility_types::input_mouse::{EditorMouseState, ScrollDelta};\nuse editor::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;\nuse editor::messages::portfolio::document::utility_types::network_interface::ImportOrExport;\nuse editor::messages::portfolio::utility_types::{FontCatalog, FontCatalogFamily};\nuse editor::messages::prelude::*;\nuse editor::messages::tool::tool_messages::tool_prelude::WidgetId;\nuse graph_craft::document::NodeId;\nuse graphene_std::raster::Image;\nuse graphene_std::raster::color::Color;\nuse graphene_std::vector::GradientStops;\nuse js_sys::{Object, Reflect};\nuse serde::Serialize;\nuse serde_wasm_bindgen::{self, from_value};\nuse std::cell::RefCell;\nuse std::path::PathBuf;\nuse std::sync::atomic::{AtomicU64, Ordering};\nuse std::time::Duration;\nuse wasm_bindgen::JsCast;\nuse wasm_bindgen::prelude::*;\nuse web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, ImageData, window};\n\n#[cfg(not(feature = \"native\"))]\nuse crate::EDITOR;\n#[cfg(not(feature = \"native\"))]\nuse editor::application::{Editor, Environment, Host, Platform};\n\nstatic IMAGE_DATA_HASH: AtomicU64 = AtomicU64::new(0);\n\nfn calculate_hash<T: std::hash::Hash>(t: &T) -> u64 {\n\tuse std::collections::hash_map::DefaultHasher;\n\tuse std::hash::Hasher;\n\tlet mut hasher = DefaultHasher::new();\n\tt.hash(&mut hasher);\n\thasher.finish()\n}\n\n/// Provides a handle to access the raw Wasm memory.\n#[wasm_bindgen(js_name = wasmMemory)]\npub fn wasm_memory() -> JsValue {\n\twasm_bindgen::memory()\n}\n\n#[wasm_bindgen(js_name = isPlatformNative)]\npub fn is_platform_native() -> bool {\n\t#[cfg(feature = \"native\")]\n\t{\n\t\ttrue\n\t}\n\t#[cfg(not(feature = \"native\"))]\n\t{\n\t\tfalse\n\t}\n}\n\n// ============================================================================\n\n/// This struct is, via wasm-bindgen, used by JS to interact with the editor backend. It does this by calling functions, which are `impl`ed\n#[wasm_bindgen]\n#[derive(Clone)]\npub struct EditorHandle {\n\t/// This callback is called by the editor's dispatcher when directing `FrontendMessage`s from Rust to JS\n\tfrontend_message_handler_callback: js_sys::Function,\n}\n\n// Defined separately from the `impl` block below since this `impl` block lacks the `#[wasm_bindgen]` attribute.\n// Quirks in wasm-bindgen prevent functions in `#[wasm_bindgen]` `impl` blocks from being made publicly accessible from Rust.\nimpl EditorHandle {\n\tpub fn send_frontend_message_to_js_rust_proxy(&self, message: FrontendMessage) {\n\t\tself.send_frontend_message_to_js(message);\n\t}\n\n\tfn initialize_handle(frontend_message_handler_callback: js_sys::Function) -> EditorHandle {\n\t\tlet panic_callback = frontend_message_handler_callback.clone();\n\t\tlet editor_handle = EditorHandle { frontend_message_handler_callback };\n\t\tif EDITOR_HANDLE.with(|handle| handle.lock().ok().map(|mut guard| *guard = Some(editor_handle.clone()))).is_none() {\n\t\t\tlog::error!(\"Attempted to initialize the editor handle more than once\");\n\t\t}\n\t\tPANIC_DIALOG_MESSAGE_CALLBACK.with_borrow_mut(|callback| *callback = Some(panic_callback));\n\t\teditor_handle\n\t}\n}\n\n#[wasm_bindgen]\nimpl EditorHandle {\n\t#[cfg(not(feature = \"native\"))]\n\tpub fn create(platform: String, uuid_random_seed: u64, frontend_message_handler_callback: js_sys::Function) -> EditorHandle {\n\t\tlet editor = Editor::new(\n\t\t\tEnvironment {\n\t\t\t\tplatform: Platform::Web,\n\t\t\t\thost: match platform.as_str() {\n\t\t\t\t\t\"Linux\" => Host::Linux,\n\t\t\t\t\t\"Mac\" => Host::Mac,\n\t\t\t\t\t\"Windows\" => Host::Windows,\n\t\t\t\t\t_ => unreachable!(),\n\t\t\t\t},\n\t\t\t},\n\t\t\tuuid_random_seed,\n\t\t);\n\n\t\tif EDITOR.with(|handle| handle.lock().ok().map(|mut guard| *guard = Some(editor))).is_none() {\n\t\t\tlog::error!(\"Attempted to initialize the editor more than once\");\n\t\t}\n\n\t\tSelf::initialize_handle(frontend_message_handler_callback)\n\t}\n\n\t#[cfg(feature = \"native\")]\n\tpub fn create(_platform: String, _uuid_random_seed: u64, frontend_message_handler_callback: js_sys::Function) -> EditorHandle {\n\t\tSelf::initialize_handle(frontend_message_handler_callback)\n\t}\n\n\t// Sends a message to the dispatcher in the Editor Backend\n\t#[cfg(not(feature = \"native\"))]\n\tfn dispatch<T: Into<Message>>(&self, message: T) {\n\t\t// Process no further messages after a crash to avoid spamming the console\n\t\tuse crate::MESSAGE_BUFFER;\n\t\tif EDITOR_HAS_CRASHED.load(Ordering::SeqCst) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Get the editor, dispatch the message, and store the `FrontendMessage` queue response\n\t\tlet frontend_messages = EDITOR.with(|editor| {\n\t\t\tlet mut guard = editor.try_lock();\n\t\t\tlet Ok(Some(editor)) = guard.as_deref_mut() else {\n\t\t\t\t// Enqueue messages which can't be procssed currently\n\t\t\t\tMESSAGE_BUFFER.with_borrow_mut(|buffer| buffer.push(message.into()));\n\t\t\t\treturn vec![];\n\t\t\t};\n\n\t\t\teditor.handle_message(message)\n\t\t});\n\n\t\t// Send each `FrontendMessage` to the JavaScript frontend\n\t\tfor message in frontend_messages.into_iter() {\n\t\t\tself.send_frontend_message_to_js(message);\n\t\t}\n\t}\n\n\t#[cfg(feature = \"native\")]\n\tfn dispatch<T: Into<Message>>(&self, message: T) {\n\t\tlet message: Message = message.into();\n\t\tlet Ok(serialized_message) = ron::to_string(&message) else {\n\t\t\tlog::error!(\"Failed to serialize message\");\n\t\t\treturn;\n\t\t};\n\t\tcrate::native_communication::send_message_to_cef(serialized_message)\n\t}\n\n\t// Sends a FrontendMessage to JavaScript\n\tfn send_frontend_message_to_js(&self, message: FrontendMessage) {\n\t\tif let FrontendMessage::UpdateImageData { ref image_data } = message {\n\t\t\tlet new_hash = calculate_hash(image_data);\n\t\t\tlet prev_hash = IMAGE_DATA_HASH.load(Ordering::Relaxed);\n\n\t\t\tif new_hash != prev_hash {\n\t\t\t\trender_image_data_to_canvases(image_data.as_slice());\n\t\t\t\tIMAGE_DATA_HASH.store(new_hash, Ordering::Relaxed);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tlet message_type = message.to_discriminant().local_name();\n\n\t\tlet serializer = serde_wasm_bindgen::Serializer::new().serialize_large_number_types_as_bigints(true);\n\t\tlet message_data = message.serialize(&serializer).expect(\"Failed to serialize FrontendMessage\");\n\n\t\tlet js_return_value = self.frontend_message_handler_callback.call2(&JsValue::null(), &JsValue::from(message_type), &message_data);\n\n\t\tif let Err(error) = js_return_value {\n\t\t\terror!(\"While handling FrontendMessage {:?}, JavaScript threw an error:\\n{:?}\", message.to_discriminant().local_name(), error,)\n\t\t}\n\t}\n\n\t// ========================================================================\n\t// Add additional JS -> Rust wrapper functions below as needed for calling\n\t// the backend from the web frontend.\n\t// ========================================================================\n\n\t/// Re-sends all UI layouts to the frontend. Called during HMR re-mounts when the frontend has lost its layout state.\n\t#[wasm_bindgen(js_name = resendAllLayouts)]\n\tpub fn resend_all_layouts(&self) {\n\t\tself.dispatch(LayoutMessage::ResendAllLayouts);\n\t}\n\n\t#[wasm_bindgen(js_name = initAfterFrontendReady)]\n\tpub fn init_after_frontend_ready(&self) {\n\t\t// Enforce idempotency, so if this is called again during an HMR re-mount, we don't initialize the editor backend twice\n\t\tif FRONTEND_READY.swap(true, Ordering::SeqCst) {\n\t\t\treturn;\n\t\t}\n\n\t\t#[cfg(feature = \"native\")]\n\t\tcrate::native_communication::initialize_native_communication();\n\n\t\tself.dispatch(PortfolioMessage::Init);\n\n\t\t// Poll node graph evaluation on `requestAnimationFrame`\n\t\t{\n\t\t\tlet f = std::rc::Rc::new(RefCell::new(None));\n\t\t\tlet g = f.clone();\n\n\t\t\t*g.borrow_mut() = Some(Closure::new(move |_timestamp| {\n\t\t\t\t#[cfg(not(feature = \"native\"))]\n\t\t\t\twasm_bindgen_futures::spawn_local(poll_node_graph_evaluation());\n\n\t\t\t\tif !EDITOR_HAS_CRASHED.load(Ordering::SeqCst) {\n\t\t\t\t\thandle(|handle| {\n\t\t\t\t\t\t// Process all messages that have been queued up\n\t\t\t\t\t\tlet mut messages = MESSAGE_BUFFER.take();\n\t\t\t\t\t\tmessages.push(\n\t\t\t\t\t\t\tInputPreprocessorMessage::CurrentTime {\n\t\t\t\t\t\t\t\ttimestamp: js_sys::Date::now() as u64,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t.into(),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tmessages.push(AnimationMessage::IncrementFrameCounter.into());\n\n\t\t\t\t\t\t// Used by auto-panning, but this could possibly be refactored in the future, see:\n\t\t\t\t\t\t// <https://github.com/GraphiteEditor/Graphite/pull/2562#discussion_r2041102786>\n\t\t\t\t\t\tmessages.push(BroadcastMessage::TriggerEvent(EventMessage::AnimationFrame).into());\n\n\t\t\t\t\t\thandle.dispatch(Message::Batched { messages: messages.into() });\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\t// Schedule ourself for another requestAnimationFrame callback\n\t\t\t\trequest_animation_frame(f.borrow().as_ref().unwrap());\n\t\t\t}));\n\n\t\t\trequest_animation_frame(g.borrow().as_ref().unwrap());\n\t\t}\n\n\t\t// Auto save all documents on `setTimeout`\n\t\t{\n\t\t\tlet f = std::rc::Rc::new(RefCell::new(None));\n\t\t\tlet g = f.clone();\n\n\t\t\t*g.borrow_mut() = Some(Closure::new(move || {\n\t\t\t\tauto_save_all_documents();\n\n\t\t\t\t// Schedule ourself for another setTimeout callback\n\t\t\t\tset_timeout(f.borrow().as_ref().unwrap(), Duration::from_secs(editor::consts::AUTO_SAVE_TIMEOUT_SECONDS));\n\t\t\t}));\n\n\t\t\tset_timeout(g.borrow().as_ref().unwrap(), Duration::from_secs(editor::consts::AUTO_SAVE_TIMEOUT_SECONDS));\n\t\t}\n\t}\n\n\t#[wasm_bindgen(js_name = addPrimaryImport)]\n\tpub fn add_primary_import(&self) {\n\t\tself.dispatch(DocumentMessage::AddTransaction);\n\t\tself.dispatch(NodeGraphMessage::AddPrimaryImport);\n\t}\n\n\t#[wasm_bindgen(js_name = addSecondaryImport)]\n\tpub fn add_secondary_import(&self) {\n\t\tself.dispatch(DocumentMessage::AddTransaction);\n\t\tself.dispatch(NodeGraphMessage::AddSecondaryImport);\n\t}\n\n\t#[wasm_bindgen(js_name = addPrimaryExport)]\n\tpub fn add_primary_export(&self) {\n\t\tself.dispatch(DocumentMessage::AddTransaction);\n\t\tself.dispatch(NodeGraphMessage::AddPrimaryExport);\n\t}\n\n\t#[wasm_bindgen(js_name = addSecondaryExport)]\n\tpub fn add_secondary_export(&self) {\n\t\tself.dispatch(DocumentMessage::AddTransaction);\n\t\tself.dispatch(NodeGraphMessage::AddSecondaryExport);\n\t}\n\n\t/// Start Pointer Lock\n\t#[wasm_bindgen(js_name = appWindowPointerLock)]\n\tpub fn app_window_pointer_lock(&self) {\n\t\tlet message = AppWindowMessage::PointerLock;\n\t\tself.dispatch(message);\n\t}\n\n\t/// Minimizes the application window to the taskbar or dock\n\t#[wasm_bindgen(js_name = appWindowMinimize)]\n\tpub fn app_window_minimize(&self) {\n\t\tlet message = AppWindowMessage::Minimize;\n\t\tself.dispatch(message);\n\t}\n\n\t/// Toggles minimizing or restoring down the application window\n\t#[wasm_bindgen(js_name = appWindowMaximize)]\n\tpub fn app_window_maximize(&self) {\n\t\tlet message = AppWindowMessage::Maximize;\n\t\tself.dispatch(message);\n\t}\n\n\t#[wasm_bindgen(js_name = appWindowFullscreen)]\n\tpub fn app_window_fullscreen(&self) {\n\t\tlet message = AppWindowMessage::Fullscreen;\n\t\tself.dispatch(message);\n\t}\n\n\t/// Closes the application window\n\t#[wasm_bindgen(js_name = appWindowClose)]\n\tpub fn app_window_close(&self) {\n\t\tlet message = AppWindowMessage::Close;\n\t\tself.dispatch(message);\n\t}\n\n\t/// Drag the application window\n\t#[wasm_bindgen(js_name = appWindowDrag)]\n\tpub fn app_window_start_drag(&self) {\n\t\tlet message = AppWindowMessage::Drag;\n\t\tself.dispatch(message);\n\t}\n\n\t/// Displays a dialog with an error message\n\t#[wasm_bindgen(js_name = errorDialog)]\n\tpub fn error_dialog(&self, title: String, description: String) {\n\t\tlet message = DialogMessage::DisplayDialogError { title, description };\n\t\tself.dispatch(message);\n\t}\n\n\t/// Answer whether or not the editor has crashed\n\t#[wasm_bindgen(js_name = hasCrashed)]\n\tpub fn has_crashed(&self) -> bool {\n\t\tEDITOR_HAS_CRASHED.load(Ordering::SeqCst)\n\t}\n\n\t/// Answer whether or not the editor is in development mode\n\t#[wasm_bindgen(js_name = inDevelopmentMode)]\n\tpub fn in_development_mode(&self) -> bool {\n\t\tcfg!(debug_assertions)\n\t}\n\n\t/// Get the constant `FILE_EXTENSION`\n\t#[wasm_bindgen(js_name = fileExtension)]\n\tpub fn file_extension(&self) -> String {\n\t\tFILE_EXTENSION.into()\n\t}\n\n\t/// Update the value of a given UI widget, but don't commit it to the history (unless `commit_layout()` is called, which handles that)\n\t#[wasm_bindgen(js_name = widgetValueUpdate)]\n\tpub fn widget_value_update(&self, layout_target: JsValue, widget_id: u64, value: JsValue, resend_widget: bool) -> Result<(), JsValue> {\n\t\tself.widget_value_update_helper(layout_target, widget_id, value, resend_widget)\n\t}\n\n\t/// Commit the value of a given UI widget to the history\n\t#[wasm_bindgen(js_name = widgetValueCommit)]\n\tpub fn widget_value_commit(&self, layout_target: JsValue, widget_id: u64, value: JsValue) -> Result<(), JsValue> {\n\t\tself.widget_value_commit_helper(layout_target, widget_id, value)\n\t}\n\n\t/// Update the value of a given UI widget, and commit it to the history\n\t#[wasm_bindgen(js_name = widgetValueCommitAndUpdate)]\n\tpub fn widget_value_commit_and_update(&self, layout_target: JsValue, widget_id: u64, value: JsValue, resend_widget: bool) -> Result<(), JsValue> {\n\t\tself.widget_value_commit_helper(layout_target.clone(), widget_id, value.clone())?;\n\t\tself.widget_value_update_helper(layout_target, widget_id, value, resend_widget)?;\n\t\tOk(())\n\t}\n\n\tpub fn widget_value_update_helper(&self, layout_target: JsValue, widget_id: u64, value: JsValue, resend_widget: bool) -> Result<(), JsValue> {\n\t\tlet widget_id = WidgetId(widget_id);\n\t\tmatch (from_value(layout_target), from_value(value)) {\n\t\t\t(Ok(layout_target), Ok(value)) => {\n\t\t\t\tlet message = LayoutMessage::WidgetValueUpdate { layout_target, widget_id, value };\n\t\t\t\tself.dispatch(message);\n\n\t\t\t\tif resend_widget {\n\t\t\t\t\tlet resend_message = LayoutMessage::ResendActiveWidget { layout_target, widget_id };\n\t\t\t\t\tself.dispatch(resend_message);\n\t\t\t\t}\n\n\t\t\t\tOk(())\n\t\t\t}\n\t\t\t(target, val) => Err(Error::new(&format!(\"Could not update UI\\nDetails:\\nTarget: {target:?}\\nValue: {val:?}\")).into()),\n\t\t}\n\t}\n\n\tpub fn widget_value_commit_helper(&self, layout_target: JsValue, widget_id: u64, value: JsValue) -> Result<(), JsValue> {\n\t\tlet widget_id = WidgetId(widget_id);\n\t\tmatch (from_value(layout_target), from_value(value)) {\n\t\t\t(Ok(layout_target), Ok(value)) => {\n\t\t\t\tlet message = LayoutMessage::WidgetValueCommit { layout_target, widget_id, value };\n\t\t\t\tself.dispatch(message);\n\t\t\t\tOk(())\n\t\t\t}\n\t\t\t(target, val) => Err(Error::new(&format!(\"Could not commit UI\\nDetails:\\nTarget: {target:?}\\nValue: {val:?}\")).into()),\n\t\t}\n\t}\n\n\t#[wasm_bindgen(js_name = loadPreferences)]\n\tpub fn load_preferences(&self, preferences: Option<String>) {\n\t\tif let Some(preferences) = preferences {\n\t\t\tlet Ok(preferences) = serde_json::from_str(&preferences) else {\n\t\t\t\tlog::error!(\"Failed to deserialize preferences\");\n\t\t\t\treturn;\n\t\t\t};\n\t\t\tlet message = PreferencesMessage::Load { preferences };\n\t\t\tself.dispatch(message);\n\t\t}\n\t}\n\n\t#[wasm_bindgen(js_name = selectDocument)]\n\tpub fn select_document(&self, document_id: u64) {\n\t\tlet document_id = DocumentId(document_id);\n\t\tlet message = PortfolioMessage::SelectDocument { document_id };\n\t\tself.dispatch(message);\n\t}\n\n\t#[wasm_bindgen(js_name = newDocumentDialog)]\n\tpub fn new_document_dialog(&self) {\n\t\tlet message = DialogMessage::RequestNewDocumentDialog;\n\t\tself.dispatch(message);\n\t}\n\n\t#[wasm_bindgen(js_name = openFile)]\n\tpub fn open_file(&self, path: String, content: Vec<u8>) {\n\t\tlet message = PortfolioMessage::OpenFile { path: PathBuf::from(path), content };\n\t\tself.dispatch(message);\n\t}\n\n\t#[wasm_bindgen(js_name = importFile)]\n\tpub fn import_file(&self, path: String, content: Vec<u8>) {\n\t\tlet message = PortfolioMessage::ImportFile { path: PathBuf::from(path), content };\n\t\tself.dispatch(message);\n\t}\n\n\t#[wasm_bindgen(js_name = openAutoSavedDocument)]\n\tpub fn open_auto_saved_document(&self, document_id: u64, document_name: String, document_is_saved: bool, document_serialized_content: String, to_front: bool) {\n\t\tlet document_id = DocumentId(document_id);\n\t\tlet message = PortfolioMessage::OpenDocumentFileWithId {\n\t\t\tdocument_id,\n\t\t\tdocument_name: Some(document_name),\n\t\t\tdocument_path: None,\n\t\t\tdocument_is_auto_saved: true,\n\t\t\tdocument_is_saved,\n\t\t\tdocument_serialized_content,\n\t\t\tto_front,\n\t\t\tselect_after_open: false,\n\t\t};\n\t\tself.dispatch(message);\n\t}\n\n\t#[wasm_bindgen(js_name = triggerAutoSave)]\n\tpub fn trigger_auto_save(&self, document_id: u64) {\n\t\tlet document_id = DocumentId(document_id);\n\t\tlet message = PortfolioMessage::AutoSaveDocument { document_id };\n\t\tself.dispatch(message);\n\t}\n\n\t#[wasm_bindgen(js_name = closeDocumentWithConfirmation)]\n\tpub fn close_document_with_confirmation(&self, document_id: u64) {\n\t\tlet document_id = DocumentId(document_id);\n\t\tlet message = PortfolioMessage::CloseDocumentWithConfirmation { document_id };\n\t\tself.dispatch(message);\n\t}\n\n\t#[wasm_bindgen(js_name = requestAboutGraphiteDialogWithLocalizedCommitDate)]\n\tpub fn request_about_graphite_dialog_with_localized_commit_date(&self, localized_commit_date: String, localized_commit_year: String) {\n\t\tlet message = DialogMessage::RequestAboutGraphiteDialogWithLocalizedCommitDate {\n\t\t\tlocalized_commit_date,\n\t\t\tlocalized_commit_year,\n\t\t};\n\t\tself.dispatch(message);\n\t}\n\n\t#[wasm_bindgen(js_name = requestLicensesThirdPartyDialogWithLicenseText)]\n\tpub fn request_licenses_third_party_dialog_with_license_text(&self, license_text: String) {\n\t\tlet message = DialogMessage::RequestLicensesThirdPartyDialogWithLicenseText { license_text };\n\t\tself.dispatch(message);\n\t}\n\n\t/// Send new viewport info to the backend\n\t#[wasm_bindgen(js_name = updateViewport)]\n\tpub fn update_viewport(&self, x: f64, y: f64, width: f64, height: f64, scale: f64) {\n\t\tlet message = ViewportMessage::Update { x, y, width, height, scale };\n\t\tself.dispatch(message);\n\t}\n\n\t/// Zoom the canvas to fit all content\n\t#[wasm_bindgen(js_name = zoomCanvasToFitAll)]\n\tpub fn zoom_canvas_to_fit_all(&self) {\n\t\tlet message = DocumentMessage::ZoomCanvasToFitAll;\n\t\tself.dispatch(message);\n\t}\n\n\t/// Mouse movement within the screenspace bounds of the viewport\n\t#[wasm_bindgen(js_name = onMouseMove)]\n\tpub fn on_mouse_move(&self, x: f64, y: f64, mouse_keys: u8, modifiers: u8) {\n\t\tlet editor_mouse_state = EditorMouseState::from_keys_and_editor_position(mouse_keys, (x, y).into());\n\n\t\tlet modifier_keys = ModifierKeys::from_bits(modifiers).expect(\"Invalid modifier keys\");\n\n\t\tlet message = InputPreprocessorMessage::PointerMove { editor_mouse_state, modifier_keys };\n\t\tself.dispatch(message);\n\t}\n\n\t/// Mouse scrolling within the screenspace bounds of the viewport\n\t#[wasm_bindgen(js_name = onWheelScroll)]\n\tpub fn on_wheel_scroll(&self, x: f64, y: f64, mouse_keys: u8, wheel_delta_x: f64, wheel_delta_y: f64, wheel_delta_z: f64, modifiers: u8) {\n\t\tlet mut editor_mouse_state = EditorMouseState::from_keys_and_editor_position(mouse_keys, (x, y).into());\n\t\teditor_mouse_state.scroll_delta = ScrollDelta::new(wheel_delta_x, wheel_delta_y, wheel_delta_z);\n\n\t\tlet modifier_keys = ModifierKeys::from_bits(modifiers).expect(\"Invalid modifier keys\");\n\n\t\tlet message = InputPreprocessorMessage::WheelScroll { editor_mouse_state, modifier_keys };\n\t\tself.dispatch(message);\n\t}\n\n\t/// A mouse button depressed within screenspace the bounds of the viewport\n\t#[wasm_bindgen(js_name = onMouseDown)]\n\tpub fn on_mouse_down(&self, x: f64, y: f64, mouse_keys: u8, modifiers: u8) {\n\t\tlet editor_mouse_state = EditorMouseState::from_keys_and_editor_position(mouse_keys, (x, y).into());\n\n\t\tlet modifier_keys = ModifierKeys::from_bits(modifiers).expect(\"Invalid modifier keys\");\n\n\t\tlet message = InputPreprocessorMessage::PointerDown { editor_mouse_state, modifier_keys };\n\t\tself.dispatch(message);\n\t}\n\n\t/// A mouse button released\n\t#[wasm_bindgen(js_name = onMouseUp)]\n\tpub fn on_mouse_up(&self, x: f64, y: f64, mouse_keys: u8, modifiers: u8) {\n\t\tlet editor_mouse_state = EditorMouseState::from_keys_and_editor_position(mouse_keys, (x, y).into());\n\n\t\tlet modifier_keys = ModifierKeys::from_bits(modifiers).expect(\"Invalid modifier keys\");\n\n\t\tlet message = InputPreprocessorMessage::PointerUp { editor_mouse_state, modifier_keys };\n\t\tself.dispatch(message);\n\t}\n\n\t/// Mouse shaken\n\t#[wasm_bindgen(js_name = onMouseShake)]\n\tpub fn on_mouse_shake(&self, x: f64, y: f64, mouse_keys: u8, modifiers: u8) {\n\t\tlet editor_mouse_state = EditorMouseState::from_keys_and_editor_position(mouse_keys, (x, y).into());\n\n\t\tlet modifier_keys = ModifierKeys::from_bits(modifiers).expect(\"Invalid modifier keys\");\n\n\t\tlet message = InputPreprocessorMessage::PointerShake { editor_mouse_state, modifier_keys };\n\t\tself.dispatch(message);\n\t}\n\n\t/// Mouse double clicked\n\t#[wasm_bindgen(js_name = onDoubleClick)]\n\tpub fn on_double_click(&self, x: f64, y: f64, mouse_keys: u8, modifiers: u8) {\n\t\tlet editor_mouse_state = EditorMouseState::from_keys_and_editor_position(mouse_keys, (x, y).into());\n\n\t\tlet modifier_keys = ModifierKeys::from_bits(modifiers).expect(\"Invalid modifier keys\");\n\n\t\tlet message = InputPreprocessorMessage::DoubleClick { editor_mouse_state, modifier_keys };\n\t\tself.dispatch(message);\n\t}\n\n\t/// A keyboard button depressed within screenspace the bounds of the viewport\n\t#[wasm_bindgen(js_name = onKeyDown)]\n\tpub fn on_key_down(&self, name: String, modifiers: u8, key_repeat: bool) {\n\t\tlet key = translate_key(&name);\n\t\tlet modifier_keys = ModifierKeys::from_bits(modifiers).expect(\"Invalid modifier keys\");\n\n\t\ttrace!(\"Key down {key:?}, name: {name}, modifiers: {modifiers:?}, key repeat: {key_repeat}\");\n\n\t\tlet message = InputPreprocessorMessage::KeyDown { key, key_repeat, modifier_keys };\n\t\tself.dispatch(message);\n\t}\n\n\t/// A keyboard button released\n\t#[wasm_bindgen(js_name = onKeyUp)]\n\tpub fn on_key_up(&self, name: String, modifiers: u8, key_repeat: bool) {\n\t\tlet key = translate_key(&name);\n\t\tlet modifier_keys = ModifierKeys::from_bits(modifiers).expect(\"Invalid modifier keys\");\n\n\t\ttrace!(\"Key up {key:?}, name: {name}, modifiers: {modifier_keys:?}, key repeat: {key_repeat}\");\n\n\t\tlet message = InputPreprocessorMessage::KeyUp { key, key_repeat, modifier_keys };\n\t\tself.dispatch(message);\n\t}\n\n\t/// A text box was committed\n\t#[wasm_bindgen(js_name = onChangeText)]\n\tpub fn on_change_text(&self, new_text: String, is_left_or_right_click: bool) -> Result<(), JsValue> {\n\t\tlet message = TextToolMessage::TextChange { new_text, is_left_or_right_click };\n\t\tself.dispatch(message);\n\n\t\tOk(())\n\t}\n\n\t/// The font catalog has been loaded\n\t#[wasm_bindgen(js_name = onFontCatalogLoad)]\n\tpub fn on_font_catalog_load(&self, catalog: JsValue) -> Result<(), JsValue> {\n\t\t// Deserializing from TS type: `{ name: string; styles: { weight: number, italic: boolean, url: string }[] }[]`\n\t\tlet families = serde_wasm_bindgen::from_value::<Vec<FontCatalogFamily>>(catalog)?;\n\t\tlet message = PortfolioMessage::FontCatalogLoaded { catalog: FontCatalog(families) };\n\t\tself.dispatch(message);\n\n\t\tOk(())\n\t}\n\n\t/// A font has been downloaded\n\t#[wasm_bindgen(js_name = onFontLoad)]\n\tpub fn on_font_load(&self, font_family: String, font_style: String, data: Vec<u8>) -> Result<(), JsValue> {\n\t\tlet message = PortfolioMessage::FontLoaded { font_family, font_style, data };\n\t\tself.dispatch(message);\n\n\t\tOk(())\n\t}\n\n\t/// Dialog got dismissed\n\t#[wasm_bindgen(js_name = onDialogDismiss)]\n\tpub fn on_dialog_dismiss(&self) {\n\t\tlet message = DialogMessage::Dismiss;\n\t\tself.dispatch(message);\n\t}\n\n\t/// A text box was changed\n\t#[wasm_bindgen(js_name = updateBounds)]\n\tpub fn update_bounds(&self, new_text: String) -> Result<(), JsValue> {\n\t\tlet message = TextToolMessage::UpdateBounds { new_text };\n\t\tself.dispatch(message);\n\n\t\tOk(())\n\t}\n\n\t/// Update primary color with values on a scale from 0 to 1.\n\t#[wasm_bindgen(js_name = updatePrimaryColor)]\n\tpub fn update_primary_color(&self, red: f32, green: f32, blue: f32, alpha: f32) -> Result<(), JsValue> {\n\t\tlet Some(primary_color) = Color::from_rgbaf32(red, green, blue, alpha) else {\n\t\t\treturn Err(Error::new(\"Invalid color\").into());\n\t\t};\n\n\t\tlet message = ToolMessage::SelectWorkingColor {\n\t\t\tcolor: primary_color.to_linear_srgb(),\n\t\t\tprimary: true,\n\t\t};\n\t\tself.dispatch(message);\n\n\t\tOk(())\n\t}\n\n\t/// Update secondary color with values on a scale from 0 to 1.\n\t#[wasm_bindgen(js_name = updateSecondaryColor)]\n\tpub fn update_secondary_color(&self, red: f32, green: f32, blue: f32, alpha: f32) -> Result<(), JsValue> {\n\t\tlet Some(secondary_color) = Color::from_rgbaf32(red, green, blue, alpha) else {\n\t\t\treturn Err(Error::new(\"Invalid color\").into());\n\t\t};\n\n\t\tlet message = ToolMessage::SelectWorkingColor {\n\t\t\tcolor: secondary_color.to_linear_srgb(),\n\t\t\tprimary: false,\n\t\t};\n\t\tself.dispatch(message);\n\n\t\tOk(())\n\t}\n\n\t/// Update the color of the currently-edited gradient stop\n\t#[wasm_bindgen(js_name = updateGradientStopColor)]\n\tpub fn update_gradient_stop_color(&self, red: f32, green: f32, blue: f32, alpha: f32) -> Result<(), JsValue> {\n\t\tlet Some(color) = Color::from_rgbaf32(red, green, blue, alpha) else {\n\t\t\treturn Err(Error::new(\"Invalid color\").into());\n\t\t};\n\t\tself.dispatch(GradientToolMessage::UpdateStopColor { color: color.to_linear_srgb() });\n\t\tOk(())\n\t}\n\n\t/// Start a new undo transaction for gradient stop color editing\n\t#[wasm_bindgen(js_name = startGradientStopColorTransaction)]\n\tpub fn start_gradient_stop_color_transaction(&self) {\n\t\tself.dispatch(GradientToolMessage::StartTransactionForColorStop);\n\t}\n\n\t/// Commit the current gradient stop color transaction (called on pointer-up after each drag/click)\n\t#[wasm_bindgen(js_name = commitGradientStopColorTransaction)]\n\tpub fn commit_gradient_stop_color_transaction(&self) {\n\t\tself.dispatch(GradientToolMessage::CommitTransactionForColorStop);\n\t}\n\n\t/// Close the gradient stop color picker and commit any pending transaction\n\t#[wasm_bindgen(js_name = closeGradientStopColorPicker)]\n\tpub fn close_gradient_stop_color_picker(&self) {\n\t\tself.dispatch(GradientToolMessage::CloseStopColorPicker);\n\t}\n\n\t#[wasm_bindgen(js_name = clipLayer)]\n\tpub fn clip_layer(&self, id: u64) {\n\t\tlet id = NodeId(id);\n\t\tlet message = DocumentMessage::ClipLayer { id };\n\t\tself.dispatch(message);\n\t}\n\n\t/// Modify the layer selection based on the layer which is clicked while holding down the <kbd>Ctrl</kbd> and/or <kbd>Shift</kbd> modifier keys used for range selection behavior\n\t#[wasm_bindgen(js_name = selectLayer)]\n\tpub fn select_layer(&self, id: u64, ctrl: bool, shift: bool) {\n\t\tlet id = NodeId(id);\n\t\tlet message = DocumentMessage::SelectLayer { id, ctrl, shift };\n\t\tself.dispatch(message);\n\t}\n\n\t/// Deselect all layers\n\t#[wasm_bindgen(js_name = deselectAllLayers)]\n\tpub fn deselect_all_layers(&self) {\n\t\tlet message = DocumentMessage::DeselectAllLayers;\n\t\tself.dispatch(message);\n\t}\n\n\t/// Move a layer to within a folder and placed down at the given index.\n\t/// If the folder is `None`, it is inserted into the document root.\n\t/// If the insert index is `None`, it is inserted at the start of the folder.\n\t#[wasm_bindgen(js_name = moveLayerInTree)]\n\tpub fn move_layer_in_tree(&self, insert_parent_id: Option<u64>, insert_index: Option<usize>) {\n\t\tlet insert_parent_id = insert_parent_id.map(NodeId);\n\t\tlet parent = insert_parent_id.map(LayerNodeIdentifier::new_unchecked).unwrap_or_default();\n\n\t\tlet message = DocumentMessage::MoveSelectedLayersTo {\n\t\t\tparent,\n\t\t\tinsert_index: insert_index.unwrap_or_default(),\n\t\t};\n\t\tself.dispatch(message);\n\t}\n\n\t/// Set the name for the layer\n\t#[wasm_bindgen(js_name = setLayerName)]\n\tpub fn set_layer_name(&self, id: u64, name: String) {\n\t\tlet layer = LayerNodeIdentifier::new_unchecked(NodeId(id));\n\t\tlet message = NodeGraphMessage::SetDisplayName {\n\t\t\tnode_id: layer.to_node(),\n\t\t\talias: name,\n\t\t\tskip_adding_history_step: false,\n\t\t};\n\t\tself.dispatch(message);\n\t}\n\n\t/// Translates document (in viewport coords)\n\t#[wasm_bindgen(js_name = panCanvasAbortPrepare)]\n\tpub fn pan_canvas_abort_prepare(&self, x_not_y_axis: bool) {\n\t\tlet message = NavigationMessage::CanvasPanAbortPrepare { x_not_y_axis };\n\t\tself.dispatch(message);\n\t}\n\n\t#[wasm_bindgen(js_name = panCanvasAbort)]\n\tpub fn pan_canvas_abort(&self, x_not_y_axis: bool) {\n\t\tlet message = NavigationMessage::CanvasPanAbort { x_not_y_axis };\n\t\tself.dispatch(message);\n\t}\n\n\t/// Translates document (in viewport coords)\n\t#[wasm_bindgen(js_name = panCanvas)]\n\tpub fn pan_canvas(&self, delta_x: f64, delta_y: f64) {\n\t\tlet message = NavigationMessage::CanvasPan { delta: (delta_x, delta_y).into() };\n\t\tself.dispatch(message);\n\t}\n\n\t/// Translates document (in viewport coords)\n\t#[wasm_bindgen(js_name = panCanvasByFraction)]\n\tpub fn pan_canvas_by_fraction(&self, delta_x: f64, delta_y: f64) {\n\t\tlet message = NavigationMessage::CanvasPanByViewportFraction { delta: (delta_x, delta_y).into() };\n\t\tself.dispatch(message);\n\t}\n\n\t/// Merge the selected nodes into a subnetwork\n\t#[wasm_bindgen(js_name = mergeSelectedNodes)]\n\tpub fn merge_nodes(&self) {\n\t\tlet message = NodeGraphMessage::MergeSelectedNodes;\n\t\tself.dispatch(message);\n\t}\n\n\t/// Toggle lock state of all selected layers\n\t#[wasm_bindgen(js_name = toggleSelectedLocked)]\n\tpub fn toggle_selected_locked(&self) {\n\t\tlet message = NodeGraphMessage::ToggleSelectedLocked;\n\t\tself.dispatch(message);\n\t}\n\n\t/// Creates a new document node in the node graph\n\t#[wasm_bindgen(js_name = createNode)]\n\tpub fn create_node(&self, node_type: JsValue, x: i32, y: i32) {\n\t\tlet value: serde_json::Value = serde_wasm_bindgen::from_value(node_type).unwrap();\n\n\t\tlet id = NodeId::new();\n\t\tlet message = NodeGraphMessage::CreateNodeFromContextMenu {\n\t\t\tnode_id: Some(id),\n\t\t\tnode_type: value.into(),\n\t\t\txy: Some((x / 24, y / 24)),\n\t\t\tadd_transaction: true,\n\t\t};\n\t\tself.dispatch(message);\n\t}\n\n\t/// Respond to selection read\n\t#[wasm_bindgen(js_name = readSelection)]\n\tpub fn read_selection(&self, content: Option<String>, cut: bool) {\n\t\tlet message = ClipboardMessage::ReadSelection { content, cut };\n\t\tself.dispatch(message);\n\t}\n\n\t/// Paste from a serialized JSON representation\n\t#[wasm_bindgen(js_name = pasteText)]\n\tpub fn paste_text(&self, data: String) {\n\t\tlet message = ClipboardMessage::ReadClipboard {\n\t\t\tcontent: ClipboardContentRaw::Text(data),\n\t\t};\n\t\tself.dispatch(message);\n\t}\n\n\t/// Pastes an image\n\t#[wasm_bindgen(js_name = pasteImage)]\n\tpub fn paste_image(\n\t\t&self,\n\t\tname: Option<String>,\n\t\timage_data: Vec<u8>,\n\t\twidth: u32,\n\t\theight: u32,\n\t\tmouse_x: Option<f64>,\n\t\tmouse_y: Option<f64>,\n\t\tinsert_parent_id: Option<u64>,\n\t\tinsert_index: Option<usize>,\n\t) {\n\t\tlet mouse = mouse_x.and_then(|x| mouse_y.map(|y| (x, y)));\n\t\tlet image = graphene_std::raster::Image::from_image_data(&image_data, width, height);\n\n\t\tlet parent_and_insert_index = if let (Some(insert_parent_id), Some(insert_index)) = (insert_parent_id, insert_index) {\n\t\t\tlet insert_parent_id = NodeId(insert_parent_id);\n\t\t\tlet parent = LayerNodeIdentifier::new_unchecked(insert_parent_id);\n\t\t\tSome((parent, insert_index))\n\t\t} else {\n\t\t\tNone\n\t\t};\n\n\t\tlet message = PortfolioMessage::PasteImage {\n\t\t\tname,\n\t\t\timage,\n\t\t\tmouse,\n\t\t\tparent_and_insert_index,\n\t\t};\n\t\tself.dispatch(message);\n\t}\n\n\t#[wasm_bindgen(js_name = pasteSvg)]\n\tpub fn paste_svg(&self, name: Option<String>, svg: String, mouse_x: Option<f64>, mouse_y: Option<f64>, insert_parent_id: Option<u64>, insert_index: Option<usize>) {\n\t\tlet mouse = mouse_x.and_then(|x| mouse_y.map(|y| (x, y)));\n\n\t\tlet parent_and_insert_index = if let (Some(insert_parent_id), Some(insert_index)) = (insert_parent_id, insert_index) {\n\t\t\tlet insert_parent_id = NodeId(insert_parent_id);\n\t\t\tlet parent = LayerNodeIdentifier::new_unchecked(insert_parent_id);\n\t\t\tSome((parent, insert_index))\n\t\t} else {\n\t\t\tNone\n\t\t};\n\n\t\tlet message = PortfolioMessage::PasteSvg {\n\t\t\tname,\n\t\t\tsvg,\n\t\t\tmouse,\n\t\t\tparent_and_insert_index,\n\t\t};\n\t\tself.dispatch(message);\n\t}\n\n\t/// Toggle visibility of a layer or node given its node ID\n\t#[wasm_bindgen(js_name = toggleNodeVisibilityLayerPanel)]\n\tpub fn toggle_node_visibility_layer(&self, id: u64) {\n\t\tlet node_id = NodeId(id);\n\t\tlet message = NodeGraphMessage::ToggleVisibility { node_id };\n\t\tself.dispatch(message);\n\t}\n\n\t/// Pin or unpin a node given its node ID\n\t#[wasm_bindgen(js_name = setNodePinned)]\n\tpub fn set_node_pinned(&self, id: u64, pinned: bool) {\n\t\tself.dispatch(DocumentMessage::SetNodePinned { node_id: NodeId(id), pinned });\n\t}\n\n\t/// Delete a layer or node given its node ID\n\t#[wasm_bindgen(js_name = deleteNode)]\n\tpub fn delete_node(&self, id: u64) {\n\t\tself.dispatch(DocumentMessage::DeleteNode { node_id: NodeId(id) });\n\t}\n\n\t/// Toggle lock state of a layer from the layer list\n\t#[wasm_bindgen(js_name = toggleLayerLock)]\n\tpub fn toggle_layer_lock(&self, node_id: u64) {\n\t\tlet message = NodeGraphMessage::ToggleLocked { node_id: NodeId(node_id) };\n\t\tself.dispatch(message);\n\t}\n\n\t/// Toggle expansions state of a layer from the layer list\n\t#[wasm_bindgen(js_name = toggleLayerExpansion)]\n\tpub fn toggle_layer_expansion(&self, id: u64, recursive: bool) {\n\t\tlet id = NodeId(id);\n\t\tlet message = DocumentMessage::ToggleLayerExpansion { id, recursive };\n\t\tself.dispatch(message);\n\t}\n\n\t/// Set the active panel to the most recently clicked panel\n\t#[wasm_bindgen(js_name = setActivePanel)]\n\tpub fn set_active_panel(&self, panel: String) {\n\t\tlet message = PortfolioMessage::SetActivePanel { panel: panel.into() };\n\t\tself.dispatch(message);\n\t}\n\n\t/// Toggle display type for a layer\n\t#[wasm_bindgen(js_name = setToNodeOrLayer)]\n\tpub fn set_to_node_or_layer(&self, id: u64, is_layer: bool) {\n\t\tself.dispatch(DocumentMessage::SetToNodeOrLayer { node_id: NodeId(id), is_layer });\n\t}\n\n\t/// Set the name of an import or export\n\t#[wasm_bindgen(js_name = setImportName)]\n\tpub fn set_import_name(&self, index: usize, name: String) {\n\t\tlet message = NodeGraphMessage::SetImportExportName {\n\t\t\tname,\n\t\t\tindex: ImportOrExport::Import(index),\n\t\t};\n\t\tself.dispatch(message);\n\t}\n\n\t/// Set the name of an export\n\t#[wasm_bindgen(js_name = setExportName)]\n\tpub fn set_export_name(&self, index: usize, name: String) {\n\t\tlet message = NodeGraphMessage::SetImportExportName {\n\t\t\tname,\n\t\t\tindex: ImportOrExport::Export(index),\n\t\t};\n\t\tself.dispatch(message);\n\t}\n}\n\n// ============================================================================\n\n#[wasm_bindgen(js_name = evaluateMathExpression)]\npub fn evaluate_math_expression(expression: &str) -> Option<f64> {\n\tlet value = math_parser::evaluate(expression)\n\t\t.inspect_err(|err| error!(\"Math parser error on \\\"{expression}\\\": {err}\"))\n\t\t.ok()?\n\t\t.0\n\t\t.inspect_err(|err| error!(\"Math evaluate error on \\\"{expression}\\\": {err} \"))\n\t\t.ok()?;\n\tlet Some(real) = value.as_real() else {\n\t\terror!(\"{value} was not a real; skipping.\");\n\t\treturn None;\n\t};\n\tSome(real)\n}\n\n#[wasm_bindgen(js_name = sampleInterpolatedGradient)]\npub fn sample_interpolated_gradient(position: Vec<f64>, midpoint: Vec<f64>, color: Vec<JsValue>, omit_alpha: bool) -> String {\n\tlet color = color.into_iter().filter_map(|c| serde_wasm_bindgen::from_value(c).ok()).collect();\n\tGradientStops { position, midpoint, color }\n\t\t.interpolated_samples()\n\t\t.into_iter()\n\t\t.map(|(position, color, _)| {\n\t\t\tlet hex = if omit_alpha { color.to_rgb_hex_srgb_from_gamma() } else { color.to_rgba_hex_srgb_from_gamma() };\n\t\t\tlet percent = ((position * 100.) * 1e2).round() / 1e2;\n\t\t\tformat!(\"#{hex} {percent}%\")\n\t\t})\n\t\t.collect::<Vec<_>>()\n\t\t.join(\", \")\n}\n\n#[wasm_bindgen(js_name = evaluateGradientAtPosition)]\npub fn evaluate_gradient_at_position(t: f64, position: Vec<f64>, midpoint: Vec<f64>, color: Vec<JsValue>) -> JsValue {\n\tlet color = color.into_iter().filter_map(|c| serde_wasm_bindgen::from_value(c).ok()).collect();\n\tlet color = GradientStops { position, midpoint, color }.evaluate(t);\n\n\tserde_wasm_bindgen::to_value(&color).unwrap()\n}\n\n/// Helper function for calling JS's `requestAnimationFrame` with the given closure\nfn request_animation_frame(f: &Closure<dyn FnMut(f64)>) {\n\tweb_sys::window()\n\t\t.expect(\"No global `window` exists\")\n\t\t.request_animation_frame(f.as_ref().unchecked_ref())\n\t\t.expect(\"Failed to call `requestAnimationFrame`\");\n}\n\n/// Helper function for calling JS's `setTimeout` with the given closure and delay\nfn set_timeout(f: &Closure<dyn FnMut()>, delay: Duration) {\n\tlet delay = delay.clamp(Duration::ZERO, Duration::from_millis(i32::MAX as u64)).as_millis() as i32;\n\tweb_sys::window()\n\t\t.expect(\"No global `window` exists\")\n\t\t.set_timeout_with_callback_and_timeout_and_arguments_0(f.as_ref().unchecked_ref(), delay)\n\t\t.expect(\"Failed to call `setTimeout`\");\n}\n\n/// Provides access to the `Editor` by calling the given closure with it as an argument.\n#[cfg(not(feature = \"native\"))]\nfn editor<T: Default>(callback: impl FnOnce(&mut editor::application::Editor) -> T) -> T {\n\tEDITOR.with(|editor| {\n\t\tlet mut guard = editor.try_lock();\n\t\tlet Ok(Some(editor)) = guard.as_deref_mut() else {\n\t\t\tlog::error!(\"Failed to borrow editor\");\n\t\t\treturn T::default();\n\t\t};\n\n\t\tcallback(editor)\n\t})\n}\n\n/// Provides access to the `Editor` and its `EditorHandle` by calling the given closure with them as arguments.\n#[cfg(not(feature = \"native\"))]\npub(crate) fn editor_and_handle(callback: impl FnOnce(&mut Editor, &mut EditorHandle)) {\n\thandle(|editor_handle| {\n\t\teditor(|editor| {\n\t\t\t// Call the closure with the editor and its handle\n\t\t\tcallback(editor, editor_handle);\n\t\t})\n\t});\n}\n/// Provides access to the `EditorHandle` by calling the given closure with them as arguments.\npub(crate) fn handle(callback: impl FnOnce(&mut EditorHandle)) {\n\tEDITOR_HANDLE.with(|editor_handle| {\n\t\tlet mut guard = editor_handle.try_lock();\n\t\tlet Ok(Some(editor_handle)) = guard.as_deref_mut() else {\n\t\t\tlog::error!(\"Failed to borrow editor handle\");\n\t\t\treturn;\n\t\t};\n\n\t\t// Call the closure with the editor and its handle\n\t\tcallback(editor_handle);\n\t});\n}\n\n#[cfg(not(feature = \"native\"))]\nasync fn poll_node_graph_evaluation() {\n\t// Process no further messages after a crash to avoid spamming the console\n\tif EDITOR_HAS_CRASHED.load(Ordering::SeqCst) {\n\t\treturn;\n\t}\n\n\tif !editor::node_graph_executor::run_node_graph().await.0 {\n\t\treturn;\n\t}\n\n\teditor_and_handle(|editor, handle| {\n\t\tlet mut messages = VecDeque::new();\n\t\tif let Err(e) = editor.poll_node_graph_evaluation(&mut messages) {\n\t\t\t// TODO: This is a hacky way to suppress the error, but it shouldn't be generated in the first place\n\t\t\tif e != \"No active document\" {\n\t\t\t\terror!(\"Error evaluating node graph:\\n{e}\");\n\t\t\t}\n\t\t}\n\n\t\t// Clear the error display if there are no more errors\n\t\tif !messages.is_empty() {\n\t\t\tcrate::NODE_GRAPH_ERROR_DISPLAYED.store(false, Ordering::SeqCst);\n\t\t}\n\n\t\t// Batch responses to pool frontend updates\n\t\tlet batched = Message::Batched {\n\t\t\tmessages: messages.into_iter().collect(),\n\t\t};\n\t\t// Send each `FrontendMessage` to the JavaScript frontend\n\t\tfor response in editor.handle_message(batched) {\n\t\t\thandle.send_frontend_message_to_js(response);\n\t\t}\n\n\t\t// If the editor cannot be borrowed then it has encountered a panic - we should just ignore new dispatches\n\t});\n}\n\nfn auto_save_all_documents() {\n\t// Process no further messages after a crash to avoid spamming the console\n\tif EDITOR_HAS_CRASHED.load(Ordering::SeqCst) {\n\t\treturn;\n\t}\n\n\thandle(|handle| {\n\t\thandle.dispatch(PortfolioMessage::AutoSaveAllDocuments);\n\t});\n}\n\nfn render_image_data_to_canvases(image_data: &[(u64, Image<Color>)]) {\n\tlet window = match window() {\n\t\tSome(window) => window,\n\t\tNone => {\n\t\t\terror!(\"Cannot render canvas: window object not found\");\n\t\t\treturn;\n\t\t}\n\t};\n\tlet document = window.document().expect(\"window should have a document\");\n\tlet window_obj = Object::from(window);\n\tlet image_canvases_key = JsValue::from_str(\"imageCanvases\");\n\n\tlet canvases_obj = match Reflect::get(&window_obj, &image_canvases_key) {\n\t\tOk(obj) if !obj.is_undefined() && !obj.is_null() => obj,\n\t\t_ => {\n\t\t\tlet new_obj = Object::new();\n\t\t\tif Reflect::set(&window_obj, &image_canvases_key, &new_obj).is_err() {\n\t\t\t\terror!(\"Failed to create and set imageCanvases object on window\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tnew_obj.into()\n\t\t}\n\t};\n\tlet canvases_obj = Object::from(canvases_obj);\n\n\tfor (placeholder_id, image) in image_data.iter() {\n\t\tlet canvas_name = placeholder_id.to_string();\n\t\tlet js_key = JsValue::from_str(&canvas_name);\n\n\t\tif Reflect::has(&canvases_obj, &js_key).unwrap_or(false) || image.width == 0 || image.height == 0 {\n\t\t\tcontinue;\n\t\t}\n\n\t\tlet canvas: HtmlCanvasElement = document\n\t\t\t.create_element(\"canvas\")\n\t\t\t.expect(\"Failed to create canvas element\")\n\t\t\t.dyn_into::<HtmlCanvasElement>()\n\t\t\t.expect(\"Failed to cast element to HtmlCanvasElement\");\n\n\t\tcanvas.set_width(image.width);\n\t\tcanvas.set_height(image.height);\n\n\t\tlet context: CanvasRenderingContext2d = canvas\n\t\t\t.get_context(\"2d\")\n\t\t\t.expect(\"Failed to get 2d context\")\n\t\t\t.expect(\"2d context was not found\")\n\t\t\t.dyn_into::<CanvasRenderingContext2d>()\n\t\t\t.expect(\"Failed to cast context to CanvasRenderingContext2d\");\n\t\tlet u8_data: Vec<u8> = image.data.iter().flat_map(|color| color.to_rgba8_srgb()).collect();\n\t\tlet clamped_u8_data = wasm_bindgen::Clamped(&u8_data[..]);\n\t\tmatch ImageData::new_with_u8_clamped_array_and_sh(clamped_u8_data, image.width, image.height) {\n\t\t\tOk(image_data_obj) => {\n\t\t\t\tif context.put_image_data(&image_data_obj, 0., 0.).is_err() {\n\t\t\t\t\terror!(\"Failed to put image data on canvas for id: {placeholder_id}\");\n\t\t\t\t}\n\t\t\t}\n\t\t\tErr(e) => {\n\t\t\t\terror!(\"Failed to create ImageData for id: {placeholder_id}: {e:?}\");\n\t\t\t}\n\t\t}\n\n\t\tlet js_value = JsValue::from(canvas);\n\n\t\tif Reflect::set(&canvases_obj, &js_key, &js_value).is_err() {\n\t\t\terror!(\"Failed to set canvas '{canvas_name}' on imageCanvases object\");\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "frontend/wasm/src/helpers.rs",
    "content": "use editor::messages::input_mapper::utility_types::input_keyboard::Key;\n\n/// Translate a keyboard key from its JS name to its Rust `Key` enum\npub fn translate_key(name: &str) -> Key {\n\tuse Key::*;\n\n\ttrace!(\"Key event received: {name}\");\n\n\tmatch name {\n\t\t// Writing system keys\n\t\t\"Digit0\" | \"Numpad0\" => Digit0,\n\t\t\"Digit1\" | \"Numpad1\" => Digit1,\n\t\t\"Digit2\" | \"Numpad2\" => Digit2,\n\t\t\"Digit3\" | \"Numpad3\" => Digit3,\n\t\t\"Digit4\" | \"Numpad4\" => Digit4,\n\t\t\"Digit5\" | \"Numpad5\" => Digit5,\n\t\t\"Digit6\" | \"Numpad6\" => Digit6,\n\t\t\"Digit7\" | \"Numpad7\" => Digit7,\n\t\t\"Digit8\" | \"Numpad8\" => Digit8,\n\t\t\"Digit9\" | \"Numpad9\" => Digit9,\n\t\t//\n\t\t\"KeyA\" => KeyA,\n\t\t\"KeyB\" => KeyB,\n\t\t\"KeyC\" => KeyC,\n\t\t\"KeyD\" => KeyD,\n\t\t\"KeyE\" => KeyE,\n\t\t\"KeyF\" => KeyF,\n\t\t\"KeyG\" => KeyG,\n\t\t\"KeyH\" => KeyH,\n\t\t\"KeyI\" => KeyI,\n\t\t\"KeyJ\" => KeyJ,\n\t\t\"KeyK\" => KeyK,\n\t\t\"KeyL\" => KeyL,\n\t\t\"KeyM\" => KeyM,\n\t\t\"KeyN\" => KeyN,\n\t\t\"KeyO\" => KeyO,\n\t\t\"KeyP\" => KeyP,\n\t\t\"KeyQ\" => KeyQ,\n\t\t\"KeyR\" => KeyR,\n\t\t\"KeyS\" => KeyS,\n\t\t\"KeyT\" => KeyT,\n\t\t\"KeyU\" => KeyU,\n\t\t\"KeyV\" => KeyV,\n\t\t\"KeyW\" => KeyW,\n\t\t\"KeyX\" => KeyX,\n\t\t\"KeyY\" => KeyY,\n\t\t\"KeyZ\" => KeyZ,\n\t\t//\n\t\t\"Backquote\" => Backquote,\n\t\t\"Backslash\" => Backslash,\n\t\t\"BracketLeft\" => BracketLeft,\n\t\t\"BracketRight\" => BracketRight,\n\t\t\"Comma\" | \"NumpadComma\" => Comma,\n\t\t\"Equal\" | \"NumpadEqual\" => Equal,\n\t\t\"Minus\" | \"NumpadSubtract\" => Minus,\n\t\t\"Period\" | \"NumpadDecimal\" => Period,\n\t\t\"Quote\" => Quote,\n\t\t\"Semicolon\" => Semicolon,\n\t\t\"Slash\" | \"NumpadDivide\" => Slash,\n\n\t\t// Functional keys\n\t\t\"AltLeft\" | \"AltRight\" | \"AltGraph\" => Alt,\n\t\t\"MetaLeft\" | \"MetaRight\" => Meta,\n\t\t\"ShiftLeft\" | \"ShiftRight\" => Shift,\n\t\t\"ControlLeft\" | \"ControlRight\" => Control,\n\t\t\"Backspace\" | \"NumpadBackspace\" => Backspace,\n\t\t\"CapsLock\" => CapsLock,\n\t\t\"ContextMenu\" => ContextMenu,\n\t\t\"Enter\" | \"NumpadEnter\" => Enter,\n\t\t\"Space\" => Space,\n\t\t\"Tab\" => Tab,\n\n\t\t// Control pad keys\n\t\t\"Delete\" => Delete,\n\t\t\"End\" => End,\n\t\t\"Help\" => Help,\n\t\t\"Home\" => Home,\n\t\t\"Insert\" => Insert,\n\t\t\"PageDown\" => PageDown,\n\t\t\"PageUp\" => PageUp,\n\n\t\t// Arrow pad keys\n\t\t\"ArrowDown\" => ArrowDown,\n\t\t\"ArrowLeft\" => ArrowLeft,\n\t\t\"ArrowRight\" => ArrowRight,\n\t\t\"ArrowUp\" => ArrowUp,\n\n\t\t// Numpad keys\n\t\t// \"Numpad0\" => KeyNumpad0,\n\t\t// \"Numpad1\" => KeyNumpad1,\n\t\t// \"Numpad2\" => KeyNumpad2,\n\t\t// \"Numpad3\" => KeyNumpad3,\n\t\t// \"Numpad4\" => KeyNumpad4,\n\t\t// \"Numpad5\" => KeyNumpad5,\n\t\t// \"Numpad6\" => KeyNumpad6,\n\t\t// \"Numpad7\" => KeyNumpad7,\n\t\t// \"Numpad8\" => KeyNumpad8,\n\t\t// \"Numpad9\" => KeyNumpad9,\n\t\t\"NumLock\" => NumLock,\n\t\t\"NumpadAdd\" => NumpadAdd,\n\t\t// \"NumpadBackspace\" => KeyNumpadBackspace,\n\t\t// \"NumpadClear\" => NumpadClear,\n\t\t// \"NumpadClearEntry\" => NumpadClearEntry,\n\t\t// \"NumpadComma\" => KeyNumpadComma,\n\t\t// \"NumpadDecimal\" => KeyNumpadDecimal,\n\t\t// \"NumpadDivide\" => KeyNumpadDivide,\n\t\t// \"NumpadEnter\" => KeyNumpadEnter,\n\t\t// \"NumpadEqual\" => KeyNumpadEqual,\n\t\t\"NumpadHash\" => NumpadHash,\n\t\t// \"NumpadMemoryAdd\" => NumpadMemoryAdd,\n\t\t// \"NumpadMemoryClear\" => NumpadMemoryClear,\n\t\t// \"NumpadMemoryRecall\" => NumpadMemoryRecall,\n\t\t// \"NumpadMemoryStore\" => NumpadMemoryStore,\n\t\t// \"NumpadMemorySubtract\" => NumpadMemorySubtract,\n\t\t\"NumpadMultiply\" | \"NumpadStar\" => NumpadMultiply,\n\t\t\"NumpadParenLeft\" => NumpadParenLeft,\n\t\t\"NumpadParenRight\" => NumpadParenRight,\n\t\t// \"NumpadStar\" => NumpadStar,\n\t\t// \"NumpadSubtract\" => KeyNumpadSubtract,\n\n\t\t// Function keys\n\t\t\"Escape\" => Escape,\n\t\t\"F1\" => F1,\n\t\t\"F2\" => F2,\n\t\t\"F3\" => F3,\n\t\t\"F4\" => F4,\n\t\t\"F5\" => F5,\n\t\t\"F6\" => F6,\n\t\t\"F7\" => F7,\n\t\t\"F8\" => F8,\n\t\t\"F9\" => F9,\n\t\t\"F10\" => F10,\n\t\t\"F11\" => F11,\n\t\t\"F12\" => F12,\n\t\t\"F13\" => F13,\n\t\t\"F14\" => F14,\n\t\t\"F15\" => F15,\n\t\t\"F16\" => F16,\n\t\t\"F17\" => F17,\n\t\t\"F18\" => F18,\n\t\t\"F19\" => F19,\n\t\t\"F20\" => F20,\n\t\t\"F21\" => F21,\n\t\t\"F22\" => F22,\n\t\t\"F23\" => F23,\n\t\t\"F24\" => F24,\n\t\t\"Fn\" => Fn,\n\t\t\"FnLock\" => FnLock,\n\t\t\"PrintScreen\" => PrintScreen,\n\t\t\"ScrollLock\" => ScrollLock,\n\t\t\"Pause\" => Pause,\n\n\t\t// Unidentified keys\n\t\t_ => Unidentified,\n\t}\n}\n"
  },
  {
    "path": "frontend/wasm/src/lib.rs",
    "content": "#![doc = include_str!(\"../README.md\")]\n\n// `macro_use` puts the log macros (`error!`, `warn!`, `debug!`, `info!` and `trace!`) in scope for the crate\n#[macro_use]\nextern crate log;\n\npub mod editor_api;\npub mod helpers;\npub mod native_communication;\n\nuse editor::messages::prelude::*;\nuse std::panic;\nuse std::sync::Mutex;\nuse std::sync::atomic::{AtomicBool, Ordering};\nuse wasm_bindgen::prelude::*;\n\n// Set up the persistent editor backend state\npub static EDITOR_HAS_CRASHED: AtomicBool = AtomicBool::new(false);\npub static FRONTEND_READY: AtomicBool = AtomicBool::new(false);\npub static NODE_GRAPH_ERROR_DISPLAYED: AtomicBool = AtomicBool::new(false);\npub static LOGGER: WasmLog = WasmLog;\n\nthread_local! {\n\t#[cfg(not(feature = \"native\"))]\n\tpub static EDITOR: Mutex<Option<editor::application::Editor>> = const { Mutex::new(None) };\n\tpub static MESSAGE_BUFFER: std::cell::RefCell<Vec<Message>> = const { std::cell::RefCell::new(Vec::new()) };\n\tpub static EDITOR_HANDLE: Mutex<Option<editor_api::EditorHandle>> = const { Mutex::new(None) };\n\tpub static PANIC_DIALOG_MESSAGE_CALLBACK: std::cell::RefCell<Option<js_sys::Function>> = const { std::cell::RefCell::new(None) };\n}\n\n/// Initialize the backend\n#[wasm_bindgen(start)]\npub fn init_graphite() {\n\t// Set up the panic hook\n\tpanic::set_hook(Box::new(panic_hook));\n\n\t// Set up the logger with a default level of debug\n\tlog::set_logger(&LOGGER).expect(\"Failed to set logger\");\n\tlog::set_max_level(log::LevelFilter::Debug);\n}\n\n/// When a panic occurs, notify the user and log the error to the JS console before the backend dies\npub fn panic_hook(info: &panic::PanicHookInfo) {\n\tlet info = info.to_string();\n\tlet backtrace = Error::new(\"stack\").stack().to_string();\n\tif backtrace.contains(\"DynAnyNode\") {\n\t\tlog::error!(\"Node graph evaluation panicked {info}\");\n\n\t\t// When the graph panics, the node runtime lock may not be released properly\n\t\tif editor::node_graph_executor::NODE_RUNTIME.try_lock().is_none() {\n\t\t\tunsafe { editor::node_graph_executor::NODE_RUNTIME.force_unlock() };\n\t\t}\n\n\t\tif !NODE_GRAPH_ERROR_DISPLAYED.load(Ordering::SeqCst) {\n\t\t\tNODE_GRAPH_ERROR_DISPLAYED.store(true, Ordering::SeqCst);\n\t\t\teditor_api::handle(|handle| {\n\t\t\t\tlet error = r#\"\n\t\t\t\t<rect x=\"50%\" y=\"50%\" width=\"600\" height=\"100\" transform=\"translate(-300 -50)\" rx=\"4\" fill=\"var(--color-error-red)\" />\n\t\t\t\t<text x=\"50%\" y=\"50%\" dominant-baseline=\"middle\" text-anchor=\"middle\" font-size=\"18\" fill=\"var(--color-2-mildblack)\">\n\t\t\t\t\t<tspan x=\"50%\" dy=\"-24\" font-weight=\"bold\">The document crashed while being rendered in its current state.</tspan>\n\t\t\t\t\t<tspan x=\"50%\" dy=\"24\">The editor is now unstable! Undo your last action to restore the artwork,</tspan>\n\t\t\t\t\t<tspan x=\"50%\" dy=\"24\">then save your document and restart the editor before continuing work.</tspan>\n\t\t\t\t/text>\"#\n\t\t\t\t// It's a mystery why the `/text>` tag above needs to be missing its `<`, but when it exists it prints the `<` character in the text. However this works with it removed.\n\t\t\t\t.to_string();\n\t\t\t\thandle.send_frontend_message_to_js_rust_proxy(FrontendMessage::UpdateDocumentArtwork { svg: error });\n\t\t\t});\n\t\t}\n\n\t\treturn;\n\t} else {\n\t\tEDITOR_HAS_CRASHED.store(true, Ordering::SeqCst);\n\t}\n\n\tlog::error!(\"{info}\");\n\n\t// Prefer using the raw JS callback to avoid mutex lock contention inside the panic hook.\n\tif let Err(info) = send_panic_dialog_via_callback(info) {\n\t\tsend_panic_dialog_deferred(info);\n\t}\n}\n\nfn send_panic_dialog_via_callback(panic_info: String) -> Result<(), String> {\n\tlet message = FrontendMessage::DisplayDialogPanic { panic_info };\n\tlet message_type = message.to_discriminant().local_name();\n\tlet Ok(message_data) = serde_wasm_bindgen::to_value(&message) else {\n\t\tlog::error!(\"Failed to serialize crash dialog panic message\");\n\t\tlet FrontendMessage::DisplayDialogPanic { panic_info } = message else {\n\t\t\tunreachable!(\"Message variant changed unexpectedly\")\n\t\t};\n\t\treturn Err(panic_info);\n\t};\n\n\tPANIC_DIALOG_MESSAGE_CALLBACK.with(|callback| {\n\t\tlet callback_ref = callback.borrow();\n\t\tlet Some(callback) = callback_ref.as_ref() else {\n\t\t\tlet FrontendMessage::DisplayDialogPanic { panic_info } = message else {\n\t\t\t\tunreachable!(\"Message variant changed unexpectedly\")\n\t\t\t};\n\t\t\treturn Err(panic_info);\n\t\t};\n\n\t\tif let Err(error) = callback.call2(&JsValue::null(), &JsValue::from(message_type), &message_data) {\n\t\t\tlog::error!(\"Failed to send crash dialog panic message to JS: {:?}\", error);\n\t\t\tlet FrontendMessage::DisplayDialogPanic { panic_info } = message else {\n\t\t\t\tunreachable!(\"Message variant changed unexpectedly\")\n\t\t\t};\n\t\t\treturn Err(panic_info);\n\t\t}\n\n\t\tOk(())\n\t})\n}\n\n#[cfg(not(feature = \"native\"))]\nfn send_panic_dialog_deferred(panic_info: String) {\n\tlet callback = Closure::once_into_js(move || {\n\t\tif send_panic_dialog_via_callback(panic_info).is_err() {\n\t\t\tlog::error!(\"Failed to send crash dialog after panic because the editor handle is unavailable\");\n\t\t}\n\t});\n\n\tlet Some(window) = web_sys::window() else {\n\t\tlog::error!(\"Failed to schedule crash dialog after panic because no window exists\");\n\t\treturn;\n\t};\n\n\tif window.set_timeout_with_callback_and_timeout_and_arguments_0(callback.unchecked_ref(), 0).is_err() {\n\t\tlog::error!(\"Failed to schedule crash dialog after panic with setTimeout\");\n\t}\n}\n\n#[cfg(feature = \"native\")]\nfn send_panic_dialog_deferred(_panic_info: String) {\n\t// Native builds do not use `setTimeout`, so just log the failure in the caller's context.\n}\n\n#[wasm_bindgen]\nextern \"C\" {\n\t/// The JavaScript `Error` type\n\t#[derive(Clone, Debug)]\n\tpub type Error;\n\n\t#[wasm_bindgen(constructor)]\n\tpub fn new(msg: &str) -> Error;\n\n\t#[wasm_bindgen(structural, method, getter)]\n\tfn stack(error: &Error) -> String;\n}\n\n/// Logging to the JS console\n#[wasm_bindgen]\nextern \"C\" {\n\t#[wasm_bindgen(js_namespace = console)]\n\tfn log(msg: &str, format: &str);\n\t#[wasm_bindgen(js_namespace = console)]\n\tfn info(msg: &str, format: &str);\n\t#[wasm_bindgen(js_namespace = console)]\n\tfn warn(msg: &str, format: &str);\n\t#[wasm_bindgen(js_namespace = console)]\n\tfn error(msg: &str, format: &str);\n\t#[wasm_bindgen(js_namespace = console)]\n\tfn trace(msg: &str, format: &str);\n}\n\n#[derive(Default)]\npub struct WasmLog;\n\nimpl log::Log for WasmLog {\n\t#[inline]\n\tfn enabled(&self, metadata: &log::Metadata) -> bool {\n\t\tmetadata.level() <= log::max_level()\n\t}\n\n\tfn log(&self, record: &log::Record) {\n\t\tif !self.enabled(record.metadata()) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet (log, name, color): (fn(&str, &str), &str, &str) = match record.level() {\n\t\t\tlog::Level::Trace => (log, \"trace\", \"color:plum\"),\n\t\t\tlog::Level::Debug => (log, \"debug\", \"color:cyan\"),\n\t\t\tlog::Level::Warn => (warn, \"warn\", \"color:goldenrod\"),\n\t\t\tlog::Level::Info => (info, \"info\", \"color:mediumseagreen\"),\n\t\t\tlog::Level::Error => (error, \"error\", \"color:red\"),\n\t\t};\n\n\t\t// The %c is replaced by the message color\n\t\tif record.level() == log::Level::Info {\n\t\t\t// We don't print the file name and line number for info-level logs because it's used for printing the message system logs\n\t\t\tlog(&format!(\"%c{}\\t{}\", name, record.args()), color);\n\t\t} else {\n\t\t\tlet file = record.file().unwrap_or_else(|| record.target());\n\t\t\tlet line = record.line().map_or_else(|| \"[Unknown]\".to_string(), |line| line.to_string());\n\t\t\tlet args = record.args();\n\n\t\t\tlog(&format!(\"%c{name}\\t{file}:{line}\\n{args}\"), color);\n\t\t}\n\t}\n\n\tfn flush(&self) {}\n}\n"
  },
  {
    "path": "frontend/wasm/src/native_communication.rs",
    "content": "use editor::messages::prelude::FrontendMessage;\nuse js_sys::{ArrayBuffer, Uint8Array};\nuse wasm_bindgen::prelude::*;\n\nuse crate::editor_api::{self, EditorHandle};\n\n#[wasm_bindgen(js_name = \"receiveNativeMessage\")]\npub fn receive_native_message(buffer: ArrayBuffer) {\n\tlet buffer = Uint8Array::new(buffer.as_ref()).to_vec();\n\tmatch ron::from_str::<Vec<FrontendMessage>>(str::from_utf8(buffer.as_slice()).unwrap()) {\n\t\tOk(messages) => {\n\t\t\tlet callback = move |handle: &mut EditorHandle| {\n\t\t\t\tfor message in messages {\n\t\t\t\t\thandle.send_frontend_message_to_js_rust_proxy(message);\n\t\t\t\t}\n\t\t\t};\n\t\t\teditor_api::handle(callback);\n\t\t}\n\t\tErr(e) => log::error!(\"Failed to deserialize frontend messages: {e:?}\"),\n\t}\n}\n\npub fn initialize_native_communication() {\n\tlet global = js_sys::global();\n\n\t// Get the function by name\n\tlet func = js_sys::Reflect::get(&global, &JsValue::from_str(\"initializeNativeCommunication\")).expect(\"Function not found\");\n\tlet func = func.dyn_into::<js_sys::Function>().expect(\"Not a function\");\n\n\t// Call it\n\tfunc.call0(&JsValue::NULL).expect(\"Function call failed\");\n}\n\npub fn send_message_to_cef(message: String) {\n\tlet global = js_sys::global();\n\n\t// Get the function by name\n\tlet func = js_sys::Reflect::get(&global, &JsValue::from_str(\"sendNativeMessage\")).expect(\"Function not found\");\n\n\tlet func = func.dyn_into::<js_sys::Function>().expect(\"Not a function\");\n\tlet array = Uint8Array::from(message.as_bytes());\n\tlet buffer = array.buffer();\n\n\t// Call it with argument\n\tfunc.call1(&JsValue::NULL, &JsValue::from(buffer)).expect(\"Function call failed\");\n}\n"
  },
  {
    "path": "libraries/dyn-any/Cargo.toml",
    "content": "[package]\nname = \"dyn-any\"\nversion = \"0.3.1\"\nrust-version = \"1.85\"\nedition = \"2024\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\ndescription = \"An Any trait that works for arbitrary lifetimes\"\nlicense = \"MIT OR Apache-2.0\"\nreadme = \"./README.md\"\nhomepage = \"https://crates.io/crates/dyn-any\"\nrepository = \"https://github.com/GraphiteEditor/Graphite/tree/master/libraries/dyn-any\"\ndocumentation = \"https://docs.rs/dyn-any\"\n\n[features]\ndefault = [\"std\", \"large-atomics\"]\nstd = [\"alloc\", \"rc\", \"glam/default\"]\nlarge-atomics = []\nalloc = []\nderive = [\"dyn-any-derive\"]\nlog-bad-types = []\n# Opt into impls for Rc<T> and Arc<T>.\nrc = []\n# Opt into impls for some glam types\nglam = [\"dep:glam\"]\n\n[dependencies]\n# Optional local dependencies\ndyn-any-derive = { path = \"derive\", optional = true }\n\n# Optional dependencies\nglam = { version = \"0.29\", optional = true, default-features = false }\nreqwest = { version = \"0.13\", optional = true, default-features = false }\n\n[package.metadata.docs.rs]\nall-features = true\n"
  },
  {
    "path": "libraries/dyn-any/LICENSE-APACHE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "libraries/dyn-any/LICENSE-MIT",
    "content": "Permission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "libraries/dyn-any/README.md",
    "content": "# DynAny Trait\n\n\nA `Any`-like trait and derive macros for non `'static` lifetimes.\n"
  },
  {
    "path": "libraries/dyn-any/derive/Cargo.toml",
    "content": "[package]\nname = \"dyn-any-derive\"\nversion = \"0.3.0\"\nedition = \"2024\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\n\ndescription = \"#[derive(DynAny)]\"\ndocumentation = \"https://docs.rs/dyn-any-derive\"\nrepository = \"https://github.com/GraphiteEditor/Graphite/tree/master/libraries/dyn-any/derive\"\nlicense = \"MIT OR Apache-2.0\"\nreadme = \"../README.md\"\n\n[lib]\nproc-macro = true\n\n[dependencies]\n# Workspace dependencies\nproc-macro2 = { workspace = true }\nquote = { workspace = true }\nsyn = { workspace = true }\n\n[dev-dependencies]\n# Local dependencies\ndyn-any = { path = \"..\", features = [\"derive\"] }\n"
  },
  {
    "path": "libraries/dyn-any/derive/src/lib.rs",
    "content": "#![doc(html_root_url = \"http://docs.rs/dyn-any-derive/0.1.0\")]\n\nextern crate proc_macro;\n\nuse proc_macro::TokenStream;\nuse proc_macro2::Span;\nuse quote::quote;\nuse syn::{DeriveInput, GenericParam, Lifetime, LifetimeParam, TypeParamBound, parse_macro_input};\n\n/// Derives an implementation for the [`DynAny`] trait.\n///\n/// # Note\n///\n/// Currently only works with `struct` inputs.\n///\n/// # Example\n///\n/// ## Struct\n///\n/// ```\n/// # use dyn_any::{DynAny, StaticType};\n/// #[derive(DynAny)]\n/// pub struct Color<'a, 'b> {\n///     r: &'a u8,\n///     g: &'b u8,\n///     b: &'a u8,\n/// }\n///\n///\n/// // Generated Impl\n///\n/// // impl<'dyn_any> StaticType for Color<'dyn_any, 'dyn_any> {\n/// //     type Static = Color<'static, 'static>;\n/// // }\n///\n/// ```\n#[proc_macro_derive(DynAny, attributes(dyn_any_derive))]\npub fn system_desc_derive(input: TokenStream) -> TokenStream {\n\tlet ast = parse_macro_input!(input as DeriveInput);\n\tlet struct_name = &ast.ident;\n\tlet generics = &ast.generics;\n\n\tlet static_params = replace_lifetimes(generics, \"'static\");\n\tlet dyn_params = replace_lifetimes(generics, \"'dyn_any\");\n\n\tlet old_params = &generics.params.iter().collect::<Vec<_>>();\n\tquote! {\n\t\tunsafe impl<'dyn_any, #(#old_params,)*> dyn_any::StaticType for #struct_name <#(#dyn_params,)*> {\n\t\t\ttype Static =  #struct_name <#(#static_params,)*>;\n\t\t}\n\t}\n\t.into()\n}\n\nfn replace_lifetimes(generics: &syn::Generics, replacement: &str) -> Vec<proc_macro2::TokenStream> {\n\tgenerics\n\t\t.params\n\t\t.iter()\n\t\t.map(|param| {\n\t\t\tlet param = match param {\n\t\t\t\tGenericParam::Lifetime(_) => GenericParam::Lifetime(LifetimeParam::new(Lifetime::new(replacement, Span::call_site()))),\n\t\t\t\tGenericParam::Type(t) => {\n\t\t\t\t\tlet mut t = t.clone();\n\t\t\t\t\tt.bounds.iter_mut().for_each(|bond| {\n\t\t\t\t\t\tif let TypeParamBound::Lifetime(t) = bond {\n\t\t\t\t\t\t\t*t = Lifetime::new(replacement, Span::call_site())\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\tGenericParam::Type(t.clone())\n\t\t\t\t}\n\t\t\t\tc => c.clone(),\n\t\t\t};\n\t\t\tquote! {#param}\n\t\t})\n\t\t.collect::<Vec<_>>()\n}\n"
  },
  {
    "path": "libraries/dyn-any/src/lib.rs",
    "content": "#![doc(html_root_url = \"http://docs.rs/const-default/1.0.0\")]\n#![cfg_attr(not(feature = \"std\"), no_std)]\n#![allow(clippy::missing_safety_doc)]\n#[cfg(feature = \"alloc\")]\nextern crate alloc;\n\n#[cfg(feature = \"derive\")]\npub use dyn_any_derive::DynAny;\n\n/// Implement this trait for your `dyn Trait` types for all `T: Trait`\npub trait UpcastFrom<T: ?Sized> {\n\tfn up_from(value: &T) -> &Self;\n\tfn up_from_mut(value: &mut T) -> &mut Self;\n\t#[cfg(feature = \"alloc\")]\n\tfn up_from_box(value: Box<T>) -> Box<Self>;\n}\n\n/// Use this trait to perform your upcasts on dyn traits. Make sure to require it in the supertrait!\npub trait Upcast<U: ?Sized> {\n\tfn up(&self) -> &U;\n\tfn up_mut(&mut self) -> &mut U;\n\t#[cfg(feature = \"alloc\")]\n\tfn up_box(self: Box<Self>) -> Box<U>;\n}\n\nimpl<T: ?Sized, U: ?Sized> Upcast<U> for T\nwhere\n\tU: UpcastFrom<T>,\n{\n\tfn up(&self) -> &U {\n\t\tU::up_from(self)\n\t}\n\tfn up_mut(&mut self) -> &mut U {\n\t\tU::up_from_mut(self)\n\t}\n\t#[cfg(feature = \"alloc\")]\n\tfn up_box(self: Box<Self>) -> Box<U> {\n\t\tU::up_from_box(self)\n\t}\n}\n\nuse core::any::TypeId;\n\nimpl<'a, T: DynAny<'a> + 'a> UpcastFrom<T> for dyn DynAny<'a> + 'a {\n\tfn up_from(value: &T) -> &(dyn DynAny<'a> + 'a) {\n\t\tvalue\n\t}\n\tfn up_from_mut(value: &mut T) -> &mut (dyn DynAny<'a> + 'a) {\n\t\tvalue\n\t}\n\t#[cfg(feature = \"alloc\")]\n\tfn up_from_box(value: Box<T>) -> Box<Self> {\n\t\tvalue\n\t}\n}\n\npub trait DynAny<'a>: 'a {\n\tfn type_id(&self) -> TypeId;\n\t#[cfg(feature = \"log-bad-types\")]\n\tfn type_name(&self) -> &'static str;\n\tfn reborrow_box<'short>(self: Box<Self>) -> Box<dyn DynAny<'short> + 'short>\n\twhere\n\t\t'a: 'short;\n\tfn reborrow_ref<'short>(&'a self) -> &'short (dyn DynAny<'short> + Send + Sync + 'short)\n\twhere\n\t\t'a: 'short,\n\t\tSelf: Send + Sync;\n}\n\nimpl<'a, T: StaticType + 'a> DynAny<'a> for T {\n\tfn type_id(&self) -> core::any::TypeId {\n\t\tcore::any::TypeId::of::<T::Static>()\n\t}\n\t#[cfg(feature = \"log-bad-types\")]\n\tfn type_name(&self) -> &'static str {\n\t\tcore::any::type_name::<T>()\n\t}\n\tfn reborrow_box<'short>(self: Box<Self>) -> Box<dyn DynAny<'short> + 'short>\n\twhere\n\t\t'a: 'short,\n\t{\n\t\tself\n\t}\n\n\tfn reborrow_ref<'short>(&'a self) -> &'short (dyn DynAny<'short> + Send + Sync + 'short)\n\twhere\n\t\t'a: 'short,\n\t\tSelf: Send + Sync,\n\t{\n\t\tself\n\t}\n}\npub fn downcast_ref<'a, V: StaticType + 'a>(i: &'a dyn DynAny<'a>) -> Option<&'a V> {\n\tif i.type_id() == core::any::TypeId::of::<<V as StaticType>::Static>() {\n\t\t// SAFETY: caller guarantees that T is the correct type\n\t\tlet ptr = i as *const dyn DynAny<'a> as *const V;\n\t\tSome(unsafe { &*ptr })\n\t} else {\n\t\tNone\n\t}\n}\n\n#[cfg(feature = \"alloc\")]\npub fn downcast<'a, V: StaticType + 'a>(i: Box<dyn DynAny<'a> + 'a>) -> Result<Box<V>, String> {\n\tlet type_id = DynAny::type_id(i.as_ref());\n\tif type_id == core::any::TypeId::of::<<V as StaticType>::Static>() {\n\t\t// SAFETY: caller guarantees that T is the correct type\n\t\tlet ptr = Box::into_raw(i) as *mut V;\n\t\tOk(unsafe { Box::from_raw(ptr) })\n\t} else {\n\t\tif type_id == core::any::TypeId::of::<&dyn DynAny<'static>>() {\n\t\t\tpanic!(\"downcast error: type_id == core::any::TypeId::of::<dyn DynAny<'a>>()\");\n\t\t}\n\t\t#[cfg(feature = \"log-bad-types\")]\n\t\t{\n\t\t\tErr(format!(\"Incorrect type, expected {} but found {}\", core::any::type_name::<V>(), DynAny::type_name(i.as_ref())))\n\t\t}\n\n\t\t#[cfg(not(feature = \"log-bad-types\"))]\n\t\t{\n\t\t\tErr(format!(\"Incorrect type, expected {}\", core::any::type_name::<V>()))\n\t\t}\n\t}\n}\n\npub unsafe trait StaticType {\n\ttype Static: 'static + ?Sized;\n\tfn type_id(&self) -> core::any::TypeId {\n\t\tcore::any::TypeId::of::<Self::Static>()\n\t}\n}\n\npub unsafe trait StaticTypeSized {\n\ttype Static: 'static;\n\tfn type_id(&self) -> core::any::TypeId {\n\t\tcore::any::TypeId::of::<<Self as StaticTypeSized>::Static>()\n\t}\n}\nunsafe impl<T: StaticType + Sized> StaticTypeSized for T\nwhere\n\tT::Static: Sized,\n{\n\ttype Static = <T as StaticType>::Static;\n}\npub unsafe trait StaticTypeClone {\n\ttype Static: 'static + Clone;\n\tfn type_id(&self) -> core::any::TypeId {\n\t\tcore::any::TypeId::of::<<Self as StaticTypeClone>::Static>()\n\t}\n}\nunsafe impl<T: StaticType + Clone> StaticTypeClone for T\nwhere\n\tT::Static: Clone,\n{\n\ttype Static = <T as StaticType>::Static;\n}\n\nmacro_rules! impl_type {\n\t($($id:ident$(<$($(($l:lifetime, $s:lifetime)),*|)?$($T:ident),*>)?),*) => {\n\t\t$(\n\t\tunsafe impl< $($($T:  $crate::StaticTypeSized ,)*)?> $crate::StaticType for $id $(<$($($l,)*)?$($T, )*>)?{\n\t\t\ttype Static = $id$(<$($($s,)*)?$(<$T as $crate::StaticTypeSized>::Static,)*>)?;\n\t\t}\n\t\t)*\n\t};\n}\n\n#[cfg(feature = \"alloc\")]\nunsafe impl<T: StaticTypeClone + Clone> StaticType for Cow<'_, T> {\n\ttype Static = Cow<'static, <T as StaticTypeClone>::Static>;\n}\nunsafe impl<T: StaticTypeSized> StaticType for *const [T] {\n\ttype Static = *const [<T as StaticTypeSized>::Static];\n}\nunsafe impl<T: StaticTypeSized> StaticType for *mut [T] {\n\ttype Static = *mut [<T as StaticTypeSized>::Static];\n}\nmacro_rules! impl_slice {\n\t($($id:ident),*) => {\n\t\t$(\n\t\tunsafe impl<'a, T: StaticTypeSized> StaticType for $id<'a, T> {\n\t\t\ttype Static = $id<'static, <T as StaticTypeSized>::Static>;\n\t\t}\n\t\t)*\n\t};\n}\n\nmod slice {\n\tuse super::*;\n\tuse core::slice::*;\n\n\timpl_slice!(Iter, IterMut, Chunks, ChunksMut, RChunks, RChunksMut, Windows);\n}\n\n#[cfg(feature = \"alloc\")]\nunsafe impl<T: StaticTypeSized> StaticType for Box<dyn Iterator<Item = T> + '_ + Send + Sync> {\n\ttype Static = Box<dyn Iterator<Item = <T as StaticTypeSized>::Static> + Send + Sync>;\n}\n\nunsafe impl StaticType for &str {\n\ttype Static = &'static str;\n}\nunsafe impl StaticType for () {\n\ttype Static = ();\n}\nunsafe impl<'a, T: 'a + StaticType + ?Sized> StaticType for &'a T {\n\ttype Static = &'static <T as StaticType>::Static;\n}\nunsafe impl<T: StaticTypeSized, const N: usize> StaticType for [T; N] {\n\ttype Static = [<T as StaticTypeSized>::Static; N];\n}\nunsafe impl<T: StaticTypeSized> StaticType for [T] {\n\ttype Static = [<T as StaticTypeSized>::Static];\n}\n\nunsafe impl StaticType for dyn for<'i> DynAny<'_> + '_ {\n\ttype Static = dyn DynAny<'static>;\n}\nunsafe impl StaticType for dyn for<'i> DynAny<'_> + Send + Sync + '_ {\n\ttype Static = dyn DynAny<'static> + Send + Sync;\n}\nunsafe impl StaticType for dyn for<'i> DynAny<'_> + Send + '_ {\n\ttype Static = dyn DynAny<'static> + Sync;\n}\nunsafe impl<T: StaticTypeSized> StaticType for dyn core::future::Future<Output = T> + Send + Sync + '_ {\n\ttype Static = dyn core::future::Future<Output = T::Static> + Send + Sync;\n}\nunsafe impl<T: StaticTypeSized> StaticType for dyn core::future::Future<Output = T> + Send + '_ {\n\ttype Static = dyn core::future::Future<Output = T::Static> + Send;\n}\nunsafe impl<T: StaticTypeSized> StaticType for dyn core::future::Future<Output = T> + '_ {\n\ttype Static = dyn core::future::Future<Output = T::Static>;\n}\n#[cfg(feature = \"alloc\")]\npub trait IntoDynAny<'n>: Sized + StaticType + 'n {\n\tfn into_dyn(self) -> Box<dyn DynAny<'n> + 'n> {\n\t\tBox::new(self)\n\t}\n}\n#[cfg(feature = \"alloc\")]\nimpl<'n, T: StaticType + 'n> IntoDynAny<'n> for T {}\n\n#[cfg(feature = \"alloc\")]\nimpl From<()> for Box<dyn DynAny<'static>> {\n\tfn from(_: ()) -> Box<dyn DynAny<'static>> {\n\t\tBox::new(())\n\t}\n}\n\n#[cfg(feature = \"alloc\")]\nuse alloc::borrow::Cow;\n#[cfg(feature = \"alloc\")]\nuse alloc::boxed::Box;\n#[cfg(feature = \"alloc\")]\nuse alloc::collections::{BTreeMap, BTreeSet, BinaryHeap, LinkedList, VecDeque};\n#[cfg(feature = \"alloc\")]\nuse alloc::string::String;\n#[cfg(feature = \"alloc\")]\nuse alloc::vec::Vec;\nuse core::cell::{Cell, RefCell, UnsafeCell};\nuse core::iter::Empty;\nuse core::marker::{PhantomData, PhantomPinned};\nuse core::mem::{ManuallyDrop, MaybeUninit};\nuse core::num::Wrapping;\nuse core::ops::Range;\nuse core::pin::Pin;\nuse core::sync::atomic::*;\nuse core::time::Duration;\n\nimpl_type!(\n\tOption<T>, Result<T, E>, Cell<T>, UnsafeCell<T>, RefCell<T>, MaybeUninit<T>,\n\t ManuallyDrop<T>, PhantomData<T>, PhantomPinned, Empty<T>, Range<T>,\n\tWrapping<T>, Pin<T>, Duration, bool, f32, f64, char,\n\tu8, AtomicU8, u16, AtomicU16, u32, AtomicU32, u64,  usize, AtomicUsize,\n\ti8, AtomicI8, i16, AtomicI16, i32, AtomicI32, i64,  isize, AtomicIsize,\n\ti128, u128, AtomicBool, AtomicPtr<T>\n);\n#[cfg(feature = \"large-atomics\")]\nimpl_type!(AtomicU64, AtomicI64);\n\n#[cfg(feature = \"alloc\")]\nimpl_type!(\n\tVec<T>, String, BTreeMap<K,V>,BTreeSet<V>, LinkedList<T>, VecDeque<T>,\n\tBinaryHeap<T>\n);\n\n#[cfg(feature = \"std\")]\nuse std::collections::{HashMap, HashSet};\n#[cfg(feature = \"std\")]\nuse std::sync::*;\n\n#[cfg(feature = \"std\")]\nimpl_type!(Once, Mutex<T>, RwLock<T>, HashSet<T>, HashMap<K, V>);\n\n#[cfg(feature = \"rc\")]\nuse std::rc::Rc;\n#[cfg(feature = \"rc\")]\nimpl_type!(Rc<T>);\n#[cfg(all(feature = \"rc\", feature = \"alloc\"))]\nuse std::sync::Arc;\n#[cfg(all(feature = \"rc\", feature = \"alloc\"))]\nunsafe impl<T: StaticType + ?Sized> StaticType for Arc<T> {\n\ttype Static = Arc<<T as StaticType>::Static>;\n}\n\n#[cfg(feature = \"glam\")]\nuse glam::*;\n#[cfg(feature = \"glam\")]\n#[rustfmt::skip]\nimpl_type!(\n\tIVec2, IVec3, IVec4, UVec2, UVec3, UVec4, BVec2, BVec3, BVec4,\n\tVec2, Vec3, Vec3A, Vec4, DVec2, DVec3, DVec4,\n\tMat2, Mat3, Mat3A, Mat4, DMat2, DMat3, DMat4,\n\tQuat, Affine2, Affine3A, DAffine2, DAffine3, DQuat\n);\n\n#[cfg(feature = \"reqwest\")]\nuse reqwest::Response;\n#[cfg(feature = \"reqwest\")]\nimpl_type!(Response);\n\n#[cfg(feature = \"alloc\")]\nunsafe impl<T: crate::StaticType + ?Sized> crate::StaticType for Box<T> {\n\ttype Static = Box<<T as crate::StaticType>::Static>;\n}\n#[test]\nfn test_tuple_of_boxes() {\n\tlet tuple = (Box::new(&1u32 as &dyn DynAny<'static>), Box::new(&2u32 as &dyn DynAny<'static>));\n\tlet dyn_any = &tuple as &dyn DynAny;\n\tassert_eq!(&1, downcast_ref::<u32>(*downcast_ref::<(Box<&dyn DynAny>, Box<&dyn DynAny>)>(dyn_any).unwrap().0).unwrap());\n\tassert_eq!(&2, downcast_ref::<u32>(*downcast_ref::<(Box<&dyn DynAny>, Box<&dyn DynAny>)>(dyn_any).unwrap().1).unwrap());\n}\n\nmacro_rules! impl_tuple {\n\t(@rec $t:ident) => { };\n\t(@rec $_:ident $($t:ident)+) => {\n\t\timpl_tuple! { @impl $($t)* }\n\t\timpl_tuple! { @rec $($t)* }\n\t};\n\t(@impl $($t:ident)*) => {\n\t\tunsafe impl< $($t: StaticTypeSized,)*> StaticType for ($($t,)*) {\n\t\t\ttype Static = ($(<$t as $crate::StaticTypeSized>::Static,)*);\n\t\t}\n\t};\n\t($($t:ident)*) => {\n\t\timpl_tuple! { @rec _t $($t)* }\n\t};\n}\n\nimpl_tuple! {\n\tA B C D E F G H I J K L\n}\n\n#[test]\nfn simple_downcast() {\n\tlet x = Box::new(3_u32) as Box<dyn DynAny>;\n\tassert_eq!(*downcast::<u32>(x).unwrap(), 3_u32);\n}\n#[test]\n#[should_panic]\nfn simple_downcast_panic() {\n\tlet x = Box::new(3_i32) as Box<dyn DynAny>;\n\tassert_eq!(*downcast::<u32>(x).expect(\"attempted to perform invalid downcast\"), 3_u32);\n}\n\n#[cfg(not(target_family = \"wasm\"))]\npub trait WasmNotSend: Send {}\n#[cfg(target_family = \"wasm\")]\npub trait WasmNotSend {}\n\n#[cfg(not(target_family = \"wasm\"))]\nimpl<T: Send> WasmNotSend for T {}\n\n#[cfg(target_family = \"wasm\")]\nimpl<T> WasmNotSend for T {}\n\n#[cfg(not(target_family = \"wasm\"))]\npub trait WasmNotSync: Sync {}\n#[cfg(target_family = \"wasm\")]\npub trait WasmNotSync {}\n\n#[cfg(not(target_family = \"wasm\"))]\nimpl<T: Sync> WasmNotSync for T {}\n\n#[cfg(target_family = \"wasm\")]\nimpl<T> WasmNotSync for T {}\n\n#[cfg(not(target_family = \"wasm\"))]\n#[cfg(feature = \"alloc\")]\npub type DynFuture<'n, T> = Pin<Box<dyn core::future::Future<Output = T> + 'n + Send>>;\n#[cfg(target_family = \"wasm\")]\n#[cfg(feature = \"alloc\")]\npub type DynFuture<'n, T> = Pin<Box<dyn core::future::Future<Output = T> + 'n>>;\n"
  },
  {
    "path": "libraries/math-parser/Cargo.toml",
    "content": "[package]\nname = \"math-parser\"\nversion = \"0.0.0\"\nrust-version = \"1.85\"\nedition = \"2024\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\ndescription = \"Parser for Graphite style mathematics expressions\"\nlicense = \"MIT OR Apache-2.0\"\n\n[dependencies]\npest = \"2.7\"\npest_derive = \"2.7\"\nthiserror = \"2.0\"\nlazy_static = \"1.5\"\nnum-complex = \"0.4\"\n\n[dev-dependencies]\ncriterion = { workspace = true }\n\n[[bench]]\nname = \"bench\"\nharness = false\n"
  },
  {
    "path": "libraries/math-parser/benches/bench.rs",
    "content": "use std::hint::black_box;\n\nuse criterion::{Criterion, criterion_group, criterion_main};\nuse math_parser::{ast, context::EvalContext};\n\nmacro_rules! generate_benchmarks {\n\t($( $input:expr_2021 ),* $(,)?) => {\n\t\tfn parsing_bench(c: &mut Criterion) {\n\t\t\t$(\n\t\t\t\tc.bench_function(concat!(\"parse \", $input), |b| {\n\t\t\t\t\tb.iter(|| {\n\t\t\t\t\t\tlet _ = black_box(ast::Node::try_parse_from_str($input)).unwrap();\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t)*\n\t\t}\n\n\t\tfn evaluation_bench(c: &mut Criterion) {\n\t\t\t$(\n\t\t\t\tlet expr = ast::Node::try_parse_from_str($input).unwrap().0;\n\t\t\t\tlet context = EvalContext::default();\n\n\t\t\t\tc.bench_function(concat!(\"eval \", $input), |b| {\n\t\t\t\t\tb.iter(|| {\n\t\t\t\t\t\tlet _ = black_box(expr.eval(&context));\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t)*\n\t\t}\n\n\t\tcriterion_group!(benches, parsing_bench, evaluation_bench);\n\t\tcriterion_main!(benches);\n\t};\n}\n\ngenerate_benchmarks! {\n\t\"(3 * (4 + sqrt(25)) - cos(pi/3) * (2^3)) + 5 * e\", // Mixed nested functions, constants, and operations\n\t\"((5 + 2 * (3 - sqrt(49)))^2) / (1 + sqrt(16)) + tau / 2\", // Complex nested expression with constants\n\t\"log(100, 10) + (5 * sin(pi/4) + sqrt(81)) / (2 * phi)\", // Logarithmic and trigonometric functions\n\t\"(sqrt(144) * 2 + 5) / (3 * (4 - sin(pi / 6))) + e^2\", // Combined square root, trigonometric, and exponential operations\n\t\"cos(2 * pi) + tan(pi / 3) * log(32, 2) - sqrt(256)\", // Multiple trigonometric and logarithmic functions\n\t\"(10 * (3 + 2) - 8 / 2)^2 + 7 * (2^4) - sqrt(225) + phi\", // Mixed arithmetic with constants\n\t\"(5^2 + 3^3) * (sqrt(81) + sqrt(64)) - tau * log(1000, 10)\", // Power and square root with constants\n\t\"((8 * sqrt(49) - 2 * e) + log(256, 2) / (2 + cos(pi))) * 1.5\", // Nested functions and constants\n\t\"(tan(pi / 4) + 5) * (3 + sqrt(36)) / (log(1024, 2) - 4)\", // Nested functions with trigonometry and logarithm\n\t\"((3 * e + 2 * sqrt(100)) - cos(tau / 4)) * log(27, 3) + phi\", // Mixed constant usage and functions\n\t\"(sqrt(100) + 5 * sin(pi / 6) - 8 / log(64, 2)) + e^(1.5)\", // Complex mix of square root, division, and exponentiation\n\t\"((sin(pi/2) + cos(0)) * (e^2 - 2 * sqrt(16))) / (log(100, 10) + pi)\", // Nested trigonometric, exponential, and logarithmic functions\n\t\"(5 * (7 + sqrt(121)) - (log(243, 3) * phi)) + 3^5 / tau\", //\n}\n"
  },
  {
    "path": "libraries/math-parser/src/ast.rs",
    "content": "use crate::value::Complex;\n\n#[derive(Debug, PartialEq, Eq)]\npub struct Unit {\n\t// Exponent of length unit (meters)\n\tpub length: i32,\n\t// Exponent of mass unit (kilograms)\n\tpub mass: i32,\n\t// Exponent of time unit (seconds)\n\tpub time: i32,\n}\n\nimpl Default for Unit {\n\tfn default() -> Self {\n\t\tSelf::BASE_UNIT\n\t}\n}\n\nimpl Unit {\n\tpub const BASE_UNIT: Unit = Unit { length: 0, mass: 0, time: 0 };\n\n\tpub const LENGTH: Unit = Unit { length: 1, mass: 0, time: 0 };\n\tpub const MASS: Unit = Unit { length: 0, mass: 1, time: 0 };\n\tpub const TIME: Unit = Unit { length: 0, mass: 0, time: 1 };\n\n\tpub const VELOCITY: Unit = Unit { length: 1, mass: 0, time: -1 };\n\tpub const ACCELERATION: Unit = Unit { length: 1, mass: 0, time: -2 };\n\n\tpub const FORCE: Unit = Unit { length: 1, mass: 1, time: -2 };\n\n\tpub fn base_unit() -> Self {\n\t\tSelf::BASE_UNIT\n\t}\n\n\tpub fn is_base(&self) -> bool {\n\t\t*self == Self::BASE_UNIT\n\t}\n}\n\n#[derive(Debug, PartialEq)]\npub enum Literal {\n\tFloat(f64),\n\tComplex(Complex),\n}\n\nimpl From<f64> for Literal {\n\tfn from(value: f64) -> Self {\n\t\tSelf::Float(value)\n\t}\n}\n\n#[derive(Debug, Clone, Copy, PartialEq)]\npub enum BinaryOp {\n\tAdd,\n\tSub,\n\tMul,\n\tDiv,\n\tPow,\n}\n\n#[derive(Debug, PartialEq, Clone, Copy)]\npub enum UnaryOp {\n\tNeg,\n\tSqrt,\n\tFac,\n}\n\n#[derive(Debug, PartialEq)]\npub enum Node {\n\tLit(Literal),\n\tVar(String),\n\tFnCall { name: String, expr: Vec<Node> },\n\tBinOp { lhs: Box<Node>, op: BinaryOp, rhs: Box<Node> },\n\tUnaryOp { expr: Box<Node>, op: UnaryOp },\n}\n"
  },
  {
    "path": "libraries/math-parser/src/constants.rs",
    "content": "use crate::value::{Number, Value};\nuse lazy_static::lazy_static;\nuse num_complex::{Complex, ComplexFloat};\nuse std::collections::HashMap;\nuse std::f64::consts::PI;\n\ntype FunctionImplementation = Box<dyn Fn(&[Value]) -> Option<Value> + Send + Sync>;\nlazy_static! {\n\tpub static ref DEFAULT_FUNCTIONS: HashMap<&'static str, FunctionImplementation> = {\n\t\tlet mut map: HashMap<&'static str, FunctionImplementation> = HashMap::new();\n\n\t\tmap.insert(\n\t\t\t\"sin\",\n\t\t\tBox::new(|values| match values {\n\t\t\t\t[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.sin()))),\n\t\t\t\t[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.sin()))),\n\t\t\t\t_ => None,\n\t\t\t}),\n\t\t);\n\n\t\tmap.insert(\n\t\t\t\"cos\",\n\t\t\tBox::new(|values| match values {\n\t\t\t\t[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.cos()))),\n\t\t\t\t[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.cos()))),\n\t\t\t\t_ => None,\n\t\t\t}),\n\t\t);\n\n\t\tmap.insert(\n\t\t\t\"tan\",\n\t\t\tBox::new(|values| match values {\n\t\t\t\t[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.tan()))),\n\t\t\t\t[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.tan()))),\n\t\t\t\t_ => None,\n\t\t\t}),\n\t\t);\n\n\t\tmap.insert(\n\t\t\t\"csc\",\n\t\t\tBox::new(|values| match values {\n\t\t\t\t[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.sin().recip()))),\n\t\t\t\t[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.sin().recip()))),\n\t\t\t\t_ => None,\n\t\t\t}),\n\t\t);\n\n\t\tmap.insert(\n\t\t\t\"sec\",\n\t\t\tBox::new(|values| match values {\n\t\t\t\t[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.cos().recip()))),\n\t\t\t\t[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.cos().recip()))),\n\t\t\t\t_ => None,\n\t\t\t}),\n\t\t);\n\n\t\tmap.insert(\n\t\t\t\"cot\",\n\t\t\tBox::new(|values| match values {\n\t\t\t\t[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.tan().recip()))),\n\t\t\t\t[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.tan().recip()))),\n\t\t\t\t_ => None,\n\t\t\t}),\n\t\t);\n\n\t\tmap.insert(\n\t\t\t\"invsin\",\n\t\t\tBox::new(|values| match values {\n\t\t\t\t[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.asin()))),\n\t\t\t\t[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.asin()))),\n\t\t\t\t_ => None,\n\t\t\t}),\n\t\t);\n\n\t\tmap.insert(\n\t\t\t\"invcos\",\n\t\t\tBox::new(|values| match values {\n\t\t\t\t[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.acos()))),\n\t\t\t\t[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.acos()))),\n\t\t\t\t_ => None,\n\t\t\t}),\n\t\t);\n\n\t\tmap.insert(\n\t\t\t\"invtan\",\n\t\t\tBox::new(|values| match values {\n\t\t\t\t[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.atan()))),\n\t\t\t\t[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.atan()))),\n\t\t\t\t_ => None,\n\t\t\t}),\n\t\t);\n\n\t\tmap.insert(\n\t\t\t\"invcsc\",\n\t\t\tBox::new(|values| match values {\n\t\t\t\t[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.recip().asin()))),\n\t\t\t\t[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.recip().asin()))),\n\t\t\t\t_ => None,\n\t\t\t}),\n\t\t);\n\n\t\tmap.insert(\n\t\t\t\"invsec\",\n\t\t\tBox::new(|values| match values {\n\t\t\t\t[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.recip().acos()))),\n\t\t\t\t[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.recip().acos()))),\n\t\t\t\t_ => None,\n\t\t\t}),\n\t\t);\n\n\t\tmap.insert(\n\t\t\t\"invcot\",\n\t\t\tBox::new(|values| match values {\n\t\t\t\t[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real((PI / 2.0 - real).atan()))),\n\t\t\t\t[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex((Complex::new(PI / 2.0, 0.0) - complex).atan()))),\n\t\t\t\t_ => None,\n\t\t\t}),\n\t\t);\n\n\t\tmap\n\t};\n}\n"
  },
  {
    "path": "libraries/math-parser/src/context.rs",
    "content": "use crate::value::Value;\nuse std::collections::HashMap;\nuse std::ops::{Deref, DerefMut};\n\n//TODO: editor integration, implement these traits for whatever is needed, maybe merge them if needed\npub trait ValueProvider {\n\tfn get_value(&self, name: &str) -> Option<Value>;\n}\n\npub trait FunctionProvider {\n\tfn run_function(&self, name: &str, args: &[Value]) -> Option<Value>;\n}\n\npub struct ValueMap(HashMap<String, Value>);\n\npub struct NothingMap;\n\nimpl ValueProvider for &ValueMap {\n\tfn get_value(&self, name: &str) -> Option<Value> {\n\t\tself.0.get(name).cloned()\n\t}\n}\n\nimpl ValueProvider for NothingMap {\n\tfn get_value(&self, _: &str) -> Option<Value> {\n\t\tNone\n\t}\n}\n\nimpl ValueProvider for ValueMap {\n\tfn get_value(&self, name: &str) -> Option<Value> {\n\t\tself.0.get(name).cloned()\n\t}\n}\n\nimpl Deref for ValueMap {\n\ttype Target = HashMap<String, Value>;\n\tfn deref(&self) -> &Self::Target {\n\t\t&self.0\n\t}\n}\nimpl DerefMut for ValueMap {\n\tfn deref_mut(&mut self) -> &mut Self::Target {\n\t\t&mut self.0\n\t}\n}\n\nimpl FunctionProvider for NothingMap {\n\tfn run_function(&self, _: &str, _: &[Value]) -> Option<Value> {\n\t\tNone\n\t}\n}\n\npub struct EvalContext<V: ValueProvider, F: FunctionProvider> {\n\tvalues: V,\n\tfunctions: F,\n}\n\nimpl Default for EvalContext<NothingMap, NothingMap> {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tvalues: NothingMap,\n\t\t\tfunctions: NothingMap,\n\t\t}\n\t}\n}\n\nimpl<V: ValueProvider, F: FunctionProvider> EvalContext<V, F> {\n\tpub fn new(values: V, functions: F) -> Self {\n\t\tSelf { values, functions }\n\t}\n\n\tpub fn get_value(&self, name: &str) -> Option<Value> {\n\t\tself.values.get_value(name)\n\t}\n\n\tpub fn run_function(&self, name: &str, args: &[Value]) -> Option<Value> {\n\t\tself.functions.run_function(name, args)\n\t}\n}\n"
  },
  {
    "path": "libraries/math-parser/src/executer.rs",
    "content": "use crate::ast::{Literal, Node};\nuse crate::constants::DEFAULT_FUNCTIONS;\nuse crate::context::{EvalContext, FunctionProvider, ValueProvider};\nuse crate::value::{Number, Value};\nuse thiserror::Error;\n\n#[derive(Debug, Error)]\npub enum EvalError {\n\t#[error(\"Missing value: {0}\")]\n\tMissingValue(String),\n\n\t#[error(\"Missing function: {0}\")]\n\tMissingFunction(String),\n\t#[error(\"Wrong type for function call\")]\n\tTypeError,\n}\n\nimpl Node {\n\tpub fn eval<V: ValueProvider, F: FunctionProvider>(&self, context: &EvalContext<V, F>) -> Result<Value, EvalError> {\n\t\tmatch self {\n\t\t\tNode::Lit(lit) => match lit {\n\t\t\t\tLiteral::Float(num) => Ok(Value::from_f64(*num)),\n\t\t\t\tLiteral::Complex(num) => Ok(Value::Number(Number::Complex(*num))),\n\t\t\t},\n\n\t\t\tNode::BinOp { lhs, op, rhs } => match (lhs.eval(context)?, rhs.eval(context)?) {\n\t\t\t\t(Value::Number(lhs), Value::Number(rhs)) => Ok(Value::Number(lhs.binary_op(*op, rhs))),\n\t\t\t},\n\t\t\tNode::UnaryOp { expr, op } => match expr.eval(context)? {\n\t\t\t\tValue::Number(num) => Ok(Value::Number(num.unary_op(*op))),\n\t\t\t},\n\t\t\tNode::Var(name) => context.get_value(name).ok_or_else(|| EvalError::MissingValue(name.clone())),\n\t\t\tNode::FnCall { name, expr } => {\n\t\t\t\tlet values = expr.iter().map(|expr| expr.eval(context)).collect::<Result<Vec<Value>, EvalError>>()?;\n\t\t\t\tif let Some(function) = DEFAULT_FUNCTIONS.get(&name.as_str()) {\n\t\t\t\t\tfunction(&values).ok_or(EvalError::TypeError)\n\t\t\t\t} else if let Some(val) = context.run_function(name, &values) {\n\t\t\t\t\tOk(val)\n\t\t\t\t} else {\n\t\t\t\t\tcontext.get_value(name).ok_or_else(|| EvalError::MissingFunction(name.to_string()))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n#[cfg(test)]\nmod tests {\n\tuse crate::ast::{BinaryOp, Literal, Node, UnaryOp};\n\tuse crate::context::{EvalContext, ValueMap};\n\tuse crate::value::Value;\n\n\tmacro_rules! eval_tests {\n\t\t($($name:ident: $expected:expr_2021 => $expr:expr_2021),* $(,)?) => {\n\t\t\t$(\n\t\t\t\t#[test]\n\t\t\t\tfn $name() {\n\t\t\t\t\tlet result = $expr.eval(&EvalContext::default()).unwrap();\n\t\t\t\t\tassert_eq!(result, $expected);\n\t\t\t\t}\n\t\t\t)*\n\t\t};\n\t}\n\n\teval_tests! {\n\t\ttest_addition: Value::from_f64(7.0) => Node::BinOp {\n\t\t\tlhs: Box::new(Node::Lit(Literal::Float(3.0))),\n\t\t\top: BinaryOp::Add,\n\t\t\trhs: Box::new(Node::Lit(Literal::Float(4.0))),\n\t\t},\n\t\ttest_subtraction: Value::from_f64(1.0) => Node::BinOp {\n\t\t\tlhs: Box::new(Node::Lit(Literal::Float(5.0))),\n\t\t\top: BinaryOp::Sub,\n\t\t\trhs: Box::new(Node::Lit(Literal::Float(4.0))),\n\t\t},\n\t\ttest_multiplication: Value::from_f64(12.0) => Node::BinOp {\n\t\t\tlhs: Box::new(Node::Lit(Literal::Float(3.0))),\n\t\t\top: BinaryOp::Mul,\n\t\t\trhs: Box::new(Node::Lit(Literal::Float(4.0))),\n\t\t},\n\t\ttest_division: Value::from_f64(2.5) => Node::BinOp {\n\t\t\tlhs: Box::new(Node::Lit(Literal::Float(5.0))),\n\t\t\top: BinaryOp::Div,\n\t\t\trhs: Box::new(Node::Lit(Literal::Float(2.0))),\n\t\t},\n\t\ttest_negation: Value::from_f64(-3.0) => Node::UnaryOp {\n\t\t\texpr: Box::new(Node::Lit(Literal::Float(3.0))),\n\t\t\top: UnaryOp::Neg,\n\t\t},\n\t\ttest_sqrt: Value::from_f64(2.0) => Node::UnaryOp {\n\t\t\texpr: Box::new(Node::Lit(Literal::Float(4.0))),\n\t\t\top: UnaryOp::Sqrt,\n\t\t},\n\t\t test_power: Value::from_f64(8.0) => Node::BinOp {\n\t\t\t lhs: Box::new(Node::Lit(Literal::Float(2.0))),\n\t\t\t op: BinaryOp::Pow,\n\t\t\t rhs: Box::new(Node::Lit(Literal::Float(3.0))),\n\t\t },\n\t}\n}\n"
  },
  {
    "path": "libraries/math-parser/src/grammer.pest",
    "content": "WHITESPACE    =  _{ \" \" | \"\\t\" }\n\n// TODO: Proper indentation and formatting\nprogram       =  _{ SOI ~ expr ~ EOI }\n\nexpr          =  { atom ~ (infix ~ atom)* }\natom          =  _{ prefix? ~ primary ~ postfix? }\ninfix         =  _{ add | sub | mul | div | pow | paren }\nadd           =  { \"+\" }    // Addition\nsub           =  { \"-\" }    // Subtraction\nmul           =  { \"*\" }    // Multiplication\ndiv           =  { \"/\" }    // Division\nmod           =  { \"%\" }    // Modulo\npow           =  { \"^\" }    // Exponentiation\nparen         =  { \"\"  }    // Implicit multiplication operator\n\nprefix        =  _{ neg | sqrt }\nneg           =  { \"-\" }    // Negation\nsqrt          =  { \"sqrt\" }\n\npostfix       =  _{ fac }\nfac           =  { \"!\" }    // Factorial\n\nprimary       =  _{ (\"(\" ~ expr ~ \")\") | lit | constant | fn_call | ident  }\nfn_call       =  { ident ~ \"(\" ~ expr ~ (\",\" ~ expr)* ~ \")\" }\nident           =  @{ (ASCII_ALPHA | \"_\") ~ (ASCII_ALPHANUMERIC | \"_\")* }\nlit           =  { unit | ((float | int) ~ unit?) }\n\nfloat         =  @{ int ~ \".\" ~ int? ~ exp? | int ~ exp }\nexp           =  _{ ^\"e\" ~ (\"+\" | \"-\")? ~ int }\nint           =  @{ ASCII_DIGIT+ }\n\nunit          =  ${ (scale ~ base_unit) | base_unit ~ !ident}\nbase_unit     =  _{ meter | second | gram }\nmeter         =  { \"m\" }\nsecond        =  { \"s\" }\ngram          =  { \"g\" }\n\nscale         =  _{ nano | micro | milli | centi | deci | deca | hecto | kilo | mega | giga | tera }\nnano          =  { \"n\" }\nmicro         =  { \"µ\" | \"u\" }\nmilli         =  { \"m\" }\ncenti         =  { \"c\" }\ndeci          =  { \"d\" }\ndeca          =  { \"da\" }\nhecto         =  { \"h\" }\nkilo          =  { \"k\" }\nmega          =  { \"M\" }\ngiga          =  { \"G\" }\ntera          =  { \"T\" }\n\n// Constants\nconstant              =  { infinity | imaginary_unit | pi | tau | euler_number | golden_ratio | gravity_acceleration }\ninfinity              =  { \"inf\" | \"INF\" | \"infinity\" | \"INFINITY\" | \"∞\" }\nimaginary_unit        =  { \"i\" | \"I\" }\npi                    =  { \"pi\" | \"PI\" | \"π\" }\ntau                   =  { \"tau\" | \"TAU\" | \"τ\" }\neuler_number          =  { \"e\" }\ngolden_ratio          =  { \"phi\" | \"PHI\" | \"φ\" }\ngravity_acceleration  =  { \"G\" }\n"
  },
  {
    "path": "libraries/math-parser/src/lib.rs",
    "content": "#![allow(unused)]\n\npub mod ast;\nmod constants;\npub mod context;\npub mod executer;\npub mod parser;\npub mod value;\n\nuse ast::Unit;\nuse context::{EvalContext, ValueMap};\nuse executer::EvalError;\nuse parser::ParseError;\nuse value::Value;\n\npub fn evaluate(expression: &str) -> Result<(Result<Value, EvalError>, Unit), ParseError> {\n\tlet expr = ast::Node::try_parse_from_str(expression);\n\tlet context = EvalContext::default();\n\texpr.map(|(node, unit)| (node.eval(&context), unit))\n}\n\n#[cfg(test)]\nmod tests {\n\tuse super::*;\n\tuse ast::Unit;\n\tuse value::Number;\n\n\tconst EPSILON: f64 = 1e-10_f64;\n\n\tmacro_rules! test_end_to_end{\n\t\t($($name:ident: $input:expr_2021 => ($expected_value:expr_2021, $expected_unit:expr_2021)),* $(,)?) => {\n\t\t\t$(\n\t\t\t\t#[test]\n\t\t\t\tfn $name() {\n\t\t\t\t\tlet expected_value = $expected_value;\n\t\t\t\t\tlet expected_unit = $expected_unit;\n\n\t\t\t\t\tlet expr = ast::Node::try_parse_from_str($input);\n\t\t\t\t\tlet context = EvalContext::default();\n\n\t\t\t\t\tlet (actual_value, actual_unit) = expr.map(|(node, unit)| (node.eval(&context), unit)).unwrap();\n\t\t\t\t\tlet actual_value = actual_value.unwrap();\n\n\n\t\t\t\t\tassert!(actual_unit == expected_unit, \"Expected unit {:?} but found unit {:?}\", expected_unit, actual_unit);\n\n\t\t\t\t\tlet expected_value = expected_value.into();\n\n\t\t\t\t\tmatch (actual_value, expected_value) {\n\t\t\t\t\t\t(Value::Number(Number::Complex(actual_c)), Value::Number(Number::Complex(expected_c))) => {\n\t\t\t\t\t\t\tassert!(\n\t\t\t\t\t\t\t\t(actual_c.re.is_infinite() && expected_c.re.is_infinite()) || (actual_c.re - expected_c.re).abs() < EPSILON,\n\t\t\t\t\t\t\t\t\"Expected real part {}, but got {}\",\n\t\t\t\t\t\t\t\texpected_c.re,\n\t\t\t\t\t\t\t\tactual_c.re\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tassert!(\n\t\t\t\t\t\t\t\t(actual_c.im.is_infinite() && expected_c.im.is_infinite()) || (actual_c.im - expected_c.im).abs() < EPSILON,\n\t\t\t\t\t\t\t\t\"Expected imaginary part {}, but got {}\",\n\t\t\t\t\t\t\t\texpected_c.im,\n\t\t\t\t\t\t\t\tactual_c.im\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\t(Value::Number(Number::Real(actual_f)), Value::Number(Number::Real(expected_f))) => {\n\t\t\t\t\t\t\tif actual_f.is_infinite() || expected_f.is_infinite() {\n\t\t\t\t\t\t\t\tassert!(\n\t\t\t\t\t\t\t\t\tactual_f.is_infinite() && expected_f.is_infinite() && actual_f == expected_f,\n\t\t\t\t\t\t\t\t\t\"Expected infinite value {}, but got {}\",\n\t\t\t\t\t\t\t\t\texpected_f,\n\t\t\t\t\t\t\t\t\tactual_f\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t} else if actual_f.is_nan() || expected_f.is_nan() {\n\t\t\t\t\t\t\t\tassert!(actual_f.is_nan() && expected_f.is_nan(), \"Expected NaN, but got {}\", actual_f);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tassert!((actual_f - expected_f).abs() < EPSILON, \"Expected {}, but got {}\", expected_f, actual_f);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Handle mismatched types\n\t\t\t\t\t\t_ => panic!(\"Mismatched types: expected {:?}, got {:?}\", expected_value, actual_value),\n\t\t\t\t\t}\n\n\t\t\t\t}\n\t\t\t)*\n\t\t};\n\t}\n\n\ttest_end_to_end! {\n\t\t// Basic arithmetic and units\n\t\tinfix_addition: \"5 + 5\" => (10., Unit::BASE_UNIT),\n\t\tinfix_subtraction_units: \"5m - 3m\" => (2., Unit::LENGTH),\n\t\tinfix_multiplication_units: \"4s * 4s\" => (16., Unit { length: 0, mass: 0, time: 2 }),\n\t\tinfix_division_units: \"8m/2s\" => (4., Unit::VELOCITY),\n\n\t\t// Order of operations\n\t\torder_of_operations_negative_prefix: \"-10 + 5\" => (-5., Unit::BASE_UNIT),\n\t\torder_of_operations_add_multiply: \"5+1*1+5\" => (11., Unit::BASE_UNIT),\n\t\torder_of_operations_add_negative_multiply: \"5+(-1)*1+5\" => (9., Unit::BASE_UNIT),\n\t\torder_of_operations_sqrt: \"sqrt25 + 11\" => (16., Unit::BASE_UNIT),\n\t\torder_of_operations_sqrt_expression: \"sqrt(25+11)\" => (6., Unit::BASE_UNIT),\n\n\t\t// Parentheses and nested expressions\n\t\tparentheses_nested_multiply: \"(5 + 3) * (2 + 6)\" => (64., Unit::BASE_UNIT),\n\t\tparentheses_mixed_operations: \"2 * (3 + 5 * (2 + 1))\" => (36., Unit::BASE_UNIT),\n\t\tparentheses_divide_add_multiply: \"10 / (2 + 3) + (7 * 2)\" => (16., Unit::BASE_UNIT),\n\n\t\t// Square root and nested square root\n\t\tsqrt_chain_operations: \"sqrt(16) + sqrt(9) * sqrt(4)\" => (10., Unit::BASE_UNIT),\n\t\tsqrt_nested: \"sqrt(sqrt(81))\" => (3., Unit::BASE_UNIT),\n\t\tsqrt_divide_expression: \"sqrt((25 + 11) / 9)\" => (2., Unit::BASE_UNIT),\n\n\t\t// Mixed square root and units\n\t\tsqrt_multiply_units: \"sqrt(16) * 2g + 5g\" => (13., Unit::MASS),\n\t\tsqrt_add_multiply: \"sqrt(49) - 1 + 2 * 3\" => (12., Unit::BASE_UNIT),\n\t\tsqrt_addition_multiply: \"(sqrt(36) + 2) * 2\" => (16., Unit::BASE_UNIT),\n\n\t\t// Exponentiation\n\t\texponent_single: \"2^3\" => (8., Unit::BASE_UNIT),\n\t\texponent_mixed_operations: \"2^3 + 4^2\" => (24., Unit::BASE_UNIT),\n\t\texponent_nested: \"2^(3+1)\" => (16., Unit::BASE_UNIT),\n\n\t\t// Operations with negative values\n\t\tnegative_units_add_multiply: \"-5s + (-3 * 2)s\" => (-11., Unit::TIME),\n\t\tnegative_nested_parentheses: \"-(5 + 3 * (2 - 1))\" => (-8., Unit::BASE_UNIT),\n\t\tnegative_sqrt_addition: \"-(sqrt(16) + sqrt(9))\" => (-7., Unit::BASE_UNIT),\n\t\tmultiply_sqrt_subtract: \"5 * 2 + sqrt(16) / 2 - 3\" => (9., Unit::BASE_UNIT),\n\t\tadd_multiply_subtract_sqrt: \"4 + 3 * (2 + 1) - sqrt(25)\" => (8., Unit::BASE_UNIT),\n\t\tadd_sqrt_subtract_nested_multiply: \"10 + sqrt(64) - (5 * (2 + 1))\" => (3., Unit::BASE_UNIT),\n\n\t\t// Mathematical constants\n\t\tconstant_pi: \"pi\" => (std::f64::consts::PI, Unit::BASE_UNIT),\n\t\tconstant_e: \"e\" => (std::f64::consts::E, Unit::BASE_UNIT),\n\t\tconstant_phi: \"phi\" => (1.61803398875, Unit::BASE_UNIT),\n\t\tconstant_tau: \"tau\" => (2.0 * std::f64::consts::PI, Unit::BASE_UNIT),\n\t\tconstant_infinity: \"inf\" => (f64::INFINITY, Unit::BASE_UNIT),\n\t\tconstant_infinity_symbol: \"∞\" => (f64::INFINITY, Unit::BASE_UNIT),\n\t\tmultiply_pi: \"2 * pi\" => (2.0 * std::f64::consts::PI, Unit::BASE_UNIT),\n\t\tadd_e_constant: \"e + 1\" => (std::f64::consts::E + 1.0, Unit::BASE_UNIT),\n\t\tmultiply_phi_constant: \"phi * 2\" => (1.61803398875 * 2.0, Unit::BASE_UNIT),\n\t\texponent_tau: \"2^tau\" => (2f64.powf(2.0 * std::f64::consts::PI), Unit::BASE_UNIT),\n\t\tinfinity_subtract_large_number: \"inf - 1000\" => (f64::INFINITY, Unit::BASE_UNIT),\n\n\t\t// Trigonometric functions\n\t\ttrig_sin_pi: \"sin(pi)\" => (0.0, Unit::BASE_UNIT),\n\t\ttrig_cos_zero: \"cos(0)\" => (1.0, Unit::BASE_UNIT),\n\t\ttrig_tan_pi_div_four: \"tan(pi/4)\" => (1.0, Unit::BASE_UNIT),\n\t\ttrig_sin_tau: \"sin(tau)\" => (0.0, Unit::BASE_UNIT),\n\t\ttrig_cos_tau_div_two: \"cos(tau/2)\" => (-1.0, Unit::BASE_UNIT),\n\t}\n}\n"
  },
  {
    "path": "libraries/math-parser/src/parser.rs",
    "content": "use crate::ast::{BinaryOp, Literal, Node, UnaryOp, Unit};\nuse crate::context::EvalContext;\nuse crate::value::{Complex, Number, Value};\nuse lazy_static::lazy_static;\nuse num_complex::ComplexFloat;\nuse pest::Parser;\nuse pest::iterators::{Pair, Pairs};\nuse pest::pratt_parser::{Assoc, Op, PrattParser};\nuse pest_derive::Parser;\nuse std::num::{ParseFloatError, ParseIntError};\nuse thiserror::Error;\n\n#[derive(Parser)]\n#[grammar = \"./grammer.pest\"] // Point to the grammar file\nstruct ExprParser;\n\nlazy_static! {\n\tstatic ref PRATT_PARSER: PrattParser<Rule> = {\n\t\tPrattParser::new()\n\t\t\t.op(Op::infix(Rule::add, Assoc::Left) | Op::infix(Rule::sub, Assoc::Left))\n\t\t\t.op(Op::infix(Rule::mul, Assoc::Left) | Op::infix(Rule::div, Assoc::Left) | Op::infix(Rule::paren, Assoc::Left))\n\t\t\t.op(Op::infix(Rule::pow, Assoc::Right))\n\t\t\t.op(Op::postfix(Rule::fac) | Op::postfix(Rule::EOI))\n\t\t\t.op(Op::prefix(Rule::sqrt))\n\t\t\t.op(Op::prefix(Rule::neg))\n\t};\n}\n\n#[derive(Error, Debug)]\npub enum TypeError {\n\t#[error(\"Invalid BinOp: {0:?} {1:?} {2:?}\")]\n\tInvalidBinaryOp(Unit, BinaryOp, Unit),\n\n\t#[error(\"Invalid UnaryOp: {0:?}\")]\n\tInvalidUnaryOp(Unit, UnaryOp),\n}\n\n#[derive(Error, Debug)]\npub enum ParseError {\n\t#[error(\"ParseIntError: {0}\")]\n\tParseInt(#[from] ParseIntError),\n\t#[error(\"ParseFloatError: {0}\")]\n\tParseFloat(#[from] ParseFloatError),\n\n\t#[error(\"TypeError: {0}\")]\n\tType(#[from] TypeError),\n\n\t#[error(\"PestError: {0}\")]\n\tPest(#[from] Box<pest::error::Error<Rule>>),\n}\n\nimpl Node {\n\tpub fn try_parse_from_str(s: &str) -> Result<(Node, Unit), ParseError> {\n\t\tlet pairs = ExprParser::parse(Rule::program, s).map_err(Box::new)?;\n\t\tlet (node, metadata) = parse_expr(pairs)?;\n\t\tOk((node, metadata.unit))\n\t}\n}\n\nstruct NodeMetadata {\n\tpub unit: Unit,\n}\n\nimpl NodeMetadata {\n\tpub fn new(unit: Unit) -> Self {\n\t\tSelf { unit }\n\t}\n}\n\nfn parse_unit(pairs: Pairs<Rule>) -> Result<(Unit, f64), ParseError> {\n\tlet mut scale = 1.0;\n\tlet mut length = 0;\n\tlet mut mass = 0;\n\tlet mut time = 0;\n\n\tfor pair in pairs {\n\t\tprintln!(\"found rule: {:?}\", pair.as_rule());\n\t\tmatch pair.as_rule() {\n\t\t\tRule::nano => scale *= 1e-9,\n\t\t\tRule::micro => scale *= 1e-6,\n\t\t\tRule::milli => scale *= 1e-3,\n\t\t\tRule::centi => scale *= 1e-2,\n\t\t\tRule::deci => scale *= 1e-1,\n\t\t\tRule::deca => scale *= 1e1,\n\t\t\tRule::hecto => scale *= 1e2,\n\t\t\tRule::kilo => scale *= 1e3,\n\t\t\tRule::mega => scale *= 1e6,\n\t\t\tRule::giga => scale *= 1e9,\n\t\t\tRule::tera => scale *= 1e12,\n\n\t\t\tRule::meter => length = 1,\n\t\t\tRule::gram => mass = 1,\n\t\t\tRule::second => time = 1,\n\n\t\t\t_ => unreachable!(), // All possible rules should be covered\n\t\t}\n\t}\n\n\tOk((Unit { length, mass, time }, scale))\n}\n\nfn parse_const(pair: Pair<Rule>) -> Literal {\n\tmatch pair.as_rule() {\n\t\tRule::infinity => Literal::Float(f64::INFINITY),\n\t\tRule::imaginary_unit => Literal::Complex(Complex::new(0.0, 1.0)),\n\t\tRule::pi => Literal::Float(std::f64::consts::PI),\n\t\tRule::tau => Literal::Float(2.0 * std::f64::consts::PI),\n\t\tRule::euler_number => Literal::Float(std::f64::consts::E),\n\t\tRule::golden_ratio => Literal::Float(1.61803398875),\n\t\t_ => unreachable!(\"Unexpected constant: {:?}\", pair),\n\t}\n}\n\nfn parse_lit(mut pairs: Pairs<Rule>) -> Result<(Literal, Unit), ParseError> {\n\tlet literal = match pairs.next() {\n\t\tSome(lit) => match lit.as_rule() {\n\t\t\tRule::int => {\n\t\t\t\tlet value = lit.as_str().parse::<i32>()? as f64;\n\t\t\t\tLiteral::Float(value)\n\t\t\t}\n\t\t\tRule::float => {\n\t\t\t\tlet value = lit.as_str().parse::<f64>()?;\n\t\t\t\tLiteral::Float(value)\n\t\t\t}\n\t\t\tRule::unit => {\n\t\t\t\tlet (unit, scale) = parse_unit(lit.into_inner())?;\n\t\t\t\treturn Ok((Literal::Float(scale), unit));\n\t\t\t}\n\t\t\trule => unreachable!(\"unexpected rule: {:?}\", rule),\n\t\t},\n\t\tNone => unreachable!(\"expected rule\"), // No literal found\n\t};\n\n\tif let Some(unit_pair) = pairs.next() {\n\t\tlet unit_pairs = unit_pair.into_inner(); // Get the inner pairs for the unit\n\t\tlet (unit, scale) = parse_unit(unit_pairs)?;\n\n\t\tprintln!(\"found unit: {unit:?}\");\n\n\t\tOk((\n\t\t\tmatch literal {\n\t\t\t\tLiteral::Float(num) => Literal::Float(num * scale),\n\t\t\t\tLiteral::Complex(num) => Literal::Complex(num * scale),\n\t\t\t},\n\t\t\tunit,\n\t\t))\n\t} else {\n\t\tOk((literal, Unit::BASE_UNIT))\n\t}\n}\n\nfn parse_expr(pairs: Pairs<Rule>) -> Result<(Node, NodeMetadata), ParseError> {\n\tPRATT_PARSER\n\t\t.map_primary(|primary| {\n\t\t\tOk(match primary.as_rule() {\n\t\t\t\tRule::lit => {\n\t\t\t\t\tlet (lit, unit) = parse_lit(primary.into_inner())?;\n\n\t\t\t\t\t(Node::Lit(lit), NodeMetadata { unit })\n\t\t\t\t}\n\t\t\t\tRule::fn_call => {\n\t\t\t\t\tlet mut pairs = primary.into_inner();\n\t\t\t\t\tlet name = pairs.next().expect(\"fn_call always has 2 children\").as_str().to_string();\n\n\t\t\t\t\t(\n\t\t\t\t\t\tNode::FnCall {\n\t\t\t\t\t\t\tname,\n\t\t\t\t\t\t\texpr: pairs.map(|p| parse_expr(p.into_inner()).map(|expr| expr.0)).collect::<Result<Vec<Node>, ParseError>>()?,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tNodeMetadata::new(Unit::BASE_UNIT),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tRule::constant => {\n\t\t\t\t\tlet lit = parse_const(primary.into_inner().next().expect(\"constant should have atleast 1 child\"));\n\n\t\t\t\t\t(Node::Lit(lit), NodeMetadata::new(Unit::BASE_UNIT))\n\t\t\t\t}\n\t\t\t\tRule::ident => {\n\t\t\t\t\tlet name = primary.as_str().to_string();\n\n\t\t\t\t\t(Node::Var(name), NodeMetadata::new(Unit::BASE_UNIT))\n\t\t\t\t}\n\t\t\t\tRule::expr => parse_expr(primary.into_inner())?,\n\t\t\t\tRule::float => {\n\t\t\t\t\tlet value = primary.as_str().parse::<f64>()?;\n\t\t\t\t\t(Node::Lit(Literal::Float(value)), NodeMetadata::new(Unit::BASE_UNIT))\n\t\t\t\t}\n\t\t\t\trule => unreachable!(\"unexpected rule: {:?}\", rule),\n\t\t\t})\n\t\t})\n\t\t.map_prefix(|op, rhs| {\n\t\t\tlet (rhs, rhs_metadata) = rhs?;\n\t\t\tlet op = match op.as_rule() {\n\t\t\t\tRule::neg => UnaryOp::Neg,\n\t\t\t\tRule::sqrt => UnaryOp::Sqrt,\n\n\t\t\t\trule => unreachable!(\"unexpected rule: {:?}\", rule),\n\t\t\t};\n\n\t\t\tlet node = Node::UnaryOp { expr: Box::new(rhs), op };\n\t\t\tlet unit = rhs_metadata.unit;\n\n\t\t\tlet unit = if !unit.is_base() {\n\t\t\t\tmatch op {\n\t\t\t\t\tUnaryOp::Sqrt if unit.length % 2 == 0 && unit.mass % 2 == 0 && unit.time % 2 == 0 => Unit {\n\t\t\t\t\t\tlength: unit.length / 2,\n\t\t\t\t\t\tmass: unit.mass / 2,\n\t\t\t\t\t\ttime: unit.time / 2,\n\t\t\t\t\t},\n\t\t\t\t\tUnaryOp::Neg => unit,\n\t\t\t\t\top => return Err(ParseError::Type(TypeError::InvalidUnaryOp(unit, op))),\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tUnit::BASE_UNIT\n\t\t\t};\n\n\t\t\tOk((node, NodeMetadata::new(unit)))\n\t\t})\n\t\t.map_postfix(|lhs, op| {\n\t\t\tlet (lhs_node, lhs_metadata) = lhs?;\n\n\t\t\tlet op = match op.as_rule() {\n\t\t\t\tRule::EOI => return Ok((lhs_node, lhs_metadata)),\n\t\t\t\tRule::fac => UnaryOp::Fac,\n\t\t\t\trule => unreachable!(\"unexpected rule: {:?}\", rule),\n\t\t\t};\n\n\t\t\tif !lhs_metadata.unit.is_base() {\n\t\t\t\treturn Err(ParseError::Type(TypeError::InvalidUnaryOp(lhs_metadata.unit, op)));\n\t\t\t}\n\n\t\t\tOk((Node::UnaryOp { expr: Box::new(lhs_node), op }, lhs_metadata))\n\t\t})\n\t\t.map_infix(|lhs, op, rhs| {\n\t\t\tlet (lhs, lhs_metadata) = lhs?;\n\t\t\tlet (rhs, rhs_metadata) = rhs?;\n\n\t\t\tlet op = match op.as_rule() {\n\t\t\t\tRule::add => BinaryOp::Add,\n\t\t\t\tRule::sub => BinaryOp::Sub,\n\t\t\t\tRule::mul => BinaryOp::Mul,\n\t\t\t\tRule::div => BinaryOp::Div,\n\t\t\t\tRule::pow => BinaryOp::Pow,\n\t\t\t\tRule::paren => BinaryOp::Mul,\n\t\t\t\trule => unreachable!(\"unexpected rule: {:?}\", rule),\n\t\t\t};\n\n\t\t\tlet (lhs_unit, rhs_unit) = (lhs_metadata.unit, rhs_metadata.unit);\n\n\t\t\tlet unit = match (!lhs_unit.is_base(), !rhs_unit.is_base()) {\n\t\t\t\t(true, true) => match op {\n\t\t\t\t\tBinaryOp::Mul => Unit {\n\t\t\t\t\t\tlength: lhs_unit.length + rhs_unit.length,\n\t\t\t\t\t\tmass: lhs_unit.mass + rhs_unit.mass,\n\t\t\t\t\t\ttime: lhs_unit.time + rhs_unit.time,\n\t\t\t\t\t},\n\t\t\t\t\tBinaryOp::Div => Unit {\n\t\t\t\t\t\tlength: lhs_unit.length - rhs_unit.length,\n\t\t\t\t\t\tmass: lhs_unit.mass - rhs_unit.mass,\n\t\t\t\t\t\ttime: lhs_unit.time - rhs_unit.time,\n\t\t\t\t\t},\n\t\t\t\t\tBinaryOp::Add | BinaryOp::Sub => {\n\t\t\t\t\t\tif lhs_unit == rhs_unit {\n\t\t\t\t\t\t\tlhs_unit\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\treturn Err(ParseError::Type(TypeError::InvalidBinaryOp(lhs_unit, op, rhs_unit)));\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tBinaryOp::Pow => {\n\t\t\t\t\t\treturn Err(ParseError::Type(TypeError::InvalidBinaryOp(lhs_unit, op, rhs_unit)));\n\t\t\t\t\t}\n\t\t\t\t},\n\n\t\t\t\t(true, false) => match op {\n\t\t\t\t\tBinaryOp::Add | BinaryOp::Sub => return Err(ParseError::Type(TypeError::InvalidBinaryOp(lhs_unit, op, Unit::BASE_UNIT))),\n\t\t\t\t\tBinaryOp::Pow => {\n\t\t\t\t\t\t//TODO: improve error type\n\t\t\t\t\t\t//TODO: support 1 / int\n\t\t\t\t\t\tif let Ok(Value::Number(Number::Real(val))) = rhs.eval(&EvalContext::default()) {\n\t\t\t\t\t\t\tif (val - val as i32 as f64).abs() <= f64::EPSILON {\n\t\t\t\t\t\t\t\tUnit {\n\t\t\t\t\t\t\t\t\tlength: lhs_unit.length * val as i32,\n\t\t\t\t\t\t\t\t\tmass: lhs_unit.mass * val as i32,\n\t\t\t\t\t\t\t\t\ttime: lhs_unit.time * val as i32,\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\treturn Err(ParseError::Type(TypeError::InvalidBinaryOp(lhs_unit, op, Unit::BASE_UNIT)));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\treturn Err(ParseError::Type(TypeError::InvalidBinaryOp(lhs_unit, op, Unit::BASE_UNIT)));\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t_ => lhs_unit,\n\t\t\t\t},\n\t\t\t\t(false, true) => match op {\n\t\t\t\t\tBinaryOp::Add | BinaryOp::Sub | BinaryOp::Pow => return Err(ParseError::Type(TypeError::InvalidBinaryOp(Unit::BASE_UNIT, op, rhs_unit))),\n\t\t\t\t\t_ => rhs_unit,\n\t\t\t\t},\n\t\t\t\t(false, false) => Unit::BASE_UNIT,\n\t\t\t};\n\n\t\t\tlet node = Node::BinOp {\n\t\t\t\tlhs: Box::new(lhs),\n\t\t\t\top,\n\t\t\t\trhs: Box::new(rhs),\n\t\t\t};\n\n\t\t\tOk((node, NodeMetadata::new(unit)))\n\t\t})\n\t\t.parse(pairs)\n}\n\n//TODO: set up Unit test for Units\n#[cfg(test)]\nmod tests {\n\tuse super::*;\n\tmacro_rules! test_parser {\n\t\t($($name:ident: $input:expr_2021 => $expected:expr_2021),* $(,)?) => {\n\t\t\t$(\n\t\t\t\t#[test]\n\t\t\t\tfn $name() {\n\t\t\t\t\tlet result = Node::try_parse_from_str($input).unwrap();\n\t\t\t\t\tassert_eq!(result.0, $expected);\n\t\t\t\t}\n\t\t\t)*\n\t\t};\n\t}\n\n\ttest_parser! {\n\t\ttest_parse_int_literal: \"42\" => Node::Lit(Literal::Float(42.0)),\n\t\ttest_parse_float_literal: \"3.14\" => Node::Lit(Literal::Float(#[allow(clippy::approx_constant)] 3.14)),\n\t\ttest_parse_ident: \"x\" => Node::Var(\"x\".to_string()),\n\t\ttest_parse_unary_neg: \"-42\" => Node::UnaryOp {\n\t\t\texpr: Box::new(Node::Lit(Literal::Float(42.0))),\n\t\t\top: UnaryOp::Neg,\n\t\t},\n\t\ttest_parse_binary_add: \"1 + 2\" => Node::BinOp {\n\t\t\tlhs: Box::new(Node::Lit(Literal::Float(1.0))),\n\t\t\top: BinaryOp::Add,\n\t\t\trhs: Box::new(Node::Lit(Literal::Float(2.0))),\n\t\t},\n\t\ttest_parse_binary_mul: \"3 * 4\" => Node::BinOp {\n\t\t\tlhs: Box::new(Node::Lit(Literal::Float(3.0))),\n\t\t\top: BinaryOp::Mul,\n\t\t\trhs: Box::new(Node::Lit(Literal::Float(4.0))),\n\t\t},\n\t\ttest_parse_binary_pow: \"2 ^ 3\" => Node::BinOp {\n\t\t\tlhs: Box::new(Node::Lit(Literal::Float(2.0))),\n\t\t\top: BinaryOp::Pow,\n\t\t\trhs: Box::new(Node::Lit(Literal::Float(3.0))),\n\t\t},\n\t\ttest_parse_unary_sqrt: \"sqrt(16)\" => Node::UnaryOp {\n\t\t\texpr: Box::new(Node::Lit(Literal::Float(16.0))),\n\t\t\top: UnaryOp::Sqrt,\n\t\t},\n\t\ttest_parse_sqr_ident: \"sqr(16)\" => Node::FnCall {\n\t\t\t name:\"sqr\".to_string(),\n\t\t\t expr: vec![Node::Lit(Literal::Float(16.0))]\n\t\t},\n\n\t\ttest_parse_complex_expr: \"(1 + 2)  3 - 4 ^ 2\" => Node::BinOp {\n\t\t\tlhs: Box::new(Node::BinOp {\n\t\t\t\tlhs: Box::new(Node::BinOp {\n\t\t\t\t\tlhs: Box::new(Node::Lit(Literal::Float(1.0))),\n\t\t\t\t\top: BinaryOp::Add,\n\t\t\t\t\trhs: Box::new(Node::Lit(Literal::Float(2.0))),\n\t\t\t\t}),\n\t\t\t\top: BinaryOp::Mul,\n\t\t\t\trhs: Box::new(Node::Lit(Literal::Float(3.0))),\n\t\t\t}),\n\t\t\top: BinaryOp::Sub,\n\t\t\trhs: Box::new(Node::BinOp {\n\t\t\t\tlhs: Box::new(Node::Lit(Literal::Float(4.0))),\n\t\t\t\top: BinaryOp::Pow,\n\t\t\t\trhs: Box::new(Node::Lit(Literal::Float(2.0))),\n\t\t\t}),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "libraries/math-parser/src/value.rs",
    "content": "use crate::ast::{BinaryOp, UnaryOp};\nuse num_complex::ComplexFloat;\nuse std::f64::consts::PI;\n\npub type Complex = num_complex::Complex<f64>;\n\n#[derive(Debug, PartialEq, Clone, Copy)]\npub enum Value {\n\tNumber(Number),\n}\n\nimpl Value {\n\tpub fn from_f64(x: f64) -> Self {\n\t\tSelf::Number(Number::Real(x))\n\t}\n\n\tpub fn as_real(&self) -> Option<f64> {\n\t\tmatch self {\n\t\t\tSelf::Number(Number::Real(val)) => Some(*val),\n\t\t\t_ => None,\n\t\t}\n\t}\n}\n\nimpl From<f64> for Value {\n\tfn from(x: f64) -> Self {\n\t\tSelf::from_f64(x)\n\t}\n}\n\nimpl core::fmt::Display for Value {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tmatch self {\n\t\t\tValue::Number(num) => num.fmt(f),\n\t\t}\n\t}\n}\n\n#[derive(Debug, PartialEq, Clone, Copy)]\npub enum Number {\n\tReal(f64),\n\tComplex(Complex),\n}\n\nimpl std::fmt::Display for Number {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tmatch self {\n\t\t\tNumber::Real(real) => real.fmt(f),\n\t\t\tNumber::Complex(complex) => complex.fmt(f),\n\t\t}\n\t}\n}\n\nimpl Number {\n\tpub fn binary_op(self, op: BinaryOp, other: Number) -> Number {\n\t\tmatch (self, other) {\n\t\t\t(Number::Real(lhs), Number::Real(rhs)) => {\n\t\t\t\tlet result = match op {\n\t\t\t\t\tBinaryOp::Add => lhs + rhs,\n\t\t\t\t\tBinaryOp::Sub => lhs - rhs,\n\t\t\t\t\tBinaryOp::Mul => lhs * rhs,\n\t\t\t\t\tBinaryOp::Div => lhs / rhs,\n\t\t\t\t\tBinaryOp::Pow => lhs.powf(rhs),\n\t\t\t\t};\n\t\t\t\tNumber::Real(result)\n\t\t\t}\n\n\t\t\t(Number::Complex(lhs), Number::Complex(rhs)) => {\n\t\t\t\tlet result = match op {\n\t\t\t\t\tBinaryOp::Add => lhs + rhs,\n\t\t\t\t\tBinaryOp::Sub => lhs - rhs,\n\t\t\t\t\tBinaryOp::Mul => lhs * rhs,\n\t\t\t\t\tBinaryOp::Div => lhs / rhs,\n\t\t\t\t\tBinaryOp::Pow => lhs.powc(rhs),\n\t\t\t\t};\n\t\t\t\tNumber::Complex(result)\n\t\t\t}\n\n\t\t\t(Number::Real(lhs), Number::Complex(rhs)) => {\n\t\t\t\tlet lhs_complex = Complex::new(lhs, 0.0);\n\t\t\t\tlet result = match op {\n\t\t\t\t\tBinaryOp::Add => lhs_complex + rhs,\n\t\t\t\t\tBinaryOp::Sub => lhs_complex - rhs,\n\t\t\t\t\tBinaryOp::Mul => lhs_complex * rhs,\n\t\t\t\t\tBinaryOp::Div => lhs_complex / rhs,\n\t\t\t\t\tBinaryOp::Pow => lhs_complex.powc(rhs),\n\t\t\t\t};\n\t\t\t\tNumber::Complex(result)\n\t\t\t}\n\n\t\t\t(Number::Complex(lhs), Number::Real(rhs)) => {\n\t\t\t\tlet rhs_complex = Complex::new(rhs, 0.0);\n\t\t\t\tlet result = match op {\n\t\t\t\t\tBinaryOp::Add => lhs + rhs_complex,\n\t\t\t\t\tBinaryOp::Sub => lhs - rhs_complex,\n\t\t\t\t\tBinaryOp::Mul => lhs * rhs_complex,\n\t\t\t\t\tBinaryOp::Div => lhs / rhs_complex,\n\t\t\t\t\tBinaryOp::Pow => lhs.powf(rhs),\n\t\t\t\t};\n\t\t\t\tNumber::Complex(result)\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn unary_op(self, op: UnaryOp) -> Number {\n\t\tmatch self {\n\t\t\tNumber::Real(real) => match op {\n\t\t\t\tUnaryOp::Neg => Number::Real(-real),\n\t\t\t\tUnaryOp::Sqrt => Number::Real(real.sqrt()),\n\n\t\t\t\tUnaryOp::Fac => todo!(\"Implement factorial\"),\n\t\t\t},\n\n\t\t\tNumber::Complex(complex) => match op {\n\t\t\t\tUnaryOp::Neg => Number::Complex(-complex),\n\t\t\t\tUnaryOp::Sqrt => Number::Complex(complex.sqrt()),\n\n\t\t\t\tUnaryOp::Fac => todo!(\"Implement factorial\"),\n\t\t\t},\n\t\t}\n\t}\n\n\tpub fn from_f64(x: f64) -> Self {\n\t\tSelf::Real(x)\n\t}\n}\n"
  },
  {
    "path": "libraries/rawkit/.gitignore",
    "content": "tests/images/*\n!tests/images/.gitkeep\n"
  },
  {
    "path": "libraries/rawkit/Cargo.toml",
    "content": "[workspace]\nmembers = [\"rawkit-proc-macros\"]\nresolver = \"2\"\n\n[workspace.dependencies]\nquote = \"1.0\"\nsyn = \"2.0\"\n\n[package]\nname = \"rawkit\"\nversion = \"0.1.0\"\nedition = \"2024\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\ndescription = \"A library to extract images from camera raw files\"\nlicense = \"MIT OR Apache-2.0\"\nreadme = \"README.md\"\nkeywords = [\"raw\", \"tiff\", \"camera\", \"image\"]\ncategories = [\"multimedia::images\", \"multimedia::encoding\"]\nhomepage = \"https://github.com/GraphiteEditor/Graphite/tree/master/libraries/rawkit\"\nrepository = \"https://github.com/GraphiteEditor/Graphite/tree/master/libraries/rawkit\"\ndocumentation = \"https://docs.rs/rawkit\"\n\n[features]\nrawkit-tests = [\"dep:image\", \"dep:libraw-rs\", \"dep:reqwest\", \"dep:rayon\"]\n\n[dependencies]\n# Local dependencies\nrawkit-proc-macros = { version = \"0.1.0\", path = \"rawkit-proc-macros\" }\n\n# Required dependencies\nbitstream-io = \"4.9.0\"\nnum_enum = \"0.7.5\"\nthiserror = \"2.0.17\"\n\n# Optional dependencies (should be dev dependencies, but Cargo currently doesn't allow optional dev dependencies)\nimage = { version = \"0.25.9\", optional = true }\nreqwest = { version = \"0.13\", optional = true, features = [\"blocking\"] }\nlibraw-rs = { version = \"0.0.4\", optional = true }\nrayon = { version = \"1.11.0\", optional = true }\n"
  },
  {
    "path": "libraries/rawkit/LICENSE-APACHE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "libraries/rawkit/LICENSE-MIT",
    "content": "Permission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "libraries/rawkit/README.md",
    "content": "[crates.io](https://crates.io/crates/rawkit) • [docs.rs](https://docs.rs/rawkit) • [repo](https://github.com/GraphiteEditor/Graphite/tree/master/libraries/rawkit)\n\n# Rawkit 🚀\n\nA library to extract images from camera raw files.\n\nIt currently only works with the `.arw` files from Sony's cameras. In the future, the library will add support for all other major camera manufacturers.\n\nRawkit is built for the needs of [Graphite](https://graphite.art), an open source 2D graphics editor. We hope it may be useful to others, but presently Graphite is its primary user. Pull requests are welcomed for new cameras, features, code cleanup, ergonomic enhancements, performance improvements, and documentation clarifications.\n\n### Using Rawkit\n\n```rust\nuse rawkit::RawImage;\nuse rawkit::tiff::values::Transform;\n\n// Open a file for reading\nlet file = BufReader::new(File::open(\"example.arw\")?);\n\n// Decode the file to extract the raw pixels and its associated metadata\nlet mut raw_image = RawImage::decode(file);\n\n// All the raw pixel data and metadata is stored within `raw_image`\nprintln!(\"Initial Bayer pixel values: {:?}\", raw_image.data[:10]);\nprintln!(\"Image size: {} x {}\", raw_image.width, raw_image.height);\nprintln!(\"CFA Pattern: {:?}\", raw_image.cfa_pattern);\nprintln!(\"Camera Model: {:?}\", raw_image.camera_model);\nprintln!(\"White balance: {:?}\", raw_image.white_balance);\n\n// The metadata could also be edited if the extracted metadata needs to be customized\nraw_image.white_balance = Some([2609, 1024, 1024, 1220]); // For RGGB camera\nraw_image.transform = Transform::Rotate90;\n\n// Process the raw image into an RGB image\nlet image = raw_image.process_8bit();\n\n// The final image data will be stored within `image`\nprintln!(\"Initial RGB pixel values: {:?}\", image.data[:10]);\nprintln!(\"Image size: {} x {}\", image.width, image.height);\n```\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/DSLR-A100.toml",
    "content": "xyz_to_camera = [0.9437, -0.2812, -0.0774, -0.8405, 1.6215, 0.2291, -0.0709, 0.0596, 0.7181]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/DSLR-A200.toml",
    "content": "xyz_to_camera = [0.9847, -0.3091, -0.0929, -0.8485, 1.6346, 0.2225, -0.0714, 0.0595, 0.7103]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/DSLR-A300.toml",
    "content": "xyz_to_camera = [0.9847, -0.3091, -0.0929, -0.8485, 1.6346, 0.2225, -0.0714, 0.0595, 0.7103]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/DSLR-A330.toml",
    "content": "xyz_to_camera = [0.9847, -0.3091, -0.0929, -0.8485, 1.6346, 0.2225, -0.0714, 0.0595, 0.7103]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/DSLR-A350.toml",
    "content": "xyz_to_camera = [0.6038, -0.1484, -0.0579, -0.9145, 1.6746, 0.2512, -0.0875, 0.0746, 0.7218]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/DSLR-A550.toml",
    "content": "xyz_to_camera = [0.4950, -0.0580, -0.0103, -0.5228, 1.2542, 0.3029, -0.0709, 0.1435, 0.7371]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/DSLR-A700.toml",
    "content": "xyz_to_camera = [0.5775, -0.0805, -0.0359, -0.8573, 1.6294, 0.2391, -0.1943, 0.2342, 0.7249]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/DSLR-A850.toml",
    "content": "xyz_to_camera = [0.5413, -0.1162, -0.0365, -0.5665, 1.3098, 0.2866, -0.0608, 0.1179, 0.8440]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/DSLR-A900.toml",
    "content": "xyz_to_camera = [0.5209, -0.1072, -0.0397, -0.8845, 1.6121, 0.2919, -0.1618, 0.1802, 0.8654]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/ILCA-68.toml",
    "content": "xyz_to_camera = [0.6435, -0.1903, -0.0536, -0.4722, 1.2449, 0.2550, -0.0663, 0.1363, 0.6517]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/ILCA-99M2.toml",
    "content": "xyz_to_camera = [0.6660, -0.1918, -0.0471, -0.4613, 1.2398, 0.2485, -0.0649, 0.1433, 0.6447]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/ILCE-1.toml",
    "content": "xyz_to_camera = [0.8161, -0.2947, -0.0739, -0.4811, 1.2668, 0.2389, -0.0437, 0.1229, 0.6524]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/ILCE-5100.toml",
    "content": "xyz_to_camera = [0.5991, -0.1456, -0.0455, -0.4764, 1.2135, 0.2980, -0.0707, 0.1425, 0.6701]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/ILCE-6000.toml",
    "content": "xyz_to_camera = [0.5991, -0.1456, -0.0455, -0.4764, 1.2135, 0.2980, -0.0707, 0.1425, 0.6701]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/ILCE-6100.toml",
    "content": "xyz_to_camera = [0.7657, -0.2847, -0.0607, -0.4083, 1.1966, 0.2389, -0.0684, 0.1418, 0.5844]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/ILCE-6300.toml",
    "content": "xyz_to_camera = [0.5973, -0.1695, -0.0419, -0.3826, 1.1797, 0.2293, -0.0639, 0.1398, 0.5789]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/ILCE-6400.toml",
    "content": "xyz_to_camera = [0.7657, -0.2847, -0.0607, -0.4083, 1.1966, 0.2389, -0.0684, 0.1418, 0.5844]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/ILCE-6500.toml",
    "content": "xyz_to_camera = [0.5973, -0.1695, -0.0419, -0.3826, 1.1797, 0.2293, -0.0639, 0.1398, 0.5789]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/ILCE-6600.toml",
    "content": "xyz_to_camera = [0.7657, -0.2847, -0.0607, -0.4083, 1.1966, 0.2389, -0.0684, 0.1418, 0.5844]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/ILCE-6700.toml",
    "content": "xyz_to_camera = [0.6972, -0.2408, -0.0600, -0.4330, 1.2101, 0.2515, -0.0388, 0.1277, 0.5847]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/ILCE-7CM2.toml",
    "content": "xyz_to_camera = [0.7460, -0.2365, -0.0588, -0.5687, 1.3442, 0.2474, -0.0624, 0.1156, 0.6584]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/ILCE-7CR.toml",
    "content": "xyz_to_camera = [0.8200, -0.2976, -0.0719, -0.4296, 1.2053, 0.2532, -0.0429, 0.1282, 0.5774]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/ILCE-7M2.toml",
    "content": "xyz_to_camera = [0.5271, -0.0712, -0.0347, -0.6153, 1.3653, 0.2763, -0.1601, 0.2366, 0.7242]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/ILCE-7M3.toml",
    "content": "xyz_to_camera = [0.7374, -0.2389, -0.0551, -0.5435, 1.3162, 0.2519, -0.1006, 0.1795, 0.6552]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/ILCE-7RM3.toml",
    "content": "xyz_to_camera = [0.6640, -0.1847, -0.0503, -0.5238, 1.3010, 0.2474, -0.0993, 0.1673, 0.6527]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/ILCE-7RM4.toml",
    "content": "xyz_to_camera = [0.7662, -0.2686, -0.0660, -0.5240, 1.2965, 0.2530, -0.0796, 0.1508, 0.6167]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/ILCE-7RM5.toml",
    "content": "xyz_to_camera = [0.8200, -0.2976, -0.0719, -0.4296, 1.2053, 0.2532, -0.0429, 0.1282, 0.5774]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/ILCE-7SM2.toml",
    "content": "xyz_to_camera = [0.5838, -0.1430, -0.0246, -0.3497, 1.1477, 0.2297, -0.0748, 0.1885, 0.5778]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/ILCE-9.toml",
    "content": "xyz_to_camera = [0.6389, -0.1703, -0.0378, -0.4562, 1.2265, 0.2587, -0.0670, 0.1489, 0.6550]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/ILCE-9M2.toml",
    "content": "xyz_to_camera = [0.6389, -0.1703, -0.0378, -0.4562, 1.2265, 0.2587, -0.0670, 0.1489, 0.6550]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/ILCE-9M3.toml",
    "content": "xyz_to_camera = [0.9811, -0.3908, -0.0752, -0.3704, 1.1577, 0.2417, -0.0073, 0.0950, 0.5980]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/NEX-3.toml",
    "content": "xyz_to_camera = [0.6549, -0.1550, -0.0436, -0.4880, 1.2435, 0.2753, -0.0854, 0.1868, 0.6976]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/NEX-3N.toml",
    "content": "xyz_to_camera = [0.6129, -0.1545, -0.0418, -0.4930, 1.2490, 0.2743, -0.0977, 0.1693, 0.6615]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/NEX-5R.toml",
    "content": "xyz_to_camera = [0.6129, -0.1545, -0.0418, -0.4930, 1.2490, 0.2743, -0.0977, 0.1693, 0.6615]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/NEX-6.toml",
    "content": "xyz_to_camera = [0.6129, -0.1545, -0.0418, -0.4930, 1.2490, 0.2743, -0.0977, 0.1693, 0.6615]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/NEX-7.toml",
    "content": "xyz_to_camera = [0.5491, -0.1192, -0.0363, -0.4951, 1.2342, 0.2948, -0.0911, 0.1722, 0.7192]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/ZV-1.toml",
    "content": "xyz_to_camera = [0.8280, -0.2987, -0.0703, -0.3531, 1.1645, 0.2133, -0.0550, 0.1542, 0.5312]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/ZV-1M2.toml",
    "content": "xyz_to_camera = [0.8280, -0.2987, -0.0703, -0.3531, 1.1645, 0.2133, -0.0550, 0.1542, 0.5312]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/ZV-E1.toml",
    "content": "xyz_to_camera = [0.6912, -0.2127, -0.0469, -0.4470, 1.2175, 0.2587, -0.0398, 0.1478, 0.6492]\n"
  },
  {
    "path": "libraries/rawkit/camera_data/Sony/ZV-E10.toml",
    "content": "xyz_to_camera = [0.6355, -0.2067, -0.0490, -0.3653, 1.1542, 0.2400, -0.0406, 0.1258, 0.5506]\n"
  },
  {
    "path": "libraries/rawkit/rawkit-proc-macros/Cargo.toml",
    "content": "[package]\nname = \"rawkit-proc-macros\"\nversion = \"0.1.0\"\nedition = \"2024\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\ndescription = \"Procedural macros for Rawkit\"\nlicense = \"MIT OR Apache-2.0\"\nreadme = \"README.md\"\nhomepage = \"https://github.com/GraphiteEditor/Graphite/tree/master/libraries/rawkit/rawkit-proc-macros\"\nrepository = \"https://github.com/GraphiteEditor/Graphite/tree/master/libraries/rawkit/rawkit-proc-macros\"\ndocumentation = \"https://docs.rs/rawkit-proc-macros\"\n\n[lib]\nproc-macro = true\n\n[dependencies]\n# Workspace dependencies\nquote = { workspace = true }\nsyn = { workspace = true }\n\n# Required dependencies\ntoml = \"0.9.10+spec-1.1.0\"\nproc-macro2 = \"1.0.103\"\n"
  },
  {
    "path": "libraries/rawkit/rawkit-proc-macros/LICENSE-APACHE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "libraries/rawkit/rawkit-proc-macros/LICENSE-MIT",
    "content": "Permission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "libraries/rawkit/rawkit-proc-macros/README.md",
    "content": "# Rawkit-proc-macros\n\nProcedural macros for Rawkit.\n\nThis library is intended to be used by Rawkit. You should not be depending on this crate directly.\n\n### Tag\n\nA derive macro that helps to specify which metadata needs to be extracted from IFD.\n\n### build_camera_data\n\nA procedural macro that reads the data of all cameras from the toml files and returns the bundled data. Helps to include camera data as part of binary.\n"
  },
  {
    "path": "libraries/rawkit/rawkit-proc-macros/src/build_camera_data.rs",
    "content": "use proc_macro::TokenStream;\nuse quote::{ToTokens, quote};\nuse std::fs;\nuse std::path::Path;\nuse toml::{Table, Value};\n\nenum CustomValue {\n\tString(String),\n\tInteger(i64),\n\tFloat(f64),\n\tBoolean(bool),\n\tArray(Vec<CustomValue>),\n}\n\nimpl ToTokens for CustomValue {\n\tfn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {\n\t\tmatch self {\n\t\t\tCustomValue::String(x) => x.to_tokens(tokens),\n\t\t\tCustomValue::Integer(x) => {\n\t\t\t\tlet x: proc_macro2::TokenStream = format!(\"{:?}\", x).parse().unwrap();\n\t\t\t\tx.to_tokens(tokens)\n\t\t\t}\n\t\t\tCustomValue::Float(x) => {\n\t\t\t\tlet x: proc_macro2::TokenStream = format!(\"{:?}\", x).parse().unwrap();\n\t\t\t\tx.to_tokens(tokens)\n\t\t\t}\n\t\t\tCustomValue::Boolean(x) => x.to_tokens(tokens),\n\t\t\tCustomValue::Array(x) => quote! { [ #( #x ),* ] }.to_tokens(tokens),\n\t\t}\n\t}\n}\n\nimpl From<Value> for CustomValue {\n\tfn from(value: Value) -> Self {\n\t\tmatch value {\n\t\t\tValue::String(x) => CustomValue::String(x),\n\t\t\tValue::Integer(x) => CustomValue::Integer(x),\n\t\t\tValue::Float(x) => CustomValue::Float(x),\n\t\t\tValue::Boolean(x) => CustomValue::Boolean(x),\n\t\t\tValue::Array(x) => CustomValue::Array(x.into_iter().map(|x| x.into()).collect()),\n\t\t\t_ => panic!(\"Unsupported data type\"),\n\t\t}\n\t}\n}\n\npub fn build_camera_data() -> TokenStream {\n\tlet mut camera_data: Vec<(String, Table)> = Vec::new();\n\n\tlet mut path = Path::new(&std::env::var(\"CARGO_MANIFEST_DIR\").unwrap()).to_path_buf();\n\tpath.push(\"camera_data\");\n\n\tfs::read_dir(path).unwrap().for_each(|entry| {\n\t\tlet company_name_path = entry.unwrap().path();\n\t\tif !company_name_path.is_dir() {\n\t\t\tpanic!(\"camera_data should only contain folders of company names\")\n\t\t}\n\n\t\tlet company_name = company_name_path.file_name().unwrap().to_str().unwrap().to_string();\n\n\t\tfs::read_dir(company_name_path).unwrap().for_each(|entry| {\n\t\t\tlet model_path = entry.unwrap().path();\n\t\t\tif !model_path.is_file() || model_path.extension().unwrap() != \"toml\" {\n\t\t\t\tpanic!(\"The folders within camera_data should only contain toml files\")\n\t\t\t}\n\n\t\t\tlet name = company_name.clone() + \" \" + model_path.file_stem().unwrap().to_str().unwrap();\n\n\t\t\tlet mut values: Table = toml::from_str(&fs::read_to_string(model_path).unwrap()).unwrap();\n\n\t\t\tif let Some(val) = values.get_mut(\"xyz_to_camera\") {\n\t\t\t\t*val = Value::Array(val.as_array().unwrap().iter().map(|x| Value::Integer((x.as_float().unwrap() * 10_000.) as i64)).collect());\n\t\t\t}\n\n\t\t\tcamera_data.push((name, values))\n\t\t});\n\t});\n\n\tlet x: Vec<_> = camera_data\n\t\t.iter()\n\t\t.map(|(name, camera_data)| {\n\t\t\tlet keys: Vec<_> = camera_data.keys().map(|key| syn::Ident::new(key, proc_macro2::Span::call_site())).collect();\n\t\t\tlet values: Vec<CustomValue> = camera_data.values().cloned().map(|x| x.into()).collect();\n\n\t\t\tquote! {\n\t\t\t\t(\n\t\t\t\t\t#name,\n\t\t\t\t\tCameraData {\n\t\t\t\t\t\t#( #keys: #values, )*\n\t\t\t\t\t\t..CameraData::DEFAULT\n\t\t\t\t\t}\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t\t.collect();\n\n\tquote!([ #(#x),* ]).into()\n}\n"
  },
  {
    "path": "libraries/rawkit/rawkit-proc-macros/src/lib.rs",
    "content": "extern crate proc_macro;\n\nmod build_camera_data;\nmod tag_derive;\n\nuse proc_macro::TokenStream;\n\n#[proc_macro_derive(Tag)]\npub fn tag_derive(input: TokenStream) -> TokenStream {\n\ttag_derive::tag_derive(input)\n}\n\n#[proc_macro]\npub fn build_camera_data(_: TokenStream) -> TokenStream {\n\tbuild_camera_data::build_camera_data()\n}\n"
  },
  {
    "path": "libraries/rawkit/rawkit-proc-macros/src/tag_derive.rs",
    "content": "use proc_macro::TokenStream;\nuse quote::{format_ident, quote};\nuse syn::{Data, DeriveInput, Fields};\n\npub fn tag_derive(input: TokenStream) -> TokenStream {\n\tlet ast: DeriveInput = syn::parse(input).unwrap();\n\n\tlet name = &ast.ident;\n\n\tlet data_struct = if let Data::Struct(data_struct) = ast.data {\n\t\tdata_struct\n\t} else {\n\t\tpanic!(\"Tag trait can only be derived for structs\")\n\t};\n\n\tlet named_fields = if let Fields::Named(named_fields) = data_struct.fields {\n\t\tnamed_fields\n\t} else {\n\t\tpanic!(\"Tag trait can only be derived for structs with named_fields\")\n\t};\n\n\tlet struct_idents: Vec<_> = named_fields.named.iter().map(|field| field.ident.clone().unwrap()).collect();\n\tlet struct_types: Vec<_> = named_fields.named.iter().map(|field| field.ty.clone()).collect();\n\n\tlet new_name = format_ident!(\"_{}\", name);\n\n\tlet r#gen = quote! {\n\t\tstruct #new_name {\n\t\t\t#( #struct_idents: <#struct_types as Tag>::Output ),*\n\t\t}\n\n\t\timpl Tag for #name {\n\t\t\ttype Output = #new_name;\n\n\t\t\tfn get<R: Read + Seek>(ifd: &Ifd, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {\n\t\t\t\t#( let #struct_idents = <#struct_types as Tag>::get(ifd, file)?; )*\n\t\t\t\tOk(#new_name { #( #struct_idents ),* })\n\t\t\t}\n\t\t}\n\t};\n\n\tr#gen.into()\n}\n"
  },
  {
    "path": "libraries/rawkit/src/decoder/arw1.rs",
    "content": "use crate::tiff::Ifd;\nuse crate::tiff::file::TiffRead;\nuse crate::tiff::tags::SonyDataOffset;\nuse crate::{OrientationValue, RawImage, SubtractBlack};\nuse bitstream_io::{BE, BitRead, BitReader, Endianness};\nuse std::io::{Read, Seek};\n\npub fn decode_a100<R: Read + Seek>(ifd: Ifd, file: &mut TiffRead<R>) -> RawImage {\n\tlet data_offset = ifd.get_value::<SonyDataOffset, _>(file).unwrap();\n\n\tlet image_width = 3881;\n\tlet image_height = 2608;\n\n\tfile.seek_from_start(data_offset).unwrap();\n\tlet mut image = sony_arw_load_raw(image_width, image_height, &mut BitReader::<_, BE>::new(file)).unwrap();\n\n\tlet len = image.len();\n\timage[len - image_width..].fill(0);\n\n\tRawImage {\n\t\tdata: image,\n\t\twidth: image_width,\n\t\theight: image_height,\n\t\tcfa_pattern: todo!(),\n\t\t#[allow(unreachable_code)]\n\t\tmaximum: (1 << 12) - 1,\n\t\tblack: SubtractBlack::None,\n\t\torientation: OrientationValue::Horizontal,\n\t\tcamera_model: None,\n\t\tcamera_white_balance: None,\n\t\twhite_balance: None,\n\t\tcamera_to_rgb: None,\n\t}\n}\n\nfn read_and_huffman_decode_file<R: Read + Seek, E: Endianness>(huff: &[u16], file: &mut BitReader<R, E>) -> u32 {\n\tlet number_of_bits = huff[0].into();\n\tlet huffman_table = &huff[1..];\n\n\t// `number_of_bits` will be no more than 32, so the result is put into a u32\n\tlet bits: u32 = file.read_var(number_of_bits).unwrap();\n\tlet bits = bits as usize;\n\n\tlet bits_to_seek_from = huffman_table[bits].to_le_bytes()[1] as i64 - number_of_bits as i64;\n\tfile.seek_bits(std::io::SeekFrom::Current(bits_to_seek_from)).unwrap();\n\n\thuffman_table[bits].to_le_bytes()[0].into()\n}\n\nfn read_n_bits_from_file<R: Read + Seek, E: Endianness>(number_of_bits: u32, file: &mut BitReader<R, E>) -> u32 {\n\t// `number_of_bits` will be no more than 32, so the result is put into a u32\n\tfile.read_var(number_of_bits).unwrap()\n}\n\n/// ljpeg is a lossless variant of JPEG which gets used for decoding the embedded (thumbnail) preview images in raw files\nfn ljpeg_diff<R: Read + Seek, E: Endianness>(huff: &[u16], file: &mut BitReader<R, E>, dng_version: Option<u32>) -> i32 {\n\tlet length = read_and_huffman_decode_file(huff, file);\n\n\tif length == 16 && dng_version.map(|x| x >= 0x1010000).unwrap_or(true) {\n\t\treturn -32768;\n\t}\n\n\tlet diff = read_n_bits_from_file(length, file) as i32;\n\n\tif length == 0 || (diff & (1 << (length - 1))) == 0 { diff - (1 << length) - 1 } else { diff }\n}\n\nfn sony_arw_load_raw<R: Read + Seek>(width: usize, height: usize, file: &mut BitReader<R, BE>) -> Option<Vec<u16>> {\n\tconst TABLE: [u16; 18] = [\n\t\t0x0f11, 0x0f10, 0x0e0f, 0x0d0e, 0x0c0d, 0x0b0c, 0x0a0b, 0x090a, 0x0809, 0x0708, 0x0607, 0x0506, 0x0405, 0x0304, 0x0303, 0x0300, 0x0202, 0x0201,\n\t];\n\n\tlet mut huffman_table = [0_u16; 32770];\n\t// The first element is the number of bits to read\n\thuffman_table[0] = 15;\n\n\tlet mut n = 0;\n\tfor x in TABLE {\n\t\tlet first_byte = x >> 8;\n\t\tlet repeats = 0x8000 >> first_byte;\n\t\tfor _ in 0_u16..repeats {\n\t\t\tn += 1;\n\t\t\thuffman_table[n] = x;\n\t\t}\n\t}\n\n\tlet mut sum = 0;\n\tlet mut image = vec![0_u16; width * height];\n\tfor column in (0..width).rev() {\n\t\tfor row in (0..height).step_by(2).chain((1..height).step_by(2)) {\n\t\t\tsum += ljpeg_diff(&huffman_table, file, None);\n\n\t\t\tif (sum >> 12) != 0 {\n\t\t\t\treturn None;\n\t\t\t}\n\n\t\t\tif row < height {\n\t\t\t\timage[row * width + column] = sum as u16;\n\t\t\t}\n\t\t}\n\t}\n\n\tSome(image)\n}\n"
  },
  {
    "path": "libraries/rawkit/src/decoder/arw2.rs",
    "content": "use crate::tiff::file::{Endian, TiffRead};\nuse crate::tiff::tags::{BitsPerSample, CfaPattern, CfaPatternDim, Compression, ImageLength, ImageWidth, SonyToneCurve, StripByteCounts, StripOffsets, Tag, WhiteBalanceRggbLevels};\nuse crate::tiff::values::{CompressionValue, CurveLookupTable};\nuse crate::tiff::{Ifd, TiffError};\nuse crate::{OrientationValue, RawImage, SubtractBlack};\nuse rawkit_proc_macros::Tag;\nuse std::io::{Read, Seek};\n\n#[allow(dead_code)]\n#[derive(Tag)]\nstruct Arw2Ifd {\n\timage_width: ImageWidth,\n\timage_height: ImageLength,\n\tbits_per_sample: BitsPerSample,\n\tcompression: Compression,\n\tcfa_pattern: CfaPattern,\n\tcfa_pattern_dim: CfaPatternDim,\n\tstrip_offsets: StripOffsets,\n\tstrip_byte_counts: StripByteCounts,\n\tsony_tone_curve: SonyToneCurve,\n\twhite_balance_levels: Option<WhiteBalanceRggbLevels>,\n}\n\npub fn decode<R: Read + Seek>(ifd: Ifd, file: &mut TiffRead<R>) -> RawImage {\n\tlet ifd = ifd.get_value::<Arw2Ifd, _>(file).unwrap();\n\n\tassert!(ifd.strip_offsets.len() == ifd.strip_byte_counts.len());\n\tassert!(ifd.strip_offsets.len() == 1);\n\tassert!(ifd.compression == CompressionValue::Sony_ARW_Compressed);\n\n\tlet image_width: usize = ifd.image_width.try_into().unwrap();\n\tlet image_height: usize = ifd.image_height.try_into().unwrap();\n\tlet bits_per_sample: usize = ifd.bits_per_sample.into();\n\tassert!(bits_per_sample == 12);\n\n\tlet [cfa_pattern_width, cfa_pattern_height] = ifd.cfa_pattern_dim;\n\tassert!(cfa_pattern_width == 2 && cfa_pattern_height == 2);\n\n\tfile.seek_from_start(ifd.strip_offsets[0]).unwrap();\n\tlet mut image = sony_arw2_load_raw(image_width, image_height, ifd.sony_tone_curve, file).unwrap();\n\n\t// Converting the bps from 12 to 14 so that ARW 2.3.1 and 2.3.5 have the same 14 bps.\n\timage.iter_mut().for_each(|x| *x <<= 2);\n\n\tRawImage {\n\t\tdata: image,\n\t\twidth: image_width,\n\t\theight: image_height,\n\t\tcfa_pattern: ifd.cfa_pattern.try_into().unwrap(),\n\t\tmaximum: (1 << 14) - 1,\n\t\tblack: SubtractBlack::CfaGrid([512, 512, 512, 512]), // TODO: Find the correct way to do this\n\t\torientation: OrientationValue::Horizontal,\n\t\tcamera_model: None,\n\t\tcamera_white_balance: ifd.white_balance_levels.map(|arr| arr.map(|x| x as f64)),\n\t\twhite_balance: None,\n\t\tcamera_to_rgb: None,\n\t}\n}\n\nfn as_u32(buffer: &[u8], endian: Endian) -> Option<u32> {\n\tSome(match endian {\n\t\tEndian::Little => u32::from_le_bytes(buffer.try_into().ok()?),\n\t\tEndian::Big => u32::from_be_bytes(buffer.try_into().ok()?),\n\t})\n}\n\nfn as_u16(buffer: &[u8], endian: Endian) -> Option<u16> {\n\tSome(match endian {\n\t\tEndian::Little => u16::from_le_bytes(buffer.try_into().ok()?),\n\t\tEndian::Big => u16::from_be_bytes(buffer.try_into().ok()?),\n\t})\n}\n\nfn sony_arw2_load_raw<R: Read + Seek>(width: usize, height: usize, curve: CurveLookupTable, file: &mut TiffRead<R>) -> Option<Vec<u16>> {\n\tlet mut image = vec![0_u16; height * width];\n\tlet mut data = vec![0_u8; width + 1];\n\n\tfor row in 0..height {\n\t\tfile.read_exact(&mut data[0..width]).unwrap();\n\n\t\tlet mut column = 0;\n\t\tlet mut data_index = 0;\n\n\t\twhile column < width - 30 {\n\t\t\tlet data_value = as_u32(&data[data_index..][..4], file.endian()).unwrap();\n\t\t\tlet max = (0x7ff & data_value) as u16;\n\t\t\tlet min = (0x7ff & data_value >> 11) as u16;\n\t\t\tlet index_to_set_max = 0x0f & data_value >> 22;\n\t\t\tlet index_to_set_min = 0x0f & data_value >> 26;\n\n\t\t\tlet max_minus_min = max as i32 - min as i32;\n\t\t\tlet shift_by_bits = (0..4).find(|&shift| (0x80 << shift) > max_minus_min).unwrap_or(4);\n\n\t\t\tlet mut pixels = [0_u16; 16];\n\t\t\tlet mut bit = 30;\n\t\t\tfor (i, pixel) in pixels.iter_mut().enumerate() {\n\t\t\t\t*pixel = match () {\n\t\t\t\t\t_ if i as u32 == index_to_set_max => max,\n\t\t\t\t\t_ if i as u32 == index_to_set_min => min,\n\t\t\t\t\t_ => {\n\t\t\t\t\t\tlet result = as_u16(&data[(data_index + (bit >> 3))..][..2], file.endian()).unwrap();\n\t\t\t\t\t\tlet result = ((result >> (bit & 7)) & 0x07f) << shift_by_bits;\n\n\t\t\t\t\t\tbit += 7;\n\n\t\t\t\t\t\t(result + min).min(0x7ff)\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tfor value in pixels {\n\t\t\t\timage[row * width + column] = curve.get((value << 1).into()) >> 2;\n\n\t\t\t\t// Skip between interlaced columns\n\t\t\t\tcolumn += 2;\n\t\t\t}\n\n\t\t\t// Switch to the opposite interlaced columns\n\t\t\tcolumn -= if column & 1 == 0 { 31 } else { 1 };\n\n\t\t\tdata_index += 16;\n\t\t}\n\t}\n\n\tSome(image)\n}\n"
  },
  {
    "path": "libraries/rawkit/src/decoder/mod.rs",
    "content": "pub mod arw1;\npub mod arw2;\npub mod uncompressed;\n"
  },
  {
    "path": "libraries/rawkit/src/decoder/uncompressed.rs",
    "content": "use crate::tiff::file::TiffRead;\nuse crate::tiff::tags::{BitsPerSample, BlackLevel, CfaPattern, CfaPatternDim, Compression, ImageLength, ImageWidth, RowsPerStrip, StripByteCounts, StripOffsets, Tag, WhiteBalanceRggbLevels};\nuse crate::tiff::values::CompressionValue;\nuse crate::tiff::{Ifd, TiffError};\nuse crate::{OrientationValue, RawImage, SubtractBlack};\nuse rawkit_proc_macros::Tag;\nuse std::io::{Read, Seek};\n\n#[allow(dead_code)]\n#[derive(Tag)]\nstruct ArwUncompressedIfd {\n\timage_width: ImageWidth,\n\timage_height: ImageLength,\n\trows_per_strip: RowsPerStrip,\n\tbits_per_sample: BitsPerSample,\n\tcompression: Compression,\n\tblack_level: BlackLevel,\n\tcfa_pattern: CfaPattern,\n\tcfa_pattern_dim: CfaPatternDim,\n\tstrip_offsets: StripOffsets,\n\tstrip_byte_counts: StripByteCounts,\n\twhite_balance_levels: Option<WhiteBalanceRggbLevels>,\n}\n\npub fn decode<R: Read + Seek>(ifd: Ifd, file: &mut TiffRead<R>) -> RawImage {\n\tlet ifd = ifd.get_value::<ArwUncompressedIfd, _>(file).unwrap();\n\n\tassert!(ifd.strip_offsets.len() == ifd.strip_byte_counts.len());\n\tassert!(ifd.strip_offsets.len() == 1);\n\tassert!(ifd.compression == CompressionValue::Uncompressed);\n\n\tlet image_width: usize = ifd.image_width.try_into().unwrap();\n\tlet image_height: usize = ifd.image_height.try_into().unwrap();\n\tlet rows_per_strip: usize = ifd.rows_per_strip.try_into().unwrap();\n\tlet bits_per_sample: usize = ifd.bits_per_sample.into();\n\tlet [cfa_pattern_width, cfa_pattern_height] = ifd.cfa_pattern_dim;\n\tassert!(cfa_pattern_width == 2 && cfa_pattern_height == 2);\n\n\tlet mut image: Vec<u16> = Vec::with_capacity(image_height * image_width);\n\n\tfor i in 0..ifd.strip_offsets.len() {\n\t\tfile.seek_from_start(ifd.strip_offsets[i]).unwrap();\n\n\t\tlet last = i == ifd.strip_offsets.len();\n\t\tlet rows = if last { image_height % rows_per_strip } else { rows_per_strip };\n\n\t\tfor _ in 0..rows {\n\t\t\tfor _ in 0..image_width {\n\t\t\t\timage.push(file.read_u16().unwrap());\n\t\t\t}\n\t\t}\n\t}\n\n\tRawImage {\n\t\tdata: image,\n\t\twidth: image_width,\n\t\theight: image_height,\n\t\tcfa_pattern: ifd.cfa_pattern.try_into().unwrap(),\n\t\tmaximum: if bits_per_sample == 16 { u16::MAX } else { (1 << bits_per_sample) - 1 },\n\t\tblack: SubtractBlack::CfaGrid(ifd.black_level),\n\t\torientation: OrientationValue::Horizontal,\n\t\tcamera_model: None,\n\t\tcamera_white_balance: ifd.white_balance_levels.map(|arr| arr.map(|x| x as f64)),\n\t\twhite_balance: None,\n\t\tcamera_to_rgb: None,\n\t}\n}\n"
  },
  {
    "path": "libraries/rawkit/src/demosaicing/linear_demosaicing.rs",
    "content": "use crate::{Pixel, RawImage};\n\nfn average(data: &[u16], indexes: impl Iterator<Item = i64>) -> u16 {\n\tlet mut sum = 0;\n\tlet mut count = 0;\n\tfor index in indexes {\n\t\tif index >= 0 && (index as usize) < data.len() {\n\t\t\tsum += data[index as usize] as u32;\n\t\t\tcount += 1;\n\t\t}\n\t}\n\n\t(sum / count) as u16\n}\n\nimpl RawImage {\n\tpub fn linear_demosaic_iter(&self) -> impl Iterator<Item = Pixel> + use<'_> {\n\t\tmatch self.cfa_pattern {\n\t\t\t[0, 1, 1, 2] => self.linear_demosaic_rggb_iter(),\n\t\t\t_ => todo!(),\n\t\t}\n\t}\n\n\tfn linear_demosaic_rggb_iter(&self) -> impl Iterator<Item = Pixel> + use<'_> {\n\t\tlet width = self.width as i64;\n\t\tlet height = self.height as i64;\n\n\t\t(0..height).flat_map(move |row| {\n\t\t\tlet row_by_width = row * width;\n\n\t\t\t(0..width).map(move |column| {\n\t\t\t\tlet pixel_index = row_by_width + column;\n\n\t\t\t\tlet vertical_indexes = [pixel_index + width, pixel_index - width];\n\t\t\t\tlet horizontal_indexes = [pixel_index + 1, pixel_index - 1];\n\t\t\t\tlet cross_indexes = [pixel_index + width, pixel_index - width, pixel_index + 1, pixel_index - 1];\n\t\t\t\tlet diagonal_indexes = [pixel_index + width + 1, pixel_index - width + 1, pixel_index + width - 1, pixel_index - width - 1];\n\n\t\t\t\tlet pixel_index = pixel_index as usize;\n\t\t\t\tmatch (row % 2 == 0, column % 2 == 0) {\n\t\t\t\t\t(true, true) => Pixel {\n\t\t\t\t\t\tvalues: [\n\t\t\t\t\t\t\tself.data[pixel_index],\n\t\t\t\t\t\t\taverage(&self.data, cross_indexes.into_iter()),\n\t\t\t\t\t\t\taverage(&self.data, diagonal_indexes.into_iter()),\n\t\t\t\t\t\t],\n\t\t\t\t\t\trow: row as usize,\n\t\t\t\t\t\tcolumn: column as usize,\n\t\t\t\t\t},\n\t\t\t\t\t(true, false) => Pixel {\n\t\t\t\t\t\tvalues: [\n\t\t\t\t\t\t\taverage(&self.data, horizontal_indexes.into_iter()),\n\t\t\t\t\t\t\tself.data[pixel_index],\n\t\t\t\t\t\t\taverage(&self.data, vertical_indexes.into_iter()),\n\t\t\t\t\t\t],\n\t\t\t\t\t\trow: row as usize,\n\t\t\t\t\t\tcolumn: column as usize,\n\t\t\t\t\t},\n\t\t\t\t\t(false, true) => Pixel {\n\t\t\t\t\t\tvalues: [\n\t\t\t\t\t\t\taverage(&self.data, vertical_indexes.into_iter()),\n\t\t\t\t\t\t\tself.data[pixel_index],\n\t\t\t\t\t\t\taverage(&self.data, horizontal_indexes.into_iter()),\n\t\t\t\t\t\t],\n\t\t\t\t\t\trow: row as usize,\n\t\t\t\t\t\tcolumn: column as usize,\n\t\t\t\t\t},\n\t\t\t\t\t(false, false) => Pixel {\n\t\t\t\t\t\tvalues: [\n\t\t\t\t\t\t\taverage(&self.data, diagonal_indexes.into_iter()),\n\t\t\t\t\t\t\taverage(&self.data, cross_indexes.into_iter()),\n\t\t\t\t\t\t\tself.data[pixel_index],\n\t\t\t\t\t\t],\n\t\t\t\t\t\trow: row as usize,\n\t\t\t\t\t\tcolumn: column as usize,\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "libraries/rawkit/src/demosaicing/mod.rs",
    "content": "pub mod linear_demosaicing;\n"
  },
  {
    "path": "libraries/rawkit/src/lib.rs",
    "content": "pub mod decoder;\npub mod demosaicing;\npub mod metadata;\npub mod postprocessing;\npub mod preprocessing;\npub mod processing;\npub mod tiff;\n\nuse crate::metadata::identify::CameraModel;\nuse processing::{Pixel, PixelTransform, RawPixel, RawPixelTransform};\nuse rawkit_proc_macros::Tag;\nuse std::io::{Read, Seek};\nuse thiserror::Error;\nuse tiff::file::TiffRead;\nuse tiff::tags::{Compression, ImageLength, ImageWidth, Orientation, StripByteCounts, SubIfd, Tag, ThumbnailLength, ThumbnailOffset};\nuse tiff::values::{CompressionValue, OrientationValue};\nuse tiff::{Ifd, TiffError};\n\npub(crate) const CHANNELS_IN_RGB: usize = 3;\npub(crate) type Histogram = [[usize; 0x2000]; CHANNELS_IN_RGB];\n\npub enum ThumbnailFormat {\n\tJpeg,\n\tUnsupported,\n}\n\n/// A thumbnail image extracted from the raw file. This is usually a JPEG image.\npub struct ThumbnailImage {\n\tpub data: Vec<u8>,\n\tpub format: ThumbnailFormat,\n}\n\n/// The amount of black level to be subtracted from Raw Image.\npub enum SubtractBlack {\n\t/// Don't subtract any value.\n\tNone,\n\n\t/// Subtract a singular value for all pixels in Bayer CFA Grid.\n\tValue(u16),\n\n\t/// Subtract the appropriate value for pixels in Bayer CFA Grid.\n\tCfaGrid([u16; 4]),\n}\n\n/// Represents a Raw Image along with its metadata.\npub struct RawImage {\n\t/// Raw pixel data stored in linear fashion.\n\tpub data: Vec<u16>,\n\n\t/// Width of the raw image.\n\tpub width: usize,\n\n\t/// Height of the raw image.\n\tpub height: usize,\n\n\t/// Bayer CFA pattern used to arrange pixels in [`RawImage::data`].\n\t///\n\t/// It encodes Red, Blue and Green as 0, 1, and 2 respectively.\n\tpub cfa_pattern: [u8; 4],\n\n\t/// Transformation to be applied to negate the orientation of camera.\n\tpub orientation: OrientationValue,\n\n\t/// The maximum possible value of pixel that the camera sensor could give.\n\tpub maximum: u16,\n\n\t/// The minimum possible value of pixel that the camera sensor could give.\n\t///\n\t/// Used to subtract the black level from the raw image.\n\tpub black: SubtractBlack,\n\n\t/// Information regarding the company and model of the camera.\n\tpub camera_model: Option<CameraModel>,\n\n\t/// White balance specified in the metadata of the raw file.\n\t///\n\t/// It represents the 4 values of CFA Grid which follows the same pattern as [`RawImage::cfa_pattern`].\n\tpub camera_white_balance: Option<[f64; 4]>,\n\n\t/// White balance of the raw image.\n\t///\n\t/// It is the same as [`RawImage::camera_white_balance`] if the raw file contains the metadata.\n\t/// Otherwise it falls back to calculating the white balance from the color space conversion matrix.\n\t///\n\t/// It represents the 4 values of CFA Grid which follows the same pattern as [`RawImage::cfa_pattern`].\n\tpub white_balance: Option<[f64; 4]>,\n\n\t/// Color space conversion matrix to convert from camera's color space to sRGB.\n\tpub camera_to_rgb: Option<[[f64; 3]; 3]>,\n}\n\n/// Represents the final RGB Image.\npub struct Image<T> {\n\t/// Pixel data stored in a linear fashion.\n\tpub data: Vec<T>,\n\n\t/// Width of the image.\n\tpub width: usize,\n\n\t/// Height of the image.\n\tpub height: usize,\n\n\t/// The number of color channels in the image.\n\t///\n\t/// We can assume this will be 3 for all non-obscure, modern cameras.\n\t/// See <https://github.com/GraphiteEditor/Graphite/pull/1923#discussion_r1725070342> for more information.\n\tpub channels: u8,\n\n\t/// The transformation required to orient the image correctly.\n\t///\n\t/// This will be [`OrientationValue::Horizontal`] after the orientation step is applied.\n\tpub orientation: OrientationValue,\n}\n\n#[allow(dead_code)]\n#[derive(Tag)]\nstruct ArwIfd {\n\timage_width: ImageWidth,\n\timage_height: ImageLength,\n\tcompression: Compression,\n\tstrip_byte_counts: StripByteCounts,\n}\n\nimpl RawImage {\n\t/// Create a [`RawImage`] from an input stream.\n\t///\n\t/// Decodes the contents of `reader` and extracts raw pixel data and metadata.\n\tpub fn decode<R: Read + Seek>(reader: &mut R) -> Result<RawImage, DecoderError> {\n\t\tlet mut file = TiffRead::new(reader)?;\n\t\tlet ifd = Ifd::new_first_ifd(&mut file)?;\n\n\t\tlet camera_model = metadata::identify::identify_camera_model(&ifd, &mut file).unwrap();\n\t\tlet orientation = ifd.get_value::<Orientation, _>(&mut file)?;\n\n\t\tlet mut raw_image = if camera_model.model == \"DSLR-A100\" {\n\t\t\tdecoder::arw1::decode_a100(ifd, &mut file)\n\t\t} else {\n\t\t\tlet sub_ifd = ifd.get_value::<SubIfd, _>(&mut file)?;\n\t\t\tlet arw_ifd = sub_ifd.get_value::<ArwIfd, _>(&mut file)?;\n\n\t\t\tif arw_ifd.compression == CompressionValue::Uncompressed {\n\t\t\t\tdecoder::uncompressed::decode(sub_ifd, &mut file)\n\t\t\t} else if arw_ifd.strip_byte_counts[0] == arw_ifd.image_width * arw_ifd.image_height {\n\t\t\t\tdecoder::arw2::decode(sub_ifd, &mut file)\n\t\t\t} else {\n\t\t\t\t// TODO: implement for arw 1.\n\t\t\t\ttodo!()\n\t\t\t}\n\t\t};\n\n\t\traw_image.camera_model = Some(camera_model);\n\t\traw_image.orientation = orientation;\n\n\t\traw_image.calculate_conversion_matrices();\n\n\t\tOk(raw_image)\n\t}\n\n\t/// Extracts the thumbnail image from the raw file.\n\tpub fn extract_thumbnail<R: Read + Seek>(reader: &mut R) -> Result<ThumbnailImage, DecoderError> {\n\t\tlet mut file = TiffRead::new(reader)?;\n\t\tlet ifd = Ifd::new_first_ifd(&mut file)?;\n\n\t\t// TODO: ARW files Store the thumbnail offset and length in the first IFD. Add support for other file types in the future.\n\t\tlet thumbnail_offset = ifd.get_value::<ThumbnailOffset, _>(&mut file)?;\n\t\tlet thumbnail_length = ifd.get_value::<ThumbnailLength, _>(&mut file)?;\n\t\tfile.seek_from_start(thumbnail_offset)?;\n\n\t\tlet mut thumbnail_data = vec![0; thumbnail_length as usize];\n\t\tfile.read_exact(&mut thumbnail_data)?;\n\n\t\t// Check the first two bytes to determine the format of the thumbnail.\n\t\t// JPEG format starts with 0xFF, 0xD8.\n\t\tif thumbnail_data[0..2] == [0xFF, 0xD8] {\n\t\t\tOk(ThumbnailImage {\n\t\t\t\tdata: thumbnail_data,\n\t\t\t\tformat: ThumbnailFormat::Jpeg,\n\t\t\t})\n\t\t} else {\n\t\t\tErr(DecoderError::UnsupportedThumbnailFormat)\n\t\t}\n\t}\n\n\t/// Converts the [`RawImage`] to an [`Image`] with 8 bit resolution for each channel.\n\t///\n\t/// Applies all the processing steps to finally get RGB pixel data.\n\tpub fn process_8bit(self) -> Image<u8> {\n\t\tlet image = self.process_16bit();\n\n\t\tImage {\n\t\t\tchannels: image.channels,\n\t\t\tdata: image.data.iter().map(|x| (x >> 8) as u8).collect(),\n\t\t\twidth: image.width,\n\t\t\theight: image.height,\n\t\t\torientation: image.orientation,\n\t\t}\n\t}\n\n\t/// Converts the [`RawImage`] to an [`Image`] with 16 bit resolution for each channel.\n\t///\n\t/// Applies all the processing steps to finally get RGB pixel data.\n\tpub fn process_16bit(self) -> Image<u16> {\n\t\tlet subtract_black = self.subtract_black_fn();\n\t\tlet scale_white_balance = self.scale_white_balance_fn();\n\t\tlet scale_to_16bit = self.scale_to_16bit_fn();\n\t\tlet raw_image = self.apply((subtract_black, scale_white_balance, scale_to_16bit));\n\n\t\tlet convert_to_rgb = raw_image.convert_to_rgb_fn();\n\t\tlet mut record_histogram = raw_image.record_histogram_fn();\n\t\tlet image = raw_image.demosaic_and_apply((convert_to_rgb, &mut record_histogram));\n\n\t\tlet gamma_correction = image.gamma_correction_fn(&record_histogram.histogram);\n\t\tif image.orientation == OrientationValue::Horizontal {\n\t\t\timage.apply(gamma_correction)\n\t\t} else {\n\t\t\timage.transform_and_apply(gamma_correction)\n\t\t}\n\t}\n}\n\nimpl RawImage {\n\tpub fn apply(mut self, mut transform: impl RawPixelTransform) -> RawImage {\n\t\tfor (index, value) in self.data.iter_mut().enumerate() {\n\t\t\tlet pixel = RawPixel {\n\t\t\t\tvalue: *value,\n\t\t\t\trow: index / self.width,\n\t\t\t\tcolumn: index % self.width,\n\t\t\t};\n\t\t\t*value = transform.apply(pixel);\n\t\t}\n\n\t\tself\n\t}\n\n\tpub fn demosaic_and_apply(self, mut transform: impl PixelTransform) -> Image<u16> {\n\t\tlet mut image = vec![0; self.width * self.height * 3];\n\t\tfor Pixel { values, row, column } in self.linear_demosaic_iter().map(|mut pixel| {\n\t\t\tpixel.values = transform.apply(pixel);\n\t\t\tpixel\n\t\t}) {\n\t\t\tlet pixel_index = row * self.width + column;\n\t\t\timage[3 * pixel_index..3 * (pixel_index + 1)].copy_from_slice(&values);\n\t\t}\n\n\t\tImage {\n\t\t\tchannels: 3,\n\t\t\tdata: image,\n\t\t\twidth: self.width,\n\t\t\theight: self.height,\n\t\t\torientation: self.orientation,\n\t\t}\n\t}\n}\n\nimpl Image<u16> {\n\tpub fn apply(mut self, mut transform: impl PixelTransform) -> Image<u16> {\n\t\tfor (index, values) in self.data.chunks_exact_mut(3).enumerate() {\n\t\t\tlet pixel = Pixel {\n\t\t\t\tvalues: values.try_into().unwrap(),\n\t\t\t\trow: index / self.width,\n\t\t\t\tcolumn: index % self.width,\n\t\t\t};\n\t\t\tvalues.copy_from_slice(&transform.apply(pixel));\n\t\t}\n\n\t\tself\n\t}\n\n\tpub fn transform_and_apply(self, mut transform: impl PixelTransform) -> Image<u16> {\n\t\tlet mut image = vec![0; self.width * self.height * 3];\n\t\tlet (width, height, iter) = self.orientation_iter();\n\t\tfor Pixel { values, row, column } in iter.map(|mut pixel| {\n\t\t\tpixel.values = transform.apply(pixel);\n\t\t\tpixel\n\t\t}) {\n\t\t\tlet pixel_index = row * width + column;\n\t\t\timage[3 * pixel_index..3 * (pixel_index + 1)].copy_from_slice(&values);\n\t\t}\n\n\t\tImage {\n\t\t\tchannels: 3,\n\t\t\tdata: image,\n\t\t\twidth,\n\t\t\theight,\n\t\t\torientation: OrientationValue::Horizontal,\n\t\t}\n\t}\n}\n\n#[derive(Error, Debug)]\npub enum DecoderError {\n\t#[error(\"An error occurred when trying to parse the TIFF format\")]\n\tTiffError(#[from] TiffError),\n\t#[error(\"An error occurred when converting integer from one type to another\")]\n\tConversionError(#[from] std::num::TryFromIntError),\n\t#[error(\"An IO Error ocurred\")]\n\tIoError(#[from] std::io::Error),\n\t#[error(\"The thumbnail format is unsupported\")]\n\tUnsupportedThumbnailFormat,\n}\n"
  },
  {
    "path": "libraries/rawkit/src/metadata/camera_data.rs",
    "content": "use crate::RawImage;\nuse rawkit_proc_macros::build_camera_data;\n\npub struct CameraData {\n\tpub black: u16,\n\tpub maximum: u16,\n\tpub xyz_to_camera: [i16; 9],\n}\n\nimpl CameraData {\n\tconst DEFAULT: CameraData = CameraData {\n\t\tblack: 0,\n\t\tmaximum: 0,\n\t\txyz_to_camera: [0; 9],\n\t};\n}\n\nconst CAMERA_DATA: [(&str, CameraData); 40] = build_camera_data!();\n\nconst RGB_TO_XYZ: [[f64; 3]; 3] = [\n\t// Matrix:\n\t[0.412453, 0.357580, 0.180423],\n\t[0.212671, 0.715160, 0.072169],\n\t[0.019334, 0.119193, 0.950227],\n];\n\nimpl RawImage {\n\tpub fn calculate_conversion_matrices(&mut self) {\n\t\tlet Some(ref camera_model) = self.camera_model else { return };\n\t\tlet camera_name_needle = camera_model.make.to_owned() + \" \" + &camera_model.model;\n\n\t\tlet xyz_to_camera = CAMERA_DATA\n\t\t\t.iter()\n\t\t\t.find(|(camera_name_haystack, _)| camera_name_needle == *camera_name_haystack)\n\t\t\t.map(|(_, data)| data.xyz_to_camera.map(|x| (x as f64) / 10_000.));\n\t\tlet Some(xyz_to_camera) = xyz_to_camera else { return };\n\n\t\tlet mut rgb_to_camera = [[0.; 3]; 3];\n\t\tfor i in 0..3 {\n\t\t\tfor j in 0..3 {\n\t\t\t\tfor k in 0..3 {\n\t\t\t\t\trgb_to_camera[i][j] += RGB_TO_XYZ[k][j] * xyz_to_camera[i * 3 + k];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tlet white_balance_multiplier = rgb_to_camera.map(|x| 1. / x.iter().sum::<f64>());\n\t\tfor (index, row) in rgb_to_camera.iter_mut().enumerate() {\n\t\t\t*row = row.map(|x| x * white_balance_multiplier[index]);\n\t\t}\n\t\tlet camera_to_rgb = transpose(pseudoinverse(rgb_to_camera));\n\n\t\tlet cfa_white_balance_multiplier = if let Some(white_balance) = self.camera_white_balance {\n\t\t\twhite_balance\n\t\t} else {\n\t\t\tself.cfa_pattern.map(|index| white_balance_multiplier[index as usize])\n\t\t};\n\n\t\tself.white_balance = Some(cfa_white_balance_multiplier);\n\t\tself.camera_to_rgb = Some(camera_to_rgb);\n\t}\n}\n\n#[allow(clippy::needless_range_loop)]\nfn pseudoinverse<const N: usize>(matrix: [[f64; 3]; N]) -> [[f64; 3]; N] {\n\tlet mut output_matrix = [[0.; 3]; N];\n\tlet mut work = [[0.; 6]; 3];\n\n\tfor i in 0..3 {\n\t\tfor j in 0..6 {\n\t\t\twork[i][j] = if j == i + 3 { 1. } else { 0. };\n\t\t}\n\t\tfor j in 0..3 {\n\t\t\tfor k in 0..N {\n\t\t\t\twork[i][j] += matrix[k][i] * matrix[k][j];\n\t\t\t}\n\t\t}\n\t}\n\n\tfor i in 0..3 {\n\t\tlet num = work[i][i];\n\t\tfor j in 0..6 {\n\t\t\twork[i][j] /= num;\n\t\t}\n\t\tfor k in 0..3 {\n\t\t\tif k == i {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tlet num = work[k][i];\n\t\t\tfor j in 0..6 {\n\t\t\t\twork[k][j] -= work[i][j] * num;\n\t\t\t}\n\t\t}\n\t}\n\n\tfor i in 0..N {\n\t\tfor j in 0..3 {\n\t\t\toutput_matrix[i][j] = 0.;\n\t\t\tfor k in 0..3 {\n\t\t\t\toutput_matrix[i][j] += work[j][k + 3] * matrix[i][k];\n\t\t\t}\n\t\t}\n\t}\n\n\toutput_matrix\n}\n\nfn transpose<const N: usize>(matrix: [[f64; 3]; N]) -> [[f64; N]; 3] {\n\tlet mut output_matrix = [[0.; N]; 3];\n\n\tfor (i, row) in matrix.iter().enumerate() {\n\t\tfor (j, &value) in row.iter().enumerate() {\n\t\t\toutput_matrix[j][i] = value;\n\t\t}\n\t}\n\n\toutput_matrix\n}\n"
  },
  {
    "path": "libraries/rawkit/src/metadata/identify.rs",
    "content": "use crate::tiff::file::TiffRead;\nuse crate::tiff::tags::{Make, Model, Tag};\nuse crate::tiff::{Ifd, TiffError};\nuse rawkit_proc_macros::Tag;\nuse std::io::{Read, Seek};\n\nconst COMPANY_NAMES: [&str; 22] = [\n\t\"AgfaPhoto\",\n\t\"Canon\",\n\t\"Casio\",\n\t\"Epson\",\n\t\"Fujifilm\",\n\t\"Mamiya\",\n\t\"Minolta\",\n\t\"Motorola\",\n\t\"Kodak\",\n\t\"Konica\",\n\t\"Leica\",\n\t\"Nikon\",\n\t\"Nokia\",\n\t\"Olympus\",\n\t\"Ricoh\",\n\t\"Pentax\",\n\t\"Phase One\",\n\t\"Samsung\",\n\t\"Sigma\",\n\t\"Sinar\",\n\t\"Sony\",\n\t\"YI\",\n];\n\n#[allow(dead_code)]\n#[derive(Tag)]\nstruct CameraModelIfd {\n\tmake: Make,\n\tmodel: Model,\n}\n\npub struct CameraModel {\n\tpub make: String,\n\tpub model: String,\n}\n\npub fn identify_camera_model<R: Read + Seek>(ifd: &Ifd, file: &mut TiffRead<R>) -> Option<CameraModel> {\n\tlet mut ifd = ifd.get_value::<CameraModelIfd, _>(file).unwrap();\n\n\tifd.make.make_ascii_lowercase();\n\tfor company_name in COMPANY_NAMES {\n\t\tlet lowercase_company_name = company_name.to_ascii_lowercase();\n\t\tif ifd.make.contains(&lowercase_company_name) {\n\t\t\treturn Some(CameraModel {\n\t\t\t\tmake: company_name.to_string(),\n\t\t\t\tmodel: ifd.model,\n\t\t\t});\n\t\t}\n\t}\n\n\tNone\n}\n"
  },
  {
    "path": "libraries/rawkit/src/metadata/mod.rs",
    "content": "pub mod camera_data;\npub mod identify;\n"
  },
  {
    "path": "libraries/rawkit/src/postprocessing/convert_to_rgb.rs",
    "content": "use crate::{CHANNELS_IN_RGB, Pixel, RawImage};\n\nimpl RawImage {\n\tpub fn convert_to_rgb_fn(&self) -> impl Fn(Pixel) -> [u16; CHANNELS_IN_RGB] + use<> {\n\t\tlet Some(camera_to_rgb) = self.camera_to_rgb else { todo!() };\n\n\t\tmove |pixel: Pixel| {\n\t\t\tstd::array::from_fn(|i| i)\n\t\t\t\t.map(|i| camera_to_rgb[i].iter().zip(pixel.values.iter()).map(|(&coeff, &value)| coeff * value as f64).sum())\n\t\t\t\t.map(|x: f64| (x as u16).clamp(0, u16::MAX))\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "libraries/rawkit/src/postprocessing/gamma_correction.rs",
    "content": "use crate::{CHANNELS_IN_RGB, Histogram, Image, Pixel};\nuse std::f64::consts::E;\n\nimpl Image<u16> {\n\tpub fn gamma_correction_fn(&self, histogram: &Histogram) -> impl Fn(Pixel) -> [u16; CHANNELS_IN_RGB] + use<> {\n\t\tlet percentage = self.width * self.height;\n\n\t\tlet mut white = 0;\n\t\tfor channel_histogram in histogram {\n\t\t\tlet mut total = 0;\n\t\t\tfor i in (0x20..0x2000).rev() {\n\t\t\t\ttotal += channel_histogram[i] as u64;\n\n\t\t\t\tif total * 100 > percentage as u64 {\n\t\t\t\t\twhite = white.max(i);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tlet curve = generate_gamma_curve(0.45, 4.5, (white << 3) as f64);\n\n\t\tmove |pixel: Pixel| pixel.values.map(|value| curve[value as usize])\n\t}\n}\n\n/// `max_intensity` must be non-zero.\nfn generate_gamma_curve(power: f64, threshold: f64, max_intensity: f64) -> Vec<u16> {\n\tdebug_assert!(max_intensity != 0.);\n\n\tlet (mut bound_start, mut bound_end) = if threshold >= 1. { (0., 1.) } else { (1., 0.) };\n\n\tlet mut transition_point = 0.;\n\tlet mut transition_ratio = 0.;\n\tlet mut curve_adjustment = 0.;\n\n\tif threshold != 0. && (threshold - 1.) * (power - 1.) <= 0. {\n\t\tfor _ in 0..48 {\n\t\t\ttransition_point = (bound_start + bound_end) / 2.;\n\n\t\t\tif power != 0. {\n\t\t\t\tlet temp_transition_ratio = transition_point / threshold;\n\t\t\t\tlet exponential_power = temp_transition_ratio.powf(-power);\n\t\t\t\tlet normalized_exponential_power = (exponential_power - 1.) / power;\n\t\t\t\tlet comparison_result = normalized_exponential_power - (1. / transition_point);\n\n\t\t\t\tlet bound_to_update = if comparison_result > -1. { &mut bound_end } else { &mut bound_start };\n\t\t\t\t*bound_to_update = transition_point;\n\t\t\t} else {\n\t\t\t\tlet adjusted_transition_point = E.powf(1. - 1. / transition_point);\n\t\t\t\tlet transition_point_ratio = transition_point / adjusted_transition_point;\n\n\t\t\t\tlet bound_to_update = if transition_point_ratio < threshold { &mut bound_end } else { &mut bound_start };\n\t\t\t\t*bound_to_update = transition_point;\n\t\t\t}\n\t\t}\n\n\t\ttransition_ratio = transition_point / threshold;\n\n\t\tif power != 0. {\n\t\t\tcurve_adjustment = transition_point * ((1. / power) - 1.);\n\t\t}\n\t}\n\n\tlet mut curve = vec![0xffff; 0x1_0000];\n\tlet length = curve.len() as f64;\n\n\tfor (i, entry) in curve.iter_mut().enumerate() {\n\t\tlet ratio = (i as f64) / max_intensity;\n\t\tif ratio < 1. {\n\t\t\tlet altered_ratio = if ratio < transition_ratio {\n\t\t\t\tratio * threshold\n\t\t\t} else if power != 0. {\n\t\t\t\tratio.powf(power) * (1. + curve_adjustment) - curve_adjustment\n\t\t\t} else {\n\t\t\t\tratio.ln() * transition_point + 1.\n\t\t\t};\n\n\t\t\t*entry = (length * altered_ratio) as u16;\n\t\t}\n\t}\n\n\tcurve\n}\n"
  },
  {
    "path": "libraries/rawkit/src/postprocessing/mod.rs",
    "content": "pub mod convert_to_rgb;\npub mod gamma_correction;\npub mod record_histogram;\npub mod transform;\n"
  },
  {
    "path": "libraries/rawkit/src/postprocessing/record_histogram.rs",
    "content": "use crate::{CHANNELS_IN_RGB, Histogram, Pixel, PixelTransform, RawImage};\n\nimpl RawImage {\n\tpub fn record_histogram_fn(&self) -> RecordHistogram {\n\t\tRecordHistogram::new()\n\t}\n}\n\npub struct RecordHistogram {\n\tpub histogram: Histogram,\n}\n\nimpl RecordHistogram {\n\tfn new() -> RecordHistogram {\n\t\tRecordHistogram {\n\t\t\thistogram: [[0; 0x2000]; CHANNELS_IN_RGB],\n\t\t}\n\t}\n}\n\nimpl PixelTransform for &mut RecordHistogram {\n\tfn apply(&mut self, pixel: Pixel) -> [u16; CHANNELS_IN_RGB] {\n\t\tself.histogram\n\t\t\t.iter_mut()\n\t\t\t.zip(pixel.values.iter())\n\t\t\t.for_each(|(histogram, &value)| histogram[value as usize >> CHANNELS_IN_RGB] += 1);\n\t\tpixel.values\n\t}\n}\n"
  },
  {
    "path": "libraries/rawkit/src/postprocessing/transform.rs",
    "content": "use crate::{Image, OrientationValue, Pixel};\n\nimpl Image<u16> {\n\tpub fn orientation_iter(&self) -> (usize, usize, impl Iterator<Item = Pixel> + use<'_>) {\n\t\tlet (final_width, final_height) = if self.orientation.will_swap_coordinates() {\n\t\t\t(self.height, self.width)\n\t\t} else {\n\t\t\t(self.width, self.height)\n\t\t};\n\n\t\tlet index_0_0 = inverse_orientation_index(self.orientation, 0, 0, self.width, self.height);\n\t\tlet index_0_1 = inverse_orientation_index(self.orientation, 0, 1, self.width, self.height);\n\t\tlet index_1_0 = inverse_orientation_index(self.orientation, 1, 0, self.width, self.height);\n\n\t\tlet column_step = (index_0_1.0 - index_0_0.0, index_0_1.1 - index_0_0.1);\n\t\tlet row_step = (index_1_0.0 - index_0_0.0, index_1_0.1 - index_0_0.1);\n\t\tlet mut index = index_0_0;\n\n\t\tlet channels = self.channels as usize;\n\n\t\t(\n\t\t\tfinal_width,\n\t\t\tfinal_height,\n\t\t\t(0..final_height).flat_map(move |row| {\n\t\t\t\tlet temp = (0..final_width).map(move |column| {\n\t\t\t\t\tlet initial_index = (self.width as i64 * index.0 + index.1) as usize;\n\t\t\t\t\tlet pixel = &self.data[channels * initial_index..channels * (initial_index + 1)];\n\t\t\t\t\tindex = (index.0 + column_step.0, index.1 + column_step.1);\n\n\t\t\t\t\tPixel {\n\t\t\t\t\t\tvalues: pixel.try_into().unwrap(),\n\t\t\t\t\t\trow,\n\t\t\t\t\t\tcolumn,\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tindex = (index.0 + row_step.0, index.1 + row_step.1);\n\n\t\t\t\ttemp\n\t\t\t}),\n\t\t)\n\t}\n}\n\npub fn inverse_orientation_index(orientation: OrientationValue, mut row: usize, mut column: usize, width: usize, height: usize) -> (i64, i64) {\n\tlet value = match orientation {\n\t\tOrientationValue::Horizontal => 0,\n\t\tOrientationValue::MirrorHorizontal => 1,\n\t\tOrientationValue::Rotate180 => 3,\n\t\tOrientationValue::MirrorVertical => 2,\n\t\tOrientationValue::MirrorHorizontalRotate270 => 4,\n\t\tOrientationValue::Rotate90 => 6,\n\t\tOrientationValue::MirrorHorizontalRotate90 => 7,\n\t\tOrientationValue::Rotate270 => 5,\n\t};\n\n\tif value & 4 != 0 {\n\t\tstd::mem::swap(&mut row, &mut column)\n\t}\n\n\tif value & 2 != 0 {\n\t\trow = height - 1 - row;\n\t}\n\n\tif value & 1 != 0 {\n\t\tcolumn = width - 1 - column;\n\t}\n\n\t(row as i64, column as i64)\n}\n"
  },
  {
    "path": "libraries/rawkit/src/preprocessing/mod.rs",
    "content": "pub mod scale_to_16bit;\npub mod scale_white_balance;\npub mod subtract_black;\n"
  },
  {
    "path": "libraries/rawkit/src/preprocessing/scale_to_16bit.rs",
    "content": "use crate::{RawImage, RawPixel, SubtractBlack};\n\nimpl RawImage {\n\tpub fn scale_to_16bit_fn(&self) -> impl Fn(RawPixel) -> u16 + use<> {\n\t\tlet black_level = match self.black {\n\t\t\tSubtractBlack::CfaGrid(x) => x,\n\t\t\t_ => unreachable!(),\n\t\t};\n\n\t\tlet maximum = self.maximum - black_level.iter().max().unwrap();\n\t\tlet scale_to_16bit_multiplier = if maximum > 0 { u16::MAX as f64 / maximum as f64 } else { 1. };\n\n\t\tmove |pixel: RawPixel| ((pixel.value as f64) * scale_to_16bit_multiplier).min(u16::MAX as f64).max(0.) as u16\n\t}\n}\n"
  },
  {
    "path": "libraries/rawkit/src/preprocessing/scale_white_balance.rs",
    "content": "use crate::{RawImage, RawPixel};\n\nimpl RawImage {\n\tpub fn scale_white_balance_fn(&self) -> impl Fn(RawPixel) -> u16 + use<> {\n\t\tlet Some(mut white_balance) = self.white_balance else { todo!() };\n\n\t\tif white_balance[1] == 0. {\n\t\t\twhite_balance[1] = 1.;\n\t\t}\n\n\t\t// TODO: Move this at its correct location when highlights are implemented correctly.\n\t\tlet highlight = 0;\n\n\t\tlet normalization_factor = if highlight == 0 {\n\t\t\twhite_balance.into_iter().fold(f64::INFINITY, f64::min)\n\t\t} else {\n\t\t\twhite_balance.into_iter().fold(f64::NEG_INFINITY, f64::max)\n\t\t};\n\n\t\tlet normalized_white_balance = if normalization_factor > 0.00001 {\n\t\t\twhite_balance.map(|x| x / normalization_factor)\n\t\t} else {\n\t\t\t[1., 1., 1., 1.]\n\t\t};\n\n\t\tmove |pixel: RawPixel| {\n\t\t\tlet cfa_index = 2 * (pixel.row % 2) + (pixel.column % 2);\n\t\t\t((pixel.value as f64) * normalized_white_balance[cfa_index]).min(u16::MAX as f64).max(0.) as u16\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "libraries/rawkit/src/preprocessing/subtract_black.rs",
    "content": "use crate::RawPixel;\nuse crate::{RawImage, SubtractBlack};\n\nimpl RawImage {\n\tpub fn subtract_black_fn(&self) -> impl Fn(RawPixel) -> u16 + use<> {\n\t\tmatch self.black {\n\t\t\tSubtractBlack::CfaGrid(black_levels) => move |pixel: RawPixel| pixel.value.saturating_sub(black_levels[2 * (pixel.row % 2) + (pixel.column % 2)]),\n\t\t\t_ => todo!(),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "libraries/rawkit/src/processing.rs",
    "content": "use crate::CHANNELS_IN_RGB;\n\n#[derive(Clone, Copy)]\npub struct RawPixel {\n\tpub value: u16,\n\tpub row: usize,\n\tpub column: usize,\n}\n\n#[derive(Clone, Copy)]\npub struct Pixel {\n\tpub values: [u16; CHANNELS_IN_RGB],\n\tpub row: usize,\n\tpub column: usize,\n}\n\npub trait RawPixelTransform {\n\tfn apply(&mut self, pixel: RawPixel) -> u16;\n}\n\nimpl<T: Fn(RawPixel) -> u16> RawPixelTransform for T {\n\tfn apply(&mut self, pixel: RawPixel) -> u16 {\n\t\tself(pixel)\n\t}\n}\n\nmacro_rules! impl_raw_pixel_transform {\n\t($($idx:tt $t:tt),+) => {\n\t\timpl<$($t,)+> RawPixelTransform for ($($t,)+)\n\t\twhere\n\t\t\t$($t: RawPixelTransform,)+\n\t\t{\n\t\t\tfn apply(&mut self, mut pixel: RawPixel) -> u16 {\n\t\t\t\t$(pixel.value = self.$idx.apply(pixel);)*\n\n\t\t\t\tpixel.value\n\t\t\t}\n\t\t}\n\t};\n}\n\nimpl_raw_pixel_transform!(0 A);\nimpl_raw_pixel_transform!(0 A, 1 B);\nimpl_raw_pixel_transform!(0 A, 1 B, 2 C);\nimpl_raw_pixel_transform!(0 A, 1 B, 2 C, 3 D);\nimpl_raw_pixel_transform!(0 A, 1 B, 2 C, 3 D, 4 E);\nimpl_raw_pixel_transform!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F);\nimpl_raw_pixel_transform!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G);\nimpl_raw_pixel_transform!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H);\n\npub trait PixelTransform {\n\tfn apply(&mut self, pixel: Pixel) -> [u16; CHANNELS_IN_RGB];\n}\n\nimpl<T: Fn(Pixel) -> [u16; CHANNELS_IN_RGB]> PixelTransform for T {\n\tfn apply(&mut self, pixel: Pixel) -> [u16; CHANNELS_IN_RGB] {\n\t\tself(pixel)\n\t}\n}\n\nmacro_rules! impl_pixel_transform {\n\t($($idx:tt $t:tt),+) => {\n\t\timpl<$($t,)+> PixelTransform for ($($t,)+)\n\t\twhere\n\t\t\t$($t: PixelTransform,)+\n\t\t{\n\t\t\tfn apply(&mut self, mut pixel: Pixel) -> [u16; CHANNELS_IN_RGB] {\n\t\t\t\t$(pixel.values = self.$idx.apply(pixel);)*\n\n\t\t\t\tpixel.values\n\t\t\t}\n\t\t}\n\t};\n}\n\nimpl_pixel_transform!(0 A);\nimpl_pixel_transform!(0 A, 1 B);\nimpl_pixel_transform!(0 A, 1 B, 2 C);\nimpl_pixel_transform!(0 A, 1 B, 2 C, 3 D);\nimpl_pixel_transform!(0 A, 1 B, 2 C, 3 D, 4 E);\nimpl_pixel_transform!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F);\nimpl_pixel_transform!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G);\nimpl_pixel_transform!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H);\n"
  },
  {
    "path": "libraries/rawkit/src/tiff/file.rs",
    "content": "use std::io::{Error, ErrorKind, Read, Result, Seek, SeekFrom};\n\n#[derive(Clone, Copy, PartialEq, Eq, Debug)]\npub enum Endian {\n\tLittle,\n\tBig,\n}\n\npub struct TiffRead<R: Read + Seek> {\n\treader: R,\n\tendian: Endian,\n}\n\nimpl<R: Read + Seek> TiffRead<R> {\n\tpub fn new(mut reader: R) -> Result<Self> {\n\t\tlet error = Error::new(ErrorKind::InvalidData, \"Invalid Tiff format\");\n\n\t\tlet mut data = [0_u8; 2];\n\t\treader.read_exact(&mut data)?;\n\t\tlet endian = if data[0] == 0x49 && data[1] == 0x49 {\n\t\t\tEndian::Little\n\t\t} else if data[0] == 0x4d && data[1] == 0x4d {\n\t\t\tEndian::Big\n\t\t} else {\n\t\t\treturn Err(error);\n\t\t};\n\n\t\treader.read_exact(&mut data)?;\n\t\tlet magic_number = match endian {\n\t\t\tEndian::Little => u16::from_le_bytes(data),\n\t\t\tEndian::Big => u16::from_be_bytes(data),\n\t\t};\n\t\tif magic_number != 42 {\n\t\t\treturn Err(error);\n\t\t}\n\n\t\tOk(Self { reader, endian })\n\t}\n\n\tpub fn endian(&self) -> Endian {\n\t\tself.endian\n\t}\n}\n\nimpl<R: Read + Seek> Read for TiffRead<R> {\n\tfn read(&mut self, buf: &mut [u8]) -> Result<usize> {\n\t\tself.reader.read(buf)\n\t}\n}\n\nimpl<R: Read + Seek> Seek for TiffRead<R> {\n\tfn seek(&mut self, pos: SeekFrom) -> Result<u64> {\n\t\tself.reader.seek(pos)\n\t}\n}\n\nimpl<R: Read + Seek> TiffRead<R> {\n\tpub fn seek_from_start(&mut self, offset: u32) -> Result<u64> {\n\t\tself.reader.seek(SeekFrom::Start(offset.into()))\n\t}\n\n\tpub fn read_ascii(&mut self) -> Result<char> {\n\t\tlet data = self.read_n::<1>()?;\n\t\tOk(data[0] as char)\n\t}\n\n\tpub fn read_n<const N: usize>(&mut self) -> Result<[u8; N]> {\n\t\tlet mut data = [0_u8; N];\n\t\tself.read_exact(&mut data)?;\n\t\tOk(data)\n\t}\n\n\tpub fn read_u8(&mut self) -> Result<u8> {\n\t\tlet data = self.read_n()?;\n\t\tmatch self.endian {\n\t\t\tEndian::Little => Ok(u8::from_le_bytes(data)),\n\t\t\tEndian::Big => Ok(u8::from_be_bytes(data)),\n\t\t}\n\t}\n\n\tpub fn read_u16(&mut self) -> Result<u16> {\n\t\tlet data = self.read_n()?;\n\t\tmatch self.endian {\n\t\t\tEndian::Little => Ok(u16::from_le_bytes(data)),\n\t\t\tEndian::Big => Ok(u16::from_be_bytes(data)),\n\t\t}\n\t}\n\n\tpub fn read_u32(&mut self) -> Result<u32> {\n\t\tlet data = self.read_n()?;\n\t\tmatch self.endian {\n\t\t\tEndian::Little => Ok(u32::from_le_bytes(data)),\n\t\t\tEndian::Big => Ok(u32::from_be_bytes(data)),\n\t\t}\n\t}\n\n\tpub fn read_u64(&mut self) -> Result<u64> {\n\t\tlet data = self.read_n()?;\n\t\tmatch self.endian {\n\t\t\tEndian::Little => Ok(u64::from_le_bytes(data)),\n\t\t\tEndian::Big => Ok(u64::from_be_bytes(data)),\n\t\t}\n\t}\n\n\tpub fn read_i8(&mut self) -> Result<i8> {\n\t\tlet data = self.read_n()?;\n\t\tmatch self.endian {\n\t\t\tEndian::Little => Ok(i8::from_le_bytes(data)),\n\t\t\tEndian::Big => Ok(i8::from_be_bytes(data)),\n\t\t}\n\t}\n\n\tpub fn read_i16(&mut self) -> Result<i16> {\n\t\tlet data = self.read_n()?;\n\t\tmatch self.endian {\n\t\t\tEndian::Little => Ok(i16::from_le_bytes(data)),\n\t\t\tEndian::Big => Ok(i16::from_be_bytes(data)),\n\t\t}\n\t}\n\n\tpub fn read_i32(&mut self) -> Result<i32> {\n\t\tlet data = self.read_n()?;\n\t\tmatch self.endian {\n\t\t\tEndian::Little => Ok(i32::from_le_bytes(data)),\n\t\t\tEndian::Big => Ok(i32::from_be_bytes(data)),\n\t\t}\n\t}\n\n\tpub fn read_i64(&mut self) -> Result<i64> {\n\t\tlet data = self.read_n()?;\n\t\tmatch self.endian {\n\t\t\tEndian::Little => Ok(i64::from_le_bytes(data)),\n\t\t\tEndian::Big => Ok(i64::from_be_bytes(data)),\n\t\t}\n\t}\n\n\tpub fn read_f32(&mut self) -> Result<f32> {\n\t\tlet data = self.read_n()?;\n\t\tmatch self.endian {\n\t\t\tEndian::Little => Ok(f32::from_le_bytes(data)),\n\t\t\tEndian::Big => Ok(f32::from_be_bytes(data)),\n\t\t}\n\t}\n\n\tpub fn read_f64(&mut self) -> Result<f64> {\n\t\tlet data = self.read_n()?;\n\t\tmatch self.endian {\n\t\t\tEndian::Little => Ok(f64::from_le_bytes(data)),\n\t\t\tEndian::Big => Ok(f64::from_be_bytes(data)),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "libraries/rawkit/src/tiff/mod.rs",
    "content": "pub mod file;\npub mod tags;\nmod types;\npub mod values;\n\nuse file::TiffRead;\nuse num_enum::{FromPrimitive, IntoPrimitive};\nuse std::fmt::Display;\nuse std::io::{Read, Seek};\nuse tags::Tag;\nuse thiserror::Error;\n\n#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, IntoPrimitive)]\n#[repr(u16)]\npub enum TagId {\n\tImageWidth = 0x100,\n\tImageLength = 0x101,\n\tBitsPerSample = 0x102,\n\tCompression = 0x103,\n\tPhotometricInterpretation = 0x104,\n\tMake = 0x10f,\n\tModel = 0x110,\n\tStripOffsets = 0x111,\n\tOrientation = 0x112,\n\tSamplesPerPixel = 0x115,\n\tRowsPerStrip = 0x116,\n\tStripByteCounts = 0x117,\n\tSubIfd = 0x14a,\n\tThumbnailOffset = 0x201,\n\tThumbnailLength = 0x202,\n\tSonyToneCurve = 0x7010,\n\tBlackLevel = 0x7310,\n\tWhiteBalanceRggbLevels = 0x7313,\n\tCfaPatternDim = 0x828d,\n\tCfaPattern = 0x828e,\n\tColorMatrix1 = 0xc621,\n\tColorMatrix2 = 0xc622,\n\n\t#[num_enum(catch_all)]\n\tUnknown(u16),\n}\n\n#[repr(u16)]\n#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, IntoPrimitive)]\npub enum IfdTagType {\n\tByte = 1,\n\tAscii = 2,\n\tShort = 3,\n\tLong = 4,\n\tRational = 5,\n\tSByte = 6,\n\tUndefined = 7,\n\tSShort = 8,\n\tSLong = 9,\n\tSRational = 10,\n\tFloat = 11,\n\tDouble = 12,\n\n\t#[num_enum(catch_all)]\n\tUnknown(u16),\n}\n\n#[derive(Copy, Clone, Debug)]\npub struct IfdEntry {\n\ttag: TagId,\n\tthe_type: IfdTagType,\n\tcount: u32,\n\tvalue: u32,\n}\n\n#[derive(Clone, Debug)]\npub struct Ifd {\n\tcurrent_ifd_offset: u32,\n\tifd_entries: Vec<IfdEntry>,\n\tnext_ifd_offset: Option<u32>,\n}\n\nimpl Ifd {\n\tpub fn new_first_ifd<R: Read + Seek>(file: &mut TiffRead<R>) -> Result<Self, TiffError> {\n\t\tfile.seek_from_start(4)?;\n\t\tlet current_ifd_offset = file.read_u32()?;\n\t\tIfd::new_from_offset(file, current_ifd_offset)\n\t}\n\n\tpub fn new_from_offset<R: Read + Seek>(file: &mut TiffRead<R>, offset: u32) -> Result<Self, TiffError> {\n\t\tif offset == 0 {\n\t\t\treturn Err(TiffError::InvalidOffset);\n\t\t}\n\n\t\tfile.seek_from_start(offset)?;\n\t\tlet num_entries = file.read_u16()?;\n\n\t\tlet mut ifd_entries = Vec::with_capacity(num_entries.into());\n\t\tfor _ in 0..num_entries {\n\t\t\tlet tag = file.read_u16()?.into();\n\t\t\tlet the_type = file.read_u16()?.into();\n\t\t\tlet count = file.read_u32()?;\n\t\t\tlet value = file.read_u32()?;\n\n\t\t\tifd_entries.push(IfdEntry { tag, the_type, count, value });\n\t\t}\n\n\t\tlet next_ifd_offset = file.read_u32()?;\n\t\tlet next_ifd_offset = if next_ifd_offset == 0 { None } else { Some(next_ifd_offset) };\n\n\t\tOk(Ifd {\n\t\t\tcurrent_ifd_offset: offset,\n\t\t\tifd_entries,\n\t\t\tnext_ifd_offset,\n\t\t})\n\t}\n\n\tfn _next_ifd<R: Read + Seek>(&self, file: &mut TiffRead<R>) -> Result<Self, TiffError> {\n\t\tIfd::new_from_offset(file, self.next_ifd_offset.unwrap_or(0))\n\t}\n\n\tpub fn ifd_entries(&self) -> &[IfdEntry] {\n\t\t&self.ifd_entries\n\t}\n\n\tpub fn iter(&self) -> impl Iterator<Item = &IfdEntry> {\n\t\tself.ifd_entries.iter()\n\t}\n\n\tpub fn get_value<T: Tag, R: Read + Seek>(&self, file: &mut TiffRead<R>) -> Result<T::Output, TiffError> {\n\t\tT::get(self, file)\n\t}\n}\n\nimpl Display for Ifd {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tf.write_str(\"IFD offset: \")?;\n\t\tself.current_ifd_offset.fmt(f)?;\n\t\tf.write_str(\"\\n\")?;\n\n\t\tfor ifd_entry in self.ifd_entries() {\n\t\t\tf.write_fmt(format_args!(\n\t\t\t\t\"|- Tag: {:x?}, Type: {:?}, Count: {}, Value: {:x}\\n\",\n\t\t\t\tifd_entry.tag, ifd_entry.the_type, ifd_entry.count, ifd_entry.value\n\t\t\t))?;\n\t\t}\n\n\t\tf.write_str(\"Next IFD offset: \")?;\n\t\tif let Some(offset) = self.next_ifd_offset {\n\t\t\toffset.fmt(f)?;\n\t\t} else {\n\t\t\tf.write_str(\"None\")?;\n\t\t}\n\t\tf.write_str(\"\\n\")?;\n\n\t\tOk(())\n\t}\n}\n\n#[derive(Error, Debug)]\npub enum TiffError {\n\t#[error(\"The value was invalid\")]\n\tInvalidValue,\n\t#[error(\"The type was invalid\")]\n\tInvalidType,\n\t#[error(\"The count was invalid\")]\n\tInvalidCount,\n\t#[error(\"The tag was missing\")]\n\tMissingTag,\n\t#[error(\"The offset was invalid or zero\")]\n\tInvalidOffset,\n\t#[error(\"An error occurred when converting integer from one type to another\")]\n\tConversionError(#[from] std::num::TryFromIntError),\n\t#[error(\"An IO Error ocurred\")]\n\tIoError(#[from] std::io::Error),\n}\n"
  },
  {
    "path": "libraries/rawkit/src/tiff/tags.rs",
    "content": "use super::types::{Array, ConstArray, TagType, TypeByte, TypeCompression, TypeIfd, TypeLong, TypeNumber, TypeOrientation, TypeSRational, TypeSShort, TypeShort, TypeSonyToneCurve, TypeString};\nuse super::{Ifd, TagId, TiffError, TiffRead};\nuse std::io::{Read, Seek};\n\npub trait SimpleTag {\n\ttype Type: TagType;\n\n\tconst ID: TagId;\n\tconst NAME: &'static str;\n}\n\npub struct ImageWidth;\npub struct ImageLength;\npub struct BitsPerSample;\npub struct Compression;\npub struct PhotometricInterpretation;\npub struct Make;\npub struct Model;\npub struct StripOffsets;\npub struct Orientation;\npub struct SamplesPerPixel;\npub struct RowsPerStrip;\npub struct StripByteCounts;\npub struct SubIfd;\npub struct ThumbnailOffset;\npub struct ThumbnailLength;\npub struct SonyDataOffset;\npub struct SonyToneCurve;\npub struct BlackLevel;\npub struct WhiteBalanceRggbLevels;\npub struct CfaPatternDim;\npub struct CfaPattern;\npub struct ColorMatrix1;\npub struct ColorMatrix2;\n\nimpl SimpleTag for ImageWidth {\n\ttype Type = TypeNumber;\n\n\tconst ID: TagId = TagId::ImageWidth;\n\tconst NAME: &'static str = \"Image Width\";\n}\n\nimpl SimpleTag for ImageLength {\n\ttype Type = TypeNumber;\n\n\tconst ID: TagId = TagId::ImageLength;\n\tconst NAME: &'static str = \"Image Length\";\n}\n\nimpl SimpleTag for BitsPerSample {\n\ttype Type = TypeShort;\n\n\tconst ID: TagId = TagId::BitsPerSample;\n\tconst NAME: &'static str = \"Bits per Sample\";\n}\n\nimpl SimpleTag for Compression {\n\ttype Type = TypeCompression;\n\n\tconst ID: TagId = TagId::Compression;\n\tconst NAME: &'static str = \"Compression\";\n}\n\nimpl SimpleTag for PhotometricInterpretation {\n\ttype Type = TypeShort;\n\n\tconst ID: TagId = TagId::PhotometricInterpretation;\n\tconst NAME: &'static str = \"Photometric Interpretation\";\n}\n\nimpl SimpleTag for Make {\n\ttype Type = TypeString;\n\n\tconst ID: TagId = TagId::Make;\n\tconst NAME: &'static str = \"Make\";\n}\n\nimpl SimpleTag for Model {\n\ttype Type = TypeString;\n\n\tconst ID: TagId = TagId::Model;\n\tconst NAME: &'static str = \"Model\";\n}\n\nimpl SimpleTag for StripOffsets {\n\ttype Type = Array<TypeNumber>;\n\n\tconst ID: TagId = TagId::StripOffsets;\n\tconst NAME: &'static str = \"Strip Offsets\";\n}\n\nimpl SimpleTag for Orientation {\n\ttype Type = TypeOrientation;\n\n\tconst ID: TagId = TagId::Orientation;\n\tconst NAME: &'static str = \"Orientation\";\n}\n\nimpl SimpleTag for SamplesPerPixel {\n\ttype Type = TypeShort;\n\n\tconst ID: TagId = TagId::SamplesPerPixel;\n\tconst NAME: &'static str = \"Samples per Pixel\";\n}\n\nimpl SimpleTag for RowsPerStrip {\n\ttype Type = TypeNumber;\n\n\tconst ID: TagId = TagId::RowsPerStrip;\n\tconst NAME: &'static str = \"Rows per Strip\";\n}\n\nimpl SimpleTag for StripByteCounts {\n\ttype Type = Array<TypeNumber>;\n\n\tconst ID: TagId = TagId::StripByteCounts;\n\tconst NAME: &'static str = \"Strip Byte Counts\";\n}\n\nimpl SimpleTag for SubIfd {\n\ttype Type = TypeIfd;\n\n\tconst ID: TagId = TagId::SubIfd;\n\tconst NAME: &'static str = \"SubIFD\";\n}\n\nimpl SimpleTag for ThumbnailOffset {\n\ttype Type = TypeLong;\n\n\tconst ID: TagId = TagId::ThumbnailOffset;\n\tconst NAME: &'static str = \"Jpeg Offset\";\n}\n\nimpl SimpleTag for ThumbnailLength {\n\ttype Type = TypeLong;\n\n\tconst ID: TagId = TagId::ThumbnailLength;\n\tconst NAME: &'static str = \"Jpeg Length\";\n}\n\nimpl SimpleTag for CfaPatternDim {\n\ttype Type = ConstArray<TypeShort, 2>;\n\n\tconst ID: TagId = TagId::CfaPatternDim;\n\tconst NAME: &'static str = \"CFA Pattern Dimension\";\n}\n\nimpl SimpleTag for CfaPattern {\n\ttype Type = Array<TypeByte>;\n\n\tconst ID: TagId = TagId::CfaPattern;\n\tconst NAME: &'static str = \"CFA Pattern\";\n}\n\nimpl SimpleTag for ColorMatrix1 {\n\ttype Type = Array<TypeSRational>;\n\n\tconst ID: TagId = TagId::ColorMatrix1;\n\tconst NAME: &'static str = \"Color Matrix 1\";\n}\n\nimpl SimpleTag for ColorMatrix2 {\n\ttype Type = Array<TypeSRational>;\n\n\tconst ID: TagId = TagId::ColorMatrix2;\n\tconst NAME: &'static str = \"Color Matrix 2\";\n}\n\nimpl SimpleTag for SonyDataOffset {\n\ttype Type = TypeLong;\n\n\tconst ID: TagId = TagId::SubIfd;\n\tconst NAME: &'static str = \"Sony Data Offset\";\n}\n\nimpl SimpleTag for SonyToneCurve {\n\ttype Type = TypeSonyToneCurve;\n\n\tconst ID: TagId = TagId::SonyToneCurve;\n\tconst NAME: &'static str = \"Sony Tone Curve\";\n}\n\nimpl SimpleTag for BlackLevel {\n\ttype Type = ConstArray<TypeShort, 4>;\n\n\tconst ID: TagId = TagId::BlackLevel;\n\tconst NAME: &'static str = \"Black Level\";\n}\n\nimpl SimpleTag for WhiteBalanceRggbLevels {\n\ttype Type = ConstArray<TypeSShort, 4>;\n\n\tconst ID: TagId = TagId::WhiteBalanceRggbLevels;\n\tconst NAME: &'static str = \"White Balance Levels (RGGB)\";\n}\n\npub trait Tag {\n\ttype Output;\n\n\tfn get<R: Read + Seek>(ifd: &Ifd, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError>;\n}\n\nimpl<T: SimpleTag> Tag for T {\n\ttype Output = <T::Type as TagType>::Output;\n\n\tfn get<R: Read + Seek>(ifd: &Ifd, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {\n\t\tlet tag_id = T::ID;\n\t\tlet index: u32 = ifd.iter().position(|x| x.tag == tag_id).ok_or(TiffError::MissingTag)?.try_into()?;\n\n\t\tfile.seek_from_start(ifd.current_ifd_offset + 2 + 12 * index + 2)?;\n\t\tT::Type::read(file)\n\t}\n}\n\nimpl<T: Tag> Tag for Option<T> {\n\ttype Output = Option<T::Output>;\n\n\tfn get<R: Read + Seek>(ifd: &Ifd, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {\n\t\tlet result = T::get(ifd, file);\n\n\t\tmatch result {\n\t\t\tErr(TiffError::MissingTag) => Ok(None),\n\t\t\tOk(x) => Ok(Some(x)),\n\t\t\tErr(x) => Err(x),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "libraries/rawkit/src/tiff/types.rs",
    "content": "use super::file::TiffRead;\nuse super::values::{CompressionValue, CurveLookupTable, OrientationValue, Rational};\nuse super::{Ifd, IfdTagType, TiffError};\nuse std::io::{Read, Seek};\n\npub struct TypeAscii;\npub struct TypeByte;\npub struct TypeShort;\npub struct TypeLong;\npub struct TypeRational;\npub struct TypeSByte;\npub struct TypeSShort;\npub struct TypeSLong;\npub struct TypeSRational;\npub struct TypeFloat;\npub struct TypeDouble;\npub struct TypeUndefined;\n\npub struct TypeNumber;\npub struct TypeSNumber;\npub struct TypeIfd;\n\npub trait PrimitiveType {\n\ttype Output;\n\n\tfn get_size(the_type: IfdTagType) -> Option<u32>;\n\n\tfn read_primitive<R: Read + Seek>(the_type: IfdTagType, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError>;\n}\n\nimpl PrimitiveType for TypeAscii {\n\ttype Output = char;\n\n\tfn get_size(the_type: IfdTagType) -> Option<u32> {\n\t\tmatch the_type {\n\t\t\tIfdTagType::Ascii => Some(1),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tfn read_primitive<R: Read + Seek>(_: IfdTagType, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {\n\t\tlet value = file.read_ascii()?;\n\t\tif value.is_ascii() { Ok(value) } else { Err(TiffError::InvalidValue) }\n\t}\n}\n\nimpl PrimitiveType for TypeByte {\n\ttype Output = u8;\n\n\tfn get_size(the_type: IfdTagType) -> Option<u32> {\n\t\tmatch the_type {\n\t\t\tIfdTagType::Byte => Some(1),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tfn read_primitive<R: Read + Seek>(_: IfdTagType, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {\n\t\tOk(file.read_u8()?)\n\t}\n}\n\nimpl PrimitiveType for TypeShort {\n\ttype Output = u16;\n\n\tfn get_size(the_type: IfdTagType) -> Option<u32> {\n\t\tmatch the_type {\n\t\t\tIfdTagType::Short => Some(2),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tfn read_primitive<R: Read + Seek>(_: IfdTagType, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {\n\t\tOk(file.read_u16()?)\n\t}\n}\n\nimpl PrimitiveType for TypeLong {\n\ttype Output = u32;\n\n\tfn get_size(the_type: IfdTagType) -> Option<u32> {\n\t\tmatch the_type {\n\t\t\tIfdTagType::Long => Some(4),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tfn read_primitive<R: Read + Seek>(_: IfdTagType, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {\n\t\tOk(file.read_u32()?)\n\t}\n}\n\nimpl PrimitiveType for TypeRational {\n\ttype Output = Rational<u32>;\n\n\tfn get_size(the_type: IfdTagType) -> Option<u32> {\n\t\tmatch the_type {\n\t\t\tIfdTagType::Rational => Some(8),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tfn read_primitive<R: Read + Seek>(the_type: IfdTagType, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {\n\t\tlet numerator = TypeLong::read_primitive(the_type, file)?;\n\t\tlet denominator = TypeLong::read_primitive(the_type, file)?;\n\n\t\tOk(Rational { numerator, denominator })\n\t}\n}\n\nimpl PrimitiveType for TypeSByte {\n\ttype Output = i8;\n\n\tfn get_size(the_type: IfdTagType) -> Option<u32> {\n\t\tmatch the_type {\n\t\t\tIfdTagType::SByte => Some(1),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tfn read_primitive<R: Read + Seek>(_: IfdTagType, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {\n\t\tOk(file.read_i8()?)\n\t}\n}\n\nimpl PrimitiveType for TypeSShort {\n\ttype Output = i16;\n\n\tfn get_size(the_type: IfdTagType) -> Option<u32> {\n\t\tmatch the_type {\n\t\t\tIfdTagType::SShort => Some(2),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tfn read_primitive<R: Read + Seek>(_: IfdTagType, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {\n\t\tOk(file.read_i16()?)\n\t}\n}\n\nimpl PrimitiveType for TypeSLong {\n\ttype Output = i32;\n\n\tfn get_size(the_type: IfdTagType) -> Option<u32> {\n\t\tmatch the_type {\n\t\t\tIfdTagType::SLong => Some(4),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tfn read_primitive<R: Read + Seek>(_: IfdTagType, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {\n\t\tOk(file.read_i32()?)\n\t}\n}\n\nimpl PrimitiveType for TypeSRational {\n\ttype Output = Rational<i32>;\n\n\tfn get_size(the_type: IfdTagType) -> Option<u32> {\n\t\tmatch the_type {\n\t\t\tIfdTagType::SRational => Some(8),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tfn read_primitive<R: Read + Seek>(the_type: IfdTagType, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {\n\t\tlet numerator = TypeSLong::read_primitive(the_type, file)?;\n\t\tlet denominator = TypeSLong::read_primitive(the_type, file)?;\n\n\t\tOk(Rational { numerator, denominator })\n\t}\n}\n\nimpl PrimitiveType for TypeFloat {\n\ttype Output = f32;\n\n\tfn get_size(the_type: IfdTagType) -> Option<u32> {\n\t\tmatch the_type {\n\t\t\tIfdTagType::Float => Some(4),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tfn read_primitive<R: Read + Seek>(_: IfdTagType, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {\n\t\tOk(file.read_f32()?)\n\t}\n}\n\nimpl PrimitiveType for TypeDouble {\n\ttype Output = f64;\n\n\tfn get_size(the_type: IfdTagType) -> Option<u32> {\n\t\tmatch the_type {\n\t\t\tIfdTagType::Double => Some(8),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tfn read_primitive<R: Read + Seek>(_: IfdTagType, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {\n\t\tOk(file.read_f64()?)\n\t}\n}\n\nimpl PrimitiveType for TypeUndefined {\n\ttype Output = ();\n\n\tfn get_size(_: IfdTagType) -> Option<u32> {\n\t\ttodo!()\n\t}\n\n\tfn read_primitive<R: Read + Seek>(_: IfdTagType, _: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {\n\t\ttodo!()\n\t}\n}\n\nimpl PrimitiveType for TypeNumber {\n\ttype Output = u32;\n\n\tfn get_size(the_type: IfdTagType) -> Option<u32> {\n\t\tmatch the_type {\n\t\t\tIfdTagType::Byte => TypeByte::get_size(the_type),\n\t\t\tIfdTagType::Short => TypeShort::get_size(the_type),\n\t\t\tIfdTagType::Long => TypeLong::get_size(the_type),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tfn read_primitive<R: Read + Seek>(the_type: IfdTagType, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {\n\t\tOk(match the_type {\n\t\t\tIfdTagType::Byte => TypeByte::read_primitive(the_type, file)?.into(),\n\t\t\tIfdTagType::Short => TypeShort::read_primitive(the_type, file)?.into(),\n\t\t\tIfdTagType::Long => TypeLong::read_primitive(the_type, file)?,\n\t\t\t_ => unreachable!(),\n\t\t})\n\t}\n}\n\nimpl PrimitiveType for TypeSNumber {\n\ttype Output = i32;\n\n\tfn get_size(the_type: IfdTagType) -> Option<u32> {\n\t\tmatch the_type {\n\t\t\tIfdTagType::SByte => TypeSByte::get_size(the_type),\n\t\t\tIfdTagType::SShort => TypeSShort::get_size(the_type),\n\t\t\tIfdTagType::SLong => TypeSLong::get_size(the_type),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tfn read_primitive<R: Read + Seek>(the_type: IfdTagType, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {\n\t\tOk(match the_type {\n\t\t\tIfdTagType::SByte => TypeSByte::read_primitive(the_type, file)?.into(),\n\t\t\tIfdTagType::SShort => TypeSShort::read_primitive(the_type, file)?.into(),\n\t\t\tIfdTagType::SLong => TypeSLong::read_primitive(the_type, file)?,\n\t\t\t_ => unreachable!(),\n\t\t})\n\t}\n}\n\nimpl PrimitiveType for TypeIfd {\n\ttype Output = Ifd;\n\n\tfn get_size(the_type: IfdTagType) -> Option<u32> {\n\t\tTypeLong::get_size(the_type)\n\t}\n\n\tfn read_primitive<R: Read + Seek>(the_type: IfdTagType, file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {\n\t\tlet offset = TypeLong::read_primitive(the_type, file)?;\n\t\tIfd::new_from_offset(file, offset)\n\t}\n}\n\npub trait TagType {\n\ttype Output;\n\n\tfn read<R: Read + Seek>(file: &mut TiffRead<R>) -> Result<Self::Output, TiffError>;\n}\n\nimpl<T: PrimitiveType> TagType for T {\n\ttype Output = T::Output;\n\n\tfn read<R: Read + Seek>(file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {\n\t\tlet the_type = IfdTagType::from(file.read_u16()?);\n\t\tlet count = file.read_u32()?;\n\n\t\tif count != 1 {\n\t\t\treturn Err(TiffError::InvalidCount);\n\t\t}\n\n\t\tlet size = T::get_size(the_type).ok_or(TiffError::InvalidType)?;\n\t\tif count * size > 4 {\n\t\t\tlet offset = file.read_u32()?;\n\t\t\tfile.seek_from_start(offset)?;\n\t\t}\n\n\t\tT::read_primitive(the_type, file)\n\t}\n}\n\npub struct Array<T: PrimitiveType> {\n\tprimitive_type: std::marker::PhantomData<T>,\n}\n\npub struct ConstArray<T: PrimitiveType, const N: usize> {\n\tprimitive_type: std::marker::PhantomData<T>,\n}\n\nimpl<T: PrimitiveType> TagType for Array<T> {\n\ttype Output = Vec<T::Output>;\n\n\tfn read<R: Read + Seek>(file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {\n\t\tlet the_type = IfdTagType::from(file.read_u16()?);\n\t\tlet count = file.read_u32()?;\n\n\t\tlet size = T::get_size(the_type).ok_or(TiffError::InvalidType)?;\n\t\tif count * size > 4 {\n\t\t\tlet offset = file.read_u32()?;\n\t\t\tfile.seek_from_start(offset)?;\n\t\t}\n\n\t\tlet mut ans = Vec::with_capacity(count.try_into()?);\n\t\tfor _ in 0..count {\n\t\t\tans.push(T::read_primitive(the_type, file)?);\n\t\t}\n\t\tOk(ans)\n\t}\n}\n\nimpl<T: PrimitiveType, const N: usize> TagType for ConstArray<T, N> {\n\ttype Output = [T::Output; N];\n\n\tfn read<R: Read + Seek>(file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {\n\t\tlet the_type = IfdTagType::from(file.read_u16()?);\n\t\tlet count = file.read_u32()?;\n\n\t\tif count != N.try_into()? {\n\t\t\treturn Err(TiffError::InvalidCount);\n\t\t}\n\n\t\tlet size = T::get_size(the_type).ok_or(TiffError::InvalidType)?;\n\t\tif count * size > 4 {\n\t\t\tlet offset = file.read_u32()?;\n\t\t\tfile.seek_from_start(offset)?;\n\t\t}\n\n\t\tlet mut ans = Vec::with_capacity(count.try_into()?);\n\t\tfor _ in 0..count {\n\t\t\tans.push(T::read_primitive(the_type, file)?);\n\t\t}\n\t\tans.try_into().map_err(|_| TiffError::InvalidCount)\n\t}\n}\n\npub struct TypeCompression;\npub struct TypeString;\npub struct TypeSonyToneCurve;\npub struct TypeOrientation;\n\nimpl TagType for TypeString {\n\ttype Output = String;\n\n\tfn read<R: Read + Seek>(file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {\n\t\tlet string = Array::<TypeAscii>::read(file)?;\n\n\t\t// Skip the NUL character at the end\n\t\tlet len = string.len();\n\t\tOk(string.into_iter().take(len - 1).collect())\n\t}\n}\n\nimpl TagType for TypeSonyToneCurve {\n\ttype Output = CurveLookupTable;\n\n\tfn read<R: Read + Seek>(file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {\n\t\tlet values = ConstArray::<TypeShort, 4>::read(file)?;\n\t\tOk(CurveLookupTable::from_sony_tone_table(values))\n\t}\n}\n\nimpl TagType for TypeOrientation {\n\ttype Output = OrientationValue;\n\n\tfn read<R: Read + Seek>(file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {\n\t\tOrientationValue::try_from(TypeShort::read(file)?).map_err(|_| TiffError::InvalidValue)\n\t}\n}\n\nimpl TagType for TypeCompression {\n\ttype Output = CompressionValue;\n\n\tfn read<R: Read + Seek>(file: &mut TiffRead<R>) -> Result<Self::Output, TiffError> {\n\t\tCompressionValue::try_from(TypeShort::read(file)?).map_err(|_| TiffError::InvalidValue)\n\t}\n}\n"
  },
  {
    "path": "libraries/rawkit/src/tiff/values.rs",
    "content": "use num_enum::{IntoPrimitive, TryFromPrimitive};\n\npub trait ToFloat {\n\tfn to_float(&self) -> f64;\n}\n\nimpl ToFloat for u32 {\n\tfn to_float(&self) -> f64 {\n\t\t*self as f64\n\t}\n}\n\nimpl ToFloat for i32 {\n\tfn to_float(&self) -> f64 {\n\t\t*self as f64\n\t}\n}\n\npub struct Rational<T: ToFloat> {\n\tpub numerator: T,\n\tpub denominator: T,\n}\n\nimpl<T: ToFloat> ToFloat for Rational<T> {\n\tfn to_float(&self) -> f64 {\n\t\tself.numerator.to_float() / self.denominator.to_float()\n\t}\n}\n\npub struct CurveLookupTable {\n\ttable: Vec<u16>,\n}\n\nimpl CurveLookupTable {\n\tpub fn from_sony_tone_table(values: [u16; 4]) -> CurveLookupTable {\n\t\tlet mut sony_curve = [0, 0, 0, 0, 0, 4095];\n\t\tfor i in 0..4 {\n\t\t\tsony_curve[i + 1] = values[i] >> 2 & 0xfff;\n\t\t}\n\n\t\tlet mut table = vec![0_u16; (sony_curve[5] + 1).into()];\n\t\tfor i in 0..5 {\n\t\t\tfor j in (sony_curve[i] + 1)..=sony_curve[i + 1] {\n\t\t\t\ttable[j as usize] = table[(j - 1) as usize] + (1 << i);\n\t\t\t}\n\t\t}\n\n\t\tCurveLookupTable { table }\n\t}\n\n\tpub fn get(&self, x: usize) -> u16 {\n\t\tself.table[x]\n\t}\n}\n\n#[derive(Copy, Clone, Eq, PartialEq, Debug, IntoPrimitive, TryFromPrimitive)]\n#[repr(u16)]\npub enum OrientationValue {\n\tHorizontal = 1,\n\tMirrorHorizontal = 2,\n\tRotate180 = 3,\n\tMirrorVertical = 4,\n\tMirrorHorizontalRotate270 = 5,\n\tRotate90 = 6,\n\tMirrorHorizontalRotate90 = 7,\n\tRotate270 = 8,\n}\n\nimpl OrientationValue {\n\tpub fn is_identity(&self) -> bool {\n\t\t*self == Self::Horizontal\n\t}\n\n\tpub fn will_swap_coordinates(&self) -> bool {\n\t\tmatch *self {\n\t\t\tSelf::Horizontal | Self::MirrorHorizontal | Self::Rotate180 | Self::MirrorVertical => false,\n\t\t\tSelf::MirrorHorizontalRotate270 | Self::Rotate90 | Self::MirrorHorizontalRotate90 | Self::Rotate270 => true,\n\t\t}\n\t}\n}\n\n#[derive(Debug, PartialEq, Eq, Clone, Copy, IntoPrimitive, TryFromPrimitive)]\n#[repr(u16)]\n#[allow(non_camel_case_types)]\npub enum CompressionValue {\n\tUncompressed = 1,\n\tCCITT_1D = 2,\n\tT4 = 3,\n\tT6 = 4,\n\tLZW = 5,\n\tJPEG_Old = 6,\n\tJPEG = 7,\n\tAdobeDeflate = 8,\n\tJBIG_BW = 9,\n\tJBIG_Color = 10,\n\tKODAK_626 = 262,\n\tNext = 32766,\n\tSony_ARW_Compressed = 32767,\n\tPacked_Raw = 32769,\n\tSamsung_SRW_Compressed = 32770,\n\tCCIRLEW = 32771,\n\tSamsung_SRW_Compressed_2 = 32772,\n\tPackedBits = 32773,\n\tThunderscan = 32809,\n\tKodak_KDC_Compressed = 32867,\n\tIT8CTPAD = 32895,\n\tIT8LW = 32896,\n\tIT8MP = 32897,\n\tIT8BL = 32898,\n\tPixarFilm = 32908,\n\tPixarLog = 32909,\n\tDeflate = 32946,\n\tDCS = 32947,\n\tAperioJPEG2K_YCbCr = 33003,\n\tAperioJPEG2K_RGB = 33005,\n\tJBIG = 34661,\n\tSGILog = 34676,\n\tSGILog24 = 34677,\n\tJPEG2K = 34712,\n\tNikonNEFCompressed = 34713,\n\tJBIG2_TIFF_FX = 34715,\n\tESRI_Lerc = 34887,\n\tLossyJPEG = 34892,\n\tLZMA2 = 34925,\n\tPNG = 34933,\n\tJPEG_XR = 34934,\n\tZstd = 50000,\n\tWebP = 50001,\n\tJPEG_XL = 52546,\n\tKodak_DCR_Compressed = 65000,\n\tPentax_PEF_Compressed = 65535,\n}\n"
  },
  {
    "path": "libraries/rawkit/tests/images/.gitkeep",
    "content": " \n"
  },
  {
    "path": "libraries/rawkit/tests/tests.rs",
    "content": "// Only compile this file if the feature \"rawkit-tests\" is enabled\n#![cfg(feature = \"rawkit-tests\")]\n\nuse image::codecs::png::{CompressionType, FilterType, PngEncoder};\nuse image::{ColorType, ImageEncoder};\nuse libraw::Processor;\nuse rawkit::RawImage;\nuse rayon::prelude::*;\nuse std::collections::HashMap;\nuse std::fmt::Write;\nuse std::fs::{File, create_dir, metadata, read_dir};\nuse std::io::{BufWriter, Cursor, Read};\nuse std::path::{Path, PathBuf};\nuse std::sync::atomic::{AtomicUsize, Ordering};\nuse std::time::Duration;\n\nconst TEST_FILES: [&str; 3] = [\"ILCE-7M3-ARW2.3.5-blossoms.arw\", \"ILCE-7RM4-ARW2.3.5-kestrel.arw\", \"ILCE-6000-ARW2.3.1-windsock.arw\"];\nconst BASE_URL: &str = \"https://static.graphite.art/test-data/libraries/rawkit/\";\nconst BASE_PATH: &str = \"./tests/images/\";\n\n#[test]\nfn test_images_match_with_libraw() {\n\tdownload_images();\n\n\tlet paths: Vec<_> = read_dir(BASE_PATH)\n\t\t.unwrap()\n\t\t.map(|dir_entry| dir_entry.unwrap().path())\n\t\t.filter(|path| path.is_file() && path.file_name().map(|file_name| file_name != \".gitkeep\").unwrap_or(false))\n\t\t.collect();\n\n\tlet failed_tests = if std::env::var(\"RAWKIT_TEST_RUN_SEQUENTIALLY\").is_ok() {\n\t\tlet mut failed_tests = 0;\n\n\t\tpaths.iter().for_each(|path| {\n\t\t\tif !test_image(path) {\n\t\t\t\tfailed_tests += 1;\n\t\t\t}\n\t\t});\n\n\t\tfailed_tests\n\t} else {\n\t\tlet failed_tests = AtomicUsize::new(0);\n\n\t\tpaths.par_iter().for_each(|path| {\n\t\t\tif !test_image(path) {\n\t\t\t\tfailed_tests.fetch_add(1, Ordering::SeqCst);\n\t\t\t}\n\t\t});\n\n\t\tfailed_tests.load(Ordering::SeqCst)\n\t};\n\n\tif failed_tests != 0 {\n\t\tpanic!(\"{} images have failed the tests\", failed_tests);\n\t}\n}\n\nfn test_image(path: &Path) -> bool {\n\tlet mut f = File::open(path).unwrap();\n\tlet mut content = vec![];\n\tf.read_to_end(&mut content).unwrap();\n\n\tlet raw_image = match test_raw_data(&content) {\n\t\tErr(err_msg) => {\n\t\t\tprintln!(\"{} => {}\", path.display(), err_msg);\n\t\t\treturn false;\n\t\t}\n\t\tOk(raw_image) => raw_image,\n\t};\n\n\t// TODO: The code below is kept commented because raw data to final image processing is\n\t// incomplete. Remove this once it is done.\n\n\t// if let Err(err_msg) = test_final_image(&content, raw_image) {\n\t// \tfailed_tests += 1;\n\t// \treturn println!(\"{}\", err_msg);\n\t// };\n\n\tprintln!(\"{} => Passed\", path.display());\n\n\t// TODO: Remove this later\n\tlet mut image = raw_image.process_8bit();\n\tstore_image(path, \"rawkit\", &mut image.data, image.width, image.height);\n\n\tlet processor = Processor::new();\n\tlet libraw_image = processor.process_8bit(&content).unwrap();\n\tlet mut data = Vec::from_iter(libraw_image.iter().copied());\n\tstore_image(path, \"libraw_rs\", &mut data[..], libraw_image.width() as usize, libraw_image.height() as usize);\n\n\ttrue\n}\n\nfn store_image(path: &Path, suffix: &str, data: &mut [u8], width: usize, height: usize) {\n\tlet mut output_path = PathBuf::new();\n\tif let Some(parent) = path.parent() {\n\t\toutput_path.push(parent);\n\t}\n\toutput_path.push(\"output\");\n\n\tif metadata(&output_path).is_err() {\n\t\tcreate_dir(&output_path).unwrap();\n\t}\n\n\tif let Some(filename) = path.file_stem() {\n\t\tlet new_filename = format!(\"{}_{}.{}\", filename.to_string_lossy(), suffix, \"png\");\n\t\toutput_path.push(new_filename);\n\t}\n\toutput_path.set_extension(\"png\");\n\n\tlet file = BufWriter::new(File::create(output_path).unwrap());\n\tlet png_encoder = PngEncoder::new_with_quality(file, CompressionType::Fast, FilterType::Adaptive);\n\tpng_encoder.write_image(data, width as u32, height as u32, ColorType::Rgb8.into()).unwrap();\n}\n\nfn download_images() {\n\tlet mut path = Path::new(BASE_PATH).to_owned();\n\tlet client = reqwest::blocking::Client::builder().timeout(Duration::from_secs(60 * 5)).build().unwrap();\n\n\tfor filename in TEST_FILES {\n\t\tpath.push(filename);\n\t\tif !path.exists() {\n\t\t\tlet url = BASE_URL.to_owned() + filename;\n\t\t\tlet mut response = client.get(url).send().unwrap();\n\t\t\tlet mut file = File::create(BASE_PATH.to_owned() + filename).unwrap();\n\t\t\tstd::io::copy(&mut response, &mut file).unwrap();\n\t\t}\n\t\tpath.pop();\n\t}\n}\n\nfn test_raw_data(content: &[u8]) -> Result<RawImage, String> {\n\tlet processor = libraw::Processor::new();\n\tlet libraw_raw_image = processor.decode(content).unwrap();\n\n\tlet mut content = Cursor::new(content);\n\tlet raw_image = RawImage::decode(&mut content).unwrap();\n\n\tif libraw_raw_image.sizes().raw_height as usize != raw_image.height {\n\t\treturn Err(format!(\n\t\t\t\"The height of raw image is {} but the expected value was {}\",\n\t\t\traw_image.height,\n\t\t\tlibraw_raw_image.sizes().raw_height\n\t\t));\n\t}\n\n\tif libraw_raw_image.sizes().raw_width as usize != raw_image.width {\n\t\treturn Err(format!(\n\t\t\t\"The width of raw image is {} but the expected value was {}\",\n\t\t\traw_image.width,\n\t\t\tlibraw_raw_image.sizes().raw_width\n\t\t));\n\t}\n\n\tif (*libraw_raw_image).len() != raw_image.data.len() {\n\t\treturn Err(format!(\n\t\t\t\"The size of data of raw image is {} but the expected value was {}\",\n\t\t\traw_image.data.len(),\n\t\t\t(*libraw_raw_image).len()\n\t\t));\n\t}\n\n\tif (*libraw_raw_image) != raw_image.data {\n\t\tlet mut err_msg = String::new();\n\n\t\twrite!(&mut err_msg, \"The raw data does not match\").unwrap();\n\n\t\tif std::env::var(\"RAWKIT_TEST_PRINT_HISTOGRAM\").is_ok() {\n\t\t\twriteln!(err_msg).unwrap();\n\n\t\t\tlet mut histogram: HashMap<i32, usize> = HashMap::new();\n\t\t\tlet mut non_zero_count: usize = 0;\n\n\t\t\t(*libraw_raw_image)\n\t\t\t\t.iter()\n\t\t\t\t.zip(raw_image.data.iter())\n\t\t\t\t.map(|(&a, &b)| {\n\t\t\t\t\tlet a: i32 = a.into();\n\t\t\t\t\tlet b: i32 = b.into();\n\t\t\t\t\ta - b\n\t\t\t\t})\n\t\t\t\t.filter(|&x| x != 0)\n\t\t\t\t.for_each(|x| {\n\t\t\t\t\t*histogram.entry(x).or_default() += 1;\n\t\t\t\t\tnon_zero_count += 1;\n\t\t\t\t});\n\n\t\t\tlet total_pixels = raw_image.height * raw_image.width;\n\t\t\twriteln!(err_msg, \"{} ({:.5}%) pixels are different from expected\", non_zero_count, non_zero_count as f64 / total_pixels as f64).unwrap();\n\n\t\t\twriteln!(err_msg, \"Diff Histogram:\").unwrap();\n\t\t\tlet mut items: Vec<_> = histogram.iter().map(|(&a, &b)| (a, b)).collect();\n\t\t\titems.sort();\n\t\t\tfor (key, value) in items {\n\t\t\t\twriteln!(err_msg, \"{:05}: {:05} ({:02.5}%)\", key, value, value as f64 / total_pixels as f64).unwrap();\n\t\t\t}\n\t\t}\n\n\t\treturn Err(err_msg);\n\t}\n\n\tOk(raw_image)\n}\n\nfn _test_final_image(content: &[u8], raw_image: RawImage) -> Result<(), String> {\n\tlet processor = libraw::Processor::new();\n\tlet libraw_image = processor.process_8bit(content).unwrap();\n\n\tlet image = raw_image.process_8bit();\n\n\tif libraw_image.height() as usize != image.height {\n\t\treturn Err(format!(\"The height of image is {} but the expected value was {}\", image.height, libraw_image.height()));\n\t}\n\n\tif libraw_image.width() as usize != image.width {\n\t\treturn Err(format!(\"The width of image is {} but the expected value was {}\", image.width, libraw_image.width()));\n\t}\n\n\tif (*libraw_image).len() != image.data.len() {\n\t\treturn Err(format!(\"The size of data of image is {} but the expected value was {}\", image.data.len(), (*libraw_image).len()));\n\t}\n\n\tif (*libraw_image) != image.data {\n\t\tlet mut err_msg = String::new();\n\n\t\twrite!(&mut err_msg, \"The final image does not match\").unwrap();\n\n\t\tif std::env::var(\"RAWKIT_TEST_PRINT_HISTOGRAM\").is_ok() {\n\t\t\twriteln!(err_msg).unwrap();\n\n\t\t\tlet mut histogram_red: HashMap<i16, usize> = HashMap::new();\n\t\t\tlet mut histogram_green: HashMap<i16, usize> = HashMap::new();\n\t\t\tlet mut histogram_blue: HashMap<i16, usize> = HashMap::new();\n\t\t\tlet mut non_zero_count: usize = 0;\n\t\t\tlet mut non_zero_count_red: usize = 0;\n\t\t\tlet mut non_zero_count_green: usize = 0;\n\t\t\tlet mut non_zero_count_blue: usize = 0;\n\n\t\t\t(*libraw_image)\n\t\t\t\t.chunks_exact(3)\n\t\t\t\t.zip(image.data.chunks_exact(3))\n\t\t\t\t.map(|(a, b)| {\n\t\t\t\t\tlet a: [u8; 3] = a.try_into().unwrap();\n\t\t\t\t\tlet b: [u8; 3] = b.try_into().unwrap();\n\t\t\t\t\t(a, b)\n\t\t\t\t})\n\t\t\t\t.map(|([r1, g1, b1], [r2, g2, b2])| {\n\t\t\t\t\tlet r1: i16 = r1.into();\n\t\t\t\t\tlet g1: i16 = g1.into();\n\t\t\t\t\tlet b1: i16 = b1.into();\n\t\t\t\t\tlet r2: i16 = r2.into();\n\t\t\t\t\tlet g2: i16 = g2.into();\n\t\t\t\t\tlet b2: i16 = b2.into();\n\t\t\t\t\t[r1 - r2, g1 - g2, b1 - b2]\n\t\t\t\t})\n\t\t\t\t.filter(|&[r, g, b]| r != 0 || g != 0 || b != 0)\n\t\t\t\t.for_each(|[r, g, b]| {\n\t\t\t\t\tnon_zero_count += 1;\n\t\t\t\t\tif r != 0 {\n\t\t\t\t\t\t*histogram_red.entry(r).or_default() += 1;\n\t\t\t\t\t\tnon_zero_count_red += 1;\n\t\t\t\t\t}\n\t\t\t\t\tif g != 0 {\n\t\t\t\t\t\t*histogram_green.entry(g).or_default() += 1;\n\t\t\t\t\t\tnon_zero_count_green += 1;\n\t\t\t\t\t}\n\t\t\t\t\tif b != 0 {\n\t\t\t\t\t\t*histogram_blue.entry(b).or_default() += 1;\n\t\t\t\t\t\tnon_zero_count_blue += 1;\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\tlet total_pixels = image.height * image.width;\n\t\t\twriteln!(err_msg, \"{} ({:.5}%) pixels are different from expected\", non_zero_count, non_zero_count as f64 / total_pixels as f64,).unwrap();\n\n\t\t\twriteln!(\n\t\t\t\terr_msg,\n\t\t\t\t\"{} ({:.5}%) red pixels are different from expected\",\n\t\t\t\tnon_zero_count_red,\n\t\t\t\tnon_zero_count_red as f64 / total_pixels as f64,\n\t\t\t)\n\t\t\t.unwrap();\n\n\t\t\twriteln!(\n\t\t\t\terr_msg,\n\t\t\t\t\"{} ({:.5}%) green pixels are different from expected\",\n\t\t\t\tnon_zero_count_green,\n\t\t\t\tnon_zero_count_green as f64 / total_pixels as f64,\n\t\t\t)\n\t\t\t.unwrap();\n\n\t\t\twriteln!(\n\t\t\t\terr_msg,\n\t\t\t\t\"{} ({:.5}%) blue pixels are different from expected\",\n\t\t\t\tnon_zero_count_blue,\n\t\t\t\tnon_zero_count_blue as f64 / total_pixels as f64,\n\t\t\t)\n\t\t\t.unwrap();\n\n\t\t\twriteln!(err_msg, \"Diff Histogram for Red pixels:\").unwrap();\n\t\t\tlet mut items: Vec<_> = histogram_red.iter().map(|(&a, &b)| (a, b)).collect();\n\t\t\titems.sort();\n\t\t\tfor (key, value) in items {\n\t\t\t\twriteln!(err_msg, \"{:05}: {:05} ({:02.5}%)\", key, value, value as f64 / total_pixels as f64).unwrap();\n\t\t\t}\n\n\t\t\twriteln!(err_msg, \"Diff Histogram for Green pixels:\").unwrap();\n\t\t\tlet mut items: Vec<_> = histogram_green.iter().map(|(&a, &b)| (a, b)).collect();\n\t\t\titems.sort();\n\t\t\tfor (key, value) in items {\n\t\t\t\twriteln!(err_msg, \"{:05}: {:05} ({:02.5}%)\", key, value, value as f64 / total_pixels as f64).unwrap();\n\t\t\t}\n\n\t\t\twriteln!(err_msg, \"Diff Histogram for Blue pixels:\").unwrap();\n\t\t\tlet mut items: Vec<_> = histogram_blue.iter().map(|(&a, &b)| (a, b)).collect();\n\t\t\titems.sort();\n\t\t\tfor (key, value) in items {\n\t\t\t\twriteln!(err_msg, \"{:05}: {:05} ({:02.5}%)\", key, value, value as f64 / total_pixels as f64).unwrap();\n\t\t\t}\n\t\t}\n\n\t\treturn Err(err_msg);\n\t}\n\n\tOk(())\n}\n\n#[ignore]\n#[test]\nfn extract_data_from_dng_images() {\n\tread_dir(BASE_PATH)\n\t\t.unwrap()\n\t\t.map(|dir_entry| dir_entry.unwrap().path())\n\t\t.filter(|path| path.is_file() && path.file_name().map(|file_name| file_name != \".gitkeep\").unwrap_or(false))\n\t\t.for_each(|path| {\n\t\t\textract_data_from_dng_image(&path);\n\t\t});\n}\n\nfn extract_data_from_dng_image(path: &Path) {\n\tuse rawkit::tiff::Ifd;\n\tuse rawkit::tiff::file::TiffRead;\n\tuse rawkit::tiff::tags::{ColorMatrix2, Make, Model};\n\tuse rawkit::tiff::values::ToFloat;\n\tuse std::io::{BufReader, Write};\n\n\tlet reader = BufReader::new(File::open(path).unwrap());\n\tlet mut file = TiffRead::new(reader).unwrap();\n\tlet ifd = Ifd::new_first_ifd(&mut file).unwrap();\n\n\tlet make = ifd.get_value::<Make, _>(&mut file).unwrap();\n\tlet model = ifd.get_value::<Model, _>(&mut file).unwrap();\n\tlet matrix = ifd.get_value::<ColorMatrix2, _>(&mut file).unwrap();\n\n\tif model == \"MODEL-NAME\" {\n\t\tprintln!(\"{}\", path.display());\n\t\treturn;\n\t}\n\n\tlet output_folder = path.parent().unwrap().join(make);\n\tstd::fs::create_dir_all(&output_folder).unwrap();\n\tlet mut output_file = File::create(output_folder.join(model + \".toml\")).unwrap();\n\tlet matrix: Vec<_> = matrix.iter().map(|x| x.to_float()).collect();\n\twriteln!(output_file, \"camera_to_xyz = {:.4?}\", matrix).unwrap();\n}\n"
  },
  {
    "path": "node-graph/LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "node-graph/README.md",
    "content": "# Creating Nodes In Graphite\n\n## Purpose of Nodes\n\nGraphite is an image editor which is centered around a node based editing workflow, which allows operations to be visually connected in a graph. This is flexible as it allows all operations to be viewed or modified at any time without losing original data. The node system has been designed to be as general as possible with all data types being representable and a broad selection of nodes for a variety of use cases being planned.\n\n## The Document Graph\n\nThe graph that is presented to users in the editor is known as the document graph, and is defined in the `NodeNetwork` struct. Each node that has been placed in this graph has the following properties:\n\n```rs\npub struct DocumentNode {\n\tpub inputs: Vec<NodeInput>,\n\tpub call_argument: Type,\n\tpub implementation: DocumentNodeImplementation,\n\tpub skip_deduplication: bool,\n\tpub visible: bool,\n\tpub original_location: OriginalLocation,\n}\n```\n(Explanatory comments omitted; the actual definition is currently found in [`node-graph/graph-craft/src/document.rs`](https://github.com/GraphiteEditor/Graphite/blob/master/node-graph/graph-craft/src/document.rs))\n\nEach `DocumentNode` is of a particular type, for example the \"Opacity\" node type. You can define your own type of document node in `editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs`. A sample document node type definition for the opacity node is shown:\n\n```rs\nDocumentNodeDefinition {\n\tname: \"Opacity\",\n\tcategory: \"Image Adjustments\",\n\timplementation: DocumentNodeImplementation::proto(\"graphene_core::raster::OpacityNode\"),\n\tinputs: vec![\n\t\tDocumentInputType::value(\"Image\", TaggedValue::ImageFrame(ImageFrame::empty()), true),\n\t\tDocumentInputType::value(\"Factor\", TaggedValue::F32(100.), false),\n\t],\n\toutputs: vec![DocumentOutputType::new(\"Image\", FrontendGraphDataType::Raster)],\n\tproperties: node_properties::multiply_opacity,\n\t..Default::default()\n},\n```\n\nThe identifier here must be the same as that of the proto-node which will be discussed soon (usually the path to the node implementation).\n\n> [!NOTE]\n> Nodes defined in `graphene_core` are re-exported by `graphene_std`. However if the strings for the type names do not match exactly then you will encounter an error.\n\n## Properties panel\n\nThe input names are shown in the graph when an input is exposed (with a dot in the properties panel). The default input is used when a node is first created or when a link is disconnected. An input is comprised from a `TaggedValue` (allowing serialization of a dynamic type with serde) in addition to an exposed boolean, which defines if the input is shown as a dot in the node graph UI by default. In the opacity node, the \"Color\" input is shown but the \"Factor\" input is hidden from the graph by default, allowing for a less cluttered graph.\n\nThe properties field is a function that defines a number input, which can be seen by selecting the opacity node in the graph. The code for this property is shown below:\n\n```rs\npub fn multiply_opacity(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {\n\tlet factor = number_widget(document_node, node_id, 1, \"Factor\", NumberInput::default().min(0.).max(100.).unit(\"%\"), true);\n\n\tvec![LayoutGroup::Row { widgets: factor }]\n}\n```\n\n## Graphene (proto node executor)\n\nThe graphene crate (found in `gcore/`) and the graphene standard library (found in `gstd/`) is where actual implementation for nodes are located. \n\nImplementing a node is done by defining a `struct` implementing the `Node` trait. The `Node` trait has a required function named `eval` that takes one generic input. A sample implementation for an opacity node acting on a color is seen below:\n\n```rs\nuse crate::{Color, Node};\n\n#[derive(Debug, Clone, Copy)]\npub struct OpacityNode<OpacityMultiplierInput> {\n\topacity_multiplier: OpacityMultiplierInput,\n}\n\nimpl<'i, OpacityMultiplierInput: Node<'i, (), Output = f64> + 'i> Node<'i, Color> for OpacityNode<OpacityMultiplierInput> {\n\ttype Output = Color;\n\tfn eval(&'i self, color: Color) -> Color {\n\t\tlet opacity_multiplier = self.opacity_multiplier.eval(()) as f32 / 100.;\n\t\tColor::from_rgbaf32_unchecked(color.r(), color.g(), color.b(), color.a() * opacity_multiplier)\n\t}\n}\n```\n\nThe `eval` function can only take one input. To support more than one input, the node struct can store references to other nodes. This can be seen here, as the `opacity_multiplier` field, which is generic and is constrained to the trait `Node<'i, (), Output = f64>`. This means that it is a node with the input of `()` (no input is required to compute the opacity) and an output of an `f64`.\n\nTo compute the value when executing the `OpacityNode`, we need to call `self.opacity_multiplier.eval(())`. This evaluates the node that provides the `opacity_multiplier` input, with the input value of `()`— nothing. This occurs each time the opacity node is run.\n\nTo test this:\n```rs\n#[test]\nfn test_opacity_node() {\n\tlet opacity_node = OpacityNode {\n\t\topacity_multiplier: crate::value::CopiedNode(10_f64), // set opacity to 10%\n\t};\n\tassert_eq!(opacity_node.eval(Color::WHITE), Color::from_rgbaf32_unchecked(1., 1., 1., 0.1));\n}\n```\n\nThe `graphene_core::value::CopiedNode` is a node that, when evaluated, copies `10_f32` and returns it.\n\n## Creating a new node\n\nInstead of manually implementing the `Node` trait with complex generics, one can use the `node` macro, which can be applied to a function like `opacity`. This will generate the struct, implementation, node_registry entry, document node definition and properties panel entries:\n\n```rs\n#[node_macro::node(category(\"Raster: Adjustments\"))]\nfn opacity(_input: (), #[default(424242)] color: Color, #[soft_min(0.1)] opacity_multiplier: f64) -> Color {\n\tlet opacity_multiplier = opacity_multiplier as f32 / 100.;\n\tColor::from_rgbaf32_unchecked(color.r(), color.g(), color.b(), color.a() * opacity_multiplier)\n}\n```\n\n## Additional Macro Options\n\nThe macro invocation can be extended with additional attributes. The currently supported attributes are (`name`, `path`, `skip_impl`, `category`). When using generics the `#[implementations()]` attribute can be used to automatically populate the node_registry for you. You can also use the `default`, `expose`, `min`, `max` and `range_mode` attributes to influence how the properties are generated.\n\n## Executing a document `NodeNetwork`\n\nWhen the document graph is executed, the following steps occur:\n- The `NodeNetwork` is flattened using `NodeNetwork::flatten`. This involves removing any `DocumentNodeImplementation::Network` - which allow for nested document node networks. Instead, all of the inner nodes are moved into a single node graph.\n- The `NodeNetwork` is converted into a proto-graph. Each node's inputs are stored as a list of node IDs in the `ConstructionArgs` struct in the `ProtoNode`. Converting a document graph into a proto graph is done with `NodeNetwork::into_proto_networks`.\n- The newly created `ProtoNode`s are then converted into the corresponding constructor functions using the mapping defined in `node-graph/interpreted-executor/src/node_registry.rs`. This is done by `BorrowTree::push_node`.\n- The constructor functions are run with the `ConstructionArgs` enum. Constructors generally evaluate the result of these inputs, e.g. if you have a `Pi` node that is used as the second input to an `Add` node, the `Add` node's constructor will evaluate the `Pi` node. This is visible if you place a log statement in the `Pi` node's implementation.\n- The resolved functions are stored in a `BorrowTree`, which allows previous proto-nodes to be referenced as inputs by later nodes. The `BorrowTree` ensures nodes can't be removed while being referenced by other nodes.\n\nThe definition for the constructor of a node that applies the opacity transformation to each pixel of an image:\n```rs\n(\n\t// Matches against the string defined in the document node.\n\tProtoNodeIdentifier::new(\"graphene_core::raster::OpacityNode\"),\n\t// This function is run when converting the `ProtoNode` struct into the desired struct.\n\t|args| {\n\t\tBox::pin(async move {\n\t\t\t// Creates an instance of the struct that defines the node.\n\t\t\tlet node = construct_node!(args, graphene_core::raster::OpacityNode<_>, [f64]).await;\n\t\t\t// Create a new map image node, that calls the `node` for each pixel.\n\t\t\tlet map_node = graphene_std::raster::MapImageNode::new(graphene_core::value::ValueNode::new(node));\n\t\t\t// Wraps this in a type erased future `Box<Pin<dyn core::future::Future<Output = T> + 'n>>` - this allows it to work with async.\n\t\t\tlet map_node = graphene_std::any::FutureWrapperNode::new(map_node);\n\t\t\t// The `DynAnyNode` downcasts its input from a `Box<dyn DynAny>` i.e. dynamically typed, to the desired statically typed input value. It then runs the wrapped node and converts the result back into a dynamically typed `Box<dyn DynAny>`.\n\t\t\tlet any: DynAnyNode<Image<Color>, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(map_node));\n\t\t\t// Nodes are stored as type erased, which means they are `Box<dyn NodeIo + Node>`. This allows us to create dynamic graphs, using dynamic dispatch so we do not have to know all node combinations at compile time.\n\t\t\tany.into_type_erased()\n\t\t})\n\t},\n\t// Defines the call argument, return value, and inputs.\n\tNodeIOTypes::new(concrete!(Image<Color>), concrete!(Image<Color>), vec![fn_type!((), f64)]),\n),\n```\n\nNodes in the borrow stack take a `Box<dyn DynAny>` as input and output another `Box<dyn DynAny>`, to allow for any type. To use a specific type, we must downcast the values that have been passed in.\nHowever the `OpacityNode` only works on one pixel at a time, so we first insert a `MapImageNode` to call the `OpacityNode` for every pixel in the image.\nFinally we call `.into_type_erased()` on the result and that is inserted into the borrow stack.\n\nWe also need to add an implementation so that the user can change the opacity of just a single color. To simplify this process for raster nodes, a `raster_node!` macro is available which can simplify the definition of the opacity node to:\n```rs\nraster_node!(graphene_core::raster::OpacityNode<_>, params: [f64]),\n```\n\nThere is also the more general `register_node!` for nodes that do not need to run per pixel.\n```rs\nregister_node!(graphene_core::transform_nodes::SetTransformNode<_>, input: Vector, params: [DAffine2]),\n```\n\n## Debugging\n\nDebugging inside your node can be done with the `log::debug!()` macro, for example `log::debug!(\"The opacity is {opacity_multiplier}\");`.\n\nWe need a utility to easily view a graph as the various steps are applied. We also need a way to transparently see which constructors are being run, which nodes are being evaluated, and in what order.\n\n## Conclusion\n\nWhile we simplify the writing of nodes using the macro by hiding some of the details, creating nodes involves many files and concepts. We will work to continue making this system easier to use.\n\nAny contributions you might have would be greatly appreciated. If any parts of this guide are outdated or difficult to understand, please feel free to ask for help in the Graphite Discord. We are very happy to answer any questions :)\n"
  },
  {
    "path": "node-graph/graph-craft/Cargo.toml",
    "content": "[package]\nname = \"graph-craft\"\nversion = \"0.1.0\"\nedition = \"2024\"\nlicense = \"MIT OR Apache-2.0\"\nauthors.workspace = true\n\n[features]\ndefault = [\"dealloc_nodes\", \"wgpu\", \"loading\"]\ndealloc_nodes = [\"core-types/dealloc_nodes\"]\nwgpu = [\"wgpu-executor\"]\ntokio = [\"dep:tokio\"]\nloading = [\"serde_json\"]\nwasm = [\n\t\"core-types/wasm\",\n\t\"graphic-types/wasm\",\n\t\"text-nodes/wasm\",\n\t\"tsify\",\n\t\"wasm-bindgen\",\n]\n\n[dependencies]\n# Local dependencies\ndyn-any = { workspace = true }\ncore-types = { workspace = true }\nbrush-nodes = { workspace = true }\ngraphene-core = { workspace = true }\ngraphene-application-io = { workspace = true }\nrendering = { workspace = true }\nraster-nodes = { workspace = true }\nvector-nodes = { workspace = true }\ngraphic-types = { workspace = true }\ntext-nodes = { workspace = true }\n\n# Workspace dependencies\nlog = { workspace = true }\nglam = { workspace = true }\nrustc-hash = { workspace = true }\nurl = { workspace = true }\nreqwest = { workspace = true }\nserde = { workspace = true }\n\n# Optional workspace dependencies\nwgpu-executor = { workspace = true, optional = true }\ntokio = { workspace = true, optional = true }\nserde_json = { workspace = true, optional = true }\ntsify = { workspace = true, optional = true }\nwasm-bindgen = { workspace = true, optional = true }\n\n# Workspace dependencies\n[target.'cfg(target_family = \"wasm\")'.dependencies]\nweb-sys = { workspace = true, features = [\"Navigator\", \"Gpu\"] }\njs-sys = { workspace = true }\nwasm-bindgen = { workspace = true }\n\n[target.'cfg(not(target_family = \"wasm\"))'.dependencies]\nwinit = { workspace = true }\n\n[dev-dependencies]\n# Workspace dependencies\ngraph-craft = { workspace = true, features = [\"loading\"] }\npretty_assertions = { workspace = true }\ncriterion = { workspace = true }\niai-callgrind = { workspace = true }\n\n# Benchmarks\n[[bench]]\nname = \"compile_demo_art_criterion\"\nharness = false\n\n[[bench]]\nname = \"compile_demo_art_iai\"\nharness = false\n"
  },
  {
    "path": "node-graph/graph-craft/benches/compile_demo_art_criterion.rs",
    "content": "use std::hint::black_box;\n\nuse criterion::{Criterion, criterion_group, criterion_main};\nuse graph_craft::util::DEMO_ART;\n\nfn compile_to_proto(c: &mut Criterion) {\n\tuse graph_craft::util::{compile, load_from_name};\n\tlet mut c = c.benchmark_group(\"Compile Network cold\");\n\n\tfor name in DEMO_ART {\n\t\tlet network = load_from_name(name);\n\t\tc.bench_function(name, |b| b.iter_batched(|| network.clone(), |network| compile(black_box(network)), criterion::BatchSize::SmallInput));\n\t}\n}\n\ncriterion_group!(benches, compile_to_proto);\ncriterion_main!(benches);\n"
  },
  {
    "path": "node-graph/graph-craft/benches/compile_demo_art_iai.rs",
    "content": "use graph_craft::document::NodeNetwork;\nuse graph_craft::util::*;\nuse iai_callgrind::{library_benchmark, library_benchmark_group, main};\n\n#[library_benchmark]\n#[benches::with_setup(args = [\"isometric-fountain\", \"painted-dreams\", \"procedural-string-lights\", \"parametric-dunescape\", \"red-dress\", \"valley-of-spires\"], setup = load_from_name)]\npub fn compile_to_proto(_input: NodeNetwork) {\n\tstd::hint::black_box(compile(_input));\n}\n\nlibrary_benchmark_group!(name = compile_group; benchmarks = compile_to_proto);\n\nmain!(library_benchmark_groups = compile_group);\n"
  },
  {
    "path": "node-graph/graph-craft/src/document/value.rs",
    "content": "use super::DocumentNode;\nuse crate::proto::{Any as DAny, FutureAny};\nuse crate::wasm_application_io::WasmEditorApi;\nuse brush_nodes::brush_cache::BrushCache;\nuse brush_nodes::brush_stroke::BrushStroke;\nuse core_types::table::Table;\nuse core_types::uuid::NodeId;\nuse core_types::{Color, ContextFeatures, MemoHash, Node, Type};\nuse dyn_any::DynAny;\npub use dyn_any::StaticType;\nuse glam::{Affine2, Vec2};\npub use glam::{DAffine2, DVec2, IVec2, UVec2};\nuse graphene_application_io::{ImageTexture, SurfaceFrame};\nuse graphic_types::Artboard;\nuse graphic_types::Graphic;\nuse graphic_types::Vector;\nuse graphic_types::raster_types::Image;\nuse graphic_types::raster_types::{CPU, Raster};\nuse graphic_types::vector_types::vector;\nuse graphic_types::vector_types::vector::ReferencePoint;\nuse graphic_types::vector_types::vector::style::Fill;\nuse graphic_types::vector_types::vector::style::GradientStops;\nuse rendering::RenderMetadata;\nuse std::fmt::Display;\nuse std::hash::Hash;\nuse std::marker::PhantomData;\nuse std::str::FromStr;\npub use std::sync::Arc;\nuse text_nodes::vector_types::GradientStop;\n\npub struct TaggedValueTypeError;\n\n/// Macro to generate the tagged value enum.\nmacro_rules! tagged_value {\n\t($ ($( #[$meta:meta] )* $identifier:ident ($ty:ty) ),* $(,)?) => {\n\t\t/// A type that is known, allowing serialization (serde::Deserialize is not object safe)\n\t\t#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]\n\t\t#[allow(clippy::large_enum_variant)] // TODO(TrueDoctor): Properly solve this disparity between the size of the largest and next largest variants\n\t\tpub enum TaggedValue {\n\t\t\tNone,\n\t\t\t$( $(#[$meta] ) *$identifier( $ty ), )*\n\t\t\tRenderOutput(RenderOutput),\n\t\t\tSurfaceFrame(SurfaceFrame),\n\t\t\t#[serde(skip)]\n\t\t\tEditorApi(Arc<WasmEditorApi>)\n\t\t}\n\n\t\t// We must manually implement hashing because some values are floats and so do not reproducibly hash (see FakeHash below)\n\t\t#[allow(clippy::derived_hash_with_manual_eq)]\n\t\timpl Hash for TaggedValue {\n\t\t\tfn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n\t\t\t\tcore::mem::discriminant(self).hash(state);\n\t\t\t\tmatch self {\n\t\t\t\t\tSelf::None => {}\n\t\t\t\t\t$( Self::$identifier(x) => {x.hash(state)}),*\n\t\t\t\t\tSelf::RenderOutput(x) => x.hash(state),\n\t\t\t\t\tSelf::SurfaceFrame(x) => x.hash(state),\n\t\t\t\t\tSelf::EditorApi(x) => x.hash(state),\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\timpl<'a> TaggedValue {\n\t\t\t/// Converts to a Box<dyn DynAny>\n\t\t\tpub fn to_dynany(self) -> DAny<'a> {\n\t\t\t\tmatch self {\n\t\t\t\t\tSelf::None => Box::new(()),\n\t\t\t\t\t$( Self::$identifier(x) => Box::new(x), )*\n\t\t\t\t\tSelf::RenderOutput(x) => Box::new(x),\n\t\t\t\t\tSelf::SurfaceFrame(x) => Box::new(x),\n\t\t\t\t\tSelf::EditorApi(x) => Box::new(x),\n\t\t\t\t}\n\t\t\t}\n\t\t\t/// Converts to a Arc<dyn Any + Send + Sync + 'static>\n\t\t\tpub fn to_any(self) -> Arc<dyn std::any::Any + Send + Sync + 'static> {\n\t\t\t\tmatch self {\n\t\t\t\t\tSelf::None => Arc::new(()),\n\t\t\t\t\t$( Self::$identifier(x) => Arc::new(x), )*\n\t\t\t\t\tSelf::RenderOutput(x) => Arc::new(x),\n\t\t\t\t\tSelf::SurfaceFrame(x) => Arc::new(x),\n\t\t\t\t\tSelf::EditorApi(x) => Arc::new(x),\n\t\t\t\t}\n\t\t\t}\n\t\t\t/// Creates a core_types::Type::Concrete(TypeDescriptor { .. }) with the type of the value inside the tagged value\n\t\t\tpub fn ty(&self) -> Type {\n\t\t\t\tmatch self {\n\t\t\t\t\tSelf::None => concrete!(()),\n\t\t\t\t\t$( Self::$identifier(_) => concrete!($ty), )*\n\t\t\t\t\tSelf::RenderOutput(_) => concrete!(RenderOutput),\n\t\t\t\t\tSelf::SurfaceFrame(_) => concrete!(SurfaceFrame),\n\t\t\t\t\tSelf::EditorApi(_) => concrete!(&WasmEditorApi)\n\t\t\t\t}\n\t\t\t}\n\t\t\t/// Attempts to downcast the dynamic type to a tagged value\n\t\t\tpub fn try_from_any(input: Box<dyn DynAny<'a> + 'a>) -> Result<Self, String> {\n\t\t\t\tuse dyn_any::downcast;\n\t\t\t\tuse std::any::TypeId;\n\n\t\t\t\tmatch DynAny::type_id(input.as_ref()) {\n\t\t\t\t\tx if x == TypeId::of::<()>() => Ok(TaggedValue::None),\n\t\t\t\t\t$( x if x == TypeId::of::<$ty>() => Ok(TaggedValue::$identifier(*downcast(input).unwrap())), )*\n\t\t\t\t\tx if x == TypeId::of::<RenderOutput>() => Ok(TaggedValue::RenderOutput(*downcast(input).unwrap())),\n\t\t\t\t\tx if x == TypeId::of::<SurfaceFrame>() => Ok(TaggedValue::SurfaceFrame(*downcast(input).unwrap())),\n\n\n\t\t\t\t\t_ => Err(format!(\"Cannot convert {:?} to TaggedValue\", DynAny::type_name(input.as_ref()))),\n\t\t\t\t}\n\t\t\t}\n\t\t\t/// Attempts to downcast the dynamic type to a tagged value\n\t\t\tpub fn try_from_std_any_ref(input: &dyn std::any::Any) -> Result<Self, String> {\n\t\t\t\tuse std::any::TypeId;\n\n\t\t\t\tmatch input.type_id() {\n\t\t\t\t\tx if x == TypeId::of::<()>() => Ok(TaggedValue::None),\n\t\t\t\t\t$( x if x == TypeId::of::<$ty>() => Ok(TaggedValue::$identifier(<$ty as Clone>::clone(input.downcast_ref().unwrap()))), )*\n\t\t\t\t\tx if x == TypeId::of::<RenderOutput>() => Ok(TaggedValue::RenderOutput(RenderOutput::clone(input.downcast_ref().unwrap()))),\n\t\t\t\t\tx if x == TypeId::of::<SurfaceFrame>() => Ok(TaggedValue::SurfaceFrame(SurfaceFrame::clone(input.downcast_ref().unwrap()))),\n\t\t\t\t\t_ => Err(format!(\"Cannot convert {:?} to TaggedValue\",std::any::type_name_of_val(input))),\n\t\t\t\t}\n\t\t\t}\n\t\t\t/// Returns a TaggedValue from the type, where that value is its type's `Default::default()`\n\t\t\tpub fn from_type(input: &Type) -> Option<Self> {\n\t\t\t\tmatch input {\n\t\t\t\t\tType::Generic(_) => None,\n\t\t\t\t\tType::Concrete(concrete_type) => {\n\t\t\t\t\t\tuse std::any::TypeId;\n\t\t\t\t\t\t// TODO: Add default implementations for types such as TaggedValue::Subpaths, and use the defaults here and in document_node_types\n\t\t\t\t\t\t// Tries using the default for the tagged value type. If it not implemented, then uses the default used in document_node_types. If it is not used there, then TaggedValue::None is returned.\n\t\t\t\t\t\tSome(match concrete_type.id? {\n\t\t\t\t\t\t\tx if x == TypeId::of::<()>() => TaggedValue::None,\n\t\t\t\t\t\t\t$( x if x == TypeId::of::<$ty>() => TaggedValue::$identifier(Default::default()), )*\n\t\t\t\t\t\t\t_ => return None,\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t\tType::Fn(_, output) => TaggedValue::from_type(output),\n\t\t\t\t\tType::Future(output) => {\n\t\t\t\t\t\tTaggedValue::from_type(output)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tpub fn from_type_or_none(input: &Type) -> Self {\n\t\t\t\tSelf::from_type(input).unwrap_or(TaggedValue::None)\n\t\t\t}\n\t\t\tpub fn to_debug_string(&self) -> String {\n\t\t\t\tmatch self {\n\t\t\t\t\tSelf::None => \"()\".to_string(),\n\t\t\t\t\t$( Self::$identifier(x) => format!(\"{:?}\", x), )*\n\t\t\t\t\tSelf::RenderOutput(_) => \"RenderOutput\".to_string(),\n\t\t\t\t\tSelf::SurfaceFrame(_) => \"SurfaceFrame\".to_string(),\n\t\t\t\t\tSelf::EditorApi(_) => \"WasmEditorApi\".to_string(),\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t$(\n\t\t\timpl From<$ty> for TaggedValue {\n\t\t\t\tfn from(value: $ty) -> Self {\n\t\t\t\t\tSelf::$identifier(value)\n\t\t\t\t}\n\t\t\t}\n\t\t)*\n\n\t\t$(\n\t\t\timpl<'a> TryFrom<&'a TaggedValue> for &'a $ty {\n\t\t\t\ttype Error = TaggedValueTypeError;\n\t\t\t\tfn try_from(value: &'a TaggedValue) -> Result<Self, Self::Error> {\n\t\t\t\t\tmatch value{\n\t\t\t\t\t\tTaggedValue::$identifier(value) => Ok(value),\n\t\t\t\t\t\t_ => Err(TaggedValueTypeError),\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t)*\n\t};\n}\n\ntagged_value! {\n\t// ===============\n\t// PRIMITIVE TYPES\n\t// ===============\n\tF32(f32),\n\tF64(f64),\n\tU32(u32),\n\tU64(u64),\n\tBool(bool),\n\tString(String),\n\t// ========================\n\t// LISTS OF PRIMITIVE TYPES\n\t// ========================\n\t#[serde(alias = \"VecF32\")] // TODO: Eventually remove this alias document upgrade code\n\tVecF64(Vec<f64>),\n\tVecDVec2(Vec<DVec2>),\n\tF64Array4([f64; 4]),\n\tVecString(Vec<String>),\n\tNodePath(Vec<NodeId>),\n\t// ===========\n\t// TABLE TYPES\n\t// ===========\n\tGraphicUnused(Graphic), // TODO: This is unused but removing it causes `cargo test` to infinitely recurse its type solving; figure out why and then remove this\n\t#[serde(deserialize_with = \"graphic_types::migrations::migrate_vector\")] // TODO: Eventually remove this migration document upgrade code\n\t#[serde(alias = \"VectorData\")]\n\tVector(Table<Vector>),\n\t#[serde(deserialize_with = \"graphic_types::raster_types::image::migrate_image_frame\")] // TODO: Eventually remove this migration document upgrade code\n\t#[serde(alias = \"ImageFrame\", alias = \"RasterData\", alias = \"Image\")]\n\tRaster(Table<Raster<CPU>>),\n\t#[serde(deserialize_with = \"graphic_types::graphic::migrate_graphic\")] // TODO: Eventually remove this migration document upgrade code\n\t#[serde(alias = \"GraphicGroup\", alias = \"Group\")]\n\tGraphic(Table<Graphic>),\n\t#[serde(deserialize_with = \"graphic_types::artboard::migrate_artboard\")] // TODO: Eventually remove this migration document upgrade code\n\t#[serde(alias = \"ArtboardGroup\")]\n\tArtboard(Table<Artboard>),\n\t#[serde(deserialize_with = \"core_types::misc::migrate_color\")] // TODO: Eventually remove this migration document upgrade code\n\t#[serde(alias = \"ColorTable\", alias = \"OptionalColor\", alias = \"ColorNotInTable\")]\n\tColor(Table<Color>),\n\t#[serde(deserialize_with = \"graphic_types::vector_types::gradient::migrate_gradient_stops\")] // TODO: Eventually remove this migration document upgrade code\n\t#[serde(alias = \"GradientPositions\", alias = \"GradientStops\")]\n\tGradientTable(Table<GradientStops>),\n\t// ============\n\t// STRUCT TYPES\n\t// ============\n\tFVec2(Vec2),\n\tFAffine2(Affine2),\n\t#[serde(alias = \"IVec2\", alias = \"UVec2\")]\n\tDVec2(DVec2),\n\tDAffine2(DAffine2),\n\tStroke(graphic_types::vector_types::vector::style::Stroke),\n\tGradient(graphic_types::vector_types::vector::style::Gradient),\n\tFont(text_nodes::Font),\n\tBrushStrokes(Vec<BrushStroke>),\n\tBrushCache(BrushCache),\n\tDocumentNode(DocumentNode),\n\tContextFeatures(ContextFeatures),\n\tCurve(raster_nodes::curve::Curve),\n\tFootprint(core_types::transform::Footprint),\n\tVectorModification(Box<vector::VectorModification>),\n\t// ==========\n\t// ENUM TYPES\n\t// ==========\n\tFill(vector::style::Fill),\n\tBlendMode(core_types::blending::BlendMode),\n\tLuminanceCalculation(raster_nodes::adjustments::LuminanceCalculation),\n\tQRCodeErrorCorrectionLevel(vector_nodes::generator_nodes::QRCodeErrorCorrectionLevel),\n\tXY(graphene_core::extract_xy::XY),\n\tRedGreenBlue(raster_nodes::adjustments::RedGreenBlue),\n\tRedGreenBlueAlpha(raster_nodes::adjustments::RedGreenBlueAlpha),\n\tRealTimeMode(graphene_core::animation::RealTimeMode),\n\tNoiseType(raster_nodes::adjustments::NoiseType),\n\tFractalType(raster_nodes::adjustments::FractalType),\n\tCellularDistanceFunction(raster_nodes::adjustments::CellularDistanceFunction),\n\tCellularReturnType(raster_nodes::adjustments::CellularReturnType),\n\tDomainWarpType(raster_nodes::adjustments::DomainWarpType),\n\tRelativeAbsolute(raster_nodes::adjustments::RelativeAbsolute),\n\tSelectiveColorChoice(raster_nodes::adjustments::SelectiveColorChoice),\n\tGridType(vector::misc::GridType),\n\tArcType(vector::misc::ArcType),\n\tRowsOrColumns(vector::misc::RowsOrColumns),\n\tMergeByDistanceAlgorithm(vector::misc::MergeByDistanceAlgorithm),\n\tExtrudeJoiningAlgorithm(vector::misc::ExtrudeJoiningAlgorithm),\n\tPointSpacingType(vector::misc::PointSpacingType),\n\tSpiralType(vector::misc::SpiralType),\n\t#[serde(alias = \"LineCap\")]\n\tStrokeCap(vector::style::StrokeCap),\n\t#[serde(alias = \"LineJoin\")]\n\tStrokeJoin(vector::style::StrokeJoin),\n\tStrokeAlign(vector::style::StrokeAlign),\n\tPaintOrder(vector::style::PaintOrder),\n\tFillType(vector::style::FillType),\n\tGradientType(vector::style::GradientType),\n\tReferencePoint(vector::ReferencePoint),\n\tCentroidType(vector::misc::CentroidType),\n\tBooleanOperation(vector::misc::BooleanOperation),\n\tTextAlign(text_nodes::TextAlign),\n}\n\nimpl TaggedValue {\n\tpub fn to_primitive_string(&self) -> String {\n\t\tmatch self {\n\t\t\tTaggedValue::None => \"()\".to_string(),\n\t\t\tTaggedValue::String(x) => format!(\"\\\"{x}\\\"\"),\n\t\t\tTaggedValue::U32(x) => x.to_string() + \"_u32\",\n\t\t\tTaggedValue::U64(x) => x.to_string() + \"_u64\",\n\t\t\tTaggedValue::F32(x) => x.to_string() + \"_f32\",\n\t\t\tTaggedValue::F64(x) => x.to_string() + \"_f64\",\n\t\t\tTaggedValue::Bool(x) => x.to_string(),\n\t\t\tTaggedValue::BlendMode(x) => \"BlendMode::\".to_string() + &x.to_string(),\n\t\t\t_ => panic!(\"Cannot convert to primitive string\"),\n\t\t}\n\t}\n\n\tpub fn from_primitive_string(string: &str, ty: &Type) -> Option<Self> {\n\t\tfn to_dvec2(input: &str) -> Option<DVec2> {\n\t\t\tlet mut split = input.split(',');\n\t\t\tlet x = split.next()?.trim().parse().ok()?;\n\t\t\tlet y = split.next()?.trim().parse().ok()?;\n\t\t\tSome(DVec2::new(x, y))\n\t\t}\n\n\t\tfn to_color(input: &str) -> Option<Color> {\n\t\t\t// String syntax (e.g. \"000000ff\")\n\t\t\tif input.starts_with('\"') && input.ends_with('\"') {\n\t\t\t\tlet hex = input.trim().trim_matches('\"').trim().trim_start_matches('#');\n\t\t\t\tlet color = Color::from_hex_str(hex);\n\t\t\t\tif color.is_none() {\n\t\t\t\t\tlog::error!(\"Invalid default value color string: {input}\");\n\t\t\t\t}\n\t\t\t\treturn color;\n\t\t\t}\n\n\t\t\t// Color constant syntax (e.g. Color::BLACK)\n\t\t\tlet mut choices = input.split(\"::\");\n\t\t\tlet (first, second) = (choices.next()?.trim(), choices.next()?.trim());\n\t\t\tif first == \"Color\" {\n\t\t\t\treturn Some(match second {\n\t\t\t\t\t\"BLACK\" => Color::BLACK,\n\t\t\t\t\t\"WHITE\" => Color::WHITE,\n\t\t\t\t\t\"RED\" => Color::RED,\n\t\t\t\t\t\"GREEN\" => Color::GREEN,\n\t\t\t\t\t\"BLUE\" => Color::BLUE,\n\t\t\t\t\t\"YELLOW\" => Color::YELLOW,\n\t\t\t\t\t\"CYAN\" => Color::CYAN,\n\t\t\t\t\t\"MAGENTA\" => Color::MAGENTA,\n\t\t\t\t\t\"TRANSPARENT\" => Color::TRANSPARENT,\n\t\t\t\t\t_ => {\n\t\t\t\t\t\tlog::error!(\"Invalid default value color constant: {input}\");\n\t\t\t\t\t\treturn None;\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tlog::error!(\"Invalid default value color: {input}\");\n\t\t\tNone\n\t\t}\n\n\t\tfn to_gradient(input: &str) -> Option<GradientStops> {\n\t\t\t// String syntax: (e.g. \"000000ff, ff0000ff\")\n\t\t\tlet stops = input.split(',').filter_map(|s| to_color(s.trim())).collect::<Vec<_>>();\n\t\t\tif stops.len() == 1 {\n\t\t\t\tSome(GradientStops::new(vec![\n\t\t\t\t\tGradientStop {\n\t\t\t\t\t\tposition: 0.,\n\t\t\t\t\t\tmidpoint: 0.5,\n\t\t\t\t\t\tcolor: stops[0],\n\t\t\t\t\t},\n\t\t\t\t\tGradientStop {\n\t\t\t\t\t\tposition: 1.,\n\t\t\t\t\t\tmidpoint: 0.5,\n\t\t\t\t\t\tcolor: stops[0],\n\t\t\t\t\t},\n\t\t\t\t]))\n\t\t\t} else if stops.len() >= 2 {\n\t\t\t\tlet step = 1. / (stops.len() - 1) as f64;\n\t\t\t\tSome(GradientStops::new(stops.into_iter().enumerate().map(|(i, color)| GradientStop {\n\t\t\t\t\tposition: i as f64 * step,\n\t\t\t\t\tmidpoint: 0.5,\n\t\t\t\t\tcolor,\n\t\t\t\t})))\n\t\t\t} else {\n\t\t\t\tlog::error!(\"Invalid default value gradient string: {input}\");\n\t\t\t\tNone\n\t\t\t}\n\t\t}\n\n\t\tfn to_reference_point(input: &str) -> Option<ReferencePoint> {\n\t\t\tlet mut choices = input.split(\"::\");\n\t\t\tlet (first, second) = (choices.next()?.trim(), choices.next()?.trim());\n\t\t\tif first == \"ReferencePoint\" {\n\t\t\t\treturn Some(match second {\n\t\t\t\t\t\"None\" => ReferencePoint::None,\n\t\t\t\t\t\"TopLeft\" => ReferencePoint::TopLeft,\n\t\t\t\t\t\"TopCenter\" => ReferencePoint::TopCenter,\n\t\t\t\t\t\"TopRight\" => ReferencePoint::TopRight,\n\t\t\t\t\t\"CenterLeft\" => ReferencePoint::CenterLeft,\n\t\t\t\t\t\"Center\" => ReferencePoint::Center,\n\t\t\t\t\t\"CenterRight\" => ReferencePoint::CenterRight,\n\t\t\t\t\t\"BottomLeft\" => ReferencePoint::BottomLeft,\n\t\t\t\t\t\"BottomCenter\" => ReferencePoint::BottomCenter,\n\t\t\t\t\t\"BottomRight\" => ReferencePoint::BottomRight,\n\t\t\t\t\t_ => {\n\t\t\t\t\t\tlog::error!(\"Invalid ReferencePoint default type variant: {input}\");\n\t\t\t\t\t\treturn None;\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tlog::error!(\"Invalid ReferencePoint default type: {input}\");\n\t\t\tNone\n\t\t}\n\n\t\tmatch ty {\n\t\t\tType::Generic(_) => None,\n\t\t\tType::Concrete(concrete_type) => {\n\t\t\t\tlet ty = concrete_type.id?;\n\t\t\t\tuse std::any::TypeId;\n\t\t\t\t// TODO: Add default implementations for types such as TaggedValue::Subpaths, and use the defaults here and in document_node_types\n\t\t\t\t// Tries using the default for the tagged value type. If it not implemented, then uses the default used in document_node_types. If it is not used there, then TaggedValue::None is returned.\n\t\t\t\tlet ty = match () {\n\t\t\t\t\t() if ty == TypeId::of::<()>() => TaggedValue::None,\n\t\t\t\t\t() if ty == TypeId::of::<String>() => TaggedValue::String(string.into()),\n\t\t\t\t\t() if ty == TypeId::of::<f64>() => FromStr::from_str(string).map(TaggedValue::F64).ok()?,\n\t\t\t\t\t() if ty == TypeId::of::<f32>() => FromStr::from_str(string).map(TaggedValue::F32).ok()?,\n\t\t\t\t\t() if ty == TypeId::of::<u64>() => FromStr::from_str(string).map(TaggedValue::U64).ok()?,\n\t\t\t\t\t() if ty == TypeId::of::<u32>() => FromStr::from_str(string).map(TaggedValue::U32).ok()?,\n\t\t\t\t\t() if ty == TypeId::of::<DVec2>() => to_dvec2(string).map(TaggedValue::DVec2)?,\n\t\t\t\t\t() if ty == TypeId::of::<bool>() => FromStr::from_str(string).map(TaggedValue::Bool).ok()?,\n\t\t\t\t\t// `Color` (not in a table) is still currently needed by `BlackAndWhiteNode` and `ColorOverlayNode` GPU `shader_node(PerPixelAdjust)` variants\n\t\t\t\t\t() if ty == TypeId::of::<Color>() => to_color(string).map(|color| TaggedValue::Color(Table::new_from_element(color)))?,\n\t\t\t\t\t() if ty == TypeId::of::<Table<Color>>() => to_color(string).map(|color| TaggedValue::Color(Table::new_from_element(color)))?,\n\t\t\t\t\t() if ty == TypeId::of::<Table<GradientStops>>() => to_gradient(string).map(|color| TaggedValue::GradientTable(Table::new_from_element(color)))?,\n\t\t\t\t\t() if ty == TypeId::of::<Fill>() => to_color(string).map(|color| TaggedValue::Fill(Fill::solid(color)))?,\n\t\t\t\t\t() if ty == TypeId::of::<ReferencePoint>() => to_reference_point(string).map(TaggedValue::ReferencePoint)?,\n\t\t\t\t\t_ => return None,\n\t\t\t\t};\n\t\t\t\tSome(ty)\n\t\t\t}\n\t\t\tType::Fn(_, output) => TaggedValue::from_primitive_string(string, output),\n\t\t\tType::Future(fut) => TaggedValue::from_primitive_string(string, fut),\n\t\t}\n\t}\n\n\tpub fn to_u32(&self) -> u32 {\n\t\tmatch self {\n\t\t\tTaggedValue::U32(x) => *x,\n\t\t\t_ => panic!(\"Passed value is not of type u32\"),\n\t\t}\n\t}\n}\n\nimpl Display for TaggedValue {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tmatch self {\n\t\t\tTaggedValue::String(x) => f.write_str(x),\n\t\t\tTaggedValue::U32(x) => f.write_fmt(format_args!(\"{x}\")),\n\t\t\tTaggedValue::U64(x) => f.write_fmt(format_args!(\"{x}\")),\n\t\t\tTaggedValue::F32(x) => f.write_fmt(format_args!(\"{x}\")),\n\t\t\tTaggedValue::F64(x) => f.write_fmt(format_args!(\"{x}\")),\n\t\t\tTaggedValue::Bool(x) => f.write_fmt(format_args!(\"{x}\")),\n\t\t\t_ => panic!(\"Cannot convert to string\"),\n\t\t}\n\t}\n}\n\npub struct UpcastNode {\n\tvalue: MemoHash<TaggedValue>,\n}\nimpl<'input> Node<'input, DAny<'input>> for UpcastNode {\n\ttype Output = FutureAny<'input>;\n\n\tfn eval(&'input self, _: DAny<'input>) -> Self::Output {\n\t\tlet memo_clone = MemoHash::clone(&self.value);\n\t\tBox::pin(async move { memo_clone.into_inner().as_ref().clone().to_dynany() })\n\t}\n}\nimpl UpcastNode {\n\tpub fn new(value: MemoHash<TaggedValue>) -> Self {\n\t\tSelf { value }\n\t}\n}\n#[derive(Default, Debug, Clone, Copy)]\npub struct UpcastAsRefNode<T: AsRef<U> + Sync + Send, U: Sync + Send>(pub T, PhantomData<U>);\n\nimpl<'i, T: 'i + AsRef<U> + Sync + Send, U: 'i + StaticType + Sync + Send> Node<'i, DAny<'i>> for UpcastAsRefNode<T, U> {\n\ttype Output = FutureAny<'i>;\n\t#[inline(always)]\n\tfn eval(&'i self, _: DAny<'i>) -> Self::Output {\n\t\tBox::pin(async move { Box::new(self.0.as_ref()) as DAny<'i> })\n\t}\n}\n\nimpl<T: AsRef<U> + Sync + Send, U: Sync + Send> UpcastAsRefNode<T, U> {\n\tpub const fn new(value: T) -> UpcastAsRefNode<T, U> {\n\t\tUpcastAsRefNode(value, PhantomData)\n\t}\n}\n\n#[derive(Debug, Clone, PartialEq, dyn_any::DynAny, serde::Serialize, serde::Deserialize)]\npub struct RenderOutput {\n\tpub data: RenderOutputType,\n\tpub metadata: RenderMetadata,\n}\n\n#[derive(Debug, Clone, Hash, PartialEq, dyn_any::DynAny, serde::Serialize, serde::Deserialize)]\npub enum RenderOutputType {\n\tCanvasFrame(SurfaceFrame),\n\t#[serde(skip)]\n\tTexture(ImageTexture),\n\t#[serde(skip)]\n\tBuffer {\n\t\tdata: Vec<u8>,\n\t\twidth: u32,\n\t\theight: u32,\n\t},\n\tSvg {\n\t\tsvg: String,\n\t\timage_data: Vec<(u64, Image<Color>)>,\n\t},\n}\n\nimpl Hash for RenderOutput {\n\tfn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n\t\tself.data.hash(state)\n\t}\n}\n\n/// We hash the floats and so-forth despite it not being reproducible because all inputs to the node graph must be hashed otherwise the graph execution breaks (so sorry about this hack)\ntrait FakeHash {\n\tfn hash<H: core::hash::Hasher>(&self, state: &mut H);\n}\nmod fake_hash {\n\tuse super::*;\n\timpl FakeHash for f64 {\n\t\tfn hash<H: core::hash::Hasher>(&self, state: &mut H) {\n\t\t\tself.to_bits().hash(state)\n\t\t}\n\t}\n\timpl FakeHash for f32 {\n\t\tfn hash<H: core::hash::Hasher>(&self, state: &mut H) {\n\t\t\tself.to_bits().hash(state)\n\t\t}\n\t}\n\timpl FakeHash for DVec2 {\n\t\tfn hash<H: core::hash::Hasher>(&self, state: &mut H) {\n\t\t\tself.to_array().iter().for_each(|x| x.to_bits().hash(state))\n\t\t}\n\t}\n\timpl FakeHash for Vec2 {\n\t\tfn hash<H: core::hash::Hasher>(&self, state: &mut H) {\n\t\t\tself.to_array().iter().for_each(|x| x.to_bits().hash(state))\n\t\t}\n\t}\n\timpl FakeHash for DAffine2 {\n\t\tfn hash<H: core::hash::Hasher>(&self, state: &mut H) {\n\t\t\tself.to_cols_array().iter().for_each(|x| x.to_bits().hash(state))\n\t\t}\n\t}\n\timpl FakeHash for Affine2 {\n\t\tfn hash<H: core::hash::Hasher>(&self, state: &mut H) {\n\t\t\tself.to_cols_array().iter().for_each(|x| x.to_bits().hash(state))\n\t\t}\n\t}\n\timpl<T: FakeHash> FakeHash for Option<T> {\n\t\tfn hash<H: core::hash::Hasher>(&self, state: &mut H) {\n\t\t\tif let Some(x) = self {\n\t\t\t\t1.hash(state);\n\t\t\t\tx.hash(state);\n\t\t\t} else {\n\t\t\t\t0.hash(state);\n\t\t\t}\n\t\t}\n\t}\n\timpl<T: FakeHash> FakeHash for Vec<T> {\n\t\tfn hash<H: core::hash::Hasher>(&self, state: &mut H) {\n\t\t\tself.len().hash(state);\n\t\t\tself.iter().for_each(|x| x.hash(state))\n\t\t}\n\t}\n\timpl<T: FakeHash, const N: usize> FakeHash for [T; N] {\n\t\tfn hash<H: core::hash::Hasher>(&self, state: &mut H) {\n\t\t\tself.iter().for_each(|x| x.hash(state))\n\t\t}\n\t}\n\timpl FakeHash for (f64, Color) {\n\t\tfn hash<H: core::hash::Hasher>(&self, state: &mut H) {\n\t\t\tself.0.to_bits().hash(state);\n\t\t\tself.1.hash(state)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "node-graph/graph-craft/src/document.rs",
    "content": "pub mod value;\n\nuse crate::document::value::TaggedValue;\nuse crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode};\nuse core_types::memo::MemoHashGuard;\npub use core_types::uuid::NodeId;\npub use core_types::uuid::generate_uuid;\nuse core_types::{Context, ContextDependencies, Cow, MemoHash, ProtoNodeIdentifier, Type};\nuse dyn_any::DynAny;\nuse glam::IVec2;\nuse log::Metadata;\nuse rustc_hash::{FxBuildHasher, FxHashMap};\nuse std::collections::HashMap;\nuse std::collections::hash_map::DefaultHasher;\nuse std::hash::{Hash, Hasher};\n\n/// Hash two IDs together, returning a new ID that is always consistent for two input IDs in a specific order.\n/// This is used during [`NodeNetwork::flatten`] in order to ensure consistent yet non-conflicting IDs for inner networks.\nfn merge_ids(a: NodeId, b: NodeId) -> NodeId {\n\tlet mut hasher = DefaultHasher::new();\n\ta.hash(&mut hasher);\n\tb.hash(&mut hasher);\n\tNodeId(hasher.finish())\n}\n\n/// Utility function for providing a default boolean value to serde.\n#[inline(always)]\nfn return_true() -> bool {\n\ttrue\n}\n\n/// An instance of a [`DocumentNodeDefinition`] that has been instantiated in a [`NodeNetwork`].\n/// Currently, when an instance is made, it lives all on its own without any lasting connection to the definition.\n/// But we will want to change it in the future so it merely references its definition.\n#[derive(Clone, Debug, PartialEq, Hash, DynAny, serde::Serialize, serde::Deserialize)]\npub struct DocumentNode {\n\t/// The inputs to a node, which are either:\n\t/// - From other nodes within this graph [`NodeInput::Node`],\n\t/// - A constant value [`NodeInput::Value`],\n\t/// - A [`NodeInput::Import`] which specifies that this input comes from outside the graph, which is resolved in the graph flattening step in the case of nested networks.\n\t///\n\t/// In the root network, it is resolved when evaluating the borrow tree.\n\t/// Ensure the click target in the encapsulating network is updated when the inputs cause the node shape to change (currently only when exposing/hiding an input)\n\t/// by using network.update_click_target(node_id).\n\t#[cfg_attr(target_family = \"wasm\", serde(alias = \"outputs\"))]\n\tpub inputs: Vec<NodeInput>,\n\t/// Type of the argument which this node can be evaluated with.\n\t#[serde(default, alias = \"manual_composition\", deserialize_with = \"migrate_call_argument\")]\n\tpub call_argument: Type,\n\t// A nested document network or a proto-node identifier.\n\tpub implementation: DocumentNodeImplementation,\n\t/// Represents the eye icon for hiding/showing the node in the graph UI. When hidden, a node gets replaced with an identity node during the graph flattening step.\n\t#[serde(default = \"return_true\")]\n\tpub visible: bool,\n\t/// When two different proto nodes hash to the same value (e.g. two value nodes each containing `2_u32` or two multiply nodes that have the same node IDs as input), the duplicates are removed.\n\t/// See [`ProtoNetwork::generate_stable_node_ids`] for details.\n\t/// However sometimes this is not desirable, for example in the case of a [`graphene_core::memo::MonitorNode`] that needs to be accessed outside of the graph.\n\t#[serde(default)]\n\tpub skip_deduplication: bool,\n\t/// List of Extract and Inject annotations for the Context.\n\t#[serde(default)]\n\tpub context_features: ContextDependencies,\n\t/// The path to this node and its inputs and outputs as of when [`NodeNetwork::generate_node_paths`] was called.\n\t#[serde(skip)]\n\tpub original_location: OriginalLocation,\n}\n\n/// Represents the original location of a node input/output when [`NodeNetwork::generate_node_paths`] was called, allowing the types and errors to be derived.\n#[derive(Clone, Debug, PartialEq, Eq, Hash, DynAny, serde::Serialize, serde::Deserialize)]\npub struct Source {\n\tpub node: Vec<NodeId>,\n\tpub index: usize,\n}\n\n/// The path to this node and its inputs and outputs as of when [`NodeNetwork::generate_node_paths`] was called.\n#[derive(Clone, Debug, PartialEq, Eq, DynAny, Default, serde::Serialize, serde::Deserialize)]\n#[non_exhaustive]\npub struct OriginalLocation {\n\t/// The original location to the document node - e.g. [grandparent_id, parent_id, node_id].\n\tpub path: Option<Vec<NodeId>>,\n\t/// Each document input source maps to one proto node input (however one proto node input may come from several sources)\n\tpub inputs_source: HashMap<Source, usize>,\n\t/// List of nodes which depend on this node\n\tpub dependants: Vec<Vec<NodeId>>,\n\t/// A list of flags indicating whether the input is exposed in the UI\n\tpub inputs_exposed: Vec<bool>,\n\t/// For automatically inserted Convert and Into nodes, if there is an error, display it on the node it is connect to.\n\tpub auto_convert_index: Option<usize>,\n}\n\nimpl Default for DocumentNode {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tinputs: Default::default(),\n\t\t\tcall_argument: concrete!(Context),\n\t\t\timplementation: Default::default(),\n\t\t\tvisible: true,\n\t\t\tskip_deduplication: Default::default(),\n\t\t\toriginal_location: OriginalLocation::default(),\n\t\t\tcontext_features: Default::default(),\n\t\t}\n\t}\n}\n\nimpl Hash for OriginalLocation {\n\tfn hash<H: Hasher>(&self, state: &mut H) {\n\t\tself.path.hash(state);\n\t\tself.inputs_source.iter().for_each(|val| val.hash(state));\n\t\tself.inputs_exposed.hash(state);\n\t}\n}\nimpl OriginalLocation {\n\tpub fn inputs(&self, index: usize) -> impl Iterator<Item = Source> + '_ {\n\t\t[(index >= 1).then(|| Source {\n\t\t\tnode: self.path.clone().unwrap_or_default(),\n\t\t\tindex: self.inputs_exposed.iter().take(index - 1).filter(|&&exposed| exposed).count(),\n\t\t})]\n\t\t.into_iter()\n\t\t.flatten()\n\t\t.chain(self.inputs_source.iter().filter(move |x| *x.1 == index).map(|(source, _)| source.clone()))\n\t}\n}\nimpl DocumentNode {\n\t/// Locate the input that is a [`NodeInput::Import`] at index `offset` and replace it with a [`NodeInput::Node`].\n\tpub fn populate_first_network_input(&mut self, node_id: NodeId, output_index: usize, offset: usize, source: impl Iterator<Item = Source>, skip: usize) {\n\t\tlet (index, _) = self\n\t\t\t.inputs\n\t\t\t.iter()\n\t\t\t.enumerate()\n\t\t\t.nth(offset)\n\t\t\t.unwrap_or_else(|| panic!(\"no network input found for {self:#?} and offset: {offset}\"));\n\n\t\tself.inputs[index] = NodeInput::Node { node_id, output_index };\n\t\tlet input_source = &mut self.original_location.inputs_source;\n\t\tfor source in source {\n\t\t\tinput_source.insert(source, (index + 1).saturating_sub(skip));\n\t\t}\n\t}\n\n\tfn resolve_proto_node(self) -> ProtoNode {\n\t\tlet DocumentNodeImplementation::ProtoNode(identifier) = self.implementation else {\n\t\t\tunreachable!(\"tried to resolve not flattened node on resolved node {self:?}\");\n\t\t};\n\t\tassert!(!self.inputs.iter().any(|input| matches!(input, NodeInput::Import { .. })), \"received non-resolved input\");\n\n\t\tlet mut construction_args = ConstructionArgs::Nodes(vec![]);\n\n\t\t// If we have one input of the type inline, set it as the construction args\n\t\tif let &[NodeInput::Inline(ref inline)] = self.inputs.as_slice() {\n\t\t\tconstruction_args = ConstructionArgs::Inline(inline.clone());\n\t\t}\n\t\t// If we have one input of the type inline, set it as the construction args\n\t\tif let &[NodeInput::Value { ref tagged_value, .. }] = self.inputs.as_slice() {\n\t\t\tconstruction_args = ConstructionArgs::Value(tagged_value.clone());\n\t\t}\n\t\tif let ConstructionArgs::Nodes(nodes) = &mut construction_args {\n\t\t\tnodes.extend(self.inputs.iter().map(|input| match input {\n\t\t\t\tNodeInput::Node { node_id, .. } => *node_id,\n\t\t\t\t_ => unreachable!(),\n\t\t\t}));\n\t\t}\n\n\t\tProtoNode {\n\t\t\tidentifier,\n\t\t\tcall_argument: self.call_argument,\n\t\t\tconstruction_args,\n\t\t\toriginal_location: self.original_location,\n\t\t\tskip_deduplication: self.skip_deduplication,\n\t\t\tcontext_features: self.context_features,\n\t\t}\n\t}\n}\n\n/// Represents the possible inputs to a node.\n#[derive(Debug, Clone, PartialEq, Hash, DynAny, serde::Serialize, serde::Deserialize)]\npub enum NodeInput {\n\t/// A reference to another node in the same network from which this node can receive its input.\n\tNode { node_id: NodeId, output_index: usize },\n\n\t/// A hardcoded value that can't change after the graph is compiled. Gets converted into a value node during graph compilation.\n\tValue { tagged_value: MemoHash<TaggedValue>, exposed: bool },\n\n\t// TODO: Remove import_type and get type from parent node input\n\t/// Input that is provided by the import from the parent network to this document node network.\n\t#[serde(alias = \"Network\")]\n\tImport { import_type: Type, import_index: usize },\n\n\t/// Input that is extracted from the parent scopes the node resides in. The string argument is the key.\n\tScope(Cow<'static, str>),\n\n\t/// Input that is extracted from the parent scopes the node resides in. The string argument is the key.\n\tReflection(DocumentNodeMetadata),\n\n\t/// A Rust source code string. Allows us to insert literal Rust code. Only used for GPU compilation.\n\t/// We can use this whenever we spin up Rustc. Sort of like inline assembly, but because our language is Rust, it acts as inline Rust.\n\tInline(InlineRust),\n}\n\n#[derive(Debug, Clone, PartialEq, Hash, DynAny, serde::Serialize, serde::Deserialize)]\npub struct InlineRust {\n\tpub expr: String,\n\tpub ty: Type,\n}\n\nimpl InlineRust {\n\tpub fn new(expr: String, ty: Type) -> Self {\n\t\tSelf { expr, ty }\n\t}\n}\n\n#[derive(Debug, Clone, PartialEq, Hash, DynAny, serde::Serialize, serde::Deserialize)]\npub enum DocumentNodeMetadata {\n\tDocumentNodePath,\n}\n\nimpl DocumentNodeMetadata {\n\tpub fn ty(&self) -> Type {\n\t\tmatch self {\n\t\t\tDocumentNodeMetadata::DocumentNodePath => concrete!(Vec<NodeId>),\n\t\t}\n\t}\n}\n\nimpl NodeInput {\n\tpub const fn node(node_id: NodeId, output_index: usize) -> Self {\n\t\tSelf::Node { node_id, output_index }\n\t}\n\n\tpub fn value(tagged_value: TaggedValue, exposed: bool) -> Self {\n\t\tlet tagged_value = tagged_value.into();\n\t\tSelf::Value { tagged_value, exposed }\n\t}\n\n\tpub const fn import(import_type: Type, import_index: usize) -> Self {\n\t\tSelf::Import { import_type, import_index }\n\t}\n\n\tpub fn scope(key: impl Into<Cow<'static, str>>) -> Self {\n\t\tSelf::Scope(key.into())\n\t}\n\n\tfn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId) {\n\t\tif let &mut NodeInput::Node { node_id, output_index } = self {\n\t\t\t*self = NodeInput::Node { node_id: f(node_id), output_index }\n\t\t}\n\t}\n\n\tpub fn is_exposed(&self) -> bool {\n\t\tmatch self {\n\t\t\tNodeInput::Node { .. } => true,\n\t\t\tNodeInput::Value { exposed, .. } => *exposed,\n\t\t\tNodeInput::Import { .. } => true,\n\t\t\tNodeInput::Inline(_) => false,\n\t\t\tNodeInput::Scope(_) => false,\n\t\t\tNodeInput::Reflection(_) => false,\n\t\t}\n\t}\n\n\tpub fn ty(&self) -> Type {\n\t\tmatch self {\n\t\t\tNodeInput::Node { .. } => unreachable!(\"ty() called on NodeInput::Node\"),\n\t\t\tNodeInput::Value { tagged_value, .. } => tagged_value.ty(),\n\t\t\tNodeInput::Import { import_type, .. } => import_type.clone(),\n\t\t\tNodeInput::Inline(_) => panic!(\"ty() called on NodeInput::Inline\"),\n\t\t\tNodeInput::Scope(_) => panic!(\"ty() called on NodeInput::Scope\"),\n\t\t\tNodeInput::Reflection(_) => concrete!(Metadata),\n\t\t}\n\t}\n\n\tpub fn as_value(&self) -> Option<&TaggedValue> {\n\t\tif let NodeInput::Value { tagged_value, .. } = self { Some(tagged_value) } else { None }\n\t}\n\tpub fn as_value_mut(&mut self) -> Option<MemoHashGuard<'_, TaggedValue>> {\n\t\tif let NodeInput::Value { tagged_value, .. } = self { Some(tagged_value.inner_mut()) } else { None }\n\t}\n\tpub fn as_non_exposed_value(&self) -> Option<&TaggedValue> {\n\t\tif let NodeInput::Value { tagged_value, exposed: false } = self { Some(tagged_value) } else { None }\n\t}\n\n\tpub fn as_node(&self) -> Option<NodeId> {\n\t\tif let NodeInput::Node { node_id, .. } = self { Some(*node_id) } else { None }\n\t}\n}\n\n// TODO: Eventually remove this document upgrade code\n#[derive(Clone, Debug, DynAny, serde::Serialize, serde::Deserialize)]\n/// Represents the implementation of a node, which can be a nested [`NodeNetwork`], a proto [`ProtoNodeIdentifier`], or `Extract`.\npub enum OldDocumentNodeImplementation {\n\tNetwork(OldNodeNetwork),\n\t#[serde(alias = \"Unresolved\")]\n\tProtoNode(ProtoNodeIdentifier),\n\tExtract,\n}\n\n#[derive(Clone, Debug, PartialEq, Hash, DynAny, serde::Serialize, serde::Deserialize)]\n/// Represents the implementation of a node, which can be a nested [`NodeNetwork`], a proto [`ProtoNodeIdentifier`], or `Extract`.\npub enum DocumentNodeImplementation {\n\t/// This describes a (document) node built out of a subgraph of other (document) nodes.\n\t///\n\t/// A nested [`NodeNetwork`] that is flattened by the [`NodeNetwork::flatten`] function.\n\tNetwork(NodeNetwork),\n\t/// This describes a (document) node implemented as a proto node.\n\t///\n\t/// A proto node identifier which can be found in `node_registry.rs`.\n\t#[serde(alias = \"Unresolved\")] // TODO: Eventually remove this alias document upgrade code\n\tProtoNode(ProtoNodeIdentifier),\n\t/// The Extract variant is a tag which tells the compilation process to do something special: it invokes language-level functionality built for use by the ExtractNode to enable metaprogramming.\n\t/// When the ExtractNode is compiled, it gets replaced by a value node containing a representation of the source code for the function/lambda of the document node that's fed into the ExtractNode\n\t/// (but only that one document node, not upstream nodes).\n\t///\n\t/// This is explained in more detail here: <https://www.youtube.com/watch?v=72KJa3jQClo>\n\t///\n\t/// Currently we use it for GPU execution, where a node has to get \"extracted\" to its source code representation and stored as a value that can be given to the GpuCompiler node at runtime\n\t/// (to become a compute shader). Future use could involve the addition of an InjectNode to convert the source code form back into an executable node, enabling metaprogramming in the node graph.\n\t/// We would use an assortment of nodes that operate on Graphene source code (just data, no different from any other data flowing through the graph) to make graph transformations.\n\t///\n\t/// We use this for dealing with macros in a syntactic way of modifying the node graph from within the graph itself. Just like we often deal with lambdas to represent a whole group of\n\t/// operations/code/logic, this allows us to basically deal with a lambda at a meta/source-code level, because we need to pass the GPU SPIR-V compiler the source code for a lambda,\n\t/// not the executable logic of a lambda.\n\t///\n\t/// This is analogous to how Rust macros operate at the level of source code, not executable code. When we speak of source code, that represents Graphene's source code in the form of a\n\t/// DocumentNode network, not the text form of Rust's source code. (Analogous to the token stream/AST of a Rust macro.)\n\t///\n\t/// `DocumentNode`s with a `DocumentNodeImplementation::Extract` are converted into a `ClonedNode` that returns the `DocumentNode` specified by the single `NodeInput::Node`. The referenced node\n\t/// (specified by the single `NodeInput::Node`) is removed from the network, and any `NodeInput::Node`s used by the referenced node are replaced with a generically typed network input.\n\tExtract,\n}\n\nimpl Default for DocumentNodeImplementation {\n\tfn default() -> Self {\n\t\tSelf::ProtoNode(graphene_core::ops::identity::IDENTIFIER)\n\t}\n}\n\nimpl DocumentNodeImplementation {\n\tpub fn get_network(&self) -> Option<&NodeNetwork> {\n\t\tmatch self {\n\t\t\tDocumentNodeImplementation::Network(n) => Some(n),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tpub fn get_network_mut(&mut self) -> Option<&mut NodeNetwork> {\n\t\tmatch self {\n\t\t\tDocumentNodeImplementation::Network(n) => Some(n),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tpub fn get_proto_node(&self) -> Option<&ProtoNodeIdentifier> {\n\t\tmatch self {\n\t\t\tDocumentNodeImplementation::ProtoNode(p) => Some(p),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tpub fn output_count(&self) -> usize {\n\t\tmatch self {\n\t\t\tDocumentNodeImplementation::Network(network) => network.exports.len(),\n\t\t\t_ => 1,\n\t\t}\n\t}\n}\n\n// TODO: Eventually remove this document upgrade code\n#[derive(Debug, serde::Deserialize)]\n#[serde(untagged)]\npub enum NodeExportVersions {\n\tOldNodeInput(NodeOutput),\n\tNodeInput(NodeInput),\n}\n\n// TODO: Eventually remove this document upgrade code\n#[derive(Debug, serde::Deserialize)]\npub struct NodeOutput {\n\tpub node_id: NodeId,\n\tpub node_output_index: usize,\n}\n\n// TODO: Eventually remove this document upgrade code\nfn deserialize_exports<'de, D>(deserializer: D) -> Result<Vec<NodeInput>, D::Error>\nwhere\n\tD: serde::Deserializer<'de>,\n{\n\tuse serde::Deserialize;\n\tlet node_input_versions = Vec::<NodeExportVersions>::deserialize(deserializer)?;\n\n\t// Convert Vec<NodeOutput> to Vec<NodeInput>\n\tlet inputs = node_input_versions\n\t\t.into_iter()\n\t\t.map(|node_input_version| {\n\t\t\tlet node_output = match node_input_version {\n\t\t\t\tNodeExportVersions::OldNodeInput(node_output) => node_output,\n\t\t\t\tNodeExportVersions::NodeInput(node_input) => return node_input,\n\t\t\t};\n\t\t\tNodeInput::node(node_output.node_id, node_output.node_output_index)\n\t\t})\n\t\t.collect();\n\n\tOk(inputs)\n}\n\n/// An instance of a [`DocumentNodeDefinition`] that has been instantiated in a [`NodeNetwork`].\n/// Currently, when an instance is made, it lives all on its own without any lasting connection to the definition.\n/// But we will want to change it in the future so it merely references its definition.\n#[derive(Clone, Debug, DynAny, serde::Serialize, serde::Deserialize)]\npub struct OldDocumentNode {\n\t/// A name chosen by the user for this instance of the node. Empty indicates no given name, in which case the node definition's name is displayed to the user in italics.\n\t///  Ensure the click target in the encapsulating network is updated when this is modified by using network.update_click_target(node_id).\n\t#[serde(default)]\n\tpub alias: String,\n\t// TODO: Replace this name with a reference to the [`DocumentNodeDefinition`] node definition to use the name from there instead.\n\t/// The name of the node definition, as originally set by [`DocumentNodeDefinition`], used to display in the UI and to display the appropriate properties.\n\t#[serde(deserialize_with = \"migrate_layer_to_merge\")]\n\tpub name: String,\n\t/// The inputs to a node, which are either:\n\t/// - From other nodes within this graph [`NodeInput::Node`],\n\t/// - A constant value [`NodeInput::Value`],\n\t/// - A [`NodeInput::Import`] which specifies that this input is from outside the graph, which is resolved in the graph flattening step in the case of nested networks.\n\t///\n\t/// In the root network, it is resolved when evaluating the borrow tree.\n\t/// Ensure the click target in the encapsulating network is updated when the inputs cause the node shape to change (currently only when exposing/hiding an input) by using network.update_click_target(node_id).\n\t#[cfg_attr(target_family = \"wasm\", serde(alias = \"outputs\"))]\n\tpub inputs: Vec<NodeInput>,\n\tpub manual_composition: Option<Type>,\n\t// TODO: Remove once this references its definition instead (see above TODO).\n\t/// Indicates to the UI if a primary output should be drawn for this node.\n\t/// True for most nodes, but the Split Channels node is an example of a node that has multiple secondary outputs but no primary output.\n\t#[serde(default = \"return_true\")]\n\tpub has_primary_output: bool,\n\t// A nested document network or a proto-node identifier.\n\tpub implementation: OldDocumentNodeImplementation,\n\t/// User chosen state for displaying this as a left-to-right node or bottom-to-top layer. Ensure the click target in the encapsulating network is updated when the node changes to a layer by using network.update_click_target(node_id).\n\t#[serde(default)]\n\tpub is_layer: bool,\n\t/// Represents the eye icon for hiding/showing the node in the graph UI. When hidden, a node gets replaced with an identity node during the graph flattening step.\n\t#[serde(default = \"return_true\")]\n\tpub visible: bool,\n\t/// Represents the lock icon for locking/unlocking the node in the graph UI. When locked, a node cannot be moved in the graph UI.\n\t#[serde(default)]\n\tpub locked: bool,\n\t/// Metadata about the node including its position in the graph UI. Ensure the click target in the encapsulating network is updated when the node moves by using network.update_click_target(node_id).\n\tpub metadata: OldDocumentNodeMetadata,\n\t/// When two different proto nodes hash to the same value (e.g. two value nodes each containing `2_u32` or two multiply nodes that have the same node IDs as input), the duplicates are removed.\n\t/// See [`ProtoNetwork::generate_stable_node_ids`] for details.\n\t/// However sometimes this is not desirable, for example in the case of a [`graphene_core::memo::MonitorNode`] that needs to be accessed outside of the graph.\n\t#[serde(default)]\n\tpub skip_deduplication: bool,\n\t/// The path to this node and its inputs and outputs as of when [`NodeNetwork::generate_node_paths`] was called.\n\t#[serde(skip)]\n\tpub original_location: OriginalLocation,\n}\n\n// TODO: Eventually remove this document upgrade code\n#[derive(Clone, Debug, PartialEq, Default, Hash, DynAny, serde::Serialize, serde::Deserialize)]\n/// Metadata about the node including its position in the graph UI\npub struct OldDocumentNodeMetadata {\n\tpub position: IVec2,\n}\n\n// TODO: Eventually remove this document upgrade code\n#[derive(Clone, Copy, Debug, PartialEq, Hash, serde::Serialize, serde::Deserialize)]\n/// Root Node is the \"default\" export for a node network. Used by document metadata, displaying UI-only \"Export\" node, and for restoring the default preview node.\npub struct OldRootNode {\n\tpub id: NodeId,\n\tpub output_index: usize,\n}\n\n// TODO: Eventually remove this document upgrade code\n#[derive(PartialEq, Debug, Clone, Hash, Default, serde::Serialize, serde::Deserialize)]\npub enum OldPreviewing {\n\t/// If there is a node to restore the connection to the export for, then it is stored in the option.\n\t/// Otherwise, nothing gets restored and the primary export is disconnected.\n\tYes { root_node_to_restore: Option<OldRootNode> },\n\t#[default]\n\tNo,\n}\n\n// TODO: Eventually remove this document upgrade code\n#[derive(Clone, Debug, DynAny, serde::Serialize, serde::Deserialize)]\n/// A network (subgraph) of nodes containing each [`DocumentNode`] and its ID, as well as list mapping each export to its connected node, or a value if disconnected\npub struct OldNodeNetwork {\n\t/// The list of data outputs that are exported from this network to the parent network.\n\t/// Each export is a reference to a node within this network, paired with its output index, that is the source of the network's exported data.\n\t#[serde(alias = \"outputs\", deserialize_with = \"deserialize_exports\")] // TODO: Eventually remove this alias document upgrade code\n\tpub exports: Vec<NodeInput>,\n\t/// The list of all nodes in this network.\n\t//cfg_attr(feature = \"serde\", #[serde(serialize_with = \"core_types::vector::serialize_hashmap\", deserialize_with = \"core_types::vector::deserialize_hashmap\"))]\n\tpub nodes: HashMap<NodeId, OldDocumentNode>,\n\t/// Indicates whether the network is currently rendered with a particular node that is previewed, and if so, which connection should be restored when the preview ends.\n\t#[serde(default)]\n\tpub previewing: OldPreviewing,\n\t/// Temporary fields to store metadata for \"Import\"/\"Export\" UI-only nodes, eventually will be replaced with lines leading to edges\n\t#[serde(default = \"default_import_metadata\")]\n\tpub imports_metadata: (NodeId, IVec2),\n\t#[serde(default = \"default_export_metadata\")]\n\tpub exports_metadata: (NodeId, IVec2),\n\n\t/// A network may expose nodes as constants which can by used by other nodes using a `NodeInput::Scope(key)`.\n\t#[serde(default)]\n\t//cfg_attr(feature = \"serde\", #[serde(serialize_with = \"core_types::vector::serialize_hashmap\", deserialize_with = \"core_types::vector::deserialize_hashmap\"))]\n\tpub scope_injections: HashMap<String, (NodeId, Type)>,\n}\n\n// TODO: Eventually remove this document upgrade code\nfn migrate_layer_to_merge<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<String, D::Error> {\n\tlet mut s: String = serde::Deserialize::deserialize(deserializer)?;\n\tif s == \"Layer\" {\n\t\ts = \"Merge\".to_string();\n\t}\n\tOk(s)\n}\n// TODO: Eventually remove this document upgrade code\nfn default_import_metadata() -> (NodeId, IVec2) {\n\t(NodeId::new(), IVec2::new(-25, -4))\n}\n// TODO: Eventually remove this document upgrade code\nfn default_export_metadata() -> (NodeId, IVec2) {\n\t(NodeId::new(), IVec2::new(8, -4))\n}\n\n#[derive(Clone, Default, Debug, DynAny, serde::Serialize, serde::Deserialize)]\n/// A network (subgraph) of nodes containing each [`DocumentNode`] and its ID, as well as list mapping each export to its connected node, or a value if disconnected\npub struct NodeNetwork {\n\t/// The list of data outputs that are exported from this network to the parent network.\n\t/// Each export is a reference to a node within this network, paired with its output index, that is the source of the network's exported data.\n\t// TODO: Eventually remove this alias document upgrade code\n\t#[cfg_attr(target_family = \"wasm\", serde(alias = \"outputs\", deserialize_with = \"deserialize_exports\"))]\n\tpub exports: Vec<NodeInput>,\n\t// TODO: Instead of storing import types in each NodeInput::Import connection, the types are stored here. This is similar to how types need to be defined for parameters when creating a function in Rust.\n\t// pub import_types: Vec<Type>,\n\t/// The list of all nodes in this network.\n\t#[serde(\n\t\tserialize_with = \"graphic_types::vector_types::vector::serialize_hashmap\",\n\t\tdeserialize_with = \"graphic_types::vector_types::vector::deserialize_hashmap\"\n\t)]\n\tpub nodes: FxHashMap<NodeId, DocumentNode>,\n\t/// A network may expose nodes as constants which can by used by other nodes using a `NodeInput::Scope(key)`.\n\t#[serde(default)]\n\t#[serde(\n\t\tserialize_with = \"graphic_types::vector_types::vector::serialize_hashmap\",\n\t\tdeserialize_with = \"graphic_types::vector_types::vector::deserialize_hashmap\"\n\t)]\n\tpub scope_injections: FxHashMap<String, (NodeId, Type)>,\n\t#[serde(skip)]\n\tpub generated: bool,\n}\n\nimpl Hash for NodeNetwork {\n\tfn hash<H: Hasher>(&self, state: &mut H) {\n\t\tself.exports.hash(state);\n\t\tlet mut nodes: Vec<_> = self.nodes.iter().collect();\n\t\tnodes.sort_by_key(|(id, _)| *id);\n\t\tfor (id, node) in nodes {\n\t\t\tid.hash(state);\n\t\t\tnode.hash(state);\n\t\t}\n\t}\n}\n\nimpl PartialEq for NodeNetwork {\n\tfn eq(&self, other: &Self) -> bool {\n\t\tself.exports == other.exports\n\t}\n}\n\n/// Graph modification functions\nimpl NodeNetwork {\n\tpub fn current_hash(&self) -> u64 {\n\t\tuse std::hash::BuildHasher;\n\t\tFxBuildHasher.hash_one(self)\n\t}\n\n\tpub fn value_network(node: DocumentNode) -> Self {\n\t\tSelf {\n\t\t\texports: vec![NodeInput::node(NodeId(0), 0)],\n\t\t\tnodes: [(NodeId(0), node)].into_iter().collect(),\n\t\t\t..Default::default()\n\t\t}\n\t}\n\n\t/// Get the nested network given by the path of node ids\n\tpub fn nested_network(&self, nested_path: &[NodeId]) -> Option<&Self> {\n\t\tlet mut network = Some(self);\n\n\t\tfor segment in nested_path {\n\t\t\tnetwork = network.and_then(|network| network.nodes.get(segment)).and_then(|node| node.implementation.get_network());\n\t\t}\n\t\tnetwork\n\t}\n\n\t/// Get the mutable nested network given by the path of node ids\n\tpub fn nested_network_mut(&mut self, nested_path: &[NodeId]) -> Option<&mut Self> {\n\t\tlet mut network = Some(self);\n\n\t\tfor segment in nested_path {\n\t\t\tnetwork = network.and_then(|network| network.nodes.get_mut(segment)).and_then(|node| node.implementation.get_network_mut());\n\t\t}\n\t\tnetwork\n\t}\n\n\t/// Is the node being used directly as an output?\n\tpub fn outputs_contain(&self, node_id_to_check: NodeId) -> bool {\n\t\tself.exports\n\t\t\t.iter()\n\t\t\t.any(|output| if let NodeInput::Node { node_id, .. } = output { *node_id == node_id_to_check } else { false })\n\t}\n\n\t/// Check there are no cycles in the graph (this should never happen).\n\tpub fn is_acyclic(&self) -> bool {\n\t\tlet mut dependencies: HashMap<NodeId, Vec<NodeId>> = HashMap::new();\n\t\tfor (node_id, node) in &self.nodes {\n\t\t\tdependencies.insert(\n\t\t\t\t*node_id,\n\t\t\t\tnode.inputs\n\t\t\t\t\t.iter()\n\t\t\t\t\t.filter_map(|input| if let NodeInput::Node { node_id, .. } = input { Some(*node_id) } else { None })\n\t\t\t\t\t.collect(),\n\t\t\t);\n\t\t}\n\t\twhile !dependencies.is_empty() {\n\t\t\tlet Some((&disconnected, _)) = dependencies.iter().find(|(_, l)| l.is_empty()) else {\n\t\t\t\terror!(\"Dependencies {dependencies:?}\");\n\t\t\t\treturn false;\n\t\t\t};\n\t\t\tdependencies.remove(&disconnected);\n\t\t\tfor connections in dependencies.values_mut() {\n\t\t\t\tconnections.retain(|&id| id != disconnected);\n\t\t\t}\n\t\t}\n\t\ttrue\n\t}\n}\n\n/// Functions for compiling the network\nimpl NodeNetwork {\n\t/// Replace all references in the graph of a node ID with a new node ID defined by the function `f`.\n\tpub fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId + Copy) {\n\t\tself.exports.iter_mut().for_each(|output| {\n\t\t\tif let NodeInput::Node { node_id, .. } = output {\n\t\t\t\t*node_id = f(*node_id)\n\t\t\t}\n\t\t});\n\t\tself.scope_injections.values_mut().for_each(|(id, _ty)| *id = f(*id));\n\t\tlet nodes = std::mem::take(&mut self.nodes);\n\t\tself.nodes = nodes\n\t\t\t.into_iter()\n\t\t\t.map(|(id, mut node)| {\n\t\t\t\tnode.inputs.iter_mut().for_each(|input| input.map_ids(f));\n\t\t\t\tnode.original_location.dependants.iter_mut().for_each(|deps| deps.iter_mut().for_each(|id| *id = f(*id)));\n\t\t\t\t(f(id), node)\n\t\t\t})\n\t\t\t.collect();\n\t}\n\n\t/// Populate the [`DocumentNode::path`], which stores the location of the document node to allow for matching the resulting proto nodes to the document node for the purposes of typing and finding monitor nodes.\n\tpub fn generate_node_paths(&mut self, prefix: &[NodeId]) {\n\t\tfor (node_id, node) in &mut self.nodes {\n\t\t\tlet mut new_path = prefix.to_vec();\n\t\t\tif !self.generated {\n\t\t\t\tnew_path.push(*node_id);\n\t\t\t}\n\t\t\tif let DocumentNodeImplementation::Network(network) = &mut node.implementation {\n\t\t\t\tnetwork.generate_node_paths(new_path.as_slice());\n\t\t\t}\n\t\t\tif node.original_location.path.is_some() {\n\t\t\t\tlog::warn!(\"Attempting to overwrite node path\");\n\t\t\t} else {\n\t\t\t\tnode.original_location.path = Some(new_path);\n\t\t\t\tnode.original_location.inputs_exposed = node.inputs.iter().map(|input| input.is_exposed()).collect();\n\t\t\t\tnode.original_location.dependants = (0..node.implementation.output_count()).map(|_| Vec::new()).collect();\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn populate_dependants(&mut self) {\n\t\tlet mut dep_changes = Vec::new();\n\t\tfor (node_id, node) in &mut self.nodes {\n\t\t\tlet len = node.original_location.dependants.len();\n\t\t\tnode.original_location.dependants.extend(vec![vec![]; (node.implementation.output_count()).max(len) - len]);\n\t\t\tfor input in &node.inputs {\n\t\t\t\tif let NodeInput::Node { node_id: dep_id, output_index, .. } = input {\n\t\t\t\t\tdep_changes.push((*dep_id, *output_index, *node_id));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// println!(\"{:#?}\", self.nodes.get(&NodeId(1)));\n\t\tfor (dep_id, output_index, node_id) in dep_changes {\n\t\t\tlet node = self.nodes.get_mut(&dep_id).expect(\"Encountered invalid node id\");\n\t\t\tlet len = node.original_location.dependants.len();\n\t\t\t// One must be added to the index to find the length because indexing in rust starts from 0.\n\t\t\tnode.original_location.dependants.extend(vec![vec![]; (output_index + 1).max(len) - len]);\n\t\t\t// println!(\"{node_id} {output_index} {}\", node.implementation.output_count());\n\t\t\tnode.original_location.dependants[output_index].push(node_id);\n\t\t}\n\t}\n\n\t/// Replace all references in any node of `old_input` with `new_input`\n\tfn replace_node_inputs(&mut self, node_id: NodeId, old_input: (NodeId, usize), new_input: (NodeId, usize)) {\n\t\tlet Some(node) = self.nodes.get_mut(&node_id) else { return };\n\t\tnode.inputs.iter_mut().for_each(|input| {\n\t\t\tif let NodeInput::Node { node_id: input_id, output_index, .. } = input\n\t\t\t\t&& (*input_id, *output_index) == old_input\n\t\t\t{\n\t\t\t\t(*input_id, *output_index) = new_input;\n\t\t\t}\n\t\t});\n\t}\n\n\t/// Replace all references in any node of `old_output` with `new_output`\n\tfn replace_network_outputs(&mut self, old_output: NodeInput, new_output: NodeInput) {\n\t\tfor output in self.exports.iter_mut() {\n\t\t\tif *output == old_output {\n\t\t\t\t*output = new_output.clone();\n\t\t\t}\n\t\t}\n\t}\n\n\t/// Removes unused nodes from the graph. Returns a list of booleans which represent if each of the inputs have been retained.\n\tpub fn remove_dead_nodes(&mut self, number_of_inputs: usize) -> Vec<bool> {\n\t\t// Take all the nodes out of the nodes list\n\t\tlet mut old_nodes = std::mem::take(&mut self.nodes);\n\n\t\tlet mut stack = self\n\t\t\t.exports\n\t\t\t.iter()\n\t\t\t.filter_map(|output| if let NodeInput::Node { node_id, .. } = output { Some(*node_id) } else { None })\n\t\t\t.collect::<Vec<_>>();\n\t\twhile let Some(node_id) = stack.pop() {\n\t\t\tlet Some((node_id, mut document_node)) = old_nodes.remove_entry(&node_id) else {\n\t\t\t\tcontinue;\n\t\t\t};\n\t\t\t// Remove dead nodes from child networks\n\t\t\tif let DocumentNodeImplementation::Network(network) = &mut document_node.implementation {\n\t\t\t\t// Remove inputs to the parent node if they have been removed from the child\n\t\t\t\tlet mut retain_inputs = network.remove_dead_nodes(document_node.inputs.len()).into_iter();\n\t\t\t\tdocument_node.inputs.retain(|_| retain_inputs.next().unwrap_or(true))\n\t\t\t}\n\t\t\t// Visit all nodes that this node references\n\t\t\tstack.extend(\n\t\t\t\tdocument_node\n\t\t\t\t\t.inputs\n\t\t\t\t\t.iter()\n\t\t\t\t\t.filter_map(|input| if let NodeInput::Node { node_id, .. } = input { Some(node_id) } else { None }),\n\t\t\t);\n\t\t\t// Add the node back to the list of nodes\n\t\t\tself.nodes.insert(node_id, document_node);\n\t\t}\n\n\t\t// Check if inputs are used and store for return value\n\t\tlet mut are_inputs_used = vec![false; number_of_inputs];\n\t\tfor node in &self.nodes {\n\t\t\tfor node_input in &node.1.inputs {\n\t\t\t\tif let NodeInput::Import { import_index, .. } = node_input\n\t\t\t\t\t&& let Some(is_used) = are_inputs_used.get_mut(*import_index)\n\t\t\t\t{\n\t\t\t\t\t*is_used = true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tare_inputs_used\n\t}\n\n\tpub fn resolve_scope_inputs(&mut self) {\n\t\tfor node in self.nodes.values_mut() {\n\t\t\tfor input in node.inputs.iter_mut() {\n\t\t\t\tif let NodeInput::Scope(key) = input {\n\t\t\t\t\tlet (import_id, _ty) = self.scope_injections.get(key.as_ref()).expect(\"Tried to import a non existent key from scope\");\n\t\t\t\t\t// TODO use correct output index\n\t\t\t\t\t*input = NodeInput::node(*import_id, 0);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/// Remove all nodes that contain [`DocumentNodeImplementation::Network`] by moving the nested nodes into the parent network.\n\tpub fn flatten(&mut self, node_id: NodeId) {\n\t\tself.flatten_with_fns(node_id, merge_ids, NodeId::new)\n\t}\n\n\t/// Remove all nodes that contain [`DocumentNodeImplementation::Network`] by moving the nested nodes into the parent network.\n\tpub fn flatten_with_fns(&mut self, node_id: NodeId, map_ids: impl Fn(NodeId, NodeId) -> NodeId + Copy, gen_id: impl Fn() -> NodeId + Copy) {\n\t\tlet Some((id, mut node)) = self.nodes.remove_entry(&node_id) else {\n\t\t\twarn!(\"The node which was supposed to be flattened does not exist in the network, id {node_id} network {self:#?}\");\n\t\t\treturn;\n\t\t};\n\n\t\t// If the node is hidden, replace it with an identity node\n\t\tlet identity_node = DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER);\n\t\tif !node.visible && node.implementation != identity_node {\n\t\t\tnode.implementation = identity_node;\n\n\t\t\t// Connect layer node to the group below\n\t\t\tnode.inputs.drain(1..);\n\t\t\tnode.call_argument = concrete!(());\n\t\t\tself.nodes.insert(id, node);\n\t\t\treturn;\n\t\t}\n\n\t\tlet path = node.original_location.path.clone().unwrap_or_default();\n\n\t\t// Replace value inputs with dedicated value nodes\n\t\tif node.implementation != DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new(\"core_types::value::ClonedNode\")) {\n\t\t\tSelf::replace_value_inputs_with_nodes(&mut node.inputs, &mut self.nodes, &path, gen_id, map_ids, id);\n\t\t}\n\n\t\tlet DocumentNodeImplementation::Network(mut inner_network) = node.implementation else {\n\t\t\t// If the node is not a network, it is a primitive node and can be inserted into the network as is.\n\t\t\tassert!(!self.nodes.contains_key(&id), \"Trying to insert a node into the network caused an id conflict\");\n\n\t\t\tself.nodes.insert(id, node);\n\t\t\treturn;\n\t\t};\n\n\t\t// Replace value and reflection imports with value nodes, added inside nested network\n\t\tSelf::replace_value_inputs_with_nodes(\n\t\t\t&mut inner_network.exports,\n\t\t\t&mut inner_network.nodes,\n\t\t\tnode.original_location.path.as_ref().unwrap_or(&vec![]),\n\t\t\tgen_id,\n\t\t\tmap_ids,\n\t\t\tid,\n\t\t);\n\n\t\t// Connect all network inputs to either the parent network nodes, or newly created value nodes for the parent node.\n\t\tinner_network.map_ids(|inner_id| map_ids(id, inner_id));\n\t\tinner_network.populate_dependants();\n\t\tlet new_nodes = inner_network.nodes.keys().cloned().collect::<Vec<_>>();\n\n\t\tfor (key, value) in inner_network.scope_injections.into_iter() {\n\t\t\tmatch self.scope_injections.entry(key) {\n\t\t\t\tstd::collections::hash_map::Entry::Occupied(o) => {\n\t\t\t\t\tlog::warn!(\"Found duplicate scope injection for key {}, ignoring\", o.key());\n\t\t\t\t}\n\t\t\t\tstd::collections::hash_map::Entry::Vacant(v) => {\n\t\t\t\t\tv.insert(value);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Match the document node input and the inputs of the inner network\n\t\tfor (nested_node_id, mut nested_node) in inner_network.nodes.into_iter() {\n\t\t\tfor (nested_input_index, nested_input) in nested_node.clone().inputs.iter().enumerate() {\n\t\t\t\tif let NodeInput::Import { import_index, .. } = nested_input {\n\t\t\t\t\tlet parent_input = node\n\t\t\t\t\t\t.inputs\n\t\t\t\t\t\t.get(*import_index)\n\t\t\t\t\t\t.unwrap_or_else(|| panic!(\"Import index {import_index} of network node implementation {:?} should always exist\", nested_node.implementation));\n\t\t\t\t\tmatch *parent_input {\n\t\t\t\t\t\t// If the input to self is a node, connect the corresponding output of the inner network to it\n\t\t\t\t\t\tNodeInput::Node { node_id, output_index } => {\n\t\t\t\t\t\t\tnested_node.populate_first_network_input(node_id, output_index, nested_input_index, node.original_location.inputs(*import_index), 1);\n\t\t\t\t\t\t\tlet input_node = self.nodes.get_mut(&node_id).unwrap_or_else(|| panic!(\"Unable to find input node {node_id:?}\"));\n\t\t\t\t\t\t\tinput_node.original_location.dependants[output_index].push(nested_node_id);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tNodeInput::Import { import_index, .. } => {\n\t\t\t\t\t\t\tlet parent_input_index = import_index;\n\t\t\t\t\t\t\tlet Some(NodeInput::Import { import_index, .. }) = nested_node.inputs.get_mut(nested_input_index) else {\n\t\t\t\t\t\t\t\tlog::error!(\"Nested node should have a network input\");\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t*import_index = parent_input_index;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tNodeInput::Value { .. } => unreachable!(\"Value inputs should have been replaced with value nodes\"),\n\t\t\t\t\t\tNodeInput::Inline(_) => (),\n\t\t\t\t\t\tNodeInput::Scope(ref key) => {\n\t\t\t\t\t\t\tlet (import_id, _ty) = self.scope_injections.get(key.as_ref()).expect(\"Tried to import a non existent key from scope\");\n\t\t\t\t\t\t\t// TODO use correct output index\n\t\t\t\t\t\t\tnested_node.inputs[nested_input_index] = NodeInput::node(*import_id, 0);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tNodeInput::Reflection(_) => unreachable!(\"Reflection inputs should have been replaced with value nodes\"),\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tself.nodes.insert(nested_node_id, nested_node);\n\t\t}\n\t\t// TODO: Add support for flattening exports that are NodeInput::Import (https://github.com/GraphiteEditor/Graphite/issues/1762)\n\n\t\t// Connect all nodes that were previously connected to this node to the nodes of the inner network\n\t\tfor (i, export) in inner_network.exports.into_iter().enumerate() {\n\t\t\tif let NodeInput::Node { node_id, output_index, .. } = &export {\n\t\t\t\tfor deps in &node.original_location.dependants {\n\t\t\t\t\tfor dep in deps {\n\t\t\t\t\t\tself.replace_node_inputs(*dep, (id, i), (*node_id, *output_index));\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif let Some(new_output_node) = self.nodes.get_mut(node_id) {\n\t\t\t\t\tfor dep in &node.original_location.dependants[i] {\n\t\t\t\t\t\tnew_output_node.original_location.dependants[*output_index].push(*dep);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tself.replace_network_outputs(NodeInput::node(id, i), export);\n\t\t}\n\n\t\tfor node_id in new_nodes {\n\t\t\tself.flatten_with_fns(node_id, map_ids, gen_id);\n\t\t}\n\t}\n\n\t#[inline(never)]\n\tfn replace_value_inputs_with_nodes(\n\t\tinputs: &mut [NodeInput],\n\t\tcollection: &mut FxHashMap<NodeId, DocumentNode>,\n\t\tpath: &[NodeId],\n\t\tgen_id: impl Fn() -> NodeId + Copy,\n\t\tmap_ids: impl Fn(NodeId, NodeId) -> NodeId + Copy,\n\t\tid: NodeId,\n\t) {\n\t\t// Replace value exports and imports with value nodes, added inside the nested network\n\t\tfor export in inputs {\n\t\t\tlet export: &mut NodeInput = export;\n\t\t\tlet previous_export = std::mem::replace(export, NodeInput::import(concrete!(()), 0));\n\n\t\t\tlet (tagged_value, exposed) = match previous_export {\n\t\t\t\tNodeInput::Value { tagged_value, exposed } => (tagged_value, exposed),\n\t\t\t\tNodeInput::Reflection(reflect) => match reflect {\n\t\t\t\t\tDocumentNodeMetadata::DocumentNodePath => (TaggedValue::NodePath(path.to_vec()).into(), false),\n\t\t\t\t},\n\t\t\t\tprevious_export => {\n\t\t\t\t\t*export = previous_export;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t};\n\t\t\tlet value_node_id = gen_id();\n\t\t\tlet merged_node_id = map_ids(id, value_node_id);\n\t\t\tlet mut original_location = OriginalLocation {\n\t\t\t\tpath: Some(path.to_vec()),\n\t\t\t\tdependants: vec![vec![id]],\n\t\t\t\t..Default::default()\n\t\t\t};\n\n\t\t\tif let Some(path) = &mut original_location.path {\n\t\t\t\tpath.push(value_node_id);\n\t\t\t}\n\t\t\tcollection.insert(\n\t\t\t\tmerged_node_id,\n\t\t\t\tDocumentNode {\n\t\t\t\t\tinputs: vec![NodeInput::Value { tagged_value, exposed }],\n\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new(\"core_types::value::ClonedNode\")),\n\t\t\t\t\toriginal_location,\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t);\n\t\t\t*export = NodeInput::Node {\n\t\t\t\tnode_id: merged_node_id,\n\t\t\t\toutput_index: 0,\n\t\t\t};\n\t\t}\n\t}\n\n\tfn remove_id_node(&mut self, id: NodeId) -> Result<(), String> {\n\t\tlet node = self.nodes.get(&id).ok_or_else(|| format!(\"Node with id {id} does not exist\"))?.clone();\n\t\tif let DocumentNodeImplementation::ProtoNode(ident) = &node.implementation\n\t\t\t&& *ident == graphene_core::ops::identity::IDENTIFIER\n\t\t{\n\t\t\tassert_eq!(node.inputs.len(), 1, \"Id node has more than one input\");\n\t\t\tif let NodeInput::Node { node_id, output_index, .. } = node.inputs[0] {\n\t\t\t\tlet node_input_output_index = output_index;\n\t\t\t\t// TODO fix\n\t\t\t\tif let Some(input_node) = self.nodes.get_mut(&node_id) {\n\t\t\t\t\tfor &dep in &node.original_location.dependants[0] {\n\t\t\t\t\t\tinput_node.original_location.dependants[output_index].push(dep);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tlet input_node_id = node_id;\n\t\t\t\tfor output in self.nodes.values_mut() {\n\t\t\t\t\tfor (index, input) in output.inputs.iter_mut().enumerate() {\n\t\t\t\t\t\tif let NodeInput::Node {\n\t\t\t\t\t\t\tnode_id: output_node_id,\n\t\t\t\t\t\t\toutput_index: output_output_index,\n\t\t\t\t\t\t\t..\n\t\t\t\t\t\t} = input && *output_node_id == id\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t*output_node_id = input_node_id;\n\t\t\t\t\t\t\t*output_output_index = node_input_output_index;\n\n\t\t\t\t\t\t\tlet input_source = &mut output.original_location.inputs_source;\n\t\t\t\t\t\t\tfor source in node.original_location.inputs(index) {\n\t\t\t\t\t\t\t\tinput_source.insert(source, index);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tfor node_input in self.exports.iter_mut() {\n\t\t\t\t\t\tif let NodeInput::Node { node_id, output_index, .. } = node_input\n\t\t\t\t\t\t\t&& *node_id == id\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t*node_id = input_node_id;\n\t\t\t\t\t\t\t*output_index = node_input_output_index;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tself.nodes.remove(&id);\n\t\t}\n\t\tOk(())\n\t}\n\n\t/// Strips out any [`graphene_core::ops::IdentityNode`]s that are unnecessary.\n\tpub fn remove_redundant_id_nodes(&mut self) {\n\t\tlet id_nodes = self\n\t\t\t.nodes\n\t\t\t.iter()\n\t\t\t.filter(|(_, node)| {\n\t\t\t\tmatches!(&node.implementation, DocumentNodeImplementation::ProtoNode(ident) if ident == &graphene_core::ops::identity::IDENTIFIER)\n\t\t\t\t\t&& node.inputs.len() == 1\n\t\t\t\t\t&& matches!(node.inputs[0], NodeInput::Node { .. })\n\t\t\t})\n\t\t\t.map(|(id, _)| *id)\n\t\t\t.collect::<Vec<_>>();\n\t\tfor id in id_nodes {\n\t\t\tif let Err(e) = self.remove_id_node(id) {\n\t\t\t\tlog::warn!(\"{e}\")\n\t\t\t}\n\t\t}\n\t}\n\n\t/// Converts the `DocumentNode`s with a `DocumentNodeImplementation::Extract` into a `ClonedNode` that returns\n\t/// the `DocumentNode` specified by the single `NodeInput::Node`.\n\t/// The referenced node is removed from the network, and any `NodeInput::Node`s used by the referenced node are replaced with a generically typed network input.\n\tpub fn resolve_extract_nodes(&mut self) {\n\t\tlet mut extraction_nodes = self\n\t\t\t.nodes\n\t\t\t.iter()\n\t\t\t.filter(|(_, node)| matches!(node.implementation, DocumentNodeImplementation::Extract))\n\t\t\t.map(|(id, node)| (*id, node.clone()))\n\t\t\t.collect::<Vec<_>>();\n\t\tself.nodes.retain(|_, node| !matches!(node.implementation, DocumentNodeImplementation::Extract));\n\n\t\tfor (_, node) in &mut extraction_nodes {\n\t\t\tassert_eq!(node.inputs.len(), 1);\n\t\t\tlet NodeInput::Node { node_id, output_index, .. } = node.inputs.pop().unwrap() else {\n\t\t\t\tpanic!(\"Extract node has no input, inputs: {:?}\", node.inputs);\n\t\t\t};\n\t\t\tassert_eq!(output_index, 0);\n\t\t\t// TODO: check if we can read lambda checking?\n\t\t\tlet mut input_node = self.nodes.remove(&node_id).unwrap();\n\t\t\tnode.implementation = DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new(\"core_types::value::ClonedNode\"));\n\t\t\tif let Some(input) = input_node.inputs.get_mut(0) {\n\t\t\t\t*input = match &input {\n\t\t\t\t\tNodeInput::Node { .. } => NodeInput::import(generic!(T), 0),\n\t\t\t\t\tni => NodeInput::import(ni.ty(), 0),\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tfor input in input_node.inputs.iter_mut() {\n\t\t\t\tif let NodeInput::Node { .. } = input {\n\t\t\t\t\t*input = NodeInput::import(generic!(T), 0)\n\t\t\t\t}\n\t\t\t}\n\t\t\tnode.inputs = vec![NodeInput::value(TaggedValue::DocumentNode(input_node), false)];\n\t\t}\n\t\tself.nodes.extend(extraction_nodes);\n\t}\n\n\t/// Creates a proto network for evaluating each output of this network.\n\tpub fn into_proto_networks(self) -> impl Iterator<Item = ProtoNetwork> {\n\t\tlet nodes: Vec<_> = self.nodes.into_iter().map(|(id, node)| (id, node.resolve_proto_node())).collect();\n\n\t\t// Create a network to evaluate each output\n\t\tif self.exports.len() == 1\n\t\t\t&& let NodeInput::Node { node_id, .. } = self.exports[0]\n\t\t{\n\t\t\treturn vec![ProtoNetwork {\n\t\t\t\tinputs: Vec::new(),\n\t\t\t\toutput: node_id,\n\t\t\t\tnodes,\n\t\t\t}]\n\t\t\t.into_iter();\n\t\t}\n\n\t\t// Create a network to evaluate each output\n\t\tlet networks: Vec<_> = self\n\t\t\t.exports\n\t\t\t.into_iter()\n\t\t\t.filter_map(move |output| {\n\t\t\t\tif let NodeInput::Node { node_id, .. } = output {\n\t\t\t\t\tSome(ProtoNetwork {\n\t\t\t\t\t\tinputs: Vec::new(), // Inputs field is not used. Should be deleted\n\t\t\t\t\t\t// inputs: vec![input_node.expect(\"Set node should always exist\")],\n\t\t\t\t\t\t// inputs: self.imports.clone(),\n\t\t\t\t\t\toutput: node_id,\n\t\t\t\t\t\tnodes: nodes.clone(),\n\t\t\t\t\t})\n\t\t\t\t} else {\n\t\t\t\t\tNone\n\t\t\t\t}\n\t\t\t})\n\t\t\t.collect();\n\t\tnetworks.into_iter()\n\t}\n\n\t/// Create a [`RecursiveNodeIter`] that iterates over all [`DocumentNode`]s, including ones that are deeply nested.\n\tpub fn recursive_nodes(&self) -> RecursiveNodeIter<'_> {\n\t\tlet nodes = self.nodes.iter().map(|(id, node)| (id, node, Vec::new())).collect();\n\t\tRecursiveNodeIter { nodes }\n\t}\n}\n\n/// An iterator over all [`DocumentNode`]s, including ones that are deeply nested.\npub struct RecursiveNodeIter<'a> {\n\tnodes: Vec<(&'a NodeId, &'a DocumentNode, Vec<NodeId>)>,\n}\n\nimpl<'a> Iterator for RecursiveNodeIter<'a> {\n\ttype Item = (&'a NodeId, &'a DocumentNode, Vec<NodeId>);\n\tfn next(&mut self) -> Option<Self::Item> {\n\t\tlet (current_id, node, path) = self.nodes.pop()?;\n\t\tif let DocumentNodeImplementation::Network(network) = &node.implementation {\n\t\t\tself.nodes.extend(network.nodes.iter().map(|(id, node)| {\n\t\t\t\tlet mut nested_path = path.clone();\n\t\t\t\tnested_path.push(*current_id);\n\t\t\t\t(id, node, nested_path)\n\t\t\t}));\n\t\t}\n\t\tSome((current_id, node, path))\n\t}\n}\n\nfn migrate_call_argument<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Type, D::Error> {\n\tuse serde::Deserialize;\n\n\t#[derive(serde::Serialize, serde::Deserialize)]\n\t#[serde(untagged)]\n\tenum CallArg {\n\t\tNew(Type),\n\t\tOld(Option<Type>),\n\t}\n\n\tOk(match CallArg::deserialize(deserializer)? {\n\t\tCallArg::New(ty) => ty,\n\t\tCallArg::Old(ty) => ty.unwrap_or_default(),\n\t})\n}\n\n#[cfg(test)]\nmod test {\n\tuse super::*;\n\tuse crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode};\n\tuse std::sync::atomic::AtomicU64;\n\n\tfn gen_node_id() -> NodeId {\n\t\tstatic NODE_ID: AtomicU64 = AtomicU64::new(4);\n\t\tNodeId(NODE_ID.fetch_add(1, std::sync::atomic::Ordering::SeqCst))\n\t}\n\n\tfn add_network() -> NodeNetwork {\n\t\tNodeNetwork {\n\t\t\texports: vec![NodeInput::node(NodeId(1), 0)],\n\t\t\tnodes: [\n\t\t\t\t(\n\t\t\t\t\tNodeId(0),\n\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\tinputs: vec![NodeInput::import(concrete!(u32), 0), NodeInput::import(concrete!(u32), 1)],\n\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new(\"core_types::structural::ConsNode\")),\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t\t(\n\t\t\t\t\tNodeId(1),\n\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\tinputs: vec![NodeInput::node(NodeId(0), 0)],\n\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new(\"core_types::ops::AddPairNode\")),\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t]\n\t\t\t.into_iter()\n\t\t\t.collect(),\n\t\t\t..Default::default()\n\t\t}\n\t}\n\n\t#[test]\n\tfn map_ids() {\n\t\tlet mut network = add_network();\n\t\tnetwork.map_ids(|id| NodeId(id.0 + 1));\n\t\tlet mapped_add = NodeNetwork {\n\t\t\texports: vec![NodeInput::node(NodeId(2), 0)],\n\t\t\tnodes: [\n\t\t\t\t(\n\t\t\t\t\tNodeId(1),\n\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\tinputs: vec![NodeInput::import(concrete!(u32), 0), NodeInput::import(concrete!(u32), 1)],\n\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new(\"core_types::structural::ConsNode\")),\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t\t(\n\t\t\t\t\tNodeId(2),\n\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\tinputs: vec![NodeInput::node(NodeId(1), 0)],\n\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new(\"core_types::ops::AddPairNode\")),\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t]\n\t\t\t.into_iter()\n\t\t\t.collect(),\n\t\t\t..Default::default()\n\t\t};\n\t\tassert_eq!(network, mapped_add);\n\t}\n\n\t#[test]\n\tfn extract_node() {\n\t\tlet id_node = DocumentNode {\n\t\t\tinputs: vec![],\n\t\t\timplementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER),\n\t\t\t..Default::default()\n\t\t};\n\t\t// TODO: Extend test cases to test nested network\n\t\tlet mut extraction_network = NodeNetwork {\n\t\t\texports: vec![NodeInput::node(NodeId(1), 0)],\n\t\t\tnodes: [\n\t\t\t\tid_node.clone(),\n\t\t\t\tDocumentNode {\n\t\t\t\t\tinputs: vec![NodeInput::node(NodeId(0), 0)],\n\t\t\t\t\timplementation: DocumentNodeImplementation::Extract,\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t]\n\t\t\t.into_iter()\n\t\t\t.enumerate()\n\t\t\t.map(|(id, node)| (NodeId(id as u64), node))\n\t\t\t.collect(),\n\t\t\t..Default::default()\n\t\t};\n\t\textraction_network.resolve_extract_nodes();\n\t\tassert_eq!(extraction_network.nodes.len(), 1);\n\t\tlet inputs = extraction_network.nodes.get(&NodeId(1)).unwrap().inputs.clone();\n\t\tassert_eq!(inputs.len(), 1);\n\t\tassert!(matches!(&inputs[0].as_value(), &Some(TaggedValue::DocumentNode(network), ..) if network == &id_node));\n\t}\n\n\t#[test]\n\tfn flatten_add() {\n\t\tlet mut network = NodeNetwork {\n\t\t\texports: vec![NodeInput::node(NodeId(1), 0)],\n\t\t\tnodes: [(\n\t\t\t\tNodeId(1),\n\t\t\t\tDocumentNode {\n\t\t\t\t\tinputs: vec![NodeInput::import(concrete!(u32), 0), NodeInput::value(TaggedValue::U32(2), false)],\n\t\t\t\t\timplementation: DocumentNodeImplementation::Network(add_network()),\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t)]\n\t\t\t.into_iter()\n\t\t\t.collect(),\n\t\t\t..Default::default()\n\t\t};\n\t\tnetwork.populate_dependants();\n\t\tnetwork.flatten_with_fns(NodeId(1), |self_id, inner_id| NodeId(self_id.0 * 10 + inner_id.0), gen_node_id);\n\t\tlet flat_network = flat_network();\n\t\tprintln!(\"{flat_network:#?}\");\n\t\tprintln!(\"{network:#?}\");\n\n\t\tassert_eq!(flat_network, network);\n\t}\n\n\t#[test]\n\tfn resolve_proto_node_add() {\n\t\tlet document_node = DocumentNode {\n\t\t\tinputs: vec![NodeInput::node(NodeId(0), 0)],\n\t\t\tcall_argument: concrete!(u32),\n\t\t\timplementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new(\"core_types::structural::ConsNode\")),\n\t\t\t..Default::default()\n\t\t};\n\n\t\tlet proto_node = document_node.resolve_proto_node();\n\t\tlet reference = ProtoNode {\n\t\t\tidentifier: ProtoNodeIdentifier::new(\"core_types::structural::ConsNode\"),\n\t\t\tcall_argument: concrete!(u32),\n\t\t\tconstruction_args: ConstructionArgs::Nodes(vec![NodeId(0)]),\n\t\t\t..Default::default()\n\t\t};\n\t\tassert_eq!(proto_node, reference);\n\t}\n\n\t#[test]\n\tfn resolve_flatten_add_as_proto_network() {\n\t\tlet construction_network = ProtoNetwork {\n\t\t\tinputs: Vec::new(),\n\t\t\toutput: NodeId(11),\n\t\t\tnodes: [\n\t\t\t\t(\n\t\t\t\t\tNodeId(10),\n\t\t\t\t\tProtoNode {\n\t\t\t\t\t\tidentifier: ProtoNodeIdentifier::new(\"core_types::structural::ConsNode\"),\n\t\t\t\t\t\tcall_argument: concrete!(u32),\n\t\t\t\t\t\tconstruction_args: ConstructionArgs::Nodes(vec![NodeId(14)]),\n\t\t\t\t\t\toriginal_location: OriginalLocation {\n\t\t\t\t\t\t\tpath: Some(vec![NodeId(1), NodeId(0)]),\n\t\t\t\t\t\t\tinputs_source: [(Source { node: vec![NodeId(1)], index: 1 }, 1)].into(),\n\t\t\t\t\t\t\tinputs_exposed: vec![true, true],\n\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t\t(\n\t\t\t\t\tNodeId(11),\n\t\t\t\t\tProtoNode {\n\t\t\t\t\t\tidentifier: ProtoNodeIdentifier::new(\"core_types::ops::AddPairNode\"),\n\t\t\t\t\t\tcall_argument: concrete!(Context),\n\t\t\t\t\t\tconstruction_args: ConstructionArgs::Nodes(vec![NodeId(10)]),\n\t\t\t\t\t\toriginal_location: OriginalLocation {\n\t\t\t\t\t\t\tpath: Some(vec![NodeId(1), NodeId(1)]),\n\t\t\t\t\t\t\tinputs_source: HashMap::new(),\n\t\t\t\t\t\t\tinputs_exposed: vec![true],\n\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t},\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t\t(\n\t\t\t\t\tNodeId(14),\n\t\t\t\t\tProtoNode {\n\t\t\t\t\t\tidentifier: ProtoNodeIdentifier::new(\"core_types::value::ClonedNode\"),\n\t\t\t\t\t\tcall_argument: concrete!(core_types::Context),\n\t\t\t\t\t\tconstruction_args: ConstructionArgs::Value(TaggedValue::U32(2).into()),\n\t\t\t\t\t\toriginal_location: OriginalLocation {\n\t\t\t\t\t\t\tpath: Some(vec![NodeId(1), NodeId(4)]),\n\t\t\t\t\t\t\tinputs_source: HashMap::new(),\n\t\t\t\t\t\t\tinputs_exposed: vec![true, false],\n\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t},\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t]\n\t\t\t.into_iter()\n\t\t\t.collect(),\n\t\t};\n\t\tlet network = flat_network();\n\t\tlet mut resolved_network = network.into_proto_networks().collect::<Vec<_>>();\n\t\tresolved_network[0].nodes.sort_unstable_by_key(|(id, _)| *id);\n\n\t\tprintln!(\"{:#?}\", resolved_network[0]);\n\t\tprintln!(\"{construction_network:#?}\");\n\t\tpretty_assertions::assert_eq!(resolved_network[0], construction_network);\n\t}\n\n\tfn flat_network() -> NodeNetwork {\n\t\tNodeNetwork {\n\t\t\texports: vec![NodeInput::node(NodeId(11), 0)],\n\t\t\tnodes: [\n\t\t\t\t(\n\t\t\t\t\tNodeId(10),\n\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\tinputs: vec![NodeInput::node(NodeId(14), 0)],\n\t\t\t\t\t\tcall_argument: concrete!(u32),\n\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new(\"core_types::structural::ConsNode\")),\n\t\t\t\t\t\toriginal_location: OriginalLocation {\n\t\t\t\t\t\t\tpath: Some(vec![NodeId(1), NodeId(0)]),\n\t\t\t\t\t\t\tinputs_source: [(Source { node: vec![NodeId(1)], index: 1 }, 1)].into(),\n\t\t\t\t\t\t\tinputs_exposed: vec![true, true],\n\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t},\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t\t(\n\t\t\t\t\tNodeId(14),\n\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\tinputs: vec![NodeInput::value(TaggedValue::U32(2), false)],\n\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new(\"core_types::value::ClonedNode\")),\n\t\t\t\t\t\toriginal_location: OriginalLocation {\n\t\t\t\t\t\t\tpath: Some(vec![NodeId(1), NodeId(4)]),\n\t\t\t\t\t\t\tinputs_source: HashMap::new(),\n\t\t\t\t\t\t\tinputs_exposed: vec![true, false],\n\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t},\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t\t(\n\t\t\t\t\tNodeId(11),\n\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\tinputs: vec![NodeInput::node(NodeId(10), 0)],\n\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new(\"core_types::ops::AddPairNode\")),\n\t\t\t\t\t\toriginal_location: OriginalLocation {\n\t\t\t\t\t\t\tpath: Some(vec![NodeId(1), NodeId(1)]),\n\t\t\t\t\t\t\tinputs_source: HashMap::new(),\n\t\t\t\t\t\t\tinputs_exposed: vec![true],\n\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t},\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t]\n\t\t\t.into_iter()\n\t\t\t.collect(),\n\t\t\t..Default::default()\n\t\t}\n\t}\n\n\tfn two_node_identity() -> NodeNetwork {\n\t\tNodeNetwork {\n\t\t\texports: vec![NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(2), 0)],\n\t\t\tnodes: [\n\t\t\t\t(\n\t\t\t\t\tNodeId(1),\n\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\tinputs: vec![NodeInput::import(concrete!(u32), 0)],\n\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER),\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t\t(\n\t\t\t\t\tNodeId(2),\n\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\tinputs: vec![NodeInput::import(concrete!(u32), 1)],\n\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER),\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t]\n\t\t\t.into_iter()\n\t\t\t.collect(),\n\t\t\t..Default::default()\n\t\t}\n\t}\n\n\tfn output_duplicate(network_outputs: Vec<NodeInput>, result_node_input: NodeInput) -> NodeNetwork {\n\t\tlet mut network = NodeNetwork {\n\t\t\texports: network_outputs,\n\t\t\tnodes: [\n\t\t\t\t(\n\t\t\t\t\tNodeId(1),\n\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\tinputs: vec![NodeInput::value(TaggedValue::F64(1.), false), NodeInput::value(TaggedValue::F64(2.), false)],\n\t\t\t\t\t\timplementation: DocumentNodeImplementation::Network(two_node_identity()),\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t\t(\n\t\t\t\t\tNodeId(2),\n\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\tinputs: vec![result_node_input],\n\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER),\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t]\n\t\t\t.into_iter()\n\t\t\t.collect(),\n\t\t\t..Default::default()\n\t\t};\n\t\tlet _new_ids = 101..;\n\t\tnetwork.populate_dependants();\n\t\tnetwork.flatten_with_fns(NodeId(1), |self_id, inner_id| NodeId(self_id.0 * 10 + inner_id.0), || NodeId(10000));\n\t\tnetwork.flatten_with_fns(NodeId(2), |self_id, inner_id| NodeId(self_id.0 * 10 + inner_id.0), || NodeId(10001));\n\t\tnetwork.remove_dead_nodes(0);\n\t\tnetwork\n\t}\n\n\t#[test]\n\tfn simple_duplicate() {\n\t\tlet result = output_duplicate(vec![NodeInput::node(NodeId(1), 0)], NodeInput::node(NodeId(1), 0));\n\t\tprintln!(\"{result:#?}\");\n\t\tassert_eq!(result.exports.len(), 1, \"The number of outputs should remain as 1\");\n\t\tassert_eq!(result.exports[0], NodeInput::node(NodeId(11), 0), \"The outer network output should be from a duplicated inner network\");\n\t\tlet mut ids = result.nodes.keys().copied().collect::<Vec<_>>();\n\t\tids.sort();\n\t\tassert_eq!(ids, vec![NodeId(11), NodeId(10010)], \"Should only contain identity and values\");\n\t}\n\n\t// TODO: Write more tests\n}\n"
  },
  {
    "path": "node-graph/graph-craft/src/graphene_compiler.rs",
    "content": "use crate::document::NodeNetwork;\nuse crate::proto::{LocalFuture, ProtoNetwork};\nuse std::error::Error;\n\npub struct Compiler {}\n\nimpl Compiler {\n\tpub fn compile(&self, mut network: NodeNetwork) -> impl Iterator<Item = Result<ProtoNetwork, String>> {\n\t\tlet node_ids = network.nodes.keys().copied().collect::<Vec<_>>();\n\t\tnetwork.populate_dependants();\n\t\tfor id in node_ids {\n\t\t\tnetwork.flatten(id);\n\t\t}\n\t\tnetwork.resolve_scope_inputs();\n\t\tnetwork.remove_redundant_id_nodes();\n\t\t// network.remove_dead_nodes(0);\n\t\tlet proto_networks = network.into_proto_networks();\n\n\t\tproto_networks.map(move |mut proto_network| {\n\t\t\tproto_network.insert_context_nullification_nodes()?;\n\t\t\tproto_network.generate_stable_node_ids();\n\t\t\tOk(proto_network)\n\t\t})\n\t}\n\tpub fn compile_single(&self, network: NodeNetwork) -> Result<ProtoNetwork, String> {\n\t\tassert_eq!(network.exports.len(), 1, \"Graph with multiple outputs not yet handled\");\n\t\tlet Some(proto_network) = self.compile(network).next() else {\n\t\t\treturn Err(\"Failed to convert graph into proto graph\".to_string());\n\t\t};\n\t\tproto_network\n\t}\n}\n\npub trait Executor<I, O> {\n\tfn execute(&self, input: I) -> LocalFuture<'_, Result<O, Box<dyn Error>>>;\n}\n"
  },
  {
    "path": "node-graph/graph-craft/src/lib.rs",
    "content": "#[macro_use]\nextern crate log;\n#[macro_use]\nextern crate core_types;\n\npub use core_types::{ProtoNodeIdentifier, Type, TypeDescriptor, concrete, generic};\n\npub mod document;\npub mod graphene_compiler;\npub mod proto;\n#[cfg(feature = \"loading\")]\npub mod util;\npub mod wasm_application_io;\n"
  },
  {
    "path": "node-graph/graph-craft/src/proto.rs",
    "content": "use crate::document::value::TaggedValue;\nuse crate::document::{InlineRust, value};\nuse crate::document::{NodeId, OriginalLocation};\npub use core_types::registry::*;\nuse core_types::*;\nuse rustc_hash::FxHashMap;\nuse std::borrow::Cow;\nuse std::collections::{HashMap, HashSet};\nuse std::fmt::Debug;\nuse std::hash::Hash;\n\n#[derive(Debug, Default, PartialEq, Clone, Hash, Eq, serde::Serialize, serde::Deserialize)]\n/// A list of [`ProtoNode`]s, which is an intermediate step between the [`crate::document::NodeNetwork`] and the `BorrowTree` containing a single flattened network.\npub struct ProtoNetwork {\n\t// TODO: remove this since it seems to be unused?\n\t// Should a proto Network even allow inputs? Don't think so\n\tpub inputs: Vec<NodeId>,\n\t/// The node ID that provides the output. This node is then responsible for calling the rest of the graph.\n\tpub output: NodeId,\n\t/// A list of nodes stored in a Vec to allow for sorting.\n\tpub nodes: Vec<(NodeId, ProtoNode)>,\n}\n\nimpl core::fmt::Display for ProtoNetwork {\n\tfn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n\t\tf.write_str(\"Proto Network with nodes: \")?;\n\t\tfn write_node(f: &mut core::fmt::Formatter<'_>, network: &ProtoNetwork, id: NodeId, indent: usize) -> core::fmt::Result {\n\t\t\tf.write_str(&\"\\t\".repeat(indent))?;\n\t\t\tlet Some((_, node)) = network.nodes.iter().find(|(node_id, _)| *node_id == id) else {\n\t\t\t\treturn f.write_str(\"{{Unknown Node}}\");\n\t\t\t};\n\t\t\tf.write_str(\"Node: \")?;\n\t\t\tf.write_str(node.identifier.as_str())?;\n\n\t\t\tf.write_str(\"\\n\")?;\n\t\t\tf.write_str(&\"\\t\".repeat(indent))?;\n\t\t\tf.write_str(\"{\\n\")?;\n\n\t\t\tf.write_str(&\"\\t\".repeat(indent + 1))?;\n\t\t\tf.write_str(\"Input: \")?;\n\t\t\tf.write_fmt(format_args!(\"Call Argument (type = {:?})\", node.call_argument))?;\n\t\t\tf.write_str(\"\\n\")?;\n\n\t\t\tmatch &node.construction_args {\n\t\t\t\tConstructionArgs::Value(value) => {\n\t\t\t\t\tf.write_str(&\"\\t\".repeat(indent + 1))?;\n\t\t\t\t\tf.write_fmt(format_args!(\"Value construction argument: {value:?}\"))?\n\t\t\t\t}\n\t\t\t\tConstructionArgs::Nodes(nodes) => {\n\t\t\t\t\tfor id in nodes {\n\t\t\t\t\t\twrite_node(f, network, *id, indent + 1)?;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tConstructionArgs::Inline(inline) => {\n\t\t\t\t\tf.write_str(&\"\\t\".repeat(indent + 1))?;\n\t\t\t\t\tf.write_fmt(format_args!(\"Inline construction argument: {inline:?}\"))?\n\t\t\t\t}\n\t\t\t}\n\t\t\tf.write_str(&\"\\t\".repeat(indent))?;\n\t\t\tf.write_str(\"}\\n\")?;\n\t\t\tOk(())\n\t\t}\n\n\t\tlet id = self.output;\n\t\twrite_node(f, self, id, 0)\n\t}\n}\n\n#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]\n/// Defines the arguments used to construct the boxed node struct. This is used to call the constructor function in the `node_registry.rs` file - which is hidden behind a wall of macros.\npub enum ConstructionArgs {\n\t/// A value of a type that is known, allowing serialization (serde::Deserialize is not object safe)\n\tValue(MemoHash<value::TaggedValue>),\n\t/// A list of nodes used as inputs to the constructor function in `node_registry.rs`.\n\t/// The bool indicates whether to treat the node as lambda node.\n\t// TODO: use a struct for clearer naming.\n\tNodes(Vec<NodeId>),\n\t/// Used for GPU computation to work around the limitations of rust-gpu.\n\tInline(InlineRust),\n}\n\nimpl Eq for ConstructionArgs {}\n\nimpl PartialEq for ConstructionArgs {\n\tfn eq(&self, other: &Self) -> bool {\n\t\tmatch (&self, &other) {\n\t\t\t(Self::Nodes(n1), Self::Nodes(n2)) => n1 == n2,\n\t\t\t(Self::Value(v1), Self::Value(v2)) => v1 == v2,\n\t\t\t_ => {\n\t\t\t\tuse std::hash::Hasher;\n\t\t\t\tlet hash = |input: &Self| {\n\t\t\t\t\tlet mut hasher = rustc_hash::FxHasher::default();\n\t\t\t\t\tinput.hash(&mut hasher);\n\t\t\t\t\thasher.finish()\n\t\t\t\t};\n\t\t\t\thash(self) == hash(other)\n\t\t\t}\n\t\t}\n\t}\n}\n\nimpl Hash for ConstructionArgs {\n\tfn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n\t\tcore::mem::discriminant(self).hash(state);\n\t\tmatch self {\n\t\t\tSelf::Nodes(nodes) => {\n\t\t\t\tfor node in nodes {\n\t\t\t\t\tnode.hash(state);\n\t\t\t\t}\n\t\t\t}\n\t\t\tSelf::Value(value) => value.hash(state),\n\t\t\tSelf::Inline(inline) => inline.hash(state),\n\t\t}\n\t}\n}\n\nimpl ConstructionArgs {\n\tpub fn new_function_args(&self) -> Vec<String> {\n\t\tmatch self {\n\t\t\tConstructionArgs::Nodes(nodes) => nodes.iter().map(|n| format!(\"n{:0x}\", n.0)).collect(),\n\t\t\tConstructionArgs::Value(value) => vec![value.to_primitive_string()],\n\t\t\tConstructionArgs::Inline(inline) => vec![inline.expr.clone()],\n\t\t}\n\t}\n}\n\n#[derive(Debug, Clone, PartialEq, Hash, Eq, serde::Serialize, serde::Deserialize)]\n/// A proto node is an intermediate step between the `DocumentNode` and the boxed struct that actually runs the node (found in the [`BorrowTree`]).\n/// At different stages in the compilation process, this struct will be transformed into a reduced (more restricted) form acting as a subset of its original form, but that restricted form is still valid in the earlier stage in the compilation process before it was transformed.\npub struct ProtoNode {\n\tpub construction_args: ConstructionArgs,\n\tpub call_argument: Type,\n\tpub identifier: ProtoNodeIdentifier,\n\tpub original_location: OriginalLocation,\n\tpub skip_deduplication: bool,\n\tpub(crate) context_features: ContextDependencies,\n}\n\nimpl Default for ProtoNode {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tidentifier: graphene_core::ops::identity::IDENTIFIER,\n\t\t\tconstruction_args: ConstructionArgs::Value(value::TaggedValue::U32(0).into()),\n\t\t\tcall_argument: concrete!(()),\n\t\t\toriginal_location: OriginalLocation::default(),\n\t\t\tskip_deduplication: false,\n\t\t\tcontext_features: Default::default(),\n\t\t}\n\t}\n}\n\nimpl ProtoNode {\n\t/// A stable node ID is a hash of a node that should stay constant. This is used in order to remove duplicates from the graph.\n\t/// In the case of `skip_deduplication`, the `document_node_path` is also hashed in order to avoid duplicate monitor nodes from being removed (which would make it impossible to load thumbnails).\n\tpub fn stable_node_id(&self) -> Option<NodeId> {\n\t\tuse std::hash::Hasher;\n\t\tlet mut hasher = rustc_hash::FxHasher::default();\n\n\t\tself.identifier.as_str().hash(&mut hasher);\n\t\tself.construction_args.hash(&mut hasher);\n\t\tif self.skip_deduplication {\n\t\t\tself.original_location.path.hash(&mut hasher);\n\t\t}\n\n\t\tstd::mem::discriminant(&self.call_argument).hash(&mut hasher);\n\t\tself.call_argument.hash(&mut hasher);\n\n\t\tSome(NodeId(hasher.finish()))\n\t}\n\n\t/// Construct a new [`ProtoNode`] with the specified construction args and a `ClonedNode` implementation.\n\tpub fn value(value: ConstructionArgs, path: Vec<NodeId>) -> Self {\n\t\tlet inputs_exposed = match &value {\n\t\t\tConstructionArgs::Nodes(nodes) => nodes.len() + 1,\n\t\t\t_ => 2,\n\t\t};\n\t\tSelf {\n\t\t\tidentifier: ProtoNodeIdentifier::new(\"core_types::value::ClonedNode\"),\n\t\t\tconstruction_args: value,\n\t\t\tcall_argument: concrete!(Context),\n\t\t\toriginal_location: OriginalLocation {\n\t\t\t\tpath: Some(path),\n\t\t\t\tinputs_exposed: vec![false; inputs_exposed],\n\t\t\t\t..Default::default()\n\t\t\t},\n\t\t\tskip_deduplication: false,\n\t\t\tcontext_features: Default::default(),\n\t\t}\n\t}\n\n\t/// Converts all references to other node IDs into new IDs by running the specified function on them.\n\t/// This can be used when changing the IDs of the nodes, for example in the case of generating stable IDs.\n\tpub fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId) {\n\t\tif let ConstructionArgs::Nodes(ids) = &mut self.construction_args {\n\t\t\tids.iter_mut().for_each(|id| *id = f(*id));\n\t\t}\n\t}\n\n\tpub fn unwrap_construction_nodes(&self) -> Vec<NodeId> {\n\t\tmatch &self.construction_args {\n\t\t\tConstructionArgs::Nodes(nodes) => nodes.clone(),\n\t\t\t_ => panic!(\"tried to unwrap nodes from non node construction args \\n node: {self:#?}\"),\n\t\t}\n\t}\n}\n\n#[derive(Clone, Copy, PartialEq)]\nenum NodeState {\n\tUnvisited,\n\tVisiting,\n\tVisited,\n}\n\nimpl ProtoNetwork {\n\tfn check_ref(&self, ref_id: &NodeId, id: &NodeId) {\n\t\tdebug_assert!(\n\t\t\tself.nodes.iter().any(|(check_id, _)| check_id == ref_id),\n\t\t\t\"Node with ID {id} has a reference which uses the node with ID {ref_id} which doesn't exist in network {self:#?}\"\n\t\t);\n\t}\n\n\t#[cfg(debug_assertions)]\n\tpub fn example() -> (Self, NodeId, ProtoNode) {\n\t\tlet node_id = NodeId(1);\n\t\tlet proto_node = ProtoNode::default();\n\t\tlet proto_network = ProtoNetwork {\n\t\t\tinputs: vec![node_id],\n\t\t\toutput: node_id,\n\t\t\tnodes: vec![(node_id, proto_node.clone())],\n\t\t};\n\t\t(proto_network, node_id, proto_node)\n\t}\n\n\t/// Construct a hashmap containing a list of the nodes that depend on this proto network.\n\tpub fn collect_outwards_edges(&self) -> HashMap<NodeId, Vec<NodeId>> {\n\t\tlet mut edges: HashMap<NodeId, Vec<NodeId>> = HashMap::new();\n\t\tfor (id, node) in &self.nodes {\n\t\t\tif let ConstructionArgs::Nodes(ref_nodes) = &node.construction_args {\n\t\t\t\tfor ref_id in ref_nodes {\n\t\t\t\t\tself.check_ref(ref_id, id);\n\t\t\t\t\tedges.entry(*ref_id).or_default().push(*id)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tedges\n\t}\n\n\t/// Convert all node IDs to be stable (based on the hash generated by [`ProtoNode::stable_node_id`]).\n\t/// This function requires that the graph be topologically sorted.\n\tpub fn generate_stable_node_ids(&mut self) {\n\t\tdebug_assert!(self.is_topologically_sorted());\n\t\tlet outwards_edges = self.collect_outwards_edges();\n\n\t\tfor index in 0..self.nodes.len() {\n\t\t\tlet Some(sni) = self.nodes[index].1.stable_node_id() else {\n\t\t\t\tpanic!(\"failed to generate stable node id for node {:#?}\", self.nodes[index].1);\n\t\t\t};\n\t\t\tself.replace_node_id(&outwards_edges, NodeId(index as u64), sni);\n\t\t\tself.nodes[index].0 = sni;\n\t\t}\n\t}\n\n\t// TODO: Remove\n\t/// Create a hashmap with the list of nodes this proto network depends on/uses as inputs.\n\tpub fn collect_inwards_edges(&self) -> HashMap<NodeId, Vec<NodeId>> {\n\t\tlet mut edges: HashMap<NodeId, Vec<NodeId>> = HashMap::new();\n\t\tfor (id, node) in &self.nodes {\n\t\t\tif let ConstructionArgs::Nodes(ref_nodes) = &node.construction_args {\n\t\t\t\tfor ref_id in ref_nodes {\n\t\t\t\t\tself.check_ref(ref_id, id);\n\t\t\t\t\tedges.entry(*id).or_default().push(*ref_id)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tedges\n\t}\n\n\tfn collect_inwards_edges_with_mapping(&self) -> (Vec<Vec<usize>>, FxHashMap<NodeId, usize>) {\n\t\tlet id_map: FxHashMap<_, _> = self.nodes.iter().enumerate().map(|(idx, (id, _))| (*id, idx)).collect();\n\n\t\t// Collect inwards edges using dense indices\n\t\tlet mut inwards_edges = vec![Vec::new(); self.nodes.len()];\n\t\tfor (node_id, node) in &self.nodes {\n\t\t\tlet node_index = id_map[node_id];\n\n\t\t\tif let ConstructionArgs::Nodes(ref_nodes) = &node.construction_args {\n\t\t\t\tfor ref_id in ref_nodes {\n\t\t\t\t\tself.check_ref(ref_id, &NodeId(node_index as u64));\n\t\t\t\t\tinwards_edges[node_index].push(id_map[ref_id]);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t(inwards_edges, id_map)\n\t}\n\n\t/// Inserts context nullification nodes to optimize caching.\n\t/// This analysis is performed after topological sorting to ensure proper dependency tracking.\n\tpub fn insert_context_nullification_nodes(&mut self) -> Result<(), String> {\n\t\t// Perform topological sort once\n\t\tself.reorder_ids()?;\n\n\t\tself.find_context_dependencies(self.output);\n\n\t\t// Perform topological sort a second time to integrate the new nodes\n\t\tself.reorder_ids()?;\n\n\t\tOk(())\n\t}\n\n\tfn insert_context_nullification_node(&mut self, node_id: NodeId, context_deps: ContextFeatures) -> NodeId {\n\t\tlet (_, node) = &self.nodes[node_id.0 as usize];\n\t\tlet mut path = node.original_location.path.clone();\n\n\t\t// Add a path extension with a placeholder value which should not conflict with existing paths\n\t\tif let Some(p) = path.as_mut() {\n\t\t\tp.push(NodeId(10))\n\t\t}\n\n\t\tlet memo_node_id = NodeId(self.nodes.len() as u64);\n\n\t\tself.nodes.push((\n\t\t\tmemo_node_id,\n\t\t\tProtoNode {\n\t\t\t\tconstruction_args: ConstructionArgs::Nodes(vec![node_id]),\n\t\t\t\tcall_argument: concrete!(Context),\n\t\t\t\tidentifier: graphene_core::memo::memo::IDENTIFIER,\n\t\t\t\toriginal_location: OriginalLocation {\n\t\t\t\t\tpath: path.clone(),\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t\t..Default::default()\n\t\t\t},\n\t\t));\n\n\t\tlet nullification_value_node_id = NodeId(self.nodes.len() as u64);\n\n\t\tself.nodes.push((\n\t\t\tnullification_value_node_id,\n\t\t\tProtoNode {\n\t\t\t\tconstruction_args: ConstructionArgs::Value(MemoHash::new(TaggedValue::ContextFeatures(context_deps))),\n\t\t\t\tcall_argument: concrete!(Context),\n\t\t\t\tidentifier: ProtoNodeIdentifier::new(\"core_types::value::ClonedNode\"),\n\t\t\t\toriginal_location: OriginalLocation {\n\t\t\t\t\tpath: path.clone(),\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t\t..Default::default()\n\t\t\t},\n\t\t));\n\t\tlet nullification_node_id = NodeId(self.nodes.len() as u64);\n\t\tself.nodes.push((\n\t\t\tnullification_node_id,\n\t\t\tProtoNode {\n\t\t\t\tconstruction_args: ConstructionArgs::Nodes(vec![memo_node_id, nullification_value_node_id]),\n\t\t\t\tcall_argument: concrete!(Context),\n\t\t\t\tidentifier: graphene_core::context_modification::context_modification::IDENTIFIER,\n\t\t\t\toriginal_location: OriginalLocation {\n\t\t\t\t\tpath: path.clone(),\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t\t..Default::default()\n\t\t\t},\n\t\t));\n\t\tnullification_node_id\n\t}\n\n\tfn find_context_dependencies(&mut self, id: NodeId) -> (ContextFeatures, Option<NodeId>) {\n\t\tlet mut branch_dependencies = Vec::new();\n\t\tlet mut combined_deps = ContextFeatures::default();\n\t\tlet node_index = id.0 as usize;\n\n\t\tlet context_features = self.nodes[node_index].1.context_features;\n\n\t\tlet mut inputs = match &self.nodes[node_index].1.construction_args {\n\t\t\t// We pretend like we have already placed context modification nodes after ourselves because value nodes don't need to be cached\n\t\t\tConstructionArgs::Value(_) => return (context_features.extract, Some(id)),\n\t\t\tConstructionArgs::Nodes(items) => items.clone(),\n\t\t\tConstructionArgs::Inline(_) => return (context_features.extract, Some(id)),\n\t\t};\n\n\t\t// Compute the dependencies for each branch and combine all of them\n\t\tfor &node in &inputs {\n\t\t\tlet branch = self.find_context_dependencies(node);\n\n\t\t\tbranch_dependencies.push(branch);\n\t\t\tcombined_deps |= branch.0;\n\t\t}\n\t\tlet mut new_deps = combined_deps;\n\n\t\t// Remove requirements which this node provides\n\t\tnew_deps &= !context_features.inject;\n\t\t// Add requirements we have\n\t\tnew_deps |= context_features.extract;\n\n\t\t// If we either introduce new dependencies, we can cache all children which don't yet need that dependency\n\t\tlet we_introduce_new_deps = !combined_deps.contains(new_deps);\n\n\t\t// For diverging branches, we can add a cache node for all branches which don't reqire all dependencies\n\t\tfor (child_node, (deps, new_id)) in inputs.iter_mut().zip(branch_dependencies.into_iter()) {\n\t\t\tif let Some(new_id) = new_id {\n\t\t\t\t*child_node = new_id;\n\t\t\t} else if we_introduce_new_deps || deps != combined_deps {\n\t\t\t\t*child_node = self.insert_context_nullification_node(*child_node, deps);\n\t\t\t}\n\t\t}\n\t\tself.nodes[node_index].1.construction_args = ConstructionArgs::Nodes(inputs);\n\n\t\t// Which dependencies do we supply (and don't need ourselves)?\n\t\tlet net_injections = context_features.inject.difference(context_features.extract);\n\n\t\t// Which dependencies still need to be met after this node?\n\t\tlet remaining_deps_from_children = combined_deps.difference(net_injections);\n\n\t\t// Do we satisfy any existing dependencies?\n\t\tlet we_supply_existing_deps = !combined_deps.difference(remaining_deps_from_children).is_empty();\n\n\t\tlet mut new_id = None;\n\t\tif we_supply_existing_deps {\n\t\t\t// Our set of context dependencies has shrunk so we can add a cache node after the current node\n\t\t\tnew_id = Some(self.insert_context_nullification_node(id, new_deps));\n\t\t}\n\n\t\t(new_deps, new_id)\n\t}\n\n\t/// Update all of the references to a node ID in the graph with a new ID named `compose_node_id`.\n\tfn replace_node_id(&mut self, outwards_edges: &HashMap<NodeId, Vec<NodeId>>, node_id: NodeId, replacement_node_id: NodeId) {\n\t\t// Update references in other nodes to use the new node\n\t\tif let Some(referring_nodes) = outwards_edges.get(&node_id) {\n\t\t\tfor &referring_node_id in referring_nodes {\n\t\t\t\tlet (_, referring_node) = &mut self.nodes[referring_node_id.0 as usize];\n\t\t\t\treferring_node.map_ids(|id| if id == node_id { replacement_node_id } else { id })\n\t\t\t}\n\t\t}\n\n\t\tif self.output == node_id {\n\t\t\tself.output = replacement_node_id;\n\t\t}\n\n\t\tself.inputs.iter_mut().for_each(|id| {\n\t\t\tif *id == node_id {\n\t\t\t\t*id = replacement_node_id;\n\t\t\t}\n\t\t});\n\t}\n\n\t// Based on https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search\n\t// This approach excludes nodes that are not connected\n\tpub fn topological_sort(&self) -> Result<(Vec<NodeId>, FxHashMap<NodeId, usize>), String> {\n\t\tlet (inwards_edges, id_map) = self.collect_inwards_edges_with_mapping();\n\t\tlet mut sorted = Vec::with_capacity(self.nodes.len());\n\t\tlet mut stack = vec![id_map[&self.output]];\n\t\tlet mut state = vec![NodeState::Unvisited; self.nodes.len()];\n\n\t\twhile let Some(&node_index) = stack.last() {\n\t\t\tmatch state[node_index] {\n\t\t\t\tNodeState::Unvisited => {\n\t\t\t\t\tstate[node_index] = NodeState::Visiting;\n\t\t\t\t\tfor &dep_index in inwards_edges[node_index].iter().rev() {\n\t\t\t\t\t\tmatch state[dep_index] {\n\t\t\t\t\t\t\tNodeState::Visiting => {\n\t\t\t\t\t\t\t\treturn Err(format!(\"Cycle detected involving node {}\", self.nodes[dep_index].0));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tNodeState::Unvisited => {\n\t\t\t\t\t\t\t\tstack.push(dep_index);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tNodeState::Visited => {}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tNodeState::Visiting => {\n\t\t\t\t\tstack.pop();\n\t\t\t\t\tstate[node_index] = NodeState::Visited;\n\t\t\t\t\tsorted.push(NodeId(node_index as u64));\n\t\t\t\t}\n\t\t\t\tNodeState::Visited => {\n\t\t\t\t\tstack.pop();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tOk((sorted, id_map))\n\t}\n\n\tfn is_topologically_sorted(&self) -> bool {\n\t\tlet mut visited = HashSet::new();\n\n\t\tlet inwards_edges = self.collect_inwards_edges();\n\t\tfor (id, _) in &self.nodes {\n\t\t\tfor &dependency in inwards_edges.get(id).unwrap_or(&Vec::new()) {\n\t\t\t\tif !visited.contains(&dependency) {\n\t\t\t\t\tdbg!(id, dependency);\n\t\t\t\t\tdbg!(&visited);\n\t\t\t\t\tdbg!(&self.nodes);\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\tvisited.insert(*id);\n\t\t}\n\t\ttrue\n\t}\n\n\t/// Sort the nodes vec so it is in a topological order. This ensures that no node takes an input from a node that is found later in the list.\n\tfn reorder_ids(&mut self) -> Result<(), String> {\n\t\tlet (order, _id_map) = self.topological_sort()?;\n\n\t\t// // Map of node ids to their current index in the nodes vector\n\t\t// let current_positions: FxHashMap<_, _> = self.nodes.iter().enumerate().map(|(pos, (id, _))| (*id, pos)).collect();\n\n\t\t// // Map of node ids to their new index based on topological order\n\t\tlet new_positions: FxHashMap<_, _> = order.iter().enumerate().map(|(pos, id)| (self.nodes[id.0 as usize].0, pos)).collect();\n\t\t// assert_eq!(id_map, current_positions);\n\n\t\t// Create a new nodes vector based on the topological order\n\n\t\tlet mut new_nodes = Vec::with_capacity(order.len());\n\t\tfor (index, &id) in order.iter().enumerate() {\n\t\t\tlet mut node = std::mem::take(&mut self.nodes[id.0 as usize].1);\n\t\t\t// Update node references to reflect the new order\n\t\t\tnode.map_ids(|id| NodeId(*new_positions.get(&id).expect(\"node not found in lookup table\") as u64));\n\t\t\tnew_nodes.push((NodeId(index as u64), node));\n\t\t}\n\n\t\t// Update node references to reflect the new order\n\t\t// new_nodes.iter_mut().for_each(|(_, node)| {\n\t\t// \tnode.map_ids(|id| *new_positions.get(&id).expect(\"node not found in lookup table\"), false);\n\t\t// });\n\n\t\t// Update the nodes vector and other references\n\t\tself.nodes = new_nodes;\n\t\tself.inputs = self.inputs.iter().filter_map(|id| new_positions.get(id).map(|x| NodeId(*x as u64))).collect();\n\t\tself.output = NodeId(*new_positions.get(&self.output).unwrap() as u64);\n\n\t\tassert_eq!(order.len(), self.nodes.len());\n\t\tOk(())\n\t}\n}\n#[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)]\npub enum GraphErrorType {\n\tNodeNotFound(NodeId),\n\tUnexpectedGenerics {\n\t\tindex: usize,\n\t\tinputs: Vec<Type>,\n\t},\n\tNoImplementations,\n\tNoConstructor,\n\t/// The `inputs` represents a formatted list of input indices corresponding to their types.\n\t/// Each element in `error_inputs` represents a valid `NodeIOTypes` implementation.\n\t/// The inner Vec stores the inputs which need to be changed and what type each needs to be changed to.\n\tInvalidImplementations {\n\t\tinputs: String,\n\t\terror_inputs: Vec<Vec<(usize, (Type, Type))>>,\n\t},\n\tMultipleImplementations {\n\t\tinputs: String,\n\t\tvalid: Vec<NodeIOTypes>,\n\t},\n}\nimpl Debug for GraphErrorType {\n\t// TODO: format with the document graph context so the input index is the same as in the graph UI.\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tmatch self {\n\t\t\tGraphErrorType::NodeNotFound(id) => write!(f, \"Input node {id} is not present in the typing context\"),\n\t\t\tGraphErrorType::UnexpectedGenerics { index, inputs } => write!(f, \"Generic inputs should not exist but found at {index}: {inputs:?}\"),\n\t\t\tGraphErrorType::NoImplementations => write!(f, \"No implementations found\"),\n\t\t\tGraphErrorType::NoConstructor => write!(f, \"No construct found for node\"),\n\t\t\tGraphErrorType::InvalidImplementations { inputs, error_inputs } => {\n\t\t\t\tlet format_error = |(index, (found, expected)): &(usize, (Type, Type))| {\n\t\t\t\t\tlet index = index + 1;\n\t\t\t\t\tformat!(\n\t\t\t\t\t\t\"\\\n\t\t\t\t\t\t• Input {index}:\\n\\\n\t\t\t\t\t\t…found:       {found}\\n\\\n\t\t\t\t\t\t…expected: {expected}\\\n\t\t\t\t\t\t\"\n\t\t\t\t\t)\n\t\t\t\t};\n\t\t\t\tlet format_error_list = |errors: &Vec<(usize, (Type, Type))>| errors.iter().map(format_error).collect::<Vec<_>>().join(\"\\n\");\n\t\t\t\tlet mut errors = error_inputs.iter().map(format_error_list).collect::<Vec<_>>();\n\t\t\t\terrors.sort();\n\t\t\t\tlet errors = errors.join(\"\\n\");\n\t\t\t\tlet incompatibility = if errors.chars().filter(|&c| c == '•').count() == 1 {\n\t\t\t\t\t\"This input type is incompatible:\"\n\t\t\t\t} else {\n\t\t\t\t\t\"These input types are incompatible:\"\n\t\t\t\t};\n\n\t\t\t\twrite!(\n\t\t\t\t\tf,\n\t\t\t\t\t\"\\\n\t\t\t\t\t{incompatibility}\\n\\\n\t\t\t\t\t{errors}\\n\\\n\t\t\t\t\t\\n\\\n\t\t\t\t\tThe node is currently receiving all of the following input types:\\n\\\n\t\t\t\t\t{inputs}\\n\\\n\t\t\t\t\tThis is not a supported arrangement of types for the node.\\\n\t\t\t\t\t\"\n\t\t\t\t)\n\t\t\t}\n\t\t\tGraphErrorType::MultipleImplementations { inputs, valid } => write!(f, \"Multiple implementations found ({inputs}):\\n{valid:#?}\"),\n\t\t}\n\t}\n}\n#[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct GraphError {\n\tpub node_path: Vec<NodeId>,\n\tpub identifier: Cow<'static, str>,\n\tpub error: GraphErrorType,\n}\nimpl GraphError {\n\tpub fn new(node: &ProtoNode, text: impl Into<GraphErrorType>) -> Self {\n\t\tSelf {\n\t\t\tnode_path: node.original_location.path.clone().unwrap_or_default(),\n\t\t\tidentifier: Cow::Owned(node.identifier.as_str().to_string()),\n\t\t\terror: text.into(),\n\t\t}\n\t}\n}\nimpl Debug for GraphError {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tf.debug_struct(\"GraphError\")\n\t\t\t.field(\"node_path\", &self.node_path.iter().map(|id| id.0).collect::<Vec<_>>())\n\t\t\t.field(\"identifier\", &self.identifier.to_string())\n\t\t\t.field(\"error\", &self.error)\n\t\t\t.finish()\n\t}\n}\npub type GraphErrors = Vec<GraphError>;\n\n/// The `TypingContext` is used to store the types of the nodes indexed by their stable node id.\n#[derive(Default, Clone, dyn_any::DynAny)]\npub struct TypingContext {\n\tlookup: Cow<'static, HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>>>,\n\tinferred: HashMap<NodeId, NodeIOTypes>,\n\tconstructor: HashMap<NodeId, NodeConstructor>,\n}\n\nimpl TypingContext {\n\t/// Creates a new `TypingContext` with the given lookup table.\n\tpub fn new(lookup: &'static HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>>) -> Self {\n\t\tSelf {\n\t\t\tlookup: Cow::Borrowed(lookup),\n\t\t\t..Default::default()\n\t\t}\n\t}\n\n\t/// Updates the `TypingContext` with a given proto network. This will infer the types of the nodes\n\t/// and store them in the `inferred` field. The proto network has to be topologically sorted\n\t/// and contain fully resolved stable node ids.\n\tpub fn update(&mut self, network: &ProtoNetwork) -> Result<(), GraphErrors> {\n\t\tfor (id, node) in network.nodes.iter() {\n\t\t\tself.infer(*id, node)?;\n\t\t}\n\n\t\tOk(())\n\t}\n\n\tpub fn remove_inference(&mut self, node_id: NodeId) -> Option<NodeIOTypes> {\n\t\tself.constructor.remove(&node_id);\n\t\tself.inferred.remove(&node_id)\n\t}\n\n\t/// Returns the node constructor for a given node id.\n\tpub fn constructor(&self, node_id: NodeId) -> Option<NodeConstructor> {\n\t\tself.constructor.get(&node_id).copied()\n\t}\n\n\t/// Returns the type of a given node id if it exists\n\tpub fn type_of(&self, node_id: NodeId) -> Option<&NodeIOTypes> {\n\t\tself.inferred.get(&node_id)\n\t}\n\n\t/// Returns the inferred types for a given node id.\n\tpub fn infer(&mut self, node_id: NodeId, node: &ProtoNode) -> Result<NodeIOTypes, GraphErrors> {\n\t\t// Return the inferred type if it is already known\n\t\tif let Some(inferred) = self.inferred.get(&node_id) {\n\t\t\treturn Ok(inferred.clone());\n\t\t}\n\n\t\tlet inputs = match node.construction_args {\n\t\t\t// If the node has a value input we can infer the return type from it\n\t\t\tConstructionArgs::Value(ref v) => {\n\t\t\t\t// TODO: This should return a reference to the value\n\t\t\t\tlet types = NodeIOTypes::new(concrete!(Context), Type::Future(Box::new(v.ty())), vec![]);\n\t\t\t\tself.inferred.insert(node_id, types.clone());\n\t\t\t\treturn Ok(types);\n\t\t\t}\n\t\t\t// If the node has nodes as inputs we can infer the types from the node outputs\n\t\t\tConstructionArgs::Nodes(ref nodes) => nodes\n\t\t\t\t.iter()\n\t\t\t\t.map(|id| {\n\t\t\t\t\tself.inferred\n\t\t\t\t\t\t.get(id)\n\t\t\t\t\t\t.ok_or_else(|| vec![GraphError::new(node, GraphErrorType::NodeNotFound(*id))])\n\t\t\t\t\t\t.map(|node| node.ty())\n\t\t\t\t})\n\t\t\t\t.collect::<Result<Vec<Type>, GraphErrors>>()?,\n\t\t\tConstructionArgs::Inline(ref inline) => vec![inline.ty.clone()],\n\t\t};\n\n\t\t// Get the node input type from the proto node declaration\n\t\tlet call_argument = &node.call_argument;\n\t\tlet impls = self.lookup.get(&node.identifier).ok_or_else(|| vec![GraphError::new(node, GraphErrorType::NoImplementations)])?;\n\n\t\tif let Some(index) = inputs.iter().position(|p| {\n\t\t\tmatches!(p,\n\t\t\tType::Fn(_, b) if matches!(b.as_ref(), Type::Generic(_)))\n\t\t}) {\n\t\t\treturn Err(vec![GraphError::new(node, GraphErrorType::UnexpectedGenerics { index, inputs })]);\n\t\t}\n\n\t\t/// Checks if a proposed input to a particular (primary or secondary) input connector is valid for its type signature.\n\t\t/// `from` indicates the value given to a input, `to` indicates the input's allowed type as specified by its type signature.\n\t\tfn valid_type(from: &Type, to: &Type) -> bool {\n\t\t\tmatch (from, to) {\n\t\t\t\t// Direct comparison of two concrete types.\n\t\t\t\t(Type::Concrete(type1), Type::Concrete(type2)) => type1 == type2,\n\t\t\t\t// Check inner type for futures\n\t\t\t\t(Type::Future(type1), Type::Future(type2)) => valid_type(type1, type2),\n\t\t\t\t// Direct comparison of two function types.\n\t\t\t\t// Note: in the presence of subtyping, functions are considered on a \"greater than or equal to\" basis of its function type's generality.\n\t\t\t\t// That means we compare their types with a contravariant relationship, which means that a more general type signature may be substituted for a more specific type signature.\n\t\t\t\t// For example, we allow `T -> V` to be substituted with `T' -> V` or `() -> V` where T' and () are more specific than T.\n\t\t\t\t// This allows us to supply anything to a function that is satisfied with `()`.\n\t\t\t\t// In other words, we are implementing these two relations, where the >= operator means that the left side is more general than the right side:\n\t\t\t\t// - `T >= T' ⇒ (T' -> V) >= (T -> V)` (functions are contravariant in their input types)\n\t\t\t\t// - `V >= V' ⇒ (T -> V) >= (T -> V')` (functions are covariant in their output types)\n\t\t\t\t// While these two relations aren't a truth about the universe, they are a design decision that we are employing in our language design that is also common in other languages.\n\t\t\t\t// For example, Rust implements these same relations as it describes here: <https://doc.rust-lang.org/nomicon/subtyping.html>\n\t\t\t\t// Graphite doesn't have subtyping currently, but it used to have it, and may do so again, so we make sure to compare types in this way to make things easier.\n\t\t\t\t// More details explained here: <https://github.com/GraphiteEditor/Graphite/issues/1741>\n\t\t\t\t(Type::Fn(in1, out1), Type::Fn(in2, out2)) => valid_type(out2, out1) && valid_type(in1, in2),\n\t\t\t\t// If either the proposed input or the allowed input are generic, we allow the substitution (meaning this is a valid subtype).\n\t\t\t\t// TODO: Add proper generic counting which is not based on the name\n\t\t\t\t(Type::Generic(_), _) | (_, Type::Generic(_)) => true,\n\t\t\t\t// Reject unknown type relationships.\n\t\t\t\t_ => false,\n\t\t\t}\n\t\t}\n\n\t\t// List of all implementations that match the input types\n\t\tlet valid_output_types = impls\n\t\t\t.keys()\n\t\t\t.filter(|node_io| valid_type(&node_io.call_argument, call_argument) && inputs.iter().zip(node_io.inputs.iter()).all(|(p1, p2)| valid_type(p1, p2)))\n\t\t\t.collect::<Vec<_>>();\n\n\t\t// Attempt to substitute generic types with concrete types and save the list of results\n\t\tlet substitution_results = valid_output_types\n\t\t\t.iter()\n\t\t\t.map(|node_io| {\n\t\t\t\tlet generics_lookup: Result<HashMap<_, _>, _> = collect_generics(node_io)\n\t\t\t\t\t.iter()\n\t\t\t\t\t.map(|generic| check_generic(node_io, call_argument, &inputs, generic).map(|x| (generic.to_string(), x)))\n\t\t\t\t\t.collect();\n\n\t\t\t\tgenerics_lookup.map(|generics_lookup| {\n\t\t\t\t\tlet orig_node_io = (*node_io).clone();\n\t\t\t\t\tlet mut new_node_io = orig_node_io.clone();\n\t\t\t\t\treplace_generics(&mut new_node_io, &generics_lookup);\n\t\t\t\t\t(new_node_io, orig_node_io)\n\t\t\t\t})\n\t\t\t})\n\t\t\t.collect::<Vec<_>>();\n\n\t\t// Collect all substitutions that are valid\n\t\tlet valid_impls = substitution_results.iter().filter_map(|result| result.as_ref().ok()).collect::<Vec<_>>();\n\n\t\tmatch valid_impls.as_slice() {\n\t\t\t[] => {\n\t\t\t\tlet convert_node_index_offset = node.original_location.auto_convert_index.unwrap_or(0);\n\t\t\t\tlet mut best_errors = usize::MAX;\n\t\t\t\tlet mut error_inputs = Vec::new();\n\t\t\t\tfor node_io in impls.keys() {\n\t\t\t\t\t// For errors on Convert nodes, offset the input index so it correctly corresponds to the node it is connected to.\n\t\t\t\t\tlet current_errors = [call_argument]\n\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t.chain(&inputs)\n\t\t\t\t\t\t.cloned()\n\t\t\t\t\t\t.zip([&node_io.call_argument].into_iter().chain(&node_io.inputs).cloned())\n\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t.filter(|(_, (p1, p2))| !valid_type(p1, p2))\n\t\t\t\t\t\t.map(|(index, expected)| (index - 1 + convert_node_index_offset, expected))\n\t\t\t\t\t\t.collect::<Vec<_>>();\n\t\t\t\t\tif current_errors.len() < best_errors {\n\t\t\t\t\t\tbest_errors = current_errors.len();\n\t\t\t\t\t\terror_inputs.clear();\n\t\t\t\t\t}\n\t\t\t\t\tif current_errors.len() <= best_errors {\n\t\t\t\t\t\terror_inputs.push(current_errors);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tlet inputs = [call_argument]\n\t\t\t\t\t.into_iter()\n\t\t\t\t\t.chain(&inputs)\n\t\t\t\t\t.enumerate()\n\t\t\t\t\t.filter_map(|(i, t)| {\n\t\t\t\t\t\tif i == 0 {\n\t\t\t\t\t\t\tNone\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tlet number = i + convert_node_index_offset;\n\t\t\t\t\t\t\tSome(format!(\"• Input {number}: {t}\"))\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\t.collect::<Vec<_>>()\n\t\t\t\t\t.join(\"\\n\");\n\t\t\t\tErr(vec![GraphError::new(node, GraphErrorType::InvalidImplementations { inputs, error_inputs })])\n\t\t\t}\n\t\t\t[(node_io, org_nio)] => {\n\t\t\t\tlet node_io = node_io.clone();\n\n\t\t\t\t// Save the inferred type\n\t\t\t\tself.inferred.insert(node_id, node_io.clone());\n\t\t\t\tself.constructor.insert(node_id, impls[org_nio]);\n\t\t\t\tOk(node_io)\n\t\t\t}\n\t\t\t// If two types are available and one of them accepts () an input, always choose that one\n\t\t\t[first, second] => {\n\t\t\t\tif first.0.call_argument != second.0.call_argument {\n\t\t\t\t\tfor (node_io, orig_nio) in [first, second] {\n\t\t\t\t\t\tif node_io.call_argument != concrete!(()) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Save the inferred type\n\t\t\t\t\t\tself.inferred.insert(node_id, node_io.clone());\n\t\t\t\t\t\tself.constructor.insert(node_id, impls[orig_nio]);\n\t\t\t\t\t\treturn Ok(node_io.clone());\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tlet inputs = [call_argument].into_iter().chain(&inputs).map(ToString::to_string).collect::<Vec<_>>().join(\", \");\n\t\t\t\tlet valid = valid_output_types.into_iter().cloned().collect();\n\t\t\t\tErr(vec![GraphError::new(node, GraphErrorType::MultipleImplementations { inputs, valid })])\n\t\t\t}\n\n\t\t\t_ => {\n\t\t\t\tlet inputs = [call_argument].into_iter().chain(&inputs).map(ToString::to_string).collect::<Vec<_>>().join(\", \");\n\t\t\t\tlet valid = valid_output_types.into_iter().cloned().collect();\n\t\t\t\tErr(vec![GraphError::new(node, GraphErrorType::MultipleImplementations { inputs, valid })])\n\t\t\t}\n\t\t}\n\t}\n}\n\n/// Returns a list of all generic types used in the node\nfn collect_generics(types: &NodeIOTypes) -> Vec<Cow<'static, str>> {\n\tlet inputs = [&types.call_argument].into_iter().chain(types.inputs.iter().map(|x| x.nested_type()));\n\tlet mut generics = inputs\n\t\t.filter_map(|t| match t {\n\t\t\tType::Generic(out) => Some(out.clone()),\n\t\t\t_ => None,\n\t\t})\n\t\t.collect::<Vec<_>>();\n\tif let Type::Generic(out) = &types.return_value {\n\t\tgenerics.push(out.clone());\n\t}\n\tgenerics.dedup();\n\tgenerics\n}\n\n/// Checks if a generic type can be substituted with a concrete type and returns the concrete type\nfn check_generic(types: &NodeIOTypes, input: &Type, parameters: &[Type], generic: &str) -> Result<Type, String> {\n\tlet inputs = [(Some(&types.call_argument), Some(input))]\n\t\t.into_iter()\n\t\t.chain(types.inputs.iter().map(|x| x.fn_input()).zip(parameters.iter().map(|x| x.fn_input())))\n\t\t.chain(types.inputs.iter().map(|x| x.fn_output()).zip(parameters.iter().map(|x| x.fn_output())));\n\tlet concrete_inputs = inputs.filter(|(ni, _)| matches!(ni, Some(Type::Generic(input)) if generic == input));\n\tlet mut outputs = concrete_inputs.flat_map(|(_, out)| out);\n\tlet out_ty = outputs\n\t\t.next()\n\t\t.ok_or_else(|| format!(\"Generic output type {generic} is not dependent on input {input:?} or parameters {parameters:?}\",))?;\n\tif outputs.any(|ty| ty != out_ty) {\n\t\treturn Err(format!(\"Generic output type {generic} is dependent on multiple inputs or parameters\",));\n\t}\n\tOk(out_ty.clone())\n}\n\n/// Returns a list of all generic types used in the node\nfn replace_generics(types: &mut NodeIOTypes, lookup: &HashMap<String, Type>) {\n\tlet replace = |ty: &Type| {\n\t\tlet Type::Generic(ident) = ty else { return None };\n\t\tlookup.get(ident.as_ref()).cloned()\n\t};\n\ttypes.call_argument.replace_nested(replace);\n\ttypes.return_value.replace_nested(replace);\n\tfor input in &mut types.inputs {\n\t\tinput.replace_nested(replace);\n\t}\n}\n\n#[cfg(test)]\nmod test {\n\tuse super::*;\n\tuse crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode};\n\n\t#[test]\n\tfn topological_sort() {\n\t\tlet construction_network = test_network();\n\t\tlet (sorted, _) = construction_network.topological_sort().expect(\"Error when calling 'topological_sort' on 'construction_network.\");\n\t\tlet sorted: Vec<_> = sorted.iter().map(|x| construction_network.nodes[x.0 as usize].0).collect();\n\t\tprintln!(\"{sorted:#?}\");\n\t\tassert_eq!(sorted, vec![NodeId(14), NodeId(10), NodeId(11), NodeId(1)]);\n\t}\n\n\t#[test]\n\tfn topological_sort_with_cycles() {\n\t\tlet construction_network = test_network_with_cycles();\n\t\tlet sorted = construction_network.topological_sort();\n\n\t\tassert!(sorted.is_err())\n\t}\n\n\t#[test]\n\tfn id_reordering() {\n\t\tlet mut construction_network = test_network();\n\t\tconstruction_network.reorder_ids().expect(\"Error when calling 'reorder_ids' on 'construction_network.\");\n\t\tlet (sorted, _) = construction_network.topological_sort().expect(\"Error when calling 'topological_sort' on 'construction_network.\");\n\t\tlet sorted: Vec<_> = sorted.iter().map(|x| construction_network.nodes[x.0 as usize].0).collect();\n\t\tprintln!(\"nodes: {:#?}\", construction_network.nodes);\n\t\tassert_eq!(sorted, vec![NodeId(0), NodeId(1), NodeId(2), NodeId(3)]);\n\t\tlet ids: Vec<_> = construction_network.nodes.iter().map(|(id, _)| *id).collect();\n\t\tprintln!(\"{ids:#?}\");\n\t\tprintln!(\"nodes: {:#?}\", construction_network.nodes);\n\t\tassert_eq!(construction_network.nodes[0].1.identifier.as_str(), \"value\");\n\t\tassert_eq!(ids, vec![NodeId(0), NodeId(1), NodeId(2), NodeId(3)]);\n\t}\n\n\t#[test]\n\tfn id_reordering_idempotent() {\n\t\tlet mut construction_network = test_network();\n\t\tconstruction_network.reorder_ids().expect(\"Error when calling 'reorder_ids' on 'construction_network.\");\n\t\tconstruction_network.reorder_ids().expect(\"Error when calling 'reorder_ids' on 'construction_network.\");\n\t\tlet (sorted, _) = construction_network.topological_sort().expect(\"Error when calling 'topological_sort' on 'construction_network.\");\n\t\tassert_eq!(sorted, vec![NodeId(0), NodeId(1), NodeId(2), NodeId(3)]);\n\t\tlet ids: Vec<_> = construction_network.nodes.iter().map(|(id, _)| *id).collect();\n\t\tprintln!(\"{ids:#?}\");\n\t\tassert_eq!(construction_network.nodes[0].1.identifier.as_str(), \"value\");\n\t\tassert_eq!(ids, vec![NodeId(0), NodeId(1), NodeId(2), NodeId(3)]);\n\t}\n\n\t#[test]\n\tfn stable_node_id_generation() {\n\t\tlet mut construction_network = test_network();\n\t\tconstruction_network\n\t\t\t.insert_context_nullification_nodes()\n\t\t\t.expect(\"Error when calling 'insert_context_nullification_nodes' on 'construction_network.\");\n\t\tconstruction_network.generate_stable_node_ids();\n\t\tassert_eq!(construction_network.nodes[0].1.identifier.as_str(), \"value\");\n\t\tlet ids: Vec<_> = construction_network.nodes.iter().map(|(id, _)| *id).collect();\n\n\t\t// If this assert fails: These NodeIds seem to be changing when you modify TaggedValue, just update them.\n\t\tassert_eq!(\n\t\t\tids,\n\t\t\tvec![NodeId(2791689253855410677), NodeId(11246167042277902310), NodeId(1014827049498980779), NodeId(4864562752646903491)]\n\t\t);\n\t}\n\n\tfn test_network() -> ProtoNetwork {\n\t\tProtoNetwork {\n\t\t\tinputs: vec![NodeId(10)],\n\t\t\toutput: NodeId(1),\n\t\t\tnodes: [\n\t\t\t\t(\n\t\t\t\t\tNodeId(7),\n\t\t\t\t\tProtoNode {\n\t\t\t\t\t\tidentifier: ProtoNodeIdentifier::new(\"id\"),\n\t\t\t\t\t\tcall_argument: concrete!(()),\n\t\t\t\t\t\tconstruction_args: ConstructionArgs::Nodes(vec![NodeId(11)]),\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t\t(\n\t\t\t\t\tNodeId(1),\n\t\t\t\t\tProtoNode {\n\t\t\t\t\t\tidentifier: ProtoNodeIdentifier::new(\"id\"),\n\t\t\t\t\t\tcall_argument: concrete!(()),\n\t\t\t\t\t\tconstruction_args: ConstructionArgs::Nodes(vec![NodeId(11)]),\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t\t(\n\t\t\t\t\tNodeId(10),\n\t\t\t\t\tProtoNode {\n\t\t\t\t\t\tidentifier: ProtoNodeIdentifier::new(\"cons\"),\n\t\t\t\t\t\tcall_argument: concrete!(u32),\n\t\t\t\t\t\tconstruction_args: ConstructionArgs::Nodes(vec![NodeId(14)]),\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t\t(\n\t\t\t\t\tNodeId(11),\n\t\t\t\t\tProtoNode {\n\t\t\t\t\t\tidentifier: ProtoNodeIdentifier::new(\"add\"),\n\t\t\t\t\t\tcall_argument: concrete!(()),\n\t\t\t\t\t\tconstruction_args: ConstructionArgs::Nodes(vec![NodeId(10)]),\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t\t(\n\t\t\t\t\tNodeId(14),\n\t\t\t\t\tProtoNode {\n\t\t\t\t\t\tidentifier: ProtoNodeIdentifier::new(\"value\"),\n\t\t\t\t\t\tcall_argument: concrete!(()),\n\t\t\t\t\t\tconstruction_args: ConstructionArgs::Value(value::TaggedValue::U32(2).into()),\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t]\n\t\t\t.into_iter()\n\t\t\t.collect(),\n\t\t}\n\t}\n\n\tfn test_network_with_cycles() -> ProtoNetwork {\n\t\tProtoNetwork {\n\t\t\tinputs: vec![NodeId(1)],\n\t\t\toutput: NodeId(1),\n\t\t\tnodes: [\n\t\t\t\t(\n\t\t\t\t\tNodeId(1),\n\t\t\t\t\tProtoNode {\n\t\t\t\t\t\tidentifier: ProtoNodeIdentifier::new(\"id\"),\n\t\t\t\t\t\tcall_argument: concrete!(()),\n\t\t\t\t\t\tconstruction_args: ConstructionArgs::Nodes(vec![NodeId(2)]),\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t\t(\n\t\t\t\t\tNodeId(2),\n\t\t\t\t\tProtoNode {\n\t\t\t\t\t\tidentifier: ProtoNodeIdentifier::new(\"id\"),\n\t\t\t\t\t\tcall_argument: concrete!(()),\n\t\t\t\t\t\tconstruction_args: ConstructionArgs::Nodes(vec![NodeId(1)]),\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t]\n\t\t\t.into_iter()\n\t\t\t.collect(),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "node-graph/graph-craft/src/util.rs",
    "content": "use crate::document::NodeNetwork;\nuse crate::graphene_compiler::Compiler;\nuse crate::proto::ProtoNetwork;\n\npub fn load_network(document_string: &str) -> NodeNetwork {\n\tlet document: serde_json::Value = serde_json::from_str(document_string).expect(\"Failed to parse document\");\n\tlet document = (document[\"network_interface\"][\"network\"].clone()).to_string();\n\tserde_json::from_str::<NodeNetwork>(&document).expect(\"Failed to parse document\")\n}\n\npub fn compile(network: NodeNetwork) -> ProtoNetwork {\n\tlet compiler = Compiler {};\n\tcompiler.compile_single(network).unwrap()\n}\n\npub fn load_from_name(name: &str) -> NodeNetwork {\n\tlet content = std::fs::read(format!(\"../../demo-artwork/{name}.graphite\")).expect(\"failed to read file\");\n\tlet content = std::str::from_utf8(&content).unwrap();\n\tload_network(content)\n}\n\npub static DEMO_ART: [&str; 7] = [\n\t\"changing-seasons\",\n\t\"painted-dreams\",\n\t\"red-dress\",\n\t\"valley-of-spires\",\n\t\"isometric-fountain\",\n\t\"procedural-string-lights\",\n\t\"parametric-dunescape\",\n];\n"
  },
  {
    "path": "node-graph/graph-craft/src/wasm_application_io.rs",
    "content": "use dyn_any::StaticType;\nuse graphene_application_io::{ApplicationError, ApplicationIo, ResourceFuture, SurfaceHandle, SurfaceId};\n#[cfg(target_family = \"wasm\")]\nuse js_sys::{Object, Reflect};\nuse std::collections::HashMap;\nuse std::hash::Hash;\nuse std::sync::Arc;\n#[cfg(target_family = \"wasm\")]\nuse std::sync::atomic::AtomicU64;\nuse std::sync::atomic::Ordering;\n#[cfg(feature = \"tokio\")]\nuse tokio::io::AsyncReadExt;\n#[cfg(target_family = \"wasm\")]\nuse wasm_bindgen::JsCast;\n#[cfg(target_family = \"wasm\")]\nuse wasm_bindgen::JsValue;\n#[cfg(target_family = \"wasm\")]\nuse web_sys::HtmlCanvasElement;\n#[cfg(target_family = \"wasm\")]\nuse web_sys::window;\n#[cfg(feature = \"wgpu\")]\nuse wgpu_executor::WgpuExecutor;\n\n#[derive(Debug)]\nstruct WindowWrapper {\n\t#[cfg(target_family = \"wasm\")]\n\twindow: SurfaceHandle<HtmlCanvasElement>,\n\t#[cfg(not(target_family = \"wasm\"))]\n\twindow: SurfaceHandle<Arc<dyn winit::window::Window>>,\n}\n\n#[cfg(target_family = \"wasm\")]\nimpl Drop for WindowWrapper {\n\tfn drop(&mut self) {\n\t\tlet window = window().expect(\"should have a window in this context\");\n\t\tlet window = Object::from(window);\n\n\t\tlet image_canvases_key = JsValue::from_str(\"imageCanvases\");\n\n\t\tlet wrapper = || {\n\t\t\tif let Ok(canvases) = Reflect::get(&window, &image_canvases_key) {\n\t\t\t\t// Convert key and value to JsValue\n\t\t\t\tlet js_key = JsValue::from_str(self.window.window_id.to_string().as_str());\n\n\t\t\t\t// Use Reflect API to set property\n\t\t\t\tReflect::delete_property(&canvases.into(), &js_key)?;\n\t\t\t}\n\t\t\tOk::<_, JsValue>(())\n\t\t};\n\n\t\twrapper().expect(\"should be able to set canvas in global scope\")\n\t}\n}\n\n#[cfg(target_family = \"wasm\")]\nunsafe impl Sync for WindowWrapper {}\n#[cfg(target_family = \"wasm\")]\nunsafe impl Send for WindowWrapper {}\n\n#[derive(Debug, Default)]\npub struct WasmApplicationIo {\n\t#[cfg(target_family = \"wasm\")]\n\tids: AtomicU64,\n\t#[cfg(feature = \"wgpu\")]\n\tpub(crate) gpu_executor: Option<WgpuExecutor>,\n\twindows: Vec<WindowWrapper>,\n\tpub resources: HashMap<String, Arc<[u8]>>,\n}\n\nstatic WGPU_AVAILABLE: std::sync::atomic::AtomicI8 = std::sync::atomic::AtomicI8::new(-1);\n\n/// Returns:\n/// - `None` if the availability of WGPU has not been determined yet\n/// - `Some(true)` if WGPU is available\n/// - `Some(false)` if WGPU is not available\npub fn wgpu_available() -> Option<bool> {\n\tmatch WGPU_AVAILABLE.load(Ordering::SeqCst) {\n\t\t-1 => None,\n\t\t0 => Some(false),\n\t\t_ => Some(true),\n\t}\n}\n\nimpl WasmApplicationIo {\n\tpub async fn new() -> Self {\n\t\t#[cfg(all(feature = \"wgpu\", target_family = \"wasm\"))]\n\t\tlet executor = if let Some(gpu) = web_sys::window().map(|w| w.navigator().gpu()) {\n\t\t\tlet request_adapter = || {\n\t\t\t\tlet request_adapter = js_sys::Reflect::get(&gpu, &wasm_bindgen::JsValue::from_str(\"requestAdapter\")).ok()?;\n\t\t\t\tlet function = request_adapter.dyn_ref::<js_sys::Function>()?;\n\t\t\t\tSome(function.call0(&gpu).ok())\n\t\t\t};\n\t\t\tlet result = request_adapter();\n\t\t\tmatch result {\n\t\t\t\tNone => None,\n\t\t\t\tSome(_) => WgpuExecutor::new().await,\n\t\t\t}\n\t\t} else {\n\t\t\tNone\n\t\t};\n\n\t\t#[cfg(all(feature = \"wgpu\", not(target_family = \"wasm\")))]\n\t\tlet executor = WgpuExecutor::new().await;\n\n\t\t#[cfg(not(feature = \"wgpu\"))]\n\t\tlet wgpu_available = false;\n\t\t#[cfg(feature = \"wgpu\")]\n\t\tlet wgpu_available = executor.is_some();\n\t\tWGPU_AVAILABLE.store(wgpu_available as i8, Ordering::SeqCst);\n\n\t\tlet mut io = Self {\n\t\t\t#[cfg(target_family = \"wasm\")]\n\t\t\tids: AtomicU64::new(0),\n\t\t\t#[cfg(feature = \"wgpu\")]\n\t\t\tgpu_executor: executor,\n\t\t\twindows: Vec::new(),\n\t\t\tresources: HashMap::new(),\n\t\t};\n\t\tlet window = io.create_window();\n\t\tio.windows.push(WindowWrapper { window });\n\t\tio.resources.insert(\"null\".to_string(), Arc::from(include_bytes!(\"null.png\").to_vec()));\n\n\t\tio\n\t}\n\n\tpub async fn new_offscreen() -> Self {\n\t\t#[cfg(feature = \"wgpu\")]\n\t\tlet executor = WgpuExecutor::new().await;\n\n\t\t#[cfg(not(feature = \"wgpu\"))]\n\t\tlet wgpu_available = false;\n\t\t#[cfg(feature = \"wgpu\")]\n\t\tlet wgpu_available = executor.is_some();\n\t\tWGPU_AVAILABLE.store(wgpu_available as i8, Ordering::SeqCst);\n\n\t\tlet mut io = Self {\n\t\t\t#[cfg(target_family = \"wasm\")]\n\t\t\tids: AtomicU64::new(0),\n\t\t\t#[cfg(feature = \"wgpu\")]\n\t\t\tgpu_executor: executor,\n\t\t\twindows: Vec::new(),\n\t\t\tresources: HashMap::new(),\n\t\t};\n\n\t\tio.resources.insert(\"null\".to_string(), Arc::from(include_bytes!(\"null.png\").to_vec()));\n\n\t\tio\n\t}\n\t#[cfg(all(not(target_family = \"wasm\"), feature = \"wgpu\"))]\n\tpub fn new_with_context(context: wgpu_executor::WgpuContext) -> Self {\n\t\t#[cfg(feature = \"wgpu\")]\n\t\tlet executor = WgpuExecutor::with_context(context);\n\n\t\t#[cfg(not(feature = \"wgpu\"))]\n\t\tlet wgpu_available = false;\n\t\t#[cfg(feature = \"wgpu\")]\n\t\tlet wgpu_available = executor.is_some();\n\t\tWGPU_AVAILABLE.store(wgpu_available as i8, Ordering::SeqCst);\n\n\t\tlet mut io = Self {\n\t\t\tgpu_executor: executor,\n\t\t\twindows: Vec::new(),\n\t\t\tresources: HashMap::new(),\n\t\t};\n\n\t\tio.resources.insert(\"null\".to_string(), Arc::from(include_bytes!(\"null.png\").to_vec()));\n\n\t\tio\n\t}\n}\n\nunsafe impl StaticType for WasmApplicationIo {\n\ttype Static = WasmApplicationIo;\n}\n\nimpl<'a> From<&'a WasmEditorApi> for &'a WasmApplicationIo {\n\tfn from(editor_api: &'a WasmEditorApi) -> Self {\n\t\teditor_api.application_io.as_ref().unwrap()\n\t}\n}\n#[cfg(feature = \"wgpu\")]\nimpl<'a> From<&'a WasmApplicationIo> for &'a WgpuExecutor {\n\tfn from(app_io: &'a WasmApplicationIo) -> Self {\n\t\tapp_io.gpu_executor.as_ref().unwrap()\n\t}\n}\n\npub type WasmEditorApi = graphene_application_io::EditorApi<WasmApplicationIo>;\n\nimpl ApplicationIo for WasmApplicationIo {\n\t#[cfg(target_family = \"wasm\")]\n\ttype Surface = HtmlCanvasElement;\n\t#[cfg(not(target_family = \"wasm\"))]\n\ttype Surface = Arc<dyn winit::window::Window>;\n\t#[cfg(feature = \"wgpu\")]\n\ttype Executor = WgpuExecutor;\n\t#[cfg(not(feature = \"wgpu\"))]\n\ttype Executor = ();\n\n\t#[cfg(target_family = \"wasm\")]\n\tfn create_window(&self) -> SurfaceHandle<Self::Surface> {\n\t\tlet wrapper = || {\n\t\t\tlet document = window().expect(\"should have a window in this context\").document().expect(\"window should have a document\");\n\n\t\t\tlet canvas: HtmlCanvasElement = document.create_element(\"canvas\")?.dyn_into::<HtmlCanvasElement>()?;\n\t\t\tlet id = self.ids.fetch_add(1, Ordering::SeqCst);\n\t\t\t// store the canvas in the global scope so it doesn't get garbage collected\n\t\t\tlet window = window().expect(\"should have a window in this context\");\n\t\t\tlet window = Object::from(window);\n\n\t\t\tlet image_canvases_key = JsValue::from_str(\"imageCanvases\");\n\n\t\t\tlet mut canvases = Reflect::get(&window, &image_canvases_key);\n\t\t\tif canvases.is_err() {\n\t\t\t\tReflect::set(&JsValue::from(web_sys::window().unwrap()), &image_canvases_key, &Object::new()).unwrap();\n\t\t\t\tcanvases = Reflect::get(&window, &image_canvases_key);\n\t\t\t}\n\n\t\t\t// Convert key and value to JsValue\n\t\t\tlet js_key = JsValue::from_str(id.to_string().as_str());\n\t\t\tlet js_value = JsValue::from(canvas.clone());\n\n\t\t\tlet canvases = Object::from(canvases.unwrap());\n\n\t\t\t// Use Reflect API to set property\n\t\t\tReflect::set(&canvases, &js_key, &js_value)?;\n\t\t\tOk::<_, JsValue>(SurfaceHandle {\n\t\t\t\twindow_id: SurfaceId(id),\n\t\t\t\tsurface: canvas,\n\t\t\t})\n\t\t};\n\n\t\twrapper().expect(\"should be able to set canvas in global scope\")\n\t}\n\t#[cfg(not(target_family = \"wasm\"))]\n\tfn create_window(&self) -> SurfaceHandle<Self::Surface> {\n\t\ttodo!(\"winit api changed, calling create_window on EventLoop is deprecated\");\n\n\t\t// log::trace!(\"Spawning window\");\n\n\t\t// #[cfg(all(not(test), target_os = \"linux\", feature = \"wayland\"))]\n\t\t// use winit::platform::wayland::EventLoopBuilderExtWayland;\n\n\t\t// #[cfg(all(not(test), target_os = \"linux\", feature = \"wayland\"))]\n\t\t// let event_loop = winit::event_loop::EventLoopBuilder::new().with_any_thread(true).build().unwrap();\n\t\t// #[cfg(not(all(not(test), target_os = \"linux\", feature = \"wayland\")))]\n\t\t// let event_loop = winit::event_loop::EventLoop::new().unwrap();\n\n\t\t// let window = event_loop\n\t\t// \t.create_window(\n\t\t// \t\twinit::window::WindowAttributes::default()\n\t\t// \t\t\t.with_title(\"Graphite\")\n\t\t// \t\t\t.with_inner_size(winit::dpi::PhysicalSize::new(800, 600)),\n\t\t// \t)\n\t\t// \t.unwrap();\n\n\t\t// SurfaceHandle {\n\t\t// \twindow_id: SurfaceId(window.id().into()),\n\t\t// \tsurface: Arc::new(window),\n\t\t// }\n\t}\n\n\t#[cfg(target_family = \"wasm\")]\n\tfn destroy_window(&self, surface_id: SurfaceId) {\n\t\tlet window = window().expect(\"should have a window in this context\");\n\t\tlet window = Object::from(window);\n\n\t\tlet image_canvases_key = JsValue::from_str(\"imageCanvases\");\n\n\t\tlet wrapper = || {\n\t\t\tif let Ok(canvases) = Reflect::get(&window, &image_canvases_key) {\n\t\t\t\t// Convert key and value to JsValue\n\t\t\t\tlet js_key = JsValue::from_str(surface_id.0.to_string().as_str());\n\n\t\t\t\t// Use Reflect API to set property\n\t\t\t\tReflect::delete_property(&canvases.into(), &js_key)?;\n\t\t\t}\n\t\t\tOk::<_, JsValue>(())\n\t\t};\n\n\t\twrapper().expect(\"should be able to set canvas in global scope\")\n\t}\n\n\t#[cfg(not(target_family = \"wasm\"))]\n\tfn destroy_window(&self, _surface_id: SurfaceId) {}\n\n\t#[cfg(feature = \"wgpu\")]\n\tfn gpu_executor(&self) -> Option<&Self::Executor> {\n\t\tself.gpu_executor.as_ref()\n\t}\n\n\tfn load_resource(&self, url: impl AsRef<str>) -> Result<ResourceFuture, ApplicationError> {\n\t\tlet url = url::Url::parse(url.as_ref()).map_err(|_| ApplicationError::InvalidUrl)?;\n\t\tlog::trace!(\"Loading resource: {url:?}\");\n\t\tmatch url.scheme() {\n\t\t\t#[cfg(feature = \"tokio\")]\n\t\t\t\"file\" => {\n\t\t\t\tlet path = url.to_file_path().map_err(|_| ApplicationError::NotFound)?;\n\t\t\t\tlet path = path.to_str().ok_or(ApplicationError::NotFound)?;\n\t\t\t\tlet path = path.to_owned();\n\t\t\t\tOk(Box::pin(async move {\n\t\t\t\t\tlet file = tokio::fs::File::open(path).await.map_err(|_| ApplicationError::NotFound)?;\n\t\t\t\t\tlet mut reader = tokio::io::BufReader::new(file);\n\t\t\t\t\tlet mut data = Vec::new();\n\t\t\t\t\treader.read_to_end(&mut data).await.map_err(|_| ApplicationError::NotFound)?;\n\t\t\t\t\tOk(Arc::from(data))\n\t\t\t\t}) as ResourceFuture)\n\t\t\t}\n\t\t\t\"http\" | \"https\" => {\n\t\t\t\tlet url = url.to_string();\n\t\t\t\tOk(Box::pin(async move {\n\t\t\t\t\tlet client = reqwest::Client::new();\n\t\t\t\t\tlet response = client.get(url).send().await.map_err(|_| ApplicationError::NotFound)?;\n\t\t\t\t\tlet data = response.bytes().await.map_err(|_| ApplicationError::NotFound)?;\n\t\t\t\t\tOk(Arc::from(data.to_vec()))\n\t\t\t\t}) as ResourceFuture)\n\t\t\t}\n\t\t\t\"graphite\" => {\n\t\t\t\tlet path = url.path();\n\t\t\t\tlet path = path.to_owned();\n\t\t\t\tlog::trace!(\"Loading local resource: {path}\");\n\t\t\t\tlet data = self.resources.get(&path).ok_or(ApplicationError::NotFound)?.clone();\n\t\t\t\tOk(Box::pin(async move { Ok(data.clone()) }) as ResourceFuture)\n\t\t\t}\n\t\t\t_ => Err(ApplicationError::NotFound),\n\t\t}\n\t}\n\n\tfn window(&self) -> Option<SurfaceHandle<Self::Surface>> {\n\t\tself.windows.first().map(|wrapper| wrapper.window.clone())\n\t}\n}\n\n#[cfg(feature = \"wgpu\")]\npub type WasmSurfaceHandle = SurfaceHandle<wgpu_executor::Window>;\n#[cfg(feature = \"wgpu\")]\npub type WasmSurfaceHandleFrame = graphene_application_io::SurfaceHandleFrame<wgpu_executor::Window>;\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, Debug, PartialEq, Hash, serde::Serialize, serde::Deserialize)]\npub struct EditorPreferences {\n\t/// Maximum render region size in pixels along one dimension of the square area.\n\tpub max_render_region_size: u32,\n}\n\nimpl graphene_application_io::GetEditorPreferences for EditorPreferences {\n\tfn max_render_region_area(&self) -> u32 {\n\t\tlet size = self.max_render_region_size.min(u32::MAX.isqrt());\n\t\tsize.pow(2)\n\t}\n}\n\nimpl Default for EditorPreferences {\n\tfn default() -> Self {\n\t\tSelf { max_render_region_size: 1280 }\n\t}\n}\n\nunsafe impl StaticType for EditorPreferences {\n\ttype Static = EditorPreferences;\n}\n"
  },
  {
    "path": "node-graph/graphene-cli/Cargo.toml",
    "content": "[package]\nname = \"graphene-cli\"\nversion = \"0.1.0\"\nedition = \"2024\"\ndescription = \"CLI interface for the graphene language\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\nlicense = \"MIT OR Apache-2.0\"\n\n[features]\ndefault = [\"wgpu\"]\nwgpu = [\"wgpu-executor\", \"gpu\", \"graphene-std/wgpu\"]\ngpu = [\"interpreted-executor/gpu\", \"graphene-std/gpu\", \"wgpu-executor\"]\n\n[dependencies]\n# Local dependencies\ngraphene-std = { workspace = true }\ninterpreted-executor = { workspace = true }\ngraph-craft = { workspace = true, features = [\"loading\"] }\npreprocessor = { workspace = true }\n\n# Workspace dependencies\nlog = { workspace = true }\nfutures = { workspace = true }\nfern = { workspace = true }\nchrono = { workspace = true }\nwgpu = { workspace = true }\ntokio = { workspace = true }\nclap = { workspace = true, features = [\"cargo\", \"derive\"] }\nimage = { workspace = true }\nwgpu-executor = { workspace = true, optional = true }\n\n[package.metadata.cargo-shear]\nignored = [\"wgpu-executor\"]\n"
  },
  {
    "path": "node-graph/graphene-cli/src/export.rs",
    "content": "use graph_craft::document::value::{RenderOutputType, TaggedValue, UVec2};\nuse graph_craft::graphene_compiler::Executor;\nuse graphene_std::application_io::{ExportFormat, RenderConfig, TimingInformation};\nuse graphene_std::core_types::ops::Convert;\nuse graphene_std::core_types::transform::Footprint;\nuse graphene_std::raster_types::{CPU, GPU, Raster};\nuse interpreted_executor::dynamic_executor::DynamicExecutor;\nuse std::error::Error;\nuse std::io::Cursor;\nuse std::path::{Path, PathBuf};\nuse std::time::Duration;\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum FileType {\n\tSvg,\n\tPng,\n\tJpg,\n\tGif,\n}\n\npub fn detect_file_type(path: &Path) -> Result<FileType, String> {\n\tmatch path.extension().and_then(|s| s.to_str()) {\n\t\tSome(\"svg\") => Ok(FileType::Svg),\n\t\tSome(\"png\") => Ok(FileType::Png),\n\t\tSome(\"jpg\" | \"jpeg\") => Ok(FileType::Jpg),\n\t\tSome(\"gif\") => Ok(FileType::Gif),\n\t\t_ => Err(\"Unsupported file extension. Supported formats: .svg, .png, .jpg, .gif\".to_string()),\n\t}\n}\n\npub async fn export_document(\n\texecutor: &DynamicExecutor,\n\twgpu_executor: &wgpu_executor::WgpuExecutor,\n\toutput_path: PathBuf,\n\tfile_type: FileType,\n\tscale: f64,\n\t(width, height): (Option<u32>, Option<u32>),\n\ttransparent: bool,\n) -> Result<(), Box<dyn Error>> {\n\t// Determine export format based on file type\n\tlet export_format = match file_type {\n\t\tFileType::Svg => ExportFormat::Svg,\n\t\t_ => ExportFormat::Raster,\n\t};\n\n\t// Create render config with export settings\n\tlet mut render_config = RenderConfig {\n\t\tscale,\n\t\texport_format,\n\t\tfor_export: true,\n\t\t..Default::default()\n\t};\n\n\t// Set viewport dimensions if specified\n\tif let (Some(w), Some(h)) = (width, height) {\n\t\trender_config.viewport.resolution = UVec2::new(w, h);\n\t}\n\n\t// Execute the graph\n\tlet result = executor.execute(render_config).await?;\n\n\t// Handle the result based on output type\n\tmatch result {\n\t\tTaggedValue::RenderOutput(output) => match output.data {\n\t\t\tRenderOutputType::Svg { svg, .. } => {\n\t\t\t\t// Write SVG directly to file\n\t\t\t\tstd::fs::write(&output_path, svg)?;\n\t\t\t\tlog::info!(\"Exported SVG to: {}\", output_path.display());\n\t\t\t}\n\t\t\tRenderOutputType::Texture(image_texture) => {\n\t\t\t\t// Convert GPU texture to CPU buffer\n\t\t\t\tlet gpu_raster = Raster::<GPU>::new_gpu(image_texture.texture.as_ref().clone());\n\t\t\t\tlet cpu_raster: Raster<CPU> = gpu_raster.convert(Footprint::BOUNDLESS, wgpu_executor).await;\n\t\t\t\tlet (data, width, height) = cpu_raster.to_flat_u8();\n\t\t\t\t// Explicitly drop texture to make sure it lives long enough\n\t\t\t\tstd::mem::drop(image_texture);\n\n\t\t\t\t// Encode and write raster image\n\t\t\t\twrite_raster_image(output_path, file_type, data, width, height, transparent)?;\n\t\t\t}\n\t\t\tRenderOutputType::Buffer { data, width, height } => {\n\t\t\t\t// Encode and write raster image when buffer is already provided\n\t\t\t\twrite_raster_image(output_path, file_type, data, width, height, transparent)?;\n\t\t\t}\n\t\t\tother => {\n\t\t\t\treturn Err(format!(\"Unexpected render output type: {:?}. Expected Texture, Buffer for raster export or Svg for SVG export.\", other).into());\n\t\t\t}\n\t\t},\n\t\tother => return Err(format!(\"Expected RenderOutput, got: {:?}\", other).into()),\n\t}\n\n\tOk(())\n}\n\nfn write_raster_image(output_path: PathBuf, file_type: FileType, data: Vec<u8>, width: u32, height: u32, transparent: bool) -> Result<(), Box<dyn Error>> {\n\tuse image::{ImageFormat, RgbaImage};\n\n\tlet image = RgbaImage::from_raw(width, height, data).ok_or(\"Failed to create image from buffer\")?;\n\n\tlet mut cursor = Cursor::new(Vec::new());\n\n\tmatch file_type {\n\t\tFileType::Png => {\n\t\t\tif transparent {\n\t\t\t\timage.write_to(&mut cursor, ImageFormat::Png)?;\n\t\t\t} else {\n\t\t\t\tlet image: image::RgbImage = image::DynamicImage::ImageRgba8(image).to_rgb8();\n\t\t\t\timage.write_to(&mut cursor, ImageFormat::Png)?;\n\t\t\t}\n\t\t\tlog::info!(\"Exported PNG to: {}\", output_path.display());\n\t\t}\n\t\tFileType::Jpg => {\n\t\t\tlet image: image::RgbImage = image::DynamicImage::ImageRgba8(image).to_rgb8();\n\t\t\timage.write_to(&mut cursor, ImageFormat::Jpeg)?;\n\t\t\tlog::info!(\"Exported JPG to: {}\", output_path.display());\n\t\t}\n\t\tFileType::Svg | FileType::Gif => unreachable!(\"SVG and GIF should have been handled in export_document\"),\n\t}\n\n\tstd::fs::write(&output_path, cursor.into_inner())?;\n\tOk(())\n}\n\n/// Parameters for GIF animation export\n#[derive(Debug, Clone, Copy)]\npub struct AnimationParams {\n\t/// Frames per second\n\tpub fps: f64,\n\t/// Total number of frames to render\n\tpub frames: u32,\n}\n\nimpl AnimationParams {\n\t/// Create animation parameters from fps and either frame count or duration\n\tpub fn new(fps: f64, frames: Option<u32>, duration: Option<f64>) -> Self {\n\t\tlet frames = match (frames, duration) {\n\t\t\t// Duration takes precedence if both provided\n\t\t\t(_, Some(dur)) => (dur * fps).round() as u32,\n\t\t\t(Some(f), None) => f,\n\t\t\t// Default to 1 frame if neither provided\n\t\t\t(None, None) => 1,\n\t\t};\n\t\tSelf { fps, frames }\n\t}\n\n\t/// Get the frame delay in centiseconds (GIF uses 10ms units)\n\tpub fn frame_delay_centiseconds(&self) -> u16 {\n\t\t((100.0 / self.fps).round() as u16).max(1)\n\t}\n}\n\n/// Export an animated GIF by rendering multiple frames at different animation times\npub async fn export_gif(\n\texecutor: &DynamicExecutor,\n\twgpu_executor: &wgpu_executor::WgpuExecutor,\n\toutput_path: PathBuf,\n\tscale: f64,\n\t(width, height): (Option<u32>, Option<u32>),\n\tanimation: AnimationParams,\n) -> Result<(), Box<dyn Error>> {\n\tuse image::codecs::gif::{GifEncoder, Repeat};\n\tuse image::{Frame, RgbaImage};\n\tuse std::fs::File;\n\n\tlog::info!(\"Exporting GIF: {} frames at {} fps\", animation.frames, animation.fps);\n\n\tlet file = File::create(&output_path)?;\n\tlet mut encoder = GifEncoder::new(file);\n\tencoder.set_repeat(Repeat::Infinite)?;\n\n\tlet frame_delay = animation.frame_delay_centiseconds();\n\n\tfor frame_idx in 0..animation.frames {\n\t\tlet animation_time = Duration::from_secs_f64(frame_idx as f64 / animation.fps);\n\n\t\t// Print progress to stderr (overwrites previous line)\n\t\teprint!(\"\\rRendering frame {}/{}...\", frame_idx + 1, animation.frames);\n\n\t\tlog::debug!(\"Rendering frame {}/{} at time {:?}\", frame_idx + 1, animation.frames, animation_time);\n\n\t\t// Create render config with animation time\n\t\tlet mut render_config = RenderConfig {\n\t\t\tscale,\n\t\t\texport_format: ExportFormat::Raster,\n\t\t\tfor_export: true,\n\t\t\ttime: TimingInformation {\n\t\t\t\ttime: animation_time.as_secs_f64(),\n\t\t\t\tanimation_time,\n\t\t\t},\n\t\t\t..Default::default()\n\t\t};\n\n\t\t// Set viewport dimensions if specified\n\t\tif let (Some(w), Some(h)) = (width, height) {\n\t\t\trender_config.viewport.resolution = UVec2::new(w, h);\n\t\t}\n\n\t\t// Execute the graph for this frame\n\t\tlet result = executor.execute(render_config).await?;\n\n\t\t// Extract RGBA data from result\n\t\tlet (data, img_width, img_height) = match result {\n\t\t\tTaggedValue::RenderOutput(output) => match output.data {\n\t\t\t\tRenderOutputType::Texture(image_texture) => {\n\t\t\t\t\tlet gpu_raster = Raster::<GPU>::new_gpu(image_texture.texture.as_ref().clone());\n\t\t\t\t\tlet cpu_raster: Raster<CPU> = gpu_raster.convert(Footprint::BOUNDLESS, wgpu_executor).await;\n\t\t\t\t\t// Explicitly drop texture to make sure it lives long enough\n\t\t\t\t\tstd::mem::drop(image_texture);\n\t\t\t\t\tcpu_raster.to_flat_u8()\n\t\t\t\t}\n\t\t\t\tRenderOutputType::Buffer { data, width, height } => (data, width, height),\n\t\t\t\tother => {\n\t\t\t\t\treturn Err(format!(\"Unexpected render output type for GIF frame: {:?}. Expected Texture or Buffer.\", other).into());\n\t\t\t\t}\n\t\t\t},\n\t\t\tother => return Err(format!(\"Expected RenderOutput for GIF frame, got: {:?}\", other).into()),\n\t\t};\n\n\t\t// Create image frame\n\t\tlet image = RgbaImage::from_raw(img_width, img_height, data).ok_or(\"Failed to create image from buffer\")?;\n\n\t\t// Create GIF frame with delay (delay is in 10ms units)\n\t\tlet frame = Frame::from_parts(image, 0, 0, image::Delay::from_saturating_duration(std::time::Duration::from_millis(frame_delay as u64 * 10)));\n\n\t\tencoder.encode_frame(frame)?;\n\t}\n\n\t// Clear the progress line\n\teprintln!();\n\n\tlog::info!(\"Exported GIF to: {}\", output_path.display());\n\tOk(())\n}\n"
  },
  {
    "path": "node-graph/graphene-cli/src/main.rs",
    "content": "mod export;\n\nuse clap::{Args, Parser, Subcommand};\nuse fern::colors::{Color, ColoredLevelConfig};\nuse futures::executor::block_on;\nuse graph_craft::document::*;\nuse graph_craft::graphene_compiler::Compiler;\nuse graph_craft::proto::ProtoNetwork;\nuse graph_craft::util::load_network;\nuse graph_craft::wasm_application_io::EditorPreferences;\nuse graphene_std::application_io::{ApplicationIo, NodeGraphUpdateMessage, NodeGraphUpdateSender};\nuse graphene_std::text::FontCache;\nuse graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};\nuse interpreted_executor::dynamic_executor::DynamicExecutor;\nuse interpreted_executor::util::wrap_network_in_scope;\nuse std::error::Error;\nuse std::path::PathBuf;\nuse std::sync::Arc;\n\nstruct UpdateLogger {}\n\nimpl NodeGraphUpdateSender for UpdateLogger {\n\tfn send(&self, message: NodeGraphUpdateMessage) {\n\t\tprintln!(\"{message:?}\");\n\t}\n}\n\n#[derive(Debug, Parser)]\n#[clap(name = \"graphene-cli\", version)]\npub struct App {\n\t#[clap(flatten)]\n\tglobal_opts: GlobalOpts,\n\n\t#[clap(subcommand)]\n\tcommand: Command,\n}\n\n#[derive(Debug, Subcommand)]\nenum Command {\n\t/// Help message for compile.\n\tCompile {\n\t\t/// Print proto network\n\t\t#[clap(long, short = 'p')]\n\t\tprint_proto: bool,\n\n\t\t/// Path to the .graphite document\n\t\tdocument: PathBuf,\n\t},\n\t/// Export a .graphite document to a file (SVG, PNG, JPG, or GIF).\n\tExport {\n\t\t/// Path to the .graphite document\n\t\tdocument: PathBuf,\n\n\t\t/// Output file path (extension determines format: .svg, .png, .jpg, .gif)\n\t\t#[clap(long, short = 'o')]\n\t\toutput: PathBuf,\n\n\t\t/// Optional input image resource\n\t\t#[clap(long)]\n\t\timage: Option<PathBuf>,\n\n\t\t/// Scale factor for export (default: 1.0)\n\t\t#[clap(long, default_value = \"1.0\")]\n\t\tscale: f64,\n\n\t\t/// Output width in pixels\n\t\t#[clap(long)]\n\t\twidth: Option<u32>,\n\n\t\t/// Output height in pixels\n\t\t#[clap(long)]\n\t\theight: Option<u32>,\n\n\t\t/// Transparent background for PNG exports\n\t\t#[clap(long)]\n\t\ttransparent: bool,\n\n\t\t/// Frames per second for GIF animation (default: 30)\n\t\t#[clap(long, default_value = \"30\")]\n\t\tfps: f64,\n\n\t\t/// Total number of frames for GIF animation\n\t\t#[clap(long)]\n\t\tframes: Option<u32>,\n\n\t\t/// Animation duration in seconds for GIF (takes precedence over --frames)\n\t\t#[clap(long)]\n\t\tduration: Option<f64>,\n\t},\n\tListNodeIdentifiers,\n}\n\n#[derive(Debug, Args)]\nstruct GlobalOpts {\n\t/// Verbosity level (can be specified multiple times)\n\t#[clap(long, short, global = true, action = clap::ArgAction::Count)]\n\tverbose: u8,\n}\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn Error>> {\n\tlet app = App::parse();\n\n\tlet log_level = app.global_opts.verbose;\n\n\tinit_logging(log_level);\n\n\tlet document_path = match app.command {\n\t\tCommand::Compile { ref document, .. } => document,\n\t\tCommand::Export { ref document, .. } => document,\n\t\tCommand::ListNodeIdentifiers => {\n\t\t\tlet mut nodes: Vec<_> = graphene_std::registry::NODE_METADATA.lock().unwrap().keys().cloned().collect();\n\t\t\tnodes.sort_by_key(|x| x.as_str().to_string());\n\t\t\tfor id in nodes {\n\t\t\t\tprintln!(\"{}\", id.as_str());\n\t\t\t}\n\t\t\treturn Ok(());\n\t\t}\n\t};\n\n\tlet document_string = std::fs::read_to_string(document_path).expect(\"Failed to read document\");\n\n\tlog::info!(\"Creating GPU context\");\n\tlet mut application_io = block_on(WasmApplicationIo::new_offscreen());\n\n\tif let Command::Export { image: Some(ref image_path), .. } = app.command {\n\t\tapplication_io.resources.insert(\"null\".to_string(), Arc::from(std::fs::read(image_path).expect(\"Failed to read image\")));\n\t}\n\n\t// Convert application_io to Arc first\n\tlet application_io_arc = Arc::new(application_io);\n\n\t// Clone the application_io Arc before borrowing to extract executor\n\tlet application_io_for_api = application_io_arc.clone();\n\n\t// Get reference to wgpu executor and clone device handle\n\tlet wgpu_executor_ref = application_io_arc.gpu_executor().unwrap();\n\tlet device = wgpu_executor_ref.context.device.clone();\n\n\tlet preferences = EditorPreferences {\n\t\tmax_render_region_size: EditorPreferences::default().max_render_region_size,\n\t};\n\tlet editor_api = Arc::new(WasmEditorApi {\n\t\tfont_cache: FontCache::default(),\n\t\tapplication_io: Some(application_io_for_api),\n\t\tnode_graph_message_sender: Box::new(UpdateLogger {}),\n\t\teditor_preferences: Box::new(preferences),\n\t});\n\n\tlet proto_graph = compile_graph(document_string, editor_api)?;\n\n\tmatch app.command {\n\t\tCommand::Compile { print_proto, .. } => {\n\t\t\tif print_proto {\n\t\t\t\tprintln!(\"{proto_graph}\");\n\t\t\t}\n\t\t}\n\t\tCommand::Export {\n\t\t\toutput,\n\t\t\tscale,\n\t\t\twidth,\n\t\t\theight,\n\t\t\ttransparent,\n\t\t\tfps,\n\t\t\tframes,\n\t\t\tduration,\n\t\t\t..\n\t\t} => {\n\t\t\t// Spawn thread to poll GPU device\n\t\t\tstd::thread::spawn(move || {\n\t\t\t\tloop {\n\t\t\t\t\tstd::thread::sleep(std::time::Duration::from_nanos(10));\n\t\t\t\t\tdevice.poll(wgpu::PollType::Poll).unwrap();\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Detect output file type\n\t\t\tlet file_type = export::detect_file_type(&output)?;\n\n\t\t\t// Create executor\n\t\t\tlet executor = create_executor(proto_graph)?;\n\n\t\t\tif fps <= 0. {\n\t\t\t\treturn Err(\"Fps number must be positive\".into());\n\t\t\t}\n\n\t\t\t// Perform export based on file type\n\t\t\tif file_type == export::FileType::Gif {\n\t\t\t\tlet animation = export::AnimationParams::new(fps, frames, duration);\n\t\t\t\texport::export_gif(&executor, wgpu_executor_ref, output, scale, (width, height), animation).await?;\n\t\t\t} else {\n\t\t\t\texport::export_document(&executor, wgpu_executor_ref, output, file_type, scale, (width, height), transparent).await?;\n\t\t\t}\n\t\t}\n\t\t_ => unreachable!(\"All other commands should be handled before this match statement is run\"),\n\t}\n\n\tOk(())\n}\n\nfn init_logging(log_level: u8) {\n\tlet default_level = match log_level {\n\t\t0 => log::LevelFilter::Error,\n\t\t1 => log::LevelFilter::Info,\n\t\t2 => log::LevelFilter::Debug,\n\t\t_ => log::LevelFilter::Trace,\n\t};\n\tlet colors = ColoredLevelConfig::new().debug(Color::Magenta).info(Color::Green).error(Color::Red);\n\tfern::Dispatch::new()\n\t\t.chain(std::io::stdout())\n\t\t.level_for(\"wgpu\", log::LevelFilter::Error)\n\t\t.level_for(\"naga\", log::LevelFilter::Error)\n\t\t.level_for(\"wgpu_hal\", log::LevelFilter::Error)\n\t\t.level_for(\"wgpu_core\", log::LevelFilter::Error)\n\t\t.level(default_level)\n\t\t.format(move |out, message, record| {\n\t\t\tout.finish(format_args!(\n\t\t\t\t\"[{}]{}{} {}\",\n\t\t\t\t// This will color the log level only, not the whole line. Just a touch.\n\t\t\t\tcolors.color(record.level()),\n\t\t\t\tchrono::Utc::now().format(\"[%Y-%m-%d %H:%M:%S]\"),\n\t\t\t\trecord.module_path().unwrap_or(\"\"),\n\t\t\t\tmessage\n\t\t\t))\n\t\t})\n\t\t.apply()\n\t\t.unwrap();\n}\n\n// Migrations are done in the editor which is unfortunately not available here.\n// TODO: remove this and share migrations between the editor and the CLI.\nfn fix_nodes(network: &mut NodeNetwork) {\n\tfor node in network.nodes.values_mut() {\n\t\tmatch &mut node.implementation {\n\t\t\t// Recursively fix\n\t\t\tDocumentNodeImplementation::Network(network) => fix_nodes(network),\n\t\t\t// This replicates the migration from the editor linked:\n\t\t\t// https://github.com/GraphiteEditor/Graphite/blob/d68f91ccca69e90e6d2df78d544d36cd1aaf348e/editor/src/messages/portfolio/portfolio_message_handler.rs#L535\n\t\t\t// Since the CLI doesn't have the document node definitions, a less robust method of just patching the inputs is used.\n\t\t\tDocumentNodeImplementation::ProtoNode(proto_node_identifier)\n\t\t\t\tif (proto_node_identifier.as_str().starts_with(\"graphene_core::ConstructLayerNode\") || proto_node_identifier.as_str().starts_with(\"graphene_core::AddArtboardNode\"))\n\t\t\t\t\t&& node.inputs.len() < 3 =>\n\t\t\t{\n\t\t\t\tnode.inputs.push(NodeInput::Reflection(DocumentNodeMetadata::DocumentNodePath));\n\t\t\t}\n\t\t\t_ => {}\n\t\t}\n\t}\n}\nfn compile_graph(document_string: String, editor_api: Arc<WasmEditorApi>) -> Result<ProtoNetwork, Box<dyn Error>> {\n\tlet mut network = load_network(&document_string);\n\tfix_nodes(&mut network);\n\n\tlet substitutions = preprocessor::generate_node_substitutions();\n\tpreprocessor::expand_network(&mut network, &substitutions);\n\n\tlet wrapped_network = wrap_network_in_scope(network.clone(), editor_api);\n\n\tlet compiler = Compiler {};\n\tcompiler.compile_single(wrapped_network).map_err(|x| x.into())\n}\n\nfn create_executor(proto_network: ProtoNetwork) -> Result<DynamicExecutor, Box<dyn Error>> {\n\tlet executor = block_on(DynamicExecutor::new(proto_network)).map_err(|errors| errors.iter().map(|e| format!(\"{e:?}\")).reduce(|acc, e| format!(\"{acc}\\n{e}\")).unwrap_or_default())?;\n\tOk(executor)\n}\n"
  },
  {
    "path": "node-graph/interpreted-executor/Cargo.toml",
    "content": "[package]\nname = \"interpreted-executor\"\nversion = \"0.1.0\"\nedition = \"2024\"\nlicense = \"MIT OR Apache-2.0\"\nauthors.workspace = true\n\n[features]\ndefault = []\ngpu = [\"graphene-std/gpu\", \"graphene-std/wgpu\"]\n\n[dependencies]\n# Local dependencies\ngraphene-std = { workspace = true }\ngraph-craft = { workspace = true }\ngraphene-core = { workspace = true }\nwgpu-executor = { workspace = true }\ncore-types = { workspace = true }\ndyn-any = { workspace = true }\n\n# Workspace dependencies\nlog = { workspace = true }\nglam = { workspace = true }\nfutures = { workspace = true }\nonce_cell = { workspace = true }\nserde = { workspace = true }\n\n[dev-dependencies]\n# Workspace dependencies\ngraph-craft = { workspace = true, features = [\"loading\"] }\ncriterion = { workspace = true }\niai-callgrind = { workspace = true }\npreprocessor = { workspace = true }\n\n# Benchmarks\n[[bench]]\nname = \"update_executor\"\nharness = false\n\n[[bench]]\nname = \"run_once\"\nharness = false\n\n[[bench]]\nname = \"run_cached\"\nharness = false\n\n[[bench]]\nname = \"update_executor_iai\"\nharness = false\n\n[[bench]]\nname = \"run_once_iai\"\nharness = false\n\n[[bench]]\nname = \"run_cached_iai\"\nharness = false\n"
  },
  {
    "path": "node-graph/interpreted-executor/benches/benchmark_util.rs",
    "content": "use criterion::BenchmarkGroup;\nuse criterion::measurement::Measurement;\nuse futures::executor::block_on;\nuse graph_craft::proto::ProtoNetwork;\nuse graph_craft::util::{DEMO_ART, compile, load_from_name};\nuse graphene_std::application_io::EditorApi;\nuse interpreted_executor::dynamic_executor::DynamicExecutor;\nuse interpreted_executor::util::wrap_network_in_scope;\n\npub fn setup_network(name: &str) -> (DynamicExecutor, ProtoNetwork) {\n\tlet mut network = load_from_name(name);\n\tlet editor_api = std::sync::Arc::new(EditorApi::default());\n\tlet substitutions = preprocessor::generate_node_substitutions();\n\tpreprocessor::expand_network(&mut network, &substitutions);\n\tlet network = wrap_network_in_scope(network, editor_api);\n\tlet proto_network = compile(network);\n\tlet executor = block_on(DynamicExecutor::new(proto_network.clone())).unwrap();\n\t(executor, proto_network)\n}\n\npub fn bench_for_each_demo<M: Measurement, F>(group: &mut BenchmarkGroup<M>, f: F)\nwhere\n\tF: Fn(&str, &mut BenchmarkGroup<M>),\n{\n\tfor name in DEMO_ART {\n\t\tf(name, group);\n\t}\n}\n"
  },
  {
    "path": "node-graph/interpreted-executor/benches/run_cached.rs",
    "content": "mod benchmark_util;\n\nuse benchmark_util::{bench_for_each_demo, setup_network};\nuse criterion::{Criterion, criterion_group, criterion_main};\nuse graphene_std::application_io::RenderConfig;\n\nfn subsequent_evaluations(c: &mut Criterion) {\n\tlet mut group = c.benchmark_group(\"Subsequent Evaluations\");\n\tlet context = RenderConfig::default();\n\tbench_for_each_demo(&mut group, |name, g| {\n\t\tlet (executor, _) = setup_network(name);\n\t\tg.bench_function(name, |b| {\n\t\t\tb.iter(|| futures::executor::block_on(executor.tree().eval_tagged_value(executor.output(), std::hint::black_box(context))).unwrap())\n\t\t});\n\t});\n\tgroup.finish();\n}\n\ncriterion_group!(benches, subsequent_evaluations);\ncriterion_main!(benches);\n"
  },
  {
    "path": "node-graph/interpreted-executor/benches/run_cached_iai.rs",
    "content": "mod benchmark_util;\n\nuse benchmark_util::setup_network;\nuse graphene_std::application_io::RenderConfig;\nuse iai_callgrind::{library_benchmark, library_benchmark_group, main};\nuse interpreted_executor::dynamic_executor::DynamicExecutor;\nuse std::hint::black_box;\n\nfn setup_run_cached(name: &str) -> DynamicExecutor {\n\tlet (executor, _) = setup_network(name);\n\n\t// Warm up the cache by running once\n\tlet context = RenderConfig::default();\n\tlet _ = futures::executor::block_on(executor.tree().eval_tagged_value(executor.output(), context));\n\n\texecutor\n}\n\n#[library_benchmark]\n#[benches::with_setup(args = [\"isometric-fountain\", \"painted-dreams\", \"parametric-dunescape\", \"red-dress\", \"valley-of-spires\"], setup = setup_run_cached)]\npub fn run_cached(executor: DynamicExecutor) {\n\tlet context = RenderConfig::default();\n\tblack_box(futures::executor::block_on(executor.tree().eval_tagged_value(executor.output(), black_box(context))).unwrap());\n}\n\nlibrary_benchmark_group!(name = run_cached_group; benchmarks = run_cached);\n\nmain!(library_benchmark_groups = run_cached_group);\n"
  },
  {
    "path": "node-graph/interpreted-executor/benches/run_demo_art_criterion.rs",
    "content": "use criterion::measurement::Measurement;\nuse criterion::{BenchmarkGroup, Criterion, criterion_group, criterion_main};\nuse graph_craft::graphene_compiler::Executor;\nuse graph_craft::proto::ProtoNetwork;\nuse graph_craft::util::{DEMO_ART, compile, load_from_name};\nuse graphene_std::transform::Footprint;\nuse interpreted_executor::dynamic_executor::DynamicExecutor;\n\nfn update_executor<M: Measurement>(name: &str, c: &mut BenchmarkGroup<M>) {\n\tlet network = load_from_name(name);\n\tlet proto_network = compile(network);\n\tlet empty = ProtoNetwork::default();\n\n\tlet executor = futures::executor::block_on(DynamicExecutor::new(empty)).unwrap();\n\n\tc.bench_function(name, |b| {\n\t\tb.iter_batched(\n\t\t\t|| (executor.clone(), proto_network.clone()),\n\t\t\t|(mut executor, network)| futures::executor::block_on(executor.update(std::hint::black_box(network))),\n\t\t\tcriterion::BatchSize::SmallInput,\n\t\t)\n\t});\n}\n\nfn update_executor_demo(c: &mut Criterion) {\n\tlet mut g = c.benchmark_group(\"Update Executor\");\n\tfor name in DEMO_ART {\n\t\tupdate_executor(name, &mut g);\n\t}\n}\n\nfn run_once<M: Measurement>(name: &str, c: &mut BenchmarkGroup<M>) {\n\tlet network = load_from_name(name);\n\tlet proto_network = compile(network);\n\n\tlet executor = futures::executor::block_on(DynamicExecutor::new(proto_network)).unwrap();\n\tlet footprint = Footprint::default();\n\n\tc.bench_function(name, |b| b.iter(|| futures::executor::block_on((&executor).execute(footprint))));\n}\nfn run_once_demo(c: &mut Criterion) {\n\tlet mut g = c.benchmark_group(\"Run Once no render\");\n\tfor name in DEMO_ART {\n\t\trun_once(name, &mut g);\n\t}\n}\n\ncriterion_group!(benches, update_executor_demo, run_once_demo);\ncriterion_main!(benches);\n"
  },
  {
    "path": "node-graph/interpreted-executor/benches/run_once.rs",
    "content": "mod benchmark_util;\n\nuse benchmark_util::{bench_for_each_demo, setup_network};\nuse criterion::{Criterion, criterion_group, criterion_main};\nuse graphene_std::application_io::RenderConfig;\n\nfn run_once(c: &mut Criterion) {\n\tlet mut group = c.benchmark_group(\"Run Once\");\n\tlet context = RenderConfig::default();\n\tbench_for_each_demo(&mut group, |name, g| {\n\t\tg.bench_function(name, |b| {\n\t\t\tb.iter_batched(\n\t\t\t\t|| setup_network(name),\n\t\t\t\t|(executor, _)| futures::executor::block_on(executor.tree().eval_tagged_value(executor.output(), std::hint::black_box(context))).unwrap(),\n\t\t\t\tcriterion::BatchSize::SmallInput,\n\t\t\t)\n\t\t});\n\t});\n\tgroup.finish();\n}\n\ncriterion_group!(benches, run_once);\ncriterion_main!(benches);\n"
  },
  {
    "path": "node-graph/interpreted-executor/benches/run_once_iai.rs",
    "content": "mod benchmark_util;\n\nuse benchmark_util::setup_network;\nuse graphene_std::application_io;\nuse iai_callgrind::{library_benchmark, library_benchmark_group, main};\nuse interpreted_executor::dynamic_executor::DynamicExecutor;\nuse std::hint::black_box;\n\nfn setup_run_once(name: &str) -> DynamicExecutor {\n\tlet (executor, _) = setup_network(name);\n\texecutor\n}\n\n#[library_benchmark]\n#[benches::with_setup(args = [\"isometric-fountain\", \"painted-dreams\", \"procedural-string-lights\", \"parametric-dunescape\", \"red-dress\", \"valley-of-spires\"], setup = setup_run_once)]\npub fn run_once(executor: DynamicExecutor) {\n\tlet context = application_io::RenderConfig::default();\n\tblack_box(futures::executor::block_on(executor.tree().eval_tagged_value(executor.output(), black_box(context))).unwrap());\n}\n\nlibrary_benchmark_group!(name = run_once_group; benchmarks = run_once);\n\nmain!(library_benchmark_groups = run_once_group);\n"
  },
  {
    "path": "node-graph/interpreted-executor/benches/update_executor.rs",
    "content": "mod benchmark_util;\n\nuse benchmark_util::{bench_for_each_demo, setup_network};\nuse criterion::{Criterion, criterion_group, criterion_main};\nuse graph_craft::proto::ProtoNetwork;\nuse interpreted_executor::dynamic_executor::DynamicExecutor;\n\nfn update_executor(c: &mut Criterion) {\n\tlet mut group = c.benchmark_group(\"Update Executor\");\n\tbench_for_each_demo(&mut group, |name, g| {\n\t\tg.bench_function(name, |b| {\n\t\t\tb.iter_batched(\n\t\t\t\t|| {\n\t\t\t\t\tlet (_, proto_network) = setup_network(name);\n\t\t\t\t\tlet empty = ProtoNetwork::default();\n\t\t\t\t\tlet executor = futures::executor::block_on(DynamicExecutor::new(empty)).unwrap();\n\t\t\t\t\t(executor, proto_network)\n\t\t\t\t},\n\t\t\t\t|(mut executor, network)| futures::executor::block_on(executor.update(std::hint::black_box(network))),\n\t\t\t\tcriterion::BatchSize::SmallInput,\n\t\t\t)\n\t\t});\n\t});\n\tgroup.finish();\n}\n\ncriterion_group!(benches, update_executor);\ncriterion_main!(benches);\n"
  },
  {
    "path": "node-graph/interpreted-executor/benches/update_executor_iai.rs",
    "content": "mod benchmark_util;\n\nuse benchmark_util::setup_network;\nuse graph_craft::proto::ProtoNetwork;\nuse iai_callgrind::{library_benchmark, library_benchmark_group, main};\nuse interpreted_executor::dynamic_executor::DynamicExecutor;\nuse std::hint::black_box;\n\nfn setup_update_executor(name: &str) -> (DynamicExecutor, ProtoNetwork) {\n\tlet (_, proto_network) = setup_network(name);\n\tlet empty = ProtoNetwork::default();\n\tlet executor = futures::executor::block_on(DynamicExecutor::new(empty)).unwrap();\n\t(executor, proto_network)\n}\n\n#[library_benchmark]\n#[benches::with_setup(args = [\"isometric-fountain\", \"painted-dreams\", \"procedural-string-lights\", \"parametric-dunescape\", \"red-dress\", \"valley-of-spires\"], setup = setup_update_executor)]\npub fn update_executor(setup: (DynamicExecutor, ProtoNetwork)) {\n\tlet (mut executor, network) = setup;\n\tlet _ = black_box(futures::executor::block_on(executor.update(black_box(network))));\n}\n\nlibrary_benchmark_group!(name = update_group; benchmarks = update_executor);\n\nmain!(library_benchmark_groups = update_group);\n"
  },
  {
    "path": "node-graph/interpreted-executor/src/dynamic_executor.rs",
    "content": "use crate::node_registry;\nuse dyn_any::StaticType;\nuse graph_craft::Type;\nuse graph_craft::document::NodeId;\nuse graph_craft::document::value::{TaggedValue, UpcastAsRefNode, UpcastNode};\nuse graph_craft::graphene_compiler::Executor;\nuse graph_craft::proto::{ConstructionArgs, GraphError, LocalFuture, NodeContainer, ProtoNetwork, ProtoNode, SharedNodeContainer, TypeErasedBox, TypingContext};\nuse graph_craft::proto::{GraphErrorType, GraphErrors};\nuse std::collections::{HashMap, HashSet};\nuse std::error::Error;\nuse std::sync::Arc;\n\n/// An executor of a node graph that does not require an online compilation server, and instead uses `Box<dyn ...>`.\n#[derive(Clone)]\npub struct DynamicExecutor {\n\toutput: NodeId,\n\t/// Stores all of the dynamic node structs.\n\ttree: BorrowTree,\n\t/// Stores the types of the proto nodes.\n\ttyping_context: TypingContext,\n\t// This allows us to keep the nodes around for one more frame which is used for introspection\n\torphaned_nodes: HashSet<NodeId>,\n}\n\nimpl Default for DynamicExecutor {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\toutput: Default::default(),\n\t\t\ttree: Default::default(),\n\t\t\ttyping_context: TypingContext::new(&node_registry::NODE_REGISTRY),\n\t\t\torphaned_nodes: HashSet::new(),\n\t\t}\n\t}\n}\n\n#[derive(PartialEq, Clone, Debug, Default, serde::Serialize, serde::Deserialize)]\npub struct NodeTypes {\n\tpub inputs: Vec<Type>,\n\tpub output: Type,\n}\n\ntype Path = Box<[NodeId]>;\n\n#[derive(PartialEq, Clone, Debug, Default, serde::Serialize, serde::Deserialize)]\npub struct ResolvedDocumentNodeTypesDelta {\n\tpub add: Vec<(Path, NodeTypes)>,\n\tpub remove: Vec<Path>,\n}\n\nimpl DynamicExecutor {\n\tpub async fn new(proto_network: ProtoNetwork) -> Result<Self, GraphErrors> {\n\t\tlet mut typing_context = TypingContext::new(&node_registry::NODE_REGISTRY);\n\t\ttyping_context.update(&proto_network)?;\n\t\tlet output = proto_network.output;\n\t\tlet tree = BorrowTree::new(proto_network, &typing_context).await?;\n\n\t\tOk(Self {\n\t\t\ttree,\n\t\t\toutput,\n\t\t\ttyping_context,\n\t\t\torphaned_nodes: HashSet::new(),\n\t\t})\n\t}\n\n\t/// Updates the existing [`BorrowTree`] to reflect the new [`ProtoNetwork`], reusing nodes where possible.\n\t#[cfg_attr(debug_assertions, inline(never))]\n\tpub async fn update(&mut self, proto_network: ProtoNetwork) -> Result<ResolvedDocumentNodeTypesDelta, (ResolvedDocumentNodeTypesDelta, GraphErrors)> {\n\t\tself.output = proto_network.output;\n\t\tself.typing_context.update(&proto_network).map_err(|e| {\n\t\t\t// If there is an error then get types that have been resolved before the error\n\t\t\tlet add = proto_network\n\t\t\t\t.nodes\n\t\t\t\t.iter()\n\t\t\t\t.filter_map(|(id, node)| node.original_location.path.as_ref().map(|path| (path.clone().into_boxed_slice(), self.typing_context.infer(*id, node))))\n\t\t\t\t.take_while(|(_, r)| r.is_ok())\n\t\t\t\t.map(|(path, r)| {\n\t\t\t\t\tlet r = r.unwrap();\n\t\t\t\t\t(\n\t\t\t\t\t\tpath,\n\t\t\t\t\t\tNodeTypes {\n\t\t\t\t\t\t\tinputs: r.inputs,\n\t\t\t\t\t\t\toutput: r.return_value,\n\t\t\t\t\t\t},\n\t\t\t\t\t)\n\t\t\t\t})\n\t\t\t\t.collect::<Vec<_>>();\n\t\t\t(ResolvedDocumentNodeTypesDelta { add, remove: Vec::new() }, e)\n\t\t})?;\n\n\t\tlet (add, orphaned) = self\n\t\t\t.tree\n\t\t\t.update(proto_network, &self.typing_context)\n\t\t\t.await\n\t\t\t.map_err(|e| (ResolvedDocumentNodeTypesDelta::default(), e))?;\n\t\tlet old_to_remove = core::mem::replace(&mut self.orphaned_nodes, orphaned);\n\t\tlet mut remove = Vec::with_capacity(old_to_remove.len() - self.orphaned_nodes.len().min(old_to_remove.len()));\n\t\tfor node_id in old_to_remove {\n\t\t\tif self.orphaned_nodes.contains(&node_id) {\n\t\t\t\tlet path = self.tree.free_node(node_id);\n\t\t\t\tself.typing_context.remove_inference(node_id);\n\t\t\t\tif let Some(path) = path {\n\t\t\t\t\tremove.push(path);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tlet add = self.document_node_types(add.into_iter()).collect();\n\t\tOk(ResolvedDocumentNodeTypesDelta { add, remove })\n\t}\n\n\t/// Calls the `Node::serialize` for that specific node, returning for example the cached value for a monitor node. The node path must match the document node path.\n\tpub fn introspect(&self, node_path: &[NodeId]) -> Result<Arc<dyn std::any::Any + Send + Sync + 'static>, IntrospectError> {\n\t\tself.tree.introspect(node_path)\n\t}\n\n\tpub fn input_type(&self) -> Option<Type> {\n\t\tself.typing_context.type_of(self.output).map(|node_io| node_io.call_argument.clone())\n\t}\n\n\tpub fn tree(&self) -> &BorrowTree {\n\t\t&self.tree\n\t}\n\n\tpub fn output(&self) -> NodeId {\n\t\tself.output\n\t}\n\n\tpub fn output_type(&self) -> Option<Type> {\n\t\tself.typing_context.type_of(self.output).map(|node_io| node_io.return_value.clone())\n\t}\n\n\tpub fn document_node_types<'a>(&'a self, nodes: impl Iterator<Item = Path> + 'a) -> impl Iterator<Item = (Path, NodeTypes)> + 'a {\n\t\tnodes.flat_map(|id| self.tree.source_map().get(&id).map(|(_, b)| (id, b.clone())))\n\t\t// TODO: https://github.com/GraphiteEditor/Graphite/issues/1767\n\t\t// TODO: Non exposed inputs are not added to the inputs_source_map, so they are not included in the resolved_document_node_types. The type is still available in the typing_context. This only affects the UI-only \"Import\" node.\n\t}\n}\n\nimpl<I> Executor<I, TaggedValue> for &DynamicExecutor\nwhere\n\tI: StaticType + 'static + Send + Sync + std::panic::UnwindSafe,\n{\n\tfn execute(&self, input: I) -> LocalFuture<'_, Result<TaggedValue, Box<dyn Error>>> {\n\t\tBox::pin(async move {\n\t\t\tuse futures::FutureExt;\n\n\t\t\tlet result = self.tree.eval_tagged_value(self.output, input);\n\t\t\tlet wrapped_result = std::panic::AssertUnwindSafe(result).catch_unwind().await;\n\n\t\t\tmatch wrapped_result {\n\t\t\t\tOk(result) => result.map_err(|e| e.into()),\n\t\t\t\tErr(e) => {\n\t\t\t\t\tBox::leak(e);\n\t\t\t\t\tErr(\"Node graph execution panicked\".into())\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\npub struct InputMapping {}\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\npub enum IntrospectError {\n\tPathNotFound(Vec<NodeId>),\n\tProtoNodeNotFound(NodeId),\n\tNoData,\n\tRuntimeNotReady,\n}\n\nimpl std::fmt::Display for IntrospectError {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tmatch self {\n\t\t\tIntrospectError::PathNotFound(path) => write!(f, \"Path not found: {path:?}\"),\n\t\t\tIntrospectError::ProtoNodeNotFound(id) => write!(f, \"ProtoNode not found: {id:?}\"),\n\t\t\tIntrospectError::NoData => write!(f, \"No data found for this node\"),\n\t\t\tIntrospectError::RuntimeNotReady => write!(f, \"Node runtime is not ready\"),\n\t\t}\n\t}\n}\n\n/// A store of dynamically typed nodes and their associated source map.\n///\n/// [`BorrowTree`] maintains two main data structures:\n/// 1. A map of [`NodeId`]s to their corresponding nodes and paths.\n/// 2. A source map that links document paths to node IDs and their types.\n///\n/// This structure is central to managing the graph of nodes in the interpreter,\n/// allowing for efficient access and manipulation of nodes based on their IDs or paths.\n///\n/// # Fields\n///\n/// * `nodes`: A [`HashMap`] of [`NodeId`]s to tuples of [`SharedNodeContainer`] and [`Path`].\n///   This stores the actual node instances and their associated paths.\n///\n/// * `source_map`: A [`HashMap`] from [`Path`] to tuples of [`NodeId`] and [`NodeTypes`].\n///   This maps document paths to node IDs and their associated type information.\n///\n/// A store of the dynamically typed nodes and also the source map.\n#[derive(Default, Clone)]\npub struct BorrowTree {\n\t/// A hashmap of node IDs and dynamically typed nodes.\n\tnodes: HashMap<NodeId, (SharedNodeContainer, Path)>,\n\t/// A hashmap from the document path to the proto node ID.\n\tsource_map: HashMap<Path, (NodeId, NodeTypes)>,\n}\n\nimpl BorrowTree {\n\tpub async fn new(proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result<BorrowTree, GraphErrors> {\n\t\tlet mut nodes = BorrowTree::default();\n\t\tfor (id, node) in proto_network.nodes {\n\t\t\tnodes.push_node(id, node, typing_context).await?\n\t\t}\n\t\tOk(nodes)\n\t}\n\n\t/// Pushes new nodes into the tree and return orphaned nodes\n\tpub async fn update(&mut self, proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result<(Vec<Path>, HashSet<NodeId>), GraphErrors> {\n\t\tlet mut old_nodes: HashSet<_> = self.nodes.keys().copied().collect();\n\t\tlet mut new_nodes: Vec<_> = Vec::new();\n\t\t// TODO: Problem: When an identity node is connected directly to an export the first input to identity node is not added to the proto network, while the second input is. This means the primary input does not have a type.\n\t\tfor (id, node) in proto_network.nodes {\n\t\t\tif !self.nodes.contains_key(&id) {\n\t\t\t\tnew_nodes.push(node.original_location.path.clone().unwrap_or_default().into());\n\t\t\t\tself.push_node(id, node, typing_context).await?;\n\t\t\t} else if self.update_source_map(id, typing_context, &node) {\n\t\t\t\tnew_nodes.push(node.original_location.path.clone().unwrap_or_default().into());\n\t\t\t}\n\t\t\told_nodes.remove(&id);\n\t\t}\n\t\tOk((new_nodes, old_nodes))\n\t}\n\n\tfn node_deps(&self, nodes: &[NodeId]) -> Vec<SharedNodeContainer> {\n\t\tnodes.iter().map(|node| self.nodes.get(node).unwrap().0.clone()).collect()\n\t}\n\n\tfn store_node(&mut self, node: SharedNodeContainer, id: NodeId, path: Path) {\n\t\tself.nodes.insert(id, (node, path));\n\t}\n\n\t/// Calls the `Node::serialize` for that specific node, returning for example the cached value for a monitor node. The node path must match the document node path.\n\tpub fn introspect(&self, node_path: &[NodeId]) -> Result<Arc<dyn std::any::Any + Send + Sync + 'static>, IntrospectError> {\n\t\tlet (id, _) = self.source_map.get(node_path).ok_or_else(|| IntrospectError::PathNotFound(node_path.to_vec()))?;\n\t\tlet (node, _path) = self.nodes.get(id).ok_or(IntrospectError::ProtoNodeNotFound(*id))?;\n\t\tnode.serialize().ok_or(IntrospectError::NoData)\n\t}\n\n\tpub fn get(&self, id: NodeId) -> Option<SharedNodeContainer> {\n\t\tself.nodes.get(&id).map(|(node, _)| node.clone())\n\t}\n\n\t/// Evaluate the output node of the [`BorrowTree`].\n\tpub async fn eval<'i, I, O>(&'i self, id: NodeId, input: I) -> Option<O>\n\twhere\n\t\tI: StaticType + 'i + Send + Sync,\n\t\tO: StaticType + 'i,\n\t{\n\t\tlet (node, _path) = self.nodes.get(&id).cloned()?;\n\t\tlet output = node.eval(Box::new(input));\n\t\tdyn_any::downcast::<O>(output.await).ok().map(|o| *o)\n\t}\n\t/// Evaluate the output node of the [`BorrowTree`] and cast it to a tagged value.\n\t/// This ensures that no borrowed data can escape the node graph.\n\tpub async fn eval_tagged_value<I>(&self, id: NodeId, input: I) -> Result<TaggedValue, String>\n\twhere\n\t\tI: StaticType + 'static + Send + Sync,\n\t{\n\t\tlet (node, _path) = self.nodes.get(&id).cloned().ok_or(\"Output node not found in executor\")?;\n\t\tlet output = node.eval(Box::new(input));\n\t\tTaggedValue::try_from_any(output.await)\n\t}\n\n\t/// Removes a node from the [`BorrowTree`] and returns its associated path.\n\t///\n\t/// This method removes the specified node from both the `nodes` HashMap and,\n\t/// if applicable, the `source_map` HashMap.\n\t///\n\t/// # Arguments\n\t///\n\t/// * `self` - Mutable reference to the [`BorrowTree`].\n\t/// * `id` - The `NodeId` of the node to be removed.\n\t///\n\t/// # Returns\n\t///\n\t/// [`Option<Path>`] - The path associated with the removed node, or `None` if the node wasn't found.\n\t///\n\t/// # Example\n\t///\n\t/// ```rust\n\t/// use std::collections::HashMap;\n\t/// use graph_craft::document::*;\n\t/// use graph_craft::proto::*;\n\t/// use interpreted_executor::dynamic_executor::BorrowTree;\n\t/// use interpreted_executor::node_registry;\n\t///\n\t///\n\t/// async fn example() -> Result<(), GraphErrors> {\n\t///     let (proto_network, node_id, proto_node) = ProtoNetwork::example();\n\t///     let typing_context = TypingContext::new(&node_registry::NODE_REGISTRY);\n\t///     let mut borrow_tree = BorrowTree::new(proto_network, &typing_context).await?;\n\t///\n\t///     // Assert that the node exists in the BorrowTree\n\t///     assert!(borrow_tree.get(node_id).is_some(), \"Node should exist before removal\");\n\t///\n\t///     // Remove the node\n\t///     let removed_path = borrow_tree.free_node(node_id);\n\t///\n\t///     // Assert that the node was successfully removed\n\t///     assert!(removed_path.is_some(), \"Node removal should return a path\");\n\t///     assert!(borrow_tree.get(node_id).is_none(), \"Node should not exist after removal\");\n\t///\n\t///     // Try to remove the same node again\n\t///     let second_removal = borrow_tree.free_node(node_id);\n\t///\n\t///     assert_eq!(second_removal, None, \"Second removal should return None\");\n\t///\n\t///     println!(\"All assertions passed. free_node function works as expected.\");\n\t///\n\t///     Ok(())\n\t/// }\n\t/// ```\n\t///\n\t/// # Notes\n\t///\n\t/// - Removes the node from `nodes` HashMap.\n\t/// - If the node is the primary node for its path in the `source_map`, it's also removed from there.\n\t/// - Returns `None` if the node is not found in the `nodes` HashMap.\n\tpub fn free_node(&mut self, id: NodeId) -> Option<Path> {\n\t\tlet (_, path) = self.nodes.remove(&id)?;\n\t\tif self.source_map.get(&path)?.0 == id {\n\t\t\tself.source_map.remove(&path);\n\t\t\treturn Some(path);\n\t\t}\n\t\tNone\n\t}\n\n\t/// Updates the source map for a given node in the [`BorrowTree`].\n\t///\n\t/// This method updates or inserts an entry in the `source_map` HashMap for the specified node,\n\t/// using type information from the provided [`TypingContext`] and [`ProtoNode`].\n\t///\n\t/// # Arguments\n\t///\n\t/// * `self` - Mutable reference to the [`BorrowTree`].\n\t/// * `id` - The `NodeId` of the node to update in the source map.\n\t/// * `typing_context` - A reference to the [`TypingContext`] containing type information.\n\t/// * `proto_node` - A reference to the [`ProtoNode`] containing original location information.\n\t///\n\t/// # Returns\n\t///\n\t/// `bool` - `true` if a new entry was inserted, `false` if an existing entry was updated.\n\t///\n\t/// # Notes\n\t///\n\t/// - Updates or inserts an entry in the `source_map` HashMap.\n\t/// - Uses the `ProtoNode`'s original location path as the key for the source map.\n\t/// - Collects input types from both the main input and parameters.\n\t/// - Returns `false` and logs a warning if the node's type information is not found in the typing context.\n\tfn update_source_map(&mut self, id: NodeId, typing_context: &TypingContext, proto_node: &ProtoNode) -> bool {\n\t\tlet Some(node_io) = typing_context.type_of(id) else {\n\t\t\tlog::warn!(\"did not find type\");\n\t\t\treturn false;\n\t\t};\n\t\tlet inputs = [&node_io.call_argument].into_iter().chain(&node_io.inputs).cloned().collect();\n\n\t\tlet node_path = &proto_node.original_location.path.as_ref().unwrap_or(const { &vec![] });\n\n\t\tlet entry = self.source_map.entry(node_path.to_vec().into()).or_default();\n\n\t\tlet update = (\n\t\t\tid,\n\t\t\tNodeTypes {\n\t\t\t\tinputs,\n\t\t\t\toutput: node_io.return_value.clone(),\n\t\t\t},\n\t\t);\n\t\tlet modified = *entry != update;\n\t\t*entry = update;\n\t\tmodified\n\t}\n\n\t/// Inserts a new node into the [`BorrowTree`], calling the constructor function from `node_registry.rs`.\n\t///\n\t/// This method creates a new node container based on the provided `ProtoNode`, updates the source map,\n\t/// and stores the node container in the `BorrowTree`.\n\t///\n\t///\n\t/// # Notes\n\t///\n\t/// - Updates the source map using [`update_source_map`](BorrowTree::update_source_map) before inserting the node.\n\t/// - Handles different types of construction arguments:\n\t///   - `Value`: Creates a node from a `TaggedValue`, with special handling for `EditorApi` values.\n\t///   - `Inline`: Currently unimplemented. Only used for `rust-gpu` support.\n\t///   - `Nodes`: Constructs a node using other nodes as dependencies.\n\t/// - Uses the constructor function from the `typing_context` for `Nodes` construction arguments.\n\t/// - Returns an error if no constructor is found for the given node ID.\n\tasync fn push_node(&mut self, id: NodeId, proto_node: ProtoNode, typing_context: &TypingContext) -> Result<(), GraphErrors> {\n\t\tself.update_source_map(id, typing_context, &proto_node);\n\t\tlet path = proto_node.original_location.path.clone().unwrap_or_default();\n\n\t\tmatch &proto_node.construction_args {\n\t\t\tConstructionArgs::Value(value) => {\n\t\t\t\tlet node = if let TaggedValue::EditorApi(api) = &**value {\n\t\t\t\t\tlet editor_api = UpcastAsRefNode::new(api.clone());\n\t\t\t\t\tlet node = Box::new(editor_api) as TypeErasedBox<'_>;\n\t\t\t\t\tNodeContainer::new(node)\n\t\t\t\t} else {\n\t\t\t\t\tlet upcasted = UpcastNode::new(value.to_owned());\n\t\t\t\t\tlet node = Box::new(upcasted) as TypeErasedBox<'_>;\n\t\t\t\t\tNodeContainer::new(node)\n\t\t\t\t};\n\t\t\t\tself.store_node(node, id, path.into());\n\t\t\t}\n\t\t\tConstructionArgs::Inline(_) => unimplemented!(\"Inline nodes are not supported yet\"),\n\t\t\tConstructionArgs::Nodes(ids) => {\n\t\t\t\tlet ids = ids.to_vec();\n\t\t\t\tlet construction_nodes = self.node_deps(&ids);\n\t\t\t\tlet constructor = typing_context.constructor(id).ok_or_else(|| vec![GraphError::new(&proto_node, GraphErrorType::NoConstructor)])?;\n\t\t\t\tlet node = constructor(construction_nodes).await;\n\t\t\t\tlet node = NodeContainer::new(node);\n\t\t\t\tself.store_node(node, id, path.into());\n\t\t\t}\n\t\t};\n\t\tOk(())\n\t}\n\n\t/// Returns the source map of the borrow tree\n\tpub fn source_map(&self) -> &HashMap<Path, (NodeId, NodeTypes)> {\n\t\t&self.source_map\n\t}\n}\n\n#[cfg(test)]\nmod test {\n\tuse super::*;\n\tuse graph_craft::document::value::TaggedValue;\n\n\t#[test]\n\tfn push_node_sync() {\n\t\tlet mut tree = BorrowTree::default();\n\t\tlet val_1_protonode = ProtoNode::value(ConstructionArgs::Value(TaggedValue::U32(2u32).into()), vec![]);\n\t\tlet context = TypingContext::default();\n\t\tlet future = tree.push_node(NodeId(0), val_1_protonode, &context);\n\t\tfutures::executor::block_on(future).unwrap();\n\t\tlet _node = tree.get(NodeId(0)).unwrap();\n\t\tlet result = futures::executor::block_on(tree.eval(NodeId(0), ()));\n\t\tassert_eq!(result, Some(2u32));\n\t}\n}\n"
  },
  {
    "path": "node-graph/interpreted-executor/src/lib.rs",
    "content": "pub mod dynamic_executor;\npub mod node_registry;\npub mod util;\n\n#[cfg(test)]\nmod tests {\n\tuse core_types::*;\n\tuse futures::executor::block_on;\n\tuse graphene_core::ops::identity;\n\n\t#[test]\n\tfn double_number() {\n\t\tuse graph_craft::document::*;\n\t\tuse graph_craft::*;\n\n\t\tlet network = NodeNetwork {\n\t\t\texports: vec![NodeInput::node(NodeId(1), 0)],\n\t\t\tnodes: [\n\t\t\t\t// Simple identity node taking a number as input from outside the graph\n\t\t\t\t(\n\t\t\t\t\tNodeId(0),\n\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\tinputs: vec![],\n\t\t\t\t\t\tcall_argument: concrete!(u32),\n\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(identity::IDENTIFIER),\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t\t// An add node adding the result of the id node to its self\n\t\t\t\t(\n\t\t\t\t\tNodeId(1),\n\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\tinputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(0), 0)],\n\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(graphene_std::math_nodes::add::IDENTIFIER),\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t]\n\t\t\t.into_iter()\n\t\t\t.collect(),\n\t\t\t..Default::default()\n\t\t};\n\n\t\tuse crate::dynamic_executor::DynamicExecutor;\n\t\tuse graph_craft::graphene_compiler::Compiler;\n\n\t\tlet compiler = Compiler {};\n\t\tlet protograph = compiler.compile_single(network).expect(\"Graph should be generated\");\n\n\t\tlet _exec = block_on(DynamicExecutor::new(protograph)).map(|_e| panic!(\"The network should not type check \")).unwrap_err();\n\t}\n}\n"
  },
  {
    "path": "node-graph/interpreted-executor/src/node_registry.rs",
    "content": "use dyn_any::StaticType;\nuse glam::{DAffine2, DVec2, IVec2};\nuse graph_craft::document::DocumentNode;\nuse graph_craft::document::value::RenderOutput;\nuse graph_craft::proto::{NodeConstructor, TypeErasedBox};\nuse graphene_std::any::DynAnyNode;\nuse graphene_std::application_io::{ImageTexture, SurfaceFrame};\nuse graphene_std::brush::brush_cache::BrushCache;\nuse graphene_std::brush::brush_stroke::BrushStroke;\nuse graphene_std::gradient::GradientStops;\n#[cfg(feature = \"gpu\")]\nuse graphene_std::raster::GPU;\nuse graphene_std::raster::color::Color;\nuse graphene_std::raster::*;\nuse graphene_std::raster::{CPU, Raster};\nuse graphene_std::render_node::RenderIntermediate;\nuse graphene_std::table::Table;\nuse graphene_std::transform::Footprint;\nuse graphene_std::uuid::NodeId;\nuse graphene_std::vector::Vector;\nuse graphene_std::wasm_application_io::WasmEditorApi;\n#[cfg(feature = \"gpu\")]\nuse graphene_std::wasm_application_io::WasmSurfaceHandle;\nuse graphene_std::{Artboard, Context, Graphic, NodeIO, NodeIOTypes, ProtoNodeIdentifier, concrete, fn_type_fut, future};\nuse node_registry_macros::{async_node, convert_node, into_node};\nuse std::collections::HashMap;\n#[cfg(feature = \"gpu\")]\nuse std::sync::Arc;\n#[cfg(feature = \"gpu\")]\nuse wgpu_executor::WgpuExecutor;\nuse wgpu_executor::{WgpuSurface, WindowHandle};\n\n// TODO: turn into hashmap\nfn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> {\n\tlet mut node_types: Vec<(ProtoNodeIdentifier, NodeConstructor, NodeIOTypes)> = vec![\n\t\t// ==========\n\t\t// INTO NODES\n\t\t// ==========\n\t\tinto_node!(from: Table<Graphic>, to: Table<Graphic>),\n\t\tinto_node!(from: Table<Vector>, to: Table<Vector>),\n\t\tinto_node!(from: Table<Raster<CPU>>, to: Table<Raster<CPU>>),\n\t\t#[cfg(feature = \"gpu\")]\n\t\tinto_node!(from: Table<Raster<GPU>>, to: Table<Raster<GPU>>),\n\t\tconvert_node!(from: Table<Vector>, to: Table<Graphic>),\n\t\tconvert_node!(from: Table<Raster<CPU>>, to: Table<Graphic>),\n\t\t#[cfg(feature = \"gpu\")]\n\t\tconvert_node!(from: Table<Raster<GPU>>, to: Table<Graphic>),\n\t\t// into_node!(from: Table<Raster<CPU>>, to: Table<Raster<SRGBA8>>),\n\t\t#[cfg(feature = \"gpu\")]\n\t\tinto_node!(from: &WasmEditorApi, to: &WgpuExecutor),\n\t\tconvert_node!(from: DVec2, to: DVec2),\n\t\tconvert_node!(from: String, to: String),\n\t\tconvert_node!(from: bool, to: String),\n\t\tconvert_node!(from: DVec2, to: String),\n\t\tconvert_node!(from: IVec2, to: String),\n\t\tconvert_node!(from: DAffine2, to: String),\n\t\t#[cfg(feature = \"gpu\")]\n\t\tconvert_node!(from: Table<Raster<CPU>>, to: Table<Raster<CPU>>, converter: &WgpuExecutor),\n\t\t#[cfg(feature = \"gpu\")]\n\t\tconvert_node!(from: Table<Raster<CPU>>, to: Table<Raster<GPU>>, converter: &WgpuExecutor),\n\t\t#[cfg(feature = \"gpu\")]\n\t\tconvert_node!(from: Table<Raster<GPU>>, to: Table<Raster<GPU>>, converter: &WgpuExecutor),\n\t\t#[cfg(feature = \"gpu\")]\n\t\tconvert_node!(from: Table<Raster<GPU>>, to: Table<Raster<CPU>>, converter: &WgpuExecutor),\n\t\t// =============\n\t\t// MONITOR NODES\n\t\t// =============\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => ()]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Artboard>]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Graphic>]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Vector>]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Raster<CPU>>]),\n\t\t#[cfg(feature = \"gpu\")]\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Raster<GPU>>]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Color>]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<GradientStops>]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => String]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => IVec2]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => DVec2]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => DAffine2]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => bool]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => f64]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => u32]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => u64]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec<f64>]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => BlendMode]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => ImageTexture]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::transform::ReferencePoint]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::BooleanOperation]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Fill]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeCap]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeJoin]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::style::PaintOrder]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeAlign]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Stroke]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Gradient]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec<graphene_std::uuid::NodeId>]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Box<graphene_std::vector::VectorModification>]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::CentroidType]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::PointSpacingType]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Option<f64>]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec<DVec2>]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec<String>]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => [f64; 4]]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec<NodeId>]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Graphic]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::text::Font]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec<BrushStroke>]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => BrushCache]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => DocumentNode]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::raster::curve::Curve]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::transform::Footprint]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::blending::BlendMode]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::raster::adjustments::LuminanceCalculation]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::extract_xy::XY]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::raster::adjustments::RedGreenBlue]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::raster::adjustments::RedGreenBlueAlpha]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::animation::RealTimeMode]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::raster::adjustments::NoiseType]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::raster::adjustments::FractalType]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::raster::adjustments::CellularDistanceFunction]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::raster::adjustments::CellularReturnType]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::raster::adjustments::DomainWarpType]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::raster::adjustments::RelativeAbsolute]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::raster::adjustments::SelectiveColorChoice]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::GridType]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::ArcType]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::RowsOrColumns]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::MergeByDistanceAlgorithm]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::ExtrudeJoiningAlgorithm]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::PointSpacingType]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::style::FillType]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::style::GradientType]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::transform::ReferencePoint]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::CentroidType]),\n\t\tasync_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::text::TextAlign]),\n\t\t// Context nullification\n\t\t#[cfg(feature = \"gpu\")]\n\t\tasync_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => &WasmEditorApi, Context => graphene_std::ContextFeatures]),\n\t\t#[cfg(feature = \"gpu\")]\n\t\tasync_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => Arc<WasmSurfaceHandle>, Context => graphene_std::ContextFeatures]),\n\t\tasync_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => RenderIntermediate, Context => graphene_std::ContextFeatures]),\n\t\tasync_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => RenderOutput, Context => graphene_std::ContextFeatures]),\n\t\tasync_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => WgpuSurface, Context => graphene_std::ContextFeatures]),\n\t\tasync_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => Option<WgpuSurface>, Context => graphene_std::ContextFeatures]),\n\t\tasync_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => WindowHandle, Context => graphene_std::ContextFeatures]),\n\t\t// ==========\n\t\t// MEMO NODES\n\t\t// ==========\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => ()]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => bool]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Artboard>]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Graphic>]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Vector>]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Raster<CPU>>]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Color>]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Image<Color>]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<GradientStops>]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<DVec2>]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<NodeId>]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<f64>]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<f32>]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<String>]),\n\t\t#[cfg(feature = \"gpu\")]\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Arc<WasmSurfaceHandle>]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => WindowHandle]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option<WgpuSurface>]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => SurfaceFrame]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => f64]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => f32]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => u32]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => u64]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => DVec2]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => String]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => DAffine2]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Footprint]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RenderOutput]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => &WasmEditorApi]),\n\t\t#[cfg(feature = \"gpu\")]\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => WgpuSurface]),\n\t\t#[cfg(feature = \"gpu\")]\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Raster<GPU>>]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option<f64>]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option<Color>]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => [f64; 4]]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Graphic]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => glam::f32::Vec2]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => glam::f32::Affine2]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Stroke]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Gradient]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::text::Font]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<BrushStroke>]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => BrushCache]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => DocumentNode]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::ContextFeatures]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::curve::Curve]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::transform::Footprint]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Box<graphene_std::vector::VectorModification>]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Fill]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::blending::BlendMode]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::LuminanceCalculation]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::QRCodeErrorCorrectionLevel]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::extract_xy::XY]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::RedGreenBlue]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::RedGreenBlueAlpha]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::animation::RealTimeMode]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::NoiseType]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::FractalType]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::CellularDistanceFunction]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::CellularReturnType]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::DomainWarpType]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::RelativeAbsolute]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::SelectiveColorChoice]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::GridType]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::ArcType]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::RowsOrColumns]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::MergeByDistanceAlgorithm]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::ExtrudeJoiningAlgorithm]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::PointSpacingType]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeCap]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeJoin]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeAlign]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::PaintOrder]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::FillType]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::GradientType]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::transform::ReferencePoint]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::CentroidType]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::BooleanOperation]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::text::TextAlign]),\n\t\tasync_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RenderIntermediate]),\n\t];\n\t// =============\n\t// CONVERT NODES\n\t// =============\n\tnode_types.extend(\n\t\t[\n\t\t\tconvert_node!(from: f32, to: numbers),\n\t\t\tconvert_node!(from: f64, to: numbers),\n\t\t\tconvert_node!(from: i8, to: numbers),\n\t\t\tconvert_node!(from: u8, to: numbers),\n\t\t\tconvert_node!(from: u16, to: numbers),\n\t\t\tconvert_node!(from: i16, to: numbers),\n\t\t\tconvert_node!(from: i32, to: numbers),\n\t\t\tconvert_node!(from: u32, to: numbers),\n\t\t\tconvert_node!(from: i64, to: numbers),\n\t\t\tconvert_node!(from: u64, to: numbers),\n\t\t\tconvert_node!(from: i128, to: numbers),\n\t\t\tconvert_node!(from: u128, to: numbers),\n\t\t\tconvert_node!(from: isize, to: numbers),\n\t\t\tconvert_node!(from: usize, to: numbers),\n\t\t\tconvert_node!(from: numbers, to: DVec2),\n\t\t\tconvert_node!(from: numbers, to: String),\n\t\t]\n\t\t.into_iter()\n\t\t.flatten(),\n\t);\n\n\tlet mut map: HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> = HashMap::new();\n\n\tfor (id, entry) in graphene_std::registry::NODE_REGISTRY.lock().unwrap().iter() {\n\t\tfor (constructor, types) in entry.iter() {\n\t\t\tmap.entry(id.clone()).or_default().insert(types.clone(), *constructor);\n\t\t}\n\t}\n\n\tfor (id, node_constructor, types) in node_types.into_iter() {\n\t\t// TODO: this is a hack to remove the newline from the node new_name\n\t\t// This occurs for the ChannelMixerNode presumably because of the long name.\n\t\t// This might be caused by the stringify! macro\n\t\tlet mut new_name = id.as_str().replace('\\n', \" \");\n\n\t\t// Remove struct generics for all nodes except for the IntoNode and ConvertNode\n\t\tif !(new_name.contains(\"IntoNode\") || new_name.contains(\"ConvertNode\"))\n\t\t\t&& let Some((path, _generics)) = new_name.split_once(\"<\")\n\t\t{\n\t\t\tnew_name = path.to_string();\n\t\t}\n\n\t\tmap.entry(ProtoNodeIdentifier::with_owned_string(new_name)).or_default().insert(types.clone(), node_constructor);\n\t}\n\n\tmap\n}\n\n// TODO: Replace with `core::cell::LazyCell` (<https://doc.rust-lang.org/core/cell/struct.LazyCell.html>) or similar\npub static NODE_REGISTRY: once_cell::sync::Lazy<HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>>> = once_cell::sync::Lazy::new(|| node_registry());\n\nmod node_registry_macros {\n\tmacro_rules! async_node {\n\t\t// TODO: we currently need to annotate the type here because the compiler would otherwise (correctly)\n\t\t// TODO: assign a Pin<Box<dyn Future<Output=T>>> type to the node, which is not what we want for now.\n\t\t//\n\t\t// This `params` variant of the macro wraps the normal `fn_params` variant and is used as a shorthand for writing `T` instead of `() => T`\n\t\t($path:ty, input: $input:ty, params: [$($type:ty),*]) => {\n\t\t\tasync_node!($path, input: $input, fn_params: [ $(() => $type),*])\n\t\t};\n\t\t($path:ty, input: $input:ty, fn_params: [$($arg:ty => $type:ty),*]) => {\n\t\t\t(\n\t\t\t\tProtoNodeIdentifier::new(stringify!($path)),\n\t\t\t\t|mut args| {\n\t\t\t\t\tBox::pin(async move {\n\t\t\t\t\t\targs.reverse();\n\t\t\t\t\t\tlet node = <$path>::new($(graphene_std::any::downcast_node::<$arg, $type>(args.pop().expect(\"Not enough arguments provided to construct node\"))),*);\n\t\t\t\t\t\tlet any: DynAnyNode<$input, _, _> = graphene_std::any::DynAnyNode::new(node);\n\t\t\t\t\t\tBox::new(any) as TypeErasedBox\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlet node = <$path>::new($(\n\t\t\t\t\t\tgraphene_std::any::PanicNode::<$arg, core::pin::Pin<Box<dyn core::future::Future<Output = $type> + Send>>>::new()\n\t\t\t\t\t),*);\n\t\t\t\t\tlet params = vec![$(fn_type_fut!($arg, $type)),*];\n\t\t\t\t\tlet mut node_io = NodeIO::<'_, $input>::to_async_node_io(&node, params);\n\t\t\t\t\tnode_io.call_argument = concrete!(<$input as StaticType>::Static);\n\t\t\t\t\tnode_io\n\t\t\t\t},\n\t\t\t)\n\t\t};\n\t}\n\n\tmacro_rules! into_node {\n\t\t(from: $from:ty, to: $to:ty) => {\n\t\t\t(\n\t\t\t\tProtoNodeIdentifier::new(concat![\"graphene_core::ops::IntoNode<\", stringify!($to), \">\"]),\n\t\t\t\t|mut args| {\n\t\t\t\t\tBox::pin(async move {\n\t\t\t\t\t\tlet node = graphene_std::ops::IntoNode::new(\n\t\t\t\t\t\t\tgraphene_std::any::downcast_node::<Context, $from>(args.pop().unwrap()),\n\t\t\t\t\t\t\tgraphene_std::any::FutureWrapperNode::new(graphene_std::value::ClonedNode::new(std::marker::PhantomData::<$to>)),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tlet any: DynAnyNode<Context, $to, _> = graphene_std::any::DynAnyNode::new(node);\n\t\t\t\t\t\tBox::new(any) as TypeErasedBox\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlet node = graphene_std::ops::IntoNode::new(\n\t\t\t\t\t\tgraphene_std::any::PanicNode::<Context, core::pin::Pin<Box<dyn core::future::Future<Output = $from> + Send>>>::new(),\n\t\t\t\t\t\tgraphene_std::any::FutureWrapperNode::new(graphene_std::value::ClonedNode::new(std::marker::PhantomData::<$to>)),\n\t\t\t\t\t);\n\t\t\t\t\tlet params = vec![fn_type_fut!(Context, $from)];\n\t\t\t\t\tlet node_io = NodeIO::<'_, Context>::to_async_node_io(&node, params);\n\t\t\t\t\tnode_io\n\t\t\t\t},\n\t\t\t)\n\t\t};\n\t}\n\tmacro_rules! convert_node {\n\t\t(from: $from:ty, to: numbers) => {{\n\t\t\tlet x: Vec<(ProtoNodeIdentifier, NodeConstructor, NodeIOTypes)> = vec![\n\t\t\t\tconvert_node!(from: $from, to: f32),\n\t\t\t\tconvert_node!(from: $from, to: f64),\n\t\t\t\tconvert_node!(from: $from, to: i8),\n\t\t\t\tconvert_node!(from: $from, to: u8),\n\t\t\t\tconvert_node!(from: $from, to: u16),\n\t\t\t\tconvert_node!(from: $from, to: i16),\n\t\t\t\tconvert_node!(from: $from, to: i32),\n\t\t\t\tconvert_node!(from: $from, to: u32),\n\t\t\t\tconvert_node!(from: $from, to: i64),\n\t\t\t\tconvert_node!(from: $from, to: u64),\n\t\t\t\tconvert_node!(from: $from, to: i128),\n\t\t\t\tconvert_node!(from: $from, to: u128),\n\t\t\t\tconvert_node!(from: $from, to: isize),\n\t\t\t\tconvert_node!(from: $from, to: usize),\n\t\t\t];\n\t\t\tx\n\t\t}};\n\t\t(from: numbers, to: $to:ty) => {{\n\t\t\tlet x: Vec<(ProtoNodeIdentifier, NodeConstructor, NodeIOTypes)> = vec![\n\t\t\t\tconvert_node!(from: f32, to: $to),\n\t\t\t\tconvert_node!(from: f64, to: $to),\n\t\t\t\tconvert_node!(from: i8, to: $to),\n\t\t\t\tconvert_node!(from: u8, to: $to),\n\t\t\t\tconvert_node!(from: u16, to: $to),\n\t\t\t\tconvert_node!(from: i16, to: $to),\n\t\t\t\tconvert_node!(from: i32, to: $to),\n\t\t\t\tconvert_node!(from: u32, to: $to),\n\t\t\t\tconvert_node!(from: i64, to: $to),\n\t\t\t\tconvert_node!(from: u64, to: $to),\n\t\t\t\tconvert_node!(from: i128, to: $to),\n\t\t\t\tconvert_node!(from: u128, to: $to),\n\t\t\t\tconvert_node!(from: isize, to: $to),\n\t\t\t\tconvert_node!(from: usize, to: $to),\n\t\t\t];\n\t\t\tx\n\t\t}};\n\t\t(from: $from:ty, to: $to:ty) => {\n\t\t\tconvert_node!(from: $from, to: $to, converter: ())\n\t\t};\n\t\t(from: $from:ty, to: $to:ty, converter: $convert:ty) => {\n\t\t\t(\n\t\t\t\tProtoNodeIdentifier::new(concat![\"graphene_core::ops::ConvertNode<\", stringify!($to), \">\"]),\n\t\t\t\t|mut args| {\n\t\t\t\t\tBox::pin(async move {\n\t\t\t\t\t\tlet mut args = args.drain(..);\n\t\t\t\t\t\tlet node = graphene_std::ops::ConvertNode::new(\n\t\t\t\t\t\t\tgraphene_std::any::downcast_node::<Context, $from>(args.next().expect(\"Convert node did not get first argument\")),\n\t\t\t\t\t\t\tgraphene_std::any::downcast_node::<Context, $convert>(args.next().expect(\"Convert node did not get converter argument\")),\n\t\t\t\t\t\t\tgraphene_std::any::FutureWrapperNode::new(graphene_std::value::ClonedNode::new(std::marker::PhantomData::<$to>))\n\t\t\t\t\t\t);\n\t\t\t\t\t\tlet any: DynAnyNode<Context, $to, _> = graphene_std::any::DynAnyNode::new(node);\n\t\t\t\t\t\tBox::new(any) as TypeErasedBox\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlet node = graphene_std::ops::ConvertNode::new(\n\n\t\t\t\t\t\tgraphene_std::any::PanicNode::<Context, core::pin::Pin<Box<dyn core::future::Future<Output = $from> + Send>>>::new(),\n\t\t\t\t\t\tgraphene_std::any::PanicNode::<Context, core::pin::Pin<Box<dyn core::future::Future<Output = $convert> + Send>>>::new(),\n\t\t\t\t\t\tgraphene_std::any::FutureWrapperNode::new(graphene_std::value::ClonedNode::new(std::marker::PhantomData::<$to>))\n\t\t\t\t\t);\n\t\t\t\t\tlet params = vec![fn_type_fut!(Context, $from), fn_type_fut!(Context, $convert)];\n\t\t\t\t\tlet node_io = NodeIO::<'_, Context>::to_async_node_io(&node, params);\n\t\t\t\t\tnode_io\n\t\t\t\t},\n\t\t\t)\n\t\t};\n\t}\n\n\tpub(crate) use async_node;\n\tpub(crate) use convert_node;\n\tpub(crate) use into_node;\n}\n"
  },
  {
    "path": "node-graph/interpreted-executor/src/util.rs",
    "content": "use graph_craft::ProtoNodeIdentifier;\nuse graph_craft::concrete;\nuse graph_craft::document::value::TaggedValue;\nuse graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeInput, NodeNetwork};\nuse graph_craft::generic;\nuse graph_craft::wasm_application_io::WasmEditorApi;\nuse graphene_std::Context;\nuse graphene_std::ContextFeatures;\nuse graphene_std::uuid::NodeId;\nuse std::sync::Arc;\nuse wgpu_executor::WgpuExecutor;\n\npub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEditorApi>) -> NodeNetwork {\n\tnetwork.generate_node_paths(&[]);\n\n\tlet inner_network = DocumentNode {\n\t\timplementation: DocumentNodeImplementation::Network(network),\n\t\tinputs: vec![],\n\t\t..Default::default()\n\t};\n\n\t// TODO: Replace with \"Output\" definition?\n\t// let render_node = resolve_document_node_type(\"Output\")\n\t// \t.expect(\"Output node type not found\")\n\t// \t.node_template_input_override(vec![Some(NodeInput::node(NodeId(1), 0)), Some(NodeInput::node(NodeId(0), 1))])\n\t// \t.document_node;\n\n\tlet render_node = DocumentNode {\n\t\tinputs: vec![NodeInput::node(NodeId(0), 0)],\n\t\timplementation: DocumentNodeImplementation::Network(NodeNetwork {\n\t\t\texports: vec![NodeInput::node(NodeId(4), 0)],\n\t\t\tnodes: [\n\t\t\t\tDocumentNode {\n\t\t\t\t\tcall_argument: concrete!(Context),\n\t\t\t\t\tinputs: vec![NodeInput::import(core_types::Type::Fn(Box::new(concrete!(Context)), Box::new(generic!(T))), 0)],\n\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(graphene_std::render_node::render_intermediate::IDENTIFIER),\n\t\t\t\t\tcontext_features: graphene_std::ContextDependencies {\n\t\t\t\t\t\textract: ContextFeatures::VARARGS,\n\t\t\t\t\t\tinject: ContextFeatures::INDEX,\n\t\t\t\t\t},\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t\t// Keep this in sync with the protonode in valid_input_types\n\t\t\t\tDocumentNode {\n\t\t\t\t\tcall_argument: concrete!(Context),\n\t\t\t\t\tinputs: vec![NodeInput::scope(\"editor-api\"), NodeInput::node(NodeId(0), 0)],\n\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(graphene_std::render_node::render::IDENTIFIER),\n\t\t\t\t\tcontext_features: graphene_std::ContextDependencies {\n\t\t\t\t\t\textract: ContextFeatures::FOOTPRINT | ContextFeatures::VARARGS,\n\t\t\t\t\t\tinject: ContextFeatures::empty(),\n\t\t\t\t\t},\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t\tDocumentNode {\n\t\t\t\t\tcall_argument: concrete!(Context),\n\t\t\t\t\tinputs: vec![NodeInput::scope(\"editor-api\"), NodeInput::node(NodeId(1), 0)],\n\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(graphene_std::render_cache::render_output_cache::IDENTIFIER),\n\t\t\t\t\tcontext_features: graphene_std::ContextDependencies {\n\t\t\t\t\t\textract: ContextFeatures::FOOTPRINT | ContextFeatures::VARARGS,\n\t\t\t\t\t\tinject: ContextFeatures::VARARGS,\n\t\t\t\t\t},\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t\tDocumentNode {\n\t\t\t\t\tcall_argument: concrete!(Context),\n\t\t\t\t\tinputs: vec![NodeInput::scope(\"editor-api\"), NodeInput::node(NodeId(2), 0)],\n\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(graphene_std::pixel_preview::pixel_preview::IDENTIFIER),\n\t\t\t\t\tcontext_features: graphene_std::ContextDependencies {\n\t\t\t\t\t\textract: ContextFeatures::FOOTPRINT | ContextFeatures::VARARGS,\n\t\t\t\t\t\tinject: ContextFeatures::FOOTPRINT | ContextFeatures::VARARGS,\n\t\t\t\t\t},\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t\tDocumentNode {\n\t\t\t\t\tcall_argument: concrete!(graphene_std::application_io::RenderConfig),\n\t\t\t\t\tinputs: vec![NodeInput::node(NodeId(3), 0)],\n\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(graphene_std::render_node::create_context::IDENTIFIER),\n\t\t\t\t\tcontext_features: graphene_std::ContextDependencies {\n\t\t\t\t\t\t// We add the extract index annotation here to force the compiler to add a context nullification node before this node so the render context is properly nullified so the render cache node can do its's work\n\t\t\t\t\t\textract: ContextFeatures::INDEX,\n\t\t\t\t\t\tinject: ContextFeatures::REAL_TIME | ContextFeatures::ANIMATION_TIME | ContextFeatures::POINTER_POSITION | ContextFeatures::FOOTPRINT | ContextFeatures::VARARGS,\n\t\t\t\t\t},\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t]\n\t\t\t.into_iter()\n\t\t\t.enumerate()\n\t\t\t.map(|(id, node)| (NodeId(id as u64), node))\n\t\t\t.collect(),\n\t\t\t..Default::default()\n\t\t}),\n\t\t..Default::default()\n\t};\n\n\t// wrap the inner network in a scope\n\tlet mut nodes = vec![\n\t\tinner_network,\n\t\trender_node,\n\t\tDocumentNode {\n\t\t\timplementation: DocumentNodeImplementation::ProtoNode(graphene_std::ops::identity::IDENTIFIER),\n\t\t\tinputs: vec![NodeInput::value(TaggedValue::EditorApi(editor_api), false)],\n\t\t\t..Default::default()\n\t\t},\n\t];\n\tlet mut scope_injections = vec![(\"editor-api\".to_string(), (NodeId(2), concrete!(&WasmEditorApi)))];\n\n\tif cfg!(feature = \"gpu\") {\n\t\tnodes.push(DocumentNode {\n\t\t\timplementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new(\"graphene_core::ops::IntoNode<&WgpuExecutor>\")),\n\t\t\tinputs: vec![NodeInput::node(NodeId(2), 0)],\n\t\t\t..Default::default()\n\t\t});\n\t\tscope_injections.push((\"wgpu-executor\".to_string(), (NodeId(3), concrete!(&WgpuExecutor))));\n\t}\n\n\tNodeNetwork {\n\t\texports: vec![NodeInput::node(NodeId(1), 0)],\n\t\tnodes: nodes.into_iter().enumerate().map(|(id, node)| (NodeId(id as u64), node)).collect(),\n\t\tscope_injections: scope_injections.into_iter().collect(),\n\t\t// TODO(TrueDoctor): check if it makes sense to set `generated` to `true`\n\t\tgenerated: false,\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/application-io/Cargo.toml",
    "content": "[package]\nname = \"graphene-application-io\"\nversion = \"0.1.0\"\nedition = \"2024\"\ndescription = \"graphene application io interface\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\nlicense = \"MIT OR Apache-2.0\"\n\n[features]\nwasm = [\"dep:web-sys\"]\nwgpu = [\"dep:wgpu\"]\n\n[dependencies]\n# Local dependencies\ndyn-any = { workspace = true }\ncore-types = { workspace = true }\nvector-types = { workspace = true }\ntext-nodes = { workspace = true }\n\n# Workspace dependencies\nglam = { workspace = true }\nserde = { workspace = true }\nlog = { workspace = true }\n\n# Optional workspace dependencies\nweb-sys = { workspace = true, optional = true }\nwgpu = { workspace = true, optional = true }\n"
  },
  {
    "path": "node-graph/libraries/application-io/src/lib.rs",
    "content": "use core_types::transform::Footprint;\nuse dyn_any::{DynAny, StaticType, StaticTypeSized};\nuse glam::{DAffine2, DVec2, UVec2};\nuse std::fmt::Debug;\nuse std::future::Future;\nuse std::hash::{Hash, Hasher};\nuse std::pin::Pin;\nuse std::ptr::addr_of;\nuse std::sync::Arc;\nuse std::time::Duration;\nuse text_nodes::FontCache;\nuse vector_types::vector::style::RenderMode;\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]\npub struct SurfaceId(pub u64);\n\nimpl std::fmt::Display for SurfaceId {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tf.write_fmt(format_args!(\"{}\", self.0))\n\t}\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct SurfaceFrame {\n\tpub surface_id: SurfaceId,\n\t/// Logical resolution in CSS pixels (used for foreignObject dimensions)\n\tpub resolution: DVec2,\n\tpub transform: DAffine2,\n}\n\nimpl Hash for SurfaceFrame {\n\tfn hash<H: Hasher>(&self, state: &mut H) {\n\t\tself.surface_id.hash(state);\n\t\tself.transform.to_cols_array().iter().for_each(|x| x.to_bits().hash(state));\n\t}\n}\n\nunsafe impl StaticType for SurfaceFrame {\n\ttype Static = SurfaceFrame;\n}\n\npub trait Size {\n\tfn size(&self) -> UVec2;\n}\n\n#[cfg(target_family = \"wasm\")]\nimpl Size for web_sys::HtmlCanvasElement {\n\tfn size(&self) -> UVec2 {\n\t\tUVec2::new(self.width(), self.height())\n\t}\n}\n\n#[derive(Debug, Clone, DynAny)]\npub struct ImageTexture {\n\t#[cfg(feature = \"wgpu\")]\n\tpub texture: Arc<wgpu::Texture>,\n\t#[cfg(not(feature = \"wgpu\"))]\n\tpub texture: (),\n}\n\nimpl<'a> serde::Deserialize<'a> for ImageTexture {\n\tfn deserialize<D>(_: D) -> Result<Self, D::Error>\n\twhere\n\t\tD: serde::Deserializer<'a>,\n\t{\n\t\tunimplemented!(\"attempted to serialize a texture\")\n\t}\n}\n\nimpl Hash for ImageTexture {\n\t#[cfg(feature = \"wgpu\")]\n\tfn hash<H: Hasher>(&self, state: &mut H) {\n\t\tself.texture.hash(state);\n\t}\n\t#[cfg(not(feature = \"wgpu\"))]\n\tfn hash<H: Hasher>(&self, _state: &mut H) {}\n}\n\nimpl PartialEq for ImageTexture {\n\tfn eq(&self, other: &Self) -> bool {\n\t\t#[cfg(feature = \"wgpu\")]\n\t\t{\n\t\t\tself.texture == other.texture\n\t\t}\n\t\t#[cfg(not(feature = \"wgpu\"))]\n\t\t{\n\t\t\tself.texture == other.texture\n\t\t}\n\t}\n}\n\n#[cfg(feature = \"wgpu\")]\nimpl Size for ImageTexture {\n\tfn size(&self) -> UVec2 {\n\t\tUVec2::new(self.texture.width(), self.texture.height())\n\t}\n}\n\nimpl<S: Size> From<SurfaceHandleFrame<S>> for SurfaceFrame {\n\tfn from(x: SurfaceHandleFrame<S>) -> Self {\n\t\tlet size = x.surface_handle.surface.size();\n\t\tSelf {\n\t\t\tsurface_id: x.surface_handle.window_id,\n\t\t\ttransform: x.transform,\n\t\t\tresolution: size.into(),\n\t\t}\n\t}\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct SurfaceHandle<Surface> {\n\tpub window_id: SurfaceId,\n\tpub surface: Surface,\n}\n\n// #[cfg(target_family = \"wasm\")]\n// unsafe impl<T: dyn_any::WasmNotSend> Send for SurfaceHandle<T> {}\n// #[cfg(target_family = \"wasm\")]\n// unsafe impl<T: dyn_any::WasmNotSync> Sync for SurfaceHandle<T> {}\n\nimpl<S: Size> Size for SurfaceHandle<S> {\n\tfn size(&self) -> UVec2 {\n\t\tself.surface.size()\n\t}\n}\n\nunsafe impl<T: 'static> StaticType for SurfaceHandle<T> {\n\ttype Static = SurfaceHandle<T>;\n}\n\n#[derive(Clone, Debug, PartialEq)]\npub struct SurfaceHandleFrame<Surface> {\n\tpub surface_handle: Arc<SurfaceHandle<Surface>>,\n\tpub transform: DAffine2,\n}\n\nunsafe impl<T: 'static> StaticType for SurfaceHandleFrame<T> {\n\ttype Static = SurfaceHandleFrame<T>;\n}\n\n#[cfg(feature = \"wasm\")]\npub type WasmSurfaceHandle = SurfaceHandle<web_sys::HtmlCanvasElement>;\n#[cfg(feature = \"wasm\")]\npub type WasmSurfaceHandleFrame = SurfaceHandleFrame<web_sys::HtmlCanvasElement>;\n\n// TODO: think about how to automatically clean up memory\n/*\nimpl<'a, Surface> Drop for SurfaceHandle<'a, Surface> {\n\tfn drop(&mut self) {\n\t\tself.application_io.destroy_surface(self.surface_id)\n\t}\n}*/\n\n#[cfg(target_family = \"wasm\")]\npub type ResourceFuture = Pin<Box<dyn Future<Output = Result<Arc<[u8]>, ApplicationError>>>>;\n#[cfg(not(target_family = \"wasm\"))]\npub type ResourceFuture = Pin<Box<dyn Future<Output = Result<Arc<[u8]>, ApplicationError>> + Send>>;\n\npub trait ApplicationIo {\n\ttype Surface;\n\ttype Executor;\n\tfn window(&self) -> Option<SurfaceHandle<Self::Surface>>;\n\tfn create_window(&self) -> SurfaceHandle<Self::Surface>;\n\tfn destroy_window(&self, surface_id: SurfaceId);\n\tfn gpu_executor(&self) -> Option<&Self::Executor> {\n\t\tNone\n\t}\n\tfn load_resource(&self, url: impl AsRef<str>) -> Result<ResourceFuture, ApplicationError>;\n}\n\nimpl<T: ApplicationIo> ApplicationIo for &T {\n\ttype Surface = T::Surface;\n\ttype Executor = T::Executor;\n\n\tfn window(&self) -> Option<SurfaceHandle<Self::Surface>> {\n\t\t(**self).window()\n\t}\n\n\tfn create_window(&self) -> SurfaceHandle<T::Surface> {\n\t\t(**self).create_window()\n\t}\n\n\tfn destroy_window(&self, surface_id: SurfaceId) {\n\t\t(**self).destroy_window(surface_id)\n\t}\n\n\tfn gpu_executor(&self) -> Option<&T::Executor> {\n\t\t(**self).gpu_executor()\n\t}\n\n\tfn load_resource<'a>(&self, url: impl AsRef<str>) -> Result<ResourceFuture, ApplicationError> {\n\t\t(**self).load_resource(url)\n\t}\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub enum ApplicationError {\n\tNotFound,\n\tInvalidUrl,\n}\n\n#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]\npub enum NodeGraphUpdateMessage {}\n\npub trait NodeGraphUpdateSender {\n\tfn send(&self, message: NodeGraphUpdateMessage);\n}\n\nimpl<T: NodeGraphUpdateSender> NodeGraphUpdateSender for std::sync::Mutex<T> {\n\tfn send(&self, message: NodeGraphUpdateMessage) {\n\t\tself.lock().as_mut().unwrap().send(message)\n\t}\n}\n\npub trait GetEditorPreferences {\n\tfn max_render_region_area(&self) -> u32;\n}\n\n#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]\npub enum ExportFormat {\n\t#[default]\n\tSvg,\n\tRaster,\n}\n\n#[derive(Debug, Default, Clone, Copy, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]\npub struct TimingInformation {\n\tpub time: f64,\n\tpub animation_time: Duration,\n}\n\n#[derive(Debug, Default, Clone, Copy, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]\npub struct RenderConfig {\n\tpub viewport: Footprint,\n\tpub scale: f64,\n\tpub time: TimingInformation,\n\tpub pointer: DVec2,\n\t#[serde(alias = \"view_mode\")]\n\tpub render_mode: RenderMode,\n\tpub export_format: ExportFormat,\n\tpub hide_artboards: bool,\n\tpub for_export: bool,\n\tpub for_eyedropper: bool,\n}\n\nstruct Logger;\n\nimpl NodeGraphUpdateSender for Logger {\n\tfn send(&self, message: NodeGraphUpdateMessage) {\n\t\tlog::warn!(\"dispatching message with fallback node graph update sender {message:?}\");\n\t}\n}\n\nstruct DummyPreferences;\n\nimpl GetEditorPreferences for DummyPreferences {\n\tfn max_render_region_area(&self) -> u32 {\n\t\t1024 * 1024\n\t}\n}\n\npub struct EditorApi<Io> {\n\t/// Font data (for rendering text) made available to the graph through the [`WasmEditorApi`].\n\tpub font_cache: FontCache,\n\t/// Gives access to APIs like a rendering surface (native window handle or HTML5 canvas) and WGPU (which becomes WebGPU on web).\n\tpub application_io: Option<Arc<Io>>,\n\tpub node_graph_message_sender: Box<dyn NodeGraphUpdateSender + Send + Sync>,\n\t/// Editor preferences made available to the graph through the [`WasmEditorApi`].\n\tpub editor_preferences: Box<dyn GetEditorPreferences + Send + Sync>,\n}\n\nimpl<Io> Eq for EditorApi<Io> {}\n\nimpl<Io: Default> Default for EditorApi<Io> {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tfont_cache: FontCache::default(),\n\t\t\tapplication_io: None,\n\t\t\tnode_graph_message_sender: Box::new(Logger),\n\t\t\teditor_preferences: Box::new(DummyPreferences),\n\t\t}\n\t}\n}\n\nimpl<Io> Hash for EditorApi<Io> {\n\tfn hash<H: Hasher>(&self, state: &mut H) {\n\t\tself.font_cache.hash(state);\n\t\tself.application_io.as_ref().map_or(0, |io| io.as_ref() as *const _ as usize).hash(state);\n\t\t(self.node_graph_message_sender.as_ref() as *const dyn NodeGraphUpdateSender).hash(state);\n\t\t(self.editor_preferences.as_ref() as *const dyn GetEditorPreferences).hash(state);\n\t}\n}\n\nimpl<Io> PartialEq for EditorApi<Io> {\n\tfn eq(&self, other: &Self) -> bool {\n\t\tself.font_cache == other.font_cache\n\t\t\t&& self.application_io.as_ref().map_or(0, |io| addr_of!(io) as usize) == other.application_io.as_ref().map_or(0, |io| addr_of!(io) as usize)\n\t\t\t&& std::ptr::eq(self.node_graph_message_sender.as_ref() as *const _, other.node_graph_message_sender.as_ref() as *const _)\n\t\t\t&& std::ptr::eq(self.editor_preferences.as_ref() as *const _, other.editor_preferences.as_ref() as *const _)\n\t}\n}\n\nimpl<T> Debug for EditorApi<T> {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tf.debug_struct(\"EditorApi\").field(\"font_cache\", &self.font_cache).finish()\n\t}\n}\n\nunsafe impl<T: StaticTypeSized> StaticType for EditorApi<T> {\n\ttype Static = EditorApi<T::Static>;\n}\n"
  },
  {
    "path": "node-graph/libraries/core-types/Cargo.toml",
    "content": "[package]\nname = \"core-types\"\nversion = \"0.1.0\"\nedition = \"2024\"\ndescription = \"Core types and traits for Graphene node system\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\nlicense = \"MIT OR Apache-2.0\"\n\n[features]\ndefault = [\"serde\"]\nnightly = []\ntype_id_logging = []\ndealloc_nodes = []\nwasm = [\"tsify\", \"wasm-bindgen\", \"no-std-types/wasm\"]\n\n[dependencies]\n# Local dependencies\nno-std-types = { workspace = true, features = [\"std\"] }\n\n# Workspace dependencies\nbitflags = { workspace = true }\nbytemuck = { workspace = true }\nnode-macro = { workspace = true }\nnum-traits = { workspace = true }\nrand = { workspace = true }\nglam = { workspace = true }\nserde_json = { workspace = true }\npetgraph = { workspace = true }\nrustc-hash = { workspace = true }\ndyn-any = { workspace = true }\nctor = { workspace = true }\nrand_chacha = { workspace = true }\nimage = { workspace = true }\ntinyvec = { workspace = true }\nparley = { workspace = true }\nskrifa = { workspace = true }\nkurbo = { workspace = true }\nlyon_geom = { workspace = true }\nlog = { workspace = true }\nbase64 = { workspace = true }\npolycool = { workspace = true }\n\n# Optional workspace dependencies\nserde = { workspace = true, optional = true }\ntsify = { workspace = true, optional = true }\nwasm-bindgen = { workspace = true, optional = true }\n\n[dev-dependencies]\n# Workspace dependencies\ntokio = { workspace = true }\nserde_json = { workspace = true }\n"
  },
  {
    "path": "node-graph/libraries/core-types/src/bounds.rs",
    "content": "use crate::Color;\nuse glam::{DAffine2, DVec2};\n\n#[derive(Clone, Copy, Default, Debug, PartialEq)]\npub enum RenderBoundingBox {\n\t#[default]\n\tNone,\n\tInfinite,\n\tRectangle([DVec2; 2]),\n}\n\npub trait BoundingBox {\n\tfn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox;\n}\n\nmacro_rules! none_impl {\n\t($t:path) => {\n\t\timpl BoundingBox for $t {\n\t\t\tfn bounding_box(&self, _transform: DAffine2, _include_stroke: bool) -> RenderBoundingBox {\n\t\t\t\tRenderBoundingBox::None\n\t\t\t}\n\t\t}\n\t};\n}\nnone_impl!(bool);\nnone_impl!(f32);\nnone_impl!(f64);\nnone_impl!(DVec2);\nnone_impl!(String);\n\nimpl BoundingBox for Color {\n\tfn bounding_box(&self, _transform: DAffine2, _include_stroke: bool) -> RenderBoundingBox {\n\t\tRenderBoundingBox::Infinite\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/core-types/src/consts.rs",
    "content": "use crate::Color;\n\n// RENDERING\npub const LAYER_OUTLINE_STROKE_COLOR: Color = Color::BLACK;\npub const LAYER_OUTLINE_STROKE_WEIGHT: f64 = 0.5;\n\n// Fonts\npub const DEFAULT_FONT_FAMILY: &str = \"Lato\";\npub const DEFAULT_FONT_STYLE: &str = \"Regular (400)\";\n"
  },
  {
    "path": "node-graph/libraries/core-types/src/context.rs",
    "content": "use crate::transform::Footprint;\nuse glam::DVec2;\npub use no_std_types::context::{ArcCtx, Ctx};\nuse std::any::Any;\nuse std::borrow::Borrow;\nuse std::hash::{Hash, Hasher};\nuse std::panic::Location;\nuse std::sync::Arc;\n\n// ==============\n// EXTRACT TRAITS\n// ==============\n\npub trait ExtractFootprint {\n\t#[track_caller]\n\tfn try_footprint(&self) -> Option<&Footprint>;\n\t#[track_caller]\n\tfn footprint(&self) -> &Footprint {\n\t\tself.try_footprint().unwrap_or_else(|| {\n\t\t\tlog::error!(\"Context did not have a footprint, called from: {}\", Location::caller());\n\t\t\t&Footprint::DEFAULT\n\t\t})\n\t}\n}\npub trait ExtractRealTime {\n\tfn try_real_time(&self) -> Option<f64>;\n}\npub trait ExtractAnimationTime {\n\tfn try_animation_time(&self) -> Option<f64>;\n}\npub trait ExtractPointerPosition {\n\tfn try_pointer_position(&self) -> Option<DVec2>;\n}\npub trait ExtractPosition {\n\tfn try_position(&self) -> Option<impl Iterator<Item = DVec2>>;\n}\npub trait ExtractIndex {\n\tfn try_index(&self) -> Option<impl Iterator<Item = usize>>;\n}\npub trait ExtractVarArgs {\n\t// TODO: Consider returning a slice or something like that\n\n\tfn vararg(&self, index: usize) -> Result<DynRef<'_>, VarArgsResult>;\n\tfn varargs_len(&self) -> Result<usize, VarArgsResult>;\n\tfn hash_varargs(&self, hasher: &mut dyn Hasher);\n}\n\npub trait CloneVarArgs: ExtractVarArgs {\n\t// TODO: Consider returning a slice or something like that\n\n\t// fn box_clone(&self) -> Vec<DynBox>;\n\tfn arc_clone(&self) -> Option<Arc<dyn ExtractVarArgs + Send + Sync>>;\n}\n\n// =============\n// INJECT TRAITS\n// =============\n\n// Inject* traits for providing context features to downstream nodes\npub trait InjectFootprint {}\npub trait InjectRealTime {}\npub trait InjectAnimationTime {}\npub trait InjectPointerPosition {}\npub trait InjectPosition {}\npub trait InjectIndex {}\npub trait InjectVarArgs {}\n\n// ================\n// EXTRACTALL TRAIT\n// ================\n\npub trait ExtractAll:\n\t// Extract traits\n\tExtractFootprint +\n\tExtractRealTime +\n\tExtractAnimationTime +\n\tExtractPointerPosition +\n\tExtractPosition +\n\tExtractIndex +\n\tExtractVarArgs {}\nimpl<\n\tT: ?Sized\n\t\t// Extract traits\n\t\t+ ExtractFootprint\n\t\t+ ExtractRealTime\n\t\t+ ExtractAnimationTime\n\t\t+ ExtractPointerPosition\n\t\t+ ExtractPosition\n\t\t+ ExtractIndex\n\t\t+ ExtractVarArgs,\n> ExtractAll for T\n{\n}\n\n// =============\n// INJECT TRAITS\n// =============\n\nimpl<T: Ctx> InjectFootprint for T {}\nimpl<T: Ctx> InjectRealTime for T {}\nimpl<T: Ctx> InjectAnimationTime for T {}\nimpl<T: Ctx> InjectPointerPosition for T {}\nimpl<T: Ctx> InjectPosition for T {}\nimpl<T: Ctx> InjectIndex for T {}\nimpl<T: Ctx> InjectVarArgs for T {}\n\n// =============\n// MODIFY TRAITS\n// =============\n\n// Modify* marker traits for context-transparent nodes\npub trait ModifyFootprint: ExtractFootprint + InjectFootprint {}\npub trait ModifyRealTime: ExtractRealTime + InjectRealTime {}\npub trait ModifyAnimationTime: ExtractAnimationTime + InjectAnimationTime {}\npub trait ModifyPointerPosition: ExtractPointerPosition + InjectPointerPosition {}\npub trait ModifyPosition: ExtractPosition + InjectPosition {}\npub trait ModifyIndex: ExtractIndex + InjectIndex {}\npub trait ModifyVarArgs: ExtractVarArgs + InjectVarArgs {}\n\nimpl<T: Ctx + InjectFootprint + ExtractFootprint> ModifyFootprint for T {}\nimpl<T: Ctx + InjectRealTime + ExtractRealTime> ModifyRealTime for T {}\nimpl<T: Ctx + InjectAnimationTime + ExtractAnimationTime> ModifyAnimationTime for T {}\nimpl<T: Ctx + InjectPointerPosition + ExtractPointerPosition> ModifyPointerPosition for T {}\nimpl<T: Ctx + InjectPosition + ExtractPosition> ModifyPosition for T {}\nimpl<T: Ctx + InjectIndex + ExtractIndex> ModifyIndex for T {}\nimpl<T: Ctx + InjectVarArgs + ExtractVarArgs> ModifyVarArgs for T {}\n\n// ================\n// CONTEXT FEATURES\n// ================\n\n// Public enum for flexible node macro codegen\n#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]\npub enum ContextFeature {\n\tExtractFootprint,\n\tExtractRealTime,\n\tExtractAnimationTime,\n\tExtractPointerPosition,\n\tExtractPosition,\n\tExtractIndex,\n\tExtractVarArgs,\n\tInjectFootprint,\n\tInjectRealTime,\n\tInjectAnimationTime,\n\tInjectPointerPosition,\n\tInjectPosition,\n\tInjectIndex,\n\tInjectVarArgs,\n}\n\n// Internal bitflags for fast compiler analysis\nuse bitflags::bitflags;\nbitflags! {\n\t#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, Default)]\n\tpub struct ContextFeatures: u32 {\n\t\tconst FOOTPRINT = 1 << 0;\n\t\tconst REAL_TIME = 1 << 1;\n\t\tconst ANIMATION_TIME = 1 << 2;\n\t\tconst POINTER_POSITION = 1 << 3;\n\t\tconst POSITION = 1 << 4;\n\t\tconst INDEX = 1 << 5;\n\t\tconst VARARGS = 1 << 6;\n\t}\n}\n\nimpl ContextFeatures {\n\tpub fn name(&self) -> &'static str {\n\t\tmatch *self {\n\t\t\tContextFeatures::FOOTPRINT => \"Footprint\",\n\t\t\tContextFeatures::REAL_TIME => \"RealTime\",\n\t\t\tContextFeatures::ANIMATION_TIME => \"AnimationTime\",\n\t\t\tContextFeatures::POINTER_POSITION => \"PointerPosition\",\n\t\t\tContextFeatures::POSITION => \"Position\",\n\t\t\tContextFeatures::INDEX => \"Index\",\n\t\t\tContextFeatures::VARARGS => \"VarArgs\",\n\t\t\t_ => \"Multiple Features\",\n\t\t}\n\t}\n}\n\n// ====================\n// CONTEXT DEPENDENCIES\n// ====================\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, Default)]\npub struct ContextDependencies {\n\tpub extract: ContextFeatures,\n\tpub inject: ContextFeatures,\n}\n\nimpl From<&[ContextFeature]> for ContextDependencies {\n\tfn from(features: &[ContextFeature]) -> Self {\n\t\tlet mut extract = ContextFeatures::empty();\n\t\tlet mut inject = ContextFeatures::empty();\n\t\tfor feature in features {\n\t\t\textract |= match feature {\n\t\t\t\tContextFeature::ExtractFootprint => ContextFeatures::FOOTPRINT,\n\t\t\t\tContextFeature::ExtractRealTime => ContextFeatures::REAL_TIME,\n\t\t\t\tContextFeature::ExtractAnimationTime => ContextFeatures::ANIMATION_TIME,\n\t\t\t\tContextFeature::ExtractPointerPosition => ContextFeatures::POINTER_POSITION,\n\t\t\t\tContextFeature::ExtractPosition => ContextFeatures::POSITION,\n\t\t\t\tContextFeature::ExtractIndex => ContextFeatures::INDEX,\n\t\t\t\tContextFeature::ExtractVarArgs => ContextFeatures::VARARGS,\n\t\t\t\t_ => ContextFeatures::empty(),\n\t\t\t};\n\t\t\tinject |= match feature {\n\t\t\t\tContextFeature::InjectFootprint => ContextFeatures::FOOTPRINT,\n\t\t\t\tContextFeature::InjectRealTime => ContextFeatures::REAL_TIME,\n\t\t\t\tContextFeature::InjectAnimationTime => ContextFeatures::ANIMATION_TIME,\n\t\t\t\tContextFeature::InjectPointerPosition => ContextFeatures::POINTER_POSITION,\n\t\t\t\tContextFeature::InjectPosition => ContextFeatures::POSITION,\n\t\t\t\tContextFeature::InjectIndex => ContextFeatures::INDEX,\n\t\t\t\tContextFeature::InjectVarArgs => ContextFeatures::VARARGS,\n\t\t\t\t_ => ContextFeatures::empty(),\n\t\t\t};\n\t\t}\n\t\tSelf { extract, inject }\n\t}\n}\n\n// ===================================\n// EXTRACT TRAIT IMPLS FOR `Option<T>`\n// ===================================\n\nimpl<T: ExtractFootprint + Sync> ExtractFootprint for Option<T> {\n\tfn try_footprint(&self) -> Option<&Footprint> {\n\t\tself.as_ref().and_then(|x| x.try_footprint())\n\t}\n\t#[track_caller]\n\tfn footprint(&self) -> &Footprint {\n\t\tself.try_footprint().unwrap_or_else(|| {\n\t\t\tlog::warn!(\"trying to extract footprint from context None {} \", Location::caller());\n\t\t\t&Footprint::DEFAULT\n\t\t})\n\t}\n}\nimpl<T: ExtractRealTime + Sync> ExtractRealTime for Option<T> {\n\tfn try_real_time(&self) -> Option<f64> {\n\t\tself.as_ref().and_then(|x| x.try_real_time())\n\t}\n}\nimpl<T: ExtractAnimationTime + Sync> ExtractAnimationTime for Option<T> {\n\tfn try_animation_time(&self) -> Option<f64> {\n\t\tself.as_ref().and_then(|x| x.try_animation_time())\n\t}\n}\nimpl<T: ExtractPointerPosition + Sync> ExtractPointerPosition for Option<T> {\n\tfn try_pointer_position(&self) -> Option<DVec2> {\n\t\tself.as_ref().and_then(|x| x.try_pointer_position())\n\t}\n}\nimpl<T: ExtractPosition + Sync> ExtractPosition for Option<T> {\n\tfn try_position(&self) -> Option<impl Iterator<Item = DVec2>> {\n\t\tself.as_ref().and_then(|x| x.try_position())\n\t}\n}\nimpl<T: ExtractIndex> ExtractIndex for Option<T> {\n\tfn try_index(&self) -> Option<impl Iterator<Item = usize>> {\n\t\tself.as_ref().and_then(|x| x.try_index())\n\t}\n}\nimpl<T: ExtractVarArgs + Sync> ExtractVarArgs for Option<T> {\n\tfn vararg(&self, index: usize) -> Result<DynRef<'_>, VarArgsResult> {\n\t\tlet Some(inner) = self else { return Err(VarArgsResult::NoVarArgs) };\n\t\tinner.vararg(index)\n\t}\n\n\tfn varargs_len(&self) -> Result<usize, VarArgsResult> {\n\t\tlet Some(inner) = self else { return Err(VarArgsResult::NoVarArgs) };\n\t\tinner.varargs_len()\n\t}\n\n\tfn hash_varargs(&self, hasher: &mut dyn Hasher) {\n\t\tif let Some(inner) = self {\n\t\t\tinner.hash_varargs(hasher)\n\t\t}\n\t}\n}\n\nimpl<T: CloneVarArgs + Sync> CloneVarArgs for Option<T> {\n\tfn arc_clone(&self) -> Option<Arc<dyn ExtractVarArgs + Send + Sync>> {\n\t\tself.as_ref().and_then(CloneVarArgs::arc_clone)\n\t}\n}\n\n// ================================\n// EXTRACT TRAIT IMPLS FOR `Arc<T>`\n// ================================\n\nimpl<T: ExtractFootprint + Sync> ExtractFootprint for Arc<T> {\n\tfn try_footprint(&self) -> Option<&Footprint> {\n\t\t(**self).try_footprint()\n\t}\n}\nimpl<T: ExtractRealTime + Sync> ExtractRealTime for Arc<T> {\n\tfn try_real_time(&self) -> Option<f64> {\n\t\t(**self).try_real_time()\n\t}\n}\nimpl<T: ExtractAnimationTime + Sync> ExtractAnimationTime for Arc<T> {\n\tfn try_animation_time(&self) -> Option<f64> {\n\t\t(**self).try_animation_time()\n\t}\n}\nimpl<T: ExtractPointerPosition + Sync> ExtractPointerPosition for Arc<T> {\n\tfn try_pointer_position(&self) -> Option<DVec2> {\n\t\t(**self).try_pointer_position()\n\t}\n}\nimpl<T: ExtractPosition + Sync> ExtractPosition for Arc<T> {\n\tfn try_position(&self) -> Option<impl Iterator<Item = DVec2>> {\n\t\t(**self).try_position()\n\t}\n}\nimpl<T: ExtractIndex> ExtractIndex for Arc<T> {\n\tfn try_index(&self) -> Option<impl Iterator<Item = usize>> {\n\t\t(**self).try_index()\n\t}\n}\nimpl<T: ExtractVarArgs + Sync> ExtractVarArgs for Arc<T> {\n\tfn vararg(&self, index: usize) -> Result<DynRef<'_>, VarArgsResult> {\n\t\t(**self).vararg(index)\n\t}\n\n\tfn varargs_len(&self) -> Result<usize, VarArgsResult> {\n\t\t(**self).varargs_len()\n\t}\n\n\tfn hash_varargs(&self, hasher: &mut dyn Hasher) {\n\t\t(**self).hash_varargs(hasher)\n\t}\n}\n\nimpl<T: CloneVarArgs + Sync> CloneVarArgs for Arc<T> {\n\tfn arc_clone(&self) -> Option<Arc<dyn ExtractVarArgs + Send + Sync>> {\n\t\t(**self).arc_clone()\n\t}\n}\n\n// ============================\n// EXTRACT TRAIT IMPLS FOR `&T`\n// ============================\n\nimpl<T: ExtractFootprint + Ctx + Sync + Send> ExtractFootprint for &T {\n\tfn try_footprint(&self) -> Option<&Footprint> {\n\t\t(*self).try_footprint()\n\t}\n}\n\nimpl<T: ExtractVarArgs + Sync> ExtractVarArgs for &T {\n\tfn vararg(&self, index: usize) -> Result<DynRef<'_>, VarArgsResult> {\n\t\t(*self).vararg(index)\n\t}\n\n\tfn varargs_len(&self) -> Result<usize, VarArgsResult> {\n\t\t(*self).varargs_len()\n\t}\n\n\tfn hash_varargs(&self, hasher: &mut dyn Hasher) {\n\t\t(*self).hash_varargs(hasher)\n\t}\n}\n\n// ============================\n// EXTRACT TRAIT IMPLS FOR `()`\n// ============================\n\nimpl Ctx for Footprint {}\n\nimpl ExtractFootprint for () {\n\tfn try_footprint(&self) -> Option<&Footprint> {\n\t\tlog::error!(\"tried to extract footprint form (), {}\", Location::caller());\n\t\tNone\n\t}\n}\n\n// =====================================\n// EXTRACT TRAIT IMPLS FOR `ContextImpl`\n// =====================================\n\nimpl Ctx for ContextImpl<'_> {}\n\nimpl ExtractFootprint for ContextImpl<'_> {\n\tfn try_footprint(&self) -> Option<&Footprint> {\n\t\tself.footprint\n\t}\n}\nimpl ExtractRealTime for ContextImpl<'_> {\n\tfn try_real_time(&self) -> Option<f64> {\n\t\tself.real_time\n\t}\n}\nimpl ExtractPosition for ContextImpl<'_> {\n\tfn try_position(&self) -> Option<impl Iterator<Item = DVec2>> {\n\t\tself.position.clone().map(|x| x.into_iter())\n\t}\n}\nimpl ExtractIndex for ContextImpl<'_> {\n\tfn try_index(&self) -> Option<impl Iterator<Item = usize>> {\n\t\tself.index.clone().map(|x| x.into_iter())\n\t}\n}\nimpl ExtractVarArgs for ContextImpl<'_> {\n\tfn vararg(&self, index: usize) -> Result<DynRef<'_>, VarArgsResult> {\n\t\tlet Some(inner) = self.varargs else { return Err(VarArgsResult::NoVarArgs) };\n\t\tinner.get(index).ok_or(VarArgsResult::IndexOutOfBounds).copied()\n\t}\n\n\tfn varargs_len(&self) -> Result<usize, VarArgsResult> {\n\t\tlet Some(inner) = self.varargs else { return Err(VarArgsResult::NoVarArgs) };\n\t\tOk(inner.len())\n\t}\n\n\tfn hash_varargs(&self, _hasher: &mut dyn Hasher) {\n\t\ttodo!()\n\t}\n}\n\n// ==========================================\n// EXTRACT TRAIT IMPLS FOR `OwnedContextImpl`\n// ==========================================\n\nimpl ArcCtx for OwnedContextImpl {}\n\nimpl ExtractFootprint for OwnedContextImpl {\n\tfn try_footprint(&self) -> Option<&Footprint> {\n\t\tself.footprint.as_ref()\n\t}\n}\nimpl ExtractRealTime for OwnedContextImpl {\n\tfn try_real_time(&self) -> Option<f64> {\n\t\tself.real_time\n\t}\n}\nimpl ExtractAnimationTime for OwnedContextImpl {\n\tfn try_animation_time(&self) -> Option<f64> {\n\t\tself.animation_time\n\t}\n}\nimpl ExtractPointerPosition for OwnedContextImpl {\n\tfn try_pointer_position(&self) -> Option<DVec2> {\n\t\tself.pointer_position\n\t}\n}\nimpl ExtractPosition for OwnedContextImpl {\n\tfn try_position(&self) -> Option<impl Iterator<Item = DVec2>> {\n\t\tself.position.clone().map(|x| x.into_iter())\n\t}\n}\nimpl ExtractIndex for OwnedContextImpl {\n\tfn try_index(&self) -> Option<impl Iterator<Item = usize>> {\n\t\tself.index.clone().map(|x| x.into_iter())\n\t}\n}\nimpl ExtractVarArgs for OwnedContextImpl {\n\tfn vararg(&self, index: usize) -> Result<DynRef<'_>, VarArgsResult> {\n\t\tlet Some(ref inner) = self.varargs else {\n\t\t\tlet Some(ref parent) = self.parent else {\n\t\t\t\treturn Err(VarArgsResult::NoVarArgs);\n\t\t\t};\n\t\t\treturn parent.vararg(index);\n\t\t};\n\t\tinner.get(index).map(|x| x.as_ref() as DynRef<'_>).ok_or(VarArgsResult::IndexOutOfBounds)\n\t}\n\n\tfn varargs_len(&self) -> Result<usize, VarArgsResult> {\n\t\tlet Some(ref inner) = self.varargs else {\n\t\t\tlet Some(ref parent) = self.parent else {\n\t\t\t\treturn Err(VarArgsResult::NoVarArgs);\n\t\t\t};\n\t\t\treturn parent.varargs_len();\n\t\t};\n\t\tOk(inner.len())\n\t}\n\n\tfn hash_varargs(&self, mut hasher: &mut dyn Hasher) {\n\t\tmatch (&self.varargs, &self.parent) {\n\t\t\t(Some(inner), _) => {\n\t\t\t\tfor arg in inner.iter() {\n\t\t\t\t\targ.hash(&mut hasher);\n\t\t\t\t}\n\t\t\t}\n\t\t\t(None, Some(parent)) => {\n\t\t\t\tparent.hash_varargs(hasher);\n\t\t\t}\n\t\t\t_ => (),\n\t\t};\n\t}\n}\n\nimpl CloneVarArgs for Arc<OwnedContextImpl> {\n\tfn arc_clone(&self) -> Option<Arc<dyn ExtractVarArgs + Send + Sync>> {\n\t\tSome(self.clone())\n\t}\n}\n\n// ======================================\n// TYPES `Context` AND `OwnedContextImpl`\n// ======================================\n\npub type Context<'a> = Option<Arc<OwnedContextImpl>>;\ntype DynRef<'a> = &'a (dyn Any + Send + Sync);\ntype DynBox = Box<dyn AnyHash + Send + Sync>;\n\n#[derive(dyn_any::DynAny)]\npub struct OwnedContextImpl {\n\tparent: Option<Arc<dyn ExtractVarArgs + Sync + Send>>,\n\tfootprint: Option<Footprint>,\n\treal_time: Option<f64>,\n\tanimation_time: Option<f64>,\n\tpointer_position: Option<DVec2>,\n\tposition: Option<Vec<DVec2>>,\n\t// This could be converted into a single enum to save extra bytes\n\tindex: Option<Vec<usize>>,\n\tvarargs: Option<Arc<[DynBox]>>,\n}\n\nimpl std::fmt::Debug for OwnedContextImpl {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tf.debug_struct(\"OwnedContextImpl\")\n\t\t\t.field(\"parent\", &self.parent.as_ref().map(|_| \"<Parent>\"))\n\t\t\t.field(\"footprint\", &self.footprint)\n\t\t\t.field(\"real_time\", &self.real_time)\n\t\t\t.field(\"animation_time\", &self.animation_time)\n\t\t\t.field(\"pointer_position\", &self.pointer_position)\n\t\t\t.field(\"index\", &self.index)\n\t\t\t.field(\"varargs_len\", &self.varargs.as_ref().map(|x| x.len()))\n\t\t\t.finish()\n\t}\n}\n\nimpl Default for OwnedContextImpl {\n\t#[track_caller]\n\tfn default() -> Self {\n\t\tSelf::empty()\n\t}\n}\n\nimpl Hash for OwnedContextImpl {\n\tfn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n\t\tself.footprint.hash(state);\n\t\tself.real_time.map(|x| x.to_bits()).hash(state);\n\t\tself.animation_time.map(|x| x.to_bits()).hash(state);\n\t\tself.pointer_position.map(|v| (v.x.to_bits(), v.y.to_bits())).hash(state);\n\t\tself.position.iter().flat_map(|x| x.iter()).map(|v| (v.x.to_bits(), v.y.to_bits())).for_each(|v| v.hash(state));\n\t\tself.index.hash(state);\n\t\tself.hash_varargs(state);\n\t}\n}\n\nimpl OwnedContextImpl {\n\t#[track_caller]\n\tpub fn from<T: ExtractAll + CloneVarArgs>(value: T) -> Self {\n\t\tOwnedContextImpl::from_flags(value, ContextFeatures::all())\n\t}\n\n\t#[track_caller]\n\tpub fn from_flags<T: ExtractAll + CloneVarArgs>(value: T, bitflags: ContextFeatures) -> Self {\n\t\tlet parent = bitflags\n\t\t\t.contains(ContextFeatures::VARARGS)\n\t\t\t.then(|| match value.varargs_len() {\n\t\t\t\tOk(x) if x > 0 => value.arc_clone(),\n\t\t\t\t_ => None,\n\t\t\t})\n\t\t\t.flatten();\n\t\tlet footprint = bitflags.contains(ContextFeatures::FOOTPRINT).then(|| value.try_footprint().copied()).flatten();\n\t\tlet real_time = bitflags.contains(ContextFeatures::REAL_TIME).then(|| value.try_real_time()).flatten();\n\t\tlet animation_time = bitflags.contains(ContextFeatures::ANIMATION_TIME).then(|| value.try_animation_time()).flatten();\n\t\tlet pointer_position = bitflags.contains(ContextFeatures::POINTER_POSITION).then(|| value.try_pointer_position()).flatten();\n\t\tlet position = bitflags.contains(ContextFeatures::POSITION).then(|| value.try_position()).flatten().map(|x| x.collect());\n\t\tlet index = bitflags.contains(ContextFeatures::INDEX).then(|| value.try_index()).flatten().map(|x| x.collect());\n\n\t\tOwnedContextImpl {\n\t\t\tparent,\n\t\t\tfootprint,\n\t\t\treal_time,\n\t\t\tanimation_time,\n\t\t\tpointer_position,\n\t\t\tposition,\n\t\t\tindex,\n\t\t\tvarargs: None,\n\t\t}\n\t}\n\n\tpub const fn empty() -> Self {\n\t\tOwnedContextImpl {\n\t\t\tparent: None,\n\t\t\tfootprint: None,\n\t\t\treal_time: None,\n\t\t\tanimation_time: None,\n\t\t\tpointer_position: None,\n\t\t\tposition: None,\n\t\t\tindex: None,\n\t\t\tvarargs: None,\n\t\t}\n\t}\n}\n\npub trait DynHash {\n\tfn dyn_hash(&self, state: &mut dyn Hasher);\n}\n\nimpl<H: Hash + ?Sized> DynHash for H {\n\tfn dyn_hash(&self, mut state: &mut dyn Hasher) {\n\t\tself.hash(&mut state);\n\t}\n}\n\nimpl Hash for dyn AnyHash {\n\tfn hash<H: Hasher>(&self, state: &mut H) {\n\t\tself.dyn_hash(state);\n\t}\n}\nimpl Hash for Box<dyn AnyHash + Send + Sync> {\n\tfn hash<H: Hasher>(&self, state: &mut H) {\n\t\t(**self).dyn_hash(state);\n\t}\n}\n\npub trait AnyHash: DynHash + Any {}\nimpl<T: DynHash + Any> AnyHash for T {}\n\nimpl OwnedContextImpl {\n\tpub fn set_footprint(&mut self, footprint: Footprint) {\n\t\tself.footprint = Some(footprint);\n\t}\n\n\tpub fn with_footprint(mut self, footprint: Footprint) -> Self {\n\t\tself.footprint = Some(footprint);\n\t\tself\n\t}\n\tpub fn with_real_time(mut self, real_time: f64) -> Self {\n\t\tself.real_time = Some(real_time);\n\t\tself\n\t}\n\tpub fn with_animation_time(mut self, animation_time: f64) -> Self {\n\t\tself.animation_time = Some(animation_time);\n\t\tself\n\t}\n\tpub fn with_pointer_position(mut self, pointer_position: DVec2) -> Self {\n\t\tself.pointer_position = Some(pointer_position);\n\t\tself\n\t}\n\tpub fn with_position(mut self, position: DVec2) -> Self {\n\t\tif let Some(current_position) = &mut self.position {\n\t\t\tcurrent_position.insert(0, position);\n\t\t} else {\n\t\t\tself.position = Some(vec![position]);\n\t\t}\n\t\tself\n\t}\n\tpub fn with_index(mut self, index: usize) -> Self {\n\t\tif let Some(current_index) = &mut self.index {\n\t\t\tcurrent_index.insert(0, index);\n\t\t} else {\n\t\t\tself.index = Some(vec![index]);\n\t\t}\n\t\tself\n\t}\n\tpub fn with_vararg(mut self, value: Box<dyn AnyHash + Send + Sync>) -> Self {\n\t\tassert!(self.varargs.is_none_or(|value| value.is_empty()));\n\t\tself.varargs = Some(Arc::new([value]));\n\t\tself\n\t}\n\tpub fn into_context(self) -> Option<Arc<Self>> {\n\t\tSome(Arc::new(self))\n\t}\n\tpub fn erase_parent(mut self) -> Self {\n\t\tself.parent = None;\n\t\tself\n\t}\n}\n\n#[derive(Default, Clone, dyn_any::DynAny)]\npub struct ContextImpl<'a> {\n\tpub(crate) footprint: Option<&'a Footprint>,\n\treal_time: Option<f64>,\n\tposition: Option<Vec<DVec2>>, // This could be converted into a single enum to save extra bytes\n\tindex: Option<Vec<usize>>,    // This could be converted into a single enum to save extra bytes\n\tvarargs: Option<&'a [DynRef<'a>]>,\n}\n\nimpl<'a> ContextImpl<'a> {\n\tpub fn with_footprint<'f>(&self, new_footprint: &'f Footprint, varargs: Option<&'f impl Borrow<[DynRef<'f>]>>) -> ContextImpl<'f>\n\twhere\n\t\t'a: 'f,\n\t{\n\t\tContextImpl {\n\t\t\tfootprint: Some(new_footprint),\n\t\t\tposition: self.position.clone(),\n\t\t\tindex: self.index.clone(),\n\t\t\tvarargs: varargs.map(|x| x.borrow()),\n\t\t\t..*self\n\t\t}\n\t}\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub enum VarArgsResult {\n\tIndexOutOfBounds,\n\tNoVarArgs,\n}\n"
  },
  {
    "path": "node-graph/libraries/core-types/src/generic.rs",
    "content": "use crate::Node;\nuse std::marker::PhantomData;\n#[derive(Clone)]\npub struct FnNode<T: Fn(I) -> O, I, O>(T, PhantomData<(I, O)>);\n\nimpl<'i, T: Fn(I) -> O + 'i, O: 'i, I: 'i> Node<'i, I> for FnNode<T, I, O> {\n\ttype Output = O;\n\tfn eval(&'i self, input: I) -> Self::Output {\n\t\tself.0(input)\n\t}\n}\n\nimpl<T: Fn(I) -> O, I, O> FnNode<T, I, O> {\n\tpub fn new(f: T) -> Self {\n\t\tFnNode(f, PhantomData)\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/core-types/src/lib.rs",
    "content": "extern crate log;\n\npub mod bounds;\npub mod consts;\npub mod context;\npub mod generic;\npub mod math;\npub mod memo;\npub mod misc;\npub mod ops;\npub mod registry;\npub mod render_complexity;\npub mod table;\npub mod transform;\npub mod uuid;\npub mod value;\n\npub use crate as core_types;\npub use blending::*;\npub use color::Color;\npub use context::*;\npub use ctor;\npub use dyn_any::{StaticTypeSized, WasmNotSend, WasmNotSync};\npub use memo::MemoHash;\npub use no_std_types::AsU32;\npub use no_std_types::blending;\npub use no_std_types::choice_type;\npub use no_std_types::color;\npub use no_std_types::shaders;\npub use num_traits;\nuse std::any::TypeId;\nuse std::future::Future;\nuse std::pin::Pin;\n#[cfg(feature = \"wasm\")]\npub use tsify;\npub use types::Cow;\n\n// pub trait Node: for<'n> NodeIO<'n> {\n/// The node trait allows for defining any node. Nodes can only take one call argument input, however they can store references to other nodes inside the struct.\n/// See `node-graph/README.md` for information on how to define a new node.\npub trait Node<'i, Input> {\n\ttype Output: 'i;\n\t/// Evaluates the node with the single specified input.\n\tfn eval(&'i self, input: Input) -> Self::Output;\n\t/// Resets the node, e.g. the LetNode's cache is set to None.\n\tfn reset(&self) {}\n\t/// Returns the name of the node for diagnostic purposes.\n\tfn node_name(&self) -> &'static str {\n\t\tstd::any::type_name::<Self>()\n\t}\n\t/// Serialize the node which is used for the `introspect` function which can retrieve values from monitor nodes.\n\tfn serialize(&self) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {\n\t\tlog::warn!(\"Node::serialize not implemented for {}\", std::any::type_name::<Self>());\n\t\tNone\n\t}\n}\n\nmod types;\npub use types::*;\n\npub trait NodeIO<'i, Input>: Node<'i, Input>\nwhere\n\tSelf::Output: 'i + StaticTypeSized,\n\tInput: StaticTypeSized,\n{\n\tfn input_type(&self) -> TypeId {\n\t\tTypeId::of::<Input::Static>()\n\t}\n\tfn input_type_name(&self) -> &'static str {\n\t\tstd::any::type_name::<Input>()\n\t}\n\tfn output_type(&self) -> TypeId {\n\t\tTypeId::of::<<Self::Output as StaticTypeSized>::Static>()\n\t}\n\tfn output_type_name(&self) -> &'static str {\n\t\tstd::any::type_name::<Self::Output>()\n\t}\n\tfn to_node_io(&self, inputs: Vec<Type>) -> NodeIOTypes {\n\t\tNodeIOTypes {\n\t\t\tcall_argument: concrete!(<Input as StaticTypeSized>::Static),\n\t\t\treturn_value: concrete!(<Self::Output as StaticTypeSized>::Static),\n\t\t\tinputs,\n\t\t}\n\t}\n\tfn to_async_node_io(&self, inputs: Vec<Type>) -> NodeIOTypes\n\twhere\n\t\t<Self::Output as Future>::Output: StaticTypeSized,\n\t\tSelf::Output: Future,\n\t{\n\t\tNodeIOTypes {\n\t\t\tcall_argument: concrete!(<Input as StaticTypeSized>::Static),\n\t\t\treturn_value: future!(<<Self::Output as Future>::Output as StaticTypeSized>::Static),\n\t\t\tinputs,\n\t\t}\n\t}\n}\n\nimpl<'i, N: Node<'i, I>, I> NodeIO<'i, I> for N\nwhere\n\tN::Output: 'i + StaticTypeSized,\n\tI: StaticTypeSized,\n{\n}\n\nimpl<'i, I: 'i, N: Node<'i, I> + ?Sized> Node<'i, I> for &'i N {\n\ttype Output = N::Output;\n\tfn eval(&'i self, input: I) -> N::Output {\n\t\t(*self).eval(input)\n\t}\n}\nimpl<'i, I: 'i, O: 'i, N: Node<'i, I, Output = O> + ?Sized> Node<'i, I> for Box<N> {\n\ttype Output = O;\n\tfn eval(&'i self, input: I) -> O {\n\t\t(**self).eval(input)\n\t}\n}\nimpl<'i, I: 'i, O: 'i, N: Node<'i, I, Output = O> + ?Sized> Node<'i, I> for std::sync::Arc<N> {\n\ttype Output = O;\n\tfn eval(&'i self, input: I) -> O {\n\t\t(**self).eval(input)\n\t}\n}\n\nimpl<'i, I, O: 'i> Node<'i, I> for Pin<Box<dyn Node<'i, I, Output = O> + 'i>> {\n\ttype Output = O;\n\tfn eval(&'i self, input: I) -> O {\n\t\t(**self).eval(input)\n\t}\n}\nimpl<'i, I, O: 'i> Node<'i, I> for Pin<&'i (dyn NodeIO<'i, I, Output = O> + 'i)> {\n\ttype Output = O;\n\tfn eval(&'i self, input: I) -> O {\n\t\t(**self).eval(input)\n\t}\n}\n\npub trait InputAccessorSource<'a, T>: InputAccessorSourceIdentifier + std::fmt::Debug {\n\tfn get_input(&'a self, index: usize) -> Option<&'a T>;\n\tfn set_input(&'a mut self, index: usize, value: T);\n}\n\npub trait InputAccessorSourceIdentifier {\n\tfn has_identifier(&self, identifier: &str) -> bool;\n}\n\npub trait InputAccessor<'n, Source: 'n>\nwhere\n\tSelf: Sized,\n{\n\tfn new_with_source(source: &'n Source) -> Option<Self>;\n}\n\npub trait NodeInputDecleration {\n\tconst INDEX: usize;\n\tfn identifier() -> ProtoNodeIdentifier;\n\ttype Result;\n}\n"
  },
  {
    "path": "node-graph/libraries/core-types/src/math/bbox.rs",
    "content": "use dyn_any::DynAny;\nuse glam::{DAffine2, DVec2};\n\n#[derive(Clone, Debug, DynAny)]\npub struct AxisAlignedBbox {\n\tpub start: DVec2,\n\tpub end: DVec2,\n}\n\nimpl AxisAlignedBbox {\n\tpub const ZERO: Self = Self { start: DVec2::ZERO, end: DVec2::ZERO };\n\tpub const ONE: Self = Self { start: DVec2::ZERO, end: DVec2::ONE };\n\n\tpub fn size(&self) -> DVec2 {\n\t\tself.end - self.start\n\t}\n\n\tpub fn to_transform(&self) -> DAffine2 {\n\t\tDAffine2::from_translation(self.start) * DAffine2::from_scale(self.size())\n\t}\n\n\tpub fn contains(&self, point: DVec2) -> bool {\n\t\tpoint.x >= self.start.x && point.x <= self.end.x && point.y >= self.start.y && point.y <= self.end.y\n\t}\n\n\tpub fn intersects(&self, other: &AxisAlignedBbox) -> bool {\n\t\tother.start.x <= self.end.x && other.end.x >= self.start.x && other.start.y <= self.end.y && other.end.y >= self.start.y\n\t}\n\n\tpub fn union(&self, other: &AxisAlignedBbox) -> AxisAlignedBbox {\n\t\tAxisAlignedBbox {\n\t\t\tstart: DVec2::new(self.start.x.min(other.start.x), self.start.y.min(other.start.y)),\n\t\t\tend: DVec2::new(self.end.x.max(other.end.x), self.end.y.max(other.end.y)),\n\t\t}\n\t}\n\tpub fn union_non_empty(&self, other: &AxisAlignedBbox) -> Option<AxisAlignedBbox> {\n\t\tmatch (self.size() == DVec2::ZERO, other.size() == DVec2::ZERO) {\n\t\t\t(true, true) => None,\n\t\t\t(true, _) => Some(other.clone()),\n\t\t\t(_, true) => Some(self.clone()),\n\t\t\t_ => Some(AxisAlignedBbox {\n\t\t\t\tstart: DVec2::new(self.start.x.min(other.start.x), self.start.y.min(other.start.y)),\n\t\t\t\tend: DVec2::new(self.end.x.max(other.end.x), self.end.y.max(other.end.y)),\n\t\t\t}),\n\t\t}\n\t}\n\n\tpub fn intersect(&self, other: &AxisAlignedBbox) -> AxisAlignedBbox {\n\t\tAxisAlignedBbox {\n\t\t\tstart: DVec2::new(self.start.x.max(other.start.x), self.start.y.max(other.start.y)),\n\t\t\tend: DVec2::new(self.end.x.min(other.end.x), self.end.y.min(other.end.y)),\n\t\t}\n\t}\n}\n\nimpl From<(DVec2, DVec2)> for AxisAlignedBbox {\n\tfn from((start, end): (DVec2, DVec2)) -> Self {\n\t\tSelf { start, end }\n\t}\n}\n\n#[derive(Clone, Debug)]\npub struct Bbox {\n\tpub top_left: DVec2,\n\tpub top_right: DVec2,\n\tpub bottom_left: DVec2,\n\tpub bottom_right: DVec2,\n}\n\nimpl Bbox {\n\tpub fn unit() -> Self {\n\t\tSelf {\n\t\t\ttop_left: DVec2::new(0., 1.),\n\t\t\ttop_right: DVec2::new(1., 1.),\n\t\t\tbottom_left: DVec2::new(0., 0.),\n\t\t\tbottom_right: DVec2::new(1., 0.),\n\t\t}\n\t}\n\n\tpub fn from_transform(transform: DAffine2) -> Self {\n\t\tSelf {\n\t\t\ttop_left: transform.transform_point2(DVec2::new(0., 1.)),\n\t\t\ttop_right: transform.transform_point2(DVec2::new(1., 1.)),\n\t\t\tbottom_left: transform.transform_point2(DVec2::new(0., 0.)),\n\t\t\tbottom_right: transform.transform_point2(DVec2::new(1., 0.)),\n\t\t}\n\t}\n\n\tpub fn affine_transform(self, transform: DAffine2) -> Self {\n\t\tSelf {\n\t\t\ttop_left: transform.transform_point2(self.top_left),\n\t\t\ttop_right: transform.transform_point2(self.top_right),\n\t\t\tbottom_left: transform.transform_point2(self.bottom_left),\n\t\t\tbottom_right: transform.transform_point2(self.bottom_right),\n\t\t}\n\t}\n\n\tpub fn to_axis_aligned_bbox(&self) -> AxisAlignedBbox {\n\t\tlet start_x = self.top_left.x.min(self.top_right.x).min(self.bottom_left.x).min(self.bottom_right.x);\n\t\tlet start_y = self.top_left.y.min(self.top_right.y).min(self.bottom_left.y).min(self.bottom_right.y);\n\t\tlet end_x = self.top_left.x.max(self.top_right.x).max(self.bottom_left.x).max(self.bottom_right.x);\n\t\tlet end_y = self.top_left.y.max(self.top_right.y).max(self.bottom_left.y).max(self.bottom_right.y);\n\n\t\tAxisAlignedBbox {\n\t\t\tstart: DVec2::new(start_x, start_y),\n\t\t\tend: DVec2::new(end_x, end_y),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/core-types/src/math/mod.rs",
    "content": "pub mod bbox;\npub mod polynomial;\npub mod quad;\npub mod rect;\n"
  },
  {
    "path": "node-graph/libraries/core-types/src/math/polynomial.rs",
    "content": "use kurbo::PathSeg;\nuse std::fmt::{self, Display, Formatter};\nuse std::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};\n\n/// A struct that represents a polynomial with a maximum degree of `N-1`.\n///\n/// It provides basic mathematical operations for polynomials like addition, multiplication, differentiation, integration, etc.\n#[derive(Copy, Clone, Debug, PartialEq)]\npub struct Polynomial<const N: usize> {\n\tcoefficients: [f64; N],\n}\n\nimpl<const N: usize> Polynomial<N> {\n\t/// Create a new polynomial from the coefficients given in the array.\n\t///\n\t/// The coefficient for nth degree is at the nth index in array. Therefore the order of coefficients are reversed than the usual order for writing polynomials mathematically.\n\tpub fn new(coefficients: [f64; N]) -> Polynomial<N> {\n\t\tPolynomial { coefficients }\n\t}\n\n\t/// Create a polynomial where all its coefficients are zero.\n\tpub fn zero() -> Polynomial<N> {\n\t\tPolynomial { coefficients: [0.; N] }\n\t}\n\n\t/// Return an immutable reference to the coefficients.\n\t///\n\t/// The coefficient for nth degree is at the nth index in array. Therefore the order of coefficients are reversed than the usual order for writing polynomials mathematically.\n\tpub fn coefficients(&self) -> &[f64; N] {\n\t\t&self.coefficients\n\t}\n\n\t/// Return a mutable reference to the coefficients.\n\t///\n\t/// The coefficient for nth degree is at the nth index in array. Therefore the order of coefficients are reversed than the usual order for writing polynomials mathematically.\n\tpub fn coefficients_mut(&mut self) -> &mut [f64; N] {\n\t\t&mut self.coefficients\n\t}\n\n\t/// Evaluate the polynomial at `value`.\n\tpub fn eval(&self, value: f64) -> f64 {\n\t\tself.coefficients.iter().rev().copied().reduce(|acc, x| acc * value + x).unwrap()\n\t}\n\n\t/// Return the same polynomial but with a different maximum degree of `M-1`.\\\n\t///\n\t/// Returns `None` if the polynomial cannot fit in the specified size.\n\tpub fn as_size<const M: usize>(&self) -> Option<Polynomial<M>> {\n\t\tlet mut coefficients = [0.; M];\n\n\t\tif M >= N {\n\t\t\tcoefficients[..N].copy_from_slice(&self.coefficients);\n\t\t} else if self.coefficients.iter().rev().take(N - M).all(|&x| x == 0.) {\n\t\t\tcoefficients.copy_from_slice(&self.coefficients[..M])\n\t\t} else {\n\t\t\treturn None;\n\t\t}\n\n\t\tSome(Polynomial { coefficients })\n\t}\n\n\t/// Computes the derivative in place.\n\tpub fn derivative_mut(&mut self) {\n\t\tself.coefficients.iter_mut().enumerate().for_each(|(index, x)| *x *= index as f64);\n\t\tself.coefficients.rotate_left(1);\n\t}\n\n\t/// Computes the antiderivative at `C = 0` in place.\n\t///\n\t/// Returns `None` if the polynomial is not big enough to accommodate the extra degree.\n\tpub fn antiderivative_mut(&mut self) -> Option<()> {\n\t\tif self.coefficients[N - 1] != 0. {\n\t\t\treturn None;\n\t\t}\n\t\tself.coefficients.rotate_right(1);\n\t\tself.coefficients.iter_mut().enumerate().skip(1).for_each(|(index, x)| *x /= index as f64);\n\t\tSome(())\n\t}\n\n\t/// Computes the polynomial's derivative.\n\tpub fn derivative(&self) -> Polynomial<N> {\n\t\tlet mut ans = *self;\n\t\tans.derivative_mut();\n\t\tans\n\t}\n\n\t/// Computes the antiderivative at `C = 0`.\n\t///\n\t/// Returns `None` if the polynomial is not big enough to accommodate the extra degree.\n\tpub fn antiderivative(&self) -> Option<Polynomial<N>> {\n\t\tlet mut ans = *self;\n\t\tans.antiderivative_mut()?;\n\t\tSome(ans)\n\t}\n}\n\nimpl<const N: usize> Default for Polynomial<N> {\n\tfn default() -> Self {\n\t\tSelf::zero()\n\t}\n}\n\nimpl<const N: usize> Display for Polynomial<N> {\n\tfn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {\n\t\tlet mut first = true;\n\t\tfor (index, coefficient) in self.coefficients.iter().enumerate().rev().filter(|&(_, &coefficient)| coefficient != 0.) {\n\t\t\tif first {\n\t\t\t\tfirst = false;\n\t\t\t} else {\n\t\t\t\tf.write_str(\" + \")?\n\t\t\t}\n\n\t\t\tcoefficient.fmt(f)?;\n\t\t\tif index == 0 {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tf.write_str(\"x\")?;\n\t\t\tif index == 1 {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tf.write_str(\"^\")?;\n\t\t\tindex.fmt(f)?;\n\t\t}\n\n\t\tOk(())\n\t}\n}\n\nimpl<const N: usize> AddAssign<&Polynomial<N>> for Polynomial<N> {\n\tfn add_assign(&mut self, rhs: &Polynomial<N>) {\n\t\tself.coefficients.iter_mut().zip(rhs.coefficients.iter()).for_each(|(a, b)| *a += b);\n\t}\n}\n\nimpl<const N: usize> Add for &Polynomial<N> {\n\ttype Output = Polynomial<N>;\n\n\tfn add(self, other: &Polynomial<N>) -> Polynomial<N> {\n\t\tlet mut output = *self;\n\t\toutput += other;\n\t\toutput\n\t}\n}\n\nimpl<const N: usize> Neg for &Polynomial<N> {\n\ttype Output = Polynomial<N>;\n\n\tfn neg(self) -> Polynomial<N> {\n\t\tlet mut output = *self;\n\t\toutput.coefficients.iter_mut().for_each(|x| *x = -*x);\n\t\toutput\n\t}\n}\n\nimpl<const N: usize> Neg for Polynomial<N> {\n\ttype Output = Polynomial<N>;\n\n\tfn neg(mut self) -> Polynomial<N> {\n\t\tself.coefficients.iter_mut().for_each(|x| *x = -*x);\n\t\tself\n\t}\n}\n\nimpl<const N: usize> SubAssign<&Polynomial<N>> for Polynomial<N> {\n\tfn sub_assign(&mut self, rhs: &Polynomial<N>) {\n\t\tself.coefficients.iter_mut().zip(rhs.coefficients.iter()).for_each(|(a, b)| *a -= b);\n\t}\n}\n\nimpl<const N: usize> Sub for &Polynomial<N> {\n\ttype Output = Polynomial<N>;\n\n\tfn sub(self, other: &Polynomial<N>) -> Polynomial<N> {\n\t\tlet mut output = *self;\n\t\toutput -= other;\n\t\toutput\n\t}\n}\n\nimpl<const N: usize> MulAssign<&Polynomial<N>> for Polynomial<N> {\n\tfn mul_assign(&mut self, rhs: &Polynomial<N>) {\n\t\tfor i in (0..N).rev() {\n\t\t\tself.coefficients[i] = self.coefficients[i] * rhs.coefficients[0];\n\t\t\tfor j in 0..i {\n\t\t\t\tself.coefficients[i] += self.coefficients[j] * rhs.coefficients[i - j];\n\t\t\t}\n\t\t}\n\t}\n}\n\nimpl<const N: usize> Mul for &Polynomial<N> {\n\ttype Output = Polynomial<N>;\n\n\tfn mul(self, other: &Polynomial<N>) -> Polynomial<N> {\n\t\tlet mut output = *self;\n\t\toutput *= other;\n\t\toutput\n\t}\n}\n\n/// Returns two [`Polynomial`]s representing the parametric equations for x and y coordinates of the bezier curve respectively.\n/// The domain of both the equations are from t=0.0 representing the start and t=1.0 representing the end of the bezier curve.\npub fn pathseg_to_parametric_polynomial(segment: PathSeg) -> (Polynomial<4>, Polynomial<4>) {\n\tmatch segment {\n\t\tPathSeg::Line(line) => {\n\t\t\tlet term1 = line.p1 - line.p0;\n\t\t\t(Polynomial::new([line.p0.x, term1.x, 0., 0.]), Polynomial::new([line.p0.y, term1.y, 0., 0.]))\n\t\t}\n\t\tPathSeg::Quad(quad_bez) => {\n\t\t\tlet term1 = 2. * (quad_bez.p1 - quad_bez.p0);\n\t\t\tlet term2 = quad_bez.p0 - 2. * quad_bez.p1.to_vec2() + quad_bez.p2.to_vec2();\n\n\t\t\t(Polynomial::new([quad_bez.p0.x, term1.x, term2.x, 0.]), Polynomial::new([quad_bez.p0.y, term1.y, term2.y, 0.]))\n\t\t}\n\t\tPathSeg::Cubic(cubic_bez) => {\n\t\t\tlet term1 = 3. * (cubic_bez.p1 - cubic_bez.p0);\n\t\t\tlet term2 = 3. * (cubic_bez.p2 - cubic_bez.p1) - term1;\n\t\t\tlet term3 = cubic_bez.p3 - cubic_bez.p0 - term2 - term1;\n\n\t\t\t(\n\t\t\t\tPolynomial::new([cubic_bez.p0.x, term1.x, term2.x, term3.x]),\n\t\t\t\tPolynomial::new([cubic_bez.p0.y, term1.y, term2.y, term3.y]),\n\t\t\t)\n\t\t}\n\t}\n}\n\n#[cfg(test)]\nmod test {\n\tuse super::*;\n\n\t#[test]\n\tfn evaluation() {\n\t\tlet p = Polynomial::new([1., 2., 3.]);\n\n\t\tassert_eq!(p.eval(1.), 6.);\n\t\tassert_eq!(p.eval(2.), 17.);\n\t}\n\n\t#[test]\n\tfn size_change() {\n\t\tlet p1 = Polynomial::new([1., 2., 3.]);\n\t\tlet p2 = Polynomial::new([1., 2., 3., 0.]);\n\n\t\tassert_eq!(p1.as_size(), Some(p2));\n\t\tassert_eq!(p2.as_size(), Some(p1));\n\n\t\tassert_eq!(p2.as_size::<2>(), None);\n\t}\n\n\t#[test]\n\tfn addition_and_subtaction() {\n\t\tlet p1 = Polynomial::new([1., 2., 3.]);\n\t\tlet p2 = Polynomial::new([4., 5., 6.]);\n\n\t\tlet addition = Polynomial::new([5., 7., 9.]);\n\t\tlet subtraction = Polynomial::new([-3., -3., -3.]);\n\n\t\tassert_eq!(&p1 + &p2, addition);\n\t\tassert_eq!(&p1 - &p2, subtraction);\n\t}\n\n\t#[test]\n\tfn multiplication() {\n\t\tlet p1 = Polynomial::new([1., 2., 3.]).as_size().unwrap();\n\t\tlet p2 = Polynomial::new([4., 5., 6.]).as_size().unwrap();\n\n\t\tlet multiplication = Polynomial::new([4., 13., 28., 27., 18.]);\n\n\t\tassert_eq!(&p1 * &p2, multiplication);\n\t}\n\n\t#[test]\n\tfn derivative_and_antiderivative() {\n\t\tlet mut p = Polynomial::new([1., 2., 3.]);\n\t\tlet p_deriv = Polynomial::new([2., 6., 0.]);\n\n\t\tassert_eq!(p.derivative(), p_deriv);\n\n\t\tp.coefficients_mut()[0] = 0.;\n\t\tassert_eq!(p_deriv.antiderivative().unwrap(), p);\n\n\t\tassert_eq!(p.antiderivative(), None);\n\t}\n\n\t#[test]\n\tfn display() {\n\t\tlet p = Polynomial::new([1., 2., 0., 3.]);\n\n\t\tassert_eq!(format!(\"{p:.2}\"), \"3.00x^3 + 2.00x + 1.00\");\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/core-types/src/math/quad.rs",
    "content": "use glam::{DAffine2, DVec2};\n\n#[derive(Debug, Clone, Default, Copy)]\n/// A quad defined by four vertices. Clockwise from the top left:\n///\n/// `top_left`, `top_right`, `bottom_right`, `bottom_left`.\npub struct Quad(pub [DVec2; 4]);\n\nimpl Quad {\n\t/// Get the top left corner of the quad.\n\tpub fn top_left(&self) -> DVec2 {\n\t\tself.0[0]\n\t}\n\n\t/// Get the top right corner of the quad.\n\tpub fn top_right(&self) -> DVec2 {\n\t\tself.0[1]\n\t}\n\n\t/// Get the bottom right corner of the quad.\n\tpub fn bottom_right(&self) -> DVec2 {\n\t\tself.0[2]\n\t}\n\n\t/// Get the bottom left corner of the quad.\n\tpub fn bottom_left(&self) -> DVec2 {\n\t\tself.0[3]\n\t}\n\n\t/// Create a zero-sized quad at the point.\n\tpub fn from_point(point: DVec2) -> Self {\n\t\tSelf([point; 4])\n\t}\n\n\t/// Convert a box defined by two corner points to a quad. The points must be given as `minimum (top left)` then `maximum (bottom right)`.\n\tpub fn from_box(bbox: [DVec2; 2]) -> Self {\n\t\tlet size = bbox[1] - bbox[0];\n\t\tSelf([bbox[0], bbox[0] + size * DVec2::X, bbox[1], bbox[0] + size * DVec2::Y])\n\t}\n\n\t/// Create a quad from the center and offset (distance from center to middle of an edge)\n\tpub fn from_square(center: DVec2, offset: f64) -> Self {\n\t\tSelf::from_box([center - offset, center + offset])\n\t}\n\n\t/// Get all the edges in the quad.\n\tpub fn all_edges(&self) -> [[DVec2; 2]; 4] {\n\t\t[[self.0[0], self.0[1]], [self.0[1], self.0[2]], [self.0[2], self.0[3]], [self.0[3], self.0[0]]]\n\t}\n\n\t/// Get two edges as bases.\n\tpub fn edges(&self) -> [[DVec2; 2]; 2] {\n\t\t[[self.0[0], self.0[1]], [self.0[1], self.0[2]]]\n\t}\n\n\t/// Returns true only if the width and height are both greater than or equal to the given width.\n\tpub fn all_sides_at_least_width(&self, width: f64) -> bool {\n\t\tself.edges().into_iter().all(|[a, b]| (a - b).length_squared() >= width.powi(2))\n\t}\n\n\t/// Generates the axis aligned bounding box of the quad\n\tpub fn bounding_box(&self) -> [DVec2; 2] {\n\t\t[\n\t\t\tself.0.into_iter().reduce(|a, b| a.min(b)).unwrap_or_default(),\n\t\t\tself.0.into_iter().reduce(|a, b| a.max(b)).unwrap_or_default(),\n\t\t]\n\t}\n\n\t/// Gets the center of a quad\n\tpub fn center(&self) -> DVec2 {\n\t\tself.0.iter().sum::<DVec2>() / 4.\n\t}\n\n\t/// Take the outside bounds of two axis aligned rectangles, which are defined by two corner points.\n\tpub fn combine_bounds(a: [DVec2; 2], b: [DVec2; 2]) -> [DVec2; 2] {\n\t\t[a[0].min(b[0]), a[1].max(b[1])]\n\t}\n\n\t/// \"Clip\" bounds of `a` to the limits of `b`.\n\tpub fn clip(a: [DVec2; 2], b: [DVec2; 2]) -> [DVec2; 2] {\n\t\t[\n\t\t\ta[0].max(b[0]), // Constrain min corner\n\t\t\ta[1].min(b[1]), // Constrain max corner\n\t\t]\n\t}\n\n\t/// Expand a quad by a certain amount on all sides.\n\t///\n\t/// Not currently very optimized\n\tpub fn inflate(&self, offset: f64) -> Quad {\n\t\tlet offset = |index_before, index, index_after| {\n\t\t\tlet [point_before, point, point_after]: [DVec2; 3] = [self.0[index_before], self.0[index], self.0[index_after]];\n\t\t\tlet [line_in, line_out] = [point - point_before, point_after - point];\n\t\t\tlet angle = line_in.angle_to(-line_out);\n\t\t\tlet offset_length = offset / (std::f64::consts::FRAC_PI_2 - angle / 2.).cos();\n\t\t\tpoint + (line_in.perp().normalize_or_zero() + line_out.perp().normalize_or_zero()).normalize_or_zero() * offset_length\n\t\t};\n\t\tSelf([offset(3, 0, 1), offset(0, 1, 2), offset(1, 2, 3), offset(2, 3, 0)])\n\t}\n\n\t/// Does this quad contain a point\n\t///\n\t/// Code from https://wrfranklin.org/Research/Short_Notes/pnpoly.html\n\tpub fn contains(&self, p: DVec2) -> bool {\n\t\tlet mut inside = false;\n\t\tfor (i, j) in (0..4).zip([3, 0, 1, 2]) {\n\t\t\tif (self.0[i].y > p.y) != (self.0[j].y > p.y) && p.x < ((self.0[j].x - self.0[i].x) * (p.y - self.0[i].y) / (self.0[j].y - self.0[i].y) + self.0[i].x) {\n\t\t\t\tinside = !inside;\n\t\t\t}\n\t\t}\n\t\tinside\n\t}\n\n\t/// https://www.cs.rpi.edu/~cutler/classes/computationalgeometry/F23/lectures/02_line_segment_intersections.pdf\n\tfn line_intersection_t(a: DVec2, b: DVec2, c: DVec2, d: DVec2) -> (f64, f64) {\n\t\tlet t = ((a.x - c.x) * (c.y - d.y) - (a.y - c.y) * (c.x - d.x)) / ((a.x - b.x) * (c.y - d.y) - (a.y - b.y) * (c.x - d.x));\n\t\tlet u = ((a.x - c.x) * (a.y - b.y) - (a.y - c.y) * (a.x - b.x)) / ((a.x - b.x) * (c.y - d.y) - (a.y - b.y) * (c.x - d.x));\n\n\t\t(t, u)\n\t}\n\n\tfn intersect_lines(a: DVec2, b: DVec2, c: DVec2, d: DVec2) -> Option<DVec2> {\n\t\tlet (t, u) = Self::line_intersection_t(a, b, c, d);\n\t\t((0. ..=1.).contains(&t) && (0. ..=1.).contains(&u)).then(|| a + t * (b - a))\n\t}\n\n\tpub fn intersect_rays(a: DVec2, a_direction: DVec2, b: DVec2, b_direction: DVec2) -> Option<DVec2> {\n\t\tlet (t, u) = Self::line_intersection_t(a, a + a_direction, b, b + b_direction);\n\t\t(t.is_finite() && u.is_finite()).then(|| a + t * a_direction)\n\t}\n\n\tpub fn intersects(&self, other: Quad) -> bool {\n\t\tlet intersects = self\n\t\t\t.all_edges()\n\t\t\t.into_iter()\n\t\t\t.any(|[a, b]| other.all_edges().into_iter().any(|[c, d]| Self::intersect_lines(a, b, c, d).is_some()));\n\t\tself.contains(other.center()) || other.contains(self.center()) || intersects\n\t}\n}\n\nimpl std::ops::Mul<Quad> for DAffine2 {\n\ttype Output = Quad;\n\n\tfn mul(self, rhs: Quad) -> Self::Output {\n\t\tQuad(rhs.0.map(|point| self.transform_point2(point)))\n\t}\n}\n\n#[cfg(test)]\nmod tests {\n\tuse super::*;\n\t#[test]\n\tfn offset_quad() {\n\t\tfn eq(a: Quad, b: Quad) -> bool {\n\t\t\ta.0.iter().zip(b.0).all(|(a, b)| a.abs_diff_eq(b, 0.0001))\n\t\t}\n\n\t\tassert!(eq(Quad::from_box([DVec2::ZERO, DVec2::ONE]).inflate(0.5), Quad::from_box([DVec2::splat(-0.5), DVec2::splat(1.5)])));\n\t\tassert!(eq(Quad::from_box([DVec2::ONE, DVec2::ZERO]).inflate(0.5), Quad::from_box([DVec2::splat(1.5), DVec2::splat(-0.5)])));\n\t\tassert!(eq(\n\t\t\t(DAffine2::from_scale(DVec2::new(-1., 1.)) * Quad::from_box([DVec2::ZERO, DVec2::ONE])).inflate(0.5),\n\t\t\tDAffine2::from_scale(DVec2::new(-1., 1.)) * Quad::from_box([DVec2::splat(-0.5), DVec2::splat(1.5)])\n\t\t));\n\t}\n\t#[test]\n\tfn quad_contains() {\n\t\tassert!(Quad::from_box([DVec2::ZERO, DVec2::ONE]).contains(DVec2::splat(0.5)));\n\t\tassert!(Quad::from_box([DVec2::ONE, DVec2::ZERO]).contains(DVec2::splat(0.5)));\n\t\tassert!(Quad::from_box([DVec2::splat(300.), DVec2::splat(500.)]).contains(DVec2::splat(350.)));\n\t\tassert!((DAffine2::from_scale(DVec2::new(-1., 1.)) * Quad::from_box([DVec2::ZERO, DVec2::ONE])).contains(DVec2::new(-0.5, 0.5)));\n\n\t\tassert!(!Quad::from_box([DVec2::ZERO, DVec2::ONE]).contains(DVec2::new(1., 1.1)));\n\t\tassert!(!Quad::from_box([DVec2::ONE, DVec2::ZERO]).contains(DVec2::new(0.5, -0.01)));\n\t\tassert!(!(DAffine2::from_scale(DVec2::new(-1., 1.)) * Quad::from_box([DVec2::ZERO, DVec2::ONE])).contains(DVec2::splat(0.5)));\n\t}\n\n\t#[test]\n\tfn intersect_lines() {\n\t\tassert_eq!(\n\t\t\tQuad::intersect_lines(DVec2::new(-5., 5.), DVec2::new(5., 5.), DVec2::new(2., 7.), DVec2::new(2., 3.)),\n\t\t\tSome(DVec2::new(2., 5.))\n\t\t);\n\t\tassert_eq!(Quad::intersect_lines(DVec2::new(4., 6.), DVec2::new(4., 5.), DVec2::new(2., 7.), DVec2::new(2., 3.)), None);\n\t\tassert_eq!(Quad::intersect_lines(DVec2::new(-5., 5.), DVec2::new(5., 5.), DVec2::new(2., 7.), DVec2::new(2., 9.)), None);\n\t}\n\t#[test]\n\tfn intersect_quad() {\n\t\tassert!(Quad::from_box([DVec2::ZERO, DVec2::splat(5.)]).intersects(Quad::from_box([DVec2::splat(4.), DVec2::splat(7.)])));\n\t\tassert!(Quad::from_box([DVec2::ZERO, DVec2::splat(5.)]).intersects(Quad::from_box([DVec2::splat(4.), DVec2::splat(4.2)])));\n\t\tassert!(!Quad::from_box([DVec2::ZERO, DVec2::splat(3.)]).intersects(Quad::from_box([DVec2::splat(4.), DVec2::splat(4.2)])));\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/core-types/src/math/rect.rs",
    "content": "use crate::math::quad::Quad;\nuse glam::{DAffine2, DVec2};\n\n#[derive(Debug, Clone, Default, Copy, PartialEq)]\n/// An axis aligned rect defined by two vertices.\npub struct Rect(pub [DVec2; 2]);\n\nimpl Rect {\n\t/// Create a zero sized quad at the point\n\t#[must_use]\n\tpub fn from_point(point: DVec2) -> Self {\n\t\tSelf([point; 2])\n\t}\n\n\t/// Convert a box defined by two corner points to a quad.\n\t#[must_use]\n\tpub fn from_box(bbox: [DVec2; 2]) -> Self {\n\t\tSelf([bbox[0].min(bbox[1]), bbox[0].max(bbox[1])])\n\t}\n\n\t/// Create a quad from the center and offset (distance from center to middle of an edge)\n\t#[must_use]\n\tpub fn from_square(center: DVec2, offset: f64) -> Self {\n\t\tSelf::from_box([center - offset, center + offset])\n\t}\n\n\t/// Create an AABB from an iter of points, returning None if empty.\n\t#[must_use]\n\tpub fn point_iter(points: impl Iterator<Item = DVec2>) -> Option<Self> {\n\t\tlet mut bounds = None;\n\t\tfor point in points {\n\t\t\tlet bounds = bounds.get_or_insert(Self::from_point(point));\n\t\t\tbounds[0] = bounds[0].min(point);\n\t\t\tbounds[1] = bounds[1].max(point);\n\t\t}\n\t\tbounds\n\t}\n\n\t/// Get all the edges in the rect.\n\t#[must_use]\n\tpub fn edges(&self) -> [[DVec2; 2]; 4] {\n\t\tlet corners = [self[0], DVec2::new(self[0].x, self[1].y), self[1], DVec2::new(self[1].y, self[0].x)];\n\t\t[[corners[0], corners[1]], [corners[1], corners[2]], [corners[2], corners[3]], [corners[3], corners[0]]]\n\t}\n\n\t/// Gets the center of a rect\n\t#[must_use]\n\tpub fn center(&self) -> DVec2 {\n\t\tself.0.iter().sum::<DVec2>() / 2.\n\t}\n\n\t/// Take the outside bounds of two axis aligned rectangles, which are defined by two corner points.\n\t#[must_use]\n\tpub fn combine_bounds(a: Self, b: Self) -> Self {\n\t\tSelf::from_box([a[0].min(b[0]), a[1].max(b[1])])\n\t}\n\n\t/// Expand a rect by a certain amount on top/bottom and on left/right\n\t#[must_use]\n\tpub fn expand_by(&self, x: f64, y: f64) -> Self {\n\t\tlet delta = DVec2::new(x, y);\n\t\tSelf::from_box([self[0] - delta, self[1] + delta])\n\t}\n\n\t/// Checks if two rects intersect\n\t#[must_use]\n\tpub fn intersects(&self, other: Self) -> bool {\n\t\tlet [mina, maxa] = [self[0].min(self[1]), self[0].max(self[1])];\n\t\tlet [minb, maxb] = [other[0].min(other[1]), other[0].max(other[1])];\n\t\tmina.x <= maxb.x && minb.x <= maxa.x && mina.y <= maxb.y && minb.y <= maxa.y\n\t}\n\n\t/// Does this rect contain a point\n\t#[must_use]\n\tpub fn contains(&self, p: DVec2) -> bool {\n\t\t(self[0].x < p.x && p.x < self[1].x) && (self[0].y < p.y && p.y < self[1].y)\n\t}\n\n\t#[must_use]\n\tpub fn min(&self) -> DVec2 {\n\t\tself.0[0].min(self.0[1])\n\t}\n\n\t#[must_use]\n\tpub fn max(&self) -> DVec2 {\n\t\tself.0[0].max(self.0[1])\n\t}\n\n\t#[must_use]\n\tpub fn translate(&self, offset: DVec2) -> Self {\n\t\tSelf([self.0[0] + offset, self.0[1] + offset])\n\t}\n}\n\nimpl std::ops::Mul<Rect> for DAffine2 {\n\ttype Output = Quad;\n\n\tfn mul(self, rhs: Rect) -> Self::Output {\n\t\tself * Quad::from_box(rhs.0)\n\t}\n}\n\nimpl std::ops::Index<usize> for Rect {\n\ttype Output = DVec2;\n\tfn index(&self, index: usize) -> &Self::Output {\n\t\t&self.0[index]\n\t}\n}\nimpl std::ops::IndexMut<usize> for Rect {\n\tfn index_mut(&mut self, index: usize) -> &mut Self::Output {\n\t\t&mut self.0[index]\n\t}\n}\n\nimpl From<Rect> for Quad {\n\tfn from(val: Rect) -> Self {\n\t\tQuad::from_box(val.0)\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/core-types/src/memo.rs",
    "content": "use std::hash::DefaultHasher;\nuse std::hash::{Hash, Hasher};\nuse std::ops::Deref;\nuse std::sync::Arc;\n\n/// Stores both what a node was called with and what it returned.\n#[derive(Clone, Debug)]\npub struct IORecord<I, O> {\n\tpub input: I,\n\tpub output: O,\n}\n\n#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]\npub struct MemoHash<T: Hash> {\n\thash: u64,\n\tvalue: Arc<T>,\n}\n\nimpl<'de, T: serde::Deserialize<'de> + Hash> serde::Deserialize<'de> for MemoHash<T> {\n\tfn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n\twhere\n\t\tD: serde::Deserializer<'de>,\n\t{\n\t\tT::deserialize(deserializer).map(|value| Self::new(value))\n\t}\n}\n\nimpl<T: Hash + serde::Serialize> serde::Serialize for MemoHash<T> {\n\tfn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n\twhere\n\t\tS: serde::Serializer,\n\t{\n\t\tself.value.serialize(serializer)\n\t}\n}\n\nimpl<T: Hash> MemoHash<T> {\n\tpub fn new(value: T) -> Self {\n\t\tlet hash = Self::calc_hash(&value);\n\t\tSelf { hash, value: value.into() }\n\t}\n\tpub fn new_with_hash(value: T, hash: u64) -> Self {\n\t\tSelf { hash, value: value.into() }\n\t}\n\n\tfn calc_hash(data: &T) -> u64 {\n\t\tlet mut hasher = DefaultHasher::new();\n\t\tdata.hash(&mut hasher);\n\t\thasher.finish()\n\t}\n\n\tpub fn inner_mut(&mut self) -> MemoHashGuard<'_, T> {\n\t\tMemoHashGuard { inner: self }\n\t}\n\tpub fn into_inner(self) -> Arc<T> {\n\t\tself.value\n\t}\n\tpub fn hash_code(&self) -> u64 {\n\t\tself.hash\n\t}\n}\nimpl<T: Hash> From<T> for MemoHash<T> {\n\tfn from(value: T) -> Self {\n\t\tSelf::new(value)\n\t}\n}\n\nimpl<T: Hash> Hash for MemoHash<T> {\n\tfn hash<H: Hasher>(&self, state: &mut H) {\n\t\tself.hash.hash(state)\n\t}\n}\n\nimpl<T: Hash> Deref for MemoHash<T> {\n\ttype Target = T;\n\n\tfn deref(&self) -> &Self::Target {\n\t\t&self.value\n\t}\n}\n\npub struct MemoHashGuard<'a, T: Hash> {\n\tinner: &'a mut MemoHash<T>,\n}\n\nimpl<T: Hash> Drop for MemoHashGuard<'_, T> {\n\tfn drop(&mut self) {\n\t\tlet hash = MemoHash::<T>::calc_hash(&self.inner.value);\n\t\tself.inner.hash = hash;\n\t}\n}\n\nimpl<T: Hash> Deref for MemoHashGuard<'_, T> {\n\ttype Target = T;\n\n\tfn deref(&self) -> &Self::Target {\n\t\t&self.inner.value\n\t}\n}\n\nimpl<T: Hash + Clone> std::ops::DerefMut for MemoHashGuard<'_, T> {\n\tfn deref_mut(&mut self) -> &mut Self::Target {\n\t\tArc::make_mut(&mut self.inner.value)\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/core-types/src/misc.rs",
    "content": "// TODO(TrueDoctor): Replace this with the more idiomatic approach instead of using `trait Clampable`.\n\n/// A trait for types that can be clamped within a min/max range defined by f64.\npub trait Clampable: Sized {\n\t/// Clamps the value to be no less than `min`.\n\tfn clamp_hard_min(self, min: f64) -> Self;\n\t/// Clamps the value to be no more than `max`.\n\tfn clamp_hard_max(self, max: f64) -> Self;\n}\n\n// Implement for common numeric types\nmacro_rules! impl_clampable_float {\n\t($($ty:ty),*) => {\n\t\t$(\n\t\t\timpl Clampable for $ty {\n\t\t\t\t#[inline(always)]\n\t\t\t\tfn clamp_hard_min(self, min: f64) -> Self {\n\t\t\t\t\tself.max(min as $ty)\n\t\t\t\t}\n\t\t\t\t#[inline(always)]\n\t\t\t\tfn clamp_hard_max(self, max: f64) -> Self {\n\t\t\t\t\tself.min(max as $ty)\n\t\t\t\t}\n\t\t\t}\n\t\t)*\n\t};\n}\nimpl_clampable_float!(f32, f64);\n\nmacro_rules! impl_clampable_int {\n\t($($ty:ty),*) => {\n\t\t$(\n\t\t\timpl Clampable for $ty {\n\t\t\t\t#[inline(always)]\n\t\t\t\tfn clamp_hard_min(self, min: f64) -> Self {\n\t\t\t\t\t// Using try_from to handle potential range issues safely, though min should ideally be valid.\n\t\t\t\t\t// Consider using a different approach if f64 precision vs integer range is a concern.\n\t\t\t\t\t<$ty>::try_from(min.ceil() as i64).ok().map_or(self, |min_val| self.max(min_val))\n\t\t\t\t}\n\t\t\t\t#[inline(always)]\n\t\t\t\tfn clamp_hard_max(self, max: f64) -> Self {\n\t\t\t\t\t<$ty>::try_from(max.floor() as i64).ok().map_or(self, |max_val| self.min(max_val))\n\t\t\t\t}\n\t\t\t}\n\t\t)*\n\t};\n}\n// Add relevant integer types (adjust as needed)\nimpl_clampable_int!(u32, u64, i32, i64);\n\n// Implement for DVec2 (component-wise clamping)\nuse glam::DVec2;\nimpl Clampable for DVec2 {\n\t#[inline(always)]\n\tfn clamp_hard_min(self, min: f64) -> Self {\n\t\tself.max(DVec2::splat(min))\n\t}\n\t#[inline(always)]\n\tfn clamp_hard_max(self, max: f64) -> Self {\n\t\tself.min(DVec2::splat(max))\n\t}\n}\n\n// TODO: Eventually remove this migration document upgrade code\npub fn migrate_color<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<crate::table::Table<no_std_types::color::Color>, D::Error> {\n\tuse crate::table::Table;\n\tuse no_std_types::color::Color;\n\tuse serde::Deserialize;\n\n\t#[derive(serde::Serialize, serde::Deserialize)]\n\t#[serde(untagged)]\n\tenum ColorFormat {\n\t\tColor(Color),\n\t\tOptionalColor(Option<Color>),\n\t\tColorTable(Table<Color>),\n\t}\n\n\tOk(match ColorFormat::deserialize(deserializer)? {\n\t\tColorFormat::Color(color) => Table::new_from_element(color),\n\t\tColorFormat::OptionalColor(color) => {\n\t\t\tif let Some(color) = color {\n\t\t\t\tTable::new_from_element(color)\n\t\t\t} else {\n\t\t\t\tTable::new()\n\t\t\t}\n\t\t}\n\t\tColorFormat::ColorTable(color_table) => color_table,\n\t})\n}\n"
  },
  {
    "path": "node-graph/libraries/core-types/src/ops.rs",
    "content": "use crate::Node;\nuse crate::table::{Table, TableRow};\nuse crate::transform::Footprint;\nuse glam::DVec2;\nuse std::future::Future;\nuse std::marker::PhantomData;\n\n// Type\n// TODO: Document this\n#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]\npub struct TypeNode<N: for<'a> Node<'a, I>, I, O>(pub N, pub PhantomData<(I, O)>);\nimpl<'i, N, I: 'i, O: 'i> Node<'i, I> for TypeNode<N, I, O>\nwhere\n\tN: for<'n> Node<'n, I, Output = O>,\n{\n\ttype Output = O;\n\tfn eval(&'i self, input: I) -> Self::Output {\n\t\tself.0.eval(input)\n\t}\n\n\tfn reset(&self) {\n\t\tself.0.reset();\n\t}\n\n\tfn serialize(&self) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {\n\t\tself.0.serialize()\n\t}\n}\nimpl<'i, N: for<'a> Node<'a, I>, I: 'i> TypeNode<N, I, <N as Node<'i, I>>::Output> {\n\tpub fn new(node: N) -> Self {\n\t\tSelf(node, PhantomData)\n\t}\n}\nimpl<'i, N: for<'a> Node<'a, I> + Clone, I: 'i> Clone for TypeNode<N, I, <N as Node<'i, I>>::Output> {\n\tfn clone(&self) -> Self {\n\t\tSelf(self.0.clone(), self.1)\n\t}\n}\nimpl<'i, N: for<'a> Node<'a, I> + Copy, I: 'i> Copy for TypeNode<N, I, <N as Node<'i, I>>::Output> {}\n\n/// The [`Convert`] trait allows for conversion between Rust primitive numeric types.\n/// Because number casting is lossy, we cannot use the normal [`Into`] trait like we do for other types.\npub trait Convert<T, C>: Sized {\n\t/// Converts this type into the (usually inferred) output type.\n\t#[must_use]\n\tfn convert(self, footprint: Footprint, converter: C) -> impl Future<Output = T> + Send;\n}\n\nimpl<T: ToString + Send> Convert<String, ()> for T {\n\t/// Converts this type into a `String` using its `ToString` implementation.\n\t#[inline]\n\tasync fn convert(self, _: Footprint, _converter: ()) -> String {\n\t\tself.to_string()\n\t}\n}\n\npub trait TableConvert<U> {\n\tfn convert_row(self) -> U;\n}\n\nimpl<U, T: TableConvert<U> + Send> Convert<Table<U>, ()> for Table<T> {\n\tasync fn convert(self, _: Footprint, _: ()) -> Table<U> {\n\t\tlet table: Table<U> = self\n\t\t\t.into_iter()\n\t\t\t.map(|row| TableRow {\n\t\t\t\telement: row.element.convert_row(),\n\t\t\t\ttransform: row.transform,\n\t\t\t\talpha_blending: row.alpha_blending,\n\t\t\t\tsource_node_id: row.source_node_id,\n\t\t\t})\n\t\t\t.collect();\n\t\ttable\n\t}\n}\n\nimpl Convert<DVec2, ()> for DVec2 {\n\tasync fn convert(self, _: Footprint, _: ()) -> DVec2 {\n\t\tself\n\t}\n}\n\n// TODO: Add a DVec2 to Table<Vector> anchor point conversion implementation to replace the 'Vec2 to Point' node\n\n/// Implements the [`Convert`] trait for conversion between the cartesian product of Rust's primitive numeric types.\nmacro_rules! impl_convert {\n\t($from:ty, $to:ty) => {\n\t\timpl Convert<$to, ()> for $from {\n\t\t\tasync fn convert(self, _: Footprint, _: ()) -> $to {\n\t\t\t\tself as $to\n\t\t\t}\n\t\t}\n\t};\n\t($to:ty) => {\n\t\timpl_convert!(f32, $to);\n\t\timpl_convert!(f64, $to);\n\t\timpl_convert!(i8, $to);\n\t\timpl_convert!(u8, $to);\n\t\timpl_convert!(u16, $to);\n\t\timpl_convert!(i16, $to);\n\t\timpl_convert!(i32, $to);\n\t\timpl_convert!(u32, $to);\n\t\timpl_convert!(i64, $to);\n\t\timpl_convert!(u64, $to);\n\t\timpl_convert!(i128, $to);\n\t\timpl_convert!(u128, $to);\n\t\timpl_convert!(isize, $to);\n\t\timpl_convert!(usize, $to);\n\n\t\timpl Convert<DVec2, ()> for $to {\n\t\t\tasync fn convert(self, _: Footprint, _: ()) -> DVec2 {\n\t\t\t\tDVec2::splat(self as f64)\n\t\t\t}\n\t\t}\n\t};\n}\nimpl_convert!(f32);\nimpl_convert!(f64);\nimpl_convert!(i8);\nimpl_convert!(u8);\nimpl_convert!(u16);\nimpl_convert!(i16);\nimpl_convert!(i32);\nimpl_convert!(u32);\nimpl_convert!(i64);\nimpl_convert!(u64);\nimpl_convert!(i128);\nimpl_convert!(u128);\nimpl_convert!(isize);\nimpl_convert!(usize);\n"
  },
  {
    "path": "node-graph/libraries/core-types/src/registry.rs",
    "content": "use crate::{ContextFeature, Node, NodeIO, NodeIOTypes, ProtoNodeIdentifier, Type, WasmNotSend};\nuse dyn_any::{DynAny, StaticType};\npub use no_std_types::registry::types;\nuse std::collections::HashMap;\nuse std::marker::PhantomData;\nuse std::ops::Deref;\nuse std::pin::Pin;\nuse std::sync::{LazyLock, Mutex};\n\n// Translation struct between macro and definition\n#[derive(Clone, Debug)]\npub struct NodeMetadata {\n\tpub display_name: &'static str,\n\tpub category: &'static str,\n\tpub fields: Vec<FieldMetadata>,\n\tpub description: &'static str,\n\tpub properties: Option<&'static str>,\n\tpub context_features: Vec<ContextFeature>,\n}\n\n// Translation struct between macro and definition\n#[derive(Clone, Debug)]\npub struct FieldMetadata {\n\tpub name: &'static str,\n\tpub description: &'static str,\n\tpub hidden: bool,\n\tpub exposed: bool,\n\tpub widget_override: RegistryWidgetOverride,\n\tpub value_source: RegistryValueSource,\n\tpub default_type: Option<Type>,\n\tpub number_min: Option<f64>,\n\tpub number_max: Option<f64>,\n\tpub number_mode_range: Option<(f64, f64)>,\n\tpub number_display_decimal_places: Option<u32>,\n\tpub number_step: Option<f64>,\n\tpub unit: Option<&'static str>,\n}\n\n#[derive(Clone, Debug)]\npub enum RegistryWidgetOverride {\n\tNone,\n\tHidden,\n\tString(&'static str),\n\tCustom(&'static str),\n}\n\n#[derive(Clone, Debug)]\npub enum RegistryValueSource {\n\tNone,\n\tDefault(&'static str),\n\tScope(&'static str),\n}\n\ntype NodeRegistry = LazyLock<Mutex<HashMap<ProtoNodeIdentifier, Vec<(NodeConstructor, NodeIOTypes)>>>>;\n\npub static NODE_REGISTRY: NodeRegistry = LazyLock::new(|| Mutex::new(HashMap::new()));\n\npub static NODE_METADATA: LazyLock<Mutex<HashMap<ProtoNodeIdentifier, NodeMetadata>>> = LazyLock::new(|| Mutex::new(HashMap::new()));\n\n#[cfg(not(target_family = \"wasm\"))]\npub type DynFuture<'n, T> = Pin<Box<dyn Future<Output = T> + 'n + Send>>;\n#[cfg(target_family = \"wasm\")]\npub type DynFuture<'n, T> = Pin<Box<dyn std::future::Future<Output = T> + 'n>>;\npub type LocalFuture<'n, T> = Pin<Box<dyn Future<Output = T> + 'n>>;\n#[cfg(not(target_family = \"wasm\"))]\npub type Any<'n> = Box<dyn DynAny<'n> + 'n + Send>;\n#[cfg(target_family = \"wasm\")]\npub type Any<'n> = Box<dyn DynAny<'n> + 'n>;\npub type FutureAny<'n> = DynFuture<'n, Any<'n>>;\n// TODO: is this safe? This is assumed to be send+sync.\n#[cfg(not(target_family = \"wasm\"))]\npub type TypeErasedNode<'n> = dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n + Send + Sync;\n#[cfg(target_family = \"wasm\")]\npub type TypeErasedNode<'n> = dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n;\npub type TypeErasedPinnedRef<'n> = Pin<&'n TypeErasedNode<'n>>;\npub type TypeErasedRef<'n> = &'n TypeErasedNode<'n>;\npub type TypeErasedBox<'n> = Box<TypeErasedNode<'n>>;\npub type TypeErasedPinned<'n> = Pin<Box<TypeErasedNode<'n>>>;\n\npub type SharedNodeContainer = std::sync::Arc<NodeContainer>;\n\npub type NodeConstructor = fn(Vec<SharedNodeContainer>) -> DynFuture<'static, TypeErasedBox<'static>>;\n\n#[derive(Clone)]\npub struct NodeContainer {\n\t#[cfg(feature = \"dealloc_nodes\")]\n\tpub node: *const TypeErasedNode<'static>,\n\t#[cfg(not(feature = \"dealloc_nodes\"))]\n\tpub node: TypeErasedRef<'static>,\n}\n\nimpl Deref for NodeContainer {\n\ttype Target = TypeErasedNode<'static>;\n\n\t#[cfg(feature = \"dealloc_nodes\")]\n\tfn deref(&self) -> &Self::Target {\n\t\tunsafe { &*(self.node) }\n\t\t#[cfg(not(feature = \"dealloc_nodes\"))]\n\t\tself.node\n\t}\n\t#[cfg(not(feature = \"dealloc_nodes\"))]\n\tfn deref(&self) -> &Self::Target {\n\t\tself.node\n\t}\n}\n\n/// # Safety\n/// Marks NodeContainer as Sync. This dissallows the use of threadlocal storage for nodes as this would invalidate references to them.\n// TODO: implement this on a higher level wrapper to avoid missuse\n#[cfg(feature = \"dealloc_nodes\")]\nunsafe impl Send for NodeContainer {}\n#[cfg(feature = \"dealloc_nodes\")]\nunsafe impl Sync for NodeContainer {}\n\n#[cfg(feature = \"dealloc_nodes\")]\nimpl Drop for NodeContainer {\n\tfn drop(&mut self) {\n\t\tunsafe { self.dealloc_unchecked() }\n\t}\n}\n\nimpl std::fmt::Debug for NodeContainer {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tf.debug_struct(\"NodeContainer\").finish()\n\t}\n}\n\nimpl NodeContainer {\n\tpub fn new(node: TypeErasedBox<'static>) -> SharedNodeContainer {\n\t\tlet node = Box::leak(node);\n\t\tSelf { node }.into()\n\t}\n\n\t#[cfg(feature = \"dealloc_nodes\")]\n\tunsafe fn dealloc_unchecked(&mut self) {\n\t\tunsafe {\n\t\t\tdrop(Box::from_raw(self.node as *mut TypeErasedNode));\n\t\t}\n\t}\n}\n\n/// Boxes the input and downcasts the output.\n/// Wraps around a node taking Box<dyn DynAny> and returning Box<dyn DynAny>\n#[derive(Clone)]\npub struct DowncastBothNode<I, O> {\n\tnode: SharedNodeContainer,\n\t_i: PhantomData<I>,\n\t_o: PhantomData<O>,\n}\nimpl<'input, O, I> Node<'input, I> for DowncastBothNode<I, O>\nwhere\n\tO: 'input + StaticType + WasmNotSend,\n\tI: 'input + StaticType + WasmNotSend,\n{\n\ttype Output = DynFuture<'input, O>;\n\t#[inline]\n\t#[track_caller]\n\tfn eval(&'input self, input: I) -> Self::Output {\n\t\t{\n\t\t\tlet node_name = self.node.node_name();\n\t\t\tlet input = Box::new(input);\n\t\t\tlet future = self.node.eval(input);\n\t\t\tBox::pin(async move {\n\t\t\t\tlet out = dyn_any::downcast(future.await).unwrap_or_else(|e| panic!(\"DowncastBothNode wrong output type: {e} in: \\n{node_name}\"));\n\t\t\t\t*out\n\t\t\t})\n\t\t}\n\t}\n\tfn reset(&self) {\n\t\tself.node.reset();\n\t}\n\n\tfn serialize(&self) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {\n\t\tself.node.serialize()\n\t}\n}\nimpl<I, O> DowncastBothNode<I, O> {\n\tpub const fn new(node: SharedNodeContainer) -> Self {\n\t\tSelf {\n\t\t\tnode,\n\t\t\t_i: PhantomData,\n\t\t\t_o: PhantomData,\n\t\t}\n\t}\n}\npub struct FutureWrapperNode<Node> {\n\tnode: Node,\n}\n\nimpl<'i, T: 'i + WasmNotSend, N> Node<'i, T> for FutureWrapperNode<N>\nwhere\n\tN: Node<'i, T, Output: WasmNotSend> + WasmNotSend,\n{\n\ttype Output = DynFuture<'i, N::Output>;\n\t#[inline(always)]\n\tfn eval(&'i self, input: T) -> Self::Output {\n\t\tlet result = self.node.eval(input);\n\t\tBox::pin(async move { result })\n\t}\n\t#[inline(always)]\n\tfn reset(&self) {\n\t\tself.node.reset();\n\t}\n\n\t#[inline(always)]\n\tfn serialize(&self) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {\n\t\tself.node.serialize()\n\t}\n}\n\nimpl<N> FutureWrapperNode<N> {\n\tpub const fn new(node: N) -> Self {\n\t\tSelf { node }\n\t}\n}\n\npub struct DynAnyNode<I, O, Node> {\n\tnode: Node,\n\t_i: PhantomData<I>,\n\t_o: PhantomData<O>,\n}\n\nimpl<'input, I, O, N> Node<'input, Any<'input>> for DynAnyNode<I, O, N>\nwhere\n\tI: 'input + StaticType + WasmNotSend,\n\tO: 'input + StaticType + WasmNotSend,\n\tN: 'input + Node<'input, I, Output = DynFuture<'input, O>>,\n{\n\ttype Output = FutureAny<'input>;\n\t#[inline]\n\tfn eval(&'input self, input: Any<'input>) -> Self::Output {\n\t\tlet node_name = std::any::type_name::<N>();\n\t\tlet output = |input| {\n\t\t\tlet result = self.node.eval(input);\n\t\t\tasync move { Box::new(result.await) as Any<'input> }\n\t\t};\n\t\tmatch dyn_any::downcast(input) {\n\t\t\tOk(input) => Box::pin(output(*input)),\n\t\t\tErr(e) => panic!(\"DynAnyNode Input, {e} in:\\n{node_name}\"),\n\t\t}\n\t}\n\n\tfn reset(&self) {\n\t\tself.node.reset();\n\t}\n\n\tfn serialize(&self) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {\n\t\tself.node.serialize()\n\t}\n}\nimpl<'input, I, O, N> DynAnyNode<I, O, N>\nwhere\n\tI: 'input + StaticType,\n\tO: 'input + StaticType,\n\tN: 'input + Node<'input, I, Output = DynFuture<'input, O>>,\n{\n\tpub const fn new(node: N) -> Self {\n\t\tSelf {\n\t\t\tnode,\n\t\t\t_i: PhantomData,\n\t\t\t_o: PhantomData,\n\t\t}\n\t}\n}\npub struct PanicNode<I: WasmNotSend, O: WasmNotSend>(PhantomData<I>, PhantomData<O>);\n\nimpl<'i, I: 'i + WasmNotSend, O: 'i + WasmNotSend> Node<'i, I> for PanicNode<I, O> {\n\ttype Output = O;\n\tfn eval(&'i self, _: I) -> Self::Output {\n\t\tunimplemented!(\"This node should never be evaluated\")\n\t}\n}\n\nimpl<I: WasmNotSend, O: WasmNotSend> PanicNode<I, O> {\n\tpub const fn new() -> Self {\n\t\tSelf(PhantomData, PhantomData)\n\t}\n}\n\nimpl<I: WasmNotSend, O: WasmNotSend> Default for PanicNode<I, O> {\n\tfn default() -> Self {\n\t\tSelf::new()\n\t}\n}\n\n// TODO: Evaluate safety\nunsafe impl<I: WasmNotSend, O: WasmNotSend> Sync for PanicNode<I, O> {}\n"
  },
  {
    "path": "node-graph/libraries/core-types/src/render_complexity.rs",
    "content": "// Raster types moved to raster-types crate\nuse crate::Color;\nuse crate::table::Table;\n\npub trait RenderComplexity {\n\tfn render_complexity(&self) -> usize {\n\t\t0\n\t}\n}\n\nimpl<T: RenderComplexity> RenderComplexity for Table<T> {\n\tfn render_complexity(&self) -> usize {\n\t\tself.iter().map(|row| row.element.render_complexity()).fold(0, usize::saturating_add)\n\t}\n}\n\nimpl RenderComplexity for Color {\n\tfn render_complexity(&self) -> usize {\n\t\t1\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/core-types/src/table.rs",
    "content": "use crate::bounds::{BoundingBox, RenderBoundingBox};\nuse crate::transform::ApplyTransform;\nuse crate::uuid::NodeId;\nuse crate::{AlphaBlending, math::quad::Quad};\nuse dyn_any::{StaticType, StaticTypeSized};\nuse glam::DAffine2;\nuse std::hash::Hash;\n\n#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]\npub struct Table<T> {\n\t#[serde(alias = \"instances\", alias = \"instance\")]\n\telement: Vec<T>,\n\ttransform: Vec<DAffine2>,\n\talpha_blending: Vec<AlphaBlending>,\n\tsource_node_id: Vec<Option<NodeId>>,\n}\n\nimpl<T> Table<T> {\n\tpub fn new() -> Self {\n\t\tSelf::default()\n\t}\n\n\tpub fn with_capacity(capacity: usize) -> Self {\n\t\tSelf {\n\t\t\telement: Vec::with_capacity(capacity),\n\t\t\ttransform: Vec::with_capacity(capacity),\n\t\t\talpha_blending: Vec::with_capacity(capacity),\n\t\t\tsource_node_id: Vec::with_capacity(capacity),\n\t\t}\n\t}\n\n\tpub fn new_from_element(element: T) -> Self {\n\t\tSelf {\n\t\t\telement: vec![element],\n\t\t\ttransform: vec![DAffine2::IDENTITY],\n\t\t\talpha_blending: vec![AlphaBlending::default()],\n\t\t\tsource_node_id: vec![None],\n\t\t}\n\t}\n\n\tpub fn new_from_row(row: TableRow<T>) -> Self {\n\t\tSelf {\n\t\t\telement: vec![row.element],\n\t\t\ttransform: vec![row.transform],\n\t\t\talpha_blending: vec![row.alpha_blending],\n\t\t\tsource_node_id: vec![row.source_node_id],\n\t\t}\n\t}\n\n\tpub fn push(&mut self, row: TableRow<T>) {\n\t\tself.element.push(row.element);\n\t\tself.transform.push(row.transform);\n\t\tself.alpha_blending.push(row.alpha_blending);\n\t\tself.source_node_id.push(row.source_node_id);\n\t}\n\n\tpub fn extend(&mut self, table: Table<T>) {\n\t\tself.element.extend(table.element);\n\t\tself.transform.extend(table.transform);\n\t\tself.alpha_blending.extend(table.alpha_blending);\n\t\tself.source_node_id.extend(table.source_node_id);\n\t}\n\n\tpub fn get(&self, index: usize) -> Option<TableRowRef<'_, T>> {\n\t\tif index >= self.element.len() {\n\t\t\treturn None;\n\t\t}\n\n\t\tSome(TableRowRef {\n\t\t\telement: &self.element[index],\n\t\t\ttransform: &self.transform[index],\n\t\t\talpha_blending: &self.alpha_blending[index],\n\t\t\tsource_node_id: &self.source_node_id[index],\n\t\t})\n\t}\n\n\tpub fn get_mut(&mut self, index: usize) -> Option<TableRowMut<'_, T>> {\n\t\tif index >= self.element.len() {\n\t\t\treturn None;\n\t\t}\n\n\t\tSome(TableRowMut {\n\t\t\telement: &mut self.element[index],\n\t\t\ttransform: &mut self.transform[index],\n\t\t\talpha_blending: &mut self.alpha_blending[index],\n\t\t\tsource_node_id: &mut self.source_node_id[index],\n\t\t})\n\t}\n\n\tpub fn len(&self) -> usize {\n\t\tself.element.len()\n\t}\n\n\tpub fn is_empty(&self) -> bool {\n\t\tself.element.is_empty()\n\t}\n\n\t/// Borrows a [`Table`] and returns an iterator of [`TableRowRef`]s, each containing references to the data of the respective row from the table.\n\tpub fn iter(&self) -> impl DoubleEndedIterator<Item = TableRowRef<'_, T>> + Clone {\n\t\tself.element\n\t\t\t.iter()\n\t\t\t.zip(self.transform.iter())\n\t\t\t.zip(self.alpha_blending.iter())\n\t\t\t.zip(self.source_node_id.iter())\n\t\t\t.map(|(((element, transform), alpha_blending), source_node_id)| TableRowRef {\n\t\t\t\telement,\n\t\t\t\ttransform,\n\t\t\t\talpha_blending,\n\t\t\t\tsource_node_id,\n\t\t\t})\n\t}\n\n\t/// Mutably borrows a [`Table`] and returns an iterator of [`TableRowMut`]s, each containing mutable references to the data of the respective row from the table.\n\tpub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = TableRowMut<'_, T>> {\n\t\tself.element\n\t\t\t.iter_mut()\n\t\t\t.zip(self.transform.iter_mut())\n\t\t\t.zip(self.alpha_blending.iter_mut())\n\t\t\t.zip(self.source_node_id.iter_mut())\n\t\t\t.map(|(((element, transform), alpha_blending), source_node_id)| TableRowMut {\n\t\t\t\telement,\n\t\t\t\ttransform,\n\t\t\t\talpha_blending,\n\t\t\t\tsource_node_id,\n\t\t\t})\n\t}\n}\n\nimpl<T: BoundingBox> BoundingBox for Table<T> {\n\tfn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox {\n\t\tlet mut combined_bounds = None;\n\n\t\tfor row in self.iter() {\n\t\t\tmatch row.element.bounding_box(transform * *row.transform, include_stroke) {\n\t\t\t\tRenderBoundingBox::None => continue,\n\t\t\t\tRenderBoundingBox::Infinite => return RenderBoundingBox::Infinite,\n\t\t\t\tRenderBoundingBox::Rectangle(bounds) => match combined_bounds {\n\t\t\t\t\tSome(existing) => combined_bounds = Some(Quad::combine_bounds(existing, bounds)),\n\t\t\t\t\tNone => combined_bounds = Some(bounds),\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\n\t\tmatch combined_bounds {\n\t\t\tSome(bounds) => RenderBoundingBox::Rectangle(bounds),\n\t\t\tNone => RenderBoundingBox::None,\n\t\t}\n\t}\n}\n\nimpl<T> IntoIterator for Table<T> {\n\ttype Item = TableRow<T>;\n\ttype IntoIter = TableRowIter<T>;\n\n\t/// Consumes a [`Table`] and returns an iterator of [`TableRow`]s, each containing the owned data of the respective row from the original table.\n\tfn into_iter(self) -> Self::IntoIter {\n\t\tTableRowIter {\n\t\t\telement: self.element.into_iter(),\n\t\t\ttransform: self.transform.into_iter(),\n\t\t\talpha_blending: self.alpha_blending.into_iter(),\n\t\t\tsource_node_id: self.source_node_id.into_iter(),\n\t\t}\n\t}\n}\n\npub struct TableRowIter<T> {\n\telement: std::vec::IntoIter<T>,\n\ttransform: std::vec::IntoIter<DAffine2>,\n\talpha_blending: std::vec::IntoIter<AlphaBlending>,\n\tsource_node_id: std::vec::IntoIter<Option<NodeId>>,\n}\nimpl<T> Iterator for TableRowIter<T> {\n\ttype Item = TableRow<T>;\n\n\tfn next(&mut self) -> Option<Self::Item> {\n\t\tlet element = self.element.next()?;\n\t\tlet transform = self.transform.next()?;\n\t\tlet alpha_blending = self.alpha_blending.next()?;\n\t\tlet source_node_id = self.source_node_id.next()?;\n\n\t\tSome(TableRow {\n\t\t\telement,\n\t\t\ttransform,\n\t\t\talpha_blending,\n\t\t\tsource_node_id,\n\t\t})\n\t}\n}\n\nimpl<T> Default for Table<T> {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\telement: Vec::new(),\n\t\t\ttransform: Vec::new(),\n\t\t\talpha_blending: Vec::new(),\n\t\t\tsource_node_id: Vec::new(),\n\t\t}\n\t}\n}\n\nimpl<T: Hash> Hash for Table<T> {\n\tfn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n\t\tfor element in &self.element {\n\t\t\telement.hash(state);\n\t\t}\n\t\tfor transform in &self.transform {\n\t\t\ttransform.to_cols_array().map(|x| x.to_bits()).hash(state);\n\t\t}\n\t\tfor alpha_blending in &self.alpha_blending {\n\t\t\talpha_blending.hash(state);\n\t\t}\n\t}\n}\n\nimpl<T: PartialEq> PartialEq for Table<T> {\n\tfn eq(&self, other: &Self) -> bool {\n\t\tself.element == other.element && self.transform == other.transform && self.alpha_blending == other.alpha_blending\n\t}\n}\n\nimpl<T> ApplyTransform for Table<T> {\n\tfn apply_transform(&mut self, modification: &DAffine2) {\n\t\tfor transform in &mut self.transform {\n\t\t\t*transform *= *modification;\n\t\t}\n\t}\n\n\tfn left_apply_transform(&mut self, modification: &DAffine2) {\n\t\tfor transform in &mut self.transform {\n\t\t\t*transform = *modification * *transform;\n\t\t}\n\t}\n}\n\nunsafe impl<T: StaticTypeSized> StaticType for Table<T> {\n\ttype Static = Table<T::Static>;\n}\n\nimpl<T> FromIterator<TableRow<T>> for Table<T> {\n\tfn from_iter<I: IntoIterator<Item = TableRow<T>>>(iter: I) -> Self {\n\t\tlet iter = iter.into_iter();\n\t\tlet (lower, _) = iter.size_hint();\n\t\tlet mut table = Self::with_capacity(lower);\n\t\tfor row in iter {\n\t\t\ttable.push(row);\n\t\t}\n\t\ttable\n\t}\n}\n\n#[derive(Copy, Clone, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct TableRow<T> {\n\t#[serde(alias = \"instance\")]\n\tpub element: T,\n\tpub transform: DAffine2,\n\tpub alpha_blending: AlphaBlending,\n\tpub source_node_id: Option<NodeId>,\n}\n\nimpl<T> TableRow<T> {\n\tpub fn new_from_element(element: T) -> Self {\n\t\tSelf {\n\t\t\telement,\n\t\t\ttransform: DAffine2::IDENTITY,\n\t\t\talpha_blending: AlphaBlending::default(),\n\t\t\tsource_node_id: None,\n\t\t}\n\t}\n\n\tpub fn as_ref(&self) -> TableRowRef<'_, T> {\n\t\tTableRowRef {\n\t\t\telement: &self.element,\n\t\t\ttransform: &self.transform,\n\t\t\talpha_blending: &self.alpha_blending,\n\t\t\tsource_node_id: &self.source_node_id,\n\t\t}\n\t}\n\n\tpub fn as_mut(&mut self) -> TableRowMut<'_, T> {\n\t\tTableRowMut {\n\t\t\telement: &mut self.element,\n\t\t\ttransform: &mut self.transform,\n\t\t\talpha_blending: &mut self.alpha_blending,\n\t\t\tsource_node_id: &mut self.source_node_id,\n\t\t}\n\t}\n}\n\n#[derive(Copy, Clone, Debug, PartialEq)]\npub struct TableRowRef<'a, T> {\n\tpub element: &'a T,\n\tpub transform: &'a DAffine2,\n\tpub alpha_blending: &'a AlphaBlending,\n\tpub source_node_id: &'a Option<NodeId>,\n}\n\nimpl<T> TableRowRef<'_, T> {\n\tpub fn into_cloned(self) -> TableRow<T>\n\twhere\n\t\tT: Clone,\n\t{\n\t\tTableRow {\n\t\t\telement: self.element.clone(),\n\t\t\ttransform: *self.transform,\n\t\t\talpha_blending: *self.alpha_blending,\n\t\t\tsource_node_id: *self.source_node_id,\n\t\t}\n\t}\n}\n\n#[derive(Debug)]\npub struct TableRowMut<'a, T> {\n\tpub element: &'a mut T,\n\tpub transform: &'a mut DAffine2,\n\tpub alpha_blending: &'a mut AlphaBlending,\n\tpub source_node_id: &'a mut Option<NodeId>,\n}\n\n// Conversion from Table<Color> to Option<Color> - extracts first element\nimpl From<Table<crate::Color>> for Option<crate::Color> {\n\tfn from(table: Table<crate::Color>) -> Self {\n\t\ttable.iter().nth(0).map(|row| row.element).copied()\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/core-types/src/text.rs",
    "content": "mod font_cache;\nmod path_builder;\nmod text_context;\nmod to_path;\n\nuse dyn_any::DynAny;\npub use font_cache::*;\npub use text_context::TextContext;\npub use to_path::*;\n\n/// Alignment of lines of type within a text block.\n#[repr(C)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)]\n#[widget(Radio)]\npub enum TextAlign {\n\t#[default]\n\tLeft,\n\tCenter,\n\tRight,\n\t#[label(\"Justify\")]\n\tJustifyLeft,\n\t// TODO: JustifyCenter, JustifyRight, JustifyAll\n}\n\nimpl From<TextAlign> for parley::Alignment {\n\tfn from(val: TextAlign) -> Self {\n\t\tmatch val {\n\t\t\tTextAlign::Left => parley::Alignment::Left,\n\t\t\tTextAlign::Center => parley::Alignment::Center,\n\t\t\tTextAlign::Right => parley::Alignment::Right,\n\t\t\tTextAlign::JustifyLeft => parley::Alignment::Justify,\n\t\t}\n\t}\n}\n\n#[derive(PartialEq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]\npub struct TypesettingConfig {\n\tpub font_size: f64,\n\tpub line_height_ratio: f64,\n\tpub character_spacing: f64,\n\tpub max_width: Option<f64>,\n\tpub max_height: Option<f64>,\n\tpub tilt: f64,\n\tpub align: TextAlign,\n}\n\nimpl Default for TypesettingConfig {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tfont_size: 24.,\n\t\t\tline_height_ratio: 1.2,\n\t\t\tcharacter_spacing: 0.,\n\t\t\tmax_width: None,\n\t\t\tmax_height: None,\n\t\t\ttilt: 0.,\n\t\t\talign: TextAlign::default(),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/core-types/src/transform.rs",
    "content": "use crate::math::bbox::AxisAlignedBbox;\nuse core::f64;\nuse glam::{DAffine2, DMat2, DVec2, UVec2};\n\npub trait Transform {\n\tfn transform(&self) -> DAffine2;\n\n\tfn local_pivot(&self, pivot: DVec2) -> DVec2 {\n\t\tpivot\n\t}\n\n\tfn decompose_scale(&self) -> DVec2 {\n\t\tDVec2::new(self.transform().transform_vector2(DVec2::X).length(), self.transform().transform_vector2(DVec2::Y).length())\n\t}\n\n\t/// Requires that the transform does not contain any skew.\n\tfn decompose_rotation(&self) -> f64 {\n\t\tlet rotation_matrix = (self.transform() * DAffine2::from_scale(self.decompose_scale().recip())).matrix2;\n\t\tlet rotation = -rotation_matrix.mul_vec2(DVec2::X).angle_to(DVec2::X);\n\t\tif rotation == -0. { 0. } else { rotation }\n\t}\n\n\t/// Detects if the transform contains skew by checking if the transformation matrix\n\t/// deviates from a pure rotation + uniform scale + translation.\n\t///\n\t/// Returns true if the matrix columns are not orthogonal or have different lengths,\n\t/// indicating the presence of skew or non-uniform scaling.\n\tfn has_skew(&self) -> bool {\n\t\tlet mat2 = self.transform().matrix2;\n\t\tlet col0 = mat2.x_axis;\n\t\tlet col1 = mat2.y_axis;\n\n\t\tconst EPSILON: f64 = 1e-10;\n\n\t\t// Check if columns are orthogonal (dot product should be ~0) and equal length\n\t\t// Non-orthogonal columns or different lengths indicate skew/non-uniform scaling\n\t\tcol0.dot(col1).abs() > EPSILON || (col0.length() - col1.length()).abs() > EPSILON\n\t}\n}\n\npub trait TransformMut: Transform {\n\tfn transform_mut(&mut self) -> &mut DAffine2;\n\tfn translate(&mut self, offset: DVec2) {\n\t\t*self.transform_mut() = DAffine2::from_translation(offset) * self.transform();\n\t}\n}\n\n// Implementation for references to anything that implements Transform\nimpl<T: Transform> Transform for &T {\n\tfn transform(&self) -> DAffine2 {\n\t\t(*self).transform()\n\t}\n}\n\n// Implementations for DAffine2\nimpl Transform for DAffine2 {\n\tfn transform(&self) -> DAffine2 {\n\t\t*self\n\t}\n}\nimpl TransformMut for DAffine2 {\n\tfn transform_mut(&mut self) -> &mut DAffine2 {\n\t\tself\n\t}\n}\n\n// Implementations for Footprint\nimpl Transform for Footprint {\n\tfn transform(&self) -> DAffine2 {\n\t\tself.transform\n\t}\n}\nimpl TransformMut for Footprint {\n\tfn transform_mut(&mut self) -> &mut DAffine2 {\n\t\t&mut self.transform\n\t}\n}\n\n#[derive(Debug, Clone, Copy, dyn_any::DynAny, PartialEq, serde::Serialize, serde::Deserialize)]\npub enum RenderQuality {\n\t/// Low quality, fast rendering\n\tPreview,\n\t/// Ensure that the render is available with at least the specified quality\n\t/// A value of 0.5 means that the render is available with at least 50% of the final image resolution\n\tScale(f32),\n\t/// Flip a coin to decide if the render should be available with the current quality or done at full quality\n\t/// This should be used to gradually update the render quality of a cached node\n\tProbability(f32),\n\t/// Render at full quality\n\tFull,\n}\n#[derive(Debug, Clone, Copy, dyn_any::DynAny, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct Footprint {\n\t/// Inverse of the transform which will be applied to the node output during the rendering process\n\tpub transform: DAffine2,\n\t/// Resolution of the target output area in pixels\n\tpub resolution: UVec2,\n\t/// Quality of the render, this may be used by caching nodes to decide if the cached render is sufficient\n\tpub quality: RenderQuality,\n}\n\nimpl Default for Footprint {\n\tfn default() -> Self {\n\t\tSelf::DEFAULT\n\t}\n}\n\nimpl Footprint {\n\tpub const DEFAULT: Self = Self {\n\t\ttransform: DAffine2::IDENTITY,\n\t\tresolution: UVec2::new(1920, 1080),\n\t\tquality: RenderQuality::Full,\n\t};\n\n\tpub const BOUNDLESS: Self = Self {\n\t\ttransform: DAffine2 {\n\t\t\tmatrix2: DMat2::from_diagonal(DVec2::splat(f64::INFINITY)),\n\t\t\ttranslation: DVec2::ZERO,\n\t\t},\n\t\tresolution: UVec2::ZERO,\n\t\tquality: RenderQuality::Full,\n\t};\n\n\tpub fn viewport_bounds_in_local_space(&self) -> AxisAlignedBbox {\n\t\tlet inverse = self.transform.inverse();\n\t\tlet res = self.resolution.as_dvec2();\n\t\tlet c0 = inverse.transform_point2(DVec2::ZERO);\n\t\tlet c1 = inverse.transform_point2(DVec2::new(res.x, 0.));\n\t\tlet c2 = inverse.transform_point2(res);\n\t\tlet c3 = inverse.transform_point2(DVec2::new(0., res.y));\n\t\tAxisAlignedBbox {\n\t\t\tstart: c0.min(c1).min(c2).min(c3),\n\t\t\tend: c0.max(c1).max(c2).max(c3),\n\t\t}\n\t}\n\n\tpub fn scale(&self) -> DVec2 {\n\t\tself.transform.decompose_scale()\n\t}\n\n\tpub fn offset(&self) -> DVec2 {\n\t\tself.transform.transform_point2(DVec2::ZERO)\n\t}\n}\n\nimpl From<()> for Footprint {\n\tfn from(_: ()) -> Self {\n\t\tFootprint::default()\n\t}\n}\n\nimpl std::hash::Hash for Footprint {\n\tfn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n\t\tself.transform.to_cols_array().iter().for_each(|x| x.to_le_bytes().hash(state));\n\t\tself.resolution.hash(state)\n\t}\n}\n\npub trait ApplyTransform {\n\tfn apply_transform(&mut self, modification: &DAffine2);\n\tfn left_apply_transform(&mut self, modification: &DAffine2);\n}\nimpl<T: TransformMut> ApplyTransform for T {\n\tfn apply_transform(&mut self, &modification: &DAffine2) {\n\t\t*self.transform_mut() = self.transform() * modification\n\t}\n\tfn left_apply_transform(&mut self, &modification: &DAffine2) {\n\t\t*self.transform_mut() = modification * self.transform()\n\t}\n}\nimpl ApplyTransform for DVec2 {\n\tfn apply_transform(&mut self, modification: &DAffine2) {\n\t\t*self = modification.transform_point2(*self);\n\t}\n\tfn left_apply_transform(&mut self, modification: &DAffine2) {\n\t\t*self = modification.inverse().transform_point2(*self);\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/core-types/src/types.rs",
    "content": "use crate::transform::Footprint;\nuse std::any::TypeId;\npub use std::borrow::Cow;\nuse std::fmt::{Display, Formatter};\n\n#[macro_export]\nmacro_rules! concrete {\n\t($type:ty) => {\n\t\t$crate::Type::Concrete($crate::TypeDescriptor {\n\t\t\tid: Some(std::any::TypeId::of::<$type>()),\n\t\t\tname: $crate::Cow::Borrowed(std::any::type_name::<$type>()),\n\t\t\talias: None,\n\t\t\tsize: std::mem::size_of::<$type>(),\n\t\t\talign: std::mem::align_of::<$type>(),\n\t\t})\n\t};\n\t($type:ty, $name:ty) => {\n\t\t$crate::Type::Concrete($crate::TypeDescriptor {\n\t\t\tid: Some(std::any::TypeId::of::<$type>()),\n\t\t\tname: $crate::Cow::Borrowed(std::any::type_name::<$type>()),\n\t\t\talias: Some($crate::Cow::Borrowed(stringify!($name))),\n\t\t\tsize: std::mem::size_of::<$type>(),\n\t\t\talign: std::mem::align_of::<$type>(),\n\t\t})\n\t};\n}\n\n#[macro_export]\nmacro_rules! concrete_with_name {\n\t($type:ty, $name:expr_2021) => {\n\t\t$crate::Type::Concrete($crate::TypeDescriptor {\n\t\t\tid: Some(std::any::TypeId::of::<$type>()),\n\t\t\tname: $crate::Cow::Borrowed($name),\n\t\t\talias: None,\n\t\t\tsize: std::mem::size_of::<$type>(),\n\t\t\talign: std::mem::align_of::<$type>(),\n\t\t})\n\t};\n}\n\n#[macro_export]\nmacro_rules! generic {\n\t($type:ty) => {{ $crate::Type::Generic($crate::Cow::Borrowed(stringify!($type))) }};\n}\n\n#[macro_export]\nmacro_rules! future {\n\t($type:ty) => {{ $crate::Type::Future(Box::new(concrete!($type))) }};\n\t($type:ty, $name:ty) => {\n\t\t$crate::Type::Future(Box::new(concrete!($type, $name)))\n\t};\n}\n\n#[macro_export]\nmacro_rules! fn_type {\n\t($type:ty) => {\n\t\t$crate::Type::Fn(Box::new(concrete!(())), Box::new(concrete!($type)))\n\t};\n\t($in_type:ty, $type:ty, alias: $outname:ty) => {\n\t\t$crate::Type::Fn(Box::new(concrete!($in_type)), Box::new(concrete!($type, $outname)))\n\t};\n\t($in_type:ty, $type:ty) => {\n\t\t$crate::Type::Fn(Box::new(concrete!($in_type)), Box::new(concrete!($type)))\n\t};\n}\n#[macro_export]\nmacro_rules! fn_type_fut {\n\t($type:ty) => {\n\t\t$crate::Type::Fn(Box::new(concrete!(())), Box::new(future!($type)))\n\t};\n\t($in_type:ty, $type:ty, alias: $outname:ty) => {\n\t\t$crate::Type::Fn(Box::new(concrete!($in_type)), Box::new(future!($type, $outname)))\n\t};\n\t($in_type:ty, $type:ty) => {\n\t\t$crate::Type::Fn(Box::new(concrete!($in_type)), Box::new(future!($type)))\n\t};\n}\n\n// TODO: Rename to NodeSignatureMonomorphization\n#[derive(Clone, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]\npub struct NodeIOTypes {\n\tpub call_argument: Type,\n\tpub return_value: Type,\n\tpub inputs: Vec<Type>,\n}\n\nimpl NodeIOTypes {\n\tpub const fn new(call_argument: Type, return_value: Type, inputs: Vec<Type>) -> Self {\n\t\tSelf { call_argument, return_value, inputs }\n\t}\n\n\tpub const fn empty() -> Self {\n\t\tlet tds1 = TypeDescriptor {\n\t\t\tid: None,\n\t\t\tname: Cow::Borrowed(\"()\"),\n\t\t\talias: None,\n\t\t\tsize: 0,\n\t\t\talign: 0,\n\t\t};\n\t\tlet tds2 = TypeDescriptor {\n\t\t\tid: None,\n\t\t\tname: Cow::Borrowed(\"()\"),\n\t\t\talias: None,\n\t\t\tsize: 0,\n\t\t\talign: 0,\n\t\t};\n\t\tSelf {\n\t\t\tcall_argument: Type::Concrete(tds1),\n\t\t\treturn_value: Type::Concrete(tds2),\n\t\t\tinputs: Vec::new(),\n\t\t}\n\t}\n\n\tpub fn ty(&self) -> Type {\n\t\tType::Fn(Box::new(self.call_argument.clone()), Box::new(self.return_value.clone()))\n\t}\n}\n\nimpl std::fmt::Debug for NodeIOTypes {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tlet inputs = self.inputs.iter().map(ToString::to_string).collect::<Vec<_>>().join(\", \");\n\t\tlet return_value = &self.return_value;\n\t\tlet call_argument = &self.call_argument;\n\t\tf.write_fmt(format_args!(\"({inputs}) → {return_value} called with {call_argument}\"))\n\t}\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]\npub struct ProtoNodeIdentifier {\n\tname: Cow<'static, str>,\n}\n\nimpl ProtoNodeIdentifier {\n\tpub const fn new(name: &'static str) -> Self {\n\t\tProtoNodeIdentifier { name: Cow::Borrowed(name) }\n\t}\n\n\tpub const fn with_owned_string(name: String) -> Self {\n\t\tProtoNodeIdentifier { name: Cow::Owned(name) }\n\t}\n\n\tpub fn as_str(&self) -> &str {\n\t\tself.name.as_ref()\n\t}\n}\n\nimpl Display for ProtoNodeIdentifier {\n\tfn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {\n\t\tf.debug_tuple(\"ProtoNodeIdentifier\").field(&self.name).finish()\n\t}\n}\n\nfn migrate_type_descriptor_names<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Cow<'static, str>, D::Error> {\n\tuse serde::Deserialize;\n\n\tlet name = String::deserialize(deserializer)?;\n\tlet name = match name.as_str() {\n\t\t\"f32\" => \"f64\".to_string(),\n\t\t\"grahpene_core::transform::Footprint\" => \"std::option::Option<std::sync::Arc<grahpene_core::context::OwnedContextImpl>>\".to_string(),\n\t\t\"grahpene_core::graphic_element::GraphicGroup\" => \"grahpene_core::table::Table<grahpene_core::graphic_types::Graphic>\".to_string(),\n\t\t\"grahpene_core::raster::image::ImageFrame<Color>\"\n\t\t| \"grahpene_core::raster::image::ImageFrame<grahpene_core::raster::color::Color>\"\n\t\t| \"grahpene_core::instances::Instances<grahpene_core::raster::image::ImageFrame<Color>>\"\n\t\t| \"grahpene_core::instances::Instances<grahpene_core::raster::image::ImageFrame<grahpene_core::raster::color::Color>>\"\n\t\t| \"grahpene_core::instances::Instances<grahpene_core::raster::image::Image<grahpene_core::raster::color::Color>>\" => {\n\t\t\t\"grahpene_core::table::Table<grahpene_core::raster::image::Image<grahpene_core::raster::color::Color>>\".to_string()\n\t\t}\n\t\t\"grahpene_core::vector::vector_data::VectorData\"\n\t\t| \"grahpene_core::instances::Instances<grahpene_core::vector::vector_data::VectorData>\"\n\t\t| \"grahpene_core::table::Table<grahpene_core::vector::vector_data::VectorData>\"\n\t\t| \"grahpene_core::table::Table<grahpene_core::vector::vector_data::Vector>\" => \"grahpene_core::table::Table<grahpene_core::vector::vector_types::Vector>\".to_string(),\n\t\t\"grahpene_core::instances::Instances<grahpene_core::graphic_element::Artboard>\" => \"grahpene_core::table::Table<grahpene_core::artboard::Artboard>\".to_string(),\n\t\t\"grahpene_core::vector::vector_data::modification::VectorModification\" => \"grahpene_core::vector::vector_modification::VectorModification\".to_string(),\n\t\t\"grahpene_core::table::Table<grahpene_core::graphic_element::Graphic>\" => \"grahpene_core::table::Table<grahpene_core::graphic_types::Graphic>\".to_string(),\n\t\t_ => name,\n\t};\n\n\tOk(Cow::Owned(name))\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, Debug, Eq, serde::Serialize, serde::Deserialize)]\npub struct TypeDescriptor {\n\t#[serde(skip)]\n\tpub id: Option<TypeId>,\n\t#[serde(deserialize_with = \"migrate_type_descriptor_names\")]\n\tpub name: Cow<'static, str>,\n\t#[serde(default)]\n\tpub alias: Option<Cow<'static, str>>,\n\t#[serde(skip)]\n\tpub size: usize,\n\t#[serde(skip)]\n\tpub align: usize,\n}\n\nimpl std::hash::Hash for TypeDescriptor {\n\tfn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n\t\tself.name.hash(state);\n\t}\n}\n\nimpl std::fmt::Display for TypeDescriptor {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tlet text = make_type_user_readable(&simplify_identifier_name(&self.name));\n\t\twrite!(f, \"{text}\")\n\t}\n}\n\nimpl PartialEq for TypeDescriptor {\n\tfn eq(&self, other: &Self) -> bool {\n\t\tmatch (self.id, other.id) {\n\t\t\t(Some(id), Some(other_id)) => id == other_id,\n\t\t\t_ => {\n\t\t\t\t// TODO: Add a flag to disable this warning\n\t\t\t\t// warn!(\"TypeDescriptor::eq: comparing types without ids based on name\");\n\t\t\t\tself.name == other.name\n\t\t\t}\n\t\t}\n\t}\n}\n\n/// Graph runtime type information used for type inference.\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]\npub enum Type {\n\t/// A wrapper for some type variable used within the inference system. Resolved at inference time and replaced with a concrete type.\n\tGeneric(Cow<'static, str>),\n\t/// A wrapper around the Rust type id for any concrete Rust type. Allows us to do equality comparisons, like checking if a String == a String.\n\tConcrete(TypeDescriptor),\n\t/// Runtime type information for a function. Given some input, gives some output.\n\tFn(Box<Type>, Box<Type>),\n\t/// Represents a future which promises to return the inner type.\n\tFuture(Box<Type>),\n}\n\nimpl Default for Type {\n\tfn default() -> Self {\n\t\tconcrete!(())\n\t}\n}\n\nunsafe impl dyn_any::StaticType for Type {\n\ttype Static = Self;\n}\n\nimpl Type {\n\tpub fn is_generic(&self) -> bool {\n\t\tmatches!(self, Type::Generic(_))\n\t}\n\n\tpub fn is_concrete(&self) -> bool {\n\t\tmatches!(self, Type::Concrete(_))\n\t}\n\n\tpub fn is_fn(&self) -> bool {\n\t\tmatches!(self, Type::Fn(_, _))\n\t}\n\n\tpub fn is_value(&self) -> bool {\n\t\tmatches!(self, Type::Fn(_, _) | Type::Concrete(_))\n\t}\n\n\tpub fn is_unit(&self) -> bool {\n\t\tmatches!(self, Type::Fn(_, _) | Type::Concrete(_))\n\t}\n\n\tpub fn is_generic_or_fn(&self) -> bool {\n\t\tmatches!(self, Type::Fn(_, _) | Type::Generic(_))\n\t}\n\n\tpub fn fn_input(&self) -> Option<&Type> {\n\t\tmatch self {\n\t\t\tType::Fn(first, _) => Some(first),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tpub fn fn_output(&self) -> Option<&Type> {\n\t\tmatch self {\n\t\t\tType::Fn(_, second) => Some(second),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tpub fn function(input: &Type, output: &Type) -> Type {\n\t\tType::Fn(Box::new(input.clone()), Box::new(output.clone()))\n\t}\n}\n\nimpl Type {\n\tpub fn new<T: dyn_any::StaticType + Sized>() -> Self {\n\t\tSelf::Concrete(TypeDescriptor {\n\t\t\tid: Some(TypeId::of::<T::Static>()),\n\t\t\tname: Cow::Borrowed(std::any::type_name::<T::Static>()),\n\t\t\talias: None,\n\t\t\tsize: size_of::<T>(),\n\t\t\talign: align_of::<T>(),\n\t\t})\n\t}\n\n\tpub fn size(&self) -> Option<usize> {\n\t\tmatch self {\n\t\t\tSelf::Generic(_) => None,\n\t\t\tSelf::Concrete(ty) => Some(ty.size),\n\t\t\tSelf::Fn(_, _) => None,\n\t\t\tSelf::Future(_) => None,\n\t\t}\n\t}\n\n\tpub fn align(&self) -> Option<usize> {\n\t\tmatch self {\n\t\t\tSelf::Generic(_) => None,\n\t\t\tSelf::Concrete(ty) => Some(ty.align),\n\t\t\tSelf::Fn(_, _) => None,\n\t\t\tSelf::Future(_) => None,\n\t\t}\n\t}\n\n\tpub fn nested_type(&self) -> &Type {\n\t\tmatch self {\n\t\t\tSelf::Generic(_) => self,\n\t\t\tSelf::Concrete(_) => self,\n\t\t\tSelf::Fn(_, output) => output.nested_type(),\n\t\t\tSelf::Future(output) => output.nested_type(),\n\t\t}\n\t}\n\n\tpub fn replace_nested(&mut self, f: impl Fn(&Type) -> Option<Type>) -> Option<Type> {\n\t\tif let Some(replacement) = f(self) {\n\t\t\treturn Some(std::mem::replace(self, replacement));\n\t\t}\n\t\tmatch self {\n\t\t\tSelf::Generic(_) => None,\n\t\t\tSelf::Concrete(_) => None,\n\t\t\tSelf::Fn(_, output) => output.replace_nested(f),\n\t\t\tSelf::Future(output) => output.replace_nested(f),\n\t\t}\n\t}\n\n\tpub fn identifier_name(&self) -> String {\n\t\tmatch self {\n\t\t\tType::Generic(name) => name.to_string(),\n\t\t\tType::Concrete(ty) => simplify_identifier_name(&ty.name),\n\t\t\tType::Fn(call_arg, return_value) => format!(\"{} called with {}\", return_value.identifier_name(), call_arg.identifier_name()),\n\t\t\tType::Future(ty) => ty.identifier_name(),\n\t\t}\n\t}\n}\n\npub fn simplify_identifier_name(ty: &str) -> String {\n\tty.split('<')\n\t\t.map(|path| path.split(',').map(|path| path.split(\"::\").last().unwrap_or(path)).collect::<Vec<_>>().join(\",\"))\n\t\t.collect::<Vec<_>>()\n\t\t.join(\"<\")\n}\n\npub fn make_type_user_readable(ty: &str) -> String {\n\tty.replace(\"Option<Arc<OwnedContextImpl>>\", \"Context\")\n\t\t.replace(\"Vector<Option<Table<Graphic>>>\", \"Vector\")\n\t\t.replace(\"Raster<CPU>\", \"Raster\")\n\t\t.replace(\"Raster<GPU>\", \"Raster\")\n}\n\nimpl std::fmt::Debug for Type {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\twrite!(f, \"{self}\")\n\t}\n}\n\n// Display\nimpl std::fmt::Display for Type {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tuse glam::*;\n\n\t\tmatch self {\n\t\t\tType::Generic(name) => write!(f, \"{}\", make_type_user_readable(name)),\n\t\t\tType::Concrete(ty) => match () {\n\t\t\t\t() if self == &concrete!(DVec2) || self == &concrete!(Vec2) || self == &concrete!(IVec2) || self == &concrete!(UVec2) => write!(f, \"Vec2\"),\n\t\t\t\t() if self == &concrete!(glam::DAffine2) => write!(f, \"Transform\"),\n\t\t\t\t() if self == &concrete!(Footprint) => write!(f, \"Footprint\"),\n\t\t\t\t() if self == &concrete!(&str) || self == &concrete!(String) => write!(f, \"String\"),\n\t\t\t\t_ => write!(f, \"{}\", make_type_user_readable(&simplify_identifier_name(&ty.name))),\n\t\t\t},\n\t\t\tType::Fn(call_arg, return_value) => write!(f, \"{return_value} called with {call_arg}\"),\n\t\t\tType::Future(ty) => write!(f, \"{ty}\"),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/core-types/src/uuid.rs",
    "content": "use dyn_any::DynAny;\npub use uuid_generation::*;\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, Copy, serde::Serialize, serde::Deserialize)]\npub struct Uuid(#[serde(with = \"u64_string\")] u64);\n\nmod u64_string {\n\tuse serde::{self, Deserialize, Deserializer, Serializer};\n\tuse std::str::FromStr;\n\n\t// The signature of a serialize_with function must follow the pattern:\n\t//\n\t//    fn serialize<S>(&T, S) -> Result<S::Ok, S::Error>\n\t//    where\n\t//        S: Serializer\n\t//\n\t// although it may also be generic over the input types T.\n\tpub fn serialize<S>(value: &u64, serializer: S) -> Result<S::Ok, S::Error>\n\twhere\n\t\tS: Serializer,\n\t{\n\t\tserializer.serialize_str(&value.to_string())\n\t}\n\n\t// The signature of a deserialize_with function must follow the pattern:\n\t//\n\t//    fn deserialize<'de, D>(D) -> Result<T, D::Error>\n\t//    where\n\t//        D: Deserializer<'de>\n\t//\n\t// although it may also be generic over the output types T.\n\tpub fn deserialize<'de, D>(deserializer: D) -> Result<u64, D::Error>\n\twhere\n\t\tD: Deserializer<'de>,\n\t{\n\t\tlet s = String::deserialize(deserializer)?;\n\t\tu64::from_str(&s).map_err(serde::de::Error::custom)\n\t}\n}\n\nmod uuid_generation {\n\tuse rand_chacha::ChaCha20Rng;\n\tuse rand_chacha::rand_core::{RngCore, SeedableRng};\n\tuse std::cell::Cell;\n\tuse std::sync::Mutex;\n\n\tstatic RNG: Mutex<Option<ChaCha20Rng>> = Mutex::new(None);\n\tthread_local! {\n\t\tpub static UUID_SEED: Cell<Option<u64>> = const { Cell::new(None) };\n\t}\n\n\tpub fn set_uuid_seed(random_seed: u64) {\n\t\tUUID_SEED.with(|seed| seed.set(Some(random_seed)))\n\t}\n\n\tpub fn generate_uuid() -> u64 {\n\t\tlet Ok(mut lock) = RNG.lock() else { panic!(\"UUID mutex poisoned\") };\n\t\tif lock.is_none() {\n\t\t\tUUID_SEED.with(|seed| {\n\t\t\t\tlet random_seed = seed.get().unwrap_or(42);\n\t\t\t\t*lock = Some(ChaCha20Rng::seed_from_u64(random_seed));\n\t\t\t})\n\t\t}\n\t\tlock.as_mut().map(ChaCha20Rng::next_u64).expect(\"UUID mutex poisoned\")\n\t}\n}\n\n#[repr(transparent)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify), tsify(large_number_types_as_bigints))]\n#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize, DynAny)]\npub struct NodeId(pub u64);\n\nimpl NodeId {\n\tpub fn new() -> Self {\n\t\tSelf(generate_uuid())\n\t}\n}\n\nimpl std::fmt::Display for NodeId {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\twrite!(f, \"{}\", self.0)\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/core-types/src/value.rs",
    "content": "use crate::Node;\nuse std::cell::{Cell, RefCell, RefMut};\nuse std::marker::PhantomData;\n\n#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]\npub struct IntNode<const N: u32>;\n\nimpl<'i, const N: u32, I> Node<'i, I> for IntNode<N> {\n\ttype Output = u32;\n\t#[inline(always)]\n\tfn eval(&'i self, _input: I) -> Self::Output {\n\t\tN\n\t}\n}\n\n#[derive(Default, Debug, Clone, Copy)]\npub struct ValueNode<T>(pub T);\n\nimpl<'i, T: 'i, I> Node<'i, I> for ValueNode<T> {\n\ttype Output = &'i T;\n\t#[inline(always)]\n\tfn eval(&'i self, _input: I) -> Self::Output {\n\t\t&self.0\n\t}\n}\n\nimpl<T> ValueNode<T> {\n\tpub const fn new(value: T) -> ValueNode<T> {\n\t\tValueNode(value)\n\t}\n}\n\nimpl<T> From<T> for ValueNode<T> {\n\tfn from(value: T) -> Self {\n\t\tValueNode::new(value)\n\t}\n}\n\n#[derive(Default, Debug, Clone, Copy)]\npub struct AsRefNode<T: AsRef<U>, U>(pub T, PhantomData<U>);\n\nimpl<'i, T: 'i + AsRef<U>, U: 'i> Node<'i, ()> for AsRefNode<T, U> {\n\ttype Output = &'i U;\n\t#[inline(always)]\n\tfn eval(&'i self, _input: ()) -> Self::Output {\n\t\tself.0.as_ref()\n\t}\n}\n\nimpl<T: AsRef<U>, U> AsRefNode<T, U> {\n\tpub const fn new(value: T) -> AsRefNode<T, U> {\n\t\tAsRefNode(value, PhantomData)\n\t}\n}\n\n#[derive(Default, Debug, Clone)]\npub struct RefCellMutNode<T>(pub RefCell<T>);\n\nimpl<'i, T: 'i> Node<'i, ()> for RefCellMutNode<T> {\n\ttype Output = RefMut<'i, T>;\n\t#[inline(always)]\n\tfn eval(&'i self, _input: ()) -> Self::Output {\n\t\tself.0.borrow_mut()\n\t}\n}\n\nimpl<T> RefCellMutNode<T> {\n\tpub const fn new(value: T) -> RefCellMutNode<T> {\n\t\tRefCellMutNode(RefCell::new(value))\n\t}\n}\n\n#[derive(Default)]\npub struct OnceCellNode<T>(pub Cell<T>);\n\nimpl<'i, T: Default + 'i, I> Node<'i, I> for OnceCellNode<T> {\n\ttype Output = T;\n\t#[inline(always)]\n\tfn eval(&'i self, _input: I) -> Self::Output {\n\t\tself.0.replace(T::default())\n\t}\n}\n\nimpl<T> OnceCellNode<T> {\n\tpub const fn new(value: T) -> OnceCellNode<T> {\n\t\tOnceCellNode(Cell::new(value))\n\t}\n}\n\n#[derive(Clone, Copy)]\npub struct ClonedNode<T: Clone>(pub T);\n\nimpl<'i, T: Clone + 'i, I> Node<'i, I> for ClonedNode<T> {\n\ttype Output = T;\n\t#[inline(always)]\n\tfn eval(&'i self, _input: I) -> Self::Output {\n\t\tself.0.clone()\n\t}\n}\n\nimpl<T: Clone> ClonedNode<T> {\n\tpub const fn new(value: T) -> ClonedNode<T> {\n\t\tClonedNode(value)\n\t}\n}\n\nimpl<T: Clone> From<T> for ClonedNode<T> {\n\tfn from(value: T) -> Self {\n\t\tClonedNode::new(value)\n\t}\n}\n\n#[derive(Clone, Copy)]\n/// The DebugClonedNode logs every time it is evaluated.\n/// This is useful for debugging.\npub struct DebugClonedNode<T: Clone>(pub T);\n\nimpl<'i, T: Clone + 'i> Node<'i, ()> for DebugClonedNode<T> {\n\ttype Output = T;\n\t#[inline(always)]\n\tfn eval(&'i self, _input: ()) -> Self::Output {\n\t\t// KEEP THIS `debug!()` - It acts as the output for the debug node itself\n\t\tlog::debug!(\"DebugClonedNode::eval\");\n\n\t\tself.0.clone()\n\t}\n}\n\nimpl<T: Clone> DebugClonedNode<T> {\n\tpub const fn new(value: T) -> DebugClonedNode<T> {\n\t\tDebugClonedNode(value)\n\t}\n}\n\n#[derive(Clone, Copy)]\npub struct CopiedNode<T: Copy>(pub T);\n\nimpl<'i, T: Copy + 'i, I> Node<'i, I> for CopiedNode<T> {\n\ttype Output = T;\n\t#[inline(always)]\n\tfn eval(&'i self, _input: I) -> Self::Output {\n\t\tself.0\n\t}\n}\n\nimpl<T: Copy> CopiedNode<T> {\n\tpub const fn new(value: T) -> CopiedNode<T> {\n\t\tCopiedNode(value)\n\t}\n}\n\n#[derive(Default)]\npub struct DefaultNode<T>(PhantomData<T>);\n\nimpl<'i, T: Default + 'i, I> Node<'i, I> for DefaultNode<T> {\n\ttype Output = T;\n\tfn eval(&'i self, _input: I) -> Self::Output {\n\t\tT::default()\n\t}\n}\n\nimpl<T> DefaultNode<T> {\n\tpub fn new() -> Self {\n\t\tSelf(PhantomData)\n\t}\n}\n\n#[repr(C)]\n/// Return the unit value\n#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]\npub struct ForgetNode;\n\nimpl<'i, T: 'i> Node<'i, T> for ForgetNode {\n\ttype Output = ();\n\tfn eval(&'i self, _input: T) -> Self::Output {}\n}\n\nimpl ForgetNode {\n\tpub const fn new() -> Self {\n\t\tForgetNode\n\t}\n}\n\n#[cfg(test)]\nmod test {\n\tuse super::*;\n\n\t#[test]\n\tfn test_int_node() {\n\t\tlet node = IntNode::<5>;\n\t\tassert_eq!(node.eval(()), 5);\n\t}\n\t#[test]\n\tfn test_value_node() {\n\t\tlet node = ValueNode::new(5);\n\t\tassert_eq!(node.eval(()), &5);\n\t\tlet type_erased = &node as &dyn for<'a> Node<'a, (), Output = &'a i32>;\n\t\tassert_eq!(type_erased.eval(()), &5);\n\t}\n\t#[test]\n\tfn test_default_node() {\n\t\tlet node = DefaultNode::<u32>::new();\n\t\tassert_eq!(node.eval(42), 0);\n\t}\n\t#[test]\n\t#[allow(clippy::unit_cmp)]\n\tfn test_unit_node() {\n\t\tlet node = ForgetNode::new();\n\t\tassert_eq!(node.eval(()), ());\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/graphic-types/Cargo.toml",
    "content": "[package]\nname = \"graphic-types\"\nversion = \"0.1.0\"\nedition = \"2024\"\ndescription = \"Graphic types for Graphene - combines vector types with core infrastructure\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\nlicense = \"MIT OR Apache-2.0\"\n\n[features]\ndefault = [\"serde\"]\nwasm = [\n\t\"core-types/wasm\",\n\t\"vector-types/wasm\",\n\t\"raster-types/wasm\",\n\t\"wasm-bindgen\",\n]\n\n[dependencies]\n# Local dependencies\ncore-types = { workspace = true }\nraster-types = { workspace = true, features = [\"wgpu\"] }\nvector-types = { workspace = true }\nnode-macro = { workspace = true }\n\n# Workspace dependencies\ndyn-any = { workspace = true }\nglam = { workspace = true }\nserde_json = { workspace = true }\n\n# Optional workspace dependencies\nserde = { workspace = true, optional = true }\nwasm-bindgen = { workspace = true, optional = true }\n"
  },
  {
    "path": "node-graph/libraries/graphic-types/src/artboard.rs",
    "content": "use crate::graphic::Graphic;\nuse core_types::Color;\nuse core_types::blending::AlphaBlending;\nuse core_types::bounds::{BoundingBox, RenderBoundingBox};\nuse core_types::math::quad::Quad;\nuse core_types::render_complexity::RenderComplexity;\nuse core_types::table::{Table, TableRow};\nuse core_types::transform::Transform;\nuse core_types::uuid::NodeId;\nuse dyn_any::DynAny;\nuse glam::{DAffine2, DVec2, IVec2};\nuse std::hash::Hash;\n\n/// Some [`ArtboardData`] with some optional clipping bounds that can be exported.\n#[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]\npub struct Artboard {\n\tpub content: Table<Graphic>,\n\tpub label: String,\n\tpub location: IVec2,\n\tpub dimensions: IVec2,\n\tpub background: Color,\n\tpub clip: bool,\n}\n\nimpl Default for Artboard {\n\tfn default() -> Self {\n\t\tSelf::new(IVec2::ZERO, IVec2::new(1920, 1080))\n\t}\n}\n\nimpl Artboard {\n\tpub fn new(location: IVec2, dimensions: IVec2) -> Self {\n\t\tSelf {\n\t\t\tcontent: Table::new(),\n\t\t\tlabel: \"Artboard\".to_string(),\n\t\t\tlocation: location.min(location + dimensions),\n\t\t\tdimensions: dimensions.abs(),\n\t\t\tbackground: Color::WHITE,\n\t\t\tclip: false,\n\t\t}\n\t}\n}\n\nimpl BoundingBox for Artboard {\n\tfn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox {\n\t\tlet artboard_bounds = || (transform * Quad::from_box([self.location.as_dvec2(), self.location.as_dvec2() + self.dimensions.as_dvec2()])).bounding_box();\n\n\t\tif self.clip {\n\t\t\treturn RenderBoundingBox::Rectangle(artboard_bounds());\n\t\t}\n\n\t\tmatch self.content.bounding_box(transform, include_stroke) {\n\t\t\tRenderBoundingBox::Rectangle(content_bounds) => RenderBoundingBox::Rectangle(Quad::combine_bounds(content_bounds, artboard_bounds())),\n\t\t\tother => other,\n\t\t}\n\t}\n}\n\nimpl RenderComplexity for Artboard {\n\tfn render_complexity(&self) -> usize {\n\t\tself.content.render_complexity()\n\t}\n}\n\n// Implementations for Artboard\nimpl Transform for Artboard {\n\tfn transform(&self) -> DAffine2 {\n\t\tDAffine2::from_translation(self.location.as_dvec2())\n\t}\n\tfn local_pivot(&self, pivot: DVec2) -> DVec2 {\n\t\tself.location.as_dvec2() + self.dimensions.as_dvec2() * pivot\n\t}\n}\n\n// TODO: Eventually remove this migration document upgrade code\npub fn migrate_artboard<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Artboard>, D::Error> {\n\tuse serde::Deserialize;\n\n\t#[derive(Clone, Default, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]\n\tpub struct ArtboardGroup {\n\t\tpub artboards: Vec<(Artboard, Option<NodeId>)>,\n\t}\n\n\t#[derive(serde::Serialize, serde::Deserialize)]\n\t#[serde(untagged)]\n\tenum ArtboardFormat {\n\t\tArtboardGroup(ArtboardGroup),\n\t\tOldArtboardTable(OldTable<Artboard>),\n\t\tArtboardTable(Table<Artboard>),\n\t}\n\n\t#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]\n\tpub struct OldTable<T> {\n\t\t#[serde(alias = \"instances\", alias = \"instance\")]\n\t\telement: Vec<T>,\n\t\ttransform: Vec<DAffine2>,\n\t\talpha_blending: Vec<AlphaBlending>,\n\t}\n\n\tOk(match ArtboardFormat::deserialize(deserializer)? {\n\t\tArtboardFormat::ArtboardGroup(artboard_group) => {\n\t\t\tlet mut table = Table::new();\n\t\t\tfor (artboard, source_node_id) in artboard_group.artboards {\n\t\t\t\ttable.push(TableRow {\n\t\t\t\t\telement: artboard,\n\t\t\t\t\ttransform: DAffine2::IDENTITY,\n\t\t\t\t\talpha_blending: AlphaBlending::default(),\n\t\t\t\t\tsource_node_id,\n\t\t\t\t});\n\t\t\t}\n\t\t\ttable\n\t\t}\n\t\tArtboardFormat::OldArtboardTable(old_table) => old_table\n\t\t\t.element\n\t\t\t.into_iter()\n\t\t\t.zip(old_table.transform.into_iter().zip(old_table.alpha_blending))\n\t\t\t.map(|(element, (transform, alpha_blending))| TableRow {\n\t\t\t\telement,\n\t\t\t\ttransform,\n\t\t\t\talpha_blending,\n\t\t\t\tsource_node_id: None,\n\t\t\t})\n\t\t\t.collect(),\n\t\tArtboardFormat::ArtboardTable(artboard_table) => artboard_table,\n\t})\n}\n\n// Node definitions moved to graphic-nodes crate\n"
  },
  {
    "path": "node-graph/libraries/graphic-types/src/graphic.rs",
    "content": "use core_types::Color;\nuse core_types::blending::AlphaBlending;\nuse core_types::bounds::{BoundingBox, RenderBoundingBox};\nuse core_types::ops::TableConvert;\nuse core_types::render_complexity::RenderComplexity;\nuse core_types::table::{Table, TableRow};\nuse core_types::uuid::NodeId;\nuse dyn_any::DynAny;\nuse glam::DAffine2;\nuse raster_types::{CPU, GPU, Raster};\nuse std::hash::Hash;\nuse vector_types::GradientStops;\n// use vector_types::Vector;\n\npub type Vector = vector_types::Vector<Option<Table<Graphic>>>;\n\n/// The possible forms of graphical content that can be rendered by the Render node into either an image or SVG syntax.\n#[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]\npub enum Graphic {\n\tGraphic(Table<Graphic>),\n\tVector(Table<Vector>),\n\tRasterCPU(Table<Raster<CPU>>),\n\tRasterGPU(Table<Raster<GPU>>),\n\tColor(Table<Color>),\n\tGradient(Table<GradientStops>),\n}\n\nimpl Default for Graphic {\n\tfn default() -> Self {\n\t\tSelf::Graphic(Table::new())\n\t}\n}\n\n// Graphic\nimpl From<Table<Graphic>> for Graphic {\n\tfn from(graphic: Table<Graphic>) -> Self {\n\t\tGraphic::Graphic(graphic)\n\t}\n}\n\n// Vector\nimpl From<Vector> for Graphic {\n\tfn from(vector: Vector) -> Self {\n\t\tGraphic::Vector(Table::new_from_element(vector))\n\t}\n}\nimpl From<Table<Vector>> for Graphic {\n\tfn from(vector: Table<Vector>) -> Self {\n\t\tGraphic::Vector(vector)\n\t}\n}\n\n// Note: Table<Vector> -> Table<Graphic> conversion handled by blanket impl in gcore\n\n// Raster<CPU>\nimpl From<Raster<CPU>> for Graphic {\n\tfn from(raster: Raster<CPU>) -> Self {\n\t\tGraphic::RasterCPU(Table::new_from_element(raster))\n\t}\n}\nimpl From<Table<Raster<CPU>>> for Graphic {\n\tfn from(raster: Table<Raster<CPU>>) -> Self {\n\t\tGraphic::RasterCPU(raster)\n\t}\n}\n// Note: Table conversions handled by blanket impl in gcore\n\n// Raster<GPU>\nimpl From<Raster<GPU>> for Graphic {\n\tfn from(raster: Raster<GPU>) -> Self {\n\t\tGraphic::RasterGPU(Table::new_from_element(raster))\n\t}\n}\nimpl From<Table<Raster<GPU>>> for Graphic {\n\tfn from(raster: Table<Raster<GPU>>) -> Self {\n\t\tGraphic::RasterGPU(raster)\n\t}\n}\n// Note: Table conversions handled by blanket impl in gcore\n\n// Color\nimpl From<Color> for Graphic {\n\tfn from(color: Color) -> Self {\n\t\tGraphic::Color(Table::new_from_element(color))\n\t}\n}\nimpl From<Table<Color>> for Graphic {\n\tfn from(color: Table<Color>) -> Self {\n\t\tGraphic::Color(color)\n\t}\n}\n// Note: Table conversions handled by blanket impl in gcore\n// Note: Table<Color> -> Option<Color> is in gcore (Color is defined there)\n\n// GradientStops\nimpl From<GradientStops> for Graphic {\n\tfn from(gradient: GradientStops) -> Self {\n\t\tGraphic::Gradient(Table::new_from_element(gradient))\n\t}\n}\nimpl From<Table<GradientStops>> for Graphic {\n\tfn from(gradient: Table<GradientStops>) -> Self {\n\t\tGraphic::Gradient(gradient)\n\t}\n}\n\n/// Deeply flattens a graphic table, collecting only elements matching a specific variant (extracted by `extract_variant`)\n/// and discarding all other non-matching content. Recursion through `Graphic::Graphic` sub-tables composes transforms and opacity.\nfn flatten_graphic_table<T>(content: Table<Graphic>, extract_variant: fn(Graphic) -> Option<Table<T>>) -> Table<T> {\n\tfn compose_alpha_blending(parent: AlphaBlending, child: AlphaBlending) -> AlphaBlending {\n\t\tAlphaBlending {\n\t\t\tblend_mode: child.blend_mode,\n\t\t\topacity: parent.opacity * child.opacity,\n\t\t\tfill: child.fill,\n\t\t\tclip: child.clip,\n\t\t}\n\t}\n\n\tfn flatten_recursive<T>(output: &mut Table<T>, current_graphic_table: Table<Graphic>, extract_variant: fn(Graphic) -> Option<Table<T>>) {\n\t\tfor current_graphic_row in current_graphic_table.into_iter() {\n\t\t\tlet source_node_id = current_graphic_row.source_node_id;\n\n\t\t\tmatch current_graphic_row.element {\n\t\t\t\t// Recurse into nested graphic tables, composing the parent's transform onto each child\n\t\t\t\tGraphic::Graphic(mut sub_table) => {\n\t\t\t\t\tfor graphic in sub_table.iter_mut() {\n\t\t\t\t\t\t*graphic.transform = current_graphic_row.transform * *graphic.transform;\n\t\t\t\t\t\t*graphic.alpha_blending = compose_alpha_blending(current_graphic_row.alpha_blending, *graphic.alpha_blending);\n\t\t\t\t\t}\n\n\t\t\t\t\tflatten_recursive(output, sub_table, extract_variant);\n\t\t\t\t}\n\t\t\t\t// Try to extract the target variant; if it matches, push its rows with composed transform and opacity\n\t\t\t\tother => {\n\t\t\t\t\tif let Some(typed_table) = extract_variant(other) {\n\t\t\t\t\t\tfor row in typed_table.into_iter() {\n\t\t\t\t\t\t\toutput.push(TableRow {\n\t\t\t\t\t\t\t\telement: row.element,\n\t\t\t\t\t\t\t\ttransform: current_graphic_row.transform * row.transform,\n\t\t\t\t\t\t\t\talpha_blending: compose_alpha_blending(current_graphic_row.alpha_blending, row.alpha_blending),\n\t\t\t\t\t\t\t\tsource_node_id,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tlet mut output = Table::new();\n\tflatten_recursive(&mut output, content, extract_variant);\n\toutput\n}\n\n/// Maps from a concrete element type to its corresponding `Graphic` enum variant,\n/// enabling type-directed casting of typed tables from a `Graphic` value.\npub trait TryFromGraphic: Clone + Sized {\n\tfn try_from_graphic(graphic: Graphic) -> Option<Table<Self>>;\n}\n\nimpl TryFromGraphic for Vector {\n\tfn try_from_graphic(graphic: Graphic) -> Option<Table<Self>> {\n\t\tif let Graphic::Vector(t) = graphic { Some(t) } else { None }\n\t}\n}\n\nimpl TryFromGraphic for Raster<CPU> {\n\tfn try_from_graphic(graphic: Graphic) -> Option<Table<Self>> {\n\t\tif let Graphic::RasterCPU(t) = graphic { Some(t) } else { None }\n\t}\n}\n\nimpl TryFromGraphic for Color {\n\tfn try_from_graphic(graphic: Graphic) -> Option<Table<Self>> {\n\t\tif let Graphic::Color(t) = graphic { Some(t) } else { None }\n\t}\n}\n\nimpl TryFromGraphic for GradientStops {\n\tfn try_from_graphic(graphic: Graphic) -> Option<Table<Self>> {\n\t\tif let Graphic::Gradient(t) = graphic { Some(t) } else { None }\n\t}\n}\n\n// Local trait to convert types to Table<Graphic> (avoids orphan rule issues)\npub trait IntoGraphicTable {\n\tfn into_graphic_table(self) -> Table<Graphic>;\n\n\t/// Deeply flattens any content of type `T` within a graphic table, discarding all other content, and returning a flat table of only `T` elements.\n\tfn into_flattened_table<T: TryFromGraphic>(self) -> Table<T>\n\twhere\n\t\tSelf: std::marker::Sized,\n\t{\n\t\tflatten_graphic_table(self.into_graphic_table(), T::try_from_graphic)\n\t}\n}\n\nimpl IntoGraphicTable for Table<Graphic> {\n\tfn into_graphic_table(self) -> Table<Graphic> {\n\t\tself\n\t}\n}\n\nimpl IntoGraphicTable for Table<Vector> {\n\tfn into_graphic_table(self) -> Table<Graphic> {\n\t\tTable::new_from_element(Graphic::Vector(self))\n\t}\n}\n\nimpl IntoGraphicTable for Table<Raster<CPU>> {\n\tfn into_graphic_table(self) -> Table<Graphic> {\n\t\tTable::new_from_element(Graphic::RasterCPU(self))\n\t}\n}\n\nimpl IntoGraphicTable for Table<Raster<GPU>> {\n\tfn into_graphic_table(self) -> Table<Graphic> {\n\t\tTable::new_from_element(Graphic::RasterGPU(self))\n\t}\n}\n\nimpl IntoGraphicTable for Table<Color> {\n\tfn into_graphic_table(self) -> Table<Graphic> {\n\t\tTable::new_from_element(Graphic::Color(self))\n\t}\n}\n\nimpl IntoGraphicTable for Table<GradientStops> {\n\tfn into_graphic_table(self) -> Table<Graphic> {\n\t\tTable::new_from_element(Graphic::Gradient(self))\n\t}\n}\n\nimpl IntoGraphicTable for DAffine2 {\n\tfn into_graphic_table(self) -> Table<Graphic> {\n\t\tTable::new_from_element(Graphic::default())\n\t}\n}\n\n// DAffine2\nimpl From<DAffine2> for Graphic {\n\tfn from(_: DAffine2) -> Self {\n\t\tGraphic::default()\n\t}\n}\n// Note: Table conversions handled by blanket impl in gcore\n\nimpl Graphic {\n\tpub fn as_graphic(&self) -> Option<&Table<Graphic>> {\n\t\tmatch self {\n\t\t\tGraphic::Graphic(graphic) => Some(graphic),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tpub fn as_graphic_mut(&mut self) -> Option<&mut Table<Graphic>> {\n\t\tmatch self {\n\t\t\tGraphic::Graphic(graphic) => Some(graphic),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tpub fn as_vector(&self) -> Option<&Table<Vector>> {\n\t\tmatch self {\n\t\t\tGraphic::Vector(vector) => Some(vector),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tpub fn as_vector_mut(&mut self) -> Option<&mut Table<Vector>> {\n\t\tmatch self {\n\t\t\tGraphic::Vector(vector) => Some(vector),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tpub fn as_raster(&self) -> Option<&Table<Raster<CPU>>> {\n\t\tmatch self {\n\t\t\tGraphic::RasterCPU(raster) => Some(raster),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tpub fn as_raster_mut(&mut self) -> Option<&mut Table<Raster<CPU>>> {\n\t\tmatch self {\n\t\t\tGraphic::RasterCPU(raster) => Some(raster),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tpub fn had_clip_enabled(&self) -> bool {\n\t\tmatch self {\n\t\t\tGraphic::Vector(vector) => vector.iter().all(|row| row.alpha_blending.clip),\n\t\t\tGraphic::Graphic(graphic) => graphic.iter().all(|row| row.alpha_blending.clip),\n\t\t\tGraphic::RasterCPU(raster) => raster.iter().all(|row| row.alpha_blending.clip),\n\t\t\tGraphic::RasterGPU(raster) => raster.iter().all(|row| row.alpha_blending.clip),\n\t\t\tGraphic::Color(color) => color.iter().all(|row| row.alpha_blending.clip),\n\t\t\tGraphic::Gradient(gradient) => gradient.iter().all(|row| row.alpha_blending.clip),\n\t\t}\n\t}\n\n\tpub fn can_reduce_to_clip_path(&self) -> bool {\n\t\tmatch self {\n\t\t\tGraphic::Vector(vector) => vector.iter().all(|row| {\n\t\t\t\tlet style = &row.element.style;\n\t\t\t\tlet alpha_blending = &row.alpha_blending;\n\t\t\t\t(alpha_blending.opacity > 1. - f32::EPSILON) && style.fill().is_opaque() && style.stroke().is_none_or(|stroke| !stroke.has_renderable_stroke())\n\t\t\t}),\n\t\t\t_ => false,\n\t\t}\n\t}\n}\n\nimpl BoundingBox for Graphic {\n\tfn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox {\n\t\tmatch self {\n\t\t\tGraphic::Vector(vector) => vector.bounding_box(transform, include_stroke),\n\t\t\tGraphic::RasterCPU(raster) => raster.bounding_box(transform, include_stroke),\n\t\t\tGraphic::RasterGPU(raster) => raster.bounding_box(transform, include_stroke),\n\t\t\tGraphic::Graphic(graphic) => graphic.bounding_box(transform, include_stroke),\n\t\t\tGraphic::Color(color) => color.bounding_box(transform, include_stroke),\n\t\t\tGraphic::Gradient(gradient) => gradient.bounding_box(transform, include_stroke),\n\t\t}\n\t}\n}\n\nimpl TableConvert<Graphic> for Vector {\n\tfn convert_row(self) -> Graphic {\n\t\tGraphic::Vector(Table::new_from_element(self))\n\t}\n}\nimpl TableConvert<Graphic> for Raster<CPU> {\n\tfn convert_row(self) -> Graphic {\n\t\tGraphic::RasterCPU(Table::new_from_element(self))\n\t}\n}\nimpl TableConvert<Graphic> for Raster<GPU> {\n\tfn convert_row(self) -> Graphic {\n\t\tGraphic::RasterGPU(Table::new_from_element(self))\n\t}\n}\n\nimpl RenderComplexity for Graphic {\n\tfn render_complexity(&self) -> usize {\n\t\tmatch self {\n\t\t\tSelf::Graphic(table) => table.render_complexity(),\n\t\t\tSelf::Vector(table) => table.render_complexity(),\n\t\t\tSelf::RasterCPU(table) => table.render_complexity(),\n\t\t\tSelf::RasterGPU(table) => table.render_complexity(),\n\t\t\tSelf::Color(table) => table.render_complexity(),\n\t\t\tSelf::Gradient(table) => table.render_complexity(),\n\t\t}\n\t}\n}\n\n// Node definitions moved to graphic-nodes crate\n\npub trait AtIndex {\n\ttype Output;\n\tfn at_index(&self, index: usize) -> Option<Self::Output>;\n\tfn at_index_from_end(&self, index: usize) -> Option<Self::Output>;\n}\nimpl<T: Clone> AtIndex for Vec<T> {\n\ttype Output = T;\n\n\tfn at_index(&self, index: usize) -> Option<Self::Output> {\n\t\tself.get(index).cloned()\n\t}\n\n\tfn at_index_from_end(&self, index: usize) -> Option<Self::Output> {\n\t\tif index == 0 || index > self.len() { None } else { self.get(self.len() - index).cloned() }\n\t}\n}\nimpl<T: Clone> AtIndex for Table<T> {\n\ttype Output = Table<T>;\n\n\tfn at_index(&self, index: usize) -> Option<Self::Output> {\n\t\tlet mut result_table = Self::default();\n\t\tif let Some(row) = self.iter().nth(index) {\n\t\t\tresult_table.push(row.into_cloned());\n\t\t\tSome(result_table)\n\t\t} else {\n\t\t\tNone\n\t\t}\n\t}\n\n\tfn at_index_from_end(&self, index: usize) -> Option<Self::Output> {\n\t\tlet mut result_table = Self::default();\n\t\tif index == 0 || index > self.len() {\n\t\t\tNone\n\t\t} else if let Some(row) = self.iter().nth(self.len() - index) {\n\t\t\tresult_table.push(row.into_cloned());\n\t\t\tSome(result_table)\n\t\t} else {\n\t\t\tNone\n\t\t}\n\t}\n}\n\n// TODO: Eventually remove this migration document upgrade code\npub fn migrate_graphic<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Graphic>, D::Error> {\n\tuse serde::Deserialize;\n\n\t#[derive(Clone, Debug, PartialEq, DynAny, Default, serde::Serialize, serde::Deserialize)]\n\tpub struct OldGraphicGroup {\n\t\telements: Vec<(Graphic, Option<NodeId>)>,\n\t\ttransform: DAffine2,\n\t\talpha_blending: AlphaBlending,\n\t}\n\t#[derive(Clone, Debug, PartialEq, DynAny, Default, serde::Serialize, serde::Deserialize)]\n\tpub struct GraphicGroup {\n\t\telements: Vec<(Graphic, Option<NodeId>)>,\n\t}\n\n\t#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]\n\tpub struct OlderTable<T> {\n\t\tid: Vec<u64>,\n\t\t#[serde(alias = \"instances\", alias = \"instance\")]\n\t\telement: Vec<T>,\n\t}\n\n\t#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]\n\tpub struct OldTable<T> {\n\t\tid: Vec<u64>,\n\t\t#[serde(alias = \"instances\", alias = \"instance\")]\n\t\telement: Vec<T>,\n\t\ttransform: Vec<DAffine2>,\n\t\talpha_blending: Vec<AlphaBlending>,\n\t}\n\n\t#[derive(serde::Serialize, serde::Deserialize)]\n\t#[serde(untagged)]\n\tenum GraphicFormat {\n\t\tOldGraphicGroup(OldGraphicGroup),\n\t\tOlderTableOldGraphicGroup(OlderTable<OldGraphicGroup>),\n\t\tOldTableOldGraphicGroup(OldTable<OldGraphicGroup>),\n\t\tOldTableGraphicGroup(OldTable<GraphicGroup>),\n\t\tTable(serde_json::Value),\n\t}\n\n\tOk(match GraphicFormat::deserialize(deserializer)? {\n\t\tGraphicFormat::OldGraphicGroup(old) => {\n\t\t\tlet mut graphic_table = Table::new();\n\t\t\tfor (graphic, source_node_id) in old.elements {\n\t\t\t\tgraphic_table.push(TableRow {\n\t\t\t\t\telement: graphic,\n\t\t\t\t\ttransform: old.transform,\n\t\t\t\t\talpha_blending: old.alpha_blending,\n\t\t\t\t\tsource_node_id,\n\t\t\t\t});\n\t\t\t}\n\t\t\tgraphic_table\n\t\t}\n\t\tGraphicFormat::OlderTableOldGraphicGroup(old) => old\n\t\t\t.element\n\t\t\t.into_iter()\n\t\t\t.flat_map(|element| {\n\t\t\t\telement.elements.into_iter().map(move |(graphic, source_node_id)| TableRow {\n\t\t\t\t\telement: graphic,\n\t\t\t\t\ttransform: element.transform,\n\t\t\t\t\talpha_blending: element.alpha_blending,\n\t\t\t\t\tsource_node_id,\n\t\t\t\t})\n\t\t\t})\n\t\t\t.collect(),\n\t\tGraphicFormat::OldTableOldGraphicGroup(old) => old\n\t\t\t.element\n\t\t\t.into_iter()\n\t\t\t.flat_map(|element| {\n\t\t\t\telement.elements.into_iter().map(move |(graphic, source_node_id)| TableRow {\n\t\t\t\t\telement: graphic,\n\t\t\t\t\ttransform: element.transform,\n\t\t\t\t\talpha_blending: element.alpha_blending,\n\t\t\t\t\tsource_node_id,\n\t\t\t\t})\n\t\t\t})\n\t\t\t.collect(),\n\t\tGraphicFormat::OldTableGraphicGroup(old) => old\n\t\t\t.element\n\t\t\t.into_iter()\n\t\t\t.flat_map(|element| {\n\t\t\t\telement.elements.into_iter().map(move |(graphic, source_node_id)| TableRow {\n\t\t\t\t\telement: graphic,\n\t\t\t\t\ttransform: Default::default(),\n\t\t\t\t\talpha_blending: Default::default(),\n\t\t\t\t\tsource_node_id,\n\t\t\t\t})\n\t\t\t})\n\t\t\t.collect(),\n\t\tGraphicFormat::Table(value) => {\n\t\t\t// Try to deserialize as either table format\n\t\t\tif let Ok(old_table) = serde_json::from_value::<Table<GraphicGroup>>(value.clone()) {\n\t\t\t\tlet mut graphic_table = Table::new();\n\t\t\t\tfor row in old_table.iter() {\n\t\t\t\t\tfor (graphic, source_node_id) in &row.element.elements {\n\t\t\t\t\t\tgraphic_table.push(TableRow {\n\t\t\t\t\t\t\telement: graphic.clone(),\n\t\t\t\t\t\t\ttransform: *row.transform,\n\t\t\t\t\t\t\talpha_blending: *row.alpha_blending,\n\t\t\t\t\t\t\tsource_node_id: *source_node_id,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tgraphic_table\n\t\t\t} else if let Ok(new_table) = serde_json::from_value::<Table<Graphic>>(value) {\n\t\t\t\tnew_table\n\t\t\t} else {\n\t\t\t\treturn Err(serde::de::Error::custom(\"Failed to deserialize Table<Graphic>\"));\n\t\t\t}\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "node-graph/libraries/graphic-types/src/lib.rs",
    "content": "pub mod artboard;\npub mod graphic;\n\n// Re-export all transitive dependencies so downstream crates only need to depend on graphic-types\npub use core_types;\npub use raster_types;\npub use vector_types;\n\n// Re-export commonly used types at the crate root\npub use artboard::Artboard;\npub use graphic::{Graphic, IntoGraphicTable, TryFromGraphic, Vector};\n\npub mod migrations {\n\tuse core_types::{\n\t\tAlphaBlending,\n\t\ttable::{Table, TableRow},\n\t};\n\tuse dyn_any::DynAny;\n\tuse glam::DAffine2;\n\tuse vector_types::vector::{PathStyle, PointDomain, RegionDomain, SegmentDomain, misc::HandleId};\n\n\tuse crate::{Graphic, Vector};\n\n\t// TODO: Eventually remove this migration document upgrade code\n\tpub fn migrate_vector<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Vector>, D::Error> {\n\t\tuse serde::Deserialize;\n\n\t\t#[derive(Clone, Debug, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]\n\t\tpub struct OldVectorData {\n\t\t\tpub transform: DAffine2,\n\t\t\tpub alpha_blending: AlphaBlending,\n\n\t\t\tpub style: PathStyle,\n\n\t\t\tpub colinear_manipulators: Vec<[HandleId; 2]>,\n\n\t\t\tpub point_domain: PointDomain,\n\t\t\tpub segment_domain: SegmentDomain,\n\t\t\tpub region_domain: RegionDomain,\n\n\t\t\tpub upstream_graphic_group: Option<Table<Graphic>>,\n\t\t}\n\n\t\t#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]\n\t\tpub struct OldTable<T> {\n\t\t\t#[serde(alias = \"instances\", alias = \"instance\")]\n\t\t\telement: Vec<T>,\n\t\t\ttransform: Vec<DAffine2>,\n\t\t\talpha_blending: Vec<AlphaBlending>,\n\t\t}\n\n\t\t#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]\n\t\tpub struct OlderTable<T> {\n\t\t\tid: Vec<u64>,\n\t\t\t#[serde(alias = \"instances\", alias = \"instance\")]\n\t\t\telement: Vec<T>,\n\t\t}\n\n\t\t#[derive(serde::Serialize, serde::Deserialize)]\n\t\t#[serde(untagged)]\n\t\t#[allow(clippy::large_enum_variant)]\n\t\tenum VectorFormat {\n\t\t\tVector(Vector),\n\t\t\tOldVectorData(OldVectorData),\n\t\t\tOldVectorTable(OldTable<Vector>),\n\t\t\tOlderVectorTable(OlderTable<Vector>),\n\t\t\tVectorTable(Table<Vector>),\n\t\t}\n\n\t\tOk(match VectorFormat::deserialize(deserializer)? {\n\t\t\tVectorFormat::Vector(vector) => Table::new_from_element(vector),\n\t\t\tVectorFormat::OldVectorData(old) => {\n\t\t\t\tlet mut vector_table = Table::new_from_element(Vector {\n\t\t\t\t\tstyle: old.style,\n\t\t\t\t\tcolinear_manipulators: old.colinear_manipulators,\n\t\t\t\t\tpoint_domain: old.point_domain,\n\t\t\t\t\tsegment_domain: old.segment_domain,\n\t\t\t\t\tregion_domain: old.region_domain,\n\t\t\t\t\tupstream_data: old.upstream_graphic_group,\n\t\t\t\t});\n\t\t\t\t*vector_table.iter_mut().next().unwrap().transform = old.transform;\n\t\t\t\t*vector_table.iter_mut().next().unwrap().alpha_blending = old.alpha_blending;\n\t\t\t\tvector_table\n\t\t\t}\n\t\t\tVectorFormat::OlderVectorTable(older_table) => older_table.element.into_iter().map(|element| TableRow { element, ..Default::default() }).collect(),\n\t\t\tVectorFormat::OldVectorTable(old_table) => old_table\n\t\t\t\t.element\n\t\t\t\t.into_iter()\n\t\t\t\t.zip(old_table.transform.into_iter().zip(old_table.alpha_blending))\n\t\t\t\t.map(|(element, (transform, alpha_blending))| TableRow {\n\t\t\t\t\telement,\n\t\t\t\t\ttransform,\n\t\t\t\t\talpha_blending,\n\t\t\t\t\tsource_node_id: None,\n\t\t\t\t})\n\t\t\t\t.collect(),\n\t\t\tVectorFormat::VectorTable(vector_table) => vector_table,\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/no-std-types/Cargo.toml",
    "content": "[package]\nname = \"no-std-types\"\nversion = \"0.1.0\"\nedition = \"2024\"\ndescription = \"no_std types for Graphene (shader-compatible)\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\nlicense = \"MIT OR Apache-2.0\"\n\n[features]\n# any feature that\n# * must be usable in shaders\n# * but requires std\n# * and should be on by default\n# should be in this list instead of `[workspace.dependency]`\nstd = [\n\t\"dep:dyn-any\",\n\t\"dep:serde\",\n\t\"dep:log\",\n\t\"glam/debug-glam-assert\",\n\t\"glam/std\",\n\t\"glam/serde\",\n\t\"half/std\",\n\t\"half/serde\",\n\t\"num-traits/std\",\n\t\"num_enum/std\",\n]\nwasm = [\"tsify\", \"wasm-bindgen\"]\n\n[dependencies]\n# Local dependencies\nnode-macro = { workspace = true }\n\n# Local std dependencies\ndyn-any = { workspace = true, optional = true }\n\n# Workspace dependencies\nbytemuck = { workspace = true }\nglam = { workspace = true }\nhalf = { workspace = true, default-features = false }\nnum-derive = { workspace = true }\nnum-traits = { workspace = true }\nnum_enum = { workspace = true }\nspirv-std = { workspace = true }\n\n# Workspace std dependencies\nserde = { workspace = true, optional = true }\nlog = { workspace = true, optional = true }\n\n# Workspace wasm dependencies\ntsify = { workspace = true, optional = true }\nwasm-bindgen = { workspace = true, optional = true }\n\n[dev-dependencies]\ncore-types = { workspace = true }\n\n[lints.rust]\n# the spirv target is not in the list of common cfgs so must be added manually\nunexpected_cfgs = { level = \"warn\", check-cfg = [\n\t'cfg(target_arch, values(\"spirv\"))',\n] }\n\n[package.metadata.cargo-shear]\nignored = [\"core-types\"]\n"
  },
  {
    "path": "node-graph/libraries/no-std-types/src/blending.rs",
    "content": "use core::fmt::Display;\nuse core::hash::{Hash, Hasher};\nuse node_macro::BufferStruct;\nuse num_enum::{FromPrimitive, IntoPrimitive};\n#[cfg(not(feature = \"std\"))]\nuse num_traits::float::Float;\n\n#[derive(Debug, Clone, Copy, PartialEq, BufferStruct)]\n#[cfg_attr(feature = \"std\", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[cfg_attr(feature = \"std\", serde(default))]\npub struct AlphaBlending {\n\tpub blend_mode: BlendMode,\n\tpub opacity: f32,\n\tpub fill: f32,\n\tpub clip: bool,\n}\nimpl Default for AlphaBlending {\n\tfn default() -> Self {\n\t\tSelf::new()\n\t}\n}\nimpl Hash for AlphaBlending {\n\tfn hash<H: Hasher>(&self, state: &mut H) {\n\t\tself.opacity.to_bits().hash(state);\n\t\tself.fill.to_bits().hash(state);\n\t\tself.blend_mode.hash(state);\n\t\tself.clip.hash(state);\n\t}\n}\nimpl Display for AlphaBlending {\n\tfn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n\t\tlet round = |x: f32| (x * 1e3).round() / 1e3;\n\t\twrite!(\n\t\t\tf,\n\t\t\t\"Blend Mode: {} — Opacity: {}% — Fill: {}% — Clip: {}\",\n\t\t\tself.blend_mode,\n\t\t\tround(self.opacity * 100.),\n\t\t\tround(self.fill * 100.),\n\t\t\tif self.clip { \"Yes\" } else { \"No\" }\n\t\t)\n\t}\n}\n\nimpl AlphaBlending {\n\tpub const fn new() -> Self {\n\t\tSelf {\n\t\t\topacity: 1.,\n\t\t\tfill: 1.,\n\t\t\tblend_mode: BlendMode::Normal,\n\t\t\tclip: false,\n\t\t}\n\t}\n\n\tpub fn lerp(&self, other: &Self, t: f32) -> Self {\n\t\tlet lerp = |a: f32, b: f32, t: f32| a + (b - a) * t;\n\n\t\tAlphaBlending {\n\t\t\topacity: lerp(self.opacity, other.opacity, t),\n\t\t\tfill: lerp(self.fill, other.fill, t),\n\t\t\tblend_mode: if t < 0.5 { self.blend_mode } else { other.blend_mode },\n\t\t\tclip: if t < 0.5 { self.clip } else { other.clip },\n\t\t}\n\t}\n\n\tpub fn opacity(&self, mask: bool) -> f32 {\n\t\tself.opacity * if mask { 1. } else { self.fill }\n\t}\n}\n\n#[repr(i32)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[cfg_attr(feature = \"std\", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))]\n#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, BufferStruct, FromPrimitive, IntoPrimitive)]\npub enum BlendMode {\n\t// Basic group\n\t#[default]\n\tNormal,\n\n\t// Darken group\n\tDarken,\n\tMultiply,\n\tColorBurn,\n\tLinearBurn,\n\tDarkerColor,\n\n\t// Lighten group\n\tLighten,\n\tScreen,\n\tColorDodge,\n\tLinearDodge,\n\tLighterColor,\n\n\t// Contrast group\n\tOverlay,\n\tSoftLight,\n\tHardLight,\n\tVividLight,\n\tLinearLight,\n\tPinLight,\n\tHardMix,\n\n\t// Inversion group\n\tDifference,\n\tExclusion,\n\tSubtract,\n\tDivide,\n\n\t// Component group\n\tHue,\n\tSaturation,\n\tColor,\n\tLuminosity,\n\n\t// Other stuff\n\tErase,\n\tRestore,\n\tMultiplyAlpha,\n}\n\nimpl BlendMode {\n\t/// All standard blend modes ordered by group.\n\tpub fn list() -> [&'static [BlendMode]; 6] {\n\t\tuse BlendMode::*;\n\t\t[\n\t\t\t// Normal group\n\t\t\t&[Normal],\n\t\t\t// Darken group\n\t\t\t&[Darken, Multiply, ColorBurn, LinearBurn, DarkerColor],\n\t\t\t// Lighten group\n\t\t\t&[Lighten, Screen, ColorDodge, LinearDodge, LighterColor],\n\t\t\t// Contrast group\n\t\t\t&[Overlay, SoftLight, HardLight, VividLight, LinearLight, PinLight, HardMix],\n\t\t\t// Inversion group\n\t\t\t&[Difference, Exclusion, Subtract, Divide],\n\t\t\t// Component group\n\t\t\t&[Hue, Saturation, Color, Luminosity],\n\t\t]\n\t}\n\n\t/// The subset of [`BlendMode::list()`] that is supported by SVG.\n\tpub fn list_svg_subset() -> [&'static [BlendMode]; 6] {\n\t\tuse BlendMode::*;\n\t\t[\n\t\t\t// Normal group\n\t\t\t&[Normal],\n\t\t\t// Darken group\n\t\t\t&[Darken, Multiply, ColorBurn],\n\t\t\t// Lighten group\n\t\t\t&[Lighten, Screen, ColorDodge],\n\t\t\t// Contrast group\n\t\t\t&[Overlay, SoftLight, HardLight],\n\t\t\t// Inversion group\n\t\t\t&[Difference, Exclusion],\n\t\t\t// Component group\n\t\t\t&[Hue, Saturation, Color, Luminosity],\n\t\t]\n\t}\n\n\tpub fn index_in_list(&self) -> Option<usize> {\n\t\tSelf::list().iter().flat_map(|x| x.iter()).position(|&blend_mode| blend_mode == *self)\n\t}\n\n\tpub fn index_in_list_svg_subset(&self) -> Option<usize> {\n\t\tSelf::list_svg_subset().iter().flat_map(|x| x.iter()).position(|&blend_mode| blend_mode == *self)\n\t}\n\n\t/// Convert the enum to the CSS string for the blend mode.\n\t/// [Read more](https://developer.mozilla.org/en-US/docs/Web/CSS/blend-mode#values)\n\tpub fn to_svg_style_name(&self) -> Option<&'static str> {\n\t\tmatch self {\n\t\t\t// Normal group\n\t\t\tBlendMode::Normal => Some(\"normal\"),\n\t\t\t// Darken group\n\t\t\tBlendMode::Darken => Some(\"darken\"),\n\t\t\tBlendMode::Multiply => Some(\"multiply\"),\n\t\t\tBlendMode::ColorBurn => Some(\"color-burn\"),\n\t\t\t// Lighten group\n\t\t\tBlendMode::Lighten => Some(\"lighten\"),\n\t\t\tBlendMode::Screen => Some(\"screen\"),\n\t\t\tBlendMode::ColorDodge => Some(\"color-dodge\"),\n\t\t\t// Contrast group\n\t\t\tBlendMode::Overlay => Some(\"overlay\"),\n\t\t\tBlendMode::SoftLight => Some(\"soft-light\"),\n\t\t\tBlendMode::HardLight => Some(\"hard-light\"),\n\t\t\t// Inversion group\n\t\t\tBlendMode::Difference => Some(\"difference\"),\n\t\t\tBlendMode::Exclusion => Some(\"exclusion\"),\n\t\t\t// Component group\n\t\t\tBlendMode::Hue => Some(\"hue\"),\n\t\t\tBlendMode::Saturation => Some(\"saturation\"),\n\t\t\tBlendMode::Color => Some(\"color\"),\n\t\t\tBlendMode::Luminosity => Some(\"luminosity\"),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\t/// Renders the blend mode CSS style declaration.\n\t#[cfg(feature = \"std\")]\n\tpub fn render(&self) -> String {\n\t\tformat!(\n\t\t\tr#\" mix-blend-mode: {};\"#,\n\t\t\tself.to_svg_style_name().unwrap_or_else(|| {\n\t\t\t\tlog::warn!(\"Unsupported blend mode {self:?}\");\n\t\t\t\t\"normal\"\n\t\t\t})\n\t\t)\n\t}\n}\n\nimpl Display for BlendMode {\n\tfn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n\t\tmatch self {\n\t\t\t// Normal group\n\t\t\tBlendMode::Normal => write!(f, \"Normal\"),\n\t\t\t// Darken group\n\t\t\tBlendMode::Darken => write!(f, \"Darken\"),\n\t\t\tBlendMode::Multiply => write!(f, \"Multiply\"),\n\t\t\tBlendMode::ColorBurn => write!(f, \"Color Burn\"),\n\t\t\tBlendMode::LinearBurn => write!(f, \"Linear Burn\"),\n\t\t\tBlendMode::DarkerColor => write!(f, \"Darker Color\"),\n\t\t\t// Lighten group\n\t\t\tBlendMode::Lighten => write!(f, \"Lighten\"),\n\t\t\tBlendMode::Screen => write!(f, \"Screen\"),\n\t\t\tBlendMode::ColorDodge => write!(f, \"Color Dodge\"),\n\t\t\tBlendMode::LinearDodge => write!(f, \"Linear Dodge\"),\n\t\t\tBlendMode::LighterColor => write!(f, \"Lighter Color\"),\n\t\t\t// Contrast group\n\t\t\tBlendMode::Overlay => write!(f, \"Overlay\"),\n\t\t\tBlendMode::SoftLight => write!(f, \"Soft Light\"),\n\t\t\tBlendMode::HardLight => write!(f, \"Hard Light\"),\n\t\t\tBlendMode::VividLight => write!(f, \"Vivid Light\"),\n\t\t\tBlendMode::LinearLight => write!(f, \"Linear Light\"),\n\t\t\tBlendMode::PinLight => write!(f, \"Pin Light\"),\n\t\t\tBlendMode::HardMix => write!(f, \"Hard Mix\"),\n\t\t\t// Inversion group\n\t\t\tBlendMode::Difference => write!(f, \"Difference\"),\n\t\t\tBlendMode::Exclusion => write!(f, \"Exclusion\"),\n\t\t\tBlendMode::Subtract => write!(f, \"Subtract\"),\n\t\t\tBlendMode::Divide => write!(f, \"Divide\"),\n\t\t\t// Component group\n\t\t\tBlendMode::Hue => write!(f, \"Hue\"),\n\t\t\tBlendMode::Saturation => write!(f, \"Saturation\"),\n\t\t\tBlendMode::Color => write!(f, \"Color\"),\n\t\t\tBlendMode::Luminosity => write!(f, \"Luminosity\"),\n\t\t\t// Other utility blend modes (hidden from the normal list)\n\t\t\tBlendMode::Erase => write!(f, \"Erase\"),\n\t\t\tBlendMode::Restore => write!(f, \"Restore\"),\n\t\t\tBlendMode::MultiplyAlpha => write!(f, \"Multiply Alpha\"),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/no-std-types/src/choice_type.rs",
    "content": "pub trait ChoiceTypeStatic: Sized + Copy + crate::AsU32 + Send + Sync {\n\tconst WIDGET_HINT: ChoiceWidgetHint;\n\tconst DESCRIPTION: Option<&'static str>;\n\tfn list() -> &'static [&'static [(Self, VariantMetadata)]];\n}\n\npub enum ChoiceWidgetHint {\n\tDropdown,\n\tRadioButtons,\n}\n\n/// Translation struct between macro and definition.\n#[derive(Clone, Debug)]\npub struct VariantMetadata {\n\t/// Name as declared in source code.\n\tpub name: &'static str,\n\n\t/// Name to be displayed in UI.\n\tpub label: &'static str,\n\n\t/// User-facing documentation text.\n\tpub description: Option<&'static str>,\n\n\t/// Name of icon to display in radio buttons and such.\n\tpub icon: Option<&'static str>,\n}\n"
  },
  {
    "path": "node-graph/libraries/no-std-types/src/color/color_traits.rs",
    "content": "pub use crate::blending::*;\nuse bytemuck::{Pod, Zeroable};\nuse core::fmt::Debug;\nuse glam::DVec2;\nuse num_derive::*;\n#[cfg(not(feature = \"std\"))]\nuse num_traits::float::Float;\n\npub trait Linear {\n\tfn from_f32(x: f32) -> Self;\n\tfn to_f32(self) -> f32;\n\tfn from_f64(x: f64) -> Self;\n\tfn to_f64(self) -> f64;\n\tfn lerp(self, other: Self, value: Self) -> Self\n\twhere\n\t\tSelf: Sized + Copy,\n\t\tSelf: core::ops::Sub<Self, Output = Self>,\n\t\tSelf: core::ops::Mul<Self, Output = Self>,\n\t\tSelf: core::ops::Add<Self, Output = Self>,\n\t{\n\t\tself + (other - self) * value\n\t}\n}\n\n#[rustfmt::skip]\nimpl Linear for f32 {\n\t#[inline(always)] fn from_f32(x: f32) -> Self { x }\n\t#[inline(always)] fn to_f32(self) -> f32 { self }\n\t#[inline(always)] fn from_f64(x: f64) -> Self { x as f32 }\n\t#[inline(always)] fn to_f64(self) -> f64 { self as f64 }\n}\n\n#[rustfmt::skip]\nimpl Linear for f64 {\n\t#[inline(always)] fn from_f32(x: f32) -> Self { x as f64 }\n\t#[inline(always)] fn to_f32(self) -> f32 { self as f32 }\n\t#[inline(always)] fn from_f64(x: f64) -> Self { x }\n\t#[inline(always)] fn to_f64(self) -> f64 { self }\n}\n\npub trait Channel: Copy + Debug {\n\tfn to_linear<Out: Linear>(self) -> Out;\n\tfn from_linear<In: Linear>(linear: In) -> Self;\n}\n\npub trait LinearChannel: Channel {\n\tfn cast_linear_channel<Out: LinearChannel>(self) -> Out {\n\t\tOut::from_linear(self.to_linear::<f64>())\n\t}\n}\n\nimpl<T: Linear + Debug + Copy> Channel for T {\n\t#[inline(always)]\n\tfn to_linear<Out: Linear>(self) -> Out {\n\t\tOut::from_f64(self.to_f64())\n\t}\n\n\t#[inline(always)]\n\tfn from_linear<In: Linear>(linear: In) -> Self {\n\t\tSelf::from_f64(linear.to_f64())\n\t}\n}\n\nimpl<T: Linear + Debug + Copy> LinearChannel for T {}\n\n#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Num, NumCast, NumOps, One, Zero, ToPrimitive, FromPrimitive)]\npub struct SRGBGammaFloat(f32);\n\nimpl Channel for SRGBGammaFloat {\n\t#[inline(always)]\n\tfn to_linear<Out: Linear>(self) -> Out {\n\t\tlet x = self.0;\n\t\tOut::from_f32(if x <= 0.04045 { x / 12.92 } else { ((x + 0.055) / 1.055).powf(2.4) })\n\t}\n\n\t#[inline(always)]\n\tfn from_linear<In: Linear>(linear: In) -> Self {\n\t\tlet x = linear.to_f32();\n\t\tif x <= 0.0031308 { Self(x * 12.92) } else { Self(1.055 * x.powf(1. / 2.4) - 0.055) }\n\t}\n}\npub trait RGBPrimaries {\n\tconst RED: DVec2;\n\tconst GREEN: DVec2;\n\tconst BLUE: DVec2;\n\tconst WHITE: DVec2;\n}\npub trait Rec709Primaries {}\nimpl<T: Rec709Primaries> RGBPrimaries for T {\n\tconst RED: DVec2 = DVec2::new(0.64, 0.33);\n\tconst GREEN: DVec2 = DVec2::new(0.3, 0.6);\n\tconst BLUE: DVec2 = DVec2::new(0.15, 0.06);\n\tconst WHITE: DVec2 = DVec2::new(0.3127, 0.329);\n}\n\npub trait SRGB: Rec709Primaries {}\n\n// TODO: Come up with a better name for this trait\npub trait Pixel: Clone + Pod + Zeroable + Default {\n\t#[cfg(feature = \"std\")]\n\tfn to_bytes(&self) -> Vec<u8> {\n\t\tbytemuck::bytes_of(self).to_vec()\n\t}\n\t// TODO: use u8 for Color\n\tfn from_bytes(bytes: &[u8]) -> Self {\n\t\t*bytemuck::try_from_bytes(bytes).expect(\"Failed to convert bytes to pixel\")\n\t}\n\n\tfn byte_size() -> usize {\n\t\tsize_of::<Self>()\n\t}\n}\npub trait RGB: Pixel {\n\ttype ColorChannel: Channel;\n\n\tfn red(&self) -> Self::ColorChannel;\n\tfn r(&self) -> Self::ColorChannel {\n\t\tself.red()\n\t}\n\tfn green(&self) -> Self::ColorChannel;\n\tfn g(&self) -> Self::ColorChannel {\n\t\tself.green()\n\t}\n\tfn blue(&self) -> Self::ColorChannel;\n\tfn b(&self) -> Self::ColorChannel {\n\t\tself.blue()\n\t}\n}\npub trait RGBMut: RGB {\n\tfn set_red(&mut self, red: Self::ColorChannel);\n\tfn set_green(&mut self, green: Self::ColorChannel);\n\tfn set_blue(&mut self, blue: Self::ColorChannel);\n}\n\npub trait AssociatedAlpha: RGB + Alpha {\n\tfn to_unassociated<Out: UnassociatedAlpha>(&self) -> Out;\n}\n\npub trait UnassociatedAlpha: RGB + Alpha {\n\tfn to_associated<Out: AssociatedAlpha>(&self) -> Out;\n}\n\npub trait Alpha {\n\ttype AlphaChannel: LinearChannel;\n\tconst TRANSPARENT: Self;\n\tfn alpha(&self) -> Self::AlphaChannel;\n\tfn a(&self) -> Self::AlphaChannel {\n\t\tself.alpha()\n\t}\n\tfn multiplied_alpha(&self, alpha: Self::AlphaChannel) -> Self;\n}\npub trait AlphaMut: Alpha {\n\tfn set_alpha(&mut self, value: Self::AlphaChannel);\n}\n\npub trait Depth {\n\ttype DepthChannel: Channel;\n\tfn depth(&self) -> Self::DepthChannel;\n\tfn d(&self) -> Self::DepthChannel {\n\t\tself.depth()\n\t}\n}\n\npub trait ExtraChannels<const NUM: usize> {\n\ttype ChannelType: Channel;\n\tfn extra_channels(&self) -> [Self::ChannelType; NUM];\n}\n\npub trait Luminance {\n\ttype LuminanceChannel: LinearChannel;\n\tfn luminance(&self) -> Self::LuminanceChannel;\n\tfn l(&self) -> Self::LuminanceChannel {\n\t\tself.luminance()\n\t}\n}\n\npub trait LuminanceMut: Luminance {\n\tfn set_luminance(&mut self, luminance: Self::LuminanceChannel);\n}\n\n// TODO: We might rename this to Raster at some point\npub trait Sample {\n\ttype Pixel: Pixel;\n\t// TODO: Add an area parameter\n\tfn sample(&self, pos: DVec2, area: DVec2) -> Option<Self::Pixel>;\n}\n\nimpl<T: Sample> Sample for &T {\n\ttype Pixel = T::Pixel;\n\n\t#[inline(always)]\n\tfn sample(&self, pos: DVec2, area: DVec2) -> Option<Self::Pixel> {\n\t\t(**self).sample(pos, area)\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/no-std-types/src/color/color_types.rs",
    "content": "use super::color_traits::{Alpha, AlphaMut, AssociatedAlpha, Luminance, LuminanceMut, Pixel, RGB, RGBMut, Rec709Primaries, SRGB};\nuse super::discrete_srgb::{float_to_srgb_u8, srgb_u8_to_float};\nuse bytemuck::{Pod, Zeroable};\nuse core::fmt::Debug;\nuse core::hash::Hash;\nuse glam::Vec4;\nuse half::f16;\nuse node_macro::BufferStruct;\n#[cfg(not(feature = \"std\"))]\nuse num_traits::Euclid;\n#[cfg(not(feature = \"std\"))]\nuse num_traits::float::Float;\n\n#[repr(C)]\n#[derive(Default, Clone, Copy, PartialEq, Pod, Zeroable)]\n#[cfg_attr(not(target_arch = \"spirv\"), derive(Debug))]\n#[cfg_attr(feature = \"std\", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))]\npub struct RGBA16F {\n\tred: f16,\n\tgreen: f16,\n\tblue: f16,\n\talpha: f16,\n}\n\n/// hack around half still masking out impl Debug for f16 on spirv\n#[cfg(target_arch = \"spirv\")]\nimpl core::fmt::Debug for RGBA16F {\n\tfn fmt(&self, _f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n\t\tOk(())\n\t}\n}\n\nimpl From<Color> for RGBA16F {\n\t#[inline(always)]\n\tfn from(c: Color) -> Self {\n\t\tSelf {\n\t\t\tred: f16::from_f32(c.r()),\n\t\t\tgreen: f16::from_f32(c.g()),\n\t\t\tblue: f16::from_f32(c.b()),\n\t\t\talpha: f16::from_f32(c.a()),\n\t\t}\n\t}\n}\n\nimpl Luminance for RGBA16F {\n\ttype LuminanceChannel = f32;\n\t#[inline(always)]\n\tfn luminance(&self) -> f32 {\n\t\t// TODO: verify this is correct for sRGB\n\t\t0.2126 * self.red() + 0.7152 * self.green() + 0.0722 * self.blue()\n\t}\n}\n\nimpl RGB for RGBA16F {\n\ttype ColorChannel = f32;\n\t#[inline(always)]\n\tfn red(&self) -> f32 {\n\t\tself.red.to_f32()\n\t}\n\t#[inline(always)]\n\tfn green(&self) -> f32 {\n\t\tself.green.to_f32()\n\t}\n\t#[inline(always)]\n\tfn blue(&self) -> f32 {\n\t\tself.blue.to_f32()\n\t}\n}\n\nimpl Rec709Primaries for RGBA16F {}\n\nimpl Alpha for RGBA16F {\n\ttype AlphaChannel = f32;\n\t#[inline(always)]\n\tfn alpha(&self) -> f32 {\n\t\tself.alpha.to_f32() / 255.\n\t}\n\n\tconst TRANSPARENT: Self = RGBA16F {\n\t\tred: f16::from_f32_const(0.),\n\t\tgreen: f16::from_f32_const(0.),\n\t\tblue: f16::from_f32_const(0.),\n\t\talpha: f16::from_f32_const(0.),\n\t};\n\n\tfn multiplied_alpha(&self, alpha: Self::AlphaChannel) -> Self {\n\t\tlet alpha = alpha * 255.;\n\t\tlet mut result = *self;\n\t\tresult.alpha = f16::from_f32(alpha * self.alpha());\n\t\tresult\n\t}\n}\n\nimpl Pixel for RGBA16F {}\n\n#[repr(C)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[cfg_attr(feature = \"std\", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))]\n#[derive(Debug, Default, Clone, Copy, PartialEq, Pod, Zeroable)]\npub struct SRGBA8 {\n\tred: u8,\n\tgreen: u8,\n\tblue: u8,\n\talpha: u8,\n}\n\nimpl From<Color> for SRGBA8 {\n\t#[inline(always)]\n\tfn from(c: Color) -> Self {\n\t\tSelf {\n\t\t\tred: float_to_srgb_u8(c.r()),\n\t\t\tgreen: float_to_srgb_u8(c.g()),\n\t\t\tblue: float_to_srgb_u8(c.b()),\n\t\t\talpha: (c.a() * 255.) as u8,\n\t\t}\n\t}\n}\n\nimpl From<SRGBA8> for Color {\n\t#[inline(always)]\n\tfn from(color: SRGBA8) -> Self {\n\t\tSelf {\n\t\t\tred: srgb_u8_to_float(color.red),\n\t\t\tgreen: srgb_u8_to_float(color.green),\n\t\t\tblue: srgb_u8_to_float(color.blue),\n\t\t\talpha: color.alpha as f32 / 255.,\n\t\t}\n\t}\n}\n\nimpl Luminance for SRGBA8 {\n\ttype LuminanceChannel = f32;\n\t#[inline(always)]\n\tfn luminance(&self) -> f32 {\n\t\t// TODO: verify this is correct for sRGB\n\t\t0.2126 * self.red() + 0.7152 * self.green() + 0.0722 * self.blue()\n\t}\n}\n\nimpl RGB for SRGBA8 {\n\ttype ColorChannel = f32;\n\t#[inline(always)]\n\tfn red(&self) -> f32 {\n\t\tself.red as f32 / 255.\n\t}\n\t#[inline(always)]\n\tfn green(&self) -> f32 {\n\t\tself.green as f32 / 255.\n\t}\n\t#[inline(always)]\n\tfn blue(&self) -> f32 {\n\t\tself.blue as f32 / 255.\n\t}\n}\n\nimpl Rec709Primaries for SRGBA8 {}\nimpl SRGB for SRGBA8 {}\n\nimpl Alpha for SRGBA8 {\n\ttype AlphaChannel = f32;\n\t#[inline(always)]\n\tfn alpha(&self) -> f32 {\n\t\tself.alpha as f32 / 255.\n\t}\n\n\tconst TRANSPARENT: Self = SRGBA8 { red: 0, green: 0, blue: 0, alpha: 0 };\n\n\tfn multiplied_alpha(&self, alpha: Self::AlphaChannel) -> Self {\n\t\tlet alpha = alpha * 255.;\n\t\tlet mut result = *self;\n\t\tresult.alpha = (alpha * self.alpha()) as u8;\n\t\tresult\n\t}\n}\n\nimpl Pixel for SRGBA8 {}\n\n#[repr(C)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[cfg_attr(feature = \"std\", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))]\n#[derive(Debug, Default, Clone, Copy, PartialEq, Pod, Zeroable)]\npub struct Luma(pub f32);\n\nimpl Luminance for Luma {\n\ttype LuminanceChannel = f32;\n\t#[inline(always)]\n\tfn luminance(&self) -> f32 {\n\t\tself.0\n\t}\n}\n\nimpl LuminanceMut for Luma {\n\tfn set_luminance(&mut self, luminance: Self::LuminanceChannel) {\n\t\tself.0 = luminance\n\t}\n}\n\nimpl RGB for Luma {\n\ttype ColorChannel = f32;\n\t#[inline(always)]\n\tfn red(&self) -> f32 {\n\t\tself.0\n\t}\n\t#[inline(always)]\n\tfn green(&self) -> f32 {\n\t\tself.0\n\t}\n\t#[inline(always)]\n\tfn blue(&self) -> f32 {\n\t\tself.0\n\t}\n}\n\nimpl Pixel for Luma {}\n\n/// Structure that represents a color.\n/// Internally alpha is stored as `f32` that ranges from `0.0` (transparent) to `1.0` (opaque).\n/// The other components (RGB) are stored as `f32` that range from `0.0` up to `f32::MAX`,\n/// the values encode the brightness of each channel proportional to the light intensity in cd/m² (nits) in HDR, and `0.0` (black) to `1.0` (white) in SDR color.\n#[repr(C)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[cfg_attr(feature = \"std\", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))]\n#[derive(Debug, Default, Clone, Copy, Pod, Zeroable, BufferStruct)]\npub struct Color {\n\tred: f32,\n\tgreen: f32,\n\tblue: f32,\n\talpha: f32,\n}\n\nimpl PartialEq for Color {\n\tfn eq(&self, other: &Self) -> bool {\n\t\tself.red == other.red && self.green == other.green && self.blue == other.blue && self.alpha == other.alpha\n\t}\n}\n\nimpl Eq for Color {}\n\n#[allow(clippy::derived_hash_with_manual_eq)]\nimpl Hash for Color {\n\tfn hash<H: core::hash::Hasher>(&self, state: &mut H) {\n\t\tself.red.to_bits().hash(state);\n\t\tself.green.to_bits().hash(state);\n\t\tself.blue.to_bits().hash(state);\n\t\tself.alpha.to_bits().hash(state);\n\t}\n}\n\nimpl RGB for Color {\n\ttype ColorChannel = f32;\n\t#[inline(always)]\n\tfn red(&self) -> f32 {\n\t\tself.red\n\t}\n\t#[inline(always)]\n\tfn green(&self) -> f32 {\n\t\tself.green\n\t}\n\t#[inline(always)]\n\tfn blue(&self) -> f32 {\n\t\tself.blue\n\t}\n}\nimpl RGBMut for Color {\n\tfn set_red(&mut self, red: Self::ColorChannel) {\n\t\tself.red = red;\n\t}\n\tfn set_green(&mut self, green: Self::ColorChannel) {\n\t\tself.green = green;\n\t}\n\tfn set_blue(&mut self, blue: Self::ColorChannel) {\n\t\tself.blue = blue;\n\t}\n}\nimpl AlphaMut for Color {\n\tfn set_alpha(&mut self, value: Self::AlphaChannel) {\n\t\tself.alpha = value;\n\t}\n}\n\nimpl Pixel for Color {\n\t#[cfg(feature = \"std\")]\n\tfn to_bytes(&self) -> Vec<u8> {\n\t\tself.to_rgba8_srgb().to_vec()\n\t}\n\n\tfn from_bytes(bytes: &[u8]) -> Self {\n\t\tColor::from_rgba8_srgb(bytes[0], bytes[1], bytes[2], bytes[3])\n\t}\n\tfn byte_size() -> usize {\n\t\t4\n\t}\n}\n\nimpl Alpha for Color {\n\ttype AlphaChannel = f32;\n\tconst TRANSPARENT: Self = Self::TRANSPARENT;\n\n\t#[inline(always)]\n\tfn alpha(&self) -> f32 {\n\t\tself.alpha\n\t}\n\t#[inline(always)]\n\tfn multiplied_alpha(&self, alpha: Self::AlphaChannel) -> Self {\n\t\tSelf {\n\t\t\tred: self.red * alpha,\n\t\t\tgreen: self.green * alpha,\n\t\t\tblue: self.blue * alpha,\n\t\t\talpha: self.alpha * alpha,\n\t\t}\n\t}\n}\n\nimpl AssociatedAlpha for Color {\n\tfn to_unassociated<Out: super::UnassociatedAlpha>(&self) -> Out {\n\t\ttodo!()\n\t}\n}\n\nimpl Luminance for Color {\n\ttype LuminanceChannel = f32;\n\t#[inline(always)]\n\tfn luminance(&self) -> f32 {\n\t\t0.2126 * self.red + 0.7152 * self.green + 0.0722 * self.blue\n\t}\n}\n\nimpl LuminanceMut for Color {\n\tfn set_luminance(&mut self, luminance: f32) {\n\t\tlet current = self.luminance();\n\t\t// When we have a black-ish color, we just set the color to a grey-scale value. This prohibits a divide-by-0.\n\t\tif current < f32::EPSILON {\n\t\t\tself.red = 0.2126 * luminance;\n\t\t\tself.green = 0.7152 * luminance;\n\t\t\tself.blue = 0.0722 * luminance;\n\t\t\treturn;\n\t\t}\n\t\tlet fac = luminance / current;\n\t\t// TODO: when we have for example the rgb color (0, 0, 1) and want to\n\t\t// TODO: do `.set_luminance(1)`, then the actual luminance is not 1 at\n\t\t// TODO: the end. With no clamp, the resulting color would be\n\t\t// TODO: (0, 0, 12.8504). The excess should be spread to the other\n\t\t// TODO: channels, but is currently just clamped away.\n\t\tself.red = (self.red * fac).clamp(0., 1.);\n\t\tself.green = (self.green * fac).clamp(0., 1.);\n\t\tself.blue = (self.blue * fac).clamp(0., 1.);\n\t}\n}\n\nimpl Rec709Primaries for Color {}\nimpl SRGB for Color {}\n\nimpl Color {\n\tpub const BLACK: Color = Color::from_rgbf32_unchecked(0., 0., 0.);\n\tpub const WHITE: Color = Color::from_rgbf32_unchecked(1., 1., 1.);\n\tpub const RED: Color = Color::from_rgbf32_unchecked(1., 0., 0.);\n\tpub const GREEN: Color = Color::from_rgbf32_unchecked(0., 1., 0.);\n\tpub const BLUE: Color = Color::from_rgbf32_unchecked(0., 0., 1.);\n\tpub const YELLOW: Color = Color::from_rgbf32_unchecked(1., 1., 0.);\n\tpub const CYAN: Color = Color::from_rgbf32_unchecked(0., 1., 1.);\n\tpub const MAGENTA: Color = Color::from_rgbf32_unchecked(1., 0., 1.);\n\tpub const TRANSPARENT: Color = Self {\n\t\tred: 0.,\n\t\tgreen: 0.,\n\t\tblue: 0.,\n\t\talpha: 0.,\n\t};\n\n\t/// Returns `Some(Color)` if `red`, `green`, `blue` and `alpha` have a valid value. Negative numbers (including `-0.0`), NaN, and infinity are not valid values and return `None`.\n\t/// Alpha values greater than `1.0` are not valid.\n\t///\n\t/// # Examples\n\t/// ```\n\t/// use core_types::color::Color;\n\t/// let color = Color::from_rgbaf32(0.3, 0.14, 0.15, 0.92).unwrap();\n\t/// assert!(color.components() == (0.3, 0.14, 0.15, 0.92));\n\t///\n\t/// let color = Color::from_rgbaf32(1., 1., 1., f32::NAN);\n\t/// assert!(color == None);\n\t/// ```\n\t#[inline(always)]\n\tpub fn from_rgbaf32(red: f32, green: f32, blue: f32, alpha: f32) -> Option<Color> {\n\t\tif alpha > 1. || [red, green, blue, alpha].iter().any(|c| c.is_sign_negative() || !c.is_finite()) {\n\t\t\treturn None;\n\t\t}\n\t\tlet color = Color { red, green, blue, alpha };\n\t\tSome(color)\n\t}\n\n\t/// Return an opaque `Color` from given `f32` RGB channels.\n\t#[inline(always)]\n\tpub const fn from_rgbf32_unchecked(red: f32, green: f32, blue: f32) -> Color {\n\t\tColor { red, green, blue, alpha: 1. }\n\t}\n\n\t/// Return an opaque `Color` from given `f32` RGB channels.\n\t#[inline(always)]\n\tpub const fn from_rgbaf32_unchecked(red: f32, green: f32, blue: f32, alpha: f32) -> Color {\n\t\tColor { red, green, blue, alpha }\n\t}\n\n\t/// Return an opaque `Color` from given `f32` RGB channels.\n\t#[inline(always)]\n\tpub fn from_unassociated_alpha(red: f32, green: f32, blue: f32, alpha: f32) -> Color {\n\t\tColor::from_rgbaf32_unchecked(red * alpha, green * alpha, blue * alpha, alpha)\n\t}\n\n\t/// Return an opaque SDR `Color` given RGB channels from `0` to `255`, premultiplied by alpha.\n\t///\n\t/// # Examples\n\t/// ```\n\t/// use core_types::color::Color;\n\t/// let color = Color::from_rgb8_srgb(0x72, 0x67, 0x62);\n\t/// let color2 = Color::from_rgba8_srgb(0x72, 0x67, 0x62, 0xFF);\n\t/// assert_eq!(color, color2)\n\t/// ```\n\t#[inline(always)]\n\tpub fn from_rgb8_srgb(red: u8, green: u8, blue: u8) -> Color {\n\t\tColor::from_rgba8_srgb(red, green, blue, 255)\n\t}\n\n\t// TODO: Should this be premult?\n\t/// Return an SDR `Color` given RGBA channels from `0` to `255`, premultiplied by alpha.\n\t///\n\t/// # Examples\n\t/// ```\n\t/// use core_types::color::Color;\n\t/// let color = Color::from_rgba8_srgb(0x72, 0x67, 0x62, 0x61);\n\t/// ```\n\t#[inline(always)]\n\tpub fn from_rgba8_srgb(red: u8, green: u8, blue: u8, alpha: u8) -> Color {\n\t\tlet red = red as f32 / 255.;\n\t\tlet green = green as f32 / 255.;\n\t\tlet blue = blue as f32 / 255.;\n\t\tlet alpha = alpha as f32 / 255.;\n\t\tColor { red, green, blue, alpha }.to_linear_srgb().map_rgb(|channel| channel * alpha)\n\t}\n\n\t/// Create a [Color] from a hue, saturation, lightness, and alpha (all between 0 and 1)\n\t///\n\t/// # Examples\n\t/// ```\n\t/// use core_types::color::Color;\n\t/// let color = Color::from_hsla(0.5, 0.2, 0.3, 1.);\n\t/// ```\n\tpub fn from_hsla(hue: f32, saturation: f32, lightness: f32, alpha: f32) -> Color {\n\t\tlet temp1 = if lightness < 0.5 {\n\t\t\tlightness * (saturation + 1.)\n\t\t} else {\n\t\t\tlightness + saturation - lightness * saturation\n\t\t};\n\t\tlet temp2 = 2. * lightness - temp1;\n\t\t#[cfg(feature = \"std\")]\n\t\tlet rem = |x: f32| x.rem_euclid(1.);\n\t\t#[cfg(not(feature = \"std\"))]\n\t\tlet rem = |x: f32| x.rem_euclid(&1.);\n\n\t\tlet mut red = rem(hue + 1. / 3.);\n\t\tlet mut green = rem(hue);\n\t\tlet mut blue = rem(hue - 1. / 3.);\n\n\t\tfn map_channel(channel: &mut f32, temp2: f32, temp1: f32) {\n\t\t\t*channel = if *channel * 6. < 1. {\n\t\t\t\ttemp2 + (temp1 - temp2) * 6. * *channel\n\t\t\t} else if *channel * 2. < 1. {\n\t\t\t\ttemp1\n\t\t\t} else if *channel * 3. < 2. {\n\t\t\t\ttemp2 + (temp1 - temp2) * (2. / 3. - *channel) * 6.\n\t\t\t} else {\n\t\t\t\ttemp2\n\t\t\t}\n\t\t\t.clamp(0., 1.);\n\t\t}\n\t\tmap_channel(&mut red, temp2, temp1);\n\t\tmap_channel(&mut green, temp2, temp1);\n\t\tmap_channel(&mut blue, temp2, temp1);\n\n\t\tColor { red, green, blue, alpha }\n\t}\n\n\t/// Create a [Color] from hue, saturation, value, and alpha (all between 0 and 1).\n\tpub fn from_hsva(hue: f32, saturation: f32, value: f32, alpha: f32) -> Color {\n\t\tlet h_prime = (hue * 6.) % 6.;\n\t\tlet i = h_prime as i32;\n\t\tlet f = h_prime - i as f32;\n\t\tlet p = value * (1. - saturation);\n\t\tlet q = value * (1. - f * saturation);\n\t\tlet t = value * (1. - (1. - f) * saturation);\n\t\tlet (red, green, blue) = match i % 6 {\n\t\t\t0 => (value, t, p),\n\t\t\t1 => (q, value, p),\n\t\t\t2 => (p, value, t),\n\t\t\t3 => (p, q, value),\n\t\t\t4 => (t, p, value),\n\t\t\t_ => (value, p, q),\n\t\t};\n\t\tColor { red, green, blue, alpha }\n\t}\n\n\t/// Return the `red` component.\n\t///\n\t/// # Examples\n\t/// ```\n\t/// use core_types::color::Color;\n\t/// let color = Color::from_rgbaf32(0.114, 0.103, 0.98, 0.97).unwrap();\n\t/// assert!(color.r() == 0.114);\n\t/// ```\n\t#[inline(always)]\n\tpub fn r(&self) -> f32 {\n\t\tself.red\n\t}\n\n\t/// Return the `green` component.\n\t///\n\t/// # Examples\n\t/// ```\n\t/// use core_types::color::Color;\n\t/// let color = Color::from_rgbaf32(0.114, 0.103, 0.98, 0.97).unwrap();\n\t/// assert!(color.g() == 0.103);\n\t/// ```\n\t#[inline(always)]\n\tpub fn g(&self) -> f32 {\n\t\tself.green\n\t}\n\n\t/// Return the `blue` component.\n\t///\n\t/// # Examples\n\t/// ```\n\t/// use core_types::color::Color;\n\t/// let color = Color::from_rgbaf32(0.114, 0.103, 0.98, 0.97).unwrap();\n\t/// assert!(color.b() == 0.98);\n\t/// ```\n\t#[inline(always)]\n\tpub fn b(&self) -> f32 {\n\t\tself.blue\n\t}\n\n\t/// Return the `alpha` component without checking its expected `0.0` to `1.0` range.\n\t///\n\t/// # Examples\n\t/// ```\n\t/// use core_types::color::Color;\n\t/// let color = Color::from_rgbaf32(0.114, 0.103, 0.98, 0.97).unwrap();\n\t/// assert!(color.a() == 0.97);\n\t/// ```\n\t#[inline(always)]\n\tpub fn a(&self) -> f32 {\n\t\tself.alpha\n\t}\n\n\t#[inline(always)]\n\tpub fn is_opaque(&self) -> bool {\n\t\tself.alpha > 1. - f32::EPSILON\n\t}\n\n\t#[inline(always)]\n\tpub fn average_rgb_channels(&self) -> f32 {\n\t\t(self.red + self.green + self.blue) / 3.\n\t}\n\n\t#[inline(always)]\n\tpub fn minimum_rgb_channels(&self) -> f32 {\n\t\tself.red.min(self.green).min(self.blue)\n\t}\n\n\t#[inline(always)]\n\tpub fn maximum_rgb_channels(&self) -> f32 {\n\t\tself.red.max(self.green).max(self.blue)\n\t}\n\n\t// From https://stackoverflow.com/a/56678483/775283\n\t#[inline(always)]\n\tpub fn luminance_srgb(&self) -> f32 {\n\t\t0.2126 * self.red + 0.7152 * self.green + 0.0722 * self.blue\n\t}\n\n\t// From https://en.wikipedia.org/wiki/Luma_(video)#Rec._601_luma_versus_Rec._709_luma_coefficients\n\t#[inline(always)]\n\tpub fn luminance_rec_601(&self) -> f32 {\n\t\t0.299 * self.red + 0.587 * self.green + 0.114 * self.blue\n\t}\n\n\t// From https://en.wikipedia.org/wiki/Luma_(video)#Rec._601_luma_versus_Rec._709_luma_coefficients\n\t#[inline(always)]\n\tpub fn luminance_rec_601_rounded(&self) -> f32 {\n\t\t0.3 * self.red + 0.59 * self.green + 0.11 * self.blue\n\t}\n\n\t// From https://stackoverflow.com/a/56678483/775283\n\t#[inline(always)]\n\tpub fn luminance_perceptual(&self) -> f32 {\n\t\tlet luminance = self.luminance_srgb();\n\n\t\tif luminance <= 0.008856 {\n\t\t\t(luminance * 903.3) / 100.\n\t\t} else {\n\t\t\t(luminance.cbrt() * 116. - 16.) / 100.\n\t\t}\n\t}\n\n\t#[inline(always)]\n\tpub fn from_luminance(luminance: f32) -> Color {\n\t\tColor {\n\t\t\tred: luminance,\n\t\t\tgreen: luminance,\n\t\t\tblue: luminance,\n\t\t\talpha: 1.,\n\t\t}\n\t}\n\n\t#[inline(always)]\n\tpub fn with_luminance(&self, luminance: f32) -> Color {\n\t\tlet delta = luminance - self.luminance_rec_601_rounded();\n\t\tself.map_rgb(|c| (c + delta).clamp(0., 1.))\n\t}\n\n\t#[inline(always)]\n\tpub fn saturation(&self) -> f32 {\n\t\tlet max = (self.red).max(self.green).max(self.blue);\n\t\tlet min = (self.red).min(self.green).min(self.blue);\n\n\t\tmax - min\n\t}\n\n\t#[inline(always)]\n\tpub fn with_saturation(&self, saturation: f32) -> Color {\n\t\tlet [hue, _, lightness, alpha] = self.to_hsla();\n\t\tColor::from_hsla(hue, saturation, lightness, alpha)\n\t}\n\n\tpub fn with_alpha(&self, alpha: f32) -> Color {\n\t\tColor {\n\t\t\tred: self.red,\n\t\t\tgreen: self.green,\n\t\t\tblue: self.blue,\n\t\t\talpha,\n\t\t}\n\t}\n\n\tpub fn with_red(&self, red: f32) -> Color {\n\t\tColor {\n\t\t\tred,\n\t\t\tgreen: self.green,\n\t\t\tblue: self.blue,\n\t\t\talpha: self.alpha,\n\t\t}\n\t}\n\n\tpub fn with_green(&self, green: f32) -> Color {\n\t\tColor {\n\t\t\tred: self.red,\n\t\t\tgreen,\n\t\t\tblue: self.blue,\n\t\t\talpha: self.alpha,\n\t\t}\n\t}\n\n\tpub fn with_blue(&self, blue: f32) -> Color {\n\t\tColor {\n\t\t\tred: self.red,\n\t\t\tgreen: self.green,\n\t\t\tblue,\n\t\t\talpha: self.alpha,\n\t\t}\n\t}\n\n\t#[inline(always)]\n\tpub fn blend_normal(_c_b: f32, c_s: f32) -> f32 {\n\t\tc_s\n\t}\n\n\t#[inline(always)]\n\tpub fn blend_multiply(c_b: f32, c_s: f32) -> f32 {\n\t\tc_s * c_b\n\t}\n\n\t#[inline(always)]\n\tpub fn blend_darken(c_b: f32, c_s: f32) -> f32 {\n\t\tc_s.min(c_b)\n\t}\n\n\t#[inline(always)]\n\tpub fn blend_color_burn(c_b: f32, c_s: f32) -> f32 {\n\t\tif c_b == 1. {\n\t\t\t1.\n\t\t} else if c_s == 0. {\n\t\t\t0.\n\t\t} else {\n\t\t\t1. - ((1. - c_b) / c_s).min(1.)\n\t\t}\n\t}\n\n\t#[inline(always)]\n\tpub fn blend_linear_burn(c_b: f32, c_s: f32) -> f32 {\n\t\tc_b + c_s - 1.\n\t}\n\n\t#[inline(always)]\n\tpub fn blend_darker_color(&self, other: Color) -> Color {\n\t\tif self.average_rgb_channels() <= other.average_rgb_channels() { *self } else { other }\n\t}\n\n\t#[inline(always)]\n\tpub fn blend_screen(c_b: f32, c_s: f32) -> f32 {\n\t\t1. - (1. - c_s) * (1. - c_b)\n\t}\n\n\t#[inline(always)]\n\tpub fn blend_lighten(c_b: f32, c_s: f32) -> f32 {\n\t\tc_s.max(c_b)\n\t}\n\n\t#[inline(always)]\n\tpub fn blend_color_dodge(c_b: f32, c_s: f32) -> f32 {\n\t\tif c_s == 1. { 1. } else { (c_b / (1. - c_s)).min(1.) }\n\t}\n\n\t#[inline(always)]\n\tpub fn blend_linear_dodge(c_b: f32, c_s: f32) -> f32 {\n\t\tc_b + c_s\n\t}\n\n\t#[inline(always)]\n\tpub fn blend_lighter_color(&self, other: Color) -> Color {\n\t\tif self.average_rgb_channels() >= other.average_rgb_channels() { *self } else { other }\n\t}\n\n\tpub fn blend_softlight(c_b: f32, c_s: f32) -> f32 {\n\t\tif c_s <= 0.5 {\n\t\t\tc_b - (1. - 2. * c_s) * c_b * (1. - c_b)\n\t\t} else {\n\t\t\tlet d = |x: f32| if x <= 0.25 { ((16. * x - 12.) * x + 4.) * x } else { x.sqrt() };\n\t\t\tc_b + (2. * c_s - 1.) * (d(c_b) - c_b)\n\t\t}\n\t}\n\n\tpub fn blend_hardlight(c_b: f32, c_s: f32) -> f32 {\n\t\tif c_s <= 0.5 {\n\t\t\tColor::blend_multiply(2. * c_s, c_b)\n\t\t} else {\n\t\t\tColor::blend_screen(2. * c_s - 1., c_b)\n\t\t}\n\t}\n\n\tpub fn blend_vivid_light(c_b: f32, c_s: f32) -> f32 {\n\t\tif c_s <= 0.5 {\n\t\t\tColor::blend_color_burn(2. * c_s, c_b)\n\t\t} else {\n\t\t\tColor::blend_color_dodge(2. * c_s - 1., c_b)\n\t\t}\n\t}\n\n\tpub fn blend_linear_light(c_b: f32, c_s: f32) -> f32 {\n\t\tif c_s <= 0.5 {\n\t\t\tColor::blend_linear_burn(2. * c_s, c_b)\n\t\t} else {\n\t\t\tColor::blend_linear_dodge(2. * c_s - 1., c_b)\n\t\t}\n\t}\n\n\tpub fn blend_pin_light(c_b: f32, c_s: f32) -> f32 {\n\t\tif c_s <= 0.5 {\n\t\t\tColor::blend_darken(2. * c_s, c_b)\n\t\t} else {\n\t\t\tColor::blend_lighten(2. * c_s - 1., c_b)\n\t\t}\n\t}\n\n\tpub fn blend_hard_mix(c_b: f32, c_s: f32) -> f32 {\n\t\tif Color::blend_linear_light(c_b, c_s) < 0.5 { 0. } else { 1. }\n\t}\n\n\tpub fn blend_difference(c_b: f32, c_s: f32) -> f32 {\n\t\t(c_b - c_s).abs()\n\t}\n\n\tpub fn blend_exclusion(c_b: f32, c_s: f32) -> f32 {\n\t\tc_b + c_s - 2. * c_b * c_s\n\t}\n\n\tpub fn blend_subtract(c_b: f32, c_s: f32) -> f32 {\n\t\tc_b - c_s\n\t}\n\n\tpub fn blend_divide(c_b: f32, c_s: f32) -> f32 {\n\t\tif c_b == 0. { 1. } else { c_b / c_s }\n\t}\n\n\tpub fn blend_hue(&self, c_s: Color) -> Color {\n\t\tlet sat_b = self.saturation();\n\t\tlet lum_b = self.luminance_rec_601();\n\t\tc_s.with_saturation(sat_b).with_luminance(lum_b)\n\t}\n\n\tpub fn blend_saturation(&self, c_s: Color) -> Color {\n\t\tlet sat_s = c_s.saturation();\n\t\tlet lum_b = self.luminance_rec_601();\n\n\t\tself.with_saturation(sat_s).with_luminance(lum_b)\n\t}\n\n\tpub fn blend_color(&self, c_s: Color) -> Color {\n\t\tlet lum_b = self.luminance_rec_601();\n\n\t\tc_s.with_luminance(lum_b)\n\t}\n\n\tpub fn blend_luminosity(&self, c_s: Color) -> Color {\n\t\tlet lum_s = c_s.luminance_rec_601();\n\n\t\tself.with_luminance(lum_s)\n\t}\n\n\t/// Return the all components as a tuple, first component is red, followed by green, followed by blue, followed by alpha.\n\t///\n\t/// # Examples\n\t/// ```\n\t/// use core_types::color::Color;\n\t/// let color = Color::from_rgbaf32(0.114, 0.103, 0.98, 0.97).unwrap();\n\t/// assert_eq!(color.components(),  (0.114, 0.103, 0.98, 0.97));\n\t/// ```\n\t#[inline(always)]\n\tpub fn components(&self) -> (f32, f32, f32, f32) {\n\t\t(self.red, self.green, self.blue, self.alpha)\n\t}\n\n\t/// Return an 8-character RGBA hex string (without a # prefix). Use this if the [`Color`] is in linear space.\n\t///\n\t/// # Examples\n\t/// ```\n\t/// use core_types::color::Color;\n\t/// let color = Color::from_rgba8_srgb(0x52, 0x67, 0xFA, 0x61); // Premultiplied alpha\n\t/// assert_eq!(\"3240a261\", color.to_rgba_hex_srgb()); // Equivalent hex incorporating premultiplied alpha\n\t/// ```\n\t#[cfg(feature = \"std\")]\n\tpub fn to_rgba_hex_srgb(&self) -> String {\n\t\tlet gamma = self.to_gamma_srgb();\n\t\tformat!(\n\t\t\t\"{:02x?}{:02x?}{:02x?}{:02x?}\",\n\t\t\t(gamma.r() * 255.) as u8,\n\t\t\t(gamma.g() * 255.) as u8,\n\t\t\t(gamma.b() * 255.) as u8,\n\t\t\t(gamma.a() * 255.) as u8,\n\t\t)\n\t}\n\n\t/// Return a 6-character RGB hex string (without a # prefix). Use this if the [`Color`] is in linear space.\n\t/// ```\n\t/// use core_types::color::Color;\n\t/// let color = Color::from_rgba8_srgb(0x52, 0x67, 0xFA, 0x61); // Premultiplied alpha\n\t/// assert_eq!(\"3240a2\", color.to_rgb_hex_srgb()); // Equivalent hex incorporating premultiplied alpha\n\t/// ```\n\t#[cfg(feature = \"std\")]\n\tpub fn to_rgb_hex_srgb(&self) -> String {\n\t\tself.to_gamma_srgb().to_rgb_hex_srgb_from_gamma()\n\t}\n\n\t/// Return a 6-character RGB hex string (without a # prefix). Use this if the [`Color`] is in gamma space.\n\t/// ```\n\t/// use core_types::color::Color;\n\t/// let color = Color::from_rgba8_srgb(0x52, 0x67, 0xFA, 0x61); // Premultiplied alpha\n\t/// assert_eq!(\"3240a2\", color.to_rgb_hex_srgb()); // Equivalent hex incorporating premultiplied alpha\n\t/// ```\n\t#[cfg(feature = \"std\")]\n\tpub fn to_rgb_hex_srgb_from_gamma(&self) -> String {\n\t\tformat!(\"{:02x?}{:02x?}{:02x?}\", (self.r() * 255.) as u8, (self.g() * 255.) as u8, (self.b() * 255.) as u8)\n\t}\n\n\t/// Return an 8-character RGBA hex string (without a # prefix). Use this if the [`Color`] is in gamma space.\n\t#[cfg(feature = \"std\")]\n\tpub fn to_rgba_hex_srgb_from_gamma(&self) -> String {\n\t\tformat!(\n\t\t\t\"{:02x?}{:02x?}{:02x?}{:02x?}\",\n\t\t\t(self.r() * 255.) as u8,\n\t\t\t(self.g() * 255.) as u8,\n\t\t\t(self.b() * 255.) as u8,\n\t\t\t(self.a() * 255.) as u8,\n\t\t)\n\t}\n\n\t/// Return the all components as a u8 slice, first component is red, followed by green, followed by blue, followed by alpha. Use this if the [`Color`] is in linear space.\n\t///\n\t/// # Examples\n\t/// ```\n\t/// use core_types::color::Color;\n\t/// let color = Color::from_rgbaf32(0.114, 0.103, 0.98, 0.97).unwrap();\n\t/// // TODO: Add test\n\t/// ```\n\t#[inline(always)]\n\tpub fn to_rgba8_srgb(&self) -> [u8; 4] {\n\t\tlet gamma = self.to_gamma_srgb();\n\t\t[(gamma.red * 255.) as u8, (gamma.green * 255.) as u8, (gamma.blue * 255.) as u8, (gamma.alpha * 255.) as u8]\n\t}\n\n\t/// Return the all RGB components as a u8 slice, first component is red, followed by green, followed by blue. Use this if the [`Color`] is in linear space.\n\t///\n\t/// # Examples\n\t/// ```\n\t/// use core_types::color::Color;\n\t/// let color = Color::from_rgbaf32(0.114, 0.103, 0.98, 0.97).unwrap();\n\t/// // TODO: Add test\n\t/// ```\n\t#[inline(always)]\n\tpub fn to_rgb8_srgb(&self) -> [u8; 3] {\n\t\tlet gamma = self.to_gamma_srgb();\n\t\t[(gamma.red * 255.) as u8, (gamma.green * 255.) as u8, (gamma.blue * 255.) as u8]\n\t}\n\n\t// https://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/\n\t/// Convert a [Color] to a hue, saturation, lightness and alpha (all between 0 and 1)\n\t///\n\t/// # Examples\n\t/// ```\n\t/// use core_types::color::Color;\n\t/// let color = Color::from_hsla(0.5, 0.2, 0.3, 1.).to_hsla();\n\t/// ```\n\tpub fn to_hsla(&self) -> [f32; 4] {\n\t\tlet min_channel = self.red.min(self.green).min(self.blue);\n\t\tlet max_channel = self.red.max(self.green).max(self.blue);\n\n\t\tlet lightness = (min_channel + max_channel) / 2.;\n\t\tlet saturation = if min_channel == max_channel {\n\t\t\t0.\n\t\t} else if lightness <= 0.5 {\n\t\t\t(max_channel - min_channel) / (max_channel + min_channel)\n\t\t} else {\n\t\t\t(max_channel - min_channel) / (2. - max_channel - min_channel)\n\t\t};\n\t\tlet hue = if self.red >= self.green && self.red >= self.blue {\n\t\t\t(self.green - self.blue) / (max_channel - min_channel)\n\t\t} else if self.green >= self.red && self.green >= self.blue {\n\t\t\t2. + (self.blue - self.red) / (max_channel - min_channel)\n\t\t} else {\n\t\t\t4. + (self.red - self.green) / (max_channel - min_channel)\n\t\t} / 6.;\n\t\t#[cfg(feature = \"std\")]\n\t\tlet hue = hue.rem_euclid(1.);\n\t\t#[cfg(not(feature = \"std\"))]\n\t\tlet hue = hue.rem_euclid(&1.);\n\n\t\t[hue, saturation, lightness, self.alpha]\n\t}\n\n\t/// Creates a color from a hex color code string with an optional `#` prefix, such as `#RRGGBB`, `RRGGBB`, `#RRGGBBAA`, or `RRGGBBAA`.\n\t/// Returns `None` for invalid or unrecognized strings.\n\t#[cfg(feature = \"std\")]\n\tpub fn from_hex_str(hex: &str) -> Option<Color> {\n\t\tlet hex = hex.trim().trim_start_matches('#');\n\t\tif hex.len() != 6 && hex.len() != 8 {\n\t\t\treturn None;\n\t\t}\n\t\tlet red = u8::from_str_radix(&hex[0..2], 16).ok()? as f32 / 255.;\n\t\tlet green = u8::from_str_radix(&hex[2..4], 16).ok()? as f32 / 255.;\n\t\tlet blue = u8::from_str_radix(&hex[4..6], 16).ok()? as f32 / 255.;\n\t\tlet alpha = if hex.len() == 8 { u8::from_str_radix(&hex[6..8], 16).ok()? as f32 / 255. } else { 1. };\n\t\tSome(Color { red, green, blue, alpha })\n\t}\n\n\t/// Linearly interpolates between two colors based on t.\n\t///\n\t/// T must be between 0 and 1.\n\t#[inline(always)]\n\tpub fn lerp(&self, other: &Color, t: f32) -> Self {\n\t\tassert!((0. ..=1.).contains(&t));\n\t\tColor::from_rgbaf32_unchecked(\n\t\t\tself.red + ((other.red - self.red) * t),\n\t\t\tself.green + ((other.green - self.green) * t),\n\t\t\tself.blue + ((other.blue - self.blue) * t),\n\t\t\tself.alpha + ((other.alpha - self.alpha) * t),\n\t\t)\n\t}\n\n\t#[inline(always)]\n\tpub fn gamma(&self, gamma: f32) -> Color {\n\t\tlet gamma = gamma.max(0.0001);\n\n\t\t// From https://www.dfstudios.co.uk/articles/programming/image-programming-algorithms/image-processing-algorithms-part-6-gamma-correction/\n\t\tlet inverse_gamma = 1. / gamma;\n\t\tself.map_rgb(|c: f32| c.powf(inverse_gamma))\n\t}\n\n\t#[inline(always)]\n\tpub fn to_linear_srgb(&self) -> Self {\n\t\tSelf {\n\t\t\tred: Self::srgb_to_linear(self.red),\n\t\t\tgreen: Self::srgb_to_linear(self.green),\n\t\t\tblue: Self::srgb_to_linear(self.blue),\n\t\t\talpha: self.alpha,\n\t\t}\n\t}\n\n\t#[inline(always)]\n\tpub fn to_gamma_srgb(&self) -> Self {\n\t\tSelf {\n\t\t\tred: Self::linear_to_srgb(self.red),\n\t\t\tgreen: Self::linear_to_srgb(self.green),\n\t\t\tblue: Self::linear_to_srgb(self.blue),\n\t\t\talpha: self.alpha,\n\t\t}\n\t}\n\n\t#[inline(always)]\n\tpub fn srgb_to_linear(channel: f32) -> f32 {\n\t\tif channel <= 0.04045 { channel / 12.92 } else { ((channel + 0.055) / 1.055).powf(2.4) }\n\t}\n\n\t#[inline(always)]\n\tpub fn linear_to_srgb(channel: f32) -> f32 {\n\t\tif channel <= 0.0031308 { channel * 12.92 } else { 1.055 * channel.powf(1. / 2.4) - 0.055 }\n\t}\n\n\t#[inline(always)]\n\tpub fn map_rgba<F: Fn(f32) -> f32>(&self, f: F) -> Self {\n\t\tSelf::from_rgbaf32_unchecked(f(self.r()), f(self.g()), f(self.b()), f(self.a()))\n\t}\n\n\t#[inline(always)]\n\tpub fn map_rgb<F: Fn(f32) -> f32>(&self, f: F) -> Self {\n\t\tSelf::from_rgbaf32_unchecked(f(self.r()), f(self.g()), f(self.b()), self.a())\n\t}\n\n\t#[inline(always)]\n\tpub fn apply_opacity(&self, opacity: f32) -> Self {\n\t\tSelf::from_rgbaf32_unchecked(self.r() * opacity, self.g() * opacity, self.b() * opacity, self.a() * opacity)\n\t}\n\n\t#[inline(always)]\n\tpub fn to_associated_alpha(&self, alpha: f32) -> Self {\n\t\tSelf {\n\t\t\tred: self.red * alpha,\n\t\t\tgreen: self.green * alpha,\n\t\t\tblue: self.blue * alpha,\n\t\t\talpha: self.alpha * alpha,\n\t\t}\n\t}\n\n\t#[inline(always)]\n\tpub fn to_unassociated_alpha(&self) -> Self {\n\t\tif self.alpha == 0. {\n\t\t\treturn *self;\n\t\t}\n\t\tlet unmultiply = 1. / self.alpha;\n\t\tSelf {\n\t\t\tred: self.red * unmultiply,\n\t\t\tgreen: self.green * unmultiply,\n\t\t\tblue: self.blue * unmultiply,\n\t\t\talpha: self.alpha,\n\t\t}\n\t}\n\n\t#[inline(always)]\n\tpub fn blend_rgb<F: Fn(f32, f32) -> f32>(&self, other: Color, f: F) -> Self {\n\t\tlet background = self.to_unassociated_alpha();\n\t\tColor {\n\t\t\tred: f(background.red, other.red).clamp(0., 1.),\n\t\t\tgreen: f(background.green, other.green).clamp(0., 1.),\n\t\t\tblue: f(background.blue, other.blue).clamp(0., 1.),\n\t\t\talpha: other.alpha,\n\t\t}\n\t}\n\n\t#[inline(always)]\n\tpub fn alpha_blend(&self, other: Color) -> Self {\n\t\tlet inv_alpha = 1. - other.alpha;\n\t\tSelf {\n\t\t\tred: self.red * inv_alpha + other.red,\n\t\t\tgreen: self.green * inv_alpha + other.green,\n\t\t\tblue: self.blue * inv_alpha + other.blue,\n\t\t\talpha: self.alpha * inv_alpha + other.alpha,\n\t\t}\n\t}\n\n\t#[inline(always)]\n\tpub fn alpha_add(&self, other: Color) -> Self {\n\t\tSelf {\n\t\t\talpha: (self.alpha + other.alpha).clamp(0., 1.),\n\t\t\t..*self\n\t\t}\n\t}\n\n\t#[inline(always)]\n\tpub fn alpha_subtract(&self, other: Color) -> Self {\n\t\tSelf {\n\t\t\talpha: (self.alpha - other.alpha).clamp(0., 1.),\n\t\t\t..*self\n\t\t}\n\t}\n\n\t#[inline(always)]\n\tpub fn alpha_multiply(&self, other: Color) -> Self {\n\t\tSelf {\n\t\t\talpha: (self.alpha * other.alpha).clamp(0., 1.),\n\t\t\t..*self\n\t\t}\n\t}\n\n\t#[inline(always)]\n\tpub const fn from_vec4(vec: Vec4) -> Self {\n\t\tSelf {\n\t\t\tred: vec.x,\n\t\t\tgreen: vec.y,\n\t\t\tblue: vec.z,\n\t\t\talpha: vec.w,\n\t\t}\n\t}\n\n\t#[inline(always)]\n\tpub fn to_vec4(&self) -> Vec4 {\n\t\tVec4::new(self.red, self.green, self.blue, self.alpha)\n\t}\n}\n\n#[cfg(test)]\nmod tests {\n\tuse super::*;\n\t#[test]\n\tfn hsl_roundtrip() {\n\t\tfor (red, green, blue) in [\n\t\t\t(24, 98, 118),\n\t\t\t(69, 11, 89),\n\t\t\t(54, 82, 38),\n\t\t\t(47, 76, 50),\n\t\t\t(25, 15, 73),\n\t\t\t(62, 57, 33),\n\t\t\t(55, 2, 18),\n\t\t\t(12, 3, 82),\n\t\t\t(91, 16, 98),\n\t\t\t(91, 39, 82),\n\t\t\t(97, 53, 32),\n\t\t\t(76, 8, 91),\n\t\t\t(54, 87, 19),\n\t\t\t(56, 24, 88),\n\t\t\t(14, 82, 34),\n\t\t\t(61, 86, 31),\n\t\t\t(73, 60, 75),\n\t\t\t(95, 79, 88),\n\t\t\t(13, 34, 4),\n\t\t\t(82, 84, 84),\n\t\t\t(255, 255, 178),\n\t\t] {\n\t\t\tlet col = Color::from_rgb8_srgb(red, green, blue);\n\t\t\tlet [hue, saturation, lightness, alpha] = col.to_hsla();\n\t\t\tlet result = Color::from_hsla(hue, saturation, lightness, alpha);\n\t\t\tassert!((col.r() - result.r()) < f32::EPSILON * 100.);\n\t\t\tassert!((col.g() - result.g()) < f32::EPSILON * 100.);\n\t\t\tassert!((col.b() - result.b()) < f32::EPSILON * 100.);\n\t\t\tassert!((col.a() - result.a()) < f32::EPSILON * 100.);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/no-std-types/src/color/discrete_srgb.rs",
    "content": "#![allow(clippy::neg_cmp_op_on_partial_ord)]\n//! Fast conversions between u8 sRGB and linear float.\n\n// Inspired by https://gist.github.com/rygorous/2203834, but with a slightly\n// modified method, custom derived constants and error correction for perfect\n// accuracy in accordance with the D3D11 spec:\n// https://microsoft.github.io/DirectX-Specs/d3d/archive/D3D11_3_FunctionalSpec.htm#FLOATtoSRGB.\n\n/// CRITICAL_POINTS[i] is the last float value such that it maps to i after\n/// conversion to integer sRGB. So if x > CRITICAL_POINTS[i] you know you need\n/// to increment i.\n#[rustfmt::skip]\nconst CRITICAL_POINTS: [f32; 256] = [\n\t0.00015176347, 0.00045529046, 0.0007588174, 0.0010623443, 0.0013658714, 0.0016693983, 0.0019729252, 0.0022764523,\n\t0.0025799791, 0.0028835062, 0.0031883009, 0.003509259, 0.003848315, 0.004205748, 0.0045818323, 0.0049768374,\n\t0.005391024, 0.00582465, 0.0062779686, 0.0067512267, 0.0072446675, 0.0077585294, 0.008293047, 0.008848451,\n\t0.0094249705, 0.010022825, 0.010642236, 0.01128342, 0.011946591, 0.012631957, 0.013339729, 0.014070111,\n\t0.0148233045, 0.015599505, 0.01639891, 0.017221717, 0.018068114, 0.018938294, 0.019832445, 0.020750746,\n\t0.021693384, 0.022660539, 0.02365239, 0.024669115, 0.025710886, 0.026777886, 0.027870273, 0.028988222,\n\t0.030131903, 0.03130148, 0.032497127, 0.033718992, 0.034967244, 0.03624204, 0.03754355, 0.03887192,\n\t0.040227327, 0.041609894, 0.04301979, 0.044457167, 0.04592218, 0.04741497, 0.04893569, 0.050484486,\n\t0.05206151, 0.053666897, 0.055300802, 0.056963358, 0.058654714, 0.060375024, 0.062124394, 0.06390298,\n\t0.065710925, 0.06754836, 0.06941542, 0.07131224, 0.07323896, 0.07519571, 0.07718261, 0.07919981,\n\t0.08124744, 0.08332562, 0.08543448, 0.08757417, 0.08974478, 0.091946445, 0.09417931, 0.09644348,\n\t0.098739095, 0.10106628, 0.10342514, 0.105815805, 0.1082384, 0.110693045, 0.11317986, 0.11569896,\n\t0.118250474, 0.12083454, 0.12345121, 0.12610064, 0.12878296, 0.13149826, 0.13424668, 0.1370283,\n\t0.13984327, 0.14269169, 0.14557366, 0.1484893, 0.15143873, 0.15442204, 0.15743938, 0.16049084,\n\t0.1635765, 0.16669647, 0.16985092, 0.1730399, 0.17626354, 0.17952198, 0.18281525, 0.1861435,\n\t0.18950681, 0.19290532, 0.19633913, 0.19980833, 0.20331302, 0.20685332, 0.21042931, 0.21404111,\n\t0.21768881, 0.22137253, 0.22509235, 0.22884844, 0.23264077, 0.23646952, 0.24033478, 0.24423665,\n\t0.24817522, 0.25215057, 0.25616285, 0.26021212, 0.26429847, 0.26842204, 0.27258286, 0.27678108,\n\t0.2810168, 0.28529006, 0.289601, 0.2939497, 0.29833627, 0.30276078, 0.30722332, 0.311724,\n\t0.31626293, 0.32084015, 0.32545578, 0.33010995, 0.3348027, 0.3395341, 0.34430432, 0.34911346,\n\t0.3539615, 0.35884857, 0.3637748, 0.36874023, 0.373745, 0.37878913, 0.38387278, 0.388996,\n\t0.39415887, 0.39936152, 0.404604, 0.4098864, 0.41520882, 0.42057133, 0.425974, 0.431417,\n\t0.43690032, 0.4424241, 0.44798836, 0.45359328, 0.45923886, 0.46492523, 0.47065246, 0.47642064,\n\t0.48222986, 0.48808017, 0.4939718, 0.49990457, 0.5058787, 0.5118943, 0.5179514, 0.5240501,\n\t0.5301905, 0.5363727, 0.5425967, 0.54886264, 0.5551706, 0.56152064, 0.5679129, 0.5743473,\n\t0.5808241, 0.5873433, 0.593905, 0.60050917, 0.60715604, 0.61384565, 0.62057805, 0.6273533,\n\t0.63417155, 0.6410328, 0.6479372, 0.65488476, 0.66187555, 0.6689097, 0.6759874, 0.68310845,\n\t0.6902731, 0.6974814, 0.7047334, 0.71202916, 0.7193688, 0.7267524, 0.73418003, 0.7416518,\n\t0.7491677, 0.7567278, 0.76433223, 0.7719811, 0.7796744, 0.7874122, 0.7951947, 0.80302185,\n\t0.8108938, 0.81881046, 0.82677215, 0.8347787, 0.8428304, 0.8509272, 0.85906917, 0.8672564,\n\t0.875489, 0.8837671, 0.89209044, 0.9004596, 0.9088741, 0.91733456, 0.9258405, 0.9343926,\n\t0.94299024, 0.95163417, 0.96032387, 0.96906, 0.977842, 0.9866705, 0.9955452, 1.,\n];\n\n#[rustfmt::skip]\nconst FLOAT_SRGB_LERP: [u32; 27] = [\n\t0x66f, 0x66f063b, 0xcaa0515, 0x11c00773, 0x193305dc, 0x1f1004f3, 0x24030481, 0x28850773,\n\t0x2ff9065e, 0x365805a1, 0x3bfa0547, 0x414108f7, 0x4a3907d8, 0x52110709, 0x591b06aa, 0x5fc50b70,\n\t0x6b350a18, 0x754e091c, 0x7e6b08aa, 0x87160ef1, 0x96070d3e, 0xa3460bfc, 0xaf430b6c, 0xbaaf13bd,\n\t0xce6d1187, 0xdff40fe3, 0xefd70f28,\n];\n\n#[inline]\npub fn float_to_srgb_u8(mut f: f32) -> u8 {\n\t// Clamp f to [0, 1], with a negated condition to handle NaNs as 0.\n\tif !(f >= 0.) {\n\t\tf = 0.;\n\t} else if f > 1. {\n\t\tf = 1.;\n\t}\n\n\t// Shift away slightly from 0.0 to reduce exponent range.\n\tconst C: f32 = 0.009842521f32;\n\tlet u = (f + C).to_bits() - C.to_bits();\n\tif u > (1. + C).to_bits() - C.to_bits() {\n\t\t// We clamped f to [0, 1], and the integer representations\n\t\t// of the positive finite non-NaN floats are monotonic.\n\t\t// This makes the later LUT lookup panicless.\n\t\tunsafe { core::hint::unreachable_unchecked() }\n\t}\n\n\t// Compute a piecewise linear interpolation that is always\n\t// the correct answer, or one less than it.\n\tlet u16mask = (1 << 16) - 1;\n\tlet lut_idx = u >> 21;\n\tlet lerp_idx = (u >> 5) & u16mask;\n\tlet bias_mult = FLOAT_SRGB_LERP[lut_idx as usize];\n\tlet bias = (bias_mult >> 16) << 16;\n\tlet mult = bias_mult & u16mask;\n\t// I don't believe this wraps, but since we test in release mode,\n\t// better make sure debug mode behaves the same.\n\tlet lerp = bias.wrapping_add(mult * lerp_idx) >> 24;\n\n\t// Adjust linear interpolation to the correct value.\n\tif f > CRITICAL_POINTS[lerp as usize] { lerp as u8 + 1 } else { lerp as u8 }\n}\n\n#[rustfmt::skip]\nconst FROM_SRGB_U8: [f32; 256] = [\n\t0., 0.000303527, 0.000607054, 0.00091058103, 0.001214108, 0.001517635, 0.0018211621, 0.002124689,\n\t0.002428216, 0.002731743, 0.00303527, 0.0033465356, 0.003676507, 0.004024717, 0.004391442,\n\t0.0047769533, 0.005181517, 0.0056053917, 0.0060488326, 0.006512091, 0.00699541, 0.0074990317,\n\t0.008023192, 0.008568125, 0.009134057, 0.009721218, 0.010329823, 0.010960094, 0.011612245,\n\t0.012286487, 0.012983031, 0.013702081, 0.014443844, 0.015208514, 0.015996292, 0.016807375,\n\t0.017641952, 0.018500218, 0.019382361, 0.020288562, 0.02121901, 0.022173883, 0.023153365,\n\t0.02415763, 0.025186857, 0.026241222, 0.027320892, 0.028426038, 0.029556843, 0.03071345, 0.03189604,\n\t0.033104774, 0.03433981, 0.035601325, 0.036889452, 0.038204376, 0.039546248, 0.04091521, 0.042311423,\n\t0.043735042, 0.045186214, 0.046665095, 0.048171833, 0.049706575, 0.051269468, 0.052860655, 0.05448028,\n\t0.056128494, 0.057805434, 0.05951124, 0.06124607, 0.06301003, 0.06480328, 0.06662595, 0.06847818,\n\t0.07036011, 0.07227186, 0.07421358, 0.07618539, 0.07818743, 0.08021983, 0.082282715, 0.084376216,\n\t0.086500466, 0.088655606, 0.09084173, 0.09305898, 0.095307484, 0.09758736, 0.09989874, 0.10224175,\n\t0.10461649, 0.10702311, 0.10946172, 0.111932434, 0.11443538, 0.116970696, 0.11953845, 0.12213881,\n\t0.12477186, 0.12743773, 0.13013652, 0.13286836, 0.13563336, 0.13843165, 0.14126332, 0.1441285,\n\t0.1470273, 0.14995982, 0.15292618, 0.1559265, 0.15896086, 0.16202943, 0.16513224, 0.16826946,\n\t0.17144115, 0.17464745, 0.17788847, 0.1811643, 0.18447503, 0.1878208, 0.19120172, 0.19461787,\n\t0.19806935, 0.2015563, 0.20507877, 0.2086369, 0.21223079, 0.21586053, 0.21952623, 0.22322798,\n\t0.22696589, 0.23074007, 0.23455065, 0.23839766, 0.2422812, 0.2462014, 0.25015837, 0.25415218,\n\t0.2581829, 0.26225072, 0.26635566, 0.27049786, 0.27467737, 0.27889434, 0.2831488, 0.2874409,\n\t0.2917707, 0.29613832, 0.30054384, 0.30498737, 0.30946895, 0.31398875, 0.31854683, 0.32314324,\n\t0.32777813, 0.33245158, 0.33716366, 0.34191445, 0.3467041, 0.3515327, 0.35640025, 0.36130688,\n\t0.3662527, 0.37123778, 0.37626222, 0.3813261, 0.38642952, 0.39157256, 0.3967553, 0.40197787,\n\t0.4072403, 0.4125427, 0.41788515, 0.42326775, 0.42869055, 0.4341537, 0.43965724, 0.44520125,\n\t0.45078585, 0.45641106, 0.46207705, 0.46778384, 0.47353154, 0.47932023, 0.48514998, 0.4910209,\n\t0.49693304, 0.5028866, 0.50888145, 0.5149178, 0.5209957, 0.52711535, 0.5332766, 0.5394797,\n\t0.5457247, 0.5520116, 0.5583406, 0.5647117, 0.57112503, 0.57758063, 0.5840786, 0.590619, 0.597202,\n\t0.60382754, 0.61049575, 0.61720675, 0.62396055, 0.63075733, 0.637597, 0.6444799, 0.6514058,\n\t0.65837497, 0.66538745, 0.67244333, 0.6795426, 0.68668544, 0.69387203, 0.70110214, 0.70837605,\n\t0.7156938, 0.72305536, 0.730461, 0.7379107, 0.7454045, 0.75294244, 0.76052475, 0.7681514, 0.77582246,\n\t0.78353804, 0.79129815, 0.79910296, 0.8069525, 0.8148468, 0.822786, 0.8307701, 0.83879924, 0.84687346,\n\t0.8549928, 0.8631574, 0.87136734, 0.8796226, 0.8879232, 0.89626956, 0.90466136, 0.913099, 0.92158204,\n\t0.93011117, 0.9386859, 0.9473069, 0.9559735, 0.9646866, 0.9734455, 0.98225087, 0.9911022, 1.,\n];\n\n#[inline]\npub fn srgb_u8_to_float(c: u8) -> f32 {\n\tFROM_SRGB_U8[c as usize]\n}\n\n#[cfg(test)]\nmod tests {\n\tuse super::*;\n\n\t// https://microsoft.github.io/DirectX-Specs/d3d/archive/D3D11_3_FunctionalSpec.htm#FLOATtoSRGB\n\tfn float_to_srgb_ref(f: f32) -> f32 {\n\t\tif !(f > 0_f32) {\n\t\t\t0_f32\n\t\t} else if f <= 0.0031308f32 {\n\t\t\t12.92_f32 * f\n\t\t} else if f < 1_f32 {\n\t\t\t1.055f32 * f.powf(1_f32 / 2.4_f32) - 0.055f32\n\t\t} else {\n\t\t\t1_f32\n\t\t}\n\t}\n\n\tfn float_to_srgb_u8_ref(f: f32) -> u8 {\n\t\t(float_to_srgb_ref(f) * 255_f32 + 0.5_f32) as u8\n\t}\n\n\t// https://microsoft.github.io/DirectX-Specs/d3d/archive/D3D11_3_FunctionalSpec.htm#SRGBtoFLOAT\n\tfn srgb_to_float_ref(f: f32) -> f32 {\n\t\tif f <= 0.04045f32 { f / 12.92f32 } else { ((f + 0.055f32) / 1.055f32).powf(2.4_f32) }\n\t}\n\n\tfn srgb_u8_to_float_ref(c: u8) -> f32 {\n\t\tsrgb_to_float_ref(c as f32 * (1_f32 / 255_f32))\n\t}\n\n\t#[test]\n\tfn test_float_to_srgb_u8() {\n\t\tfor u in 0..=u8::MAX {\n\t\t\tassert!(srgb_u8_to_float(u) == srgb_u8_to_float_ref(u));\n\t\t}\n\t}\n\n\t#[ignore = \"expensive, test in release mode\"]\n\t#[test]\n\tfn test_srgb_u8_to_float() {\n\t\t// Simply... check all float values.\n\t\tfor u in 0..=u32::MAX {\n\t\t\tlet f = f32::from_bits(u);\n\t\t\tassert!(float_to_srgb_u8(f) == float_to_srgb_u8_ref(f));\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/no-std-types/src/color/mod.rs",
    "content": "mod color_traits;\nmod color_types;\nmod discrete_srgb;\n\npub use color_traits::*;\npub use color_types::*;\npub use discrete_srgb::*;\n"
  },
  {
    "path": "node-graph/libraries/no-std-types/src/context.rs",
    "content": "pub trait Ctx: Clone + Send {}\n\nimpl<T: Ctx> Ctx for Option<T> {}\nimpl<T: Ctx + Sync> Ctx for &T {}\nimpl Ctx for () {}\n\npub trait ArcCtx: Send + Sync {}\n#[cfg(feature = \"std\")]\nimpl<T: ArcCtx> Ctx for std::sync::Arc<T> {}\n"
  },
  {
    "path": "node-graph/libraries/no-std-types/src/lib.rs",
    "content": "#![cfg_attr(not(feature = \"std\"), no_std)]\n\npub mod blending;\npub mod choice_type;\npub mod color;\npub mod context;\npub mod registry;\npub mod shaders;\n\npub use context::Ctx;\npub use glam;\n\npub trait AsU32 {\n\tfn as_u32(&self) -> u32;\n}\nimpl AsU32 for u32 {\n\tfn as_u32(&self) -> u32 {\n\t\t*self\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/no-std-types/src/registry.rs",
    "content": "pub mod types {\n\t/// 0% - 100%\n\tpub type Percentage = f64;\n\t/// 0% - 100%\n\tpub type PercentageF32 = f32;\n\t/// -100% - 100%\n\tpub type SignedPercentage = f64;\n\t/// -100% - 100%\n\tpub type SignedPercentageF32 = f32;\n\t/// -180° - 180°\n\tpub type Angle = f64;\n\t/// -180° - 180°\n\tpub type AngleF32 = f32;\n\t/// Ends in the unit of x\n\tpub type Multiplier = f64;\n\t/// Non-negative integer with px unit\n\tpub type PixelLength = f64;\n\t/// Non-negative\n\tpub type Length = f64;\n\t/// 0 to 1\n\tpub type Fraction = f64;\n\t/// Non-negative number broken into whole and fractional parts\n\tpub type Progression = f64;\n\t/// Signed integer that's actually a float because we don't handle type conversions very well yet\n\tpub type SignedInteger = f64;\n\t/// Unsigned integer\n\tpub type IntegerCount = u32;\n\t/// Unsigned integer to be used for random seeds\n\tpub type SeedValue = u32;\n\t/// DVec2 with px unit\n\tpub type PixelSize = glam::DVec2;\n\t/// String with one or more than one line\n\t#[cfg(feature = \"std\")]\n\tpub type TextArea = String;\n}\n"
  },
  {
    "path": "node-graph/libraries/no-std-types/src/shaders/buffer_struct/glam.rs",
    "content": "use crate::shaders::buffer_struct::BufferStruct;\n\nmacro_rules! glam_array {\n\t($t:ty, $a:ty) => {\n\t\tunsafe impl BufferStruct for $t {\n\t\t\ttype Buffer = $a;\n\n\t\t\t#[inline]\n\t\t\tfn write(from: Self) -> Self::Buffer {\n\t\t\t\t<$t>::to_array(&from)\n\t\t\t}\n\n\t\t\t#[inline]\n\t\t\tfn read(from: Self::Buffer) -> Self {\n\t\t\t\t<$t>::from_array(from)\n\t\t\t}\n\t\t}\n\t};\n}\n\nmacro_rules! glam_cols_array {\n\t($t:ty, $a:ty) => {\n\t\tunsafe impl BufferStruct for $t {\n\t\t\ttype Buffer = $a;\n\n\t\t\t#[inline]\n\t\t\tfn write(from: Self) -> Self::Buffer {\n\t\t\t\t<$t>::to_cols_array(&from)\n\t\t\t}\n\n\t\t\t#[inline]\n\t\t\tfn read(from: Self::Buffer) -> Self {\n\t\t\t\t<$t>::from_cols_array(&from)\n\t\t\t}\n\t\t}\n\t};\n}\n\nglam_array!(glam::Vec2, [f32; 2]);\nglam_array!(glam::Vec3, [f32; 3]);\n// glam_array!(Vec3A, [f32; 4]);\nglam_array!(glam::Vec4, [f32; 4]);\nglam_array!(glam::Quat, [f32; 4]);\nglam_cols_array!(glam::Mat2, [f32; 4]);\nglam_cols_array!(glam::Mat3, [f32; 9]);\n// glam_cols_array!(Mat3A, [f32; 4]);\nglam_cols_array!(glam::Mat4, [f32; 16]);\nglam_cols_array!(glam::Affine2, [f32; 6]);\nglam_cols_array!(glam::Affine3A, [f32; 12]);\n\nglam_array!(glam::DVec2, [f64; 2]);\nglam_array!(glam::DVec3, [f64; 3]);\nglam_array!(glam::DVec4, [f64; 4]);\nglam_array!(glam::DQuat, [f64; 4]);\nglam_cols_array!(glam::DMat2, [f64; 4]);\nglam_cols_array!(glam::DMat3, [f64; 9]);\nglam_cols_array!(glam::DMat4, [f64; 16]);\nglam_cols_array!(glam::DAffine2, [f64; 6]);\nglam_cols_array!(glam::DAffine3, [f64; 12]);\n\nglam_array!(glam::I16Vec2, [i16; 2]);\nglam_array!(glam::I16Vec3, [i16; 3]);\nglam_array!(glam::I16Vec4, [i16; 4]);\n\nglam_array!(glam::U16Vec2, [u16; 2]);\nglam_array!(glam::U16Vec3, [u16; 3]);\nglam_array!(glam::U16Vec4, [u16; 4]);\n\nglam_array!(glam::IVec2, [i32; 2]);\nglam_array!(glam::IVec3, [i32; 3]);\nglam_array!(glam::IVec4, [i32; 4]);\n\nglam_array!(glam::UVec2, [u32; 2]);\nglam_array!(glam::UVec3, [u32; 3]);\nglam_array!(glam::UVec4, [u32; 4]);\n\nglam_array!(glam::I64Vec2, [i64; 2]);\nglam_array!(glam::I64Vec3, [i64; 3]);\nglam_array!(glam::I64Vec4, [i64; 4]);\n\nglam_array!(glam::U64Vec2, [u64; 2]);\nglam_array!(glam::U64Vec3, [u64; 3]);\nglam_array!(glam::U64Vec4, [u64; 4]);\n\nunsafe impl BufferStruct for glam::Vec3A {\n\ttype Buffer = [f32; 4];\n\n\t#[inline]\n\tfn write(from: Self) -> Self::Buffer {\n\t\tglam::Vec4::to_array(&from.extend(0.))\n\t}\n\n\t#[inline]\n\tfn read(from: Self::Buffer) -> Self {\n\t\tglam::Vec3A::from_vec4(glam::Vec4::from_array(from))\n\t}\n}\n\n/// do NOT use slices, otherwise spirv will fail to compile\nunsafe impl BufferStruct for glam::Mat3A {\n\ttype Buffer = [f32; 12];\n\n\t#[inline]\n\tfn write(from: Self) -> Self::Buffer {\n\t\tlet a = from.to_cols_array();\n\t\t[a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], 0., 0., 0.]\n\t}\n\n\t#[inline]\n\tfn read(from: Self::Buffer) -> Self {\n\t\tlet a = from;\n\t\tglam::Mat3A::from_cols_array(&[a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]])\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/no-std-types/src/shaders/buffer_struct/mod.rs",
    "content": "//! I (@firestar99) copied this entire mod from one of my projects, as I haven't uploaded that lib to crates. Hopefully\n//! rust-gpu improves and this entire thing becomes unnecessary in the future.\n//!\n//! https://github.com/Firestar99/nanite-at-home/tree/008dac8df656959c71efeddd2d3ddabcb801771c/rust-gpu-bindless/crates/buffer-content\n\nuse bytemuck::Pod;\n\nmod glam;\nmod primitive;\n\n/// A BufferStruct is a \"parallel representation\" of the original struct with some fundamental types remapped. This\n/// struct hierarchy represents how data is stored in GPU Buffers, where all types must be [`Pod`] to allow\n/// transmuting them to `&[u8]` with [`bytemuck`].\n///\n/// Notable type remappings (original: buffer):\n/// * bool: u32 of 0 or 1\n/// * any repr(u32) enum: u32 with remapping via [`num_enum`]\n///\n/// By adding `#[derive(ShaderStruct)]` to your struct (or enum), a parallel `{name}Buffer` struct is created with all\n/// the members of the original struct, but with their types using the associated remapped types as specified by this\n/// trait.\n///\n/// # Origin\n/// I (@firestar99) copied this entire mod from my [Nanite-at-home] project, specifically the [buffer-content] crate\n/// and the [buffer_struct] proc macro. The variant here has quite some modifications, to both cleaned up some of the\n/// mistakes my implementation has and to customize it a bit for graphite.\n///\n/// Hopefully rust-gpu improves to the point where this remapping becomes unnecessary.\n///\n/// [Nanite-at-home]: https://github.com/Firestar99/nanite-at-home\n/// [buffer-content]: https://github.com/Firestar99/nanite-at-home/tree/008dac8df656959c71efeddd2d3ddabcb801771c/rust-gpu-bindless/crates/buffer-content\n/// [buffer_struct]: https://github.com/Firestar99/nanite-at-home/blob/008dac8df656959c71efeddd2d3ddabcb801771c/rust-gpu-bindless/crates/macros/src/buffer_struct.rs\n///\n/// # Safety\n/// The associated type Transfer must be the same on all targets. Writing followed by reading back a value must result\n/// in the same value.\npub unsafe trait BufferStruct: Copy + Send + Sync + 'static {\n\ttype Buffer: Pod + Send + Sync;\n\n\tfn write(from: Self) -> Self::Buffer;\n\n\tfn read(from: Self::Buffer) -> Self;\n}\n\n/// Trait marking all [`BufferStruct`] whose read and write methods are identity. While [`BufferStruct`] only\n/// requires `t == read(write(t))`, this trait additionally requires `t == read(t) == write(t)`. As this removes the\n/// conversion requirement for writing to or reading from a buffer, one can acquire slices from buffers created of these\n/// types.\n///\n/// Implementing this type is completely safe due to the [`Pod`] requirement.\npub trait BufferStructIdentity: Pod + Send + Sync {}\n\nunsafe impl<T: BufferStructIdentity> BufferStruct for T {\n\ttype Buffer = Self;\n\n\tfn write(from: Self) -> Self::Buffer {\n\t\tfrom\n\t}\n\n\tfn read(from: Self::Buffer) -> Self {\n\t\tfrom\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/no-std-types/src/shaders/buffer_struct/primitive.rs",
    "content": "use crate::shaders::buffer_struct::{BufferStruct, BufferStructIdentity};\nuse bytemuck::Pod;\nuse core::marker::PhantomData;\nuse core::num::Wrapping;\nuse spirv_std::arch::IndexUnchecked;\n\nmacro_rules! identity {\n\t($t:ty) => {\n\t\timpl BufferStructIdentity for $t {}\n\t};\n}\n\nidentity!(());\nidentity!(u8);\nidentity!(u16);\nidentity!(u32);\nidentity!(u64);\nidentity!(u128);\nidentity!(usize);\nidentity!(i8);\nidentity!(i16);\nidentity!(i32);\nidentity!(i64);\nidentity!(i128);\nidentity!(isize);\nidentity!(f32);\nidentity!(f64);\n\nidentity!(spirv_std::arch::SubgroupMask);\nidentity!(spirv_std::memory::Semantics);\nidentity!(spirv_std::ray_tracing::RayFlags);\nidentity!(spirv_std::indirect_command::DrawIndirectCommand);\nidentity!(spirv_std::indirect_command::DrawIndexedIndirectCommand);\nidentity!(spirv_std::indirect_command::DispatchIndirectCommand);\nidentity!(spirv_std::indirect_command::DrawMeshTasksIndirectCommandEXT);\nidentity!(spirv_std::indirect_command::TraceRaysIndirectCommandKHR);\n// not pod\n// identity!(spirv_std::indirect_command::TraceRaysIndirectCommand2KHR);\n\nunsafe impl BufferStruct for bool {\n\ttype Buffer = u32;\n\n\t#[inline]\n\tfn write(from: Self) -> Self::Buffer {\n\t\tfrom as u32\n\t}\n\n\t#[inline]\n\tfn read(from: Self::Buffer) -> Self {\n\t\tfrom != 0\n\t}\n}\n\nunsafe impl<T: BufferStruct> BufferStruct for Wrapping<T>\nwhere\n\t// unfortunately has to be Pod, even though AnyBitPattern would be sufficient,\n\t// due to bytemuck doing `impl<T: Pod> AnyBitPattern for T {}`\n\t// see https://github.com/Lokathor/bytemuck/issues/164\n\tT::Buffer: Pod,\n{\n\ttype Buffer = Wrapping<T::Buffer>;\n\n\t#[inline]\n\tfn write(from: Self) -> Self::Buffer {\n\t\tWrapping(T::write(from.0))\n\t}\n\n\t#[inline]\n\tfn read(from: Self::Buffer) -> Self {\n\t\tWrapping(T::read(from.0))\n\t}\n}\n\nunsafe impl<T: BufferStruct + 'static> BufferStruct for PhantomData<T> {\n\ttype Buffer = PhantomData<T>;\n\n\t#[inline]\n\tfn write(_: Self) -> Self::Buffer {\n\t\tPhantomData {}\n\t}\n\n\t#[inline]\n\tfn read(_: Self::Buffer) -> Self {\n\t\tPhantomData {}\n\t}\n}\n\n/// Potential problem: you can't impl this for an array of BufferStruct, as it'll conflict with this impl due to the\n/// blanket impl on all BufferStructPlain types.\nunsafe impl<T: BufferStruct, const N: usize> BufferStruct for [T; N]\nwhere\n\t// rust-gpu does not like `[T; N].map()` nor `core::array::from_fn()` nor transmuting arrays with a const generic\n\t// length, so for now we need to require T: Default and T::Transfer: Default for all arrays.\n\tT: Default,\n\t// unfortunately has to be Pod, even though AnyBitPattern would be sufficient,\n\t// due to bytemuck doing `impl<T: Pod> AnyBitPattern for T {}`\n\t// see https://github.com/Lokathor/bytemuck/issues/164\n\tT::Buffer: Pod + Default,\n{\n\ttype Buffer = [T::Buffer; N];\n\n\t#[inline]\n\tfn write(from: Self) -> Self::Buffer {\n\t\tunsafe {\n\t\t\tlet mut ret = [T::Buffer::default(); N];\n\t\t\tfor i in 0..N {\n\t\t\t\t*ret.index_unchecked_mut(i) = T::write(*from.index_unchecked(i));\n\t\t\t}\n\t\t\tret\n\t\t}\n\t}\n\n\t#[inline]\n\tfn read(from: Self::Buffer) -> Self {\n\t\tunsafe {\n\t\t\tlet mut ret = [T::default(); N];\n\t\t\tfor i in 0..N {\n\t\t\t\t*ret.index_unchecked_mut(i) = T::read(*from.index_unchecked(i));\n\t\t\t}\n\t\t\tret\n\t\t}\n\t}\n}\n\n#[cfg(test)]\nmod tests {\n\tuse super::*;\n\n\t#[test]\n\tfn roundtrip_bool() {\n\t\tfor x in [false, true] {\n\t\t\tassert_eq!(x, <bool as BufferStruct>::read(<bool as BufferStruct>::write(x)));\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/no-std-types/src/shaders/mod.rs",
    "content": "//! supporting infrastructure for shaders\n\npub mod buffer_struct;\n\npub mod __private {\n\tpub use bytemuck;\n\tpub use glam;\n\tpub use num_enum;\n\tpub use spirv_std;\n}\n"
  },
  {
    "path": "node-graph/libraries/raster-types/Cargo.toml",
    "content": "[package]\nname = \"raster-types\"\nversion = \"0.1.0\"\nedition = \"2024\"\ndescription = \"Raster data types for Graphene node system\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\nlicense = \"MIT OR Apache-2.0\"\n\n[features]\ndefault = [\"serde\"]\nwgpu = [\"dep:wgpu\"]\nwasm = [\"core-types/wasm\", \"tsify\", \"wasm-bindgen\"]\n\n[dependencies]\n# Local dependencies\ncore-types = { workspace = true }\nnode-macro = { workspace = true }\n\n# Workspace dependencies\ndyn-any = { workspace = true }\nglam = { workspace = true }\nbase64 = { workspace = true }\nbytemuck = { workspace = true }\nimage = { workspace = true }\nserde_json = { workspace = true }\n\n# Optional workspace dependencies\nserde = { workspace = true, optional = true }\ntsify = { workspace = true, optional = true }\nwgpu = { workspace = true, optional = true }\nwasm-bindgen = { workspace = true, optional = true }\n"
  },
  {
    "path": "node-graph/libraries/raster-types/src/image.rs",
    "content": "use crate::raster_types::{CPU, Raster};\nuse crate::{Bitmap, BitmapMut};\nuse core_types::AlphaBlending;\nuse core_types::Color;\nuse core_types::color::float_to_srgb_u8;\nuse core_types::table::{Table, TableRow};\n// use crate::vector::Vector; // TODO: Check if Vector is actually used, if so handle differently\nuse core::hash::{Hash, Hasher};\nuse core_types::color::*;\nuse dyn_any::{DynAny, StaticType};\nuse glam::{DAffine2, DVec2};\nuse std::vec::Vec;\n\nmod base64_serde {\n\t//! Basic wrapper for [`serde`] to perform [`base64`] encoding\n\n\tuse base64::Engine;\n\tuse core_types::color::*;\n\tuse serde::{Deserialize, Deserializer, Serialize, Serializer};\n\n\tpub fn as_base64<S: Serializer, P: Pixel>(key: &[P], serializer: S) -> Result<S::Ok, S::Error> {\n\t\tlet u8_data = bytemuck::cast_slice(key);\n\t\tlet string = base64::engine::general_purpose::STANDARD.encode(u8_data);\n\t\t(key.len() as u64, string).serialize(serializer)\n\t}\n\n\tpub fn from_base64<'a, D: Deserializer<'a>, P: Pixel>(deserializer: D) -> Result<Vec<P>, D::Error> {\n\t\tuse serde::de::Error;\n\t\t<(u64, &[u8])>::deserialize(deserializer)\n\t\t\t.and_then(|(len, str)| {\n\t\t\t\tlet mut output: Vec<P> = vec![P::zeroed(); len as usize];\n\t\t\t\tbase64::engine::general_purpose::STANDARD\n\t\t\t\t\t.decode_slice(str, bytemuck::cast_slice_mut(output.as_mut_slice()))\n\t\t\t\t\t.map_err(|err| Error::custom(err.to_string()))?;\n\n\t\t\t\tOk(output)\n\t\t\t})\n\t\t\t.map_err(serde::de::Error::custom)\n\t}\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, Eq, Default, serde::Serialize, serde::Deserialize)]\npub struct Image<P: Pixel> {\n\tpub width: u32,\n\tpub height: u32,\n\t#[serde(serialize_with = \"base64_serde::as_base64\", deserialize_with = \"base64_serde::from_base64\")]\n\tpub data: Vec<P>,\n\t/// Optional: Stores a base64 string representation of the image which can be used to speed up the conversion\n\t/// to an svg string. This is used as a cache in order to not have to encode the data on every graph evaluation.\n\t#[serde(skip)]\n\tpub base64_string: Option<String>,\n\t// TODO: Add an `origin` field to store where in the local space the image is anchored.\n\t// TODO: Currently it is always anchored at the top left corner at (0, 0). The bottom right corner of the new origin field would correspond to (1, 1).\n}\n\nimpl<P: Pixel + PartialEq> PartialEq for Image<P> {\n\tfn eq(&self, other: &Self) -> bool {\n\t\tself.width == other.width && self.height == other.height && self.data == other.data\n\t}\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, dyn_any::DynAny, Default, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct TransformImage(pub DAffine2);\n\nimpl Hash for TransformImage {\n\tfn hash<H: std::hash::Hasher>(&self, _: &mut H) {}\n}\n\nimpl<P: Pixel + std::fmt::Debug> std::fmt::Debug for Image<P> {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tlet length = self.data.len();\n\t\tf.debug_struct(\"Image\")\n\t\t\t.field(\"width\", &self.width)\n\t\t\t.field(\"height\", &self.height)\n\t\t\t.field(\"data\", if length < 100 { &self.data } else { &length })\n\t\t\t.finish()\n\t}\n}\n\nunsafe impl<P> StaticType for Image<P>\nwhere\n\tP: dyn_any::StaticTypeSized + Pixel,\n\tP::Static: Pixel,\n{\n\ttype Static = Image<P::Static>;\n}\n\nimpl<P: Copy + Pixel> Bitmap for Image<P> {\n\ttype Pixel = P;\n\t#[inline(always)]\n\tfn get_pixel(&self, x: u32, y: u32) -> Option<P> {\n\t\tself.data.get((x + y * self.width) as usize).copied()\n\t}\n\t#[inline(always)]\n\tfn width(&self) -> u32 {\n\t\tself.width\n\t}\n\t#[inline(always)]\n\tfn height(&self) -> u32 {\n\t\tself.height\n\t}\n}\n\nimpl<P: Copy + Pixel> BitmapMut for Image<P> {\n\tfn get_pixel_mut(&mut self, x: u32, y: u32) -> Option<&mut P> {\n\t\tself.data.get_mut((x + y * self.width) as usize)\n\t}\n}\n\n// TODO: Evaluate if this will be a problem for our use case.\n/// Warning: This is an approximation of a hash, and is not guaranteed to not collide.\nimpl<P: Hash + Pixel> Hash for Image<P> {\n\tfn hash<H: Hasher>(&self, state: &mut H) {\n\t\tconst HASH_SAMPLES: u64 = 1000;\n\t\tlet data_length = self.data.len() as u64;\n\t\tself.width.hash(state);\n\t\tself.height.hash(state);\n\t\tfor i in 0..HASH_SAMPLES.min(data_length) {\n\t\t\tself.data[(i * data_length / HASH_SAMPLES) as usize].hash(state);\n\t\t}\n\t}\n}\n\nimpl<P: Pixel> Image<P> {\n\tpub fn new(width: u32, height: u32, color: P) -> Self {\n\t\tSelf {\n\t\t\twidth,\n\t\t\theight,\n\t\t\tdata: vec![color; (width * height) as usize],\n\t\t\tbase64_string: None,\n\t\t}\n\t}\n}\n\nimpl Image<Color> {\n\t/// Generate Image from some frontend image data (the canvas pixels as u8s in a flat array)\n\tpub fn from_image_data(image_data: &[u8], width: u32, height: u32) -> Self {\n\t\tlet data = image_data.chunks_exact(4).map(|v| Color::from_rgba8_srgb(v[0], v[1], v[2], v[3])).collect();\n\t\tImage {\n\t\t\twidth,\n\t\t\theight,\n\t\t\tdata,\n\t\t\tbase64_string: None,\n\t\t}\n\t}\n\n\tpub fn to_png(&self) -> Vec<u8> {\n\t\tuse ::image::ImageEncoder;\n\t\tlet (data, width, height) = self.to_flat_u8();\n\t\tlet mut png = Vec::new();\n\t\tlet encoder = ::image::codecs::png::PngEncoder::new(&mut png);\n\t\tencoder.write_image(&data, width, height, ::image::ExtendedColorType::Rgba8).expect(\"failed to encode image as png\");\n\t\tpng\n\t}\n}\n\nuse super::*;\nimpl<P: Alpha + RGB + AssociatedAlpha> Image<P>\nwhere\n\tP::ColorChannel: Linear,\n\t<P as Alpha>::AlphaChannel: Linear,\n{\n\t/// Flattens each channel cast to a u8\n\tpub fn to_flat_u8(&self) -> (Vec<u8>, u32, u32) {\n\t\tlet Image { width, height, data, .. } = self;\n\t\tassert_eq!(data.len(), *width as usize * *height as usize);\n\n\t\t// Cache the last sRGB value we computed, speeds up fills.\n\t\tlet mut last_r = 0.;\n\t\tlet mut last_r_srgb = 0u8;\n\t\tlet mut last_g = 0.;\n\t\tlet mut last_g_srgb = 0u8;\n\t\tlet mut last_b = 0.;\n\t\tlet mut last_b_srgb = 0u8;\n\n\t\tlet mut result = vec![0; data.len() * 4];\n\t\tlet mut i = 0;\n\t\tfor color in data {\n\t\t\tlet a = color.a().to_f32();\n\t\t\t// Smaller alpha values than this would map to fully transparent\n\t\t\t// anyway, avoid expensive encoding.\n\t\t\tif a >= 0.5 / 255. {\n\t\t\t\tlet undo_premultiply = 1. / a;\n\t\t\t\tlet r = color.r().to_f32() * undo_premultiply;\n\t\t\t\tlet g = color.g().to_f32() * undo_premultiply;\n\t\t\t\tlet b = color.b().to_f32() * undo_premultiply;\n\n\t\t\t\t// Compute new sRGB value if necessary.\n\t\t\t\tif r != last_r {\n\t\t\t\t\tlast_r = r;\n\t\t\t\t\tlast_r_srgb = float_to_srgb_u8(r);\n\t\t\t\t}\n\t\t\t\tif g != last_g {\n\t\t\t\t\tlast_g = g;\n\t\t\t\t\tlast_g_srgb = float_to_srgb_u8(g);\n\t\t\t\t}\n\t\t\t\tif b != last_b {\n\t\t\t\t\tlast_b = b;\n\t\t\t\t\tlast_b_srgb = float_to_srgb_u8(b);\n\t\t\t\t}\n\n\t\t\t\tresult[i] = last_r_srgb;\n\t\t\t\tresult[i + 1] = last_g_srgb;\n\t\t\t\tresult[i + 2] = last_b_srgb;\n\t\t\t\tresult[i + 3] = (a * 255. + 0.5) as u8;\n\t\t\t}\n\n\t\t\ti += 4;\n\t\t}\n\n\t\t(result, *width, *height)\n\t}\n}\n\nimpl<P: Pixel> IntoIterator for Image<P> {\n\ttype Item = P;\n\ttype IntoIter = std::vec::IntoIter<P>;\n\tfn into_iter(self) -> Self::IntoIter {\n\t\tself.data.into_iter()\n\t}\n}\n\n// TODO: Eventually remove this migration document upgrade code\npub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Raster<CPU>>, D::Error> {\n\tuse serde::Deserialize;\n\n\t#[derive(Clone, Debug, Hash, PartialEq, DynAny)]\n\tenum RasterFrame {\n\t\tImageFrame(Table<Image<Color>>),\n\t}\n\timpl<'de> serde::Deserialize<'de> for RasterFrame {\n\t\tfn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {\n\t\t\tOk(RasterFrame::ImageFrame(Table::new_from_element(Image::deserialize(deserializer)?)))\n\t\t}\n\t}\n\timpl serde::Serialize for RasterFrame {\n\t\tfn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {\n\t\t\tmatch self {\n\t\t\t\tRasterFrame::ImageFrame(table) => table.serialize(serializer),\n\t\t\t}\n\t\t}\n\t}\n\n\t#[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]\n\tpub enum GraphicElement {\n\t\tGraphicGroup(Table<GraphicElement>),\n\t\tRasterFrame(RasterFrame),\n\t}\n\n\t#[derive(Clone, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize)]\n\tpub struct ImageFrame<P: Pixel> {\n\t\tpub image: Image<P>,\n\t}\n\timpl From<ImageFrame<Color>> for GraphicElement {\n\t\tfn from(image_frame: ImageFrame<Color>) -> Self {\n\t\t\tGraphicElement::RasterFrame(RasterFrame::ImageFrame(Table::new_from_element(image_frame.image)))\n\t\t}\n\t}\n\timpl From<GraphicElement> for ImageFrame<Color> {\n\t\tfn from(element: GraphicElement) -> Self {\n\t\t\tmatch element {\n\t\t\t\tGraphicElement::RasterFrame(RasterFrame::ImageFrame(image)) => Self {\n\t\t\t\t\timage: image.iter().next().unwrap().element.clone(),\n\t\t\t\t},\n\t\t\t\t_ => panic!(\"Expected Image, found {element:?}\"),\n\t\t\t}\n\t\t}\n\t}\n\n\tunsafe impl<P> StaticType for ImageFrame<P>\n\twhere\n\t\tP: dyn_any::StaticTypeSized + Pixel,\n\t\tP::Static: Pixel,\n\t{\n\t\ttype Static = ImageFrame<P::Static>;\n\t}\n\n\t#[derive(Clone, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize)]\n\tpub struct OldImageFrame<P: Pixel> {\n\t\timage: Image<P>,\n\t\ttransform: DAffine2,\n\t\talpha_blending: AlphaBlending,\n\t}\n\n\t#[derive(serde::Serialize, serde::Deserialize)]\n\t#[serde(untagged)]\n\tenum FormatVersions {\n\t\tImage(Image<Color>),\n\t\tOldImageFrame(OldImageFrame<Color>),\n\t\tOlderImageFrameTable(OlderTable<ImageFrame<Color>>),\n\t\tOldImageFrameTable(OldTable<ImageFrame<Color>>),\n\t\tOldImageTable(OldTable<Image<Color>>),\n\t\tOldRasterTable(OldTable<Raster<CPU>>),\n\t\tImageFrameTable(Table<ImageFrame<Color>>),\n\t\tImageTable(Table<Image<Color>>),\n\t\tRasterTable(Table<Raster<CPU>>),\n\t}\n\n\t#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]\n\tpub struct OldTable<T> {\n\t\t#[serde(alias = \"instances\", alias = \"instance\")]\n\t\telement: Vec<T>,\n\t\ttransform: Vec<DAffine2>,\n\t\talpha_blending: Vec<AlphaBlending>,\n\t}\n\n\t#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]\n\tpub struct OlderTable<T> {\n\t\tid: Vec<u64>,\n\t\t#[serde(alias = \"instances\", alias = \"instance\")]\n\t\telement: Vec<T>,\n\t}\n\n\tfn from_image_table(table: Table<Image<Color>>) -> Table<Raster<CPU>> {\n\t\tTable::new_from_element(Raster::new_cpu(table.iter().next().unwrap().element.clone()))\n\t}\n\n\tfn old_table_to_new_table<T>(old_table: OldTable<T>) -> Table<T> {\n\t\told_table\n\t\t\t.element\n\t\t\t.into_iter()\n\t\t\t.zip(old_table.transform.into_iter().zip(old_table.alpha_blending))\n\t\t\t.map(|(element, (transform, alpha_blending))| TableRow {\n\t\t\t\telement,\n\t\t\t\ttransform,\n\t\t\t\talpha_blending,\n\t\t\t\tsource_node_id: None,\n\t\t\t})\n\t\t\t.collect()\n\t}\n\n\tfn older_table_to_new_table<T>(old_table: OlderTable<T>) -> Table<T> {\n\t\told_table\n\t\t\t.element\n\t\t\t.into_iter()\n\t\t\t.map(|element| TableRow {\n\t\t\t\telement,\n\t\t\t\ttransform: DAffine2::IDENTITY,\n\t\t\t\talpha_blending: AlphaBlending::default(),\n\t\t\t\tsource_node_id: None,\n\t\t\t})\n\t\t\t.collect()\n\t}\n\n\tfn from_image_frame_table(image_frame: Table<ImageFrame<Color>>) -> Table<Raster<CPU>> {\n\t\tTable::new_from_element(Raster::new_cpu(\n\t\t\timage_frame\n\t\t\t\t.iter()\n\t\t\t\t.next()\n\t\t\t\t.unwrap_or(Table::new_from_element(ImageFrame::default()).iter().next().unwrap())\n\t\t\t\t.element\n\t\t\t\t.image\n\t\t\t\t.clone(),\n\t\t))\n\t}\n\n\tOk(match FormatVersions::deserialize(deserializer)? {\n\t\tFormatVersions::Image(image) => Table::new_from_element(Raster::new_cpu(image)),\n\t\tFormatVersions::OldImageFrame(OldImageFrame { image, transform, alpha_blending }) => {\n\t\t\tlet mut image_frame_table = Table::new_from_element(Raster::new_cpu(image));\n\t\t\t*image_frame_table.iter_mut().next().unwrap().transform = transform;\n\t\t\t*image_frame_table.iter_mut().next().unwrap().alpha_blending = alpha_blending;\n\t\t\timage_frame_table\n\t\t}\n\t\tFormatVersions::OlderImageFrameTable(old_table) => from_image_frame_table(older_table_to_new_table(old_table)),\n\t\tFormatVersions::OldImageFrameTable(old_table) => from_image_frame_table(old_table_to_new_table(old_table)),\n\t\tFormatVersions::OldImageTable(old_table) => from_image_table(old_table_to_new_table(old_table)),\n\t\tFormatVersions::OldRasterTable(old_table) => old_table_to_new_table(old_table),\n\t\tFormatVersions::ImageFrameTable(image_frame) => from_image_frame_table(image_frame),\n\t\tFormatVersions::ImageTable(table) => from_image_table(table),\n\t\tFormatVersions::RasterTable(table) => table,\n\t})\n}\n\n// TODO: Eventually remove this migration document upgrade code\npub fn migrate_image_frame_row<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<TableRow<Raster<CPU>>, D::Error> {\n\tuse serde::Deserialize;\n\n\t#[derive(Clone, Debug, Hash, PartialEq, DynAny)]\n\tenum RasterFrame {\n\t\t/// A CPU-based bitmap image with a finite position and extent, equivalent to the SVG <image> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image\n\t\tImageFrame(Table<Image<Color>>),\n\t}\n\timpl<'de> serde::Deserialize<'de> for RasterFrame {\n\t\tfn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {\n\t\t\tOk(RasterFrame::ImageFrame(Table::new_from_element(Image::deserialize(deserializer)?)))\n\t\t}\n\t}\n\timpl serde::Serialize for RasterFrame {\n\t\tfn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {\n\t\t\tmatch self {\n\t\t\t\tRasterFrame::ImageFrame(table) => table.serialize(serializer),\n\t\t\t}\n\t\t}\n\t}\n\n\t#[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]\n\tpub enum GraphicElement {\n\t\t/// Equivalent to the SVG <g> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g\n\t\tGraphicGroup(Table<GraphicElement>),\n\t\tRasterFrame(RasterFrame),\n\t}\n\n\t#[derive(Clone, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize)]\n\tpub struct ImageFrame<P: Pixel> {\n\t\tpub image: Image<P>,\n\t}\n\timpl From<ImageFrame<Color>> for GraphicElement {\n\t\tfn from(image_frame: ImageFrame<Color>) -> Self {\n\t\t\tGraphicElement::RasterFrame(RasterFrame::ImageFrame(Table::new_from_element(image_frame.image)))\n\t\t}\n\t}\n\timpl From<GraphicElement> for ImageFrame<Color> {\n\t\tfn from(element: GraphicElement) -> Self {\n\t\t\tmatch element {\n\t\t\t\tGraphicElement::RasterFrame(RasterFrame::ImageFrame(image)) => Self {\n\t\t\t\t\timage: image.iter().next().unwrap().element.clone(),\n\t\t\t\t},\n\t\t\t\t_ => panic!(\"Expected Image, found {element:?}\"),\n\t\t\t}\n\t\t}\n\t}\n\n\tunsafe impl<P> StaticType for ImageFrame<P>\n\twhere\n\t\tP: dyn_any::StaticTypeSized + Pixel,\n\t\tP::Static: Pixel,\n\t{\n\t\ttype Static = ImageFrame<P::Static>;\n\t}\n\n\t#[derive(Clone, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize)]\n\tpub struct OldImageFrame<P: Pixel> {\n\t\timage: Image<P>,\n\t\ttransform: DAffine2,\n\t\talpha_blending: AlphaBlending,\n\t}\n\n\t#[derive(serde::Serialize, serde::Deserialize)]\n\t#[serde(untagged)]\n\tenum FormatVersions {\n\t\tImage(Image<Color>),\n\t\tOldImageFrame(OldImageFrame<Color>),\n\t\tImageFrameTable(Table<ImageFrame<Color>>),\n\t\tRasterTable(Table<Raster<CPU>>),\n\t\tRasterTableRow(TableRow<Raster<CPU>>),\n\t}\n\n\tOk(match FormatVersions::deserialize(deserializer)? {\n\t\tFormatVersions::Image(image) => TableRow {\n\t\t\telement: Raster::new_cpu(image),\n\t\t\t..Default::default()\n\t\t},\n\t\tFormatVersions::OldImageFrame(image_frame_with_transform_and_blending) => TableRow {\n\t\t\telement: Raster::new_cpu(image_frame_with_transform_and_blending.image),\n\t\t\ttransform: image_frame_with_transform_and_blending.transform,\n\t\t\talpha_blending: image_frame_with_transform_and_blending.alpha_blending,\n\t\t\tsource_node_id: None,\n\t\t},\n\t\tFormatVersions::ImageFrameTable(image_frame) => TableRow {\n\t\t\telement: Raster::new_cpu(image_frame.iter().next().unwrap().element.image.clone()),\n\t\t\t..Default::default()\n\t\t},\n\t\tFormatVersions::RasterTable(image_frame_table) => image_frame_table.into_iter().next().unwrap_or_default(),\n\t\tFormatVersions::RasterTableRow(image_table_row) => image_table_row,\n\t})\n}\n\nimpl<P: std::fmt::Debug + Copy + Pixel> Sample for Image<P> {\n\ttype Pixel = P;\n\n\t// TODO: Improve sampling logic\n\t#[inline(always)]\n\tfn sample(&self, pos: DVec2, _area: DVec2) -> Option<Self::Pixel> {\n\t\tlet image_size = DVec2::new(self.width() as f64, self.height() as f64);\n\t\tif pos.x < 0. || pos.y < 0. || pos.x >= image_size.x || pos.y >= image_size.y {\n\t\t\treturn None;\n\t\t}\n\t\tself.get_pixel(pos.x as u32, pos.y as u32)\n\t}\n}\n\nimpl<P: Copy + Pixel> Image<P> {\n\tpub fn get_mut(&mut self, x: usize, y: usize) -> &mut P {\n\t\t&mut self.data[y * (self.width as usize) + x]\n\t}\n\n\t/// Clamps the provided point to ((0, 0), (ImageSize.x, ImageSize.y)) and returns the closest pixel\n\tpub fn sample(&self, position: DVec2) -> P {\n\t\tlet x = position.x.clamp(0., self.width as f64 - 1.) as usize;\n\t\tlet y = position.y.clamp(0., self.height as f64 - 1.) as usize;\n\n\t\tself.data[x + y * self.width as usize]\n\t}\n}\n\nimpl<P: Pixel> AsRef<Image<P>> for Image<P> {\n\tfn as_ref(&self) -> &Image<P> {\n\t\tself\n\t}\n}\n\nimpl From<Image<Color>> for Image<SRGBA8> {\n\tfn from(image: Image<Color>) -> Self {\n\t\tlet data = image.data.into_iter().map(|x| x.into()).collect();\n\t\tSelf {\n\t\t\tdata,\n\t\t\twidth: image.width,\n\t\t\theight: image.height,\n\t\t\tbase64_string: None,\n\t\t}\n\t}\n}\n\nimpl From<Image<SRGBA8>> for Image<Color> {\n\tfn from(image: Image<SRGBA8>) -> Self {\n\t\tlet data = image.data.into_iter().map(|x| x.into()).collect();\n\t\tSelf {\n\t\t\tdata,\n\t\t\twidth: image.width,\n\t\t\theight: image.height,\n\t\t\tbase64_string: None,\n\t\t}\n\t}\n}\n\n#[cfg(test)]\nmod test {\n\t#[test]\n\tfn test_image_serialization_roundtrip() {\n\t\tuse super::*;\n\t\tuse crate::Color;\n\t\tlet image = Image {\n\t\t\twidth: 2,\n\t\t\theight: 2,\n\t\t\tdata: vec![Color::WHITE, Color::BLACK, Color::RED, Color::GREEN],\n\t\t\tbase64_string: None,\n\t\t};\n\n\t\tlet serialized = serde_json::to_string(&image).unwrap();\n\t\tprintln!(\"{serialized}\");\n\t\tlet deserialized: Image<Color> = serde_json::from_str(&serialized).unwrap();\n\t\tprintln!(\"{deserialized:?}\");\n\n\t\tassert_eq!(image, deserialized);\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/raster-types/src/lib.rs",
    "content": "pub mod image;\npub mod raster_types;\n\n// Re-exports for convenience\npub use image::Image;\npub use raster_types::*;\n\n// Re-export color types from no-std-types\npub use core_types::color::*;\n\n/// as to not yet rename all references\npub mod color {\n\tpub use super::*;\n}\n\nuse std::fmt::Debug;\n\npub trait Bitmap {\n\ttype Pixel: Pixel;\n\tfn width(&self) -> u32;\n\tfn height(&self) -> u32;\n\tfn dimensions(&self) -> (u32, u32) {\n\t\t(self.width(), self.height())\n\t}\n\tfn dim(&self) -> (u32, u32) {\n\t\tself.dimensions()\n\t}\n\tfn get_pixel(&self, x: u32, y: u32) -> Option<Self::Pixel>;\n}\n\nimpl<T: Bitmap> Bitmap for &T {\n\ttype Pixel = T::Pixel;\n\n\tfn width(&self) -> u32 {\n\t\t(**self).width()\n\t}\n\n\tfn height(&self) -> u32 {\n\t\t(**self).height()\n\t}\n\n\tfn get_pixel(&self, x: u32, y: u32) -> Option<Self::Pixel> {\n\t\t(**self).get_pixel(x, y)\n\t}\n}\n\nimpl<T: Bitmap> Bitmap for &mut T {\n\ttype Pixel = T::Pixel;\n\n\tfn width(&self) -> u32 {\n\t\t(**self).width()\n\t}\n\n\tfn height(&self) -> u32 {\n\t\t(**self).height()\n\t}\n\n\tfn get_pixel(&self, x: u32, y: u32) -> Option<Self::Pixel> {\n\t\t(**self).get_pixel(x, y)\n\t}\n}\n\npub trait BitmapMut: Bitmap {\n\tfn get_pixel_mut(&mut self, x: u32, y: u32) -> Option<&mut Self::Pixel>;\n\tfn set_pixel(&mut self, x: u32, y: u32, pixel: Self::Pixel) {\n\t\t*self.get_pixel_mut(x, y).unwrap() = pixel;\n\t}\n\tfn map_pixels<F: Fn(Self::Pixel) -> Self::Pixel>(&mut self, map_fn: F) {\n\t\tfor y in 0..self.height() {\n\t\t\tfor x in 0..self.width() {\n\t\t\t\tlet pixel = self.get_pixel(x, y).unwrap();\n\t\t\t\tself.set_pixel(x, y, map_fn(pixel));\n\t\t\t}\n\t\t}\n\t}\n}\n\nimpl<T: BitmapMut + Bitmap> BitmapMut for &mut T {\n\tfn get_pixel_mut(&mut self, x: u32, y: u32) -> Option<&mut Self::Pixel> {\n\t\t(*self).get_pixel_mut(x, y)\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/raster-types/src/raster_types.rs",
    "content": "use crate::image::Image;\nuse core::ops::Deref;\nuse core_types::Color;\nuse core_types::bounds::{BoundingBox, RenderBoundingBox};\nuse core_types::math::quad::Quad;\nuse dyn_any::DynAny;\nuse glam::{DAffine2, DVec2};\nuse std::fmt::Debug;\nuse std::ops::DerefMut;\n\nmod __private {\n\tpub trait Sealed {}\n}\n\npub trait Storage: __private::Sealed + Clone + Debug + 'static {\n\tfn is_empty(&self) -> bool;\n}\n\n#[derive(Clone, Debug, PartialEq, Hash, Default)]\npub struct Raster<T>\nwhere\n\tRaster<T>: Storage,\n{\n\tstorage: T,\n}\n\nunsafe impl<T> dyn_any::StaticType for Raster<T>\nwhere\n\tRaster<T>: Storage,\n{\n\ttype Static = Raster<T>;\n}\n\nimpl<T> Raster<T>\nwhere\n\tRaster<T>: Storage,\n{\n\tpub fn new(t: T) -> Self {\n\t\tSelf { storage: t }\n\t}\n}\n\nimpl<T> Deref for Raster<T>\nwhere\n\tRaster<T>: Storage,\n{\n\ttype Target = T;\n\n\tfn deref(&self) -> &Self::Target {\n\t\t&self.storage\n\t}\n}\n\nimpl<T> DerefMut for Raster<T>\nwhere\n\tRaster<T>: Storage,\n{\n\tfn deref_mut(&mut self) -> &mut Self::Target {\n\t\t&mut self.storage\n\t}\n}\n\npub use cpu::CPU;\n\nmod cpu {\n\tuse super::*;\n\tuse crate::raster_types::__private::Sealed;\n\n\t#[derive(Clone, Debug, Default, PartialEq, Hash, DynAny)]\n\tpub struct CPU(Image<Color>);\n\n\timpl Sealed for Raster<CPU> {}\n\n\timpl Storage for Raster<CPU> {\n\t\tfn is_empty(&self) -> bool {\n\t\t\tself.0.height == 0 || self.0.width == 0\n\t\t}\n\t}\n\n\timpl Raster<CPU> {\n\t\tpub fn new_cpu(image: Image<Color>) -> Self {\n\t\t\tSelf::new(CPU(image))\n\t\t}\n\n\t\tpub fn data(&self) -> &Image<Color> {\n\t\t\tself\n\t\t}\n\n\t\tpub fn data_mut(&mut self) -> &mut Image<Color> {\n\t\t\tself\n\t\t}\n\n\t\tpub fn into_data(self) -> Image<Color> {\n\t\t\tself.storage.0\n\t\t}\n\t}\n\n\timpl Deref for CPU {\n\t\ttype Target = Image<Color>;\n\n\t\tfn deref(&self) -> &Self::Target {\n\t\t\t&self.0\n\t\t}\n\t}\n\n\timpl DerefMut for CPU {\n\t\tfn deref_mut(&mut self) -> &mut Self::Target {\n\t\t\t&mut self.0\n\t\t}\n\t}\n\n\timpl<'de> serde::Deserialize<'de> for Raster<CPU> {\n\t\tfn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n\t\twhere\n\t\t\tD: serde::Deserializer<'de>,\n\t\t{\n\t\t\tOk(Raster::new_cpu(Image::deserialize(deserializer)?))\n\t\t}\n\t}\n\n\timpl serde::Serialize for Raster<CPU> {\n\t\tfn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n\t\twhere\n\t\t\tS: serde::Serializer,\n\t\t{\n\t\t\tself.0.serialize(serializer)\n\t\t}\n\t}\n}\n\npub use gpu::GPU;\n\n#[cfg(feature = \"wgpu\")]\nmod gpu {\n\tuse super::*;\n\tuse crate::raster_types::__private::Sealed;\n\n\t#[derive(Clone, Debug, PartialEq, Hash)]\n\tpub struct GPU {\n\t\tpub texture: wgpu::Texture,\n\t}\n\n\timpl Sealed for Raster<GPU> {}\n\n\timpl Storage for Raster<GPU> {\n\t\tfn is_empty(&self) -> bool {\n\t\t\tself.texture.width() == 0 || self.texture.height() == 0\n\t\t}\n\t}\n\n\timpl Raster<GPU> {\n\t\tpub fn new_gpu(texture: wgpu::Texture) -> Self {\n\t\t\tSelf::new(GPU { texture })\n\t\t}\n\n\t\tpub fn data(&self) -> &wgpu::Texture {\n\t\t\t&self.texture\n\t\t}\n\t}\n}\n\n#[cfg(not(feature = \"wgpu\"))]\nmod gpu {\n\tuse super::*;\n\tuse crate::raster_types::__private::Sealed;\n\n\t#[derive(Clone, Debug, PartialEq, Hash)]\n\tpub struct GPU;\n\n\timpl Sealed for Raster<GPU> {}\n\n\timpl Storage for Raster<GPU> {\n\t\tfn is_empty(&self) -> bool {\n\t\t\ttrue\n\t\t}\n\t}\n}\n\nmod gpu_common {\n\tuse super::*;\n\n\timpl<'de> serde::Deserialize<'de> for Raster<GPU> {\n\t\tfn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>\n\t\twhere\n\t\t\tD: serde::Deserializer<'de>,\n\t\t{\n\t\t\tunimplemented!()\n\t\t}\n\t}\n\n\timpl serde::Serialize for Raster<GPU> {\n\t\tfn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>\n\t\twhere\n\t\t\tS: serde::Serializer,\n\t\t{\n\t\t\tunimplemented!()\n\t\t}\n\t}\n}\n\nimpl<T> BoundingBox for Raster<T>\nwhere\n\tRaster<T>: Storage,\n{\n\tfn bounding_box(&self, transform: DAffine2, _include_stroke: bool) -> RenderBoundingBox {\n\t\tif self.is_empty() || transform.matrix2.determinant() == 0. {\n\t\t\treturn RenderBoundingBox::None;\n\t\t}\n\n\t\tlet unit_rectangle = Quad::from_box([DVec2::ZERO, DVec2::ONE]);\n\t\tRenderBoundingBox::Rectangle((transform * unit_rectangle).bounding_box())\n\t}\n}\n\n// RenderComplexity trait implementations\nimpl core_types::render_complexity::RenderComplexity for Raster<CPU> {\n\tfn render_complexity(&self) -> usize {\n\t\t(self.width * self.height / 500) as usize\n\t}\n}\n\nimpl core_types::render_complexity::RenderComplexity for Raster<GPU> {\n\tfn render_complexity(&self) -> usize {\n\t\t// GPU textures currently can't have a thumbnail\n\t\tusize::MAX\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/rendering/Cargo.toml",
    "content": "[package]\nname = \"rendering\"\nversion = \"0.1.0\"\nedition = \"2024\"\ndescription = \"SVG rendering for Graphene\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\nlicense = \"MIT OR Apache-2.0\"\n\n[dependencies]\n# Local dependencies\ndyn-any = { workspace = true }\ncore-types = { workspace = true }\n\n# Workspace dependencies\nglam = { workspace = true }\nserde = { workspace = true }\nbase64 = { workspace = true }\nlog = { workspace = true }\nnum-traits = { workspace = true }\nusvg = { workspace = true }\nkurbo = { workspace = true }\nvector-types = { workspace = true }\ngraphic-types = { workspace = true }\n\n\n# Workspace dependencies\nvello = { workspace = true }\n"
  },
  {
    "path": "node-graph/libraries/rendering/src/convert_usvg_path.rs",
    "content": "use glam::DVec2;\nuse vector_types::subpath::{ManipulatorGroup, Subpath};\nuse vector_types::vector::PointId;\n\npub fn convert_usvg_path(path: &usvg::Path) -> Vec<Subpath<PointId>> {\n\tlet mut subpaths = Vec::new();\n\tlet mut manipulators_list = Vec::new();\n\n\tlet mut points = path.data().points().iter();\n\tlet to_vec = |p: &usvg::tiny_skia_path::Point| DVec2::new(p.x as f64, p.y as f64);\n\n\tfor verb in path.data().verbs() {\n\t\tmatch verb {\n\t\t\tusvg::tiny_skia_path::PathVerb::Move => {\n\t\t\t\tsubpaths.push(Subpath::new(std::mem::take(&mut manipulators_list), false));\n\t\t\t\tlet Some(start) = points.next().map(to_vec) else { continue };\n\t\t\t\tmanipulators_list.push(ManipulatorGroup::new(start, Some(start), Some(start)));\n\t\t\t}\n\t\t\tusvg::tiny_skia_path::PathVerb::Line => {\n\t\t\t\tlet Some(end) = points.next().map(to_vec) else { continue };\n\t\t\t\tmanipulators_list.push(ManipulatorGroup::new(end, Some(end), Some(end)));\n\t\t\t}\n\t\t\tusvg::tiny_skia_path::PathVerb::Quad => {\n\t\t\t\tlet Some(handle) = points.next().map(to_vec) else { continue };\n\t\t\t\tlet Some(end) = points.next().map(to_vec) else { continue };\n\t\t\t\tif let Some(last) = manipulators_list.last_mut() {\n\t\t\t\t\tlast.out_handle = Some(last.anchor + (2. / 3.) * (handle - last.anchor));\n\t\t\t\t}\n\t\t\t\tmanipulators_list.push(ManipulatorGroup::new(end, Some(end + (2. / 3.) * (handle - end)), Some(end)));\n\t\t\t}\n\t\t\tusvg::tiny_skia_path::PathVerb::Cubic => {\n\t\t\t\tlet Some(first_handle) = points.next().map(to_vec) else { continue };\n\t\t\t\tlet Some(second_handle) = points.next().map(to_vec) else { continue };\n\t\t\t\tlet Some(end) = points.next().map(to_vec) else { continue };\n\t\t\t\tif let Some(last) = manipulators_list.last_mut() {\n\t\t\t\t\tlast.out_handle = Some(first_handle);\n\t\t\t\t}\n\t\t\t\tmanipulators_list.push(ManipulatorGroup::new(end, Some(second_handle), Some(end)));\n\t\t\t}\n\t\t\tusvg::tiny_skia_path::PathVerb::Close => {\n\t\t\t\tsubpaths.push(Subpath::new(std::mem::take(&mut manipulators_list), true));\n\t\t\t}\n\t\t}\n\t}\n\tsubpaths.push(Subpath::new(manipulators_list, false));\n\tsubpaths\n}\n"
  },
  {
    "path": "node-graph/libraries/rendering/src/lib.rs",
    "content": "pub mod convert_usvg_path;\npub mod render_ext;\nmod renderer;\npub mod to_peniko;\n\npub use renderer::*;\n"
  },
  {
    "path": "node-graph/libraries/rendering/src/render_ext.rs",
    "content": "use crate::renderer::{RenderParams, format_transform_matrix};\nuse core_types::uuid::generate_uuid;\nuse glam::DAffine2;\nuse graphic_types::vector_types::gradient::{Gradient, GradientType};\nuse graphic_types::vector_types::vector::style::{Fill, PaintOrder, PathStyle, Stroke, StrokeAlign, StrokeCap, StrokeJoin};\nuse std::fmt::Write;\n\npub trait RenderExt {\n\ttype Output;\n\tfn render(&self, svg_defs: &mut String, element_transform: DAffine2, stroke_transform: DAffine2, bounds: DAffine2, transformed_bounds: DAffine2, render_params: &RenderParams) -> Self::Output;\n}\n\nimpl RenderExt for Gradient {\n\ttype Output = u64;\n\n\t/// Adds the gradient def through mutating the first argument, returning the gradient ID.\n\tfn render(&self, svg_defs: &mut String, element_transform: DAffine2, stroke_transform: DAffine2, bounds: DAffine2, transformed_bounds: DAffine2, _render_params: &RenderParams) -> Self::Output {\n\t\tlet mut stop = String::new();\n\t\tfor (position, color, original_midpoint) in self.stops.interpolated_samples() {\n\t\t\tstop.push_str(\"<stop\");\n\t\t\tif position != 0. {\n\t\t\t\tlet _ = write!(stop, r#\" offset=\"{}\"\"#, (position * 1_000_000.).round() / 1_000_000.);\n\t\t\t}\n\t\t\tlet _ = write!(stop, r##\" stop-color=\"#{}\"\"##, color.to_rgb_hex_srgb_from_gamma());\n\t\t\tif color.a() < 1. {\n\t\t\t\tlet _ = write!(stop, r#\" stop-opacity=\"{}\"\"#, (color.a() * 1000.).round() / 1000.);\n\t\t\t}\n\t\t\tif let Some(midpoint) = original_midpoint {\n\t\t\t\tlet _ = write!(stop, r#\" graphite:midpoint=\"{}\"\"#, (midpoint * 1000.).round() / 1000.);\n\t\t\t}\n\t\t\tstop.push_str(\" />\")\n\t\t}\n\n\t\tlet transform_points = element_transform * stroke_transform * bounds;\n\t\tlet start = transform_points.transform_point2(self.start);\n\t\tlet end = transform_points.transform_point2(self.end);\n\n\t\tlet gradient_transform = if transformed_bounds.matrix2.determinant() != 0. {\n\t\t\ttransformed_bounds.inverse()\n\t\t} else {\n\t\t\tDAffine2::IDENTITY // Ignore if the transform cannot be inverted (the bounds are zero). See issue #1944.\n\t\t};\n\t\tlet gradient_transform = format_transform_matrix(gradient_transform);\n\t\tlet gradient_transform = if gradient_transform.is_empty() {\n\t\t\tString::new()\n\t\t} else {\n\t\t\tformat!(r#\" gradientTransform=\"{gradient_transform}\"\"#)\n\t\t};\n\n\t\tlet gradient_id = generate_uuid();\n\n\t\tmatch self.gradient_type {\n\t\t\tGradientType::Linear => {\n\t\t\t\tlet _ = write!(\n\t\t\t\t\tsvg_defs,\n\t\t\t\t\tr#\"<linearGradient id=\"{}\" x1=\"{}\" y1=\"{}\" x2=\"{}\" y2=\"{}\"{gradient_transform}>{}</linearGradient>\"#,\n\t\t\t\t\tgradient_id, start.x, start.y, end.x, end.y, stop\n\t\t\t\t);\n\t\t\t}\n\t\t\tGradientType::Radial => {\n\t\t\t\tlet radius = (f64::powi(start.x - end.x, 2) + f64::powi(start.y - end.y, 2)).sqrt();\n\t\t\t\tlet _ = write!(\n\t\t\t\t\tsvg_defs,\n\t\t\t\t\tr#\"<radialGradient id=\"{}\" cx=\"{}\" cy=\"{}\" r=\"{}\"{gradient_transform}>{}</radialGradient>\"#,\n\t\t\t\t\tgradient_id, start.x, start.y, radius, stop\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tgradient_id\n\t}\n}\n\nimpl RenderExt for Fill {\n\ttype Output = String;\n\n\t/// Renders the fill, adding necessary defs through mutating the first argument.\n\tfn render(&self, svg_defs: &mut String, element_transform: DAffine2, stroke_transform: DAffine2, bounds: DAffine2, transformed_bounds: DAffine2, render_params: &RenderParams) -> Self::Output {\n\t\tmatch self {\n\t\t\tSelf::None => r#\" fill=\"none\"\"#.to_string(),\n\t\t\tSelf::Solid(color) => {\n\t\t\t\tlet mut result = format!(r##\" fill=\"#{}\"\"##, color.to_rgb_hex_srgb_from_gamma());\n\t\t\t\tif color.a() < 1. {\n\t\t\t\t\tlet _ = write!(result, r#\" fill-opacity=\"{}\"\"#, (color.a() * 1000.).round() / 1000.);\n\t\t\t\t}\n\t\t\t\tresult\n\t\t\t}\n\t\t\tSelf::Gradient(gradient) => {\n\t\t\t\tlet gradient_id = gradient.render(svg_defs, element_transform, stroke_transform, bounds, transformed_bounds, render_params);\n\t\t\t\tformat!(r##\" fill=\"url('#{gradient_id}')\"\"##)\n\t\t\t}\n\t\t}\n\t}\n}\n\nimpl RenderExt for Stroke {\n\ttype Output = String;\n\n\t/// Provide the SVG attributes for the stroke.\n\tfn render(\n\t\t&self,\n\t\t_svg_defs: &mut String,\n\t\t_element_transform: DAffine2,\n\t\t_stroke_transform: DAffine2,\n\t\t_bounds: DAffine2,\n\t\t_transformed_bounds: DAffine2,\n\t\trender_params: &RenderParams,\n\t) -> Self::Output {\n\t\t// Don't render a stroke at all if it would be invisible\n\t\tlet Some(color) = self.color else { return String::new() };\n\t\tif !self.has_renderable_stroke() {\n\t\t\treturn String::new();\n\t\t}\n\n\t\t// Set to None if the value is the SVG default\n\t\tlet weight = (self.weight != 1.).then_some(self.weight);\n\t\tlet dash_array = (!self.dash_lengths.is_empty()).then_some(self.dash_lengths());\n\t\tlet dash_offset = (self.dash_offset != 0.).then_some(self.dash_offset);\n\t\tlet stroke_cap = (self.cap != StrokeCap::Butt).then_some(self.cap);\n\t\tlet stroke_join = (self.join != StrokeJoin::Miter).then_some(self.join);\n\t\tlet stroke_join_miter_limit = (self.join_miter_limit != 4.).then_some(self.join_miter_limit);\n\t\tlet stroke_align = (self.align != StrokeAlign::Center).then_some(self.align);\n\t\tlet paint_order = (self.paint_order != PaintOrder::StrokeAbove || render_params.override_paint_order).then_some(PaintOrder::StrokeBelow);\n\n\t\t// Render the needed stroke attributes\n\t\tlet mut attributes = format!(r##\" stroke=\"#{}\"\"##, color.to_rgb_hex_srgb_from_gamma());\n\t\tif color.a() < 1. {\n\t\t\tlet _ = write!(&mut attributes, r#\" stroke-opacity=\"{}\"\"#, (color.a() * 1000.).round() / 1000.);\n\t\t}\n\t\tif let Some(mut weight) = weight {\n\t\t\tif stroke_align.is_some() && render_params.aligned_strokes {\n\t\t\t\tweight *= 2.;\n\t\t\t}\n\t\t\tlet _ = write!(&mut attributes, r#\" stroke-width=\"{weight}\"\"#);\n\t\t}\n\t\tif let Some(dash_array) = dash_array {\n\t\t\tlet _ = write!(&mut attributes, r#\" stroke-dasharray=\"{dash_array}\"\"#);\n\t\t}\n\t\tif let Some(dash_offset) = dash_offset {\n\t\t\tlet _ = write!(&mut attributes, r#\" stroke-dashoffset=\"{dash_offset}\"\"#);\n\t\t}\n\t\tif let Some(stroke_cap) = stroke_cap {\n\t\t\tlet _ = write!(&mut attributes, r#\" stroke-linecap=\"{}\"\"#, stroke_cap.svg_name());\n\t\t}\n\t\tif let Some(stroke_join) = stroke_join {\n\t\t\tlet _ = write!(&mut attributes, r#\" stroke-linejoin=\"{}\"\"#, stroke_join.svg_name());\n\t\t}\n\t\tif let Some(stroke_join_miter_limit) = stroke_join_miter_limit {\n\t\t\tlet _ = write!(&mut attributes, r#\" stroke-miterlimit=\"{stroke_join_miter_limit}\"\"#);\n\t\t}\n\t\tif paint_order.is_some() {\n\t\t\tlet _ = write!(&mut attributes, r#\" style=\"paint-order: stroke;\" \"#);\n\t\t}\n\t\tattributes\n\t}\n}\n\nimpl RenderExt for PathStyle {\n\ttype Output = String;\n\n\t/// Renders the shape's fill and stroke attributes as a string with them concatenated together.\n\t#[allow(clippy::too_many_arguments)]\n\tfn render(&self, svg_defs: &mut String, element_transform: DAffine2, stroke_transform: DAffine2, bounds: DAffine2, transformed_bounds: DAffine2, render_params: &RenderParams) -> String {\n\t\tlet fill_attribute = self.fill.render(svg_defs, element_transform, stroke_transform, bounds, transformed_bounds, render_params);\n\t\tlet stroke_attribute = self\n\t\t\t.stroke\n\t\t\t.as_ref()\n\t\t\t.map(|stroke| stroke.render(svg_defs, element_transform, stroke_transform, bounds, transformed_bounds, render_params))\n\t\t\t.unwrap_or_default();\n\t\tformat!(\"{fill_attribute}{stroke_attribute}\")\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/rendering/src/renderer.rs",
    "content": "use crate::render_ext::RenderExt;\nuse crate::to_peniko::BlendModeExt;\nuse core_types::blending::BlendMode;\nuse core_types::bounds::BoundingBox;\nuse core_types::bounds::RenderBoundingBox;\nuse core_types::color::Color;\nuse core_types::math::quad::Quad;\nuse core_types::render_complexity::RenderComplexity;\nuse core_types::table::{Table, TableRow};\nuse core_types::transform::{Footprint, Transform};\nuse core_types::uuid::{NodeId, generate_uuid};\nuse dyn_any::DynAny;\nuse glam::{DAffine2, DVec2};\nuse graphic_types::Vector;\nuse graphic_types::raster_types::{BitmapMut, CPU, GPU, Image, Raster};\nuse graphic_types::vector_types::gradient::{GradientStops, GradientType};\nuse graphic_types::vector_types::subpath::Subpath;\nuse graphic_types::vector_types::vector::click_target::{ClickTarget, FreePoint};\nuse graphic_types::vector_types::vector::style::{Fill, PaintOrder, RenderMode, Stroke, StrokeAlign};\nuse graphic_types::{Artboard, Graphic};\nuse kurbo::{Affine, Cap, Join, Shape};\nuse num_traits::Zero;\nuse std::collections::{HashMap, HashSet};\nuse std::fmt::Write;\nuse std::hash::{Hash, Hasher};\nuse std::ops::Deref;\nuse std::sync::{Arc, LazyLock};\nuse vello::*;\n\n#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)]\nenum MaskType {\n\tClip,\n\tMask,\n}\n\nimpl MaskType {\n\tfn to_attribute(self) -> String {\n\t\tmatch self {\n\t\t\tSelf::Mask => \"mask\".to_string(),\n\t\t\tSelf::Clip => \"clip-path\".to_string(),\n\t\t}\n\t}\n\n\tfn write_to_defs(self, svg_defs: &mut String, uuid: u64, svg_string: String) {\n\t\tlet id = format!(\"mask-{uuid}\");\n\t\tmatch self {\n\t\t\tSelf::Clip => write!(svg_defs, r##\"<clipPath id=\"{id}\">{svg_string}</clipPath>\"##).unwrap(),\n\t\t\tSelf::Mask => write!(svg_defs, r##\"<mask id=\"{id}\" mask-type=\"alpha\">{svg_string}</mask>\"##).unwrap(),\n\t\t}\n\t}\n}\n\n/// Mutable state used whilst rendering to an SVG\npub struct SvgRender {\n\tpub svg: Vec<SvgSegment>,\n\tpub svg_defs: String,\n\tpub transform: DAffine2,\n\tpub image_data: HashMap<Image<Color>, u64>,\n\tindent: usize,\n}\n\nimpl SvgRender {\n\tpub fn new() -> Self {\n\t\tSelf {\n\t\t\tsvg: Vec::default(),\n\t\t\tsvg_defs: String::new(),\n\t\t\ttransform: DAffine2::IDENTITY,\n\t\t\timage_data: HashMap::new(),\n\t\t\tindent: 0,\n\t\t}\n\t}\n\n\tpub fn indent(&mut self) {\n\t\tself.svg.push(\"\\n\".into());\n\t\tself.svg.push(\"\\t\".repeat(self.indent).into());\n\t}\n\n\t/// Add an outer `<svg>...</svg>` tag with a `viewBox` and the `<defs />`\n\tpub fn format_svg(&mut self, bounds_min: DVec2, bounds_max: DVec2) {\n\t\tlet (x, y) = bounds_min.into();\n\t\tlet (size_x, size_y) = (bounds_max - bounds_min).into();\n\t\tlet defs = &self.svg_defs;\n\t\tlet svg_header = format!(r#\"<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:graphite=\"https://graphite.art\" viewBox=\"{x} {y} {size_x} {size_y}\"><defs>{defs}</defs>\"#,);\n\t\tself.svg.insert(0, svg_header.into());\n\t\tself.svg.push(\"</svg>\".into());\n\t}\n\n\t/// Wraps the SVG with `<svg><g transform=\"...\">...</g></svg>`, which allows for rotation\n\tpub fn wrap_with_transform(&mut self, transform: DAffine2, size: Option<DVec2>) {\n\t\tlet defs = &self.svg_defs;\n\t\tlet view_box = size\n\t\t\t.map(|size| format!(\"viewBox=\\\"0 0 {} {}\\\" width=\\\"{}\\\" height=\\\"{}\\\"\", size.x, size.y, size.x, size.y))\n\t\t\t.unwrap_or_default();\n\n\t\tlet matrix = format_transform_matrix(transform);\n\t\tlet transform = if matrix.is_empty() { String::new() } else { format!(r#\" transform=\"{matrix}\"\"#) };\n\n\t\tlet svg_header = format!(r#\"<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:graphite=\"https://graphite.art\" {view_box}><defs>{defs}</defs><g{transform}>\"#);\n\t\tself.svg.insert(0, svg_header.into());\n\t\tself.svg.push(\"</g></svg>\".into());\n\t}\n\n\tpub fn leaf_tag(&mut self, name: impl Into<SvgSegment>, attributes: impl FnOnce(&mut SvgRenderAttrs)) {\n\t\tself.indent();\n\n\t\tself.svg.push(\"<\".into());\n\t\tself.svg.push(name.into());\n\n\t\tattributes(&mut SvgRenderAttrs(self));\n\n\t\tself.svg.push(\"/>\".into());\n\t}\n\n\tpub fn leaf_node(&mut self, content: impl Into<SvgSegment>) {\n\t\tself.indent();\n\t\tself.svg.push(content.into());\n\t}\n\n\tpub fn parent_tag(&mut self, name: impl Into<SvgSegment>, attributes: impl FnOnce(&mut SvgRenderAttrs), inner: impl FnOnce(&mut Self)) {\n\t\tlet name = name.into();\n\t\tself.indent();\n\t\tself.svg.push(\"<\".into());\n\t\tself.svg.push(name.clone());\n\t\t// Wraps `self` in a newtype (1-tuple) which is then mutated by the `attributes` closure\n\t\tattributes(&mut SvgRenderAttrs(self));\n\t\tself.svg.push(\">\".into());\n\t\tlet length = self.svg.len();\n\t\tself.indent += 1;\n\t\tinner(self);\n\t\tself.indent -= 1;\n\t\tif self.svg.len() != length {\n\t\t\tself.indent();\n\t\t\tself.svg.push(\"</\".into());\n\t\t\tself.svg.push(name);\n\t\t\tself.svg.push(\">\".into());\n\t\t} else {\n\t\t\tself.svg.pop();\n\t\t\tself.svg.push(\"/>\".into());\n\t\t}\n\t}\n}\n\nimpl Default for SvgRender {\n\tfn default() -> Self {\n\t\tSelf::new()\n\t}\n}\n\n#[derive(Clone, Debug, Default)]\npub struct RenderContext {\n\tpub resource_overrides: Vec<(peniko::ImageBrush, wgpu::Texture)>,\n}\n\n#[derive(Default, Clone, Copy, Hash)]\npub enum RenderOutputType {\n\t#[default]\n\tSvg,\n\tVello,\n}\n\n/// Static state used whilst rendering\n#[derive(Default, Clone)]\npub struct RenderParams {\n\tpub render_mode: RenderMode,\n\tpub footprint: Footprint,\n\t/// Ratio of physical pixels to logical pixels. `scale := physical_pixels / logical_pixels`\n\t/// Ignored when rendering to SVG.\n\tpub scale: f64,\n\tpub render_output_type: RenderOutputType,\n\tpub thumbnail: bool,\n\t/// Don't render the rectangle for an artboard to allow exporting with a transparent background.\n\tpub hide_artboards: bool,\n\t/// Are we exporting\n\tpub for_export: bool,\n\t/// Are we generating a mask in this render pass? Used to see if fill should be multiplied with alpha.\n\tpub for_mask: bool,\n\t/// Are we generating a mask for alignment? Used to prevent unnecessary transforms in masks\n\tpub alignment_parent_transform: Option<DAffine2>,\n\tpub aligned_strokes: bool,\n\tpub override_paint_order: bool,\n\tpub artboard_background: Option<Color>,\n\t/// Viewport zoom level (document-space scale). Used to compute constant viewport-pixel stroke widths in Outline mode.\n\tpub viewport_zoom: f64,\n}\n\nimpl Hash for RenderParams {\n\tfn hash<H: Hasher>(&self, state: &mut H) {\n\t\tself.render_mode.hash(state);\n\t\tself.footprint.hash(state);\n\t\tself.render_output_type.hash(state);\n\t\tself.thumbnail.hash(state);\n\t\tself.hide_artboards.hash(state);\n\t\tself.for_export.hash(state);\n\t\tself.for_mask.hash(state);\n\t\tif let Some(x) = self.alignment_parent_transform {\n\t\t\tx.to_cols_array().iter().for_each(|x| x.to_bits().hash(state))\n\t\t}\n\t\tself.aligned_strokes.hash(state);\n\t\tself.override_paint_order.hash(state);\n\t\tself.artboard_background.hash(state);\n\t\tself.viewport_zoom.to_bits().hash(state);\n\t}\n}\n\nimpl RenderParams {\n\tpub fn for_clipper(&self) -> Self {\n\t\tSelf { for_mask: true, ..*self }\n\t}\n\n\tpub fn for_alignment(&self, transform: DAffine2) -> Self {\n\t\tlet alignment_parent_transform = Some(transform);\n\t\tSelf { alignment_parent_transform, ..*self }\n\t}\n\n\tpub fn to_canvas(&self) -> bool {\n\t\t!self.for_export && !self.thumbnail && !self.for_mask\n\t}\n}\n\npub fn format_transform_matrix(transform: DAffine2) -> String {\n\tif transform == DAffine2::IDENTITY {\n\t\treturn String::new();\n\t}\n\n\ttransform.to_cols_array().iter().enumerate().fold(\"matrix(\".to_string(), |val, (i, num)| {\n\t\tlet num = if num.abs() < 1_000_000_000. { (num * 1_000_000_000.).round() / 1_000_000_000. } else { *num };\n\t\tlet num = if num.is_zero() { \"0\".to_string() } else { num.to_string() };\n\t\tlet comma = if i == 5 { \"\" } else { \",\" };\n\t\tval + &(num + comma)\n\t}) + \")\"\n}\n\nfn max_scale(transform: DAffine2) -> f64 {\n\tlet sx = transform.x_axis.length_squared();\n\tlet sy = transform.y_axis.length_squared();\n\t(sx + sy).sqrt()\n}\n\npub fn black_or_white_for_best_contrast(background: Option<Color>) -> Color {\n\tlet Some(bg) = background else { return core_types::consts::LAYER_OUTLINE_STROKE_COLOR };\n\n\tlet alpha = bg.a();\n\n\t// Un-premultiply, then convert to gamma sRGB\n\tlet srgb = if alpha > f32::EPSILON {\n\t\tColor::from_rgbaf32_unchecked(bg.r() / alpha, bg.g() / alpha, bg.b() / alpha, alpha).to_gamma_srgb()\n\t} else {\n\t\tColor::TRANSPARENT\n\t};\n\n\t// Composite over black in sRGB space, then convert back to linear for luminance\n\tlet composited = Color::from_rgbaf32_unchecked(srgb.r() * alpha, srgb.g() * alpha, srgb.b() * alpha, 1.).to_linear_srgb();\n\n\tlet threshold = (1.05 * 0.05f32).sqrt() - 0.05;\n\n\tif composited.luminance_srgb() > threshold { Color::BLACK } else { Color::WHITE }\n}\n\npub fn to_transform(transform: DAffine2) -> usvg::Transform {\n\tlet cols = transform.to_cols_array();\n\tusvg::Transform::from_row(cols[0] as f32, cols[1] as f32, cols[2] as f32, cols[3] as f32, cols[4] as f32, cols[5] as f32)\n}\n\nfn get_outline_styles(render_params: &RenderParams) -> (kurbo::Stroke, peniko::Color) {\n\tuse core_types::consts::LAYER_OUTLINE_STROKE_WEIGHT;\n\n\tlet outline_stroke = kurbo::Stroke {\n\t\twidth: LAYER_OUTLINE_STROKE_WEIGHT / if render_params.viewport_zoom > 0. { render_params.viewport_zoom } else { 1. },\n\t\tmiter_limit: 4.,\n\t\tjoin: Join::Miter,\n\t\tstart_cap: Cap::Butt,\n\t\tend_cap: Cap::Butt,\n\t\tdash_pattern: Default::default(),\n\t\tdash_offset: 0.,\n\t};\n\n\tlet outline_color = black_or_white_for_best_contrast(render_params.artboard_background);\n\tlet outline_color_peniko = peniko::Color::new([outline_color.r(), outline_color.g(), outline_color.b(), outline_color.a()]);\n\n\t(outline_stroke, outline_color_peniko)\n}\n\nfn draw_raster_outline(scene: &mut Scene, outline_transform: &DAffine2, render_params: &RenderParams) {\n\tuse graphic_types::vector_types::vector::PointId;\n\n\tlet (outline_stroke, outline_color_peniko) = get_outline_styles(render_params);\n\n\tlet mut outline_path = Subpath::<PointId>::new_rectangle(DVec2::ZERO, DVec2::ONE).to_bezpath();\n\toutline_path.apply_affine(Affine::new(outline_transform.to_cols_array()));\n\n\tscene.stroke(&outline_stroke, Affine::IDENTITY, outline_color_peniko, None, &outline_path);\n}\n\n// TODO: Click targets can be removed from the render output, since the vector data is available in the vector modify data from Monitor nodes.\n// This will require that the transform for child layers into that layer space be calculated, or it could be returned from the RenderOutput instead of click targets.\n#[derive(Debug, Default, Clone, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]\npub struct RenderMetadata {\n\tpub upstream_footprints: HashMap<NodeId, Footprint>,\n\tpub local_transforms: HashMap<NodeId, DAffine2>,\n\tpub first_element_source_id: HashMap<NodeId, Option<NodeId>>,\n\tpub click_targets: HashMap<NodeId, Vec<Arc<ClickTarget>>>,\n\tpub clip_targets: HashSet<NodeId>,\n\tpub vector_data: HashMap<NodeId, Arc<Vector>>,\n}\n\nimpl RenderMetadata {\n\tpub fn apply_transform(&mut self, transform: DAffine2) {\n\t\tfor value in self.upstream_footprints.values_mut() {\n\t\t\tvalue.transform = transform * value.transform;\n\t\t}\n\t}\n\n\t/// Merge another RenderMetadata into this one.\n\t/// Values from `other` take precedence for duplicate keys.\n\tpub fn merge(&mut self, other: &RenderMetadata) {\n\t\t// Destructure Self to get errors when new fields are added to the struct\n\t\tlet RenderMetadata {\n\t\t\tupstream_footprints,\n\t\t\tlocal_transforms,\n\t\t\tfirst_element_source_id,\n\t\t\tclick_targets,\n\t\t\tclip_targets,\n\t\t\tvector_data,\n\t\t} = self;\n\t\tupstream_footprints.extend(other.upstream_footprints.iter());\n\t\tlocal_transforms.extend(other.local_transforms.iter());\n\t\tfirst_element_source_id.extend(other.first_element_source_id.iter());\n\t\tclick_targets.extend(other.click_targets.iter().map(|(k, v)| (*k, v.clone())));\n\t\tclip_targets.extend(other.clip_targets.iter());\n\t\tvector_data.extend(other.vector_data.iter().map(|(id, data)| (*id, data.clone())));\n\t}\n}\n\n// TODO: Rename to \"Graphical\"\npub trait Render: BoundingBox + RenderComplexity {\n\tfn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams);\n\n\tfn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, _render_params: &RenderParams);\n\n\t/// The upstream click targets for each layer are collected during the render so that they do not have to be calculated for each click detection.\n\tfn add_upstream_click_targets(&self, _click_targets: &mut Vec<ClickTarget>) {}\n\n\t// TODO: Store all click targets in a vec which contains the AABB, click target, and path\n\t// fn add_click_targets(&self, click_targets: &mut Vec<([DVec2; 2], ClickTarget, Vec<NodeId>)>, current_path: Option<NodeId>) {}\n\n\t/// Recursively iterate over data in the render (including nested layer stacks upstream of a vector node, in the case of a boolean operation) to collect the footprints, click targets, and vector modify.\n\tfn collect_metadata(&self, _metadata: &mut RenderMetadata, _footprint: Footprint, _element_id: Option<NodeId>) {}\n\n\tfn contains_artboard(&self) -> bool {\n\t\tfalse\n\t}\n\n\tfn new_ids_from_hash(&mut self, _reference: Option<NodeId>) {}\n}\n\nimpl Render for Graphic {\n\tfn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {\n\t\tmatch self {\n\t\t\tGraphic::Graphic(table) => table.render_svg(render, render_params),\n\t\t\tGraphic::Vector(table) => table.render_svg(render, render_params),\n\t\t\tGraphic::RasterCPU(table) => table.render_svg(render, render_params),\n\t\t\tGraphic::RasterGPU(_) => (),\n\t\t\tGraphic::Color(table) => table.render_svg(render, render_params),\n\t\t\tGraphic::Gradient(table) => table.render_svg(render, render_params),\n\t\t}\n\t}\n\n\tfn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {\n\t\tmatch self {\n\t\t\tGraphic::Graphic(table) => table.render_to_vello(scene, transform, context, render_params),\n\t\t\tGraphic::Vector(table) => table.render_to_vello(scene, transform, context, render_params),\n\t\t\tGraphic::RasterCPU(table) => table.render_to_vello(scene, transform, context, render_params),\n\t\t\tGraphic::RasterGPU(table) => table.render_to_vello(scene, transform, context, render_params),\n\t\t\tGraphic::Color(table) => table.render_to_vello(scene, transform, context, render_params),\n\t\t\tGraphic::Gradient(table) => table.render_to_vello(scene, transform, context, render_params),\n\t\t}\n\t}\n\n\tfn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {\n\t\tif let Some(element_id) = element_id {\n\t\t\tmatch self {\n\t\t\t\tGraphic::Graphic(_) => {\n\t\t\t\t\tmetadata.upstream_footprints.insert(element_id, footprint);\n\t\t\t\t}\n\t\t\t\tGraphic::Vector(table) => {\n\t\t\t\t\tmetadata.upstream_footprints.insert(element_id, footprint);\n\t\t\t\t\t// TODO: Find a way to handle more than the first row\n\t\t\t\t\tif let Some(row) = table.iter().next() {\n\t\t\t\t\t\tmetadata.first_element_source_id.insert(element_id, *row.source_node_id);\n\t\t\t\t\t\tmetadata.local_transforms.insert(element_id, *row.transform);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tGraphic::RasterCPU(table) => {\n\t\t\t\t\tmetadata.upstream_footprints.insert(element_id, footprint);\n\n\t\t\t\t\t// TODO: Find a way to handle more than the first row\n\t\t\t\t\tif let Some(row) = table.iter().next() {\n\t\t\t\t\t\tmetadata.local_transforms.insert(element_id, *row.transform);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tGraphic::RasterGPU(table) => {\n\t\t\t\t\tmetadata.upstream_footprints.insert(element_id, footprint);\n\n\t\t\t\t\t// TODO: Find a way to handle more than the first row\n\t\t\t\t\tif let Some(row) = table.iter().next() {\n\t\t\t\t\t\tmetadata.local_transforms.insert(element_id, *row.transform);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tGraphic::Color(table) => {\n\t\t\t\t\tmetadata.upstream_footprints.insert(element_id, footprint);\n\n\t\t\t\t\t// TODO: Find a way to handle more than the first row\n\t\t\t\t\tif let Some(row) = table.iter().next() {\n\t\t\t\t\t\tmetadata.local_transforms.insert(element_id, *row.transform);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tGraphic::Gradient(table) => {\n\t\t\t\t\tmetadata.upstream_footprints.insert(element_id, footprint);\n\n\t\t\t\t\t// TODO: Find a way to handle more than the first row\n\t\t\t\t\tif let Some(row) = table.iter().next() {\n\t\t\t\t\t\tmetadata.local_transforms.insert(element_id, *row.transform);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tmatch self {\n\t\t\tGraphic::Graphic(table) => table.collect_metadata(metadata, footprint, element_id),\n\t\t\tGraphic::Vector(table) => table.collect_metadata(metadata, footprint, element_id),\n\t\t\tGraphic::RasterCPU(table) => table.collect_metadata(metadata, footprint, element_id),\n\t\t\tGraphic::RasterGPU(table) => table.collect_metadata(metadata, footprint, element_id),\n\t\t\tGraphic::Color(table) => table.collect_metadata(metadata, footprint, element_id),\n\t\t\tGraphic::Gradient(table) => table.collect_metadata(metadata, footprint, element_id),\n\t\t}\n\t}\n\n\tfn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {\n\t\tmatch self {\n\t\t\tGraphic::Graphic(table) => table.add_upstream_click_targets(click_targets),\n\t\t\tGraphic::Vector(table) => table.add_upstream_click_targets(click_targets),\n\t\t\tGraphic::RasterCPU(table) => table.add_upstream_click_targets(click_targets),\n\t\t\tGraphic::RasterGPU(table) => table.add_upstream_click_targets(click_targets),\n\t\t\tGraphic::Color(table) => table.add_upstream_click_targets(click_targets),\n\t\t\tGraphic::Gradient(table) => table.add_upstream_click_targets(click_targets),\n\t\t}\n\t}\n\n\tfn contains_artboard(&self) -> bool {\n\t\tmatch self {\n\t\t\tGraphic::Graphic(table) => table.contains_artboard(),\n\t\t\tGraphic::Vector(table) => table.contains_artboard(),\n\t\t\tGraphic::RasterCPU(table) => table.contains_artboard(),\n\t\t\tGraphic::RasterGPU(table) => table.contains_artboard(),\n\t\t\tGraphic::Color(table) => table.contains_artboard(),\n\t\t\tGraphic::Gradient(table) => table.contains_artboard(),\n\t\t}\n\t}\n\n\tfn new_ids_from_hash(&mut self, reference: Option<NodeId>) {\n\t\tmatch self {\n\t\t\tGraphic::Graphic(table) => table.new_ids_from_hash(reference),\n\t\t\tGraphic::Vector(table) => table.new_ids_from_hash(reference),\n\t\t\tGraphic::RasterCPU(_) => (),\n\t\t\tGraphic::RasterGPU(_) => (),\n\t\t\tGraphic::Color(_) => (),\n\t\t\tGraphic::Gradient(_) => (),\n\t\t}\n\t}\n}\n\nimpl Render for Artboard {\n\tfn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {\n\t\t// Rectangle for the artboard\n\t\tif !render_params.hide_artboards {\n\t\t\t// Background\n\t\t\trender.leaf_tag(\"rect\", |attributes| {\n\t\t\t\tattributes.push(\"fill\", format!(\"#{}\", self.background.to_rgb_hex_srgb_from_gamma()));\n\t\t\t\tif self.background.a() < 1. {\n\t\t\t\t\tattributes.push(\"fill-opacity\", ((self.background.a() * 1000.).round() / 1000.).to_string());\n\t\t\t\t}\n\t\t\t\tattributes.push(\"x\", self.location.x.min(self.location.x + self.dimensions.x).to_string());\n\t\t\t\tattributes.push(\"y\", self.location.y.min(self.location.y + self.dimensions.y).to_string());\n\t\t\t\tattributes.push(\"width\", self.dimensions.x.abs().to_string());\n\t\t\t\tattributes.push(\"height\", self.dimensions.y.abs().to_string());\n\t\t\t});\n\t\t}\n\n\t\t// Artwork\n\t\trender.parent_tag(\n\t\t\t// SVG group tag\n\t\t\t\"g\",\n\t\t\t// Group tag attributes\n\t\t\t|attributes| {\n\t\t\t\tlet matrix = format_transform_matrix(self.transform());\n\t\t\t\tif !matrix.is_empty() {\n\t\t\t\t\tattributes.push(\"transform\", matrix);\n\t\t\t\t}\n\n\t\t\t\tif self.clip {\n\t\t\t\t\tlet id = format!(\"artboard-{}\", generate_uuid());\n\t\t\t\t\tlet selector = format!(\"url(#{id})\");\n\n\t\t\t\t\twrite!(\n\t\t\t\t\t\t&mut attributes.0.svg_defs,\n\t\t\t\t\t\tr##\"<clipPath id=\"{id}\"><rect x=\"0\" y=\"0\" width=\"{}\" height=\"{}\"/></clipPath>\"##,\n\t\t\t\t\t\tself.dimensions.x, self.dimensions.y,\n\t\t\t\t\t)\n\t\t\t\t\t.unwrap();\n\t\t\t\t\tattributes.push(\"clip-path\", selector);\n\t\t\t\t}\n\t\t\t},\n\t\t\t// Artwork content\n\t\t\t|render| {\n\t\t\t\tlet mut render_params = render_params.clone();\n\t\t\t\trender_params.artboard_background = Some(self.background);\n\t\t\t\tself.content.render_svg(render, &render_params);\n\t\t\t},\n\t\t);\n\t}\n\n\tfn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {\n\t\tuse vello::peniko;\n\n\t\t// Render background\n\t\tlet color = peniko::Color::new([self.background.r(), self.background.g(), self.background.b(), self.background.a()]);\n\t\tlet [a, b] = [self.location.as_dvec2(), self.location.as_dvec2() + self.dimensions.as_dvec2()];\n\t\tlet rect = kurbo::Rect::new(a.x.min(b.x), a.y.min(b.y), a.x.max(b.x), a.y.max(b.y));\n\n\t\tscene.push_layer(peniko::Fill::NonZero, peniko::Mix::Normal, 1., kurbo::Affine::new(transform.to_cols_array()), &rect);\n\t\tscene.fill(peniko::Fill::NonZero, kurbo::Affine::new(transform.to_cols_array()), color, None, &rect);\n\t\tscene.pop_layer();\n\n\t\tif self.clip {\n\t\t\tscene.push_clip_layer(peniko::Fill::NonZero, kurbo::Affine::new(transform.to_cols_array()), &rect);\n\t\t}\n\t\t// Since the content's transform is right multiplied in when rendering the content, we just need to right multiply by the artboard offset here.\n\t\tlet child_transform = transform * DAffine2::from_translation(self.location.as_dvec2());\n\t\tlet mut render_params = render_params.clone();\n\t\trender_params.artboard_background = Some(self.background);\n\t\tself.content.render_to_vello(scene, child_transform, context, &render_params);\n\t\tif self.clip {\n\t\t\tscene.pop_layer();\n\t\t}\n\t}\n\n\tfn collect_metadata(&self, metadata: &mut RenderMetadata, mut footprint: Footprint, element_id: Option<NodeId>) {\n\t\tif let Some(element_id) = element_id {\n\t\t\tlet subpath = Subpath::new_rectangle(DVec2::ZERO, self.dimensions.as_dvec2());\n\t\t\tmetadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.).into()]);\n\t\t\tmetadata.upstream_footprints.insert(element_id, footprint);\n\t\t\tmetadata.local_transforms.insert(element_id, DAffine2::from_translation(self.location.as_dvec2()));\n\t\t\tif self.clip {\n\t\t\t\tmetadata.clip_targets.insert(element_id);\n\t\t\t}\n\t\t}\n\t\tfootprint.transform *= self.transform();\n\t\tself.content.collect_metadata(metadata, footprint, None);\n\t}\n\n\tfn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {\n\t\tlet subpath_rectangle = Subpath::new_rectangle(DVec2::ZERO, self.dimensions.as_dvec2());\n\t\tclick_targets.push(ClickTarget::new_with_subpath(subpath_rectangle, 0.));\n\t}\n\n\tfn contains_artboard(&self) -> bool {\n\t\ttrue\n\t}\n}\n\nimpl Render for Table<Artboard> {\n\tfn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {\n\t\tfor artboard in self.iter() {\n\t\t\tartboard.element.render_svg(render, render_params);\n\t\t}\n\t}\n\n\tfn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {\n\t\tfor row in self.iter() {\n\t\t\trow.element.render_to_vello(scene, transform, context, render_params);\n\t\t}\n\t}\n\n\tfn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, _element_id: Option<NodeId>) {\n\t\tfor row in self.iter() {\n\t\t\trow.element.collect_metadata(metadata, footprint, *row.source_node_id);\n\t\t}\n\t}\n\n\tfn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {\n\t\tfor row in self.iter() {\n\t\t\trow.element.add_upstream_click_targets(click_targets);\n\t\t}\n\t}\n\n\tfn contains_artboard(&self) -> bool {\n\t\tself.iter().count() > 0\n\t}\n}\n\nimpl Render for Table<Graphic> {\n\tfn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {\n\t\tlet mut iter = self.iter().peekable();\n\t\tlet mut mask_state = None;\n\n\t\twhile let Some(row) = iter.next() {\n\t\t\trender.parent_tag(\n\t\t\t\t\"g\",\n\t\t\t\t|attributes| {\n\t\t\t\t\tlet matrix = format_transform_matrix(*row.transform);\n\t\t\t\t\tif !matrix.is_empty() {\n\t\t\t\t\t\tattributes.push(\"transform\", matrix);\n\t\t\t\t\t}\n\n\t\t\t\t\tlet opacity = row.alpha_blending.opacity(render_params.for_mask);\n\t\t\t\t\tif opacity < 1. {\n\t\t\t\t\t\tattributes.push(\"opacity\", opacity.to_string());\n\t\t\t\t\t}\n\n\t\t\t\t\tif row.alpha_blending.blend_mode != BlendMode::default() {\n\t\t\t\t\t\tattributes.push(\"style\", row.alpha_blending.blend_mode.render());\n\t\t\t\t\t}\n\n\t\t\t\t\tlet next_clips = iter.peek().is_some_and(|next_row| next_row.element.had_clip_enabled());\n\n\t\t\t\t\tif next_clips && mask_state.is_none() {\n\t\t\t\t\t\tlet uuid = generate_uuid();\n\t\t\t\t\t\tlet mask_type = if row.element.can_reduce_to_clip_path() { MaskType::Clip } else { MaskType::Mask };\n\t\t\t\t\t\tmask_state = Some((uuid, mask_type));\n\t\t\t\t\t\tlet mut svg = SvgRender::new();\n\t\t\t\t\t\trow.element.render_svg(&mut svg, &render_params.for_clipper());\n\n\t\t\t\t\t\twrite!(&mut attributes.0.svg_defs, r##\"{}\"##, svg.svg_defs).unwrap();\n\t\t\t\t\t\tmask_type.write_to_defs(&mut attributes.0.svg_defs, uuid, svg.svg.to_svg_string());\n\t\t\t\t\t} else if let Some((uuid, mask_type)) = mask_state {\n\t\t\t\t\t\tif !next_clips {\n\t\t\t\t\t\t\tmask_state = None;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlet id = format!(\"mask-{uuid}\");\n\t\t\t\t\t\tlet selector = format!(\"url(#{id})\");\n\n\t\t\t\t\t\tattributes.push(mask_type.to_attribute(), selector);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t|render| {\n\t\t\t\t\trow.element.render_svg(render, render_params);\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t}\n\n\tfn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {\n\t\tlet mut iter = self.iter().peekable();\n\t\tlet mut mask_element_and_transform = None;\n\n\t\twhile let Some(row) = iter.next() {\n\t\t\tlet transform = transform * *row.transform;\n\t\t\tlet alpha_blending = *row.alpha_blending;\n\n\t\t\tlet mut layer = false;\n\n\t\t\tlet blend_mode = match render_params.render_mode {\n\t\t\t\tRenderMode::Outline => peniko::Mix::Normal,\n\t\t\t\t_ => alpha_blending.blend_mode.to_peniko(),\n\t\t\t};\n\t\t\tlet mut bounds = RenderBoundingBox::None;\n\n\t\t\tlet opacity = row.alpha_blending.opacity(render_params.for_mask);\n\t\t\tif opacity < 1. || (render_params.render_mode != RenderMode::Outline && alpha_blending.blend_mode != BlendMode::default()) {\n\t\t\t\tbounds = row.element.bounding_box(transform, true);\n\n\t\t\t\tif let RenderBoundingBox::Rectangle(bounds) = bounds {\n\t\t\t\t\tscene.push_layer(\n\t\t\t\t\t\tpeniko::Fill::NonZero,\n\t\t\t\t\t\tpeniko::BlendMode::new(blend_mode, peniko::Compose::SrcOver),\n\t\t\t\t\t\topacity,\n\t\t\t\t\t\tkurbo::Affine::IDENTITY,\n\t\t\t\t\t\t&kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y),\n\t\t\t\t\t);\n\t\t\t\t\tlayer = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet next_clips = iter.peek().is_some_and(|next_row| next_row.element.had_clip_enabled());\n\t\t\tif next_clips && mask_element_and_transform.is_none() {\n\t\t\t\tmask_element_and_transform = Some((row.element, transform));\n\n\t\t\t\trow.element.render_to_vello(scene, transform, context, render_params);\n\t\t\t} else if let Some((mask_element, transform_mask)) = mask_element_and_transform {\n\t\t\t\tif !next_clips {\n\t\t\t\t\tmask_element_and_transform = None;\n\t\t\t\t}\n\t\t\t\tif !layer {\n\t\t\t\t\tbounds = row.element.bounding_box(transform, true);\n\t\t\t\t}\n\n\t\t\t\tif let RenderBoundingBox::Rectangle(bounds) = bounds {\n\t\t\t\t\tlet rect = kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y);\n\n\t\t\t\t\tscene.push_layer(peniko::Fill::NonZero, peniko::Mix::Normal, 1., kurbo::Affine::IDENTITY, &rect);\n\t\t\t\t\tmask_element.render_to_vello(scene, transform_mask, context, &render_params.for_clipper());\n\t\t\t\t\tscene.push_layer(\n\t\t\t\t\t\tpeniko::Fill::NonZero,\n\t\t\t\t\t\tpeniko::BlendMode::new(peniko::Mix::Normal, peniko::Compose::SrcIn),\n\t\t\t\t\t\t1.,\n\t\t\t\t\t\tkurbo::Affine::IDENTITY,\n\t\t\t\t\t\t&rect,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\trow.element.render_to_vello(scene, transform, context, render_params);\n\n\t\t\t\tif matches!(bounds, RenderBoundingBox::Rectangle(_)) {\n\t\t\t\t\tscene.pop_layer();\n\t\t\t\t\tscene.pop_layer();\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\trow.element.render_to_vello(scene, transform, context, render_params);\n\t\t\t}\n\n\t\t\tif layer {\n\t\t\t\tscene.pop_layer();\n\t\t\t}\n\t\t}\n\t}\n\n\tfn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {\n\t\tfor row in self.iter() {\n\t\t\tif let Some(element_id) = row.source_node_id {\n\t\t\t\tlet mut footprint = footprint;\n\t\t\t\tfootprint.transform *= *row.transform;\n\n\t\t\t\trow.element.collect_metadata(metadata, footprint, Some(*element_id));\n\t\t\t}\n\t\t}\n\n\t\tif let Some(element_id) = element_id {\n\t\t\tlet mut all_upstream_click_targets = Vec::new();\n\n\t\t\tfor row in self.iter() {\n\t\t\t\tlet mut new_click_targets = Vec::new();\n\t\t\t\trow.element.add_upstream_click_targets(&mut new_click_targets);\n\n\t\t\t\tfor click_target in new_click_targets.iter_mut() {\n\t\t\t\t\tclick_target.apply_transform(*row.transform)\n\t\t\t\t}\n\n\t\t\t\tall_upstream_click_targets.extend(new_click_targets);\n\t\t\t}\n\n\t\t\tmetadata.click_targets.insert(element_id, all_upstream_click_targets.into_iter().map(|x| x.into()).collect());\n\t\t}\n\t}\n\n\tfn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {\n\t\tfor row in self.iter() {\n\t\t\tlet mut new_click_targets = Vec::new();\n\n\t\t\trow.element.add_upstream_click_targets(&mut new_click_targets);\n\n\t\t\tfor click_target in new_click_targets.iter_mut() {\n\t\t\t\tclick_target.apply_transform(*row.transform)\n\t\t\t}\n\n\t\t\tclick_targets.extend(new_click_targets);\n\t\t}\n\t}\n\n\tfn contains_artboard(&self) -> bool {\n\t\tself.iter().any(|row| row.element.contains_artboard())\n\t}\n\n\tfn new_ids_from_hash(&mut self, _reference: Option<NodeId>) {\n\t\tfor row in self.iter_mut() {\n\t\t\trow.element.new_ids_from_hash(*row.source_node_id);\n\t\t}\n\t}\n}\n\nimpl Render for Table<Vector> {\n\tfn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {\n\t\tfor row in self.iter() {\n\t\t\tlet multiplied_transform = *row.transform;\n\t\t\tlet vector = &row.element;\n\t\t\t// Only consider strokes with non-zero weight, since default strokes with zero weight would prevent assigning the correct stroke transform\n\t\t\tlet has_real_stroke = vector.style.stroke().filter(|stroke| stroke.weight() > 0.);\n\t\t\tlet set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.);\n\t\t\tlet applied_stroke_transform = set_stroke_transform.unwrap_or(*row.transform);\n\t\t\tlet applied_stroke_transform = render_params.alignment_parent_transform.unwrap_or(applied_stroke_transform);\n\t\t\tlet element_transform = set_stroke_transform.map(|stroke_transform| multiplied_transform * stroke_transform.inverse());\n\t\t\tlet element_transform = element_transform.unwrap_or(DAffine2::IDENTITY);\n\t\t\tlet layer_bounds = vector.bounding_box().unwrap_or_default();\n\t\t\tlet transformed_bounds = vector.bounding_box_with_transform(applied_stroke_transform).unwrap_or_default();\n\n\t\t\tlet bounds_matrix = DAffine2::from_scale_angle_translation(layer_bounds[1] - layer_bounds[0], 0., layer_bounds[0]);\n\t\t\tlet transformed_bounds_matrix = element_transform * DAffine2::from_scale_angle_translation(transformed_bounds[1] - transformed_bounds[0], 0., transformed_bounds[0]);\n\n\t\t\tlet mut path = String::new();\n\n\t\t\tfor mut bezpath in row.element.stroke_bezpath_iter() {\n\t\t\t\tbezpath.apply_affine(Affine::new(applied_stroke_transform.to_cols_array()));\n\t\t\t\tpath.push_str(bezpath.to_svg().as_str());\n\t\t\t}\n\n\t\t\tlet mask_type = if vector.style.stroke().map(|x| x.align) == Some(StrokeAlign::Inside) {\n\t\t\t\tMaskType::Clip\n\t\t\t} else {\n\t\t\t\tMaskType::Mask\n\t\t\t};\n\n\t\t\tlet path_is_closed = vector.stroke_bezier_paths().all(|path| path.closed());\n\t\t\tlet can_draw_aligned_stroke = path_is_closed && vector.style.stroke().is_some_and(|stroke| stroke.has_renderable_stroke() && stroke.align.is_not_centered());\n\t\t\tlet can_use_paint_order = !(row.element.style.fill().is_none() || !row.element.style.fill().is_opaque() || mask_type == MaskType::Clip);\n\n\t\t\tlet needs_separate_alignment_fill = can_draw_aligned_stroke && !can_use_paint_order;\n\t\t\tlet wants_stroke_below = vector.style.stroke().map(|s| s.paint_order) == Some(PaintOrder::StrokeBelow);\n\n\t\t\tif needs_separate_alignment_fill && !wants_stroke_below {\n\t\t\t\trender.leaf_tag(\"path\", |attributes| {\n\t\t\t\t\tattributes.push(\"d\", path.clone());\n\t\t\t\t\tlet matrix = format_transform_matrix(element_transform);\n\t\t\t\t\tif !matrix.is_empty() {\n\t\t\t\t\t\tattributes.push(\"transform\", matrix);\n\t\t\t\t\t}\n\t\t\t\t\tlet mut style = row.element.style.clone();\n\t\t\t\t\tstyle.clear_stroke();\n\t\t\t\t\tlet fill_and_stroke = style.render(\n\t\t\t\t\t\t&mut attributes.0.svg_defs,\n\t\t\t\t\t\telement_transform,\n\t\t\t\t\t\tapplied_stroke_transform,\n\t\t\t\t\t\tbounds_matrix,\n\t\t\t\t\t\ttransformed_bounds_matrix,\n\t\t\t\t\t\trender_params,\n\t\t\t\t\t);\n\t\t\t\t\tattributes.push_val(fill_and_stroke);\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tlet push_id = needs_separate_alignment_fill.then_some({\n\t\t\t\tlet id = format!(\"alignment-{}\", generate_uuid());\n\n\t\t\t\tlet mut element = row.element.clone();\n\t\t\t\telement.style.clear_stroke();\n\t\t\t\telement.style.set_fill(Fill::solid(Color::BLACK));\n\n\t\t\t\tlet vector_row = Table::new_from_row(TableRow {\n\t\t\t\t\telement,\n\t\t\t\t\talpha_blending: *row.alpha_blending,\n\t\t\t\t\ttransform: *row.transform,\n\t\t\t\t\tsource_node_id: None,\n\t\t\t\t});\n\n\t\t\t\t(id, mask_type, vector_row)\n\t\t\t});\n\n\t\t\tlet use_face_fill = vector.use_face_fill();\n\t\t\tif use_face_fill {\n\t\t\t\tfor mut face_path in vector.construct_faces().filter(|face| face.area() >= 0.) {\n\t\t\t\t\tface_path.apply_affine(Affine::new(applied_stroke_transform.to_cols_array()));\n\n\t\t\t\t\tlet face_d = face_path.to_svg();\n\t\t\t\t\trender.leaf_tag(\"path\", |attributes| {\n\t\t\t\t\t\tattributes.push(\"d\", face_d.clone());\n\t\t\t\t\t\tlet matrix = format_transform_matrix(element_transform);\n\t\t\t\t\t\tif !matrix.is_empty() {\n\t\t\t\t\t\t\tattributes.push(\"transform\", matrix);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlet mut style = row.element.style.clone();\n\t\t\t\t\t\tstyle.clear_stroke();\n\t\t\t\t\t\tlet fill_only = style.render(\n\t\t\t\t\t\t\t&mut attributes.0.svg_defs,\n\t\t\t\t\t\t\telement_transform,\n\t\t\t\t\t\t\tapplied_stroke_transform,\n\t\t\t\t\t\t\tbounds_matrix,\n\t\t\t\t\t\t\ttransformed_bounds_matrix,\n\t\t\t\t\t\t\trender_params,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tattributes.push_val(fill_only);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\trender.leaf_tag(\"path\", |attributes| {\n\t\t\t\tattributes.push(\"d\", path.clone());\n\t\t\t\tlet matrix = format_transform_matrix(element_transform);\n\t\t\t\tif !matrix.is_empty() {\n\t\t\t\t\tattributes.push(\"transform\", matrix);\n\t\t\t\t}\n\n\t\t\t\tlet defs = &mut attributes.0.svg_defs;\n\t\t\t\tif let Some((ref id, mask_type, ref vector_row)) = push_id {\n\t\t\t\t\tlet mut svg = SvgRender::new();\n\t\t\t\t\tvector_row.render_svg(&mut svg, &render_params.for_alignment(applied_stroke_transform));\n\t\t\t\t\tlet stroke = row.element.style.stroke().unwrap();\n\t\t\t\t\tlet weight = stroke.effective_width() * max_scale(applied_stroke_transform);\n\t\t\t\t\tlet quad = Quad::from_box(transformed_bounds).inflate(weight);\n\t\t\t\t\tlet (x, y) = quad.top_left().into();\n\t\t\t\t\tlet (width, height) = (quad.bottom_right() - quad.top_left()).into();\n\n\t\t\t\t\twrite!(defs, r##\"{}\"##, svg.svg_defs).unwrap();\n\t\t\t\t\tlet rect = format!(r##\"<rect x=\"{x}\" y=\"{y}\" width=\"{width}\" height=\"{height}\" fill=\"white\" />\"##);\n\n\t\t\t\t\tmatch mask_type {\n\t\t\t\t\t\tMaskType::Clip => write!(defs, r##\"<clipPath id=\"{id}\">{}</clipPath>\"##, svg.svg.to_svg_string()).unwrap(),\n\t\t\t\t\t\tMaskType::Mask => write!(\n\t\t\t\t\t\t\tdefs,\n\t\t\t\t\t\t\tr##\"<mask id=\"{id}\" maskUnits=\"userSpaceOnUse\" maskContentUnits=\"userSpaceOnUse\" x=\"{x}\" y=\"{y}\" width=\"{width}\" height=\"{height}\">{}{}</mask>\"##,\n\t\t\t\t\t\t\trect,\n\t\t\t\t\t\t\tsvg.svg.to_svg_string()\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.unwrap(),\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tlet mut render_params = render_params.clone();\n\t\t\t\trender_params.aligned_strokes = can_draw_aligned_stroke;\n\t\t\t\trender_params.override_paint_order = can_draw_aligned_stroke && can_use_paint_order;\n\n\t\t\t\tlet mut style = row.element.style.clone();\n\t\t\t\tif needs_separate_alignment_fill || use_face_fill {\n\t\t\t\t\tstyle.clear_fill();\n\t\t\t\t}\n\n\t\t\t\tlet fill_and_stroke = style.render(defs, element_transform, applied_stroke_transform, bounds_matrix, transformed_bounds_matrix, &render_params);\n\n\t\t\t\tif let Some((id, mask_type, _)) = push_id {\n\t\t\t\t\tlet selector = format!(\"url(#{id})\");\n\t\t\t\t\tattributes.push(mask_type.to_attribute(), selector);\n\t\t\t\t}\n\t\t\t\tattributes.push_val(fill_and_stroke);\n\n\t\t\t\tif vector.is_branching() && !use_face_fill {\n\t\t\t\t\tattributes.push(\"fill-rule\", \"evenodd\");\n\t\t\t\t}\n\n\t\t\t\tlet opacity = row.alpha_blending.opacity(render_params.for_mask);\n\t\t\t\tif opacity < 1. {\n\t\t\t\t\tattributes.push(\"opacity\", opacity.to_string());\n\t\t\t\t}\n\n\t\t\t\tif row.alpha_blending.blend_mode != BlendMode::default() {\n\t\t\t\t\tattributes.push(\"style\", row.alpha_blending.blend_mode.render());\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// When splitting passes and stroke is below, draw the fill after the stroke.\n\t\t\tif needs_separate_alignment_fill && wants_stroke_below {\n\t\t\t\trender.leaf_tag(\"path\", |attributes| {\n\t\t\t\t\tattributes.push(\"d\", path);\n\t\t\t\t\tlet matrix = format_transform_matrix(element_transform);\n\t\t\t\t\tif !matrix.is_empty() {\n\t\t\t\t\t\tattributes.push(\"transform\", matrix);\n\t\t\t\t\t}\n\t\t\t\t\tlet mut style = row.element.style.clone();\n\t\t\t\t\tstyle.clear_stroke();\n\t\t\t\t\tlet fill_and_stroke = style.render(\n\t\t\t\t\t\t&mut attributes.0.svg_defs,\n\t\t\t\t\t\telement_transform,\n\t\t\t\t\t\tapplied_stroke_transform,\n\t\t\t\t\t\tbounds_matrix,\n\t\t\t\t\t\ttransformed_bounds_matrix,\n\t\t\t\t\t\trender_params,\n\t\t\t\t\t);\n\t\t\t\t\tattributes.push_val(fill_and_stroke);\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\tfn render_to_vello(&self, scene: &mut Scene, parent_transform: DAffine2, _context: &mut RenderContext, render_params: &RenderParams) {\n\t\tuse graphic_types::vector_types::vector::style::{GradientType, StrokeCap, StrokeJoin};\n\n\t\tfor row in self.iter() {\n\t\t\tuse graphic_types::vector_types::vector;\n\n\t\t\tlet multiplied_transform = parent_transform * *row.transform;\n\t\t\tlet has_real_stroke = row.element.style.stroke().filter(|stroke| stroke.weight() > 0.);\n\t\t\tlet set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.);\n\t\t\tlet mut applied_stroke_transform = set_stroke_transform.unwrap_or(multiplied_transform);\n\t\t\tlet mut element_transform = set_stroke_transform\n\t\t\t\t.map(|stroke_transform| multiplied_transform * stroke_transform.inverse())\n\t\t\t\t.unwrap_or(DAffine2::IDENTITY);\n\t\t\tif let Some(alignment_transform) = render_params.alignment_parent_transform {\n\t\t\t\tapplied_stroke_transform = alignment_transform;\n\t\t\t\telement_transform = if alignment_transform.matrix2.determinant() != 0. {\n\t\t\t\t\tmultiplied_transform * alignment_transform.inverse()\n\t\t\t\t} else {\n\t\t\t\t\tmultiplied_transform\n\t\t\t\t};\n\t\t\t}\n\t\t\tlet layer_bounds = row.element.bounding_box().unwrap_or_default();\n\n\t\t\tlet to_point = |p: DVec2| kurbo::Point::new(p.x, p.y);\n\t\t\tlet mut path = kurbo::BezPath::new();\n\t\t\tfor mut bezpath in row.element.stroke_bezpath_iter() {\n\t\t\t\tbezpath.apply_affine(Affine::new(applied_stroke_transform.to_cols_array()));\n\t\t\t\tfor element in bezpath {\n\t\t\t\t\tpath.push(element);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If we're using opacity or a blend mode, we need to push a layer\n\t\t\tlet blend_mode = match render_params.render_mode {\n\t\t\t\tRenderMode::Outline => peniko::Mix::Normal,\n\t\t\t\t_ => row.alpha_blending.blend_mode.to_peniko(),\n\t\t\t};\n\t\t\tlet mut layer = false;\n\n\t\t\tlet opacity = row.alpha_blending.opacity(render_params.for_mask);\n\t\t\tif opacity < 1. || row.alpha_blending.blend_mode != BlendMode::default() {\n\t\t\t\tlayer = true;\n\t\t\t\tlet weight = row.element.style.stroke().as_ref().map_or(0., Stroke::effective_width);\n\t\t\t\tlet quad = Quad::from_box(layer_bounds).inflate(weight * max_scale(applied_stroke_transform));\n\t\t\t\tlet layer_bounds = quad.bounding_box();\n\t\t\t\tscene.push_layer(\n\t\t\t\t\tpeniko::Fill::NonZero,\n\t\t\t\t\tpeniko::BlendMode::new(blend_mode, peniko::Compose::SrcOver),\n\t\t\t\t\topacity,\n\t\t\t\t\tkurbo::Affine::new(multiplied_transform.to_cols_array()),\n\t\t\t\t\t&kurbo::Rect::new(layer_bounds[0].x, layer_bounds[0].y, layer_bounds[1].x, layer_bounds[1].y),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tlet can_draw_aligned_stroke =\n\t\t\t\trow.element.style.stroke().is_some_and(|stroke| stroke.has_renderable_stroke() && stroke.align.is_not_centered()) && row.element.stroke_bezier_paths().all(|path| path.closed());\n\n\t\t\tlet use_layer = can_draw_aligned_stroke;\n\t\t\tlet wants_stroke_below = row.element.style.stroke().is_some_and(|s| s.paint_order == vector::style::PaintOrder::StrokeBelow);\n\n\t\t\t// Closures to avoid duplicated fill/stroke drawing logic\n\t\t\tlet do_fill_path = |scene: &mut Scene, path: &kurbo::BezPath, fill_rule: peniko::Fill| match row.element.style.fill() {\n\t\t\t\tFill::Solid(color) => {\n\t\t\t\t\tlet fill = peniko::Brush::Solid(peniko::Color::new([color.r(), color.g(), color.b(), color.a()]));\n\t\t\t\t\tscene.fill(fill_rule, kurbo::Affine::new(element_transform.to_cols_array()), &fill, None, path);\n\t\t\t\t}\n\t\t\t\tFill::Gradient(gradient) => {\n\t\t\t\t\tlet mut stops = peniko::ColorStops::new();\n\t\t\t\t\tfor (position, color, _) in gradient.stops.interpolated_samples() {\n\t\t\t\t\t\tstops.push(peniko::ColorStop {\n\t\t\t\t\t\t\toffset: position as f32,\n\t\t\t\t\t\t\tcolor: peniko::color::DynamicColor::from_alpha_color(peniko::Color::new([color.r(), color.g(), color.b(), color.a()])),\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\tlet bounds = row.element.nonzero_bounding_box();\n\t\t\t\t\tlet bound_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);\n\n\t\t\t\t\tlet inverse_parent_transform = if parent_transform.matrix2.determinant() != 0. {\n\t\t\t\t\t\tparent_transform.inverse()\n\t\t\t\t\t} else {\n\t\t\t\t\t\tDefault::default()\n\t\t\t\t\t};\n\t\t\t\t\tlet mod_points = inverse_parent_transform * multiplied_transform * bound_transform;\n\n\t\t\t\t\tlet start = mod_points.transform_point2(gradient.start);\n\t\t\t\t\tlet end = mod_points.transform_point2(gradient.end);\n\n\t\t\t\t\tlet fill = peniko::Brush::Gradient(peniko::Gradient {\n\t\t\t\t\t\tkind: match gradient.gradient_type {\n\t\t\t\t\t\t\tGradientType::Linear => peniko::LinearGradientPosition {\n\t\t\t\t\t\t\t\tstart: to_point(start),\n\t\t\t\t\t\t\t\tend: to_point(end),\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t.into(),\n\t\t\t\t\t\t\tGradientType::Radial => {\n\t\t\t\t\t\t\t\tlet radius = start.distance(end);\n\t\t\t\t\t\t\t\tpeniko::RadialGradientPosition {\n\t\t\t\t\t\t\t\t\tstart_center: to_point(start),\n\t\t\t\t\t\t\t\t\tstart_radius: 0.,\n\t\t\t\t\t\t\t\t\tend_center: to_point(start),\n\t\t\t\t\t\t\t\t\tend_radius: radius as f32,\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t.into()\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\tstops,\n\t\t\t\t\t\tinterpolation_alpha_space: peniko::InterpolationAlphaSpace::Premultiplied,\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t});\n\t\t\t\t\tlet inverse_element_transform = if element_transform.matrix2.determinant() != 0. {\n\t\t\t\t\t\telement_transform.inverse()\n\t\t\t\t\t} else {\n\t\t\t\t\t\tDefault::default()\n\t\t\t\t\t};\n\t\t\t\t\tlet brush_transform = kurbo::Affine::new((inverse_element_transform * parent_transform).to_cols_array());\n\t\t\t\t\tscene.fill(fill_rule, kurbo::Affine::new(element_transform.to_cols_array()), &fill, Some(brush_transform), path);\n\t\t\t\t}\n\t\t\t\tFill::None => {}\n\t\t\t};\n\n\t\t\t// Branching vectors without regions (e.g. mesh grids) need face-by-face fill rendering.\n\t\t\tlet use_face_fill = row.element.use_face_fill();\n\t\t\tlet do_fill = |scene: &mut Scene| {\n\t\t\t\tif use_face_fill {\n\t\t\t\t\tfor mut face_path in row.element.construct_faces().filter(|face| face.area() >= 0.) {\n\t\t\t\t\t\tface_path.apply_affine(Affine::new(applied_stroke_transform.to_cols_array()));\n\t\t\t\t\t\tlet mut kurbo_path = kurbo::BezPath::new();\n\t\t\t\t\t\tfor element in face_path {\n\t\t\t\t\t\t\tkurbo_path.push(element);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdo_fill_path(scene, &kurbo_path, peniko::Fill::NonZero);\n\t\t\t\t\t}\n\t\t\t\t} else if row.element.is_branching() {\n\t\t\t\t\tdo_fill_path(scene, &path, peniko::Fill::EvenOdd);\n\t\t\t\t} else {\n\t\t\t\t\tdo_fill_path(scene, &path, peniko::Fill::NonZero);\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tlet do_stroke = |scene: &mut Scene, width_scale: f64| {\n\t\t\t\tif let Some(stroke) = row.element.style.stroke() {\n\t\t\t\t\tlet color = match stroke.color {\n\t\t\t\t\t\tSome(color) => peniko::Color::new([color.r(), color.g(), color.b(), color.a()]),\n\t\t\t\t\t\tNone => peniko::Color::TRANSPARENT,\n\t\t\t\t\t};\n\t\t\t\t\tlet cap = match stroke.cap {\n\t\t\t\t\t\tStrokeCap::Butt => Cap::Butt,\n\t\t\t\t\t\tStrokeCap::Round => Cap::Round,\n\t\t\t\t\t\tStrokeCap::Square => Cap::Square,\n\t\t\t\t\t};\n\t\t\t\t\tlet join = match stroke.join {\n\t\t\t\t\t\tStrokeJoin::Miter => Join::Miter,\n\t\t\t\t\t\tStrokeJoin::Bevel => Join::Bevel,\n\t\t\t\t\t\tStrokeJoin::Round => Join::Round,\n\t\t\t\t\t};\n\t\t\t\t\tlet dash_pattern = stroke.dash_lengths.iter().map(|l| l.max(0.)).collect();\n\t\t\t\t\tlet stroke = kurbo::Stroke {\n\t\t\t\t\t\twidth: stroke.weight * width_scale,\n\t\t\t\t\t\tmiter_limit: stroke.join_miter_limit,\n\t\t\t\t\t\tjoin,\n\t\t\t\t\t\tstart_cap: cap,\n\t\t\t\t\t\tend_cap: cap,\n\t\t\t\t\t\tdash_pattern,\n\t\t\t\t\t\tdash_offset: stroke.dash_offset,\n\t\t\t\t\t};\n\n\t\t\t\t\tif stroke.width > 0. {\n\t\t\t\t\t\tscene.stroke(&stroke, kurbo::Affine::new(element_transform.to_cols_array()), color, None, &path);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\n\t\t\t// Render the path\n\t\t\tmatch render_params.render_mode {\n\t\t\t\tRenderMode::Outline => {\n\t\t\t\t\tlet (outline_stroke, outline_color_peniko) = get_outline_styles(render_params);\n\n\t\t\t\t\tscene.stroke(&outline_stroke, kurbo::Affine::new(element_transform.to_cols_array()), outline_color_peniko, None, &path);\n\t\t\t\t}\n\t\t\t\t_ => {\n\t\t\t\t\tif use_layer {\n\t\t\t\t\t\tlet mut element = row.element.clone();\n\t\t\t\t\t\telement.style.clear_stroke();\n\t\t\t\t\t\telement.style.set_fill(Fill::solid(Color::BLACK));\n\n\t\t\t\t\t\tlet vector_table = Table::new_from_row(TableRow {\n\t\t\t\t\t\t\telement,\n\t\t\t\t\t\t\talpha_blending: *row.alpha_blending,\n\t\t\t\t\t\t\ttransform: *row.transform,\n\t\t\t\t\t\t\tsource_node_id: None,\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tlet bounds = row.element.bounding_box_with_transform(multiplied_transform).unwrap_or(layer_bounds);\n\t\t\t\t\t\tlet weight = row.element.style.stroke().as_ref().map_or(0., Stroke::effective_width);\n\t\t\t\t\t\tlet quad = Quad::from_box(bounds).inflate(weight * max_scale(applied_stroke_transform));\n\t\t\t\t\t\tlet bounds = quad.bounding_box();\n\t\t\t\t\t\tlet rect = kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y);\n\n\t\t\t\t\t\tlet compose = if row.element.style.stroke().is_some_and(|x| x.align == StrokeAlign::Outside) {\n\t\t\t\t\t\t\tpeniko::Compose::SrcOut\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tpeniko::Compose::SrcIn\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tif wants_stroke_below {\n\t\t\t\t\t\t\tscene.push_layer(peniko::Fill::NonZero, peniko::Mix::Normal, 1., kurbo::Affine::IDENTITY, &rect);\n\t\t\t\t\t\t\tvector_table.render_to_vello(scene, parent_transform, _context, &render_params.for_alignment(applied_stroke_transform));\n\t\t\t\t\t\t\tscene.push_layer(peniko::Fill::NonZero, peniko::BlendMode::new(peniko::Mix::Normal, compose), 1., kurbo::Affine::IDENTITY, &rect);\n\n\t\t\t\t\t\t\tdo_stroke(scene, 2.);\n\n\t\t\t\t\t\t\tscene.pop_layer();\n\t\t\t\t\t\t\tscene.pop_layer();\n\n\t\t\t\t\t\t\tdo_fill(scene);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Fill first (unclipped), then stroke (clipped) above\n\t\t\t\t\t\t\tdo_fill(scene);\n\n\t\t\t\t\t\t\tscene.push_layer(peniko::Fill::NonZero, peniko::Mix::Normal, 1., kurbo::Affine::IDENTITY, &rect);\n\t\t\t\t\t\t\tvector_table.render_to_vello(scene, parent_transform, _context, &render_params.for_alignment(applied_stroke_transform));\n\t\t\t\t\t\t\tscene.push_layer(peniko::Fill::NonZero, peniko::BlendMode::new(peniko::Mix::Normal, compose), 1., kurbo::Affine::IDENTITY, &rect);\n\n\t\t\t\t\t\t\tdo_stroke(scene, 2.);\n\n\t\t\t\t\t\t\tscene.pop_layer();\n\t\t\t\t\t\t\tscene.pop_layer();\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Non-aligned strokes or open paths: default order behavior\n\t\t\t\t\t\tenum Op {\n\t\t\t\t\t\t\tFill,\n\t\t\t\t\t\t\tStroke,\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlet order = match row.element.style.stroke().is_some_and(|stroke| !stroke.paint_order.is_default()) {\n\t\t\t\t\t\t\ttrue => [Op::Stroke, Op::Fill],\n\t\t\t\t\t\t\tfalse => [Op::Fill, Op::Stroke], // Default\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tfor operation in &order {\n\t\t\t\t\t\t\tmatch operation {\n\t\t\t\t\t\t\t\tOp::Fill => do_fill(scene),\n\t\t\t\t\t\t\t\tOp::Stroke => do_stroke(scene, 1.),\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If we pushed a layer for opacity or a blend mode, we need to pop it\n\t\t\tif layer {\n\t\t\t\tscene.pop_layer();\n\t\t\t}\n\t\t}\n\t}\n\n\tfn collect_metadata(&self, metadata: &mut RenderMetadata, mut footprint: Footprint, element_id: Option<NodeId>) {\n\t\tfor row in self.iter() {\n\t\t\tlet transform = *row.transform;\n\t\t\tlet vector = row.element;\n\n\t\t\tif let Some(element_id) = element_id {\n\t\t\t\tlet stroke_width = vector.style.stroke().as_ref().map_or(0., Stroke::effective_width);\n\t\t\t\tlet filled = vector.style.fill() != &Fill::None;\n\t\t\t\tlet fill = |mut subpath: Subpath<_>| {\n\t\t\t\t\tif filled {\n\t\t\t\t\t\tsubpath.set_closed(true);\n\t\t\t\t\t}\n\t\t\t\t\tsubpath\n\t\t\t\t};\n\n\t\t\t\t// For free-floating anchors, we need to add a click target for each\n\t\t\t\tlet single_anchors_targets = vector.point_domain.ids().iter().filter_map(|&point_id| {\n\t\t\t\t\tif !vector.any_connected(point_id) {\n\t\t\t\t\t\tlet anchor = vector.point_domain.position_from_id(point_id).unwrap_or_default();\n\t\t\t\t\t\tlet point = FreePoint::new(point_id, anchor);\n\n\t\t\t\t\t\tSome(ClickTarget::new_with_free_point(point).into())\n\t\t\t\t\t} else {\n\t\t\t\t\t\tNone\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tlet click_targets = vector\n\t\t\t\t\t.stroke_bezier_paths()\n\t\t\t\t\t.map(fill)\n\t\t\t\t\t.map(|subpath| ClickTarget::new_with_subpath(subpath, stroke_width).into())\n\t\t\t\t\t.chain(single_anchors_targets.into_iter())\n\t\t\t\t\t.collect::<Vec<_>>();\n\n\t\t\t\tmetadata.click_targets.entry(element_id).or_insert(click_targets);\n\t\t\t\t// Store the full vector data including segment IDs for accurate segment modification\n\t\t\t\tmetadata.vector_data.entry(element_id).or_insert_with(|| Arc::new(vector.clone()));\n\t\t\t}\n\n\t\t\tif let Some(upstream_nested_layers) = &vector.upstream_data {\n\t\t\t\tfootprint.transform *= transform;\n\t\t\t\tupstream_nested_layers.collect_metadata(metadata, footprint, None);\n\t\t\t}\n\t\t}\n\t}\n\n\tfn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {\n\t\tfor row in self.iter() {\n\t\t\tlet stroke_width = row.element.style.stroke().as_ref().map_or(0., Stroke::effective_width);\n\t\t\tlet filled = row.element.style.fill() != &Fill::None;\n\t\t\tlet fill = |mut subpath: Subpath<_>| {\n\t\t\t\tif filled {\n\t\t\t\t\tsubpath.set_closed(true);\n\t\t\t\t}\n\t\t\t\tsubpath\n\t\t\t};\n\t\t\tclick_targets.extend(row.element.stroke_bezier_paths().map(fill).map(|subpath| {\n\t\t\t\tlet mut click_target = ClickTarget::new_with_subpath(subpath, stroke_width);\n\t\t\t\tclick_target.apply_transform(*row.transform);\n\t\t\t\tclick_target\n\t\t\t}));\n\n\t\t\t// For free-floating anchors, we need to add a click target for each\n\t\t\tlet single_anchors_targets = row.element.point_domain.ids().iter().filter_map(|&point_id| {\n\t\t\t\tif row.element.any_connected(point_id) {\n\t\t\t\t\treturn None;\n\t\t\t\t}\n\n\t\t\t\tlet anchor = row.element.point_domain.position_from_id(point_id).unwrap_or_default();\n\t\t\t\tlet point = FreePoint::new(point_id, anchor);\n\n\t\t\t\tlet mut click_target = ClickTarget::new_with_free_point(point);\n\t\t\t\tclick_target.apply_transform(*row.transform);\n\t\t\t\tSome(click_target)\n\t\t\t});\n\t\t\tclick_targets.extend(single_anchors_targets);\n\t\t}\n\t}\n\n\tfn new_ids_from_hash(&mut self, reference: Option<NodeId>) {\n\t\tfor row in self.iter_mut() {\n\t\t\trow.element.vector_new_ids_from_hash(reference.map(|id| id.0).unwrap_or_default());\n\t\t}\n\t}\n}\n\nimpl Render for Table<Raster<CPU>> {\n\tfn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {\n\t\tfor row in self.iter() {\n\t\t\tlet image = row.element;\n\n\t\t\tlet transform = *row.transform;\n\n\t\t\tif image.data.is_empty() {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif render_params.to_canvas() {\n\t\t\t\tlet mut image_copy = image.clone();\n\t\t\t\timage_copy.data_mut().map_pixels(|p| p.to_unassociated_alpha());\n\t\t\t\tlet id = *render.image_data.entry(image_copy.into_data()).or_insert_with(generate_uuid);\n\n\t\t\t\trender.parent_tag(\n\t\t\t\t\t\"foreignObject\",\n\t\t\t\t\t|attributes| {\n\t\t\t\t\t\tlet mut transform_values = transform.to_scale_angle_translation();\n\t\t\t\t\t\tlet size = DVec2::new(image.width as f64, image.height as f64);\n\t\t\t\t\t\ttransform_values.0 /= size;\n\n\t\t\t\t\t\tlet matrix = DAffine2::from_scale_angle_translation(transform_values.0, transform_values.1, transform_values.2);\n\t\t\t\t\t\tlet matrix = format_transform_matrix(matrix);\n\t\t\t\t\t\tif !matrix.is_empty() {\n\t\t\t\t\t\t\tattributes.push(\"transform\", matrix);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tattributes.push(\"width\", size.x.to_string());\n\t\t\t\t\t\tattributes.push(\"height\", size.y.to_string());\n\n\t\t\t\t\t\tlet opacity = row.alpha_blending.opacity(render_params.for_mask);\n\t\t\t\t\t\tif opacity < 1. {\n\t\t\t\t\t\t\tattributes.push(\"opacity\", opacity.to_string());\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif row.alpha_blending.blend_mode != BlendMode::default() {\n\t\t\t\t\t\t\tattributes.push(\"style\", row.alpha_blending.blend_mode.render());\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t|render| {\n\t\t\t\t\t\trender.leaf_tag(\n\t\t\t\t\t\t\t\"img\", // Must be a self-closing (void element) tag, so we can't use `div` or `span`, for example\n\t\t\t\t\t\t\t|attributes| {\n\t\t\t\t\t\t\t\tattributes.push(\"data-canvas-placeholder\", id.to_string());\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t)\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tlet base64_string = image.base64_string.clone().unwrap_or_else(|| {\n\t\t\t\t\tuse base64::Engine;\n\n\t\t\t\t\tlet output = image.to_png();\n\t\t\t\t\tlet preamble = \"data:image/png;base64,\";\n\t\t\t\t\tlet mut base64_string = String::with_capacity(preamble.len() + output.len() * 4);\n\t\t\t\t\tbase64_string.push_str(preamble);\n\t\t\t\t\tbase64::engine::general_purpose::STANDARD.encode_string(output, &mut base64_string);\n\t\t\t\t\tbase64_string\n\t\t\t\t});\n\n\t\t\t\trender.leaf_tag(\"image\", |attributes| {\n\t\t\t\t\tattributes.push(\"width\", \"1\");\n\t\t\t\t\tattributes.push(\"height\", \"1\");\n\t\t\t\t\tattributes.push(\"preserveAspectRatio\", \"none\");\n\t\t\t\t\tattributes.push(\"href\", base64_string);\n\t\t\t\t\tlet matrix = format_transform_matrix(transform);\n\t\t\t\t\tif !matrix.is_empty() {\n\t\t\t\t\t\tattributes.push(\"transform\", matrix);\n\t\t\t\t\t}\n\n\t\t\t\t\tlet opacity = row.alpha_blending.opacity(render_params.for_mask);\n\t\t\t\t\tif opacity < 1. {\n\t\t\t\t\t\tattributes.push(\"opacity\", opacity.to_string());\n\t\t\t\t\t}\n\t\t\t\t\tif row.alpha_blending.blend_mode != BlendMode::default() {\n\t\t\t\t\t\tattributes.push(\"style\", row.alpha_blending.blend_mode.render());\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\tfn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, _: &mut RenderContext, render_params: &RenderParams) {\n\t\tfor row in self.iter() {\n\t\t\tlet image = &row.element;\n\t\t\tif image.data.is_empty() {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet alpha_blending = *row.alpha_blending;\n\t\t\tlet blend_mode = alpha_blending.blend_mode.to_peniko();\n\n\t\t\tlet opacity = alpha_blending.opacity(render_params.for_mask);\n\t\t\tlet mut layer = false;\n\n\t\t\tif (opacity < 1. || (render_params.render_mode != RenderMode::Outline && alpha_blending.blend_mode != BlendMode::default()))\n\t\t\t\t&& let RenderBoundingBox::Rectangle(bounds) = self.bounding_box(transform, false)\n\t\t\t{\n\t\t\t\tlet blending = peniko::BlendMode::new(blend_mode, peniko::Compose::SrcOver);\n\t\t\t\tlet rect = kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y);\n\t\t\t\tscene.push_layer(peniko::Fill::NonZero, blending, opacity, kurbo::Affine::IDENTITY, &rect);\n\t\t\t\tlayer = true;\n\t\t\t}\n\n\t\t\tif let RenderMode::Outline = render_params.render_mode {\n\t\t\t\tlet outline_transform = transform * *row.transform;\n\t\t\t\tdraw_raster_outline(scene, &outline_transform, render_params);\n\n\t\t\t\tif layer {\n\t\t\t\t\tscene.pop_layer();\n\t\t\t\t}\n\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet image_transform = transform * *row.transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));\n\n\t\t\tlet image_brush = peniko::ImageBrush::new(peniko::ImageData {\n\t\t\t\tdata: image.to_flat_u8().0.into(),\n\t\t\t\tformat: peniko::ImageFormat::Rgba8,\n\t\t\t\twidth: image.width,\n\t\t\t\theight: image.height,\n\t\t\t\talpha_type: peniko::ImageAlphaType::Alpha,\n\t\t\t})\n\t\t\t.with_extend(peniko::Extend::Repeat);\n\n\t\t\tscene.draw_image(&image_brush, kurbo::Affine::new(image_transform.to_cols_array()));\n\n\t\t\tif layer {\n\t\t\t\tscene.pop_layer();\n\t\t\t}\n\t\t}\n\t}\n\n\tfn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {\n\t\tlet Some(element_id) = element_id else { return };\n\t\tlet subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);\n\n\t\tmetadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.).into()]);\n\t\tmetadata.upstream_footprints.insert(element_id, footprint);\n\t\t// TODO: Find a way to handle more than one row of the raster table\n\t\tif let Some(raster) = self.iter().next() {\n\t\t\tmetadata.local_transforms.insert(element_id, *raster.transform);\n\t\t}\n\t}\n\n\tfn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {\n\t\tlet subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);\n\t\tclick_targets.push(ClickTarget::new_with_subpath(subpath, 0.));\n\t}\n}\n\nstatic LAZY_ARC_VEC_ZERO_U8: LazyLock<Arc<Vec<u8>>> = LazyLock::new(|| Arc::new(Vec::new()));\n\nimpl Render for Table<Raster<GPU>> {\n\tfn render_svg(&self, _render: &mut SvgRender, _render_params: &RenderParams) {\n\t\tlog::warn!(\"tried to render texture as an svg\");\n\t}\n\n\tfn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {\n\t\tfor row in self.iter() {\n\t\t\tlet alpha_blending = *row.alpha_blending;\n\t\t\tlet blend_mode = match render_params.render_mode {\n\t\t\t\tRenderMode::Outline => peniko::Mix::Normal,\n\t\t\t\t_ => alpha_blending.blend_mode.to_peniko(),\n\t\t\t};\n\n\t\t\tlet mut layer = false;\n\n\t\t\tif (render_params.render_mode != RenderMode::Outline && alpha_blending != Default::default())\n\t\t\t\t&& let RenderBoundingBox::Rectangle(bounds) = self.bounding_box(transform, true)\n\t\t\t{\n\t\t\t\tlet blending = peniko::BlendMode::new(blend_mode, peniko::Compose::SrcOver);\n\t\t\t\tlet rect = kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y);\n\t\t\t\tscene.push_layer(peniko::Fill::NonZero, blending, alpha_blending.opacity, kurbo::Affine::IDENTITY, &rect);\n\t\t\t\tlayer = true;\n\t\t\t}\n\n\t\t\tif let RenderMode::Outline = render_params.render_mode {\n\t\t\t\tlet outline_transform = transform * *row.transform;\n\t\t\t\tdraw_raster_outline(scene, &outline_transform, render_params);\n\n\t\t\t\tif layer {\n\t\t\t\t\tscene.pop_layer();\n\t\t\t\t}\n\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet width = row.element.data().width();\n\t\t\tlet height = row.element.data().height();\n\t\t\tlet image = peniko::ImageBrush::new(peniko::ImageData {\n\t\t\t\tdata: peniko::Blob::new(LAZY_ARC_VEC_ZERO_U8.deref().clone()),\n\t\t\t\tformat: peniko::ImageFormat::Rgba8,\n\t\t\t\twidth,\n\t\t\t\theight,\n\t\t\t\talpha_type: peniko::ImageAlphaType::Alpha,\n\t\t\t})\n\t\t\t.with_extend(peniko::Extend::Repeat);\n\t\t\tlet image_transform = transform * *row.transform * DAffine2::from_scale(1. / DVec2::new(width as f64, height as f64));\n\t\t\tscene.draw_image(&image, kurbo::Affine::new(image_transform.to_cols_array()));\n\t\t\tcontext.resource_overrides.push((image, row.element.data().clone()));\n\n\t\t\tif layer {\n\t\t\t\tscene.pop_layer()\n\t\t\t}\n\t\t}\n\t}\n\n\tfn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {\n\t\tlet Some(element_id) = element_id else { return };\n\t\tlet subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);\n\n\t\tmetadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.).into()]);\n\t\tmetadata.upstream_footprints.insert(element_id, footprint);\n\t\t// TODO: Find a way to handle more than one row of the raster table\n\t\tif let Some(raster) = self.iter().next() {\n\t\t\tmetadata.local_transforms.insert(element_id, *raster.transform);\n\t\t}\n\t}\n\n\tfn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {\n\t\tlet subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);\n\t\tclick_targets.push(ClickTarget::new_with_subpath(subpath, 0.));\n\t}\n}\n\n// Since colors and gradients are technically infinitely big, we have to implement\n// workarounds for rendering them correctly in a way which still allows us\n// to cache the intermediate render data (SVG string/Vello scene).\n// For SVG, this is is achived by creating a truly giant rectangle.\n// For Vello, we create a layer with a placeholder transform which we\n// later replace with the current viewport transform before each render.\nimpl Render for Table<Color> {\n\tfn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {\n\t\tfor row in self.iter() {\n\t\t\trender.leaf_tag(\"polyline\", |attributes| {\n\t\t\t\t// Chrome doesn't like drawing centered rectangles bigger than ~20 million so we draw a polyline quad instead\n\t\t\t\tlet max = u64::MAX;\n\t\t\t\tattributes.push(\"points\", format!(\"{max},{max} -{max},{max} -{max},-{max} {max},-{max}\"));\n\n\t\t\t\tlet color = row.element;\n\t\t\t\tattributes.push(\"fill\", format!(\"#{}\", color.to_rgb_hex_srgb_from_gamma()));\n\t\t\t\tif color.a() < 1. {\n\t\t\t\t\tattributes.push(\"fill-opacity\", ((color.a() * 1000.).round() / 1000.).to_string());\n\t\t\t\t}\n\n\t\t\t\tlet opacity = row.alpha_blending.opacity(render_params.for_mask);\n\t\t\t\tif opacity < 1. {\n\t\t\t\t\tattributes.push(\"opacity\", opacity.to_string());\n\t\t\t\t}\n\n\t\t\t\tif row.alpha_blending.blend_mode != BlendMode::default() {\n\t\t\t\t\tattributes.push(\"style\", row.alpha_blending.blend_mode.render());\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n\n\tfn render_to_vello(&self, scene: &mut Scene, _parent_transform: DAffine2, _context: &mut RenderContext, render_params: &RenderParams) {\n\t\tuse vello::peniko;\n\n\t\tfor row in self.iter() {\n\t\t\tlet alpha_blending = *row.alpha_blending;\n\t\t\tlet blend_mode = alpha_blending.blend_mode.to_peniko();\n\t\t\tlet opacity = alpha_blending.opacity(render_params.for_mask);\n\n\t\t\tlet color = row.element;\n\t\t\tlet vello_color = peniko::Color::new([color.r(), color.g(), color.b(), color.a()]);\n\n\t\t\tlet rect = kurbo::Rect::from_origin_size(kurbo::Point::ZERO, kurbo::Size::new(1., 1.));\n\n\t\t\tlet mut layer = false;\n\t\t\tif opacity < 1. || alpha_blending.blend_mode != BlendMode::default() {\n\t\t\t\tlet blending = peniko::BlendMode::new(blend_mode, peniko::Compose::SrcOver);\n\t\t\t\tscene.push_layer(peniko::Fill::NonZero, blending, opacity, kurbo::Affine::scale(f64::INFINITY), &rect);\n\t\t\t\tlayer = true;\n\t\t\t}\n\n\t\t\tscene.fill(peniko::Fill::NonZero, kurbo::Affine::scale(f64::INFINITY), vello_color, None, &rect);\n\n\t\t\tif layer {\n\t\t\t\tscene.pop_layer();\n\t\t\t}\n\t\t}\n\t}\n}\n\nimpl Render for Table<GradientStops> {\n\t// TODO: Fix infinite gradient rendering\n\tfn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {\n\t\tfor row in self.iter() {\n\t\t\trender.leaf_tag(\"rect\", |attributes| {\n\t\t\t\t// Chrome doesn't like drawing centered rectangles bigger than ~20 million so we draw a polyline quad instead\n\t\t\t\tlet max = u64::MAX;\n\t\t\t\tattributes.push(\"points\", format!(\"{max},{max} -{max},{max} -{max},-{max} {max},-{max}\"));\n\n\t\t\t\tlet mut stop_string = String::new();\n\t\t\t\tfor (position, color, original_midpoint) in row.element.interpolated_samples() {\n\t\t\t\t\tlet _ = write!(stop_string, r##\"<stop offset=\"{}\" stop-color=\"#{}\"\"##, position, color.to_rgb_hex_srgb_from_gamma());\n\t\t\t\t\tif color.a() < 1. {\n\t\t\t\t\t\tlet _ = write!(stop_string, r#\" stop-opacity=\"{}\"\"#, color.a());\n\t\t\t\t\t}\n\t\t\t\t\tif let Some(midpoint) = original_midpoint {\n\t\t\t\t\t\tlet _ = write!(stop_string, r#\" graphite:midpoint=\"{}\"\"#, (midpoint * 1000.).round() / 1000.);\n\t\t\t\t\t}\n\t\t\t\t\tstop_string.push_str(\" />\");\n\t\t\t\t}\n\n\t\t\t\tlet gradient_transform = render_params.footprint.transform * *row.transform;\n\t\t\t\tlet gradient_transform_matrix = format_transform_matrix(gradient_transform);\n\t\t\t\tlet gradient_transform_attribute = if gradient_transform_matrix.is_empty() {\n\t\t\t\t\tString::new()\n\t\t\t\t} else {\n\t\t\t\t\tformat!(r#\" gradientTransform=\"{gradient_transform_matrix}\"\"#)\n\t\t\t\t};\n\n\t\t\t\tlet gradient_id = generate_uuid();\n\t\t\t\tlet start = DVec2::ZERO;\n\t\t\t\tlet end = DVec2::X;\n\n\t\t\t\tmatch GradientType::Radial {\n\t\t\t\t\tGradientType::Linear => {\n\t\t\t\t\t\tlet (x1, y1) = (start.x, start.y);\n\t\t\t\t\t\tlet (x2, y2) = (end.x, end.y);\n\t\t\t\t\t\tlet _ = write!(\n\t\t\t\t\t\t\t&mut attributes.0.svg_defs,\n\t\t\t\t\t\t\tr#\"<linearGradient id=\"{gradient_id}\" gradientUnits=\"userSpaceOnUse\" x1=\"{x1}\" y1=\"{y1}\" x2=\"{x2}\" y2=\"{y2}\"{gradient_transform_attribute}>{stop_string}</linearGradient>\"#\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tGradientType::Radial => {\n\t\t\t\t\t\tlet (cx, cy) = (start.x, start.y);\n\t\t\t\t\t\tlet r = start.distance(end);\n\t\t\t\t\t\tlet _ = write!(\n\t\t\t\t\t\t\t&mut attributes.0.svg_defs,\n\t\t\t\t\t\t\tr#\"<radialGradient id=\"{gradient_id}\" gradientUnits=\"userSpaceOnUse\" cx=\"{cx}\" cy=\"{cy}\" r=\"{r}\"{gradient_transform_attribute}>{stop_string}</radialGradient>\"#\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tattributes.push(\"fill\", format!(\"url('#{gradient_id}')\"));\n\n\t\t\t\tlet opacity = row.alpha_blending.opacity(render_params.for_mask);\n\t\t\t\tif opacity < 1. {\n\t\t\t\t\tattributes.push(\"opacity\", opacity.to_string());\n\t\t\t\t}\n\n\t\t\t\tif row.alpha_blending.blend_mode != BlendMode::default() {\n\t\t\t\t\tattributes.push(\"style\", row.alpha_blending.blend_mode.render());\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n\n\t// TODO: Fix infinite gradient rendering\n\tfn render_to_vello(&self, scene: &mut Scene, _parent_transform: DAffine2, _context: &mut RenderContext, render_params: &RenderParams) {\n\t\tuse vello::peniko;\n\n\t\tfor row in self.iter() {\n\t\t\tlet alpha_blending = *row.alpha_blending;\n\t\t\tlet blend_mode = alpha_blending.blend_mode.to_peniko();\n\t\t\tlet opacity = alpha_blending.opacity(render_params.for_mask);\n\n\t\t\tlet color = row.element.color.first().copied().unwrap_or(Color::MAGENTA);\n\t\t\tlet vello_color = peniko::Color::new([color.r(), color.g(), color.b(), color.a()]);\n\n\t\t\tlet rect = kurbo::Rect::from_origin_size(kurbo::Point::ZERO, kurbo::Size::new(1., 1.));\n\n\t\t\tlet mut layer = false;\n\t\t\tif opacity < 1. || alpha_blending.blend_mode != BlendMode::default() {\n\t\t\t\tlet blending = peniko::BlendMode::new(blend_mode, peniko::Compose::SrcOver);\n\t\t\t\t// See implemenation in `Table<Color>` for more detail\n\t\t\t\tscene.push_layer(peniko::Fill::NonZero, blending, opacity, kurbo::Affine::scale(f64::INFINITY), &rect);\n\t\t\t\tlayer = true;\n\t\t\t}\n\n\t\t\tscene.fill(peniko::Fill::NonZero, kurbo::Affine::scale(f64::INFINITY), vello_color, None, &rect);\n\n\t\t\tif layer {\n\t\t\t\tscene.pop_layer();\n\t\t\t}\n\t\t}\n\t}\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub enum SvgSegment {\n\tSlice(&'static str),\n\tString(String),\n}\n\nimpl From<String> for SvgSegment {\n\tfn from(value: String) -> Self {\n\t\tSelf::String(value)\n\t}\n}\n\nimpl From<&'static str> for SvgSegment {\n\tfn from(value: &'static str) -> Self {\n\t\tSelf::Slice(value)\n\t}\n}\n\npub trait RenderSvgSegmentList {\n\tfn to_svg_string(&self) -> String;\n}\n\nimpl RenderSvgSegmentList for Vec<SvgSegment> {\n\tfn to_svg_string(&self) -> String {\n\t\tlet mut result = String::new();\n\t\tfor segment in self.iter() {\n\t\t\tresult.push_str(match segment {\n\t\t\t\tSvgSegment::Slice(x) => x,\n\t\t\t\tSvgSegment::String(x) => x,\n\t\t\t});\n\t\t}\n\t\tresult\n\t}\n}\n\npub struct SvgRenderAttrs<'a>(&'a mut SvgRender);\n\nimpl SvgRenderAttrs<'_> {\n\tpub fn push_complex(&mut self, name: impl Into<SvgSegment>, value: impl FnOnce(&mut SvgRender)) {\n\t\tself.0.svg.push(\" \".into());\n\t\tself.0.svg.push(name.into());\n\t\tself.0.svg.push(\"=\\\"\".into());\n\t\tvalue(self.0);\n\t\tself.0.svg.push(\"\\\"\".into());\n\t}\n\tpub fn push(&mut self, name: impl Into<SvgSegment>, value: impl Into<SvgSegment>) {\n\t\tself.push_complex(name, move |renderer| renderer.svg.push(value.into()));\n\t}\n\tpub fn push_val(&mut self, value: impl Into<SvgSegment>) {\n\t\tself.0.svg.push(value.into());\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/rendering/src/to_peniko.rs",
    "content": "use core_types::BlendMode;\nuse vello::peniko;\n\npub trait BlendModeExt {\n\tfn to_peniko(&self) -> peniko::Mix;\n}\n\nimpl BlendModeExt for BlendMode {\n\tfn to_peniko(&self) -> peniko::Mix {\n\t\tmatch self {\n\t\t\t// Normal group\n\t\t\tBlendMode::Normal => peniko::Mix::Normal,\n\t\t\t// Darken group\n\t\t\tBlendMode::Darken => peniko::Mix::Darken,\n\t\t\tBlendMode::Multiply => peniko::Mix::Multiply,\n\t\t\tBlendMode::ColorBurn => peniko::Mix::ColorBurn,\n\t\t\t// Lighten group\n\t\t\tBlendMode::Lighten => peniko::Mix::Lighten,\n\t\t\tBlendMode::Screen => peniko::Mix::Screen,\n\t\t\tBlendMode::ColorDodge => peniko::Mix::ColorDodge,\n\t\t\t// Contrast group\n\t\t\tBlendMode::Overlay => peniko::Mix::Overlay,\n\t\t\tBlendMode::SoftLight => peniko::Mix::SoftLight,\n\t\t\tBlendMode::HardLight => peniko::Mix::HardLight,\n\t\t\t// Inversion group\n\t\t\tBlendMode::Difference => peniko::Mix::Difference,\n\t\t\tBlendMode::Exclusion => peniko::Mix::Exclusion,\n\t\t\t// Component group\n\t\t\tBlendMode::Hue => peniko::Mix::Hue,\n\t\t\tBlendMode::Saturation => peniko::Mix::Saturation,\n\t\t\tBlendMode::Color => peniko::Mix::Color,\n\t\t\tBlendMode::Luminosity => peniko::Mix::Luminosity,\n\t\t\t_ => todo!(),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/vector-types/Cargo.toml",
    "content": "[package]\nname = \"vector-types\"\nversion = \"0.1.0\"\nedition = \"2024\"\ndescription = \"Vector graphics types and algorithms for Graphene\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\nlicense = \"MIT OR Apache-2.0\"\n\n[features]\ndefault = [\"serde\"]\nwasm = [\"core-types/wasm\", \"tsify\", \"wasm-bindgen\"]\n\n[dependencies]\n# Local dependencies\ncore-types = { workspace = true }\nnode-macro = { workspace = true }\n\n# Workspace dependencies\nbitflags = { workspace = true }\nbytemuck = { workspace = true }\nnum-traits = { workspace = true }\nglam = { workspace = true }\nkurbo = { workspace = true }\nlyon_geom = { workspace = true }\ndyn-any = { workspace = true }\nlog = { workspace = true }\npetgraph = { workspace = true }\nrustc-hash = { workspace = true }\npolycool = { workspace = true }\ntinyvec = { workspace = true }\n\n# Optional workspace dependencies\nserde = { workspace = true, optional = true }\ntsify = { workspace = true, optional = true }\nwasm-bindgen = { workspace = true, optional = true }\nfixedbitset = \"0.5.7\"\n"
  },
  {
    "path": "node-graph/libraries/vector-types/src/gradient.rs",
    "content": "use core_types::{Color, render_complexity::RenderComplexity};\nuse dyn_any::DynAny;\nuse glam::{DAffine2, DVec2};\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize, DynAny, node_macro::ChoiceType)]\n#[widget(Radio)]\npub enum GradientType {\n\t#[default]\n\tLinear,\n\tRadial,\n}\n\n// TODO: Someday we could switch this to a Box[T] to avoid over-allocation\n// TODO: Use linear not gamma colors\n/// A list of colors associated with positions (in the range 0 to 1) along a gradient.\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, PartialEq, serde::Serialize, DynAny)]\npub struct GradientStops {\n\t/// The position of this stop, a factor from 0-1 along the length of the full gradient.\n\tpub position: Vec<f64>,\n\t/// The midpoint to the right of this stop, a factor from 0-1 along the distance to the next stop. The final stop's midpoint is ignored.\n\tpub midpoint: Vec<f64>,\n\t/// The color at this stop.\n\tpub color: Vec<Color>,\n}\n\n// TODO: Eventually remove this migration document upgrade code\nimpl<'de> serde::Deserialize<'de> for GradientStops {\n\tfn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {\n\t\t#[derive(serde::Deserialize)]\n\t\tstruct NewFormat {\n\t\t\tposition: Vec<f64>,\n\t\t\tmidpoint: Vec<f64>,\n\t\t\tcolor: Vec<Color>,\n\t\t}\n\n\t\t#[derive(serde::Deserialize)]\n\t\t#[serde(untagged)]\n\t\tenum GradientStopsFormat {\n\t\t\tNew(NewFormat),\n\t\t\tOld(Vec<(f64, Color)>),\n\t\t}\n\n\t\tOk(match GradientStopsFormat::deserialize(deserializer)? {\n\t\t\tGradientStopsFormat::New(new) => Self {\n\t\t\t\tposition: new.position,\n\t\t\t\tmidpoint: new.midpoint,\n\t\t\t\tcolor: new.color,\n\t\t\t},\n\t\t\tGradientStopsFormat::Old(stops) => {\n\t\t\t\tlet count = stops.len();\n\t\t\t\tSelf {\n\t\t\t\t\tposition: stops.iter().map(|(p, _)| *p).collect(),\n\t\t\t\t\tmidpoint: vec![0.5; count],\n\t\t\t\t\tcolor: stops.into_iter().map(|(_, c)| c).collect(),\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nimpl std::hash::Hash for GradientStops {\n\tfn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n\t\tself.position.len().hash(state);\n\t\tfor i in 0..self.position.len() {\n\t\t\tself.position[i].to_bits().hash(state);\n\t\t\tself.midpoint[i].to_bits().hash(state);\n\t\t\tself.color[i].hash(state);\n\t\t}\n\t}\n}\n\nimpl Default for GradientStops {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tposition: vec![0., 1.],\n\t\t\tmidpoint: vec![0.5, 0.5],\n\t\t\tcolor: vec![Color::BLACK, Color::WHITE],\n\t\t}\n\t}\n}\n\nimpl RenderComplexity for GradientStops {\n\tfn render_complexity(&self) -> usize {\n\t\t1\n\t}\n}\n\n/// Apply the midpoint curve to a normalized parameter `t` (0 to 1) given a `midpoint` (0 to 1, where 0.5 is linear).\nfn apply_midpoint(t: f64, midpoint: f64) -> f64 {\n\tif (midpoint - 0.5).abs() < 1e-6 {\n\t\treturn t;\n\t}\n\n\tlet midpoint = midpoint.clamp(f64::EPSILON, 1. - f64::EPSILON);\n\n\tif midpoint < 0.5 {\n\t\tlet q = -1. / (1. - midpoint).log2();\n\t\t1. - (1. - t).powf(q)\n\t} else {\n\t\tlet p = -1. / midpoint.log2();\n\t\tt.powf(p)\n\t}\n}\n\n#[derive(Debug, Clone, Copy)]\npub struct GradientStop {\n\tpub position: f64,\n\tpub midpoint: f64,\n\tpub color: Color,\n}\n\npub struct GradientStopsIter<'a> {\n\tstops: &'a GradientStops,\n\tindex: usize,\n}\n\nimpl<'a> Iterator for GradientStopsIter<'a> {\n\ttype Item = GradientStop;\n\n\tfn next(&mut self) -> Option<Self::Item> {\n\t\tif self.index >= self.stops.position.len() {\n\t\t\treturn None;\n\t\t}\n\n\t\tlet stop = GradientStop {\n\t\t\tposition: self.stops.position[self.index],\n\t\t\tmidpoint: self.stops.midpoint[self.index],\n\t\t\tcolor: self.stops.color[self.index],\n\t\t};\n\t\tself.index += 1;\n\t\tSome(stop)\n\t}\n\n\tfn size_hint(&self) -> (usize, Option<usize>) {\n\t\tlet remaining = self.stops.position.len() - self.index;\n\t\t(remaining, Some(remaining))\n\t}\n}\n\nimpl ExactSizeIterator for GradientStopsIter<'_> {}\n\nimpl<'a> IntoIterator for &'a GradientStops {\n\ttype Item = GradientStop;\n\ttype IntoIter = GradientStopsIter<'a>;\n\n\tfn into_iter(self) -> Self::IntoIter {\n\t\tGradientStopsIter { stops: self, index: 0 }\n\t}\n}\n\nimpl IntoIterator for GradientStops {\n\ttype Item = GradientStop;\n\ttype IntoIter = std::vec::IntoIter<GradientStop>;\n\n\tfn into_iter(self) -> Self::IntoIter {\n\t\tself.position\n\t\t\t.into_iter()\n\t\t\t.zip(self.midpoint)\n\t\t\t.zip(self.color)\n\t\t\t.map(|((position, midpoint), color)| GradientStop { position, midpoint, color })\n\t\t\t.collect::<Vec<_>>()\n\t\t\t.into_iter()\n\t}\n}\n\nimpl GradientStops {\n\tpub fn new(stops: impl IntoIterator<Item = GradientStop>) -> Self {\n\t\tlet mut position = Vec::new();\n\t\tlet mut midpoint = Vec::new();\n\t\tlet mut color = Vec::new();\n\n\t\tfor stop in stops {\n\t\t\tposition.push(stop.position);\n\t\t\tmidpoint.push(stop.midpoint);\n\t\t\tcolor.push(stop.color);\n\t\t}\n\n\t\tSelf { position, midpoint, color }\n\t}\n\n\tpub fn len(&self) -> usize {\n\t\tself.position.len()\n\t}\n\n\tpub fn is_empty(&self) -> bool {\n\t\tself.position.is_empty()\n\t}\n\n\tpub fn iter(&self) -> GradientStopsIter<'_> {\n\t\tself.into_iter()\n\t}\n\n\t/// Remove a stop at the given index.\n\tpub fn remove(&mut self, index: usize) {\n\t\tself.position.remove(index);\n\t\tself.midpoint.remove(index);\n\t\tself.color.remove(index);\n\t}\n\n\t/// Remove and return the last stop's color, or `None` if empty.\n\tpub fn pop(&mut self) -> Option<Color> {\n\t\tself.position.pop();\n\t\tself.midpoint.pop();\n\t\tself.color.pop()\n\t}\n\n\tpub fn evaluate(&self, t: f64) -> Color {\n\t\tif self.position.is_empty() {\n\t\t\treturn Color::BLACK;\n\t\t}\n\n\t\tif t <= self.position[0] {\n\t\t\treturn self.color[0];\n\t\t}\n\t\tlet last = self.position.len() - 1;\n\t\tif t >= self.position[last] {\n\t\t\treturn self.color[last];\n\t\t}\n\n\t\tfor i in 0..self.position.len() - 1 {\n\t\t\tlet (t1, c1) = (self.position[i], self.color[i]);\n\t\t\tlet (t2, c2) = (self.position[i + 1], self.color[i + 1]);\n\t\t\tif t >= t1 && t <= t2 {\n\t\t\t\tlet normalized_t = (t - t1) / (t2 - t1);\n\t\t\t\tlet adjusted_t = apply_midpoint(normalized_t, self.midpoint[i]);\n\t\t\t\treturn c1.lerp(&c2, adjusted_t as f32);\n\t\t\t}\n\t\t}\n\n\t\tColor::BLACK\n\t}\n\n\tpub fn sort(&mut self) {\n\t\tlet mut indices: Vec<usize> = (0..self.position.len()).collect();\n\t\tindices.sort_unstable_by(|&a, &b| self.position[a].total_cmp(&self.position[b]));\n\t\tself.position = indices.iter().map(|&i| self.position[i]).collect();\n\t\tself.midpoint = indices.iter().map(|&i| self.midpoint[i]).collect();\n\t\tself.color = indices.iter().map(|&i| self.color[i]).collect();\n\t}\n\n\tpub fn reversed(&self) -> Self {\n\t\tlet position: Vec<f64> = self.position.iter().rev().map(|&p| 1. - p).collect();\n\n\t\tlet count = self.midpoint.len();\n\t\tlet midpoint = (0..count).map(|i| if i < count - 1 { 1. - self.midpoint[count - 2 - i] } else { 0.5 }).collect::<Vec<_>>();\n\n\t\tlet color: Vec<Color> = self.color.iter().rev().cloned().collect();\n\n\t\tSelf { position, midpoint, color }\n\t}\n\n\tpub fn map_colors<F: Fn(&Color) -> Color>(&self, f: F) -> Self {\n\t\tSelf {\n\t\t\tposition: self.position.clone(),\n\t\t\tmidpoint: self.midpoint.clone(),\n\t\t\tcolor: self.color.iter().map(f).collect(),\n\t\t}\n\t}\n\n\t/// Produce a set of linearly-interpolated color samples that approximate the gradient's midpoint curves.\n\t///\n\t/// Each sample is `(position, color, original_midpoint)` where `original_midpoint` is `Some(f64)` with the corresponding\n\t/// midpoint for actual gradient stops, and `None` for interpolated samples added to approximate midpoint curves.\n\tpub fn interpolated_samples(&self) -> Vec<(f64, Color, Option<f64>)> {\n\t\t/// Controls accuracy vs. number of samples tradeoff.\n\t\t/// 2/255 means the linear approximation will deviate by no more than 2 gradations of 8-bit color from the theoretically perfect curve with this midpoint bias.\n\t\tconst THRESHOLD: f64 = 2. / 255.;\n\n\t\t#[allow(clippy::too_many_arguments)]\n\t\tfn subdivide(left: f64, right: f64, midpoint: f64, pos_a: f64, pos_b: f64, color_a: Color, color_b: Color, result: &mut Vec<(f64, Color, Option<f64>)>, depth: u32) {\n\t\t\tconst MAX_DEPTH: u32 = 20;\n\t\t\tif depth >= MAX_DEPTH {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet mid = (left + right) / 2.;\n\n\t\t\tlet y_actual = apply_midpoint(mid, midpoint);\n\t\t\tlet y_left = apply_midpoint(left, midpoint);\n\t\t\tlet y_right = apply_midpoint(right, midpoint);\n\t\t\tlet y_linear = (y_left + y_right) / 2.;\n\n\t\t\tif (y_actual - y_linear).abs() > THRESHOLD {\n\t\t\t\tsubdivide(left, mid, midpoint, pos_a, pos_b, color_a, color_b, result, depth + 1);\n\n\t\t\t\tlet global_pos = pos_a + mid * (pos_b - pos_a);\n\t\t\t\tlet color = color_a.lerp(&color_b, y_actual as f32);\n\t\t\t\tresult.push((global_pos, color, None));\n\n\t\t\t\tsubdivide(mid, right, midpoint, pos_a, pos_b, color_a, color_b, result, depth + 1);\n\t\t\t}\n\t\t}\n\n\t\tif self.position.is_empty() {\n\t\t\treturn vec![];\n\t\t}\n\n\t\tif self.position.len() == 1 {\n\t\t\treturn vec![(self.position[0], self.color[0], Some(self.midpoint[0]))];\n\t\t}\n\n\t\tlet mut result = Vec::new();\n\n\t\tfor i in 0..self.position.len() - 1 {\n\t\t\tlet pos_a = self.position[i];\n\t\t\tlet pos_b = self.position[i + 1];\n\t\t\tlet color_a = self.color[i];\n\t\t\tlet color_b = self.color[i + 1];\n\t\t\tlet midpoint = self.midpoint[i].clamp(0.01, 0.99);\n\t\t\tlet next_midpoint = self.midpoint[i + 1].clamp(0.01, 0.99);\n\n\t\t\t// Add the start stop (subsequent segments share the previous end stop)\n\t\t\tif i == 0 {\n\t\t\t\tresult.push((pos_a, color_a, Some(midpoint)));\n\t\t\t}\n\n\t\t\t// Only subdivide if midpoint deviates from linear (0.5)\n\t\t\tif (midpoint - 0.5).abs() >= 1e-6 {\n\t\t\t\tsubdivide(0., 1., midpoint, pos_a, pos_b, color_a, color_b, &mut result, 0);\n\t\t\t}\n\n\t\t\t// Add the end stop\n\t\t\tresult.push((pos_b, color_b, Some(next_midpoint)));\n\t\t}\n\n\t\t// If every midpoint is 0.5 (or within epsilon), turn all midpoints to None\n\t\tif result.iter().all(|(_, _, midpoint)| matches!(midpoint, Some(m) if (m - 0.5).abs() < 1e-6)) {\n\t\t\tresult.iter_mut().for_each(|(_, _, midpoint)| *midpoint = None);\n\t\t}\n\n\t\tresult\n\t}\n}\n\n/// A gradient fill.\n///\n/// Contains the start and end points, along with the colors at varying points along the length.\n#[repr(C)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, DynAny)]\npub struct Gradient {\n\tpub stops: GradientStops,\n\tpub gradient_type: GradientType,\n\tpub start: DVec2,\n\tpub end: DVec2,\n}\n\nimpl Default for Gradient {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tstops: GradientStops::default(),\n\t\t\tgradient_type: GradientType::Linear,\n\t\t\tstart: DVec2::new(0., 0.5),\n\t\t\tend: DVec2::new(1., 0.5),\n\t\t}\n\t}\n}\n\nimpl std::hash::Hash for Gradient {\n\tfn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n\t\tself.stops.len().hash(state);\n\t\t[].iter()\n\t\t\t.chain(self.start.to_array().iter())\n\t\t\t.chain(self.end.to_array().iter())\n\t\t\t.chain(self.stops.position.iter())\n\t\t\t.chain(self.stops.midpoint.iter())\n\t\t\t.for_each(|x| x.to_bits().hash(state));\n\t\tself.stops.color.iter().for_each(|color| color.hash(state));\n\t\tself.gradient_type.hash(state);\n\t}\n}\n\nimpl std::fmt::Display for Gradient {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tlet round = |x: f64| (x * 1e3).round() / 1e3;\n\t\tlet stops = self\n\t\t\t.stops\n\t\t\t.iter()\n\t\t\t.map(|stop| format!(\"[{}%: #{}]\", round(stop.position * 100.), stop.color.to_rgba_hex_srgb()))\n\t\t\t.collect::<Vec<_>>()\n\t\t\t.join(\", \");\n\t\twrite!(f, \"{} Gradient: {stops}\", self.gradient_type)\n\t}\n}\n\nimpl Gradient {\n\t/// Constructs a new gradient with the colors at 0 and 1 specified.\n\tpub fn new(start: DVec2, start_color: Color, end: DVec2, end_color: Color, gradient_type: GradientType) -> Self {\n\t\tlet stops = GradientStops::new([\n\t\t\tGradientStop {\n\t\t\t\tposition: 0.,\n\t\t\t\tmidpoint: 0.5,\n\t\t\t\tcolor: start_color.to_gamma_srgb(),\n\t\t\t},\n\t\t\tGradientStop {\n\t\t\t\tposition: 1.,\n\t\t\t\tmidpoint: 0.5,\n\t\t\t\tcolor: end_color.to_gamma_srgb(),\n\t\t\t},\n\t\t]);\n\n\t\tSelf { start, end, stops, gradient_type }\n\t}\n\n\tpub fn lerp(&self, other: &Self, time: f64) -> Self {\n\t\tlet start = self.start + (other.start - self.start) * time;\n\t\tlet end = self.end + (other.end - self.end) * time;\n\t\tlet stops = self.stops.iter().zip(other.stops.iter()).map(|(a, b)| {\n\t\t\tlet position = a.position + (b.position - a.position) * time;\n\t\t\tlet color = a.color.lerp(&b.color, time as f32);\n\t\t\tGradientStop { position, midpoint: 0.5, color }\n\t\t});\n\t\tlet stops = GradientStops::new(stops);\n\t\tlet gradient_type = if time < 0.5 { self.gradient_type } else { other.gradient_type };\n\n\t\tSelf { start, end, stops, gradient_type }\n\t}\n\n\t/// Insert a stop into the gradient, the index if successful\n\tpub fn insert_stop(&mut self, mouse: DVec2, transform: DAffine2) -> Option<usize> {\n\t\t// Transform the start and end positions to the same coordinate space as the mouse.\n\t\tlet (start, end) = (transform.transform_point2(self.start), transform.transform_point2(self.end));\n\n\t\t// Calculate the new position by finding the closest point on the line\n\t\tlet new_position = ((end - start).angle_to(mouse - start)).cos() * start.distance(mouse) / start.distance(end);\n\n\t\t// Don't insert point past end of line\n\t\tif !(0. ..=1.).contains(&new_position) {\n\t\t\treturn None;\n\t\t}\n\n\t\t// Compute the color of the inserted stop using evaluate (which respects midpoints)\n\t\tlet new_color = self.stops.evaluate(new_position);\n\n\t\t// Compute the correct index to keep the positions in order\n\t\tlet mut index = 0;\n\t\twhile self.stops.len() > index && self.stops.position[index] <= new_position {\n\t\t\tindex += 1;\n\t\t}\n\n\t\t// Insert the new stop, duplicating the midpoint ratio of the interval being split\n\t\tlet inherited_midpoint = if index > 0 { self.stops.midpoint[index - 1] } else { 0.5 };\n\t\tself.stops.position.insert(index, new_position);\n\t\tself.stops.midpoint.insert(index, inherited_midpoint);\n\t\tself.stops.color.insert(index, new_color);\n\n\t\tSome(index)\n\t}\n}\n\n// TODO: Eventually remove this migration document upgrade code\npub fn migrate_gradient_stops<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<core_types::table::Table<GradientStops>, D::Error> {\n\tuse core_types::table::Table;\n\tuse serde::Deserialize;\n\n\t#[derive(serde::Deserialize)]\n\t#[serde(untagged)]\n\tenum GradientStopsFormat {\n\t\tGradientStops(GradientStops),\n\t\tGradientTable(Table<GradientStops>),\n\t}\n\n\tOk(match GradientStopsFormat::deserialize(deserializer)? {\n\t\tGradientStopsFormat::GradientStops(stops) => Table::new_from_element(stops),\n\t\tGradientStopsFormat::GradientTable(table) => table,\n\t})\n}\n\nimpl core_types::bounds::BoundingBox for GradientStops {\n\tfn bounding_box(&self, _transform: DAffine2, _include_stroke: bool) -> core_types::bounds::RenderBoundingBox {\n\t\tcore_types::bounds::RenderBoundingBox::Infinite\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/vector-types/src/lib.rs",
    "content": "#[macro_use]\nextern crate log;\n\npub mod gradient;\npub mod math;\npub mod subpath;\npub mod vector;\n\n// Re-export commonly used types at the crate root\npub use core_types as gcore;\npub use gradient::{GradientStop, GradientStops, GradientType};\npub use math::{QuadExt, RectExt};\npub use subpath::Subpath;\npub use vector::Vector;\npub use vector::reference_point::ReferencePoint;\n\n// Re-export dependencies that users of this crate will need\npub use dyn_any;\npub use glam;\npub use kurbo;\n"
  },
  {
    "path": "node-graph/libraries/vector-types/src/math/mod.rs",
    "content": "use crate::subpath::Bezier;\nuse crate::vector::misc::dvec2_to_point;\nuse core_types::math::quad::Quad;\nuse core_types::math::rect::Rect;\nuse kurbo::{Line, PathSeg};\n\npub trait QuadExt {\n\t/// Get all the edges in the rect as linear bezier curves\n\tfn bezier_lines(&self) -> impl Iterator<Item = Bezier> + '_;\n\tfn to_lines(&self) -> impl Iterator<Item = PathSeg>;\n}\n\nimpl QuadExt for Quad {\n\tfn bezier_lines(&self) -> impl Iterator<Item = Bezier> + '_ {\n\t\tself.all_edges().into_iter().map(|[start, end]| Bezier::from_linear_dvec2(start, end))\n\t}\n\n\tfn to_lines(&self) -> impl Iterator<Item = PathSeg> {\n\t\tself.all_edges().into_iter().map(|[start, end]| PathSeg::Line(Line::new(dvec2_to_point(start), dvec2_to_point(end))))\n\t}\n}\n\npub trait RectExt {\n\t/// Get all the edges in the quad as linear bezier curves\n\tfn bezier_lines(&self) -> impl Iterator<Item = Bezier> + '_;\n}\n\nimpl RectExt for Rect {\n\tfn bezier_lines(&self) -> impl Iterator<Item = Bezier> + '_ {\n\t\tself.edges().into_iter().map(|[start, end]| Bezier::from_linear_dvec2(start, end))\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/vector-types/src/subpath/consts.rs",
    "content": "// Implementation constants\n\n/// Constant used to determine if `f64`s are equivalent.\npub const MAX_ABSOLUTE_DIFFERENCE: f64 = 1e-3;\n"
  },
  {
    "path": "node-graph/libraries/vector-types/src/subpath/core.rs",
    "content": "use super::consts::*;\nuse super::*;\nuse crate::vector::misc::{SpiralType, point_to_dvec2};\nuse glam::DVec2;\nuse kurbo::PathSeg;\nuse std::f64::consts::TAU;\n\npub struct PathSegPoints {\n\tpub p0: DVec2,\n\tpub p1: Option<DVec2>,\n\tpub p2: Option<DVec2>,\n\tpub p3: DVec2,\n}\n\nimpl PathSegPoints {\n\tpub fn new(p0: DVec2, p1: Option<DVec2>, p2: Option<DVec2>, p3: DVec2) -> Self {\n\t\tSelf { p0, p1, p2, p3 }\n\t}\n}\n\npub fn pathseg_points(segment: PathSeg) -> PathSegPoints {\n\tmatch segment {\n\t\tPathSeg::Line(line) => PathSegPoints::new(point_to_dvec2(line.p0), None, None, point_to_dvec2(line.p1)),\n\t\tPathSeg::Quad(quad) => PathSegPoints::new(point_to_dvec2(quad.p0), None, Some(point_to_dvec2(quad.p1)), point_to_dvec2(quad.p2)),\n\t\tPathSeg::Cubic(cube) => PathSegPoints::new(point_to_dvec2(cube.p0), Some(point_to_dvec2(cube.p1)), Some(point_to_dvec2(cube.p2)), point_to_dvec2(cube.p3)),\n\t}\n}\n\n/// Functionality relating to core `Subpath` operations, such as constructors and `iter`.\nimpl<PointId: Identifier> Subpath<PointId> {\n\t/// Create a new `Subpath` using a list of [ManipulatorGroup]s.\n\t/// A `Subpath` with less than 2 [ManipulatorGroup]s may not be closed.\n\t#[track_caller]\n\tpub fn new(manipulator_groups: Vec<ManipulatorGroup<PointId>>, closed: bool) -> Self {\n\t\tassert!(!closed || !manipulator_groups.is_empty(), \"A closed Subpath must contain more than 0 ManipulatorGroups.\");\n\t\tSelf { manipulator_groups, closed }\n\t}\n\n\t/// Create a `Subpath` consisting of 2 manipulator groups from a `Bezier`.\n\tpub fn from_bezier(segment: PathSeg) -> Self {\n\t\tlet PathSegPoints { p0, p1, p2, p3 } = pathseg_points(segment);\n\t\tSubpath::new(vec![ManipulatorGroup::new(p0, None, p1), ManipulatorGroup::new(p3, p2, None)], false)\n\t}\n\n\t/// Creates a subpath from a slice of [Bezier]. When two consecutive Beziers do not share an end and start point, this function\n\t/// resolves the discrepancy by simply taking the start-point of the second Bezier as the anchor of the Manipulator Group.\n\tpub fn from_beziers(beziers: &[PathSeg], closed: bool) -> Self {\n\t\tassert!(!closed || beziers.len() > 1, \"A closed Subpath must contain at least 1 Bezier.\");\n\t\tif beziers.is_empty() {\n\t\t\treturn Subpath::new(vec![], closed);\n\t\t}\n\n\t\tlet beziers: Vec<_> = beziers.iter().map(|b| pathseg_points(*b)).collect();\n\n\t\tlet first = beziers.first().unwrap();\n\t\tlet mut manipulator_groups = vec![ManipulatorGroup {\n\t\t\tanchor: first.p0,\n\t\t\tin_handle: None,\n\t\t\tout_handle: first.p1,\n\t\t\tid: PointId::new(),\n\t\t}];\n\t\tlet mut inner_groups: Vec<ManipulatorGroup<PointId>> = beziers\n\t\t\t.windows(2)\n\t\t\t.map(|bezier_pair| ManipulatorGroup {\n\t\t\t\tanchor: bezier_pair[1].p0,\n\t\t\t\tin_handle: bezier_pair[0].p2,\n\t\t\t\tout_handle: bezier_pair[1].p1,\n\t\t\t\tid: PointId::new(),\n\t\t\t})\n\t\t\t.collect::<Vec<ManipulatorGroup<PointId>>>();\n\t\tmanipulator_groups.append(&mut inner_groups);\n\n\t\tlet last = beziers.last().unwrap();\n\t\tif !closed {\n\t\t\tmanipulator_groups.push(ManipulatorGroup {\n\t\t\t\tanchor: last.p3,\n\t\t\t\tin_handle: last.p2,\n\t\t\t\tout_handle: None,\n\t\t\t\tid: PointId::new(),\n\t\t\t});\n\t\t\treturn Subpath::new(manipulator_groups, false);\n\t\t}\n\n\t\tmanipulator_groups[0].in_handle = last.p2;\n\t\tSubpath::new(manipulator_groups, true)\n\t}\n\n\t/// Returns true if the `Subpath` contains no [ManipulatorGroup].\n\tpub fn is_empty(&self) -> bool {\n\t\tself.manipulator_groups.is_empty()\n\t}\n\n\t/// Returns the number of [ManipulatorGroup]s contained within the `Subpath`.\n\tpub fn len(&self) -> usize {\n\t\tself.manipulator_groups.len()\n\t}\n\n\t/// Returns the number of segments contained within the `Subpath`.\n\tpub fn len_segments(&self) -> usize {\n\t\tlet mut number_of_curves = self.len();\n\t\tif !self.closed && number_of_curves > 0 {\n\t\t\tnumber_of_curves -= 1\n\t\t}\n\t\tnumber_of_curves\n\t}\n\n\t/// Returns a copy of the bezier segment at the given segment index, if this segment exists.\n\tpub fn get_segment(&self, segment_index: usize) -> Option<PathSeg> {\n\t\tif segment_index >= self.len_segments() {\n\t\t\treturn None;\n\t\t}\n\t\tSome(self[segment_index].to_bezier(&self[(segment_index + 1) % self.len()]))\n\t}\n\n\t/// Returns an iterator of the [Bezier]s along the `Subpath`.\n\tpub fn iter(&self) -> SubpathIter<'_, PointId> {\n\t\tSubpathIter {\n\t\t\tsubpath: self,\n\t\t\tindex: 0,\n\t\t\tis_always_closed: false,\n\t\t}\n\t}\n\n\t/// Returns an iterator of the [Bezier]s along the `Subpath` always considering it as a closed subpath.\n\tpub fn iter_closed(&self) -> SubpathIter<'_, PointId> {\n\t\tSubpathIter {\n\t\t\tsubpath: self,\n\t\t\tindex: 0,\n\t\t\tis_always_closed: true,\n\t\t}\n\t}\n\n\t/// Returns a slice of the [ManipulatorGroup]s in the `Subpath`.\n\tpub fn manipulator_groups(&self) -> &[ManipulatorGroup<PointId>] {\n\t\t&self.manipulator_groups\n\t}\n\n\t/// Returns a mutable reference to the [ManipulatorGroup]s in the `Subpath`.\n\tpub fn manipulator_groups_mut(&mut self) -> &mut Vec<ManipulatorGroup<PointId>> {\n\t\t&mut self.manipulator_groups\n\t}\n\n\t/// Returns a vector of all the anchors (DVec2) for this `Subpath`.\n\tpub fn anchors(&self) -> Vec<DVec2> {\n\t\tself.manipulator_groups().iter().map(|group| group.anchor).collect()\n\t}\n\n\t/// Returns if the Subpath is equivalent to a single point.\n\tpub fn is_point(&self) -> bool {\n\t\tif self.is_empty() {\n\t\t\treturn false;\n\t\t}\n\t\tlet point = self.manipulator_groups[0].anchor;\n\t\tself.manipulator_groups\n\t\t\t.iter()\n\t\t\t.all(|manipulator_group| manipulator_group.anchor.abs_diff_eq(point, MAX_ABSOLUTE_DIFFERENCE))\n\t}\n\n\tpub fn from_anchors(anchor_positions: impl IntoIterator<Item = DVec2>, closed: bool) -> Self {\n\t\tSelf::new(anchor_positions.into_iter().map(|anchor| ManipulatorGroup::new_anchor(anchor)).collect(), closed)\n\t}\n\n\t/// Constructs a rectangle with `corner1` and `corner2` as the two corners.\n\tpub fn new_rectangle(corner1: DVec2, corner2: DVec2) -> Self {\n\t\tSelf::from_anchors([corner1, DVec2::new(corner2.x, corner1.y), corner2, DVec2::new(corner1.x, corner2.y)], true)\n\t}\n\n\t/// Constructs a rounded rectangle with `corner1` and `corner2` as the two corners and `corner_radii` as the radii of the corners: `[top_left, top_right, bottom_right, bottom_left]`.\n\tpub fn new_rounded_rectangle(corner1: DVec2, corner2: DVec2, corner_radii: [f64; 4]) -> Self {\n\t\tif corner_radii.iter().all(|radii| radii.abs() < f64::EPSILON * 100.) {\n\t\t\treturn Self::new_rectangle(corner1, corner2);\n\t\t}\n\n\t\tuse std::f64::consts::{FRAC_1_SQRT_2, PI};\n\n\t\tlet new_arc = |center: DVec2, corner: DVec2, radius: f64| -> Vec<ManipulatorGroup<PointId>> {\n\t\t\tlet point1 = center + DVec2::from_angle(-PI * 0.25).rotate(corner - center) * FRAC_1_SQRT_2;\n\t\t\tlet point2 = center + DVec2::from_angle(PI * 0.25).rotate(corner - center) * FRAC_1_SQRT_2;\n\t\t\tif radius == 0. {\n\t\t\t\treturn vec![ManipulatorGroup::new_anchor(point1), ManipulatorGroup::new_anchor(point2)];\n\t\t\t}\n\n\t\t\t// Constant from https://pomax.github.io/bezierinfo/#circles_cubic\n\t\t\tconst HANDLE_OFFSET_FACTOR: f64 = 0.551784777779014;\n\t\t\tlet handle_offset = radius * HANDLE_OFFSET_FACTOR;\n\t\t\tvec![\n\t\t\t\tManipulatorGroup::new(point1, None, Some(point1 + handle_offset * (corner - point1).normalize())),\n\t\t\t\tManipulatorGroup::new(point2, Some(point2 + handle_offset * (corner - point2).normalize()), None),\n\t\t\t]\n\t\t};\n\t\tSelf::new(\n\t\t\t[\n\t\t\t\tnew_arc(DVec2::new(corner1.x + corner_radii[0], corner1.y + corner_radii[0]), DVec2::new(corner1.x, corner1.y), corner_radii[0]),\n\t\t\t\tnew_arc(DVec2::new(corner2.x - corner_radii[1], corner1.y + corner_radii[1]), DVec2::new(corner2.x, corner1.y), corner_radii[1]),\n\t\t\t\tnew_arc(DVec2::new(corner2.x - corner_radii[2], corner2.y - corner_radii[2]), DVec2::new(corner2.x, corner2.y), corner_radii[2]),\n\t\t\t\tnew_arc(DVec2::new(corner1.x + corner_radii[3], corner2.y - corner_radii[3]), DVec2::new(corner1.x, corner2.y), corner_radii[3]),\n\t\t\t]\n\t\t\t.concat(),\n\t\t\ttrue,\n\t\t)\n\t}\n\n\t/// Constructs an ellipse with `corner1` and `corner2` as the two corners of the bounding box.\n\tpub fn new_ellipse(corner1: DVec2, corner2: DVec2) -> Self {\n\t\tlet size = (corner1 - corner2).abs();\n\t\tlet center = (corner1 + corner2) / 2.;\n\t\tlet top = DVec2::new(center.x, corner1.y);\n\t\tlet bottom = DVec2::new(center.x, corner2.y);\n\t\tlet left = DVec2::new(corner1.x, center.y);\n\t\tlet right = DVec2::new(corner2.x, center.y);\n\n\t\t// Based on https://pomax.github.io/bezierinfo/#circles_cubic\n\t\tconst HANDLE_OFFSET_FACTOR: f64 = 0.551784777779014;\n\t\tlet handle_offset = size * HANDLE_OFFSET_FACTOR * 0.5;\n\n\t\tlet manipulator_groups = vec![\n\t\t\tManipulatorGroup::new(top, Some(top - handle_offset * DVec2::X), Some(top + handle_offset * DVec2::X)),\n\t\t\tManipulatorGroup::new(right, Some(right - handle_offset * DVec2::Y), Some(right + handle_offset * DVec2::Y)),\n\t\t\tManipulatorGroup::new(bottom, Some(bottom + handle_offset * DVec2::X), Some(bottom - handle_offset * DVec2::X)),\n\t\t\tManipulatorGroup::new(left, Some(left + handle_offset * DVec2::Y), Some(left - handle_offset * DVec2::Y)),\n\t\t];\n\t\tSelf::new(manipulator_groups, true)\n\t}\n\n\t/// Constructs an arc by a `radius`, `angle_start` and `angle_size`. Angles must be in radians. Slice option makes it look like pie or pacman.\n\tpub fn new_arc(radius: f64, start_angle: f64, sweep_angle: f64, arc_type: ArcType) -> Self {\n\t\t// Prevents glitches from numerical imprecision that have been observed during animation playback after about a minute\n\t\tlet start_angle = start_angle % (std::f64::consts::TAU * 2.);\n\t\tlet sweep_angle = sweep_angle % (std::f64::consts::TAU * 2.);\n\n\t\tlet original_start_angle = start_angle;\n\t\tlet sweep_angle_sign = sweep_angle.signum();\n\n\t\tlet mut start_angle = 0.;\n\t\tlet mut sweep_angle = sweep_angle.abs();\n\n\t\tif ((sweep_angle / std::f64::consts::TAU).floor() as u32).is_multiple_of(2) {\n\t\t\tsweep_angle %= std::f64::consts::TAU;\n\t\t} else {\n\t\t\tstart_angle = sweep_angle % std::f64::consts::TAU;\n\t\t\tsweep_angle = std::f64::consts::TAU - start_angle;\n\t\t}\n\n\t\tsweep_angle *= sweep_angle_sign;\n\t\tstart_angle *= sweep_angle_sign;\n\t\tstart_angle += original_start_angle;\n\n\t\tlet closed = arc_type == ArcType::Closed;\n\t\tlet slice = arc_type == ArcType::PieSlice;\n\n\t\tlet center = DVec2::new(0., 0.);\n\t\tlet segments = (sweep_angle.abs() / (std::f64::consts::PI / 4.)).ceil().max(1.) as usize;\n\t\tlet step = sweep_angle / segments as f64;\n\t\tlet factor = 4. / 3. * (step / 2.).sin() / (1. + (step / 2.).cos());\n\n\t\tlet mut manipulator_groups = Vec::with_capacity(segments);\n\t\tlet mut prev_in_handle = None;\n\t\tlet mut prev_end = DVec2::new(0., 0.);\n\n\t\tfor i in 0..segments {\n\t\t\tlet start_angle = start_angle + step * i as f64;\n\t\t\tlet end_angle = start_angle + step;\n\t\t\tlet start_vec = DVec2::from_angle(start_angle);\n\t\t\tlet end_vec = DVec2::from_angle(end_angle);\n\n\t\t\tlet start = center + radius * start_vec;\n\t\t\tlet end = center + radius * end_vec;\n\n\t\t\tlet handle_start = start + start_vec.perp() * radius * factor;\n\t\t\tlet handle_end = end - end_vec.perp() * radius * factor;\n\n\t\t\tmanipulator_groups.push(ManipulatorGroup::new(start, prev_in_handle, Some(handle_start)));\n\t\t\tprev_in_handle = Some(handle_end);\n\t\t\tprev_end = end;\n\t\t}\n\t\tmanipulator_groups.push(ManipulatorGroup::new(prev_end, prev_in_handle, None));\n\n\t\tif slice {\n\t\t\tmanipulator_groups.push(ManipulatorGroup::new(center, None, None));\n\t\t}\n\n\t\tSelf::new(manipulator_groups, closed || slice)\n\t}\n\n\t/// Constructs a regular polygon (ngon). Based on `sides` and `radius`, which is the distance from the center to any vertex.\n\tpub fn new_regular_polygon(center: DVec2, sides: u64, radius: f64) -> Self {\n\t\tlet sides = sides.max(3);\n\t\tlet angle_increment = std::f64::consts::TAU / (sides as f64);\n\t\tlet anchor_positions = (0..sides).map(|i| {\n\t\t\tlet angle = (i as f64) * angle_increment - std::f64::consts::FRAC_PI_2;\n\t\t\tlet center = center + DVec2::ONE * radius;\n\t\t\tDVec2::new(center.x + radius * f64::cos(angle), center.y + radius * f64::sin(angle)) * 0.5\n\t\t});\n\t\tSelf::from_anchors(anchor_positions, true)\n\t}\n\n\t/// Constructs a star polygon (n-star). See [new_regular_polygon], but with interspersed vertices at an `inner_radius`.\n\tpub fn new_star_polygon(center: DVec2, sides: u64, radius: f64, inner_radius: f64) -> Self {\n\t\tlet sides = sides.max(2);\n\t\tlet angle_increment = 0.5 * std::f64::consts::TAU / (sides as f64);\n\t\tlet anchor_positions = (0..sides * 2).map(|i| {\n\t\t\tlet angle = (i as f64) * angle_increment - std::f64::consts::FRAC_PI_2;\n\t\t\tlet center = center + DVec2::ONE * radius;\n\t\t\tlet r = if i % 2 == 0 { radius } else { inner_radius };\n\t\t\tDVec2::new(center.x + r * f64::cos(angle), center.y + r * f64::sin(angle)) * 0.5\n\t\t});\n\t\tSelf::from_anchors(anchor_positions, true)\n\t}\n\n\t/// Constructs a line from `p1` to `p2`\n\tpub fn new_line(p1: DVec2, p2: DVec2) -> Self {\n\t\tSelf::from_anchors([p1, p2], false)\n\t}\n\n\t/// Constructs an arrow shape from start and end points with parametric control over dimensions\n\tpub fn new_arrow(start: DVec2, end: DVec2, shaft_width: f64, head_width: f64, head_length: f64) -> Self {\n\t\tlet delta = end - start;\n\t\tlet length = delta.length();\n\n\t\tif length < 1e-10 {\n\t\t\t// Degenerate case: return a point\n\t\t\treturn Self::from_anchors([start], true);\n\t\t}\n\n\t\tlet direction = delta / length;\n\t\tlet perpendicular = DVec2::new(-direction.y, direction.x);\n\n\t\tlet half_shaft = shaft_width * 0.5;\n\t\tlet half_head = head_width * 0.5;\n\t\tlet head_base_distance = (length - head_length).max(0.);\n\t\tlet head_base = start + direction * head_base_distance;\n\n\t\t// Arrow path starts at the tail, traces around the shape, and returns to the tail\n\t\tlet anchors = [\n\t\t\tstart,                                  // Tail center (origin)\n\t\t\tstart + perpendicular * half_shaft,     // Tail top\n\t\t\thead_base + perpendicular * half_shaft, // Head base top (shaft)\n\t\t\thead_base + perpendicular * half_head,  // Head base top (wide)\n\t\t\tend,                                    // Tip\n\t\t\thead_base - perpendicular * half_head,  // Head base bottom (wide)\n\t\t\thead_base - perpendicular * half_shaft, // Head base bottom (shaft)\n\t\t\tstart - perpendicular * half_shaft,     // Tail bottom\n\t\t];\n\n\t\tSelf::from_anchors(anchors, true)\n\t}\n\n\tpub fn new_spiral(a: f64, outer_radius: f64, turns: f64, start_angle: f64, delta_theta: f64, spiral_type: SpiralType) -> Self {\n\t\tlet mut manipulator_groups = Vec::new();\n\t\tlet mut prev_in_handle = None;\n\t\tlet theta_end = turns * std::f64::consts::TAU + start_angle;\n\n\t\tlet a = if spiral_type == SpiralType::Logarithmic { a.max(1e-10) } else { a };\n\t\tlet b = calculate_growth_factor(a, turns, outer_radius, spiral_type);\n\n\t\tlet mut theta = start_angle;\n\t\twhile theta < theta_end {\n\t\t\tlet theta_next = f64::min(theta + delta_theta, theta_end);\n\n\t\t\tlet p0 = spiral_point(theta, a, b, spiral_type);\n\t\t\tlet p3 = spiral_point(theta_next, a, b, spiral_type);\n\t\t\tlet t0 = spiral_tangent(theta, a, b, spiral_type);\n\t\t\tlet t1 = spiral_tangent(theta_next, a, b, spiral_type);\n\n\t\t\tlet arc_len = spiral_arc_length(theta, theta_next, a, b, spiral_type);\n\t\t\tlet d = arc_len / 3.;\n\n\t\t\tlet p1 = p0 + d * t0;\n\t\t\tlet p2 = p3 - d * t1;\n\n\t\t\tmanipulator_groups.push(ManipulatorGroup::new(p0, prev_in_handle, Some(p1)));\n\t\t\tprev_in_handle = Some(p2);\n\n\t\t\t// If final segment, end with anchor at theta_end\n\t\t\tif (theta_next - theta_end).abs() < f64::EPSILON {\n\t\t\t\tmanipulator_groups.push(ManipulatorGroup::new(p3, prev_in_handle, None));\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\ttheta = theta_next;\n\t\t}\n\n\t\tSelf::new(manipulator_groups, false)\n\t}\n}\n\npub fn calculate_growth_factor(a: f64, turns: f64, outer_radius: f64, spiral_type: SpiralType) -> f64 {\n\tmatch spiral_type {\n\t\tSpiralType::Archimedean => {\n\t\t\tlet total_theta = turns * TAU;\n\t\t\t(outer_radius - a) / total_theta\n\t\t}\n\t\tSpiralType::Logarithmic => {\n\t\t\tlet total_theta = turns * TAU;\n\t\t\t((outer_radius.abs() / a).ln()) / total_theta\n\t\t}\n\t}\n}\n\n/// Returns a point on the given spiral type at angle `theta`.\npub fn spiral_point(theta: f64, a: f64, b: f64, spiral_type: SpiralType) -> DVec2 {\n\tmatch spiral_type {\n\t\tSpiralType::Archimedean => archimedean_spiral_point(theta, a, b),\n\t\tSpiralType::Logarithmic => log_spiral_point(theta, a, b),\n\t}\n}\n\n/// Returns the tangent direction at angle `theta` for the given spiral type.\npub fn spiral_tangent(theta: f64, a: f64, b: f64, spiral_type: SpiralType) -> DVec2 {\n\tmatch spiral_type {\n\t\tSpiralType::Archimedean => archimedean_spiral_tangent(theta, a, b),\n\t\tSpiralType::Logarithmic => log_spiral_tangent(theta, a, b),\n\t}\n}\n\n/// Computes arc length between two angles for the given spiral type.\npub fn spiral_arc_length(theta_start: f64, theta_end: f64, a: f64, b: f64, spiral_type: SpiralType) -> f64 {\n\tmatch spiral_type {\n\t\tSpiralType::Archimedean => archimedean_spiral_arc_length(theta_start, theta_end, a, b),\n\t\tSpiralType::Logarithmic => log_spiral_arc_length(theta_start, theta_end, a, b),\n\t}\n}\n\n/// Returns a point on a logarithmic spiral at angle `theta`.\npub fn log_spiral_point(theta: f64, a: f64, b: f64) -> DVec2 {\n\tlet r = a * (b * theta).exp(); // a * e^(bθ)\n\tDVec2::new(r * theta.cos(), -r * theta.sin())\n}\n\n/// Computes arc length along a logarithmic spiral between two angles.\npub fn log_spiral_arc_length(theta_start: f64, theta_end: f64, a: f64, b: f64) -> f64 {\n\tlet factor = (1. + b * b).sqrt();\n\t(a / b) * factor * ((b * theta_end).exp() - (b * theta_start).exp())\n}\n\n/// Returns the tangent direction of a logarithmic spiral at angle `theta`.\npub fn log_spiral_tangent(theta: f64, a: f64, b: f64) -> DVec2 {\n\tlet r = a * (b * theta).exp();\n\tlet dx = r * (b * theta.cos() - theta.sin());\n\tlet dy = r * (b * theta.sin() + theta.cos());\n\n\tDVec2::new(dx, -dy).normalize_or(DVec2::X)\n}\n\n/// Returns a point on an Archimedean spiral at angle `theta`.\npub fn archimedean_spiral_point(theta: f64, a: f64, b: f64) -> DVec2 {\n\tlet r = a + b * theta;\n\tDVec2::new(r * theta.cos(), -r * theta.sin())\n}\n\n/// Returns the tangent direction of an Archimedean spiral at angle `theta`.\npub fn archimedean_spiral_tangent(theta: f64, a: f64, b: f64) -> DVec2 {\n\tlet r = a + b * theta;\n\tlet dx = b * theta.cos() - r * theta.sin();\n\tlet dy = b * theta.sin() + r * theta.cos();\n\tDVec2::new(dx, -dy).normalize_or(DVec2::X)\n}\n\n/// Computes arc length along an Archimedean spiral between two angles.\npub fn archimedean_spiral_arc_length(theta_start: f64, theta_end: f64, a: f64, b: f64) -> f64 {\n\tarchimedean_spiral_arc_length_origin(theta_end, a, b) - archimedean_spiral_arc_length_origin(theta_start, a, b)\n}\n\n/// Computes arc length from origin to a point on Archimedean spiral at angle `theta`.\npub fn archimedean_spiral_arc_length_origin(theta: f64, a: f64, b: f64) -> f64 {\n\tlet r = a + b * theta;\n\tlet sqrt_term = (r * r + b * b).sqrt();\n\t(r * sqrt_term + b * b * ((r + sqrt_term).ln())) / (2. * b)\n}\n"
  },
  {
    "path": "node-graph/libraries/vector-types/src/subpath/lookup.rs",
    "content": "use super::consts::MAX_ABSOLUTE_DIFFERENCE;\nuse super::*;\nuse crate::vector::algorithms::bezpath_algorithms::pathseg_length_centroid_and_length;\nuse crate::vector::algorithms::intersection::{filtered_all_segment_intersections, pathseg_self_intersections};\nuse core_types::math::polynomial::pathseg_to_parametric_polynomial;\nuse glam::DVec2;\n\nimpl<PointId: Identifier> Subpath<PointId> {\n\t/// Returns a list of `t` values that correspond to all the self intersection points of the subpath always considering it as a closed subpath. The index and `t` value of both will be returned that corresponds to a point.\n\t/// The points will be sorted based on their index and `t` repsectively.\n\t/// - `error` - For intersections with non-linear beziers, `error` defines the threshold for bounding boxes to be considered an intersection point.\n\t/// - `minimum_separation`: the minimum difference two adjacent `t`-values must have when comparing adjacent `t`-values in sorted order.\n\t///\n\t/// If the comparison condition is not satisfied, the function takes the larger `t`-value of the two\n\t///\n\t/// **NOTE**: if an intersection were to occur within an `error` distance away from an anchor point, the algorithm will filter that intersection out.\n\tpub fn all_self_intersections(&self, accuracy: Option<f64>, minimum_separation: Option<f64>) -> Vec<(usize, f64)> {\n\t\tlet mut intersections_vec = Vec::new();\n\t\tlet err = accuracy.unwrap_or(MAX_ABSOLUTE_DIFFERENCE);\n\t\tlet num_curves = self.len();\n\t\t// TODO: optimization opportunity - this for-loop currently compares all intersections with all curve-segments in the subpath collection\n\t\tself.iter_closed().enumerate().for_each(|(i, other)| {\n\t\t\tintersections_vec.extend(pathseg_self_intersections(other, accuracy, minimum_separation).iter().flat_map(|value| [(i, value.0), (i, value.1)]));\n\t\t\tself.iter_closed().enumerate().skip(i + 1).for_each(|(j, curve)| {\n\t\t\t\tintersections_vec.extend(\n\t\t\t\t\tfiltered_all_segment_intersections(curve, other, accuracy, minimum_separation)\n\t\t\t\t\t\t.iter()\n\t\t\t\t\t\t.filter(|&value| (j != i + 1 || value.0 > err || (1. - value.1) > err) && (j != num_curves - 1 || i != 0 || value.1 > err || (1. - value.0) > err))\n\t\t\t\t\t\t.flat_map(|value| [(j, value.0), (i, value.1)]),\n\t\t\t\t);\n\t\t\t});\n\t\t});\n\n\t\tintersections_vec.sort_by(|a, b| a.partial_cmp(b).unwrap());\n\n\t\tintersections_vec\n\t}\n\n\t/// Return the area centroid, together with the area, of the `Subpath` always considering it as a closed subpath. The area will always be a positive value.\n\t///\n\t/// The area centroid is the center of mass for the area of a solid shape's interior.\n\t/// An infinitely flat material forming the subpath's closed shape would balance at this point.\n\t///\n\t/// It will return `None` if no manipulator is present. If the area is less than `error`, it will return `Some((DVec2::NAN, 0.))`.\n\t///\n\t/// Because the calculation of area and centroid for self-intersecting path requires finding the intersections, the following parameters are used:\n\t/// - `error` - For intersections with non-linear beziers, `error` defines the threshold for bounding boxes to be considered an intersection point.\n\t/// - `minimum_separation` - the minimum difference two adjacent `t`-values must have when comparing adjacent `t`-values in sorted order.\n\t///\n\t/// If the comparison condition is not satisfied, the function takes the larger `t`-value of the two.\n\t///\n\t/// **NOTE**: if an intersection were to occur within an `error` distance away from an anchor point, the algorithm will filter that intersection out.\n\tpub fn area_centroid_and_area(&self, error: Option<f64>, minimum_separation: Option<f64>) -> Option<(DVec2, f64)> {\n\t\tlet all_intersections = self.all_self_intersections(error, minimum_separation);\n\t\tlet mut current_sign: f64 = 1.;\n\n\t\tlet (x_sum, y_sum, area) = self\n\t\t\t.iter_closed()\n\t\t\t.enumerate()\n\t\t\t.map(|(index, bezier)| {\n\t\t\t\tlet (f_x, f_y) = pathseg_to_parametric_polynomial(bezier);\n\t\t\t\tlet (f_x, f_y) = (f_x.as_size::<10>().unwrap(), f_y.as_size::<10>().unwrap());\n\t\t\t\tlet f_y_prime = f_y.derivative();\n\t\t\t\tlet f_x_prime = f_x.derivative();\n\t\t\t\tlet f_xy = &f_x * &f_y;\n\n\t\t\t\tlet mut x_part = &f_xy * &f_x_prime;\n\t\t\t\tlet mut y_part = &f_xy * &f_y_prime;\n\t\t\t\tlet mut area_part = &f_x * &f_y_prime;\n\t\t\t\tx_part.antiderivative_mut();\n\t\t\t\ty_part.antiderivative_mut();\n\t\t\t\tarea_part.antiderivative_mut();\n\n\t\t\t\tlet mut curve_sum_x = -current_sign * x_part.eval(0.);\n\t\t\t\tlet mut curve_sum_y = -current_sign * y_part.eval(0.);\n\t\t\t\tlet mut curve_sum_area = -current_sign * area_part.eval(0.);\n\t\t\t\tfor (_, t) in all_intersections.iter().filter(|(i, _)| *i == index) {\n\t\t\t\t\tcurve_sum_x += 2. * current_sign * x_part.eval(*t);\n\t\t\t\t\tcurve_sum_y += 2. * current_sign * y_part.eval(*t);\n\t\t\t\t\tcurve_sum_area += 2. * current_sign * area_part.eval(*t);\n\t\t\t\t\tcurrent_sign *= -1.;\n\t\t\t\t}\n\t\t\t\tcurve_sum_x += current_sign * x_part.eval(1.);\n\t\t\t\tcurve_sum_y += current_sign * y_part.eval(1.);\n\t\t\t\tcurve_sum_area += current_sign * area_part.eval(1.);\n\n\t\t\t\t(-curve_sum_x, curve_sum_y, curve_sum_area)\n\t\t\t})\n\t\t\t.reduce(|(x1, y1, area1), (x2, y2, area2)| (x1 + x2, y1 + y2, area1 + area2))?;\n\n\t\tif area.abs() < error.unwrap_or(MAX_ABSOLUTE_DIFFERENCE) {\n\t\t\treturn Some((DVec2::NAN, 0.));\n\t\t}\n\n\t\tSome((DVec2::new(x_sum / area, y_sum / area), area.abs()))\n\t}\n\n\t/// Return the approximation of the length centroid, together with the length, of the `Subpath`.\n\t///\n\t/// The length centroid is the center of mass for the arc length of the solid shape's perimeter.\n\t/// An infinitely thin wire forming the subpath's closed shape would balance at this point.\n\t///\n\t/// It will return `None` if no manipulator is present.\n\t/// - `accuracy` is used to approximate the curve.\n\t/// - `always_closed` is to consider the subpath as closed always.\n\tpub fn length_centroid_and_length(&self, accuracy: Option<f64>, always_closed: bool) -> Option<(DVec2, f64)> {\n\t\tif always_closed { self.iter_closed() } else { self.iter() }\n\t\t\t.map(|bezier| pathseg_length_centroid_and_length(bezier, accuracy))\n\t\t\t.map(|(centroid, length)| (centroid * length, length))\n\t\t\t.reduce(|(centroid_part1, length1), (centroid_part2, length2)| (centroid_part1 + centroid_part2, length1 + length2))\n\t\t\t.map(|(centroid_part, length)| (centroid_part / length, length))\n\t\t\t.map(|(centroid_part, length)| (DVec2::new(centroid_part.x, centroid_part.y), length))\n\t}\n}\n\n#[cfg(test)]\nmod test_centroid {\n\tuse crate::vector::PointId;\n\n\tuse super::*;\n\t#[test]\n\tfn centroid_rect() {\n\t\tlet rect = Subpath::<PointId>::new_rectangle(DVec2::new(100., 100.), DVec2::new(300., 200.));\n\t\tlet (center, area) = rect.area_centroid_and_area(Some(1e-3), Some(1e-3)).unwrap();\n\t\tassert_eq!(area, 200. * 100.);\n\t\tassert_eq!(center, DVec2::new(200., 150.))\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/vector-types/src/subpath/manipulators.rs",
    "content": "// use super::consts::MAX_ABSOLUTE_DIFFERENCE;\n// use super::utils::{SubpathTValue};\nuse super::*;\n\nimpl<PointId: super::structs::Identifier> Subpath<PointId> {\n\t/// Get whether the subpath is closed.\n\tpub fn closed(&self) -> bool {\n\t\tself.closed\n\t}\n\n\t/// Set whether the subpath is closed.\n\tpub fn set_closed(&mut self, new_closed: bool) {\n\t\tself.closed = new_closed;\n\t}\n\n\t/// Access a [ManipulatorGroup] from a PointId.\n\tpub fn manipulator_from_id(&self, id: PointId) -> Option<&ManipulatorGroup<PointId>> {\n\t\tself.manipulator_groups.iter().find(|manipulator_group| manipulator_group.id == id)\n\t}\n\n\t/// Access a mutable [ManipulatorGroup] from a PointId.\n\tpub fn manipulator_mut_from_id(&mut self, id: PointId) -> Option<&mut ManipulatorGroup<PointId>> {\n\t\tself.manipulator_groups.iter_mut().find(|manipulator_group| manipulator_group.id == id)\n\t}\n\n\t/// Access the index of a [ManipulatorGroup] from a PointId.\n\tpub fn manipulator_index_from_id(&self, id: PointId) -> Option<usize> {\n\t\tself.manipulator_groups.iter().position(|manipulator_group| manipulator_group.id == id)\n\t}\n\n\t/// Insert a manipulator group at an index.\n\tpub fn insert_manipulator_group(&mut self, index: usize, group: ManipulatorGroup<PointId>) {\n\t\tassert!(group.is_finite(), \"Inserting non finite manipulator group\");\n\t\tself.manipulator_groups.insert(index, group)\n\t}\n\n\t/// Push a manipulator group to the end.\n\tpub fn push_manipulator_group(&mut self, group: ManipulatorGroup<PointId>) {\n\t\tassert!(group.is_finite(), \"Pushing non finite manipulator group\");\n\t\tself.manipulator_groups.push(group)\n\t}\n\n\t/// Get a mutable reference to the last manipulator\n\tpub fn last_manipulator_group_mut(&mut self) -> Option<&mut ManipulatorGroup<PointId>> {\n\t\tself.manipulator_groups.last_mut()\n\t}\n\n\t/// Remove a manipulator group at an index.\n\tpub fn remove_manipulator_group(&mut self, index: usize) -> ManipulatorGroup<PointId> {\n\t\tself.manipulator_groups.remove(index)\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/vector-types/src/subpath/mod.rs",
    "content": "mod consts;\nmod core;\nmod lookup;\nmod manipulators;\nmod solvers;\nmod structs;\nmod transform;\n\npub use core::*;\nuse kurbo::PathSeg;\nuse std::fmt::{Debug, Formatter, Result};\nuse std::ops::{Index, IndexMut};\npub use structs::*;\n\n/// Structure used to represent a path composed of [Bezier] curves.\n#[derive(Clone, PartialEq, Hash)]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\npub struct Subpath<PointId: Identifier> {\n\tmanipulator_groups: Vec<ManipulatorGroup<PointId>>,\n\tpub closed: bool,\n}\n\n/// Iteration structure for iterating across each curve of a `Subpath`, using an intermediate `Bezier` representation.\npub struct SubpathIter<'a, PointId: Identifier> {\n\tindex: usize,\n\tsubpath: &'a Subpath<PointId>,\n\tis_always_closed: bool,\n}\n\nimpl<PointId: Identifier> Index<usize> for Subpath<PointId> {\n\ttype Output = ManipulatorGroup<PointId>;\n\n\tfn index(&self, index: usize) -> &Self::Output {\n\t\tassert!(index < self.len(), \"Index out of bounds in trait Index of SubPath.\");\n\t\t&self.manipulator_groups[index]\n\t}\n}\n\nimpl<PointId: Identifier> IndexMut<usize> for Subpath<PointId> {\n\tfn index_mut(&mut self, index: usize) -> &mut Self::Output {\n\t\tassert!(index < self.len(), \"Index out of bounds in trait IndexMut of SubPath.\");\n\t\t&mut self.manipulator_groups[index]\n\t}\n}\n\nimpl<PointId: Identifier> Iterator for SubpathIter<'_, PointId> {\n\ttype Item = PathSeg;\n\n\t// Returns the Bezier representation of each `Subpath` segment, defined between a pair of adjacent manipulator points.\n\tfn next(&mut self) -> Option<Self::Item> {\n\t\tif self.subpath.is_empty() {\n\t\t\treturn None;\n\t\t}\n\t\tlet closed = if self.is_always_closed { true } else { self.subpath.closed };\n\t\tlet len = self.subpath.len() - 1 + if closed { 1 } else { 0 };\n\t\tif self.index >= len {\n\t\t\treturn None;\n\t\t}\n\t\tlet start_index = self.index;\n\t\tlet end_index = (self.index + 1) % self.subpath.len();\n\t\tself.index += 1;\n\n\t\tSome(self.subpath[start_index].to_bezier(&self.subpath[end_index]))\n\t}\n}\n\nimpl<PointId: Identifier> Debug for Subpath<PointId> {\n\tfn fmt(&self, f: &mut Formatter<'_>) -> Result {\n\t\tf.debug_struct(\"Subpath\").field(\"closed\", &self.closed).field(\"manipulator_groups\", &self.manipulator_groups).finish()\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/vector-types/src/subpath/solvers.rs",
    "content": "use crate::subpath::{Identifier, Subpath};\nuse crate::vector::algorithms::bezpath_algorithms::bezpath_is_inside_bezpath;\nuse crate::vector::misc::dvec2_to_point;\nuse glam::DVec2;\nuse kurbo::{Affine, BezPath, Shape};\n\nimpl<PointId: Identifier> Subpath<PointId> {\n\tpub fn contains_point(&self, point: DVec2) -> bool {\n\t\tself.to_bezpath().contains(dvec2_to_point(point))\n\t}\n\n\tpub fn to_bezpath(&self) -> BezPath {\n\t\tlet mut bezpath = kurbo::BezPath::new();\n\t\tlet mut out_handle;\n\n\t\tlet Some(first) = self.manipulator_groups.first() else { return bezpath };\n\t\tbezpath.move_to(dvec2_to_point(first.anchor));\n\t\tout_handle = first.out_handle;\n\n\t\tfor manipulator in self.manipulator_groups.iter().skip(1) {\n\t\t\tmatch (out_handle, manipulator.in_handle) {\n\t\t\t\t(Some(handle_start), Some(handle_end)) => bezpath.curve_to(dvec2_to_point(handle_start), dvec2_to_point(handle_end), dvec2_to_point(manipulator.anchor)),\n\t\t\t\t(None, None) => bezpath.line_to(dvec2_to_point(manipulator.anchor)),\n\t\t\t\t(None, Some(handle)) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(manipulator.anchor)),\n\t\t\t\t(Some(handle), None) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(manipulator.anchor)),\n\t\t\t}\n\t\t\tout_handle = manipulator.out_handle;\n\t\t}\n\n\t\tif self.closed {\n\t\t\tmatch (out_handle, first.in_handle) {\n\t\t\t\t(Some(handle_start), Some(handle_end)) => bezpath.curve_to(dvec2_to_point(handle_start), dvec2_to_point(handle_end), dvec2_to_point(first.anchor)),\n\t\t\t\t(None, None) => bezpath.line_to(dvec2_to_point(first.anchor)),\n\t\t\t\t(None, Some(handle)) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(first.anchor)),\n\t\t\t\t(Some(handle), None) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(first.anchor)),\n\t\t\t}\n\t\t\tbezpath.close_path();\n\t\t}\n\t\tbezpath\n\t}\n\n\t/// Returns `true` if this subpath is completely inside the `other` subpath.\n\tpub fn is_inside_subpath(&self, other: &Subpath<PointId>, accuracy: Option<f64>, minimum_separation: Option<f64>) -> bool {\n\t\tbezpath_is_inside_bezpath(&self.to_bezpath(), &other.to_bezpath(), accuracy, minimum_separation)\n\t}\n\n\t/// Return the min and max corners that represent the bounding box of the subpath. Return `None` if the subpath is empty.\n\tpub fn bounding_box(&self) -> Option<[DVec2; 2]> {\n\t\tself.iter()\n\t\t\t.map(|bezier| bezier.bounding_box())\n\t\t\t.map(|bbox| [DVec2::new(bbox.min_x(), bbox.min_y()), DVec2::new(bbox.max_x(), bbox.max_y())])\n\t\t\t.reduce(|bbox1, bbox2| [bbox1[0].min(bbox2[0]), bbox1[1].max(bbox2[1])])\n\t}\n\n\t/// Return the min and max corners that represent the bounding box of the subpath, after a given affine transform.\n\tpub fn bounding_box_with_transform(&self, transform: glam::DAffine2) -> Option<[DVec2; 2]> {\n\t\tself.iter()\n\t\t\t.map(|bezier| (Affine::new(transform.to_cols_array()) * bezier).bounding_box())\n\t\t\t.map(|bbox| [DVec2::new(bbox.min_x(), bbox.min_y()), DVec2::new(bbox.max_x(), bbox.max_y())])\n\t\t\t.reduce(|bbox1, bbox2| [bbox1[0].min(bbox2[0]), bbox1[1].max(bbox2[1])])\n\t}\n\n\t/// Return the min and max corners that represent the loose bounding box of the subpath (bounding box of all handles and anchors).\n\tpub fn loose_bounding_box(&self) -> Option<[DVec2; 2]> {\n\t\tself.manipulator_groups\n\t\t\t.iter()\n\t\t\t.flat_map(|group| [group.in_handle, group.out_handle, Some(group.anchor)])\n\t\t\t.flatten()\n\t\t\t.map(|pos| [pos, pos])\n\t\t\t.reduce(|bbox1, bbox2| [bbox1[0].min(bbox2[0]), bbox1[1].max(bbox2[1])])\n\t}\n\n\t/// Return the min and max corners that represent the loose bounding box of the subpath, after a given affine transform.\n\tpub fn loose_bounding_box_with_transform(&self, transform: glam::DAffine2) -> Option<[DVec2; 2]> {\n\t\tself.manipulator_groups\n\t\t\t.iter()\n\t\t\t.flat_map(|group| [group.in_handle, group.out_handle, Some(group.anchor)])\n\t\t\t.flatten()\n\t\t\t.map(|pos| transform.transform_point2(pos))\n\t\t\t.map(|pos| [pos, pos])\n\t\t\t.reduce(|bbox1, bbox2| [bbox1[0].min(bbox2[0]), bbox1[1].max(bbox2[1])])\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/vector-types/src/subpath/structs.rs",
    "content": "use crate::vector::algorithms::intersection::filtered_segment_intersections;\nuse crate::vector::misc::{dvec2_to_point, handles_to_segment};\nuse glam::{DAffine2, DVec2};\nuse kurbo::{CubicBez, Line, PathSeg, QuadBez, Shape};\nuse std::fmt::{Debug, Formatter, Result};\nuse std::hash::Hash;\n\n/// An id type used for each [ManipulatorGroup].\npub trait Identifier: Sized + Clone + PartialEq + Hash + 'static {\n\tfn new() -> Self;\n}\n\n/// Structure used to represent a single anchor with up to two optional associated handles along a `Subpath`\n#[derive(Copy, Clone, PartialEq)]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\npub struct ManipulatorGroup<PointId: Identifier> {\n\tpub anchor: DVec2,\n\tpub in_handle: Option<DVec2>,\n\tpub out_handle: Option<DVec2>,\n\tpub id: PointId,\n}\n\n// TODO: Remove once we no longer need to hash floats in Graphite\nimpl<PointId: Identifier> Hash for ManipulatorGroup<PointId> {\n\tfn hash<H: core::hash::Hasher>(&self, state: &mut H) {\n\t\tself.anchor.to_array().iter().for_each(|x| x.to_bits().hash(state));\n\t\tself.in_handle.is_some().hash(state);\n\t\tif let Some(in_handle) = self.in_handle {\n\t\t\tin_handle.to_array().iter().for_each(|x| x.to_bits().hash(state));\n\t\t}\n\t\tself.out_handle.is_some().hash(state);\n\t\tif let Some(out_handle) = self.out_handle {\n\t\t\tout_handle.to_array().iter().for_each(|x| x.to_bits().hash(state));\n\t\t}\n\t\tself.id.hash(state);\n\t}\n}\n\nimpl<PointId: Identifier> Debug for ManipulatorGroup<PointId> {\n\tfn fmt(&self, f: &mut Formatter<'_>) -> Result {\n\t\tf.debug_struct(\"ManipulatorGroup\")\n\t\t\t.field(\"anchor\", &self.anchor)\n\t\t\t.field(\"in_handle\", &self.in_handle)\n\t\t\t.field(\"out_handle\", &self.out_handle)\n\t\t\t.finish()\n\t}\n}\n\nimpl<PointId: Identifier> ManipulatorGroup<PointId> {\n\t/// Construct a new manipulator group from an anchor, in handle and out handle\n\tpub fn new(anchor: DVec2, in_handle: Option<DVec2>, out_handle: Option<DVec2>) -> Self {\n\t\tlet id = PointId::new();\n\t\tSelf { anchor, in_handle, out_handle, id }\n\t}\n\n\t/// Construct a new manipulator point with just an anchor position\n\tpub fn new_anchor(anchor: DVec2) -> Self {\n\t\tSelf::new(anchor, None, None)\n\t}\n\n\t/// Construct a new manipulator group from an anchor, in handle, out handle and an id\n\tpub fn new_with_id(anchor: DVec2, in_handle: Option<DVec2>, out_handle: Option<DVec2>, id: PointId) -> Self {\n\t\tSelf { anchor, in_handle, out_handle, id }\n\t}\n\n\t/// Construct a new manipulator point with just an anchor position and an id\n\tpub fn new_anchor_with_id(anchor: DVec2, id: PointId) -> Self {\n\t\tSelf::new_with_id(anchor, Some(anchor), Some(anchor), id)\n\t}\n\n\t/// Create a bezier curve that starts at the current manipulator group and finishes in the `end_group` manipulator group.\n\tpub fn to_bezier(&self, end_group: &ManipulatorGroup<PointId>) -> PathSeg {\n\t\tlet start = self.anchor;\n\t\tlet end = end_group.anchor;\n\t\tlet out_handle = self.out_handle;\n\t\tlet in_handle = end_group.in_handle;\n\n\t\tmatch (out_handle, in_handle) {\n\t\t\t(Some(handle1), Some(handle2)) => PathSeg::Cubic(CubicBez::new(dvec2_to_point(start), dvec2_to_point(handle1), dvec2_to_point(handle2), dvec2_to_point(end))),\n\t\t\t(Some(handle), None) | (None, Some(handle)) => PathSeg::Quad(QuadBez::new(dvec2_to_point(start), dvec2_to_point(handle), dvec2_to_point(end))),\n\t\t\t(None, None) => PathSeg::Line(Line::new(dvec2_to_point(start), dvec2_to_point(end))),\n\t\t}\n\t}\n\n\t/// Apply a transformation to all of the [ManipulatorGroup] points\n\tpub fn apply_transform(&mut self, affine_transform: DAffine2) {\n\t\tself.anchor = affine_transform.transform_point2(self.anchor);\n\t\tself.in_handle = self.in_handle.map(|in_handle| affine_transform.transform_point2(in_handle));\n\t\tself.out_handle = self.out_handle.map(|out_handle| affine_transform.transform_point2(out_handle));\n\t}\n\n\t/// Are all handles at finite positions\n\tpub fn is_finite(&self) -> bool {\n\t\tself.anchor.is_finite() && self.in_handle.is_none_or(|handle| handle.is_finite()) && self.out_handle.is_none_or(|handle| handle.is_finite())\n\t}\n\n\t/// Reverse directions of handles\n\tpub fn flip(mut self) -> Self {\n\t\tstd::mem::swap(&mut self.in_handle, &mut self.out_handle);\n\t\tself\n\t}\n\n\tpub fn has_in_handle(&self) -> bool {\n\t\tself.in_handle.map(|handle| Self::has_handle(self.anchor, handle)).unwrap_or(false)\n\t}\n\n\tpub fn has_out_handle(&self) -> bool {\n\t\tself.out_handle.map(|handle| Self::has_handle(self.anchor, handle)).unwrap_or(false)\n\t}\n\n\tfn has_handle(anchor: DVec2, handle: DVec2) -> bool {\n\t\t!((handle.x - anchor.x).abs() < f64::EPSILON && (handle.y - anchor.y).abs() < f64::EPSILON)\n\t}\n}\n\n#[derive(Copy, Clone)]\npub enum AppendType {\n\tIgnoreStart,\n\tSmoothJoin(f64),\n}\n\n#[derive(Copy, Clone, Eq, PartialEq, Hash)]\npub enum ArcType {\n\tOpen,\n\tClosed,\n\tPieSlice,\n}\n\n/// Representation of the handle point(s) in a bezier segment.\n#[derive(Copy, Clone, PartialEq, Debug)]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\npub enum BezierHandles {\n\tLinear,\n\t/// Handles for a quadratic curve.\n\tQuadratic {\n\t\t/// Point representing the location of the single handle.\n\t\thandle: DVec2,\n\t},\n\t/// Handles for a cubic curve.\n\tCubic {\n\t\t/// Point representing the location of the handle associated to the start point.\n\t\thandle_start: DVec2,\n\t\t/// Point representing the location of the handle associated to the end point.\n\t\thandle_end: DVec2,\n\t},\n}\n\nimpl std::hash::Hash for BezierHandles {\n\tfn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n\t\tstd::mem::discriminant(self).hash(state);\n\t\tmatch self {\n\t\t\tBezierHandles::Linear => {}\n\t\t\tBezierHandles::Quadratic { handle } => handle.to_array().map(|v| v.to_bits()).hash(state),\n\t\t\tBezierHandles::Cubic { handle_start, handle_end } => [handle_start, handle_end].map(|handle| handle.to_array().map(|v| v.to_bits())).hash(state),\n\t\t}\n\t}\n}\n\nimpl BezierHandles {\n\tpub fn is_cubic(&self) -> bool {\n\t\tmatches!(self, Self::Cubic { .. })\n\t}\n\n\tpub fn is_finite(&self) -> bool {\n\t\tmatch self {\n\t\t\tBezierHandles::Linear => true,\n\t\t\tBezierHandles::Quadratic { handle } => handle.is_finite(),\n\t\t\tBezierHandles::Cubic { handle_start, handle_end } => handle_start.is_finite() && handle_end.is_finite(),\n\t\t}\n\t}\n\n\t/// Get the coordinates of the bezier segment's first handle point. This represents the only handle in a quadratic segment.\n\tpub fn start(&self) -> Option<DVec2> {\n\t\tmatch *self {\n\t\t\tBezierHandles::Cubic { handle_start, .. } | BezierHandles::Quadratic { handle: handle_start } => Some(handle_start),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\t/// Get the coordinates of the second handle point. This will return `None` for a quadratic segment.\n\tpub fn end(&self) -> Option<DVec2> {\n\t\tmatch *self {\n\t\t\tBezierHandles::Cubic { handle_end, .. } => Some(handle_end),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tpub fn move_start(&mut self, delta: DVec2) {\n\t\tif let BezierHandles::Cubic { handle_start, .. } | BezierHandles::Quadratic { handle: handle_start } = self {\n\t\t\t*handle_start += delta\n\t\t}\n\t}\n\n\tpub fn move_end(&mut self, delta: DVec2) {\n\t\tif let BezierHandles::Cubic { handle_end, .. } = self {\n\t\t\t*handle_end += delta\n\t\t}\n\t}\n\n\t/// Returns a Bezier curve that results from applying the transformation function to each handle point in the Bezier.\n\t#[must_use]\n\tpub fn apply_transformation(&self, transformation_function: impl Fn(DVec2) -> DVec2) -> Self {\n\t\tmatch *self {\n\t\t\tBezierHandles::Linear => Self::Linear,\n\t\t\tBezierHandles::Quadratic { handle } => {\n\t\t\t\tlet handle = transformation_function(handle);\n\t\t\t\tSelf::Quadratic { handle }\n\t\t\t}\n\t\t\tBezierHandles::Cubic { handle_start, handle_end } => {\n\t\t\t\tlet handle_start = transformation_function(handle_start);\n\t\t\t\tlet handle_end = transformation_function(handle_end);\n\t\t\t\tSelf::Cubic { handle_start, handle_end }\n\t\t\t}\n\t\t}\n\t}\n\n\t#[must_use]\n\tpub fn reversed(self) -> Self {\n\t\tmatch self {\n\t\t\tBezierHandles::Cubic { handle_start, handle_end } => Self::Cubic {\n\t\t\t\thandle_start: handle_end,\n\t\t\t\thandle_end: handle_start,\n\t\t\t},\n\t\t\t_ => self,\n\t\t}\n\t}\n}\n\n/// Representation of a bezier curve with 2D points.\n#[derive(Copy, Clone, PartialEq)]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\npub struct Bezier {\n\t/// Start point of the bezier curve.\n\tpub start: DVec2,\n\t/// End point of the bezier curve.\n\tpub end: DVec2,\n\t/// Handles of the bezier curve.\n\tpub handles: BezierHandles,\n}\n\nimpl Debug for Bezier {\n\tfn fmt(&self, f: &mut Formatter<'_>) -> Result {\n\t\tlet mut debug_struct = f.debug_struct(\"Bezier\");\n\t\tlet mut debug_struct_ref = debug_struct.field(\"start\", &self.start);\n\t\tdebug_struct_ref = match self.handles {\n\t\t\tBezierHandles::Linear => debug_struct_ref,\n\t\t\tBezierHandles::Quadratic { handle } => debug_struct_ref.field(\"handle\", &handle),\n\t\t\tBezierHandles::Cubic { handle_start, handle_end } => debug_struct_ref.field(\"handle_start\", &handle_start).field(\"handle_end\", &handle_end),\n\t\t};\n\t\tdebug_struct_ref.field(\"end\", &self.end).finish()\n\t}\n}\n\n/// Functionality for the getters and setters of the various points in a Bezier\nimpl Bezier {\n\t/// Set the coordinates of the start point.\n\tpub fn set_start(&mut self, s: DVec2) {\n\t\tself.start = s;\n\t}\n\n\t/// Set the coordinates of the end point.\n\tpub fn set_end(&mut self, e: DVec2) {\n\t\tself.end = e;\n\t}\n\n\t/// Set the coordinates of the first handle point. This represents the only handle in a quadratic segment. If used on a linear segment, it will be changed to a quadratic.\n\tpub fn set_handle_start(&mut self, h1: DVec2) {\n\t\tmatch self.handles {\n\t\t\tBezierHandles::Linear => {\n\t\t\t\tself.handles = BezierHandles::Quadratic { handle: h1 };\n\t\t\t}\n\t\t\tBezierHandles::Quadratic { ref mut handle } => {\n\t\t\t\t*handle = h1;\n\t\t\t}\n\t\t\tBezierHandles::Cubic { ref mut handle_start, .. } => {\n\t\t\t\t*handle_start = h1;\n\t\t\t}\n\t\t};\n\t}\n\n\t/// Set the coordinates of the second handle point. This will convert both linear and quadratic segments into cubic ones. For a linear segment, the first handle will be set to the start point.\n\tpub fn set_handle_end(&mut self, h2: DVec2) {\n\t\tmatch self.handles {\n\t\t\tBezierHandles::Linear => {\n\t\t\t\tself.handles = BezierHandles::Cubic {\n\t\t\t\t\thandle_start: self.start,\n\t\t\t\t\thandle_end: h2,\n\t\t\t\t};\n\t\t\t}\n\t\t\tBezierHandles::Quadratic { handle } => {\n\t\t\t\tself.handles = BezierHandles::Cubic { handle_start: handle, handle_end: h2 };\n\t\t\t}\n\t\t\tBezierHandles::Cubic { ref mut handle_end, .. } => {\n\t\t\t\t*handle_end = h2;\n\t\t\t}\n\t\t};\n\t}\n\n\t/// Get the coordinates of the bezier segment's start point.\n\tpub fn start(&self) -> DVec2 {\n\t\tself.start\n\t}\n\n\t/// Get the coordinates of the bezier segment's end point.\n\tpub fn end(&self) -> DVec2 {\n\t\tself.end\n\t}\n\n\t/// Get the coordinates of the bezier segment's first handle point. This represents the only handle in a quadratic segment.\n\tpub fn handle_start(&self) -> Option<DVec2> {\n\t\tself.handles.start()\n\t}\n\n\t/// Get the coordinates of the second handle point. This will return `None` for a quadratic segment.\n\tpub fn handle_end(&self) -> Option<DVec2> {\n\t\tself.handles.end()\n\t}\n\n\t/// Get an iterator over the coordinates of all points in a vector.\n\t/// - For a linear segment, the order of the points will be: `start`, `end`.\n\t/// - For a quadratic segment, the order of the points will be: `start`, `handle`, `end`.\n\t/// - For a cubic segment, the order of the points will be: `start`, `handle_start`, `handle_end`, `end`.\n\tpub fn get_points(&self) -> impl Iterator<Item = DVec2> + use<> {\n\t\tmatch self.handles {\n\t\t\tBezierHandles::Linear => [self.start, self.end, DVec2::ZERO, DVec2::ZERO].into_iter().take(2),\n\t\t\tBezierHandles::Quadratic { handle } => [self.start, handle, self.end, DVec2::ZERO].into_iter().take(3),\n\t\t\tBezierHandles::Cubic { handle_start, handle_end } => [self.start, handle_start, handle_end, self.end].into_iter().take(4),\n\t\t}\n\t}\n\n\t// TODO: Consider removing this function\n\t/// Create a linear bezier using the provided coordinates as the start and end points.\n\tpub fn from_linear_coordinates(x1: f64, y1: f64, x2: f64, y2: f64) -> Self {\n\t\tBezier {\n\t\t\tstart: DVec2::new(x1, y1),\n\t\t\thandles: BezierHandles::Linear,\n\t\t\tend: DVec2::new(x2, y2),\n\t\t}\n\t}\n\n\t/// Create a linear bezier using the provided DVec2s as the start and end points.\n\tpub fn from_linear_dvec2(p1: DVec2, p2: DVec2) -> Self {\n\t\tBezier {\n\t\t\tstart: p1,\n\t\t\thandles: BezierHandles::Linear,\n\t\t\tend: p2,\n\t\t}\n\t}\n\n\t// TODO: Consider removing this function\n\t/// Create a quadratic bezier using the provided coordinates as the start, handle, and end points.\n\tpub fn from_quadratic_coordinates(x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64) -> Self {\n\t\tBezier {\n\t\t\tstart: DVec2::new(x1, y1),\n\t\t\thandles: BezierHandles::Quadratic { handle: DVec2::new(x2, y2) },\n\t\t\tend: DVec2::new(x3, y3),\n\t\t}\n\t}\n\n\t/// Create a quadratic bezier using the provided DVec2s as the start, handle, and end points.\n\tpub fn from_quadratic_dvec2(p1: DVec2, p2: DVec2, p3: DVec2) -> Self {\n\t\tBezier {\n\t\t\tstart: p1,\n\t\t\thandles: BezierHandles::Quadratic { handle: p2 },\n\t\t\tend: p3,\n\t\t}\n\t}\n\n\t// TODO: Consider removing this function\n\t/// Create a cubic bezier using the provided coordinates as the start, handles, and end points.\n\t#[allow(clippy::too_many_arguments)]\n\tpub fn from_cubic_coordinates(x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64, x4: f64, y4: f64) -> Self {\n\t\tBezier {\n\t\t\tstart: DVec2::new(x1, y1),\n\t\t\thandles: BezierHandles::Cubic {\n\t\t\t\thandle_start: DVec2::new(x2, y2),\n\t\t\t\thandle_end: DVec2::new(x3, y3),\n\t\t\t},\n\t\t\tend: DVec2::new(x4, y4),\n\t\t}\n\t}\n\n\t/// Create a cubic bezier using the provided DVec2s as the start, handles, and end points.\n\tpub fn from_cubic_dvec2(p1: DVec2, p2: DVec2, p3: DVec2, p4: DVec2) -> Self {\n\t\tBezier {\n\t\t\tstart: p1,\n\t\t\thandles: BezierHandles::Cubic { handle_start: p2, handle_end: p3 },\n\t\t\tend: p4,\n\t\t}\n\t}\n\n\t/// Returns a Bezier curve that results from applying the transformation function to each point in the Bezier.\n\tpub fn apply_transformation(&self, transformation_function: impl Fn(DVec2) -> DVec2) -> Bezier {\n\t\tSelf {\n\t\t\tstart: transformation_function(self.start),\n\t\t\tend: transformation_function(self.end),\n\t\t\thandles: self.handles.apply_transformation(transformation_function),\n\t\t}\n\t}\n\n\tpub fn intersections(&self, other: &Bezier, accuracy: Option<f64>, minimum_separation: Option<f64>) -> Vec<f64> {\n\t\tlet this = handles_to_segment(self.start, self.handles, self.end);\n\t\tlet other = handles_to_segment(other.start, other.handles, other.end);\n\t\tfiltered_segment_intersections(this, other, accuracy, minimum_separation)\n\t}\n\n\tpub fn winding(&self, point: DVec2) -> i32 {\n\t\tlet this = handles_to_segment(self.start, self.handles, self.end);\n\t\tthis.winding(dvec2_to_point(point))\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/vector-types/src/subpath/transform.rs",
    "content": "use super::structs::Identifier;\nuse super::*;\nuse glam::{DAffine2, DVec2};\n\n/// Functionality that transforms Subpaths, such as split, reduce, offset, etc.\nimpl<PointId: Identifier> Subpath<PointId> {\n\t/// Returns [ManipulatorGroup]s with a reversed winding order.\n\tfn reverse_manipulator_groups(manipulator_groups: &[ManipulatorGroup<PointId>]) -> Vec<ManipulatorGroup<PointId>> {\n\t\tmanipulator_groups\n\t\t\t.iter()\n\t\t\t.rev()\n\t\t\t.map(|group| ManipulatorGroup {\n\t\t\t\tanchor: group.anchor,\n\t\t\t\tin_handle: group.out_handle,\n\t\t\t\tout_handle: group.in_handle,\n\t\t\t\tid: PointId::new(),\n\t\t\t})\n\t\t\t.collect::<Vec<ManipulatorGroup<PointId>>>()\n\t}\n\n\t/// Returns a [Subpath] with a reversed winding order.\n\t/// Note that a reversed closed subpath will start on the same manipulator group and simply wind the other direction\n\tpub fn reverse(&self) -> Subpath<PointId> {\n\t\tlet mut reversed = Subpath::reverse_manipulator_groups(self.manipulator_groups());\n\t\tif self.closed {\n\t\t\treversed.rotate_right(1);\n\t\t};\n\t\tSubpath {\n\t\t\tmanipulator_groups: reversed,\n\t\t\tclosed: self.closed,\n\t\t}\n\t}\n\n\t/// Apply a transformation to all of the [ManipulatorGroup]s in the [Subpath].\n\tpub fn apply_transform(&mut self, affine_transform: DAffine2) {\n\t\tfor manipulator_group in &mut self.manipulator_groups {\n\t\t\tmanipulator_group.apply_transform(affine_transform);\n\t\t}\n\t}\n\n\t/// Returns a subpath that results from rotating this subpath around the origin by the given angle (in radians).\n\tpub fn rotate(&self, angle: f64) -> Subpath<PointId> {\n\t\tlet mut rotated_subpath = self.clone();\n\n\t\tlet affine_transform: DAffine2 = DAffine2::from_angle(angle);\n\t\trotated_subpath.apply_transform(affine_transform);\n\n\t\trotated_subpath\n\t}\n\n\t/// Returns a subpath that results from rotating this subpath around the provided point by the given angle (in radians).\n\tpub fn rotate_about_point(&self, angle: f64, pivot: DVec2) -> Subpath<PointId> {\n\t\t// Translate before and after the rotation to account for the pivot\n\t\tlet translate: DAffine2 = DAffine2::from_translation(pivot);\n\t\tlet rotate: DAffine2 = DAffine2::from_angle(angle);\n\t\tlet translate_inverse = translate.inverse();\n\n\t\tlet mut rotated_subpath = self.clone();\n\t\trotated_subpath.apply_transform(translate * rotate * translate_inverse);\n\t\trotated_subpath\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/vector-types/src/vector/algorithms/bezpath_algorithms.rs",
    "content": "use super::intersection::bezpath_intersections;\nuse super::poisson_disk::poisson_disk_sample;\nuse super::util::pathseg_tangent;\nuse crate::vector::algorithms::offset_subpath::MAX_ABSOLUTE_DIFFERENCE;\nuse crate::vector::misc::{PointSpacingType, dvec2_to_point, point_to_dvec2};\nuse core_types::math::polynomial::pathseg_to_parametric_polynomial;\nuse glam::{DMat2, DVec2};\nuse kurbo::common::{solve_cubic, solve_quadratic};\nuse kurbo::{BezPath, CubicBez, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveDeriv, PathEl, PathSeg, Point, QuadBez, Rect, Shape, Vec2};\nuse std::f64::consts::{FRAC_PI_2, PI};\n\n/// Splits the [`BezPath`] at segment index at `t` value which lie in the range of [0, 1].\n/// Returns [`None`] if the given [`BezPath`] has no segments or `t` is within f64::EPSILON of 0 or 1.\npub fn split_bezpath_at_segment(bezpath: &BezPath, segment_index: usize, t: f64) -> Option<(BezPath, BezPath)> {\n\tif t <= f64::EPSILON || (1. - t) <= f64::EPSILON || bezpath.segments().count() == 0 {\n\t\treturn None;\n\t}\n\n\t// Get the segment which lies at the split.\n\tlet segment = bezpath.get_seg(segment_index + 1).unwrap();\n\n\t// Divide the segment.\n\tlet first_segment = segment.subsegment(0.0..t);\n\tlet second_segment = segment.subsegment(t..1.);\n\n\tlet mut first_bezpath = BezPath::new();\n\tlet mut second_bezpath = BezPath::new();\n\n\t// Append the segments up to the subdividing segment from original bezpath to first bezpath.\n\tfor segment in bezpath.segments().take(segment_index) {\n\t\tif first_bezpath.elements().is_empty() {\n\t\t\tfirst_bezpath.move_to(segment.start());\n\t\t}\n\t\tfirst_bezpath.push(segment.as_path_el());\n\t}\n\n\t// Append the first segment of the subdivided segment.\n\tif first_bezpath.elements().is_empty() {\n\t\tfirst_bezpath.move_to(first_segment.start());\n\t}\n\tfirst_bezpath.push(first_segment.as_path_el());\n\n\t// Append the second segment of the subdivided segment in the second bezpath.\n\tif second_bezpath.elements().is_empty() {\n\t\tsecond_bezpath.move_to(second_segment.start());\n\t}\n\tsecond_bezpath.push(second_segment.as_path_el());\n\n\t// Append the segments after the subdividing segment from original bezpath to second bezpath.\n\tfor segment in bezpath.segments().skip(segment_index + 1) {\n\t\tif second_bezpath.elements().is_empty() {\n\t\t\tsecond_bezpath.move_to(segment.start());\n\t\t}\n\t\tsecond_bezpath.push(segment.as_path_el());\n\t}\n\n\tSome((first_bezpath, second_bezpath))\n}\n\n/// Splits the [`BezPath`] at a `t` value which lies in the range of [0, 1].\n/// Returns [`None`] if the given [`BezPath`] has no segments.\npub fn split_bezpath(bezpath: &BezPath, t_value: TValue) -> Option<(BezPath, BezPath)> {\n\tif bezpath.segments().count() == 0 {\n\t\treturn None;\n\t}\n\n\t// Get the segment which lies at the split.\n\tlet (segment_index, t) = eval_bezpath(bezpath, t_value, None);\n\tsplit_bezpath_at_segment(bezpath, segment_index, t)\n}\n\npub fn evaluate_bezpath(bezpath: &BezPath, t_value: TValue, segments_length: Option<&[f64]>) -> Point {\n\tlet (segment_index, t) = eval_bezpath(bezpath, t_value, segments_length);\n\tbezpath.get_seg(segment_index + 1).unwrap().eval(t)\n}\n\npub fn tangent_on_bezpath(bezpath: &BezPath, t_value: TValue, segments_length: Option<&[f64]>) -> Point {\n\tlet (segment_index, t) = eval_bezpath(bezpath, t_value, segments_length);\n\tlet segment = bezpath.get_seg(segment_index + 1).unwrap();\n\n\tmatch segment {\n\t\tPathSeg::Line(line) => line.deriv().eval(t),\n\t\tPathSeg::Quad(quad_bez) => quad_bez.deriv().eval(t),\n\t\tPathSeg::Cubic(cubic_bez) => cubic_bez.deriv().eval(t),\n\t}\n}\n\npub fn sample_polyline_on_bezpath(\n\tbezpath: BezPath,\n\tpoint_spacing_type: PointSpacingType,\n\tamount: f64,\n\tstart_offset: f64,\n\tstop_offset: f64,\n\tadaptive_spacing: bool,\n\tsegments_length: &[f64],\n) -> Option<BezPath> {\n\tlet mut sample_bezpath = BezPath::new();\n\n\tlet was_closed = matches!(bezpath.elements().last(), Some(PathEl::ClosePath));\n\n\t// Calculate the total length of the collected segments.\n\tlet total_length: f64 = segments_length.iter().sum();\n\n\t// Adjust the usable length by subtracting start and stop offsets.\n\tlet mut used_length = total_length - start_offset - stop_offset;\n\n\t// Sanity check that the usable length is positive.\n\tif used_length <= 0. {\n\t\treturn None;\n\t}\n\n\tconst SAFETY_MAX_COUNT: f64 = 10_000. - 1.;\n\n\t// Determine the number of points to generate along the path.\n\tlet sample_count = match point_spacing_type {\n\t\tPointSpacingType::Separation => {\n\t\t\tlet spacing = amount.min(used_length - f64::EPSILON);\n\n\t\t\tif adaptive_spacing {\n\t\t\t\t// Calculate point count to evenly distribute points while covering the entire path.\n\t\t\t\t// With adaptive spacing, we widen or narrow the points as necessary to ensure the last point is always at the end of the path.\n\t\t\t\t(used_length / spacing).round().min(SAFETY_MAX_COUNT)\n\t\t\t} else {\n\t\t\t\t// Calculate point count based on exact spacing, which may not cover the entire path.\n\t\t\t\t// Without adaptive spacing, we just evenly space the points at the exact specified spacing, usually falling short before the end of the path.\n\t\t\t\tlet count = (used_length / spacing + f64::EPSILON).floor().min(SAFETY_MAX_COUNT);\n\t\t\t\tif count != SAFETY_MAX_COUNT {\n\t\t\t\t\tused_length -= used_length % spacing;\n\t\t\t\t}\n\t\t\t\tcount\n\t\t\t}\n\t\t}\n\t\tPointSpacingType::Quantity => (amount - 1.).floor().clamp(1., SAFETY_MAX_COUNT),\n\t};\n\n\t// Skip if there are no points to generate.\n\tif sample_count < 1. {\n\t\treturn None;\n\t}\n\n\t// Decide how many loop-iterations: if closed, skip the last duplicate point\n\tlet sample_count_usize = sample_count as usize;\n\tlet max_i = if was_closed { sample_count_usize } else { sample_count_usize + 1 };\n\n\t// Generate points along the path based on calculated intervals.\n\tlet mut length_up_to_previous_segment = 0.;\n\tlet mut next_segment_index = 0;\n\n\tfor count in 0..max_i {\n\t\tlet fraction = count as f64 / sample_count;\n\t\tlet length_up_to_next_sample_point = fraction * used_length + start_offset;\n\t\tlet mut next_length = length_up_to_next_sample_point - length_up_to_previous_segment;\n\t\tlet mut next_segment_length = segments_length[next_segment_index];\n\n\t\t// Keep moving to the next segment while the length up to the next sample point is greater than the length up to the current segment.\n\t\twhile next_length > next_segment_length {\n\t\t\tif next_segment_index == segments_length.len() - 1 {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tlength_up_to_previous_segment += next_segment_length;\n\t\t\tnext_length = length_up_to_next_sample_point - length_up_to_previous_segment;\n\t\t\tnext_segment_index += 1;\n\t\t\tnext_segment_length = segments_length[next_segment_index];\n\t\t}\n\n\t\tlet t = (next_length / next_segment_length).clamp(0., 1.);\n\n\t\tlet segment = bezpath.get_seg(next_segment_index + 1).unwrap();\n\t\tlet t = eval_pathseg_euclidean(segment, t, DEFAULT_ACCURACY);\n\t\tlet point = segment.eval(t);\n\n\t\tif sample_bezpath.elements().is_empty() {\n\t\t\tsample_bezpath.move_to(point)\n\t\t} else {\n\t\t\tsample_bezpath.line_to(point)\n\t\t}\n\t}\n\n\tif was_closed {\n\t\tsample_bezpath.close_path();\n\t}\n\n\tSome(sample_bezpath)\n}\n\n#[derive(Debug, Clone, Copy)]\npub enum TValue {\n\tParametric(f64),\n\tEuclidean(f64),\n}\n\n/// Default LUT step size in `compute_lookup_table` function.\npub const DEFAULT_LUT_STEP_SIZE: usize = 10;\n\n/// Return a selection of equidistant points on the bezier curve.\n/// If no value is provided for `steps`, then the function will default `steps` to be 10.\npub fn pathseg_compute_lookup_table(segment: PathSeg, steps: Option<usize>, eucliean: bool) -> impl Iterator<Item = DVec2> {\n\tlet steps = steps.unwrap_or(DEFAULT_LUT_STEP_SIZE);\n\n\t(0..=steps).map(move |t| {\n\t\tlet tvalue = if eucliean {\n\t\t\tTValue::Euclidean(t as f64 / steps as f64)\n\t\t} else {\n\t\t\tTValue::Parametric(t as f64 / steps as f64)\n\t\t};\n\t\tlet t = eval_pathseg(segment, tvalue);\n\t\tpoint_to_dvec2(segment.eval(t))\n\t})\n}\n\n/// Returns an `Iterator` containing all possible parametric `t`-values at the given `x`-coordinate.\npub fn pathseg_find_tvalues_for_x(segment: PathSeg, x: f64) -> impl Iterator<Item = f64> + use<> {\n\tmatch segment {\n\t\tPathSeg::Line(Line { p0, p1 }) => {\n\t\t\t// If the transformed linear bezier is on the x-axis, `a` and `b` will both be zero and `solve_linear` will return no roots\n\t\t\tlet a = p1.x - p0.x;\n\t\t\tlet b = p0.x - x;\n\n\t\t\t// Find the roots of the linear equation `ax + b`.\n\t\t\t// There exist roots when `a` is not 0\n\t\t\tif a.abs() > MAX_ABSOLUTE_DIFFERENCE { [Some(-b / a), None, None] } else { [None; 3] }\n\t\t}\n\t\tPathSeg::Quad(QuadBez { p0, p1, p2 }) => {\n\t\t\tlet a = p2.x - 2.0 * p1.x + p0.x;\n\t\t\tlet b = 2.0 * (p1.x - p0.x);\n\t\t\tlet c = p0.x - x;\n\t\t\tlet r = solve_quadratic(c, b, a);\n\t\t\t[r.first().copied(), r.get(1).copied(), None]\n\t\t}\n\t\tPathSeg::Cubic(CubicBez { p0, p1, p2, p3 }) => {\n\t\t\tlet a = p3.x - 3.0 * p2.x + 3.0 * p1.x - p0.x;\n\t\t\tlet b = 3.0 * (p2.x - 2.0 * p1.x + p0.x);\n\t\t\tlet c = 3.0 * (p1.x - p0.x);\n\t\t\tlet d = p0.x - x;\n\t\t\tlet r = solve_cubic(d, c, b, a);\n\t\t\t[r.first().copied(), r.get(1).copied(), r.get(2).copied()]\n\t\t}\n\t}\n\t.into_iter()\n\t.flatten()\n\t.filter(|&t| (0.0..1.).contains(&t))\n}\n\n/// Find the `t`-value(s) such that the normal(s) at `t` pass through the specified point.\npub fn pathseg_normals_to_point(segment: PathSeg, point: Point) -> Vec<f64> {\n\t// We solve deriv(t) dot (self(t) - point) = 0.\n\tlet (mut x, mut y) = pathseg_to_parametric_polynomial(segment);\n\tlet x = x.coefficients_mut();\n\tlet y = y.coefficients_mut();\n\tx[0] -= point.x;\n\ty[0] -= point.y;\n\tlet poly = polycool::Poly::new([\n\t\tx[0] * x[1] + y[0] * y[1],\n\t\tx[1] * x[1] + y[1] * y[1] + 2. * (x[0] * x[2] + y[0] * y[2]),\n\t\t3. * (x[2] * x[1] + y[2] * y[1]) + 3. * (x[0] * x[3] + y[0] * y[3]),\n\t\t4. * (x[3] * x[1] + y[3] * y[1]) + 2. * (x[2] * x[2] + y[2] * y[2]),\n\t\t5. * (x[3] * x[2] + y[3] * y[2]),\n\t\t3. * (x[3] * x[3] + y[3] * y[3]),\n\t]);\n\tpoly.roots_between(0., 1., 1e-8).to_vec()\n}\n\n/// Find the `t`-value(s) such that the tangent(s) at `t` pass through the given point.\npub fn pathseg_tangents_to_point(segment: PathSeg, point: Point) -> Vec<f64> {\n\tsegment.to_cubic().tangents_to_point(point).to_vec()\n}\n\n/// Return the subsegment for the given [TValue] range. Returns None if parametric value of `t1` is greater than `t2`.\npub fn trim_pathseg(segment: PathSeg, t1: TValue, t2: TValue) -> Option<PathSeg> {\n\tlet t1 = eval_pathseg(segment, t1);\n\tlet t2 = eval_pathseg(segment, t2);\n\n\tif t1 > t2 { None } else { Some(segment.subsegment(t1..t2)) }\n}\n\npub fn eval_pathseg(segment: PathSeg, t_value: TValue) -> f64 {\n\tmatch t_value {\n\t\tTValue::Parametric(t) => t,\n\t\tTValue::Euclidean(t) => eval_pathseg_euclidean(segment, t, DEFAULT_ACCURACY),\n\t}\n}\n\n/// Return an approximation of the length centroid, together with the length, of the bezier curve.\n///\n/// The length centroid is the center of mass for the arc length of the Bezier segment.\n/// An infinitely thin wire forming the Bezier segment's shape would balance at this point.\n///\n/// - `accuracy` is used to approximate the curve.\npub(crate) fn pathseg_length_centroid_and_length(segment: PathSeg, accuracy: Option<f64>) -> (Vec2, f64) {\n\tmatch segment {\n\t\tPathSeg::Line(line) => ((line.start().to_vec2() + line.end().to_vec2()) / 2., (line.start().to_vec2() - line.end().to_vec2()).length()),\n\t\tPathSeg::Quad(quad_bez) => {\n\t\t\tlet QuadBez { p0, p1, p2 } = quad_bez;\n\t\t\t// Use Casteljau subdivision, noting that the length is more than the straight line distance from start to end but less than the straight line distance through the handles\n\t\t\tfn recurse(a0: Vec2, a1: Vec2, a2: Vec2, accuracy: f64, level: u8) -> (f64, Vec2) {\n\t\t\t\tlet lower = (a2 - a1).length();\n\t\t\t\tlet upper = (a1 - a0).length() + (a2 - a1).length();\n\t\t\t\tif upper - lower <= 2. * accuracy || level >= 8 {\n\t\t\t\t\tlet length = (lower + upper) / 2.;\n\t\t\t\t\treturn (length, length * (a0 + a1 + a2) / 3.);\n\t\t\t\t}\n\n\t\t\t\tlet b1 = 0.5 * (a0 + a1);\n\t\t\t\tlet c1 = 0.5 * (a1 + a2);\n\t\t\t\tlet b2 = 0.5 * (b1 + c1);\n\n\t\t\t\tlet (length1, centroid_part1) = recurse(a0, b1, b2, 0.5 * accuracy, level + 1);\n\t\t\t\tlet (length2, centroid_part2) = recurse(b2, c1, a2, 0.5 * accuracy, level + 1);\n\t\t\t\t(length1 + length2, centroid_part1 + centroid_part2)\n\t\t\t}\n\n\t\t\tlet (length, centroid_parts) = recurse(p0.to_vec2(), p1.to_vec2(), p2.to_vec2(), accuracy.unwrap_or_default(), 0);\n\t\t\t(centroid_parts / length, length)\n\t\t}\n\t\tPathSeg::Cubic(cubic_bez) => {\n\t\t\tlet CubicBez { p0, p1, p2, p3 } = cubic_bez;\n\n\t\t\t// Use Casteljau subdivision, noting that the length is more than the straight line distance from start to end but less than the straight line distance through the handles\n\t\t\tfn recurse(a0: Vec2, a1: Vec2, a2: Vec2, a3: Vec2, accuracy: f64, level: u8) -> (f64, Vec2) {\n\t\t\t\tlet lower = (a3 - a0).length();\n\t\t\t\tlet upper = (a1 - a0).length() + (a2 - a1).length() + (a3 - a2).length();\n\t\t\t\tif upper - lower <= 2. * accuracy || level >= 8 {\n\t\t\t\t\tlet length = (lower + upper) / 2.;\n\t\t\t\t\treturn (length, length * (a0 + a1 + a2 + a3) / 4.);\n\t\t\t\t}\n\n\t\t\t\tlet b1 = 0.5 * (a0 + a1);\n\t\t\t\tlet t0 = 0.5 * (a1 + a2);\n\t\t\t\tlet c1 = 0.5 * (a2 + a3);\n\t\t\t\tlet b2 = 0.5 * (b1 + t0);\n\t\t\t\tlet c2 = 0.5 * (t0 + c1);\n\t\t\t\tlet b3 = 0.5 * (b2 + c2);\n\n\t\t\t\tlet (length1, centroid_part1) = recurse(a0, b1, b2, b3, 0.5 * accuracy, level + 1);\n\t\t\t\tlet (length2, centroid_part2) = recurse(b3, c2, c1, a3, 0.5 * accuracy, level + 1);\n\t\t\t\t(length1 + length2, centroid_part1 + centroid_part2)\n\t\t\t}\n\n\t\t\tlet (length, centroid_parts) = recurse(p0.to_vec2(), p1.to_vec2(), p2.to_vec2(), p3.to_vec2(), accuracy.unwrap_or_default(), 0);\n\t\t\t(centroid_parts / length, length)\n\t\t}\n\t}\n}\n\n/// Finds the t value of point on the given path segment i.e fractional distance along the segment's total length.\n/// It uses a binary search to find the value `t` such that the ratio `length_up_to_t / total_length` approximates the input `distance`.\npub fn eval_pathseg_euclidean(segment: PathSeg, distance: f64, accuracy: f64) -> f64 {\n\tlet mut low_t = 0.;\n\tlet mut mid_t = 0.5;\n\tlet mut high_t = 1.;\n\n\tlet total_length = segment.perimeter(accuracy);\n\n\tif !total_length.is_finite() || total_length <= f64::EPSILON {\n\t\treturn 0.;\n\t}\n\n\tlet distance = distance.clamp(0., 1.);\n\n\twhile high_t - low_t > accuracy {\n\t\tlet current_length = segment.subsegment(0.0..mid_t).perimeter(accuracy);\n\t\tlet current_distance = current_length / total_length;\n\n\t\tif current_distance > distance {\n\t\t\thigh_t = mid_t;\n\t\t} else {\n\t\t\tlow_t = mid_t;\n\t\t}\n\t\tmid_t = (high_t + low_t) / 2.;\n\t}\n\n\tmid_t\n}\n\n/// Converts from a bezpath (composed of multiple segments) to a point along a certain segment represented.\n/// The returned tuple represents the segment index and the `t` value along that segment.\n/// Both the input global `t` value and the output `t` value are in euclidean space, meaning there is a constant rate of change along the arc length.\nfn eval_bazpath_to_euclidean(bezpath: &BezPath, global_t: f64, lengths: &[f64], total_length: f64) -> (usize, f64) {\n\tlet mut accumulator = 0.;\n\tfor (index, length) in lengths.iter().enumerate() {\n\t\tlet length_ratio = length / total_length;\n\t\tif (index == 0 || accumulator <= global_t) && global_t <= accumulator + length_ratio {\n\t\t\treturn (index, ((global_t - accumulator) / length_ratio).clamp(0., 1.));\n\t\t}\n\t\taccumulator += length_ratio;\n\t}\n\t(bezpath.segments().count() - 1, 1.)\n}\n\n/// Convert a [TValue] to a parametric `(segment_index, t)` tuple.\n/// - Asserts that `t` values contained within the `TValue` argument lie in the range [0, 1].\nfn eval_bezpath(bezpath: &BezPath, t: TValue, precomputed_segments_length: Option<&[f64]>) -> (usize, f64) {\n\tlet segment_count = bezpath.segments().count();\n\tassert!(segment_count >= 1);\n\n\tmatch t {\n\t\tTValue::Euclidean(t) => {\n\t\t\tlet computed_segments_length;\n\n\t\t\tlet segments_length = if let Some(segments_length) = precomputed_segments_length {\n\t\t\t\tsegments_length\n\t\t\t} else {\n\t\t\t\tcomputed_segments_length = bezpath.segments().map(|segment| segment.perimeter(DEFAULT_ACCURACY)).collect::<Vec<f64>>();\n\t\t\t\tcomputed_segments_length.as_slice()\n\t\t\t};\n\n\t\t\tlet total_length = segments_length.iter().sum();\n\n\t\t\tlet (segment_index, t) = eval_bazpath_to_euclidean(bezpath, t, segments_length, total_length);\n\t\t\tlet segment = bezpath.get_seg(segment_index + 1).unwrap();\n\t\t\t(segment_index, eval_pathseg_euclidean(segment, t, DEFAULT_ACCURACY))\n\t\t}\n\t\tTValue::Parametric(t) => {\n\t\t\tassert!((0.0..=1.).contains(&t));\n\n\t\t\tif t == 1. {\n\t\t\t\treturn (segment_count - 1, 1.);\n\t\t\t}\n\n\t\t\tlet scaled_t = t * segment_count as f64;\n\t\t\tlet segment_index = scaled_t.floor() as usize;\n\t\t\tlet t = scaled_t - segment_index as f64;\n\n\t\t\t(segment_index, t)\n\t\t}\n\t}\n}\n\n/// Randomly places points across the filled surface of this subpath (which is assumed to be closed).\n/// The `separation_disk_diameter` determines the minimum distance between all points from one another.\n/// Conceptually, this works by \"throwing a dart\" at the subpath's bounding box and keeping the dart only if:\n/// - It's inside the shape\n/// - It's not closer than `separation_disk_diameter` to any other point from a previous accepted dart throw\n///\n/// This repeats until accepted darts fill all possible areas between one another.\n///\n/// While the conceptual process described above asymptotically slows down and is never guaranteed to produce a maximal set in finite time,\n/// this is implemented with an algorithm that produces a maximal set in O(n) time. The slowest part is actually checking if points are inside the subpath shape.\npub fn poisson_disk_points(bezpath_index: usize, bezpaths: &[(BezPath, Rect)], separation_disk_diameter: f64, rng: impl FnMut() -> f64) -> Vec<DVec2> {\n\tlet (this_bezpath, this_bbox) = bezpaths[bezpath_index].clone();\n\n\tif this_bezpath.elements().is_empty() {\n\t\treturn Vec::new();\n\t}\n\n\tlet point_in_shape_checker = |point: DVec2| {\n\t\t// Check against all paths the point is contained in to compute the correct winding number\n\t\tlet mut number = 0;\n\n\t\tfor (i, (shape, bbox)) in bezpaths.iter().enumerate() {\n\t\t\tif bbox.x0 > point.x || bbox.y0 > point.y || bbox.x1 < point.x || bbox.y1 < point.y {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet winding = shape.winding(dvec2_to_point(point));\n\t\t\tif winding == 0 && i == bezpath_index {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tnumber += winding;\n\t\t}\n\n\t\t// Non-zero fill rule\n\t\tnumber != 0\n\t};\n\n\tlet line_intersect_shape_checker = |p0: (f64, f64), p1: (f64, f64)| {\n\t\tfor segment in this_bezpath.segments() {\n\t\t\tif !segment.intersect_line(Line::new(p0, p1)).is_empty() {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\tfalse\n\t};\n\n\tlet offset = DVec2::new(this_bbox.x0, this_bbox.y0);\n\tlet width = this_bbox.width();\n\tlet height = this_bbox.height();\n\n\tpoisson_disk_sample(offset, width, height, separation_disk_diameter, point_in_shape_checker, line_intersect_shape_checker, rng)\n}\n\n/// Returns true if the Bezier curve is equivalent to a line.\n///\n/// **NOTE**: This is different from simply checking if the segment is [`PathSeg::Line`] or [`PathSeg::Quad`] or [`PathSeg::Cubic`]. Bezier curve can also be a line if the control points are colinear to the start and end points. Therefore if the handles exceed the start and end point, it will still be considered as a line.\npub fn is_linear(segment: &PathSeg) -> bool {\n\tlet is_colinear = |a: Point, b: Point, c: Point| -> bool { ((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)).abs() < MAX_ABSOLUTE_DIFFERENCE };\n\n\tmatch *segment {\n\t\tPathSeg::Line(_) => true,\n\t\tPathSeg::Quad(QuadBez { p0, p1, p2 }) => is_colinear(p0, p1, p2),\n\t\tPathSeg::Cubic(CubicBez { p0, p1, p2, p3 }) => is_colinear(p0, p1, p3) && is_colinear(p0, p2, p3),\n\t}\n}\n\n// TODO: If a segment curls back on itself tightly enough it could intersect again at the portion that should be trimmed. This could cause the Subpaths to be clipped\n// TODO: at the incorrect location. This can be avoided by first trimming the two Subpaths at any extrema, effectively ignoring loopbacks.\n/// Helper function to clip overlap of two intersecting open BezPaths. Returns an Option because intersections may not exist for certain arrangements and distances.\n/// Assumes that the BezPaths represents simple Bezier segments, and clips the BezPaths at the last intersection of the first BezPath, and first intersection of the last BezPath.\npub fn clip_simple_bezpaths(bezpath1: &BezPath, bezpath2: &BezPath) -> Option<(BezPath, BezPath)> {\n\t// Split the first subpath at its last intersection\n\tlet subpath_1_intersections = bezpath_intersections(bezpath1, bezpath2, None, None);\n\tif subpath_1_intersections.is_empty() {\n\t\treturn None;\n\t}\n\tlet (segment_index, t) = *subpath_1_intersections.last()?;\n\tlet (clipped_subpath1, _) = split_bezpath_at_segment(bezpath1, segment_index, t)?;\n\n\t// Split the second subpath at its first intersection\n\tlet subpath_2_intersections = bezpath_intersections(bezpath2, bezpath1, None, None);\n\tif subpath_2_intersections.is_empty() {\n\t\treturn None;\n\t}\n\tlet (segment_index, t) = subpath_2_intersections[0];\n\tlet (_, clipped_subpath2) = split_bezpath_at_segment(bezpath2, segment_index, t)?;\n\n\tSome((clipped_subpath1, clipped_subpath2))\n}\n\n/// Returns the [`PathEl`] that is needed for a miter join if it is possible.\n///\n/// `miter_limit` defines a limit for the ratio between the miter length and the stroke width.\n/// Alternatively, this can be interpreted as limiting the angle that the miter can form.\n/// When the limit is exceeded, no [`PathEl`] will be returned.\n/// This value should be greater than 0. If not, the default of 4 will be used.\npub fn miter_line_join(bezpath1: &BezPath, bezpath2: &BezPath, miter_limit: Option<f64>) -> Option<[PathEl; 2]> {\n\tlet miter_limit = match miter_limit {\n\t\tSome(miter_limit) if miter_limit > f64::EPSILON => miter_limit,\n\t\t_ => 4.,\n\t};\n\t// TODO: Besides returning None using the `?` operator, is there a more appropriate way to handle a `None` result from `get_segment`?\n\tlet in_segment = bezpath1.segments().last()?;\n\tlet out_segment = bezpath2.segments().next()?;\n\n\tlet in_tangent = pathseg_tangent(in_segment, 1.);\n\tlet out_tangent = pathseg_tangent(out_segment, 0.);\n\n\tif in_tangent == DVec2::ZERO || out_tangent == DVec2::ZERO {\n\t\t// Avoid panic from normalizing zero vectors\n\t\t// TODO: Besides returning None, is there a more appropriate way to handle this?\n\t\treturn None;\n\t}\n\n\tlet angle = (in_tangent * -1.).angle_to(out_tangent).abs();\n\n\tif angle.to_degrees() < miter_limit {\n\t\treturn None;\n\t}\n\n\tlet p1 = in_segment.end();\n\tlet p2 = point_to_dvec2(p1) + in_tangent.normalize();\n\tlet line1 = Line::new(p1, dvec2_to_point(p2));\n\n\tlet p1 = out_segment.start();\n\tlet p2 = point_to_dvec2(p1) + out_tangent.normalize();\n\tlet line2 = Line::new(p1, dvec2_to_point(p2));\n\n\t// If we don't find the intersection point to draw the miter join, we instead default to a bevel join.\n\t// Otherwise, we return the element to create the join.\n\tlet intersection = line1.crossing_point(line2)?;\n\n\tSome([PathEl::LineTo(intersection), PathEl::LineTo(out_segment.start())])\n}\n\n/// Computes the [`PathEl`] to form a circular join from `left` to `right`, along a circle around `center`.\n/// By default, the angle is assumed to be 180 degrees.\npub fn compute_circular_subpath_details(left: DVec2, arc_point: DVec2, right: DVec2, center: DVec2, angle: Option<f64>) -> [PathEl; 2] {\n\tlet center_to_arc_point = arc_point - center;\n\n\t// Based on https://pomax.github.io/bezierinfo/#circles_cubic\n\tlet handle_offset_factor = if let Some(angle) = angle { 4. / 3. * (angle / 4.).tan() } else { 0.551784777779014 };\n\n\tlet p1 = dvec2_to_point(left - (left - center).perp() * handle_offset_factor);\n\tlet p2 = dvec2_to_point(arc_point + center_to_arc_point.perp() * handle_offset_factor);\n\tlet p3 = dvec2_to_point(arc_point);\n\n\tlet first_half = PathEl::CurveTo(p1, p2, p3);\n\n\tlet p1 = dvec2_to_point(arc_point - center_to_arc_point.perp() * handle_offset_factor);\n\tlet p2 = dvec2_to_point(right + (right - center).perp() * handle_offset_factor);\n\tlet p3 = dvec2_to_point(right);\n\n\tlet second_half = PathEl::CurveTo(p1, p2, p3);\n\n\t[first_half, second_half]\n}\n\n/// Returns two [`PathEl`] to create a round join with the provided center.\npub fn round_line_join(bezpath1: &BezPath, bezpath2: &BezPath, center: DVec2) -> [PathEl; 2] {\n\tlet left = point_to_dvec2(bezpath1.segments().last().unwrap().end());\n\tlet right = point_to_dvec2(bezpath2.segments().next().unwrap().start());\n\n\tlet center_to_right = right - center;\n\tlet center_to_left = left - center;\n\n\tlet in_segment = bezpath1.segments().last();\n\tlet in_tangent = in_segment.map(|in_segment| pathseg_tangent(in_segment, 1.));\n\n\tlet mut angle = center_to_right.angle_to(center_to_left) / 2.;\n\tlet mut arc_point = center + DMat2::from_angle(angle).mul_vec2(center_to_right);\n\n\tif in_tangent.map(|in_tangent| (arc_point - left).angle_to(in_tangent).abs()).unwrap_or_default() > FRAC_PI_2 {\n\t\tangle = angle - PI * (if angle < 0. { -1. } else { 1. });\n\t\tarc_point = center + DMat2::from_angle(angle).mul_vec2(center_to_right);\n\t}\n\n\tcompute_circular_subpath_details(left, arc_point, right, center, Some(angle))\n}\n\n/// Returns `true` if the `bezpath1` is completely inside the `bezpath2`.\n/// NOTE: `bezpath2` must be a closed path to get correct results.\npub fn bezpath_is_inside_bezpath(bezpath1: &BezPath, bezpath2: &BezPath, accuracy: Option<f64>, minimum_separation: Option<f64>) -> bool {\n\t// Eliminate any possibility of one being inside the other, if either of them are empty\n\tif bezpath1.is_empty() || bezpath2.is_empty() {\n\t\treturn false;\n\t}\n\n\tlet inner_bbox = bezpath1.bounding_box();\n\tlet outer_bbox = bezpath2.bounding_box();\n\n\t// Eliminate bezpath1 if its bounding box is not completely inside the bezpath2's bounding box.\n\t// Reasoning:\n\t// If the inner bezpath bounding box is larger than the outer bezpath bounding box in any direction\n\t// then the inner bezpath is intersecting with or outside the outer bezpath.\n\tif !outer_bbox.contains_rect(inner_bbox) && outer_bbox.intersect(inner_bbox).is_zero_area() {\n\t\treturn false;\n\t}\n\n\t// Eliminate bezpath1 if any of its anchor points are outside the bezpath2.\n\tif !bezpath1.elements().iter().filter_map(|el| el.end_point()).all(|point| bezpath2.contains(point)) {\n\t\treturn false;\n\t}\n\n\t// Eliminate this subpath if it intersects with the other subpath.\n\tif !bezpath_intersections(bezpath1, bezpath2, accuracy, minimum_separation).is_empty() {\n\t\treturn false;\n\t}\n\n\t// At this point:\n\t// (1) This subpath's bounding box is inside the other subpath's bounding box,\n\t// (2) Its anchors are inside the other subpath, and\n\t// (3) It is not intersecting with the other subpath.\n\t// Hence, this subpath is completely inside the given other subpath.\n\ttrue\n}\n\n#[cfg(test)]\nmod tests {\n\t// TODO: add more intersection tests\n\n\tuse super::bezpath_is_inside_bezpath;\n\tuse kurbo::{BezPath, DEFAULT_ACCURACY, Line, Point, Rect, Shape};\n\n\t#[test]\n\tfn is_inside_subpath() {\n\t\tlet boundary_polygon = Rect::new(100., 100., 500., 500.).to_path(DEFAULT_ACCURACY);\n\n\t\tlet mut curve_intersection = BezPath::new();\n\t\tcurve_intersection.move_to(Point::new(189., 289.));\n\t\tcurve_intersection.quad_to(Point::new(9., 286.), Point::new(45., 410.));\n\t\tassert!(!bezpath_is_inside_bezpath(&curve_intersection, &boundary_polygon, None, None));\n\n\t\tlet mut curve_outside = BezPath::new();\n\t\tcurve_outside.move_to(Point::new(115., 37.));\n\t\tcurve_outside.quad_to(Point::new(51.4, 91.8), Point::new(76.5, 242.));\n\t\tassert!(!bezpath_is_inside_bezpath(&curve_outside, &boundary_polygon, None, None));\n\n\t\tlet mut curve_inside = BezPath::new();\n\t\tcurve_inside.move_to(Point::new(210.1, 133.5));\n\t\tcurve_inside.curve_to(Point::new(150.2, 436.9), Point::new(436., 285.), Point::new(247.6, 240.7));\n\t\tassert!(bezpath_is_inside_bezpath(&curve_inside, &boundary_polygon, None, None));\n\n\t\tlet line_inside = Line::new(Point::new(101., 101.5), Point::new(150.2, 499.)).to_path(DEFAULT_ACCURACY);\n\t\tassert!(bezpath_is_inside_bezpath(&line_inside, &boundary_polygon, None, None));\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/vector-types/src/vector/algorithms/contants.rs",
    "content": "/// Minimum allowable separation between adjacent `t` values when calculating curve intersections\npub const MIN_SEPARATION_VALUE: f64 = 5. * 1e-3;\n\n/// Constant used to determine if `f64`s are equivalent.\n#[cfg(test)]\npub const MAX_ABSOLUTE_DIFFERENCE: f64 = 1e-3;\n"
  },
  {
    "path": "node-graph/libraries/vector-types/src/vector/algorithms/intersection.rs",
    "content": "use super::contants::MIN_SEPARATION_VALUE;\nuse kurbo::{BezPath, DEFAULT_ACCURACY, ParamCurve, PathSeg, Shape};\nuse lyon_geom::{CubicBezierSegment, Point};\n\n/// Converts a kurbo cubic bezier to a lyon_geom CubicBezierSegment\nfn kurbo_cubic_to_lyon(cubic: kurbo::CubicBez) -> CubicBezierSegment<f64> {\n\tCubicBezierSegment {\n\t\tfrom: Point::new(cubic.p0.x, cubic.p0.y),\n\t\tctrl1: Point::new(cubic.p1.x, cubic.p1.y),\n\t\tctrl2: Point::new(cubic.p2.x, cubic.p2.y),\n\t\tto: Point::new(cubic.p3.x, cubic.p3.y),\n\t}\n}\n\n/// Fast cubic-cubic intersection using lyon_geom's analytical approach\nfn cubic_cubic_intersections_lyon(cubic1: kurbo::CubicBez, cubic2: kurbo::CubicBez) -> Vec<(f64, f64)> {\n\tlet lyon_cubic1 = kurbo_cubic_to_lyon(cubic1);\n\tlet lyon_cubic2 = kurbo_cubic_to_lyon(cubic2);\n\n\tlyon_cubic1.cubic_intersections_t(&lyon_cubic2).to_vec()\n}\n\n/// Calculates the intersection points the bezpath has with a given segment and returns a list of `(usize, f64)` tuples,\n/// where the `usize` represents the index of the segment in the bezpath, and the `f64` represents the `t`-value local to\n/// that segment where the intersection occurred.\n///\n/// `minimum_separation` is the minimum difference that two adjacent `t`-values must have when comparing adjacent `t`-values in sorted order.\npub fn bezpath_and_segment_intersections(bezpath: &BezPath, segment: PathSeg, accuracy: Option<f64>, minimum_separation: Option<f64>) -> Vec<(usize, f64)> {\n\tbezpath\n\t\t.segments()\n\t\t.enumerate()\n\t\t.flat_map(|(index, this_segment)| {\n\t\t\tfiltered_segment_intersections(this_segment, segment, accuracy, minimum_separation)\n\t\t\t\t.into_iter()\n\t\t\t\t.map(|t| (index, t))\n\t\t\t\t.collect::<Vec<(usize, f64)>>()\n\t\t})\n\t\t.collect()\n}\n\n/// Calculates the intersection points the bezpath has with another given bezpath and returns a list of parametric `t`-values.\npub fn bezpath_intersections(bezpath1: &BezPath, bezpath2: &BezPath, accuracy: Option<f64>, minimum_separation: Option<f64>) -> Vec<(usize, f64)> {\n\tlet mut intersection_t_values: Vec<(usize, f64)> = bezpath2\n\t\t.segments()\n\t\t.flat_map(|bezier| bezpath_and_segment_intersections(bezpath1, bezier, accuracy, minimum_separation))\n\t\t.collect();\n\n\tintersection_t_values.sort_by(|a, b| a.partial_cmp(b).unwrap());\n\tintersection_t_values\n}\n\n/// Calculates the intersection points the segment has with another given segment and returns a list of parametric `t`-values with given accuracy.\npub fn segment_intersections(segment1: PathSeg, segment2: PathSeg, accuracy: Option<f64>) -> Vec<(f64, f64)> {\n\tlet accuracy = accuracy.unwrap_or(DEFAULT_ACCURACY);\n\n\tmatch (segment1, segment2) {\n\t\t(PathSeg::Line(line), segment2) => segment2.intersect_line(line).iter().map(|i| (i.line_t, i.segment_t)).collect(),\n\t\t(segment1, PathSeg::Line(line)) => segment1.intersect_line(line).iter().map(|i| (i.segment_t, i.line_t)).collect(),\n\t\t// Fast path for cubic-cubic intersections using lyon_geom\n\t\t(PathSeg::Cubic(cubic1), PathSeg::Cubic(cubic2)) => cubic_cubic_intersections_lyon(cubic1, cubic2),\n\t\t(segment1, segment2) => {\n\t\t\tlet mut intersections = Vec::new();\n\t\t\tsegment_intersections_inner(segment1, 0., 1., segment2, 0., 1., accuracy, &mut intersections);\n\t\t\tintersections\n\t\t}\n\t}\n}\n\npub fn subsegment_intersections(segment1: PathSeg, min_t1: f64, max_t1: f64, segment2: PathSeg, min_t2: f64, max_t2: f64, accuracy: Option<f64>) -> Vec<(f64, f64)> {\n\tlet accuracy = accuracy.unwrap_or(DEFAULT_ACCURACY);\n\n\tmatch (segment1, segment2) {\n\t\t(PathSeg::Line(line), segment2) => segment2.intersect_line(line).iter().map(|i| (i.line_t, i.segment_t)).collect(),\n\t\t(segment1, PathSeg::Line(line)) => segment1.intersect_line(line).iter().map(|i| (i.segment_t, i.line_t)).collect(),\n\t\t// Fast path for cubic-cubic intersections using lyon_geom with subsegment parameters\n\t\t(PathSeg::Cubic(cubic1), PathSeg::Cubic(cubic2)) => {\n\t\t\tlet sub_cubic1 = cubic1.subsegment(min_t1..max_t1);\n\t\t\tlet sub_cubic2 = cubic2.subsegment(min_t2..max_t2);\n\n\t\t\tcubic_cubic_intersections_lyon(sub_cubic1, sub_cubic2)\n\t\t\t\t.into_iter()\n\t\t\t\t// Convert subsegment t-values back to original segment t-values\n\t\t\t\t.map(|(t1, t2)| {\n\t\t\t\t\tlet original_t1 = min_t1 + t1 * (max_t1 - min_t1);\n\t\t\t\t\tlet original_t2 = min_t2 + t2 * (max_t2 - min_t2);\n\t\t\t\t\t(original_t1, original_t2)\n\t\t\t\t})\n\t\t\t\t.collect()\n\t\t}\n\t\t(segment1, segment2) => {\n\t\t\tlet mut intersections = Vec::new();\n\t\t\tsegment_intersections_inner(segment1, min_t1, max_t1, segment2, min_t2, max_t2, accuracy, &mut intersections);\n\t\t\tintersections\n\t\t}\n\t}\n}\n\nfn approx_bounding_box(path_seg: PathSeg) -> kurbo::Rect {\n\tuse kurbo::Rect;\n\tmatch path_seg {\n\t\tPathSeg::Line(line) => kurbo::Rect::from_points(line.p0, line.p1),\n\t\tPathSeg::Quad(quad_bez) => {\n\t\t\tlet r1 = Rect::from_points(quad_bez.p0, quad_bez.p1);\n\t\t\tlet r2 = Rect::from_points(quad_bez.p1, quad_bez.p2);\n\t\t\tr1.union(r2)\n\t\t}\n\t\tPathSeg::Cubic(cubic_bez) => {\n\t\t\tlet r1 = Rect::from_points(cubic_bez.p0, cubic_bez.p1);\n\t\t\tlet r2 = Rect::from_points(cubic_bez.p2, cubic_bez.p3);\n\t\t\tr1.union(r2)\n\t\t}\n\t}\n}\n\n/// Implements [https://pomax.github.io/bezierinfo/#curveintersection] to find intersection between two Bezier segments\n/// by splitting the segment recursively until the size of the subsegment's bounding box is smaller than the accuracy.\n#[allow(clippy::too_many_arguments)]\nfn segment_intersections_inner(segment1: PathSeg, min_t1: f64, max_t1: f64, segment2: PathSeg, min_t2: f64, max_t2: f64, accuracy: f64, intersections: &mut Vec<(f64, f64)>) {\n\tlet bbox1 = approx_bounding_box(segment1.subsegment(min_t1..max_t1));\n\tlet bbox2 = approx_bounding_box(segment2.subsegment(min_t2..max_t2));\n\n\tif intersections.len() > 50 {\n\t\treturn;\n\t}\n\n\tlet mid_t1 = (min_t1 + max_t1) / 2.;\n\tlet mid_t2 = (min_t2 + max_t2) / 2.;\n\n\t// Check if the bounding boxes overlap\n\tif bbox1.overlaps(bbox2) {\n\t\t// If bounding boxes overlap and they are small enough, we have found an intersection\n\t\tif bbox1.width().abs() < accuracy && bbox1.height().abs() < accuracy && bbox2.width().abs() < accuracy && bbox2.height().abs() < accuracy {\n\t\t\t// Use the middle `t` value, append the corresponding `t` value\n\t\t\tintersections.push((mid_t1, mid_t2));\n\t\t\treturn;\n\t\t}\n\n\t\t// Split curves in half\n\t\tlet (seg11, seg12) = segment1.subdivide();\n\t\tlet (seg21, seg22) = segment2.subdivide();\n\n\t\t// Repeat checking the intersection with the combinations of the two halves of each curve\n\t\tsegment_intersections_inner(seg11, min_t1, mid_t1, seg21, min_t2, mid_t2, accuracy, intersections);\n\t\tsegment_intersections_inner(seg11, min_t1, mid_t1, seg22, mid_t2, max_t2, accuracy, intersections);\n\t\tsegment_intersections_inner(seg12, mid_t1, max_t1, seg21, min_t2, mid_t2, accuracy, intersections);\n\t\tsegment_intersections_inner(seg12, mid_t1, max_t1, seg22, mid_t2, max_t2, accuracy, intersections);\n\t}\n}\n\n// TODO: Use an `impl Iterator` return type instead of a `Vec`\n/// Returns a list of filtered parametric `t` values that correspond to intersection points between the current bezier segment and the provided one\n/// such that the difference between adjacent `t` values in sorted order is greater than some minimum separation value. If the difference\n/// between 2 adjacent `t` values is less than the minimum difference, the filtering takes the larger `t` value and discards the smaller `t` value.\n/// The returned `t` values are with respect to the current bezier segment, not the provided parameter.\n/// If the provided segment is linear, then zero intersection points will be returned along colinear segments.\n///\n/// `accuracy` defines, for intersections where the provided bezier segment is non-linear, the maximum size of the bounding boxes to be considered an intersection point.\n///\n/// `minimum_separation` is the minimum difference between adjacent `t` values in sorted order.\npub fn filtered_segment_intersections(segment1: PathSeg, segment2: PathSeg, accuracy: Option<f64>, minimum_separation: Option<f64>) -> Vec<f64> {\n\tlet mut intersection_t_values = segment_intersections(segment1, segment2, accuracy);\n\tintersection_t_values.sort_by(|a, b| a.partial_cmp(b).unwrap());\n\n\tintersection_t_values.iter().map(|x| x.0).fold(Vec::new(), |mut accumulator, t| {\n\t\tif !accumulator.is_empty() && (accumulator.last().unwrap() - t).abs() < minimum_separation.unwrap_or(MIN_SEPARATION_VALUE) {\n\t\t\taccumulator.pop();\n\t\t}\n\t\taccumulator.push(t);\n\t\taccumulator\n\t})\n}\n\n// TODO: Use an `impl Iterator` return type instead of a `Vec`\n/// Returns a list of pairs of filtered parametric `t` values that correspond to intersection points between the current bezier curve and the provided\n/// one such that the difference between adjacent `t` values in sorted order is greater than some minimum separation value. If the difference between\n/// two adjacent `t` values is less than the minimum difference, the filtering takes the larger `t` value and discards the smaller `t` value.\n/// The first value in pair is with respect to the current bezier and the second value in pair is with respect to the provided parameter.\n/// If the provided curve is linear, then zero intersection points will be returned along colinear segments.\n///\n/// `error`, for intersections where the provided bezier is non-linear, defines the threshold for bounding boxes to be considered an intersection point.\n///\n/// `minimum_separation` is the minimum difference between adjacent `t` values in sorted order\npub fn filtered_all_segment_intersections(segment1: PathSeg, segment2: PathSeg, accuracy: Option<f64>, minimum_separation: Option<f64>) -> Vec<(f64, f64)> {\n\tlet mut intersection_t_values = segment_intersections(segment1, segment2, accuracy);\n\tintersection_t_values.sort_by(|a, b| (a.0 + a.1).partial_cmp(&(b.0 + b.1)).unwrap());\n\n\tintersection_t_values.iter().fold(Vec::new(), |mut accumulator, t| {\n\t\tif !accumulator.is_empty()\n\t\t\t&& (accumulator.last().unwrap().0 - t.0).abs() < minimum_separation.unwrap_or(MIN_SEPARATION_VALUE)\n\t\t\t&& (accumulator.last().unwrap().1 - t.1).abs() < minimum_separation.unwrap_or(MIN_SEPARATION_VALUE)\n\t\t{\n\t\t\taccumulator.pop();\n\t\t}\n\t\taccumulator.push(*t);\n\t\taccumulator\n\t})\n}\n\n/// Helper function to compute intersections between lists of subcurves.\n/// This function uses the algorithm implemented in `intersections_between_subcurves`.\nfn intersections_between_vectors_of_path_segments(subcurves1: &[(f64, f64, PathSeg)], subcurves2: &[(f64, f64, PathSeg)], accuracy: Option<f64>) -> Vec<(f64, f64)> {\n\tlet segment_pairs = subcurves1.iter().flat_map(move |(t11, t12, curve1)| {\n\t\tsubcurves2\n\t\t\t.iter()\n\t\t\t.filter_map(move |(t21, t22, curve2)| curve1.bounding_box().overlaps(curve2.bounding_box()).then_some((t11, t12, curve1, t21, t22, curve2)))\n\t});\n\n\tsegment_pairs\n\t\t.flat_map(|(&t11, &t12, &curve1, &t21, &t22, &curve2)| subsegment_intersections(curve1, t11, t12, curve2, t21, t22, accuracy))\n\t\t.collect::<Vec<(f64, f64)>>()\n}\n\nfn pathseg_self_intersection(segment: PathSeg, accuracy: Option<f64>) -> Vec<(f64, f64)> {\n\tlet cubic_bez = match segment {\n\t\tPathSeg::Line(_) | PathSeg::Quad(_) => return vec![],\n\t\tPathSeg::Cubic(cubic_bez) => cubic_bez,\n\t};\n\n\t// Get 2 copies of the reduced curves\n\tlet quads1 = cubic_bez.to_quads(DEFAULT_ACCURACY).map(|(t1, t2, quad_bez)| (t1, t2, PathSeg::Quad(quad_bez))).collect::<Vec<_>>();\n\tlet quads2 = quads1.clone();\n\n\tlet num_curves = quads1.len();\n\n\t// Adjacent reduced curves cannot intersect\n\tif num_curves <= 2 {\n\t\treturn vec![];\n\t}\n\n\t// For each curve, look for intersections with every curve that is at least 2 indices away\n\tquads1\n\t\t.iter()\n\t\t.take(num_curves - 2)\n\t\t.enumerate()\n\t\t.flat_map(|(index, &subsegment)| intersections_between_vectors_of_path_segments(&[subsegment], &quads2[index + 2..], accuracy))\n\t\t.collect()\n}\n\n/// Returns a list of parametric `t` values that correspond to the self intersection points of the current bezier curve. For each intersection point, the returned `t` value is the smaller of the two that correspond to the point.\n/// If the difference between 2 adjacent `t` values is less than the minimum difference, the filtering takes the larger `t` value and discards the smaller `t` value.\n/// - `error` - For intersections with non-linear beziers, `error` defines the threshold for bounding boxes to be considered an intersection point.\n/// - `minimum_separation` - The minimum difference between adjacent `t` values in sorted order\npub fn pathseg_self_intersections(segment: PathSeg, accuracy: Option<f64>, minimum_separation: Option<f64>) -> Vec<(f64, f64)> {\n\tlet mut intersection_t_values = pathseg_self_intersection(segment, accuracy);\n\tintersection_t_values.sort_by(|a, b| (a.0 + a.1).partial_cmp(&(b.0 + b.1)).unwrap());\n\n\tintersection_t_values.iter().fold(Vec::new(), |mut accumulator, t| {\n\t\tif !accumulator.is_empty()\n\t\t\t&& (accumulator.last().unwrap().0 - t.0).abs() < minimum_separation.unwrap_or(MIN_SEPARATION_VALUE)\n\t\t\t&& (accumulator.last().unwrap().1 - t.1).abs() < minimum_separation.unwrap_or(MIN_SEPARATION_VALUE)\n\t\t{\n\t\t\taccumulator.pop();\n\t\t}\n\t\taccumulator.push(*t);\n\t\taccumulator\n\t})\n}\n\n#[cfg(test)]\nmod tests {\n\tuse super::{bezpath_and_segment_intersections, filtered_segment_intersections};\n\tuse crate::vector::algorithms::{\n\t\tcontants::MAX_ABSOLUTE_DIFFERENCE,\n\t\tutil::{compare_points, compare_vec_of_points, dvec2_compare},\n\t};\n\n\tuse kurbo::{BezPath, CubicBez, Line, ParamCurve, PathEl, PathSeg, Point, QuadBez};\n\n\t#[test]\n\tfn test_intersect_line_segment_quadratic() {\n\t\tlet p1 = Point::new(30., 50.);\n\t\tlet p2 = Point::new(140., 30.);\n\t\tlet p3 = Point::new(160., 170.);\n\n\t\t// Intersection at edge of curve\n\t\tlet bezier = PathSeg::Quad(QuadBez::new(p1, p2, p3));\n\t\tlet line1 = PathSeg::Line(Line::new(Point::new(20., 50.), Point::new(40., 50.)));\n\t\tlet intersections1 = filtered_segment_intersections(bezier, line1, None, None);\n\t\tassert!(intersections1.len() == 1);\n\t\tassert!(compare_points(bezier.eval(intersections1[0]), p1));\n\n\t\t// Intersection in the middle of curve\n\t\tlet line2 = PathSeg::Line(Line::new(Point::new(150., 150.), Point::new(30., 30.)));\n\t\tlet intersections2 = filtered_segment_intersections(bezier, line2, None, None);\n\t\tassert!(compare_points(bezier.eval(intersections2[0]), Point::new(47.77355, 47.77354)));\n\t}\n\n\t#[test]\n\tfn test_intersect_curve_cubic_edge_case() {\n\t\t// M34 107 C40 40 120 120 102 29\n\n\t\tlet p1 = Point::new(34., 107.);\n\t\tlet p2 = Point::new(40., 40.);\n\t\tlet p3 = Point::new(120., 120.);\n\t\tlet p4 = Point::new(102., 29.);\n\t\tlet cubic_segment = PathSeg::Cubic(CubicBez::new(p1, p2, p3, p4));\n\n\t\tlet linear_segment = PathSeg::Line(Line::new(Point::new(150., 150.), Point::new(20., 20.)));\n\t\tlet intersections = filtered_segment_intersections(cubic_segment, linear_segment, None, None);\n\n\t\tassert_eq!(intersections.len(), 1);\n\t}\n\n\t#[test]\n\tfn test_intersect_curve() {\n\t\tlet p0 = Point::new(30., 30.);\n\t\tlet p1 = Point::new(60., 140.);\n\t\tlet p2 = Point::new(150., 30.);\n\t\tlet p3 = Point::new(160., 160.);\n\n\t\tlet cubic_segment = PathSeg::Cubic(CubicBez::new(p0, p1, p2, p3));\n\n\t\tlet p0 = Point::new(175., 140.);\n\t\tlet p1 = Point::new(20., 20.);\n\t\tlet p2 = Point::new(120., 20.);\n\n\t\tlet quadratic_segment = PathSeg::Quad(QuadBez::new(p0, p1, p2));\n\n\t\tlet intersections1 = filtered_segment_intersections(cubic_segment, quadratic_segment, None, None);\n\t\tlet intersections2 = filtered_segment_intersections(quadratic_segment, cubic_segment, None, None);\n\n\t\tlet intersections1_points: Vec<Point> = intersections1.iter().map(|&t| cubic_segment.eval(t)).collect();\n\t\tlet intersections2_points: Vec<Point> = intersections2.iter().map(|&t| quadratic_segment.eval(t)).rev().collect();\n\n\t\tassert!(compare_vec_of_points(intersections1_points, intersections2_points, 2.));\n\t}\n\n\t#[test]\n\tfn intersection_linear_multiple_subpath_curves_test_one() {\n\t\t// M 35 125 C 40 40 120 120 43 43 Q 175 90 145 150 Q 70 185 35 125 Z\n\n\t\tlet cubic_start = Point::new(35., 125.);\n\t\tlet cubic_handle_1 = Point::new(40., 40.);\n\t\tlet cubic_handle_2 = Point::new(120., 120.);\n\t\tlet cubic_end = Point::new(43., 43.);\n\n\t\tlet quadratic_1_handle = Point::new(175., 90.);\n\t\tlet quadratic_end = Point::new(145., 150.);\n\n\t\tlet quadratic_2_handle = Point::new(70., 185.);\n\n\t\tlet cubic_segment = PathSeg::Cubic(CubicBez::new(cubic_start, cubic_handle_1, cubic_handle_2, cubic_end));\n\t\tlet quadratic_segment = PathSeg::Quad(QuadBez::new(cubic_end, quadratic_1_handle, quadratic_end));\n\n\t\tlet bezpath = BezPath::from_vec(vec![\n\t\t\tPathEl::MoveTo(cubic_start),\n\t\t\tPathEl::CurveTo(cubic_handle_1, cubic_handle_2, cubic_end),\n\t\t\tPathEl::QuadTo(quadratic_1_handle, quadratic_end),\n\t\t\tPathEl::QuadTo(quadratic_2_handle, cubic_start),\n\t\t\tPathEl::ClosePath,\n\t\t]);\n\n\t\tlet linear_segment = PathSeg::Line(Line::new(Point::new(150., 150.), Point::new(20., 20.)));\n\n\t\tlet cubic_intersections = filtered_segment_intersections(cubic_segment, linear_segment, None, None);\n\t\tlet quadratic_1_intersections = filtered_segment_intersections(quadratic_segment, linear_segment, None, None);\n\t\tlet bezpath_intersections = bezpath_and_segment_intersections(&bezpath, linear_segment, None, None);\n\n\t\tassert!(\n\t\t\tdvec2_compare(\n\t\t\t\tcubic_segment.eval(cubic_intersections[0]),\n\t\t\t\tbezpath.segments().nth(bezpath_intersections[0].0).unwrap().eval(bezpath_intersections[0].1),\n\t\t\t\tMAX_ABSOLUTE_DIFFERENCE\n\t\t\t)\n\t\t\t.all()\n\t\t);\n\n\t\tassert!(\n\t\t\tdvec2_compare(\n\t\t\t\tquadratic_segment.eval(quadratic_1_intersections[0]),\n\t\t\t\tbezpath.segments().nth(bezpath_intersections[1].0).unwrap().eval(bezpath_intersections[1].1),\n\t\t\t\tMAX_ABSOLUTE_DIFFERENCE\n\t\t\t)\n\t\t\t.all()\n\t\t);\n\n\t\tassert!(\n\t\t\tdvec2_compare(\n\t\t\t\tquadratic_segment.eval(quadratic_1_intersections[1]),\n\t\t\t\tbezpath.segments().nth(bezpath_intersections[2].0).unwrap().eval(bezpath_intersections[2].1),\n\t\t\t\tMAX_ABSOLUTE_DIFFERENCE\n\t\t\t)\n\t\t\t.all()\n\t\t);\n\t}\n\n\t#[test]\n\tfn intersection_linear_multiple_subpath_curves_test_two() {\n\t\t// M34 107 C40 40 120 120 102 29 Q175 90 129 171 Q70 185 34 107 Z\n\t\t// M150 150 L 20 20\n\n\t\tlet cubic_start = Point::new(34., 107.);\n\t\tlet cubic_handle_1 = Point::new(40., 40.);\n\t\tlet cubic_handle_2 = Point::new(120., 120.);\n\t\tlet cubic_end = Point::new(102., 29.);\n\n\t\tlet quadratic_1_handle = Point::new(175., 90.);\n\t\tlet quadratic_end = Point::new(129., 171.);\n\n\t\tlet quadratic_2_handle = Point::new(70., 185.);\n\n\t\tlet cubic_segment = PathSeg::Cubic(CubicBez::new(cubic_start, cubic_handle_1, cubic_handle_2, cubic_end));\n\t\tlet quadratic_segment = PathSeg::Quad(QuadBez::new(cubic_end, quadratic_1_handle, quadratic_end));\n\n\t\tlet bezpath = BezPath::from_vec(vec![\n\t\t\tPathEl::MoveTo(cubic_start),\n\t\t\tPathEl::CurveTo(cubic_handle_1, cubic_handle_2, cubic_end),\n\t\t\tPathEl::QuadTo(quadratic_1_handle, quadratic_end),\n\t\t\tPathEl::QuadTo(quadratic_2_handle, cubic_start),\n\t\t\tPathEl::ClosePath,\n\t\t]);\n\n\t\tlet line = PathSeg::Line(Line::new(Point::new(150., 150.), Point::new(20., 20.)));\n\n\t\tlet cubic_intersections = filtered_segment_intersections(cubic_segment, line, None, None);\n\t\tlet quadratic_1_intersections = filtered_segment_intersections(quadratic_segment, line, None, None);\n\t\tlet bezpath_intersections = bezpath_and_segment_intersections(&bezpath, line, None, None);\n\n\t\tassert!(\n\t\t\tdvec2_compare(\n\t\t\t\tcubic_segment.eval(cubic_intersections[0]),\n\t\t\t\tbezpath.segments().nth(bezpath_intersections[0].0).unwrap().eval(bezpath_intersections[0].1),\n\t\t\t\tMAX_ABSOLUTE_DIFFERENCE\n\t\t\t)\n\t\t\t.all()\n\t\t);\n\n\t\tassert!(\n\t\t\tdvec2_compare(\n\t\t\t\tquadratic_segment.eval(quadratic_1_intersections[0]),\n\t\t\t\tbezpath.segments().nth(bezpath_intersections[1].0).unwrap().eval(bezpath_intersections[1].1),\n\t\t\t\tMAX_ABSOLUTE_DIFFERENCE\n\t\t\t)\n\t\t\t.all()\n\t\t);\n\t}\n\n\t#[test]\n\tfn intersection_linear_multiple_subpath_curves_test_three() {\n\t\t// M35 125 C40 40 120 120 44 44 Q175 90 145 150 Q70 185 35 125 Z\n\n\t\tlet cubic_start = Point::new(35., 125.);\n\t\tlet cubic_handle_1 = Point::new(40., 40.);\n\t\tlet cubic_handle_2 = Point::new(120., 120.);\n\t\tlet cubic_end = Point::new(44., 44.);\n\n\t\tlet quadratic_1_handle = Point::new(175., 90.);\n\t\tlet quadratic_end = Point::new(145., 150.);\n\n\t\tlet quadratic_2_handle = Point::new(70., 185.);\n\n\t\tlet cubic_segment = PathSeg::Cubic(CubicBez::new(cubic_start, cubic_handle_1, cubic_handle_2, cubic_end));\n\t\tlet quadratic_segment = PathSeg::Quad(QuadBez::new(cubic_end, quadratic_1_handle, quadratic_end));\n\n\t\tlet bezpath = BezPath::from_vec(vec![\n\t\t\tPathEl::MoveTo(cubic_start),\n\t\t\tPathEl::CurveTo(cubic_handle_1, cubic_handle_2, cubic_end),\n\t\t\tPathEl::QuadTo(quadratic_1_handle, quadratic_end),\n\t\t\tPathEl::QuadTo(quadratic_2_handle, cubic_start),\n\t\t\tPathEl::ClosePath,\n\t\t]);\n\n\t\tlet line = PathSeg::Line(Line::new(Point::new(150., 150.), Point::new(20., 20.)));\n\n\t\tlet cubic_intersections = filtered_segment_intersections(cubic_segment, line, None, None);\n\t\tlet quadratic_1_intersections = filtered_segment_intersections(quadratic_segment, line, None, None);\n\t\tlet bezpath_intersections = bezpath_and_segment_intersections(&bezpath, line, None, None);\n\n\t\tassert!(\n\t\t\tdvec2_compare(\n\t\t\t\tcubic_segment.eval(cubic_intersections[0]),\n\t\t\t\tbezpath.segments().nth(bezpath_intersections[0].0).unwrap().eval(bezpath_intersections[0].1),\n\t\t\t\tMAX_ABSOLUTE_DIFFERENCE\n\t\t\t)\n\t\t\t.all()\n\t\t);\n\n\t\tassert!(\n\t\t\tdvec2_compare(\n\t\t\t\tquadratic_segment.eval(quadratic_1_intersections[0]),\n\t\t\t\tbezpath.segments().nth(bezpath_intersections[1].0).unwrap().eval(bezpath_intersections[1].1),\n\t\t\t\tMAX_ABSOLUTE_DIFFERENCE\n\t\t\t)\n\t\t\t.all()\n\t\t);\n\n\t\tassert!(\n\t\t\tdvec2_compare(\n\t\t\t\tquadratic_segment.eval(quadratic_1_intersections[1]),\n\t\t\t\tbezpath.segments().nth(bezpath_intersections[2].0).unwrap().eval(bezpath_intersections[2].1),\n\t\t\t\tMAX_ABSOLUTE_DIFFERENCE\n\t\t\t)\n\t\t\t.all()\n\t\t);\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/vector-types/src/vector/algorithms/merge_by_distance.rs",
    "content": "use crate::vector::{PointDomain, PointId, SegmentDomain, SegmentId, Vector};\nuse glam::{DAffine2, DVec2};\nuse petgraph::graph::{EdgeIndex, NodeIndex, UnGraph};\nuse petgraph::prelude::UnGraphMap;\nuse rustc_hash::FxHashMap;\nuse rustc_hash::FxHashSet;\n\npub trait MergeByDistanceExt {\n\t/// Collapse all points with edges shorter than the specified distance\n\tfn merge_by_distance_topological(&mut self, distance: f64);\n\tfn merge_by_distance_spatial(&mut self, transform: DAffine2, distance: f64);\n}\n\nimpl<Upstream: 'static> MergeByDistanceExt for Vector<Upstream> {\n\tfn merge_by_distance_topological(&mut self, distance: f64) {\n\t\t// Treat self as an undirected graph\n\t\tlet indices = VectorIndex::build_from(self);\n\n\t\t// TODO: We lose information on the winding order by using an undirected graph. Switch to a directed graph and fix the algorithm to handle that.\n\t\t// Graph containing only short edges, referencing the data graph\n\t\tlet mut short_edges = UnGraphMap::new();\n\n\t\tfor segment_id in self.segment_ids().iter().copied() {\n\t\t\tlet length = indices.segment_chord_length(segment_id);\n\t\t\tif length < distance {\n\t\t\t\tlet [start, end] = indices.segment_ends(segment_id);\n\t\t\t\tlet start = indices.point_graph.node_weight(start).unwrap().id;\n\t\t\t\tlet end = indices.point_graph.node_weight(end).unwrap().id;\n\n\t\t\t\tshort_edges.add_node(start);\n\t\t\t\tshort_edges.add_node(end);\n\t\t\t\tshort_edges.add_edge(start, end, segment_id);\n\t\t\t}\n\t\t}\n\n\t\t// Group connected segments to collapse them into a single point\n\t\t// TODO: there are a few possible algorithms for this - perhaps test empirically to find fastest\n\t\tlet collapse: Vec<FxHashSet<PointId>> = petgraph::algo::tarjan_scc(&short_edges).into_iter().map(|connected| connected.into_iter().collect()).collect();\n\t\tlet average_position = collapse\n\t\t\t.iter()\n\t\t\t.map(|collapse_set| {\n\t\t\t\tlet sum: DVec2 = collapse_set.iter().map(|&id| indices.point_position(id, self)).sum();\n\t\t\t\tsum / collapse_set.len() as f64\n\t\t\t})\n\t\t\t.collect::<Vec<_>>();\n\n\t\t// Collect points and segments to delete at the end to avoid invalidating indices\n\t\tlet mut points_to_delete = FxHashSet::default();\n\t\tlet mut segments_to_delete = FxHashSet::default();\n\t\tfor (mut collapse_set, average_pos) in collapse.into_iter().zip(average_position.into_iter()) {\n\t\t\t// Remove any segments where both endpoints are in the collapse set\n\t\t\tsegments_to_delete.extend(self.segment_domain.iter().filter_map(|(id, start_offset, end_offset, _)| {\n\t\t\t\tlet start = self.point_domain.ids()[start_offset];\n\t\t\t\tlet end = self.point_domain.ids()[end_offset];\n\t\t\t\tif collapse_set.contains(&start) && collapse_set.contains(&end) { Some(id) } else { None }\n\t\t\t}));\n\n\t\t\t// Delete all points but the first, set its position to the average, and update segments\n\t\t\tlet first_id = collapse_set.iter().copied().next().unwrap();\n\t\t\tcollapse_set.remove(&first_id);\n\t\t\tlet first_offset = indices.point_to_offset[&first_id];\n\n\t\t\t// Look for segments with endpoints in `collapse_set` and replace them with the point we are collapsing to\n\t\t\tfor (_, start_offset, end_offset, handles) in self.segment_domain.iter_mut() {\n\t\t\t\tlet start_id = self.point_domain.ids()[*start_offset];\n\t\t\t\tlet end_id = self.point_domain.ids()[*end_offset];\n\n\t\t\t\t// Update Bezier handles for moved points\n\t\t\t\tif start_id == first_id {\n\t\t\t\t\tlet point_position = self.point_domain.position[*start_offset];\n\t\t\t\t\thandles.move_start(average_pos - point_position);\n\t\t\t\t}\n\t\t\t\tif end_id == first_id {\n\t\t\t\t\tlet point_position = self.point_domain.position[*end_offset];\n\t\t\t\t\thandles.move_end(average_pos - point_position);\n\t\t\t\t}\n\n\t\t\t\t// Replace removed points with the collapsed point\n\t\t\t\tif collapse_set.contains(&start_id) {\n\t\t\t\t\tlet point_position = self.point_domain.position[*start_offset];\n\t\t\t\t\t*start_offset = first_offset;\n\t\t\t\t\thandles.move_start(average_pos - point_position);\n\t\t\t\t}\n\t\t\t\tif collapse_set.contains(&end_id) {\n\t\t\t\t\tlet point_position = self.point_domain.position[*end_offset];\n\t\t\t\t\t*end_offset = first_offset;\n\t\t\t\t\thandles.move_end(average_pos - point_position);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Update the position of the collapsed point\n\t\t\tself.point_domain.position[first_offset] = average_pos;\n\n\t\t\tpoints_to_delete.extend(collapse_set)\n\t\t}\n\n\t\t// Remove faces whose start or end segments are removed\n\t\t// TODO: Adjust faces and only delete if all (or all but one) segments are removed\n\t\tself.region_domain\n\t\t\t.retain_with_region(|_, segment_range| segments_to_delete.contains(segment_range.start()) || segments_to_delete.contains(segment_range.end()));\n\t\tself.segment_domain.retain(|id| !segments_to_delete.contains(id), usize::MAX);\n\t\tself.point_domain.retain(&mut self.segment_domain, |id| !points_to_delete.contains(id));\n\t}\n\n\tfn merge_by_distance_spatial(&mut self, transform: DAffine2, distance: f64) {\n\t\tlet point_count = self.point_domain.positions().len();\n\n\t\t// Find min x and y for grid cell normalization\n\t\tlet mut min_x = f64::MAX;\n\t\tlet mut min_y = f64::MAX;\n\n\t\t// Calculate mins without collecting all positions\n\t\tfor &pos in self.point_domain.positions() {\n\t\t\tlet transformed_pos = transform.transform_point2(pos);\n\t\t\tmin_x = min_x.min(transformed_pos.x);\n\t\t\tmin_y = min_y.min(transformed_pos.y);\n\t\t}\n\n\t\t// Create a spatial grid with cell size of 'distance'\n\t\tuse std::collections::HashMap;\n\t\tlet mut grid: HashMap<(i32, i32), Vec<usize>> = HashMap::new();\n\n\t\t// Add points to grid cells without collecting all positions first\n\t\tfor i in 0..point_count {\n\t\t\tlet pos = transform.transform_point2(self.point_domain.positions()[i]);\n\t\t\tlet grid_x = ((pos.x - min_x) / distance).floor() as i32;\n\t\t\tlet grid_y = ((pos.y - min_y) / distance).floor() as i32;\n\n\t\t\tgrid.entry((grid_x, grid_y)).or_default().push(i);\n\t\t}\n\n\t\t// Create point index mapping for merged points\n\t\tlet mut point_index_map = vec![None; point_count];\n\t\tlet mut merged_positions = Vec::new();\n\t\tlet mut merged_indices = Vec::new();\n\n\t\t// Process each point\n\t\tfor i in 0..point_count {\n\t\t\t// Skip points that have already been processed\n\t\t\tif point_index_map[i].is_some() {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet pos_i = transform.transform_point2(self.point_domain.positions()[i]);\n\t\t\tlet grid_x = ((pos_i.x - min_x) / distance).floor() as i32;\n\t\t\tlet grid_y = ((pos_i.y - min_y) / distance).floor() as i32;\n\n\t\t\tlet mut group = vec![i];\n\n\t\t\t// Check only neighboring cells (3x3 grid around current cell)\n\t\t\tfor dx in -1..=1 {\n\t\t\t\tfor dy in -1..=1 {\n\t\t\t\t\tlet neighbor_cell = (grid_x + dx, grid_y + dy);\n\n\t\t\t\t\tif let Some(indices) = grid.get(&neighbor_cell) {\n\t\t\t\t\t\tfor &j in indices {\n\t\t\t\t\t\t\tif j > i && point_index_map[j].is_none() {\n\t\t\t\t\t\t\t\tlet pos_j = transform.transform_point2(self.point_domain.positions()[j]);\n\t\t\t\t\t\t\t\tif pos_i.distance(pos_j) <= distance {\n\t\t\t\t\t\t\t\t\tgroup.push(j);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Create merged point - calculate positions as needed\n\t\t\tlet merged_position = group\n\t\t\t\t.iter()\n\t\t\t\t.map(|&idx| transform.transform_point2(self.point_domain.positions()[idx]))\n\t\t\t\t.fold(DVec2::ZERO, |sum, pos| sum + pos)\n\t\t\t\t/ group.len() as f64;\n\n\t\t\tlet merged_position = transform.inverse().transform_point2(merged_position);\n\t\t\tlet merged_index = merged_positions.len();\n\n\t\t\tmerged_positions.push(merged_position);\n\t\t\tmerged_indices.push(self.point_domain.ids()[group[0]]);\n\n\t\t\t// Update mapping for all points in the group\n\t\t\tfor &idx in &group {\n\t\t\t\tpoint_index_map[idx] = Some(merged_index);\n\t\t\t}\n\t\t}\n\n\t\t// Create new point domain with merged points\n\t\tlet mut new_point_domain = PointDomain::new();\n\t\tfor (idx, pos) in merged_indices.into_iter().zip(merged_positions) {\n\t\t\tnew_point_domain.push(idx, pos);\n\t\t}\n\n\t\t// Update segment domain\n\t\tlet mut new_segment_domain = SegmentDomain::new();\n\t\tfor segment_idx in 0..self.segment_domain.ids().len() {\n\t\t\tlet id = self.segment_domain.ids()[segment_idx];\n\t\t\tlet start = self.segment_domain.start_point()[segment_idx];\n\t\t\tlet end = self.segment_domain.end_point()[segment_idx];\n\t\t\tlet handles = self.segment_domain.handles()[segment_idx];\n\t\t\tlet stroke = self.segment_domain.stroke()[segment_idx];\n\n\t\t\t// Get new indices for start and end points\n\t\t\tlet new_start = point_index_map[start].unwrap();\n\t\t\tlet new_end = point_index_map[end].unwrap();\n\n\t\t\t// Skip segments where start and end points were merged\n\t\t\tif new_start != new_end {\n\t\t\t\tnew_segment_domain.push(id, new_start, new_end, handles, stroke);\n\t\t\t}\n\t\t}\n\n\t\t// Create new vector geometry\n\t\tself.point_domain = new_point_domain;\n\t\tself.segment_domain = new_segment_domain;\n\t}\n}\n\n/// All the fixed fields of a point from the point domain.\npub(crate) struct Point {\n\tpub id: PointId,\n\tpub position: DVec2,\n}\n\n/// Useful indexes to speed up various operations on [`Vector`].\n///\n/// Important: It is the user's responsibility to ensure the indexes remain valid after mutations to the data.\npub struct VectorIndex {\n\t/// Points and segments form a graph. Store it here in a form amenable to graph algorithms.\n\t///\n\t/// Currently, segment data is not stored as it is not used, but it could easily be added.\n\tpub(crate) point_graph: UnGraph<Point, ()>,\n\tpub(crate) segment_to_edge: FxHashMap<SegmentId, EdgeIndex>,\n\t/// Get the offset from the point ID.\n\tpub(crate) point_to_offset: FxHashMap<PointId, usize>,\n\t// TODO: faces\n}\n\nimpl VectorIndex {\n\t/// Construct a [`VectorIndex`] by building indexes from the given [`Vector`]. Takes `O(n)` time.\n\tpub fn build_from<Upstream: 'static>(data: &Vector<Upstream>) -> Self {\n\t\tlet point_to_offset = data.point_domain.ids().iter().copied().enumerate().map(|(a, b)| (b, a)).collect::<FxHashMap<_, _>>();\n\n\t\tlet mut point_to_node = FxHashMap::default();\n\t\tlet mut segment_to_edge = FxHashMap::default();\n\n\t\tlet mut graph = UnGraph::new_undirected();\n\n\t\tfor (point_id, position) in data.point_domain.iter() {\n\t\t\tlet idx = graph.add_node(Point { id: point_id, position });\n\t\t\tpoint_to_node.insert(point_id, idx);\n\t\t}\n\n\t\tfor (segment_id, start_offset, end_offset, ..) in data.segment_domain.iter() {\n\t\t\tlet start_id = data.point_domain.ids()[start_offset];\n\t\t\tlet end_id = data.point_domain.ids()[end_offset];\n\t\t\tlet edge = graph.add_edge(point_to_node[&start_id], point_to_node[&end_id], ());\n\n\t\t\tsegment_to_edge.insert(segment_id, edge);\n\t\t}\n\n\t\tSelf {\n\t\t\tpoint_graph: graph,\n\t\t\tsegment_to_edge,\n\t\t\tpoint_to_offset,\n\t\t}\n\t}\n\n\t/// Fetch the length of given segment's chord. Takes `O(1)` time.\n\t///\n\t/// # Panics\n\t///\n\t/// Will panic if no segment with the given ID is found.\n\tpub fn segment_chord_length(&self, id: SegmentId) -> f64 {\n\t\tlet edge_idx = self.segment_to_edge[&id];\n\t\tlet (start, end) = self.point_graph.edge_endpoints(edge_idx).unwrap();\n\t\tlet start_position = self.point_graph.node_weight(start).unwrap().position;\n\t\tlet end_position = self.point_graph.node_weight(end).unwrap().position;\n\t\t(start_position - end_position).length()\n\t}\n\n\t/// Get the ends of a segment. Takes `O(1)` time.\n\t///\n\t/// The IDs will be ordered [smallest, largest] so they can be used to find other segments with the same endpoints, regardless of direction.\n\t///\n\t/// # Panics\n\t///\n\t/// This function will panic if the ID is not present.\n\tpub fn segment_ends(&self, id: SegmentId) -> [NodeIndex; 2] {\n\t\tlet (start, end) = self.point_graph.edge_endpoints(self.segment_to_edge[&id]).unwrap();\n\t\tif start < end { [start, end] } else { [end, start] }\n\t}\n\n\t/// Get the physical location of a point. Takes `O(1)` time.\n\t///\n\t/// # Panics\n\t///\n\t/// Will panic if `id` isn't in the data.\n\tpub fn point_position<Upstream: 'static>(&self, id: PointId, data: &Vector<Upstream>) -> DVec2 {\n\t\tlet offset = self.point_to_offset[&id];\n\t\tdata.point_domain.positions()[offset]\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/vector-types/src/vector/algorithms/mod.rs",
    "content": "pub mod bezpath_algorithms;\nmod contants;\npub mod intersection;\npub mod merge_by_distance;\npub mod offset_subpath;\npub mod poisson_disk;\npub mod spline;\npub mod util;\n"
  },
  {
    "path": "node-graph/libraries/vector-types/src/vector/algorithms/offset_subpath.rs",
    "content": "use super::bezpath_algorithms::{clip_simple_bezpaths, miter_line_join, round_line_join};\nuse crate::vector::misc::point_to_dvec2;\nuse kurbo::{BezPath, Join, ParamCurve, PathEl, PathSeg};\n\n/// Value to control smoothness and mathematical accuracy to offset a cubic Bezier.\nconst CUBIC_REGULARIZATION_ACCURACY: f64 = 0.5;\n/// Constant used to determine if `f64`s are equivalent.\npub const MAX_ABSOLUTE_DIFFERENCE: f64 = 1e-7;\n/// Squared version to avoid sqrt in distance checks.\nconst MAX_ABSOLUTE_DIFFERENCE_SQUARED: f64 = MAX_ABSOLUTE_DIFFERENCE * MAX_ABSOLUTE_DIFFERENCE;\nconst MAX_FITTED_SEGMENTS: usize = 10000;\n\n/// Reduces the segments of the bezpath into simple subcurves, then offset each subcurve a set `distance` away.\n/// The intersections of segments of the subpath are joined using the method specified by the `join` argument.\npub fn offset_bezpath(bezpath: &BezPath, distance: f64, join: Join, miter_limit: Option<f64>) -> BezPath {\n\t// An offset at a distance 0 from the curve is simply the same curve.\n\t// An offset of a single point is not defined.\n\tif distance == 0. || bezpath.get_seg(1).is_none() {\n\t\treturn bezpath.clone();\n\t}\n\n\tlet mut bezpaths = bezpath\n\t\t.segments()\n\t\t.map(|bezier| bezier.to_cubic())\n\t\t.filter_map(|cubic_bez| {\n\t\t\t// Skip degenerate curves where all control points are at the same location.\n\t\t\t// Offsetting a point is undefined and causes infinite recursion in fit_to_bezpath.\n\t\t\tlet start = cubic_bez.p0;\n\t\t\tlet is_degenerate = start.distance_squared(cubic_bez.p1) < MAX_ABSOLUTE_DIFFERENCE_SQUARED\n\t\t\t\t&& start.distance_squared(cubic_bez.p2) < MAX_ABSOLUTE_DIFFERENCE_SQUARED\n\t\t\t\t&& start.distance_squared(cubic_bez.p3) < MAX_ABSOLUTE_DIFFERENCE_SQUARED;\n\n\t\t\tif is_degenerate {\n\t\t\t\treturn None;\n\t\t\t}\n\n\t\t\tlet mut fitted = BezPath::new();\n\t\t\tkurbo::offset::offset_cubic(cubic_bez, distance, CUBIC_REGULARIZATION_ACCURACY, &mut fitted);\n\n\t\t\tif fitted.segments().count() > MAX_FITTED_SEGMENTS {\n\t\t\t\tNone\n\t\t\t} else {\n\t\t\t\tfitted.get_seg(1).is_some().then_some(fitted)\n\t\t\t}\n\t\t})\n\t\t.collect::<Vec<BezPath>>();\n\n\t// Clip or join consecutive Subpaths\n\tfor i in 0..bezpaths.len() - 1 {\n\t\tlet j = i + 1;\n\t\tlet bezpath1 = &bezpaths[i];\n\t\tlet bezpath2 = &bezpaths[j];\n\n\t\tlet last_segment_end = point_to_dvec2(bezpath1.segments().last().unwrap().end());\n\t\tlet first_segment_start = point_to_dvec2(bezpath2.segments().next().unwrap().start());\n\n\t\t// If the anchors are approximately equal, there is no need to clip / join the segments\n\t\tif last_segment_end.abs_diff_eq(first_segment_start, MAX_ABSOLUTE_DIFFERENCE) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// The angle is concave. The Subpath overlap and must be clipped\n\t\tlet mut apply_join = true;\n\n\t\tif let Some((clipped_subpath1, clipped_subpath2)) = clip_simple_bezpaths(bezpath1, bezpath2) {\n\t\t\tbezpaths[i] = clipped_subpath1;\n\t\t\tbezpaths[j] = clipped_subpath2;\n\t\t\tapply_join = false;\n\t\t}\n\t\t// The angle is convex. The Subpath must be joined using the specified join type\n\t\tif apply_join {\n\t\t\tmatch join {\n\t\t\t\tJoin::Bevel => {\n\t\t\t\t\tlet element = PathEl::LineTo(bezpaths[j].segments().next().unwrap().start());\n\t\t\t\t\tbezpaths[i].push(element);\n\t\t\t\t}\n\t\t\t\tJoin::Miter => {\n\t\t\t\t\tlet element = miter_line_join(&bezpaths[i], &bezpaths[j], miter_limit);\n\t\t\t\t\tif let Some(element) = element {\n\t\t\t\t\t\tbezpaths[i].push(element[0]);\n\t\t\t\t\t\tbezpaths[i].push(element[1]);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlet element = PathEl::LineTo(bezpaths[j].segments().next().unwrap().start());\n\t\t\t\t\t\tbezpaths[i].push(element);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tJoin::Round => {\n\t\t\t\t\tlet center = point_to_dvec2(bezpath.get_seg(i + 1).unwrap().end());\n\t\t\t\t\tlet elements = round_line_join(&bezpaths[i], &bezpaths[j], center);\n\t\t\t\t\tbezpaths[i].push(elements[0]);\n\t\t\t\t\tbezpaths[i].push(elements[1]);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Clip any overlap in the last segment\n\tlet is_bezpath_closed = bezpath.elements().last().is_some_and(|element| *element == PathEl::ClosePath);\n\tif is_bezpath_closed {\n\t\tlet mut apply_join = true;\n\t\tif let Some((clipped_subpath1, clipped_subpath2)) = clip_simple_bezpaths(&bezpaths[bezpaths.len() - 1], &bezpaths[0]) {\n\t\t\t// Merge the clipped subpaths\n\t\t\tlet last_index = bezpaths.len() - 1;\n\t\t\tbezpaths[last_index] = clipped_subpath1;\n\t\t\tbezpaths[0] = clipped_subpath2;\n\t\t\tapply_join = false;\n\t\t}\n\n\t\tif apply_join {\n\t\t\tmatch join {\n\t\t\t\tJoin::Bevel => {\n\t\t\t\t\tlet last_subpath_index = bezpaths.len() - 1;\n\t\t\t\t\tlet element = PathEl::LineTo(bezpaths[0].segments().next().unwrap().start());\n\t\t\t\t\tbezpaths[last_subpath_index].push(element);\n\t\t\t\t}\n\t\t\t\tJoin::Miter => {\n\t\t\t\t\tlet last_subpath_index = bezpaths.len() - 1;\n\t\t\t\t\tlet element = miter_line_join(&bezpaths[last_subpath_index], &bezpaths[0], miter_limit);\n\t\t\t\t\tif let Some(element) = element {\n\t\t\t\t\t\tbezpaths[last_subpath_index].push(element[0]);\n\t\t\t\t\t\tbezpaths[last_subpath_index].push(element[1]);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlet element = PathEl::LineTo(bezpaths[0].segments().next().unwrap().start());\n\t\t\t\t\t\tbezpaths[last_subpath_index].push(element);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tJoin::Round => {\n\t\t\t\t\tlet last_subpath_index = bezpaths.len() - 1;\n\t\t\t\t\tlet center = point_to_dvec2(bezpath.get_seg(1).unwrap().start());\n\t\t\t\t\tlet elements = round_line_join(&bezpaths[last_subpath_index], &bezpaths[0], center);\n\t\t\t\t\tbezpaths[last_subpath_index].push(elements[0]);\n\t\t\t\t\tbezpaths[last_subpath_index].push(elements[1]);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Merge the bezpaths and its segments. Drop points which overlap with one another.\n\tlet segments = bezpaths.iter().flat_map(|bezpath| bezpath.segments().collect::<Vec<PathSeg>>()).collect::<Vec<PathSeg>>();\n\tlet mut offset_bezpath = segments.iter().fold(BezPath::new(), |mut acc, segment| {\n\t\tif acc.elements().is_empty() {\n\t\t\tacc.move_to(segment.start());\n\t\t}\n\t\tacc.push(segment.as_path_el());\n\t\tacc\n\t});\n\n\tif is_bezpath_closed {\n\t\toffset_bezpath.close_path();\n\t}\n\n\toffset_bezpath\n}\n"
  },
  {
    "path": "node-graph/libraries/vector-types/src/vector/algorithms/poisson_disk.rs",
    "content": "use glam::DVec2;\nuse std::collections::HashMap;\nuse std::f64;\n\nconst DEEPEST_SUBDIVISION_LEVEL_BEFORE_DISCARDING: usize = 8;\n\n/// Fast (O(n) with respect to time and memory) algorithm for generating a maximal set of points using Poisson-disk sampling.\n/// Based on the paper:\n/// \"Poisson Disk Point Sets by Hierarchical Dart Throwing\"\n/// <https://scholarsarchive.byu.edu/facpub/237/>\npub fn poisson_disk_sample(\n\toffset: DVec2,\n\twidth: f64,\n\theight: f64,\n\tdiameter: f64,\n\tpoint_in_shape_checker: impl Fn(DVec2) -> bool,\n\tline_intersect_shape_checker: impl Fn((f64, f64), (f64, f64)) -> bool,\n\trng: impl FnMut() -> f64,\n) -> Vec<DVec2> {\n\tlet mut rng = rng;\n\tlet diameter_squared = diameter.powi(2);\n\n\t// Initialize a place to store the generated points within a spatial acceleration structure\n\tlet mut points_grid = AccelerationGrid::new(width, height, diameter);\n\n\t// Pick a grid size for the base-level domain that's as large as possible, while also:\n\t// - Dividing into an integer number of cells across the dartboard domain, to avoid wastefully throwing darts beyond the width and height of the dartboard domain\n\t// - Being fully covered by the radius around a dart thrown anywhere in its area, where the worst-case is a corner which has a distance of sqrt(2) to the opposite corner\n\tlet greater_dimension = width.max(height);\n\tlet base_level_grid_size = greater_dimension / (greater_dimension * f64::consts::SQRT_2 / (diameter / 2.)).ceil();\n\n\t// Initialize the problem by including all base-level squares in the active list since they're all part of the yet-to-be-targetted dartboard domain\n\tlet base_level = ActiveListLevel::new_filled(base_level_grid_size, offset, width, height, &point_in_shape_checker, &line_intersect_shape_checker);\n\t// In the future, if necessary, this could be turned into a fixed-length array with worst-case length `f64::MANTISSA_DIGITS`\n\tlet mut active_list_levels = vec![base_level];\n\n\t// Loop until all active squares have been processed, meaning all of the dartboard domain has been checked\n\twhile active_list_levels.iter().any(|active_list| active_list.not_empty()) {\n\t\t// Randomly pick a square in the dartboard domain, with probability proportional to its area\n\t\tlet (active_square_level, active_square_index_in_level) = target_active_square(&active_list_levels, &mut rng);\n\n\t\t// The level contains the list of all active squares at this target square's subdivision depth\n\t\tlet level = &mut active_list_levels[active_square_level];\n\n\t\t// Take the targetted active square out of the list and get its size\n\t\tlet active_square = level.take_square(active_square_index_in_level);\n\t\tlet active_square_size = level.square_size();\n\n\t\t// Skip this target square if it's within range of any current points, since more nearby points could have been added after this square was included in the active list\n\t\tif !square_not_covered_by_poisson_points(active_square.top_left_corner(), active_square_size / 2., diameter_squared, &points_grid) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Throw a dart by picking a random point within this target square\n\t\tlet point = {\n\t\t\tlet active_top_left_corner = active_square.top_left_corner();\n\t\t\tlet x = active_top_left_corner.x + rng() * active_square_size;\n\t\t\tlet y = active_top_left_corner.y + rng() * active_square_size;\n\t\t\t(x, y).into()\n\t\t};\n\n\t\t// If the dart hit a valid spot, save that point (we're now permanently done with this target square's region)\n\t\tif point_not_covered_by_poisson_points(point, diameter_squared, &points_grid) {\n\t\t\t// Silently reject the point if it lies outside the shape\n\t\t\tif active_square.fully_in_shape() || point_in_shape_checker(point + offset) {\n\t\t\t\tpoints_grid.insert(point);\n\t\t\t}\n\t\t}\n\t\t// Otherwise, subdivide this target square and add valid sub-squares back to the active list for later targetting\n\t\telse {\n\t\t\t// Discard any targetable domain smaller than this limited number of subdivision levels since it's too small to matter\n\t\t\tlet next_level_deeper_level = active_square_level + 1;\n\t\t\tif next_level_deeper_level > DEEPEST_SUBDIVISION_LEVEL_BEFORE_DISCARDING {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// If necessary for the following step, add another layer of depth to store squares at the next subdivision level\n\t\t\tif active_list_levels.len() <= next_level_deeper_level {\n\t\t\t\tactive_list_levels.push(ActiveListLevel::new(active_square_size / 2.))\n\t\t\t}\n\n\t\t\t// Get the list of active squares at the level of depth beneath this target square's level\n\t\t\tlet next_level_deeper = &mut active_list_levels[next_level_deeper_level];\n\n\t\t\t// Subdivide this target square into four sub-squares; running out of numerical precision will make this terminate at very small scales\n\t\t\tlet subdivided_size = active_square_size / 2.;\n\t\t\tlet active_top_left_corner = active_square.top_left_corner();\n\t\t\tlet subdivided = [\n\t\t\t\tactive_top_left_corner + DVec2::new(0., 0.),\n\t\t\t\tactive_top_left_corner + DVec2::new(subdivided_size, 0.),\n\t\t\t\tactive_top_left_corner + DVec2::new(0., subdivided_size),\n\t\t\t\tactive_top_left_corner + DVec2::new(subdivided_size, subdivided_size),\n\t\t\t];\n\n\t\t\t// Add the sub-squares which aren't within the radius of a nearby point to the sub-level's active list\n\t\t\tlet half_subdivided_size = subdivided_size / 2.;\n\t\t\tlet new_sub_squares = subdivided.into_iter().filter_map(|sub_square| {\n\t\t\t\t// Any sub-squares within the radius of a nearby point are filtered out\n\t\t\t\tif !square_not_covered_by_poisson_points(sub_square, half_subdivided_size, diameter_squared, &points_grid) {\n\t\t\t\t\treturn None;\n\t\t\t\t}\n\n\t\t\t\t// Fully inside the shape\n\t\t\t\tif active_square.fully_in_shape() {\n\t\t\t\t\tSome(ActiveSquare::new(sub_square, true))\n\t\t\t\t}\n\t\t\t\t// Intersecting the shape's border\n\t\t\t\telse {\n\t\t\t\t\t// The sub-square is fully inside the shape if its top-left corner is inside and its edges don't intersect the shape border\n\t\t\t\t\tlet point_with_offset = sub_square + offset;\n\t\t\t\t\tlet square_edges_intersect_shape = {\n\t\t\t\t\t\tlet min = point_with_offset;\n\t\t\t\t\t\tlet max = min + DVec2::splat(subdivided_size);\n\n\t\t\t\t\t\t// Top edge line\n\t\t\t\t\t\tline_intersect_shape_checker((min.x, min.y), (max.x, min.y)) ||\n\t\t\t\t\t\t// Right edge line\n\t\t\t\t\t\tline_intersect_shape_checker((max.x, min.y), (max.x, max.y)) ||\n\t\t\t\t\t\t// Bottom edge line\n\t\t\t\t\t\tline_intersect_shape_checker((max.x, max.y), (min.x, max.y)) ||\n\t\t\t\t\t\t// Left edge line\n\t\t\t\t\t\tline_intersect_shape_checker((min.x, max.y), (min.x, min.y))\n\t\t\t\t\t};\n\t\t\t\t\tlet sub_square_fully_inside_shape = !square_edges_intersect_shape && point_in_shape_checker(point_with_offset) && point_in_shape_checker(point_with_offset + subdivided_size);\n\n\t\t\t\t\tSome(ActiveSquare::new(sub_square, sub_square_fully_inside_shape))\n\t\t\t\t}\n\t\t\t});\n\t\t\tnext_level_deeper.add_squares(new_sub_squares);\n\t\t}\n\t}\n\n\tpoints_grid.final_points(offset)\n}\n\n/// Randomly pick a square in the dartboard domain, with probability proportional to its area.\n/// Returns a tuple with the subdivision level depth and the square index at that depth.\nfn target_active_square(active_list_levels: &[ActiveListLevel], rng: &mut impl FnMut() -> f64) -> (usize, usize) {\n\tlet active_squares_total_area: f64 = active_list_levels.iter().map(|active_list| active_list.total_area()).sum();\n\tlet mut index_into_area = rng() * active_squares_total_area;\n\n\tfor (level, active_list_level) in active_list_levels.iter().enumerate() {\n\t\tlet subtracted = index_into_area - active_list_level.total_area();\n\t\tif subtracted > 0. {\n\t\t\tindex_into_area = subtracted;\n\t\t\tcontinue;\n\t\t}\n\n\t\tlet active_square_index_in_level = (index_into_area / active_list_levels[level].square_area()).floor() as usize;\n\t\treturn (level, active_square_index_in_level);\n\t}\n\n\tpanic!(\"index_into_area couldn't be be mapped to a square in any level of the active lists\");\n}\n\nfn point_not_covered_by_poisson_points(point: DVec2, diameter_squared: f64, points_grid: &AccelerationGrid) -> bool {\n\tpoints_grid.nearby_points(point).all(|nearby_point| {\n\t\tlet x_separation = nearby_point.x - point.x;\n\t\tlet y_separation = nearby_point.y - point.y;\n\n\t\tx_separation.powi(2) + y_separation.powi(2) > diameter_squared\n\t})\n}\n\nfn square_not_covered_by_poisson_points(point: DVec2, half_square_size: f64, diameter_squared: f64, points_grid: &AccelerationGrid) -> bool {\n\tlet square_center_x = point.x + half_square_size;\n\tlet square_center_y = point.y + half_square_size;\n\n\tpoints_grid.nearby_points(point).all(|nearby_point| {\n\t\tlet x_distance = (square_center_x - nearby_point.x).abs() + half_square_size;\n\t\tlet y_distance = (square_center_y - nearby_point.y).abs() + half_square_size;\n\n\t\tx_distance.powi(2) + y_distance.powi(2) > diameter_squared\n\t})\n}\n\n#[inline(always)]\nfn cartesian_product<A, B>(a: A, b: B) -> impl Iterator<Item = (A::Item, B::Item)>\nwhere\n\tA: Iterator + Clone,\n\tB: Iterator + Clone,\n\tA::Item: Clone,\n\tB::Item: Clone,\n{\n\ta.flat_map(move |i| b.clone().map(move |j| (i.clone(), j)))\n}\n\n/// A square (represented by its top left corner position and width/height of `square_size`) that is currently a candidate for targetting by the dart throwing process.\n/// The positive sign bit encodes if the square is contained entirely within the masking shape, or negative if it's outside or intersects the shape path.\npub struct ActiveSquare(DVec2);\n\nimpl ActiveSquare {\n\tpub fn new(top_left_corner: DVec2, fully_in_shape: bool) -> Self {\n\t\tSelf(if fully_in_shape { top_left_corner } else { -top_left_corner })\n\t}\n\n\tpub fn top_left_corner(&self) -> DVec2 {\n\t\tself.0.abs()\n\t}\n\n\tpub fn fully_in_shape(&self) -> bool {\n\t\tself.0.x.is_sign_positive()\n\t}\n}\n\npub struct ActiveListLevel {\n\t/// List of all subdivided squares of the same size that are currently candidates for targetting by the dart throwing process\n\tactive_squares: Vec<ActiveSquare>,\n\t/// Width and height of the squares in this level of subdivision\n\tsquare_size: f64,\n\t/// Current sum of the area in all active squares in this subdivision level\n\ttotal_area: f64,\n}\n\nimpl ActiveListLevel {\n\t#[inline(always)]\n\tpub fn new(square_size: f64) -> Self {\n\t\tSelf {\n\t\t\tactive_squares: Vec::new(),\n\t\t\tsquare_size,\n\t\t\ttotal_area: 0.,\n\t\t}\n\t}\n\n\tpub fn new_filled(\n\t\tsquare_size: f64,\n\t\toffset: DVec2,\n\t\twidth: f64,\n\t\theight: f64,\n\t\tpoint_in_shape_checker: impl Fn(DVec2) -> bool,\n\t\tline_intersect_shape_checker: impl Fn((f64, f64), (f64, f64)) -> bool,\n\t) -> Self {\n\t\t// These should divide evenly but rounding is to protect against small numerical imprecision errors\n\t\tlet x_squares = (width / square_size).round() as usize;\n\t\tlet y_squares = (height / square_size).round() as usize;\n\n\t\t// Hashes based on the grid cell coordinates and direction of the line: (x, y, is_vertical)\n\t\tlet mut line_intersection_cache: HashMap<(usize, usize, bool), bool> = HashMap::new();\n\n\t\t// Populate each square with its top-left corner coordinate\n\t\tlet active_squares: Vec<_> = cartesian_product(0..x_squares, 0..y_squares)\n\t\t\t.filter_map(|(x, y)| {\n\t\t\t\tlet corner = DVec2::new(x as f64 * square_size, y as f64 * square_size);\n\t\t\t\tlet corner_with_offset = corner + offset;\n\n\t\t\t\t// Lazily check (and cache) if the square's edges intersect the shape, which is an expensive operation\n\t\t\t\tlet mut square_edges_intersect_shape_value = None;\n\t\t\t\tlet mut square_edges_intersect_shape = || {\n\t\t\t\t\tsquare_edges_intersect_shape_value.unwrap_or_else(|| {\n\t\t\t\t\t\tlet square_edges_intersect_shape = {\n\t\t\t\t\t\t\tlet min = corner_with_offset;\n\t\t\t\t\t\t\tlet max = min + DVec2::splat(square_size);\n\n\t\t\t\t\t\t\t// Top edge line\n\t\t\t\t\t\t\t*line_intersection_cache.entry((x, y, false)).or_insert_with(|| line_intersect_shape_checker((min.x, min.y), (max.x, min.y))) ||\n\t\t\t\t\t\t\t// Right edge line\n\t\t\t\t\t\t\t*line_intersection_cache.entry((x + 1, y, true)).or_insert_with(|| line_intersect_shape_checker((max.x, min.y), (max.x, max.y))) ||\n\t\t\t\t\t\t\t// Bottom edge line\n\t\t\t\t\t\t\t*line_intersection_cache.entry((x, y + 1, false)).or_insert_with(|| line_intersect_shape_checker((max.x, max.y), (min.x, max.y))) ||\n\t\t\t\t\t\t\t// Left edge line\n\t\t\t\t\t\t\t*line_intersection_cache.entry((x, y, true)).or_insert_with(|| line_intersect_shape_checker((min.x, max.y), (min.x, min.y)))\n\t\t\t\t\t\t};\n\t\t\t\t\t\tsquare_edges_intersect_shape_value = Some(square_edges_intersect_shape);\n\t\t\t\t\t\tsquare_edges_intersect_shape\n\t\t\t\t\t})\n\t\t\t\t};\n\n\t\t\t\t// Check if this cell's top-left corner is inside the shape\n\t\t\t\tlet point_in_shape = point_in_shape_checker(corner_with_offset);\n\n\t\t\t\t// Determine if the square is inside the shape\n\t\t\t\tlet square_not_outside_shape = point_in_shape || square_edges_intersect_shape();\n\t\t\t\tif square_not_outside_shape {\n\t\t\t\t\t// Check if this cell's bottom-right corner is inside the shape\n\t\t\t\t\tlet opposite_corner_with_offset = DVec2::new((x + 1) as f64 * square_size, (y + 1) as f64 * square_size) + offset;\n\t\t\t\t\tlet opposite_corner_in_shape = point_in_shape_checker(opposite_corner_with_offset);\n\n\t\t\t\t\tlet square_in_shape = opposite_corner_in_shape && !square_edges_intersect_shape();\n\t\t\t\t\tSome(ActiveSquare::new(corner, square_in_shape))\n\t\t\t\t} else {\n\t\t\t\t\tNone\n\t\t\t\t}\n\t\t\t})\n\t\t\t.collect();\n\n\t\t// Sum every square's area to get the total\n\t\tlet total_area = square_size.powi(2) * active_squares.len() as f64;\n\n\t\tSelf {\n\t\t\tactive_squares,\n\t\t\tsquare_size,\n\t\t\ttotal_area,\n\t\t}\n\t}\n\n\t#[must_use]\n\t#[inline(always)]\n\tpub fn take_square(&mut self, active_square_index: usize) -> ActiveSquare {\n\t\tlet targetted_square = self.active_squares.swap_remove(active_square_index);\n\t\tself.total_area = self.square_size.powi(2) * self.active_squares.len() as f64;\n\t\ttargetted_square\n\t}\n\n\t#[inline(always)]\n\tpub fn add_squares(&mut self, new_squares: impl Iterator<Item = ActiveSquare>) {\n\t\tfor new_square in new_squares {\n\t\t\tself.active_squares.push(new_square);\n\t\t}\n\t\tself.total_area = self.square_size.powi(2) * self.active_squares.len() as f64;\n\t}\n\n\t#[inline(always)]\n\tpub fn square_size(&self) -> f64 {\n\t\tself.square_size\n\t}\n\n\t#[inline(always)]\n\tpub fn square_area(&self) -> f64 {\n\t\tself.square_size.powi(2)\n\t}\n\n\t#[inline(always)]\n\tpub fn total_area(&self) -> f64 {\n\t\tself.total_area\n\t}\n\n\t#[inline(always)]\n\tpub fn not_empty(&self) -> bool {\n\t\t!self.active_squares.is_empty()\n\t}\n}\n\n#[derive(Clone, Default)]\npub struct PointsList {\n\t// The worst-case number of points in a 3x3 grid is 16 (one at each intersection of the four gridlines per axis)\n\tstorage_slots: [DVec2; 16],\n\tlength: usize,\n}\n\nimpl PointsList {\n\t#[inline(always)]\n\tpub fn push(&mut self, point: DVec2) {\n\t\tself.storage_slots[self.length] = point;\n\t\tself.length += 1;\n\t}\n\n\t#[inline(always)]\n\tpub fn list_cell_and_neighbors(&self) -> impl Iterator<Item = DVec2> {\n\t\t// The negative bit is used to store whether a point belongs to a neighboring cell\n\t\tself.storage_slots.into_iter().take(self.length).map(|point| (point.x.abs(), point.y.abs()).into())\n\t}\n\n\t#[inline(always)]\n\tpub fn list_cell(&self) -> impl Iterator<Item = DVec2> {\n\t\t// The negative bit is used to store whether a point belongs to a neighboring cell\n\t\tself.storage_slots\n\t\t\t.into_iter()\n\t\t\t.take(self.length)\n\t\t\t.filter(|point| point.x.is_sign_positive() && point.y.is_sign_positive())\n\t}\n}\n\npub struct AccelerationGrid {\n\tsize: f64,\n\tdimension_x: usize,\n\tdimension_y: usize,\n\tcells: Vec<PointsList>,\n}\n\nimpl AccelerationGrid {\n\t#[inline(always)]\n\tpub fn new(width: f64, height: f64, size: f64) -> Self {\n\t\tlet dimension_x = (width / size).ceil() as usize + 1;\n\t\tlet dimension_y = (height / size).ceil() as usize + 1;\n\n\t\tSelf {\n\t\t\tsize,\n\t\t\tdimension_x,\n\t\t\tdimension_y,\n\t\t\tcells: vec![PointsList::default(); dimension_x * dimension_y],\n\t\t}\n\t}\n\n\t#[inline(always)]\n\tpub fn insert(&mut self, point: DVec2) {\n\t\tlet x = (point.x / self.size).floor() as usize;\n\t\tlet y = (point.y / self.size).floor() as usize;\n\n\t\t// Insert this point at this cell and the surrounding cells in a 3x3 patch\n\t\tfor (x_offset, y_offset) in cartesian_product((-1)..=1, (-1)..=1) {\n\t\t\t// Avoid going negative\n\t\t\tlet (x, y) = (x as isize + x_offset, y as isize + y_offset);\n\t\t\tif x < 0 || y < 0 {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// Avoid going beyond the width or height\n\t\t\tlet (x, y) = (x as usize, y as usize);\n\t\t\tif x > self.dimension_x - 1 || y > self.dimension_y - 1 {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Get the cell corresponding to the (x, y) index\n\t\t\tlet cell = &mut self.cells[y * self.dimension_x + x];\n\n\t\t\t// Store the given point in this grid cell, and use the negative bit to indicate if this belongs to a neighboring cell\n\t\t\tcell.push(if x_offset == 0 && y_offset == 0 { point } else { -point });\n\t\t}\n\t}\n\n\t#[inline(always)]\n\tpub fn nearby_points(&self, point: DVec2) -> impl Iterator<Item = DVec2> {\n\t\tlet x = (point.x / self.size).floor() as usize;\n\t\tlet y = (point.y / self.size).floor() as usize;\n\n\t\tself.cells[y * self.dimension_x + x].list_cell_and_neighbors()\n\t}\n\n\t#[inline(always)]\n\tpub fn final_points(&self, offset: DVec2) -> Vec<DVec2> {\n\t\tself.cells.iter().flat_map(|cell| cell.list_cell()).map(|point| point + offset).collect()\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/vector-types/src/vector/algorithms/spline.rs",
    "content": "use glam::DVec2;\n\n/// Solve for the first handle of an open spline. (The opposite handle can be found by mirroring the result about the anchor.)\npub fn solve_spline_first_handle_open(points: &[DVec2]) -> Vec<DVec2> {\n\tlet len_points = points.len();\n\tif len_points == 0 {\n\t\treturn Vec::new();\n\t}\n\tif len_points == 1 {\n\t\treturn vec![points[0]];\n\t}\n\n\t// Matrix coefficients a, b and c (see https://mathworld.wolfram.com/CubicSpline.html).\n\t// Because the `a` coefficients are all 1, they need not be stored.\n\t// This algorithm does a variation of the above algorithm.\n\t// Instead of using the traditional cubic (a + bt + ct^2 + dt^3), we use the bezier cubic.\n\n\tlet mut b = vec![DVec2::new(4., 4.); len_points];\n\tb[0] = DVec2::new(2., 2.);\n\tb[len_points - 1] = DVec2::new(2., 2.);\n\n\tlet mut c = vec![DVec2::new(1., 1.); len_points];\n\n\t// 'd' is the the second point in a cubic bezier, which is what we solve for\n\tlet mut d = vec![DVec2::ZERO; len_points];\n\n\td[0] = DVec2::new(2. * points[1].x + points[0].x, 2. * points[1].y + points[0].y);\n\td[len_points - 1] = DVec2::new(3. * points[len_points - 1].x, 3. * points[len_points - 1].y);\n\tfor idx in 1..(len_points - 1) {\n\t\td[idx] = DVec2::new(4. * points[idx].x + 2. * points[idx + 1].x, 4. * points[idx].y + 2. * points[idx + 1].y);\n\t}\n\n\t// Solve with Thomas algorithm (see https://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm)\n\t// Now we do row operations to eliminate `a` coefficients.\n\tc[0] /= -b[0];\n\td[0] /= -b[0];\n\t#[allow(clippy::assign_op_pattern)]\n\tfor i in 1..len_points {\n\t\tb[i] += c[i - 1];\n\t\t// For some reason this `+=` version makes the borrow checker mad:\n\t\t// d[i] += d[i-1]\n\t\td[i] = d[i] + d[i - 1];\n\t\tc[i] /= -b[i];\n\t\td[i] /= -b[i];\n\t}\n\n\t// At this point b[i] == -a[i + 1] and a[i] == 0.\n\t// Now we do row operations to eliminate 'c' coefficients and solve.\n\td[len_points - 1] *= -1.;\n\t#[allow(clippy::assign_op_pattern)]\n\tfor i in (0..len_points - 1).rev() {\n\t\td[i] = d[i] - (c[i] * d[i + 1]);\n\t\td[i] *= -1.; // d[i] /= b[i]\n\t}\n\n\td\n}\n\n/// Solve for the first handle of a closed spline. (The opposite handle can be found by mirroring the result about the anchor.)\n/// If called with fewer than 3 points, this function will return an empty result.\npub fn solve_spline_first_handle_closed(points: &[DVec2]) -> Vec<DVec2> {\n\tlet len_points = points.len();\n\tif len_points < 3 {\n\t\treturn Vec::new();\n\t}\n\n\t// Matrix coefficients `a`, `b` and `c` (see https://mathworld.wolfram.com/CubicSpline.html).\n\t// We don't really need to allocate them but it keeps the maths understandable.\n\tlet a = vec![DVec2::ONE; len_points];\n\tlet b = vec![DVec2::splat(4.); len_points];\n\tlet c = vec![DVec2::ONE; len_points];\n\n\tlet mut cmod = vec![DVec2::ZERO; len_points];\n\tlet mut u = vec![DVec2::ZERO; len_points];\n\n\t// `x` is initially the output of the matrix multiplication, but is converted to the second value.\n\tlet mut x = vec![DVec2::ZERO; len_points];\n\n\tfor (i, point) in x.iter_mut().enumerate() {\n\t\tlet previous_i = i.checked_sub(1).unwrap_or(len_points - 1);\n\t\tlet next_i = (i + 1) % len_points;\n\t\t*point = 3. * (points[next_i] - points[previous_i]);\n\t}\n\n\t// Solve using https://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm#Variants (the variant using periodic boundary conditions).\n\t// This code below is based on the reference C language implementation provided in that section of the article.\n\tlet alpha = a[0];\n\tlet beta = c[len_points - 1];\n\n\t// Arbitrary, but chosen such that division by zero is avoided.\n\tlet gamma = -b[0];\n\n\tcmod[0] = alpha / (b[0] - gamma);\n\tu[0] = gamma / (b[0] - gamma);\n\tx[0] /= b[0] - gamma;\n\n\t// Handle from from `1` to `len_points - 2` (inclusive).\n\tfor ix in 1..=(len_points - 2) {\n\t\tlet m = 1.0 / (b[ix] - a[ix] * cmod[ix - 1]);\n\t\tcmod[ix] = c[ix] * m;\n\t\tu[ix] = (0.0 - a[ix] * u[ix - 1]) * m;\n\t\tx[ix] = (x[ix] - a[ix] * x[ix - 1]) * m;\n\t}\n\n\t// Handle `len_points - 1`.\n\tlet m = 1.0 / (b[len_points - 1] - alpha * beta / gamma - beta * cmod[len_points - 2]);\n\tu[len_points - 1] = (alpha - a[len_points - 1] * u[len_points - 2]) * m;\n\tx[len_points - 1] = (x[len_points - 1] - a[len_points - 1] * x[len_points - 2]) * m;\n\n\t// Loop from `len_points - 2` to `0` (inclusive).\n\tfor ix in (0..=(len_points - 2)).rev() {\n\t\tu[ix] = u[ix] - cmod[ix] * u[ix + 1];\n\t\tx[ix] = x[ix] - cmod[ix] * x[ix + 1];\n\t}\n\n\tlet fact = (x[0] + x[len_points - 1] * beta / gamma) / (1.0 + u[0] + u[len_points - 1] * beta / gamma);\n\n\tfor ix in 0..(len_points) {\n\t\tx[ix] -= fact * u[ix];\n\t}\n\n\tlet mut real = vec![DVec2::ZERO; len_points];\n\tfor i in 0..len_points {\n\t\tlet previous = i.checked_sub(1).unwrap_or(len_points - 1);\n\t\tlet next = (i + 1) % len_points;\n\t\treal[i] = x[previous] * a[next] + x[i] * b[i] + x[next] * c[i];\n\t}\n\n\t// The matrix is now solved.\n\n\t// Since we have computed the derivative, work back to find the start handle.\n\tfor i in 0..len_points {\n\t\tx[i] = (x[i] / 3.) + points[i];\n\t}\n\n\tx\n}\n\n#[cfg(test)]\nmod tests {\n\tuse super::*;\n\t#[test]\n\tfn closed_spline() {\n\t\tuse crate::vector::misc::{dvec2_to_point, point_to_dvec2};\n\t\tuse kurbo::{BezPath, ParamCurve, ParamCurveDeriv};\n\n\t\t// These points are just chosen arbitrary\n\t\tlet points = [DVec2::new(0., 0.), DVec2::new(0., 0.), DVec2::new(6., 5.), DVec2::new(7., 9.), DVec2::new(2., 3.)];\n\n\t\t// List of first handle or second point in a cubic bezier curve.\n\t\tlet first_handles = solve_spline_first_handle_closed(&points);\n\n\t\t// Construct the Subpath\n\t\tlet mut bezpath = BezPath::new();\n\t\tbezpath.move_to(dvec2_to_point(points[0]));\n\n\t\tfor i in 0..first_handles.len() {\n\t\t\tlet next_i = i + 1;\n\t\t\tlet next_i = if next_i == first_handles.len() { 0 } else { next_i };\n\n\t\t\t// First handle or second point of a cubic Bezier curve.\n\t\t\tlet p1 = dvec2_to_point(first_handles[i]);\n\t\t\t// Second handle or third point of a cubic Bezier curve.\n\t\t\tlet p2 = dvec2_to_point(2. * points[next_i] - first_handles[next_i]);\n\t\t\t// Endpoint or fourth point of a cubic Bezier curve.\n\t\t\tlet p3 = dvec2_to_point(points[next_i]);\n\n\t\t\tbezpath.curve_to(p1, p2, p3);\n\t\t}\n\n\t\t// For each pair of bézier curves, ensure that the second derivative is continuous\n\t\tfor (bézier_a, bézier_b) in bezpath.segments().zip(bezpath.segments().skip(1).chain(bezpath.segments().take(1))) {\n\t\t\tlet derivative2_end_a = point_to_dvec2(bézier_a.to_cubic().deriv().eval(1.));\n\t\t\tlet derivative2_start_b = point_to_dvec2(bézier_b.to_cubic().deriv().eval(0.));\n\n\t\t\tassert!(\n\t\t\t\tderivative2_end_a.abs_diff_eq(derivative2_start_b, 1e-10),\n\t\t\t\t\"second derivative at the end of a {derivative2_end_a} is equal to the second derivative at the start of b {derivative2_start_b}\"\n\t\t\t);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/vector-types/src/vector/algorithms/util.rs",
    "content": "use glam::DVec2;\nuse kurbo::{ParamCurve, ParamCurveDeriv, PathSeg};\n\npub fn pathseg_tangent(segment: PathSeg, t: f64) -> DVec2 {\n\t// NOTE: .deriv() method gives inaccurate result when it is 1.\n\tlet t = if t == 1. { 1. - f64::EPSILON } else { t };\n\n\tlet tangent = match segment {\n\t\tPathSeg::Line(line) => line.deriv().eval(t),\n\t\tPathSeg::Quad(quad_bez) => quad_bez.deriv().eval(t),\n\t\tPathSeg::Cubic(cubic_bez) => cubic_bez.deriv().eval(t),\n\t};\n\n\tDVec2::new(tangent.x, tangent.y)\n}\n\n// Compare two f64s with some maximum absolute difference to account for floating point errors\n#[cfg(test)]\npub fn compare_f64s(f1: f64, f2: f64) -> bool {\n\t(f1 - f2).abs() < super::contants::MAX_ABSOLUTE_DIFFERENCE\n}\n\n/// Compare points by allowing some maximum absolute difference to account for floating point errors\n#[cfg(test)]\npub fn compare_points(p1: kurbo::Point, p2: kurbo::Point) -> bool {\n\tlet (p1, p2) = (crate::vector::misc::point_to_dvec2(p1), crate::vector::misc::point_to_dvec2(p2));\n\tp1.abs_diff_eq(p2, super::contants::MAX_ABSOLUTE_DIFFERENCE)\n}\n\n/// Compare vectors of points by allowing some maximum absolute difference to account for floating point errors\n#[cfg(test)]\npub fn compare_vec_of_points(a: Vec<kurbo::Point>, b: Vec<kurbo::Point>, max_absolute_difference: f64) -> bool {\n\ta.len() == b.len()\n\t\t&& a.into_iter()\n\t\t\t.zip(b)\n\t\t\t.map(|(p1, p2)| (crate::vector::misc::point_to_dvec2(p1), crate::vector::misc::point_to_dvec2(p2)))\n\t\t\t.all(|(p1, p2)| p1.abs_diff_eq(p2, max_absolute_difference))\n}\n\n/// Compare the two values in a `DVec2` independently with a provided max absolute value difference.\n#[cfg(test)]\npub fn dvec2_compare(a: kurbo::Point, b: kurbo::Point, max_abs_diff: f64) -> glam::BVec2 {\n\tglam::BVec2::new((a.x - b.x).abs() < max_abs_diff, (a.y - b.y).abs() < max_abs_diff)\n}\n"
  },
  {
    "path": "node-graph/libraries/vector-types/src/vector/click_target.rs",
    "content": "use std::sync::{Arc, RwLock};\n\nuse super::algorithms::{bezpath_algorithms::bezpath_is_inside_bezpath, intersection::filtered_segment_intersections};\nuse super::misc::dvec2_to_point;\nuse crate::math::QuadExt;\nuse crate::subpath::Subpath;\nuse crate::vector::PointId;\nuse crate::vector::misc::point_to_dvec2;\nuse core_types::math::quad::Quad;\nuse core_types::transform::Transform;\nuse glam::{DAffine2, DMat2, DVec2};\nuse kurbo::{Affine, BezPath, ParamCurve, PathSeg, Shape};\n\ntype BoundingBox = Option<[DVec2; 2]>;\n\n#[derive(Copy, Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct FreePoint {\n\tpub id: PointId,\n\tpub position: DVec2,\n}\n\nimpl FreePoint {\n\tpub fn new(id: PointId, position: DVec2) -> Self {\n\t\tSelf { id, position }\n\t}\n\n\tpub fn apply_transform(&mut self, transform: DAffine2) {\n\t\tself.position = transform.transform_point2(self.position);\n\t}\n}\n\n#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]\npub enum ClickTargetType {\n\tSubpath(Subpath<PointId>),\n\tFreePoint(FreePoint),\n}\n\n/// Fixed-size ring buffer cache for rotated bounding boxes.\n///\n/// Stores up to 8 rotation angles and their corresponding bounding boxes to avoid\n/// recomputing expensive bezier curve bounds for repeated rotations. Uses 7-bit\n/// fingerprint hashing with MSB as presence flag for fast lookup.\n#[derive(Clone, Debug, Default)]\nstruct BoundingBoxCache {\n\t/// Packed 7-bit fingerprints with MSB presence flags for cache lookup\n\tfingerprints: u64,\n\t/// (rotation_angle, cached_bounds) pairs\n\telements: [(f64, BoundingBox); Self::CACHE_SIZE],\n\t/// Next position to write in ring buffer\n\twrite_ptr: usize,\n}\n\nimpl BoundingBoxCache {\n\t/// Cache size - must be ≤ 8 since fingerprints is u64 (8 bytes, 1 byte per element)\n\tconst CACHE_SIZE: usize = 8;\n\tconst FINGERPRINT_BITS: u32 = 7;\n\tconst PRESENCE_FLAG: u8 = 1 << Self::FINGERPRINT_BITS;\n\n\t/// Generates a 7-bit fingerprint from rotation with MSB as presence flag\n\tfn rotation_fingerprint(rotation: f64) -> u8 {\n\t\t(rotation.to_bits() % (1 << Self::FINGERPRINT_BITS)) as u8 | Self::PRESENCE_FLAG\n\t}\n\t/// Attempts to find cached bounding box for the given rotation.\n\t/// Returns Some(bounds) if found, None if not cached.\n\tfn try_read(&self, rotation: f64, scale: DVec2, translation: DVec2, fingerprint: u8) -> Option<BoundingBox> {\n\t\t// Build bitmask of positions with matching fingerprints for vectorized comparison\n\t\tlet mut mask: u8 = 0;\n\t\tfor (i, fp) in (0..Self::CACHE_SIZE).zip(self.fingerprints.to_le_bytes()) {\n\t\t\t// Check MSB for presence and lower 7 bits for fingerprint match\n\t\t\tif fp == fingerprint {\n\t\t\t\tmask |= 1 << i;\n\t\t\t}\n\t\t}\n\t\t// Check each position with matching fingerprint for exact rotation match\n\t\twhile mask != 0 {\n\t\t\tlet pos = mask.trailing_zeros() as usize;\n\n\t\t\tif rotation == self.elements[pos].0 {\n\t\t\t\t// Found cached rotation - apply scale and translation to cached bounds\n\t\t\t\tlet transform = DAffine2::from_scale_angle_translation(scale, 0., translation);\n\t\t\t\tlet new_bounds = self.elements[pos].1.map(|[a, b]| [transform.transform_point2(a), transform.transform_point2(b)]);\n\n\t\t\t\treturn Some(new_bounds);\n\t\t\t}\n\t\t\tmask &= !(1 << pos);\n\t\t}\n\t\tNone\n\t}\n\t/// Computes and caches bounding box for the given rotation, then applies scale/translation.\n\t/// Returns the final transformed bounds.\n\tfn add_to_cache(&mut self, subpath: &Subpath<PointId>, rotation: f64, scale: DVec2, translation: DVec2, fingerprint: u8) -> BoundingBox {\n\t\t// Compute bounds for pure rotation (expensive operation we want to cache)\n\t\tlet bounds = subpath.bounding_box_with_transform(DAffine2::from_angle(rotation));\n\n\t\tif bounds.is_none() {\n\t\t\treturn bounds;\n\t\t}\n\n\t\t// Store in ring buffer at current write position\n\t\tlet write_ptr = self.write_ptr;\n\t\tself.elements[write_ptr] = (rotation, bounds);\n\n\t\t// Update fingerprint byte for this position\n\t\tlet mut bytes = self.fingerprints.to_le_bytes();\n\t\tbytes[write_ptr] = fingerprint;\n\t\tself.fingerprints = u64::from_le_bytes(bytes);\n\n\t\t// Advance write pointer (ring buffer behavior)\n\t\tself.write_ptr = (write_ptr + 1) % Self::CACHE_SIZE;\n\n\t\t// Apply scale and translation to cached rotated bounds\n\t\tlet transform = DAffine2::from_scale_angle_translation(scale, 0., translation);\n\t\tbounds.map(|[a, b]| [transform.transform_point2(a), transform.transform_point2(b)])\n\t}\n}\n\n/// Represents a clickable target for the layer\n#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]\npub struct ClickTarget {\n\ttarget_type: ClickTargetType,\n\tstroke_width: f64,\n\tbounding_box: BoundingBox,\n\t#[serde(skip)]\n\tbounding_box_cache: Arc<RwLock<BoundingBoxCache>>,\n}\n\nimpl PartialEq for ClickTarget {\n\tfn eq(&self, other: &Self) -> bool {\n\t\tself.target_type == other.target_type && self.stroke_width == other.stroke_width && self.bounding_box == other.bounding_box\n\t}\n}\n\nimpl ClickTarget {\n\tpub fn new_with_subpath(subpath: Subpath<PointId>, stroke_width: f64) -> Self {\n\t\tlet bounding_box = subpath.loose_bounding_box();\n\t\tSelf {\n\t\t\ttarget_type: ClickTargetType::Subpath(subpath),\n\t\t\tstroke_width,\n\t\t\tbounding_box,\n\t\t\tbounding_box_cache: Default::default(),\n\t\t}\n\t}\n\n\tpub fn new_with_free_point(point: FreePoint) -> Self {\n\t\tconst MAX_LENGTH_FOR_NO_WIDTH_OR_HEIGHT: f64 = 1e-4 / 2.;\n\t\tlet stroke_width = 10.;\n\t\tlet bounding_box = Some([\n\t\t\tpoint.position - DVec2::splat(MAX_LENGTH_FOR_NO_WIDTH_OR_HEIGHT),\n\t\t\tpoint.position + DVec2::splat(MAX_LENGTH_FOR_NO_WIDTH_OR_HEIGHT),\n\t\t]);\n\n\t\tSelf {\n\t\t\ttarget_type: ClickTargetType::FreePoint(point),\n\t\t\tstroke_width,\n\t\t\tbounding_box,\n\t\t\tbounding_box_cache: Default::default(),\n\t\t}\n\t}\n\n\tpub fn target_type(&self) -> &ClickTargetType {\n\t\t&self.target_type\n\t}\n\n\tpub fn bounding_box(&self) -> BoundingBox {\n\t\tself.bounding_box\n\t}\n\n\tpub fn bounding_box_center(&self) -> Option<DVec2> {\n\t\tself.bounding_box.map(|bbox| bbox[0] + (bbox[1] - bbox[0]) / 2.)\n\t}\n\n\tpub fn bounding_box_with_transform(&self, transform: DAffine2) -> BoundingBox {\n\t\tmatch self.target_type {\n\t\t\tClickTargetType::Subpath(ref subpath) => {\n\t\t\t\t// Bypass cache for skewed transforms since rotation decomposition isn't valid\n\t\t\t\tif transform.has_skew() {\n\t\t\t\t\treturn subpath.bounding_box_with_transform(transform);\n\t\t\t\t}\n\n\t\t\t\t// Decompose transform into rotation, scale, translation for caching strategy\n\t\t\t\tlet rotation = transform.decompose_rotation();\n\t\t\t\tlet scale = transform.decompose_scale();\n\t\t\t\tlet translation = transform.translation;\n\n\t\t\t\t// Generate fingerprint for cache lookup\n\t\t\t\tlet fingerprint = BoundingBoxCache::rotation_fingerprint(rotation);\n\n\t\t\t\t// Try to read from cache first\n\t\t\t\tlet read_lock = self.bounding_box_cache.read().unwrap();\n\t\t\t\tif let Some(value) = read_lock.try_read(rotation, scale, translation, fingerprint) {\n\t\t\t\t\treturn value;\n\t\t\t\t}\n\t\t\t\tstd::mem::drop(read_lock);\n\n\t\t\t\t// Cache miss - compute and store new entry\n\t\t\t\tlet mut write_lock = self.bounding_box_cache.write().unwrap();\n\t\t\t\twrite_lock.add_to_cache(subpath, rotation, scale, translation, fingerprint)\n\t\t\t}\n\t\t\t// TODO: use point for calculation of bbox\n\t\t\tClickTargetType::FreePoint(_) => self.bounding_box.map(|[a, b]| [transform.transform_point2(a), transform.transform_point2(b)]),\n\t\t}\n\t}\n\n\tpub fn apply_transform(&mut self, affine_transform: DAffine2) {\n\t\tmatch self.target_type {\n\t\t\tClickTargetType::Subpath(ref mut subpath) => {\n\t\t\t\tsubpath.apply_transform(affine_transform);\n\t\t\t}\n\t\t\tClickTargetType::FreePoint(ref mut point) => {\n\t\t\t\tpoint.apply_transform(affine_transform);\n\t\t\t}\n\t\t}\n\t\tself.update_bbox();\n\t}\n\n\tfn update_bbox(&mut self) {\n\t\tmatch self.target_type {\n\t\t\tClickTargetType::Subpath(ref subpath) => {\n\t\t\t\tself.bounding_box = subpath.bounding_box();\n\t\t\t}\n\t\t\tClickTargetType::FreePoint(ref point) => {\n\t\t\t\tself.bounding_box = Some([point.position - DVec2::splat(self.stroke_width / 2.), point.position + DVec2::splat(self.stroke_width / 2.)]);\n\t\t\t}\n\t\t}\n\t}\n\n\t/// Does the click target intersect the path\n\tpub fn intersect_path<It: Iterator<Item = PathSeg>>(&self, mut bezier_iter: impl FnMut() -> It, layer_transform: DAffine2) -> bool {\n\t\t// Check if the matrix is not invertible\n\t\tlet mut layer_transform = layer_transform;\n\t\tif layer_transform.matrix2.determinant().abs() <= f64::EPSILON {\n\t\t\tlayer_transform.matrix2 += DMat2::IDENTITY * 1e-4; // TODO: Is this the cleanest way to handle this?\n\t\t}\n\n\t\tlet inverse = layer_transform.inverse();\n\t\tlet mut bezier_iter = || bezier_iter().map(|bezier| Affine::new(inverse.to_cols_array()) * bezier);\n\n\t\tmatch self.target_type() {\n\t\t\tClickTargetType::Subpath(subpath) => {\n\t\t\t\t// Check if outlines intersect\n\t\t\t\tlet outline_intersects = |path_segment: PathSeg| bezier_iter().any(|line| !filtered_segment_intersections(path_segment, line, None, None).is_empty());\n\t\t\t\tif subpath.iter().any(outline_intersects) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\t// Check if selection is entirely within the shape\n\t\t\t\tif subpath.closed() && bezier_iter().next().is_some_and(|bezier| subpath.contains_point(point_to_dvec2(bezier.start()))) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\tlet mut selection = BezPath::from_path_segments(bezier_iter());\n\t\t\t\tselection.close_path();\n\n\t\t\t\t// Check if shape is entirely within selection\n\t\t\t\tbezpath_is_inside_bezpath(&subpath.to_bezpath(), &selection, None, None)\n\t\t\t}\n\t\t\tClickTargetType::FreePoint(point) => bezier_iter().map(|bezier: PathSeg| bezier.winding(dvec2_to_point(point.position))).sum::<i32>() != 0,\n\t\t}\n\t}\n\n\t/// Does the click target intersect the point (accounting for stroke size)\n\tpub fn intersect_point(&self, point: DVec2, layer_transform: DAffine2) -> bool {\n\t\tlet target_bounds = [point - DVec2::splat(self.stroke_width / 2.), point + DVec2::splat(self.stroke_width / 2.)];\n\t\tlet intersects = |a: [DVec2; 2], b: [DVec2; 2]| a[0].x <= b[1].x && a[1].x >= b[0].x && a[0].y <= b[1].y && a[1].y >= b[0].y;\n\t\t// This bounding box is not very accurate as it is the axis aligned version of the transformed bounding box. However it is fast.\n\t\tif !self\n\t\t\t.bounding_box\n\t\t\t.is_some_and(|loose| (loose[0] - loose[1]).abs().cmpgt(DVec2::splat(1e-4)).any() && intersects((layer_transform * Quad::from_box(loose)).bounding_box(), target_bounds))\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\t// Allows for selecting lines\n\t\t// TODO: actual intersection of stroke\n\t\tlet inflated_quad = Quad::from_box(target_bounds);\n\t\tself.intersect_path(|| inflated_quad.to_lines(), layer_transform)\n\t}\n\n\t/// Does the click target intersect the point (not accounting for stroke size)\n\tpub fn intersect_point_no_stroke(&self, point: DVec2) -> bool {\n\t\t// Check if the point is within the bounding box\n\t\tif self\n\t\t\t.bounding_box\n\t\t\t.is_some_and(|bbox| bbox[0].x <= point.x && point.x <= bbox[1].x && bbox[0].y <= point.y && point.y <= bbox[1].y)\n\t\t{\n\t\t\t// Check if the point is within the shape\n\t\t\tmatch self.target_type() {\n\t\t\t\tClickTargetType::Subpath(subpath) => subpath.closed() && subpath.contains_point(point),\n\t\t\t\tClickTargetType::FreePoint(free_point) => free_point.position == point,\n\t\t\t}\n\t\t} else {\n\t\t\tfalse\n\t\t}\n\t}\n}\n\n#[cfg(test)]\nmod tests {\n\tuse super::*;\n\tuse crate::subpath::Subpath;\n\tuse glam::DVec2;\n\tuse std::f64::consts::PI;\n\n\t#[test]\n\tfn test_bounding_box_cache_fingerprint_generation() {\n\t\t// Test that fingerprints have MSB set and use only 7 bits for data\n\t\tlet rotation1 = 0.0;\n\t\tlet rotation2 = PI / 3.0;\n\t\tlet rotation3 = PI / 2.0;\n\n\t\tlet fp1 = BoundingBoxCache::rotation_fingerprint(rotation1);\n\t\tlet fp2 = BoundingBoxCache::rotation_fingerprint(rotation2);\n\t\tlet fp3 = BoundingBoxCache::rotation_fingerprint(rotation3);\n\n\t\t// All fingerprints should have MSB set (presence flag)\n\t\tassert_eq!(fp1 & BoundingBoxCache::PRESENCE_FLAG, BoundingBoxCache::PRESENCE_FLAG);\n\t\tassert_eq!(fp2 & BoundingBoxCache::PRESENCE_FLAG, BoundingBoxCache::PRESENCE_FLAG);\n\t\tassert_eq!(fp3 & BoundingBoxCache::PRESENCE_FLAG, BoundingBoxCache::PRESENCE_FLAG);\n\n\t\t// Lower 7 bits should contain the actual fingerprint data\n\t\tlet data1 = fp1 & !BoundingBoxCache::PRESENCE_FLAG;\n\t\tlet data2 = fp2 & !BoundingBoxCache::PRESENCE_FLAG;\n\t\tlet data3 = fp3 & !BoundingBoxCache::PRESENCE_FLAG;\n\n\t\t// Data portions should be different (unless collision)\n\t\tassert!(data1 != data2 && data2 != data3 && data3 != data1);\n\t}\n\n\t#[test]\n\tfn test_bounding_box_cache_basic_operations() {\n\t\tlet mut cache = BoundingBoxCache::default();\n\n\t\t// Create a simple rectangle subpath for testing\n\t\tlet subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::new(100.0, 50.0));\n\n\t\tlet rotation = PI / 4.0;\n\t\tlet scale = DVec2::new(2.0, 2.0);\n\t\tlet translation = DVec2::new(10.0, 20.0);\n\t\tlet fingerprint = BoundingBoxCache::rotation_fingerprint(rotation);\n\n\t\t// Cache should be empty initially\n\t\tassert!(cache.try_read(rotation, scale, translation, fingerprint).is_none());\n\n\t\t// Add to cache\n\t\tlet result = cache.add_to_cache(&subpath, rotation, scale, translation, fingerprint);\n\t\tassert!(result.is_some());\n\n\t\t// Should now be able to read from cache\n\t\tlet cached = cache.try_read(rotation, scale, translation, fingerprint);\n\t\tassert!(cached.is_some());\n\t\tassert_eq!(cached.unwrap(), result);\n\t}\n\n\t#[test]\n\tfn test_bounding_box_cache_ring_buffer_behavior() {\n\t\tlet mut cache = BoundingBoxCache::default();\n\t\tlet subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::new(10.0, 10.0));\n\t\tlet scale = DVec2::ONE;\n\t\tlet translation = DVec2::ZERO;\n\n\t\t// Fill cache beyond capacity to test ring buffer behavior\n\t\tlet rotations: Vec<f64> = (0..10).map(|i| i as f64 * PI / 8.0).collect();\n\n\t\tfor rotation in &rotations {\n\t\t\tlet fingerprint = BoundingBoxCache::rotation_fingerprint(*rotation);\n\t\t\tcache.add_to_cache(&subpath, *rotation, scale, translation, fingerprint);\n\t\t}\n\n\t\t// First two entries should be overwritten (cache size is 8)\n\t\tlet first_fp = BoundingBoxCache::rotation_fingerprint(rotations[0]);\n\t\tlet second_fp = BoundingBoxCache::rotation_fingerprint(rotations[1]);\n\t\tlet last_fp = BoundingBoxCache::rotation_fingerprint(rotations[9]);\n\n\t\tassert!(cache.try_read(rotations[0], scale, translation, first_fp).is_none());\n\t\tassert!(cache.try_read(rotations[1], scale, translation, second_fp).is_none());\n\t\tassert!(cache.try_read(rotations[9], scale, translation, last_fp).is_some());\n\t}\n\n\t#[test]\n\tfn test_click_target_bounding_box_caching() {\n\t\t// Create a click target with a simple rectangle\n\t\tlet subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::new(100.0, 50.0));\n\t\tlet click_target = ClickTarget::new_with_subpath(subpath, 1.0);\n\n\t\tlet rotation = PI / 6.0;\n\t\tlet scale = DVec2::new(1.5, 1.5);\n\t\tlet translation = DVec2::new(20.0, 30.0);\n\t\tlet transform = DAffine2::from_scale_angle_translation(scale, rotation, translation);\n\n\t\t// Helper function to count present values in cache\n\t\tlet count_present_values = || {\n\t\t\tlet cache = click_target.bounding_box_cache.read().unwrap();\n\t\t\tcache.fingerprints.to_le_bytes().iter().filter(|&&fp| fp & BoundingBoxCache::PRESENCE_FLAG != 0).count()\n\t\t};\n\n\t\t// Initially cache should be empty\n\t\tassert_eq!(count_present_values(), 0);\n\n\t\t// First call should compute and cache\n\t\tlet result1 = click_target.bounding_box_with_transform(transform);\n\t\tassert!(result1.is_some());\n\t\tassert_eq!(count_present_values(), 1);\n\n\t\t// Second call with same transform should use cache, not add new entry\n\t\tlet result2 = click_target.bounding_box_with_transform(transform);\n\t\tassert_eq!(result1, result2);\n\t\tassert_eq!(count_present_values(), 1); // Should still be 1, not 2\n\n\t\t// Different scale/translation but same rotation should use cached rotation\n\t\tlet transform2 = DAffine2::from_scale_angle_translation(DVec2::new(2.0, 2.0), rotation, DVec2::new(50.0, 60.0));\n\t\tlet result3 = click_target.bounding_box_with_transform(transform2);\n\t\tassert!(result3.is_some());\n\t\tassert_ne!(result1, result3); // Different due to different scale/translation\n\t\tassert_eq!(count_present_values(), 1); // Should still be 1, reused same rotation\n\t}\n\n\t#[test]\n\tfn test_click_target_skew_bypass_cache() {\n\t\tlet subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::new(100.0, 50.0));\n\t\tlet click_target = ClickTarget::new_with_subpath(subpath.clone(), 1.0);\n\n\t\t// Create a transform with skew (non-uniform scaling in different directions)\n\t\tlet skew_transform = DAffine2::from_cols_array(&[2.0, 0.5, 0.0, 1.0, 10.0, 20.0]);\n\t\tassert!(skew_transform.has_skew());\n\n\t\t// Should bypass cache and compute directly\n\t\tlet result = click_target.bounding_box_with_transform(skew_transform);\n\t\tlet expected = subpath.bounding_box_with_transform(skew_transform);\n\t\tassert_eq!(result, expected);\n\t}\n\n\t#[test]\n\tfn test_cache_fingerprint_collision_handling() {\n\t\tlet mut cache = BoundingBoxCache::default();\n\t\tlet subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::new(10.0, 10.0));\n\t\tlet scale = DVec2::ONE;\n\t\tlet translation = DVec2::ZERO;\n\n\t\t// Find two rotations that produce the same fingerprint (collision)\n\t\tlet rotation1 = 0.0;\n\t\tlet rotation2 = 0.25;\n\t\tlet fp1 = BoundingBoxCache::rotation_fingerprint(rotation1);\n\t\tlet fp2 = BoundingBoxCache::rotation_fingerprint(rotation2);\n\n\t\t// If we found a collision, test that exact rotation matching still works\n\t\tif fp1 == fp2 && rotation1 != rotation2 {\n\t\t\t// Add first rotation\n\t\t\tcache.add_to_cache(&subpath, rotation1, scale, translation, fp1);\n\n\t\t\t// Should find the exact rotation\n\t\t\tassert!(cache.try_read(rotation1, scale, translation, fp1).is_some());\n\n\t\t\t// Should not find the colliding rotation (different exact value)\n\t\t\tassert!(cache.try_read(rotation2, scale, translation, fp2).is_none());\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/vector-types/src/vector/misc.rs",
    "content": "use super::PointId;\nuse super::algorithms::offset_subpath::MAX_ABSOLUTE_DIFFERENCE;\nuse crate::subpath::{BezierHandles, ManipulatorGroup};\nuse crate::vector::{SegmentId, Vector};\nuse dyn_any::DynAny;\nuse glam::DVec2;\nuse kurbo::{BezPath, CubicBez, Line, ParamCurve, ParamCurveDeriv, PathSeg, Point, QuadBez};\nuse std::ops::Sub;\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)]\n#[widget(Radio)]\npub enum BooleanOperation {\n\t#[default]\n\t#[icon(\"BooleanUnion\")]\n\tUnion,\n\t#[icon(\"BooleanSubtractFront\")]\n\tSubtractFront,\n\t#[icon(\"BooleanSubtractBack\")]\n\tSubtractBack,\n\t#[icon(\"BooleanIntersect\")]\n\tIntersect,\n\t#[icon(\"BooleanDifference\")]\n\tDifference,\n}\n\n/// Represents different geometric interpretations of calculating the centroid (center of mass).\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)]\n#[widget(Radio)]\npub enum CentroidType {\n\t/// The center of mass for the area of a solid shape's interior, as if made out of an infinitely flat material.\n\t#[default]\n\tArea,\n\t/// The center of mass for the arc length of a curved shape's perimeter, as if made out of an infinitely thin wire.\n\tLength,\n}\n\n#[repr(C)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)]\n#[widget(Radio)]\npub enum RowsOrColumns {\n\t#[default]\n\tRows = 0,\n\tColumns,\n}\n\npub trait AsU64 {\n\tfn as_u64(&self) -> u64;\n}\nimpl AsU64 for u32 {\n\tfn as_u64(&self) -> u64 {\n\t\t*self as u64\n\t}\n}\nimpl AsU64 for u64 {\n\tfn as_u64(&self) -> u64 {\n\t\t*self\n\t}\n}\nimpl AsU64 for f64 {\n\tfn as_u64(&self) -> u64 {\n\t\t*self as u64\n\t}\n}\n\npub trait AsI64 {\n\tfn as_i64(&self) -> i64;\n}\nimpl AsI64 for u32 {\n\tfn as_i64(&self) -> i64 {\n\t\t*self as i64\n\t}\n}\nimpl AsI64 for u64 {\n\tfn as_i64(&self) -> i64 {\n\t\t*self as i64\n\t}\n}\nimpl AsI64 for f64 {\n\tfn as_i64(&self) -> i64 {\n\t\t*self as i64\n\t}\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)]\n#[widget(Radio)]\npub enum GridType {\n\t#[default]\n\tRectangular = 0,\n\tIsometric,\n}\n\n#[repr(C)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)]\n#[widget(Radio)]\npub enum ArcType {\n\t#[default]\n\tOpen = 0,\n\tClosed,\n\tPieSlice,\n}\n\n#[repr(C)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)]\n#[widget(Radio)]\npub enum MergeByDistanceAlgorithm {\n\t#[default]\n\tSpatial,\n\tTopological,\n}\n\n#[repr(C)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)]\n#[widget(Radio)]\npub enum ExtrudeJoiningAlgorithm {\n\tAll,\n\t#[default]\n\tExtrema,\n\tNone,\n}\n\n#[repr(C)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)]\n#[widget(Radio)]\npub enum PointSpacingType {\n\t#[default]\n\t/// The desired spacing distance between points.\n\tSeparation,\n\t/// The exact number of points to span the path.\n\tQuantity,\n}\n\npub fn point_to_dvec2(point: Point) -> DVec2 {\n\tDVec2 { x: point.x, y: point.y }\n}\n\npub fn dvec2_to_point(value: DVec2) -> Point {\n\tPoint { x: value.x, y: value.y }\n}\n\npub fn get_line_endpoints(line: Line) -> (DVec2, DVec2) {\n\t(point_to_dvec2(line.p0), point_to_dvec2(line.p1))\n}\n\npub fn segment_to_handles(segment: &PathSeg) -> BezierHandles {\n\tmatch *segment {\n\t\tPathSeg::Line(_) => BezierHandles::Linear,\n\t\tPathSeg::Quad(QuadBez { p0: _, p1, p2: _ }) => BezierHandles::Quadratic { handle: point_to_dvec2(p1) },\n\t\tPathSeg::Cubic(CubicBez { p0: _, p1, p2, p3: _ }) => BezierHandles::Cubic {\n\t\t\thandle_start: point_to_dvec2(p1),\n\t\t\thandle_end: point_to_dvec2(p2),\n\t\t},\n\t}\n}\n\npub fn handles_to_segment(start: DVec2, handles: BezierHandles, end: DVec2) -> PathSeg {\n\tmatch handles {\n\t\tBezierHandles::Linear => {\n\t\t\tlet p0 = dvec2_to_point(start);\n\t\t\tlet p1 = dvec2_to_point(end);\n\t\t\tPathSeg::Line(Line::new(p0, p1))\n\t\t}\n\t\tBezierHandles::Quadratic { handle } => {\n\t\t\tlet p0 = dvec2_to_point(start);\n\t\t\tlet p1 = dvec2_to_point(handle);\n\t\t\tlet p2 = dvec2_to_point(end);\n\t\t\tPathSeg::Quad(QuadBez::new(p0, p1, p2))\n\t\t}\n\t\tBezierHandles::Cubic { handle_start, handle_end } => {\n\t\t\tlet p0 = dvec2_to_point(start);\n\t\t\tlet p1 = dvec2_to_point(handle_start);\n\t\t\tlet p2 = dvec2_to_point(handle_end);\n\t\t\tlet p3 = dvec2_to_point(end);\n\t\t\tPathSeg::Cubic(CubicBez::new(p0, p1, p2, p3))\n\t\t}\n\t}\n}\n\npub fn bezpath_from_manipulator_groups(manipulator_groups: &[ManipulatorGroup<PointId>], closed: bool) -> BezPath {\n\tlet mut bezpath = kurbo::BezPath::new();\n\tlet mut out_handle;\n\n\tlet Some(first) = manipulator_groups.first() else { return bezpath };\n\tbezpath.move_to(dvec2_to_point(first.anchor));\n\tout_handle = first.out_handle;\n\n\tfor manipulator in manipulator_groups.iter().skip(1) {\n\t\tmatch (out_handle, manipulator.in_handle) {\n\t\t\t(Some(handle_start), Some(handle_end)) => bezpath.curve_to(dvec2_to_point(handle_start), dvec2_to_point(handle_end), dvec2_to_point(manipulator.anchor)),\n\t\t\t(None, None) => bezpath.line_to(dvec2_to_point(manipulator.anchor)),\n\t\t\t(None, Some(handle)) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(manipulator.anchor)),\n\t\t\t(Some(handle), None) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(manipulator.anchor)),\n\t\t}\n\t\tout_handle = manipulator.out_handle;\n\t}\n\n\tif closed {\n\t\tmatch (out_handle, first.in_handle) {\n\t\t\t(Some(handle_start), Some(handle_end)) => bezpath.curve_to(dvec2_to_point(handle_start), dvec2_to_point(handle_end), dvec2_to_point(first.anchor)),\n\t\t\t(None, None) => bezpath.line_to(dvec2_to_point(first.anchor)),\n\t\t\t(None, Some(handle)) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(first.anchor)),\n\t\t\t(Some(handle), None) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(first.anchor)),\n\t\t}\n\t\tbezpath.close_path();\n\t}\n\tbezpath\n}\n\npub fn bezpath_to_manipulator_groups(bezpath: &BezPath) -> (Vec<ManipulatorGroup<PointId>>, bool) {\n\tlet mut manipulator_groups = Vec::<ManipulatorGroup<PointId>>::new();\n\tlet mut is_closed = false;\n\n\tfor element in bezpath.elements() {\n\t\tlet manipulator_group = match *element {\n\t\t\tkurbo::PathEl::MoveTo(point) => ManipulatorGroup::new(point_to_dvec2(point), None, None),\n\t\t\tkurbo::PathEl::LineTo(point) => ManipulatorGroup::new(point_to_dvec2(point), None, None),\n\t\t\tkurbo::PathEl::QuadTo(point, point1) => ManipulatorGroup::new(point_to_dvec2(point1), Some(point_to_dvec2(point)), None),\n\t\t\tkurbo::PathEl::CurveTo(point, point1, point2) => {\n\t\t\t\tif let Some(last_manipulator_group) = manipulator_groups.last_mut() {\n\t\t\t\t\tlast_manipulator_group.out_handle = Some(point_to_dvec2(point));\n\t\t\t\t}\n\t\t\t\tManipulatorGroup::new(point_to_dvec2(point2), Some(point_to_dvec2(point1)), None)\n\t\t\t}\n\t\t\tkurbo::PathEl::ClosePath => {\n\t\t\t\tif let Some(last_manipulators) = manipulator_groups.pop()\n\t\t\t\t\t&& let Some(first_manipulators) = manipulator_groups.first_mut()\n\t\t\t\t{\n\t\t\t\t\tfirst_manipulators.out_handle = last_manipulators.in_handle;\n\t\t\t\t}\n\t\t\t\tis_closed = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t};\n\n\t\tmanipulator_groups.push(manipulator_group);\n\t}\n\n\t(manipulator_groups, is_closed)\n}\n\n/// Returns true if the [`PathSeg`] is equivalent to a line.\n///\n/// This is different from simply checking if the segment is [`PathSeg::Line`] or [`PathSeg::Quad`] or [`PathSeg::Cubic`]. Bezier curve can also be a line if the control points are colinear to the start and end points. Therefore if the handles exceed the start and end point, it will still be considered as a line.\npub fn is_linear(segment: PathSeg) -> bool {\n\tlet is_colinear = |a: Point, b: Point, c: Point| -> bool { ((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)).abs() < MAX_ABSOLUTE_DIFFERENCE };\n\n\tmatch segment {\n\t\tPathSeg::Line(_) => true,\n\t\tPathSeg::Quad(QuadBez { p0, p1, p2 }) => is_colinear(p0, p1, p2),\n\t\tPathSeg::Cubic(CubicBez { p0, p1, p2, p3 }) => is_colinear(p0, p1, p3) && is_colinear(p0, p2, p3),\n\t}\n}\n\n/// Get an vec of all the points in a path segment.\npub fn pathseg_points_vec(segment: PathSeg) -> Vec<Point> {\n\tmatch segment {\n\t\tPathSeg::Line(line) => [line.p0, line.p1].to_vec(),\n\t\tPathSeg::Quad(quad_bez) => [quad_bez.p0, quad_bez.p1, quad_bez.p2].to_vec(),\n\t\tPathSeg::Cubic(cubic_bez) => [cubic_bez.p0, cubic_bez.p1, cubic_bez.p2, cubic_bez.p3].to_vec(),\n\t}\n}\n\n/// Returns true if the corresponding points of the two [`PathSeg`]s are within the provided absolute value difference from each other.\npub fn pathseg_abs_diff_eq(seg1: PathSeg, seg2: PathSeg, max_abs_diff: f64) -> bool {\n\tlet seg1 = if is_linear(seg1) { PathSeg::Line(Line::new(seg1.start(), seg1.end())) } else { seg1 };\n\tlet seg2 = if is_linear(seg2) { PathSeg::Line(Line::new(seg2.start(), seg2.end())) } else { seg2 };\n\n\tlet seg1_points = pathseg_points_vec(seg1);\n\tlet seg2_points = pathseg_points_vec(seg2);\n\n\tlet cmp = |a: f64, b: f64| a.sub(b).abs() < max_abs_diff;\n\n\tseg1_points.len() == seg2_points.len() && seg1_points.into_iter().zip(seg2_points).all(|(a, b)| cmp(a.x, b.x) && cmp(a.y, b.y))\n}\npub trait Tangent {\n\tfn tangent_at(&self, t: f64) -> DVec2;\n\n\tfn tangent_at_start(&self) -> DVec2 {\n\t\tself.tangent_at(0.0)\n\t}\n\n\tfn tangent_at_end(&self) -> DVec2 {\n\t\tself.tangent_at(1.0)\n\t}\n}\n\ntrait ControlPoints {\n\ttype Points: AsRef<[Point]>;\n\tfn control_points(&self) -> Self::Points;\n}\n\nimpl ControlPoints for kurbo::Line {\n\ttype Points = [Point; 2];\n\tfn control_points(&self) -> Self::Points {\n\t\t[self.p0, self.p1]\n\t}\n}\n\nimpl ControlPoints for kurbo::QuadBez {\n\ttype Points = [Point; 3];\n\tfn control_points(&self) -> Self::Points {\n\t\t[self.p0, self.p1, self.p2]\n\t}\n}\n\nimpl ControlPoints for kurbo::CubicBez {\n\ttype Points = [Point; 4];\n\tfn control_points(&self) -> Self::Points {\n\t\t[self.p0, self.p1, self.p2, self.p3]\n\t}\n}\n\nimpl<T: ControlPoints + ParamCurveDeriv> Tangent for T {\n\tfn tangent_at(&self, t: f64) -> DVec2 {\n\t\tpoint_to_dvec2(self.deriv().eval(t))\n\t}\n\n\tfn tangent_at_start(&self) -> DVec2 {\n\t\tlet pts = self.control_points();\n\t\tlet pts = pts.as_ref();\n\t\tlet mut iter = pts.iter();\n\t\titer.next()\n\t\t\t.and_then(|&start| iter.find(|&&p| p != start).map(|&p| DVec2 { x: p.x - start.x, y: p.y - start.y }))\n\t\t\t.unwrap_or_default()\n\t}\n\n\tfn tangent_at_end(&self) -> DVec2 {\n\t\tlet pts = self.control_points();\n\t\tlet pts = pts.as_ref();\n\t\tlet mut iter = pts.iter().rev();\n\t\titer.next()\n\t\t\t.and_then(|&end| iter.find(|&&p| p != end).map(|&p| DVec2 { x: end.x - p.x, y: end.y - p.y }))\n\t\t\t.unwrap_or_default()\n\t}\n}\n\nimpl Tangent for kurbo::PathSeg {\n\tfn tangent_at(&self, t: f64) -> DVec2 {\n\t\tmatch self {\n\t\t\tPathSeg::Line(line) => line.tangent_at(t),\n\t\t\tPathSeg::Quad(quad) => quad.tangent_at(t),\n\t\t\tPathSeg::Cubic(cubic) => cubic.tangent_at(t),\n\t\t}\n\t}\n\n\tfn tangent_at_start(&self) -> DVec2 {\n\t\tmatch self {\n\t\t\tPathSeg::Line(line) => line.tangent_at_start(),\n\t\t\tPathSeg::Quad(quad) => quad.tangent_at_start(),\n\t\t\tPathSeg::Cubic(cubic) => cubic.tangent_at_start(),\n\t\t}\n\t}\n\n\tfn tangent_at_end(&self) -> DVec2 {\n\t\tmatch self {\n\t\t\tPathSeg::Line(line) => line.tangent_at_end(),\n\t\t\tPathSeg::Quad(quad) => quad.tangent_at_end(),\n\t\t\tPathSeg::Cubic(cubic) => cubic.tangent_at_end(),\n\t\t}\n\t}\n}\n\n/// A selectable part of a curve, either an anchor (start or end of a bézier) or a handle (doesn't necessarily go through the bézier but influences curvature).\n#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny, serde::Serialize, serde::Deserialize)]\npub enum ManipulatorPointId {\n\t/// A control anchor - the start or end point of a bézier.\n\tAnchor(PointId),\n\t/// The handle for a bézier - the first handle on a cubic and the only handle on a quadratic.\n\tPrimaryHandle(SegmentId),\n\t/// The end handle on a cubic bézier.\n\tEndHandle(SegmentId),\n}\n\nimpl ManipulatorPointId {\n\t/// Attempt to retrieve the manipulator position in layer space (no transformation applied).\n\t#[must_use]\n\t#[track_caller]\n\tpub fn get_position<Upstream: 'static>(&self, vector: &Vector<Upstream>) -> Option<DVec2> {\n\t\tmatch self {\n\t\t\tManipulatorPointId::Anchor(id) => vector.point_domain.position_from_id(*id),\n\t\t\tManipulatorPointId::PrimaryHandle(id) => vector.segment_from_id(*id).and_then(|bezier| bezier.handle_start()),\n\t\t\tManipulatorPointId::EndHandle(id) => vector.segment_from_id(*id).and_then(|bezier| bezier.handle_end()),\n\t\t}\n\t}\n\n\tpub fn get_anchor_position<Upstream: 'static>(&self, vector: &Vector<Upstream>) -> Option<DVec2> {\n\t\tmatch self {\n\t\t\tManipulatorPointId::EndHandle(_) | ManipulatorPointId::PrimaryHandle(_) => self.get_anchor(vector).and_then(|id| vector.point_domain.position_from_id(id)),\n\t\t\t_ => self.get_position(vector),\n\t\t}\n\t}\n\n\t/// Attempt to get a pair of handles. For an anchor this is the first two handles connected. For a handle it is self and the first opposing handle.\n\t#[must_use]\n\tpub fn get_handle_pair<Upstream: 'static>(self, vector: &Vector<Upstream>) -> Option<[HandleId; 2]> {\n\t\tmatch self {\n\t\t\tManipulatorPointId::Anchor(point) => vector.all_connected(point).take(2).collect::<Vec<_>>().try_into().ok(),\n\t\t\tManipulatorPointId::PrimaryHandle(segment) => {\n\t\t\t\tlet point = vector.segment_domain.segment_start_from_id(segment)?;\n\t\t\t\tlet current = HandleId::primary(segment);\n\t\t\t\tlet other = vector.segment_domain.all_connected(point).find(|&value| value != current);\n\t\t\t\tother.map(|other| [current, other])\n\t\t\t}\n\t\t\tManipulatorPointId::EndHandle(segment) => {\n\t\t\t\tlet point = vector.segment_domain.segment_end_from_id(segment)?;\n\t\t\t\tlet current = HandleId::end(segment);\n\t\t\t\tlet other = vector.segment_domain.all_connected(point).find(|&value| value != current);\n\t\t\t\tother.map(|other| [current, other])\n\t\t\t}\n\t\t}\n\t}\n\n\t/// Finds all the connected handles of a point.\n\t/// For an anchor it is all the connected handles.\n\t/// For a handle it is all the handles connected to its corresponding anchor other than the current handle.\n\tpub fn get_all_connected_handles<Upstream: 'static>(self, vector: &Vector<Upstream>) -> Option<Vec<HandleId>> {\n\t\tmatch self {\n\t\t\tManipulatorPointId::Anchor(point) => {\n\t\t\t\tlet connected = vector.all_connected(point).collect::<Vec<_>>();\n\t\t\t\tSome(connected)\n\t\t\t}\n\t\t\tManipulatorPointId::PrimaryHandle(segment) => {\n\t\t\t\tlet point = vector.segment_domain.segment_start_from_id(segment)?;\n\t\t\t\tlet current = HandleId::primary(segment);\n\t\t\t\tlet connected = vector.segment_domain.all_connected(point).filter(|&value| value != current).collect::<Vec<_>>();\n\t\t\t\tSome(connected)\n\t\t\t}\n\t\t\tManipulatorPointId::EndHandle(segment) => {\n\t\t\t\tlet point = vector.segment_domain.segment_end_from_id(segment)?;\n\t\t\t\tlet current = HandleId::end(segment);\n\t\t\t\tlet connected = vector.segment_domain.all_connected(point).filter(|&value| value != current).collect::<Vec<_>>();\n\t\t\t\tSome(connected)\n\t\t\t}\n\t\t}\n\t}\n\n\t/// Attempt to find the closest anchor. If self is already an anchor then it is just self. If it is a start or end handle, then the start or end point is chosen.\n\t#[must_use]\n\tpub fn get_anchor<Upstream: 'static>(self, vector: &Vector<Upstream>) -> Option<PointId> {\n\t\tmatch self {\n\t\t\tManipulatorPointId::Anchor(point) => Some(point),\n\t\t\tManipulatorPointId::PrimaryHandle(segment) => vector.segment_start_from_id(segment),\n\t\t\tManipulatorPointId::EndHandle(segment) => vector.segment_end_from_id(segment),\n\t\t}\n\t}\n\n\t/// Attempt to convert self to a [`HandleId`], returning none for an anchor.\n\t#[must_use]\n\tpub fn as_handle(self) -> Option<HandleId> {\n\t\tmatch self {\n\t\t\tManipulatorPointId::PrimaryHandle(segment) => Some(HandleId::primary(segment)),\n\t\t\tManipulatorPointId::EndHandle(segment) => Some(HandleId::end(segment)),\n\t\t\tManipulatorPointId::Anchor(_) => None,\n\t\t}\n\t}\n\n\t/// Attempt to convert self to an anchor, returning None for a handle.\n\t#[must_use]\n\tpub fn as_anchor(self) -> Option<PointId> {\n\t\tmatch self {\n\t\t\tManipulatorPointId::Anchor(point) => Some(point),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tpub fn get_segment(self) -> Option<SegmentId> {\n\t\tmatch self {\n\t\t\tManipulatorPointId::PrimaryHandle(segment) | ManipulatorPointId::EndHandle(segment) => Some(segment),\n\t\t\t_ => None,\n\t\t}\n\t}\n}\n\n/// The type of handle found on a bézier curve.\n#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, DynAny, serde::Serialize, serde::Deserialize)]\npub enum HandleType {\n\t/// The first handle on a cubic bézier or the only handle on a quadratic bézier.\n\tPrimary,\n\t/// The second handle on a cubic bézier.\n\tEnd,\n}\n\n/// Represents a primary or end handle found in a particular segment.\n#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, DynAny, serde::Serialize, serde::Deserialize)]\npub struct HandleId {\n\tpub ty: HandleType,\n\tpub segment: SegmentId,\n}\n\nimpl std::fmt::Display for HandleId {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tmatch self.ty {\n\t\t\t// I haven't checked if \"out\" and \"in\" are reversed, or are accurate translations of the \"primary\" and \"end\" terms used in the `HandleType` enum, so this naming is an assumption.\n\t\t\tHandleType::Primary => write!(f, \"{} out\", self.segment.inner()),\n\t\t\tHandleType::End => write!(f, \"{} in\", self.segment.inner()),\n\t\t}\n\t}\n}\n\nimpl HandleId {\n\t/// Construct a handle for the first handle on a cubic bézier or the only handle on a quadratic bézier.\n\t#[must_use]\n\tpub const fn primary(segment: SegmentId) -> Self {\n\t\tSelf { ty: HandleType::Primary, segment }\n\t}\n\n\t/// Construct a handle for the end handle on a cubic bézier.\n\t#[must_use]\n\tpub const fn end(segment: SegmentId) -> Self {\n\t\tSelf { ty: HandleType::End, segment }\n\t}\n\n\t/// Convert to [`ManipulatorPointId`].\n\t#[must_use]\n\tpub fn to_manipulator_point(self) -> ManipulatorPointId {\n\t\tmatch self.ty {\n\t\t\tHandleType::Primary => ManipulatorPointId::PrimaryHandle(self.segment),\n\t\t\tHandleType::End => ManipulatorPointId::EndHandle(self.segment),\n\t\t}\n\t}\n\n\t/// Calculate the magnitude of the handle from the anchor.\n\tpub fn length<Upstream: 'static>(self, vector: &Vector<Upstream>) -> f64 {\n\t\tlet Some(anchor_position) = self.to_manipulator_point().get_anchor_position(vector) else {\n\t\t\t// TODO: This was previously an unwrap which was encountered, so this is a temporary way to avoid a crash\n\t\t\treturn 0.;\n\t\t};\n\t\tlet handle_position = self.to_manipulator_point().get_position(vector);\n\t\thandle_position.map(|pos| (pos - anchor_position).length()).unwrap_or(f64::MAX)\n\t}\n\n\t/// Convert an end handle to the primary handle and a primary handle to an end handle. Note that the new handle may not exist (e.g. for a quadratic bézier).\n\t#[must_use]\n\tpub fn opposite(self) -> Self {\n\t\tmatch self.ty {\n\t\t\tHandleType::Primary => Self::end(self.segment),\n\t\t\tHandleType::End => Self::primary(self.segment),\n\t\t}\n\t}\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)]\n#[widget(Dropdown)]\npub enum SpiralType {\n\t#[default]\n\tArchimedean,\n\tLogarithmic,\n}\n"
  },
  {
    "path": "node-graph/libraries/vector-types/src/vector/mod.rs",
    "content": "pub mod algorithms;\npub mod click_target;\npub mod misc;\npub mod reference_point;\npub mod style;\nmod vector_attributes;\nmod vector_modification;\nmod vector_types;\n\npub use reference_point::*;\npub use style::PathStyle;\npub use vector_attributes::*;\npub use vector_modification::*;\npub use vector_types::*;\n"
  },
  {
    "path": "node-graph/libraries/vector-types/src/vector/reference_point.rs",
    "content": "use core_types::math::bbox::AxisAlignedBbox;\nuse glam::DVec2;\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Clone, Copy, Debug, Default, Hash, Eq, PartialEq, dyn_any::DynAny, serde::Serialize, serde::Deserialize)]\npub enum ReferencePoint {\n\t#[default]\n\tNone,\n\tTopLeft,\n\tTopCenter,\n\tTopRight,\n\tCenterLeft,\n\tCenter,\n\tCenterRight,\n\tBottomLeft,\n\tBottomCenter,\n\tBottomRight,\n}\n\nimpl ReferencePoint {\n\tpub fn point_in_bounding_box(&self, bounding_box: AxisAlignedBbox) -> Option<DVec2> {\n\t\tlet size = bounding_box.size();\n\t\tlet offset = match self {\n\t\t\tReferencePoint::None => return None,\n\t\t\tReferencePoint::TopLeft => DVec2::ZERO,\n\t\t\tReferencePoint::TopCenter => DVec2::new(size.x / 2., 0.),\n\t\t\tReferencePoint::TopRight => DVec2::new(size.x, 0.),\n\t\t\tReferencePoint::CenterLeft => DVec2::new(0., size.y / 2.),\n\t\t\tReferencePoint::Center => DVec2::new(size.x / 2., size.y / 2.),\n\t\t\tReferencePoint::CenterRight => DVec2::new(size.x, size.y / 2.),\n\t\t\tReferencePoint::BottomLeft => DVec2::new(0., size.y),\n\t\t\tReferencePoint::BottomCenter => DVec2::new(size.x / 2., size.y),\n\t\t\tReferencePoint::BottomRight => DVec2::new(size.x, size.y),\n\t\t};\n\t\tSome(bounding_box.start + offset)\n\t}\n}\n\nimpl From<&str> for ReferencePoint {\n\tfn from(input: &str) -> Self {\n\t\tmatch input {\n\t\t\t\"None\" => ReferencePoint::None,\n\t\t\t\"TopLeft\" => ReferencePoint::TopLeft,\n\t\t\t\"TopCenter\" => ReferencePoint::TopCenter,\n\t\t\t\"TopRight\" => ReferencePoint::TopRight,\n\t\t\t\"CenterLeft\" => ReferencePoint::CenterLeft,\n\t\t\t\"Center\" => ReferencePoint::Center,\n\t\t\t\"CenterRight\" => ReferencePoint::CenterRight,\n\t\t\t\"BottomLeft\" => ReferencePoint::BottomLeft,\n\t\t\t\"BottomCenter\" => ReferencePoint::BottomCenter,\n\t\t\t\"BottomRight\" => ReferencePoint::BottomRight,\n\t\t\t_ => panic!(\"Failed parsing unrecognized ReferencePosition enum value '{input}'\"),\n\t\t}\n\t}\n}\n\nimpl From<ReferencePoint> for Option<DVec2> {\n\tfn from(input: ReferencePoint) -> Self {\n\t\tmatch input {\n\t\t\tReferencePoint::None => None,\n\t\t\tReferencePoint::TopLeft => Some(DVec2::new(0., 0.)),\n\t\t\tReferencePoint::TopCenter => Some(DVec2::new(0.5, 0.)),\n\t\t\tReferencePoint::TopRight => Some(DVec2::new(1., 0.)),\n\t\t\tReferencePoint::CenterLeft => Some(DVec2::new(0., 0.5)),\n\t\t\tReferencePoint::Center => Some(DVec2::new(0.5, 0.5)),\n\t\t\tReferencePoint::CenterRight => Some(DVec2::new(1., 0.5)),\n\t\t\tReferencePoint::BottomLeft => Some(DVec2::new(0., 1.)),\n\t\t\tReferencePoint::BottomCenter => Some(DVec2::new(0.5, 1.)),\n\t\t\tReferencePoint::BottomRight => Some(DVec2::new(1., 1.)),\n\t\t}\n\t}\n}\n\nimpl From<DVec2> for ReferencePoint {\n\tfn from(input: DVec2) -> Self {\n\t\tconst TOLERANCE: f64 = 1e-5_f64;\n\t\tif input.y.abs() < TOLERANCE {\n\t\t\tif input.x.abs() < TOLERANCE {\n\t\t\t\treturn ReferencePoint::TopLeft;\n\t\t\t} else if (input.x - 0.5).abs() < TOLERANCE {\n\t\t\t\treturn ReferencePoint::TopCenter;\n\t\t\t} else if (input.x - 1.).abs() < TOLERANCE {\n\t\t\t\treturn ReferencePoint::TopRight;\n\t\t\t}\n\t\t} else if (input.y - 0.5).abs() < TOLERANCE {\n\t\t\tif input.x.abs() < TOLERANCE {\n\t\t\t\treturn ReferencePoint::CenterLeft;\n\t\t\t} else if (input.x - 0.5).abs() < TOLERANCE {\n\t\t\t\treturn ReferencePoint::Center;\n\t\t\t} else if (input.x - 1.).abs() < TOLERANCE {\n\t\t\t\treturn ReferencePoint::CenterRight;\n\t\t\t}\n\t\t} else if (input.y - 1.).abs() < TOLERANCE {\n\t\t\tif input.x.abs() < TOLERANCE {\n\t\t\t\treturn ReferencePoint::BottomLeft;\n\t\t\t} else if (input.x - 0.5).abs() < TOLERANCE {\n\t\t\t\treturn ReferencePoint::BottomCenter;\n\t\t\t} else if (input.x - 1.).abs() < TOLERANCE {\n\t\t\t\treturn ReferencePoint::BottomRight;\n\t\t\t}\n\t\t}\n\t\tReferencePoint::None\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/vector-types/src/vector/style.rs",
    "content": "//! Contains stylistic options for SVG elements.\n\npub use crate::gradient::*;\nuse core_types::Color;\nuse core_types::color::Alpha;\nuse core_types::table::Table;\nuse dyn_any::DynAny;\nuse glam::DAffine2;\n\n/// Describes the fill of a layer.\n///\n/// Can be None, a solid [Color], or a linear/radial [Gradient].\n///\n/// In the future we'll probably also add a pattern fill. This will probably be named \"Paint\" in the future.\n#[repr(C)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, DynAny, Hash)]\npub enum Fill {\n\t#[default]\n\tNone,\n\tSolid(Color),\n\tGradient(Gradient),\n}\n\nimpl std::fmt::Display for Fill {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tmatch self {\n\t\t\tSelf::None => write!(f, \"None\"),\n\t\t\tSelf::Solid(color) => write!(f, \"#{} (Alpha: {}%)\", color.to_rgb_hex_srgb(), color.a() * 100.),\n\t\t\tSelf::Gradient(gradient) => write!(f, \"{gradient}\"),\n\t\t}\n\t}\n}\n\nimpl Fill {\n\t/// Construct a new [Fill::Solid] from a [Color].\n\tpub fn solid(color: Color) -> Self {\n\t\tSelf::Solid(color)\n\t}\n\n\t/// Construct a new [Fill::Solid] or [Fill::None] from an optional [Color].\n\tpub fn solid_or_none(color: Option<Color>) -> Self {\n\t\tmatch color {\n\t\t\tSome(color) => Self::Solid(color),\n\t\t\tNone => Self::None,\n\t\t}\n\t}\n\n\t/// Evaluate the color at some point on the fill. Doesn't currently work for Gradient.\n\tpub fn color(&self) -> Color {\n\t\tmatch self {\n\t\t\tSelf::None => Color::BLACK,\n\t\t\tSelf::Solid(color) => *color,\n\t\t\t// TODO: Should correctly sample the gradient the equation here: https://svgwg.org/svg2-draft/pservers.html#Gradients\n\t\t\tSelf::Gradient(Gradient { stops, .. }) => {\n\t\t\t\tif stops.is_empty() {\n\t\t\t\t\tColor::BLACK\n\t\t\t\t} else {\n\t\t\t\t\tstops.color[0]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn lerp(&self, other: &Self, time: f64) -> Self {\n\t\tlet transparent = Self::solid(Color::TRANSPARENT);\n\t\tlet a = if *self == Self::None { &transparent } else { self };\n\t\tlet b = if *other == Self::None { &transparent } else { other };\n\n\t\tmatch (a, b) {\n\t\t\t(Self::Solid(a), Self::Solid(b)) => Self::Solid(a.lerp(b, time as f32)),\n\t\t\t(Self::Solid(a), Self::Gradient(b)) => {\n\t\t\t\tlet mut solid_to_gradient = b.clone();\n\t\t\t\tsolid_to_gradient.stops.color.iter_mut().for_each(|color| *color = *a);\n\t\t\t\tlet a = &solid_to_gradient;\n\t\t\t\tSelf::Gradient(a.lerp(b, time))\n\t\t\t}\n\t\t\t(Self::Gradient(a), Self::Solid(b)) => {\n\t\t\t\tlet mut gradient_to_solid = a.clone();\n\t\t\t\tgradient_to_solid.stops.color.iter_mut().for_each(|color| *color = *b);\n\t\t\t\tlet b = &gradient_to_solid;\n\t\t\t\tSelf::Gradient(a.lerp(b, time))\n\t\t\t}\n\t\t\t(Self::Gradient(a), Self::Gradient(b)) => Self::Gradient(a.lerp(b, time)),\n\t\t\t_ => Self::None,\n\t\t}\n\t}\n\n\t/// Extract a gradient from the fill\n\tpub fn as_gradient(&self) -> Option<&Gradient> {\n\t\tmatch self {\n\t\t\tSelf::Gradient(gradient) => Some(gradient),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\t/// Extract a solid color from the fill\n\tpub fn as_solid(&self) -> Option<Color> {\n\t\tmatch self {\n\t\t\tSelf::Solid(color) => Some(*color),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\t/// Find if fill can be represented with only opaque colors\n\tpub fn is_opaque(&self) -> bool {\n\t\tmatch self {\n\t\t\tFill::Solid(color) => color.is_opaque(),\n\t\t\tFill::Gradient(gradient) => gradient.stops.color.iter().all(|color| color.is_opaque()),\n\t\t\tFill::None => true,\n\t\t}\n\t}\n\n\t/// Returns if fill is none\n\tpub fn is_none(&self) -> bool {\n\t\t*self == Self::None\n\t}\n}\n\nimpl From<Color> for Fill {\n\tfn from(color: Color) -> Fill {\n\t\tFill::Solid(color)\n\t}\n}\n\nimpl From<Option<Color>> for Fill {\n\tfn from(color: Option<Color>) -> Fill {\n\t\tFill::solid_or_none(color)\n\t}\n}\n\nimpl From<Table<Color>> for Fill {\n\tfn from(color: Table<Color>) -> Fill {\n\t\tlet alpha = color.get(0).map(|c| c.alpha_blending.opacity).unwrap_or(1.);\n\t\tlet color: Option<Color> = color.into();\n\t\tFill::solid_or_none(color.map(|c| c.with_alpha(c.alpha() * alpha)))\n\t}\n}\n\nimpl From<Table<GradientStops>> for Fill {\n\tfn from(gradient: Table<GradientStops>) -> Fill {\n\t\tFill::Gradient(Gradient {\n\t\t\tstops: gradient.iter().nth(0).map(|row| row.element.clone()).unwrap_or_default(),\n\t\t\t..Default::default()\n\t\t})\n\t}\n}\n\nimpl From<Gradient> for Fill {\n\tfn from(gradient: Gradient) -> Fill {\n\t\tFill::Gradient(gradient)\n\t}\n}\n\n/// Describes the fill of a layer, but unlike [`Fill`], this doesn't store a [`Gradient`] directly but just its [`GradientStops`].\n///\n/// Can be None, a solid [Color], or a linear/radial [Gradient].\n///\n/// In the future we'll probably also add a pattern fill.\n#[repr(C)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, DynAny, Hash)]\npub enum FillChoice {\n\t#[default]\n\tNone,\n\t/// WARNING: Color is gamma, not linear!\n\tSolid(Color),\n\t/// WARNING: Color stops are gamma, not linear!\n\tGradient(GradientStops),\n}\n\nimpl FillChoice {\n\tpub fn as_solid(&self) -> Option<Color> {\n\t\tlet Self::Solid(color) = self else { return None };\n\t\tSome(*color)\n\t}\n\n\tpub fn as_gradient(&self) -> Option<&GradientStops> {\n\t\tlet Self::Gradient(gradient) = self else { return None };\n\t\tSome(gradient)\n\t}\n\n\t/// Convert this [`FillChoice`] to a [`Fill`] using the provided [`Gradient`] as a base for the positional information of the gradient.\n\t/// If a gradient isn't provided, default gradient positional information is used in cases where the [`FillChoice`] is a [`Gradient`].\n\tpub fn to_fill(&self, existing_gradient: Option<&Gradient>) -> Fill {\n\t\tmatch self {\n\t\t\tSelf::None => Fill::None,\n\t\t\tSelf::Solid(color) => Fill::Solid(*color),\n\t\t\tSelf::Gradient(stops) => {\n\t\t\t\tlet mut fill = existing_gradient.cloned().unwrap_or_default();\n\t\t\t\tfill.stops = stops.clone();\n\t\t\t\tFill::Gradient(fill)\n\t\t\t}\n\t\t}\n\t}\n}\n\nimpl From<Fill> for FillChoice {\n\tfn from(fill: Fill) -> Self {\n\t\tmatch fill {\n\t\t\tFill::None => FillChoice::None,\n\t\t\tFill::Solid(color) => FillChoice::Solid(color),\n\t\t\tFill::Gradient(gradient) => FillChoice::Gradient(gradient.stops),\n\t\t}\n\t}\n}\n\n#[repr(C)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, Copy, Default, PartialEq, serde::Serialize, serde::Deserialize, DynAny, Hash, node_macro::ChoiceType)]\n#[widget(Radio)]\npub enum FillType {\n\t#[default]\n\tSolid,\n\tGradient,\n}\n\n/// The stroke (outline) style of an SVG element.\n#[repr(C)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)]\n#[widget(Radio)]\npub enum StrokeCap {\n\t#[default]\n\tButt,\n\tRound,\n\tSquare,\n}\n\nimpl StrokeCap {\n\tpub fn svg_name(&self) -> &'static str {\n\t\tmatch self {\n\t\t\tStrokeCap::Butt => \"butt\",\n\t\t\tStrokeCap::Round => \"round\",\n\t\t\tStrokeCap::Square => \"square\",\n\t\t}\n\t}\n}\n\n#[repr(C)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)]\n#[widget(Radio)]\npub enum StrokeJoin {\n\t#[default]\n\tMiter,\n\tBevel,\n\tRound,\n}\n\nimpl StrokeJoin {\n\tpub fn svg_name(&self) -> &'static str {\n\t\tmatch self {\n\t\t\tStrokeJoin::Bevel => \"bevel\",\n\t\t\tStrokeJoin::Miter => \"miter\",\n\t\t\tStrokeJoin::Round => \"round\",\n\t\t}\n\t}\n}\n\n#[repr(C)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)]\n#[widget(Radio)]\npub enum StrokeAlign {\n\t#[default]\n\tCenter,\n\tInside,\n\tOutside,\n}\n\nimpl StrokeAlign {\n\tpub fn is_not_centered(self) -> bool {\n\t\tself != Self::Center\n\t}\n}\n\n#[repr(C)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)]\n#[widget(Radio)]\npub enum PaintOrder {\n\t#[default]\n\tStrokeAbove,\n\tStrokeBelow,\n}\n\nimpl PaintOrder {\n\tpub fn is_default(self) -> bool {\n\t\tself == Self::default()\n\t}\n}\n\nfn daffine2_identity() -> DAffine2 {\n\tDAffine2::IDENTITY\n}\n\n#[repr(C)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, DynAny)]\n#[serde(default)]\npub struct Stroke {\n\t/// Stroke color\n\tpub color: Option<Color>,\n\t/// Line thickness\n\tpub weight: f64,\n\tpub dash_lengths: Vec<f64>,\n\tpub dash_offset: f64,\n\t#[serde(alias = \"line_cap\")]\n\tpub cap: StrokeCap,\n\t#[serde(alias = \"line_join\")]\n\tpub join: StrokeJoin,\n\t#[serde(alias = \"line_join_miter_limit\")]\n\tpub join_miter_limit: f64,\n\t#[serde(default)]\n\tpub align: StrokeAlign,\n\t#[serde(default = \"daffine2_identity\")]\n\tpub transform: DAffine2,\n\t#[serde(default)]\n\tpub paint_order: PaintOrder,\n}\n\nimpl std::hash::Hash for Stroke {\n\tfn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n\t\tself.color.hash(state);\n\t\tself.weight.to_bits().hash(state);\n\t\t{\n\t\t\tself.dash_lengths.len().hash(state);\n\t\t\tself.dash_lengths.iter().for_each(|length| length.to_bits().hash(state));\n\t\t}\n\t\tself.dash_offset.to_bits().hash(state);\n\t\tself.cap.hash(state);\n\t\tself.join.hash(state);\n\t\tself.join_miter_limit.to_bits().hash(state);\n\t\tself.align.hash(state);\n\t\tself.transform.to_cols_array().iter().for_each(|x| x.to_bits().hash(state));\n\t\tself.paint_order.hash(state);\n\t}\n}\n\nimpl Stroke {\n\tpub const fn new(color: Option<Color>, weight: f64) -> Self {\n\t\tSelf {\n\t\t\tcolor,\n\t\t\tweight,\n\t\t\tdash_lengths: Vec::new(),\n\t\t\tdash_offset: 0.,\n\t\t\tcap: StrokeCap::Butt,\n\t\t\tjoin: StrokeJoin::Miter,\n\t\t\tjoin_miter_limit: 4.,\n\t\t\talign: StrokeAlign::Center,\n\t\t\ttransform: DAffine2::IDENTITY,\n\t\t\tpaint_order: PaintOrder::StrokeAbove,\n\t\t}\n\t}\n\n\tpub fn lerp(&self, other: &Self, time: f64) -> Self {\n\t\tSelf {\n\t\t\tcolor: self.color.map(|color| color.lerp(&other.color.unwrap_or(color), time as f32)),\n\t\t\tweight: self.weight + (other.weight - self.weight) * time,\n\t\t\tdash_lengths: self.dash_lengths.iter().zip(other.dash_lengths.iter()).map(|(a, b)| a + (b - a) * time).collect(),\n\t\t\tdash_offset: self.dash_offset + (other.dash_offset - self.dash_offset) * time,\n\t\t\tcap: if time < 0.5 { self.cap } else { other.cap },\n\t\t\tjoin: if time < 0.5 { self.join } else { other.join },\n\t\t\tjoin_miter_limit: self.join_miter_limit + (other.join_miter_limit - self.join_miter_limit) * time,\n\t\t\talign: if time < 0.5 { self.align } else { other.align },\n\t\t\ttransform: DAffine2::from_mat2_translation(\n\t\t\t\ttime * self.transform.matrix2 + (1. - time) * other.transform.matrix2,\n\t\t\t\tself.transform.translation * time + other.transform.translation * (1. - time),\n\t\t\t),\n\t\t\tpaint_order: if time < 0.5 { self.paint_order } else { other.paint_order },\n\t\t}\n\t}\n\n\t/// Get the current stroke color.\n\tpub fn color(&self) -> Option<Color> {\n\t\tself.color\n\t}\n\n\t/// Get the current stroke weight.\n\tpub fn weight(&self) -> f64 {\n\t\tself.weight\n\t}\n\n\t/// Get the effective stroke weight.\n\tpub fn effective_width(&self) -> f64 {\n\t\tself.weight\n\t\t\t* match self.align {\n\t\t\t\tStrokeAlign::Center => 1.,\n\t\t\t\tStrokeAlign::Inside => 0.,\n\t\t\t\tStrokeAlign::Outside => 2.,\n\t\t\t}\n\t}\n\n\tpub fn dash_lengths(&self) -> String {\n\t\tif self.dash_lengths.is_empty() {\n\t\t\t\"none\".to_string()\n\t\t} else {\n\t\t\tself.dash_lengths.iter().map(|v| v.to_string()).collect::<Vec<_>>().join(\", \")\n\t\t}\n\t}\n\n\tpub fn dash_offset(&self) -> f64 {\n\t\tself.dash_offset\n\t}\n\n\tpub fn cap_index(&self) -> u32 {\n\t\tself.cap as u32\n\t}\n\n\tpub fn join_index(&self) -> u32 {\n\t\tself.join as u32\n\t}\n\n\tpub fn join_miter_limit(&self) -> f32 {\n\t\tself.join_miter_limit as f32\n\t}\n\n\tpub fn with_color(mut self, color: &Option<Color>) -> Option<Self> {\n\t\tself.color = *color;\n\n\t\tSome(self)\n\t}\n\n\tpub fn with_weight(mut self, weight: f64) -> Self {\n\t\tself.weight = weight;\n\t\tself\n\t}\n\n\tpub fn with_dash_lengths(mut self, dash_lengths: &str) -> Option<Self> {\n\t\tdash_lengths\n\t\t\t.split(&[',', ' '])\n\t\t\t.filter(|x| !x.is_empty())\n\t\t\t.map(str::parse::<f64>)\n\t\t\t.collect::<Result<Vec<_>, _>>()\n\t\t\t.ok()\n\t\t\t.map(|lengths| {\n\t\t\t\tself.dash_lengths = lengths;\n\t\t\t\tself\n\t\t\t})\n\t}\n\n\tpub fn with_dash_offset(mut self, dash_offset: f64) -> Self {\n\t\tself.dash_offset = dash_offset;\n\t\tself\n\t}\n\n\tpub fn with_stroke_cap(mut self, stroke_cap: StrokeCap) -> Self {\n\t\tself.cap = stroke_cap;\n\t\tself\n\t}\n\n\tpub fn with_stroke_join(mut self, stroke_join: StrokeJoin) -> Self {\n\t\tself.join = stroke_join;\n\t\tself\n\t}\n\n\tpub fn with_stroke_join_miter_limit(mut self, limit: f64) -> Self {\n\t\tself.join_miter_limit = limit;\n\t\tself\n\t}\n\n\tpub fn with_stroke_align(mut self, stroke_align: StrokeAlign) -> Self {\n\t\tself.align = stroke_align;\n\t\tself\n\t}\n\n\tpub fn has_renderable_stroke(&self) -> bool {\n\t\tself.weight > 0. && self.color.is_some_and(|color| color.a() != 0.)\n\t}\n}\n\n// Having an alpha of 1 to start with leads to a better experience with the properties panel\nimpl Default for Stroke {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tweight: 0.,\n\t\t\tcolor: Some(Color::from_rgba8_srgb(0, 0, 0, 255)),\n\t\t\tdash_lengths: Vec::new(),\n\t\t\tdash_offset: 0.,\n\t\t\tcap: StrokeCap::Butt,\n\t\t\tjoin: StrokeJoin::Miter,\n\t\t\tjoin_miter_limit: 4.,\n\t\t\talign: StrokeAlign::Center,\n\t\t\ttransform: DAffine2::IDENTITY,\n\t\t\tpaint_order: PaintOrder::default(),\n\t\t}\n\t}\n}\n\n#[repr(C)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize, DynAny)]\npub struct PathStyle {\n\tpub stroke: Option<Stroke>,\n\tpub fill: Fill,\n}\n\nimpl std::hash::Hash for PathStyle {\n\tfn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n\t\tself.stroke.hash(state);\n\t\tself.fill.hash(state);\n\t}\n}\n\nimpl std::fmt::Display for PathStyle {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tlet fill = &self.fill;\n\n\t\tlet stroke = match &self.stroke {\n\t\t\tSome(stroke) => format!(\"#{} (Weight: {} px)\", stroke.color.map_or(\"None\".to_string(), |c| c.to_rgba_hex_srgb()), stroke.weight),\n\t\t\tNone => \"None\".to_string(),\n\t\t};\n\n\t\twrite!(f, \"Fill: {fill}\\nStroke: {stroke}\")\n\t}\n}\n\nimpl PathStyle {\n\tpub const fn new(stroke: Option<Stroke>, fill: Fill) -> Self {\n\t\tSelf { stroke, fill }\n\t}\n\n\tpub fn lerp(&self, other: &Self, time: f64) -> Self {\n\t\tSelf {\n\t\t\tfill: self.fill.lerp(&other.fill, time),\n\t\t\tstroke: match (self.stroke.as_ref(), other.stroke.as_ref()) {\n\t\t\t\t(Some(a), Some(b)) => Some(a.lerp(b, time)),\n\t\t\t\t(Some(a), None) => {\n\t\t\t\t\tif time < 0.5 {\n\t\t\t\t\t\tSome(a.clone())\n\t\t\t\t\t} else {\n\t\t\t\t\t\tNone\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t(None, Some(b)) => {\n\t\t\t\t\tif time < 0.5 {\n\t\t\t\t\t\tSome(b.clone())\n\t\t\t\t\t} else {\n\t\t\t\t\t\tNone\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t(None, None) => None,\n\t\t\t},\n\t\t}\n\t}\n\n\t/// Get the current path's [Fill].\n\t///\n\t/// # Example\n\t/// ```\n\t/// # use vector_types::vector::style::{Fill, PathStyle};\n\t/// # use core_types::Color;\n\t/// let fill = Fill::solid(Color::RED);\n\t/// let style = PathStyle::new(None, fill.clone());\n\t///\n\t/// assert_eq!(*style.fill(), fill);\n\t/// ```\n\tpub fn fill(&self) -> &Fill {\n\t\t&self.fill\n\t}\n\n\t/// Get the current path's [Stroke].\n\t///\n\t/// # Example\n\t/// ```\n\t/// # use vector_types::vector::style::{Fill, Stroke, PathStyle};\n\t/// # use core_types::Color;\n\t/// let stroke = Stroke::new(Some(Color::GREEN), 42.);\n\t/// let style = PathStyle::new(Some(stroke.clone()), Fill::None);\n\t///\n\t/// assert_eq!(style.stroke(), Some(stroke));\n\t/// ```\n\tpub fn stroke(&self) -> Option<Stroke> {\n\t\tself.stroke.clone()\n\t}\n\n\t/// Replace the path's [Fill] with a provided one.\n\t///\n\t/// # Example\n\t/// ```\n\t/// # use vector_types::vector::style::{Fill, PathStyle};\n\t/// # use core_types::Color;\n\t/// let mut style = PathStyle::default();\n\t///\n\t/// assert_eq!(*style.fill(), Fill::None);\n\t///\n\t/// let fill = Fill::solid(Color::RED);\n\t/// style.set_fill(fill.clone());\n\t///\n\t/// assert_eq!(*style.fill(), fill);\n\t/// ```\n\tpub fn set_fill(&mut self, fill: Fill) {\n\t\tself.fill = fill;\n\t}\n\n\tpub fn set_stroke_transform(&mut self, transform: DAffine2) {\n\t\tif let Some(stroke) = &mut self.stroke {\n\t\t\tstroke.transform = transform;\n\t\t}\n\t}\n\n\t/// Replace the path's [Stroke] with a provided one.\n\t///\n\t/// # Example\n\t/// ```\n\t/// # use vector_types::vector::style::{Stroke, PathStyle};\n\t/// # use core_types::Color;\n\t/// let mut style = PathStyle::default();\n\t///\n\t/// assert_eq!(style.stroke(), None);\n\t///\n\t/// let stroke = Stroke::new(Some(Color::GREEN), 42.);\n\t/// style.set_stroke(stroke.clone());\n\t///\n\t/// assert_eq!(style.stroke(), Some(stroke));\n\t/// ```\n\tpub fn set_stroke(&mut self, stroke: Stroke) {\n\t\tself.stroke = Some(stroke);\n\t}\n\n\t/// Set the path's fill to None.\n\t///\n\t/// # Example\n\t/// ```\n\t/// # use vector_types::vector::style::{Fill, PathStyle};\n\t/// # use core_types::Color;\n\t/// let mut style = PathStyle::new(None, Fill::Solid(Color::RED));\n\t///\n\t/// assert_ne!(*style.fill(), Fill::None);\n\t///\n\t/// style.clear_fill();\n\t///\n\t/// assert_eq!(*style.fill(), Fill::None);\n\t/// ```\n\tpub fn clear_fill(&mut self) {\n\t\tself.fill = Fill::None;\n\t}\n\n\t/// Set the path's stroke to None.\n\t///\n\t/// # Example\n\t/// ```\n\t/// # use vector_types::vector::style::{Fill, Stroke, PathStyle};\n\t/// # use core_types::Color;\n\t/// let mut style = PathStyle::new(Some(Stroke::new(Some(Color::GREEN), 42.)), Fill::None);\n\t///\n\t/// assert!(style.stroke().is_some());\n\t///\n\t/// style.clear_stroke();\n\t///\n\t/// assert!(!style.stroke().is_some());\n\t/// ```\n\tpub fn clear_stroke(&mut self) {\n\t\tself.stroke = None;\n\t}\n}\n\n/// Ways the user can choose to view the artwork in the viewport.\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny)]\npub enum RenderMode {\n\t/// Render with normal coloration at the current viewport resolution\n\t#[default]\n\tNormal = 0,\n\t/// Render only the outlines of shapes at the current viewport resolution\n\tOutline,\n\t/// Render with normal coloration at the document export resolution; at zoom > 100% this shows individual export pixels upscaled with nearest-neighbor filtering\n\tPixelPreview,\n\t/// Render a preview of how the object would be exported as an SVG.\n\tSvgPreview,\n}\n"
  },
  {
    "path": "node-graph/libraries/vector-types/src/vector/vector_attributes.rs",
    "content": "use crate::subpath::{Bezier, BezierHandles, Identifier, ManipulatorGroup, Subpath};\nuse crate::vector::misc::{HandleId, Tangent, dvec2_to_point};\nuse crate::vector::vector_types::Vector;\nuse dyn_any::DynAny;\nuse fixedbitset::FixedBitSet;\nuse glam::{DAffine2, DVec2};\nuse kurbo::{CubicBez, Line, PathSeg, QuadBez};\nuse std::collections::HashMap;\nuse std::hash::{Hash, Hasher};\nuse std::iter::zip;\n\n/// A simple macro for creating strongly typed ids (to avoid confusion when passing around ids).\nmacro_rules! create_ids {\n\t($($id:ident),*) => {\n\t\t$(\n\t\t\t#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Ord, Eq, Hash, DynAny)]\n\t\t\t#[derive(serde::Serialize, serde::Deserialize)]\n\t\t\t/// A strongly typed ID\n\t\t\tpub struct $id(u64);\n\n\t\t\timpl $id {\n\t\t\t\tpub const ZERO: $id = $id(0);\n\n\t\t\t\t/// Generate a new random id\n\t\t\t\tpub fn generate() -> Self {\n\t\t\t\t\tSelf(core_types::uuid::generate_uuid())\n\t\t\t\t}\n\n\t\t\t\tpub fn generate_from_hash(self, node_id: u64) -> Self {\n\t\t\t\t\tlet mut hasher = std::hash::DefaultHasher::new();\n\t\t\t\t\tnode_id.hash(&mut hasher);\n\t\t\t\t\tself.hash(&mut hasher);\n\t\t\t\t\tlet hash_value = hasher.finish();\n\t\t\t\t\tSelf(hash_value)\n\t\t\t\t}\n\n\t\t\t\t/// Gets the inner raw value.\n\t\t\t\tpub fn inner(self) -> u64 {\n\t\t\t\t\tself.0\n\t\t\t\t}\n\n\t\t\t\t/// Adds one to the current value and returns the old value. Note that the ids are not going to be unique unless you use the largest id.\n\t\t\t\tpub fn next_id(&mut self) -> Self {\n\t\t\t\t\tself.0 += 1;\n\t\t\t\t\t*self\n\t\t\t\t}\n\t\t\t}\n\t\t)*\n\t};\n}\n\ncreate_ids! { PointId, SegmentId, RegionId, StrokeId, FillId }\n\n/// A no-op hasher that allows writing u64s (the id type).\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]\npub struct NoHash(Option<u64>);\n\nimpl Hasher for NoHash {\n\tfn finish(&self) -> u64 {\n\t\tself.0.unwrap()\n\t}\n\tfn write(&mut self, _bytes: &[u8]) {\n\t\tunimplemented!()\n\t}\n\tfn write_u64(&mut self, i: u64) {\n\t\tdebug_assert!(self.0.is_none());\n\t\tself.0 = Some(i)\n\t}\n}\n\n/// A hash builder that builds the [`NoHash`] hasher.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]\npub struct NoHashBuilder;\n\nimpl std::hash::BuildHasher for NoHashBuilder {\n\ttype Hasher = NoHash;\n\tfn build_hasher(&self) -> Self::Hasher {\n\t\tNoHash::default()\n\t}\n}\n\n#[derive(Clone, Debug, Default, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]\n/// Stores data which is per-point. Each point is merely a position and can be used in a point cloud or to for a bézier path. In future this will be extendable at runtime with custom attributes.\npub struct PointDomain {\n\tid: Vec<PointId>,\n\t#[serde(alias = \"positions\")]\n\tpub(crate) position: Vec<DVec2>,\n}\n\nimpl Hash for PointDomain {\n\tfn hash<H: Hasher>(&self, state: &mut H) {\n\t\tself.id.hash(state);\n\t\tself.position.iter().for_each(|pos| pos.to_array().map(|v| v.to_bits()).hash(state));\n\t}\n}\n\nimpl PointDomain {\n\tpub const fn new() -> Self {\n\t\tSelf { id: Vec::new(), position: Vec::new() }\n\t}\n\n\tpub fn clear(&mut self) {\n\t\tself.id.clear();\n\t\tself.position.clear();\n\t}\n\n\tpub fn retain(&mut self, segment_domain: &mut SegmentDomain, f: impl Fn(&PointId) -> bool) {\n\t\tlet mut keep = self.id.iter().map(&f);\n\t\tself.position.retain(|_| keep.next().unwrap_or_default());\n\n\t\t// TODO(TrueDoctor): Consider using a prefix sum to avoid this Vec allocation (https://github.com/GraphiteEditor/Graphite/pull/1949#discussion_r1741711562)\n\t\tlet mut id_map = Vec::with_capacity(self.ids().len());\n\t\tlet mut new_index = 0;\n\t\tfor id in self.ids() {\n\t\t\tif f(id) {\n\t\t\t\tid_map.push(new_index);\n\t\t\t\tnew_index += 1;\n\t\t\t} else {\n\t\t\t\t// A placeholder for invalid IDs. This is checked after the segment domain is modified.\n\t\t\t\tid_map.push(usize::MAX);\n\t\t\t}\n\t\t}\n\n\t\tlet update_index = |index: &mut usize| *index = id_map[*index];\n\t\tsegment_domain.start_point.iter_mut().for_each(update_index);\n\t\tsegment_domain.end_point.iter_mut().for_each(update_index);\n\n\t\tself.id.retain(f);\n\t}\n\n\tpub fn push(&mut self, id: PointId, position: DVec2) {\n\t\tif self.id.contains(&id) {\n\t\t\treturn;\n\t\t}\n\n\t\tself.id.push(id);\n\t\tself.position.push(position);\n\t}\n\n\tpub fn push_unchecked(&mut self, id: PointId, position: DVec2) {\n\t\tself.id.push(id);\n\t\tself.position.push(position);\n\t}\n\n\tpub fn positions(&self) -> &[DVec2] {\n\t\t&self.position\n\t}\n\n\tpub fn positions_mut(&mut self) -> impl Iterator<Item = (PointId, &mut DVec2)> {\n\t\tself.id.iter().copied().zip(self.position.iter_mut())\n\t}\n\n\tpub fn set_position(&mut self, index: usize, position: DVec2) {\n\t\tself.position[index] = position;\n\t}\n\n\tpub fn ids(&self) -> &[PointId] {\n\t\t&self.id\n\t}\n\n\tpub fn next_id(&self) -> PointId {\n\t\tself.ids().iter().copied().max_by(|a, b| a.0.cmp(&b.0)).map(|mut id| id.next_id()).unwrap_or(PointId::ZERO)\n\t}\n\n\t#[track_caller]\n\tpub fn position_from_id(&self, id: PointId) -> Option<DVec2> {\n\t\tlet pos = self.resolve_id(id).map(|index| self.position[index]);\n\t\tif pos.is_none() {\n\t\t\twarn!(\"Resolving pos of invalid id\");\n\t\t}\n\t\tpos\n\t}\n\n\tpub fn resolve_id(&self, id: PointId) -> Option<usize> {\n\t\tself.id.iter().position(|&check_id| check_id == id)\n\t}\n\n\tpub fn concat(&mut self, other: &Self, transform: DAffine2, id_map: &IdMap) {\n\t\tself.id.extend(other.id.iter().map(|id| *id_map.point_map.get(id).unwrap_or(id)));\n\t\tself.position.extend(other.position.iter().map(|&pos| transform.transform_point2(pos)));\n\t}\n\n\tpub fn map_ids(&mut self, id_map: &IdMap) {\n\t\tself.id.iter_mut().for_each(|id| *id = *id_map.point_map.get(id).unwrap_or(id));\n\t}\n\n\tpub fn transform(&mut self, transform: DAffine2) {\n\t\tfor pos in &mut self.position {\n\t\t\t*pos = transform.transform_point2(*pos);\n\t\t}\n\t}\n\n\tpub fn len(&self) -> usize {\n\t\tself.id.len()\n\t}\n\n\tpub fn is_empty(&self) -> bool {\n\t\tself.id.is_empty()\n\t}\n\n\t/// Iterate over point IDs and positions\n\tpub fn iter(&self) -> impl Iterator<Item = (PointId, DVec2)> + '_ {\n\t\tself.ids().iter().copied().zip(self.positions().iter().copied())\n\t}\n}\n\n#[derive(Clone, Debug, Default, PartialEq, Hash, DynAny, serde::Serialize, serde::Deserialize)]\n/// Stores data which is per-segment. A segment is a bézier curve between two end points with a stroke. In future this will be extendable at runtime with custom attributes.\npub struct SegmentDomain {\n\t#[serde(alias = \"ids\")]\n\tid: Vec<SegmentId>,\n\tstart_point: Vec<usize>,\n\tend_point: Vec<usize>,\n\thandles: Vec<BezierHandles>,\n\tstroke: Vec<StrokeId>,\n}\n\nimpl SegmentDomain {\n\tpub const fn new() -> Self {\n\t\tSelf {\n\t\t\tid: Vec::new(),\n\t\t\tstart_point: Vec::new(),\n\t\t\tend_point: Vec::new(),\n\t\t\thandles: Vec::new(),\n\t\t\tstroke: Vec::new(),\n\t\t}\n\t}\n\n\tpub fn clear(&mut self) {\n\t\tself.id.clear();\n\t\tself.start_point.clear();\n\t\tself.end_point.clear();\n\t\tself.handles.clear();\n\t\tself.stroke.clear();\n\t}\n\n\tpub fn retain(&mut self, f: impl Fn(&SegmentId) -> bool, points_length: usize) {\n\t\tlet additional_delete_ids = self\n\t\t\t.id\n\t\t\t.iter()\n\t\t\t.zip(&self.start_point)\n\t\t\t.zip(&self.end_point)\n\t\t\t.filter(|((_, start), end)| **start >= points_length || **end >= points_length)\n\t\t\t.map(|x| *x.0.0)\n\t\t\t.collect::<Vec<_>>();\n\n\t\tlet can_delete = || {\n\t\t\tlet f = &f;\n\t\t\tlet mut delete_iter = additional_delete_ids.iter().peekable();\n\t\t\tmove |id| {\n\t\t\t\tif delete_iter.peek() == Some(&id) {\n\t\t\t\t\tdelete_iter.next();\n\t\t\t\t\tfalse\n\t\t\t\t} else {\n\t\t\t\t\tf(id)\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\tlet mut keep = self.id.iter().map(can_delete());\n\t\tself.start_point.retain(|_| keep.next().unwrap_or_default());\n\t\tlet mut keep = self.id.iter().map(can_delete());\n\t\tself.end_point.retain(|_| keep.next().unwrap_or_default());\n\t\tlet mut keep = self.id.iter().map(can_delete());\n\t\tself.handles.retain(|_| keep.next().unwrap_or_default());\n\t\tlet mut keep = self.id.iter().map(can_delete());\n\t\tself.stroke.retain(|_| keep.next().unwrap_or_default());\n\n\t\tlet mut delete_iter = additional_delete_ids.iter().peekable();\n\t\tself.id.retain(move |id| {\n\t\t\tif delete_iter.peek() == Some(&id) {\n\t\t\t\tdelete_iter.next();\n\t\t\t\tfalse\n\t\t\t} else {\n\t\t\t\tf(id)\n\t\t\t}\n\t\t});\n\t}\n\n\tpub fn ids(&self) -> &[SegmentId] {\n\t\t&self.id\n\t}\n\n\tpub fn next_id(&self) -> SegmentId {\n\t\tself.ids().iter().copied().max_by(|a, b| a.0.cmp(&b.0)).map(|mut id| id.next_id()).unwrap_or(SegmentId::ZERO)\n\t}\n\n\tpub fn start_point(&self) -> &[usize] {\n\t\t&self.start_point\n\t}\n\n\tpub fn end_point(&self) -> &[usize] {\n\t\t&self.end_point\n\t}\n\n\tpub fn set_start_point(&mut self, segment_index: usize, new: usize) {\n\t\tself.start_point[segment_index] = new;\n\t}\n\n\tpub fn set_end_point(&mut self, segment_index: usize, new: usize) {\n\t\tself.end_point[segment_index] = new;\n\t}\n\n\tpub fn set_handles(&mut self, segment_index: usize, new: BezierHandles) {\n\t\tself.handles[segment_index] = new;\n\t}\n\n\tpub fn handles(&self) -> &[BezierHandles] {\n\t\t&self.handles\n\t}\n\n\tpub fn stroke(&self) -> &[StrokeId] {\n\t\t&self.stroke\n\t}\n\n\tpub fn push(&mut self, id: SegmentId, start: usize, end: usize, handles: BezierHandles, stroke: StrokeId) {\n\t\t#[cfg(debug_assertions)]\n\t\tif self.id.contains(&id) {\n\t\t\twarn!(\"Tried to push an existing point to a point domain\");\n\t\t}\n\n\t\tself.id.push(id);\n\t\tself.start_point.push(start);\n\t\tself.end_point.push(end);\n\t\tself.handles.push(handles);\n\t\tself.stroke.push(stroke);\n\t}\n\n\tpub(crate) fn start_point_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut usize)> {\n\t\tself.id.iter().copied().zip(self.start_point.iter_mut())\n\t}\n\n\tpub(crate) fn end_point_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut usize)> {\n\t\tself.id.iter().copied().zip(self.end_point.iter_mut())\n\t}\n\n\tpub(crate) fn handles_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut BezierHandles, usize, usize)> {\n\t\tlet nested = self.id.iter().zip(&mut self.handles).zip(&self.start_point).zip(&self.end_point);\n\t\tnested.map(|(((&a, b), &c), &d)| (a, b, c, d))\n\t}\n\n\tpub fn handles_and_points_mut(&mut self) -> impl Iterator<Item = (&mut BezierHandles, &mut usize, &mut usize)> {\n\t\tlet nested = self.handles.iter_mut().zip(&mut self.start_point).zip(&mut self.end_point);\n\t\tnested.map(|((a, b), c)| (a, b, c))\n\t}\n\n\tpub fn stroke_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut StrokeId)> {\n\t\tself.id.iter().copied().zip(self.stroke.iter_mut())\n\t}\n\n\tpub(crate) fn segment_start_from_id(&self, segment: SegmentId) -> Option<usize> {\n\t\tself.id_to_index(segment).and_then(|index| self.start_point.get(index)).copied()\n\t}\n\n\tpub(crate) fn segment_end_from_id(&self, segment: SegmentId) -> Option<usize> {\n\t\tself.id_to_index(segment).and_then(|index| self.end_point.get(index)).copied()\n\t}\n\n\t/// Returns an array for the start and end points of a segment.\n\tpub(crate) fn points_from_id(&self, segment: SegmentId) -> Option<[usize; 2]> {\n\t\tself.segment_start_from_id(segment).and_then(|start| self.segment_end_from_id(segment).map(|end| [start, end]))\n\t}\n\n\t/// Attempts to find another point in the segment that is not the one passed in.\n\tpub(crate) fn other_point(&self, segment: SegmentId, current: usize) -> Option<usize> {\n\t\tself.points_from_id(segment).and_then(|points| points.into_iter().find(|&point| point != current))\n\t}\n\n\t/// Gets all points connected to the current one but not including the current one.\n\tpub(crate) fn connected_points(&self, current: usize) -> impl Iterator<Item = usize> + '_ {\n\t\tself.start_point.iter().zip(&self.end_point).filter_map(move |(&a, &b)| match (a == current, b == current) {\n\t\t\t(true, false) => Some(b),\n\t\t\t(false, true) => Some(a),\n\t\t\t_ => None,\n\t\t})\n\t}\n\n\t/// Get index from ID by linear search. Takes `O(n)` time.\n\tfn id_to_index(&self, id: SegmentId) -> Option<usize> {\n\t\tdebug_assert_eq!(self.id.len(), self.handles.len());\n\t\tdebug_assert_eq!(self.id.len(), self.start_point.len());\n\t\tdebug_assert_eq!(self.id.len(), self.end_point.len());\n\t\tself.id.iter().position(|&check_id| check_id == id)\n\t}\n\n\tfn resolve_range(&self, range: &std::ops::RangeInclusive<SegmentId>) -> Option<std::ops::RangeInclusive<usize>> {\n\t\tmatch (self.id_to_index(*range.start()), self.id_to_index(*range.end())) {\n\t\t\t(Some(start), Some(end)) if start.max(end) < self.handles.len().min(self.id.len()).min(self.start_point.len()).min(self.end_point.len()) => Some(start..=end),\n\t\t\t_ => {\n\t\t\t\twarn!(\"Resolving range with invalid id\");\n\t\t\t\tNone\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn concat(&mut self, other: &Self, transform: DAffine2, id_map: &IdMap) {\n\t\tself.id.extend(other.id.iter().map(|id| *id_map.segment_map.get(id).unwrap_or(id)));\n\t\tself.start_point.extend(other.start_point.iter().map(|&index| id_map.point_offset + index));\n\t\tself.end_point.extend(other.end_point.iter().map(|&index| id_map.point_offset + index));\n\t\tself.handles.extend(other.handles.iter().map(|handles| handles.apply_transformation(|p| transform.transform_point2(p))));\n\t\tself.stroke.extend(&other.stroke);\n\t}\n\n\tpub fn map_ids(&mut self, id_map: &IdMap) {\n\t\tself.id.iter_mut().for_each(|id| *id = *id_map.segment_map.get(id).unwrap_or(id));\n\t}\n\n\tpub fn transform(&mut self, transform: DAffine2) {\n\t\tfor handles in &mut self.handles {\n\t\t\t*handles = handles.apply_transformation(|p| transform.transform_point2(p));\n\t\t}\n\t}\n\n\t/// Enumerate all segments that start at the point.\n\tpub(crate) fn start_connected(&self, point: usize) -> impl Iterator<Item = SegmentId> + '_ {\n\t\tself.start_point.iter().zip(&self.id).filter(move |&(&found_point, _)| found_point == point).map(|(_, &seg)| seg)\n\t}\n\n\t/// Enumerate all segments that end at the point.\n\tpub(crate) fn end_connected(&self, point: usize) -> impl Iterator<Item = SegmentId> + '_ {\n\t\tself.end_point.iter().zip(&self.id).filter(move |&(&found_point, _)| found_point == point).map(|(_, &seg)| seg)\n\t}\n\n\t/// Enumerate all segments that start or end at a point, converting them to [`HandleId`s]. Note that the handles may not exist e.g. for a linear segment.\n\tpub(crate) fn all_connected(&self, point: usize) -> impl Iterator<Item = HandleId> + '_ {\n\t\tself.start_connected(point).map(HandleId::primary).chain(self.end_connected(point).map(HandleId::end))\n\t}\n\n\t/// Enumerate the number of segments connected to a point. If a segment starts and ends at a point then it is counted twice.\n\tpub(crate) fn connected_count(&self, point: usize) -> usize {\n\t\tself.all_connected(point).count()\n\t}\n\n\t/// Enumerate the number of segments connected to a point. If a segment starts and ends at a point then it is counted twice.\n\tpub(crate) fn any_connected(&self, point: usize) -> bool {\n\t\tself.all_connected(point).next().is_some()\n\t}\n\n\t/// Iterates over segments in the domain.\n\t///\n\t/// Tuple is: (id, start point, end point, handles)\n\tpub fn iter(&self) -> impl Iterator<Item = (SegmentId, usize, usize, BezierHandles)> + '_ {\n\t\tlet ids = self.id.iter().copied();\n\t\tlet start_point = self.start_point.iter().copied();\n\t\tlet end_point = self.end_point.iter().copied();\n\t\tlet handles = self.handles.iter().copied();\n\t\tzip(ids, zip(start_point, zip(end_point, handles))).map(|(id, (start_point, (end_point, handles)))| (id, start_point, end_point, handles))\n\t}\n\n\t/// Iterates over segments in the domain, mutably.\n\t///\n\t/// Tuple is: (id, start point, end point, handles)\n\tpub(crate) fn iter_mut(&mut self) -> impl Iterator<Item = (&mut SegmentId, &mut usize, &mut usize, &mut BezierHandles)> + '_ {\n\t\tlet ids = self.id.iter_mut();\n\t\tlet start_point = self.start_point.iter_mut();\n\t\tlet end_point = self.end_point.iter_mut();\n\t\tlet handles = self.handles.iter_mut();\n\t\tzip(ids, zip(start_point, zip(end_point, handles))).map(|(id, (start_point, (end_point, handles)))| (id, start_point, end_point, handles))\n\t}\n\n\tpub fn pair_handles_and_points_mut_by_index(&mut self, index1: usize, index2: usize) -> (&mut BezierHandles, &mut usize, &mut usize, &mut BezierHandles, &mut usize, &mut usize) {\n\t\t// Use split_at_mut to avoid multiple mutable borrows of the same slice\n\t\tlet (handles_first, handles_second) = self.handles.split_at_mut(index2.max(index1));\n\t\tlet (start_first, start_second) = self.start_point.split_at_mut(index2.max(index1));\n\t\tlet (end_first, end_second) = self.end_point.split_at_mut(index2.max(index1));\n\n\t\tlet (h1, h2) = if index1 < index2 {\n\t\t\t(&mut handles_first[index1], &mut handles_second[0])\n\t\t} else {\n\t\t\t(&mut handles_second[0], &mut handles_first[index2])\n\t\t};\n\t\tlet (sp1, sp2) = if index1 < index2 {\n\t\t\t(&mut start_first[index1], &mut start_second[0])\n\t\t} else {\n\t\t\t(&mut start_second[0], &mut start_first[index2])\n\t\t};\n\t\tlet (ep1, ep2) = if index1 < index2 {\n\t\t\t(&mut end_first[index1], &mut end_second[0])\n\t\t} else {\n\t\t\t(&mut end_second[0], &mut end_first[index2])\n\t\t};\n\n\t\t(h1, sp1, ep1, h2, sp2, ep2)\n\t}\n}\n\n#[derive(Clone, Debug, Default, PartialEq, Hash, DynAny, serde::Serialize, serde::Deserialize)]\n/// Stores data which is per-region. A region is an enclosed area composed of a range of segments from the\n/// [`SegmentDomain`] that can be given a fill. In future this will be extendable at runtime with custom attributes.\npub struct RegionDomain {\n\t#[serde(alias = \"ids\")]\n\tid: Vec<RegionId>,\n\tsegment_range: Vec<std::ops::RangeInclusive<SegmentId>>,\n\tfill: Vec<FillId>,\n}\n\nimpl RegionDomain {\n\tpub const fn new() -> Self {\n\t\tSelf {\n\t\t\tid: Vec::new(),\n\t\t\tsegment_range: Vec::new(),\n\t\t\tfill: Vec::new(),\n\t\t}\n\t}\n\n\tpub fn clear(&mut self) {\n\t\tself.id.clear();\n\t\tself.segment_range.clear();\n\t\tself.fill.clear();\n\t}\n\n\tpub fn retain(&mut self, f: impl Fn(&RegionId) -> bool) {\n\t\tlet mut keep = self.id.iter().map(&f);\n\t\tself.segment_range.retain(|_| keep.next().unwrap_or_default());\n\t\tlet mut keep = self.id.iter().map(&f);\n\t\tself.fill.retain(|_| keep.next().unwrap_or_default());\n\t\tself.id.retain(&f);\n\t}\n\n\t/// Like [`Self::retain`] but also gives the function access to the segment range.\n\t///\n\t/// Note that this function requires an allocation that `retain` avoids.\n\tpub fn retain_with_region(&mut self, f: impl Fn(&RegionId, &std::ops::RangeInclusive<SegmentId>) -> bool) {\n\t\tlet keep = self.id.iter().zip(self.segment_range.iter()).map(|(id, range)| f(id, range)).collect::<Vec<_>>();\n\t\tlet mut iter = keep.iter().copied();\n\t\tself.segment_range.retain(|_| iter.next().unwrap());\n\t\tlet mut iter = keep.iter().copied();\n\t\tself.fill.retain(|_| iter.next().unwrap());\n\t\tlet mut iter = keep.iter().copied();\n\t\tself.id.retain(|_| iter.next().unwrap());\n\t}\n\n\tpub fn push(&mut self, id: RegionId, segment_range: std::ops::RangeInclusive<SegmentId>, fill: FillId) {\n\t\tif self.id.contains(&id) {\n\t\t\twarn!(\"Duplicate region\");\n\t\t\treturn;\n\t\t}\n\t\tself.id.push(id);\n\t\tself.segment_range.push(segment_range);\n\t\tself.fill.push(fill);\n\t}\n\n\tfn _resolve_id(&self, id: RegionId) -> Option<usize> {\n\t\tself.id.iter().position(|&check_id| check_id == id)\n\t}\n\n\tpub fn next_id(&self) -> RegionId {\n\t\tself.id.iter().copied().max_by(|a, b| a.0.cmp(&b.0)).map(|mut id| id.next_id()).unwrap_or(RegionId::ZERO)\n\t}\n\n\tpub fn segment_range_mut(&mut self) -> impl Iterator<Item = (RegionId, &mut std::ops::RangeInclusive<SegmentId>)> {\n\t\tself.id.iter().copied().zip(self.segment_range.iter_mut())\n\t}\n\n\tpub fn fill_mut(&mut self) -> impl Iterator<Item = (RegionId, &mut FillId)> {\n\t\tself.id.iter().copied().zip(self.fill.iter_mut())\n\t}\n\n\tpub fn ids(&self) -> &[RegionId] {\n\t\t&self.id\n\t}\n\n\tpub fn segment_range(&self) -> &[std::ops::RangeInclusive<SegmentId>] {\n\t\t&self.segment_range\n\t}\n\n\tpub fn fill(&self) -> &[FillId] {\n\t\t&self.fill\n\t}\n\n\tpub fn concat(&mut self, other: &Self, _transform: DAffine2, id_map: &IdMap) {\n\t\tself.id.extend(other.id.iter().map(|id| *id_map.region_map.get(id).unwrap_or(id)));\n\t\tself.segment_range.extend(\n\t\t\tother\n\t\t\t\t.segment_range\n\t\t\t\t.iter()\n\t\t\t\t.map(|range| *id_map.segment_map.get(range.start()).unwrap_or(range.start())..=*id_map.segment_map.get(range.end()).unwrap_or(range.end())),\n\t\t);\n\t\tself.fill.extend(&other.fill);\n\t}\n\n\tpub fn map_ids(&mut self, id_map: &IdMap) {\n\t\tself.id.iter_mut().for_each(|id| *id = *id_map.region_map.get(id).unwrap_or(id));\n\t\tself.segment_range\n\t\t\t.iter_mut()\n\t\t\t.for_each(|range| *range = *id_map.segment_map.get(range.start()).unwrap_or(range.start())..=*id_map.segment_map.get(range.end()).unwrap_or(range.end()));\n\t}\n\n\t/// Iterates over regions in the domain.\n\t///\n\t/// Tuple is: (id, segment_range, fill)\n\tpub fn iter(&self) -> impl Iterator<Item = (RegionId, std::ops::RangeInclusive<SegmentId>, FillId)> + '_ {\n\t\tlet ids = self.id.iter().copied();\n\t\tlet segment_range = self.segment_range.iter().cloned();\n\t\tlet fill = self.fill.iter().copied();\n\t\tzip(ids, zip(segment_range, fill)).map(|(id, (segment_range, fill))| (id, segment_range, fill))\n\t}\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub struct HalfEdge {\n\tpub id: SegmentId,\n\tpub start: usize,\n\tpub end: usize,\n\tpub reverse: bool,\n}\n\nimpl HalfEdge {\n\tpub fn new(id: SegmentId, start: usize, end: usize, reverse: bool) -> Self {\n\t\tSelf { id, start, end, reverse }\n\t}\n\n\tpub fn reversed(&self) -> Self {\n\t\tSelf {\n\t\t\tid: self.id,\n\t\t\tstart: self.start,\n\t\t\tend: self.end,\n\t\t\treverse: !self.reverse,\n\t\t}\n\t}\n\n\tpub fn normalize_direction(&self) -> Self {\n\t\tif self.reverse {\n\t\t\tSelf {\n\t\t\t\tid: self.id,\n\t\t\t\tstart: self.end,\n\t\t\t\tend: self.start,\n\t\t\t\treverse: false,\n\t\t\t}\n\t\t} else {\n\t\t\t*self\n\t\t}\n\t}\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\npub struct FoundSubpath {\n\tpub edges: Vec<HalfEdge>,\n}\n\nimpl FoundSubpath {\n\tpub fn new(segments: Vec<HalfEdge>) -> Self {\n\t\tSelf { edges: segments }\n\t}\n\n\tpub fn endpoints(&self) -> Option<(&HalfEdge, &HalfEdge)> {\n\t\tmatch (self.edges.first(), self.edges.last()) {\n\t\t\t(Some(first), Some(last)) => Some((first, last)),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tpub fn push(&mut self, segment: HalfEdge) {\n\t\tself.edges.push(segment);\n\t}\n\n\tpub fn insert(&mut self, index: usize, segment: HalfEdge) {\n\t\tself.edges.insert(index, segment);\n\t}\n\n\tpub fn extend(&mut self, segments: impl IntoIterator<Item = HalfEdge>) {\n\t\tself.edges.extend(segments);\n\t}\n\n\tpub fn splice<I>(&mut self, range: std::ops::Range<usize>, replace_with: I)\n\twhere\n\t\tI: IntoIterator<Item = HalfEdge>,\n\t{\n\t\tself.edges.splice(range, replace_with);\n\t}\n\n\tpub fn is_closed(&self) -> bool {\n\t\tmatch (self.edges.first(), self.edges.last()) {\n\t\t\t(Some(first), Some(last)) => first.start == last.end,\n\t\t\t_ => false,\n\t\t}\n\t}\n\n\tpub fn from_segment(segment: HalfEdge) -> Self {\n\t\tSelf { edges: vec![segment] }\n\t}\n\n\tpub fn contains(&self, segment_id: SegmentId) -> bool {\n\t\tself.edges.iter().any(|s| s.id == segment_id)\n\t}\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]\nstruct FaceSide {\n\tsegment_index: usize,\n\treversed: bool,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\nstruct FaceSideSet {\n\tset: FixedBitSet,\n}\nimpl FaceSideSet {\n\tfn new(size: usize) -> Self {\n\t\tSelf {\n\t\t\tset: FixedBitSet::with_capacity(size * 2),\n\t\t}\n\t}\n\n\tfn index(&self, side: FaceSide) -> usize {\n\t\t(side.segment_index << 1) | (side.reversed as usize)\n\t}\n\n\tfn insert(&mut self, side: FaceSide) {\n\t\tself.set.insert(self.index(side));\n\t}\n\n\tfn remove(&mut self, side: FaceSide) {\n\t\tself.set.set(self.index(side), false);\n\t}\n\n\tfn contains(&self, side: FaceSide) -> bool {\n\t\tself.set.contains(self.index(side))\n\t}\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\nstruct Faces {\n\tsides: Vec<FaceSide>,\n\tface_start: Vec<usize>,\n}\n\n#[derive(Debug, Clone, PartialEq, Hash)]\npub struct FaceIterator<'a, Upstream> {\n\tvector: &'a Vector<Upstream>,\n\tfaces: Faces,\n\tcurrent_face: usize,\n}\n\nimpl<Upstream> FaceIterator<'_, Upstream> {\n\tfn new<'a>(faces: Faces, vector: &'a Vector<Upstream>) -> FaceIterator<'a, Upstream> {\n\t\tFaceIterator { vector, faces, current_face: 0 }\n\t}\n\n\tfn get_point(&self, point: usize) -> kurbo::Point {\n\t\tdvec2_to_point(self.vector.point_domain.positions()[point])\n\t}\n}\n\nimpl<Upstream> Iterator for FaceIterator<'_, Upstream> {\n\ttype Item = kurbo::BezPath;\n\tfn next(&mut self) -> Option<Self::Item> {\n\t\tlet start_side = self.faces.face_start.get(self.current_face).copied()?;\n\t\tself.current_face += 1;\n\t\tlet end_side = self.faces.face_start.get(self.current_face).copied().unwrap_or(self.faces.sides.len());\n\n\t\tlet mut path = kurbo::BezPath::new();\n\n\t\tlet segment_domain = &self.vector.segment_domain;\n\t\tlet first_side = self.faces.sides.get(start_side)?;\n\t\tlet start_point_index = if first_side.reversed {\n\t\t\tsegment_domain.end_point[first_side.segment_index]\n\t\t} else {\n\t\t\tsegment_domain.start_point[first_side.segment_index]\n\t\t};\n\t\tpath.move_to(self.get_point(start_point_index));\n\t\tfor side in &self.faces.sides[start_side..end_side] {\n\t\t\tlet (handle, end_index) = match side.reversed {\n\t\t\t\tfalse => (segment_domain.handles[side.segment_index], segment_domain.end_point[side.segment_index]),\n\t\t\t\ttrue => (segment_domain.handles[side.segment_index].reversed(), segment_domain.start_point[side.segment_index]),\n\t\t\t};\n\t\t\tlet path_element = match handle {\n\t\t\t\tBezierHandles::Linear => kurbo::PathEl::LineTo(self.get_point(end_index)),\n\t\t\t\tBezierHandles::Quadratic { handle } => kurbo::PathEl::QuadTo(dvec2_to_point(handle), self.get_point(end_index)),\n\t\t\t\tBezierHandles::Cubic { handle_start, handle_end } => kurbo::PathEl::CurveTo(dvec2_to_point(handle_start), dvec2_to_point(handle_end), self.get_point(end_index)),\n\t\t\t};\n\t\t\tpath.push(path_element);\n\t\t}\n\n\t\tSome(path)\n\t}\n}\n\nimpl Faces {\n\tpub fn new() -> Self {\n\t\tSelf {\n\t\t\tsides: Vec::new(),\n\t\t\tface_start: Vec::new(),\n\t\t}\n\t}\n\tpub fn add_side(&mut self, side: FaceSide) {\n\t\tself.sides.push(side);\n\t}\n\tpub fn start_new_face(&mut self) {\n\t\tself.face_start.push(self.sides.len());\n\t}\n\tpub fn backtrack(&mut self) {\n\t\tif let Some(last_start) = self.face_start.pop() {\n\t\t\tself.sides.truncate(last_start);\n\t\t}\n\t}\n}\n\nimpl<Upstream> Vector<Upstream> {\n\t/// Construct a [`kurbo::PathSeg`] by resolving the points from their ids.\n\tfn path_segment_from_index(&self, start: usize, end: usize, handles: BezierHandles) -> PathSeg {\n\t\tlet start = dvec2_to_point(self.point_domain.positions()[start]);\n\t\tlet end = dvec2_to_point(self.point_domain.positions()[end]);\n\n\t\tmatch handles {\n\t\t\tBezierHandles::Linear => PathSeg::Line(Line::new(start, end)),\n\t\t\tBezierHandles::Quadratic { handle } => PathSeg::Quad(QuadBez::new(start, dvec2_to_point(handle), end)),\n\t\t\tBezierHandles::Cubic { handle_start, handle_end } => PathSeg::Cubic(CubicBez::new(start, dvec2_to_point(handle_start), dvec2_to_point(handle_end), end)),\n\t\t}\n\t}\n\n\t/// Construct a [`Bezier`] curve spanning from the resolved position of the start and end points with the specified handles.\n\tfn segment_to_bezier_with_index(&self, start: usize, end: usize, handles: BezierHandles) -> Bezier {\n\t\tlet start = self.point_domain.positions()[start];\n\t\tlet end = self.point_domain.positions()[end];\n\t\tBezier { start, end, handles }\n\t}\n\n\t/// Tries to convert a segment with the specified id to a [`Bezier`], returning None if the id is invalid.\n\tpub fn segment_from_id(&self, id: SegmentId) -> Option<Bezier> {\n\t\tself.segment_points_from_id(id).map(|(_, _, bezier)| bezier)\n\t}\n\n\t/// Tries to convert a segment with the specified id to the start and end points and a [`Bezier`], returning None if the id is invalid.\n\tpub fn segment_points_from_id(&self, id: SegmentId) -> Option<(PointId, PointId, Bezier)> {\n\t\tSome(self.segment_points_from_index(self.segment_domain.id_to_index(id)?))\n\t}\n\n\t/// Tries to convert a segment with the specified index to the start and end points and a [`Bezier`].\n\tpub fn segment_points_from_index(&self, index: usize) -> (PointId, PointId, Bezier) {\n\t\tlet start = self.segment_domain.start_point[index];\n\t\tlet end = self.segment_domain.end_point[index];\n\t\tlet start_id = self.point_domain.ids()[start];\n\t\tlet end_id = self.point_domain.ids()[end];\n\t\t(start_id, end_id, self.segment_to_bezier_with_index(start, end, self.segment_domain.handles[index]))\n\t}\n\n\t/// Iterator over all of the [`Bezier`] following the order that they are stored in the segment domain, skipping invalid segments.\n\tpub fn segment_iter(&self) -> impl Iterator<Item = (SegmentId, PathSeg, PointId, PointId)> {\n\t\tlet to_segment = |(((&handles, &id), &start), &end)| (id, self.path_segment_from_index(start, end, handles), self.point_domain.ids()[start], self.point_domain.ids()[end]);\n\n\t\tself.segment_domain\n\t\t\t.handles\n\t\t\t.iter()\n\t\t\t.zip(&self.segment_domain.id)\n\t\t\t.zip(self.segment_domain.start_point())\n\t\t\t.zip(self.segment_domain.end_point())\n\t\t\t.map(to_segment)\n\t}\n\n\t/// Iterator over all of the [`Bezier`] following the order that they are stored in the segment domain, skipping invalid segments.\n\tpub fn segment_bezier_iter(&self) -> impl Iterator<Item = (SegmentId, Bezier, PointId, PointId)> + '_ {\n\t\tlet to_bezier = |(((&handles, &id), &start), &end)| (id, self.segment_to_bezier_with_index(start, end, handles), self.point_domain.ids()[start], self.point_domain.ids()[end]);\n\t\tself.segment_domain\n\t\t\t.handles\n\t\t\t.iter()\n\t\t\t.zip(&self.segment_domain.id)\n\t\t\t.zip(self.segment_domain.start_point())\n\t\t\t.zip(self.segment_domain.end_point())\n\t\t\t.map(to_bezier)\n\t}\n\n\tpub fn auto_join_paths(&self) -> Vec<FoundSubpath> {\n\t\tlet segments = self.segment_domain.iter().map(|(id, start, end, _)| HalfEdge::new(id, start, end, false));\n\n\t\tlet mut paths: Vec<FoundSubpath> = Vec::new();\n\t\tlet mut current_path: Option<&mut FoundSubpath> = None;\n\t\tlet mut previous: Option<(usize, usize)> = None;\n\n\t\t// First pass. Generates subpaths from continuous segments.\n\t\tfor seg_ref in segments {\n\t\t\tlet (start, end) = (seg_ref.start, seg_ref.end);\n\n\t\t\tif previous.is_some_and(|(_, prev_end)| start == prev_end) {\n\t\t\t\tif let Some(path) = current_path.as_mut() {\n\t\t\t\t\tpath.push(seg_ref);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tpaths.push(FoundSubpath::from_segment(seg_ref));\n\t\t\t\tcurrent_path = paths.last_mut();\n\t\t\t}\n\n\t\t\tprevious = Some((start, end));\n\t\t}\n\n\t\t// Second pass. Try to join paths together.\n\t\tlet mut joined_paths = Vec::new();\n\n\t\tloop {\n\t\t\tlet mut prev_index: Option<usize> = None;\n\t\t\tlet original_len = paths.len();\n\n\t\t\tfor current in paths.into_iter() {\n\t\t\t\t// If there's no previous subpath, start a new one\n\t\t\t\tif prev_index.is_none() {\n\t\t\t\t\tjoined_paths.push(current);\n\t\t\t\t\tprev_index = Some(joined_paths.len() - 1);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tlet prev = &mut joined_paths[prev_index.unwrap()];\n\n\t\t\t\t// Compare segment connections\n\t\t\t\tlet (prev_first, prev_last) = prev.endpoints().unwrap();\n\t\t\t\tlet (cur_first, cur_last) = current.endpoints().unwrap();\n\n\t\t\t\t// Join paths if the endpoints connect\n\t\t\t\tif prev_last.end == cur_first.start {\n\t\t\t\t\tprev.edges.extend(current.edges.into_iter().map(|s| s.normalize_direction()));\n\t\t\t\t} else if prev_first.start == cur_last.end {\n\t\t\t\t\tprev.edges.splice(0..0, current.edges.into_iter().rev().map(|s| s.normalize_direction()));\n\t\t\t\t} else if prev_last.end == cur_last.end {\n\t\t\t\t\tprev.edges.extend(current.edges.into_iter().rev().map(|s| s.reversed().normalize_direction()));\n\t\t\t\t} else if prev_first.start == cur_first.start {\n\t\t\t\t\tprev.edges.splice(0..0, current.edges.into_iter().map(|s| s.reversed().normalize_direction()));\n\t\t\t\t} else {\n\t\t\t\t\t// If not connected, start a new subpath\n\t\t\t\t\tjoined_paths.push(current);\n\t\t\t\t\tprev_index = Some(joined_paths.len() - 1);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If no paths were joined in this pass, we're done\n\t\t\tif joined_paths.len() == original_len {\n\t\t\t\treturn joined_paths;\n\t\t\t}\n\n\t\t\t// Repeat pass with newly joined paths\n\t\t\tpaths = joined_paths;\n\t\t\tjoined_paths = Vec::new();\n\t\t}\n\t}\n\n\t/// Construct a [`Bezier`] curve from an iterator of segments with (handles, start point, end point) independently of discontinuities.\n\tpub fn subpath_from_segments_ignore_discontinuities(&self, segments: impl Iterator<Item = (BezierHandles, usize, usize)>) -> Option<Subpath<PointId>> {\n\t\tlet mut first_point = None;\n\t\tlet mut manipulators_list = Vec::new();\n\t\tlet mut last: Option<(usize, BezierHandles)> = None;\n\n\t\tfor (handle, start, end) in segments {\n\t\t\tfirst_point = Some(first_point.unwrap_or(start));\n\n\t\t\tmanipulators_list.push(ManipulatorGroup {\n\t\t\t\tanchor: self.point_domain.positions()[start],\n\t\t\t\tin_handle: last.and_then(|(_, handle)| handle.end()),\n\t\t\t\tout_handle: handle.start(),\n\t\t\t\tid: self.point_domain.ids()[start],\n\t\t\t});\n\n\t\t\tlast = Some((end, handle));\n\t\t}\n\n\t\tlet closed = manipulators_list.len() > 1 && last.map(|(point, _)| point) == first_point;\n\n\t\tif let Some((end, last_handle)) = last {\n\t\t\tif closed {\n\t\t\t\tmanipulators_list[0].in_handle = last_handle.end();\n\t\t\t} else {\n\t\t\t\tmanipulators_list.push(ManipulatorGroup {\n\t\t\t\t\tanchor: self.point_domain.positions()[end],\n\t\t\t\t\tin_handle: last_handle.end(),\n\t\t\t\t\tout_handle: None,\n\t\t\t\t\tid: self.point_domain.ids()[end],\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tSome(Subpath::new(manipulators_list, closed))\n\t}\n\n\t/// Construct a [`Bezier`] curve for each region, skipping invalid regions.\n\tpub fn region_manipulator_groups(&self) -> impl Iterator<Item = (RegionId, Vec<ManipulatorGroup<PointId>>)> + '_ {\n\t\tself.region_domain\n\t\t\t.id\n\t\t\t.iter()\n\t\t\t.zip(&self.region_domain.segment_range)\n\t\t\t.filter_map(|(&id, segment_range)| self.segment_domain.resolve_range(segment_range).map(|range| (id, range)))\n\t\t\t.filter_map(|(id, range)| {\n\t\t\t\tlet segments_iter = self\n\t\t\t\t\t.segment_domain\n\t\t\t\t\t.handles\n\t\t\t\t\t.get(range.clone())?\n\t\t\t\t\t.iter()\n\t\t\t\t\t.zip(self.segment_domain.start_point.get(range.clone())?)\n\t\t\t\t\t.zip(self.segment_domain.end_point.get(range)?)\n\t\t\t\t\t.map(|((&handles, &start), &end)| (handles, start, end));\n\n\t\t\t\tlet mut manipulator_groups = Vec::new();\n\t\t\t\tlet mut in_handle = None;\n\n\t\t\t\tfor segment in segments_iter {\n\t\t\t\t\tlet (handles, start_point_index, _end_point_index) = segment;\n\t\t\t\t\tlet start_point_id = self.point_domain.id[start_point_index];\n\t\t\t\t\tlet start_point = self.point_domain.position[start_point_index];\n\n\t\t\t\t\tlet (manipulator_group, next_in_handle) = match handles {\n\t\t\t\t\t\tBezierHandles::Linear => (ManipulatorGroup::new_with_id(start_point, in_handle, None, start_point_id), None),\n\t\t\t\t\t\tBezierHandles::Quadratic { handle } => (ManipulatorGroup::new_with_id(start_point, in_handle, Some(handle), start_point_id), None),\n\t\t\t\t\t\tBezierHandles::Cubic { handle_start, handle_end } => (ManipulatorGroup::new_with_id(start_point, in_handle, Some(handle_start), start_point_id), Some(handle_end)),\n\t\t\t\t\t};\n\n\t\t\t\t\tin_handle = next_in_handle;\n\t\t\t\t\tmanipulator_groups.push(manipulator_group);\n\t\t\t\t}\n\n\t\t\t\tif let Some(first) = manipulator_groups.first_mut() {\n\t\t\t\t\tfirst.in_handle = in_handle;\n\t\t\t\t}\n\n\t\t\t\tSome((id, manipulator_groups))\n\t\t\t})\n\t}\n\n\tpub fn build_stroke_path_iter(&self) -> StrokePathIter<'_, Upstream> {\n\t\tlet mut points = vec![StrokePathIterPointMetadata::default(); self.point_domain.ids().len()];\n\t\tfor (segment_index, (&start, &end)) in self.segment_domain.start_point.iter().zip(&self.segment_domain.end_point).enumerate() {\n\t\t\tpoints[start].set(StrokePathIterPointSegmentMetadata::new(segment_index, false));\n\t\t\tpoints[end].set(StrokePathIterPointSegmentMetadata::new(segment_index, true));\n\t\t}\n\n\t\tStrokePathIter {\n\t\t\tvector: self,\n\t\t\tpoints,\n\t\t\tskip: 0,\n\t\t\tdone_one: false,\n\t\t}\n\t}\n\n\t/// Construct a [`Bezier`] curve for stroke.\n\tpub fn stroke_bezier_paths(&self) -> impl Iterator<Item = Subpath<PointId>> {\n\t\tself.build_stroke_path_iter().map(|(manipulators_list, closed)| Subpath::new(manipulators_list, closed))\n\t}\n\n\t/// Construct and return an iterator of Vec of `(ManipulatorGroup<PointId>], bool)` for stroke.\n\t/// The boolean in the tuple indicates if the path is closed.\n\tpub fn stroke_manipulator_groups(&self) -> impl Iterator<Item = (Vec<ManipulatorGroup<PointId>>, bool)> {\n\t\tself.build_stroke_path_iter()\n\t}\n\n\t/// Construct a [`kurbo::BezPath`] curve for stroke.\n\tpub fn stroke_bezpath_iter(&self) -> impl Iterator<Item = kurbo::BezPath> {\n\t\tself.build_stroke_path_iter().map(|(manipulators_list, closed)| {\n\t\t\tlet mut bezpath = kurbo::BezPath::new();\n\t\t\tlet mut out_handle;\n\n\t\t\tlet Some(first) = manipulators_list.first() else { return bezpath };\n\t\t\tbezpath.move_to(dvec2_to_point(first.anchor));\n\t\t\tout_handle = first.out_handle;\n\n\t\t\tfor manipulator in manipulators_list.iter().skip(1) {\n\t\t\t\tmatch (out_handle, manipulator.in_handle) {\n\t\t\t\t\t(Some(handle_start), Some(handle_end)) => bezpath.curve_to(dvec2_to_point(handle_start), dvec2_to_point(handle_end), dvec2_to_point(manipulator.anchor)),\n\t\t\t\t\t(None, None) => bezpath.line_to(dvec2_to_point(manipulator.anchor)),\n\t\t\t\t\t(None, Some(handle)) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(manipulator.anchor)),\n\t\t\t\t\t(Some(handle), None) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(manipulator.anchor)),\n\t\t\t\t}\n\t\t\t\tout_handle = manipulator.out_handle;\n\t\t\t}\n\n\t\t\tif closed {\n\t\t\t\tmatch (out_handle, first.in_handle) {\n\t\t\t\t\t(Some(handle_start), Some(handle_end)) => bezpath.curve_to(dvec2_to_point(handle_start), dvec2_to_point(handle_end), dvec2_to_point(first.anchor)),\n\t\t\t\t\t(None, None) => bezpath.line_to(dvec2_to_point(first.anchor)),\n\t\t\t\t\t(None, Some(handle)) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(first.anchor)),\n\t\t\t\t\t(Some(handle), None) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(first.anchor)),\n\t\t\t\t}\n\t\t\t\tbezpath.close_path();\n\t\t\t}\n\t\t\tbezpath\n\t\t})\n\t}\n\n\t/// Construct an iterator [`ManipulatorGroup`] for stroke.\n\tpub fn manipulator_groups(&self) -> impl Iterator<Item = ManipulatorGroup<PointId>> + '_ {\n\t\tself.stroke_bezier_paths().flat_map(|mut path| std::mem::take(path.manipulator_groups_mut()))\n\t}\n\n\tpub fn manipulator_group_id(&self, id: impl Into<PointId>) -> Option<ManipulatorGroup<PointId>> {\n\t\tlet id = id.into();\n\t\tself.manipulator_groups().find(|manipulators| manipulators.id == id)\n\t}\n\n\tpub fn transform(&mut self, transform: DAffine2) {\n\t\tself.point_domain.transform(transform);\n\t\tself.segment_domain.transform(transform);\n\t}\n\n\tpub fn vector_new_ids_from_hash(&mut self, node_id: u64) {\n\t\tlet point_map = self.point_domain.ids().iter().map(|&old| (old, old.generate_from_hash(node_id))).collect::<HashMap<_, _>>();\n\t\tlet segment_map = self.segment_domain.ids().iter().map(|&old| (old, old.generate_from_hash(node_id))).collect::<HashMap<_, _>>();\n\t\tlet region_map = self.region_domain.ids().iter().map(|&old| (old, old.generate_from_hash(node_id))).collect::<HashMap<_, _>>();\n\n\t\tlet id_map = IdMap {\n\t\t\tpoint_offset: self.point_domain.ids().len(),\n\t\t\tpoint_map,\n\t\t\tsegment_map,\n\t\t\tregion_map,\n\t\t};\n\n\t\tself.point_domain.map_ids(&id_map);\n\t\tself.segment_domain.map_ids(&id_map);\n\t\tself.region_domain.map_ids(&id_map);\n\t}\n\n\tpub fn is_branching(&self) -> bool {\n\t\t(0..self.point_domain.len()).any(|point_index| self.segment_domain.connected_count(point_index) > 2)\n\t}\n\n\tpub fn has_regions(&self) -> bool {\n\t\t!self.region_domain.id.is_empty()\n\t}\n\n\t/// Determines if face-by-face fill rendering should be used.\n\t/// Branching vectors without regions (e.g. mesh grids) need face-by-face fill rendering.\n\t/// Branching vectors with regions (e.g. boolean operation results) use even-odd fill\n\t/// on the main stroke path instead, since face decomposition can't determine which\n\t/// bounded faces should vs. shouldn't be filled in boolean results.\n\tpub fn use_face_fill(&self) -> bool {\n\t\tself.is_branching() && !self.has_regions()\n\t}\n\n\tpub fn construct_faces(&self) -> FaceIterator<'_, Upstream> {\n\t\tlet mut adjacency: Vec<Vec<FaceSide>> = vec![Vec::new(); self.point_domain.len()];\n\t\tfor (segment_index, (&start, &end)) in self.segment_domain.start_point.iter().zip(&self.segment_domain.end_point).enumerate() {\n\t\t\tadjacency[start].push(FaceSide { segment_index, reversed: false });\n\t\t\tadjacency[end].push(FaceSide { segment_index, reversed: true });\n\t\t}\n\n\t\tfor neighbors in &mut adjacency {\n\t\t\tneighbors.sort_by(|a, b| {\n\t\t\t\tlet angle = [a, b].map(|side| {\n\t\t\t\t\tlet curve = self.path_segment_from_index(\n\t\t\t\t\t\tself.segment_domain.start_point[side.segment_index],\n\t\t\t\t\t\tself.segment_domain.end_point[side.segment_index],\n\t\t\t\t\t\tself.segment_domain.handles[side.segment_index],\n\t\t\t\t\t);\n\t\t\t\t\tlet curve = if side.reversed { curve.reverse() } else { curve };\n\t\t\t\t\tlet tangent = curve.tangent_at_start();\n\t\t\t\t\ttangent.y.atan2(tangent.x)\n\t\t\t\t});\n\t\t\t\tangle[0].partial_cmp(&angle[1]).unwrap_or(std::cmp::Ordering::Equal)\n\t\t\t})\n\t\t}\n\n\t\tlet mut faces: Faces = Faces::new();\n\t\tlet mut seen = FaceSideSet::new(self.segment_domain.id.len());\n\n\t\tfor segment_index in 0..self.segment_domain.id.len() {\n\t\t\tfor &reversed in &[false, true] {\n\t\t\t\tlet side = FaceSide { segment_index, reversed };\n\t\t\t\tif seen.contains(side) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif self.construct_face(&adjacency, side, &mut faces, &mut seen).is_none() {\n\t\t\t\t\t// Undo `seen` markings for sides added during this failed face construction,\n\t\t\t\t\t// so they remain available for future face constructions starting from different sides.\n\t\t\t\t\tif let Some(&last_start) = faces.face_start.last() {\n\t\t\t\t\t\tfor &failed_side in &faces.sides[last_start..] {\n\t\t\t\t\t\t\tseen.remove(failed_side);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tfaces.backtrack();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tFaceIterator::new(faces, self)\n\t}\n\n\tfn construct_face(&self, adjacency: &[Vec<FaceSide>], first: FaceSide, faces: &mut Faces, seen: &mut FaceSideSet) -> Option<()> {\n\t\tfaces.start_new_face();\n\t\tlet max_iterations = self.segment_domain.id.len() * 2;\n\t\tlet mut side = first;\n\t\tfor _iteration in 1..max_iterations {\n\t\t\tif seen.contains(side) {\n\t\t\t\treturn None;\n\t\t\t}\n\t\t\tseen.insert(side);\n\t\t\tfaces.add_side(side);\n\t\t\tlet next_vertex = if side.reversed {\n\t\t\t\tself.segment_domain.start_point[side.segment_index]\n\t\t\t} else {\n\t\t\t\tself.segment_domain.end_point[side.segment_index]\n\t\t\t};\n\t\t\tlet neighbors = &adjacency[next_vertex];\n\t\t\tlet side_index = neighbors.iter().position(|s| {\n\t\t\t\tFaceSide {\n\t\t\t\t\tsegment_index: s.segment_index,\n\t\t\t\t\treversed: !s.reversed,\n\t\t\t\t} == side\n\t\t\t})?;\n\t\t\tside = neighbors[(side_index + 1) % neighbors.len()];\n\t\t\tif side == first {\n\t\t\t\treturn Some(());\n\t\t\t}\n\t\t}\n\t\tNone\n\t}\n}\n\n#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]\nstruct StrokePathIterPointSegmentMetadata {\n\tsegment_index: usize,\n\tstart_from_end: bool,\n}\n\nimpl StrokePathIterPointSegmentMetadata {\n\t#[must_use]\n\tconst fn new(segment_index: usize, start_from_end: bool) -> Self {\n\t\tSelf { segment_index, start_from_end }\n\t}\n\t#[must_use]\n\tconst fn flipped(&self) -> Self {\n\t\tSelf {\n\t\t\tsegment_index: self.segment_index,\n\t\t\tstart_from_end: !self.start_from_end,\n\t\t}\n\t}\n}\n\n#[derive(Clone, Default)]\nstruct StrokePathIterPointMetadata(tinyvec::TinyVec<[StrokePathIterPointSegmentMetadata; 2]>);\n\nimpl StrokePathIterPointMetadata {\n\tfn set(&mut self, value: StrokePathIterPointSegmentMetadata) {\n\t\tself.0.insert(0, value);\n\t}\n\t#[must_use]\n\tfn connected(&self) -> usize {\n\t\tself.0.len()\n\t}\n\t#[must_use]\n\tfn take_first(&mut self) -> Option<StrokePathIterPointSegmentMetadata> {\n\t\tself.0.pop()\n\t}\n\tfn take_eq(&mut self, target: StrokePathIterPointSegmentMetadata) -> bool {\n\t\tlet has_taken = self.0.contains(&target);\n\t\tself.0.retain(|value| *value != target);\n\t\thas_taken\n\t}\n}\n\n#[derive(Clone)]\npub struct StrokePathIter<'a, Upstream> {\n\tvector: &'a Vector<Upstream>,\n\tpoints: Vec<StrokePathIterPointMetadata>,\n\tskip: usize,\n\tdone_one: bool,\n}\n\nimpl<Upstream> Iterator for StrokePathIter<'_, Upstream> {\n\ttype Item = (Vec<ManipulatorGroup<PointId>>, bool);\n\n\tfn next(&mut self) -> Option<Self::Item> {\n\t\tlet mut current_start = None;\n\t\t// First iterate over the single connected points\n\t\tif !self.done_one {\n\t\t\tcurrent_start = self.points.iter().enumerate().skip(self.skip).find(|(_, val)| val.connected() == 1);\n\t\t\tself.done_one = current_start.is_none();\n\t\t\tself.skip = current_start.map_or(0, |(index, _)| index + 1);\n\t\t}\n\n\t\t// If we've already done the single connected, then go through looking at multi connected\n\t\tif current_start.is_none() {\n\t\t\tcurrent_start = self.points.iter().enumerate().skip(self.skip).find(|(_, val)| val.connected() > 0);\n\t\t\tself.skip = current_start.map_or(self.points.len(), |(index, _)| index);\n\t\t}\n\n\t\t// If there is no starting point, exit\n\t\tlet current_start = current_start?.0;\n\n\t\t// There will always be at least one segment connected to this one\n\t\tlet mut point_index = current_start;\n\t\tlet mut manipulators_list = Vec::new();\n\t\tlet mut in_handle = None;\n\t\tlet mut closed = false;\n\t\tloop {\n\t\t\tlet Some(val) = self.points[point_index].take_first() else {\n\t\t\t\t// Dead end\n\t\t\t\tmanipulators_list.push(ManipulatorGroup {\n\t\t\t\t\tanchor: self.vector.point_domain.positions()[point_index],\n\t\t\t\t\tin_handle,\n\t\t\t\t\tout_handle: None,\n\t\t\t\t\tid: self.vector.point_domain.ids()[point_index],\n\t\t\t\t});\n\n\t\t\t\tbreak;\n\t\t\t};\n\n\t\t\tlet mut handles = self.vector.segment_domain.handles()[val.segment_index];\n\t\t\tif val.start_from_end {\n\t\t\t\thandles = handles.reversed();\n\t\t\t}\n\t\t\tlet next_point_index = if val.start_from_end {\n\t\t\t\tself.vector.segment_domain.start_point()[val.segment_index]\n\t\t\t} else {\n\t\t\t\tself.vector.segment_domain.end_point()[val.segment_index]\n\t\t\t};\n\t\t\tmanipulators_list.push(ManipulatorGroup {\n\t\t\t\tanchor: self.vector.point_domain.positions()[point_index],\n\t\t\t\tin_handle,\n\t\t\t\tout_handle: handles.start(),\n\t\t\t\tid: self.vector.point_domain.ids()[point_index],\n\t\t\t});\n\n\t\t\tin_handle = handles.end();\n\n\t\t\tpoint_index = next_point_index;\n\t\t\tself.points[next_point_index].take_eq(val.flipped());\n\t\t\tif next_point_index == current_start {\n\t\t\t\tclosed = true;\n\t\t\t\tmanipulators_list[0].in_handle = in_handle;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tSome((manipulators_list, closed))\n\t}\n}\n\nimpl Identifier for PointId {\n\tfn new() -> Self {\n\t\tSelf::generate()\n\t}\n}\n\n/// Represents the conversion of IDs used when concatenating vector paths with conflicting IDs.\npub struct IdMap {\n\tpub point_offset: usize,\n\tpub point_map: HashMap<PointId, PointId>,\n\tpub segment_map: HashMap<SegmentId, SegmentId>,\n\tpub region_map: HashMap<RegionId, RegionId>,\n}\n"
  },
  {
    "path": "node-graph/libraries/vector-types/src/vector/vector_modification.rs",
    "content": "use super::*;\nuse crate::subpath::BezierHandles;\nuse crate::vector::misc::{HandleId, HandleType, point_to_dvec2};\nuse core_types::uuid::generate_uuid;\nuse dyn_any::DynAny;\nuse glam::DVec2;\nuse kurbo::{BezPath, PathEl, Point};\nuse std::collections::{HashMap, HashSet};\nuse std::hash::BuildHasher;\n\n/// Represents a procedural change to the [`PointDomain`] in [`Vector`].\n#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct PointModification {\n\tadd: Vec<PointId>,\n\tremove: HashSet<PointId>,\n\t#[serde(serialize_with = \"serialize_hashmap\", deserialize_with = \"deserialize_hashmap\")]\n\tdelta: HashMap<PointId, DVec2>,\n}\n\nimpl Hash for PointModification {\n\tfn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n\t\tgenerate_uuid().hash(state)\n\t}\n}\n\nimpl PointModification {\n\t/// Apply this modification to the specified [`PointDomain`].\n\tpub fn apply(&self, point_domain: &mut PointDomain, segment_domain: &mut SegmentDomain) {\n\t\tpoint_domain.retain(segment_domain, |id| !self.remove.contains(id));\n\n\t\tfor (index, (id, position)) in point_domain.positions_mut().enumerate() {\n\t\t\tlet Some(&delta) = self.delta.get(&id) else { continue };\n\t\t\tif !delta.is_finite() {\n\t\t\t\twarn!(\"Invalid delta when applying a point modification\");\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t*position += delta;\n\n\t\t\tfor (_, handles, start, end) in segment_domain.handles_mut() {\n\t\t\t\tif start == index {\n\t\t\t\t\thandles.move_start(delta);\n\t\t\t\t}\n\t\t\t\tif end == index {\n\t\t\t\t\thandles.move_end(delta);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor &add_id in &self.add {\n\t\t\tlet Some(&position) = self.delta.get(&add_id) else { continue };\n\t\t\tif !position.is_finite() {\n\t\t\t\twarn!(\"Invalid position when applying a point modification\");\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tpoint_domain.push(add_id, position);\n\t\t}\n\t}\n\n\t/// Create a new modification that will convert an empty [`Vector`] into the target [`Vector`].\n\tpub fn create_from_vector<Upstream>(vector: &Vector<Upstream>) -> Self {\n\t\tSelf {\n\t\t\tadd: vector.point_domain.ids().to_vec(),\n\t\t\tremove: HashSet::new(),\n\t\t\tdelta: vector.point_domain.ids().iter().copied().zip(vector.point_domain.positions().iter().cloned()).collect(),\n\t\t}\n\t}\n\n\tfn push(&mut self, id: PointId, position: DVec2) {\n\t\tself.add.push(id);\n\t\tself.delta.insert(id, position);\n\t}\n\n\tfn remove(&mut self, id: PointId) {\n\t\tself.remove.insert(id);\n\t\tself.add.retain(|&add| add != id);\n\t\tself.delta.remove(&id);\n\t}\n}\n\n/// Represents a procedural change to the [`SegmentDomain`] in [`Vector`].\n#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct SegmentModification {\n\tadd: Vec<SegmentId>,\n\tremove: HashSet<SegmentId>,\n\t#[serde(serialize_with = \"serialize_hashmap\", deserialize_with = \"deserialize_hashmap\")]\n\tstart_point: HashMap<SegmentId, PointId>,\n\t#[serde(serialize_with = \"serialize_hashmap\", deserialize_with = \"deserialize_hashmap\")]\n\tend_point: HashMap<SegmentId, PointId>,\n\t#[serde(serialize_with = \"serialize_hashmap\", deserialize_with = \"deserialize_hashmap\")]\n\thandle_primary: HashMap<SegmentId, Option<DVec2>>,\n\t#[serde(serialize_with = \"serialize_hashmap\", deserialize_with = \"deserialize_hashmap\")]\n\thandle_end: HashMap<SegmentId, Option<DVec2>>,\n\t#[serde(serialize_with = \"serialize_hashmap\", deserialize_with = \"deserialize_hashmap\")]\n\tstroke: HashMap<SegmentId, StrokeId>,\n}\n\nimpl SegmentModification {\n\t/// Apply this modification to the specified [`SegmentDomain`].\n\tpub fn apply(&self, segment_domain: &mut SegmentDomain, point_domain: &PointDomain) {\n\t\tsegment_domain.retain(|id| !self.remove.contains(id), point_domain.ids().len());\n\n\t\tfor (id, point) in segment_domain.start_point_mut() {\n\t\t\tlet Some(&new) = self.start_point.get(&id) else { continue };\n\t\t\tlet Some(index) = point_domain.resolve_id(new) else {\n\t\t\t\twarn!(\"Invalid start ID when applying a segment modification\");\n\t\t\t\tcontinue;\n\t\t\t};\n\n\t\t\t*point = index;\n\t\t}\n\n\t\tfor (id, point) in segment_domain.end_point_mut() {\n\t\t\tlet Some(&new) = self.end_point.get(&id) else { continue };\n\t\t\tlet Some(index) = point_domain.resolve_id(new) else {\n\t\t\t\twarn!(\"Invalid end ID when applying a segment modification\");\n\t\t\t\tcontinue;\n\t\t\t};\n\n\t\t\t*point = index;\n\t\t}\n\n\t\tfor (id, handles, start, end) in segment_domain.handles_mut() {\n\t\t\tlet Some(&start) = point_domain.positions().get(start) else { continue };\n\t\t\tlet Some(&end) = point_domain.positions().get(end) else { continue };\n\n\t\t\t// Compute the actual start and end position based on the offset from the anchor\n\t\t\tlet start = self.handle_primary.get(&id).copied().map(|handle| handle.map(|handle| handle + start));\n\t\t\tlet end = self.handle_end.get(&id).copied().map(|handle| handle.map(|handle| handle + end));\n\n\t\t\tif !start.unwrap_or_default().is_none_or(|start| start.is_finite()) || !end.unwrap_or_default().is_none_or(|end| end.is_finite()) {\n\t\t\t\twarn!(\"Invalid handles when applying a segment modification\");\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tmatch (start, end) {\n\t\t\t\t// The new handles are fully specified by the modification\n\t\t\t\t(Some(Some(handle_start)), Some(Some(handle_end))) => *handles = BezierHandles::Cubic { handle_start, handle_end },\n\t\t\t\t(Some(Some(handle)), Some(None)) | (Some(None), Some(Some(handle))) => *handles = BezierHandles::Quadratic { handle },\n\t\t\t\t(Some(None), Some(None)) => *handles = BezierHandles::Linear,\n\t\t\t\t// Remove the end handle\n\t\t\t\t(None, Some(None)) => {\n\t\t\t\t\tif let BezierHandles::Cubic { handle_start, .. } = *handles {\n\t\t\t\t\t\t*handles = BezierHandles::Quadratic { handle: handle_start }\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Change the end handle\n\t\t\t\t(None, Some(Some(handle_end))) => match *handles {\n\t\t\t\t\tBezierHandles::Linear => *handles = BezierHandles::Quadratic { handle: handle_end },\n\t\t\t\t\tBezierHandles::Quadratic { handle: handle_start } => *handles = BezierHandles::Cubic { handle_start, handle_end },\n\t\t\t\t\tBezierHandles::Cubic { handle_start, .. } => *handles = BezierHandles::Cubic { handle_start, handle_end },\n\t\t\t\t},\n\t\t\t\t// Remove the start handle\n\t\t\t\t(Some(None), None) => *handles = BezierHandles::Linear,\n\t\t\t\t// Change the start handle\n\t\t\t\t(Some(Some(handle_start)), None) => match *handles {\n\t\t\t\t\tBezierHandles::Linear => *handles = BezierHandles::Quadratic { handle: handle_start },\n\t\t\t\t\tBezierHandles::Quadratic { .. } => *handles = BezierHandles::Quadratic { handle: handle_start },\n\t\t\t\t\tBezierHandles::Cubic { handle_end, .. } => *handles = BezierHandles::Cubic { handle_start, handle_end },\n\t\t\t\t},\n\t\t\t\t// No change\n\t\t\t\t(None, None) => {}\n\t\t\t};\n\t\t}\n\n\t\tfor (id, stroke) in segment_domain.stroke_mut() {\n\t\t\tlet Some(&new) = self.stroke.get(&id) else { continue };\n\t\t\t*stroke = new;\n\t\t}\n\n\t\tfor &add_id in &self.add {\n\t\t\tlet Some(&start) = self.start_point.get(&add_id) else { continue };\n\t\t\tlet Some(&end) = self.end_point.get(&add_id) else { continue };\n\t\t\tlet Some(&handle_start) = self.handle_primary.get(&add_id) else { continue };\n\t\t\tlet Some(&handle_end) = self.handle_end.get(&add_id) else { continue };\n\t\t\tlet Some(&stroke) = self.stroke.get(&add_id) else { continue };\n\n\t\t\tlet Some(start_index) = point_domain.resolve_id(start) else {\n\t\t\t\twarn!(\"invalid start id: {start:#?}\");\n\t\t\t\tcontinue;\n\t\t\t};\n\t\t\tlet Some(end_index) = point_domain.resolve_id(end) else {\n\t\t\t\twarn!(\"invalid end id: {end:#?}\");\n\t\t\t\tcontinue;\n\t\t\t};\n\n\t\t\tlet start_position = point_domain.positions()[start_index];\n\t\t\tlet end_position = point_domain.positions()[end_index];\n\t\t\tlet handles = match (handle_start, handle_end) {\n\t\t\t\t(Some(handle_start), Some(handle_end)) => BezierHandles::Cubic {\n\t\t\t\t\thandle_start: handle_start + start_position,\n\t\t\t\t\thandle_end: handle_end + end_position,\n\t\t\t\t},\n\t\t\t\t(Some(handle), None) | (None, Some(handle)) => BezierHandles::Quadratic { handle: handle + start_position },\n\t\t\t\t(None, None) => BezierHandles::Linear,\n\t\t\t};\n\n\t\t\tif !handles.is_finite() {\n\t\t\t\twarn!(\"invalid handles\");\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tsegment_domain.push(add_id, start_index, end_index, handles, stroke);\n\t\t}\n\n\t\tassert!(\n\t\t\tsegment_domain.start_point().iter().all(|&index| index < point_domain.ids().len()),\n\t\t\t\"index should be in range {segment_domain:#?}\"\n\t\t);\n\t\tassert!(\n\t\t\tsegment_domain.end_point().iter().all(|&index| index < point_domain.ids().len()),\n\t\t\t\"index should be in range {segment_domain:#?}\"\n\t\t);\n\t}\n\n\t/// Create a new modification that will convert an empty [`Vector`] into the target [`Vector`].\n\tpub fn create_from_vector<Upstream>(vector: &Vector<Upstream>) -> Self {\n\t\tlet point_id = |(&segment, &index)| (segment, vector.point_domain.ids()[index]);\n\t\tSelf {\n\t\t\tadd: vector.segment_domain.ids().to_vec(),\n\t\t\tremove: HashSet::new(),\n\t\t\tstart_point: vector.segment_domain.ids().iter().zip(vector.segment_domain.start_point()).map(point_id).collect(),\n\t\t\tend_point: vector.segment_domain.ids().iter().zip(vector.segment_domain.end_point()).map(point_id).collect(),\n\t\t\thandle_primary: vector.segment_bezier_iter().map(|(id, b, _, _)| (id, b.handle_start().map(|handle| handle - b.start))).collect(),\n\t\t\thandle_end: vector.segment_bezier_iter().map(|(id, b, _, _)| (id, b.handle_end().map(|handle| handle - b.end))).collect(),\n\t\t\tstroke: vector.segment_domain.ids().iter().copied().zip(vector.segment_domain.stroke().iter().cloned()).collect(),\n\t\t}\n\t}\n\n\tfn push(&mut self, id: SegmentId, points: [PointId; 2], handles: [Option<DVec2>; 2], stroke: StrokeId) {\n\t\tself.remove.remove(&id);\n\t\tself.add.push(id);\n\t\tself.start_point.insert(id, points[0]);\n\t\tself.end_point.insert(id, points[1]);\n\t\tself.handle_primary.insert(id, handles[0]);\n\t\tself.handle_end.insert(id, handles[1]);\n\t\tself.stroke.insert(id, stroke);\n\t}\n\n\tfn remove(&mut self, id: SegmentId) {\n\t\tself.remove.insert(id);\n\t\tself.add.retain(|&add| add != id);\n\t\tself.start_point.remove(&id);\n\t\tself.end_point.remove(&id);\n\t\tself.handle_primary.remove(&id);\n\t\tself.handle_end.remove(&id);\n\t\tself.stroke.remove(&id);\n\t}\n}\n\n/// Represents a procedural change to the [`RegionDomain`] in [`Vector`].\n#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct RegionModification {\n\tadd: Vec<RegionId>,\n\tremove: HashSet<RegionId>,\n\t#[serde(serialize_with = \"serialize_hashmap\", deserialize_with = \"deserialize_hashmap\")]\n\tsegment_range: HashMap<RegionId, std::ops::RangeInclusive<SegmentId>>,\n\t#[serde(serialize_with = \"serialize_hashmap\", deserialize_with = \"deserialize_hashmap\")]\n\tfill: HashMap<RegionId, FillId>,\n}\n\nimpl RegionModification {\n\t/// Apply this modification to the specified [`RegionDomain`].\n\tpub fn apply(&self, region_domain: &mut RegionDomain) {\n\t\tregion_domain.retain(|id| !self.remove.contains(id));\n\n\t\tfor (id, segment_range) in region_domain.segment_range_mut() {\n\t\t\tlet Some(new) = self.segment_range.get(&id) else { continue };\n\t\t\t*segment_range = new.clone(); // Range inclusive is not copy\n\t\t}\n\n\t\tfor (id, fill) in region_domain.fill_mut() {\n\t\t\tlet Some(&new) = self.fill.get(&id) else { continue };\n\t\t\t*fill = new;\n\t\t}\n\n\t\tfor &add_id in &self.add {\n\t\t\tlet Some(segment_range) = self.segment_range.get(&add_id) else { continue };\n\t\t\tlet Some(&fill) = self.fill.get(&add_id) else { continue };\n\t\t\tregion_domain.push(add_id, segment_range.clone(), fill);\n\t\t}\n\t}\n\n\t/// Create a new modification that will convert an empty [`Vector`] into the target [`Vector`].\n\tpub fn create_from_vector<Upstream>(vector: &Vector<Upstream>) -> Self {\n\t\tSelf {\n\t\t\tadd: vector.region_domain.ids().to_vec(),\n\t\t\tremove: HashSet::new(),\n\t\t\tsegment_range: vector.region_domain.ids().iter().copied().zip(vector.region_domain.segment_range().iter().cloned()).collect(),\n\t\t\tfill: vector.region_domain.ids().iter().copied().zip(vector.region_domain.fill().iter().cloned()).collect(),\n\t\t}\n\t}\n}\n\n/// Represents a procedural change to the [`Vector`].\n#[derive(Clone, Debug, Default, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]\npub struct VectorModification {\n\tpoints: PointModification,\n\tsegments: SegmentModification,\n\tregions: RegionModification,\n\tadd_g1_continuous: HashSet<[HandleId; 2]>,\n\tremove_g1_continuous: HashSet<[HandleId; 2]>,\n}\n\n/// A modification type that can be added to a [`VectorModification`].\n#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]\npub enum VectorModificationType {\n\tInsertSegment { id: SegmentId, points: [PointId; 2], handles: [Option<DVec2>; 2] },\n\tInsertPoint { id: PointId, position: DVec2 },\n\n\tRemoveSegment { id: SegmentId },\n\tRemovePoint { id: PointId },\n\n\tSetG1Continuous { handles: [HandleId; 2], enabled: bool },\n\tSetHandles { segment: SegmentId, handles: [Option<DVec2>; 2] },\n\tSetPrimaryHandle { segment: SegmentId, relative_position: DVec2 },\n\tSetEndHandle { segment: SegmentId, relative_position: DVec2 },\n\tSetStartPoint { segment: SegmentId, id: PointId },\n\tSetEndPoint { segment: SegmentId, id: PointId },\n\n\tApplyPointDelta { point: PointId, delta: DVec2 },\n\tApplyPrimaryDelta { segment: SegmentId, delta: DVec2 },\n\tApplyEndDelta { segment: SegmentId, delta: DVec2 },\n}\n\nimpl VectorModification {\n\t/// Apply this modification to the specified [`Vector`].\n\tpub fn apply<Upstream>(&self, vector: &mut Vector<Upstream>) {\n\t\tself.points.apply(&mut vector.point_domain, &mut vector.segment_domain);\n\t\tself.segments.apply(&mut vector.segment_domain, &vector.point_domain);\n\t\tself.regions.apply(&mut vector.region_domain);\n\n\t\tlet valid = |val: &[HandleId; 2]| vector.segment_domain.ids().contains(&val[0].segment) && vector.segment_domain.ids().contains(&val[1].segment);\n\t\tvector\n\t\t\t.colinear_manipulators\n\t\t\t.retain(|val| !self.remove_g1_continuous.contains(val) && !self.remove_g1_continuous.contains(&[val[1], val[0]]) && valid(val));\n\n\t\tfor handles in &self.add_g1_continuous {\n\t\t\tif !vector.colinear_manipulators.iter().any(|test| test == handles || test == &[handles[1], handles[0]]) && valid(handles) {\n\t\t\t\tvector.colinear_manipulators.push(*handles);\n\t\t\t}\n\t\t}\n\t}\n\n\t/// Add a [`VectorModificationType`] to this modification.\n\tpub fn modify(&mut self, vector_modification: &VectorModificationType) {\n\t\tmatch vector_modification {\n\t\t\tVectorModificationType::InsertSegment { id, points, handles } => self.segments.push(*id, *points, *handles, StrokeId::ZERO),\n\t\t\tVectorModificationType::InsertPoint { id, position } => self.points.push(*id, *position),\n\n\t\t\tVectorModificationType::RemoveSegment { id } => self.segments.remove(*id),\n\t\t\tVectorModificationType::RemovePoint { id } => self.points.remove(*id),\n\n\t\t\tVectorModificationType::SetG1Continuous { handles, enabled } => {\n\t\t\t\tif *enabled {\n\t\t\t\t\tif !self.add_g1_continuous.contains(&[handles[1], handles[0]]) {\n\t\t\t\t\t\tself.add_g1_continuous.insert(*handles);\n\t\t\t\t\t}\n\t\t\t\t\tself.remove_g1_continuous.remove(handles);\n\t\t\t\t\tself.remove_g1_continuous.remove(&[handles[1], handles[0]]);\n\t\t\t\t} else {\n\t\t\t\t\tif !self.remove_g1_continuous.contains(&[handles[1], handles[0]]) {\n\t\t\t\t\t\tself.remove_g1_continuous.insert(*handles);\n\t\t\t\t\t}\n\t\t\t\t\tself.add_g1_continuous.remove(handles);\n\t\t\t\t\tself.add_g1_continuous.remove(&[handles[1], handles[0]]);\n\t\t\t\t}\n\t\t\t}\n\t\t\tVectorModificationType::SetHandles { segment, handles } => {\n\t\t\t\tself.segments.handle_primary.insert(*segment, handles[0]);\n\t\t\t\tself.segments.handle_end.insert(*segment, handles[1]);\n\t\t\t}\n\t\t\tVectorModificationType::SetPrimaryHandle { segment, relative_position } => {\n\t\t\t\tself.segments.handle_primary.insert(*segment, Some(*relative_position));\n\t\t\t}\n\t\t\tVectorModificationType::SetEndHandle { segment, relative_position } => {\n\t\t\t\tself.segments.handle_end.insert(*segment, Some(*relative_position));\n\t\t\t}\n\t\t\tVectorModificationType::SetStartPoint { segment, id } => {\n\t\t\t\tself.segments.start_point.insert(*segment, *id);\n\t\t\t}\n\t\t\tVectorModificationType::SetEndPoint { segment, id } => {\n\t\t\t\tself.segments.end_point.insert(*segment, *id);\n\t\t\t}\n\n\t\t\tVectorModificationType::ApplyPointDelta { point, delta } => {\n\t\t\t\t*self.points.delta.entry(*point).or_default() += *delta;\n\t\t\t}\n\t\t\tVectorModificationType::ApplyPrimaryDelta { segment, delta } => {\n\t\t\t\tlet position = self.segments.handle_primary.entry(*segment).or_default();\n\t\t\t\t*position = Some(position.unwrap_or_default() + *delta);\n\t\t\t}\n\t\t\tVectorModificationType::ApplyEndDelta { segment, delta } => {\n\t\t\t\tlet position = self.segments.handle_end.entry(*segment).or_default();\n\t\t\t\t*position = Some(position.unwrap_or_default() + *delta);\n\t\t\t}\n\t\t}\n\t}\n\n\t/// Create a new modification that will convert an empty [`Vector`] into the target [`Vector`].\n\tpub fn create_from_vector<Upstream>(vector: &Vector<Upstream>) -> Self {\n\t\tSelf {\n\t\t\tpoints: PointModification::create_from_vector(vector),\n\t\t\tsegments: SegmentModification::create_from_vector(vector),\n\t\t\tregions: RegionModification::create_from_vector(vector),\n\t\t\tadd_g1_continuous: vector.colinear_manipulators.iter().copied().collect(),\n\t\t\tremove_g1_continuous: HashSet::new(),\n\t\t}\n\t}\n}\n\nimpl Hash for VectorModification {\n\tfn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n\t\tgenerate_uuid().hash(state)\n\t}\n}\n\n// Do we want to enforce that all serialized/deserialized hashmaps are a vec of tuples?\n// TODO: Eventually remove this document upgrade code\nuse serde::de::{SeqAccess, Visitor};\nuse serde::ser::SerializeSeq;\nuse serde::{Deserialize, Deserializer, Serialize, Serializer};\nuse std::fmt;\nuse std::hash::Hash;\npub fn serialize_hashmap<K, V, S, H>(hashmap: &HashMap<K, V, H>, serializer: S) -> Result<S::Ok, S::Error>\nwhere\n\tK: Serialize + Eq + Hash,\n\tV: Serialize,\n\tS: Serializer,\n\tH: BuildHasher,\n{\n\tlet mut seq = serializer.serialize_seq(Some(hashmap.len()))?;\n\tfor (key, value) in hashmap {\n\t\tseq.serialize_element(&(key, value))?;\n\t}\n\tseq.end()\n}\n\npub fn deserialize_hashmap<'de, K, V, D, H>(deserializer: D) -> Result<HashMap<K, V, H>, D::Error>\nwhere\n\tK: Deserialize<'de> + Eq + Hash,\n\tV: Deserialize<'de>,\n\tD: Deserializer<'de>,\n\tH: BuildHasher + Default,\n{\n\tstruct HashMapVisitor<K, V, H> {\n\t\t#[allow(clippy::type_complexity)]\n\t\tmarker: std::marker::PhantomData<fn() -> HashMap<K, V, H>>,\n\t}\n\n\timpl<'de, K, V, H> Visitor<'de> for HashMapVisitor<K, V, H>\n\twhere\n\t\tK: Deserialize<'de> + Eq + Hash,\n\t\tV: Deserialize<'de>,\n\t\tH: BuildHasher + Default,\n\t{\n\t\ttype Value = HashMap<K, V, H>;\n\n\t\tfn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {\n\t\t\tformatter.write_str(\"a sequence of tuples\")\n\t\t}\n\n\t\tfn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>\n\t\twhere\n\t\t\tA: SeqAccess<'de>,\n\t\t{\n\t\t\tlet mut hashmap = HashMap::default();\n\t\t\twhile let Some((key, value)) = seq.next_element()? {\n\t\t\t\thashmap.insert(key, value);\n\t\t\t}\n\t\t\tOk(hashmap)\n\t\t}\n\t}\n\n\tlet visitor = HashMapVisitor { marker: std::marker::PhantomData };\n\tdeserializer.deserialize_seq(visitor)\n}\n\npub struct AppendBezpath<'a, Upstream: 'static> {\n\tfirst_point: Option<Point>,\n\tlast_point: Option<Point>,\n\tfirst_point_index: Option<usize>,\n\tlast_point_index: Option<usize>,\n\tfirst_segment_id: Option<SegmentId>,\n\tlast_segment_id: Option<SegmentId>,\n\tpoint_id: PointId,\n\tsegment_id: SegmentId,\n\tvector: &'a mut Vector<Upstream>,\n}\n\nimpl<'a, Upstream> AppendBezpath<'a, Upstream> {\n\tfn new(vector: &'a mut Vector<Upstream>) -> Self {\n\t\tSelf {\n\t\t\tfirst_point: None,\n\t\t\tlast_point: None,\n\t\t\tfirst_point_index: None,\n\t\t\tlast_point_index: None,\n\t\t\tfirst_segment_id: None,\n\t\t\tlast_segment_id: None,\n\t\t\tpoint_id: vector.point_domain.next_id(),\n\t\t\tsegment_id: vector.segment_domain.next_id(),\n\t\t\tvector,\n\t\t}\n\t}\n\n\tfn append_segment_and_close_path(&mut self, point: Point, handle: BezierHandles) {\n\t\tlet handle = if self.first_point.unwrap() != point {\n\t\t\t// If the first point is not the same as the last point of the path then we append the segment\n\t\t\t// with given handle and point and then close the path with linear handle.\n\t\t\tself.append_segment(point, handle);\n\t\t\tBezierHandles::Linear\n\t\t} else {\n\t\t\t// if the endpoints are the same then we close the path with given handle.\n\t\t\thandle\n\t\t};\n\n\t\t// Create a new segment.\n\t\tlet next_segment_id = self.segment_id.next_id();\n\t\tself.vector\n\t\t\t.segment_domain\n\t\t\t.push(next_segment_id, self.last_point_index.unwrap(), self.first_point_index.unwrap(), handle, StrokeId::ZERO);\n\n\t\t// Create a new region.\n\t\tlet next_region_id = self.vector.region_domain.next_id();\n\t\tlet first_segment_id = self.first_segment_id.unwrap_or(next_segment_id);\n\t\tlet last_segment_id = next_segment_id;\n\n\t\tself.vector.region_domain.push(next_region_id, first_segment_id..=last_segment_id, FillId::ZERO);\n\t}\n\n\tfn append_segment(&mut self, end_point: Point, handle: BezierHandles) {\n\t\t// Append the point.\n\t\tlet next_point_index = self.vector.point_domain.ids().len();\n\t\tlet next_point_id = self.point_id.next_id();\n\n\t\tself.vector.point_domain.push(next_point_id, point_to_dvec2(end_point));\n\n\t\t// Append the segment.\n\t\tlet next_segment_id = self.segment_id.next_id();\n\t\tself.vector\n\t\t\t.segment_domain\n\t\t\t.push(next_segment_id, self.last_point_index.unwrap(), next_point_index, handle, StrokeId::ZERO);\n\n\t\t// Update the states.\n\t\tself.last_point = Some(end_point);\n\t\tself.last_point_index = Some(next_point_index);\n\n\t\tself.first_segment_id = Some(self.first_segment_id.unwrap_or(next_segment_id));\n\t\tself.last_segment_id = Some(next_segment_id);\n\t}\n\n\tfn append_first_point(&mut self, point: Point) {\n\t\tself.first_point = Some(point);\n\t\tself.last_point = Some(point);\n\n\t\t// Append the first point.\n\t\tlet next_point_index = self.vector.point_domain.ids().len();\n\t\tself.vector.point_domain.push(self.point_id.next_id(), point_to_dvec2(point));\n\n\t\t// Update the state.\n\t\tself.first_point_index = Some(next_point_index);\n\t\tself.last_point_index = Some(next_point_index);\n\t}\n\n\tfn reset(&mut self) {\n\t\tself.first_point = None;\n\t\tself.last_point = None;\n\t\tself.first_point_index = None;\n\t\tself.last_point_index = None;\n\t\tself.first_segment_id = None;\n\t\tself.last_segment_id = None;\n\t}\n\n\tpub fn append_bezpath(vector: &'a mut Vector<Upstream>, bezpath: BezPath) {\n\t\tlet mut this = Self::new(vector);\n\t\tlet mut elements = bezpath.elements().iter().peekable();\n\n\t\twhile let Some(element) = elements.next() {\n\t\t\tlet close_path = elements.peek().is_some_and(|elm| **elm == PathEl::ClosePath);\n\n\t\t\tmatch *element {\n\t\t\t\tPathEl::MoveTo(point) => this.append_first_point(point),\n\t\t\t\tPathEl::LineTo(point) => {\n\t\t\t\t\tlet handle = BezierHandles::Linear;\n\t\t\t\t\tif close_path {\n\t\t\t\t\t\tthis.append_segment_and_close_path(point, handle);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.append_segment(point, handle);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tPathEl::QuadTo(point, point1) => {\n\t\t\t\t\tlet handle = BezierHandles::Quadratic { handle: point_to_dvec2(point) };\n\t\t\t\t\tif close_path {\n\t\t\t\t\t\tthis.append_segment_and_close_path(point1, handle);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.append_segment(point1, handle);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tPathEl::CurveTo(point, point1, point2) => {\n\t\t\t\t\tlet handle = BezierHandles::Cubic {\n\t\t\t\t\t\thandle_start: point_to_dvec2(point),\n\t\t\t\t\t\thandle_end: point_to_dvec2(point1),\n\t\t\t\t\t};\n\n\t\t\t\t\tif close_path {\n\t\t\t\t\t\tthis.append_segment_and_close_path(point2, handle);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.append_segment(point2, handle);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tPathEl::ClosePath => {\n\t\t\t\t\t// Already handled using `append_segment_and_close_path()` hence we reset state and continue.\n\t\t\t\t\tthis.reset();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\npub trait VectorExt {\n\tfn append_bezpath(&mut self, bezpath: BezPath);\n}\n\nimpl<Upstream: 'static> VectorExt for Vector<Upstream> {\n\tfn append_bezpath(&mut self, bezpath: BezPath) {\n\t\tAppendBezpath::append_bezpath(self, bezpath);\n\t}\n}\n\npub trait HandleExt {\n\t/// Set the handle's position relative to the anchor which is the start anchor for the primary handle and end anchor for the end handle.\n\t#[must_use]\n\tfn set_relative_position(self, relative_position: DVec2) -> VectorModificationType;\n}\n\nimpl HandleExt for HandleId {\n\tfn set_relative_position(self, relative_position: DVec2) -> VectorModificationType {\n\t\tlet Self { ty, segment } = self;\n\t\tmatch ty {\n\t\t\tHandleType::Primary => VectorModificationType::SetPrimaryHandle { segment, relative_position },\n\t\t\tHandleType::End => VectorModificationType::SetEndHandle { segment, relative_position },\n\t\t}\n\t}\n}\n\n#[cfg(test)]\nmod tests {\n\tuse kurbo::{PathSeg, QuadBez};\n\n\tuse super::*;\n\n\tuse crate::subpath::{Bezier, Subpath};\n\n\t#[test]\n\tfn modify_new() {\n\t\tlet vector: Vector<()> = Vector::from_subpaths([Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE), Subpath::new_rectangle(DVec2::NEG_ONE, DVec2::ZERO)], false);\n\n\t\tlet modify = VectorModification::create_from_vector(&vector);\n\n\t\tlet mut new = Vector::default();\n\t\tmodify.apply(&mut new);\n\t\tassert_eq!(vector, new);\n\t}\n\n\t#[test]\n\tfn modify_existing() {\n\t\tlet subpaths = [\n\t\t\tSubpath::new_ellipse(DVec2::ZERO, DVec2::ONE),\n\t\t\tSubpath::new_rectangle(DVec2::NEG_ONE, DVec2::ZERO),\n\t\t\tSubpath::from_beziers(\n\t\t\t\t&[\n\t\t\t\t\tPathSeg::Quad(QuadBez::new(Point::new(0., 0.), Point::new(5., 10.), Point::new(10., 0.))),\n\t\t\t\t\tPathSeg::Quad(QuadBez::new(Point::new(10., 0.), Point::new(15., 10.), Point::new(20., 0.))),\n\t\t\t\t],\n\t\t\t\tfalse,\n\t\t\t),\n\t\t];\n\t\tlet mut vector: Vector<()> = Vector::from_subpaths(subpaths, false);\n\n\t\tlet mut modify_new = VectorModification::create_from_vector(&vector);\n\t\tlet mut modify_original = VectorModification::default();\n\n\t\tfor modification in [&mut modify_new, &mut modify_original] {\n\t\t\tlet point = vector.point_domain.ids()[0];\n\t\t\tmodification.modify(&VectorModificationType::ApplyPointDelta { point, delta: DVec2::X * 0.5 });\n\t\t\tlet point = vector.point_domain.ids()[9];\n\t\t\tmodification.modify(&VectorModificationType::ApplyPointDelta { point, delta: DVec2::X });\n\t\t}\n\n\t\tlet mut new = Vector::default();\n\t\tmodify_new.apply(&mut new);\n\n\t\tmodify_original.apply(&mut vector);\n\n\t\tassert_eq!(vector, new);\n\t\tassert_eq!(vector.point_domain.positions()[0], DVec2::X);\n\t\tassert_eq!(vector.point_domain.positions()[9], DVec2::new(11., 0.));\n\t\tassert_eq!(\n\t\t\tvector.segment_bezier_iter().nth(8).unwrap().1,\n\t\t\tBezier::from_quadratic_dvec2(DVec2::new(0., 0.), DVec2::new(5., 10.), DVec2::new(11., 0.))\n\t\t);\n\t\tassert_eq!(\n\t\t\tvector.segment_bezier_iter().nth(9).unwrap().1,\n\t\t\tBezier::from_quadratic_dvec2(DVec2::new(11., 0.), DVec2::new(16., 10.), DVec2::new(20., 0.))\n\t\t);\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/vector-types/src/vector/vector_types.rs",
    "content": "use super::misc::dvec2_to_point;\nuse super::style::{PathStyle, Stroke};\npub use super::vector_attributes::*;\nuse crate::subpath::{BezierHandles, ManipulatorGroup, Subpath};\nuse crate::vector::click_target::{ClickTargetType, FreePoint};\nuse crate::vector::misc::{HandleId, ManipulatorPointId};\nuse crate::vector::vector_modification::VectorExt;\nuse core::borrow::Borrow;\nuse core_types::Color;\nuse core_types::bounds::{BoundingBox, RenderBoundingBox};\nuse core_types::render_complexity::RenderComplexity;\nuse core_types::transform::Transform;\nuse dyn_any::StaticType;\nuse glam::{DAffine2, DVec2};\nuse kurbo::{Affine, BezPath, Rect, Shape};\nuse std::collections::HashMap;\n\n/// Represents vector graphics data, composed of Bézier curves in a path or mesh arrangement.\n///\n/// Generic over `Upstream` to avoid circular dependency with the Graphic type.\n/// - Use `Vector<()>` for basic vectors without upstream tracking\n/// - Use `Vector<Option<Table<Graphic>>>` in the graphic crate for vectors with upstream layers\n#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct Vector<Upstream> {\n\tpub style: PathStyle,\n\n\t/// A list of all manipulator groups (referenced in `subpaths`) that have colinear handles (where they're locked at 180° angles from one another).\n\t/// This gets read in `graph_operation_message_handler.rs` by calling `inputs.as_mut_slice()` (search for the string `\"Shape does not have both `subpath` and `colinear_manipulators` inputs\"` to find it).\n\tpub colinear_manipulators: Vec<[HandleId; 2]>,\n\n\tpub point_domain: PointDomain,\n\tpub segment_domain: SegmentDomain,\n\tpub region_domain: RegionDomain,\n\n\t/// Used to store the upstream group/folder of nested layers during destructive Boolean Operations (and other nodes with a similar effect) so that click targets can be preserved for the child layers.\n\t/// Without this, the tools would be working with a collapsed version of the data which has no reference to the original child layers that were booleaned together, resulting in the inner layers not being editable.\n\t#[serde(alias = \"upstream_group\")]\n\tpub upstream_data: Upstream,\n}\nunsafe impl<Upstream: 'static> StaticType for Vector<Upstream> {\n\ttype Static = Self;\n}\n\nimpl<Upstream: Default + 'static> Default for Vector<Upstream> {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tstyle: PathStyle::new(Some(Stroke::new(Some(Color::BLACK), 0.)), super::style::Fill::None),\n\t\t\tcolinear_manipulators: Vec::new(),\n\t\t\tpoint_domain: PointDomain::new(),\n\t\t\tsegment_domain: SegmentDomain::new(),\n\t\t\tregion_domain: RegionDomain::new(),\n\t\t\tupstream_data: Upstream::default(),\n\t\t}\n\t}\n}\n\nimpl<Upstream> std::hash::Hash for Vector<Upstream> {\n\tfn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n\t\tself.point_domain.hash(state);\n\t\tself.segment_domain.hash(state);\n\t\tself.region_domain.hash(state);\n\t\tself.style.hash(state);\n\t\tself.colinear_manipulators.hash(state);\n\t\t// We don't hash the upstream_data intentionally\n\t}\n}\n\nimpl<Upstream> Vector<Upstream> {\n\t/// Add a subpath to this vector path.\n\tpub fn append_subpath(&mut self, subpath: impl Borrow<Subpath<PointId>>, preserve_id: bool) {\n\t\tlet subpath: &Subpath<PointId> = subpath.borrow();\n\t\tlet stroke_id = StrokeId::ZERO;\n\t\tlet mut point_id = self.point_domain.next_id();\n\n\t\tlet handles = |a: &ManipulatorGroup<_>, b: &ManipulatorGroup<_>| match (a.out_handle, b.in_handle) {\n\t\t\t(None, None) => BezierHandles::Linear,\n\t\t\t(Some(handle), None) | (None, Some(handle)) => BezierHandles::Quadratic { handle },\n\t\t\t(Some(handle_start), Some(handle_end)) => BezierHandles::Cubic { handle_start, handle_end },\n\t\t};\n\t\tlet [mut first_seg, mut last_seg] = [None, None];\n\t\tlet mut segment_id = self.segment_domain.next_id();\n\t\tlet mut last_point = None;\n\t\tlet mut first_point = None;\n\n\t\t// Construct a bezier segment from the two manipulators on the subpath.\n\t\tfor pair in subpath.manipulator_groups().windows(2) {\n\t\t\tlet start = last_point.unwrap_or_else(|| {\n\t\t\t\tlet id = if preserve_id && !self.point_domain.ids().contains(&pair[0].id) {\n\t\t\t\t\tpair[0].id\n\t\t\t\t} else {\n\t\t\t\t\tpoint_id.next_id()\n\t\t\t\t};\n\t\t\t\tself.point_domain.push(id, pair[0].anchor);\n\t\t\t\tself.point_domain.ids().len() - 1\n\t\t\t});\n\t\t\tfirst_point = Some(first_point.unwrap_or(start));\n\t\t\tlet end = if preserve_id && !self.point_domain.ids().contains(&pair[1].id) {\n\t\t\t\tpair[1].id\n\t\t\t} else {\n\t\t\t\tpoint_id.next_id()\n\t\t\t};\n\t\t\tlet end_index = self.point_domain.ids().len();\n\t\t\tself.point_domain.push(end, pair[1].anchor);\n\n\t\t\tlet id = segment_id.next_id();\n\t\t\tfirst_seg = Some(first_seg.unwrap_or(id));\n\t\t\tlast_seg = Some(id);\n\t\t\tself.segment_domain.push(id, start, end_index, handles(&pair[0], &pair[1]), stroke_id);\n\n\t\t\tlast_point = Some(end_index);\n\t\t}\n\n\t\tlet fill_id = FillId::ZERO;\n\n\t\tif subpath.closed() {\n\t\t\tif let (Some(last), Some(first), Some(first_id), Some(last_id)) = (subpath.manipulator_groups().last(), subpath.manipulator_groups().first(), first_point, last_point) {\n\t\t\t\tlet id = segment_id.next_id();\n\t\t\t\tfirst_seg = Some(first_seg.unwrap_or(id));\n\t\t\t\tlast_seg = Some(id);\n\t\t\t\tself.segment_domain.push(id, last_id, first_id, handles(last, first), stroke_id);\n\t\t\t}\n\n\t\t\tif let [Some(first_seg), Some(last_seg)] = [first_seg, last_seg] {\n\t\t\t\tself.region_domain.push(self.region_domain.next_id(), first_seg..=last_seg, fill_id);\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn append_free_point(&mut self, point: &FreePoint, preserve_id: bool) {\n\t\tlet mut point_id = self.point_domain.next_id();\n\n\t\t// Use the current point ID if it's not already in the domain, otherwise generate a new one\n\t\tlet id = if preserve_id && !self.point_domain.ids().contains(&point.id) {\n\t\t\tpoint.id\n\t\t} else {\n\t\t\tpoint_id.next_id()\n\t\t};\n\t\tself.point_domain.push(id, point.position);\n\t}\n\n\t/// Construct some new vector path from a single subpath with an identity transform and black fill.\n\tpub fn from_subpath(subpath: impl Borrow<Subpath<PointId>>) -> Self\n\twhere\n\t\tUpstream: Default + 'static,\n\t{\n\t\tSelf::from_subpaths([subpath], false)\n\t}\n\n\t/// Construct some new vector path from a single [`BezPath`] with an identity transform and black fill.\n\tpub fn from_bezpath(bezpath: BezPath) -> Self\n\twhere\n\t\tUpstream: Default + 'static,\n\t{\n\t\tlet mut vector = Self::default();\n\t\tvector.append_bezpath(bezpath);\n\t\tvector\n\t}\n\n\t/// Construct some new vector path from subpaths with an identity transform and black fill.\n\tpub fn from_subpaths(subpaths: impl IntoIterator<Item = impl Borrow<Subpath<PointId>>>, preserve_id: bool) -> Self\n\twhere\n\t\tUpstream: Default + 'static,\n\t{\n\t\tlet mut vector = Self::default();\n\n\t\tfor subpath in subpaths.into_iter() {\n\t\t\tvector.append_subpath(subpath, preserve_id);\n\t\t}\n\n\t\tvector\n\t}\n\n\tpub fn from_target_types(target_types: impl IntoIterator<Item = impl Borrow<ClickTargetType>>, preserve_id: bool) -> Self\n\twhere\n\t\tUpstream: Default + 'static,\n\t{\n\t\tlet mut vector = Self::default();\n\n\t\tfor target_type in target_types.into_iter() {\n\t\t\tmatch target_type.borrow() {\n\t\t\t\tClickTargetType::Subpath(subpath) => vector.append_subpath(subpath, preserve_id),\n\t\t\t\tClickTargetType::FreePoint(point) => vector.append_free_point(point, preserve_id),\n\t\t\t}\n\t\t}\n\n\t\tvector\n\t}\n\n\t/// Compute the bounding boxes of the bezpaths without any transform\n\tpub fn bounding_box_rect(&self) -> Option<Rect> {\n\t\tself.bounding_box_with_transform_rect(DAffine2::IDENTITY)\n\t}\n\n\tpub fn close_subpaths(&mut self) {\n\t\tlet segments_to_add: Vec<_> = self\n\t\t\t.build_stroke_path_iter()\n\t\t\t.filter(|(_, closed)| !closed)\n\t\t\t.filter_map(|(manipulator_groups, _)| {\n\t\t\t\tlet (first, last) = manipulator_groups.first().zip(manipulator_groups.last())?;\n\t\t\t\tlet (start, end) = self.point_domain.resolve_id(first.id).zip(self.point_domain.resolve_id(last.id))?;\n\t\t\t\tSome((start, end))\n\t\t\t})\n\t\t\t.collect();\n\n\t\tfor (start, end) in segments_to_add {\n\t\t\tlet segment_id = self.segment_domain.next_id().next_id();\n\t\t\tself.segment_domain.push(segment_id, start, end, BezierHandles::Linear, StrokeId::ZERO);\n\t\t}\n\t}\n\n\t/// Compute the bounding boxes of the subpaths without any transform\n\tpub fn bounding_box(&self) -> Option<[DVec2; 2]> {\n\t\tself.bounding_box_with_transform_rect(DAffine2::IDENTITY)\n\t\t\t.map(|rect| [DVec2::new(rect.x0, rect.y0), DVec2::new(rect.x1, rect.y1)])\n\t}\n\n\t/// Compute the bounding boxes of the subpaths with the specified transform\n\tpub fn bounding_box_with_transform(&self, transform: DAffine2) -> Option<[DVec2; 2]> {\n\t\tself.bounding_box_with_transform_rect(transform)\n\t\t\t.map(|rect| [DVec2::new(rect.x0, rect.y0), DVec2::new(rect.x1, rect.y1)])\n\t}\n\n\t/// Compute the bounding boxes of the bezpaths with the specified transform\n\tpub fn bounding_box_with_transform_rect(&self, transform: DAffine2) -> Option<Rect> {\n\t\tlet combine = |r1: Rect, r2: Rect| r1.union(r2);\n\t\tself.stroke_bezpath_iter()\n\t\t\t.map(|mut bezpath| {\n\t\t\t\tbezpath.apply_affine(Affine::new(transform.to_cols_array()));\n\t\t\t\tbezpath.bounding_box()\n\t\t\t})\n\t\t\t.reduce(combine)\n\t}\n\n\t/// Calculate the corners of the bounding box but with a nonzero size.\n\t///\n\t/// If the layer bounds are `0` in either axis then they are changed to be `1`.\n\tpub fn nonzero_bounding_box(&self) -> [DVec2; 2] {\n\t\tlet [bounds_min, mut bounds_max] = self.bounding_box().unwrap_or_default();\n\n\t\tlet bounds_size = bounds_max - bounds_min;\n\t\tif bounds_size.x < 1e-10 {\n\t\t\tbounds_max.x = bounds_min.x + 1.;\n\t\t}\n\t\tif bounds_size.y < 1e-10 {\n\t\t\tbounds_max.y = bounds_min.y + 1.;\n\t\t}\n\n\t\t[bounds_min, bounds_max]\n\t}\n\n\t/// Compute the pivot of the layer in layerspace (the coordinates of the subpaths)\n\tpub fn layerspace_pivot(&self, normalized_pivot: DVec2) -> DVec2 {\n\t\tlet [bounds_min, bounds_max] = self.nonzero_bounding_box();\n\t\tlet bounds_size = bounds_max - bounds_min;\n\t\tbounds_min + bounds_size * normalized_pivot\n\t}\n\n\tpub fn start_point(&self) -> impl Iterator<Item = PointId> + '_ {\n\t\tself.segment_domain.start_point().iter().map(|&index| self.point_domain.ids()[index])\n\t}\n\n\tpub fn end_point(&self) -> impl Iterator<Item = PointId> + '_ {\n\t\tself.segment_domain.end_point().iter().map(|&index| self.point_domain.ids()[index])\n\t}\n\n\tpub fn push(&mut self, id: SegmentId, start: PointId, end: PointId, handles: (Option<DVec2>, Option<DVec2>), stroke: StrokeId) {\n\t\tlet [Some(start), Some(end)] = [start, end].map(|id| self.point_domain.resolve_id(id)) else {\n\t\t\treturn;\n\t\t};\n\t\tlet handles = match handles {\n\t\t\t(None, None) => BezierHandles::Linear,\n\t\t\t(None, Some(handle)) | (Some(handle), None) => BezierHandles::Quadratic { handle },\n\t\t\t(Some(handle_start), Some(handle_end)) => BezierHandles::Cubic { handle_start, handle_end },\n\t\t};\n\t\tself.segment_domain.push(id, start, end, handles, stroke)\n\t}\n\n\tpub fn handles_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut BezierHandles, PointId, PointId)> {\n\t\tself.segment_domain\n\t\t\t.handles_mut()\n\t\t\t.map(|(id, handles, start, end)| (id, handles, self.point_domain.ids()[start], self.point_domain.ids()[end]))\n\t}\n\n\tpub fn segment_start_from_id(&self, segment: SegmentId) -> Option<PointId> {\n\t\tself.segment_domain.segment_start_from_id(segment).map(|index| self.point_domain.ids()[index])\n\t}\n\n\tpub fn segment_end_from_id(&self, segment: SegmentId) -> Option<PointId> {\n\t\tself.segment_domain.segment_end_from_id(segment).map(|index| self.point_domain.ids()[index])\n\t}\n\n\t/// Returns an array for the start and end points of a segment.\n\tpub fn points_from_id(&self, segment: SegmentId) -> Option<[PointId; 2]> {\n\t\tself.segment_domain.points_from_id(segment).map(|val| val.map(|index| self.point_domain.ids()[index]))\n\t}\n\n\t/// Attempts to find another point in the segment that is not the one passed in.\n\tpub fn other_point(&self, segment: SegmentId, current: PointId) -> Option<PointId> {\n\t\tlet index = self.point_domain.resolve_id(current);\n\t\tindex.and_then(|index| self.segment_domain.other_point(segment, index)).map(|index| self.point_domain.ids()[index])\n\t}\n\n\t/// Gets all points connected to the current one but not including the current one.\n\tpub fn connected_points(&self, current: PointId) -> impl Iterator<Item = PointId> + '_ {\n\t\tlet index = [self.point_domain.resolve_id(current)].into_iter().flatten();\n\t\tindex.flat_map(|index| self.segment_domain.connected_points(index).map(|index| self.point_domain.ids()[index]))\n\t}\n\n\t/// Returns the number of linear segments connected to the given point.\n\tpub fn connected_linear_segments(&self, point_id: PointId) -> usize {\n\t\tself.segment_bezier_iter()\n\t\t\t.filter(|(_, bez, start, end)| (*start == point_id || *end == point_id) && matches!(bez.handles, BezierHandles::Linear))\n\t\t\t.count()\n\t}\n\n\t/// Get an array slice of all segment IDs.\n\tpub fn segment_ids(&self) -> &[SegmentId] {\n\t\tself.segment_domain.ids()\n\t}\n\n\t/// Enumerate all segments that start at the point.\n\tpub fn start_connected(&self, point: PointId) -> impl Iterator<Item = SegmentId> + '_ {\n\t\tlet index = [self.point_domain.resolve_id(point)].into_iter().flatten();\n\t\tindex.flat_map(|index| self.segment_domain.start_connected(index))\n\t}\n\n\t/// Enumerate all segments that end at the point.\n\tpub fn end_connected(&self, point: PointId) -> impl Iterator<Item = SegmentId> + '_ {\n\t\tlet index = [self.point_domain.resolve_id(point)].into_iter().flatten();\n\t\tindex.flat_map(|index| self.segment_domain.end_connected(index))\n\t}\n\n\t/// Enumerate all segments that start or end at a point, converting them to [`HandleId`s]. Note that the handles may not exist e.g. for a linear segment.\n\tpub fn all_connected(&self, point: PointId) -> impl Iterator<Item = HandleId> + '_ {\n\t\tlet index = [self.point_domain.resolve_id(point)].into_iter().flatten();\n\t\tindex.flat_map(|index| self.segment_domain.all_connected(index))\n\t}\n\n\t/// Enumerate the number of segments connected to a point. If a segment starts and ends at a point then it is counted twice.\n\tpub fn connected_count(&self, point: PointId) -> usize {\n\t\tself.point_domain.resolve_id(point).map_or(0, |point| self.segment_domain.connected_count(point))\n\t}\n\n\t/// Enumerate the number of segments connected to a point. If a segment starts and ends at a point then it is counted twice.\n\tpub fn any_connected(&self, point: PointId) -> bool {\n\t\tself.point_domain.resolve_id(point).is_some_and(|point| self.segment_domain.any_connected(point))\n\t}\n\n\tpub fn check_point_inside_shape(&self, transform: DAffine2, point: DVec2) -> bool {\n\t\tlet number = self\n\t\t\t.stroke_bezpath_iter()\n\t\t\t.map(|mut bezpath| {\n\t\t\t\t// TODO: apply transform to points instead of modifying the paths\n\t\t\t\tbezpath.apply_affine(Affine::new(transform.to_cols_array()));\n\t\t\t\tbezpath.close_path();\n\t\t\t\tlet bbox = bezpath.bounding_box();\n\t\t\t\t(bezpath, bbox)\n\t\t\t})\n\t\t\t.filter(|(_, bbox)| bbox.contains(dvec2_to_point(point)))\n\t\t\t.map(|(bezpath, _)| bezpath.winding(dvec2_to_point(point)))\n\t\t\t.sum::<i32>();\n\n\t\t// Non-zero fill rule\n\t\tnumber != 0\n\t}\n\n\t/// Iterator over all anchor points.\n\tpub fn anchor_points(&self) -> impl Iterator<Item = PointId> + '_ {\n\t\tself.point_domain.ids().iter().copied()\n\t}\n\n\t/// Anchor points at the ends of open subpaths. These are points with exactly one connection by a segment to another anchor.\n\tpub fn anchor_endpoints(&self) -> impl Iterator<Item = PointId> + '_ {\n\t\tself.anchor_points().enumerate().filter(|&(index, _)| self.segment_domain.connected_count(index) == 1).map(|(_, id)| id)\n\t}\n\n\t/// Computes if all the connected handles are colinear for an anchor, or if that handle is colinear for a handle.\n\tpub fn colinear(&self, point: ManipulatorPointId) -> bool {\n\t\tlet has_handle = |target| self.colinear_manipulators.iter().flatten().any(|&handle| handle == target);\n\t\tmatch point {\n\t\t\tManipulatorPointId::Anchor(id) => {\n\t\t\t\tself.start_connected(id).all(|segment| has_handle(HandleId::primary(segment))) && self.end_connected(id).all(|segment| has_handle(HandleId::end(segment)))\n\t\t\t}\n\t\t\tManipulatorPointId::PrimaryHandle(segment) => has_handle(HandleId::primary(segment)),\n\t\t\tManipulatorPointId::EndHandle(segment) => has_handle(HandleId::end(segment)),\n\t\t}\n\t}\n\n\tpub fn other_colinear_handle(&self, handle: HandleId) -> Option<HandleId>\n\twhere\n\t\tUpstream: 'static,\n\t{\n\t\tlet pair = self.colinear_manipulators.iter().find(|pair| pair.contains(&handle))?;\n\t\tlet other = pair.iter().copied().find(|&val| val != handle)?;\n\t\tif handle.to_manipulator_point().get_anchor(self) == other.to_manipulator_point().get_anchor(self) {\n\t\t\tSome(other)\n\t\t} else {\n\t\t\tNone\n\t\t}\n\t}\n\n\tpub fn adjacent_segment(&self, manipulator_id: &ManipulatorPointId) -> Option<(PointId, SegmentId)> {\n\t\tmatch manipulator_id {\n\t\t\tManipulatorPointId::PrimaryHandle(segment_id) => {\n\t\t\t\t// For start handle, find segments ending at our start point\n\t\t\t\tlet (start_point_id, _, _) = self.segment_points_from_id(*segment_id)?;\n\t\t\t\tlet start_index = self.point_domain.resolve_id(start_point_id)?;\n\n\t\t\t\tself.segment_domain.end_connected(start_index).find(|&id| id != *segment_id).map(|id| (start_point_id, id)).or(self\n\t\t\t\t\t.segment_domain\n\t\t\t\t\t.start_connected(start_index)\n\t\t\t\t\t.find(|&id| id != *segment_id)\n\t\t\t\t\t.map(|id| (start_point_id, id)))\n\t\t\t}\n\t\t\tManipulatorPointId::EndHandle(segment_id) => {\n\t\t\t\t// For end handle, find segments starting at our end point\n\t\t\t\tlet (_, end_point_id, _) = self.segment_points_from_id(*segment_id)?;\n\t\t\t\tlet end_index = self.point_domain.resolve_id(end_point_id)?;\n\n\t\t\t\tself.segment_domain.start_connected(end_index).find(|&id| id != *segment_id).map(|id| (end_point_id, id)).or(self\n\t\t\t\t\t.segment_domain\n\t\t\t\t\t.end_connected(end_index)\n\t\t\t\t\t.find(|&id| id != *segment_id)\n\t\t\t\t\t.map(|id| (end_point_id, id)))\n\t\t\t}\n\t\t\tManipulatorPointId::Anchor(_) => None,\n\t\t}\n\t}\n\n\tpub fn concat(&mut self, additional: &Self, transform_of_additional: DAffine2, collision_hash_seed: u64) {\n\t\tlet point_map = additional\n\t\t\t.point_domain\n\t\t\t.ids()\n\t\t\t.iter()\n\t\t\t.filter(|id| self.point_domain.ids().contains(id))\n\t\t\t.map(|&old| (old, old.generate_from_hash(collision_hash_seed)))\n\t\t\t.collect::<HashMap<_, _>>();\n\n\t\tlet segment_map = additional\n\t\t\t.segment_domain\n\t\t\t.ids()\n\t\t\t.iter()\n\t\t\t.filter(|id| self.segment_domain.ids().contains(id))\n\t\t\t.map(|&old| (old, old.generate_from_hash(collision_hash_seed)))\n\t\t\t.collect::<HashMap<_, _>>();\n\n\t\tlet region_map = additional\n\t\t\t.region_domain\n\t\t\t.ids()\n\t\t\t.iter()\n\t\t\t.filter(|id| self.region_domain.ids().contains(id))\n\t\t\t.map(|&old| (old, old.generate_from_hash(collision_hash_seed)))\n\t\t\t.collect::<HashMap<_, _>>();\n\n\t\tlet id_map = IdMap {\n\t\t\tpoint_offset: self.point_domain.ids().len(),\n\t\t\tpoint_map,\n\t\t\tsegment_map,\n\t\t\tregion_map,\n\t\t};\n\n\t\tself.point_domain.concat(&additional.point_domain, transform_of_additional, &id_map);\n\t\tself.segment_domain.concat(&additional.segment_domain, transform_of_additional, &id_map);\n\t\tself.region_domain.concat(&additional.region_domain, transform_of_additional, &id_map);\n\n\t\t// TODO: properly deal with fills such as gradients\n\t\tself.style = additional.style.clone();\n\n\t\tself.colinear_manipulators.extend(additional.colinear_manipulators.iter().copied());\n\t}\n}\n\nimpl<Upstream> BoundingBox for Vector<Upstream> {\n\tfn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox {\n\t\tif !include_stroke {\n\t\t\t// Just use the path bounds without stroke\n\t\t\treturn match self.bounding_box_with_transform(transform) {\n\t\t\t\tSome(bounds) => RenderBoundingBox::Rectangle(bounds),\n\t\t\t\tNone => RenderBoundingBox::None,\n\t\t\t};\n\t\t}\n\n\t\t// Include stroke by adding offset based on stroke width\n\t\tlet stroke_width = self.style.stroke().map(|s| s.weight()).unwrap_or_default();\n\t\tlet miter_limit = self.style.stroke().map(|s| s.join_miter_limit).unwrap_or(1.);\n\t\tlet scale = transform.decompose_scale();\n\n\t\t// Use the full line width to account for different styles of stroke caps\n\t\tlet offset = DVec2::splat(stroke_width * scale.x.max(scale.y) * miter_limit);\n\n\t\tmatch self.bounding_box_with_transform(transform) {\n\t\t\tSome([a, b]) => RenderBoundingBox::Rectangle([a - offset, b + offset]),\n\t\t\tNone => RenderBoundingBox::None,\n\t\t}\n\t}\n}\n\nimpl<Upstream> RenderComplexity for Vector<Upstream> {\n\tfn render_complexity(&self) -> usize {\n\t\tself.segment_domain.ids().len()\n\t}\n}\n\n// Note: BoundingBox for Table<Vector> is handled by blanket impl in gcore\n\n#[cfg(test)]\nmod tests {\n\tuse kurbo::{CubicBez, PathSeg, Point};\n\n\tuse super::*;\n\n\tfn assert_subpath_eq(generated: &[Subpath<PointId>], expected: &[Subpath<PointId>]) {\n\t\tassert_eq!(generated.len(), expected.len());\n\t\tfor (generated, expected) in generated.iter().zip(expected) {\n\t\t\tassert_eq!(generated.manipulator_groups().len(), expected.manipulator_groups().len());\n\t\t\tassert_eq!(generated.closed(), expected.closed());\n\t\t\tfor (generated, expected) in generated.manipulator_groups().iter().zip(expected.manipulator_groups()) {\n\t\t\t\tassert_eq!(generated.in_handle, expected.in_handle);\n\t\t\t\tassert_eq!(generated.out_handle, expected.out_handle);\n\t\t\t\tassert_eq!(generated.anchor, expected.anchor);\n\t\t\t}\n\t\t}\n\t}\n\n\t#[test]\n\tfn construct_closed_subpath() {\n\t\tlet circle = Subpath::new_ellipse(DVec2::NEG_ONE, DVec2::ONE);\n\t\tlet vector: Vector<()> = Vector::from_subpath(&circle);\n\t\tassert_eq!(vector.point_domain.ids().len(), 4);\n\t\tlet bezier_paths = vector.segment_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();\n\t\tassert_eq!(bezier_paths.len(), 4);\n\t\tassert!(bezier_paths.iter().all(|&bezier| circle.iter().any(|original_bezier| original_bezier == bezier)));\n\n\t\tlet generated = vector.stroke_bezier_paths().collect::<Vec<_>>();\n\t\tassert_subpath_eq(&generated, &[circle]);\n\t}\n\n\t#[test]\n\tfn construct_open_subpath() {\n\t\tlet bezier = PathSeg::Cubic(CubicBez::new(Point::ZERO, Point::new(-1., -1.), Point::new(1., 1.), Point::new(1., 0.)));\n\t\tlet subpath = Subpath::from_bezier(bezier);\n\t\tlet vector: Vector<()> = Vector::from_subpath(&subpath);\n\t\tassert_eq!(vector.point_domain.ids().len(), 2);\n\t\tlet bezier_paths = vector.segment_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();\n\t\tassert_eq!(bezier_paths, vec![bezier]);\n\n\t\tlet generated = vector.stroke_bezier_paths().collect::<Vec<_>>();\n\t\tassert_subpath_eq(&generated, &[subpath]);\n\t}\n\n\t#[test]\n\tfn construct_many_subpath() {\n\t\tlet curve = PathSeg::Cubic(CubicBez::new(Point::ZERO, Point::new(-1., -1.), Point::new(1., 1.), Point::new(1., 0.)));\n\t\tlet curve = Subpath::from_bezier(curve);\n\t\tlet circle = Subpath::new_ellipse(DVec2::NEG_ONE, DVec2::ONE);\n\n\t\tlet vector: Vector<()> = Vector::from_subpaths([&curve, &circle], false);\n\t\tassert_eq!(vector.point_domain.ids().len(), 6);\n\n\t\tlet bezier_paths = vector.segment_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();\n\t\tassert_eq!(bezier_paths.len(), 5);\n\t\tassert!(bezier_paths.iter().all(|&bezier| circle.iter().chain(curve.iter()).any(|original_bezier| original_bezier == bezier)));\n\n\t\tlet generated = vector.stroke_bezier_paths().collect::<Vec<_>>();\n\t\tassert_subpath_eq(&generated, &[curve, circle]);\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/wgpu-executor/Cargo.toml",
    "content": "[package]\nname = \"wgpu-executor\"\nversion = \"0.1.0\"\nedition = \"2024\"\nlicense = \"MIT OR Apache-2.0\"\nauthors.workspace = true\n\n[dependencies]\n# Local dependencies\ncore-types = { workspace = true }\nraster-types = { workspace = true, features = [\"wgpu\"] }\ngraphene-application-io = { workspace = true, features = [\"wgpu\"] }\nrendering = { workspace = true }\ndyn-any = { workspace = true }\nnode-macro = { workspace = true }\n\n# Workspace dependencies\nglam = { workspace = true }\nanyhow = { workspace = true }\nwgpu = { workspace = true }\nfutures = { workspace = true }\nweb-sys = { workspace = true }\nwinit = { workspace = true }\nvello = { workspace = true }\nbytemuck = { workspace = true }\n"
  },
  {
    "path": "node-graph/libraries/wgpu-executor/src/context.rs",
    "content": "use std::sync::Arc;\nuse wgpu::{Adapter, Backends, Device, Features, Instance, Queue};\n\n#[derive(Debug, Clone)]\npub struct Context {\n\tpub device: Arc<Device>,\n\tpub queue: Arc<Queue>,\n\tpub instance: Arc<Instance>,\n\tpub adapter: Arc<Adapter>,\n}\n\nimpl Context {\n\tpub async fn new() -> Option<Self> {\n\t\tContextBuilder::new().build().await\n\t}\n}\n\n#[derive(Default)]\npub struct ContextBuilder {\n\tbackends: Backends,\n\tfeatures: Features,\n}\nimpl ContextBuilder {\n\tpub fn new() -> Self {\n\t\tSelf {\n\t\t\tbackends: Backends::all(),\n\t\t\tfeatures: Features::empty(),\n\t\t}\n\t}\n\tpub fn with_backends(mut self, backends: Backends) -> Self {\n\t\tself.backends = backends;\n\t\tself\n\t}\n\tpub fn with_features(mut self, features: Features) -> Self {\n\t\tself.features = features;\n\t\tself\n\t}\n}\n#[cfg(not(target_family = \"wasm\"))]\nimpl ContextBuilder {\n\tpub async fn build(self) -> Option<Context> {\n\t\tself.build_with_adapter_selection_inner(None::<fn(&[Adapter]) -> Option<usize>>).await\n\t}\n\tpub async fn build_with_adapter_selection<S>(self, select: S) -> Option<Context>\n\twhere\n\t\tS: Fn(&[Adapter]) -> Option<usize>,\n\t{\n\t\tself.build_with_adapter_selection_inner(Some(select)).await\n\t}\n\tpub async fn available_adapters_fmt(&self) -> impl std::fmt::Display {\n\t\tlet instance = self.build_instance();\n\t\tfmt::AvailableAdaptersFormatter(instance.enumerate_adapters(self.backends))\n\t}\n}\n#[cfg(target_family = \"wasm\")]\nimpl ContextBuilder {\n\tpub async fn build(self) -> Option<Context> {\n\t\tlet instance = self.build_instance();\n\t\tlet adapter = self.request_adapter(&instance).await?;\n\t\tlet (device, queue) = self.request_device(&adapter).await?;\n\t\tSome(Context {\n\t\t\tdevice: Arc::new(device),\n\t\t\tqueue: Arc::new(queue),\n\t\t\tadapter: Arc::new(adapter),\n\t\t\tinstance: Arc::new(instance),\n\t\t})\n\t}\n}\nimpl ContextBuilder {\n\tfn build_instance(&self) -> Instance {\n\t\tInstance::new(&wgpu::InstanceDescriptor {\n\t\t\tbackends: self.backends,\n\t\t\t..Default::default()\n\t\t})\n\t}\n\tasync fn request_adapter(&self, instance: &Instance) -> Option<Adapter> {\n\t\tlet request_adapter_options = wgpu::RequestAdapterOptions {\n\t\t\tpower_preference: wgpu::PowerPreference::HighPerformance,\n\t\t\tcompatible_surface: None,\n\t\t\tforce_fallback_adapter: false,\n\t\t};\n\t\tinstance.request_adapter(&request_adapter_options).await.ok()\n\t}\n\tasync fn request_device(&self, adapter: &Adapter) -> Option<(Device, Queue)> {\n\t\tlet device_descriptor = wgpu::DeviceDescriptor {\n\t\t\tlabel: None,\n\t\t\trequired_features: self.features,\n\t\t\trequired_limits: adapter.limits(),\n\t\t\tmemory_hints: Default::default(),\n\t\t\ttrace: wgpu::Trace::Off,\n\t\t\texperimental_features: Default::default(),\n\t\t};\n\t\tadapter.request_device(&device_descriptor).await.ok()\n\t}\n}\n#[cfg(not(target_family = \"wasm\"))]\nimpl ContextBuilder {\n\tasync fn build_with_adapter_selection_inner<S>(self, select: Option<S>) -> Option<Context>\n\twhere\n\t\tS: Fn(&[Adapter]) -> Option<usize>,\n\t{\n\t\tlet instance = self.build_instance();\n\n\t\tlet selected_adapter = if let Some(select) = select {\n\t\t\tself.select_adapter(&instance, select)\n\t\t} else if cfg!(target_os = \"windows\") {\n\t\t\tself.select_adapter(&instance, |adapters: &[Adapter]| adapters.iter().position(|a| a.get_info().backend == wgpu::Backend::Dx12))\n\t\t} else {\n\t\t\tNone\n\t\t};\n\n\t\tlet adapter = if let Some(adapter) = selected_adapter { adapter } else { self.request_adapter(&instance).await? };\n\n\t\tlet (device, queue) = self.request_device(&adapter).await?;\n\t\tSome(Context {\n\t\t\tdevice: Arc::new(device),\n\t\t\tqueue: Arc::new(queue),\n\t\t\tadapter: Arc::new(adapter),\n\t\t\tinstance: Arc::new(instance),\n\t\t})\n\t}\n\tfn select_adapter<S>(&self, instance: &Instance, select: S) -> Option<Adapter>\n\twhere\n\t\tS: Fn(&[Adapter]) -> Option<usize>,\n\t{\n\t\tlet mut adapters = instance.enumerate_adapters(self.backends);\n\t\tlet selected_index = select(&adapters)?;\n\t\tif selected_index >= adapters.len() {\n\t\t\treturn None;\n\t\t}\n\t\tSome(adapters.remove(selected_index))\n\t}\n}\n#[cfg(not(target_family = \"wasm\"))]\nmod fmt {\n\tuse super::*;\n\n\tpub(super) struct AvailableAdaptersFormatter(pub(super) Vec<Adapter>);\n\timpl std::fmt::Display for AvailableAdaptersFormatter {\n\t\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\t\tfor (i, adapter) in self.0.iter().enumerate() {\n\t\t\t\tlet info = adapter.get_info();\n\t\t\t\twriteln!(\n\t\t\t\t\tf,\n\t\t\t\t\t\"[{}] {:?} {:?} (Name: {}, Driver: {}, Device: {})\",\n\t\t\t\t\ti, info.backend, info.device_type, info.name, info.driver, info.device,\n\t\t\t\t)?;\n\t\t\t}\n\t\t\tOk(())\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/wgpu-executor/src/lib.rs",
    "content": "mod context;\nmod resample;\npub mod shader_runtime;\npub mod texture_conversion;\n\nuse crate::resample::Resampler;\nuse crate::shader_runtime::ShaderRuntime;\nuse anyhow::Result;\nuse core_types::Color;\nuse dyn_any::StaticType;\nuse futures::lock::Mutex;\nuse glam::UVec2;\nuse graphene_application_io::{ApplicationIo, EditorApi, SurfaceHandle, SurfaceId};\nuse std::sync::Arc;\nuse vello::{AaConfig, AaSupport, RenderParams, Renderer, RendererOptions, Scene};\nuse wgpu::util::TextureBlitter;\nuse wgpu::{Origin3d, TextureAspect};\n\npub use context::Context as WgpuContext;\npub use context::ContextBuilder as WgpuContextBuilder;\npub use rendering::RenderContext;\npub use wgpu::Backends as WgpuBackends;\npub use wgpu::Features as WgpuFeatures;\n\n#[derive(dyn_any::DynAny)]\npub struct WgpuExecutor {\n\tpub context: WgpuContext,\n\tvello_renderer: Mutex<Renderer>,\n\tresampler: Resampler,\n\tpub shader_runtime: ShaderRuntime,\n}\n\nimpl std::fmt::Debug for WgpuExecutor {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tf.debug_struct(\"WgpuExecutor\").field(\"context\", &self.context).finish()\n\t}\n}\n\nimpl<'a, T: ApplicationIo<Executor = WgpuExecutor>> From<&'a EditorApi<T>> for &'a WgpuExecutor {\n\tfn from(editor_api: &'a EditorApi<T>) -> Self {\n\t\teditor_api.application_io.as_ref().unwrap().gpu_executor().unwrap()\n\t}\n}\n\npub type WgpuSurface = Arc<SurfaceHandle<Surface>>;\npub type WgpuWindow = Arc<SurfaceHandle<WindowHandle>>;\n\npub struct Surface {\n\tpub inner: wgpu::Surface<'static>,\n\tpub target_texture: Mutex<Option<TargetTexture>>,\n\tpub blitter: TextureBlitter,\n}\n\n#[derive(Clone, Debug)]\npub struct TargetTexture {\n\ttexture: wgpu::Texture,\n\tview: wgpu::TextureView,\n\tsize: UVec2,\n}\n\nimpl TargetTexture {\n\t/// Creates a new TargetTexture with the specified size.\n\tpub fn new(device: &wgpu::Device, size: UVec2) -> Self {\n\t\tlet size = size.max(UVec2::ONE);\n\t\tlet texture = device.create_texture(&wgpu::TextureDescriptor {\n\t\t\tlabel: None,\n\t\t\tsize: wgpu::Extent3d {\n\t\t\t\twidth: size.x,\n\t\t\t\theight: size.y,\n\t\t\t\tdepth_or_array_layers: 1,\n\t\t\t},\n\t\t\tmip_level_count: 1,\n\t\t\tsample_count: 1,\n\t\t\tdimension: wgpu::TextureDimension::D2,\n\t\t\tusage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_SRC,\n\t\t\tformat: VELLO_SURFACE_FORMAT,\n\t\t\tview_formats: &[],\n\t\t});\n\t\tlet view = texture.create_view(&wgpu::TextureViewDescriptor::default());\n\n\t\tSelf { texture, view, size }\n\t}\n\n\t/// Ensures the texture has the specified size, creating a new one if needed.\n\t/// This allows reusing the same texture across frames when the size hasn't changed.\n\tpub fn ensure_size(&mut self, device: &wgpu::Device, size: UVec2) {\n\t\tlet size = size.max(UVec2::ONE);\n\t\tif self.size == size {\n\t\t\treturn;\n\t\t}\n\n\t\t*self = Self::new(device, size);\n\t}\n\n\t/// Returns a reference to the texture view for rendering.\n\tpub fn view(&self) -> &wgpu::TextureView {\n\t\t&self.view\n\t}\n\n\t/// Returns a reference to the underlying texture.\n\tpub fn texture(&self) -> &wgpu::Texture {\n\t\t&self.texture\n\t}\n}\n\n#[cfg(target_family = \"wasm\")]\npub type Window = web_sys::HtmlCanvasElement;\n#[cfg(not(target_family = \"wasm\"))]\npub type Window = Arc<dyn winit::window::Window>;\n\nunsafe impl StaticType for Surface {\n\ttype Static = Surface;\n}\n\nconst VELLO_SURFACE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;\n\nimpl WgpuExecutor {\n\tpub async fn render_vello_scene_to_texture(&self, scene: &Scene, size: UVec2, context: &RenderContext, background: Option<Color>) -> Result<wgpu::Texture> {\n\t\tlet mut output = None;\n\t\tself.render_vello_scene_to_target_texture(scene, size, context, background, &mut output).await?;\n\t\tOk(output.unwrap().texture)\n\t}\n\tpub async fn render_vello_scene_to_target_texture(&self, scene: &Scene, size: UVec2, context: &RenderContext, background: Option<Color>, output: &mut Option<TargetTexture>) -> Result<()> {\n\t\t// Initialize (lazily) if this is the first call\n\t\tif output.is_none() {\n\t\t\t*output = Some(TargetTexture::new(&self.context.device, size));\n\t\t}\n\n\t\tif let Some(target_texture) = output.as_mut() {\n\t\t\ttarget_texture.ensure_size(&self.context.device, size);\n\n\t\t\tlet [r, g, b, a] = background.unwrap_or(Color::TRANSPARENT).to_rgba8_srgb();\n\t\t\tlet render_params = RenderParams {\n\t\t\t\tbase_color: vello::peniko::Color::from_rgba8(r, g, b, a),\n\t\t\t\twidth: size.x,\n\t\t\t\theight: size.y,\n\t\t\t\tantialiasing_method: AaConfig::Msaa16,\n\t\t\t};\n\n\t\t\t{\n\t\t\t\tlet mut renderer = self.vello_renderer.lock().await;\n\t\t\t\tfor (image_brush, texture) in context.resource_overrides.iter() {\n\t\t\t\t\tlet texture_view = wgpu::TexelCopyTextureInfoBase {\n\t\t\t\t\t\ttexture: texture.clone(),\n\t\t\t\t\t\tmip_level: 0,\n\t\t\t\t\t\torigin: Origin3d::ZERO,\n\t\t\t\t\t\taspect: TextureAspect::All,\n\t\t\t\t\t};\n\t\t\t\t\trenderer.override_image(&image_brush.image, Some(texture_view));\n\t\t\t\t}\n\t\t\t\trenderer.render_to_texture(&self.context.device, &self.context.queue, scene, target_texture.view(), &render_params)?;\n\t\t\t\tfor (image_brush, _) in context.resource_overrides.iter() {\n\t\t\t\t\trenderer.override_image(&image_brush.image, None);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tOk(())\n\t}\n\n\tpub fn resample_texture(&self, source: &wgpu::Texture, target_size: UVec2, transform: &glam::DAffine2) -> wgpu::Texture {\n\t\tself.resampler.resample(&self.context, source, target_size, transform)\n\t}\n\n\t#[cfg(target_family = \"wasm\")]\n\tpub fn create_surface(&self, canvas: graphene_application_io::WasmSurfaceHandle) -> Result<SurfaceHandle<Surface>> {\n\t\tlet surface = self.context.instance.create_surface(wgpu::SurfaceTarget::Canvas(canvas.surface))?;\n\t\tself.create_surface_inner(surface, canvas.window_id)\n\t}\n\t#[cfg(not(target_family = \"wasm\"))]\n\tpub fn create_surface(&self, window: SurfaceHandle<Window>) -> Result<SurfaceHandle<Surface>> {\n\t\tlet surface = self.context.instance.create_surface(wgpu::SurfaceTarget::Window(Box::new(window.surface)))?;\n\t\tself.create_surface_inner(surface, window.window_id)\n\t}\n\n\tpub fn create_surface_inner(&self, surface: wgpu::Surface<'static>, window_id: SurfaceId) -> Result<SurfaceHandle<Surface>> {\n\t\tlet blitter = TextureBlitter::new(&self.context.device, VELLO_SURFACE_FORMAT);\n\t\tOk(SurfaceHandle {\n\t\t\twindow_id,\n\t\t\tsurface: Surface {\n\t\t\t\tinner: surface,\n\t\t\t\ttarget_texture: Mutex::new(None),\n\t\t\t\tblitter,\n\t\t\t},\n\t\t})\n\t}\n}\n\nimpl WgpuExecutor {\n\tpub async fn new() -> Option<Self> {\n\t\tSelf::with_context(WgpuContext::new().await?)\n\t}\n\n\tpub fn with_context(context: WgpuContext) -> Option<Self> {\n\t\tlet vello_renderer = Renderer::new(\n\t\t\t&context.device,\n\t\t\tRendererOptions {\n\t\t\t\tpipeline_cache: None,\n\t\t\t\tuse_cpu: false,\n\t\t\t\tantialiasing_support: AaSupport::all(),\n\t\t\t\tnum_init_threads: std::num::NonZeroUsize::new(1),\n\t\t\t},\n\t\t)\n\t\t.map_err(|e| anyhow::anyhow!(\"Failed to create Vello renderer: {:?}\", e))\n\t\t.ok()?;\n\n\t\tlet resampler = Resampler::new(&context.device);\n\n\t\tSome(Self {\n\t\t\tshader_runtime: ShaderRuntime::new(&context),\n\t\t\tcontext,\n\t\t\tresampler,\n\t\t\tvello_renderer: vello_renderer.into(),\n\t\t})\n\t}\n}\n\npub type WindowHandle = Arc<SurfaceHandle<Window>>;\n"
  },
  {
    "path": "node-graph/libraries/wgpu-executor/src/resample.rs",
    "content": "use crate::WgpuContext;\nuse glam::{DAffine2, UVec2, Vec2};\n\npub struct Resampler {\n\tpipeline: wgpu::RenderPipeline,\n\tbind_group_layout: wgpu::BindGroupLayout,\n}\n\nimpl Resampler {\n\tpub fn new(device: &wgpu::Device) -> Self {\n\t\tlet shader = device.create_shader_module(wgpu::include_wgsl!(\"resample_shader.wgsl\"));\n\n\t\tlet bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {\n\t\t\tlabel: Some(\"resample_bind_group_layout\"),\n\t\t\tentries: &[\n\t\t\t\twgpu::BindGroupLayoutEntry {\n\t\t\t\t\tbinding: 0,\n\t\t\t\t\tvisibility: wgpu::ShaderStages::FRAGMENT,\n\t\t\t\t\tty: wgpu::BindingType::Texture {\n\t\t\t\t\t\tmultisampled: false,\n\t\t\t\t\t\tview_dimension: wgpu::TextureViewDimension::D2,\n\t\t\t\t\t\tsample_type: wgpu::TextureSampleType::Float { filterable: false },\n\t\t\t\t\t},\n\t\t\t\t\tcount: None,\n\t\t\t\t},\n\t\t\t\twgpu::BindGroupLayoutEntry {\n\t\t\t\t\tbinding: 1,\n\t\t\t\t\tvisibility: wgpu::ShaderStages::FRAGMENT,\n\t\t\t\t\tty: wgpu::BindingType::Buffer {\n\t\t\t\t\t\tty: wgpu::BufferBindingType::Uniform,\n\t\t\t\t\t\thas_dynamic_offset: false,\n\t\t\t\t\t\tmin_binding_size: None,\n\t\t\t\t\t},\n\t\t\t\t\tcount: None,\n\t\t\t\t},\n\t\t\t],\n\t\t});\n\n\t\tlet pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {\n\t\t\tlabel: Some(\"resample_pipeline_layout\"),\n\t\t\tbind_group_layouts: &[&bind_group_layout],\n\t\t\tpush_constant_ranges: &[],\n\t\t});\n\n\t\tlet pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {\n\t\t\tlabel: Some(\"resample_pipeline\"),\n\t\t\tlayout: Some(&pipeline_layout),\n\t\t\tvertex: wgpu::VertexState {\n\t\t\t\tmodule: &shader,\n\t\t\t\tentry_point: Some(\"vs_main\"),\n\t\t\t\tbuffers: &[],\n\t\t\t\tcompilation_options: wgpu::PipelineCompilationOptions::default(),\n\t\t\t},\n\t\t\tfragment: Some(wgpu::FragmentState {\n\t\t\t\tmodule: &shader,\n\t\t\t\tentry_point: Some(\"fs_main\"),\n\t\t\t\ttargets: &[Some(wgpu::ColorTargetState {\n\t\t\t\t\tformat: wgpu::TextureFormat::Rgba8Unorm,\n\t\t\t\t\tblend: None,\n\t\t\t\t\twrite_mask: wgpu::ColorWrites::ALL,\n\t\t\t\t})],\n\t\t\t\tcompilation_options: wgpu::PipelineCompilationOptions::default(),\n\t\t\t}),\n\t\t\tprimitive: wgpu::PrimitiveState {\n\t\t\t\ttopology: wgpu::PrimitiveTopology::TriangleList,\n\t\t\t\t..Default::default()\n\t\t\t},\n\t\t\tdepth_stencil: None,\n\t\t\tmultisample: wgpu::MultisampleState::default(),\n\t\t\tmultiview: None,\n\t\t\tcache: None,\n\t\t});\n\n\t\tResampler { pipeline, bind_group_layout }\n\t}\n\n\tpub fn resample(&self, context: &WgpuContext, source: &wgpu::Texture, target_size: UVec2, transform: &DAffine2) -> wgpu::Texture {\n\t\tlet device = &context.device;\n\t\tlet queue = &context.queue;\n\n\t\tlet output_texture = device.create_texture(&wgpu::TextureDescriptor {\n\t\t\tlabel: Some(\"resample_output\"),\n\t\t\tsize: wgpu::Extent3d {\n\t\t\t\twidth: target_size.x.max(1),\n\t\t\t\theight: target_size.y.max(1),\n\t\t\t\tdepth_or_array_layers: 1,\n\t\t\t},\n\t\t\tmip_level_count: 1,\n\t\t\tsample_count: 1,\n\t\t\tdimension: wgpu::TextureDimension::D2,\n\t\t\tformat: wgpu::TextureFormat::Rgba8Unorm,\n\t\t\tusage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::TEXTURE_BINDING,\n\t\t\tview_formats: &[],\n\t\t});\n\n\t\tlet source_view = source.create_view(&wgpu::TextureViewDescriptor::default());\n\t\tlet output_view = output_texture.create_view(&wgpu::TextureViewDescriptor::default());\n\n\t\tlet params_buffer = device.create_buffer(&wgpu::BufferDescriptor {\n\t\t\tlabel: Some(\"resample_params\"),\n\t\t\tsize: 32,\n\t\t\tusage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,\n\t\t\tmapped_at_creation: false,\n\t\t});\n\n\t\tlet params_data = [transform.matrix2.x_axis.as_vec2(), transform.matrix2.y_axis.as_vec2(), transform.translation.as_vec2(), Vec2::ZERO];\n\t\tqueue.write_buffer(&params_buffer, 0, bytemuck::cast_slice(&params_data));\n\n\t\tlet bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {\n\t\t\tlabel: Some(\"resample_bind_group\"),\n\t\t\tlayout: &self.bind_group_layout,\n\t\t\tentries: &[\n\t\t\t\twgpu::BindGroupEntry {\n\t\t\t\t\tbinding: 0,\n\t\t\t\t\tresource: wgpu::BindingResource::TextureView(&source_view),\n\t\t\t\t},\n\t\t\t\twgpu::BindGroupEntry {\n\t\t\t\t\tbinding: 1,\n\t\t\t\t\tresource: params_buffer.as_entire_binding(),\n\t\t\t\t},\n\t\t\t],\n\t\t});\n\n\t\tlet mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some(\"resample_encoder\") });\n\n\t\t{\n\t\t\tlet mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {\n\t\t\t\tlabel: Some(\"resample_pass\"),\n\t\t\t\tcolor_attachments: &[Some(wgpu::RenderPassColorAttachment {\n\t\t\t\t\tview: &output_view,\n\t\t\t\t\tresolve_target: None,\n\t\t\t\t\tops: wgpu::Operations {\n\t\t\t\t\t\tload: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),\n\t\t\t\t\t\tstore: wgpu::StoreOp::Store,\n\t\t\t\t\t},\n\t\t\t\t\tdepth_slice: None,\n\t\t\t\t})],\n\t\t\t\tdepth_stencil_attachment: None,\n\t\t\t\ttimestamp_writes: None,\n\t\t\t\tocclusion_query_set: None,\n\t\t\t});\n\n\t\t\trender_pass.set_pipeline(&self.pipeline);\n\t\t\trender_pass.set_bind_group(0, &bind_group, &[]);\n\t\t\trender_pass.draw(0..3, 0..1);\n\t\t}\n\n\t\tqueue.submit([encoder.finish()]);\n\n\t\toutput_texture\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/wgpu-executor/src/resample_shader.wgsl",
    "content": "// =============\n// VERTEX SHADER\n// =============\n\nstruct VertexOutput {\n\t@builtin(position) clip_position: vec4<f32>,\n\t@location(0) tex_coords: vec2<f32>,\n}\n\n@vertex\nfn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {\n\tvar out: VertexOutput;\n\tlet pos = array(\n\t\tvec2f(-1.0, -1.0),\n\t\tvec2f(3.0, -1.0),\n\t\tvec2f(-1.0, 3.0),\n\t);\n\tlet xy = pos[vertex_index];\n\tout.clip_position = vec4f(xy, 0.0, 1.0);\n\tlet coords = xy / 2. + 0.5;\n\tout.tex_coords = vec2f(coords.x, 1. - coords.y);\n\treturn out;\n}\n\n// ===============\n// FRAGMENT SHADER\n// ===============\n\n@group(0) @binding(0)\nvar t_source: texture_2d<f32>;\n\nstruct Params {\n\tmatrix: mat2x2<f32>,\n\ttranslation: vec2<f32>,\n\t_pad: vec2<f32>,\n};\n\n// We need to use a uniform buffer for the params because push constants are not supported on web\n@group(0) @binding(1)\nvar<uniform> params: Params;\n\n@fragment\nfn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {\n\tlet position = params.matrix * in.tex_coords + params.translation;\n\tlet texel = vec2<i32>(floor(position));\n\tlet texture_size = vec2<i32>(textureDimensions(t_source));\n\tif (texel.x >= 0 && texel.x < texture_size.x && texel.y >= 0 && texel.y < texture_size.y) {\n\t\treturn textureLoad(t_source, texel, 0);\n\t}\n\treturn vec4<f32>(0.0);\n}\n"
  },
  {
    "path": "node-graph/libraries/wgpu-executor/src/shader_runtime/mod.rs",
    "content": "use crate::WgpuContext;\nuse crate::shader_runtime::per_pixel_adjust_runtime::PerPixelAdjustShaderRuntime;\n\npub mod per_pixel_adjust_runtime;\n\npub const FULLSCREEN_VERTEX_SHADER_NAME: &str = \"fullscreen_vertexfullscreen_vertex\";\n\npub struct ShaderRuntime {\n\tcontext: WgpuContext,\n\tper_pixel_adjust: PerPixelAdjustShaderRuntime,\n}\n\nimpl ShaderRuntime {\n\tpub fn new(context: &WgpuContext) -> Self {\n\t\tSelf {\n\t\t\tcontext: context.clone(),\n\t\t\tper_pixel_adjust: PerPixelAdjustShaderRuntime::new(),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/wgpu-executor/src/shader_runtime/per_pixel_adjust_runtime.rs",
    "content": "use crate::WgpuContext;\nuse crate::shader_runtime::{FULLSCREEN_VERTEX_SHADER_NAME, ShaderRuntime};\nuse core_types::shaders::buffer_struct::BufferStruct;\nuse core_types::table::{Table, TableRow};\nuse futures::lock::Mutex;\nuse raster_types::{GPU, Raster};\nuse std::borrow::Cow;\nuse std::collections::HashMap;\nuse wgpu::util::{BufferInitDescriptor, DeviceExt};\nuse wgpu::{\n\tBindGroupDescriptor, BindGroupEntry, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BindingType, Buffer, BufferBinding, BufferBindingType, BufferUsages, ColorTargetState, Face,\n\tFragmentState, FrontFace, LoadOp, Operations, PipelineLayoutDescriptor, PolygonMode, PrimitiveState, PrimitiveTopology, RenderPassColorAttachment, RenderPassDescriptor, RenderPipelineDescriptor,\n\tShaderModuleDescriptor, ShaderSource, ShaderStages, StoreOp, TextureDescriptor, TextureDimension, TextureFormat, TextureSampleType, TextureViewDescriptor, TextureViewDimension, VertexState,\n};\n\npub struct PerPixelAdjustShaderRuntime {\n\t// TODO: PerPixelAdjustGraphicsPipeline already contains the key as `name`\n\tpipeline_cache: Mutex<HashMap<String, PerPixelAdjustGraphicsPipeline>>,\n}\n\nimpl Default for PerPixelAdjustShaderRuntime {\n\tfn default() -> Self {\n\t\tSelf::new()\n\t}\n}\n\nimpl PerPixelAdjustShaderRuntime {\n\tpub fn new() -> Self {\n\t\tSelf {\n\t\t\tpipeline_cache: Mutex::new(HashMap::new()),\n\t\t}\n\t}\n}\n\nimpl ShaderRuntime {\n\tpub async fn run_per_pixel_adjust<T: BufferStruct>(&self, shaders: &Shaders<'_>, textures: Table<Raster<GPU>>, args: Option<&T>) -> Table<Raster<GPU>> {\n\t\tlet mut cache = self.per_pixel_adjust.pipeline_cache.lock().await;\n\t\tlet pipeline = cache\n\t\t\t.entry(shaders.fragment_shader_name.to_owned())\n\t\t\t.or_insert_with(|| PerPixelAdjustGraphicsPipeline::new(&self.context, shaders));\n\n\t\tlet arg_buffer = args.map(|args| {\n\t\t\tlet device = &self.context.device;\n\t\t\tdevice.create_buffer_init(&BufferInitDescriptor {\n\t\t\t\tlabel: Some(&format!(\"{} arg buffer\", pipeline.name.as_str())),\n\t\t\t\tusage: BufferUsages::STORAGE,\n\t\t\t\tcontents: bytemuck::bytes_of(&T::write(*args)),\n\t\t\t})\n\t\t});\n\t\tpipeline.dispatch(&self.context, textures, arg_buffer)\n\t}\n}\n\npub struct Shaders<'a> {\n\tpub wgsl_shader: &'a str,\n\tpub fragment_shader_name: &'a str,\n\tpub has_uniform: bool,\n}\n\npub struct PerPixelAdjustGraphicsPipeline {\n\tname: String,\n\thas_uniform: bool,\n\tpipeline: wgpu::RenderPipeline,\n}\n\nimpl PerPixelAdjustGraphicsPipeline {\n\tpub fn new(context: &WgpuContext, info: &Shaders) -> Self {\n\t\tlet device = &context.device;\n\t\tlet name = info.fragment_shader_name.to_owned();\n\n\t\tlet fragment_name = &name;\n\t\tlet fragment_name = &fragment_name[(fragment_name.find(\"::\").unwrap() + 2)..];\n\t\t// TODO workaround to naga removing `:`\n\t\tlet fragment_name = fragment_name.replace(\":\", \"\");\n\t\tlet shader_module = device.create_shader_module(ShaderModuleDescriptor {\n\t\t\tlabel: Some(&format!(\"PerPixelAdjust {name} wgsl shader\")),\n\t\t\tsource: ShaderSource::Wgsl(Cow::Borrowed(info.wgsl_shader)),\n\t\t});\n\n\t\tlet entries: &[_] = if info.has_uniform {\n\t\t\t&[\n\t\t\t\tBindGroupLayoutEntry {\n\t\t\t\t\tbinding: 0,\n\t\t\t\t\tvisibility: ShaderStages::FRAGMENT,\n\t\t\t\t\tty: BindingType::Buffer {\n\t\t\t\t\t\tty: BufferBindingType::Storage { read_only: true },\n\t\t\t\t\t\thas_dynamic_offset: false,\n\t\t\t\t\t\tmin_binding_size: None,\n\t\t\t\t\t},\n\t\t\t\t\tcount: None,\n\t\t\t\t},\n\t\t\t\tBindGroupLayoutEntry {\n\t\t\t\t\tbinding: 1,\n\t\t\t\t\tvisibility: ShaderStages::FRAGMENT,\n\t\t\t\t\tty: BindingType::Texture {\n\t\t\t\t\t\tsample_type: TextureSampleType::Float { filterable: false },\n\t\t\t\t\t\tview_dimension: TextureViewDimension::D2,\n\t\t\t\t\t\tmultisampled: false,\n\t\t\t\t\t},\n\t\t\t\t\tcount: None,\n\t\t\t\t},\n\t\t\t]\n\t\t} else {\n\t\t\t&[BindGroupLayoutEntry {\n\t\t\t\tbinding: 0,\n\t\t\t\tvisibility: ShaderStages::FRAGMENT,\n\t\t\t\tty: BindingType::Texture {\n\t\t\t\t\tsample_type: TextureSampleType::Float { filterable: false },\n\t\t\t\t\tview_dimension: TextureViewDimension::D2,\n\t\t\t\t\tmultisampled: false,\n\t\t\t\t},\n\t\t\t\tcount: None,\n\t\t\t}]\n\t\t};\n\t\tlet pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {\n\t\t\tlabel: Some(&format!(\"PerPixelAdjust {name} PipelineLayout\")),\n\t\t\tbind_group_layouts: &[&device.create_bind_group_layout(&BindGroupLayoutDescriptor {\n\t\t\t\tlabel: Some(&format!(\"PerPixelAdjust {name} BindGroupLayout 0\")),\n\t\t\t\tentries,\n\t\t\t})],\n\t\t\tpush_constant_ranges: &[],\n\t\t});\n\n\t\tlet pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {\n\t\t\tlabel: Some(&format!(\"PerPixelAdjust {name} Pipeline\")),\n\t\t\tlayout: Some(&pipeline_layout),\n\t\t\tvertex: VertexState {\n\t\t\t\tmodule: &shader_module,\n\t\t\t\tentry_point: Some(FULLSCREEN_VERTEX_SHADER_NAME),\n\t\t\t\tcompilation_options: Default::default(),\n\t\t\t\tbuffers: &[],\n\t\t\t},\n\t\t\tprimitive: PrimitiveState {\n\t\t\t\ttopology: PrimitiveTopology::TriangleList,\n\t\t\t\tstrip_index_format: None,\n\t\t\t\tfront_face: FrontFace::Ccw,\n\t\t\t\tcull_mode: Some(Face::Back),\n\t\t\t\tunclipped_depth: false,\n\t\t\t\tpolygon_mode: PolygonMode::Fill,\n\t\t\t\tconservative: false,\n\t\t\t},\n\t\t\tdepth_stencil: None,\n\t\t\tmultisample: Default::default(),\n\t\t\tfragment: Some(FragmentState {\n\t\t\t\tmodule: &shader_module,\n\t\t\t\tentry_point: Some(&fragment_name),\n\t\t\t\tcompilation_options: Default::default(),\n\t\t\t\ttargets: &[Some(ColorTargetState {\n\t\t\t\t\tformat: TextureFormat::Rgba8UnormSrgb,\n\t\t\t\t\tblend: None,\n\t\t\t\t\twrite_mask: Default::default(),\n\t\t\t\t})],\n\t\t\t}),\n\t\t\tmultiview: None,\n\t\t\tcache: None,\n\t\t});\n\t\tSelf {\n\t\t\tpipeline,\n\t\t\tname,\n\t\t\thas_uniform: info.has_uniform,\n\t\t}\n\t}\n\n\tpub fn dispatch(&self, context: &WgpuContext, textures: Table<Raster<GPU>>, arg_buffer: Option<Buffer>) -> Table<Raster<GPU>> {\n\t\tassert_eq!(self.has_uniform, arg_buffer.is_some());\n\t\tlet device = &context.device;\n\t\tlet name = self.name.as_str();\n\n\t\tlet mut cmd = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {\n\t\t\tlabel: Some(&format!(\"{name} cmd encoder\")),\n\t\t});\n\t\tlet out = textures\n\t\t\t.iter()\n\t\t\t.map(|instance| {\n\t\t\t\tlet tex_in = &instance.element.texture;\n\t\t\t\tlet view_in = tex_in.create_view(&TextureViewDescriptor::default());\n\t\t\t\tlet format = tex_in.format();\n\n\t\t\t\tlet entries: &[_] = if let Some(arg_buffer) = arg_buffer.as_ref() {\n\t\t\t\t\t&[\n\t\t\t\t\t\tBindGroupEntry {\n\t\t\t\t\t\t\tbinding: 0,\n\t\t\t\t\t\t\tresource: BindingResource::Buffer(BufferBinding {\n\t\t\t\t\t\t\t\tbuffer: arg_buffer,\n\t\t\t\t\t\t\t\toffset: 0,\n\t\t\t\t\t\t\t\tsize: None,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tBindGroupEntry {\n\t\t\t\t\t\t\tbinding: 1,\n\t\t\t\t\t\t\tresource: BindingResource::TextureView(&view_in),\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t} else {\n\t\t\t\t\t&[BindGroupEntry {\n\t\t\t\t\t\tbinding: 0,\n\t\t\t\t\t\tresource: BindingResource::TextureView(&view_in),\n\t\t\t\t\t}]\n\t\t\t\t};\n\t\t\t\tlet bind_group = device.create_bind_group(&BindGroupDescriptor {\n\t\t\t\t\tlabel: Some(&format!(\"{name} bind group\")),\n\t\t\t\t\t// `get_bind_group_layout` allocates unnecessary memory, we could create it manually to not do that\n\t\t\t\t\tlayout: &self.pipeline.get_bind_group_layout(0),\n\t\t\t\t\tentries,\n\t\t\t\t});\n\n\t\t\t\tlet tex_out = device.create_texture(&TextureDescriptor {\n\t\t\t\t\tlabel: Some(&format!(\"{name} texture out\")),\n\t\t\t\t\tsize: tex_in.size(),\n\t\t\t\t\tmip_level_count: 1,\n\t\t\t\t\tsample_count: 1,\n\t\t\t\t\tdimension: TextureDimension::D2,\n\t\t\t\t\tformat,\n\t\t\t\t\tusage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::RENDER_ATTACHMENT,\n\t\t\t\t\tview_formats: &[format],\n\t\t\t\t});\n\n\t\t\t\tlet view_out = tex_out.create_view(&TextureViewDescriptor::default());\n\t\t\t\tlet mut rp = cmd.begin_render_pass(&RenderPassDescriptor {\n\t\t\t\t\tlabel: Some(&format!(\"{name} render pipeline\")),\n\t\t\t\t\tcolor_attachments: &[Some(RenderPassColorAttachment {\n\t\t\t\t\t\tview: &view_out,\n\t\t\t\t\t\tresolve_target: None,\n\t\t\t\t\t\tops: Operations {\n\t\t\t\t\t\t\t// should be dont_care but wgpu doesn't expose that\n\t\t\t\t\t\t\tload: LoadOp::Clear(wgpu::Color::BLACK),\n\t\t\t\t\t\t\tstore: StoreOp::Store,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tdepth_slice: None,\n\t\t\t\t\t})],\n\t\t\t\t\tdepth_stencil_attachment: None,\n\t\t\t\t\ttimestamp_writes: None,\n\t\t\t\t\tocclusion_query_set: None,\n\t\t\t\t});\n\t\t\t\trp.set_pipeline(&self.pipeline);\n\t\t\t\trp.set_bind_group(0, Some(&bind_group), &[]);\n\t\t\t\trp.draw(0..3, 0..1);\n\n\t\t\t\tTableRow {\n\t\t\t\t\telement: Raster::new(GPU { texture: tex_out }),\n\t\t\t\t\ttransform: *instance.transform,\n\t\t\t\t\talpha_blending: *instance.alpha_blending,\n\t\t\t\t\tsource_node_id: *instance.source_node_id,\n\t\t\t\t}\n\t\t\t})\n\t\t\t.collect::<Table<_>>();\n\t\tcontext.queue.submit([cmd.finish()]);\n\t\tout\n\t}\n}\n"
  },
  {
    "path": "node-graph/libraries/wgpu-executor/src/texture_conversion.rs",
    "content": "use crate::WgpuExecutor;\nuse core_types::Color;\nuse core_types::Ctx;\nuse core_types::color::SRGBA8;\nuse core_types::ops::Convert;\nuse core_types::table::{Table, TableRow};\nuse core_types::transform::Footprint;\nuse raster_types::Image;\nuse raster_types::{CPU, GPU, Raster};\nuse wgpu::util::{DeviceExt, TextureDataOrder};\nuse wgpu::{Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages};\n\n/// Uploads CPU image data to a GPU texture\n///\n/// Creates a new WGPU texture with RGBA8UnormSrgb format and uploads the provided\n/// image data. The texture is configured for binding, copying, and source operations.\nfn upload_to_texture(device: &std::sync::Arc<wgpu::Device>, queue: &std::sync::Arc<wgpu::Queue>, image: &Raster<CPU>) -> wgpu::Texture {\n\tlet rgba8_data: Vec<SRGBA8> = image.data.iter().map(|x| (*x).into()).collect();\n\n\tdevice.create_texture_with_data(\n\t\tqueue,\n\t\t&TextureDescriptor {\n\t\t\tlabel: Some(\"upload_texture node texture\"),\n\t\t\tsize: Extent3d {\n\t\t\t\twidth: image.width,\n\t\t\t\theight: image.height,\n\t\t\t\tdepth_or_array_layers: 1,\n\t\t\t},\n\t\t\tmip_level_count: 1,\n\t\t\tsample_count: 1,\n\t\t\tdimension: TextureDimension::D2,\n\t\t\tformat: TextureFormat::Rgba8UnormSrgb,\n\t\t\tusage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST | TextureUsages::COPY_SRC,\n\t\t\tview_formats: &[],\n\t\t},\n\t\tTextureDataOrder::LayerMajor,\n\t\tbytemuck::cast_slice(rgba8_data.as_slice()),\n\t)\n}\n\n/// Converts a Raster<GPU> texture to Raster<CPU> by downloading the underlying texture data.\n///\n/// Assumptions:\n/// - 2D texture, mip level 0\n/// - 4 bytes-per-pixel RGBA8\n/// - Texture has COPY_SRC usage\nstruct RasterGpuToRasterCpuConverter {\n\tbuffer: wgpu::Buffer,\n\twidth: u32,\n\theight: u32,\n\tunpadded_bytes_per_row: u32,\n\tpadded_bytes_per_row: u32,\n}\nimpl RasterGpuToRasterCpuConverter {\n\tfn new(device: &std::sync::Arc<wgpu::Device>, encoder: &mut wgpu::CommandEncoder, data_gpu: Raster<GPU>) -> Self {\n\t\tlet texture = data_gpu.data();\n\t\tlet width = texture.width();\n\t\tlet height = texture.height();\n\t\tlet bytes_per_pixel = 4; // RGBA8\n\t\tlet unpadded_bytes_per_row = width * bytes_per_pixel;\n\t\tlet align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;\n\t\tlet padded_bytes_per_row = unpadded_bytes_per_row.div_ceil(align) * align;\n\t\tlet buffer_size = padded_bytes_per_row as u64 * height as u64;\n\n\t\tlet buffer = device.create_buffer(&wgpu::BufferDescriptor {\n\t\t\tlabel: Some(\"texture_download_buffer\"),\n\t\t\tsize: buffer_size,\n\t\t\tusage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,\n\t\t\tmapped_at_creation: false,\n\t\t});\n\n\t\tencoder.copy_texture_to_buffer(\n\t\t\twgpu::TexelCopyTextureInfo {\n\t\t\t\ttexture,\n\t\t\t\tmip_level: 0,\n\t\t\t\torigin: wgpu::Origin3d::ZERO,\n\t\t\t\taspect: wgpu::TextureAspect::All,\n\t\t\t},\n\t\t\twgpu::TexelCopyBufferInfo {\n\t\t\t\tbuffer: &buffer,\n\t\t\t\tlayout: wgpu::TexelCopyBufferLayout {\n\t\t\t\t\toffset: 0,\n\t\t\t\t\tbytes_per_row: Some(padded_bytes_per_row),\n\t\t\t\t\trows_per_image: Some(height),\n\t\t\t\t},\n\t\t\t},\n\t\t\tExtent3d {\n\t\t\t\twidth,\n\t\t\t\theight,\n\t\t\t\tdepth_or_array_layers: 1,\n\t\t\t},\n\t\t);\n\n\t\tSelf {\n\t\t\tbuffer,\n\t\t\twidth,\n\t\t\theight,\n\t\t\tunpadded_bytes_per_row,\n\t\t\tpadded_bytes_per_row,\n\t\t}\n\t}\n\n\tasync fn convert(self, device: &std::sync::Arc<wgpu::Device>) -> Result<Raster<CPU>, wgpu::BufferAsyncError> {\n\t\tlet buffer_slice = self.buffer.slice(..);\n\t\tlet (sender, receiver) = futures::channel::oneshot::channel();\n\t\tbuffer_slice.map_async(wgpu::MapMode::Read, move |result| {\n\t\t\tlet _ = sender.send(result);\n\t\t});\n\n\t\tlet _ = device.poll(wgpu::wgt::PollType::wait_indefinitely());\n\n\t\treceiver.await.expect(\"Failed to receive map result\")?;\n\n\t\tlet view = buffer_slice.get_mapped_range();\n\n\t\tlet row_stride = self.padded_bytes_per_row as usize;\n\t\tlet row_bytes = self.unpadded_bytes_per_row as usize;\n\t\tlet mut cpu_data: Vec<Color> = Vec::with_capacity((self.width * self.height) as usize);\n\t\tfor row in 0..self.height as usize {\n\t\t\tlet start = row * row_stride;\n\t\t\tlet row_slice = &view[start..start + row_bytes];\n\t\t\tfor px in row_slice.chunks_exact(4) {\n\t\t\t\tcpu_data.push(Color::from_rgba8_srgb(px[0], px[1], px[2], px[3]));\n\t\t\t}\n\t\t}\n\n\t\tdrop(view);\n\t\tself.buffer.unmap();\n\t\tlet cpu_image = Image {\n\t\t\tdata: cpu_data,\n\t\t\twidth: self.width,\n\t\t\theight: self.height,\n\t\t\tbase64_string: None,\n\t\t};\n\n\t\tOk(Raster::new_cpu(cpu_image))\n\t}\n}\n\n/// Passthrough conversion for GPU tables - no conversion needed\nimpl<'i> Convert<Table<Raster<GPU>>, &'i WgpuExecutor> for Table<Raster<GPU>> {\n\tasync fn convert(self, _: Footprint, _converter: &'i WgpuExecutor) -> Table<Raster<GPU>> {\n\t\tself\n\t}\n}\n\n/// Converts CPU raster table to GPU by uploading each image to a texture\nimpl<'i> Convert<Table<Raster<GPU>>, &'i WgpuExecutor> for Table<Raster<CPU>> {\n\tasync fn convert(self, _: Footprint, executor: &'i WgpuExecutor) -> Table<Raster<GPU>> {\n\t\tlet device = &executor.context.device;\n\t\tlet queue = &executor.context.queue;\n\t\tlet table = self\n\t\t\t.iter()\n\t\t\t.map(|row| {\n\t\t\t\tlet image = row.element;\n\t\t\t\tlet texture = upload_to_texture(device, queue, image);\n\n\t\t\t\tTableRow {\n\t\t\t\t\telement: Raster::new_gpu(texture),\n\t\t\t\t\ttransform: *row.transform,\n\t\t\t\t\talpha_blending: *row.alpha_blending,\n\t\t\t\t\tsource_node_id: *row.source_node_id,\n\t\t\t\t}\n\t\t\t})\n\t\t\t.collect();\n\n\t\tqueue.submit([]);\n\t\ttable\n\t}\n}\n\n/// Converts single CPU raster to GPU by uploading to texture\nimpl<'i> Convert<Raster<GPU>, &'i WgpuExecutor> for Raster<CPU> {\n\tasync fn convert(self, _: Footprint, executor: &'i WgpuExecutor) -> Raster<GPU> {\n\t\tlet device = &executor.context.device;\n\t\tlet queue = &executor.context.queue;\n\t\tlet texture = upload_to_texture(device, queue, &self);\n\n\t\tqueue.submit([]);\n\t\tRaster::new_gpu(texture)\n\t}\n}\n\n/// Passthrough conversion for CPU tables - no conversion needed\nimpl<'i> Convert<Table<Raster<CPU>>, &'i WgpuExecutor> for Table<Raster<CPU>> {\n\tasync fn convert(self, _: Footprint, _converter: &'i WgpuExecutor) -> Table<Raster<CPU>> {\n\t\tself\n\t}\n}\n\n/// Converts GPU raster table to CPU by downloading texture data in one go\n///\n/// then asynchronously maps all buffers and processes the results.\nimpl<'i> Convert<Table<Raster<CPU>>, &'i WgpuExecutor> for Table<Raster<GPU>> {\n\tasync fn convert(self, _: Footprint, executor: &'i WgpuExecutor) -> Table<Raster<CPU>> {\n\t\tlet device = &executor.context.device;\n\t\tlet queue = &executor.context.queue;\n\n\t\tlet mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {\n\t\t\tlabel: Some(\"batch_texture_download_encoder\"),\n\t\t});\n\n\t\tlet mut converters = Vec::new();\n\t\tlet mut rows_meta = Vec::new();\n\n\t\tfor row in self {\n\t\t\tlet gpu_raster = row.element;\n\t\t\tconverters.push(RasterGpuToRasterCpuConverter::new(device, &mut encoder, gpu_raster));\n\t\t\trows_meta.push(TableRow {\n\t\t\t\telement: (),\n\t\t\t\ttransform: row.transform,\n\t\t\t\talpha_blending: row.alpha_blending,\n\t\t\t\tsource_node_id: row.source_node_id,\n\t\t\t});\n\t\t}\n\n\t\tqueue.submit([encoder.finish()]);\n\n\t\tlet mut map_futures = Vec::new();\n\t\tfor converter in converters {\n\t\t\tmap_futures.push(converter.convert(device));\n\t\t}\n\n\t\tlet map_results = futures::future::try_join_all(map_futures)\n\t\t\t.await\n\t\t\t.map_err(|_| \"Failed to receive map result\")\n\t\t\t.expect(\"Buffer mapping communication failed\");\n\n\t\tmap_results\n\t\t\t.into_iter()\n\t\t\t.zip(rows_meta.into_iter())\n\t\t\t.map(|(element, row)| TableRow {\n\t\t\t\telement,\n\t\t\t\ttransform: row.transform,\n\t\t\t\talpha_blending: row.alpha_blending,\n\t\t\t\tsource_node_id: row.source_node_id,\n\t\t\t})\n\t\t\t.collect()\n\t}\n}\n\n/// Converts single GPU raster to CPU by downloading texture data\nimpl<'i> Convert<Raster<CPU>, &'i WgpuExecutor> for Raster<GPU> {\n\tasync fn convert(self, _: Footprint, executor: &'i WgpuExecutor) -> Raster<CPU> {\n\t\tlet device = &executor.context.device;\n\t\tlet queue = &executor.context.queue;\n\n\t\tlet mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {\n\t\t\tlabel: Some(\"single_texture_download_encoder\"),\n\t\t});\n\n\t\tlet converter = RasterGpuToRasterCpuConverter::new(device, &mut encoder, self);\n\n\t\tqueue.submit([encoder.finish()]);\n\n\t\tconverter.convert(device).await.expect(\"Failed to download texture data\")\n\t}\n}\n\n/// Uploads an raster texture from the CPU to the GPU. This is now deprecated and the Convert node should be used in the future.\n///\n/// Accepts either individual raster data or a table of raster elements and converts it to the GPU format using the WgpuExecutor's device and queue.\n#[node_macro::node(category(\"\"))]\npub async fn upload_texture<'a: 'n, T: Convert<Table<Raster<GPU>>, &'a WgpuExecutor>>(\n\t_: impl Ctx,\n\t#[implementations(Table<Raster<CPU>>, Table<Raster<GPU>>)] input: T,\n\texecutor: &'a WgpuExecutor,\n) -> Table<Raster<GPU>> {\n\tinput.convert(Footprint::DEFAULT, executor).await\n}\n"
  },
  {
    "path": "node-graph/node-macro/Cargo.toml",
    "content": "[package]\nname = \"node-macro\"\npublish = false\nversion = \"0.0.0\"\nrust-version = \"1.88\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\nedition = \"2024\"\nreadme = \"../../README.md\"\nhomepage = \"https://graphite.art\"\nrepository = \"https://github.com/GraphiteEditor/Graphite\"\nlicense = \"Apache-2.0\"\n\n[lib]\nproc-macro = true\n\n[features]\ndisable-registration = []\n\n[dependencies]\n# Workspace dependencies\nsyn = { workspace = true }\nproc-macro2 = { workspace = true }\nquote = { workspace = true }\nconvert_case = { workspace = true }\nstrum = { workspace = true }\nindoc = { workspace = true }\n\nproc-macro-crate = \"3.1.0\"\nproc-macro-error2 = \"2\"\n\n[dev-dependencies]\ncore-types = { workspace = true }\nno-std-types = { workspace = true }\n"
  },
  {
    "path": "node-graph/node-macro/src/buffer_struct.rs",
    "content": "use crate::crate_ident::CrateIdent;\nuse proc_macro2::{Ident, Span, TokenStream};\nuse quote::{ToTokens, format_ident, quote};\nuse std::collections::HashSet;\nuse syn::punctuated::Punctuated;\nuse syn::visit_mut::VisitMut;\nuse syn::{Fields, GenericParam, Generics, Item, ItemEnum, ItemStruct, Meta, MetaList, Path, PathSegment, Result, Token, TypeParam, TypeParamBound, visit_mut};\n\npub fn derive_buffer_struct(crate_ident: &CrateIdent, content: proc_macro::TokenStream) -> Result<TokenStream> {\n\tlet item = syn::parse::<Item>(content)?;\n\tmatch &item {\n\t\tItem::Enum(item) => derive_buffer_struct_enum(crate_ident, item),\n\t\tItem::Struct(item) => derive_buffer_struct_struct(crate_ident, item),\n\t\t_ => Err(syn::Error::new_spanned(&item, \"Expected a struct or an enum\")),\n\t}\n}\n\npub fn derive_buffer_struct_enum(crate_ident: &CrateIdent, item: &ItemEnum) -> Result<TokenStream> {\n\tlet gcore_shaders = crate_ident.gcore_shaders()?;\n\tlet mod_buffer_struct = quote!(#gcore_shaders::shaders::buffer_struct);\n\tlet reexport = quote!(#gcore_shaders::shaders::__private);\n\n\tif !item.generics.params.is_empty() {\n\t\treturn Err(syn::Error::new_spanned(&item.generics, \"enum must not have any generics\"));\n\t}\n\n\tlet enum_requirements_error = || {\n\t\tsyn::Error::new(\n\t\t\tSpan::call_site(),\n\t\t\t\"deriving `BufferStruct` on an enum requires `#[repr(u32)]` and `#[derive(num_enum::FromPrimitive, num_enum::IntoPrimitive)]`\",\n\t\t)\n\t};\n\tlet repr_path = Path::from(format_ident!(\"repr\"));\n\tlet repr = item\n\t\t.attrs\n\t\t.iter()\n\t\t.filter_map(|a| match &a.meta {\n\t\t\tMeta::List(MetaList { path, tokens, .. }) if *path == repr_path => Some(tokens),\n\t\t\t_ => None,\n\t\t})\n\t\t.next()\n\t\t.ok_or_else(enum_requirements_error)?;\n\n\tlet ident = &item.ident;\n\tOk(quote! {\n\t\tunsafe impl #mod_buffer_struct::BufferStruct for #ident\n\t\t{\n\t\t\ttype Buffer = #repr;\n\n\t\t\tfn write(from: Self) -> Self::Buffer {\n\t\t\t\t<#repr as From<Self>>::from(from)\n\t\t\t}\n\n\t\t\tfn read(from: Self::Buffer) -> Self {\n\t\t\t\t<Self as #reexport::num_enum::FromPrimitive>::from_primitive(from)\n\t\t\t}\n\t\t}\n\t})\n}\n\n/// see [`BufferStruct`] docs\n///\n/// This is also largely copied from my (@firestar99) project and adjusted\n///\n/// [`BufferStruct`]: `no_std_types::shaders::buffer_struct::BufferStruct`\npub fn derive_buffer_struct_struct(crate_ident: &CrateIdent, item: &ItemStruct) -> Result<TokenStream> {\n\tlet gcore_shaders = crate_ident.gcore_shaders()?;\n\tlet mod_buffer_struct = quote!(#gcore_shaders::shaders::buffer_struct);\n\tlet reexport = quote!(#gcore_shaders::shaders::__private);\n\n\tlet generics = item\n\t\t.generics\n\t\t.params\n\t\t.iter()\n\t\t.filter_map(|g| match g {\n\t\t\tGenericParam::Lifetime(_) => None,\n\t\t\tGenericParam::Type(t) => Some(t.ident.clone()),\n\t\t\tGenericParam::Const(c) => Some(c.ident.clone()),\n\t\t})\n\t\t.collect();\n\n\tlet mut members_buffer = Punctuated::<TokenStream, Token![,]>::new();\n\tlet mut write = Punctuated::<TokenStream, Token![,]>::new();\n\tlet mut read = Punctuated::<TokenStream, Token![,]>::new();\n\tlet mut gen_name_gen = GenericNameGen::new();\n\tlet mut gen_ref_tys = Vec::new();\n\tlet (members_buffer, write, read) = match &item.fields {\n\t\tFields::Named(named) => {\n\t\t\tfor f in &named.named {\n\t\t\t\tlet name = f.ident.as_ref().unwrap();\n\t\t\t\tlet mut ty = f.ty.clone();\n\t\t\t\tlet mut visitor = GenericsVisitor::new(&item.ident, &generics);\n\t\t\t\tvisit_mut::visit_type_mut(&mut visitor, &mut ty);\n\t\t\t\tif visitor.found_generics {\n\t\t\t\t\tgen_ref_tys.push(f.ty.clone());\n\t\t\t\t\tlet gen_ident = gen_name_gen.next();\n\t\t\t\t\tmembers_buffer.push(quote!(#name: #gen_ident));\n\t\t\t\t} else {\n\t\t\t\t\tmembers_buffer.push(quote! {\n\t\t\t\t\t\t#name: <#ty as #mod_buffer_struct::BufferStruct>::Buffer\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\twrite.push(quote! {\n\t\t\t\t\t#name: <#ty as #mod_buffer_struct::BufferStruct>::write(from.#name)\n\t\t\t\t});\n\t\t\t\tread.push(quote! {\n\t\t\t\t\t#name: <#ty as #mod_buffer_struct::BufferStruct>::read(from.#name)\n\t\t\t\t});\n\t\t\t}\n\t\t\t(quote!({#members_buffer}), quote!(Self::Buffer {#write}), quote!(Self {#read}))\n\t\t}\n\t\tFields::Unnamed(unnamed) => {\n\t\t\tfor (i, f) in unnamed.unnamed.iter().enumerate() {\n\t\t\t\tlet mut ty = f.ty.clone();\n\t\t\t\tlet mut visitor = GenericsVisitor::new(&item.ident, &generics);\n\t\t\t\tvisit_mut::visit_type_mut(&mut visitor, &mut ty);\n\t\t\t\tif visitor.found_generics {\n\t\t\t\t\tgen_ref_tys.push(f.ty.clone());\n\t\t\t\t\tmembers_buffer.push(gen_name_gen.next().into_token_stream());\n\t\t\t\t} else {\n\t\t\t\t\tmembers_buffer.push(quote! {\n\t\t\t\t\t\t<#ty as #mod_buffer_struct::BufferStruct>::Buffer\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tlet index = syn::Index::from(i);\n\t\t\t\twrite.push(quote! {\n\t\t\t\t\t<#ty as #mod_buffer_struct::BufferStruct>::write(from.#index)\n\t\t\t\t});\n\t\t\t\tread.push(quote! {\n\t\t\t\t\t<#ty as #mod_buffer_struct::BufferStruct>::read(from.#index)\n\t\t\t\t});\n\t\t\t}\n\t\t\t(quote!((#members_buffer);), quote!(Self::Buffer(#write)), quote!(Self(#read)))\n\t\t}\n\t\tFields::Unit => (quote!(;), quote!(let _ = from; Self::Buffer {}), quote!(let _ = from; Self::Shader {})),\n\t};\n\n\tlet generics_decl = &item.generics;\n\tlet generics_ref = decl_to_ref(item.generics.params.iter());\n\tlet generics_where = gen_ref_tys\n\t\t.iter()\n\t\t.map(|ty| quote!(#ty: #mod_buffer_struct::BufferStruct))\n\t\t.collect::<Punctuated<TokenStream, Token![,]>>()\n\t\t.into_token_stream();\n\n\tlet generics_decl_any = gen_name_gen.decl(quote! {\n\t\t#reexport::bytemuck::Pod + Send + Sync\n\t});\n\tlet generics_ref_buffer = gen_ref_tys\n\t\t.iter()\n\t\t.map(|ty| quote!(<#ty as #mod_buffer_struct::BufferStruct>::Buffer))\n\t\t.collect::<Punctuated<TokenStream, Token![,]>>()\n\t\t.into_token_stream();\n\n\tlet vis = &item.vis;\n\tlet ident = &item.ident;\n\tlet buffer_ident = format_ident!(\"{}Buffer\", ident);\n\tOk(quote! {\n\t\t#[repr(C)]\n\t\t#[derive(Copy, Clone, #reexport::bytemuck::Zeroable, #reexport::bytemuck::Pod)]\n\t\t#vis struct #buffer_ident #generics_decl_any #members_buffer\n\n\t\tunsafe impl #generics_decl #mod_buffer_struct::BufferStruct for #ident #generics_ref\n\t\twhere\n\t\t\t#ident #generics_ref: Copy,\n\t\t\t#generics_where\n\t\t{\n\t\t\ttype Buffer = #buffer_ident <#generics_ref_buffer>;\n\n\t\t\tfn write(from: Self) -> Self::Buffer {\n\t\t\t\t#write\n\t\t\t}\n\n\t\t\tfn read(from: Self::Buffer) -> Self {\n\t\t\t\t#read\n\t\t\t}\n\t\t}\n\t})\n}\n\nstruct GenericsVisitor<'a> {\n\tself_ident: &'a Ident,\n\tgenerics: &'a HashSet<Ident>,\n\tfound_generics: bool,\n}\n\nimpl<'a> GenericsVisitor<'a> {\n\tpub fn new(self_ident: &'a Ident, generics: &'a HashSet<Ident>) -> Self {\n\t\tSelf {\n\t\t\tself_ident,\n\t\t\tgenerics,\n\t\t\tfound_generics: false,\n\t\t}\n\t}\n}\n\nimpl VisitMut for GenericsVisitor<'_> {\n\tfn visit_ident_mut(&mut self, i: &mut Ident) {\n\t\tif self.generics.contains(i) {\n\t\t\tself.found_generics = true;\n\t\t}\n\t\tvisit_mut::visit_ident_mut(self, i);\n\t}\n\n\tfn visit_path_segment_mut(&mut self, i: &mut PathSegment) {\n\t\tif i.ident == \"Self\" {\n\t\t\ti.ident = self.self_ident.clone();\n\t\t}\n\t\tvisit_mut::visit_path_segment_mut(self, i);\n\t}\n}\n\nstruct GenericNameGen(u32);\n\nimpl GenericNameGen {\n\tpub fn new() -> Self {\n\t\tSelf(0)\n\t}\n\n\tpub fn next(&mut self) -> Ident {\n\t\tlet i = self.0;\n\t\tself.0 += 1;\n\t\tformat_ident!(\"T{}\", i)\n\t}\n\n\tpub fn decl(self, ty: TokenStream) -> Generics {\n\t\tlet params: Punctuated<GenericParam, Token![,]> = (0..self.0)\n\t\t\t.map(|i| {\n\t\t\t\tGenericParam::Type(TypeParam {\n\t\t\t\t\tattrs: Vec::new(),\n\t\t\t\t\tident: format_ident!(\"T{}\", i),\n\t\t\t\t\tcolon_token: Some(Default::default()),\n\t\t\t\t\tbounds: Punctuated::from_iter([TypeParamBound::Verbatim(ty.clone())]),\n\t\t\t\t\teq_token: None,\n\t\t\t\t\tdefault: None,\n\t\t\t\t})\n\t\t\t})\n\t\t\t.collect();\n\t\tif !params.is_empty() {\n\t\t\tGenerics {\n\t\t\t\tlt_token: Some(Default::default()),\n\t\t\t\tparams,\n\t\t\t\tgt_token: Some(Default::default()),\n\t\t\t\twhere_clause: None,\n\t\t\t}\n\t\t} else {\n\t\t\tGenerics::default()\n\t\t}\n\t}\n}\n\nfn decl_to_ref<'a>(generics: impl Iterator<Item = &'a GenericParam>) -> TokenStream {\n\tlet out = generics\n\t\t.map(|generic| match generic {\n\t\t\tGenericParam::Lifetime(l) => l.lifetime.to_token_stream(),\n\t\t\tGenericParam::Type(t) => t.ident.to_token_stream(),\n\t\t\tGenericParam::Const(c) => c.ident.to_token_stream(),\n\t\t})\n\t\t.collect::<Punctuated<TokenStream, Token![,]>>();\n\tif out.is_empty() { TokenStream::new() } else { quote!(<#out>) }\n}\n"
  },
  {
    "path": "node-graph/node-macro/src/codegen.rs",
    "content": "use crate::parsing::*;\nuse convert_case::{Case, Casing};\nuse proc_macro2::TokenStream as TokenStream2;\nuse quote::{ToTokens, format_ident, quote, quote_spanned};\nuse std::sync::atomic::AtomicU64;\nuse syn::punctuated::Punctuated;\nuse syn::spanned::Spanned;\nuse syn::token::Comma;\nuse syn::{Error, Ident, PatIdent, Token, WhereClause, WherePredicate, parse_quote};\nstatic NODE_ID: AtomicU64 = AtomicU64::new(0);\n\npub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn) -> syn::Result<TokenStream2> {\n\tlet ParsedNodeFn {\n\t\tvis,\n\t\tattributes,\n\t\tfn_name,\n\t\tstruct_name,\n\t\tmod_name,\n\t\tfn_generics,\n\t\twhere_clause,\n\t\tinput,\n\t\toutput_type,\n\t\tis_async,\n\t\tfields,\n\t\tbody,\n\t\tdescription,\n\t\t..\n\t} = parsed;\n\tlet core_types = crate_ident.gcore()?;\n\n\tlet category = attributes\n\t\t.category\n\t\t.as_ref()\n\t\t.expect(\"The 'category' attribute is required and should be checked during parsing, but was not found during codegen\");\n\tlet mod_name = format_ident!(\"_{}_mod\", mod_name);\n\n\tlet display_name = match &attributes.display_name.as_ref() {\n\t\tSome(lit) => lit.value(),\n\t\tNone => struct_name.to_string().to_case(Case::Title),\n\t};\n\tlet struct_name = format_ident!(\"{}Node\", struct_name);\n\n\t// Separate data fields from regular fields\n\tlet (data_fields, regular_fields): (Vec<_>, Vec<_>) = fields.iter().partition(|f| f.is_data_field);\n\n\t// Extract function generics used by data fields\n\tlet data_field_generics: Vec<_> = fn_generics\n\t\t.iter()\n\t\t.filter(|generic| {\n\t\t\tlet generic_ident = match generic {\n\t\t\t\tsyn::GenericParam::Type(type_param) => &type_param.ident,\n\t\t\t\t_ => return false,\n\t\t\t};\n\n\t\t\t// Check if this generic is used in any data field type\n\t\t\tdata_fields.iter().any(|field| match &field.ty {\n\t\t\t\tParsedFieldType::Regular(RegularParsedField { ty, .. }) => type_contains_ident(ty, generic_ident),\n\t\t\t\t_ => false,\n\t\t\t})\n\t\t})\n\t\t.cloned()\n\t\t.collect();\n\n\t// Node generics for regular fields (Node0, Node1, ...)\n\tlet node_generics: Vec<Ident> = regular_fields.iter().enumerate().map(|(i, _)| format_ident!(\"Node{}\", i)).collect();\n\n\t// Extract just the idents from data_field_generics for struct type parameters\n\tlet data_field_generic_idents: Vec<Ident> = data_field_generics\n\t\t.iter()\n\t\t.filter_map(|gp| match gp {\n\t\t\tsyn::GenericParam::Type(tp) => Some(tp.ident.clone()),\n\t\t\t_ => None,\n\t\t})\n\t\t.collect();\n\n\t// Combined struct type parameters: data field generic idents (T, U, ...) + node generics (Node0, Node1, ...)\n\t// For struct type instantiation: MemoNode<T, Node0>\n\tlet struct_type_params: Vec<Ident> = data_field_generic_idents.iter().cloned().chain(node_generics.iter().cloned()).collect();\n\n\t// Combined struct generic parameters with bounds for struct definition\n\t// struct MemoNode<T: Clone, Node0>\n\tlet struct_generic_params: Vec<TokenStream2> = data_field_generics.iter().map(|gp| quote!(#gp)).chain(node_generics.iter().map(|id| quote!(#id))).collect();\n\tlet input_ident = &input.pat_ident;\n\n\tlet context_features = &input.context_features;\n\n\t// Regular field idents and names (for function parameters)\n\tlet field_idents: Vec<_> = regular_fields.iter().map(|f| &f.pat_ident).collect();\n\tlet field_names: Vec<_> = field_idents.iter().map(|pat_ident| &pat_ident.ident).collect();\n\tlet regular_field_names: Vec<_> = regular_fields.iter().map(|f| &f.pat_ident.ident).collect();\n\tlet data_field_names: Vec<_> = data_fields.iter().map(|f| &f.pat_ident.ident).collect();\n\n\t// Only regular fields have input names/descriptions (for UI)\n\tlet input_names: Vec<_> = regular_fields\n\t\t.iter()\n\t\t.map(|f| &f.name)\n\t\t.zip(regular_field_names.iter())\n\t\t.map(|zipped| match zipped {\n\t\t\t(Some(name), _) => name.value(),\n\t\t\t(_, name) => name.to_string().to_case(Case::Title),\n\t\t})\n\t\t.collect();\n\n\tlet input_hidden = regular_field_names.iter().map(|name| name.to_string().starts_with('_')).collect::<Vec<_>>();\n\n\tlet input_descriptions: Vec<_> = regular_fields.iter().map(|f| &f.description).collect();\n\n\t// Generate struct fields: data fields (concrete types) + regular fields (generic types)\n\tlet data_field_defs = data_fields.iter().map(|field| {\n\t\tlet name = &field.pat_ident.ident;\n\t\tlet ty = match &field.ty {\n\t\t\tParsedFieldType::Regular(RegularParsedField { ty, .. }) => ty,\n\t\t\t_ => unreachable!(\"Data fields must be Regular types, not Node types\"),\n\t\t};\n\t\tquote! { pub(super) #name: #ty }\n\t});\n\n\tlet regular_field_defs = regular_field_names.iter().zip(node_generics.iter()).map(|(name, r#gen)| {\n\t\tquote! { pub(super) #name: #r#gen }\n\t});\n\n\tlet struct_fields = data_field_defs.chain(regular_field_defs);\n\n\tlet mut future_idents = Vec::new();\n\n\t// Data fields get passed as references to the underlying function\n\tlet data_field_idents: Vec<_> = data_fields.iter().map(|f| &f.pat_ident).collect();\n\tlet data_field_types: Vec<_> = data_fields\n\t\t.iter()\n\t\t.map(|field| match &field.ty {\n\t\t\tParsedFieldType::Regular(RegularParsedField { ty, .. }) => {\n\t\t\t\tlet ty = ty.clone();\n\t\t\t\tquote!(&#ty)\n\t\t\t}\n\t\t\t_ => unreachable!(\"Data fields must be Regular types, not Node types\"),\n\t\t})\n\t\t.collect();\n\n\t// Regular fields have types passed to the function\n\tlet field_types: Vec<_> = regular_fields\n\t\t.iter()\n\t\t.map(|field| match &field.ty {\n\t\t\tParsedFieldType::Regular(RegularParsedField { ty, .. }) => ty.clone(),\n\t\t\tParsedFieldType::Node(NodeParsedField { output_type, input_type, .. }) => match parsed.is_async {\n\t\t\t\ttrue => parse_quote!(&'n impl #core_types::Node<'n, #input_type, Output = impl core::future::Future<Output=#output_type>>),\n\t\t\t\tfalse => parse_quote!(&'n impl #core_types::Node<'n, #input_type, Output = #output_type>),\n\t\t\t},\n\t\t})\n\t\t.collect();\n\n\t// Only regular fields have UI metadata (data fields are internal state)\n\tlet widget_override: Vec<_> = regular_fields\n\t\t.iter()\n\t\t.map(|field| match &field.widget_override {\n\t\t\tParsedWidgetOverride::None => quote!(RegistryWidgetOverride::None),\n\t\t\tParsedWidgetOverride::Hidden => quote!(RegistryWidgetOverride::Hidden),\n\t\t\tParsedWidgetOverride::String(lit_str) => quote!(RegistryWidgetOverride::String(#lit_str)),\n\t\t\tParsedWidgetOverride::Custom(lit_str) => quote!(RegistryWidgetOverride::Custom(#lit_str)),\n\t\t})\n\t\t.collect();\n\n\tlet value_sources: Vec<_> = regular_fields\n\t\t.iter()\n\t\t.map(|field| match &field.ty {\n\t\t\tParsedFieldType::Regular(RegularParsedField { value_source, .. }) => match value_source {\n\t\t\t\tParsedValueSource::Default(data) => {\n\t\t\t\t\t// Check if the data is a string literal by parsing the token stream\n\t\t\t\t\tlet data_str = data.to_string();\n\t\t\t\t\tif data_str.starts_with('\"') && data_str.ends_with('\"') && data_str.len() >= 2 {\n\t\t\t\t\t\tquote!(RegistryValueSource::Default(#data))\n\t\t\t\t\t} else {\n\t\t\t\t\t\tquote!(RegistryValueSource::Default(stringify!(#data)))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tParsedValueSource::Scope(data) => quote!(RegistryValueSource::Scope(#data)),\n\t\t\t\t_ => quote!(RegistryValueSource::None),\n\t\t\t},\n\t\t\t_ => quote!(RegistryValueSource::None),\n\t\t})\n\t\t.collect();\n\n\tlet default_types: Vec<_> = regular_fields\n\t\t.iter()\n\t\t.map(|field| match &field.ty {\n\t\t\tParsedFieldType::Regular(RegularParsedField { implementations, .. }) => match implementations.first() {\n\t\t\t\tSome(ty) => quote!(Some(concrete!(#ty))),\n\t\t\t\t_ => quote!(None),\n\t\t\t},\n\t\t\t_ => quote!(None),\n\t\t})\n\t\t.collect();\n\n\tlet number_min_values: Vec<_> = regular_fields\n\t\t.iter()\n\t\t.map(|field| match &field.ty {\n\t\t\tParsedFieldType::Regular(RegularParsedField { number_soft_min, number_hard_min, .. }) => match (number_soft_min, number_hard_min) {\n\t\t\t\t(Some(soft_min), _) => quote!(Some(#soft_min)),\n\t\t\t\t(None, Some(hard_min)) => quote!(Some(#hard_min)),\n\t\t\t\t(None, None) => quote!(None),\n\t\t\t},\n\t\t\t_ => quote!(None),\n\t\t})\n\t\t.collect();\n\tlet number_max_values: Vec<_> = regular_fields\n\t\t.iter()\n\t\t.map(|field| match &field.ty {\n\t\t\tParsedFieldType::Regular(RegularParsedField { number_soft_max, number_hard_max, .. }) => match (number_soft_max, number_hard_max) {\n\t\t\t\t(Some(soft_max), _) => quote!(Some(#soft_max)),\n\t\t\t\t(None, Some(hard_max)) => quote!(Some(#hard_max)),\n\t\t\t\t(None, None) => quote!(None),\n\t\t\t},\n\t\t\t_ => quote!(None),\n\t\t})\n\t\t.collect();\n\tlet number_mode_range_values: Vec<_> = regular_fields\n\t\t.iter()\n\t\t.map(|field| match &field.ty {\n\t\t\tParsedFieldType::Regular(RegularParsedField {\n\t\t\t\tnumber_mode_range: Some(number_mode_range),\n\t\t\t\t..\n\t\t\t}) => quote!(Some(#number_mode_range)),\n\t\t\t_ => quote!(None),\n\t\t})\n\t\t.collect();\n\tlet number_display_decimal_places: Vec<_> = regular_fields\n\t\t.iter()\n\t\t.map(|field| field.number_display_decimal_places.as_ref().map_or(quote!(None), |i| quote!(Some(#i))))\n\t\t.collect();\n\tlet number_step: Vec<_> = regular_fields.iter().map(|field| field.number_step.as_ref().map_or(quote!(None), |i| quote!(Some(#i)))).collect();\n\n\tlet unit_suffix: Vec<_> = regular_fields.iter().map(|field| field.unit.as_ref().map_or(quote!(None), |i| quote!(Some(#i)))).collect();\n\n\tlet exposed: Vec<_> = regular_fields\n\t\t.iter()\n\t\t.map(|field| match &field.ty {\n\t\t\tParsedFieldType::Regular(RegularParsedField { exposed, .. }) => quote!(#exposed),\n\t\t\t_ => quote!(true),\n\t\t})\n\t\t.collect();\n\n\t// Only eval regular fields (data fields are accessed directly as self.field_name)\n\tlet eval_args = regular_fields.iter().map(|field| {\n\t\tlet name = &field.pat_ident.ident;\n\t\tmatch &field.ty {\n\t\t\tParsedFieldType::Regular { .. } => {\n\t\t\t\tquote! { let #name = self.#name.eval(__input.clone()).await; }\n\t\t\t}\n\t\t\tParsedFieldType::Node { .. } => {\n\t\t\t\tquote! { let #name = &self.#name; }\n\t\t\t}\n\t\t}\n\t});\n\n\t// Only regular fields can have min/max constraints\n\tlet min_max_args = regular_fields.iter().map(|field| match &field.ty {\n\t\tParsedFieldType::Regular(RegularParsedField { number_hard_min, number_hard_max, .. }) => {\n\t\t\tlet name = &field.pat_ident.ident;\n\t\t\tlet mut tokens = quote!();\n\t\t\tif let Some(min) = number_hard_min {\n\t\t\t\ttokens.extend(quote_spanned! {min.span()=>\n\t\t\t\t\tlet #name = #core_types::misc::Clampable::clamp_hard_min(#name, #min);\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif let Some(max) = number_hard_max {\n\t\t\t\ttokens.extend(quote_spanned! {max.span()=>\n\t\t\t\t\tlet #name = #core_types::misc::Clampable::clamp_hard_max(#name, #max);\n\t\t\t\t});\n\t\t\t}\n\t\t\ttokens\n\t\t}\n\t\tParsedFieldType::Node { .. } => quote!(),\n\t});\n\n\tlet all_implementation_types = fields.iter().flat_map(|field| match &field.ty {\n\t\tParsedFieldType::Regular(RegularParsedField { implementations, .. }) => implementations.iter().cloned().collect::<Vec<_>>(),\n\t\tParsedFieldType::Node(NodeParsedField { implementations, .. }) => implementations\n\t\t\t.iter()\n\t\t\t.flat_map(|implementation| [implementation.input.clone(), implementation.output.clone()])\n\t\t\t.collect(),\n\t});\n\tlet all_implementation_types = all_implementation_types.chain(input.implementations.iter().cloned());\n\n\tlet input_type = &parsed.input.ty;\n\tlet mut clauses = Vec::new();\n\tlet mut clampable_clauses = Vec::new();\n\n\tfor (field, name) in regular_fields.iter().zip(node_generics.iter()) {\n\t\tclauses.push(match (&field.ty, *is_async) {\n\t\t\t(\n\t\t\t\tParsedFieldType::Regular(RegularParsedField {\n\t\t\t\t\tty, number_hard_min, number_hard_max, ..\n\t\t\t\t}),\n\t\t\t\t_,\n\t\t\t) => {\n\t\t\t\tlet all_lifetime_ty = substitute_lifetimes(ty.clone(), \"all\");\n\t\t\t\tlet id = future_idents.len();\n\t\t\t\tlet fut_ident = format_ident!(\"F{}\", id);\n\t\t\t\tfuture_idents.push(fut_ident.clone());\n\n\t\t\t\t// Add Clampable bound if this field uses hard_min or hard_max\n\t\t\t\tif number_hard_min.is_some() || number_hard_max.is_some() {\n\t\t\t\t\t// The bound applies to the Output type of the future, which is #ty\n\t\t\t\t\tclampable_clauses.push(quote!(#ty: #core_types::misc::Clampable));\n\t\t\t\t}\n\n\t\t\t\tquote!(\n\t\t\t\t\t#fut_ident: core::future::Future<Output = #ty> + #core_types::WasmNotSend + 'n,\n\t\t\t\t\tfor<'all> #all_lifetime_ty: #core_types::WasmNotSend,\n\t\t\t\t\t#name: #core_types::Node<'n, #input_type, Output = #fut_ident> + #core_types::WasmNotSync\n\t\t\t\t)\n\t\t\t}\n\t\t\t(ParsedFieldType::Node(NodeParsedField { input_type, output_type, .. }), true) => {\n\t\t\t\tlet id = future_idents.len();\n\t\t\t\tlet fut_ident = format_ident!(\"F{}\", id);\n\t\t\t\tfuture_idents.push(fut_ident.clone());\n\n\t\t\t\tquote!(\n\t\t\t\t\t#fut_ident: core::future::Future<Output = #output_type> + #core_types::WasmNotSend + 'n,\n\t\t\t\t\t#name: #core_types::Node<'n, #input_type, Output = #fut_ident > + #core_types::WasmNotSync\n\t\t\t\t)\n\t\t\t}\n\t\t\t(ParsedFieldType::Node { .. }, false) => unreachable!(\"Found node which takes an impl Node<> input but is not async\"),\n\t\t});\n\t}\n\tlet where_clause = where_clause.clone().unwrap_or(WhereClause {\n\t\twhere_token: Token![where](output_type.span()),\n\t\tpredicates: Default::default(),\n\t});\n\n\tlet mut struct_where_clause = where_clause.clone();\n\tlet extra_where: Punctuated<WherePredicate, Comma> = parse_quote!(\n\t\t#(#clauses,)*\n\t\t#(#clampable_clauses,)*\n\t\t#output_type: 'n,\n\t);\n\tstruct_where_clause.predicates.extend(extra_where);\n\n\t// Only regular fields are parameters to new()\n\tlet new_args = node_generics.iter().zip(regular_field_names.iter()).map(|(r#gen, name)| {\n\t\tquote! { #name: #r#gen }\n\t});\n\n\t// Initialize data fields with Default, regular fields with parameters\n\tlet data_inits = data_field_names.iter().map(|name| {\n\t\tquote! { #name: Default::default() }\n\t});\n\tlet regular_inits = regular_field_names.iter().map(|name| {\n\t\tquote! { #name }\n\t});\n\tlet all_field_inits = data_inits.chain(regular_inits);\n\n\tlet async_keyword = is_async.then(|| quote!(async));\n\tlet await_keyword = is_async.then(|| quote!(.await));\n\n\t// Data fields may not implement Copy, PartialEq, etc., so only derive Debug and Clone\n\tlet struct_derives = if data_fields.is_empty() {\n\t\tquote!(#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)])\n\t} else {\n\t\tquote!(#[derive(Debug, Clone)])\n\t};\n\n\t// Generate serialize method if serialize attribute is specified\n\tlet serialize_impl = if let Some(serialize_fn) = &parsed.attributes.serialize {\n\t\tlet data_field_refs = data_field_names.iter().map(|name| quote!(&self.#name));\n\t\tquote! {\n\t\t\tfn serialize(&self) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {\n\t\t\t\t#serialize_fn(#(#data_field_refs),*)\n\t\t\t}\n\t\t}\n\t} else {\n\t\tquote!()\n\t};\n\n\tlet eval_impl = quote! {\n\t\ttype Output = #core_types::registry::DynFuture<'n, #output_type>;\n\t\t#[inline]\n\t\tfn eval(&'n self, __input: #input_type) -> Self::Output {\n\t\t\tBox::pin(async move {\n\t\t\t\tuse #core_types::misc::Clampable;\n\n\t\t\t\t#(#eval_args)*\n\t\t\t\t#(#min_max_args)*\n\t\t\t\tself::#fn_name(__input #(, &self.#data_field_names)* #(, #regular_field_names)*) #await_keyword\n\t\t\t})\n\t\t}\n\n\t\t#serialize_impl\n\t};\n\n\tlet identifier = format_ident!(\"{}_proto_ident\", fn_name);\n\tlet identifier_path = match parsed.attributes.path.as_ref() {\n\t\tSome(path) => {\n\t\t\tlet path = path.to_token_stream().to_string().replace(' ', \"\");\n\t\t\tquote!(#path)\n\t\t}\n\t\tNone => quote!(std::module_path!()),\n\t};\n\n\tlet register_node_impl = generate_register_node_impl(parsed, &field_names, &struct_name, &identifier)?;\n\tlet import_name = format_ident!(\"_IMPORT_STUB_{}\", mod_name.to_string().to_case(Case::UpperSnake));\n\n\tlet properties = &attributes.properties_string.as_ref().map(|value| quote!(Some(#value))).unwrap_or(quote!(None));\n\n\tlet cfg = crate::shader_nodes::modify_cfg(attributes);\n\tlet node_input_accessor = generate_node_input_references(parsed, fn_generics, &field_idents, core_types, &identifier, &cfg);\n\tlet ShaderTokens { shader_entry_point, gpu_node } = attributes.shader_node.as_ref().map(|n| n.codegen(crate_ident, parsed)).unwrap_or(Ok(ShaderTokens::default()))?;\n\n\tOk(quote! {\n\t\t/// Underlying implementation for [#struct_name]\n\t\t#[inline]\n\t\t#[allow(clippy::too_many_arguments)]\n\t\t#vis #async_keyword fn #fn_name <'n, #(#fn_generics,)*> (#input_ident: #input_type #(, #data_field_idents: #data_field_types)* #(, #field_idents: #field_types)*) -> #output_type #where_clause #body\n\n\t\t#cfg\n\t\t#[automatically_derived]\n\t\timpl<'n, #(#fn_generics,)* #(#node_generics,)* #(#future_idents,)*> #core_types::Node<'n, #input_type> for #mod_name::#struct_name<#(#struct_type_params,)*>\n\t\t#struct_where_clause\n\t\t{\n\t\t\t#eval_impl\n\t\t}\n\n\t\t#cfg\n\t\tconst fn #identifier() -> #core_types::ProtoNodeIdentifier {\n\t\t\t#core_types::ProtoNodeIdentifier::new(std::concat!(#identifier_path, \"::\", std::stringify!(#struct_name)))\n\t\t}\n\n\t\t#cfg\n\t\t#[doc(inline)]\n\t\tpub use #mod_name::#struct_name;\n\n\t\t#[doc(hidden)]\n\t\t#node_input_accessor\n\n\t\t#cfg\n\t\t#[doc(hidden)]\n\t\t#[allow(clippy::module_inception)]\n\t\tmod #mod_name {\n\t\t\tuse super::*;\n\t\t\tuse #core_types as gcore;\n\t\t\tuse gcore::{Node, NodeIOTypes, concrete, fn_type, fn_type_fut, future, ProtoNodeIdentifier, WasmNotSync, NodeIO, ContextFeature};\n\t\t\tuse gcore::value::ClonedNode;\n\t\t\tuse gcore::ops::TypeNode;\n\t\t\tuse gcore::registry::{NodeMetadata, FieldMetadata, NODE_REGISTRY, NODE_METADATA, DynAnyNode, DowncastBothNode, DynFuture, TypeErasedBox, PanicNode, RegistryValueSource, RegistryWidgetOverride};\n\t\t\tuse gcore::ctor::ctor;\n\n\t\t\t// Use the types specified in the implementation\n\n\t\t\tstatic #import_name: core::marker::PhantomData<(#(#all_implementation_types,)*)> = core::marker::PhantomData;\n\n\t\t\t#struct_derives\n\t\t\tpub struct #struct_name<#(#struct_generic_params,)*> {\n\t\t\t\t#(#struct_fields,)*\n\t\t\t}\n\n\t\t\t#[automatically_derived]\n\t\t\timpl<'n, #(#struct_generic_params,)*> #struct_name<#(#struct_type_params,)*>\n\t\t\t{\n\t\t\t\t#[allow(clippy::too_many_arguments)]\n\t\t\t\tpub fn new(#(#new_args,)*) -> Self {\n\t\t\t\t\tSelf {\n\t\t\t\t\t\t#(#all_field_inits,)*\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t#register_node_impl\n\n\t\t\t#[cfg_attr(not(target_family = \"wasm\"), ctor)]\n\t\t\tfn register_metadata() {\n\t\t\t\tlet metadata = NodeMetadata {\n\t\t\t\t\tdisplay_name: #display_name,\n\t\t\t\t\tcategory: #category,\n\t\t\t\t\tdescription: #description,\n\t\t\t\t\tproperties: #properties,\n\t\t\t\t\tcontext_features: vec![#(ContextFeature::#context_features,)*],\n\t\t\t\t\tfields: vec![\n\t\t\t\t\t\t#(\n\t\t\t\t\t\t\tFieldMetadata {\n\t\t\t\t\t\t\t\tname: #input_names,\n\t\t\t\t\t\t\t\twidget_override: #widget_override,\n\t\t\t\t\t\t\t\tdescription: #input_descriptions,\n\t\t\t\t\t\t\t\thidden: #input_hidden,\n\t\t\t\t\t\t\t\texposed: #exposed,\n\t\t\t\t\t\t\t\tvalue_source: #value_sources,\n\t\t\t\t\t\t\t\tdefault_type: #default_types,\n\t\t\t\t\t\t\t\tnumber_min: #number_min_values,\n\t\t\t\t\t\t\t\tnumber_max: #number_max_values,\n\t\t\t\t\t\t\t\tnumber_mode_range: #number_mode_range_values,\n\t\t\t\t\t\t\t\tnumber_display_decimal_places: #number_display_decimal_places,\n\t\t\t\t\t\t\t\tnumber_step: #number_step,\n\t\t\t\t\t\t\t\tunit: #unit_suffix,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t)*\n\t\t\t\t\t],\n\t\t\t\t};\n\t\t\t\tNODE_METADATA.lock().unwrap().insert(#identifier(), metadata);\n\t\t\t}\n\t\t}\n\n\t\t#shader_entry_point\n\n\t\t#gpu_node\n\t})\n}\n\n/// Generates strongly typed utilites to access inputs\nfn generate_node_input_references(\n\tparsed: &ParsedNodeFn,\n\tfn_generics: &[crate::GenericParam],\n\tfield_idents: &[&PatIdent],\n\tcore_types: &TokenStream2,\n\tidentifier: &Ident,\n\tcfg: &TokenStream2,\n) -> TokenStream2 {\n\tlet inputs_module_name = format_ident!(\"{}\", parsed.struct_name.to_string().to_case(Case::Snake));\n\n\tlet mut generated_input_accessor = Vec::new();\n\tif !parsed.attributes.skip_impl {\n\t\tlet (mut modified, mut generic_collector) = FilterUsedGenerics::new(fn_generics);\n\n\t\tfor (input_index, (parsed_input, input_ident)) in parsed.fields.iter().zip(field_idents).enumerate() {\n\t\t\tlet mut ty = match &parsed_input.ty {\n\t\t\t\tParsedFieldType::Regular(RegularParsedField { ty, .. }) => ty,\n\t\t\t\tParsedFieldType::Node(NodeParsedField { output_type, .. }) => output_type,\n\t\t\t}\n\t\t\t.clone();\n\n\t\t\t// We only want the necessary generics.\n\t\t\tlet used = generic_collector.filter_unnecessary_generics(&mut modified, &mut ty);\n\t\t\t// TODO: figure out a better name that doesn't conflict with so many types\n\t\t\tlet struct_name = format_ident!(\"{}Input\", input_ident.ident.to_string().to_case(Case::Pascal));\n\t\t\tlet (fn_generic_params, phantom_data_declerations) = generate_phantom_data(used.iter());\n\n\t\t\t// Only create structs with phantom data where necessary.\n\t\t\tgenerated_input_accessor.push(if phantom_data_declerations.is_empty() {\n\t\t\t\tquote! {\n\t\t\t\t\tpub struct #struct_name;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tquote! {\n\t\t\t\t\tpub struct #struct_name <#(#used),*>{\n\t\t\t\t\t\t#(#phantom_data_declerations,)*\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t\tgenerated_input_accessor.push(quote! {\n\t\t\t\timpl <#(#used),*> #core_types::NodeInputDecleration for #struct_name <#(#fn_generic_params),*> {\n\t\t\t\t\tconst INDEX: usize = #input_index;\n\t\t\t\t\tfn identifier() -> #core_types::ProtoNodeIdentifier {\n\t\t\t\t\t\t#inputs_module_name::IDENTIFIER.clone()\n\t\t\t\t\t}\n\t\t\t\t\ttype Result = #ty;\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t}\n\n\tquote! {\n\t\t#cfg\n\t\tpub mod #inputs_module_name {\n\t\t\tuse super::*;\n\n\t\t\t/// The `ProtoNodeIdentifier` of this node without any generics attached to it\n\t\t\tpub const IDENTIFIER: #core_types::ProtoNodeIdentifier = #identifier();\n\t\t\t#(#generated_input_accessor)*\n\t\t}\n\t}\n}\n\n/// It is necessary to generate PhantomData for each fn generic to avoid compiler errors.\nfn generate_phantom_data<'a>(fn_generics: impl Iterator<Item = &'a crate::GenericParam>) -> (Vec<TokenStream2>, Vec<TokenStream2>) {\n\tlet mut phantom_data_declerations = Vec::new();\n\tlet mut fn_generic_params = Vec::new();\n\n\tfor fn_generic_param in fn_generics {\n\t\tlet field_name = format_ident!(\"phantom_{}\", phantom_data_declerations.len());\n\n\t\tmatch fn_generic_param {\n\t\t\tcrate::GenericParam::Lifetime(lifetime_param) => {\n\t\t\t\tlet lifetime = &lifetime_param.lifetime;\n\n\t\t\t\tfn_generic_params.push(quote! {#lifetime});\n\t\t\t\tphantom_data_declerations.push(quote! {#field_name: core::marker::PhantomData<&#lifetime ()>})\n\t\t\t}\n\t\t\tcrate::GenericParam::Type(type_param) => {\n\t\t\t\tlet generic_name = &type_param.ident;\n\n\t\t\t\tfn_generic_params.push(quote! {#generic_name});\n\t\t\t\tphantom_data_declerations.push(quote! {#field_name: core::marker::PhantomData<#generic_name>});\n\t\t\t}\n\t\t\t_ => {}\n\t\t}\n\t}\n\t(fn_generic_params, phantom_data_declerations)\n}\n\nfn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], struct_name: &Ident, identifier: &Ident) -> Result<TokenStream2, Error> {\n\tif parsed.attributes.skip_impl {\n\t\treturn Ok(quote!());\n\t}\n\n\tlet mut constructors = Vec::new();\n\tlet unit = parse_quote!(gcore::Context);\n\n\tlet regular_fields: Vec<_> = parsed.fields.iter().filter(|f| !f.is_data_field).collect();\n\n\tlet parameter_types: Vec<_> = regular_fields\n\t\t.iter()\n\t\t.map(|field| {\n\t\t\tmatch &field.ty {\n\t\t\t\tParsedFieldType::Regular(RegularParsedField { implementations, ty, .. }) => {\n\t\t\t\t\tif !implementations.is_empty() {\n\t\t\t\t\t\timplementations.iter().map(|ty| (&unit, ty)).collect()\n\t\t\t\t\t} else {\n\t\t\t\t\t\tvec![(&unit, ty)]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tParsedFieldType::Node(NodeParsedField {\n\t\t\t\t\timplementations,\n\t\t\t\t\tinput_type,\n\t\t\t\t\toutput_type,\n\t\t\t\t\t..\n\t\t\t\t}) => {\n\t\t\t\t\tif !implementations.is_empty() {\n\t\t\t\t\t\timplementations.iter().map(|impl_| (&impl_.input, &impl_.output)).collect()\n\t\t\t\t\t} else {\n\t\t\t\t\t\tvec![(input_type, output_type)]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t.into_iter()\n\t\t\t.map(|(input, out)| (substitute_lifetimes(input.clone(), \"_\"), substitute_lifetimes(out.clone(), \"_\")))\n\t\t\t.collect::<Vec<_>>()\n\t\t})\n\t\t.collect();\n\n\tlet max_implementations = parameter_types.iter().map(|x| x.len()).chain([parsed.input.implementations.len().max(1)]).max();\n\n\tfor i in 0..max_implementations.unwrap_or(0) {\n\t\tlet mut temp_constructors = Vec::new();\n\t\tlet mut temp_node_io = Vec::new();\n\t\tlet mut panic_node_types = Vec::new();\n\n\t\tfor (j, types) in parameter_types.iter().enumerate() {\n\t\t\tlet field_name = field_names[j];\n\t\t\tlet (input_type, output_type) = &types[i.min(types.len() - 1)];\n\n\t\t\tlet node = matches!(regular_fields[j].ty, ParsedFieldType::Node { .. });\n\n\t\t\tlet downcast_node = quote!(\n\t\t\t\tlet #field_name: DowncastBothNode<#input_type, #output_type> = DowncastBothNode::new(args[#j].clone());\n\t\t\t);\n\t\t\tif node && !parsed.is_async {\n\t\t\t\treturn Err(Error::new_spanned(&parsed.fn_name, \"Node needs to be async if you want to use lambda parameters\"));\n\t\t\t}\n\t\t\ttemp_constructors.push(downcast_node);\n\t\t\ttemp_node_io.push(quote!(fn_type_fut!(#input_type, #output_type, alias: #output_type)));\n\t\t\tpanic_node_types.push(quote!(#input_type, DynFuture<'static, #output_type>));\n\t\t}\n\t\tlet input_type = match parsed.input.implementations.is_empty() {\n\t\t\ttrue => parsed.input.ty.clone(),\n\t\t\tfalse => parsed.input.implementations[i.min(parsed.input.implementations.len() - 1)].clone(),\n\t\t};\n\t\tconstructors.push(quote!(\n\t\t\t(\n\t\t\t\t|args| {\n\t\t\t\t\tBox::pin(async move {\n\t\t\t\t\t\t#(#temp_constructors;)*\n\t\t\t\t\t\tlet node = #struct_name::new(#(#field_names,)*);\n\t\t\t\t\t\t// try polling futures\n\t\t\t\t\t\tlet any: DynAnyNode<#input_type, _, _> = DynAnyNode::new(node);\n\t\t\t\t\t\tBox::new(any) as TypeErasedBox<'_>\n\t\t\t\t\t})\n\t\t\t\t}, {\n\t\t\t\t\tlet node = #struct_name::new(#(PanicNode::<#panic_node_types>::new(),)*);\n\t\t\t\t\tlet params = vec![#(#temp_node_io,)*];\n\t\t\t\t\tlet mut node_io = NodeIO::<'_, #input_type>::to_async_node_io(&node, params);\n\t\t\t\t\tnode_io\n\n\t\t\t\t}\n\t\t\t)\n\t\t));\n\t}\n\tlet registry_name = format_ident!(\"__node_registry_{}_{}\", NODE_ID.fetch_add(1, std::sync::atomic::Ordering::SeqCst), struct_name);\n\n\tlet native = quote! {\n\t#[cfg_attr(not(target_family = \"wasm\"), ctor)]\n\tfn register_node() {\n\t\tlet mut registry = NODE_REGISTRY.lock().unwrap();\n\t\tregistry.insert(\n\t\t\t#identifier(),\n\t\t\tvec![\n\t\t\t\t#(#constructors,)*\n\t\t\t]\n\t\t);\n\t}\n\t};\n\tif cfg!(feature = \"disable-registration\") {\n\t\treturn Ok(native);\n\t}\n\n\tOk(quote! {\n\t\t#native\n\n\t\t#[cfg(target_family = \"wasm\")]\n\t\t#[unsafe(no_mangle)]\n\t\textern \"C\" fn #registry_name() {\n\t\t\tregister_node();\n\t\t\tregister_metadata();\n\t\t}\n\t})\n}\n\nuse crate::crate_ident::CrateIdent;\nuse crate::shader_nodes::{ShaderCodegen, ShaderTokens};\nuse syn::visit_mut::VisitMut;\nuse syn::{GenericArgument, Lifetime, Type};\n\nstruct LifetimeReplacer(&'static str);\n\nimpl VisitMut for LifetimeReplacer {\n\tfn visit_lifetime_mut(&mut self, lifetime: &mut Lifetime) {\n\t\tlifetime.ident = Ident::new(self.0, lifetime.ident.span());\n\t}\n\n\tfn visit_type_mut(&mut self, ty: &mut Type) {\n\t\tmatch ty {\n\t\t\tType::Reference(type_reference) => {\n\t\t\t\tif let Some(lifetime) = &mut type_reference.lifetime {\n\t\t\t\t\tself.visit_lifetime_mut(lifetime);\n\t\t\t\t}\n\t\t\t\tself.visit_type_mut(&mut type_reference.elem);\n\t\t\t}\n\t\t\t_ => syn::visit_mut::visit_type_mut(self, ty),\n\t\t}\n\t}\n\n\tfn visit_generic_argument_mut(&mut self, arg: &mut GenericArgument) {\n\t\tif let GenericArgument::Lifetime(lifetime) = arg {\n\t\t\tself.visit_lifetime_mut(lifetime);\n\t\t} else {\n\t\t\tsyn::visit_mut::visit_generic_argument_mut(self, arg);\n\t\t}\n\t}\n}\n\n#[must_use]\nfn substitute_lifetimes(mut ty: Type, lifetime: &'static str) -> Type {\n\tLifetimeReplacer(lifetime).visit_type_mut(&mut ty);\n\tty\n}\n\n/// Get only the necessary generics.\nstruct FilterUsedGenerics {\n\tall: Vec<crate::GenericParam>,\n\tused: Vec<bool>,\n}\n\nimpl VisitMut for FilterUsedGenerics {\n\tfn visit_lifetime_mut(&mut self, used_lifetime: &mut Lifetime) {\n\t\tfor (generic, used) in self.all.iter().zip(self.used.iter_mut()) {\n\t\t\tlet crate::GenericParam::Lifetime(lifetime_param) = generic else { continue };\n\t\t\tif used_lifetime == &lifetime_param.lifetime {\n\t\t\t\t*used = true;\n\t\t\t}\n\t\t}\n\t}\n\n\tfn visit_path_mut(&mut self, path: &mut syn::Path) {\n\t\tfor (index, (generic, used)) in self.all.iter().zip(self.used.iter_mut()).enumerate() {\n\t\t\tlet crate::GenericParam::Type(type_param) = generic else { continue };\n\t\t\tif path.leading_colon.is_none() && !path.segments.is_empty() && path.segments[0].arguments.is_none() && path.segments[0].ident == type_param.ident {\n\t\t\t\t*used = true;\n\t\t\t\t// Sometimes the generics conflict with the type name so we rename the generics.\n\t\t\t\tpath.segments[0].ident = format_ident!(\"G{index}\");\n\t\t\t}\n\t\t}\n\t\tfor mut el in Punctuated::pairs_mut(&mut path.segments) {\n\t\t\tself.visit_path_segment_mut(el.value_mut());\n\t\t}\n\t}\n}\n\nimpl FilterUsedGenerics {\n\tfn new(fn_generics: &[crate::GenericParam]) -> (Vec<crate::GenericParam>, Self) {\n\t\tlet mut all_possible_generics = fn_generics.to_vec();\n\t\t// The 'n lifetime may also be needed; we must add it in\n\t\tall_possible_generics.insert(0, syn::GenericParam::Lifetime(syn::LifetimeParam::new(Lifetime::new(\"'n\", proc_macro2::Span::call_site()))));\n\n\t\tlet modified = all_possible_generics\n\t\t\t.iter()\n\t\t\t.cloned()\n\t\t\t.enumerate()\n\t\t\t.map(|(index, mut generic)| {\n\t\t\t\tlet crate::GenericParam::Type(type_param) = &mut generic else { return generic };\n\t\t\t\t// Sometimes the generics conflict with the type name so we rename the generics.\n\t\t\t\ttype_param.ident = format_ident!(\"G{index}\");\n\t\t\t\tgeneric\n\t\t\t})\n\t\t\t.collect::<Vec<_>>();\n\n\t\tlet generic_collector = Self {\n\t\t\tused: vec![false; all_possible_generics.len()],\n\t\t\tall: all_possible_generics,\n\t\t};\n\n\t\t(modified, generic_collector)\n\t}\n\n\tfn used<'a>(&'a self, modified: &'a [crate::GenericParam]) -> impl Iterator<Item = &'a crate::GenericParam> {\n\t\tmodified.iter().zip(&self.used).filter(|(_, used)| **used).map(move |(value, _)| value)\n\t}\n\n\tfn filter_unnecessary_generics(&mut self, modified: &mut Vec<syn::GenericParam>, ty: &mut Type) -> Vec<syn::GenericParam> {\n\t\tself.used.fill(false);\n\n\t\t// Find out which generics are necessary to support the node input\n\t\tself.visit_type_mut(ty);\n\n\t\t// Sometimes generics may reference other generics. This is a non-optimal way of dealing with that.\n\t\tfor _ in 0..=self.all.len() {\n\t\t\tfor (index, item) in modified.iter_mut().enumerate() {\n\t\t\t\tif self.used[index] {\n\t\t\t\t\tself.visit_generic_param_mut(item);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tself.used(&*modified).cloned().collect()\n\t}\n}\n\n/// Check if a type contains a reference to a specific identifier (e.g., a generic type parameter)\nfn type_contains_ident(ty: &Type, ident: &Ident) -> bool {\n\tstruct IdentChecker<'a> {\n\t\ttarget: &'a Ident,\n\t\tfound: bool,\n\t}\n\n\timpl<'a, 'ast> syn::visit::Visit<'ast> for IdentChecker<'a> {\n\t\tfn visit_ident(&mut self, i: &'ast Ident) {\n\t\t\tif i == self.target {\n\t\t\t\tself.found = true;\n\t\t\t}\n\t\t}\n\t}\n\n\tlet mut checker = IdentChecker { target: ident, found: false };\n\tsyn::visit::visit_type(&mut checker, ty);\n\tchecker.found\n}\n"
  },
  {
    "path": "node-graph/node-macro/src/crate_ident.rs",
    "content": "use proc_macro_crate::{FoundCrate, crate_name};\nuse proc_macro2::{Span, TokenStream};\nuse quote::{format_ident, quote};\n\npub struct CrateIdent {\n\tgcore: syn::Result<TokenStream>,\n\tgcore_shaders: syn::Result<TokenStream>,\n\traster_types: syn::Result<TokenStream>,\n\twgpu_executor: syn::Result<TokenStream>,\n}\n\nimpl CrateIdent {\n\tpub fn gcore(&self) -> syn::Result<&TokenStream> {\n\t\tself.gcore.as_ref().map_err(Clone::clone)\n\t}\n\n\tpub fn gcore_shaders(&self) -> syn::Result<&TokenStream> {\n\t\tself.gcore_shaders.as_ref().map_err(Clone::clone)\n\t}\n\n\tpub fn raster_types(&self) -> syn::Result<&TokenStream> {\n\t\tself.raster_types.as_ref().map_err(Clone::clone)\n\t}\n\n\tpub fn wgpu_executor(&self) -> syn::Result<&TokenStream> {\n\t\tself.wgpu_executor.as_ref().map_err(Clone::clone)\n\t}\n}\n\nimpl Default for CrateIdent {\n\tfn default() -> Self {\n\t\tlet find_crate = |orig_name| match crate_name(orig_name) {\n\t\t\tOk(FoundCrate::Itself) => Ok(quote!(crate)),\n\t\t\tOk(FoundCrate::Name(name)) => {\n\t\t\t\tlet name = format_ident!(\"{}\", name);\n\t\t\t\tOk(quote!(::#name))\n\t\t\t}\n\t\t\tErr(e) => Err(syn::Error::new(Span::call_site(), format!(\"Could not find dependency on `{orig_name}`:\\n{e}\"))),\n\t\t};\n\n\t\tlet gcore = find_crate(\"core-types\");\n\t\tlet gcore_shaders = find_crate(\"no-std-types\").or_else(|eshaders| gcore.clone().map_err(|ecore| syn::Error::new(Span::call_site(), format!(\"{ecore}\\n\\nFallback: {eshaders}\"))));\n\t\tlet raster_types = find_crate(\"raster-types\");\n\t\tlet wgpu_executor = find_crate(\"wgpu-executor\");\n\t\tSelf {\n\t\t\tgcore,\n\t\t\tgcore_shaders,\n\t\t\traster_types,\n\t\t\twgpu_executor,\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "node-graph/node-macro/src/derive_choice_type.rs",
    "content": "use proc_macro2::{Ident, Span, TokenStream};\nuse quote::quote;\nuse syn::parse::Parse;\nuse syn::{Attribute, DeriveInput, Expr, LitStr, Meta};\n\npub fn derive_choice_type_impl(input_item: TokenStream) -> syn::Result<TokenStream> {\n\tlet input = syn::parse2::<DeriveInput>(input_item).unwrap();\n\n\tmatch input.data {\n\t\tsyn::Data::Enum(data_enum) => derive_enum(&input.attrs, input.ident, data_enum),\n\t\t_ => Err(syn::Error::new(input.ident.span(), \"Only enums are supported at the moment\")),\n\t}\n}\n\nstruct Type {\n\tbasic_item: BasicItem,\n\twidget_hint: WidgetHint,\n}\n\nenum WidgetHint {\n\tRadio,\n\tDropdown,\n}\nimpl Parse for WidgetHint {\n\tfn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {\n\t\tlet tokens: Ident = input.parse()?;\n\t\tif tokens == \"Radio\" {\n\t\t\tOk(Self::Radio)\n\t\t} else if tokens == \"Dropdown\" {\n\t\t\tOk(Self::Dropdown)\n\t\t} else {\n\t\t\tErr(syn::Error::new_spanned(tokens, \"Widget must be either Radio or Dropdown\"))\n\t\t}\n\t}\n}\n\n#[derive(Default)]\nstruct BasicItem {\n\tlabel: String,\n\tdescription: Option<String>,\n\ticon: Option<String>,\n}\nimpl BasicItem {\n\tfn read_attribute(&mut self, attribute: &Attribute) -> syn::Result<()> {\n\t\tif attribute.path().is_ident(\"label\") {\n\t\t\tlet token: LitStr = attribute.parse_args()?;\n\t\t\tself.label = token.value();\n\t\t}\n\t\tif attribute.path().is_ident(\"icon\") {\n\t\t\tlet token: LitStr = attribute.parse_args()?;\n\t\t\tself.icon = Some(token.value());\n\t\t}\n\t\tif attribute.path().is_ident(\"doc\")\n\t\t\t&& let Meta::NameValue(meta_name_value) = &attribute.meta\n\t\t\t&& let Expr::Lit(el) = &meta_name_value.value\n\t\t\t&& let syn::Lit::Str(token) = &el.lit\n\t\t{\n\t\t\tself.description = Some(token.value());\n\t\t}\n\t\tOk(())\n\t}\n}\n\nstruct Variant {\n\tname: Ident,\n\tbasic_item: BasicItem,\n}\n\nfn derive_enum(enum_attributes: &[Attribute], name: Ident, input: syn::DataEnum) -> syn::Result<TokenStream> {\n\tlet mut enum_info = Type {\n\t\tbasic_item: BasicItem::default(),\n\t\twidget_hint: WidgetHint::Dropdown,\n\t};\n\tfor attribute in enum_attributes {\n\t\tenum_info.basic_item.read_attribute(attribute)?;\n\t\tif attribute.path().is_ident(\"widget\") {\n\t\t\tenum_info.widget_hint = attribute.parse_args()?;\n\t\t}\n\t}\n\n\tlet mut variants = vec![Vec::new()];\n\tfor variant in &input.variants {\n\t\tlet mut basic_item = BasicItem::default();\n\n\t\tfor attribute in &variant.attrs {\n\t\t\tif attribute.path().is_ident(\"menu_separator\") {\n\t\t\t\tattribute.meta.require_path_only()?;\n\t\t\t\tvariants.push(Vec::new());\n\t\t\t}\n\t\t\tbasic_item.read_attribute(attribute)?;\n\t\t}\n\n\t\tif basic_item.label.is_empty() {\n\t\t\tbasic_item.label = ident_to_label(&variant.ident);\n\t\t}\n\n\t\tvariants.last_mut().unwrap().push(Variant {\n\t\t\tname: variant.ident.clone(),\n\t\t\tbasic_item,\n\t\t})\n\t}\n\tlet display_arm: Vec<_> = variants\n\t\t.iter()\n\t\t.flat_map(|variants| variants.iter())\n\t\t.map(|variant| {\n\t\t\tlet variant_name = &variant.name;\n\t\t\tlet variant_label = &variant.basic_item.label;\n\t\t\tquote! { #name::#variant_name => write!(f, #variant_label), }\n\t\t})\n\t\t.collect();\n\n\tlet crate_name = {\n\t\tlet crate_name = proc_macro_crate::crate_name(\"no-std-types\").or_else(|_e| proc_macro_crate::crate_name(\"core-types\")).map_err(|e| {\n\t\t\tsyn::Error::new(\n\t\t\t\tSpan::call_site(),\n\t\t\t\tformat!(\"Failed to find location of 'no-std-types' or 'core-types'. Make sure it is imported as a dependency: {e}\"),\n\t\t\t)\n\t\t})?;\n\t\tmatch crate_name {\n\t\t\tproc_macro_crate::FoundCrate::Itself => quote!(crate),\n\t\t\tproc_macro_crate::FoundCrate::Name(name) => {\n\t\t\t\tlet identifier = Ident::new(&name, Span::call_site());\n\t\t\t\tquote! { #identifier }\n\t\t\t}\n\t\t}\n\t};\n\n\tlet enum_description = match &enum_info.basic_item.description {\n\t\tSome(s) => {\n\t\t\tlet s = s.trim();\n\t\t\tquote! { Some(#s) }\n\t\t}\n\t\tNone => quote! { None },\n\t};\n\tlet group: Vec<_> = variants\n\t\t.iter()\n\t\t.map(|variants| {\n\t\t\tlet items = variants\n\t\t\t\t.iter()\n\t\t\t\t.map(|variant| {\n\t\t\t\t\tlet vname = &variant.name;\n\t\t\t\t\tlet vname_str = variant.name.to_string();\n\t\t\t\t\tlet label = &variant.basic_item.label;\n\t\t\t\t\tlet description = match &variant.basic_item.description {\n\t\t\t\t\t\tSome(s) => {\n\t\t\t\t\t\t\tlet s = s.trim();\n\t\t\t\t\t\t\tquote! { Some(#s) }\n\t\t\t\t\t\t}\n\t\t\t\t\t\tNone => quote! { None },\n\t\t\t\t\t};\n\t\t\t\t\tlet icon = match &variant.basic_item.icon {\n\t\t\t\t\t\tSome(s) => quote! { Some(#s) },\n\t\t\t\t\t\tNone => quote! { None },\n\t\t\t\t\t};\n\t\t\t\t\tquote! {\n\t\t\t\t\t\t(\n\t\t\t\t\t\t\t#name::#vname, #crate_name::choice_type::VariantMetadata {\n\t\t\t\t\t\t\t\tname: #vname_str,\n\t\t\t\t\t\t\t\tlabel: #label,\n\t\t\t\t\t\t\t\tdescription: #description,\n\t\t\t\t\t\t\t\ticon: #icon,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t),\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.collect::<Vec<_>>();\n\t\t\tquote! { &[ #(#items)* ], }\n\t\t})\n\t\t.collect();\n\tlet widget_hint = match enum_info.widget_hint {\n\t\tWidgetHint::Radio => quote! { RadioButtons },\n\t\tWidgetHint::Dropdown => quote! { Dropdown },\n\t};\n\tOk(quote! {\n\t\timpl #crate_name::AsU32 for #name {\n\t\t\tfn as_u32(&self) -> u32 {\n\t\t\t\t*self as u32\n\t\t\t}\n\t\t}\n\n\t\timpl #crate_name::choice_type::ChoiceTypeStatic for #name {\n\t\t\tconst WIDGET_HINT: #crate_name::choice_type::ChoiceWidgetHint = #crate_name::choice_type::ChoiceWidgetHint::#widget_hint;\n\t\t\tconst DESCRIPTION: Option<&'static str> = #enum_description;\n\t\t\tfn list() -> &'static [&'static [(Self, #crate_name::choice_type::VariantMetadata)]] {\n\t\t\t\t&[ #(#group)* ]\n\t\t\t}\n\t\t}\n\n\t\timpl core::fmt::Display for #name {\n\t\t\tfn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n\t\t\t\tmatch self {\n\t\t\t\t\t#( #display_arm )*\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t})\n}\n\nfn ident_to_label(id: &Ident) -> String {\n\tuse convert_case::{Case, Casing};\n\tid.to_string().from_case(Case::Pascal).to_case(Case::Title)\n}\n"
  },
  {
    "path": "node-graph/node-macro/src/lib.rs",
    "content": "use crate::crate_ident::CrateIdent;\nuse proc_macro::TokenStream;\nuse proc_macro_error2::proc_macro_error;\nuse syn::GenericParam;\n\nmod buffer_struct;\nmod codegen;\nmod crate_ident;\nmod derive_choice_type;\nmod parsing;\nmod shader_nodes;\nmod validation;\n\n/// Used to create a node definition.\n#[proc_macro_error]\n#[proc_macro_attribute]\npub fn node(attr: TokenStream, item: TokenStream) -> TokenStream {\n\t// Performs the `node_impl` macro's functionality of attaching an `impl Node for TheGivenStruct` block to the node struct\n\tparsing::new_node_fn(attr.into(), item.into()).unwrap_or_else(|err| err.to_compile_error()).into()\n}\n\n/// Generate meta-information for an enum.\n///\n/// `#[widget(F)]` on a type indicates the type of widget to use to display/edit the type, currently `Radio` and `Dropdown` are supported.\n///\n/// `#[label(\"Foo\")]` on a variant overrides the default UI label (which is otherwise the name converted to title case). All labels are collected into a [`core::fmt::Display`] impl.\n///\n/// `#[icon(\"tag\"))]` sets the icon to use when a variant is shown in a menu or radio button.\n///\n/// Doc comments on a variant become tooltip description text.\n#[proc_macro_derive(ChoiceType, attributes(widget, menu_separator, label, icon))]\npub fn derive_choice_type(input_item: TokenStream) -> TokenStream {\n\tderive_choice_type::derive_choice_type_impl(input_item.into()).unwrap_or_else(|err| err.to_compile_error()).into()\n}\n\n/// Derive a struct to implement `ShaderStruct`, see that for docs.\n#[proc_macro_derive(BufferStruct)]\npub fn derive_buffer_struct(input_item: TokenStream) -> TokenStream {\n\tlet crate_ident = CrateIdent::default();\n\tTokenStream::from(buffer_struct::derive_buffer_struct(&crate_ident, input_item).unwrap_or_else(|err| err.to_compile_error()))\n}\n"
  },
  {
    "path": "node-graph/node-macro/src/parsing.rs",
    "content": "use convert_case::{Case, Casing};\nuse indoc::{formatdoc, indoc};\nuse proc_macro2::TokenStream as TokenStream2;\nuse quote::{ToTokens, format_ident};\nuse syn::parse::{Parse, ParseStream, Parser};\nuse syn::punctuated::Punctuated;\nuse syn::spanned::Spanned;\nuse syn::token::{Comma, RArrow};\nuse syn::{\n\tAttrStyle, Attribute, Error, Expr, ExprTuple, FnArg, GenericParam, Ident, ItemFn, Lit, LitFloat, LitInt, LitStr, Meta, Pat, PatIdent, PatType, Path, ReturnType, TraitBound, Type, TypeImplTrait,\n\tTypeParam, TypeParamBound, Visibility, WhereClause, parse_quote,\n};\n\nuse crate::codegen::generate_node_code;\nuse crate::crate_ident::CrateIdent;\nuse crate::shader_nodes::ShaderNodeType;\n\n#[derive(Clone, Debug)]\npub(crate) struct Implementation {\n\tpub(crate) input: Type,\n\tpub(crate) _arrow: RArrow,\n\tpub(crate) output: Type,\n}\n\n#[derive(Debug)]\npub(crate) struct ParsedNodeFn {\n\tpub(crate) vis: Visibility,\n\tpub(crate) attributes: NodeFnAttributes,\n\tpub(crate) fn_name: Ident,\n\tpub(crate) struct_name: Ident,\n\tpub(crate) mod_name: Ident,\n\tpub(crate) fn_generics: Vec<GenericParam>,\n\tpub(crate) where_clause: Option<WhereClause>,\n\tpub(crate) input: Input,\n\tpub(crate) output_type: Type,\n\tpub(crate) is_async: bool,\n\tpub(crate) fields: Vec<ParsedField>,\n\tpub(crate) body: TokenStream2,\n\tpub(crate) description: String,\n}\n\n#[derive(Debug, Default, Clone)]\npub(crate) struct NodeFnAttributes {\n\tpub(crate) category: Option<LitStr>,\n\tpub(crate) display_name: Option<LitStr>,\n\tpub(crate) path: Option<Path>,\n\tpub(crate) skip_impl: bool,\n\tpub(crate) properties_string: Option<LitStr>,\n\t/// whether to `#[cfg]` gate the node implementation, defaults to None\n\tpub(crate) cfg: Option<TokenStream2>,\n\t/// if this node should get a gpu implementation, defaults to None\n\tpub(crate) shader_node: Option<ShaderNodeType>,\n\t/// Custom serialization function path (e.g., \"my_module::custom_serialize\")\n\tpub(crate) serialize: Option<Path>,\n\t// Add more attributes as needed\n}\n\n#[derive(Clone, Debug, Default)]\npub enum ParsedValueSource {\n\t#[default]\n\tNone,\n\tDefault(TokenStream2),\n\tScope(LitStr),\n}\n\n// #[widget(ParsedWidgetOverride::Hidden)]\n// #[widget(ParsedWidgetOverride::String = \"Some string\")]\n// #[widget(ParsedWidgetOverride::Custom = \"Custom string\")]\n#[derive(Clone, Debug, Default)]\npub enum ParsedWidgetOverride {\n\t#[default]\n\tNone,\n\tHidden,\n\tString(LitStr),\n\tCustom(LitStr),\n}\n\nimpl Parse for ParsedWidgetOverride {\n\tfn parse(input: ParseStream) -> syn::Result<Self> {\n\t\t// Parse the full path (e.g., ParsedWidgetOverride::Hidden)\n\t\tlet path: Path = input.parse()?;\n\n\t\t// Ensure the path starts with `ParsedWidgetOverride`\n\t\tif path.segments.len() == 2 && path.segments[0].ident == \"ParsedWidgetOverride\" {\n\t\t\tlet variant = &path.segments[1].ident;\n\n\t\t\tmatch variant.to_string().as_str() {\n\t\t\t\t\"Hidden\" => Ok(ParsedWidgetOverride::Hidden),\n\t\t\t\t\"String\" => {\n\t\t\t\t\tinput.parse::<syn::Token![=]>()?;\n\t\t\t\t\tlet lit: LitStr = input.parse()?;\n\t\t\t\t\tOk(ParsedWidgetOverride::String(lit))\n\t\t\t\t}\n\t\t\t\t\"Custom\" => {\n\t\t\t\t\tinput.parse::<syn::Token![=]>()?;\n\t\t\t\t\tlet lit: LitStr = input.parse()?;\n\t\t\t\t\tOk(ParsedWidgetOverride::Custom(lit))\n\t\t\t\t}\n\t\t\t\t_ => Err(Error::new(variant.span(), \"Unknown ParsedWidgetOverride variant\")),\n\t\t\t}\n\t\t} else {\n\t\t\tErr(Error::new(input.span(), \"Expected ParsedWidgetOverride::<variant>\"))\n\t\t}\n\t}\n}\n\n#[derive(Clone, Debug)]\npub struct ParsedField {\n\tpub pat_ident: PatIdent,\n\tpub name: Option<LitStr>,\n\tpub description: String,\n\tpub widget_override: ParsedWidgetOverride,\n\tpub ty: ParsedFieldType,\n\tpub number_display_decimal_places: Option<LitInt>,\n\tpub number_step: Option<LitFloat>,\n\tpub unit: Option<LitStr>,\n\tpub is_data_field: bool,\n}\n\n#[derive(Clone, Debug)]\npub enum ParsedFieldType {\n\tRegular(RegularParsedField),\n\tNode(NodeParsedField),\n}\n\n/// a param of any kind, either a concrete type or a generic type with a set of possible types specified via\n/// `#[implementation(type)]`\n#[derive(Clone, Debug)]\npub struct RegularParsedField {\n\tpub ty: Type,\n\tpub exposed: bool,\n\tpub value_source: ParsedValueSource,\n\tpub number_soft_min: Option<LitFloat>,\n\tpub number_soft_max: Option<LitFloat>,\n\tpub number_hard_min: Option<LitFloat>,\n\tpub number_hard_max: Option<LitFloat>,\n\tpub number_mode_range: Option<ExprTuple>,\n\tpub implementations: Punctuated<Type, Comma>,\n\tpub gpu_image: bool,\n}\n\n/// a param of `impl Node` with `#[implementation(in -> out)]`\n#[derive(Clone, Debug)]\npub struct NodeParsedField {\n\tpub input_type: Type,\n\tpub output_type: Type,\n\tpub implementations: Punctuated<Implementation, Comma>,\n}\n\n#[derive(Clone, Debug)]\npub(crate) struct Input {\n\tpub(crate) pat_ident: PatIdent,\n\tpub(crate) ty: Type,\n\tpub(crate) implementations: Punctuated<Type, Comma>,\n\tpub(crate) context_features: Vec<Ident>,\n}\n\nimpl Parse for Implementation {\n\tfn parse(input: ParseStream) -> syn::Result<Self> {\n\t\tlet input_type: Type = input.parse().map_err(|e| {\n\t\t\tError::new(\n\t\t\t\tinput.span(),\n\t\t\t\tformatdoc!(\n\t\t\t\t\t\"Failed to parse input type for #[implementation(...)]. Expected a valid Rust type.\n\t\t\t\t\tError: {}\",\n\t\t\t\t\te,\n\t\t\t\t),\n\t\t\t)\n\t\t})?;\n\t\tlet arrow: RArrow = input.parse().map_err(|_| {\n\t\t\tError::new(\n\t\t\t\tinput.span(),\n\t\t\t\tindoc!(\n\t\t\t\t\t\"Expected `->` arrow after input type in #[implementations(...)] on a field of type `impl Node`.\n\t\t\t\t\tThe correct syntax is `InputType -> OutputType`.\"\n\t\t\t\t),\n\t\t\t)\n\t\t})?;\n\t\tlet output_type: Type = input.parse().map_err(|e| {\n\t\t\tError::new(\n\t\t\t\tinput.span(),\n\t\t\t\tformatdoc!(\n\t\t\t\t\t\"Failed to parse output type for #[implementation(...)]. Expected a valid Rust type after `->`.\n\t\t\t\t\tError: {}\",\n\t\t\t\t\te\n\t\t\t\t),\n\t\t\t)\n\t\t})?;\n\n\t\tOk(Implementation {\n\t\t\tinput: input_type,\n\t\t\t_arrow: arrow,\n\t\t\toutput: output_type,\n\t\t})\n\t}\n}\n\nimpl Parse for NodeFnAttributes {\n\tfn parse(input: ParseStream) -> syn::Result<Self> {\n\t\tlet mut category = None;\n\t\tlet mut display_name = None;\n\t\tlet mut path = None;\n\t\tlet mut skip_impl = false;\n\t\tlet mut properties_string = None;\n\t\tlet mut cfg = None;\n\t\tlet mut shader_node = None;\n\t\tlet mut serialize = None;\n\n\t\tlet content = input;\n\t\t// let content;\n\t\t// syn::parenthesized!(content in input);\n\n\t\tlet nested = content.call(Punctuated::<Meta, Comma>::parse_terminated)?;\n\t\tfor meta in nested.iter() {\n\t\t\tlet name = meta.path().get_ident().ok_or_else(|| Error::new_spanned(meta.path(), \"Node macro expects a known Ident, not a path\"))?;\n\t\t\tmatch name.to_string().as_str() {\n\t\t\t\t// User-facing category in the node catalog. The empty string `category(\"\")` hides the node from the catalog.\n\t\t\t\t//\n\t\t\t\t// Example usage:\n\t\t\t\t// #[node_macro::node(..., category(\"Math: Arithmetic\"), ...)]\n\t\t\t\t\"category\" => {\n\t\t\t\t\tlet meta = meta.require_list()?;\n\t\t\t\t\tif category.is_some() {\n\t\t\t\t\t\treturn Err(Error::new_spanned(meta, \"Multiple 'category' attributes are not allowed\"));\n\t\t\t\t\t}\n\t\t\t\t\tlet lit: LitStr = meta\n\t\t\t\t\t\t.parse_args()\n\t\t\t\t\t\t.map_err(|_| Error::new_spanned(meta, \"Expected a string literal for 'category', e.g., category(\\\"Value\\\")\"))?;\n\t\t\t\t\tcategory = Some(lit);\n\t\t\t\t}\n\t\t\t\t// Override for the display name in the node catalog in place of the auto-generated name taken from the function name with inferred Title Case formatting.\n\t\t\t\t// Use this if capitalization or formatting needs to be overridden.\n\t\t\t\t//\n\t\t\t\t// Example usage:\n\t\t\t\t// #[node_macro::node(..., name(\"Request URL\"), ...)]\n\t\t\t\t\"name\" => {\n\t\t\t\t\tlet meta = meta.require_list()?;\n\t\t\t\t\tif display_name.is_some() {\n\t\t\t\t\t\treturn Err(Error::new_spanned(meta, \"Multiple 'name' attributes are not allowed\"));\n\t\t\t\t\t}\n\t\t\t\t\tlet parsed_name: LitStr = meta.parse_args().map_err(|_| Error::new_spanned(meta, \"Expected a string for 'name', e.g., name(\\\"Memoize\\\")\"))?;\n\t\t\t\t\tdisplay_name = Some(parsed_name);\n\t\t\t\t}\n\t\t\t\t// Override for the fully qualified path used by Graphene to identify the node implementation.\n\t\t\t\t// If not provided, the path will be inferred from the module path and function name.\n\t\t\t\t// Use this if the node implementation has moved to a different module or crate but a migration to that new path is not desired.\n\t\t\t\t//\n\t\t\t\t// Example usage:\n\t\t\t\t// #[node_macro::node(..., path(core_types::vector), ...)]\n\t\t\t\t\"path\" => {\n\t\t\t\t\tlet meta = meta.require_list()?;\n\t\t\t\t\tif path.is_some() {\n\t\t\t\t\t\treturn Err(Error::new_spanned(meta, \"Multiple 'path' attributes are not allowed\"));\n\t\t\t\t\t}\n\t\t\t\t\tlet parsed_path: Path = meta\n\t\t\t\t\t\t.parse_args()\n\t\t\t\t\t\t.map_err(|_| Error::new_spanned(meta, \"Expected a valid path for 'path', e.g., path(crate::MemoizeNode)\"))?;\n\t\t\t\t\tpath = Some(parsed_path);\n\t\t\t\t}\n\t\t\t\t// Indicator that the node should allow generic type arguments but skip the automatic generation of concrete type implementations.\n\t\t\t\t// It allows the type arguments in this node to not include the normally required `#[implementations(...)]` attribute on each generic parameter.\n\t\t\t\t// Instead, concrete implementations must be manually listed in the Node Registry, or where impossible, produced at runtime by the compile server.\n\t\t\t\t// This is used by a few advanced nodes that need to support many types where listing them all would be cumbersome or impossible.\n\t\t\t\t//\n\t\t\t\t// Example usage:\n\t\t\t\t// #[node_macro::node(..., skip_impl, ...)]\n\t\t\t\t\"skip_impl\" => {\n\t\t\t\t\tlet path = meta.require_path_only()?;\n\t\t\t\t\tif skip_impl {\n\t\t\t\t\t\treturn Err(Error::new_spanned(path, \"Multiple 'skip_impl' attributes are not allowed\"));\n\t\t\t\t\t}\n\t\t\t\t\tskip_impl = true;\n\t\t\t\t}\n\t\t\t\t// Override UI layout generator function name defined in `node_properties.rs` that returns a custom Properties panel layout for this node.\n\t\t\t\t// This is used to create custom UI for the input parameters of the node in cases where the defaults generated from the type and attributes are insufficient.\n\t\t\t\t//\n\t\t\t\t// Example usage:\n\t\t\t\t// #[node_macro::node(..., properties(\"channel_mixer_properties\"), ...)]\n\t\t\t\t\"properties\" => {\n\t\t\t\t\tlet meta = meta.require_list()?;\n\t\t\t\t\tif properties_string.is_some() {\n\t\t\t\t\t\treturn Err(Error::new_spanned(path, \"Multiple 'properties' attributes are not allowed\"));\n\t\t\t\t\t}\n\t\t\t\t\tlet parsed_properties_string: LitStr = meta\n\t\t\t\t\t\t.parse_args()\n\t\t\t\t\t\t.map_err(|_| Error::new_spanned(meta, \"Expected a string for 'properties', e.g., properties(\\\"channel_mixer_properties\\\")\"))?;\n\n\t\t\t\t\tproperties_string = Some(parsed_properties_string);\n\t\t\t\t}\n\t\t\t\t// Conditional compilation tokens to gate when this node is included in the build.\n\t\t\t\t//\n\t\t\t\t// Example usage:\n\t\t\t\t// #[node_macro::node(..., cfg(feature = \"std\"), ...)]\n\t\t\t\t\"cfg\" => {\n\t\t\t\t\tif cfg.is_some() {\n\t\t\t\t\t\treturn Err(Error::new_spanned(path, \"Multiple 'cfg' attributes are not allowed\"));\n\t\t\t\t\t}\n\t\t\t\t\tlet meta = meta.require_list()?;\n\t\t\t\t\tcfg = Some(meta.tokens.clone());\n\t\t\t\t}\n\t\t\t\t// Reference to a specific shader definition struct that is used to run the logic of this node on the GPU.\n\t\t\t\t//\n\t\t\t\t// Example usage:\n\t\t\t\t// #[node_macro::node(..., shader_node(PerPixelAdjust), ...)]\n\t\t\t\t\"shader_node\" => {\n\t\t\t\t\tif shader_node.is_some() {\n\t\t\t\t\t\treturn Err(Error::new_spanned(path, \"Multiple 'shader_node' attributes are not allowed\"));\n\t\t\t\t\t}\n\t\t\t\t\tlet meta = meta.require_list()?;\n\t\t\t\t\tshader_node = Some(syn::parse2(meta.tokens.to_token_stream())?);\n\t\t\t\t}\n\t\t\t\t// Function name for custom serialization of this node's data. This is only used by the Monitor node.\n\t\t\t\t//\n\t\t\t\t// Example usage:\n\t\t\t\t// #[node_macro::node(..., serialize(my_module::custom_serialize), ...)]\n\t\t\t\t\"serialize\" => {\n\t\t\t\t\tlet meta = meta.require_list()?;\n\t\t\t\t\tif serialize.is_some() {\n\t\t\t\t\t\treturn Err(Error::new_spanned(meta, \"Multiple 'serialize' attributes are not allowed\"));\n\t\t\t\t\t}\n\t\t\t\t\tlet parsed_path: Path = meta\n\t\t\t\t\t\t.parse_args()\n\t\t\t\t\t\t.map_err(|_| Error::new_spanned(meta, \"Expected a valid path for 'serialize', e.g., serialize(my_module::custom_serialize)\"))?;\n\t\t\t\t\tserialize = Some(parsed_path);\n\t\t\t\t}\n\t\t\t\t_ => {\n\t\t\t\t\treturn Err(Error::new_spanned(\n\t\t\t\t\t\tmeta,\n\t\t\t\t\t\tindoc!(\n\t\t\t\t\t\t\tr#\"\n\t\t\t\t\t\t\tUnsupported attribute in `node`.\n\t\t\t\t\t\t\tSupported attributes are 'category', 'name', 'path', 'skip_impl', 'properties', 'cfg', 'shader_node', and 'serialize'.\n\t\t\t\t\t\t\tExample usage:\n\t\t\t\t\t\t\t#[node_macro::node(..., name(\"Test Node\"), ...)]\n\t\t\t\t\t\t\t\"#\n\t\t\t\t\t\t),\n\t\t\t\t\t));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif category.is_none() {\n\t\t\treturn Err(Error::new_spanned(\n\t\t\t\tnested,\n\t\t\t\tindoc!(\n\t\t\t\t\tr#\"\n\t\t\t\t\tThe attribute 'category' is required.\n\t\t\t\t\tExample usage:\n\t\t\t\t\t#[node_macro::node(..., category(\"Value\"), ...)]\n\t\t\t\t\t\"#,\n\t\t\t\t),\n\t\t\t));\n\t\t}\n\n\t\tOk(NodeFnAttributes {\n\t\t\tcategory,\n\t\t\tdisplay_name,\n\t\t\tpath,\n\t\t\tskip_impl,\n\t\t\tproperties_string,\n\t\t\tcfg,\n\t\t\tshader_node,\n\t\t\tserialize,\n\t\t})\n\t}\n}\n\nfn parse_node_fn(attr: TokenStream2, item: TokenStream2) -> syn::Result<ParsedNodeFn> {\n\tlet attributes = syn::parse2::<NodeFnAttributes>(attr.clone()).map_err(|e| Error::new(e.span(), format!(\"Failed to parse node_fn attributes:\\n{e}\")))?;\n\tlet input_fn = syn::parse2::<ItemFn>(item.clone()).map_err(|e| Error::new(e.span(), format!(\"Failed to parse function: {e}. Make sure it's a valid Rust function.\")))?;\n\n\tlet vis = input_fn.vis;\n\tlet fn_name = input_fn.sig.ident.clone();\n\tlet struct_name = format_ident!(\"{}\", fn_name.to_string().to_case(Case::Pascal));\n\tlet mod_name = fn_name.clone();\n\tlet fn_generics = input_fn.sig.generics.params.into_iter().collect();\n\tlet is_async = input_fn.sig.asyncness.is_some();\n\n\tlet (input, fields) = parse_inputs(&input_fn.sig.inputs)?;\n\tlet output_type = parse_output(&input_fn.sig.output)?;\n\tlet where_clause = input_fn.sig.generics.where_clause;\n\tlet body = input_fn.block.to_token_stream();\n\tlet description = input_fn\n\t\t.attrs\n\t\t.iter()\n\t\t.filter_map(|a| {\n\t\t\tif a.style != AttrStyle::Outer {\n\t\t\t\treturn None;\n\t\t\t}\n\t\t\tlet Meta::NameValue(name_val) = &a.meta else { return None };\n\t\t\tif name_val.path.get_ident().map(|x| x.to_string()) != Some(\"doc\".into()) {\n\t\t\t\treturn None;\n\t\t\t}\n\t\t\tlet Expr::Lit(expr_lit) = &name_val.value else { return None };\n\t\t\tlet Lit::Str(ref text) = expr_lit.lit else { return None };\n\t\t\tSome(text.value().trim().to_string())\n\t\t})\n\t\t.fold(String::new(), |acc, b| acc + &b + \"\\n\");\n\n\tOk(ParsedNodeFn {\n\t\tvis,\n\t\tattributes,\n\t\tfn_name,\n\t\tstruct_name,\n\t\tmod_name,\n\t\tfn_generics,\n\t\tinput,\n\t\toutput_type,\n\t\tis_async,\n\t\tfields,\n\t\twhere_clause,\n\t\tbody,\n\t\tdescription,\n\t})\n}\n\nfn parse_inputs(inputs: &Punctuated<FnArg, Comma>) -> syn::Result<(Input, Vec<ParsedField>)> {\n\tlet mut fields = Vec::new();\n\tlet mut input = None;\n\n\tfor (index, arg) in inputs.iter().enumerate() {\n\t\tif let FnArg::Typed(PatType { pat, ty, attrs, .. }) = arg {\n\t\t\t// Call argument\n\t\t\tif index == 0 {\n\t\t\t\tif extract_attribute(attrs, \"default\").is_some() {\n\t\t\t\t\treturn Err(Error::new_spanned(&attrs[0], \"Call argument cannot be given a default value\".to_string()));\n\t\t\t\t}\n\t\t\t\tif extract_attribute(attrs, \"expose\").is_some() {\n\t\t\t\t\treturn Err(Error::new_spanned(&attrs[0], \"Call argument cannot be exposed\".to_string()));\n\t\t\t\t}\n\t\t\t\tlet pat_ident = match (**pat).clone() {\n\t\t\t\t\tPat::Ident(pat_ident) => pat_ident,\n\t\t\t\t\tPat::Wild(wild) => PatIdent {\n\t\t\t\t\t\tattrs: wild.attrs,\n\t\t\t\t\t\tby_ref: None,\n\t\t\t\t\t\tmutability: None,\n\t\t\t\t\t\tident: wild.underscore_token.into(),\n\t\t\t\t\t\tsubpat: None,\n\t\t\t\t\t},\n\t\t\t\t\t_ => continue,\n\t\t\t\t};\n\n\t\t\t\tlet implementations = extract_attribute(attrs, \"implementations\")\n\t\t\t\t\t.map(|attr| parse_implementations(attr, &pat_ident.ident))\n\t\t\t\t\t.transpose()?\n\t\t\t\t\t.unwrap_or_default();\n\t\t\t\tlet context_features = parse_context_feature_idents(ty);\n\t\t\t\tinput = Some(Input {\n\t\t\t\t\tpat_ident,\n\t\t\t\t\tty: (**ty).clone(),\n\t\t\t\t\timplementations,\n\t\t\t\t\tcontext_features,\n\t\t\t\t});\n\t\t\t} else if let Pat::Ident(pat_ident) = &**pat {\n\t\t\t\tlet field = parse_field(pat_ident.clone(), (**ty).clone(), attrs).map_err(|e| Error::new_spanned(pat_ident, format!(\"Failed to parse argument '{}': {}\", pat_ident.ident, e)))?;\n\t\t\t\tfields.push(field);\n\t\t\t} else {\n\t\t\t\treturn Err(Error::new_spanned(pat, \"Expected a simple identifier for the field name\"));\n\t\t\t}\n\t\t} else {\n\t\t\treturn Err(Error::new_spanned(arg, \"Expected a typed argument (e.g., `x: i32`)\"));\n\t\t}\n\t}\n\n\tlet input = input.ok_or_else(|| Error::new_spanned(inputs, \"Expected at least one input argument. The first argument should be the node input type.\"))?;\n\tOk((input, fields))\n}\n\n/// Parse context feature identifiers from the trait bounds of a context parameter.\nfn parse_context_feature_idents(ty: &Type) -> Vec<Ident> {\n\tlet mut features = Vec::new();\n\n\t// Check if this is an impl trait (impl Ctx + ...)\n\tif let Type::ImplTrait(TypeImplTrait { bounds, .. }) = ty {\n\t\tfor bound in bounds {\n\t\t\tif let TypeParamBound::Trait(TraitBound { path, .. }) = bound {\n\t\t\t\t// Extract the last segment of the trait path\n\t\t\t\tif let Some(segment) = path.segments.last() {\n\t\t\t\t\tmatch segment.ident.to_string().as_str() {\n\t\t\t\t\t\t\"ExtractFootprint\"\n\t\t\t\t\t\t| \"ExtractRealTime\"\n\t\t\t\t\t\t| \"ExtractAnimationTime\"\n\t\t\t\t\t\t| \"ExtractPointerPosition\"\n\t\t\t\t\t\t| \"ExtractIndex\"\n\t\t\t\t\t\t| \"ExtractVarArgs\"\n\t\t\t\t\t\t| \"InjectFootprint\"\n\t\t\t\t\t\t| \"InjectRealTime\"\n\t\t\t\t\t\t| \"InjectAnimationTime\"\n\t\t\t\t\t\t| \"InjectPointerPosition\"\n\t\t\t\t\t\t| \"InjectIndex\"\n\t\t\t\t\t\t| \"InjectVarArgs\" => {\n\t\t\t\t\t\t\tfeatures.push(segment.ident.clone());\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Skip Modify* traits as they don't affect usage tracking\n\t\t\t\t\t\t// Also ignore other traits like Ctx, ExtractAll, etc.\n\t\t\t\t\t\t_ => {}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfeatures\n}\n\nfn parse_implementations(attr: &Attribute, name: &Ident) -> syn::Result<Punctuated<Type, Comma>> {\n\tlet content: TokenStream2 = attr.parse_args()?;\n\tlet parser = Punctuated::<Type, Comma>::parse_terminated;\n\tparser.parse2(content.clone()).map_err(|e| {\n\t\tlet span = e.span(); // Get the span of the error\n\t\tError::new(span, format!(\"Failed to parse implementations for argument '{name}': {e}\"))\n\t})\n}\n\nfn parse_node_implementations<T: Parse>(attr: &Attribute, name: &Ident) -> syn::Result<Punctuated<T, Comma>> {\n\tlet content: TokenStream2 = attr.parse_args()?;\n\tlet parser = Punctuated::<T, Comma>::parse_terminated;\n\tparser.parse2(content.clone()).map_err(|e| {\n\t\tError::new(\n\t\t\te.span(),\n\t\t\tformatdoc!(\n\t\t\t\t\"Invalid #[implementations(...)] for argument `{}`.\n\t\t\t\tExpected a comma-separated list of `InputType -> OutputType` pairs.\n\t\t\t\tExample: #[implementations(i32 -> f64, String -> Vec<u8>)]\n\t\t\t\tError: {}\",\n\t\t\t\tname,\n\t\t\t\te\n\t\t\t),\n\t\t)\n\t})\n}\n\nfn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Result<ParsedField> {\n\tlet ident = &pat_ident.ident;\n\n\t// Checks for the #[data] attribute, indicating that this is a data field rather than an input parameter to the node.\n\t// Data fields act as internal state, using interior mutability to cache data between node evaluations.\n\t//\n\t// Normally, an input parameter is a construction argument to the node that is stored as a field on the node struct.\n\t// Specifically, its struct field stores the connected upstream node (an evaluatable lambda that returns data of the connection wire's type).\n\t// By comparison, a data field is also stored as a field on the node struct, allowing it to persist state between evaluations.\n\t// But it acts as internal state only, not exposed as a parameter in the UI or able to be wired to another node.\n\t//\n\t// Nodes implemented using a data field must ensure the persistent state is used in a manner that respects the invariant of idempotence,\n\t// meaning the node's output is always deterministic whether or not the internal state is present.\n\tlet is_data_field = extract_attribute(attrs, \"data\").is_some();\n\n\tlet default_value = extract_attribute(attrs, \"default\")\n\t\t.map(|attr| attr.parse_args().map_err(|e| Error::new_spanned(attr, format!(\"Invalid `default` value for argument '{ident}': {e}\"))))\n\t\t.transpose()?;\n\n\tlet scope = extract_attribute(attrs, \"scope\")\n\t\t.map(|attr| attr.parse_args().map_err(|e| Error::new_spanned(attr, format!(\"Invalid `scope` value for argument '{ident}': {e}\"))))\n\t\t.transpose()?;\n\n\tlet name = extract_attribute(attrs, \"name\")\n\t\t.map(|attr| attr.parse_args().map_err(|e| Error::new_spanned(attr, format!(\"Invalid `name` value for argument '{ident}': {e}\"))))\n\t\t.transpose()?;\n\n\tlet widget_override = extract_attribute(attrs, \"widget\")\n\t\t.map(|attr| {\n\t\t\tattr.parse_args()\n\t\t\t\t.map_err(|e| Error::new_spanned(attr, format!(\"Invalid `widget override` value for argument '{ident}': {e}\")))\n\t\t})\n\t\t.transpose()?\n\t\t.unwrap_or_default();\n\n\tlet exposed = extract_attribute(attrs, \"expose\").is_some();\n\n\t// Validate data field attributes\n\tif is_data_field {\n\t\tif default_value.is_some() {\n\t\t\treturn Err(Error::new_spanned(\n\t\t\t\t&pat_ident,\n\t\t\t\t\"Data fields (#[data]) cannot have #[default] attribute. They are automatically initialized with Default::default()\",\n\t\t\t));\n\t\t}\n\t\tif scope.is_some() {\n\t\t\treturn Err(Error::new_spanned(&pat_ident, \"Data fields (#[data]) cannot have #[scope] attribute\"));\n\t\t}\n\t\tif exposed {\n\t\t\treturn Err(Error::new_spanned(\n\t\t\t\t&pat_ident,\n\t\t\t\t\"Data fields (#[data]) cannot be exposed (#[expose]). They are internal state, not node parameters\",\n\t\t\t));\n\t\t}\n\t}\n\n\tlet value_source = match (default_value, scope) {\n\t\t(Some(_), Some(_)) => return Err(Error::new_spanned(&pat_ident, \"Cannot have both `default` and `scope` attributes\")),\n\t\t(Some(default_value), _) => ParsedValueSource::Default(default_value),\n\t\t(_, Some(scope)) => ParsedValueSource::Scope(scope),\n\t\t_ => ParsedValueSource::None,\n\t};\n\n\tlet number_soft_min = extract_attribute(attrs, \"soft_min\")\n\t\t.map(|attr| {\n\t\t\tattr.parse_args()\n\t\t\t\t.map_err(|e| Error::new_spanned(attr, format!(\"Invalid numerical `soft_min` value for argument '{ident}': {e}\")))\n\t\t})\n\t\t.transpose()?;\n\tlet number_soft_max = extract_attribute(attrs, \"soft_max\")\n\t\t.map(|attr| {\n\t\t\tattr.parse_args()\n\t\t\t\t.map_err(|e| Error::new_spanned(attr, format!(\"Invalid numerical `soft_max` value for argument '{ident}': {e}\")))\n\t\t})\n\t\t.transpose()?;\n\n\tlet number_hard_min = extract_attribute(attrs, \"hard_min\")\n\t\t.map(|attr| {\n\t\t\tattr.parse_args()\n\t\t\t\t.map_err(|e| Error::new_spanned(attr, format!(\"Invalid numerical `hard_min` value for argument '{ident}': {e}\")))\n\t\t})\n\t\t.transpose()?;\n\tlet number_hard_max = extract_attribute(attrs, \"hard_max\")\n\t\t.map(|attr| {\n\t\t\tattr.parse_args()\n\t\t\t\t.map_err(|e| Error::new_spanned(attr, format!(\"Invalid numerical `hard_max` value for argument '{ident}': {e}\")))\n\t\t})\n\t\t.transpose()?;\n\n\tlet number_mode_range = extract_attribute(attrs, \"range\")\n\t\t.map(|attr| {\n\t\t\tattr.parse_args::<ExprTuple>().map_err(|e| {\n\t\t\t\tError::new_spanned(\n\t\t\t\t\tattr,\n\t\t\t\t\tformat!(\"Invalid `range` tuple of min and max range slider values for argument '{ident}': {e}\\nUSAGE EXAMPLE: #[range((0., 100.))]\"),\n\t\t\t\t)\n\t\t\t})\n\t\t})\n\t\t.transpose()?;\n\tif let Some(range) = &number_mode_range\n\t\t&& range.elems.len() != 2\n\t{\n\t\treturn Err(Error::new_spanned(range, \"Expected a tuple of two values for `range` for the min and max, respectively\"));\n\t}\n\n\tlet unit = extract_attribute(attrs, \"unit\")\n\t\t.map(|attr| attr.parse_args::<LitStr>().map_err(|_e| Error::new_spanned(attr, \"Expected a unit type as string\".to_string())))\n\t\t.transpose()?;\n\n\tlet number_display_decimal_places = extract_attribute(attrs, \"display_decimal_places\")\n\t\t.map(|attr| {\n\t\t\tattr.parse_args::<LitInt>().map_err(|e| {\n\t\t\t\tError::new_spanned(\n\t\t\t\t\tattr,\n\t\t\t\t\tformat!(\"Invalid `integer` for number of decimals for argument '{ident}': {e}\\nUSAGE EXAMPLE: #[display_decimal_places(2)]\"),\n\t\t\t\t)\n\t\t\t})\n\t\t})\n\t\t.transpose()?\n\t\t.map(|f| {\n\t\t\tif let Err(e) = f.base10_parse::<u32>() {\n\t\t\t\tErr(Error::new_spanned(f, format!(\"Expected a `u32` for `display_decimal_places` for '{ident}': {e}\")))\n\t\t\t} else {\n\t\t\t\tOk(f)\n\t\t\t}\n\t\t})\n\t\t.transpose()?;\n\tlet number_step = extract_attribute(attrs, \"step\")\n\t\t.map(|attr| {\n\t\t\tattr.parse_args::<LitFloat>()\n\t\t\t\t.map_err(|e| Error::new_spanned(attr, format!(\"Invalid `step` for argument '{ident}': {e}\\nUSAGE EXAMPLE: #[step(2.)]\")))\n\t\t})\n\t\t.transpose()?;\n\tlet gpu_image = extract_attribute(attrs, \"gpu_image\").is_some();\n\n\tlet (is_node, node_input_type, node_output_type) = parse_node_type(&ty);\n\tlet description = attrs\n\t\t.iter()\n\t\t.filter_map(|a| {\n\t\t\tif a.style != AttrStyle::Outer {\n\t\t\t\treturn None;\n\t\t\t}\n\t\t\tlet Meta::NameValue(name_val) = &a.meta else { return None };\n\t\t\tif name_val.path.get_ident().map(|x| x.to_string()) != Some(\"doc\".into()) {\n\t\t\t\treturn None;\n\t\t\t}\n\t\t\tlet Expr::Lit(expr_lit) = &name_val.value else { return None };\n\t\t\tlet Lit::Str(ref text) = expr_lit.lit else { return None };\n\t\t\tSome(text.value().trim().to_string())\n\t\t})\n\t\t.fold(String::new(), |acc, b| acc + &b + \"\\n\");\n\n\tif is_node {\n\t\t// Data fields cannot be impl Node types\n\t\tif is_data_field {\n\t\t\treturn Err(Error::new_spanned(\n\t\t\t\t&ty,\n\t\t\t\t\"Data fields (#[data]) cannot be of type `impl Node`. Data fields must be concrete types that implement Default\",\n\t\t\t));\n\t\t}\n\n\t\tlet (input_type, output_type) = node_input_type\n\t\t\t.zip(node_output_type)\n\t\t\t.ok_or_else(|| Error::new_spanned(&ty, \"Invalid Node type. Expected `impl Node<Input, Output = OutputType>`\"))?;\n\t\tif !matches!(&value_source, ParsedValueSource::None) {\n\t\t\treturn Err(Error::new_spanned(&ty, \"No default values for `impl Node` allowed\"));\n\t\t}\n\t\tlet implementations = extract_attribute(attrs, \"implementations\")\n\t\t\t.map(|attr| parse_node_implementations(attr, ident))\n\t\t\t.transpose()?\n\t\t\t.unwrap_or_default();\n\n\t\tOk(ParsedField {\n\t\t\tpat_ident,\n\t\t\tty: ParsedFieldType::Node(NodeParsedField {\n\t\t\t\tinput_type,\n\t\t\t\toutput_type,\n\t\t\t\timplementations,\n\t\t\t}),\n\t\t\tname,\n\t\t\tdescription,\n\t\t\twidget_override,\n\t\t\tnumber_display_decimal_places,\n\t\t\tnumber_step,\n\t\t\tunit,\n\t\t\tis_data_field,\n\t\t})\n\t} else {\n\t\tlet implementations = extract_attribute(attrs, \"implementations\")\n\t\t\t.map(|attr| parse_implementations(attr, ident))\n\t\t\t.transpose()?\n\t\t\t.unwrap_or_default();\n\t\tOk(ParsedField {\n\t\t\tpat_ident,\n\t\t\tty: ParsedFieldType::Regular(RegularParsedField {\n\t\t\t\texposed,\n\t\t\t\tnumber_soft_min,\n\t\t\t\tnumber_soft_max,\n\t\t\t\tnumber_hard_min,\n\t\t\t\tnumber_hard_max,\n\t\t\t\tnumber_mode_range,\n\t\t\t\tty,\n\t\t\t\tvalue_source,\n\t\t\t\timplementations,\n\t\t\t\tgpu_image,\n\t\t\t}),\n\t\t\tname,\n\t\t\tdescription,\n\t\t\twidget_override,\n\t\t\tnumber_display_decimal_places,\n\t\t\tnumber_step,\n\t\t\tunit,\n\t\t\tis_data_field,\n\t\t})\n\t}\n}\n\nfn parse_node_type(ty: &Type) -> (bool, Option<Type>, Option<Type>) {\n\tif let Type::ImplTrait(impl_trait) = ty {\n\t\tfor bound in &impl_trait.bounds {\n\t\t\tif let syn::TypeParamBound::Trait(trait_bound) = bound\n\t\t\t\t&& trait_bound.path.segments.last().is_some_and(|seg| seg.ident == \"Node\")\n\t\t\t\t&& let syn::PathArguments::AngleBracketed(args) = &trait_bound.path.segments.last().unwrap().arguments\n\t\t\t{\n\t\t\t\tlet input_type = args.args.iter().find_map(|arg| if let syn::GenericArgument::Type(ty) = arg { Some(ty.clone()) } else { None });\n\t\t\t\tlet output_type = args.args.iter().find_map(|arg| {\n\t\t\t\t\tif let syn::GenericArgument::AssocType(assoc_type) = arg {\n\t\t\t\t\t\tif assoc_type.ident == \"Output\" { Some(assoc_type.ty.clone()) } else { None }\n\t\t\t\t\t} else {\n\t\t\t\t\t\tNone\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\treturn (true, input_type, output_type);\n\t\t\t}\n\t\t}\n\t}\n\t(false, None, None)\n}\n\nfn parse_output(output: &ReturnType) -> syn::Result<Type> {\n\tmatch output {\n\t\tReturnType::Default => Ok(syn::parse_quote!(())),\n\t\tReturnType::Type(_, ty) => Ok((**ty).clone()),\n\t}\n}\n\nfn extract_attribute<'a>(attrs: &'a [Attribute], name: &str) -> Option<&'a Attribute> {\n\tattrs.iter().find(|attr| attr.path().is_ident(name))\n}\n\n// Modify the new_node_fn function to use the code generation\npub fn new_node_fn(attr: TokenStream2, item: TokenStream2) -> syn::Result<TokenStream2> {\n\tlet crate_ident = CrateIdent::default();\n\tlet mut parsed_node = parse_node_fn(attr, item.clone()).map_err(|e| Error::new(e.span(), format!(\"Failed to parse node function:\\n{e}\")))?;\n\tparsed_node.replace_impl_trait_in_input();\n\tcrate::validation::validate_node_fn(&parsed_node).map_err(|e| Error::new(e.span(), format!(\"Validation error:\\n{e}\")))?;\n\tgenerate_node_code(&crate_ident, &parsed_node).map_err(|e| Error::new(e.span(), format!(\"Failed to generate node code:\\n{e}\")))\n}\n\nimpl ParsedNodeFn {\n\tpub fn replace_impl_trait_in_input(&mut self) {\n\t\tif let Type::ImplTrait(impl_trait) = self.input.ty.clone() {\n\t\t\tlet ident = Ident::new(\"_Input\", impl_trait.span());\n\t\t\tlet mut bounds = impl_trait.bounds;\n\t\t\tbounds.push(parse_quote!('n));\n\t\t\tself.fn_generics.push(GenericParam::Type(TypeParam {\n\t\t\t\tattrs: Default::default(),\n\t\t\t\tident: ident.clone(),\n\t\t\t\tcolon_token: Some(Default::default()),\n\t\t\t\tbounds,\n\t\t\t\teq_token: None,\n\t\t\t\tdefault: None,\n\t\t\t}));\n\t\t\tself.input.ty = parse_quote!(#ident);\n\t\t\tif self.input.implementations.is_empty() {\n\t\t\t\tself.input.implementations.push(parse_quote!(gcore::Context));\n\t\t\t}\n\t\t}\n\t\tif self.input.pat_ident.ident == \"_\" {\n\t\t\tself.input.pat_ident.ident = Ident::new(\"__ctx\", self.input.pat_ident.ident.span());\n\t\t}\n\t}\n}\n\n#[cfg(test)]\nmod tests {\n\tuse super::*;\n\tuse proc_macro2::Span;\n\tuse quote::{quote, quote_spanned};\n\tuse syn::parse_quote;\n\tfn pat_ident(name: &str) -> PatIdent {\n\t\tPatIdent {\n\t\t\tattrs: Vec::new(),\n\t\t\tby_ref: None,\n\t\t\tmutability: None,\n\t\t\tident: Ident::new(name, Span::call_site()),\n\t\t\tsubpat: None,\n\t\t}\n\t}\n\n\tfn assert_parsed_node_fn(parsed: &ParsedNodeFn, expected: &ParsedNodeFn) {\n\t\tassert_eq!(parsed.fn_name, expected.fn_name);\n\t\tassert_eq!(parsed.struct_name, expected.struct_name);\n\t\tassert_eq!(parsed.mod_name, expected.mod_name);\n\t\tassert_eq!(parsed.is_async, expected.is_async);\n\t\tassert_eq!(format!(\"{:?}\", parsed.input), format!(\"{:?}\", expected.input));\n\t\tassert_eq!(format!(\"{:?}\", parsed.output_type), format!(\"{:?}\", expected.output_type));\n\t\tassert_eq!(parsed.attributes.category, expected.attributes.category);\n\t\tassert_eq!(parsed.attributes.display_name, expected.attributes.display_name);\n\t\tassert_eq!(parsed.attributes.path, expected.attributes.path);\n\t\tassert_eq!(parsed.attributes.skip_impl, expected.attributes.skip_impl);\n\t\tassert_eq!(parsed.fields.len(), expected.fields.len());\n\t\tassert_eq!(parsed.description, expected.description);\n\n\t\tfor (parsed_field, expected_field) in parsed.fields.iter().zip(expected.fields.iter()) {\n\t\t\tmatch (parsed_field, expected_field) {\n\t\t\t\t(\n\t\t\t\t\tParsedField {\n\t\t\t\t\t\tpat_ident: p_name,\n\t\t\t\t\t\tty: ParsedFieldType::Regular(RegularParsedField {\n\t\t\t\t\t\t\tty: p_ty,\n\t\t\t\t\t\t\texposed: p_exp,\n\t\t\t\t\t\t\tvalue_source: p_default,\n\t\t\t\t\t\t\t..\n\t\t\t\t\t\t}),\n\t\t\t\t\t\t..\n\t\t\t\t\t},\n\t\t\t\t\tParsedField {\n\t\t\t\t\t\tpat_ident: e_name,\n\t\t\t\t\t\tty: ParsedFieldType::Regular(RegularParsedField {\n\t\t\t\t\t\t\tty: e_ty,\n\t\t\t\t\t\t\texposed: e_exp,\n\t\t\t\t\t\t\tvalue_source: e_default,\n\t\t\t\t\t\t\t..\n\t\t\t\t\t\t}),\n\t\t\t\t\t\t..\n\t\t\t\t\t},\n\t\t\t\t) => {\n\t\t\t\t\tassert_eq!(p_name, e_name);\n\t\t\t\t\tassert_eq!(p_exp, e_exp);\n\t\t\t\t\tmatch (p_default, e_default) {\n\t\t\t\t\t\t(ParsedValueSource::None, ParsedValueSource::None) => {}\n\t\t\t\t\t\t(ParsedValueSource::Default(p), ParsedValueSource::Default(e)) => {\n\t\t\t\t\t\t\tassert_eq!(p.to_token_stream().to_string(), e.to_token_stream().to_string());\n\t\t\t\t\t\t}\n\t\t\t\t\t\t(ParsedValueSource::Scope(p), ParsedValueSource::Scope(e)) => {\n\t\t\t\t\t\t\tassert_eq!(p.value(), e.value());\n\t\t\t\t\t\t}\n\t\t\t\t\t\t_ => panic!(\"Mismatched default values\"),\n\t\t\t\t\t}\n\t\t\t\t\tassert_eq!(format!(\"{p_ty:?}\"), format!(\"{:?}\", e_ty));\n\t\t\t\t}\n\t\t\t\t(\n\t\t\t\t\tParsedField {\n\t\t\t\t\t\tpat_ident: p_name,\n\t\t\t\t\t\tty: ParsedFieldType::Node(NodeParsedField {\n\t\t\t\t\t\t\tinput_type: p_input,\n\t\t\t\t\t\t\toutput_type: p_output,\n\t\t\t\t\t\t\t..\n\t\t\t\t\t\t}),\n\t\t\t\t\t\t..\n\t\t\t\t\t},\n\t\t\t\t\tParsedField {\n\t\t\t\t\t\tpat_ident: e_name,\n\t\t\t\t\t\tty: ParsedFieldType::Node(NodeParsedField {\n\t\t\t\t\t\t\tinput_type: e_input,\n\t\t\t\t\t\t\toutput_type: e_output,\n\t\t\t\t\t\t\t..\n\t\t\t\t\t\t}),\n\t\t\t\t\t\t..\n\t\t\t\t\t},\n\t\t\t\t) => {\n\t\t\t\t\tassert_eq!(p_name, e_name);\n\t\t\t\t\tassert_eq!(format!(\"{p_input:?}\"), format!(\"{:?}\", e_input));\n\t\t\t\t\tassert_eq!(format!(\"{p_output:?}\"), format!(\"{:?}\", e_output));\n\t\t\t\t}\n\t\t\t\t_ => panic!(\"Mismatched field types\"),\n\t\t\t}\n\t\t}\n\t}\n\n\t#[test]\n\tfn test_basic_node() {\n\t\tlet attr = quote!(category(\"Math: Arithmetic\"), path(core_types::TestNode), skip_impl);\n\t\tlet input = quote!(\n\t\t\t/// Multi\n\t\t\t/// Line\n\t\t\tfn add(a: f64, b: f64) -> f64 {\n\t\t\t\ta + b\n\t\t\t}\n\t\t);\n\n\t\tlet parsed = parse_node_fn(attr, input).unwrap();\n\t\tlet expected = ParsedNodeFn {\n\t\t\tvis: Visibility::Inherited,\n\t\t\tattributes: NodeFnAttributes {\n\t\t\t\tcategory: Some(parse_quote!(\"Math: Arithmetic\")),\n\t\t\t\tdisplay_name: None,\n\t\t\t\tpath: Some(parse_quote!(core_types::TestNode)),\n\t\t\t\tskip_impl: true,\n\t\t\t\tproperties_string: None,\n\t\t\t\tcfg: None,\n\t\t\t\tshader_node: None,\n\t\t\t\tserialize: None,\n\t\t\t},\n\t\t\tfn_name: Ident::new(\"add\", Span::call_site()),\n\t\t\tstruct_name: Ident::new(\"Add\", Span::call_site()),\n\t\t\tmod_name: Ident::new(\"add\", Span::call_site()),\n\t\t\tfn_generics: vec![],\n\t\t\twhere_clause: None,\n\t\t\tinput: Input {\n\t\t\t\tpat_ident: pat_ident(\"a\"),\n\t\t\t\tty: parse_quote!(f64),\n\t\t\t\timplementations: Punctuated::new(),\n\t\t\t\tcontext_features: vec![],\n\t\t\t},\n\t\t\toutput_type: parse_quote!(f64),\n\t\t\tis_async: false,\n\t\t\tfields: vec![ParsedField {\n\t\t\t\tpat_ident: pat_ident(\"b\"),\n\t\t\t\tname: None,\n\t\t\t\tdescription: String::new(),\n\t\t\t\twidget_override: ParsedWidgetOverride::None,\n\t\t\t\tty: ParsedFieldType::Regular(RegularParsedField {\n\t\t\t\t\tty: parse_quote!(f64),\n\t\t\t\t\texposed: false,\n\t\t\t\t\tvalue_source: ParsedValueSource::None,\n\t\t\t\t\tnumber_soft_min: None,\n\t\t\t\t\tnumber_soft_max: None,\n\t\t\t\t\tnumber_hard_min: None,\n\t\t\t\t\tnumber_hard_max: None,\n\t\t\t\t\tnumber_mode_range: None,\n\t\t\t\t\timplementations: Punctuated::new(),\n\t\t\t\t\tgpu_image: false,\n\t\t\t\t}),\n\t\t\t\tnumber_display_decimal_places: None,\n\t\t\t\tnumber_step: None,\n\t\t\t\tunit: None,\n\t\t\t\tis_data_field: false,\n\t\t\t}],\n\t\t\tbody: TokenStream2::new(),\n\t\t\tdescription: String::from(\"Multi\\nLine\\n\"),\n\t\t};\n\n\t\tassert_parsed_node_fn(&parsed, &expected);\n\t}\n\n\t#[test]\n\tfn test_node_with_impl_node() {\n\t\tlet attr = quote!(category(\"General\"));\n\t\tlet input = quote!(\n\t\t\t/**\n\t\t\t\tHello\n\t\t\t\tWorld\n\t\t\t*/\n\t\t\tfn transform<T: 'static>(footprint: Footprint, transform_target: impl Node<Footprint, Output = T>, translate: DVec2) -> T {\n\t\t\t\t// Implementation details...\n\t\t\t}\n\t\t);\n\n\t\tlet parsed = parse_node_fn(attr, input).unwrap();\n\t\tlet expected = ParsedNodeFn {\n\t\t\tvis: Visibility::Inherited,\n\t\t\tattributes: NodeFnAttributes {\n\t\t\t\tcategory: Some(parse_quote!(\"General\")),\n\t\t\t\tdisplay_name: None,\n\t\t\t\tpath: None,\n\t\t\t\tskip_impl: false,\n\t\t\t\tproperties_string: None,\n\t\t\t\tcfg: None,\n\t\t\t\tshader_node: None,\n\t\t\t\tserialize: None,\n\t\t\t},\n\t\t\tfn_name: Ident::new(\"transform\", Span::call_site()),\n\t\t\tstruct_name: Ident::new(\"Transform\", Span::call_site()),\n\t\t\tmod_name: Ident::new(\"transform\", Span::call_site()),\n\t\t\tfn_generics: vec![parse_quote!(T: 'static)],\n\t\t\twhere_clause: None,\n\t\t\tinput: Input {\n\t\t\t\tpat_ident: pat_ident(\"footprint\"),\n\t\t\t\tty: parse_quote!(Footprint),\n\t\t\t\timplementations: Punctuated::new(),\n\t\t\t\tcontext_features: vec![],\n\t\t\t},\n\t\t\toutput_type: parse_quote!(T),\n\t\t\tis_async: false,\n\t\t\tfields: vec![\n\t\t\t\tParsedField {\n\t\t\t\t\tpat_ident: pat_ident(\"transform_target\"),\n\t\t\t\t\tname: None,\n\t\t\t\t\tdescription: String::new(),\n\t\t\t\t\twidget_override: ParsedWidgetOverride::None,\n\t\t\t\t\tty: ParsedFieldType::Node(NodeParsedField {\n\t\t\t\t\t\tinput_type: parse_quote!(Footprint),\n\t\t\t\t\t\toutput_type: parse_quote!(T),\n\t\t\t\t\t\timplementations: Punctuated::new(),\n\t\t\t\t\t}),\n\t\t\t\t\tnumber_display_decimal_places: None,\n\t\t\t\t\tnumber_step: None,\n\t\t\t\t\tunit: None,\n\t\t\t\t\tis_data_field: false,\n\t\t\t\t},\n\t\t\t\tParsedField {\n\t\t\t\t\tpat_ident: pat_ident(\"translate\"),\n\t\t\t\t\tname: None,\n\t\t\t\t\tdescription: String::new(),\n\t\t\t\t\twidget_override: ParsedWidgetOverride::None,\n\t\t\t\t\tty: ParsedFieldType::Regular(RegularParsedField {\n\t\t\t\t\t\tty: parse_quote!(DVec2),\n\t\t\t\t\t\texposed: false,\n\t\t\t\t\t\tvalue_source: ParsedValueSource::None,\n\t\t\t\t\t\tnumber_soft_min: None,\n\t\t\t\t\t\tnumber_soft_max: None,\n\t\t\t\t\t\tnumber_hard_min: None,\n\t\t\t\t\t\tnumber_hard_max: None,\n\t\t\t\t\t\tnumber_mode_range: None,\n\t\t\t\t\t\timplementations: Punctuated::new(),\n\t\t\t\t\t\tgpu_image: false,\n\t\t\t\t\t}),\n\t\t\t\t\tnumber_display_decimal_places: None,\n\t\t\t\t\tnumber_step: None,\n\t\t\t\t\tunit: None,\n\t\t\t\t\tis_data_field: false,\n\t\t\t\t},\n\t\t\t],\n\t\t\tbody: TokenStream2::new(),\n\t\t\tdescription: String::from(\"Hello\\n\\t\\t\\t\\tWorld\\n\"),\n\t\t};\n\n\t\tassert_parsed_node_fn(&parsed, &expected);\n\t}\n\n\t#[test]\n\tfn test_node_with_default_values() {\n\t\tlet attr = quote!(category(\"Vector: Shape\"));\n\t\tlet input = quote!(\n\t\t\t/// Test\n\t\t\tfn circle(_: impl Ctx + ExtractFootprint, #[default(50.)] radius: f64) -> Vector {\n\t\t\t\t// Implementation details...\n\t\t\t}\n\t\t);\n\n\t\tlet parsed = parse_node_fn(attr, input).unwrap();\n\t\tlet expected = ParsedNodeFn {\n\t\t\tvis: Visibility::Inherited,\n\t\t\tattributes: NodeFnAttributes {\n\t\t\t\tcategory: Some(parse_quote!(\"Vector: Shape\")),\n\t\t\t\tdisplay_name: None,\n\t\t\t\tpath: None,\n\t\t\t\tskip_impl: false,\n\t\t\t\tproperties_string: None,\n\t\t\t\tcfg: None,\n\t\t\t\tshader_node: None,\n\t\t\t\tserialize: None,\n\t\t\t},\n\t\t\tfn_name: Ident::new(\"circle\", Span::call_site()),\n\t\t\tstruct_name: Ident::new(\"Circle\", Span::call_site()),\n\t\t\tmod_name: Ident::new(\"circle\", Span::call_site()),\n\t\t\tfn_generics: vec![],\n\t\t\twhere_clause: None,\n\t\t\tinput: Input {\n\t\t\t\tpat_ident: pat_ident(\"_\"),\n\t\t\t\tty: parse_quote!(impl Ctx + ExtractFootprint),\n\t\t\t\timplementations: Punctuated::new(),\n\t\t\t\tcontext_features: vec![format_ident!(\"ExtractFootprint\")],\n\t\t\t},\n\t\t\toutput_type: parse_quote!(Vector),\n\t\t\tis_async: false,\n\t\t\tfields: vec![ParsedField {\n\t\t\t\tpat_ident: pat_ident(\"radius\"),\n\t\t\t\tname: None,\n\t\t\t\tdescription: String::new(),\n\t\t\t\twidget_override: ParsedWidgetOverride::None,\n\t\t\t\tty: ParsedFieldType::Regular(RegularParsedField {\n\t\t\t\t\tty: parse_quote!(f64),\n\t\t\t\t\texposed: false,\n\t\t\t\t\tvalue_source: ParsedValueSource::Default(quote!(50.)),\n\t\t\t\t\tnumber_soft_min: None,\n\t\t\t\t\tnumber_soft_max: None,\n\t\t\t\t\tnumber_hard_min: None,\n\t\t\t\t\tnumber_hard_max: None,\n\t\t\t\t\tnumber_mode_range: None,\n\t\t\t\t\timplementations: Punctuated::new(),\n\t\t\t\t\tgpu_image: false,\n\t\t\t\t}),\n\t\t\t\tnumber_display_decimal_places: None,\n\t\t\t\tnumber_step: None,\n\t\t\t\tunit: None,\n\t\t\t\tis_data_field: false,\n\t\t\t}],\n\t\t\tbody: TokenStream2::new(),\n\t\t\tdescription: \"Test\\n\".into(),\n\t\t};\n\n\t\tassert_parsed_node_fn(&parsed, &expected);\n\t}\n\n\t#[test]\n\tfn test_node_with_implementations() {\n\t\tlet attr = quote!(category(\"Raster: Adjustment\"));\n\t\tlet input = quote!(\n\t\t\tfn levels<P: Pixel>(image: Table<Raster<P>>, #[implementations(f32, f64)] shadows: f64) -> Table<Raster<P>> {\n\t\t\t\t// Implementation details...\n\t\t\t}\n\t\t);\n\n\t\tlet parsed = parse_node_fn(attr, input).unwrap();\n\t\tlet expected = ParsedNodeFn {\n\t\t\tvis: Visibility::Inherited,\n\t\t\tattributes: NodeFnAttributes {\n\t\t\t\tcategory: Some(parse_quote!(\"Raster: Adjustment\")),\n\t\t\t\tdisplay_name: None,\n\t\t\t\tpath: None,\n\t\t\t\tskip_impl: false,\n\t\t\t\tproperties_string: None,\n\t\t\t\tcfg: None,\n\t\t\t\tshader_node: None,\n\t\t\t\tserialize: None,\n\t\t\t},\n\t\t\tfn_name: Ident::new(\"levels\", Span::call_site()),\n\t\t\tstruct_name: Ident::new(\"Levels\", Span::call_site()),\n\t\t\tmod_name: Ident::new(\"levels\", Span::call_site()),\n\t\t\tfn_generics: vec![parse_quote!(P: Pixel)],\n\t\t\twhere_clause: None,\n\t\t\tinput: Input {\n\t\t\t\tpat_ident: pat_ident(\"image\"),\n\t\t\t\tty: parse_quote!(Table<Raster<P>>),\n\t\t\t\timplementations: Punctuated::new(),\n\t\t\t\tcontext_features: vec![],\n\t\t\t},\n\t\t\toutput_type: parse_quote!(Table<Raster<P>>),\n\t\t\tis_async: false,\n\t\t\tfields: vec![ParsedField {\n\t\t\t\tpat_ident: pat_ident(\"shadows\"),\n\t\t\t\tname: None,\n\t\t\t\tdescription: String::new(),\n\t\t\t\twidget_override: ParsedWidgetOverride::None,\n\t\t\t\tty: ParsedFieldType::Regular(RegularParsedField {\n\t\t\t\t\tty: parse_quote!(f64),\n\t\t\t\t\texposed: false,\n\t\t\t\t\tvalue_source: ParsedValueSource::None,\n\t\t\t\t\tnumber_soft_min: None,\n\t\t\t\t\tnumber_soft_max: None,\n\t\t\t\t\tnumber_hard_min: None,\n\t\t\t\t\tnumber_hard_max: None,\n\t\t\t\t\tnumber_mode_range: None,\n\t\t\t\t\timplementations: {\n\t\t\t\t\t\tlet mut p = Punctuated::new();\n\t\t\t\t\t\tp.push(parse_quote!(f32));\n\t\t\t\t\t\tp.push(parse_quote!(f64));\n\t\t\t\t\t\tp\n\t\t\t\t\t},\n\t\t\t\t\tgpu_image: false,\n\t\t\t\t}),\n\t\t\t\tnumber_display_decimal_places: None,\n\t\t\t\tnumber_step: None,\n\t\t\t\tunit: None,\n\t\t\t\tis_data_field: false,\n\t\t\t}],\n\t\t\tbody: TokenStream2::new(),\n\t\t\tdescription: String::new(),\n\t\t};\n\n\t\tassert_parsed_node_fn(&parsed, &expected);\n\t}\n\n\t#[test]\n\tfn test_number_min_max_range_mode() {\n\t\tlet attr = quote!(category(\"Math: Arithmetic\"), path(core_types::TestNode));\n\t\tlet input = quote!(\n\t\t\tfn add(\n\t\t\t\ta: f64,\n\t\t\t\t/// b\n\t\t\t\t#[range((0., 100.))]\n\t\t\t\t#[soft_min(-500.)]\n\t\t\t\t#[soft_max(500.)]\n\t\t\t\tb: f64,\n\t\t\t) -> f64 {\n\t\t\t\ta + b\n\t\t\t}\n\t\t);\n\n\t\tlet parsed = parse_node_fn(attr, input).unwrap();\n\t\tlet expected = ParsedNodeFn {\n\t\t\tvis: Visibility::Inherited,\n\t\t\tattributes: NodeFnAttributes {\n\t\t\t\tcategory: Some(parse_quote!(\"Math: Arithmetic\")),\n\t\t\t\tdisplay_name: None,\n\t\t\t\tpath: Some(parse_quote!(core_types::TestNode)),\n\t\t\t\tskip_impl: false,\n\t\t\t\tproperties_string: None,\n\t\t\t\tcfg: None,\n\t\t\t\tshader_node: None,\n\t\t\t\tserialize: None,\n\t\t\t},\n\t\t\tfn_name: Ident::new(\"add\", Span::call_site()),\n\t\t\tstruct_name: Ident::new(\"Add\", Span::call_site()),\n\t\t\tmod_name: Ident::new(\"add\", Span::call_site()),\n\t\t\tfn_generics: vec![],\n\t\t\twhere_clause: None,\n\t\t\tinput: Input {\n\t\t\t\tpat_ident: pat_ident(\"a\"),\n\t\t\t\tty: parse_quote!(f64),\n\t\t\t\timplementations: Punctuated::new(),\n\t\t\t\tcontext_features: vec![],\n\t\t\t},\n\t\t\toutput_type: parse_quote!(f64),\n\t\t\tis_async: false,\n\t\t\tfields: vec![ParsedField {\n\t\t\t\tpat_ident: pat_ident(\"b\"),\n\t\t\t\tname: None,\n\t\t\t\tdescription: String::from(\"b\"),\n\t\t\t\twidget_override: ParsedWidgetOverride::None,\n\t\t\t\tty: ParsedFieldType::Regular(RegularParsedField {\n\t\t\t\t\tty: parse_quote!(f64),\n\t\t\t\t\texposed: false,\n\t\t\t\t\tvalue_source: ParsedValueSource::None,\n\t\t\t\t\tnumber_soft_min: Some(parse_quote!(-500.)),\n\t\t\t\t\tnumber_soft_max: Some(parse_quote!(500.)),\n\t\t\t\t\tnumber_hard_min: None,\n\t\t\t\t\tnumber_hard_max: None,\n\t\t\t\t\tnumber_mode_range: Some(parse_quote!((0., 100.))),\n\t\t\t\t\timplementations: Punctuated::new(),\n\t\t\t\t\tgpu_image: false,\n\t\t\t\t}),\n\t\t\t\tnumber_display_decimal_places: None,\n\t\t\t\tnumber_step: None,\n\t\t\t\tunit: None,\n\t\t\t\tis_data_field: false,\n\t\t\t}],\n\t\t\tbody: TokenStream2::new(),\n\t\t\tdescription: String::new(),\n\t\t};\n\n\t\tassert_parsed_node_fn(&parsed, &expected);\n\t}\n\n\t#[test]\n\tfn test_async_node() {\n\t\tlet attr = quote!(category(\"IO\"));\n\t\tlet input = quote!(\n\t\t\tasync fn load_image(api: &WasmEditorApi, #[expose] path: String) -> Table<Raster<CPU>> {\n\t\t\t\t// Implementation details...\n\t\t\t}\n\t\t);\n\n\t\tlet parsed = parse_node_fn(attr, input).unwrap();\n\t\tlet expected = ParsedNodeFn {\n\t\t\tvis: Visibility::Inherited,\n\t\t\tattributes: NodeFnAttributes {\n\t\t\t\tcategory: Some(parse_quote!(\"IO\")),\n\t\t\t\tdisplay_name: None,\n\t\t\t\tpath: None,\n\t\t\t\tskip_impl: false,\n\t\t\t\tproperties_string: None,\n\t\t\t\tcfg: None,\n\t\t\t\tshader_node: None,\n\t\t\t\tserialize: None,\n\t\t\t},\n\t\t\tfn_name: Ident::new(\"load_image\", Span::call_site()),\n\t\t\tstruct_name: Ident::new(\"LoadImage\", Span::call_site()),\n\t\t\tmod_name: Ident::new(\"load_image\", Span::call_site()),\n\t\t\tfn_generics: vec![],\n\t\t\twhere_clause: None,\n\t\t\tinput: Input {\n\t\t\t\tpat_ident: pat_ident(\"api\"),\n\t\t\t\tty: parse_quote!(&WasmEditorApi),\n\t\t\t\timplementations: Punctuated::new(),\n\t\t\t\tcontext_features: vec![],\n\t\t\t},\n\t\t\toutput_type: parse_quote!(Table<Raster<CPU>>),\n\t\t\tis_async: true,\n\t\t\tfields: vec![ParsedField {\n\t\t\t\tpat_ident: pat_ident(\"path\"),\n\t\t\t\tname: None,\n\t\t\t\tdescription: String::new(),\n\t\t\t\twidget_override: ParsedWidgetOverride::None,\n\t\t\t\tty: ParsedFieldType::Regular(RegularParsedField {\n\t\t\t\t\tty: parse_quote!(String),\n\t\t\t\t\texposed: true,\n\t\t\t\t\tvalue_source: ParsedValueSource::None,\n\t\t\t\t\tnumber_soft_min: None,\n\t\t\t\t\tnumber_soft_max: None,\n\t\t\t\t\tnumber_hard_min: None,\n\t\t\t\t\tnumber_hard_max: None,\n\t\t\t\t\tnumber_mode_range: None,\n\t\t\t\t\timplementations: Punctuated::new(),\n\t\t\t\t\tgpu_image: false,\n\t\t\t\t}),\n\t\t\t\tnumber_display_decimal_places: None,\n\t\t\t\tnumber_step: None,\n\t\t\t\tunit: None,\n\t\t\t\tis_data_field: false,\n\t\t\t}],\n\t\t\tbody: TokenStream2::new(),\n\t\t\tdescription: String::new(),\n\t\t};\n\n\t\tassert_parsed_node_fn(&parsed, &expected);\n\t}\n\n\t#[test]\n\tfn test_node_with_custom_name() {\n\t\tlet attr = quote!(category(\"Custom\"), name(\"CustomNode2\"));\n\t\tlet input = quote!(\n\t\t\tfn custom_node(input: i32) -> i32 {\n\t\t\t\tinput * 2\n\t\t\t}\n\t\t);\n\n\t\tlet parsed = parse_node_fn(attr, input).unwrap();\n\t\tlet expected = ParsedNodeFn {\n\t\t\tvis: Visibility::Inherited,\n\t\t\tattributes: NodeFnAttributes {\n\t\t\t\tcategory: Some(parse_quote!(\"Custom\")),\n\t\t\t\tdisplay_name: Some(parse_quote!(\"CustomNode2\")),\n\t\t\t\tpath: None,\n\t\t\t\tskip_impl: false,\n\t\t\t\tproperties_string: None,\n\t\t\t\tcfg: None,\n\t\t\t\tshader_node: None,\n\t\t\t\tserialize: None,\n\t\t\t},\n\t\t\tfn_name: Ident::new(\"custom_node\", Span::call_site()),\n\t\t\tstruct_name: Ident::new(\"CustomNode\", Span::call_site()),\n\t\t\tmod_name: Ident::new(\"custom_node\", Span::call_site()),\n\t\t\tfn_generics: vec![],\n\t\t\twhere_clause: None,\n\t\t\tinput: Input {\n\t\t\t\tpat_ident: pat_ident(\"input\"),\n\t\t\t\tty: parse_quote!(i32),\n\t\t\t\timplementations: Punctuated::new(),\n\t\t\t\tcontext_features: vec![],\n\t\t\t},\n\t\t\toutput_type: parse_quote!(i32),\n\t\t\tis_async: false,\n\t\t\tfields: vec![],\n\t\t\tbody: TokenStream2::new(),\n\t\t\tdescription: String::new(),\n\t\t};\n\n\t\tassert_parsed_node_fn(&parsed, &expected);\n\t}\n\n\t#[test]\n\t#[should_panic(expected = \"Multiple 'category' attributes are not allowed\")]\n\tfn test_multiple_categories() {\n\t\tlet attr = quote!(category(\"Math: Arithmetic\"), category(\"General\"));\n\t\tlet input = quote!(\n\t\t\tfn add(a: i32, b: i32) -> i32 {\n\t\t\t\ta + b\n\t\t\t}\n\t\t);\n\t\tparse_node_fn(attr, input).unwrap();\n\t}\n\n\t#[test]\n\t#[should_panic(expected = \"Call argument cannot be given a default value\")]\n\tfn test_default_value_for_first_arg() {\n\t\tlet attr = quote!(category(\"Invalid\"));\n\t\tlet input = quote!(\n\t\t\tfn invalid_node(#[default(())] node: impl Node<(), Output = i32>) -> i32 {\n\t\t\t\tnode.eval(())\n\t\t\t}\n\t\t);\n\t\tparse_node_fn(attr, input).unwrap();\n\t}\n\n\t#[test]\n\t#[should_panic(expected = \"No default values for `impl Node` allowed\")]\n\tfn test_default_value_for_impl_node() {\n\t\tlet attr = quote!(category(\"Invalid\"));\n\t\tlet input = quote!(\n\t\t\tfn invalid_node(_: (), #[default(())] node: impl Node<(), Output = i32>) -> i32 {\n\t\t\t\tnode.eval(())\n\t\t\t}\n\t\t);\n\t\tparse_node_fn(attr, input).unwrap();\n\t}\n\n\t#[test]\n\t#[should_panic(expected = \"Unsupported attribute in `node`\")]\n\tfn test_unsupported_attribute() {\n\t\tlet attr = quote!(unsupported(\"Value\"));\n\t\tlet input = quote!(\n\t\t\tfn test_node(input: i32) -> i32 {\n\t\t\t\tinput\n\t\t\t}\n\t\t);\n\t\tparse_node_fn(attr, input).unwrap();\n\t}\n\n\t#[test]\n\tfn test_invalid_implementation_syntax() {\n\t\tlet attr = quote!(category(\"Test\"));\n\t\tlet input = quote!(\n\t\t\tfn test_node(_: (), #[implementations((Footprint, Color), (Footprint, Table<Raster<CPU>>))] input: impl Node<Footprint, Output = T>) -> T {\n\t\t\t\t// Implementation details...\n\t\t\t}\n\t\t);\n\n\t\tlet result = parse_node_fn(attr, input);\n\t\tassert!(result.is_err());\n\t\tlet error = result.unwrap_err();\n\t\tlet error_message = error.to_string();\n\t\tassert!(error_message.contains(\"Invalid #[implementations(...)] for argument `input`\"));\n\t\tassert!(error_message.contains(\"Expected a comma-separated list of `InputType -> OutputType` pairs\"));\n\t\tassert!(error_message.contains(\"Expected `->` arrow after input type in #[implementations(...)] on a field of type `impl Node`\"));\n\t}\n\n\t#[test]\n\tfn test_implementation_on_first_arg() {\n\t\tlet attr = quote!(category(\"Test\"));\n\n\t\t// Use quote_spanned! to attach a specific span to the problematic part\n\t\tlet problem_span = Span::call_site(); // You could create a custom span here if needed\n\t\tlet tuples = quote_spanned!(problem_span=> () ());\n\t\tlet input = quote! {\n\t\t\tfn test_node(\n\t\t\t\t#[implementations((), #tuples, Footprint)]\n\t\t\t\tfootprint: F,\n\t\t\t\t#[implementations(\n\t\t\t\t\t() -> Table<Raster<CPU>>,\n\t\t\t\t\t() -> Table<Color>,\n\t\t\t\t\t() -> Table<GradientStops>,\n\t\t\t\t\tFootprint -> Table<Raster<CPU>>,\n\t\t\t\t\tFootprint -> Table<Color>,\n\t\t\t\t\tFootprint -> Table<GradientStops>,\n\t\t\t\t)]\n\t\t\t\timage: impl Node<F, Output = T>,\n\t\t\t) -> T {\n\t\t\t\t// Implementation details...\n\t\t\t}\n\t\t};\n\n\t\tlet result = parse_node_fn(attr, input);\n\t\tassert!(result.is_err(), \"Expected an error, but parsing succeeded\");\n\n\t\tlet error = result.unwrap_err();\n\t\tlet error_string = error.to_string();\n\t\tassert!(error_string.contains(\"Failed to parse implementations for argument 'footprint'\"));\n\t\tassert!(error_string.contains(\"expected `,`\"));\n\n\t\t// Instead of checking for exact line and column,\n\t\t// verify that the error span is the one we specified\n\t\tassert_eq!(error.span().start(), problem_span.start());\n\t}\n}\n"
  },
  {
    "path": "node-graph/node-macro/src/shader_nodes/mod.rs",
    "content": "use crate::crate_ident::CrateIdent;\nuse crate::parsing::{NodeFnAttributes, ParsedNodeFn};\nuse crate::shader_nodes::per_pixel_adjust::PerPixelAdjust;\nuse proc_macro2::{Ident, TokenStream};\nuse quote::quote;\nuse strum::VariantNames;\nuse syn::parse::{Parse, ParseStream};\nuse syn::punctuated::Punctuated;\nuse syn::{Error, Token};\n\npub mod per_pixel_adjust;\n\npub const STD_FEATURE_GATE: &str = \"std\";\npub const SHADER_NODES_FEATURE_GATE: &str = \"shader-nodes\";\n\npub fn modify_cfg(attributes: &NodeFnAttributes) -> TokenStream {\n\tlet feature_gate = match &attributes.shader_node {\n\t\t// shader node cfg is done on the mod\n\t\tSome(ShaderNodeType::ShaderNode) => quote!(),\n\t\tSome(_) => quote!(feature = #STD_FEATURE_GATE),\n\t\tNone => quote!(),\n\t};\n\tlet cfgs: Punctuated<_, Token![,]> = match &attributes.cfg {\n\t\tNone => [&feature_gate].into_iter().collect(),\n\t\tSome(cfg) => [cfg, &feature_gate].into_iter().collect(),\n\t};\n\tquote!(#[cfg(all(#cfgs))])\n}\n\n#[derive(Debug, Clone, VariantNames)]\npub(crate) enum ShaderNodeType {\n\t/// Marker for this node being in a gpu node crate, but not having a gpu implementation. This is distinct from not\n\t/// declaring `shader_node` at all, as it will wrap the CPU node with a `#[cfg(feature = \"std\")]` feature gate.\n\tNone,\n\t/// Marker for this node being a generated gpu node implementation, that should not emit anything to prevent\n\t/// recursively generating more gpu nodes. But it still counts as a gpu node and will get the\n\t/// `#[cfg(feature = \"std\")]` feature gate around it's impl.\n\tShaderNode,\n\tPerPixelAdjust(PerPixelAdjust),\n}\n\nimpl Parse for ShaderNodeType {\n\tfn parse(input: ParseStream) -> syn::Result<Self> {\n\t\tlet ident: Ident = input.parse()?;\n\t\tOk(match ident.to_string().as_str() {\n\t\t\t\"None\" => ShaderNodeType::None,\n\t\t\t\"PerPixelAdjust\" => ShaderNodeType::PerPixelAdjust(PerPixelAdjust::parse(input)?),\n\t\t\t_ => return Err(Error::new_spanned(&ident, format!(\"attr 'shader_node' must be one of {:?}\", Self::VARIANTS))),\n\t\t})\n\t}\n}\n\npub trait ShaderCodegen {\n\tfn codegen(&self, crate_ident: &CrateIdent, parsed: &ParsedNodeFn) -> syn::Result<ShaderTokens>;\n}\n\nimpl ShaderCodegen for ShaderNodeType {\n\tfn codegen(&self, crate_ident: &CrateIdent, parsed: &ParsedNodeFn) -> syn::Result<ShaderTokens> {\n\t\tmatch self {\n\t\t\tShaderNodeType::None | ShaderNodeType::ShaderNode => (),\n\t\t\t_ => {\n\t\t\t\tif parsed.is_async {\n\t\t\t\t\treturn Err(Error::new_spanned(&parsed.fn_name, \"Shader nodes must not be async\"));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tmatch self {\n\t\t\tShaderNodeType::None | ShaderNodeType::ShaderNode => Ok(ShaderTokens::default()),\n\t\t\tShaderNodeType::PerPixelAdjust(x) => x.codegen(crate_ident, parsed),\n\t\t}\n\t}\n}\n\n#[derive(Clone, Default)]\npub struct ShaderTokens {\n\tpub shader_entry_point: TokenStream,\n\tpub gpu_node: TokenStream,\n}\n"
  },
  {
    "path": "node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs",
    "content": "use crate::crate_ident::CrateIdent;\nuse crate::parsing::{Input, NodeFnAttributes, ParsedField, ParsedFieldType, ParsedNodeFn, ParsedValueSource, RegularParsedField};\nuse crate::shader_nodes::{SHADER_NODES_FEATURE_GATE, ShaderCodegen, ShaderNodeType, ShaderTokens};\nuse convert_case::{Case, Casing};\nuse proc_macro2::{Ident, Span, TokenStream};\nuse quote::{ToTokens, format_ident, quote};\nuse std::borrow::Cow;\nuse syn::parse::{Parse, ParseStream};\nuse syn::punctuated::Punctuated;\nuse syn::{LitStr, PatIdent, Type, parse_quote};\n\n#[derive(Debug, Clone)]\npub struct PerPixelAdjust {}\n\nimpl Parse for PerPixelAdjust {\n\tfn parse(_input: ParseStream) -> syn::Result<Self> {\n\t\tOk(Self {})\n\t}\n}\n\nimpl ShaderCodegen for PerPixelAdjust {\n\tfn codegen(&self, crate_ident: &CrateIdent, parsed: &ParsedNodeFn) -> syn::Result<ShaderTokens> {\n\t\tlet fn_name = &parsed.fn_name;\n\n\t\tlet mut params;\n\t\tlet has_uniform;\n\t\t{\n\t\t\t// categorize params\n\t\t\tparams = parsed\n\t\t\t\t.fields\n\t\t\t\t.iter()\n\t\t\t\t.map(|f| {\n\t\t\t\t\tlet ident = &f.pat_ident;\n\t\t\t\t\tmatch &f.ty {\n\t\t\t\t\t\tParsedFieldType::Node { .. } => Err(syn::Error::new_spanned(ident, \"PerPixelAdjust shader nodes cannot accept other nodes as generics\")),\n\t\t\t\t\t\tParsedFieldType::Regular(RegularParsedField { gpu_image: false, ty, .. }) => Ok(Param {\n\t\t\t\t\t\t\tident: Cow::Borrowed(&ident.ident),\n\t\t\t\t\t\t\tty: ty.to_token_stream(),\n\t\t\t\t\t\t\tparam_type: ParamType::Uniform,\n\t\t\t\t\t\t}),\n\t\t\t\t\t\tParsedFieldType::Regular(RegularParsedField { gpu_image: true, .. }) => {\n\t\t\t\t\t\t\tlet param = Param {\n\t\t\t\t\t\t\t\tident: Cow::Owned(format_ident!(\"image_{}\", &ident.ident)),\n\t\t\t\t\t\t\t\tty: quote!(Image2d),\n\t\t\t\t\t\t\t\tparam_type: ParamType::Image { binding: 0 },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tOk(param)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.collect::<syn::Result<Vec<_>>>()?;\n\n\t\t\thas_uniform = params.iter().any(|p| matches!(p.param_type, ParamType::Uniform));\n\n\t\t\t// assign image bindings\n\t\t\t// if an arg_buffer exists, bindings for images start at 1 to leave 0 for arg buffer\n\t\t\tlet mut binding_cnt = if has_uniform { 1 } else { 0 };\n\t\t\tfor p in params.iter_mut() {\n\t\t\t\tmatch &mut p.param_type {\n\t\t\t\t\tParamType::Image { binding } => {\n\t\t\t\t\t\t*binding = binding_cnt;\n\t\t\t\t\t\tbinding_cnt += 1;\n\t\t\t\t\t}\n\t\t\t\t\tParamType::Uniform => {}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tlet entry_point_mod = format_ident!(\"{}_gpu_entry_point\", fn_name);\n\t\tlet entry_point_name_ident = format_ident!(\"ENTRY_POINT_NAME\");\n\t\tlet entry_point_name = quote!(#entry_point_mod::#entry_point_name_ident);\n\t\tlet uniform_struct_ident = format_ident!(\"Uniform\");\n\t\tlet uniform_struct = quote!(#entry_point_mod::#uniform_struct_ident);\n\t\tlet shader_node_mod = format_ident!(\"{}_shader_node\", fn_name);\n\n\t\tlet codegen = PerPixelAdjustCodegen {\n\t\t\tcrate_ident,\n\t\t\tparsed,\n\t\t\tparams,\n\t\t\thas_uniform,\n\t\t\tentry_point_mod,\n\t\t\tentry_point_name_ident,\n\t\t\tentry_point_name,\n\t\t\tuniform_struct_ident,\n\t\t\tuniform_struct,\n\t\t\tshader_node_mod,\n\t\t};\n\n\t\tOk(ShaderTokens {\n\t\t\tshader_entry_point: codegen.codegen_shader_entry_point()?,\n\t\t\tgpu_node: codegen.codegen_gpu_node()?,\n\t\t})\n\t}\n}\n\npub struct PerPixelAdjustCodegen<'a> {\n\tcrate_ident: &'a CrateIdent,\n\tparsed: &'a ParsedNodeFn,\n\tparams: Vec<Param<'a>>,\n\thas_uniform: bool,\n\tentry_point_mod: Ident,\n\tentry_point_name_ident: Ident,\n\tentry_point_name: TokenStream,\n\tuniform_struct_ident: Ident,\n\tuniform_struct: TokenStream,\n\tshader_node_mod: Ident,\n}\n\nimpl PerPixelAdjustCodegen<'_> {\n\tfn codegen_shader_entry_point(&self) -> syn::Result<TokenStream> {\n\t\tlet fn_name = &self.parsed.fn_name;\n\t\tlet gcore_shaders = self.crate_ident.gcore_shaders()?;\n\t\tlet reexport = quote!(#gcore_shaders::shaders::__private);\n\n\t\tlet uniform_members = self\n\t\t\t.params\n\t\t\t.iter()\n\t\t\t.filter_map(|Param { ident, ty, param_type }| match param_type {\n\t\t\t\tParamType::Image { .. } => None,\n\t\t\t\tParamType::Uniform => Some(quote! {#ident: #ty}),\n\t\t\t})\n\t\t\t.collect::<Vec<_>>();\n\t\tlet uniform_struct_ident = &self.uniform_struct_ident;\n\t\tlet uniform_struct = parse_quote! {\n\t\t\t#[repr(C)]\n\t\t\t#[derive(Copy, Clone)]\n\t\t\tpub struct #uniform_struct_ident {\n\t\t\t\t#(pub #uniform_members),*\n\t\t\t}\n\t\t};\n\t\tlet uniform_struct_shader_struct_derive = crate::buffer_struct::derive_buffer_struct_struct(self.crate_ident, &uniform_struct)?;\n\n\t\tlet image_params = self\n\t\t\t.params\n\t\t\t.iter()\n\t\t\t.filter_map(|Param { ident, ty, param_type }| match param_type {\n\t\t\t\tParamType::Image { binding } => Some(quote! {#[spirv(descriptor_set = 0, binding = #binding)] #ident: &#ty}),\n\t\t\t\tParamType::Uniform => None,\n\t\t\t})\n\t\t\t.collect::<Vec<_>>();\n\t\tlet call_args = self\n\t\t\t.params\n\t\t\t.iter()\n\t\t\t.map(|Param { ident, param_type, .. }| match param_type {\n\t\t\t\tParamType::Image { .. } => quote!(Color::from_vec4(#ident.fetch_with(texel_coord, lod(0)))),\n\t\t\t\tParamType::Uniform => quote!(uniform.#ident),\n\t\t\t})\n\t\t\t.collect::<Vec<_>>();\n\t\tlet context = quote!(());\n\n\t\tlet entry_point_mod = &self.entry_point_mod;\n\t\tlet entry_point_name = &self.entry_point_name_ident;\n\t\tOk(quote! {\n\t\t\tpub mod #entry_point_mod {\n\t\t\t\tuse super::*;\n\t\t\t\tuse #gcore_shaders::color::Color;\n\t\t\t\tuse #reexport::glam::{Vec4, Vec4Swizzles};\n\t\t\t\tuse #reexport::spirv_std::spirv;\n\t\t\t\tuse #reexport::spirv_std::image::{Image2d, ImageWithMethods};\n\t\t\t\tuse #reexport::spirv_std::image::sample_with::lod;\n\n\t\t\t\tpub const #entry_point_name: &str = core::concat!(core::module_path!(), \"::entry_point\");\n\n\t\t\t\t#uniform_struct\n\t\t\t\t#uniform_struct_shader_struct_derive\n\n\t\t\t\t#[spirv(fragment)]\n\t\t\t\tpub fn entry_point(\n\t\t\t\t\t#[spirv(frag_coord)] frag_coord: Vec4,\n\t\t\t\t\tcolor_out: &mut Vec4,\n\t\t\t\t\t#[spirv(descriptor_set = 0, binding = 0, storage_buffer)] uniform: &UniformBuffer,\n\t\t\t\t\t#(#image_params),*\n\t\t\t\t) {\n\t\t\t\t\tlet uniform = <Uniform as #gcore_shaders::shaders::buffer_struct::BufferStruct>::read(*uniform);\n\t\t\t\t\tlet texel_coord = frag_coord.xy().as_uvec2();\n\t\t\t\t\tlet color: Color = #fn_name(#context, #(#call_args),*);\n\t\t\t\t\t*color_out = color.to_vec4();\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n\n\tfn codegen_gpu_node(&self) -> syn::Result<TokenStream> {\n\t\tlet gcore = self.crate_ident.gcore()?;\n\t\tlet raster_types = self.crate_ident.raster_types()?;\n\t\tlet wgpu_executor = self.crate_ident.wgpu_executor()?;\n\n\t\t// adapt fields for gpu node\n\t\tlet raster_gpu: Type = parse_quote!(#gcore::table::Table<#raster_types::Raster<#raster_types::GPU>>);\n\t\tlet mut fields = self\n\t\t\t.parsed\n\t\t\t.fields\n\t\t\t.iter()\n\t\t\t.map(|f| match &f.ty {\n\t\t\t\tParsedFieldType::Regular(reg @ RegularParsedField { gpu_image: true, .. }) => Ok(ParsedField {\n\t\t\t\t\tpat_ident: PatIdent {\n\t\t\t\t\t\tmutability: None,\n\t\t\t\t\t\tby_ref: None,\n\t\t\t\t\t\t..f.pat_ident.clone()\n\t\t\t\t\t},\n\t\t\t\t\tty: ParsedFieldType::Regular(RegularParsedField {\n\t\t\t\t\t\tty: raster_gpu.clone(),\n\t\t\t\t\t\timplementations: Punctuated::default(),\n\t\t\t\t\t\t..reg.clone()\n\t\t\t\t\t}),\n\t\t\t\t\t..f.clone()\n\t\t\t\t}),\n\t\t\t\tParsedFieldType::Regular(RegularParsedField { gpu_image: false, .. }) => Ok(ParsedField {\n\t\t\t\t\tpat_ident: PatIdent {\n\t\t\t\t\t\tmutability: None,\n\t\t\t\t\t\tby_ref: None,\n\t\t\t\t\t\t..f.pat_ident.clone()\n\t\t\t\t\t},\n\t\t\t\t\t..f.clone()\n\t\t\t\t}),\n\t\t\t\tParsedFieldType::Node { .. } => Err(syn::Error::new_spanned(&f.pat_ident, \"PerPixelAdjust shader nodes cannot accept other nodes as generics\")),\n\t\t\t})\n\t\t\t.collect::<syn::Result<Vec<_>>>()?;\n\n\t\t// insert wgpu_executor field\n\t\tlet executor = format_ident!(\"__wgpu_executor\");\n\t\tfields.push(ParsedField {\n\t\t\tpat_ident: PatIdent {\n\t\t\t\tattrs: vec![],\n\t\t\t\tby_ref: None,\n\t\t\t\tmutability: None,\n\t\t\t\tident: parse_quote!(#executor),\n\t\t\t\tsubpat: None,\n\t\t\t},\n\t\t\tname: None,\n\t\t\tdescription: \"\".to_string(),\n\t\t\twidget_override: Default::default(),\n\t\t\tty: ParsedFieldType::Regular(RegularParsedField {\n\t\t\t\tty: parse_quote!(&'a WgpuExecutor),\n\t\t\t\texposed: true,\n\t\t\t\tvalue_source: ParsedValueSource::Scope(LitStr::new(\"wgpu-executor\", Span::call_site())),\n\t\t\t\tnumber_soft_min: None,\n\t\t\t\tnumber_soft_max: None,\n\t\t\t\tnumber_hard_min: None,\n\t\t\t\tnumber_hard_max: None,\n\t\t\t\tnumber_mode_range: None,\n\t\t\t\timplementations: Default::default(),\n\t\t\t\tgpu_image: false,\n\t\t\t}),\n\t\t\tnumber_display_decimal_places: None,\n\t\t\tnumber_step: None,\n\t\t\tunit: None,\n\t\t\tis_data_field: false,\n\t\t});\n\n\t\t// find exactly one gpu_image field, runtime doesn't support more than 1 atm\n\t\tlet gpu_image_field = {\n\t\t\tlet mut iter = fields.iter().filter(|f| matches!(f.ty, ParsedFieldType::Regular(RegularParsedField { gpu_image: true, .. })));\n\t\t\tmatch (iter.next(), iter.next()) {\n\t\t\t\t(Some(v), None) => Ok(v),\n\t\t\t\t(Some(_), Some(more)) => Err(syn::Error::new_spanned(&more.pat_ident, \"No more than one parameter must be annotated with `#[gpu_image]`\")),\n\t\t\t\t(None, _) => Err(syn::Error::new_spanned(&self.parsed.fn_name, \"At least one parameter must be annotated with `#[gpu_image]`\")),\n\t\t\t}?\n\t\t};\n\t\tlet gpu_image = &gpu_image_field.pat_ident.ident;\n\n\t\t// uniform buffer struct construction\n\t\tlet has_uniform = self.has_uniform;\n\t\tlet uniform_buffer = if has_uniform {\n\t\t\tlet uniform_struct = &self.uniform_struct;\n\t\t\tlet uniform_members = self\n\t\t\t\t.params\n\t\t\t\t.iter()\n\t\t\t\t.filter_map(|p| match p.param_type {\n\t\t\t\t\tParamType::Image { .. } => None,\n\t\t\t\t\tParamType::Uniform => Some(p.ident.as_ref()),\n\t\t\t\t})\n\t\t\t\t.collect::<Vec<_>>();\n\t\t\tquote!(Some(&super::#uniform_struct {\n\t\t\t\t#(#uniform_members),*\n\t\t\t}))\n\t\t} else {\n\t\t\t// explicit generics placed here cause it's easier than explicitly writing `run_per_pixel_adjust::<()>`\n\t\t\tquote!(Option::<&()>::None)\n\t\t};\n\n\t\t// node function body\n\t\tlet entry_point_name = &self.entry_point_name;\n\t\tlet body = quote! {\n\t\t\t{\n\t\t\t\t#executor.shader_runtime.run_per_pixel_adjust(&::wgpu_executor::shader_runtime::per_pixel_adjust_runtime::Shaders {\n\t\t\t\t\twgsl_shader: crate::WGSL_SHADER,\n\t\t\t\t\tfragment_shader_name: super::#entry_point_name,\n\t\t\t\t\thas_uniform: #has_uniform,\n\t\t\t\t}, #gpu_image, #uniform_buffer).await\n\t\t\t}\n\t\t};\n\n\t\t// call node codegen\n\t\tlet display_name = self.parsed.attributes.display_name.clone();\n\t\tlet display_name = display_name.unwrap_or_else(|| LitStr::new(&self.shader_node_mod.to_string().strip_suffix(\"_shader_node\").unwrap().to_case(Case::Title), Span::call_site()));\n\t\tlet display_name = LitStr::new(&format!(\"{} GPU\", display_name.value()), display_name.span());\n\t\tlet mut parsed_node_fn = ParsedNodeFn {\n\t\t\tvis: self.parsed.vis.clone(),\n\t\t\tattributes: NodeFnAttributes {\n\t\t\t\tdisplay_name: Some(display_name),\n\t\t\t\tshader_node: Some(ShaderNodeType::ShaderNode),\n\t\t\t\t..self.parsed.attributes.clone()\n\t\t\t},\n\t\t\tfn_name: self.shader_node_mod.clone(),\n\t\t\tstruct_name: format_ident!(\"{}\", self.shader_node_mod.to_string().to_case(Case::Pascal)),\n\t\t\tmod_name: self.shader_node_mod.clone(),\n\t\t\tfn_generics: vec![parse_quote!('a: 'n)],\n\t\t\twhere_clause: None,\n\t\t\tinput: Input {\n\t\t\t\tpat_ident: self.parsed.input.pat_ident.clone(),\n\t\t\t\tty: parse_quote!(impl #gcore::context::Ctx),\n\t\t\t\timplementations: Default::default(),\n\t\t\t\tcontext_features: self.parsed.input.context_features.clone(),\n\t\t\t},\n\t\t\toutput_type: raster_gpu,\n\t\t\tis_async: true,\n\t\t\tfields,\n\t\t\tbody,\n\t\t\tdescription: self.parsed.description.clone(),\n\t\t};\n\t\tparsed_node_fn.replace_impl_trait_in_input();\n\t\tlet gpu_node_impl = crate::codegen::generate_node_code(self.crate_ident, &parsed_node_fn)?;\n\n\t\t// wrap node in `mod #gpu_node_mod`\n\t\tlet shader_node_mod = &self.shader_node_mod;\n\t\tOk(quote! {\n\t\t\t#[cfg(feature = #SHADER_NODES_FEATURE_GATE)]\n\t\t\tmod #shader_node_mod {\n\t\t\t\tuse super::*;\n\t\t\t\tuse #wgpu_executor::WgpuExecutor;\n\n\t\t\t\t#gpu_node_impl\n\t\t\t}\n\t\t})\n\t}\n}\n\nstruct Param<'a> {\n\tident: Cow<'a, Ident>,\n\tty: TokenStream,\n\tparam_type: ParamType,\n}\n\nenum ParamType {\n\tImage { binding: u32 },\n\tUniform,\n}\n"
  },
  {
    "path": "node-graph/node-macro/src/validation.rs",
    "content": "use crate::parsing::{Implementation, NodeParsedField, ParsedField, ParsedFieldType, ParsedNodeFn, RegularParsedField};\nuse proc_macro_error2::emit_error;\nuse quote::quote;\nuse syn::spanned::Spanned;\nuse syn::{GenericParam, Type};\n\npub fn validate_node_fn(parsed: &ParsedNodeFn) -> syn::Result<()> {\n\tlet validators: &[fn(&ParsedNodeFn)] = &[\n\t\t// Add more validators here as needed\n\t\tvalidate_implementations_for_generics,\n\t\tvalidate_primary_input_expose,\n\t\tvalidate_min_max,\n\t];\n\n\tfor validator in validators {\n\t\tvalidator(parsed);\n\t}\n\n\tOk(())\n}\n\nfn validate_min_max(parsed: &ParsedNodeFn) {\n\tfor field in &parsed.fields {\n\t\tif let ParsedField {\n\t\t\tty: ParsedFieldType::Regular(RegularParsedField {\n\t\t\t\tnumber_hard_max,\n\t\t\t\tnumber_hard_min,\n\t\t\t\tnumber_soft_max,\n\t\t\t\tnumber_soft_min,\n\t\t\t\t..\n\t\t\t}),\n\t\t\tpat_ident,\n\t\t\t..\n\t\t} = field\n\t\t{\n\t\t\tif let (Some(soft_min), Some(hard_min)) = (number_soft_min, number_hard_min) {\n\t\t\t\tlet soft_min_value: f64 = soft_min.base10_parse().unwrap_or_default();\n\t\t\t\tlet hard_min_value: f64 = hard_min.base10_parse().unwrap_or_default();\n\t\t\t\tif soft_min_value == hard_min_value {\n\t\t\t\t\temit_error!(\n\t\t\t\t\t\tpat_ident.span(),\n\t\t\t\t\t\t\"Unnecessary #[soft_min] attribute on `{}`, as #[hard_min] has the same value.\",\n\t\t\t\t\t\tpat_ident.ident;\n\t\t\t\t\t\thelp = \"You can safely remove the #[soft_min] attribute from this field.\";\n\t\t\t\t\t\tnote = \"#[soft_min] is redundant when it equals #[hard_min].\",\n\t\t\t\t\t);\n\t\t\t\t} else if soft_min_value < hard_min_value {\n\t\t\t\t\temit_error!(\n\t\t\t\t\t\tpat_ident.span(),\n\t\t\t\t\t\t\"The #[soft_min] attribute on `{}` is incorrectly greater than #[hard_min].\",\n\t\t\t\t\t\tpat_ident.ident;\n\t\t\t\t\t\thelp = \"You probably meant to reverse the two attribute values.\";\n\t\t\t\t\t\tnote = \"Allowing the possible slider range to preceed #[hard_min] doesn't make sense.\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif let (Some(soft_max), Some(hard_max)) = (number_soft_max, number_hard_max) {\n\t\t\t\tlet soft_max_value: f64 = soft_max.base10_parse().unwrap_or_default();\n\t\t\t\tlet hard_max_value: f64 = hard_max.base10_parse().unwrap_or_default();\n\t\t\t\tif soft_max_value == hard_max_value {\n\t\t\t\t\temit_error!(\n\t\t\t\t\t\tpat_ident.span(),\n\t\t\t\t\t\t\"Unnecessary #[soft_max] attribute on `{}`, as #[hard_max] has the same value.\",\n\t\t\t\t\t\tpat_ident.ident;\n\t\t\t\t\t\thelp = \"You can safely remove the #[soft_max] attribute from this field.\";\n\t\t\t\t\t\tnote = \"#[soft_max] is redundant when it equals #[hard_max].\",\n\t\t\t\t\t);\n\t\t\t\t} else if soft_max_value < hard_max_value {\n\t\t\t\t\temit_error!(\n\t\t\t\t\t\tpat_ident.span(),\n\t\t\t\t\t\t\"The #[soft_max] attribute on `{}` is incorrectly greater than #[hard_max].\",\n\t\t\t\t\t\tpat_ident.ident;\n\t\t\t\t\t\thelp = \"You probably meant to reverse the two attribute values.\";\n\t\t\t\t\t\tnote = \"Allowing the possible slider range to exceed #[hard_max] doesn't make sense.\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfn validate_primary_input_expose(parsed: &ParsedNodeFn) {\n\tif let Some(ParsedField {\n\t\tty: ParsedFieldType::Regular(RegularParsedField { exposed: true, .. }),\n\t\tpat_ident,\n\t\t..\n\t}) = parsed.fields.first()\n\t{\n\t\temit_error!(\n\t\t\tpat_ident.span(),\n\t\t\t\"Unnecessary #[expose] attribute on primary input `{}`. Primary inputs are always exposed.\",\n\t\t\tpat_ident.ident;\n\t\t\thelp = \"You can safely remove the #[expose] attribute from this field.\";\n\t\t\tnote = \"The function's second argument, `{}`, is the node's primary input and it's always exposed by default\", pat_ident.ident\n\t\t);\n\t}\n}\n\nfn validate_implementations_for_generics(parsed: &ParsedNodeFn) {\n\tlet has_skip_impl = parsed.attributes.skip_impl;\n\n\tif !has_skip_impl && !parsed.fn_generics.is_empty() {\n\t\tfor field in &parsed.fields {\n\t\t\t// Skip validation for data fields - they're internal state and can be generic\n\t\t\tif field.is_data_field {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet pat_ident = &field.pat_ident;\n\t\t\tmatch &field.ty {\n\t\t\t\tParsedFieldType::Regular(RegularParsedField { ty, implementations, .. }) => {\n\t\t\t\t\tif contains_generic_param(ty, &parsed.fn_generics) && implementations.is_empty() {\n\t\t\t\t\t\temit_error!(\n\t\t\t\t\t\t\tty.span(),\n\t\t\t\t\t\t\t\"Generic type `{}` in field `{}` requires an #[implementations(...)] attribute\",\n\t\t\t\t\t\t\tquote!(#ty),\n\t\t\t\t\t\t\tpat_ident.ident;\n\t\t\t\t\t\t\thelp = \"Add #[implementations(ConcreteType1, ConcreteType2)] to field '{}'\", pat_ident.ident;\n\t\t\t\t\t\t\thelp = \"Or use #[node_macro::node(category(...), skip_impl)] if you want to manually implement the node\"\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tParsedFieldType::Node(NodeParsedField {\n\t\t\t\t\tinput_type,\n\t\t\t\t\toutput_type,\n\t\t\t\t\timplementations,\n\t\t\t\t\t..\n\t\t\t\t}) => {\n\t\t\t\t\tif (contains_generic_param(input_type, &parsed.fn_generics) || contains_generic_param(output_type, &parsed.fn_generics)) && implementations.is_empty() {\n\t\t\t\t\t\temit_error!(\n\t\t\t\t\t\t\tpat_ident.span(),\n\t\t\t\t\t\t\t\"Generic types in Node field `{}` require an #[implementations(...)] attribute\",\n\t\t\t\t\t\t\tpat_ident.ident;\n\t\t\t\t\t\t\thelp = \"Add #[implementations(InputType1 -> OutputType1, InputType2 -> OutputType2)] to field '{}'\", pat_ident.ident;\n\t\t\t\t\t\t\thelp = \"Or use #[node_macro::node(category(...), skip_impl)] if you want to manually implement the node\"\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\t// Additional check for Node implementations\n\t\t\t\t\tfor impl_ in implementations {\n\t\t\t\t\t\tvalidate_node_implementation(impl_, input_type, output_type, &parsed.fn_generics);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfn validate_node_implementation(impl_: &Implementation, input_type: &Type, output_type: &Type, fn_generics: &[GenericParam]) {\n\tif contains_generic_param(&impl_.input, fn_generics) || contains_generic_param(&impl_.output, fn_generics) {\n\t\temit_error!(\n\t\t\timpl_.input.span(),\n\t\t\t\"Implementation types `{}` and `{}` must be concrete, not generic\",\n\t\t\tquote!(#input_type), quote!(#output_type);\n\t\t\thelp = \"Replace generic types with concrete types in the implementation\"\n\t\t);\n\t}\n}\n\nfn contains_generic_param(ty: &Type, fn_generics: &[GenericParam]) -> bool {\n\tstruct GenericParamChecker<'a> {\n\t\tfn_generics: &'a [GenericParam],\n\t\tfound: bool,\n\t}\n\n\timpl<'a> syn::visit::Visit<'a> for GenericParamChecker<'a> {\n\t\tfn visit_ident(&mut self, ident: &'a syn::Ident) {\n\t\t\tif self\n\t\t\t\t.fn_generics\n\t\t\t\t.iter()\n\t\t\t\t.any(|param| if let GenericParam::Type(type_param) = param { type_param.ident == *ident } else { false })\n\t\t\t{\n\t\t\t\tself.found = true;\n\t\t\t}\n\t\t}\n\t}\n\n\tlet mut checker = GenericParamChecker { fn_generics, found: false };\n\tsyn::visit::visit_type(&mut checker, ty);\n\tchecker.found\n}\n"
  },
  {
    "path": "node-graph/nodes/blending/Cargo.toml",
    "content": "[package]\nname = \"blending-nodes\"\nversion = \"0.1.0\"\nedition = \"2024\"\ndescription = \"Blending operation nodes for Graphene\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\nlicense = \"MIT OR Apache-2.0\"\n\n[features]\ndefault = [\"serde\"]\n\n[dependencies]\n# Local dependencies\ncore-types = { workspace = true }\nvector-types = { workspace = true }\ngraphic-types = { workspace = true }\nnode-macro = { workspace = true }\n\n# Workspace dependencies\nglam = { workspace = true }\n\n# Optional workspace dependencies\nserde = { workspace = true, optional = true }\n"
  },
  {
    "path": "node-graph/nodes/blending/src/lib.rs",
    "content": "use core_types::registry::types::Percentage;\nuse core_types::table::Table;\nuse core_types::{BlendMode, Color, Ctx};\nuse graphic_types::Graphic;\nuse graphic_types::Vector;\nuse graphic_types::raster_types::{CPU, Raster};\nuse vector_types::GradientStops;\n\npub(crate) trait MultiplyAlpha {\n\tfn multiply_alpha(&mut self, factor: f64);\n}\n\nimpl MultiplyAlpha for Color {\n\tfn multiply_alpha(&mut self, factor: f64) {\n\t\t*self = Color::from_rgbaf32_unchecked(self.r(), self.g(), self.b(), (self.a() * factor as f32).clamp(0., 1.))\n\t}\n}\nimpl MultiplyAlpha for Table<Vector> {\n\tfn multiply_alpha(&mut self, factor: f64) {\n\t\tfor row in self.iter_mut() {\n\t\t\trow.alpha_blending.opacity *= factor as f32;\n\t\t}\n\t}\n}\nimpl MultiplyAlpha for Table<Graphic> {\n\tfn multiply_alpha(&mut self, factor: f64) {\n\t\tfor row in self.iter_mut() {\n\t\t\trow.alpha_blending.opacity *= factor as f32;\n\t\t}\n\t}\n}\nimpl MultiplyAlpha for Table<Raster<CPU>> {\n\tfn multiply_alpha(&mut self, factor: f64) {\n\t\tfor row in self.iter_mut() {\n\t\t\trow.alpha_blending.opacity *= factor as f32;\n\t\t}\n\t}\n}\nimpl MultiplyAlpha for Table<Color> {\n\tfn multiply_alpha(&mut self, factor: f64) {\n\t\tfor row in self.iter_mut() {\n\t\t\trow.alpha_blending.opacity *= factor as f32;\n\t\t}\n\t}\n}\nimpl MultiplyAlpha for Table<GradientStops> {\n\tfn multiply_alpha(&mut self, factor: f64) {\n\t\tfor row in self.iter_mut() {\n\t\t\trow.alpha_blending.opacity *= factor as f32;\n\t\t}\n\t}\n}\n\npub(crate) trait MultiplyFill {\n\tfn multiply_fill(&mut self, factor: f64);\n}\nimpl MultiplyFill for Color {\n\tfn multiply_fill(&mut self, factor: f64) {\n\t\t*self = Color::from_rgbaf32_unchecked(self.r(), self.g(), self.b(), (self.a() * factor as f32).clamp(0., 1.))\n\t}\n}\nimpl MultiplyFill for Table<Vector> {\n\tfn multiply_fill(&mut self, factor: f64) {\n\t\tfor row in self.iter_mut() {\n\t\t\trow.alpha_blending.fill *= factor as f32;\n\t\t}\n\t}\n}\nimpl MultiplyFill for Table<Graphic> {\n\tfn multiply_fill(&mut self, factor: f64) {\n\t\tfor row in self.iter_mut() {\n\t\t\trow.alpha_blending.fill *= factor as f32;\n\t\t}\n\t}\n}\nimpl MultiplyFill for Table<Raster<CPU>> {\n\tfn multiply_fill(&mut self, factor: f64) {\n\t\tfor row in self.iter_mut() {\n\t\t\trow.alpha_blending.fill *= factor as f32;\n\t\t}\n\t}\n}\nimpl MultiplyFill for Table<Color> {\n\tfn multiply_fill(&mut self, factor: f64) {\n\t\tfor row in self.iter_mut() {\n\t\t\trow.alpha_blending.fill *= factor as f32;\n\t\t}\n\t}\n}\nimpl MultiplyFill for Table<GradientStops> {\n\tfn multiply_fill(&mut self, factor: f64) {\n\t\tfor row in self.iter_mut() {\n\t\t\trow.alpha_blending.fill *= factor as f32;\n\t\t}\n\t}\n}\n\ntrait SetBlendMode {\n\tfn set_blend_mode(&mut self, blend_mode: BlendMode);\n}\n\nimpl SetBlendMode for Table<Vector> {\n\tfn set_blend_mode(&mut self, blend_mode: BlendMode) {\n\t\tfor row in self.iter_mut() {\n\t\t\trow.alpha_blending.blend_mode = blend_mode;\n\t\t}\n\t}\n}\nimpl SetBlendMode for Table<Graphic> {\n\tfn set_blend_mode(&mut self, blend_mode: BlendMode) {\n\t\tfor row in self.iter_mut() {\n\t\t\trow.alpha_blending.blend_mode = blend_mode;\n\t\t}\n\t}\n}\nimpl SetBlendMode for Table<Raster<CPU>> {\n\tfn set_blend_mode(&mut self, blend_mode: BlendMode) {\n\t\tfor row in self.iter_mut() {\n\t\t\trow.alpha_blending.blend_mode = blend_mode;\n\t\t}\n\t}\n}\nimpl SetBlendMode for Table<Color> {\n\tfn set_blend_mode(&mut self, blend_mode: BlendMode) {\n\t\tfor row in self.iter_mut() {\n\t\t\trow.alpha_blending.blend_mode = blend_mode;\n\t\t}\n\t}\n}\nimpl SetBlendMode for Table<GradientStops> {\n\tfn set_blend_mode(&mut self, blend_mode: BlendMode) {\n\t\tfor row in self.iter_mut() {\n\t\t\trow.alpha_blending.blend_mode = blend_mode;\n\t\t}\n\t}\n}\n\ntrait SetClip {\n\tfn set_clip(&mut self, clip: bool);\n}\n\nimpl SetClip for Table<Vector> {\n\tfn set_clip(&mut self, clip: bool) {\n\t\tfor row in self.iter_mut() {\n\t\t\trow.alpha_blending.clip = clip;\n\t\t}\n\t}\n}\nimpl SetClip for Table<Graphic> {\n\tfn set_clip(&mut self, clip: bool) {\n\t\tfor row in self.iter_mut() {\n\t\t\trow.alpha_blending.clip = clip;\n\t\t}\n\t}\n}\nimpl SetClip for Table<Raster<CPU>> {\n\tfn set_clip(&mut self, clip: bool) {\n\t\tfor row in self.iter_mut() {\n\t\t\trow.alpha_blending.clip = clip;\n\t\t}\n\t}\n}\nimpl SetClip for Table<Color> {\n\tfn set_clip(&mut self, clip: bool) {\n\t\tfor row in self.iter_mut() {\n\t\t\trow.alpha_blending.clip = clip;\n\t\t}\n\t}\n}\nimpl SetClip for Table<GradientStops> {\n\tfn set_clip(&mut self, clip: bool) {\n\t\tfor row in self.iter_mut() {\n\t\t\trow.alpha_blending.clip = clip;\n\t\t}\n\t}\n}\n\n/// Applies the blend mode to the input graphics. Setting this allows for customizing how overlapping content is composited together.\n#[node_macro::node(category(\"Blending\"))]\nfn blend_mode<T: SetBlendMode>(\n\t_: impl Ctx,\n\t/// The layer stack that will be composited when rendering.\n\t#[implementations(\n\t\tTable<Graphic>,\n\t\tTable<Vector>,\n\t\tTable<Raster<CPU>>,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t)]\n\tmut content: T,\n\t/// The choice of equation that controls how brightness and color blends between overlapping pixels.\n\tblend_mode: BlendMode,\n) -> T {\n\t// TODO: Find a way to make this apply once to the table's parent (i.e. its row in its parent table or TableRow<T>) rather than applying to each row in its own table, which produces the undesired result\n\tcontent.set_blend_mode(blend_mode);\n\tcontent\n}\n\n/// Modifies the opacity of the input graphics by multiplying the existing opacity by this percentage.\n/// This affects the transparency of the content (together with anything above which is clipped to it).\n#[node_macro::node(category(\"Blending\"))]\nfn opacity<T: MultiplyAlpha>(\n\t_: impl Ctx,\n\t/// The layer stack that will be composited when rendering.\n\t#[implementations(\n\t\tTable<Graphic>,\n\t\tTable<Vector>,\n\t\tTable<Raster<CPU>>,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t)]\n\tmut content: T,\n\t/// How visible the content should be, including any content clipped to it.\n\t/// Ranges from the default of 100% (fully opaque) to 0% (fully transparent).\n\t#[default(100.)]\n\topacity: Percentage,\n) -> T {\n\t// TODO: Find a way to make this apply once to the table's parent (i.e. its row in its parent table or TableRow<T>) rather than applying to each row in its own table, which produces the undesired result\n\tcontent.multiply_alpha(opacity / 100.);\n\tcontent\n}\n\n/// Sets each of the blending properties at once. The blend mode determines how overlapping content is composited together. The opacity affects the transparency of the content (together with anything above which is clipped to it). The fill affects the transparency of the content itself, without affecting that of content clipped to it. The clip property determines whether the content inherits the alpha of the content beneath it.\n#[node_macro::node(category(\"Blending\"))]\nfn blending<T: SetBlendMode + MultiplyAlpha + MultiplyFill + SetClip>(\n\t_: impl Ctx,\n\t/// The layer stack that will be composited when rendering.\n\t#[implementations(\n\t\tTable<Graphic>,\n\t\tTable<Vector>,\n\t\tTable<Raster<CPU>>,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t)]\n\tmut content: T,\n\t/// The choice of equation that controls how brightness and color blends between overlapping pixels.\n\tblend_mode: BlendMode,\n\t/// How visible the content should be, including any content clipped to it.\n\t/// Ranges from the default of 100% (fully opaque) to 0% (fully transparent).\n\t#[default(100.)]\n\topacity: Percentage,\n\t/// How visible the content should be, independent of any content clipped to it.\n\t/// Ranges from 0% (fully transparent) to 100% (fully opaque).\n\t#[default(100.)]\n\tfill: Percentage,\n\t/// Whether the content inherits the alpha of the content beneath it.\n\tclip: bool,\n) -> T {\n\t// TODO: Find a way to make this apply once to the table's parent (i.e. its row in its parent table or TableRow<T>) rather than applying to each row in its own table, which produces the undesired result\n\tcontent.set_blend_mode(blend_mode);\n\tcontent.multiply_alpha(opacity / 100.);\n\tcontent.multiply_fill(fill / 100.);\n\tcontent.set_clip(clip);\n\tcontent\n}\n"
  },
  {
    "path": "node-graph/nodes/brush/Cargo.toml",
    "content": "[package]\nname = \"brush-nodes\"\nversion = \"0.1.0\"\nedition = \"2024\"\ndescription = \"Brush rendering nodes for Graphene\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\nlicense = \"MIT OR Apache-2.0\"\n\n[features]\ndefault = [\"serde\"]\nserde = [\"dep:serde\"]\n\n[dependencies]\n# Local dependencies\ndyn-any = { workspace = true }\ncore-types = { workspace = true }\nraster-types = { workspace = true }\nraster-nodes = { workspace = true }\nnode-macro = { workspace = true }\n\n# Workspace dependencies\nglam = { workspace = true }\n\n# Optional workspace dependencies\nserde = { workspace = true, optional = true, features = [\"derive\"] }\n\n[dev-dependencies]\n# Workspace dependencies\ntokio = { workspace = true }\n"
  },
  {
    "path": "node-graph/nodes/brush/src/brush.rs",
    "content": "use crate::brush_cache::BrushCache;\nuse crate::brush_stroke::{BrushStroke, BrushStyle};\nuse core_types::blending::BlendMode;\nuse core_types::bounds::{BoundingBox, RenderBoundingBox};\nuse core_types::color::{Alpha, Color, Pixel, Sample};\nuse core_types::generic::FnNode;\nuse core_types::math::bbox::{AxisAlignedBbox, Bbox};\nuse core_types::registry::FutureWrapperNode;\nuse core_types::table::{Table, TableRow};\nuse core_types::transform::Transform;\nuse core_types::value::ClonedNode;\nuse core_types::{Ctx, Node};\nuse glam::{DAffine2, DVec2};\nuse raster_nodes::blending_nodes::blend_colors;\nuse raster_nodes::std_nodes::{empty_image, extend_image_to_bounds};\nuse raster_types::BitmapMut;\nuse raster_types::Image;\nuse raster_types::{CPU, Raster};\n\n#[derive(Clone, Copy, Debug, PartialEq)]\npub struct BrushStampGenerator<P: Pixel + Alpha> {\n\tcolor: P,\n\tfeather_exponent: f32,\n\ttransform: DAffine2,\n}\n\nimpl<P: Pixel + Alpha> Transform for BrushStampGenerator<P> {\n\tfn transform(&self) -> DAffine2 {\n\t\tself.transform\n\t}\n}\n\nimpl<P: Pixel + Alpha> Sample for BrushStampGenerator<P> {\n\ttype Pixel = P;\n\n\t#[inline]\n\tfn sample(&self, position: DVec2, area: DVec2) -> Option<P> {\n\t\tlet position = self.transform.inverse().transform_point2(position);\n\t\tlet area = self.transform.inverse().transform_vector2(area);\n\t\tlet aa_blur_radius = area.length() as f32 * 2.;\n\t\tlet center = DVec2::splat(0.5);\n\n\t\tlet distance = (position + area / 2. - center).length() as f32 * 2.;\n\n\t\tlet edge_opacity = 1. - (1. - aa_blur_radius).powf(self.feather_exponent);\n\t\tlet result = if distance < 1. - aa_blur_radius {\n\t\t\t1. - distance.powf(self.feather_exponent)\n\t\t} else if distance < 1. {\n\t\t\t// TODO: Replace this with a proper analytical AA implementation\n\t\t\tedge_opacity * ((1. - distance) / aa_blur_radius)\n\t\t} else {\n\t\t\treturn None;\n\t\t};\n\n\t\tuse core_types::color::Channel;\n\t\tSome(self.color.multiplied_alpha(P::AlphaChannel::from_linear(result)))\n\t}\n}\n\n/// Controls the brush shape with diameter and hardness, plus color and opacity (via flow).\n/// The feather exponent is calculated from hardness to determine edge softness.\n/// Used internally to create the brush texture before stamping it repeatedly along a stroke path.\n#[node_macro::node(category(\"\"), skip_impl)]\nfn brush_stamp_generator(#[unit(\" px\")] diameter: f64, color: Color, hardness: f64, flow: f64) -> BrushStampGenerator<Color> {\n\t// Diameter\n\tlet radius = diameter / 2.;\n\n\t// Hardness\n\tlet hardness = hardness / 100.;\n\tlet feather_exponent = 1. / (1. - hardness) as f32;\n\n\t// Flow\n\tlet flow = flow / 100.;\n\n\t// Color\n\tlet color = color.apply_opacity(flow as f32);\n\n\tlet transform = DAffine2::from_scale_angle_translation(DVec2::splat(diameter), 0., -DVec2::splat(radius));\n\tBrushStampGenerator { color, feather_exponent, transform }\n}\n\n/// Used to efficiently paint brush strokes. Applies the same texture repeatedly at different positions with proper blending and boundary handling.\n#[node_macro::node(category(\"\"), skip_impl)]\nfn blit<BlendFn>(mut target: Table<Raster<CPU>>, texture: Raster<CPU>, positions: Vec<DVec2>, blend_mode: BlendFn) -> Table<Raster<CPU>>\nwhere\n\tBlendFn: for<'any_input> Node<'any_input, (Color, Color), Output = Color>,\n{\n\tif positions.is_empty() {\n\t\treturn target;\n\t}\n\n\tfor table_row in target.iter_mut() {\n\t\tlet target_width = table_row.element.width;\n\t\tlet target_height = table_row.element.height;\n\t\tlet target_size = DVec2::new(target_width as f64, target_height as f64);\n\n\t\tlet texture_size = DVec2::new(texture.width as f64, texture.height as f64);\n\n\t\tlet document_to_target = DAffine2::from_translation(-texture_size / 2.) * DAffine2::from_scale(target_size) * table_row.transform.inverse();\n\n\t\tfor position in &positions {\n\t\t\tlet start = document_to_target.transform_point2(*position).round();\n\t\t\tlet stop = start + texture_size;\n\n\t\t\t// Half-open integer ranges [start, stop).\n\t\t\tlet clamp_start = start.clamp(DVec2::ZERO, target_size).as_uvec2();\n\t\t\tlet clamp_stop = stop.clamp(DVec2::ZERO, target_size).as_uvec2();\n\n\t\t\tlet blit_area_offset = (clamp_start.as_dvec2() - start).as_uvec2().min(texture_size.as_uvec2());\n\t\t\tlet blit_area_dimensions = (clamp_stop - clamp_start).min(texture_size.as_uvec2() - blit_area_offset);\n\n\t\t\t// Tight blitting loop. Eagerly assert bounds to hopefully eliminate bounds check inside loop.\n\t\t\tlet texture_index = |x: u32, y: u32| -> usize { (y as usize * texture.width as usize) + (x as usize) };\n\t\t\tlet target_index = |x: u32, y: u32| -> usize { (y as usize * target_width as usize) + (x as usize) };\n\n\t\t\tlet max_y = (blit_area_offset.y + blit_area_dimensions.y).saturating_sub(1);\n\t\t\tlet max_x = (blit_area_offset.x + blit_area_dimensions.x).saturating_sub(1);\n\t\t\tassert!(texture_index(max_x, max_y) < texture.data.len());\n\t\t\tassert!(target_index(max_x, max_y) < table_row.element.data.len());\n\n\t\t\tfor y in blit_area_offset.y..blit_area_offset.y + blit_area_dimensions.y {\n\t\t\t\tfor x in blit_area_offset.x..blit_area_offset.x + blit_area_dimensions.x {\n\t\t\t\t\tlet src_pixel = texture.data[texture_index(x, y)];\n\t\t\t\t\tlet dst_pixel = &mut table_row.element.data_mut().data[target_index(x + clamp_start.x, y + clamp_start.y)];\n\t\t\t\t\t*dst_pixel = blend_mode.eval((src_pixel, *dst_pixel));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\ttarget\n}\n\npub async fn create_brush_texture(brush_style: &BrushStyle) -> Raster<CPU> {\n\tlet stamp = brush_stamp_generator(brush_style.diameter, brush_style.color, brush_style.hardness, brush_style.flow);\n\tlet transform = DAffine2::from_scale_angle_translation(DVec2::splat(brush_style.diameter), 0., -DVec2::splat(brush_style.diameter / 2.));\n\tlet blank_texture = empty_image((), transform, Table::new_from_element(Color::TRANSPARENT)).into_iter().next().unwrap_or_default();\n\tlet image = blend_stamp_closure(stamp, blank_texture, |a, b| blend_colors(a, b, BlendMode::Normal, 1.));\n\n\timage.element\n}\n\npub fn blend_with_mode(background: TableRow<Raster<CPU>>, foreground: TableRow<Raster<CPU>>, blend_mode: BlendMode, opacity: f64) -> TableRow<Raster<CPU>> {\n\tlet opacity = opacity as f32 / 100.;\n\tmatch std::hint::black_box(blend_mode) {\n\t\t// Normal group\n\t\tBlendMode::Normal => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Normal, opacity)),\n\t\t// Darken group\n\t\tBlendMode::Darken => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Darken, opacity)),\n\t\tBlendMode::Multiply => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Multiply, opacity)),\n\t\tBlendMode::ColorBurn => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::ColorBurn, opacity)),\n\t\tBlendMode::LinearBurn => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::LinearBurn, opacity)),\n\t\tBlendMode::DarkerColor => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::DarkerColor, opacity)),\n\t\t// Lighten group\n\t\tBlendMode::Lighten => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Lighten, opacity)),\n\t\tBlendMode::Screen => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Screen, opacity)),\n\t\tBlendMode::ColorDodge => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::ColorDodge, opacity)),\n\t\tBlendMode::LinearDodge => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::LinearDodge, opacity)),\n\t\tBlendMode::LighterColor => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::LighterColor, opacity)),\n\t\t// Contrast group\n\t\tBlendMode::Overlay => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Overlay, opacity)),\n\t\tBlendMode::SoftLight => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::SoftLight, opacity)),\n\t\tBlendMode::HardLight => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::HardLight, opacity)),\n\t\tBlendMode::VividLight => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::VividLight, opacity)),\n\t\tBlendMode::LinearLight => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::LinearLight, opacity)),\n\t\tBlendMode::PinLight => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::PinLight, opacity)),\n\t\tBlendMode::HardMix => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::HardMix, opacity)),\n\t\t// Inversion group\n\t\tBlendMode::Difference => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Difference, opacity)),\n\t\tBlendMode::Exclusion => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Exclusion, opacity)),\n\t\tBlendMode::Subtract => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Subtract, opacity)),\n\t\tBlendMode::Divide => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Divide, opacity)),\n\t\t// Component group\n\t\tBlendMode::Hue => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Hue, opacity)),\n\t\tBlendMode::Saturation => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Saturation, opacity)),\n\t\tBlendMode::Color => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Color, opacity)),\n\t\tBlendMode::Luminosity => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Luminosity, opacity)),\n\t\t// Other utility blend modes (hidden from the normal list)\n\t\tBlendMode::Erase => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Erase, opacity)),\n\t\tBlendMode::Restore => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Restore, opacity)),\n\t\tBlendMode::MultiplyAlpha => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::MultiplyAlpha, opacity)),\n\t}\n}\n\n/// Generates the brush strokes painted with the Brush tool as a raster image.\n/// If an input image is supplied, strokes are drawn on top of it, expanding bounds as needed.\n#[node_macro::node(category(\"\"))]\nasync fn brush(\n\t_: impl Ctx,\n\t/// Optional raster content that may be drawn onto.\n\tmut image: Table<Raster<CPU>>,\n\t/// The list of brush stroke paths drawn by the Brush tool, with each including both its coordinates and styles.\n\tstrokes: Vec<BrushStroke>,\n\t/// Internal cache data used to accelerate rendering of the brush content.\n\tcache: BrushCache,\n) -> Table<Raster<CPU>> {\n\tif image.is_empty() {\n\t\timage.push(TableRow::default());\n\t}\n\t// TODO: Find a way to handle more than one row\n\tlet table_row = image.iter().next().expect(\"Expected the one row we just pushed\").into_cloned();\n\n\tlet bounds = Table::new_from_row(table_row.clone()).bounding_box(DAffine2::IDENTITY, false);\n\tlet [start, end] = if let RenderBoundingBox::Rectangle(rect) = bounds { rect } else { [DVec2::ZERO, DVec2::ZERO] };\n\tlet image_bbox = AxisAlignedBbox { start, end };\n\tlet stroke_bbox = strokes.iter().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO);\n\tlet bbox = if image_bbox.size().length() < 0.1 { stroke_bbox } else { stroke_bbox.union(&image_bbox) };\n\tlet background_bounds = bbox.to_transform();\n\n\tlet mut draw_strokes: Vec<_> = strokes.iter().filter(|&s| !matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore)).cloned().collect();\n\n\tlet mut brush_plan = cache.compute_brush_plan(table_row, &draw_strokes);\n\n\t// TODO: Find a way to handle more than one row\n\tlet Some(mut actual_image) = extend_image_to_bounds((), Table::new_from_row(brush_plan.background), background_bounds).into_iter().next() else {\n\t\treturn Table::new();\n\t};\n\n\tlet final_stroke_idx = brush_plan.strokes.len().saturating_sub(1);\n\tfor (idx, stroke) in brush_plan.strokes.into_iter().enumerate() {\n\t\t// Create brush texture.\n\t\t// TODO: apply rotation from layer to stamp for non-rotationally-symmetric brushes.\n\t\tlet mut brush_texture = cache.get_cached_brush(&stroke.style);\n\t\tif brush_texture.is_none() {\n\t\t\tlet tex = create_brush_texture(&stroke.style).await;\n\t\t\tcache.store_brush(stroke.style.clone(), tex.clone());\n\t\t\tbrush_texture = Some(tex);\n\t\t}\n\t\tlet brush_texture = brush_texture.unwrap();\n\n\t\t// Compute transformation from stroke texture space into layer space, and create the stroke texture.\n\t\tlet skip = if idx == 0 { brush_plan.first_stroke_point_skip } else { 0 };\n\t\tlet positions: Vec<_> = stroke.compute_blit_points().into_iter().skip(skip).collect();\n\t\tlet stroke_texture = if idx == 0 && positions.is_empty() {\n\t\t\tcore::mem::take(&mut brush_plan.first_stroke_texture)\n\t\t} else {\n\t\t\tlet mut bbox = stroke.bounding_box();\n\t\t\tbbox.start = bbox.start.floor();\n\t\t\tbbox.end = bbox.end.floor();\n\t\t\tlet stroke_size = bbox.size() + DVec2::splat(stroke.style.diameter);\n\t\t\t// For numerical stability we want to place the first blit point at a stable, integer offset in layer space.\n\t\t\tlet snap_offset = positions[0].floor() - positions[0];\n\t\t\tlet stroke_origin_in_layer = bbox.start - snap_offset - DVec2::splat(stroke.style.diameter / 2.);\n\t\t\tlet stroke_to_layer = DAffine2::from_translation(stroke_origin_in_layer) * DAffine2::from_scale(stroke_size);\n\n\t\t\tlet normal_blend = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::Normal, 1.));\n\t\t\tlet blit_node = BlitNode::new(\n\t\t\t\tFutureWrapperNode::new(ClonedNode::new(brush_texture)),\n\t\t\t\tFutureWrapperNode::new(ClonedNode::new(positions)),\n\t\t\t\tFutureWrapperNode::new(ClonedNode::new(normal_blend)),\n\t\t\t);\n\t\t\tlet blit_target = if idx == 0 {\n\t\t\t\tlet target = core::mem::take(&mut brush_plan.first_stroke_texture);\n\t\t\t\textend_image_to_bounds((), Table::new_from_row(target), stroke_to_layer)\n\t\t\t} else {\n\t\t\t\tempty_image((), stroke_to_layer, Table::new_from_element(Color::TRANSPARENT))\n\t\t\t\t// EmptyImageNode::new(CopiedNode::new(stroke_to_layer), CopiedNode::new(Color::TRANSPARENT)).eval(())\n\t\t\t};\n\n\t\t\tlet table = blit_node.eval(blit_target).await;\n\t\t\tassert_eq!(table.len(), 1);\n\t\t\ttable.into_iter().next().unwrap_or_default()\n\t\t};\n\n\t\t// Cache image before doing final blend, and store final stroke texture.\n\t\tif idx == final_stroke_idx {\n\t\t\tcache.cache_results(core::mem::take(&mut draw_strokes), actual_image.clone(), stroke_texture.clone());\n\t\t}\n\n\t\t// TODO: Is this the correct way to do opacity in blending?\n\t\tactual_image = blend_with_mode(actual_image, stroke_texture, stroke.style.blend_mode, (stroke.style.color.a() * 100.) as f64);\n\t}\n\n\tlet has_erase_or_restore_strokes = strokes.iter().any(|s| matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore));\n\tif has_erase_or_restore_strokes {\n\t\tlet opaque_image = Image::new(bbox.size().x as u32, bbox.size().y as u32, Color::WHITE);\n\t\tlet mut erase_restore_mask = TableRow {\n\t\t\telement: Raster::new_cpu(opaque_image),\n\t\t\ttransform: background_bounds,\n\t\t\t..Default::default()\n\t\t};\n\n\t\tfor stroke in strokes {\n\t\t\tlet mut brush_texture = cache.get_cached_brush(&stroke.style);\n\t\t\tif brush_texture.is_none() {\n\t\t\t\tlet tex = create_brush_texture(&stroke.style).await;\n\t\t\t\tcache.store_brush(stroke.style.clone(), tex.clone());\n\t\t\t\tbrush_texture = Some(tex);\n\t\t\t}\n\t\t\tlet brush_texture = brush_texture.unwrap();\n\t\t\tlet positions: Vec<_> = stroke.compute_blit_points().into_iter().collect();\n\n\t\t\t// For mask composition: Erase subtracts alpha, Restore adds alpha, and Draw acts like Restore to allow repainting erased areas.\n\t\t\tlet mask_blend_mode = match stroke.style.blend_mode {\n\t\t\t\tBlendMode::Erase => BlendMode::Erase,\n\t\t\t\tBlendMode::Restore => BlendMode::Restore,\n\t\t\t\t_ => BlendMode::Restore,\n\t\t\t};\n\n\t\t\tlet blend_params = FnNode::new(move |(a, b)| blend_colors(a, b, mask_blend_mode, 1.));\n\t\t\tlet blit_node = BlitNode::new(\n\t\t\t\tFutureWrapperNode::new(ClonedNode::new(brush_texture)),\n\t\t\t\tFutureWrapperNode::new(ClonedNode::new(positions)),\n\t\t\t\tFutureWrapperNode::new(ClonedNode::new(blend_params)),\n\t\t\t);\n\t\t\terase_restore_mask = blit_node.eval(Table::new_from_row(erase_restore_mask)).await.into_iter().next().unwrap_or_default();\n\t\t}\n\n\t\tlet blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::MultiplyAlpha, 1.));\n\t\tactual_image = blend_image_closure(erase_restore_mask, actual_image, |a, b| blend_params.eval((a, b)));\n\t}\n\n\tlet first_row = image.iter_mut().next().unwrap();\n\t*first_row.element = actual_image.element;\n\t*first_row.transform = actual_image.transform;\n\t*first_row.alpha_blending = actual_image.alpha_blending;\n\t*first_row.source_node_id = actual_image.source_node_id;\n\n\timage\n}\n\npub fn blend_image_closure(foreground: TableRow<Raster<CPU>>, mut background: TableRow<Raster<CPU>>, map_fn: impl Fn(Color, Color) -> Color) -> TableRow<Raster<CPU>> {\n\tlet foreground_size = DVec2::new(foreground.element.width as f64, foreground.element.height as f64);\n\tlet background_size = DVec2::new(background.element.width as f64, background.element.height as f64);\n\n\t// Transforms a point from the background image to the foreground image\n\tlet background_to_foreground = DAffine2::from_scale(foreground_size) * foreground.transform.inverse() * background.transform * DAffine2::from_scale(1. / background_size);\n\n\t// Footprint of the foreground image (0, 0)..(1, 1) in the background image space\n\tlet background_aabb = Bbox::unit().affine_transform(background.transform.inverse() * foreground.transform).to_axis_aligned_bbox();\n\n\t// Clamp the foreground image to the background image\n\tlet start = (background_aabb.start * background_size).max(DVec2::ZERO).as_uvec2();\n\tlet end = (background_aabb.end * background_size).min(background_size).as_uvec2();\n\n\tfor y in start.y..end.y {\n\t\tfor x in start.x..end.x {\n\t\t\tlet background_point = DVec2::new(x as f64, y as f64);\n\t\t\tlet foreground_point = background_to_foreground.transform_point2(background_point);\n\n\t\t\tlet source_pixel = foreground.element.sample(foreground_point);\n\t\t\tlet Some(destination_pixel) = background.element.data_mut().get_pixel_mut(x, y) else { continue };\n\n\t\t\t*destination_pixel = map_fn(source_pixel, *destination_pixel);\n\t\t}\n\t}\n\n\tbackground\n}\n\npub fn blend_stamp_closure(foreground: BrushStampGenerator<Color>, mut background: TableRow<Raster<CPU>>, map_fn: impl Fn(Color, Color) -> Color) -> TableRow<Raster<CPU>> {\n\tlet background_size = DVec2::new(background.element.width as f64, background.element.height as f64);\n\n\t// Transforms a point from the background image to the foreground image\n\tlet background_to_foreground = background.transform * DAffine2::from_scale(1. / background_size);\n\n\t// Footprint of the foreground image (0, 0)..(1, 1) in the background image space\n\tlet background_aabb = Bbox::unit().affine_transform(background.transform.inverse() * foreground.transform).to_axis_aligned_bbox();\n\n\t// Clamp the foreground image to the background image\n\tlet start = (background_aabb.start * background_size).max(DVec2::ZERO).as_uvec2();\n\tlet end = (background_aabb.end * background_size).min(background_size).as_uvec2();\n\n\tlet area = background_to_foreground.transform_point2(DVec2::new(1., 1.)) - background_to_foreground.transform_point2(DVec2::ZERO);\n\tfor y in start.y..end.y {\n\t\tfor x in start.x..end.x {\n\t\t\tlet background_point = DVec2::new(x as f64, y as f64);\n\t\t\tlet foreground_point = background_to_foreground.transform_point2(background_point);\n\n\t\t\tlet Some(source_pixel) = foreground.sample(foreground_point, area) else { continue };\n\t\t\tlet Some(destination_pixel) = background.element.data_mut().get_pixel_mut(x, y) else { continue };\n\n\t\t\t*destination_pixel = map_fn(source_pixel, *destination_pixel);\n\t\t}\n\t}\n\n\tbackground\n}\n\n#[cfg(test)]\nmod test {\n\tuse super::*;\n\tuse core_types::transform::Transform;\n\tuse glam::DAffine2;\n\n\t#[test]\n\tfn test_brush_texture() {\n\t\tlet size = 20.;\n\t\tlet image = brush_stamp_generator(size, Color::BLACK, 100., 100.);\n\t\tassert_eq!(image.transform(), DAffine2::from_scale_angle_translation(DVec2::splat(size.ceil()), 0., -DVec2::splat(size / 2.)));\n\t\t// center pixel should be BLACK\n\t\tassert_eq!(image.sample(DVec2::splat(0.), DVec2::ONE), Some(Color::BLACK));\n\t}\n\n\t#[tokio::test]\n\tasync fn test_brush_output_size() {\n\t\tlet image = brush(\n\t\t\t(),\n\t\t\tTable::new_from_element(Raster::new_cpu(Image::<Color>::default())),\n\t\t\tvec![BrushStroke {\n\t\t\t\ttrace: vec![crate::brush_stroke::BrushInputSample { position: DVec2::ZERO }],\n\t\t\t\tstyle: BrushStyle {\n\t\t\t\t\tcolor: Color::BLACK,\n\t\t\t\t\tdiameter: 20.,\n\t\t\t\t\thardness: 20.,\n\t\t\t\t\tflow: 20.,\n\t\t\t\t\tspacing: 20.,\n\t\t\t\t\tblend_mode: BlendMode::Normal,\n\t\t\t\t},\n\t\t\t}],\n\t\t\tBrushCache::default(),\n\t\t)\n\t\t.await;\n\t\tassert_eq!(image.iter().next().unwrap().element.width, 20);\n\t}\n}\n"
  },
  {
    "path": "node-graph/nodes/brush/src/brush_cache.rs",
    "content": "use crate::brush_stroke::BrushStroke;\nuse crate::brush_stroke::BrushStyle;\nuse core_types::table::TableRow;\nuse dyn_any::DynAny;\nuse raster_types::CPU;\nuse raster_types::Raster;\nuse std::collections::HashMap;\nuse std::hash::Hash;\nuse std::hash::Hasher;\nuse std::sync::atomic::{AtomicU64, Ordering};\nuse std::sync::{Arc, Mutex};\n\n// TODO: This is a temporary hack, be sure to not reuse this when the brush system is replaced/rewritten.\nstatic NEXT_BRUSH_CACHE_IMPL_ID: AtomicU64 = AtomicU64::new(0);\n\n#[derive(Clone, Debug, DynAny, serde::Serialize, serde::Deserialize)]\nstruct BrushCacheImpl {\n\t#[serde(default = \"new_unique_id\")]\n\tunique_id: u64,\n\t// The full previous input that was cached.\n\t#[serde(default)]\n\tprev_input: Vec<BrushStroke>,\n\n\t// The strokes that have been fully processed and blended into the background.\n\t#[serde(default, deserialize_with = \"raster_types::image::migrate_image_frame_row\")]\n\tbackground: TableRow<Raster<CPU>>,\n\t#[serde(default, deserialize_with = \"raster_types::image::migrate_image_frame_row\")]\n\tblended_image: TableRow<Raster<CPU>>,\n\t#[serde(default, deserialize_with = \"raster_types::image::migrate_image_frame_row\")]\n\tlast_stroke_texture: TableRow<Raster<CPU>>,\n\n\t// A cache for brush textures.\n\t#[serde(skip)]\n\tbrush_texture_cache: HashMap<BrushStyle, Raster<CPU>>,\n}\n\nimpl BrushCacheImpl {\n\tfn compute_brush_plan(&mut self, mut background: TableRow<Raster<CPU>>, input: &[BrushStroke]) -> BrushPlan {\n\t\t// Do background invalidation.\n\t\tif background != self.background {\n\t\t\tself.background = background.clone();\n\t\t\treturn BrushPlan {\n\t\t\t\tstrokes: input.to_vec(),\n\t\t\t\tbackground,\n\t\t\t\t..Default::default()\n\t\t\t};\n\t\t}\n\n\t\t// Do blended_image invalidation.\n\t\tlet blended_strokes = &self.prev_input[..self.prev_input.len().saturating_sub(1)];\n\t\tlet num_blended_strokes = blended_strokes.len();\n\t\tif input.get(..num_blended_strokes) != Some(blended_strokes) {\n\t\t\treturn BrushPlan {\n\t\t\t\tstrokes: input.to_vec(),\n\t\t\t\tbackground,\n\t\t\t\t..Default::default()\n\t\t\t};\n\t\t}\n\n\t\t// Take our previous blended image (and invalidate the cache).\n\t\t// Since we're about to replace our cache anyway, this saves a clone.\n\t\tbackground = std::mem::take(&mut self.blended_image);\n\n\t\t// Check if the first non-blended stroke is an extension of the last one.\n\t\tlet mut first_stroke_texture = TableRow {\n\t\t\telement: Raster::<CPU>::default(),\n\t\t\ttransform: glam::DAffine2::ZERO,\n\t\t\t..Default::default()\n\t\t};\n\t\tlet mut first_stroke_point_skip = 0;\n\t\tlet strokes = input[num_blended_strokes..].to_vec();\n\t\tif !strokes.is_empty() && self.prev_input.len() > num_blended_strokes {\n\t\t\tlet last_stroke = &self.prev_input[num_blended_strokes];\n\t\t\tlet same_style = strokes[0].style == last_stroke.style;\n\t\t\tlet prev_points = last_stroke.compute_blit_points();\n\t\t\tlet new_points = strokes[0].compute_blit_points();\n\t\t\tlet is_point_prefix = new_points.get(..prev_points.len()) == Some(&prev_points);\n\t\t\tif same_style && is_point_prefix {\n\t\t\t\tfirst_stroke_texture = std::mem::take(&mut self.last_stroke_texture);\n\t\t\t\tfirst_stroke_point_skip = prev_points.len();\n\t\t\t}\n\t\t}\n\n\t\tself.prev_input = Vec::new();\n\t\tBrushPlan {\n\t\t\tstrokes,\n\t\t\tbackground,\n\t\t\tfirst_stroke_texture,\n\t\t\tfirst_stroke_point_skip,\n\t\t}\n\t}\n\n\tpub fn cache_results(&mut self, input: Vec<BrushStroke>, blended_image: TableRow<Raster<CPU>>, last_stroke_texture: TableRow<Raster<CPU>>) {\n\t\tself.prev_input = input;\n\t\tself.blended_image = blended_image;\n\t\tself.last_stroke_texture = last_stroke_texture;\n\t}\n}\n\nimpl Default for BrushCacheImpl {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tunique_id: new_unique_id(),\n\t\t\tprev_input: Vec::new(),\n\t\t\tbackground: Default::default(),\n\t\t\tblended_image: Default::default(),\n\t\t\tlast_stroke_texture: Default::default(),\n\t\t\tbrush_texture_cache: HashMap::new(),\n\t\t}\n\t}\n}\n\nimpl PartialEq for BrushCacheImpl {\n\tfn eq(&self, other: &Self) -> bool {\n\t\tself.unique_id == other.unique_id\n\t}\n}\n\nimpl Hash for BrushCacheImpl {\n\tfn hash<H: Hasher>(&self, state: &mut H) {\n\t\tself.unique_id.hash(state);\n\t}\n}\n\nfn new_unique_id() -> u64 {\n\tNEXT_BRUSH_CACHE_IMPL_ID.fetch_add(1, Ordering::SeqCst)\n}\n\n#[derive(Clone, Debug, Default)]\npub struct BrushPlan {\n\tpub strokes: Vec<BrushStroke>,\n\tpub background: TableRow<Raster<CPU>>,\n\tpub first_stroke_texture: TableRow<Raster<CPU>>,\n\tpub first_stroke_point_skip: usize,\n}\n\n#[derive(Debug, Default, DynAny, serde::Serialize, serde::Deserialize)]\npub struct BrushCache(Arc<Mutex<BrushCacheImpl>>);\n\n// A bit of a cursed implementation to work around the current node system.\n// The original object is a 'prototype' that when cloned gives you a independent\n// new object. Any further clones however are all the same underlying cache object.\nimpl Clone for BrushCache {\n\tfn clone(&self) -> Self {\n\t\tSelf(Arc::new(Mutex::new(self.0.lock().unwrap().clone())))\n\t}\n}\n\nimpl PartialEq for BrushCache {\n\tfn eq(&self, other: &Self) -> bool {\n\t\tif Arc::ptr_eq(&self.0, &other.0) {\n\t\t\treturn true;\n\t\t}\n\n\t\tlet s = self.0.lock().unwrap();\n\t\tlet o = other.0.lock().unwrap();\n\n\t\t*s == *o\n\t}\n}\n\nimpl Hash for BrushCache {\n\tfn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n\t\tself.0.lock().unwrap().hash(state);\n\t}\n}\n\nimpl BrushCache {\n\tpub fn compute_brush_plan(&self, background: TableRow<Raster<CPU>>, input: &[BrushStroke]) -> BrushPlan {\n\t\tlet mut inner = self.0.lock().unwrap();\n\t\tinner.compute_brush_plan(background, input)\n\t}\n\n\tpub fn cache_results(&self, input: Vec<BrushStroke>, blended_image: TableRow<Raster<CPU>>, last_stroke_texture: TableRow<Raster<CPU>>) {\n\t\tlet mut inner = self.0.lock().unwrap();\n\t\tinner.cache_results(input, blended_image, last_stroke_texture)\n\t}\n\n\tpub fn get_cached_brush(&self, style: &BrushStyle) -> Option<Raster<CPU>> {\n\t\tlet inner = self.0.lock().unwrap();\n\t\tinner.brush_texture_cache.get(style).cloned()\n\t}\n\n\tpub fn store_brush(&self, style: BrushStyle, brush: Raster<CPU>) {\n\t\tlet mut inner = self.0.lock().unwrap();\n\t\tinner.brush_texture_cache.insert(style, brush);\n\t}\n}\n"
  },
  {
    "path": "node-graph/nodes/brush/src/brush_stroke.rs",
    "content": "use core_types::blending::BlendMode;\nuse core_types::color::Color;\nuse core_types::math::bbox::AxisAlignedBbox;\nuse dyn_any::DynAny;\nuse glam::DVec2;\nuse std::hash::{Hash, Hasher};\n\n/// The style of a brush.\n#[derive(Clone, Debug, DynAny, serde::Serialize, serde::Deserialize)]\npub struct BrushStyle {\n\tpub color: Color,\n\tpub diameter: f64,\n\tpub hardness: f64,\n\tpub flow: f64,\n\tpub spacing: f64, // Spacing as a fraction of the diameter.\n\tpub blend_mode: BlendMode,\n}\n\nimpl Default for BrushStyle {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tcolor: Color::BLACK,\n\t\t\tdiameter: 40.,\n\t\t\thardness: 50.,\n\t\t\tflow: 100.,\n\t\t\tspacing: 50., // Percentage of diameter.\n\t\t\tblend_mode: BlendMode::Normal,\n\t\t}\n\t}\n}\n\nimpl Hash for BrushStyle {\n\tfn hash<H: Hasher>(&self, state: &mut H) {\n\t\tself.color.hash(state);\n\t\tself.diameter.to_bits().hash(state);\n\t\tself.hardness.to_bits().hash(state);\n\t\tself.flow.to_bits().hash(state);\n\t\tself.spacing.to_bits().hash(state);\n\t\tself.blend_mode.hash(state);\n\t}\n}\n\nimpl Eq for BrushStyle {}\n\nimpl PartialEq for BrushStyle {\n\tfn eq(&self, other: &Self) -> bool {\n\t\tself.color == other.color\n\t\t\t&& self.diameter.to_bits() == other.diameter.to_bits()\n\t\t\t&& self.hardness.to_bits() == other.hardness.to_bits()\n\t\t\t&& self.flow.to_bits() == other.flow.to_bits()\n\t\t\t&& self.spacing.to_bits() == other.spacing.to_bits()\n\t\t\t&& self.blend_mode == other.blend_mode\n\t}\n}\n\n/// A single sample of brush parameters across the brush stroke.\n#[derive(Clone, Debug, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]\npub struct BrushInputSample {\n\t// The position of the sample in layer space, in pixels.\n\t// The origin of layer space is not specified.\n\tpub position: DVec2,\n\t// Future work: pressure, stylus angle, etc.\n}\n\nimpl Hash for BrushInputSample {\n\tfn hash<H: Hasher>(&self, state: &mut H) {\n\t\tself.position.x.to_bits().hash(state);\n\t\tself.position.y.to_bits().hash(state);\n\t}\n}\n\n/// The parameters for a single stroke brush.\n#[derive(Clone, Debug, PartialEq, Hash, Default, DynAny, serde::Serialize, serde::Deserialize)]\npub struct BrushStroke {\n\tpub style: BrushStyle,\n\tpub trace: Vec<BrushInputSample>,\n}\n\nimpl BrushStroke {\n\tpub fn bounding_box(&self) -> AxisAlignedBbox {\n\t\tlet radius = self.style.diameter / 2.;\n\t\tself.compute_blit_points()\n\t\t\t.iter()\n\t\t\t.map(|pos| AxisAlignedBbox {\n\t\t\t\tstart: *pos + DVec2::new(-radius, -radius),\n\t\t\t\tend: *pos + DVec2::new(radius, radius),\n\t\t\t})\n\t\t\t.reduce(|a, b| a.union(&b))\n\t\t\t.unwrap_or(AxisAlignedBbox::ZERO)\n\t}\n\n\tpub fn compute_blit_points(&self) -> Vec<DVec2> {\n\t\t// We always travel in a straight line towards the next user input,\n\t\t// placing a blit point every time we travelled our spacing distance.\n\t\tlet spacing_dist = self.style.spacing / 100. * self.style.diameter;\n\n\t\tlet Some(first_sample) = self.trace.first() else {\n\t\t\treturn Vec::new();\n\t\t};\n\n\t\tlet mut cur_pos = first_sample.position;\n\t\tlet mut result = vec![cur_pos];\n\t\tlet mut dist_until_next_blit = spacing_dist;\n\t\tfor sample in &self.trace[1..] {\n\t\t\t// Travel to the next sample.\n\t\t\tlet delta = sample.position - cur_pos;\n\t\t\tlet mut dist_left = delta.length();\n\t\t\tlet unit_step = delta / dist_left;\n\n\t\t\twhile dist_left >= dist_until_next_blit {\n\t\t\t\t// Take a step to the next blit point.\n\t\t\t\tcur_pos += dist_until_next_blit * unit_step;\n\t\t\t\tdist_left -= dist_until_next_blit;\n\n\t\t\t\t// Blit.\n\t\t\t\tresult.push(cur_pos);\n\t\t\t\tdist_until_next_blit = spacing_dist;\n\t\t\t}\n\n\t\t\t// Take the partial step to land at the sample.\n\t\t\tdist_until_next_blit -= dist_left;\n\t\t\tcur_pos = sample.position;\n\t\t}\n\n\t\tresult\n\t}\n}\n"
  },
  {
    "path": "node-graph/nodes/brush/src/lib.rs",
    "content": "pub mod brush;\npub mod brush_cache;\npub mod brush_stroke;\n"
  },
  {
    "path": "node-graph/nodes/gcore/Cargo.toml",
    "content": "[package]\nname = \"graphene-core\"\nversion = \"0.1.0\"\nedition = \"2024\"\ndescription = \"Core utility nodes for Graphene\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\nlicense = \"MIT OR Apache-2.0\"\n\n[features]\ndefault = [\"serde\"]\nwasm = [\n\t\"core-types/wasm\",\n\t\"raster-types/wasm\",\n\t\"graphic-types/wasm\",\n\t\"tsify\",\n\t\"wasm-bindgen\",\n]\n\n[dependencies]\n# Local dependencies\ncore-types = { workspace = true }\nraster-types = { workspace = true }\ngraphic-types = { workspace = true }\nnode-macro = { workspace = true }\n\n# Workspace dependencies\ndyn-any = { workspace = true }\nglam = { workspace = true }\nlog = { workspace = true }\nserde_json = { workspace = true }\n\n# Optional workspace dependencies\nserde = { workspace = true, optional = true }\ntsify = { workspace = true, optional = true }\nwasm-bindgen = { workspace = true, optional = true }\n"
  },
  {
    "path": "node-graph/nodes/gcore/src/animation.rs",
    "content": "use core_types::table::Table;\nuse core_types::transform::Footprint;\nuse core_types::uuid::NodeId;\nuse core_types::{CloneVarArgs, Color, Context, Ctx, ExtractAll, ExtractAnimationTime, ExtractPointerPosition, ExtractRealTime, OwnedContextImpl};\nuse glam::{DAffine2, DVec2};\nuse graphic_types::vector_types::GradientStops;\nuse graphic_types::{Artboard, Graphic, Vector};\nuse raster_types::{CPU, GPU, Raster};\n\nconst DAY: f64 = 1000. * 3600. * 24.;\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, dyn_any::DynAny, Default, Hash, node_macro::ChoiceType, serde::Serialize, serde::Deserialize)]\npub enum RealTimeMode {\n\t#[label(\"UTC\")]\n\tUtc,\n\tYear,\n\tHour,\n\tMinute,\n\t#[default]\n\tSecond,\n\tMillisecond,\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum AnimationTimeMode {\n\tAnimationTime,\n\tFrameNumber,\n}\n\n/// Produces a chosen representation of the current real time and date (in UTC) based on the system clock.\n#[node_macro::node(category(\"Animation\"))]\nfn real_time(\n\tctx: impl Ctx + ExtractRealTime,\n\t_primary: (),\n\t/// The time and date component to be produced as a number.\n\tcomponent: RealTimeMode,\n) -> f64 {\n\tlet real_time = ctx.try_real_time().unwrap_or_default();\n\n\t// TODO: Implement proper conversion using and existing time implementation\n\tmatch component {\n\t\tRealTimeMode::Utc => real_time,\n\t\tRealTimeMode::Year => (real_time / DAY / 365.25).floor() + 1970., // TODO: Factor in a chosen timezone\n\t\tRealTimeMode::Hour => (real_time / 1000. / 3600.).floor() % 24.,  // TODO: Factor in a chosen timezone\n\t\tRealTimeMode::Minute => (real_time / 1000. / 60.).floor() % 60.,  // TODO: Factor in a chosen timezone\n\t\tRealTimeMode::Second => (real_time / 1000.).floor() % 60.,\n\t\tRealTimeMode::Millisecond => real_time % 1000.,\n\t}\n}\n\n/// Produces the time, in seconds on the timeline, since the beginning of animation playback.\n#[node_macro::node(category(\"Animation\"))]\nfn animation_time(\n\tctx: impl Ctx + ExtractAnimationTime,\n\t_primary: (),\n\t#[default(1)]\n\t#[unit(\"/sec\")]\n\trate: f64,\n) -> f64 {\n\tctx.try_animation_time().unwrap_or_default() * rate\n}\n\n#[node_macro::node(category(\"Animation\"))]\nasync fn quantize_real_time<T>(\n\tctx: impl Ctx + ExtractAll + CloneVarArgs,\n\t#[implementations(\n\t\tContext -> bool,\n\t\tContext -> u32,\n\t\tContext -> u64,\n\t\tContext -> f32,\n\t\tContext -> f64,\n\t\tContext -> String,\n\t\tContext -> DAffine2,\n\t\tContext -> Footprint,\n\t\tContext -> DVec2,\n\t\tContext -> Vec<DVec2>,\n\t\tContext -> Vec<NodeId>,\n\t\tContext -> Vec<f64>,\n\t\tContext -> Vec<f32>,\n\t\tContext -> Vec<String>,\n\t\tContext -> Table<Vector>,\n\t\tContext -> Table<Graphic>,\n\t\tContext -> Table<Raster<CPU>>,\n\t\tContext -> Table<Raster<GPU>>,\n\t\tContext -> Table<Color>,\n\t\tContext -> Table<Artboard>,\n\t\tContext -> Table<GradientStops>,\n\t\tContext -> (),\n\t)]\n\tvalue: impl Node<'n, Context<'static>, Output = T>,\n\t#[default(1)]\n\t#[unit(\"sec\")]\n\tquantum: f64,\n) -> T {\n\tlet time = ctx.try_real_time().unwrap_or_default();\n\tlet time = time / 1000.;\n\tlet mut quantized_time = (time * quantum.recip()).round() / quantum.recip();\n\tif !quantized_time.is_finite() {\n\t\tquantized_time = time;\n\t}\n\tlet quantized_time = quantized_time * 1000.;\n\tlet new_context = OwnedContextImpl::from(ctx).with_real_time(quantized_time);\n\tvalue.eval(Some(new_context.into())).await\n}\n\n#[node_macro::node(category(\"Animation\"))]\nasync fn quantize_animation_time<T>(\n\tctx: impl Ctx + ExtractAll + CloneVarArgs,\n\t#[implementations(\n\t\tContext -> bool,\n\t\tContext -> u32,\n\t\tContext -> u64,\n\t\tContext -> f32,\n\t\tContext -> f64,\n\t\tContext -> String,\n\t\tContext -> DAffine2,\n\t\tContext -> Footprint,\n\t\tContext -> DVec2,\n\t\tContext -> Vec<DVec2>,\n\t\tContext -> Vec<NodeId>,\n\t\tContext -> Vec<f64>,\n\t\tContext -> Vec<f32>,\n\t\tContext -> Vec<String>,\n\t\tContext -> Table<Vector>,\n\t\tContext -> Table<Graphic>,\n\t\tContext -> Table<Raster<CPU>>,\n\t\tContext -> Table<Raster<GPU>>,\n\t\tContext -> Table<Color>,\n\t\tContext -> Table<Artboard>,\n\t\tContext -> Table<GradientStops>,\n\t\tContext -> (),\n\t)]\n\tvalue: impl Node<'n, Context<'static>, Output = T>,\n\t#[default(1)]\n\t#[unit(\"sec\")]\n\tquantum: f64,\n) -> T {\n\tlet time = ctx.try_animation_time().unwrap_or_default();\n\tlet mut quantized_time = (time * quantum.recip()).round() / quantum.recip();\n\tif !quantized_time.is_finite() {\n\t\tquantized_time = time;\n\t}\n\tlet new_context = OwnedContextImpl::from(ctx).with_animation_time(quantized_time);\n\tvalue.eval(Some(new_context.into())).await\n}\n\n/// Produces the current position of the user's pointer within the document canvas.\n#[node_macro::node(category(\"Animation\"))]\nfn pointer_position(ctx: impl Ctx + ExtractPointerPosition) -> DVec2 {\n\tctx.try_pointer_position().unwrap_or_default()\n}\n\n// TODO: These nodes require more sophisticated algorithms for giving the correct result\n// #[node_macro::node(category(\"Animation\"))]\n// fn month(ctx: impl Ctx + ExtractRealTime) -> f64 {\n// \t((ctx.try_real_time().unwrap_or_default() / DAY / 365.25 % 1.) * 12.).floor()\n// }\n// #[node_macro::node(category(\"Animation\"))]\n// fn day(ctx: impl Ctx + ExtractRealTime) -> f64 {\n// \t(ctx.try_real_time().unwrap_or_default() / DAY\n// }\n"
  },
  {
    "path": "node-graph/nodes/gcore/src/context.rs",
    "content": "use core_types::table::Table;\nuse core_types::{Color, ExtractVarArgs};\nuse core_types::{Ctx, ExtractIndex, ExtractPosition};\nuse glam::DVec2;\nuse graphic_types::vector_types::GradientStops;\nuse graphic_types::{Graphic, Vector};\nuse raster_types::{CPU, Raster};\n\n#[node_macro::node(category(\"Context\"), path(graphene_core::vector))]\nfn read_graphic(ctx: impl Ctx + ExtractVarArgs) -> Table<Graphic> {\n\tlet Ok(var_arg) = ctx.vararg(0) else { return Default::default() };\n\tlet var_arg = var_arg as &dyn std::any::Any;\n\n\tvar_arg.downcast_ref().cloned().unwrap_or_default()\n}\n\n#[node_macro::node(category(\"Context\"), path(graphene_core::vector))]\nfn read_vector(ctx: impl Ctx + ExtractVarArgs) -> Table<Vector> {\n\tlet Ok(var_arg) = ctx.vararg(0) else { return Default::default() };\n\tlet var_arg = var_arg as &dyn std::any::Any;\n\n\tvar_arg.downcast_ref().cloned().unwrap_or_default()\n}\n\n#[node_macro::node(category(\"Context\"), path(graphene_core::vector))]\nfn read_raster(ctx: impl Ctx + ExtractVarArgs) -> Table<Raster<CPU>> {\n\tlet Ok(var_arg) = ctx.vararg(0) else { return Default::default() };\n\tlet var_arg = var_arg as &dyn std::any::Any;\n\n\tvar_arg.downcast_ref().cloned().unwrap_or_default()\n}\n\n#[node_macro::node(category(\"Context\"), path(graphene_core::vector))]\nfn read_color(ctx: impl Ctx + ExtractVarArgs) -> Table<Color> {\n\tlet Ok(var_arg) = ctx.vararg(0) else { return Default::default() };\n\tlet var_arg = var_arg as &dyn std::any::Any;\n\n\tvar_arg.downcast_ref().cloned().unwrap_or_default()\n}\n\n#[node_macro::node(category(\"Context\"), path(graphene_core::vector))]\nfn read_gradient(ctx: impl Ctx + ExtractVarArgs) -> Table<GradientStops> {\n\tlet Ok(var_arg) = ctx.vararg(0) else { return Default::default() };\n\tlet var_arg = var_arg as &dyn std::any::Any;\n\n\tvar_arg.downcast_ref().cloned().unwrap_or_default()\n}\n\n#[node_macro::node(category(\"Context\"), path(core_types::vector))]\nasync fn read_position(\n\tctx: impl Ctx + ExtractPosition,\n\t_primary: (),\n\t/// The number of nested loops to traverse outwards (from the innermost loop) to get the position from. The most upstream loop is level 0, and downstream loops add levels.\n\t///\n\t/// In programming terms: inside the double loop `i { j { ... } }`, *Loop Level* 0 = `j` and 1 = `i`. After inserting a third loop `k { ... }`, inside it, levels would be 0 = `k`, 1 = `j`, and 2 = `i`.\n\tloop_level: u32,\n) -> DVec2 {\n\tctx.try_position().and_then(|mut iter| iter.nth(loop_level as usize).or_else(|| iter.last())).unwrap_or(DVec2::ZERO)\n}\n\n// TODO: Return u32, u64, or usize instead of f64 after #1621 is resolved and has allowed us to implement automatic type conversion in the node graph for nodes with generic type inputs.\n// TODO: (Currently automatic type conversion only works for concrete types, via the Graphene preprocessor and not the full Graphene type system.)\n/// Produces the index of the current iteration of a loop by reading from the evaluation context, which is supplied by downstream nodes such as *Instance Repeat*.\n///\n/// Nested loops can enable 2D or higher-dimensional iteration by using the *Loop Level* parameter to read the index from outer levels of loops.\n#[node_macro::node(category(\"Context\"), path(core_types::vector))]\nasync fn read_index(\n\tctx: impl Ctx + ExtractIndex,\n\t_primary: (),\n\t/// The number of nested loops to traverse outwards (from the innermost loop) to get the index from. The most upstream loop is level 0, and downstream loops add levels.\n\t///\n\t/// In programming terms: inside the double loop `i { j { ... } }`, *Loop Level* 0 = `j` and 1 = `i`. After inserting a third loop `k { ... }`, inside it, levels would be 0 = `k`, 1 = `j`, and 2 = `i`.\n\tloop_level: u32,\n) -> f64 {\n\tctx.try_index().and_then(|mut iter| iter.nth(loop_level as usize).or_else(|| iter.last())).unwrap_or(0) as f64\n}\n"
  },
  {
    "path": "node-graph/nodes/gcore/src/context_modification.rs",
    "content": "use core::f64;\nuse core_types::context::{CloneVarArgs, Context, ContextFeatures, Ctx, ExtractAll};\nuse core_types::table::Table;\nuse core_types::transform::Footprint;\nuse core_types::uuid::NodeId;\nuse core_types::{Color, OwnedContextImpl};\nuse glam::{DAffine2, DVec2};\nuse graphic_types::vector_types::GradientStops;\nuse graphic_types::{Artboard, Graphic, Vector};\nuse raster_types::{CPU, GPU, Raster};\n\n/// Filters out what should be unused components of the context based on the specified requirements.\n/// This node is inserted by the compiler to \"zero out\" unused context components.\n#[node_macro::node(category(\"\"))]\nasync fn context_modification<T>(\n\tctx: impl Ctx + CloneVarArgs + ExtractAll,\n\t/// The data to pass through, evaluated with the stripped down context.\n\t#[implementations(\n\t\tContext -> (),\n\t\tContext -> bool,\n\t\tContext -> u32,\n\t\tContext -> u64,\n\t\tContext -> f32,\n\t\tContext -> f64,\n\t\tContext -> String,\n\t\tContext -> DAffine2,\n\t\tContext -> Footprint,\n\t\tContext -> DVec2,\n\t\tContext -> Vec<DVec2>,\n\t\tContext -> Vec<NodeId>,\n\t\tContext -> Vec<f64>,\n\t\tContext -> Vec<f32>,\n\t\tContext -> Vec<String>,\n\t\tContext -> Table<Vector>,\n\t\tContext -> Table<Graphic>,\n\t\tContext -> Table<Raster<CPU>>,\n\t\tContext -> Table<Raster<GPU>>,\n\t\tContext -> Table<Color>,\n\t\tContext -> Table<Artboard>,\n\t\tContext -> Table<GradientStops>,\n\t)]\n\tvalue: impl Node<Context<'static>, Output = T>,\n\t/// The parts of the context to keep when evaluating the input value. All other parts are nullified.\n\tfeatures_to_keep: ContextFeatures,\n) -> T {\n\tlet new_context = OwnedContextImpl::from_flags(ctx, features_to_keep);\n\n\tvalue.eval(Some(new_context.into())).await\n}\n\n#[cfg(test)]\nmod tests {\n\tuse super::*;\n\tuse core_types::transform::Footprint;\n\tuse std::collections::hash_map::DefaultHasher;\n\tuse std::hash::{Hash, Hasher};\n\n\t/// Test that the hash of a nullified context remains stable even when nullified inputs change\n\t#[test]\n\tfn test_nullified_context_hash_stability() {\n\t\tuse core_types::Context;\n\t\tuse std::sync::Arc;\n\n\t\t// Create original contexts using the Context type (Option<Arc<OwnedContextImpl>>)\n\t\tlet original_ctx: Context = Some(Arc::new(\n\t\t\tOwnedContextImpl::empty()\n\t\t\t\t.with_footprint(Footprint::default())\n\t\t\t\t.with_index(1)\n\t\t\t\t.with_real_time(10.5)\n\t\t\t\t.with_vararg(Box::new(\"test\"))\n\t\t\t\t.with_animation_time(20.25),\n\t\t));\n\n\t\t// Test nullifying different features - hash should remain stable for each nullification\n\t\tlet features_to_keep = ContextFeatures::empty(); // Nullify everything\n\n\t\t// Create nullified context - this should only keep features specified in features_to_keep\n\t\tlet nullified_ctx = OwnedContextImpl::from_flags(original_ctx.clone().unwrap(), features_to_keep);\n\n\t\t// Calculate hash of nullified context\n\t\tlet mut hasher1 = DefaultHasher::new();\n\t\tnullified_ctx.hash(&mut hasher1);\n\t\tlet hash1 = hasher1.finish();\n\n\t\t// Create a different original context with changed values\n\t\tlet changed_ctx: Context = Some(Arc::new(\n\t\t\tOwnedContextImpl::empty()\n\t\t\t\t.with_footprint(Footprint::default()) // Same footprint\n\t\t\t\t.with_index(2)\n\t\t\t\t.with_real_time(999.9) // Different real time\n\t\t\t\t.with_vararg(Box::new(\"test\"))\n\t\t\t\t.with_animation_time(888.8), // Different animation time\n\t\t));\n\n\t\t// Create nullified context from the changed original - should have same hash since everything is nullified\n\t\tlet nullified_changed_ctx = OwnedContextImpl::from_flags(changed_ctx.clone().unwrap(), features_to_keep);\n\n\t\tlet mut hasher2 = DefaultHasher::new();\n\t\tnullified_changed_ctx.hash(&mut hasher2);\n\t\tlet hash2 = hasher2.finish();\n\n\t\t// Hash should be the same because all features were nullified\n\t\tassert_eq!(hash1, hash2, \"Hash of nullified context should remain stable regardless of input changes when features are nullified\");\n\n\t\t// Test partial nullification - keep only footprint\n\t\tlet partial_features = ContextFeatures::FOOTPRINT | ContextFeatures::VARARGS;\n\n\t\tlet partial_nullified1 = OwnedContextImpl::from_flags(original_ctx.clone().unwrap(), partial_features);\n\t\tlet partial_nullified2 = OwnedContextImpl::from_flags(changed_ctx.clone().unwrap(), partial_features);\n\n\t\tlet mut hasher3 = DefaultHasher::new();\n\t\tpartial_nullified1.hash(&mut hasher3);\n\t\tlet hash3 = hasher3.finish();\n\n\t\tlet mut hasher4 = DefaultHasher::new();\n\t\tpartial_nullified2.hash(&mut hasher4);\n\t\tlet hash4 = hasher4.finish();\n\n\t\t// These should be the same because both have the same footprint (Footprint::default()) and varargs\n\t\t// and other features are nullified\n\t\tassert_eq!(hash3, hash4, \"Hash should be stable when keeping only footprint and footprint values are the same\");\n\t}\n}\n"
  },
  {
    "path": "node-graph/nodes/gcore/src/debug.rs",
    "content": "use core_types::Ctx;\nuse core_types::table::Table;\nuse glam::{DAffine2, DVec2};\nuse raster_types::{CPU, Raster};\n\n/// Meant for debugging purposes, not general use. Logs the input value to the console and passes it through unchanged.\n#[node_macro::node(category(\"Debug\"), name(\"Log to Console\"))]\nfn log_to_console<T: std::fmt::Debug>(_: impl Ctx, #[implementations(bool, f64, u32, u64, DVec2, DAffine2, String)] value: T) -> T {\n\t// KEEP THIS `debug!()` - It acts as the output for the debug node itself\n\tlog::debug!(\"{value:#?}\");\n\tvalue\n}\n\n/// Meant for debugging purposes, not general use. Returns the size of the input type in bytes.\n#[node_macro::node(category(\"Debug\"))]\nfn size_of(_: impl Ctx, ty: core_types::Type) -> Option<usize> {\n\tty.size()\n}\n\n/// Meant for debugging purposes, not general use. Wraps the input value in the Some variant of an Option.\n#[node_macro::node(category(\"Debug\"))]\nfn some<T>(_: impl Ctx, #[implementations(f64, f32, u32, u64, String)] input: T) -> Option<T> {\n\tSome(input)\n}\n\n/// Meant for debugging purposes, not general use. Unwraps the input value from an Option, returning the default value if the input is None.\n#[node_macro::node(category(\"Debug\"))]\nfn unwrap_option<T: Default>(_: impl Ctx, #[implementations(Option<f64>, Option<u32>, Option<u64>, Option<String>)] input: Option<T>) -> T {\n\tinput.unwrap_or_default()\n}\n\n/// Meant for debugging purposes, not general use. Clones the input value.\n#[node_macro::node(category(\"Debug\"))]\nfn clone<'i, T: Clone + 'i>(_: impl Ctx, #[implementations(&Table<Raster<CPU>>)] value: &'i T) -> T {\n\tvalue.clone()\n}\n"
  },
  {
    "path": "node-graph/nodes/gcore/src/extract_xy.rs",
    "content": "use core_types::Ctx;\nuse dyn_any::DynAny;\nuse glam::{DVec2, IVec2, UVec2};\n\n/// Obtains the X or Y component of a vec2.\n///\n/// The inverse of this node is \"Vec2 Value\", which can have either or both its X and Y parameters exposed as graph inputs.\n#[node_macro::node(name(\"Extract XY\"), category(\"Math: Vector\"))]\nfn extract_xy<T: Into<DVec2>>(_: impl Ctx, #[implementations(DVec2, IVec2, UVec2)] vector: T, axis: XY) -> f64 {\n\tmatch axis {\n\t\tXY::X => vector.into().x,\n\t\tXY::Y => vector.into().y,\n\t}\n}\n\n/// The X or Y component of a vec2.\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType, serde::Serialize, serde::Deserialize)]\n#[widget(Radio)]\npub enum XY {\n\t#[default]\n\tX,\n\tY,\n}\n"
  },
  {
    "path": "node-graph/nodes/gcore/src/lib.rs",
    "content": "pub mod animation;\npub mod context;\npub mod context_modification;\npub mod debug;\npub mod extract_xy;\npub mod logic;\npub mod memo;\npub mod ops;\n\n// Re-export all nodes\npub use animation::*;\npub use context::*;\npub use context_modification::*;\npub use debug::*;\npub use extract_xy::*;\npub use logic::*;\npub use memo::*;\npub use ops::*;\n"
  },
  {
    "path": "node-graph/nodes/gcore/src/logic.rs",
    "content": "use core_types::Color;\nuse core_types::registry::types::TextArea;\nuse core_types::table::Table;\nuse core_types::{Context, Ctx};\nuse glam::{DAffine2, DVec2};\nuse graphic_types::vector_types::GradientStops;\nuse graphic_types::{Artboard, Graphic, Vector};\nuse raster_types::{CPU, GPU, Raster};\n\n/// Type-asserts a value to be a string.\n#[node_macro::node(category(\"Debug\"))]\nfn to_string(_: impl Ctx, value: String) -> String {\n\tvalue\n}\n\n/// Converts a value to a JSON string representation.\n#[node_macro::node(category(\"Text\"))]\nfn serialize<T: serde::Serialize>(\n\t_: impl Ctx,\n\t#[implementations(String, bool, f64, u32, u64, DVec2, DAffine2, /* Table<Artboard>, Table<Graphic>, Table<Vector>, */ Table<Raster<CPU>>, Table<Color> /* , Table<GradientStops> */)] value: T,\n) -> String {\n\tserde_json::to_string(&value).unwrap_or_else(|_| \"Serialization Error\".to_string())\n}\n\n/// Joins two strings together.\n#[node_macro::node(category(\"Text\"))]\nfn string_concatenate(_: impl Ctx, #[implementations(String)] first: String, second: TextArea) -> String {\n\tfirst.clone() + &second\n}\n\n/// Replaces all occurrences of \"From\" with \"To\" in the input string.\n#[node_macro::node(category(\"Text\"))]\nfn string_replace(_: impl Ctx, string: String, from: TextArea, to: TextArea) -> String {\n\tstring.replace(&from, &to)\n}\n\n/// Extracts a substring from the input string, starting at \"Start\" and ending before \"End\".\n/// Negative indices count from the end of the string.\n/// If \"Start\" equals or exceeds \"End\", the result is an empty string.\n#[node_macro::node(category(\"Text\"))]\nfn string_slice(_: impl Ctx, string: String, start: f64, end: f64) -> String {\n\tlet total_chars = string.chars().count();\n\n\tlet start = if start < 0. {\n\t\ttotal_chars.saturating_sub(start.abs() as usize)\n\t} else {\n\t\t(start as usize).min(total_chars)\n\t};\n\tlet end = if end <= 0. {\n\t\ttotal_chars.saturating_sub(end.abs() as usize)\n\t} else {\n\t\t(end as usize).min(total_chars)\n\t};\n\n\tif start >= end {\n\t\treturn String::new();\n\t}\n\n\tstring.chars().skip(start).take(end - start).collect()\n}\n\n// TODO: Return u32, u64, or usize instead of f64 after #1621 is resolved and has allowed us to implement automatic type conversion in the node graph for nodes with generic type inputs.\n// TODO: (Currently automatic type conversion only works for concrete types, via the Graphene preprocessor and not the full Graphene type system.)\n/// Counts the number of characters in a string.\n#[node_macro::node(category(\"Text\"))]\nfn string_length(_: impl Ctx, string: String) -> f64 {\n\tstring.chars().count() as f64\n}\n\n/// Splits a string into a list of substrings based on the specified delimeter.\n/// For example, the delimeter \",\" will split \"a,b,c\" into the strings \"a\", \"b\", and \"c\".\n#[node_macro::node(category(\"Text\"))]\nfn string_split(\n\t_: impl Ctx,\n\t/// The string to split into substrings.\n\tstring: String,\n\t/// The character(s) that separate the substrings. These are not included in the outputs.\n\t#[default(\"\\\\n\")]\n\tdelimeter: String,\n\t/// Whether to convert escape sequences found in the delimeter into their corresponding characters:\n\t/// \"\\n\" (newline), \"\\r\" (carriage return), \"\\t\" (tab), \"\\0\" (null), and \"\\\\\" (backslash).\n\t#[default(true)]\n\tdelimeter_escaping: bool,\n) -> Vec<String> {\n\tlet delimeter = if delimeter_escaping {\n\t\tdelimeter.replace(\"\\\\n\", \"\\n\").replace(\"\\\\r\", \"\\r\").replace(\"\\\\t\", \"\\t\").replace(\"\\\\0\", \"\\0\").replace(\"\\\\\\\\\", \"\\\\\")\n\t} else {\n\t\tdelimeter\n\t};\n\n\tstring.split(&delimeter).map(str::to_string).collect()\n}\n\n/// Gets a value from either a json object or array given as a string input.\n/// For example, for the input {\"name\": \"ferris\"} the key \"name\" will return \"ferris\".\n#[node_macro::node(category(\"Text\"))]\nfn json_get(\n\t_: impl Ctx,\n\t/// The json data.\n\tdata: String,\n\t/// The key to index the object with.\n\tkey: String,\n) -> String {\n\tuse serde_json::Value;\n\tlet Ok(value): Result<Value, _> = serde_json::from_str(&data) else {\n\t\treturn \"Input is not valid json\".into();\n\t};\n\tmatch value {\n\t\tValue::Array(ref arr) => {\n\t\t\tlet Ok(index): Result<usize, _> = key.parse() else {\n\t\t\t\tlog::error!(\"Json input is an array, but key is not a number\");\n\t\t\t\treturn String::new();\n\t\t\t};\n\t\t\tlet Some(value) = arr.get(index) else {\n\t\t\t\tlog::error!(\"Index {} out of bounds for len {}\", index, arr.len());\n\t\t\t\treturn String::new();\n\t\t\t};\n\t\t\tvalue.to_string()\n\t\t}\n\t\tValue::Object(map) => {\n\t\t\tlet Some(value) = map.get(&key) else {\n\t\t\t\tlog::error!(\"Key {key} not found in object\");\n\t\t\t\treturn String::new();\n\t\t\t};\n\t\t\tmatch value {\n\t\t\t\tValue::String(s) => s.clone(),\n\t\t\t\tValue::Number(n) => n.to_string(),\n\t\t\t\tcomplex => complex.to_string(),\n\t\t\t}\n\t\t}\n\t\t_ => String::new(),\n\t}\n}\n\n/// Evaluates either the \"If True\" or \"If False\" input branch based on whether the input condition is true or false.\n#[node_macro::node(category(\"Math: Logic\"))]\nasync fn switch<T, C: Send + 'n + Clone>(\n\t#[implementations(Context)] ctx: C,\n\tcondition: bool,\n\t#[expose]\n\t#[implementations(\n\t\tContext -> String,\n\t\tContext -> bool,\n\t\tContext -> f32,\n\t\tContext -> f64,\n\t\tContext -> u32,\n\t\tContext -> u64,\n\t\tContext -> DVec2,\n\t\tContext -> DAffine2,\n\t\tContext -> Table<Artboard>,\n\t\tContext -> Table<Graphic>,\n\t\tContext -> Table<Vector>,\n\t\tContext -> Table<Raster<CPU>>,\n\t\tContext -> Table<Raster<GPU>>,\n\t\tContext -> Table<Color>,\n\t\tContext -> Table<GradientStops>,\n\t)]\n\tif_true: impl Node<C, Output = T>,\n\t#[expose]\n\t#[implementations(\n\t\tContext -> String,\n\t\tContext -> bool,\n\t\tContext -> f32,\n\t\tContext -> f64,\n\t\tContext -> u32,\n\t\tContext -> u64,\n\t\tContext -> DVec2,\n\t\tContext -> DAffine2,\n\t\tContext -> Table<Artboard>,\n\t\tContext -> Table<Graphic>,\n\t\tContext -> Table<Vector>,\n\t\tContext -> Table<Raster<CPU>>,\n\t\tContext -> Table<Raster<GPU>>,\n\t\tContext -> Table<Color>,\n\t\tContext -> Table<GradientStops>,\n\t)]\n\tif_false: impl Node<C, Output = T>,\n) -> T {\n\tif condition { if_true.eval(ctx).await } else { if_false.eval(ctx).await }\n}\n"
  },
  {
    "path": "node-graph/nodes/gcore/src/memo.rs",
    "content": "use core_types::WasmNotSend;\nuse core_types::memo::*;\nuse std::hash::DefaultHasher;\nuse std::hash::{Hash, Hasher};\nuse std::sync::Arc;\nuse std::sync::Mutex;\n\n/// Caches the output of a given node called with a specific input.\n///\n/// A cache miss occurs when the Option is None. In this case, the node evaluates the inner node and memoizes (stores) the result.\n///\n/// A cache hit occurs when the Option is Some and has a stored hash matching the hash of the call argument. In this case, the node returns the cached value without re-evaluating the inner node.\n///\n/// Currently, only one input-output pair is cached. Subsequent calls with different inputs will overwrite the previous cache.\n#[node_macro::node(category(\"\"), path(graphene_core::memo), skip_impl)]\nasync fn memo<I: Hash + Send + 'n, T: Clone + WasmNotSend>(input: I, #[data] cache: Arc<Mutex<Option<(u64, T)>>>, node: impl Node<I, Output = T>) -> T {\n\tlet mut hasher = DefaultHasher::new();\n\tinput.hash(&mut hasher);\n\tlet hash = hasher.finish();\n\n\tif let Some(data) = cache.lock().as_ref().unwrap().as_ref().and_then(|data| (data.0 == hash).then_some(data.1.clone())) {\n\t\treturn data;\n\t}\n\n\tlet value = node.eval(input).await;\n\t*cache.lock().unwrap() = Some((hash, value.clone()));\n\tvalue\n}\n\ntype MonitorValue<I, T> = Arc<Mutex<Option<Arc<IORecord<I, T>>>>>;\n\n/// Caches the output of the last graph evaluation for introspection.\n#[node_macro::node(category(\"\"), path(graphene_core::memo), serialize(serialize_monitor), skip_impl)]\nasync fn monitor<I: Clone + 'static + Send + Sync, T: Clone + 'static + Send + Sync>(\n\tinput: I,\n\t#[allow(clippy::type_complexity)]\n\t#[data]\n\tio: MonitorValue<I, T>,\n\tnode: impl Node<I, Output = T>,\n) -> T {\n\tlet output = node.eval(input.clone()).await;\n\t*io.lock().unwrap() = Some(Arc::new(IORecord { input, output: output.clone() }));\n\toutput\n}\n\nfn serialize_monitor<I: Clone + 'static + Send + Sync, T: Clone + 'static + Send + Sync>(io: &MonitorValue<I, T>) -> Option<Arc<dyn std::any::Any + Send + Sync>> {\n\tlet io = io.lock().unwrap();\n\tio.as_ref().map(|output| output.clone() as Arc<dyn std::any::Any + Send + Sync>)\n}\n"
  },
  {
    "path": "node-graph/nodes/gcore/src/ops.rs",
    "content": "use core_types::{Ctx, ExtractFootprint, ops::Convert, transform::Footprint};\nuse std::marker::PhantomData;\n\n// Re-export TypeNode from core-types for convenience\npub use core_types::ops::TypeNode;\n\n// TODO: Rename to \"Passthrough\" and make this the node that users use, not the one defined in document_node_definitions.rs\n/// Passes-through the input value without changing it.\n/// This is useful for rerouting wires for organization purposes.\n#[node_macro::node(category(\"\"), skip_impl)]\nfn identity<'i, T: 'i + Send>(value: T) -> T {\n\tvalue\n}\n\n#[node_macro::node(category(\"\"), skip_impl)]\nfn into<'i, T: 'i + Send + Into<O>, O: 'i + Send>(_: impl Ctx, value: T, _out_ty: PhantomData<O>) -> O {\n\tvalue.into()\n}\n\n#[node_macro::node(category(\"\"), skip_impl)]\nasync fn convert<'i, T: 'i + Send + Convert<O, C>, O: 'i + Send, C: 'i + Send>(ctx: impl Ctx + ExtractFootprint, value: T, converter: C, _out_ty: PhantomData<O>) -> O {\n\tvalue.convert(*ctx.try_footprint().unwrap_or(&Footprint::DEFAULT), converter).await\n}\n\n#[cfg(test)]\nmod test {\n\tuse super::*;\n\n\t#[test]\n\tpub fn identity_node() {\n\t\tassert_eq!(identity(&4), &4);\n\t}\n}\n"
  },
  {
    "path": "node-graph/nodes/graphic/Cargo.toml",
    "content": "[package]\nname = \"graphic-nodes\"\nversion = \"0.1.0\"\nedition = \"2024\"\nlicense = \"MIT OR Apache-2.0\"\nauthors.workspace = true\n\n[dependencies]\n# Local dependencies\ncore-types = { workspace = true }\ngraphic-types = { workspace = true }\nvector-types = { workspace = true }\nraster-types = { workspace = true }\n\n# Workspace dependencies\ndyn-any = { workspace = true }\nglam = { workspace = true }\nserde = { workspace = true }\nnode-macro = { workspace = true }\n"
  },
  {
    "path": "node-graph/nodes/graphic/src/artboard.rs",
    "content": "use core_types::{CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl, table::Table, transform::TransformMut};\nuse glam::{DAffine2, DVec2, IVec2};\nuse graphic_types::{\n\tArtboard, Vector,\n\tgraphic::{Graphic, IntoGraphicTable},\n};\nuse raster_types::{CPU, GPU, Raster};\nuse vector_types::GradientStops;\n\n/// Constructs a new single artboard table with the chosen properties.\n#[node_macro::node(category(\"\"))]\npub async fn create_artboard<T: IntoGraphicTable + 'n>(\n\tctx: impl ExtractAll + CloneVarArgs + Ctx,\n\t/// Graphics to include within the artboard.\n\t#[implementations(\n\t\tContext -> Table<Graphic>,\n\t\tContext -> Table<Vector>,\n\t\tContext -> Table<Raster<CPU>>,\n\t\tContext -> Table<Raster<GPU>>,\n\t\tContext -> Table<Color>,\n\t\tContext -> Table<GradientStops>,\n\t\tContext -> DAffine2,\n\t)]\n\tcontent: impl Node<Context<'static>, Output = T>,\n\t/// Name of the artboard, shown in parts of the editor.\n\tlabel: String,\n\t/// Coordinate of the top-left corner of the artboard within the document.\n\tlocation: DVec2,\n\t/// Width and height of the artboard within the document. Only integers are valid.\n\tdimensions: DVec2,\n\t/// Color of the artboard background. Only positive integers are valid.\n\tbackground: Table<Color>,\n\t/// Whether to cut off the contained content that extends outside the artboard, or keep it visible.\n\tclip: bool,\n) -> Table<Artboard> {\n\tlet location = location.as_ivec2();\n\n\tlet footprint = ctx.try_footprint().copied();\n\tlet mut new_ctx = OwnedContextImpl::from(ctx);\n\tif let Some(mut footprint) = footprint {\n\t\tfootprint.translate(location.as_dvec2());\n\t\tnew_ctx = new_ctx.with_footprint(footprint);\n\t}\n\tlet content = content.eval(new_ctx.into_context()).await.into_graphic_table();\n\n\tlet dimensions = dimensions.as_ivec2().max(IVec2::ONE);\n\n\tlet location = location.min(location + dimensions);\n\n\tlet dimensions = dimensions.abs();\n\n\tlet background: Option<Color> = background.into();\n\tlet background = background.unwrap_or(Color::WHITE);\n\n\tTable::new_from_element(Artboard {\n\t\tcontent,\n\t\tlabel,\n\t\tlocation,\n\t\tdimensions,\n\t\tbackground,\n\t\tclip,\n\t})\n}\n"
  },
  {
    "path": "node-graph/nodes/graphic/src/graphic.rs",
    "content": "use core_types::bounds::{BoundingBox, RenderBoundingBox};\nuse core_types::registry::types::{Angle, SignedInteger};\nuse core_types::table::{Table, TableRow};\nuse core_types::uuid::NodeId;\nuse core_types::{AnyHash, CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl};\nuse glam::{DAffine2, DVec2};\nuse graphic_types::graphic::{Graphic, IntoGraphicTable};\nuse graphic_types::{Artboard, Vector};\nuse raster_types::{CPU, GPU, Raster};\nuse vector_types::{GradientStop, GradientStops, ReferencePoint};\n\n/// Returns the value at the specified index in the collection.\n/// If no value exists at that index, the type's default value is returned.\n#[node_macro::node(category(\"General\"))]\npub fn index_elements<T: graphic_types::graphic::AtIndex + Clone + Default>(\n\t_: impl Ctx,\n\t/// The collection of data, such as a list or table.\n\t#[implementations(\n\t\tVec<f64>,\n\t\tVec<u32>,\n\t\tVec<u64>,\n\t\tVec<DVec2>,\n\t\tVec<String>,\n\t\tTable<Artboard>,\n\t\tTable<Graphic>,\n\t\tTable<Vector>,\n\t\tTable<Raster<CPU>>,\n\t\tTable<Raster<GPU>>,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t)]\n\tcollection: T,\n\t/// The index of the item to retrieve, starting from 0 for the first item. Negative indices count backwards from the end of the collection, starting from -1 for the last item.\n\tindex: SignedInteger,\n) -> T::Output\nwhere\n\tT::Output: Clone + Default,\n{\n\tlet index = index as i32;\n\n\tif index < 0 {\n\t\tcollection.at_index_from_end(-index as usize)\n\t} else {\n\t\tcollection.at_index(index as usize)\n\t}\n\t.unwrap_or_default()\n}\n\n#[node_macro::node(category(\"General\"))]\nasync fn map<Item: AnyHash + Send + Sync + std::hash::Hash>(\n\tctx: impl Ctx + CloneVarArgs + ExtractAll,\n\t#[implementations(\n\t\tTable<Graphic>,\n\t\tTable<Vector>,\n\t\tTable<Raster<CPU>>,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t)]\n\tcontent: Table<Item>,\n\t#[implementations(\n\t\tContext -> Table<Graphic>,\n\t\tContext -> Table<Vector>,\n\t\tContext -> Table<Raster<CPU>>,\n\t\tContext -> Table<Color>,\n\t\tContext -> Table<GradientStops>,\n\t)]\n\tmapped: impl Node<Context<'static>, Output = Table<Item>>,\n) -> Table<Item> {\n\tlet mut rows = Table::new();\n\n\tfor (i, row) in content.into_iter().enumerate() {\n\t\tlet owned_ctx = OwnedContextImpl::from(ctx.clone());\n\t\tlet owned_ctx = owned_ctx.with_vararg(Box::new(Table::new_from_row(row))).with_index(i);\n\t\tlet table = mapped.eval(owned_ctx.into_context()).await;\n\n\t\trows.extend(table);\n\t}\n\n\trows\n}\n\n#[node_macro::node(category(\"General\"))]\nasync fn mirror<T: 'n + Send + Clone>(\n\t_: impl Ctx,\n\t#[implementations(\n\t\tTable<Graphic>,\n\t\tTable<Vector>,\n\t\tTable<Raster<CPU>>,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t)]\n\tcontent: Table<T>,\n\t#[default(ReferencePoint::Center)] relative_to_bounds: ReferencePoint,\n\t#[unit(\" px\")] offset: f64,\n\t#[range((-90., 90.))] angle: Angle,\n\t#[default(true)] keep_original: bool,\n) -> Table<T>\nwhere\n\tTable<T>: BoundingBox,\n{\n\t// Normalize the direction vector\n\tlet normal = DVec2::from_angle(angle.to_radians());\n\n\t// The mirror reference may be based on the bounding box if an explicit reference point is chosen\n\tlet RenderBoundingBox::Rectangle(bounding_box) = content.bounding_box(DAffine2::IDENTITY, false) else {\n\t\treturn content;\n\t};\n\n\tlet reference_point_location = relative_to_bounds.point_in_bounding_box((bounding_box[0], bounding_box[1]).into());\n\tlet mirror_reference_point = reference_point_location.map(|point| point + normal * offset);\n\n\t// Create the reflection matrix\n\tlet reflection = DAffine2::from_mat2_translation(\n\t\tglam::DMat2::from_cols(\n\t\t\tDVec2::new(1. - 2. * normal.x * normal.x, -2. * normal.y * normal.x),\n\t\t\tDVec2::new(-2. * normal.x * normal.y, 1. - 2. * normal.y * normal.y),\n\t\t),\n\t\tDVec2::ZERO,\n\t);\n\n\t// Apply reflection around the reference point\n\tlet reflected_transform = if let Some(mirror_reference_point) = mirror_reference_point {\n\t\tDAffine2::from_translation(mirror_reference_point) * reflection * DAffine2::from_translation(-mirror_reference_point)\n\t} else {\n\t\treflection * DAffine2::from_translation(DVec2::from_angle(angle.to_radians()) * DVec2::splat(-offset))\n\t};\n\n\tlet mut result_table = Table::new();\n\n\t// Add original instance depending on the keep_original flag\n\tif keep_original {\n\t\tfor instance in content.clone().into_iter() {\n\t\t\tresult_table.push(instance);\n\t\t}\n\t}\n\n\t// Create and add mirrored instance\n\tfor mut row in content.into_iter() {\n\t\trow.transform = reflected_transform * row.transform;\n\t\tresult_table.push(row);\n\t}\n\n\tresult_table\n}\n\n/// Performs internal editor record-keeping that enables tools to target this network's layer.\n/// This node associates the ID of the network's parent layer to every element of output data.\n/// This technical detail may be ignored by users, and will be phased out in the future.\n#[node_macro::node(category(\"\"))]\npub async fn source_node_id<T: 'n + Send + Clone>(\n\t_: impl Ctx,\n\t#[implementations(\n\t\tTable<Artboard>,\n\t\tTable<Graphic>,\n\t\tTable<Vector>,\n\t\tTable<Raster<CPU>>,\n\t\tTable<Raster<GPU>>,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t)]\n\tcontent: Table<T>,\n\tnode_path: Vec<NodeId>,\n) -> Table<T> {\n\t// Get the penultimate element of the node path, or None if the path is too short\n\t// This is used to get the ID of the user-facing parent layer node (whose network contains this internal node).\n\tlet source_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();\n\n\tlet mut content = content;\n\tfor row in content.iter_mut() {\n\t\t*row.source_node_id = source_node_id;\n\t}\n\n\tcontent\n}\n\n/// Joins two tables of the same type, extending the base table with the rows of the new table.\n#[node_macro::node(category(\"General\"))]\npub async fn extend<T: 'n + Send + Clone>(\n\t_: impl Ctx,\n\t/// The table whose rows will appear at the start of the extended table.\n\t#[implementations(Table<Artboard>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>, Table<GradientStops>)]\n\tbase: Table<T>,\n\t/// The table whose rows will appear at the end of the extended table.\n\t#[expose]\n\t#[implementations(Table<Artboard>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>, Table<GradientStops>)]\n\tnew: Table<T>,\n) -> Table<T> {\n\tlet mut base = base;\n\tbase.extend(new);\n\n\tbase\n}\n\n// TODO: Eventually remove this document upgrade code\n/// Performs an obsolete function as part of a migration from an older document format.\n/// Users are advised to delete this node and replace it with a new one.\n#[node_macro::node(category(\"\"))]\npub async fn legacy_layer_extend<T: 'n + Send + Clone>(\n\t_: impl Ctx,\n\t#[implementations(Table<Artboard>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>, Table<GradientStops>)] base: Table<T>,\n\t#[expose]\n\t#[implementations(Table<Artboard>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>, Table<GradientStops>)]\n\tnew: Table<T>,\n\tnested_node_path: Vec<NodeId>,\n) -> Table<T> {\n\t// Get the penultimate element of the node path, or None if the path is too short\n\t// This is used to get the ID of the user-facing parent layer-style node (which encapsulates this internal node).\n\tlet source_node_id = nested_node_path.get(nested_node_path.len().wrapping_sub(2)).copied();\n\n\tlet mut base = base;\n\tfor row in new.into_iter() {\n\t\tbase.push(TableRow { source_node_id, ..row });\n\t}\n\n\tbase\n}\n\n/// Nests the input graphical content in a wrapper graphic. This essentially \"groups\" the input.\n/// The inverse of this node is 'Flatten Graphic'.\n#[node_macro::node(category(\"General\"))]\npub async fn wrap_graphic<T: Into<Graphic> + 'n>(\n\t_: impl Ctx,\n\t#[implementations(\n\t\tTable<Graphic>,\n\t \tTable<Vector>,\n\t\tTable<Raster<CPU>>,\n\t \tTable<Raster<GPU>>,\n\t \tTable<Color>,\n\t\tTable<GradientStops>,\n\t\tDAffine2,\n\t)]\n\tcontent: T,\n) -> Table<Graphic> {\n\tTable::new_from_element(content.into())\n}\n\n/// Converts a table of graphical content into a graphic table by placing it into an element of a new wrapper graphic table.\n/// If it is already a graphic table, it is not wrapped again. Use the 'Wrap Graphic' node if wrapping is always desired.\n#[node_macro::node(category(\"General\"))]\npub async fn to_graphic<T: IntoGraphicTable + 'n>(\n\t_: impl Ctx,\n\t#[implementations(\n\t\tTable<Graphic>,\n\t\tTable<Vector>,\n\t\tTable<Raster<CPU>>,\n\t\tTable<Raster<GPU>>,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t)]\n\tcontent: T,\n) -> Table<Graphic> {\n\tcontent.into_graphic_table()\n}\n\n/// Removes a level of nesting from a graphic table, or all nesting if \"Fully Flatten\" is enabled.\n#[node_macro::node(category(\"General\"))]\npub async fn flatten_graphic(_: impl Ctx, content: Table<Graphic>, fully_flatten: bool) -> Table<Graphic> {\n\t// TODO: Avoid mutable reference, instead return a new Table<Graphic>?\n\tfn flatten_table(output_graphic_table: &mut Table<Graphic>, current_graphic_table: Table<Graphic>, fully_flatten: bool, recursion_depth: usize) {\n\t\tfor current_row in current_graphic_table.iter() {\n\t\t\tlet current_element = current_row.element.clone();\n\t\t\tlet reference = *current_row.source_node_id;\n\n\t\t\tlet recurse = fully_flatten || recursion_depth == 0;\n\n\t\t\tmatch current_element {\n\t\t\t\t// If we're allowed to recurse, flatten any graphics we encounter\n\t\t\t\tGraphic::Graphic(mut current_element) if recurse => {\n\t\t\t\t\t// Apply the parent graphic's transform to all child elements\n\t\t\t\t\tfor graphic in current_element.iter_mut() {\n\t\t\t\t\t\t*graphic.transform = *current_row.transform * *graphic.transform;\n\t\t\t\t\t}\n\n\t\t\t\t\tflatten_table(output_graphic_table, current_element, fully_flatten, recursion_depth + 1);\n\t\t\t\t}\n\t\t\t\t// Push any leaf Graphic elements we encounter, which can be either Graphic table elements beyond the recursion depth, or table elements other than Graphic tables\n\t\t\t\t_ => {\n\t\t\t\t\toutput_graphic_table.push(TableRow {\n\t\t\t\t\t\telement: current_element,\n\t\t\t\t\t\ttransform: *current_row.transform,\n\t\t\t\t\t\talpha_blending: *current_row.alpha_blending,\n\t\t\t\t\t\tsource_node_id: reference,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tlet mut output = Table::new();\n\tflatten_table(&mut output, content, fully_flatten, 0);\n\n\toutput\n}\n\n/// Converts a graphic table into a vector table by deeply flattening any vector content it contains, and discarding any non-vector content.\n#[node_macro::node(category(\"Vector\"))]\npub async fn flatten_vector<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Vector>)] content: T) -> Table<Vector> {\n\tcontent.into_flattened_table()\n}\n\n/// Converts a graphic table into a raster table by deeply flattening any raster content it contains, and discarding any non-raster content.\n#[node_macro::node(category(\"Raster\"))]\npub async fn flatten_raster<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Raster<CPU>>)] content: T) -> Table<Raster<CPU>> {\n\tcontent.into_flattened_table()\n}\n\n/// Converts a graphic table into a color table by deeply flattening any color content it contains, and discarding any non-color content.\n#[node_macro::node(category(\"General\"))]\npub async fn flatten_color<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Color>)] content: T) -> Table<Color> {\n\tcontent.into_flattened_table()\n}\n\n/// Converts a graphic table into a gradient table by deeply flattening any gradient content it contains, and discarding any non-gradient content.\n#[node_macro::node(category(\"General\"))]\npub async fn flatten_gradient<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<GradientStops>)] content: T) -> Table<GradientStops> {\n\tcontent.into_flattened_table()\n}\n\n/// Constructs a gradient from a table of colors, where the colors are evenly distributed as gradient stops across the range from 0 to 1.\n#[node_macro::node(category(\"Color\"))]\nfn colors_to_gradient<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Color>)] colors: T) -> Table<GradientStops> {\n\tlet colors = colors.into_flattened_table::<Color>();\n\tlet total_colors = colors.len();\n\n\tif total_colors == 0 {\n\t\treturn Table::new_from_element(GradientStops::new(vec![\n\t\t\tGradientStop {\n\t\t\t\tposition: 0.,\n\t\t\t\tmidpoint: 0.5,\n\t\t\t\tcolor: Color::BLACK,\n\t\t\t},\n\t\t\tGradientStop {\n\t\t\t\tposition: 1.,\n\t\t\t\tmidpoint: 0.5,\n\t\t\t\tcolor: Color::BLACK,\n\t\t\t},\n\t\t]));\n\t}\n\n\tif let (Some(color), None) = {\n\t\tlet mut colors_iter = colors.iter();\n\t\t(colors_iter.next(), colors_iter.next())\n\t} {\n\t\treturn Table::new_from_element(GradientStops::new(vec![\n\t\t\tGradientStop {\n\t\t\t\tposition: 0.,\n\t\t\t\tmidpoint: 0.5,\n\t\t\t\tcolor: *color.element,\n\t\t\t},\n\t\t\tGradientStop {\n\t\t\t\tposition: 1.,\n\t\t\t\tmidpoint: 0.5,\n\t\t\t\tcolor: *color.element,\n\t\t\t},\n\t\t]));\n\t}\n\n\tlet colors = colors.into_iter().enumerate().map(|(index, row)| GradientStop {\n\t\tposition: index as f64 / (total_colors - 1) as f64,\n\t\tmidpoint: 0.5,\n\t\tcolor: row.element,\n\t});\n\tTable::new_from_element(GradientStops::new(colors))\n}\n"
  },
  {
    "path": "node-graph/nodes/graphic/src/lib.rs",
    "content": "pub mod artboard;\npub mod graphic;\n\n// Re-export all nodes\npub use artboard::*;\npub use graphic::*;\n"
  },
  {
    "path": "node-graph/nodes/gstd/Cargo.toml",
    "content": "[package]\nname = \"graphene-std\"\nversion = \"0.1.0\"\nedition = \"2024\"\ndescription = \"Graphene standard library\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\nlicense = \"MIT OR Apache-2.0\"\n\n[features]\ndefault = [\"wasm\", \"wgpu\"]\ngpu = []\nwgpu = [\"gpu\", \"graph-craft/wgpu\", \"graphene-application-io/wgpu\"]\nwasm = [\n\t\"wasm-bindgen\",\n\t\"wasm-bindgen-futures\",\n\t\"web-sys\",\n\t\"graphene-application-io/wasm\",\n\t\"image/png\",\n\t\"core-types/wasm\",\n\t\"vector-types/wasm\",\n\t\"graphic-types/wasm\",\n\t\"text-nodes/wasm\",\n\t\"raster-nodes/wasm\",\n\t\"vector-nodes/wasm\",\n\t\"graphene-core/wasm\",\n\t\"graph-craft/wasm\",\n]\nimage-compare = []\nvello = [\"gpu\"]\nresvg = []\nshader-nodes = [\"raster-nodes/shader-nodes\"]\n\n[dependencies]\n# Local dependencies\ndyn-any = { workspace = true }\ngraph-craft = { workspace = true }\nwgpu-executor = { workspace = true }\ncore-types = { workspace = true }\nvector-types = { workspace = true }\ngraphic-types = { workspace = true }\nblending-nodes = { workspace = true }\ntext-nodes = { workspace = true }\ntransform-nodes = { workspace = true }\nvector-nodes = { workspace = true }\npath-bool-nodes = { workspace = true }\nmath-nodes = { workspace = true }\nrendering = { workspace = true }\ngraphene-application-io = { workspace = true }\nraster-nodes = { workspace = true }\nbrush-nodes = { workspace = true }\ngraphene-core = { workspace = true }\ngraphic-nodes = { workspace = true }\nrepeat-nodes = { workspace = true }\n\n# Workspace dependencies\nlog = { workspace = true }\nglam = { workspace = true }\nnode-macro = { workspace = true }\nreqwest = { workspace = true }\nimage = { workspace = true }\nbase64 = { workspace = true }\nwgpu = { workspace = true }\n\n# Optional workspace dependencies\nwasm-bindgen = { workspace = true, optional = true }\nwasm-bindgen-futures = { workspace = true, optional = true }\ntokio = { workspace = true, optional = true }\nvello = { workspace = true }\nvello_encoding = { workspace = true }\nweb-sys = { workspace = true, optional = true, features = [\n\t\"Window\",\n\t\"CanvasRenderingContext2d\",\n\t\"ImageData\",\n\t\"Document\",\n\t\"Navigator\",\n\t\"Gpu\",\n\t\"HtmlCanvasElement\",\n\t\"HtmlImageElement\",\n\t\"ImageBitmapRenderingContext\",\n] }\n\n[dev-dependencies]\ntokio = { workspace = true }\n"
  },
  {
    "path": "node-graph/nodes/gstd/src/any.rs",
    "content": "use core_types::NodeIO;\nuse core_types::WasmNotSend;\npub use core_types::registry::{DowncastBothNode, DynAnyNode, FutureWrapperNode, PanicNode};\npub use core_types::{Node, generic, ops};\nuse dyn_any::StaticType;\npub use graph_craft::proto::{Any, NodeContainer, TypeErasedBox, TypeErasedNode};\nuse graph_craft::proto::{FutureAny, SharedNodeContainer};\n\npub trait IntoTypeErasedNode<'n> {\n\tfn into_type_erased(self) -> TypeErasedBox<'n>;\n}\n\nimpl<'n, N: 'n> IntoTypeErasedNode<'n> for N\nwhere\n\tN: for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + Sync + WasmNotSend,\n{\n\tfn into_type_erased(self) -> TypeErasedBox<'n> {\n\t\tBox::new(self)\n\t}\n}\n\npub fn input_node<O: StaticType>(n: SharedNodeContainer) -> DowncastBothNode<(), O> {\n\tdowncast_node(n)\n}\npub fn downcast_node<I: StaticType, O: StaticType>(n: SharedNodeContainer) -> DowncastBothNode<I, O> {\n\tDowncastBothNode::new(n)\n}\n"
  },
  {
    "path": "node-graph/nodes/gstd/src/lib.rs",
    "content": "pub mod any;\npub mod pixel_preview;\npub mod render_cache;\npub mod render_node;\npub mod text;\n#[cfg(feature = \"wasm\")]\npub mod wasm_application_io;\npub use blending_nodes;\npub use brush_nodes as brush;\npub use core_types::*;\npub use graphene_application_io as application_io;\npub use graphene_core;\npub use graphene_core::debug;\npub use graphic_nodes;\npub use graphic_types::{Artboard, Graphic, Vector};\npub use math_nodes;\npub use path_bool_nodes;\npub use raster_nodes;\npub use repeat_nodes;\npub use text_nodes;\npub use transform_nodes;\npub use vector_nodes;\npub use vector_types;\n\n/// Backward compatibility re-exports\npub mod vector {\n\tpub use graphic_types::Vector;\n\tpub use vector_types::vector::{VectorModification, VectorModificationType, misc, style};\n\tpub use vector_types::*;\n\n\t// Re-export commonly used types and submodules\n\tpub use vector_types::vector::algorithms;\n\tpub use vector_types::vector::click_target;\n\tpub use vector_types::vector::misc::HandleId;\n\tpub use vector_types::vector::{PointId, RegionId, SegmentId, StrokeId};\n\tpub use vector_types::vector::{deserialize_hashmap, serialize_hashmap};\n\n\t// Re-export HandleExt trait and NoHashBuilder\n\tpub use vector_types::vector::HandleExt;\n\tpub use vector_types::vector::NoHashBuilder;\n\n\t// Re-export vector node modules and functions\n\tpub use vector_nodes::*;\n}\n\npub mod graphic {\n\tpub use graphic_nodes::graphic::*;\n\tpub use graphic_types::Artboard;\n\tpub use graphic_types::graphic::*;\n}\n\npub mod artboard {\n\tpub use graphic_nodes::artboard::*;\n\tpub use graphic_types::artboard::*;\n}\n\npub mod subpath {\n\tpub use vector_types::subpath::*;\n}\n\npub mod gradient {\n\tpub use vector_types::{GradientStop, GradientStops};\n}\n\npub mod transform {\n\tpub use core_types::transform::*;\n\tpub use vector_types::ReferencePoint;\n}\n\npub mod repeat {\n\tpub use repeat_nodes::repeat_nodes::*;\n}\n\npub mod math {\n\tpub use core_types::math::quad;\n\n\tpub mod math_ext {\n\t\tpub use vector_types::{QuadExt, RectExt};\n\t}\n}\n\npub mod logic {\n\tpub use graphene_core::logic::*;\n}\n\npub mod context {\n\tpub use graphene_core::context::*;\n}\n\n// Re-export graphene_core modules for backward compatibility\npub mod ops {\n\tpub use core_types::ops::*;\n\tpub use graphene_core::ops::*;\n}\n\npub mod extract_xy {\n\tpub use graphene_core::extract_xy::*;\n}\n\npub mod animation {\n\tpub use graphene_core::animation::*;\n}\n\n/// stop gap solutions until all paths have been replaced with their absolute ones\npub mod renderer {\n\tpub use core_types::math::quad::Quad;\n\tpub use core_types::math::rect::Rect;\n\tpub use rendering::*;\n}\n\npub mod raster {\n\tpub use graphic_types::raster_types::*;\n\tpub use raster_nodes::adjustments::*;\n\tpub use raster_nodes::*;\n}\n\npub mod raster_types {\n\tpub use graphic_types::raster_types::*;\n}\n\npub mod memo {\n\tpub use core_types::memo::*;\n\tpub use graphene_core::memo::*;\n}\n"
  },
  {
    "path": "node-graph/nodes/gstd/src/pixel_preview.rs",
    "content": "use crate::render_node::RenderOutputType;\nuse core_types::transform::{Footprint, Transform};\nuse core_types::{CloneVarArgs, Context, Ctx, ExtractAll, OwnedContextImpl};\nuse glam::{DAffine2, DVec2, UVec2};\nuse graph_craft::document::value::RenderOutput;\nuse graph_craft::wasm_application_io::WasmEditorApi;\nuse graphene_application_io::{ApplicationIo, ImageTexture};\nuse rendering::{RenderOutputType as RenderOutputTypeRequest, RenderParams};\nuse vector_types::vector::style::RenderMode;\n\n#[node_macro::node(category(\"\"))]\npub async fn pixel_preview<'a: 'n>(\n\tctx: impl Ctx + ExtractAll + CloneVarArgs + Sync,\n\teditor_api: &'a WasmEditorApi,\n\tdata: impl Node<Context<'static>, Output = RenderOutput> + Send + Sync,\n) -> RenderOutput {\n\tlet Some(render_params) = ctx.vararg(0).ok().and_then(|v| v.downcast_ref::<RenderParams>()).cloned() else {\n\t\tlog::error!(\"invalid render params for pixel preview\");\n\t\tlet context = OwnedContextImpl::from(ctx).into_context();\n\t\treturn data.eval(context).await;\n\t};\n\tlet physical_scale = render_params.scale;\n\n\tlet footprint = *ctx.footprint();\n\tlet viewport_zoom = footprint.decompose_scale().x * physical_scale;\n\n\tif render_params.render_mode != RenderMode::PixelPreview || !matches!(render_params.render_output_type, RenderOutputTypeRequest::Vello) || viewport_zoom <= 1. {\n\t\tlet context = OwnedContextImpl::from(ctx).into_context();\n\t\treturn data.eval(context).await;\n\t}\n\n\tlet physical_resolution = footprint.resolution;\n\tlet logical_resolution = physical_resolution.as_dvec2() / physical_scale;\n\n\tlet logical_footprint = Footprint {\n\t\tresolution: logical_resolution.as_uvec2().max(UVec2::ONE),\n\t\t..footprint\n\t};\n\n\tlet bounds = logical_footprint.viewport_bounds_in_local_space();\n\n\tlet upstream_min = bounds.start.floor();\n\tlet upstream_max = bounds.end.ceil();\n\n\tlet upstream_size = (upstream_max - upstream_min).max(DVec2::ONE);\n\tlet upstream_resolution = upstream_size.as_uvec2().max(UVec2::ONE);\n\n\tlet upstream_footprint = Footprint {\n\t\ttransform: DAffine2::from_scale(DVec2::splat(1.0 / physical_scale)) * DAffine2::from_translation(-upstream_min),\n\t\tresolution: upstream_resolution,\n\t\tquality: footprint.quality,\n\t};\n\n\tlet new_ctx = OwnedContextImpl::from(ctx).with_footprint(upstream_footprint).with_vararg(Box::new(render_params)).into_context();\n\tlet mut result = data.eval(new_ctx).await;\n\n\tlet RenderOutputType::Texture(ref source_texture) = result.data else { return result };\n\n\tlet transform = DAffine2::from_translation(-upstream_min) * footprint.transform.inverse() * DAffine2::from_scale(logical_resolution);\n\n\tlet exec = editor_api.application_io.as_ref().unwrap().gpu_executor().unwrap();\n\tlet resampled = exec.resample_texture(&source_texture.texture, physical_resolution, &transform);\n\n\tresult.data = RenderOutputType::Texture(ImageTexture { texture: resampled.into() });\n\n\tresult\n\t\t.metadata\n\t\t.apply_transform(footprint.transform * DAffine2::from_translation(upstream_min) * DAffine2::from_scale(DVec2::splat(physical_scale)));\n\n\tresult\n}\n"
  },
  {
    "path": "node-graph/nodes/gstd/src/render_cache.rs",
    "content": "//! Tile-based render caching for efficient viewport panning.\n\nuse core_types::math::bbox::AxisAlignedBbox;\nuse core_types::transform::{Footprint, RenderQuality, Transform};\nuse core_types::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractAnimationTime, ExtractPointerPosition, ExtractRealTime, OwnedContextImpl};\nuse glam::{DAffine2, DVec2, IVec2, UVec2};\nuse graph_craft::document::value::RenderOutput;\nuse graph_craft::wasm_application_io::WasmEditorApi;\nuse graphene_application_io::{ApplicationIo, ImageTexture};\nuse rendering::{RenderOutputType as RenderOutputTypeRequest, RenderParams};\nuse std::collections::HashSet;\nuse std::hash::Hash;\nuse std::sync::{Arc, Mutex};\n\nuse crate::render_node::RenderOutputType;\n\npub const TILE_SIZE: u32 = 256;\npub const MAX_CACHE_MEMORY_BYTES: usize = 512 * 1024 * 1024;\nconst BYTES_PER_PIXEL: usize = 4;\n\n#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]\npub struct TileCoord {\n\tpub x: i32,\n\tpub y: i32,\n}\n\n#[derive(Debug, Clone)]\npub struct CachedRegion {\n\tpub texture: wgpu::Texture,\n\tpub texture_size: UVec2,\n\tpub tiles: Vec<TileCoord>,\n\tpub metadata: rendering::RenderMetadata,\n\tlast_access: u64,\n\tmemory_size: usize,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]\npub struct CacheKey {\n\tpub max_region_area: u32,\n\tpub render_mode_hash: u64,\n\tpub device_scale: u64,\n\tpub zoom: u64,\n\tpub rotation: u64,\n\tpub hide_artboards: bool,\n\tpub for_export: bool,\n\tpub for_mask: bool,\n\tpub thumbnail: bool,\n\tpub aligned_strokes: bool,\n\tpub override_paint_order: bool,\n\tpub animation_time_ms: i64,\n\tpub real_time_ms: i64,\n\tpub pointer: [u8; 16],\n}\n\nimpl CacheKey {\n\t#[expect(clippy::too_many_arguments)]\n\tfn new(\n\t\tmax_region_area: u32,\n\t\trender_mode_hash: u64,\n\t\tdevice_scale: f64,\n\t\tzoom: f64,\n\t\trotation: f64,\n\t\thide_artboards: bool,\n\t\tfor_export: bool,\n\t\tfor_mask: bool,\n\t\tthumbnail: bool,\n\t\taligned_strokes: bool,\n\t\toverride_paint_order: bool,\n\t\tanimation_time: f64,\n\t\treal_time: f64,\n\t\tpointer: Option<DVec2>,\n\t) -> Self {\n\t\tlet pointer_bytes = pointer\n\t\t\t.map(|p| {\n\t\t\t\tlet mut bytes = [0u8; 16];\n\t\t\t\tbytes[..8].copy_from_slice(&p.x.to_le_bytes());\n\t\t\t\tbytes[8..].copy_from_slice(&p.y.to_le_bytes());\n\t\t\t\tbytes\n\t\t\t})\n\t\t\t.unwrap_or([0u8; 16]);\n\t\tconst ROTATION_QUANTIZATION_DIGITS: i32 = 5;\n\t\tlet quantization_amount = 10f64.powi(ROTATION_QUANTIZATION_DIGITS);\n\t\tlet quantized_rotation = (rotation * quantization_amount).round() * quantization_amount.recip();\n\t\tSelf {\n\t\t\tmax_region_area,\n\t\t\trender_mode_hash,\n\t\t\tdevice_scale: device_scale.to_bits(),\n\t\t\tzoom: zoom.to_bits(),\n\t\t\trotation: quantized_rotation.to_bits(),\n\t\t\thide_artboards,\n\t\t\tfor_export,\n\t\t\tfor_mask,\n\t\t\tthumbnail,\n\t\t\taligned_strokes,\n\t\t\toverride_paint_order,\n\t\t\tanimation_time_ms: (animation_time * 1000.0).round() as i64,\n\t\t\treal_time_ms: (real_time * 1000.0).round() as i64,\n\t\t\tpointer: pointer_bytes,\n\t\t}\n\t}\n}\n\n#[derive(Default, Debug)]\nstruct TileCacheImpl {\n\tregions: Vec<CachedRegion>,\n\ttimestamp: u64,\n\ttotal_memory: usize,\n\tcache_key: CacheKey,\n\ttexture_cache_resolution: UVec2,\n\t/// Pool of textures of the same size: `texture_cache_resolution`.\n\t/// Reusing textures reduces the wgpu allocation pressure,\n\t/// which is a problem on web since we have to wait for\n\t/// the browser to garbage collect unused textures, eating up memory.\n\ttexture_cache: Vec<Arc<wgpu::Texture>>,\n}\n\n#[derive(Clone, Default, dyn_any::DynAny, Debug)]\npub struct TileCache(Arc<Mutex<TileCacheImpl>>);\n\n#[derive(Debug, Clone)]\npub struct RenderRegion {\n\tpub tiles: Vec<TileCoord>,\n}\n\n#[derive(Debug)]\npub struct CacheQuery {\n\tpub cached_regions: Vec<CachedRegion>,\n\tpub missing_regions: Vec<RenderRegion>,\n}\n\nfn bounds_to_tiles(bounds: &AxisAlignedBbox) -> Vec<TileCoord> {\n\tlet tile_size = TILE_SIZE as f64;\n\tlet tile_start_x = (bounds.start.x / tile_size).floor() as i32;\n\tlet tile_start_y = (bounds.start.y / tile_size).floor() as i32;\n\tlet tile_end_x = (bounds.end.x / tile_size).ceil() as i32;\n\tlet tile_end_y = (bounds.end.y / tile_size).ceil() as i32;\n\n\tlet mut tiles = Vec::new();\n\tfor y in tile_start_y..tile_end_y {\n\t\tfor x in tile_start_x..tile_end_x {\n\t\t\ttiles.push(TileCoord { x, y });\n\t\t}\n\t}\n\ttiles\n}\n\nfn tile_bounds(coord: &TileCoord) -> AxisAlignedBbox {\n\tlet tile_size = TILE_SIZE as f64;\n\tlet start = DVec2::new(coord.x as f64, coord.y as f64) * tile_size;\n\tAxisAlignedBbox {\n\t\tstart,\n\t\tend: start + DVec2::splat(tile_size),\n\t}\n}\n\nfn tiles_bounds(tiles: &[TileCoord]) -> AxisAlignedBbox {\n\tif tiles.is_empty() {\n\t\treturn AxisAlignedBbox::ZERO;\n\t}\n\tlet mut result = tile_bounds(&tiles[0]);\n\tfor tile in &tiles[1..] {\n\t\tresult = result.union(&tile_bounds(tile));\n\t}\n\tresult\n}\n\nimpl TileCacheImpl {\n\tfn query(&mut self, viewport_bounds: &AxisAlignedBbox, cache_key: &CacheKey, max_region_area: u32) -> CacheQuery {\n\t\tif &self.cache_key != cache_key {\n\t\t\tself.invalidate_all();\n\t\t\tself.cache_key = cache_key.clone();\n\t\t}\n\n\t\tlet required_tiles = bounds_to_tiles(viewport_bounds);\n\t\tlet required_tile_set: HashSet<_> = required_tiles.iter().cloned().collect();\n\t\tlet mut cached_regions = Vec::new();\n\t\tlet mut covered_tiles = HashSet::new();\n\n\t\tfor region in &mut self.regions {\n\t\t\tlet region_tiles: HashSet<_> = region.tiles.iter().cloned().collect();\n\t\t\tif region_tiles.iter().any(|t| required_tile_set.contains(t)) {\n\t\t\t\tregion.last_access = self.timestamp;\n\t\t\t\tself.timestamp += 1;\n\t\t\t\tcached_regions.push(region.clone());\n\t\t\t\tcovered_tiles.extend(region_tiles);\n\t\t\t}\n\t\t}\n\n\t\tlet missing_tiles: Vec<_> = required_tiles.into_iter().filter(|t| !covered_tiles.contains(t)).collect();\n\t\tlet missing_regions = group_into_regions(&missing_tiles, max_region_area);\n\t\tCacheQuery { cached_regions, missing_regions }\n\t}\n\n\tfn store_regions(&mut self, new_regions: Vec<CachedRegion>) {\n\t\tfor mut region in new_regions {\n\t\t\tregion.last_access = self.timestamp;\n\t\t\tself.timestamp += 1;\n\t\t\tself.total_memory += region.memory_size;\n\t\t\tself.regions.push(region);\n\t\t}\n\t\tself.evict_until_under_budget();\n\t}\n\n\tfn evict_until_under_budget(&mut self) {\n\t\twhile self.total_memory > MAX_CACHE_MEMORY_BYTES && !self.regions.is_empty() {\n\t\t\tif let Some((oldest_idx, _)) = self.regions.iter().enumerate().min_by_key(|(_, r)| r.last_access) {\n\t\t\t\tlet removed = self.regions.remove(oldest_idx);\n\t\t\t\tremoved.texture.destroy();\n\t\t\t\tself.total_memory = self.total_memory.saturating_sub(removed.memory_size);\n\t\t\t} else {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tfn invalidate_all(&mut self) {\n\t\tfor region in &self.regions {\n\t\t\tregion.texture.destroy();\n\t\t}\n\t\tself.regions.clear();\n\t\tself.total_memory = 0;\n\t}\n\n\tpub fn request_texture(&mut self, size: UVec2, device: &wgpu::Device) -> Arc<wgpu::Texture> {\n\t\tif self.texture_cache_resolution != size {\n\t\t\tself.texture_cache_resolution = size;\n\t\t\tself.texture_cache.clear();\n\t\t}\n\t\tself.texture_cache.truncate(5);\n\t\tfor texture in &self.texture_cache {\n\t\t\tif Arc::strong_count(texture) == 1 {\n\t\t\t\treturn Arc::clone(texture);\n\t\t\t}\n\t\t}\n\t\tlet texture = Arc::new(device.create_texture(&wgpu::TextureDescriptor {\n\t\t\tlabel: Some(\"viewport_output\"),\n\t\t\tsize: wgpu::Extent3d {\n\t\t\t\twidth: size.x,\n\t\t\t\theight: size.y,\n\t\t\t\tdepth_or_array_layers: 1,\n\t\t\t},\n\t\t\tmip_level_count: 1,\n\t\t\tsample_count: 1,\n\t\t\tdimension: wgpu::TextureDimension::D2,\n\t\t\tformat: wgpu::TextureFormat::Rgba8Unorm,\n\t\t\tusage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::TEXTURE_BINDING,\n\t\t\tview_formats: &[],\n\t\t}));\n\t\tself.texture_cache.push(texture.clone());\n\n\t\ttexture\n\t}\n}\n\nimpl TileCache {\n\tpub fn query(&self, viewport_bounds: &AxisAlignedBbox, cache_key: &CacheKey, max_region_area: u32) -> CacheQuery {\n\t\tself.0.lock().unwrap().query(viewport_bounds, cache_key, max_region_area)\n\t}\n\n\tpub fn store_regions(&self, regions: Vec<CachedRegion>) {\n\t\tself.0.lock().unwrap().store_regions(regions);\n\t}\n\n\tpub fn request_texture(&self, size: UVec2, device: &wgpu::Device) -> Arc<wgpu::Texture> {\n\t\tself.0.lock().unwrap().request_texture(size, device)\n\t}\n}\n\nfn group_into_regions(tiles: &[TileCoord], max_region_area: u32) -> Vec<RenderRegion> {\n\tif tiles.is_empty() {\n\t\treturn Vec::new();\n\t}\n\n\tlet tile_set: HashSet<_> = tiles.iter().cloned().collect();\n\tlet mut visited = HashSet::new();\n\tlet mut regions = Vec::new();\n\n\tfor &tile in tiles {\n\t\tif visited.contains(&tile) {\n\t\t\tcontinue;\n\t\t}\n\t\tlet region_tiles = flood_fill(&tile, &tile_set, &mut visited);\n\t\tlet region = RenderRegion { tiles: region_tiles };\n\t\tregions.extend(split_oversized_region(region, max_region_area));\n\t}\n\tregions\n}\n\n/// Recursively subdivides a region until all sub-regions have area <= max_region_area.\n/// Uses axis-aligned splits on the longest dimension.\nfn split_oversized_region(region: RenderRegion, max_region_area: u32) -> Vec<RenderRegion> {\n\tlet pixel_size = tiles_bounds(&region.tiles).size();\n\tlet area = (pixel_size.x * pixel_size.y) as u32;\n\n\t// Base case: region is small enough\n\tif area <= max_region_area {\n\t\treturn vec![region];\n\t}\n\n\t// Determine split axis: choose the longer dimension\n\tlet split_horizontally = pixel_size.x > pixel_size.y;\n\n\t// Split tiles into two groups based on midpoint\n\tlet mut group1 = Vec::new();\n\tlet mut group2 = Vec::new();\n\n\tif split_horizontally {\n\t\tlet min_x = region.tiles.iter().map(|t| t.x).min().unwrap();\n\t\tlet max_x = region.tiles.iter().map(|t| t.x).max().unwrap();\n\t\tlet mid_x = min_x + (max_x - min_x) / 2;\n\n\t\tfor &tile in &region.tiles {\n\t\t\tif tile.x <= mid_x {\n\t\t\t\tgroup1.push(tile);\n\t\t\t} else {\n\t\t\t\tgroup2.push(tile);\n\t\t\t}\n\t\t}\n\t} else {\n\t\tlet min_y = region.tiles.iter().map(|t| t.y).min().unwrap();\n\t\tlet max_y = region.tiles.iter().map(|t| t.y).max().unwrap();\n\t\tlet mid_y = min_y + (max_y - min_y) / 2;\n\n\t\tfor &tile in &region.tiles {\n\t\t\tif tile.y <= mid_y {\n\t\t\t\tgroup1.push(tile);\n\t\t\t} else {\n\t\t\t\tgroup2.push(tile);\n\t\t\t}\n\t\t}\n\t}\n\n\tif group1.is_empty() || group2.is_empty() {\n\t\tlog::error!(\"Failed to split oversized region.\");\n\t\treturn vec![region];\n\t}\n\n\tlet mut result = Vec::new();\n\tfor tiles in [group1, group2] {\n\t\tif !tiles.is_empty() {\n\t\t\tlet sub_region = RenderRegion { tiles };\n\t\t\tresult.extend(split_oversized_region(sub_region, max_region_area));\n\t\t}\n\t}\n\n\tresult\n}\n\nfn flood_fill(start: &TileCoord, tile_set: &HashSet<TileCoord>, visited: &mut HashSet<TileCoord>) -> Vec<TileCoord> {\n\tlet mut result = Vec::new();\n\tlet mut stack = vec![*start];\n\n\twhile let Some(current) = stack.pop() {\n\t\tif visited.contains(&current) || !tile_set.contains(&current) {\n\t\t\tcontinue;\n\t\t}\n\t\tvisited.insert(current);\n\t\tresult.push(current);\n\n\t\tfor neighbor in [\n\t\t\tTileCoord { x: current.x - 1, y: current.y },\n\t\t\tTileCoord { x: current.x + 1, y: current.y },\n\t\t\tTileCoord { x: current.x, y: current.y - 1 },\n\t\t\tTileCoord { x: current.x, y: current.y + 1 },\n\t\t] {\n\t\t\tif tile_set.contains(&neighbor) && !visited.contains(&neighbor) {\n\t\t\t\tstack.push(neighbor);\n\t\t\t}\n\t\t}\n\t}\n\tresult\n}\n\n#[node_macro::node(category(\"\"))]\npub async fn render_output_cache<'a: 'n>(\n\tctx: impl Ctx + ExtractAll + CloneVarArgs + ExtractRealTime + ExtractAnimationTime + ExtractPointerPosition + Sync,\n\teditor_api: &'a WasmEditorApi,\n\tdata: impl Node<Context<'static>, Output = RenderOutput> + Send + Sync,\n\t#[data] tile_cache: TileCache,\n) -> RenderOutput {\n\tlet footprint = ctx.footprint();\n\tlet Some(render_params) = ctx.vararg(0).ok().and_then(|v| v.downcast_ref::<RenderParams>()) else {\n\t\tlog::warn!(\"render_output_cache: missing or invalid render params, falling back to direct render\");\n\t\tlet context = OwnedContextImpl::empty().with_footprint(*footprint);\n\t\treturn data.eval(context.into_context()).await;\n\t};\n\n\t// Fall back to direct render for non-Vello or zero-size viewports\n\tlet physical_resolution = footprint.resolution;\n\tif !matches!(render_params.render_output_type, RenderOutputTypeRequest::Vello) || physical_resolution.x == 0 || physical_resolution.y == 0 {\n\t\tlet context = OwnedContextImpl::empty().with_footprint(*footprint).with_vararg(Box::new(render_params.clone()));\n\t\treturn data.eval(context.into_context()).await;\n\t}\n\n\tlet device_scale = render_params.scale;\n\tlet zoom = footprint.decompose_scale().x;\n\tlet rotation = footprint.decompose_rotation();\n\n\tlet viewport_origin_offset = footprint.transform.translation;\n\tlet device_origin_offset = viewport_origin_offset * device_scale;\n\tlet viewport_bounds_device = AxisAlignedBbox {\n\t\tstart: -device_origin_offset,\n\t\tend: footprint.resolution.as_dvec2() - device_origin_offset,\n\t};\n\n\tlet max_region_area = editor_api.editor_preferences.max_render_region_area();\n\n\tlet cache_key = CacheKey::new(\n\t\tmax_region_area,\n\t\trender_params.render_mode as u64,\n\t\tdevice_scale,\n\t\tzoom,\n\t\trotation,\n\t\trender_params.hide_artboards,\n\t\trender_params.for_export,\n\t\trender_params.for_mask,\n\t\trender_params.thumbnail,\n\t\trender_params.aligned_strokes,\n\t\trender_params.override_paint_order,\n\t\tctx.try_animation_time().unwrap_or(0.0),\n\t\tctx.try_real_time().unwrap_or(0.0),\n\t\tctx.try_pointer_position(),\n\t);\n\n\tlet cache_query = tile_cache.query(&viewport_bounds_device, &cache_key, max_region_area);\n\n\tlet mut new_regions = Vec::new();\n\tfor missing_region in &cache_query.missing_regions {\n\t\tif missing_region.tiles.is_empty() {\n\t\t\tcontinue;\n\t\t}\n\t\tlet region = render_missing_region(\n\t\t\tmissing_region,\n\t\t\t|ctx| data.eval(ctx),\n\t\t\tctx.clone(),\n\t\t\trender_params,\n\t\t\t&footprint.transform,\n\t\t\t&viewport_origin_offset,\n\t\t\tdevice_scale,\n\t\t)\n\t\t.await;\n\t\tnew_regions.push(region);\n\t}\n\n\ttile_cache.store_regions(new_regions.clone());\n\n\tlet all_regions: Vec<_> = cache_query.cached_regions.into_iter().chain(new_regions.into_iter()).collect();\n\n\t// If no regions, fall back to direct render\n\tif all_regions.is_empty() {\n\t\tlet context = OwnedContextImpl::empty().with_footprint(*footprint).with_vararg(Box::new(render_params.clone()));\n\t\treturn data.eval(context.into_context()).await;\n\t}\n\n\tlet exec = editor_api.application_io.as_ref().unwrap().gpu_executor().unwrap();\n\n\tlet device = &exec.context.device;\n\tlet output_texture = tile_cache.request_texture(physical_resolution, device);\n\n\tlet combined_metadata = composite_cached_regions(&all_regions, output_texture.as_ref(), &device_origin_offset, &footprint.transform, exec);\n\n\tRenderOutput {\n\t\tdata: RenderOutputType::Texture(ImageTexture { texture: output_texture }),\n\t\tmetadata: combined_metadata,\n\t}\n}\n\nasync fn render_missing_region<F, Fut>(\n\tregion: &RenderRegion,\n\trender_fn: F,\n\tctx: impl Ctx + ExtractAll + CloneVarArgs,\n\trender_params: &RenderParams,\n\tviewport_transform: &DAffine2,\n\tviewport_origin_offset: &DVec2,\n\tdevice_scale: f64,\n) -> CachedRegion\nwhere\n\tF: Fn(Context<'static>) -> Fut,\n\tFut: std::future::Future<Output = RenderOutput>,\n{\n\tlet min_tile = region.tiles.iter().fold(IVec2::new(i32::MAX, i32::MAX), |acc, t| acc.min(IVec2::new(t.x, t.y)));\n\tlet max_tile = region.tiles.iter().fold(IVec2::new(i32::MIN, i32::MIN), |acc, t| acc.max(IVec2::new(t.x, t.y)));\n\n\tlet tile_count = (max_tile - min_tile) + IVec2::ONE;\n\tlet region_pixel_size = (tile_count * TILE_SIZE as i32).as_uvec2();\n\n\tlet tile_global_offset = min_tile.as_dvec2() * (TILE_SIZE as f64 / device_scale) + *viewport_origin_offset;\n\tlet region_transform = DAffine2::from_translation(-tile_global_offset) * *viewport_transform;\n\tlet region_footprint = Footprint {\n\t\ttransform: region_transform,\n\t\tresolution: region_pixel_size,\n\t\tquality: RenderQuality::Full,\n\t};\n\n\tlet region_params = render_params.clone();\n\tlet region_ctx = OwnedContextImpl::from(ctx).with_footprint(region_footprint).with_vararg(Box::new(region_params)).into_context();\n\tlet mut result = render_fn(region_ctx).await;\n\n\tlet RenderOutputType::Texture(rendered_texture) = result.data else {\n\t\tunreachable!(\"render_missing_region: expected texture output from Vello render\");\n\t};\n\n\tlet pixel_to_document = region_transform.inverse();\n\tresult.metadata.apply_transform(pixel_to_document);\n\n\tlet memory_size = (region_pixel_size.x * region_pixel_size.y) as usize * BYTES_PER_PIXEL;\n\n\tCachedRegion {\n\t\ttexture: rendered_texture.texture.as_ref().clone(),\n\t\ttexture_size: region_pixel_size,\n\t\ttiles: region.tiles.clone(),\n\t\tmetadata: result.metadata,\n\t\tlast_access: 0,\n\t\tmemory_size,\n\t}\n}\n\nfn composite_cached_regions(\n\tregions: &[CachedRegion],\n\toutput_texture: &wgpu::Texture,\n\tdevice_origin_offset: &DVec2,\n\tviewport_transform: &DAffine2,\n\texec: &wgpu_executor::WgpuExecutor,\n) -> rendering::RenderMetadata {\n\tlet device = &exec.context.device;\n\tlet queue = &exec.context.queue;\n\tlet output_resolution = UVec2::new(output_texture.width(), output_texture.height());\n\n\tlet mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some(\"composite\") });\n\tlet mut combined_metadata = rendering::RenderMetadata::default();\n\n\tfor region in regions {\n\t\tlet min_tile = region.tiles.iter().fold(IVec2::new(i32::MAX, i32::MAX), |acc, t| acc.min(IVec2::new(t.x, t.y)));\n\n\t\t// Convert global tile position to physical pixel offset in the output texture\n\t\tlet offset_pixels = min_tile * TILE_SIZE as i32 + device_origin_offset.round().as_ivec2();\n\n\t\tlet (src_x, dst_x, width) = if offset_pixels.x >= 0 {\n\t\t\t(0, offset_pixels.x as u32, region.texture_size.x.min(output_resolution.x.saturating_sub(offset_pixels.x as u32)))\n\t\t} else {\n\t\t\tlet skip = (-offset_pixels.x) as u32;\n\t\t\t(skip, 0, region.texture_size.x.saturating_sub(skip).min(output_resolution.x))\n\t\t};\n\n\t\tlet (src_y, dst_y, height) = if offset_pixels.y >= 0 {\n\t\t\t(0, offset_pixels.y as u32, region.texture_size.y.min(output_resolution.y.saturating_sub(offset_pixels.y as u32)))\n\t\t} else {\n\t\t\tlet skip = (-offset_pixels.y) as u32;\n\t\t\t(skip, 0, region.texture_size.y.saturating_sub(skip).min(output_resolution.y))\n\t\t};\n\n\t\tif width > 0 && height > 0 {\n\t\t\tencoder.copy_texture_to_texture(\n\t\t\t\twgpu::TexelCopyTextureInfo {\n\t\t\t\t\ttexture: &region.texture,\n\t\t\t\t\tmip_level: 0,\n\t\t\t\t\torigin: wgpu::Origin3d { x: src_x, y: src_y, z: 0 },\n\t\t\t\t\taspect: wgpu::TextureAspect::All,\n\t\t\t\t},\n\t\t\t\twgpu::TexelCopyTextureInfo {\n\t\t\t\t\ttexture: &output_texture,\n\t\t\t\t\tmip_level: 0,\n\t\t\t\t\torigin: wgpu::Origin3d { x: dst_x, y: dst_y, z: 0 },\n\t\t\t\t\taspect: wgpu::TextureAspect::All,\n\t\t\t\t},\n\t\t\t\twgpu::Extent3d {\n\t\t\t\t\twidth,\n\t\t\t\t\theight,\n\t\t\t\t\tdepth_or_array_layers: 1,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\tlet mut region_metadata = region.metadata.clone();\n\t\tregion_metadata.apply_transform(*viewport_transform);\n\t\tcombined_metadata.merge(&region_metadata);\n\t}\n\n\tqueue.submit([encoder.finish()]);\n\tcombined_metadata\n}\n"
  },
  {
    "path": "node-graph/nodes/gstd/src/render_node.rs",
    "content": "use core_types::table::Table;\nuse core_types::transform::{Footprint, Transform};\nuse core_types::{CloneVarArgs, ExtractAll, ExtractVarArgs};\nuse core_types::{Color, Context, Ctx, ExtractFootprint, OwnedContextImpl, WasmNotSend};\nuse graph_craft::document::value::RenderOutput;\npub use graph_craft::document::value::RenderOutputType;\npub use graph_craft::wasm_application_io::*;\nuse graphene_application_io::{ApplicationIo, ExportFormat, ImageTexture, RenderConfig};\nuse graphic_types::raster_types::Image;\nuse graphic_types::raster_types::{CPU, Raster};\nuse graphic_types::{Artboard, Graphic, Vector};\nuse rendering::{Render, RenderOutputType as RenderOutputTypeRequest, RenderParams, RenderSvgSegmentList, SvgRender, format_transform_matrix};\nuse rendering::{RenderMetadata, SvgSegment};\nuse std::collections::HashMap;\nuse std::sync::Arc;\nuse vector_types::GradientStops;\nuse wgpu_executor::RenderContext;\n\n// Re-export render_output_cache from render_cache module\npub use crate::render_cache::render_output_cache;\n\n/// List of (canvas id, image data) pairs for embedding images as canvases in the final SVG string.\ntype ImageData = HashMap<Image<Color>, u64>;\n\n#[derive(Clone, dyn_any::DynAny)]\npub enum RenderIntermediateType {\n\tVello(Arc<(vello::Scene, RenderContext)>),\n\tSvg(Arc<(String, ImageData, String)>),\n}\n#[derive(Clone, dyn_any::DynAny)]\npub struct RenderIntermediate {\n\tpub(crate) ty: RenderIntermediateType,\n\tpub(crate) metadata: RenderMetadata,\n\tpub(crate) contains_artboard: bool,\n}\n\n#[node_macro::node(category(\"\"))]\nasync fn render_intermediate<'a: 'n, T: 'static + Render + WasmNotSend + Send + Sync>(\n\tctx: impl Ctx + ExtractVarArgs + ExtractAll + CloneVarArgs,\n\t#[implementations(\n\t\tContext -> Table<Artboard>,\n\t\tContext -> Table<Graphic>,\n\t\tContext -> Table<Vector>,\n\t\tContext -> Table<Raster<CPU>>,\n\t\tContext -> Table<Color>,\n\t\tContext -> Table<GradientStops>,\n\t)]\n\tdata: impl Node<Context<'static>, Output = T>,\n) -> RenderIntermediate {\n\tlet render_params = ctx\n\t\t.vararg(0)\n\t\t.expect(\"Did not find var args\")\n\t\t.downcast_ref::<RenderParams>()\n\t\t.expect(\"Downcasting render params yielded invalid type\");\n\n\tlet ctx = OwnedContextImpl::from(ctx.clone()).into_context();\n\tlet data = data.eval(ctx).await;\n\n\tlet footprint = Footprint::default();\n\tlet mut metadata = RenderMetadata::default();\n\tdata.collect_metadata(&mut metadata, footprint, None);\n\tlet contains_artboard = data.contains_artboard();\n\n\tmatch &render_params.render_output_type {\n\t\tRenderOutputTypeRequest::Vello => {\n\t\t\tlet mut scene = vello::Scene::new();\n\n\t\t\tlet mut context = wgpu_executor::RenderContext::default();\n\t\t\tdata.render_to_vello(&mut scene, Default::default(), &mut context, render_params);\n\n\t\t\tRenderIntermediate {\n\t\t\t\tty: RenderIntermediateType::Vello(Arc::new((scene, context))),\n\t\t\t\tmetadata,\n\t\t\t\tcontains_artboard,\n\t\t\t}\n\t\t}\n\t\tRenderOutputTypeRequest::Svg => {\n\t\t\tlet mut render = SvgRender::new();\n\n\t\t\tdata.render_svg(&mut render, render_params);\n\n\t\t\tRenderIntermediate {\n\t\t\t\tty: RenderIntermediateType::Svg(Arc::new((render.svg.to_svg_string(), render.image_data, render.svg_defs.clone()))),\n\t\t\t\tmetadata,\n\t\t\t\tcontains_artboard,\n\t\t\t}\n\t\t}\n\t}\n}\n\n#[node_macro::node(category(\"\"))]\nasync fn create_context<'a: 'n>(\n\t// Context injections are defined in the wrap_network_in_scope function\n\trender_config: RenderConfig,\n\tdata: impl Node<Context<'static>, Output = RenderOutput>,\n) -> RenderOutput {\n\tlet footprint = render_config.viewport;\n\n\tlet render_output_type = match render_config.export_format {\n\t\tExportFormat::Svg => RenderOutputTypeRequest::Svg,\n\t\tExportFormat::Raster => RenderOutputTypeRequest::Vello,\n\t};\n\n\tlet render_params = RenderParams {\n\t\trender_mode: render_config.render_mode,\n\t\thide_artboards: render_config.hide_artboards,\n\t\tfor_export: render_config.for_export,\n\t\trender_output_type,\n\t\tfootprint: Footprint::default(),\n\t\tscale: render_config.scale,\n\t\tviewport_zoom: footprint.decompose_scale().x,\n\t\t..Default::default()\n\t};\n\n\tlet ctx = OwnedContextImpl::default()\n\t\t.with_footprint(footprint)\n\t\t.with_real_time(render_config.time.time)\n\t\t.with_animation_time(render_config.time.animation_time.as_secs_f64())\n\t\t.with_pointer_position(render_config.pointer)\n\t\t.with_vararg(Box::new(render_params))\n\t\t.into_context();\n\n\tdata.eval(ctx).await\n}\n\n#[node_macro::node(category(\"\"))]\nasync fn render<'a: 'n>(ctx: impl Ctx + ExtractFootprint + ExtractVarArgs, editor_api: &'a WasmEditorApi, data: RenderIntermediate) -> RenderOutput {\n\tlet footprint = ctx.footprint();\n\tlet render_params = ctx\n\t\t.vararg(0)\n\t\t.expect(\"Did not find var args\")\n\t\t.downcast_ref::<RenderParams>()\n\t\t.expect(\"Downcasting render params yielded invalid type\");\n\tlet mut render_params = render_params.clone();\n\trender_params.footprint = *footprint;\n\tlet render_params = &render_params;\n\n\tlet scale = render_params.scale;\n\tlet physical_resolution = render_params.footprint.resolution;\n\tlet logical_resolution = render_params.footprint.resolution.as_dvec2() / scale;\n\n\tlet RenderIntermediate { ty, mut metadata, contains_artboard } = data;\n\tmetadata.apply_transform(footprint.transform);\n\n\tlet data = match (render_params.render_output_type, &ty) {\n\t\t(RenderOutputTypeRequest::Svg, RenderIntermediateType::Svg(svg_data)) => {\n\t\t\tlet mut rendering = SvgRender::new();\n\t\t\tif !contains_artboard && !render_params.hide_artboards {\n\t\t\t\trendering.leaf_tag(\"rect\", |attributes| {\n\t\t\t\t\tattributes.push(\"x\", \"0\");\n\t\t\t\t\tattributes.push(\"y\", \"0\");\n\t\t\t\t\tattributes.push(\"width\", logical_resolution.x.to_string());\n\t\t\t\t\tattributes.push(\"height\", logical_resolution.y.to_string());\n\t\t\t\t\tlet matrix = format_transform_matrix(footprint.transform.inverse());\n\t\t\t\t\tif !matrix.is_empty() {\n\t\t\t\t\t\tattributes.push(\"transform\", matrix);\n\t\t\t\t\t}\n\t\t\t\t\tattributes.push(\"fill\", \"white\");\n\t\t\t\t});\n\t\t\t}\n\t\t\trendering.svg.push(SvgSegment::from(svg_data.0.clone()));\n\t\t\trendering.image_data = svg_data.1.clone();\n\t\t\trendering.svg_defs = svg_data.2.clone();\n\n\t\t\trendering.wrap_with_transform(footprint.transform, Some(logical_resolution));\n\t\t\tRenderOutputType::Svg {\n\t\t\t\tsvg: rendering.svg.to_svg_string(),\n\t\t\t\timage_data: rendering.image_data.into_iter().map(|(image, id)| (id, image)).collect(),\n\t\t\t}\n\t\t}\n\t\t(RenderOutputTypeRequest::Vello, RenderIntermediateType::Vello(vello_data)) => {\n\t\t\tlet Some(exec) = editor_api.application_io.as_ref().unwrap().gpu_executor() else {\n\t\t\t\tunreachable!(\"Attempted to render with Vello when no GPU executor is available\");\n\t\t\t};\n\t\t\tlet (child, context) = Arc::as_ref(vello_data);\n\n\t\t\tlet scale_transform = glam::DAffine2::from_scale(glam::DVec2::splat(scale));\n\t\t\tlet footprint_transform = scale_transform * footprint.transform;\n\t\t\tlet footprint_transform_vello = vello::kurbo::Affine::new(footprint_transform.to_cols_array());\n\n\t\t\tlet mut scene = vello::Scene::new();\n\t\t\tscene.append(child, Some(footprint_transform_vello));\n\n\t\t\t// We now replace all transforms which are supposed to be infinite with a transform which covers the entire viewport\n\t\t\t// See <https://xi.zulipchat.com/#narrow/channel/197075-vello/topic/Full.20screen.20color.2Fgradients/near/538435044> for more detail\n\t\t\tlet scaled_infinite_transform = vello::kurbo::Affine::scale_non_uniform(physical_resolution.x as f64, physical_resolution.y as f64);\n\t\t\tfor transform in scene.encoding_mut().transforms.iter_mut() {\n\t\t\t\tif transform.matrix[0] == f32::INFINITY {\n\t\t\t\t\t*transform = vello_encoding::Transform::from_kurbo(&scaled_infinite_transform);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet background = if !render_params.for_export && !contains_artboard && !render_params.hide_artboards {\n\t\t\t\tSome(Color::WHITE)\n\t\t\t} else {\n\t\t\t\tNone\n\t\t\t};\n\n\t\t\tlet texture = Arc::new(\n\t\t\t\texec.render_vello_scene_to_texture(&scene, physical_resolution, context, background)\n\t\t\t\t\t.await\n\t\t\t\t\t.expect(\"Failed to render Vello scene\"),\n\t\t\t);\n\n\t\t\tRenderOutputType::Texture(ImageTexture { texture })\n\t\t}\n\t\t_ => unreachable!(\"Render node did not receive its requested data type\"),\n\t};\n\tRenderOutput { data, metadata }\n}\n"
  },
  {
    "path": "node-graph/nodes/gstd/src/text.rs",
    "content": "use core_types::{Ctx, table::Table};\nuse graph_craft::wasm_application_io::WasmEditorApi;\nuse graphic_types::Vector;\npub use text_nodes::*;\n\n/// Draws a text string as vector geometry with a choice of font and styling.\n#[node_macro::node(category(\"Text\"))]\nfn text<'i: 'n>(\n\t_: impl Ctx,\n\t/// The Graphite editor's source for global font resources.\n\t#[scope(\"editor-api\")]\n\teditor_resources: &'i WasmEditorApi,\n\t/// The text content to be drawn.\n\t#[widget(ParsedWidgetOverride::Custom = \"text_area\")]\n\t#[default(\"Lorem ipsum\")]\n\ttext: String,\n\t/// The typeface used to draw the text.\n\t#[widget(ParsedWidgetOverride::Custom = \"text_font\")]\n\tfont: Font,\n\t/// The font size used to draw the text.\n\t#[unit(\" px\")]\n\t#[default(24.)]\n\t#[hard_min(1.)]\n\tsize: f64,\n\t/// The line height ratio, relative to the font size. Each line is drawn lower than its previous line by the distance of *Size* × *Line Height*.\n\t///\n\t/// 0 means all lines overlap. 1 means all lines are spaced by just the font size. 1.2 is a common default for readable text. 2 means double-spaced text.\n\t#[unit(\"x\")]\n\t#[hard_min(0.)]\n\t#[step(0.1)]\n\t#[default(1.2)]\n\tline_height: f64,\n\t/// Additional spacing, in pixels, added between each character.\n\t#[unit(\" px\")]\n\t#[step(0.1)]\n\tcharacter_spacing: f64,\n\t/// Whether the *Max Width* property is enabled so that lines can wrap to fit its specified block width.\n\t#[widget(ParsedWidgetOverride::Hidden)]\n\thas_max_width: bool,\n\t/// The maximum width that the text block can occupy before wrapping to a new line. Otherwise, lines do not wrap.\n\t#[unit(\" px\")]\n\t#[hard_min(1.)]\n\t#[widget(ParsedWidgetOverride::Custom = \"optional_f64\")]\n\tmax_width: f64,\n\t/// Whether the *Max Height* property is enabled so that lines beyond it are not drawn.\n\t#[widget(ParsedWidgetOverride::Hidden)]\n\thas_max_height: bool,\n\t/// The maximum height that the text block can occupy. Excess lines are not drawn.\n\t#[unit(\" px\")]\n\t#[hard_min(1.)]\n\t#[widget(ParsedWidgetOverride::Custom = \"optional_f64\")]\n\tmax_height: f64,\n\t/// The angle of faux italic slant applied to each glyph.\n\t#[unit(\"°\")]\n\t#[hard_min(-85.)]\n\t#[hard_max(85.)]\n\ttilt: f64,\n\t/// The horizontal alignment of each line of text within its surrounding box.\n\t/// To have an effect on a single line of text, *Max Width* must be set.\n\t#[widget(ParsedWidgetOverride::Custom = \"text_align\")]\n\talign: TextAlign,\n\t/// Whether to split every letterform into its own vector path element. Otherwise, a single compound path is produced.\n\tseparate_glyph_elements: bool,\n) -> Table<Vector> {\n\tlet typesetting = TypesettingConfig {\n\t\tfont_size: size,\n\t\tline_height_ratio: line_height,\n\t\tcharacter_spacing,\n\t\tmax_width: has_max_width.then_some(max_width),\n\t\tmax_height: has_max_height.then_some(max_height),\n\t\ttilt,\n\t\talign,\n\t};\n\n\tto_path(&text, &font, &editor_resources.font_cache, typesetting, separate_glyph_elements)\n}\n"
  },
  {
    "path": "node-graph/nodes/gstd/src/wasm_application_io.rs",
    "content": "#[cfg(target_family = \"wasm\")]\nuse base64::Engine;\n#[cfg(target_family = \"wasm\")]\nuse core_types::WasmNotSend;\n#[cfg(target_family = \"wasm\")]\nuse core_types::math::bbox::Bbox;\nuse core_types::table::Table;\n#[cfg(target_family = \"wasm\")]\nuse core_types::transform::Footprint;\nuse core_types::{Color, Ctx};\npub use graph_craft::document::value::RenderOutputType;\npub use graph_craft::wasm_application_io::*;\nuse graphene_application_io::ApplicationIo;\n#[cfg(target_family = \"wasm\")]\nuse graphic_types::Graphic;\n#[cfg(target_family = \"wasm\")]\nuse graphic_types::Vector;\nuse graphic_types::raster_types::Image;\nuse graphic_types::raster_types::{CPU, Raster};\n#[cfg(target_family = \"wasm\")]\nuse graphic_types::vector_types::gradient::GradientStops;\n#[cfg(target_family = \"wasm\")]\nuse rendering::{Render, RenderParams, RenderSvgSegmentList, SvgRender};\nuse std::sync::Arc;\n#[cfg(target_family = \"wasm\")]\nuse wasm_bindgen::JsCast;\n#[cfg(target_family = \"wasm\")]\nuse web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};\n\n/// Allocates GPU memory and a rendering context for vector-to-raster conversion.\n#[cfg(feature = \"wgpu\")]\n#[node_macro::node(category(\"\"))]\nasync fn create_surface<'a: 'n>(_: impl Ctx, editor: &'a WasmEditorApi) -> Arc<WasmSurfaceHandle> {\n\tArc::new(editor.application_io.as_ref().unwrap().create_window())\n}\n\nfn parse_headers(headers: &str) -> reqwest::header::HeaderMap {\n\tuse reqwest::header::{HeaderMap, HeaderName, HeaderValue};\n\n\tlet mut header_map = HeaderMap::new();\n\tfor line in headers.lines() {\n\t\tif let Some((key, value)) = line.split_once(':') {\n\t\t\tlet Ok(header_name) = HeaderName::from_bytes(key.trim().as_bytes()) else { continue };\n\t\t\tlet Ok(header_value) = HeaderValue::from_str(value.trim()) else { continue };\n\t\t\theader_map.insert(header_name, header_value);\n\t\t}\n\t}\n\theader_map\n}\n\n/// Sends an HTTP GET request to a specified URL and optionally waits for the response (unless discarded) which is output as a string.\n#[node_macro::node(category(\"Web Request\"))]\nasync fn get_request(\n\t_: impl Ctx,\n\t_primary: (),\n\t/// The web address to send the GET request to.\n\t#[name(\"URL\")]\n\turl: String,\n\t/// Makes the request run in the background without waiting on a response. This is useful for triggering webhooks without blocking the continued execution of the graph.\n\tdiscard_result: bool,\n\t#[widget(ParsedWidgetOverride::Custom = \"text_area\")] headers: String,\n) -> String {\n\tlet header_map = parse_headers(&headers);\n\tlet request = reqwest::Client::new().get(url).headers(header_map);\n\n\tif discard_result {\n\t\t#[cfg(target_family = \"wasm\")]\n\t\twasm_bindgen_futures::spawn_local(async move {\n\t\t\tlet _ = request.send().await;\n\t\t});\n\t\t#[cfg(all(not(target_family = \"wasm\"), feature = \"tokio\"))]\n\t\ttokio::spawn(async move {\n\t\t\tlet _ = request.send().await;\n\t\t});\n\t\treturn String::new();\n\t}\n\n\tlet Ok(response) = request.send().await else {\n\t\treturn String::new();\n\t};\n\tresponse.text().await.ok().unwrap_or_default()\n}\n\n/// Sends an HTTP POST request to a specified URL with the provided binary data and optionally waits for the response (unless discarded) which is output as a string.\n#[node_macro::node(category(\"Web Request\"))]\nasync fn post_request(\n\t_: impl Ctx,\n\t_primary: (),\n\t/// The web address to send the POST request to.\n\t#[name(\"URL\")]\n\turl: String,\n\t/// The binary data to include in the body of the POST request.\n\tbody: Vec<u8>,\n\t/// Makes the request run in the background without waiting on a response. This is useful for triggering webhooks without blocking the continued execution of the graph.\n\tdiscard_result: bool,\n\t#[widget(ParsedWidgetOverride::Custom = \"text_area\")] headers: String,\n) -> String {\n\tlet mut header_map = parse_headers(&headers);\n\theader_map.insert(\"Content-Type\", \"application/octet-stream\".parse().unwrap());\n\tlet request = reqwest::Client::new().post(url).body(body).headers(header_map);\n\n\tif discard_result {\n\t\t#[cfg(target_family = \"wasm\")]\n\t\twasm_bindgen_futures::spawn_local(async move {\n\t\t\tlet _ = request.send().await;\n\t\t});\n\t\t#[cfg(all(not(target_family = \"wasm\"), feature = \"tokio\"))]\n\t\ttokio::spawn(async move {\n\t\t\tlet _ = request.send().await;\n\t\t});\n\t\treturn String::new();\n\t}\n\n\tlet Ok(response) = request.send().await else {\n\t\treturn String::new();\n\t};\n\tresponse.text().await.ok().unwrap_or_default()\n}\n\n/// Converts a text string to raw binary data. Useful for transmission over HTTP or writing to files.\n#[node_macro::node(category(\"Web Request\"), name(\"String to Bytes\"))]\nfn string_to_bytes(_: impl Ctx, string: String) -> Vec<u8> {\n\tstring.into_bytes()\n}\n\n/// Converts extracted raw RGBA pixel data from an input image. Each pixel becomes 4 sequential bytes. Useful for transmission over HTTP or writing to files.\n#[node_macro::node(category(\"Web Request\"), name(\"Image to Bytes\"))]\nfn image_to_bytes(_: impl Ctx, image: Table<Raster<CPU>>) -> Vec<u8> {\n\tlet Some(image) = image.iter().next() else { return vec![] };\n\timage.element.data.iter().flat_map(|color| color.to_rgb8_srgb().into_iter()).collect::<Vec<u8>>()\n}\n\n/// Loads binary from URLs and local asset paths. Returns a transparent placeholder if the resource fails to load, allowing rendering to continue.\n#[node_macro::node(category(\"Web Request\"))]\nasync fn load_resource<'a: 'n>(_: impl Ctx, _primary: (), #[scope(\"editor-api\")] editor_resources: &'a WasmEditorApi, #[name(\"URL\")] url: String) -> Arc<[u8]> {\n\tlet Some(api) = editor_resources.application_io.as_ref() else {\n\t\treturn Arc::from(include_bytes!(\"../../../graph-craft/src/null.png\").to_vec());\n\t};\n\tlet Ok(data) = api.load_resource(url) else {\n\t\treturn Arc::from(include_bytes!(\"../../../graph-craft/src/null.png\").to_vec());\n\t};\n\tlet Ok(data) = data.await else {\n\t\treturn Arc::from(include_bytes!(\"../../../graph-craft/src/null.png\").to_vec());\n\t};\n\n\tdata\n}\n\n/// Converts raw binary data to a raster image.\n///\n/// Works with standard image format (PNG, JPEG, WebP, etc.). Automatically converts the color space to linear sRGB for accurate compositing.\n#[node_macro::node(category(\"Web Request\"))]\nfn decode_image(_: impl Ctx, data: Arc<[u8]>) -> Table<Raster<CPU>> {\n\tlet Some(image) = image::load_from_memory(data.as_ref()).ok() else {\n\t\treturn Table::new();\n\t};\n\tlet image = image.to_rgba32f();\n\tlet image = Image {\n\t\tdata: image\n\t\t\t.chunks(4)\n\t\t\t.map(|pixel| Color::from_unassociated_alpha(pixel[0], pixel[1], pixel[2], pixel[3]).to_linear_srgb())\n\t\t\t.collect(),\n\t\twidth: image.width(),\n\t\theight: image.height(),\n\t\t..Default::default()\n\t};\n\n\tTable::new_from_element(Raster::new_cpu(image))\n}\n\n/// Renders a view of the input graphic within an area defined by the *Footprint*.\n#[cfg(target_family = \"wasm\")]\n#[node_macro::node(category(\"\"))]\nasync fn rasterize<T: WasmNotSend + 'n>(\n\t_: impl Ctx,\n\t#[implementations(\n\t\tTable<Vector>,\n\t\tTable<Raster<CPU>>,\n\t\tTable<Graphic>,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t)]\n\tmut data: Table<T>,\n\tfootprint: Footprint,\n\tsurface_handle: Arc<graphene_application_io::SurfaceHandle<HtmlCanvasElement>>,\n) -> Table<Raster<CPU>>\nwhere\n\tTable<T>: Render,\n{\n\tuse core_types::table::TableRow;\n\n\tif footprint.transform.matrix2.determinant() == 0. {\n\t\tlog::trace!(\"Invalid footprint received for rasterization\");\n\t\treturn Table::new();\n\t}\n\n\tlet mut render = SvgRender::new();\n\tlet aabb = Bbox::from_transform(footprint.transform).to_axis_aligned_bbox();\n\tlet size = aabb.size();\n\tlet resolution = footprint.resolution;\n\tlet render_params = RenderParams {\n\t\tfootprint,\n\t\tfor_export: true,\n\t\t..Default::default()\n\t};\n\n\tfor row in data.iter_mut() {\n\t\t*row.transform = glam::DAffine2::from_translation(-aabb.start) * *row.transform;\n\t}\n\tdata.render_svg(&mut render, &render_params);\n\trender.format_svg(glam::DVec2::ZERO, size);\n\tlet svg_string = render.svg.to_svg_string();\n\n\tlet canvas = &surface_handle.surface;\n\tcanvas.set_width(resolution.x);\n\tcanvas.set_height(resolution.y);\n\n\tlet context = canvas.get_context(\"2d\").unwrap().unwrap().dyn_into::<CanvasRenderingContext2d>().unwrap();\n\n\tlet preamble = \"data:image/svg+xml;base64,\";\n\tlet mut base64_string = String::with_capacity(preamble.len() + svg_string.len() * 4);\n\tbase64_string.push_str(preamble);\n\tbase64::engine::general_purpose::STANDARD.encode_string(svg_string, &mut base64_string);\n\n\tlet image_data = web_sys::HtmlImageElement::new().unwrap();\n\timage_data.set_src(base64_string.as_str());\n\twasm_bindgen_futures::JsFuture::from(image_data.decode()).await.unwrap();\n\tcontext\n\t\t.draw_image_with_html_image_element_and_dw_and_dh(&image_data, 0., 0., resolution.x as f64, resolution.y as f64)\n\t\t.unwrap();\n\n\tlet rasterized = context.get_image_data(0., 0., resolution.x as f64, resolution.y as f64).unwrap();\n\n\tlet image = Image::from_image_data(&rasterized.data().0, resolution.x as u32, resolution.y as u32);\n\tTable::new_from_row(TableRow {\n\t\telement: Raster::new_cpu(image),\n\t\ttransform: footprint.transform,\n\t\t..Default::default()\n\t})\n}\n"
  },
  {
    "path": "node-graph/nodes/math/Cargo.toml",
    "content": "[package]\nname = \"math-nodes\"\nversion = \"0.1.0\"\nedition = \"2024\"\ndescription = \"Math operation nodes for Graphene\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\nlicense = \"MIT OR Apache-2.0\"\n\n[dependencies]\ncore-types = { workspace = true }\nnode-macro = { workspace = true }\nvector-types = { workspace = true }\n\n# Workspace dependencies\nglam = { workspace = true }\nrand = { workspace = true }\nmath-parser = { workspace = true }\nlog = { workspace = true }\n"
  },
  {
    "path": "node-graph/nodes/math/src/lib.rs",
    "content": "use core_types::registry::types::{Fraction, Percentage, PixelSize, TextArea};\nuse core_types::table::Table;\nuse core_types::transform::Footprint;\nuse core_types::{Color, Ctx, num_traits};\nuse glam::{DAffine2, DVec2};\nuse log::warn;\nuse math_parser::ast;\nuse math_parser::context::{EvalContext, NothingMap, ValueProvider};\nuse math_parser::value::{Number, Value};\nuse num_traits::Pow;\nuse rand::{Rng, SeedableRng};\nuse std::ops::{Add, Div, Mul, Rem, Sub};\nuse vector_types::GradientStops;\n\n/// The struct that stores the context for the maths parser.\n/// This is currently just limited to supplying `a` and `b` until we add better node graph support and UI for variadic inputs.\nstruct MathNodeContext {\n\ta: f64,\n\tb: f64,\n}\n\nimpl ValueProvider for MathNodeContext {\n\tfn get_value(&self, name: &str) -> Option<Value> {\n\t\tif name.eq_ignore_ascii_case(\"a\") {\n\t\t\tSome(Value::from_f64(self.a))\n\t\t} else if name.eq_ignore_ascii_case(\"b\") {\n\t\t\tSome(Value::from_f64(self.b))\n\t\t} else {\n\t\t\tNone\n\t\t}\n\t}\n}\n\n/// Calculates a mathematical expression with input values \"A\" and \"B\".\n#[node_macro::node(category(\"Math: Arithmetic\"), properties(\"math_properties\"))]\nfn math<T: num_traits::float::Float>(\n\t_: impl Ctx,\n\t/// The value of \"A\" when calculating the expression.\n\t#[implementations(f64, f32)]\n\toperand_a: T,\n\t/// A math expression that may incorporate \"A\" and/or \"B\", such as `sqrt(A + B) - B^2`.\n\t#[default(A + B)]\n\texpression: String,\n\t/// The value of \"B\" when calculating the expression.\n\t#[implementations(f64, f32)]\n\t#[default(1.)]\n\toperand_b: T,\n) -> T {\n\tlet (node, _unit) = match ast::Node::try_parse_from_str(&expression) {\n\t\tOk(expr) => expr,\n\t\tErr(e) => {\n\t\t\twarn!(\"Invalid expression: `{expression}`\\n{e:?}\");\n\t\t\treturn T::from(0.).unwrap();\n\t\t}\n\t};\n\tlet context = EvalContext::new(\n\t\tMathNodeContext {\n\t\t\ta: operand_a.to_f64().unwrap(),\n\t\t\tb: operand_b.to_f64().unwrap(),\n\t\t},\n\t\tNothingMap,\n\t);\n\n\tlet value = match node.eval(&context) {\n\t\tOk(value) => value,\n\t\tErr(e) => {\n\t\t\twarn!(\"Expression evaluation error: {e:?}\");\n\t\t\treturn T::from(0.).unwrap();\n\t\t}\n\t};\n\n\tlet Value::Number(num) = value;\n\tmatch num {\n\t\tNumber::Real(val) => T::from(val).unwrap(),\n\t\tNumber::Complex(c) => T::from(c.re).unwrap(),\n\t}\n}\n\n/// The addition operation (`+`) calculates the sum of two scalar numbers or vectors.\n#[node_macro::node(category(\"Math: Arithmetic\"))]\nfn add<A: Add<B>, B>(\n\t_: impl Ctx,\n\t/// The left-hand side of the addition operation.\n\t#[implementations(f64, f32, u32, DVec2, f64, DVec2)]\n\taugend: A,\n\t/// The right-hand side of the addition operation.\n\t#[implementations(f64, f32, u32, DVec2, DVec2, f64)]\n\taddend: B,\n) -> <A as Add<B>>::Output {\n\taugend + addend\n}\n\n/// The subtraction operation (`-`) calculates the difference between two scalar numbers or vectors.\n#[node_macro::node(category(\"Math: Arithmetic\"))]\nfn subtract<A: Sub<B>, B>(\n\t_: impl Ctx,\n\t/// The left-hand side of the subtraction operation.\n\t#[implementations(f64, f32, u32, DVec2, f64, DVec2)]\n\tminuend: A,\n\t/// The right-hand side of the subtraction operation.\n\t#[implementations(f64, f32, u32, DVec2, DVec2, f64)]\n\tsubtrahend: B,\n) -> <A as Sub<B>>::Output {\n\tminuend - subtrahend\n}\n\n/// The multiplication operation (`×`) calculates the product of two scalar numbers, vectors, or transforms.\n#[node_macro::node(category(\"Math: Arithmetic\"))]\nfn multiply<A: Mul<B>, B>(\n\t_: impl Ctx,\n\t/// The left-hand side of the multiplication operation.\n\t#[implementations(f64, f32, u32, DVec2, f64, DVec2, DAffine2)]\n\tmultiplier: A,\n\t/// The right-hand side of the multiplication operation.\n\t#[default(1.)]\n\t#[implementations(f64, f32, u32, DVec2, DVec2, f64, DAffine2)]\n\tmultiplicand: B,\n) -> <A as Mul<B>>::Output {\n\tmultiplier * multiplicand\n}\n\n/// The division operation (`÷`) calculates the quotient of two scalar numbers or vectors.\n///\n/// Produces 0 if the denominator is 0.\n#[node_macro::node(category(\"Math: Arithmetic\"))]\nfn divide<A: Div<B> + Default + PartialEq, B: Default + PartialEq>(\n\t_: impl Ctx,\n\t/// The left-hand side of the division operation.\n\t#[implementations(f64, f32, u32, DVec2, DVec2, f64)]\n\tnumerator: A,\n\t/// The right-hand side of the division operation.\n\t#[default(1.)]\n\t#[implementations(f64, f32, u32, DVec2, f64, DVec2)]\n\tdenominator: B,\n) -> <A as Div<B>>::Output\nwhere\n\t<A as Div<B>>::Output: Default,\n{\n\tif denominator == B::default() {\n\t\treturn <A as Div<B>>::Output::default();\n\t}\n\tnumerator / denominator\n}\n\n/// The reciprocal operation (`1/x`) calculates the multiplicative inverse of a number.\n///\n/// Produces 0 if the input is 0.\n#[node_macro::node(category(\"Math: Arithmetic\"))]\nfn reciprocal<T: num_traits::float::Float>(\n\t_: impl Ctx,\n\t/// The number for which the reciprocal is calculated.\n\t#[implementations(f64, f32)]\n\tvalue: T,\n) -> T {\n\tif value == T::from(0.).unwrap() { T::from(0.).unwrap() } else { T::from(1.).unwrap() / value }\n}\n\n/// The modulo operation (`%`) calculates the remainder from the division of two scalar numbers or vectors.\n///\n/// The sign of the result shares the sign of the numerator unless *Always Positive* is enabled.\n#[node_macro::node(category(\"Math: Arithmetic\"))]\nfn modulo<A: Rem<B, Output: Add<B, Output: Rem<B, Output = A::Output>>>, B: Copy>(\n\t_: impl Ctx,\n\t/// The left-hand side of the modulo operation.\n\t#[implementations(f64, f32, u32, DVec2, DVec2, f64)]\n\tnumerator: A,\n\t/// The right-hand side of the modulo operation.\n\t#[default(2.)]\n\t#[implementations(f64, f32, u32, DVec2, f64, DVec2)]\n\tmodulus: B,\n\t/// Ensures the result is always positive, even if the numerator is negative.\n\t#[default(true)]\n\talways_positive: bool,\n) -> <A as Rem<B>>::Output {\n\tif always_positive { (numerator % modulus + modulus) % modulus } else { numerator % modulus }\n}\n\n/// The exponent operation (`^`) calculates the result of raising a number to a power.\n#[node_macro::node(category(\"Math: Arithmetic\"))]\nfn exponent<T: Pow<T>>(\n\t_: impl Ctx,\n\t/// The base number that is raised to the power.\n\t#[implementations(f64, f32, u32)]\n\tbase: T,\n\t/// The power to which the base number is raised.\n\t#[implementations(f64, f32, u32)]\n\t#[default(2.)]\n\tpower: T,\n) -> <T as num_traits::Pow<T>>::Output {\n\tbase.pow(power)\n}\n\n/// The `n`th root operation (`√`) calculates the inverse of exponentiation. Square root inverts squaring, cube root inverts cubing, and so on.\n///\n/// This is equivalent to raising the number to the power of `1/n`.\n#[node_macro::node(category(\"Math: Arithmetic\"))]\nfn root<T: num_traits::float::Float>(\n\t_: impl Ctx,\n\t/// The number inside the radical for which the `n`th root is calculated.\n\t#[default(2.)]\n\t#[implementations(f64, f32)]\n\tradicand: T,\n\t/// The degree of the root to be calculated. Square root is 2, cube root is 3, and so on.\n\t/// Degrees 0 or less are invalid and will produce an output of 0.\n\t#[default(2.)]\n\t#[implementations(f64, f32)]\n\tdegree: T,\n) -> T {\n\tif degree == T::from(2.).unwrap() {\n\t\tradicand.sqrt()\n\t} else if degree == T::from(3.).unwrap() {\n\t\tradicand.cbrt()\n\t} else if degree <= T::from(0.).unwrap() {\n\t\tT::from(0.).unwrap()\n\t} else {\n\t\tradicand.powf(T::from(1.).unwrap() / degree)\n\t}\n}\n\n/// The logarithmic function (`log`) calculates the logarithm of a number with a specified base. If the natural logarithm function (`ln`) is desired, set the base to \"e\".\n#[node_macro::node(category(\"Math: Arithmetic\"))]\nfn logarithm<T: num_traits::float::Float>(\n\t_: impl Ctx,\n\t/// The number for which the logarithm is calculated.\n\t#[implementations(f64, f32)]\n\tvalue: T,\n\t/// The base of the logarithm, such as 2 (binary), 10 (decimal), and e (natural logarithm).\n\t#[default(2.)]\n\t#[implementations(f64, f32)]\n\tbase: T,\n) -> T {\n\tif base == T::from(2.).unwrap() {\n\t\tvalue.log2()\n\t} else if base == T::from(10.).unwrap() {\n\t\tvalue.log10()\n\t} else if base - T::from(std::f64::consts::E).unwrap() < T::epsilon() * T::from(1e6).unwrap() {\n\t\tvalue.ln()\n\t} else {\n\t\tvalue.log(base)\n\t}\n}\n\n/// The sine trigonometric function (`sin`) calculates the ratio of the angle's opposite side length to its hypotenuse length.\n#[node_macro::node(category(\"Math: Trig\"))]\nfn sine<T: num_traits::float::Float>(\n\t_: impl Ctx,\n\t/// The given angle.\n\t#[implementations(f64, f32)]\n\ttheta: T,\n\t/// Whether the given angle should be interpreted as radians instead of degrees.\n\tradians: bool,\n) -> T {\n\tif radians { theta.sin() } else { theta.to_radians().sin() }\n}\n\n/// The cosine trigonometric function (`cos`) calculates the ratio of the angle's adjacent side length to its hypotenuse length.\n#[node_macro::node(category(\"Math: Trig\"))]\nfn cosine<T: num_traits::float::Float>(\n\t_: impl Ctx,\n\t/// The given angle.\n\t#[implementations(f64, f32)]\n\ttheta: T,\n\t/// Whether the given angle should be interpreted as radians instead of degrees.\n\tradians: bool,\n) -> T {\n\tif radians { theta.cos() } else { theta.to_radians().cos() }\n}\n\n/// The tangent trigonometric function (`tan`) calculates the ratio of the angle's opposite side length to its adjacent side length.\n#[node_macro::node(category(\"Math: Trig\"))]\nfn tangent<T: num_traits::float::Float>(\n\t_: impl Ctx,\n\t/// The given angle.\n\t#[implementations(f64, f32)]\n\ttheta: T,\n\t/// Whether the given angle should be interpreted as radians instead of degrees.\n\tradians: bool,\n) -> T {\n\tif radians { theta.tan() } else { theta.to_radians().tan() }\n}\n\n/// The inverse sine trigonometric function (`asin`) calculates the angle whose sine is the input value.\n#[node_macro::node(category(\"Math: Trig\"))]\nfn sine_inverse<T: num_traits::float::Float>(\n\t_: impl Ctx,\n\t/// The given value for which the angle is calculated. Must be in the domain `[-1, 1]` (it will be clamped to -1 or 1 otherwise).\n\t#[implementations(f64, f32)]\n\tvalue: T,\n\t/// Whether the resulting angle should be given in as radians instead of degrees.\n\tradians: bool,\n) -> T {\n\tlet angle = value.clamp(T::from(-1.).unwrap(), T::from(1.).unwrap()).asin();\n\tif radians { angle } else { angle.to_degrees() }\n}\n\n/// The inverse cosine trigonometric function (`acos`) calculates the angle whose cosine is the input value.\n#[node_macro::node(category(\"Math: Trig\"))]\nfn cosine_inverse<T: num_traits::float::Float>(\n\t_: impl Ctx,\n\t/// The given value for which the angle is calculated. Must be in the domain `[-1, 1]` (it will be clamped to -1 or 1 otherwise).\n\t#[implementations(f64, f32)]\n\tvalue: T,\n\t/// Whether the resulting angle should be given in as radians instead of degrees.\n\tradians: bool,\n) -> T {\n\tlet angle = value.clamp(T::from(-1.).unwrap(), T::from(1.).unwrap()).acos();\n\tif radians { angle } else { angle.to_degrees() }\n}\n\n/// The inverse tangent trigonometric function (`atan` or `atan2`, depending on input type) calculates:\n/// `atan`: the angle whose tangent is the input scalar number.\n/// `atan2`: the angle of a ray from the origin to the input vec2.\n///\n/// The resulting angle is always in the range `[-90°, 90°]` or, in radians, `[-π/2, π/2]`.\n#[node_macro::node(category(\"Math: Trig\"))]\nfn tangent_inverse<T: TangentInverse>(\n\t_: impl Ctx,\n\t/// The given value for which the angle is calculated.\n\t#[implementations(f64, f32, DVec2)]\n\tvalue: T,\n\t/// Whether the resulting angle should be given in as radians instead of degrees.\n\tradians: bool,\n) -> T::Output {\n\tvalue.atan(radians)\n}\n\npub trait TangentInverse {\n\ttype Output: num_traits::float::Float;\n\tfn atan(self, radians: bool) -> Self::Output;\n}\nimpl TangentInverse for f32 {\n\ttype Output = f32;\n\tfn atan(self, radians: bool) -> Self::Output {\n\t\tif radians { self.atan() } else { self.atan().to_degrees() }\n\t}\n}\nimpl TangentInverse for f64 {\n\ttype Output = f64;\n\tfn atan(self, radians: bool) -> Self::Output {\n\t\tif radians { self.atan() } else { self.atan().to_degrees() }\n\t}\n}\nimpl TangentInverse for DVec2 {\n\ttype Output = f64;\n\tfn atan(self, radians: bool) -> Self::Output {\n\t\tif radians { self.y.atan2(self.x) } else { self.y.atan2(self.x).to_degrees() }\n\t}\n}\n\n/// Linearly maps an input value from one range to another. The ranges may be reversed.\n///\n/// For example, 0.5 in the input range `[0, 1]` would map to 0 in the output range `[-180, 180]`.\n#[node_macro::node(category(\"Math: Numeric\"))]\nfn remap<U: num_traits::float::Float>(\n\t_: impl Ctx,\n\t/// The value to be mapped between ranges.\n\t#[implementations(f64, f32)]\n\tvalue: U,\n\t/// The lower bound of the input range.\n\t#[implementations(f64, f32)]\n\tinput_min: U,\n\t/// The upper bound of the input range.\n\t#[implementations(f64, f32)]\n\t#[default(1.)]\n\tinput_max: U,\n\t/// The lower bound of the output range.\n\t#[implementations(f64, f32)]\n\toutput_min: U,\n\t/// The upper bound of the output range.\n\t#[implementations(f64, f32)]\n\t#[default(1.)]\n\toutput_max: U,\n\t/// Whether to constrain the result within the output range instead of extrapolating beyond its bounds.\n\tclamped: bool,\n) -> U {\n\tlet input_range = input_max - input_min;\n\n\t// Handle division by zero\n\tif input_range.abs() < U::epsilon() {\n\t\treturn output_min;\n\t}\n\n\tlet normalized = (value - input_min) / input_range;\n\tlet output_range = output_max - output_min;\n\n\tlet result = output_min + normalized * output_range;\n\n\tif clamped {\n\t\t// Handle both normal and inverted ranges, since we want to allow the user to use this node to also reverse a range.\n\t\tif output_min <= output_max {\n\t\t\tresult.clamp(output_min, output_max)\n\t\t} else {\n\t\t\tresult.clamp(output_max, output_min)\n\t\t}\n\t} else {\n\t\tresult\n\t}\n}\n\n/// The random function (`rand`) converts a seed into a random number within the specified range, inclusive of the minimum and exclusive of the maximum. The minimum and maximum values are automatically swapped if they are reversed.\n#[node_macro::node(category(\"Math: Numeric\"))]\nfn random(\n\t_: impl Ctx,\n\t_primary: (),\n\t/// Seed to determine the unique variation of which number is generated.\n\tseed: u64,\n\t/// The smaller end of the range within which the random number is generated.\n\tmin: f64,\n\t/// The larger end of the range within which the random number is generated.\n\t#[default(1.)]\n\tmax: f64,\n) -> f64 {\n\tlet mut rng = rand::rngs::StdRng::seed_from_u64(seed);\n\tlet result = rng.random::<f64>();\n\tlet (min, max) = if min < max { (min, max) } else { (max, min) };\n\tresult * (max - min) + min\n}\n\n// TODO: Test that these are no longer needed in all circumstances, then remove them and add a migration to convert these into Passthrough nodes. Note: these act more as type annotations than as identity functions.\n/// Convert a number to an integer of the type u32, which may be the required type for certain node inputs.\n#[node_macro::node(name(\"To u32\"), category(\"Debug\"))]\nfn to_u32(_: impl Ctx, value: u32) -> u32 {\n\tvalue\n}\n\n// TODO: Test that these are no longer needed in all circumstances, then remove them and add a migration to convert these into Passthrough nodes. Note: these act more as type annotations than as identity functions.\n/// Convert a number to an integer of the type u64, which may be the required type for certain node inputs.\n#[node_macro::node(name(\"To u64\"), category(\"Debug\"))]\nfn to_u64(_: impl Ctx, value: u64) -> u64 {\n\tvalue\n}\n\n// TODO: Test that these are no longer needed in all circumstances, then remove them and add a migration to convert these into Passthrough nodes. Note: these act more as type annotations than as identity functions.\n/// Convert an integer to a decimal number of the type f64, which may be the required type for certain node inputs.\n#[node_macro::node(name(\"To f64\"), category(\"Debug\"))]\nfn to_f64(_: impl Ctx, value: f64) -> f64 {\n\tvalue\n}\n\n/// The rounding function (`round`) maps an input value to its nearest whole number. Halfway values are rounded away from zero.\n#[node_macro::node(category(\"Math: Numeric\"))]\nfn round<T: num_traits::float::Float>(\n\t_: impl Ctx,\n\t/// The number to be rounded to the nearest whole number.\n\t#[implementations(f64, f32)]\n\tvalue: T,\n) -> T {\n\tvalue.round()\n}\n\n/// The floor function (`floor`) rounds down an input value to the nearest whole number, unless the input number is already whole.\n#[node_macro::node(category(\"Math: Numeric\"))]\nfn floor<T: num_traits::float::Float>(\n\t_: impl Ctx,\n\t/// The number to be rounded down.\n\t#[implementations(f64, f32)]\n\tvalue: T,\n) -> T {\n\tvalue.floor()\n}\n\n/// The ceiling function (`ceil`) rounds up an input value to the nearest whole number, unless the input number is already whole.\n#[node_macro::node(category(\"Math: Numeric\"))]\nfn ceiling<T: num_traits::float::Float>(\n\t_: impl Ctx,\n\t/// The number to be rounded up.\n\t#[implementations(f64, f32)]\n\tvalue: T,\n) -> T {\n\tvalue.ceil()\n}\n\ntrait AbsoluteValue {\n\tfn abs(self) -> Self;\n}\nimpl AbsoluteValue for DVec2 {\n\tfn abs(self) -> Self {\n\t\tDVec2::new(self.x.abs(), self.y.abs())\n\t}\n}\nimpl AbsoluteValue for f32 {\n\tfn abs(self) -> Self {\n\t\tself.abs()\n\t}\n}\nimpl AbsoluteValue for f64 {\n\tfn abs(self) -> Self {\n\t\tself.abs()\n\t}\n}\nimpl AbsoluteValue for i32 {\n\tfn abs(self) -> Self {\n\t\tself.abs()\n\t}\n}\nimpl AbsoluteValue for i64 {\n\tfn abs(self) -> Self {\n\t\tself.abs()\n\t}\n}\n\n/// The absolute value function (`abs`) removes the negative sign from an input value, if present.\n#[node_macro::node(category(\"Math: Numeric\"))]\nfn absolute_value<T: AbsoluteValue>(\n\t_: impl Ctx,\n\t/// The number to be made positive.\n\t#[implementations(f64, f32, i32, i64, DVec2)]\n\tvalue: T,\n) -> T {\n\tvalue.abs()\n}\n\n/// The minimum function (`min`) picks the smaller of two numbers.\n#[node_macro::node(category(\"Math: Numeric\"))]\nfn min<T: std::cmp::PartialOrd>(\n\t_: impl Ctx,\n\t/// One of the two numbers, of which the lesser is returned.\n\t#[implementations(f64, f32, u32, &str)]\n\tvalue: T,\n\t/// The other of the two numbers, of which the lesser is returned.\n\t#[implementations(f64, f32, u32, &str)]\n\tother_value: T,\n) -> T {\n\tif value < other_value { value } else { other_value }\n}\n\n/// The maximum function (`max`) picks the larger of two numbers.\n#[node_macro::node(category(\"Math: Numeric\"))]\nfn max<T: std::cmp::PartialOrd>(\n\t_: impl Ctx,\n\t/// One of the two numbers, of which the greater is returned.\n\t#[implementations(f64, f32, u32, &str)]\n\tvalue: T,\n\t/// The other of the two numbers, of which the greater is returned.\n\t#[implementations(f64, f32, u32, &str)]\n\tother_value: T,\n) -> T {\n\tif value > other_value { value } else { other_value }\n}\n\n/// The clamp function (`clamp`) restricts a number to a specified range between a minimum and maximum value. The minimum and maximum values are automatically swapped if they are reversed.\n#[node_macro::node(category(\"Math: Numeric\"))]\nfn clamp<T: std::cmp::PartialOrd>(\n\t_: impl Ctx,\n\t/// The number to be clamped, which is restricted to the range between the minimum and maximum values.\n\t#[implementations(f64, f32, u32, &str)]\n\tvalue: T,\n\t/// The left (smaller) side of the range. The output is never less than this number.\n\t#[implementations(f64, f32, u32, &str)]\n\tmin: T,\n\t/// The right (greater) side of the range. The output is never greater than this number.\n\t#[implementations(f64, f32, u32, &str)]\n\t#[default(1)]\n\tmax: T,\n) -> T {\n\tlet (min, max) = if min < max { (min, max) } else { (max, min) };\n\tif value < min {\n\t\tmin\n\t} else if value > max {\n\t\tmax\n\t} else {\n\t\tvalue\n\t}\n}\n\n/// The greatest common divisor (GCD) calculates the largest positive integer that divides both of the two input numbers without leaving a remainder.\n#[node_macro::node(category(\"Math: Numeric\"))]\nfn greatest_common_divisor<T: num_traits::int::PrimInt + std::ops::ShrAssign<i32> + std::ops::SubAssign>(\n\t_: impl Ctx,\n\t/// One of the two numbers for which the GCD is calculated.\n\t#[implementations(u32, u64, i32)]\n\tvalue: T,\n\t/// The other of the two numbers for which the GCD is calculated.\n\t#[implementations(u32, u64, i32)]\n\tother_value: T,\n) -> T {\n\tif value == T::zero() {\n\t\treturn other_value;\n\t}\n\tif other_value == T::zero() {\n\t\treturn value;\n\t}\n\tbinary_gcd(value, other_value)\n}\n\n/// The least common multiple (LCM) calculates the smallest positive integer that is a multiple of both of the two input numbers.\n#[node_macro::node(category(\"Math: Numeric\"))]\nfn least_common_multiple<T: num_traits::ToPrimitive + num_traits::FromPrimitive + num_traits::identities::Zero>(\n\t_: impl Ctx,\n\t/// One of the two numbers for which the LCM is calculated.\n\t#[implementations(u32, u64, i32)]\n\tvalue: T,\n\t/// The other of the two numbers for which the LCM is calculated.\n\t#[implementations(u32, u64, i32)]\n\tother_value: T,\n) -> T {\n\tlet value = value.to_i128().unwrap();\n\tlet other_value = other_value.to_i128().unwrap();\n\n\tif value == 0 || other_value == 0 {\n\t\treturn T::zero();\n\t}\n\tlet gcd = binary_gcd(value, other_value);\n\n\tT::from_i128((value * other_value).abs() / gcd).unwrap()\n}\n\nfn binary_gcd<T: num_traits::int::PrimInt + std::ops::ShrAssign<i32> + std::ops::SubAssign>(mut a: T, mut b: T) -> T {\n\tif a == T::zero() {\n\t\treturn b;\n\t}\n\tif b == T::zero() {\n\t\treturn a;\n\t}\n\n\tlet mut shift = 0;\n\twhile (a | b) & T::one() == T::zero() {\n\t\ta >>= 1;\n\t\tb >>= 1;\n\t\tshift += 1;\n\t}\n\n\twhile a & T::one() == T::zero() {\n\t\ta >>= 1;\n\t}\n\n\twhile b != T::zero() {\n\t\twhile b & T::one() == T::zero() {\n\t\t\tb >>= 1;\n\t\t}\n\t\tif a > b {\n\t\t\tstd::mem::swap(&mut a, &mut b);\n\t\t}\n\t\tb -= a;\n\t}\n\n\ta << shift\n}\n\n/// The less-than operation (`<`) compares two values and returns true if the first value is less than the second, or false if it is not.\n/// If enabled with *Or Equal*, the less-than-or-equal operation (`<=`) is used instead.\n#[node_macro::node(category(\"Math: Logic\"))]\nfn less_than<T: std::cmp::PartialOrd<T>>(\n\t_: impl Ctx,\n\t/// The number on the left-hand side of the comparison.\n\t#[implementations(f64, f32, u32)]\n\tvalue: T,\n\t/// The number on the right-hand side of the comparison.\n\t#[implementations(f64, f32, u32)]\n\tother_value: T,\n\t/// Uses the less-than-or-equal operation (`<=`) instead of the less-than operation (`<`).\n\tor_equal: bool,\n) -> bool {\n\tif or_equal { value <= other_value } else { value < other_value }\n}\n\n/// The greater-than operation (`>`) compares two values and returns true if the first value is greater than the second, or false if it is not.\n/// If enabled with *Or Equal*, the greater-than-or-equal operation (`>=`) is used instead.\n#[node_macro::node(category(\"Math: Logic\"))]\nfn greater_than<T: std::cmp::PartialOrd<T>>(\n\t_: impl Ctx,\n\t/// The number on the left-hand side of the comparison.\n\t#[implementations(f64, f32, u32)]\n\tvalue: T,\n\t/// The number on the right-hand side of the comparison.\n\t#[implementations(f64, f32, u32)]\n\tother_value: T,\n\t/// Uses the greater-than-or-equal operation (`>=`) instead of the greater-than operation (`>`).\n\tor_equal: bool,\n) -> bool {\n\tif or_equal { value >= other_value } else { value > other_value }\n}\n\n/// The equality operation (`==`, `XNOR`) compares two values and returns true if they are equal, or false if they are not.\n#[node_macro::node(category(\"Math: Logic\"))]\nfn equals<T: std::cmp::PartialEq<T>>(\n\t_: impl Ctx,\n\t/// One of the two values to compare for equality.\n\t#[implementations(f64, f32, u32, DVec2, bool, &str, String)]\n\tvalue: T,\n\t/// The other of the two values to compare for equality.\n\t#[implementations(f64, f32, u32, DVec2, bool, &str, String)]\n\tother_value: T,\n) -> bool {\n\tother_value == value\n}\n\n/// The inequality operation (`!=`, `XOR`) compares two values and returns true if they are not equal, or false if they are.\n#[node_macro::node(category(\"Math: Logic\"))]\nfn not_equals<T: std::cmp::PartialEq<T>>(\n\t_: impl Ctx,\n\t/// One of the two values to compare for inequality.\n\t#[implementations(f64, f32, u32, DVec2, bool, &str)]\n\tvalue: T,\n\t/// The other of the two values to compare for inequality.\n\t#[implementations(f64, f32, u32, DVec2, bool, &str)]\n\tother_value: T,\n) -> bool {\n\tother_value != value\n}\n\n/// The logical OR operation (`||`) returns true if either of the two inputs are true, or false if both are false.\n#[node_macro::node(category(\"Math: Logic\"))]\nfn logical_or(\n\t_: impl Ctx,\n\t/// One of the two boolean values, either of which may be true for the node to output true.\n\tvalue: bool,\n\t/// The other of the two boolean values, either of which may be true for the node to output true.\n\t#[expose]\n\tother_value: bool,\n) -> bool {\n\tvalue || other_value\n}\n\n/// The logical AND operation (`&&`) returns true if both of the two inputs are true, or false if any are false.\n#[node_macro::node(category(\"Math: Logic\"))]\nfn logical_and(\n\t_: impl Ctx,\n\t/// One of the two boolean values, both of which must be true for the node to output true.\n\tvalue: bool,\n\t/// The other of the two boolean values, both of which must be true for the node to output true.\n\t#[expose]\n\tother_value: bool,\n) -> bool {\n\tvalue && other_value\n}\n\n/// The logical NOT operation (`!`) reverses true and false value of the input.\n#[node_macro::node(category(\"Math: Logic\"))]\nfn logical_not(\n\t_: impl Ctx,\n\t/// The boolean value to be reversed.\n\tinput: bool,\n) -> bool {\n\t!input\n}\n\n/// Constructs a bool value which may be set to true or false.\n#[node_macro::node(category(\"Value\"))]\nfn bool_value(_: impl Ctx, _primary: (), #[name(\"Bool\")] bool_value: bool) -> bool {\n\tbool_value\n}\n\n/// Constructs a number value which may be set to any real number.\n#[node_macro::node(category(\"Value\"))]\nfn number_value(_: impl Ctx, _primary: (), number: f64) -> f64 {\n\tnumber\n}\n\n/// Constructs a number value which may be set to any value from 0% to 100% by dragging the slider.\n#[node_macro::node(category(\"Value\"))]\nfn percentage_value(_: impl Ctx, _primary: (), percentage: Percentage) -> f64 {\n\tpercentage\n}\n\n/// Constructs a two-dimensional vector value which may be set to any XY pair.\n#[node_macro::node(category(\"Value\"), name(\"Vec2 Value\"))]\nfn vec2_value(_: impl Ctx, _primary: (), x: f64, y: f64) -> DVec2 {\n\tDVec2::new(x, y)\n}\n\n/// Constructs a color value which may be set to any color, or no color.\n#[node_macro::node(category(\"Value\"))]\nfn color_value(_: impl Ctx, _primary: (), #[default(Color::BLACK)] color: Table<Color>) -> Table<Color> {\n\tcolor\n}\n\n/// Constructs a color value from red, green, blue, and alpha components given as numbers from 0 to 1.\n#[node_macro::node(category(\"Color\"), name(\"RGBA to Color\"))]\nfn rgba_to_color(_: impl Ctx, _primary: (), red: Fraction, green: Fraction, blue: Fraction, #[default(1.)] alpha: Fraction) -> Table<Color> {\n\tlet red = (red as f32).clamp(0., 1.);\n\tlet green = (green as f32).clamp(0., 1.);\n\tlet blue = (blue as f32).clamp(0., 1.);\n\tlet alpha = (alpha as f32).clamp(0., 1.);\n\n\tTable::new_from_element(Color::from_rgbaf32_unchecked(red, green, blue, alpha))\n}\n\n/// Constructs a color value from hue, saturation, value, and alpha components given as numbers from 0 to 1.\n#[node_macro::node(category(\"Color\"), name(\"HSVA to Color\"))]\nfn hsva_to_color(_: impl Ctx, _primary: (), hue: Fraction, #[default(1.)] saturation: Fraction, #[default(1.)] value: Fraction, #[default(1.)] alpha: Fraction) -> Table<Color> {\n\tlet hue = (hue as f32) - (hue as f32).floor();\n\tlet saturation = (saturation as f32).clamp(0., 1.);\n\tlet value = (value as f32).clamp(0., 1.);\n\tlet alpha = (alpha as f32).clamp(0., 1.);\n\n\tTable::new_from_element(Color::from_hsva(hue, saturation, value, alpha))\n}\n\n/// Constructs a color value from hue, saturation, lightness, and alpha components given as numbers from 0 to 1.\n#[node_macro::node(category(\"Color\"), name(\"HSLA to Color\"))]\nfn hsla_to_color(_: impl Ctx, _primary: (), hue: Fraction, #[default(1.)] saturation: Fraction, #[default(0.5)] lightness: Fraction, #[default(1.)] alpha: Fraction) -> Table<Color> {\n\tlet hue = (hue as f32) - (hue as f32).floor();\n\tlet saturation = (saturation as f32).clamp(0., 1.);\n\tlet lightness = (lightness as f32).clamp(0., 1.);\n\tlet alpha = (alpha as f32).clamp(0., 1.);\n\n\tTable::new_from_element(Color::from_hsla(hue, saturation, lightness, alpha))\n}\n\n/// Constructs a color value from an sRGB color code string, such as `#RRGGBB` or `#RRGGBBAA`. Invalid hex code strings produce no color.\n#[node_macro::node(category(\"Color\"), name(\"Hex to Color\"))]\nfn hex_to_color(_: impl Ctx, hex_code: String) -> Table<Color> {\n\tmatch Color::from_hex_str(&hex_code) {\n\t\tSome(c) => Table::new_from_element(c),\n\t\tNone => Table::new(),\n\t}\n}\n\n/// Constructs a gradient value which may be set to any sequence of color stops to represent the transition between colors.\n#[node_macro::node(category(\"Value\"))]\nfn gradient_value(_: impl Ctx, _primary: (), gradient: Table<GradientStops>) -> Table<GradientStops> {\n\tgradient\n}\n\n/// Gets the color at the specified position along the gradient, given a position from 0 (left) to 1 (right).\n#[node_macro::node(category(\"Color\"))]\nfn sample_gradient(_: impl Ctx, _primary: (), gradient: Table<GradientStops>, position: Fraction) -> Table<Color> {\n\tlet Some(row) = gradient.get(0) else { return Table::new() };\n\n\tlet position = position.clamp(0., 1.);\n\tlet color = row.element.evaluate(position);\n\tTable::new_from_element(color)\n}\n\n/// Constructs a string value which may be set to any plain text.\n#[node_macro::node(category(\"Value\"))]\nfn string_value(_: impl Ctx, _primary: (), string: TextArea) -> String {\n\tstring\n}\n\n/// Constructs a footprint value which may be set to any transformation of a unit square describing a render area, and a render resolution at least 1x1 integer pixels.\n#[node_macro::node(category(\"Value\"))]\nfn footprint_value(_: impl Ctx, _primary: (), transform: DAffine2, #[default(100., 100.)] resolution: PixelSize) -> Footprint {\n\tFootprint {\n\t\ttransform,\n\t\tresolution: resolution.max(DVec2::ONE).as_uvec2(),\n\t\t..Default::default()\n\t}\n}\n\n/// The dot product operation (`·`) calculates the degree of similarity of a vec2 pair based on their angles and lengths.\n///\n/// Calculated as `‖a‖‖b‖cos(θ)`, it represents the product of their lengths (`‖a‖‖b‖`) scaled by the alignment of their directions (`cos(θ)`).\n/// The output ranges from the positive to negative product of their lengths based on when they are pointing in the same or opposite directions.\n/// If any vector has zero length, the output is 0.\n#[node_macro::node(category(\"Math: Vector\"))]\nfn dot_product(\n\t_: impl Ctx,\n\t/// An operand of the dot product operation.\n\tvector_a: DVec2,\n\t/// The other operand of the dot product operation.\n\t#[default(1., 0.)]\n\tvector_b: DVec2,\n\t/// Whether to normalize both input vectors so the calculation ranges in `[-1, 1]` by considering only their degree of directional alignment.\n\tnormalize: bool,\n) -> f64 {\n\tif normalize {\n\t\tvector_a.normalize_or_zero().dot(vector_b.normalize_or_zero())\n\t} else {\n\t\tvector_a.dot(vector_b)\n\t}\n}\n\n/// Calculates the angle swept between two vectors.\n///\n/// The value is always positive and ranges from 0° (both vectors point the same direction) to 180° (both vectors point opposite directions).\n#[node_macro::node(category(\"Math: Vector\"))]\nfn angle_between(_: impl Ctx, vector_a: DVec2, vector_b: DVec2, radians: bool) -> f64 {\n\tlet dot_product = vector_a.normalize_or_zero().dot(vector_b.normalize_or_zero());\n\tlet angle = dot_product.acos();\n\tif radians { angle } else { angle.to_degrees() }\n}\n\npub trait ToPosition {\n\tfn to_position(self) -> DVec2;\n}\nimpl ToPosition for DVec2 {\n\tfn to_position(self) -> DVec2 {\n\t\tself\n\t}\n}\nimpl ToPosition for DAffine2 {\n\tfn to_position(self) -> DVec2 {\n\t\tself.translation\n\t}\n}\n\n/// Calculates the angle needed for a rightward-facing object placed at the observer position to turn so it points toward the target position.\n#[node_macro::node(category(\"Math: Vector\"))]\nfn angle_to<T: ToPosition, U: ToPosition>(\n\t_: impl Ctx,\n\t/// The position from which the angle is measured.\n\t#[implementations(DVec2, DAffine2, DVec2, DAffine2)]\n\tobserver: T,\n\t/// The position toward which the angle is measured.\n\t#[expose]\n\t#[implementations(DVec2, DVec2, DAffine2, DAffine2)]\n\ttarget: U,\n\t/// Whether the resulting angle should be given in radians instead of degrees.\n\tradians: bool,\n) -> f64 {\n\tlet from = observer.to_position();\n\tlet to = target.to_position();\n\tlet delta = to - from;\n\tlet angle = delta.y.atan2(delta.x);\n\tif radians { angle } else { angle.to_degrees() }\n}\n\n// TODO: Rename to \"Magnitude\"\n/// The magnitude operator (`‖x‖`) calculates the length of a vec2, which is the distance from the base to the tip of the arrow represented by the vector.\n#[node_macro::node(category(\"Math: Vector\"))]\nfn length(_: impl Ctx, vector: DVec2) -> f64 {\n\tvector.length()\n}\n\n/// Scales the input vector to unit length while preserving its direction. This is equivalent to dividing the input vector by its own magnitude.\n///\n/// Returns 0 when the input vector has zero length.\n#[node_macro::node(category(\"Math: Vector\"))]\nfn normalize(_: impl Ctx, vector: DVec2) -> DVec2 {\n\tvector.normalize_or_zero()\n}\n\n#[cfg(test)]\nmod test {\n\tuse super::*;\n\tuse core_types::Node;\n\tuse core_types::generic::FnNode;\n\n\t#[test]\n\tpub fn dot_product_function() {\n\t\tlet vector_a = DVec2::new(1., 2.);\n\t\tlet vector_b = DVec2::new(3., 4.);\n\t\tassert_eq!(dot_product((), vector_a, vector_b, false), 11.);\n\t}\n\n\t#[test]\n\tpub fn length_function() {\n\t\tlet vector = DVec2::new(3., 4.);\n\t\tassert_eq!(length((), vector), 5.);\n\t}\n\n\t#[test]\n\tfn test_basic_expression() {\n\t\tlet result = math((), 0., \"2 + 2\".to_string(), 0.);\n\t\tassert_eq!(result, 4.);\n\t}\n\n\t#[test]\n\tfn test_complex_expression() {\n\t\tlet result = math((), 0., \"(5 * 3) + (10 / 2)\".to_string(), 0.);\n\t\tassert_eq!(result, 20.);\n\t}\n\n\t#[test]\n\tfn test_default_expression() {\n\t\tlet result = math((), 0., \"0\".to_string(), 0.);\n\t\tassert_eq!(result, 0.);\n\t}\n\n\t#[test]\n\tfn test_invalid_expression() {\n\t\tlet result = math((), 0., \"invalid\".to_string(), 0.);\n\t\tassert_eq!(result, 0.);\n\t}\n\n\t#[test]\n\tpub fn foo() {\n\t\tlet fnn = FnNode::new(|(a, b)| (b, a));\n\t\tassert_eq!(fnn.eval((1u32, 2u32)), (2, 1));\n\t}\n\n\t#[test]\n\tpub fn add_vectors() {\n\t\tassert_eq!(super::add((), DVec2::ONE, DVec2::ONE), DVec2::ONE * 2.);\n\t}\n\n\t#[test]\n\tpub fn subtract_f64() {\n\t\tassert_eq!(super::subtract((), 5_f64, 3_f64), 2.);\n\t}\n\n\t#[test]\n\tpub fn divide_vectors() {\n\t\tassert_eq!(super::divide((), DVec2::ONE, 2_f64), DVec2::ONE / 2.);\n\t}\n\n\t#[test]\n\tpub fn modulo_positive() {\n\t\tassert_eq!(super::modulo((), -5_f64, 2_f64, true), 1_f64);\n\t}\n\n\t#[test]\n\tpub fn modulo_negative() {\n\t\tassert_eq!(super::modulo((), -5_f64, 2_f64, false), -1_f64);\n\t}\n}\n"
  },
  {
    "path": "node-graph/nodes/path-bool/Cargo.toml",
    "content": "[package]\nname = \"path-bool-nodes\"\nversion = \"0.1.0\"\nedition = \"2024\"\ndescription = \"Path boolean operation nodes for Graphene\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\nlicense = \"MIT OR Apache-2.0\"\n\n[dependencies]\n# Local dependencies\ncore-types = { workspace = true }\ngraphic-types = { workspace = true }\nnode-macro = { workspace = true }\nglam = { workspace = true }\nlinesweeper = { workspace = true }\nlog = { workspace = true }\nsmallvec = { workspace = true }\nvector-types = { workspace = true }\n"
  },
  {
    "path": "node-graph/nodes/path-bool/src/lib.rs",
    "content": "use core_types::table::{Table, TableRow, TableRowRef};\nuse core_types::{Color, Ctx};\nuse glam::{DAffine2, DVec2};\nuse graphic_types::vector_types::subpath::{ManipulatorGroup, Subpath};\nuse graphic_types::vector_types::vector::PointId;\nuse graphic_types::vector_types::vector::algorithms::merge_by_distance::MergeByDistanceExt;\nuse graphic_types::vector_types::vector::style::Fill;\nuse graphic_types::{Graphic, Vector};\nuse linesweeper::topology::Topology;\nuse linesweeper::{BinaryOp, FillRule, binary_op};\nuse smallvec::SmallVec;\nuse vector_types::kurbo::{Affine, BezPath, CubicBez, Line, ParamCurve, PathSeg, Point, QuadBez};\npub use vector_types::vector::misc::BooleanOperation;\n\n// TODO: Fix boolean ops to work by removing .transform() and .one_instance_*() calls,\n// TODO: since before we used a Vec of single-row tables and now we use a single table\n// TODO: with multiple rows while still assuming a single row for the boolean operations.\n\n/// Combines the geometric forms of one or more closed paths into a new vector path that results from cutting or joining the paths by the chosen method.\n#[node_macro::node(category(\"\"))]\nasync fn boolean_operation<I: graphic_types::IntoGraphicTable + 'n + Send + Clone>(\n\t_: impl Ctx,\n\t/// The table of vector paths to perform the boolean operation on. Nested tables are automatically flattened.\n\t#[implementations(Table<Graphic>, Table<Vector>)]\n\tcontent: I,\n\t/// Which boolean operation to perform on the paths.\n\t///\n\t/// Union combines all paths while cutting out overlapping areas (even the interiors of a single path).\n\t/// Subtraction cuts overlapping areas out from the last (Subtract Front) or first (Subtract Back) path.\n\t/// Intersection cuts away all but the overlapping areas shared by every path.\n\t/// Difference cuts away the overlapping areas shared by every path, leaving only the non-overlapping areas.\n\toperation: BooleanOperation,\n) -> Table<Vector> {\n\tlet content = content.into_graphic_table();\n\n\t// The first index is the bottom of the stack\n\tlet mut result_vector_table = boolean_operation_on_vector_table(flatten_vector(&content).iter(), operation);\n\n\t// Replace the transformation matrix with a mutation of the vector points themselves\n\tif let Some(result_vector) = result_vector_table.iter_mut().next() {\n\t\tlet transform = *result_vector.transform;\n\t\t*result_vector.transform = DAffine2::IDENTITY;\n\n\t\tVector::transform(result_vector.element, transform);\n\t\tresult_vector.element.style.set_stroke_transform(DAffine2::IDENTITY);\n\t\tresult_vector.element.upstream_data = Some(content.clone());\n\n\t\t// Clean up the boolean operation result by merging duplicated points\n\t\tresult_vector.element.merge_by_distance_spatial(*result_vector.transform, 0.0001);\n\t}\n\n\tresult_vector_table\n}\n\n#[derive(Clone, Debug, Default, PartialEq, Eq)]\nstruct WindingNumber {\n\telems: SmallVec<[i16; 8]>,\n}\n\nimpl linesweeper::topology::WindingNumber for WindingNumber {\n\ttype Tag = (usize, usize);\n\n\tfn single((tag, out_of): (usize, usize), positive: bool) -> Self {\n\t\tlet mut elems = SmallVec::with_capacity(out_of);\n\t\telems.resize(out_of, 0);\n\t\telems[tag] = if positive { 1 } else { -1 };\n\t\tSelf { elems }\n\t}\n}\n\nimpl std::ops::AddAssign for WindingNumber {\n\tfn add_assign(&mut self, rhs: Self) {\n\t\tif rhs.elems.is_empty() {\n\t\t\treturn;\n\t\t}\n\t\tif self.elems.is_empty() {\n\t\t\tself.elems = rhs.elems;\n\t\t} else {\n\t\t\tfor (me, them) in self.elems.iter_mut().zip(&rhs.elems) {\n\t\t\t\t*me += *them;\n\t\t\t}\n\t\t}\n\t}\n}\n\nimpl std::ops::Add for WindingNumber {\n\ttype Output = WindingNumber;\n\n\tfn add(mut self, rhs: Self) -> Self::Output {\n\t\tself += rhs;\n\t\tself\n\t}\n}\n\nimpl WindingNumber {\n\tfn is_inside(&self, op: BooleanOperation) -> bool {\n\t\tlet is_in = |w: &i16| *w != 0;\n\t\tlet is_out = |w: &i16| *w == 0;\n\t\tmatch op {\n\t\t\tBooleanOperation::Union => self.elems.iter().any(is_in),\n\t\t\tBooleanOperation::SubtractFront => self.elems.first().is_some_and(is_in) && self.elems.iter().skip(1).all(is_out),\n\t\t\tBooleanOperation::SubtractBack => self.elems.last().is_some_and(is_in) && self.elems.iter().rev().skip(1).all(is_out),\n\t\t\tBooleanOperation::Intersect => !self.elems.is_empty() && self.elems.iter().all(is_in),\n\t\t\tBooleanOperation::Difference => self.elems.iter().any(is_in) && !self.elems.iter().all(is_in),\n\t\t}\n\t}\n}\n\nfn boolean_operation_on_vector_table<'a>(vector: impl DoubleEndedIterator<Item = TableRowRef<'a, Vector>> + Clone, boolean_operation: BooleanOperation) -> Table<Vector> {\n\tconst EPSILON: f64 = 1e-5;\n\tlet mut table = Table::new();\n\tlet mut paths = Vec::new();\n\tlet mut row = TableRow::<Vector>::default();\n\n\tlet copy_from = if matches!(boolean_operation, BooleanOperation::SubtractFront) {\n\t\tvector.clone().next()\n\t} else {\n\t\tvector.clone().next_back()\n\t};\n\tif let Some(copy_from) = copy_from {\n\t\trow.alpha_blending = *copy_from.alpha_blending;\n\t\trow.source_node_id = *copy_from.source_node_id;\n\t\trow.element.style = copy_from.element.style.clone();\n\t\trow.element.upstream_data = copy_from.element.upstream_data.clone();\n\t}\n\n\tfor element in vector {\n\t\tpaths.push(to_bez_path(element.element, *element.transform));\n\t}\n\n\tlet top = match Topology::<WindingNumber>::from_paths(paths.iter().enumerate().map(|(idx, path)| (path, (idx, paths.len()))), EPSILON) {\n\t\tOk(top) => top,\n\t\tErr(e) => {\n\t\t\tlog::error!(\"Boolean operation failed while building topology: {e}\");\n\t\t\ttable.push(row);\n\t\t\treturn table;\n\t\t}\n\t};\n\tlet contours = top.contours(|winding| winding.is_inside(boolean_operation));\n\n\tfor subpath in from_bez_paths(contours.contours().map(|c| &c.path)) {\n\t\trow.element.append_subpath(subpath, false);\n\t}\n\n\ttable.push(row);\n\ttable\n}\n\nfn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {\n\tgraphic_table\n\t\t.iter()\n\t\t.flat_map(|element| {\n\t\t\tmatch element.element.clone() {\n\t\t\t\tGraphic::Vector(vector) => {\n\t\t\t\t\t// Apply the parent graphic's transform to each element of the vector table\n\t\t\t\t\tvector\n\t\t\t\t\t\t.into_iter()\n\t\t\t\t\t\t.map(|mut sub_vector| {\n\t\t\t\t\t\t\tsub_vector.transform = *element.transform * sub_vector.transform;\n\n\t\t\t\t\t\t\tsub_vector\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.collect::<Vec<_>>()\n\t\t\t\t}\n\t\t\t\tGraphic::RasterCPU(image) => {\n\t\t\t\t\tlet make_row = |transform| {\n\t\t\t\t\t\t// Convert the image frame into a rectangular subpath with the image's transform\n\t\t\t\t\t\tlet mut subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);\n\t\t\t\t\t\tsubpath.apply_transform(transform);\n\n\t\t\t\t\t\t// Create a vector table row from the rectangular subpath, with a default black fill\n\t\t\t\t\t\tlet mut element = Vector::from_subpath(subpath);\n\t\t\t\t\t\telement.style.set_fill(Fill::Solid(Color::BLACK));\n\n\t\t\t\t\t\tTableRow { element, ..Default::default() }\n\t\t\t\t\t};\n\n\t\t\t\t\t// Apply the parent graphic's transform to each raster element\n\t\t\t\t\timage.iter().map(|row| make_row(*element.transform * *row.transform)).collect::<Vec<_>>()\n\t\t\t\t}\n\t\t\t\tGraphic::RasterGPU(image) => {\n\t\t\t\t\tlet make_row = |transform| {\n\t\t\t\t\t\t// Convert the image frame into a rectangular subpath with the image's transform\n\t\t\t\t\t\tlet mut subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);\n\t\t\t\t\t\tsubpath.apply_transform(transform);\n\n\t\t\t\t\t\t// Create a vector table row from the rectangular subpath, with a default black fill\n\t\t\t\t\t\tlet mut element = Vector::from_subpath(subpath);\n\t\t\t\t\t\telement.style.set_fill(Fill::Solid(Color::BLACK));\n\n\t\t\t\t\t\tTableRow { element, ..Default::default() }\n\t\t\t\t\t};\n\n\t\t\t\t\t// Apply the parent graphic's transform to each raster element\n\t\t\t\t\timage.iter().map(|row| make_row(*element.transform * *row.transform)).collect::<Vec<_>>()\n\t\t\t\t}\n\t\t\t\tGraphic::Graphic(mut graphic) => {\n\t\t\t\t\t// Apply the parent graphic's transform to each element of inner table\n\t\t\t\t\tfor sub_element in graphic.iter_mut() {\n\t\t\t\t\t\t*sub_element.transform = *element.transform * *sub_element.transform;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Recursively flatten the inner table into the output vector table\n\t\t\t\t\tlet unioned = boolean_operation_on_vector_table(flatten_vector(&graphic).iter(), BooleanOperation::Union);\n\n\t\t\t\t\tunioned.into_iter().collect::<Vec<_>>()\n\t\t\t\t}\n\t\t\t\tGraphic::Color(color) => color\n\t\t\t\t\t.into_iter()\n\t\t\t\t\t.map(|row| {\n\t\t\t\t\t\tlet mut element = Vector::default();\n\t\t\t\t\t\telement.style.set_fill(Fill::Solid(row.element));\n\t\t\t\t\t\telement.style.set_stroke_transform(DAffine2::IDENTITY);\n\n\t\t\t\t\t\tTableRow {\n\t\t\t\t\t\t\telement,\n\t\t\t\t\t\t\ttransform: row.transform,\n\t\t\t\t\t\t\talpha_blending: row.alpha_blending,\n\t\t\t\t\t\t\tsource_node_id: row.source_node_id,\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\t.collect::<Vec<_>>(),\n\t\t\t\tGraphic::Gradient(gradient) => gradient\n\t\t\t\t\t.into_iter()\n\t\t\t\t\t.map(|row| {\n\t\t\t\t\t\tlet mut element = Vector::default();\n\t\t\t\t\t\telement.style.set_fill(Fill::Gradient(graphic_types::vector_types::gradient::Gradient {\n\t\t\t\t\t\t\tstops: row.element,\n\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t}));\n\t\t\t\t\t\telement.style.set_stroke_transform(DAffine2::IDENTITY);\n\n\t\t\t\t\t\tTableRow {\n\t\t\t\t\t\t\telement,\n\t\t\t\t\t\t\ttransform: row.transform,\n\t\t\t\t\t\t\talpha_blending: row.alpha_blending,\n\t\t\t\t\t\t\tsource_node_id: row.source_node_id,\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\t.collect::<Vec<_>>(),\n\t\t\t}\n\t\t})\n\t\t.collect()\n}\n\n// This quantization should potentially be removed since it's not conceptually necessary,\n// but without it, the oak leaf in the Changing Seasons demo artwork is funky because\n// quantization is needed for the top and bottom points to line up vertically.\nfn quantize_segment(seg: PathSeg) -> PathSeg {\n\tconst QUANTIZE_EPS: f64 = 1e-8;\n\tfn q(p: Point) -> Point {\n\t\tPoint::new((p.x / QUANTIZE_EPS).round() * QUANTIZE_EPS, (p.y / QUANTIZE_EPS).round() * QUANTIZE_EPS)\n\t}\n\n\tmatch seg {\n\t\tPathSeg::Line(s) => PathSeg::Line(Line::new(q(s.p0), q(s.p1))),\n\t\tPathSeg::Quad(s) => PathSeg::Quad(QuadBez::new(q(s.p0), q(s.p1), q(s.p2))),\n\t\tPathSeg::Cubic(s) => PathSeg::Cubic(CubicBez::new(q(s.p0), q(s.p1), q(s.p2), q(s.p3))),\n\t}\n}\n\nfn to_bez_path(vector: &Vector, transform: DAffine2) -> BezPath {\n\tlet mut path = BezPath::new();\n\tfor subpath in vector.stroke_bezier_paths() {\n\t\tpush_subpath(&mut path, &subpath, transform);\n\t}\n\tpath\n}\n\nfn push_subpath(path: &mut BezPath, subpath: &Subpath<PointId>, transform: DAffine2) {\n\tlet transform = Affine::new(transform.to_cols_array());\n\tlet mut first = true;\n\n\tfor seg in subpath.iter_closed() {\n\t\tlet quantized = quantize_segment(transform * seg);\n\t\tif first {\n\t\t\tfirst = false;\n\t\t\tpath.move_to(quantized.start());\n\t\t}\n\t\tpath.push(quantized.as_path_el());\n\t}\n\tpath.close_path();\n}\n\nfn from_bez_paths<'a>(paths: impl Iterator<Item = &'a BezPath>) -> Vec<Subpath<PointId>> {\n\tlet mut all_subpaths = Vec::new();\n\n\tfor path in paths {\n\t\tlet cubics: Vec<CubicBez> = path.segments().map(|segment| segment.to_cubic()).collect();\n\t\tlet mut manipulators_list = Vec::new();\n\t\tlet mut current_start = None;\n\n\t\tfor (index, cubic) in cubics.iter().enumerate() {\n\t\t\tlet d = |p: Point| DVec2::new(p.x, p.y);\n\t\t\tlet [start, handle1, handle2, end] = [d(cubic.p0), d(cubic.p1), d(cubic.p2), d(cubic.p3)];\n\n\t\t\tif current_start.is_none() {\n\t\t\t\t// Use the correct in-handle (None) and out-handle for the start point\n\t\t\t\tmanipulators_list.push(ManipulatorGroup::new(start, None, Some(handle1)));\n\t\t\t} else {\n\t\t\t\t// Update the out-handle of the previous point\n\t\t\t\tif let Some(last) = manipulators_list.last_mut() {\n\t\t\t\t\tlast.out_handle = Some(handle1);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Add the end point with the correct in-handle and out-handle (None)\n\t\t\tmanipulators_list.push(ManipulatorGroup::new(end, Some(handle2), None));\n\n\t\t\tcurrent_start = Some(end);\n\n\t\t\t// Check if this is the last segment\n\t\t\tif index == cubics.len() - 1 {\n\t\t\t\tall_subpaths.push(Subpath::new(manipulators_list, true));\n\t\t\t\tmanipulators_list = Vec::new(); // Reset manipulators for the next path\n\t\t\t}\n\t\t}\n\t}\n\n\tall_subpaths\n}\n\npub fn boolean_intersect(a: &BezPath, b: &BezPath) -> Vec<BezPath> {\n\tmatch binary_op(a, b, FillRule::NonZero, BinaryOp::Intersection) {\n\t\tOk(contours) => contours.contours().map(|c| c.path.clone()).collect(),\n\t\tErr(e) => {\n\t\t\tlog::error!(\"Boolean Operation failed (a: {} segments, b: {} segments): {e}\", a.segments().count(), b.segments().count());\n\t\t\tVec::new()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "node-graph/nodes/raster/Cargo.toml",
    "content": "[package]\nname = \"raster-nodes\"\nversion = \"0.1.0\"\nedition = \"2024\"\ndescription = \"Raster operation nodes for Graphene\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\nlicense = \"MIT OR Apache-2.0\"\n\n[lints]\nworkspace = true\n\n[features]\ndefault = [\"std\"]\nshader-nodes = [\"std\", \"dep:raster-nodes-shaders\", \"dep:wgpu-executor\"]\nstd = [\n\t\"dep:core-types\",\n\t\"dep:dyn-any\",\n\t\"dep:raster-types\",\n\t\"dep:vector-types\",\n\t\"dep:image\",\n\t\"dep:ndarray\",\n\t\"dep:rand\",\n\t\"dep:rand_chacha\",\n\t\"dep:fastnoise-lite\",\n\t\"dep:serde\",\n\t\"dep:kurbo\",\n]\nwasm = [\n\t\"core-types/wasm\",\n\t\"raster-types/wasm\",\n\t\"vector-types/wasm\",\n\t\"tsify\",\n\t\"wasm-bindgen\",\n]\n\n[dependencies]\n# Local dependencies\nno-std-types = { workspace = true }\nnode-macro = { workspace = true }\n\n# Local std dependencies\ndyn-any = { workspace = true, optional = true }\ncore-types = { workspace = true, optional = true }\nraster-types = { workspace = true, optional = true }\nvector-types = { workspace = true, optional = true }\nwgpu-executor = { workspace = true, optional = true }\nraster-nodes-shaders = { path = \"./shaders\", optional = true }\n\n# Workspace dependencies\nbytemuck = { workspace = true }\nglam = { workspace = true }\nspirv-std = { workspace = true }\nnum-traits = { workspace = true }\nnum_enum = { workspace = true }\n\n# Workspace std dependencies\nimage = { workspace = true, optional = true }\nndarray = { workspace = true, optional = true }\nrand = { workspace = true, optional = true }\nrand_chacha = { workspace = true, optional = true }\nfastnoise-lite = { workspace = true, optional = true }\nserde = { workspace = true, optional = true }\nkurbo = { workspace = true, optional = true }\n\n# Workspace wasm dependencies\ntsify = { workspace = true, optional = true }\nwasm-bindgen = { workspace = true, optional = true }\n\n[dev-dependencies]\ntokio = { workspace = true }\nfutures = { workspace = true }\n"
  },
  {
    "path": "node-graph/nodes/raster/shaders/Cargo.toml",
    "content": "[package]\nname = \"raster-nodes-shaders\"\nversion = \"0.1.0\"\nedition = \"2024\"\ndescription = \"graphene raster data format\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\nlicense = \"MIT OR Apache-2.0\"\n\n[lib]\ncrate-type = [\"rlib\", \"dylib\"]\n\n[dependencies]\n\n[build-dependencies]\ncargo-gpu = { workspace = true }\nenv_logger = { workspace = true }\nlog = { workspace = true }\n"
  },
  {
    "path": "node-graph/nodes/raster/shaders/build.rs",
    "content": "use cargo_gpu::InstalledBackend;\nuse cargo_gpu::spirv_builder::{MetadataPrintout, SpirvMetadata};\nuse std::path::PathBuf;\n\npub fn main() -> Result<(), Box<dyn std::error::Error>> {\n\tenv_logger::builder().filter_level(log::LevelFilter::Debug).init();\n\n\t// Skip building the shaders if they are provided externally\n\tprintln!(\"cargo:rerun-if-env-changed=RASTER_NODES_SHADER_PATH\");\n\tif !std::env::var(\"RASTER_NODES_SHADER_PATH\").unwrap_or_default().is_empty() {\n\t\treturn Ok(());\n\t}\n\n\t// Allows overriding the PATH to inject the rust-gpu rust toolchain when building the rest of the project with stable rustc.\n\t// Used in nix shell. Do not remove without checking with developers using nix.\n\tprintln!(\"cargo:rerun-if-env-changed=RUST_GPU_PATH_OVERRIDE\");\n\tif let Ok(path_override) = std::env::var(\"RUST_GPU_PATH_OVERRIDE\") {\n\t\tlet current_path = std::env::var(\"PATH\").unwrap_or_default();\n\t\tlet new_path = format!(\"{path_override}:{current_path}\");\n\t\t// SAFETY: Build script is single-threaded therefore this cannot lead to undefined behavior.\n\t\tunsafe {\n\t\t\tstd::env::set_var(\"PATH\", &new_path);\n\t\t}\n\t}\n\n\tlet shader_crate = PathBuf::from(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/entrypoint\"));\n\n\tprintln!(\"cargo:rerun-if-env-changed=RUSTC_CODEGEN_SPIRV_PATH\");\n\tlet rustc_codegen_spirv_path = std::env::var(\"RUSTC_CODEGEN_SPIRV_PATH\").unwrap_or_default();\n\tlet backend = if rustc_codegen_spirv_path.is_empty() {\n\t\t// install the toolchain and build the `rustc_codegen_spirv` codegen backend with it\n\t\tcargo_gpu::Install::from_shader_crate(shader_crate.clone()).run()?\n\t} else {\n\t\t// use the `RUSTC_CODEGEN_SPIRV` environment variable to find the codegen backend\n\t\tlet mut backend = InstalledBackend::default();\n\t\tbackend.rustc_codegen_spirv_location = PathBuf::from(rustc_codegen_spirv_path);\n\t\tbackend.toolchain_channel = \"nightly\".to_string();\n\t\tbackend.target_spec_dir = PathBuf::from(env!(\"CARGO_MANIFEST_DIR\"));\n\t\tbackend\n\t};\n\n\t// build the shader crate\n\tlet mut builder = backend.to_spirv_builder(shader_crate, \"spirv-unknown-naga-wgsl\");\n\tbuilder.print_metadata = MetadataPrintout::DependencyOnly;\n\tbuilder.spirv_metadata = SpirvMetadata::Full;\n\tlet wgsl_result = builder.build()?;\n\tlet path_to_spv = wgsl_result.module.unwrap_single();\n\n\t// needs to be fixed upstream\n\tlet path_to_wgsl = path_to_spv.with_extension(\"wgsl\");\n\n\tprintln!(\"cargo::rustc-env=RASTER_NODES_SHADER_PATH={}\", path_to_wgsl.display());\n\tOk(())\n}\n"
  },
  {
    "path": "node-graph/nodes/raster/shaders/entrypoint/Cargo.toml",
    "content": "[package]\nname = \"raster-nodes-shaders-entrypoint\"\nversion = \"0.1.0\"\nedition = \"2024\"\ndescription = \"graphene raster nodes shaders entrypoint\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\nlicense = \"MIT OR Apache-2.0\"\n\n[lib]\ncrate-type = [\"rlib\", \"dylib\"]\n\n[dependencies]\nraster-nodes = { path = \"../..\", default-features = false }\n"
  },
  {
    "path": "node-graph/nodes/raster/shaders/entrypoint/src/lib.rs",
    "content": "#![no_std]\npub use raster_nodes::*;\n"
  },
  {
    "path": "node-graph/nodes/raster/shaders/spirv-unknown-naga-wgsl.json",
    "content": "{\n\t\"allows-weak-linkage\": false,\n\t\"arch\": \"spirv\",\n\t\"crt-objects-fallback\": \"false\",\n\t\"crt-static-allows-dylibs\": true,\n\t\"crt-static-respected\": true,\n\t\"data-layout\": \"e-m:e-p:32:32:32-i64:64-n8:16:32:64\",\n\t\"dll-prefix\": \"\",\n\t\"dll-suffix\": \".spv.json\",\n\t\"dynamic-linking\": true,\n\t\"emit-debug-gdb-scripts\": false,\n\t\"env\": \"naga-wgsl\",\n\t\"linker-flavor\": \"unix\",\n\t\"linker-is-gnu\": false,\n\t\"llvm-target\": \"spirv-unknown-naga-wgsl\",\n\t\"main-needs-argc-argv\": false,\n\t\"metadata\": {\n\t\t\"description\": null,\n\t\t\"host_tools\": null,\n\t\t\"std\": null,\n\t\t\"tier\": null\n\t},\n\t\"panic-strategy\": \"abort\",\n\t\"simd-types-indirect\": false,\n\t\"target-pointer-width\": \"32\"\n}\n"
  },
  {
    "path": "node-graph/nodes/raster/shaders/src/lib.rs",
    "content": "pub const WGSL_SHADER: &str = include_str!(env!(\"RASTER_NODES_SHADER_PATH\"));\n"
  },
  {
    "path": "node-graph/nodes/raster/src/adjust.rs",
    "content": "use no_std_types::color::Color;\n\npub trait Adjust<P> {\n\tfn adjust(&mut self, map_fn: impl Fn(&P) -> P);\n}\nimpl Adjust<Color> for Color {\n\tfn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {\n\t\t*self = map_fn(self);\n\t}\n}\n\n#[cfg(feature = \"std\")]\nmod adjust_std {\n\tuse super::*;\n\tuse core_types::table::Table;\n\tuse raster_types::{CPU, Raster};\n\tuse vector_types::GradientStops;\n\n\timpl Adjust<Color> for Table<Raster<CPU>> {\n\t\tfn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {\n\t\t\tfor row in self.iter_mut() {\n\t\t\t\tfor color in row.element.data_mut().data.iter_mut() {\n\t\t\t\t\t*color = map_fn(color);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\timpl Adjust<Color> for Table<Color> {\n\t\tfn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {\n\t\t\tfor row in self.iter_mut() {\n\t\t\t\t*row.element = map_fn(row.element);\n\t\t\t}\n\t\t}\n\t}\n\timpl Adjust<Color> for Table<GradientStops> {\n\t\tfn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {\n\t\t\tfor row in self.iter_mut() {\n\t\t\t\trow.element.adjust(&map_fn);\n\t\t\t}\n\t\t}\n\t}\n\timpl Adjust<Color> for GradientStops {\n\t\tfn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {\n\t\t\tfor color in self.color.iter_mut() {\n\t\t\t\t*color = map_fn(color);\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "node-graph/nodes/raster/src/adjustments.rs",
    "content": "#![allow(clippy::too_many_arguments)]\n\nuse crate::adjust::Adjust;\nuse crate::cubic_spline::CubicSplines;\nuse core::fmt::Debug;\n#[cfg(feature = \"std\")]\nuse core_types::table::Table;\nuse glam::{Vec3, Vec4};\nuse no_std_types::color::Color;\nuse no_std_types::context::Ctx;\nuse no_std_types::registry::types::{AngleF32, PercentageF32, SignedPercentageF32};\nuse node_macro::BufferStruct;\nuse num_enum::{FromPrimitive, IntoPrimitive};\n#[cfg(not(feature = \"std\"))]\nuse num_traits::float::Float;\n#[cfg(feature = \"std\")]\nuse raster_types::{CPU, Raster};\n#[cfg(feature = \"std\")]\nuse vector_types::GradientStops;\n\n// TODO: Implement the following:\n// Color Balance\n// Aims for interoperable compatibility with:\n// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27blnc%27%20%3D%20Color%20Balance\n//\n// Photo Filter\n// Aims for interoperable compatibility with:\n// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27phfl%27%20%3D%20Photo%20Filter\n// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=of%20the%20file.-,Photo%20Filter,-Key%20is%20%27phfl\n//\n// Color Lookup\n// Aims for interoperable compatibility with:\n// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27clrL%27%20%3D%20Color%20Lookup\n// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Color%20Lookup%20(Photoshop%20CS6\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[cfg_attr(feature = \"std\", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))]\n#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, node_macro::ChoiceType, bytemuck::NoUninit, BufferStruct, FromPrimitive, IntoPrimitive)]\n#[widget(Dropdown)]\n#[repr(u32)]\npub enum LuminanceCalculation {\n\t#[default]\n\t#[label(\"sRGB\")]\n\tSRGB,\n\tPerceptual,\n\tAverageChannels,\n\tMinimumChannels,\n\tMaximumChannels,\n}\n\n#[node_macro::node(category(\"Raster: Adjustment\"), shader_node(PerPixelAdjust))]\nfn luminance<T: Adjust<Color>>(\n\t_: impl Ctx,\n\t#[implementations(\n\t\tTable<Raster<CPU>>,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t)]\n\t#[gpu_image]\n\tmut input: T,\n\tluminance_calc: LuminanceCalculation,\n) -> T {\n\tinput.adjust(|color| {\n\t\tlet luminance = match luminance_calc {\n\t\t\tLuminanceCalculation::SRGB => color.luminance_srgb(),\n\t\t\tLuminanceCalculation::Perceptual => color.luminance_perceptual(),\n\t\t\tLuminanceCalculation::AverageChannels => color.average_rgb_channels(),\n\t\t\tLuminanceCalculation::MinimumChannels => color.minimum_rgb_channels(),\n\t\t\tLuminanceCalculation::MaximumChannels => color.maximum_rgb_channels(),\n\t\t};\n\t\tcolor.map_rgb(|_| luminance)\n\t});\n\tinput\n}\n\n#[node_macro::node(category(\"Raster: Adjustment\"), shader_node(PerPixelAdjust))]\nfn gamma_correction<T: Adjust<Color>>(\n\t_: impl Ctx,\n\t#[implementations(\n\t\tTable<Raster<CPU>>,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t)]\n\t#[gpu_image]\n\tmut input: T,\n\t#[default(2.2)]\n\t#[range((0.01, 10.))]\n\t#[hard_min(0.0001)]\n\tgamma: f32,\n\tinverse: bool,\n) -> T {\n\tlet exponent = if inverse { 1. / gamma } else { gamma };\n\tinput.adjust(|color| color.gamma(exponent));\n\tinput\n}\n\n#[node_macro::node(category(\"Raster: Channels\"), shader_node(PerPixelAdjust))]\nfn extract_channel<T: Adjust<Color>>(\n\t_: impl Ctx,\n\t#[implementations(\n\t\tTable<Raster<CPU>>,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t)]\n\t#[gpu_image]\n\tmut input: T,\n\tchannel: RedGreenBlueAlpha,\n) -> T {\n\tinput.adjust(|color| {\n\t\tlet extracted_value = match channel {\n\t\t\tRedGreenBlueAlpha::Red => color.r(),\n\t\t\tRedGreenBlueAlpha::Green => color.g(),\n\t\t\tRedGreenBlueAlpha::Blue => color.b(),\n\t\t\tRedGreenBlueAlpha::Alpha => color.a(),\n\t\t};\n\t\tcolor.map_rgb(|_| extracted_value).with_alpha(1.)\n\t});\n\tinput\n}\n\n#[node_macro::node(category(\"Raster: Channels\"), shader_node(PerPixelAdjust))]\nfn make_opaque<T: Adjust<Color>>(\n\t_: impl Ctx,\n\t#[implementations(\n\t\tTable<Raster<CPU>>,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t)]\n\t#[gpu_image]\n\tmut input: T,\n) -> T {\n\tinput.adjust(|color| {\n\t\tif color.a() == 0. {\n\t\t\treturn color.with_alpha(1.);\n\t\t}\n\t\tColor::from_rgbaf32_unchecked(color.r() / color.a(), color.g() / color.a(), color.b() / color.a(), 1.)\n\t});\n\tinput\n}\n\n// TODO: Remove this once GPU shader nodes are able to support the non-classic algorithm\n#[node_macro::node(\n\tname(\"Brightness/Contrast Classic\"),\n\tcategory(\"Raster: Adjustment\"),\n\tproperties(\"brightness_contrast_properties\"),\n\tshader_node(PerPixelAdjust)\n)]\nfn brightness_contrast_classic<T: Adjust<Color>>(\n\t_: impl Ctx,\n\t#[implementations(\n\t\tTable<Raster<CPU>>,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t)]\n\t#[gpu_image]\n\tmut input: T,\n\tbrightness: SignedPercentageF32,\n\tcontrast: SignedPercentageF32,\n) -> T {\n\tlet brightness = brightness / 255.;\n\n\tlet contrast = contrast / 100.;\n\tlet contrast = if contrast > 0. { (contrast * core::f32::consts::FRAC_PI_2 - 0.01).tan() } else { contrast };\n\n\tlet offset = brightness * contrast + brightness - contrast / 2.;\n\n\tinput.adjust(|color| color.to_gamma_srgb().map_rgb(|c| (c + c * contrast + offset).clamp(0., 1.)).to_linear_srgb());\n\n\tinput\n}\n\n// Aims for interoperable compatibility with:\n// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27brit%27%20%3D%20Brightness/Contrast\n// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Padding-,Brightness%20and%20Contrast,-Key%20is%20%27brit\n//\n// Some further analysis available at:\n// https://geraldbakker.nl/psnumbers/brightness-contrast.html\n#[node_macro::node(name(\"Brightness/Contrast\"), category(\"Raster: Adjustment\"), properties(\"brightness_contrast_properties\"), cfg(feature = \"std\"))]\nfn brightness_contrast<T: Adjust<Color>>(\n\t_ctx: impl Ctx,\n\t#[implementations(\n\t\tTable<Raster<CPU>>,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t)]\n\t#[gpu_image]\n\tmut input: T,\n\tbrightness: SignedPercentageF32,\n\tcontrast: SignedPercentageF32,\n\tuse_classic: bool,\n) -> T {\n\tif use_classic {\n\t\treturn brightness_contrast_classic(_ctx, input, brightness, contrast);\n\t}\n\n\tconst WINDOW_SIZE: usize = 1024;\n\n\t// Brightness LUT\n\tlet brightness_is_negative = brightness < 0.;\n\t// We clamp the brightness before the two curve X-axis points `130 - brightness * 26` and `233 - brightness * 48` intersect.\n\t// Beyond the point of intersection, the cubic spline fitting becomes invalid and fails an assertion, which we need to avoid.\n\t// See the intersection of the red lines at x = 103/22*100 = 468.18182 in the graph: https://www.desmos.com/calculator/ekvz4zyd9c\n\tlet brightness = (brightness.abs() / 100.).min(103. / 22. - 0.00001);\n\tlet brightness_curve_points = CubicSplines {\n\t\tx: [0., 130. - brightness * 26., 233. - brightness * 48., 255.].map(|x| x / 255.),\n\t\ty: [0., 130. + brightness * 51., 233. + brightness * 10., 255.].map(|x| x / 255.),\n\t};\n\tlet brightness_curve_solutions = brightness_curve_points.solve();\n\tlet mut brightness_lut: [f32; WINDOW_SIZE] = core::array::from_fn(|i| {\n\t\tlet x = i as f32 / (WINDOW_SIZE as f32 - 1.);\n\t\tbrightness_curve_points.interpolate(x, &brightness_curve_solutions)\n\t});\n\t// Special handling for when brightness is negative\n\tif brightness_is_negative {\n\t\tbrightness_lut = core::array::from_fn(|i| {\n\t\t\tlet mut x = i;\n\t\t\twhile x > 1 && brightness_lut[x] > i as f32 / WINDOW_SIZE as f32 {\n\t\t\t\tx -= 1;\n\t\t\t}\n\t\t\tx as f32 / WINDOW_SIZE as f32\n\t\t});\n\t}\n\n\t// Contrast LUT\n\t// Unlike with brightness, the X-axis points `64` and `192` don't intersect at any contrast value, because they are constants.\n\t// So we don't have to worry about clamping the contrast value to avoid invalid cubic spline fitting.\n\t// See the graph: https://www.desmos.com/calculator/iql9vsca56\n\tlet contrast = contrast / 100.;\n\tlet contrast_curve_points = CubicSplines {\n\t\tx: [0., 64., 192., 255.].map(|x| x / 255.),\n\t\ty: [0., 64. - contrast * 30., 192. + contrast * 30., 255.].map(|x| x / 255.),\n\t};\n\tlet contrast_curve_solutions = contrast_curve_points.solve();\n\tlet contrast_lut: [f32; WINDOW_SIZE] = core::array::from_fn(|i| {\n\t\tlet x = i as f32 / (WINDOW_SIZE as f32 - 1.);\n\t\tcontrast_curve_points.interpolate(x, &contrast_curve_solutions)\n\t});\n\n\t// Composed brightness and contrast LUTs\n\tlet combined_lut = brightness_lut.map(|brightness| {\n\t\tlet index_in_contrast_lut = (brightness * (contrast_lut.len() - 1) as f32).round() as usize;\n\t\tcontrast_lut[index_in_contrast_lut]\n\t});\n\tlet lut_max = (combined_lut.len() - 1) as f32;\n\n\tinput.adjust(|color| color.to_gamma_srgb().map_rgb(|c| combined_lut[(c * lut_max).round() as usize]).to_linear_srgb());\n\n\tinput\n}\n\n// Aims for interoperable compatibility with:\n// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=levl%27%20%3D%20Levels\n//\n// Algorithm from:\n// https://stackoverflow.com/questions/39510072/algorithm-for-adjustment-of-image-levels\n//\n// Some further analysis available at:\n// https://geraldbakker.nl/psnumbers/levels.html\n#[node_macro::node(category(\"Raster: Adjustment\"), shader_node(PerPixelAdjust))]\nfn levels<T: Adjust<Color>>(\n\t_: impl Ctx,\n\t#[implementations(\n\t\tTable<Raster<CPU>>,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t)]\n\t#[gpu_image]\n\tmut image: T,\n\t#[default(0.)] shadows: PercentageF32,\n\t#[default(50.)] midtones: PercentageF32,\n\t#[default(100.)] highlights: PercentageF32,\n\t#[default(0.)] output_minimums: PercentageF32,\n\t#[default(100.)] output_maximums: PercentageF32,\n) -> T {\n\timage.adjust(|color| {\n\t\tlet color = color.to_gamma_srgb();\n\n\t\t// Input Range (Range: 0-1)\n\t\tlet input_shadows = shadows / 100.;\n\t\tlet input_midtones = midtones / 100.;\n\t\tlet input_highlights = highlights / 100.;\n\n\t\t// Output Range (Range: 0-1)\n\t\tlet output_minimums = output_minimums / 100.;\n\t\tlet output_maximums = output_maximums / 100.;\n\n\t\t// Midtones interpolation factor between minimums and maximums (Range: 0-1)\n\t\tlet midtones = output_minimums + (output_maximums - output_minimums) * input_midtones;\n\n\t\t// Gamma correction (Range: 0.01-10)\n\t\tlet gamma = if midtones < 0.5 {\n\t\t\t// Range: 0-1\n\t\t\tlet x = 1. - midtones * 2.;\n\t\t\t// Range: 1-10\n\t\t\t1. + 9. * x\n\t\t} else {\n\t\t\t// Range: 0-0.5\n\t\t\tlet x = 1. - midtones;\n\t\t\t// Range: 0-1\n\t\t\tlet x = x * 2.;\n\t\t\t// Range: 0.01-1\n\t\t\tx.max(0.01)\n\t\t};\n\n\t\t// Input levels (Range: 0-1)\n\t\tlet highlights_minus_shadows = (input_highlights - input_shadows).clamp(f32::EPSILON, 1.);\n\t\tlet color = color.map_rgb(|c| ((c - input_shadows).max(0.) / highlights_minus_shadows).min(1.));\n\n\t\t// Midtones (Range: 0-1)\n\t\tlet color = color.gamma(gamma);\n\n\t\t// Output levels (Range: 0-1)\n\t\tlet color = color.map_rgb(|c| c * (output_maximums - output_minimums) + output_minimums);\n\n\t\tcolor.to_linear_srgb()\n\t});\n\timage\n}\n\n// Aims for interoperable compatibility with:\n// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27blwh%27%20%3D%20Black%20and%20White\n// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Black%20White%20(Photoshop%20CS3)\n//\n// Algorithm from:\n// https://stackoverflow.com/a/55233732/775283\n// Works the same for gamma and linear color\n#[node_macro::node(name(\"Black & White\"), category(\"Raster: Adjustment\"), shader_node(PerPixelAdjust))]\nfn black_and_white<T: Adjust<Color>>(\n\t_: impl Ctx,\n\t#[implementations(\n\t\tTable<Raster<CPU>>,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t)]\n\t#[gpu_image]\n\tmut image: T,\n\t#[default(Color::BLACK)] tint: Color,\n\t#[default(40.)]\n\t#[range((-200., 300.))]\n\treds: PercentageF32,\n\t#[default(60.)]\n\t#[range((-200., 300.))]\n\tyellows: PercentageF32,\n\t#[default(40.)]\n\t#[range((-200., 300.))]\n\tgreens: PercentageF32,\n\t#[default(60.)]\n\t#[range((-200., 300.))]\n\tcyans: PercentageF32,\n\t#[default(20.)]\n\t#[range((-200., 300.))]\n\tblues: PercentageF32,\n\t#[default(80.)]\n\t#[range((-200., 300.))]\n\tmagentas: PercentageF32,\n) -> T {\n\timage.adjust(|color| {\n\t\tlet color = color.to_gamma_srgb();\n\n\t\tlet reds = reds / 100.;\n\t\tlet yellows = yellows / 100.;\n\t\tlet greens = greens / 100.;\n\t\tlet cyans = cyans / 100.;\n\t\tlet blues = blues / 100.;\n\t\tlet magentas = magentas / 100.;\n\n\t\tlet gray_base = color.r().min(color.g()).min(color.b());\n\n\t\tlet red_part = color.r() - gray_base;\n\t\tlet green_part = color.g() - gray_base;\n\t\tlet blue_part = color.b() - gray_base;\n\t\tlet alpha_part = color.a();\n\n\t\tlet additional = if red_part == 0. {\n\t\t\tlet cyan_part = green_part.min(blue_part);\n\t\t\tcyan_part * cyans + (green_part - cyan_part) * greens + (blue_part - cyan_part) * blues\n\t\t} else if green_part == 0. {\n\t\t\tlet magenta_part = red_part.min(blue_part);\n\t\t\tmagenta_part * magentas + (red_part - magenta_part) * reds + (blue_part - magenta_part) * blues\n\t\t} else {\n\t\t\tlet yellow_part = red_part.min(green_part);\n\t\t\tyellow_part * yellows + (red_part - yellow_part) * reds + (green_part - yellow_part) * greens\n\t\t};\n\n\t\tlet luminance = gray_base + additional;\n\n\t\t// TODO: Fix \"Color\" blend mode implementation so it matches the expected behavior perfectly (it's currently close)\n\t\tlet color = tint.with_luminance(luminance);\n\n\t\tlet color = Color::from_rgbaf32_unchecked(color.r(), color.g(), color.b(), alpha_part);\n\n\t\tcolor.to_linear_srgb()\n\t});\n\timage\n}\n\n// Aims for interoperable compatibility with:\n// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27hue%20%27%20%3D%20Old,saturation%2C%20Photoshop%205.0\n// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=0%20%3D%20Use%20other.-,Hue/Saturation,-Hue/Saturation%20settings\n#[node_macro::node(name(\"Hue/Saturation\"), category(\"Raster: Adjustment\"), shader_node(PerPixelAdjust))]\nfn hue_saturation<T: Adjust<Color>>(\n\t_: impl Ctx,\n\t#[implementations(\n\t\tTable<Raster<CPU>>,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t)]\n\t#[gpu_image]\n\tmut input: T,\n\thue_shift: AngleF32,\n\tsaturation_shift: SignedPercentageF32,\n\tlightness_shift: SignedPercentageF32,\n) -> T {\n\tinput.adjust(|color| {\n\t\tlet color = color.to_gamma_srgb();\n\n\t\tlet [hue, saturation, lightness, alpha] = color.to_hsla();\n\n\t\tlet color = Color::from_hsla(\n\t\t\t(hue + hue_shift / 360.) % 1.,\n\t\t\t// TODO: Improve the way saturation works (it's slightly off)\n\t\t\t(saturation + saturation_shift / 100.).clamp(0., 1.),\n\t\t\t// TODO: Fix the way lightness works (it's very off)\n\t\t\t(lightness + lightness_shift / 100.).clamp(0., 1.),\n\t\t\talpha,\n\t\t);\n\n\t\tcolor.to_linear_srgb()\n\t});\n\tinput\n}\n\n// Aims for interoperable compatibility with:\n// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27%20%3D%20Color%20Lookup-,%27nvrt%27%20%3D%20Invert,-%27post%27%20%3D%20Posterize\n#[node_macro::node(category(\"Raster: Adjustment\"), shader_node(PerPixelAdjust))]\nfn invert<T: Adjust<Color>>(\n\t_: impl Ctx,\n\t#[implementations(\n\t\tTable<Raster<CPU>>,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t)]\n\t#[gpu_image]\n\tmut input: T,\n) -> T {\n\tinput.adjust(|color| {\n\t\tlet color = color.to_gamma_srgb();\n\n\t\tlet color = color.map_rgb(|c| color.a() - c);\n\n\t\tcolor.to_linear_srgb()\n\t});\n\tinput\n}\n\n// Aims for interoperable compatibility with:\n// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=post%27%20%3D%20Posterize-,%27thrs%27%20%3D%20Threshold,-%27grdm%27%20%3D%20Gradient\n#[node_macro::node(category(\"Raster: Adjustment\"), shader_node(PerPixelAdjust))]\nfn threshold<T: Adjust<Color>>(\n\t_: impl Ctx,\n\t#[implementations(\n\t\tTable<Raster<CPU>>,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t)]\n\t#[gpu_image]\n\tmut image: T,\n\t#[default(50.)] min_luminance: PercentageF32,\n\t#[default(100.)] max_luminance: PercentageF32,\n\tluminance_calc: LuminanceCalculation,\n) -> T {\n\timage.adjust(|color| {\n\t\tlet min_luminance = Color::srgb_to_linear(min_luminance / 100.);\n\t\tlet max_luminance = Color::srgb_to_linear(max_luminance / 100.);\n\n\t\tlet luminance = match luminance_calc {\n\t\t\tLuminanceCalculation::SRGB => color.luminance_srgb(),\n\t\t\tLuminanceCalculation::Perceptual => color.luminance_perceptual(),\n\t\t\tLuminanceCalculation::AverageChannels => color.average_rgb_channels(),\n\t\t\tLuminanceCalculation::MinimumChannels => color.minimum_rgb_channels(),\n\t\t\tLuminanceCalculation::MaximumChannels => color.maximum_rgb_channels(),\n\t\t};\n\n\t\tif luminance >= min_luminance && luminance <= max_luminance { Color::WHITE } else { Color::BLACK }\n\t});\n\timage\n}\n\n// Aims for interoperable compatibility with:\n// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27-,vibA%27%20%3D%20Vibrance,-%27hue%20%27%20%3D%20Old\n// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Vibrance%20(Photoshop%20CS3)\n//\n// Algorithm based on:\n// https://stackoverflow.com/questions/33966121/what-is-the-algorithm-for-vibrance-filters\n// The results of this implementation are very close to correct, but not quite perfect.\n//\n// Some further analysis available at:\n// https://www.photo-mark.com/notes/analyzing-photoshop-vibrance-and-saturation/\n//\n// This algorithm is currently lacking a \"Saturation\" parameter which is needed for interoperability.\n// It's not the same as the saturation component of Hue/Saturation/Value. Vibrance and Saturation are both separable.\n// When both parameters are set, it is equivalent to running this adjustment twice, with only vibrance set and then only saturation set.\n// (Except for some noise probably due to rounding error.)\n#[node_macro::node(category(\"Raster: Adjustment\"), shader_node(PerPixelAdjust))]\nfn vibrance<T: Adjust<Color>>(\n\t_: impl Ctx,\n\t#[implementations(\n\t\tTable<Raster<CPU>>,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t)]\n\t#[gpu_image]\n\tmut image: T,\n\tvibrance: SignedPercentageF32,\n) -> T {\n\timage.adjust(|color| {\n\t\tlet vibrance = vibrance / 100.;\n\t\t// Slow the effect down by half when it's negative, since artifacts begin appearing past -50%.\n\t\t// So this scales the 0% to -50% range to 0% to -100%.\n\t\tlet slowed_vibrance = if vibrance >= 0. { vibrance } else { vibrance * 0.5 };\n\n\t\tlet channel_max = color.r().max(color.g()).max(color.b());\n\t\tlet channel_min = color.r().min(color.g()).min(color.b());\n\t\tlet channel_difference = channel_max - channel_min;\n\n\t\tlet scale_multiplier = if channel_max == color.r() {\n\t\t\tlet green_blue_difference = (color.g() - color.b()).abs();\n\t\t\tlet t = (green_blue_difference / channel_difference).min(1.);\n\t\t\tt * 0.5 + 0.5\n\t\t} else {\n\t\t\t1.\n\t\t};\n\t\tlet scale = slowed_vibrance * scale_multiplier * (2. - channel_difference);\n\t\tlet channel_reduction = channel_min * scale;\n\t\tlet scale = 1. + scale * (1. - channel_difference);\n\n\t\tlet luminance_initial = color.to_linear_srgb().luminance_srgb();\n\t\tlet altered_color = color.map_rgb(|c| c * scale - channel_reduction).to_linear_srgb();\n\t\tlet luminance = altered_color.luminance_srgb();\n\t\tlet altered_color = altered_color.map_rgb(|c| c * luminance_initial / luminance);\n\n\t\tlet channel_max = altered_color.r().max(altered_color.g()).max(altered_color.b());\n\t\tlet altered_color = if Color::linear_to_srgb(channel_max) > 1. {\n\t\t\tlet scale = (1. - luminance) / (channel_max - luminance);\n\t\t\taltered_color.map_rgb(|c| (c - luminance) * scale + luminance)\n\t\t} else {\n\t\t\taltered_color\n\t\t};\n\t\tlet altered_color = altered_color.to_gamma_srgb();\n\n\t\tif vibrance >= 0. {\n\t\t\taltered_color\n\t\t} else {\n\t\t\t// TODO: The result ends up a bit darker than it should be, further investigation is needed\n\t\t\tlet luminance = color.luminance_rec_601();\n\n\t\t\t// Near -0% vibrance we mostly use `altered_color`.\n\t\t\t// Near -100% vibrance, we mostly use half the desaturated luminance color and half `altered_color`.\n\t\t\tlet factor = -slowed_vibrance;\n\t\t\taltered_color.map_rgb(|c| c * (1. - factor) + luminance * factor)\n\t\t}\n\t});\n\timage\n}\n\n#[repr(u32)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[cfg_attr(feature = \"std\", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))]\n#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType, BufferStruct, FromPrimitive, IntoPrimitive)]\n#[widget(Radio)]\npub enum RedGreenBlue {\n\t#[default]\n\tRed,\n\tGreen,\n\tBlue,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[cfg_attr(feature = \"std\", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))]\n#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType, bytemuck::NoUninit, BufferStruct, FromPrimitive, IntoPrimitive)]\n#[widget(Radio)]\n#[repr(u32)]\npub enum RedGreenBlueAlpha {\n\t#[default]\n\tRed,\n\tGreen,\n\tBlue,\n\tAlpha,\n}\n\n/// Style of noise pattern.\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[cfg_attr(feature = \"std\", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))]\n#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)]\n#[widget(Dropdown)]\npub enum NoiseType {\n\t#[default]\n\tPerlin,\n\t#[label(\"OpenSimplex2\")]\n\tOpenSimplex2,\n\t#[label(\"OpenSimplex2S\")]\n\tOpenSimplex2S,\n\tCellular,\n\tValueCubic,\n\tValue,\n\tWhiteNoise,\n}\n\n/// Style of layered levels of the noise pattern.\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[cfg_attr(feature = \"std\", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))]\n#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)]\npub enum FractalType {\n\t#[default]\n\tNone,\n\t#[label(\"Fractional Brownian Motion\")]\n\tFBm,\n\tRidged,\n\tPingPong,\n\t#[label(\"Progressive (Domain Warp Only)\")]\n\tDomainWarpProgressive,\n\t#[label(\"Independent (Domain Warp Only)\")]\n\tDomainWarpIndependent,\n}\n\n/// Distance function used by the cellular noise.\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[cfg_attr(feature = \"std\", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))]\n#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)]\npub enum CellularDistanceFunction {\n\t#[default]\n\tEuclidean,\n\t#[label(\"Euclidean Squared (Faster)\")]\n\tEuclideanSq,\n\tManhattan,\n\tHybrid,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[cfg_attr(feature = \"std\", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))]\n#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)]\npub enum CellularReturnType {\n\tCellValue,\n\t#[default]\n\t#[label(\"Nearest (F1)\")]\n\tNearest,\n\t#[label(\"Next Nearest (F2)\")]\n\tNextNearest,\n\t#[label(\"Average (F1 / 2 + F2 / 2)\")]\n\tAverage,\n\t#[label(\"Difference (F2 - F1)\")]\n\tDifference,\n\t#[label(\"Product (F2 * F1 / 2)\")]\n\tProduct,\n\t#[label(\"Division (F1 / F2)\")]\n\tDivision,\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[cfg_attr(feature = \"std\", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))]\n#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)]\n#[widget(Dropdown)]\npub enum DomainWarpType {\n\t#[default]\n\tNone,\n\t#[label(\"OpenSimplex2\")]\n\tOpenSimplex2,\n\t#[label(\"OpenSimplex2 Reduced\")]\n\tOpenSimplex2Reduced,\n\tBasicGrid,\n}\n\n// Aims for interoperable compatibility with:\n// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27mixr%27%20%3D%20Channel%20Mixer\n// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Lab%20color%20only-,Channel%20Mixer,-Key%20is%20%27mixr\n#[node_macro::node(category(\"Raster: Adjustment\"), properties(\"channel_mixer_properties\"), shader_node(PerPixelAdjust))]\nfn channel_mixer<T: Adjust<Color>>(\n\t_: impl Ctx,\n\t#[implementations(\n\t\tTable<Raster<CPU>>,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t)]\n\t#[gpu_image]\n\tmut image: T,\n\n\tmonochrome: bool,\n\n\t#[default(40.)]\n\t#[name(\"Red\")]\n\tmonochrome_r: f32,\n\t#[default(40.)]\n\t#[name(\"Green\")]\n\tmonochrome_g: f32,\n\t#[default(20.)]\n\t#[name(\"Blue\")]\n\tmonochrome_b: f32,\n\t#[default(0.)]\n\t#[name(\"Constant\")]\n\tmonochrome_c: f32,\n\n\t#[default(100.)]\n\t#[name(\"(Red) Red\")]\n\tred_r: f32,\n\t#[default(0.)]\n\t#[name(\"(Red) Green\")]\n\tred_g: f32,\n\t#[default(0.)]\n\t#[name(\"(Red) Blue\")]\n\tred_b: f32,\n\t#[default(0.)]\n\t#[name(\"(Red) Constant\")]\n\tred_c: f32,\n\n\t#[default(0.)]\n\t#[name(\"(Green) Red\")]\n\tgreen_r: f32,\n\t#[default(100.)]\n\t#[name(\"(Green) Green\")]\n\tgreen_g: f32,\n\t#[default(0.)]\n\t#[name(\"(Green) Blue\")]\n\tgreen_b: f32,\n\t#[default(0.)]\n\t#[name(\"(Green) Constant\")]\n\tgreen_c: f32,\n\n\t#[default(0.)]\n\t#[name(\"(Blue) Red\")]\n\tblue_r: f32,\n\t#[default(0.)]\n\t#[name(\"(Blue) Green\")]\n\tblue_g: f32,\n\t#[default(100.)]\n\t#[name(\"(Blue) Blue\")]\n\tblue_b: f32,\n\t#[default(0.)]\n\t#[name(\"(Blue) Constant\")]\n\tblue_c: f32,\n\n\t// Display-only properties (not used within the node)\n\t_output_channel: RedGreenBlue,\n) -> T {\n\timage.adjust(|color| {\n\t\tlet color = color.to_gamma_srgb();\n\n\t\tlet (r, g, b, a) = color.components();\n\n\t\tlet color = if monochrome {\n\t\t\tlet (monochrome_r, monochrome_g, monochrome_b, monochrome_c) = (monochrome_r / 100., monochrome_g / 100., monochrome_b / 100., monochrome_c / 100.);\n\n\t\t\tlet gray = (r * monochrome_r + g * monochrome_g + b * monochrome_b + monochrome_c).clamp(0., 1.);\n\n\t\t\tColor::from_rgbaf32_unchecked(gray, gray, gray, a)\n\t\t} else {\n\t\t\tlet (red_r, red_g, red_b, red_c) = (red_r / 100., red_g / 100., red_b / 100., red_c / 100.);\n\t\t\tlet (green_r, green_g, green_b, green_c) = (green_r / 100., green_g / 100., green_b / 100., green_c / 100.);\n\t\t\tlet (blue_r, blue_g, blue_b, blue_c) = (blue_r / 100., blue_g / 100., blue_b / 100., blue_c / 100.);\n\n\t\t\tlet red = (r * red_r + g * red_g + b * red_b + red_c).clamp(0., 1.);\n\t\t\tlet green = (r * green_r + g * green_g + b * green_b + green_c).clamp(0., 1.);\n\t\t\tlet blue = (r * blue_r + g * blue_g + b * blue_b + blue_c).clamp(0., 1.);\n\n\t\t\tColor::from_rgbaf32_unchecked(red, green, blue, a)\n\t\t};\n\n\t\tcolor.to_linear_srgb()\n\t});\n\timage\n}\n\n#[repr(u32)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[cfg_attr(feature = \"std\", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))]\n#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType, BufferStruct, FromPrimitive, IntoPrimitive)]\n#[widget(Radio)]\npub enum RelativeAbsolute {\n\t#[default]\n\tRelative,\n\tAbsolute,\n}\n\n#[repr(u32)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[cfg_attr(feature = \"std\", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))]\n#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType, BufferStruct, FromPrimitive, IntoPrimitive)]\npub enum SelectiveColorChoice {\n\t#[default]\n\tReds,\n\tYellows,\n\tGreens,\n\tCyans,\n\tBlues,\n\tMagentas,\n\n\t#[menu_separator]\n\tWhites,\n\tNeutrals,\n\tBlacks,\n}\n\n// Aims for interoperable compatibility with:\n// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27selc%27%20%3D%20Selective%20color\n// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=from%20%2D100...100.%20.-,Selective%20Color,-Selective%20Color%20settings\n//\n// Algorithm based on:\n// https://blog.pkh.me/p/22-understanding-selective-coloring-in-adobe-photoshop.html\n#[node_macro::node(category(\"Raster: Adjustment\"), properties(\"selective_color_properties\"), shader_node(PerPixelAdjust))]\nfn selective_color<T: Adjust<Color>>(\n\t_: impl Ctx,\n\t#[implementations(\n\t\tTable<Raster<CPU>>,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t)]\n\t#[gpu_image]\n\tmut image: T,\n\n\tmode: RelativeAbsolute,\n\n\t#[name(\"(Reds) Cyan\")] r_c: f32,\n\t#[name(\"(Reds) Magenta\")] r_m: f32,\n\t#[name(\"(Reds) Yellow\")] r_y: f32,\n\t#[name(\"(Reds) Black\")] r_k: f32,\n\n\t#[name(\"(Yellows) Cyan\")] y_c: f32,\n\t#[name(\"(Yellows) Magenta\")] y_m: f32,\n\t#[name(\"(Yellows) Yellow\")] y_y: f32,\n\t#[name(\"(Yellows) Black\")] y_k: f32,\n\n\t#[name(\"(Greens) Cyan\")] g_c: f32,\n\t#[name(\"(Greens) Magenta\")] g_m: f32,\n\t#[name(\"(Greens) Yellow\")] g_y: f32,\n\t#[name(\"(Greens) Black\")] g_k: f32,\n\n\t#[name(\"(Cyans) Cyan\")] c_c: f32,\n\t#[name(\"(Cyans) Magenta\")] c_m: f32,\n\t#[name(\"(Cyans) Yellow\")] c_y: f32,\n\t#[name(\"(Cyans) Black\")] c_k: f32,\n\n\t#[name(\"(Blues) Cyan\")] b_c: f32,\n\t#[name(\"(Blues) Magenta\")] b_m: f32,\n\t#[name(\"(Blues) Yellow\")] b_y: f32,\n\t#[name(\"(Blues) Black\")] b_k: f32,\n\n\t#[name(\"(Magentas) Cyan\")] m_c: f32,\n\t#[name(\"(Magentas) Magenta\")] m_m: f32,\n\t#[name(\"(Magentas) Yellow\")] m_y: f32,\n\t#[name(\"(Magentas) Black\")] m_k: f32,\n\n\t#[name(\"(Whites) Cyan\")] w_c: f32,\n\t#[name(\"(Whites) Magenta\")] w_m: f32,\n\t#[name(\"(Whites) Yellow\")] w_y: f32,\n\t#[name(\"(Whites) Black\")] w_k: f32,\n\n\t#[name(\"(Neutrals) Cyan\")] n_c: f32,\n\t#[name(\"(Neutrals) Magenta\")] n_m: f32,\n\t#[name(\"(Neutrals) Yellow\")] n_y: f32,\n\t#[name(\"(Neutrals) Black\")] n_k: f32,\n\n\t#[name(\"(Blacks) Cyan\")] k_c: f32,\n\t#[name(\"(Blacks) Magenta\")] k_m: f32,\n\t#[name(\"(Blacks) Yellow\")] k_y: f32,\n\t#[name(\"(Blacks) Black\")] k_k: f32,\n\n\t_colors: SelectiveColorChoice,\n) -> T {\n\timage.adjust(|color| {\n\t\tlet color = color.to_gamma_srgb();\n\n\t\tlet (r, g, b, a) = color.components();\n\n\t\tlet min = |a: f32, b: f32, c: f32| a.min(b).min(c);\n\t\tlet max = |a: f32, b: f32, c: f32| a.max(b).max(c);\n\t\tlet med = |a: f32, b: f32, c: f32| a + b + c - min(a, b, c) - max(a, b, c);\n\n\t\tlet max_channel = max(r, g, b);\n\t\tlet min_channel = min(r, g, b);\n\n\t\tlet pixel_color_range = |choice| match choice {\n\t\t\tSelectiveColorChoice::Reds => max_channel == r,\n\t\t\tSelectiveColorChoice::Yellows => min_channel == b,\n\t\t\tSelectiveColorChoice::Greens => max_channel == g,\n\t\t\tSelectiveColorChoice::Cyans => min_channel == r,\n\t\t\tSelectiveColorChoice::Blues => max_channel == b,\n\t\t\tSelectiveColorChoice::Magentas => min_channel == g,\n\t\t\tSelectiveColorChoice::Whites => r > 0.5 && g > 0.5 && b > 0.5,\n\t\t\tSelectiveColorChoice::Neutrals => r > 0. && g > 0. && b > 0. && r < 1. && g < 1. && b < 1.,\n\t\t\tSelectiveColorChoice::Blacks => r < 0.5 && g < 0.5 && b < 0.5,\n\t\t};\n\n\t\tlet color_parameter_group_scale_factor_rgb = max(r, g, b) - med(r, g, b);\n\t\tlet color_parameter_group_scale_factor_cmy = med(r, g, b) - min(r, g, b);\n\n\t\t// Used to apply the r, g, or b channel slope (by multiplying it by 1) in relative mode, or no slope (by multiplying it by 0) in absolute mode\n\t\tlet (slope_r, slope_g, slope_b) = match mode {\n\t\t\tRelativeAbsolute::Relative => (r - 1., g - 1., b - 1.),\n\t\t\tRelativeAbsolute::Absolute => (-1., -1., -1.),\n\t\t};\n\n\t\tlet array = [\n\t\t\t(SelectiveColorChoice::Reds, (r_c, r_m, r_y, r_k)),\n\t\t\t(SelectiveColorChoice::Yellows, (y_c, y_m, y_y, y_k)),\n\t\t\t(SelectiveColorChoice::Greens, (g_c, g_m, g_y, g_k)),\n\t\t\t(SelectiveColorChoice::Cyans, (c_c, c_m, c_y, c_k)),\n\t\t\t(SelectiveColorChoice::Blues, (b_c, b_m, b_y, b_k)),\n\t\t\t(SelectiveColorChoice::Magentas, (m_c, m_m, m_y, m_k)),\n\t\t\t(SelectiveColorChoice::Whites, (w_c, w_m, w_y, w_k)),\n\t\t\t(SelectiveColorChoice::Neutrals, (n_c, n_m, n_y, n_k)),\n\t\t\t(SelectiveColorChoice::Blacks, (k_c, k_m, k_y, k_k)),\n\t\t];\n\t\tlet mut sum = Vec3::ZERO;\n\t\tfor i in 0..array.len() {\n\t\t\tlet (color_parameter_group, (c, m, y, k)) = array[i];\n\n\t\t\t// Skip this color parameter group...\n\t\t\t// ...if it's unchanged from the default of zero offset on all CMYK parameters, or...\n\t\t\t// ...if this pixel's color isn't in the range affected by this color parameter group\n\t\t\tif (c < f32::EPSILON && m < f32::EPSILON && y < f32::EPSILON && k < f32::EPSILON) || (!pixel_color_range(color_parameter_group)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet (c, m, y, k) = (c / 100., m / 100., y / 100., k / 100.);\n\n\t\t\tlet color_parameter_group_scale_factor = match color_parameter_group {\n\t\t\t\tSelectiveColorChoice::Reds | SelectiveColorChoice::Greens | SelectiveColorChoice::Blues => color_parameter_group_scale_factor_rgb,\n\t\t\t\tSelectiveColorChoice::Cyans | SelectiveColorChoice::Magentas | SelectiveColorChoice::Yellows => color_parameter_group_scale_factor_cmy,\n\t\t\t\tSelectiveColorChoice::Whites => min(r, g, b) * 2. - 1.,\n\t\t\t\tSelectiveColorChoice::Neutrals => 1. - ((max(r, g, b) - 0.5).abs() + (min(r, g, b) - 0.5).abs()),\n\t\t\t\tSelectiveColorChoice::Blacks => 1. - max(r, g, b) * 2.,\n\t\t\t};\n\n\t\t\tlet offset_r = f32::clamp((c + k * (c + 1.)) * slope_r, -r, -r + 1.) * color_parameter_group_scale_factor;\n\t\t\tlet offset_g = f32::clamp((m + k * (m + 1.)) * slope_g, -g, -g + 1.) * color_parameter_group_scale_factor;\n\t\t\tlet offset_b = f32::clamp((y + k * (y + 1.)) * slope_b, -b, -b + 1.) * color_parameter_group_scale_factor;\n\n\t\t\tsum += Vec3::new(offset_r, offset_g, offset_b);\n\t\t}\n\n\t\tlet rgb = Vec3::new(r, g, b);\n\t\tlet color = Color::from_vec4(Vec4::from(((sum + rgb).clamp(Vec3::ZERO, Vec3::ONE), a)));\n\n\t\tcolor.to_linear_srgb()\n\t});\n\timage\n}\n\n// Aims for interoperable compatibility with:\n// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=nvrt%27%20%3D%20Invert-,%27post%27%20%3D%20Posterize,-%27thrs%27%20%3D%20Threshold\n//\n// Algorithm based on:\n// https://www.axiomx.com/posterize.htm\n// This algorithm produces fully accurate output in relation to the industry standard.\n#[node_macro::node(category(\"Raster: Adjustment\"), shader_node(PerPixelAdjust))]\nfn posterize<T: Adjust<Color>>(\n\t_: impl Ctx,\n\t#[implementations(\n\t\tTable<Raster<CPU>>,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t)]\n\t#[gpu_image]\n\tmut input: T,\n\t#[default(4)]\n\t#[hard_min(2.)]\n\tlevels: u32,\n) -> T {\n\tinput.adjust(|color| {\n\t\tlet color = color.to_gamma_srgb();\n\n\t\tlet levels = levels as f32;\n\t\tlet number_of_areas = levels.recip();\n\t\tlet size_of_areas = (levels - 1.).recip();\n\t\tlet channel = |channel: f32| (channel / number_of_areas).floor() * size_of_areas;\n\t\tlet color = color.map_rgb(channel);\n\n\t\tcolor.to_linear_srgb()\n\t});\n\tinput\n}\n\n// Aims for interoperable compatibility with:\n// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=curv%27%20%3D%20Curves-,%27expA%27%20%3D%20Exposure,-%27vibA%27%20%3D%20Vibrance\n// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Flag%20(%20%3D%20128%20)-,Exposure,-Key%20is%20%27expA\n//\n// Algorithm based on:\n// https://geraldbakker.nl/psnumbers/exposure.html\n#[node_macro::node(category(\"Raster: Adjustment\"), properties(\"exposure_properties\"), shader_node(PerPixelAdjust))]\nfn exposure<T: Adjust<Color>>(\n\t_: impl Ctx,\n\t#[implementations(\n\t\tTable<Raster<CPU>>,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t)]\n\t#[gpu_image]\n\tmut input: T,\n\texposure: f32,\n\toffset: f32,\n\t#[default(1.)]\n\t#[range((0.01, 10.))]\n\t#[hard_min(0.0001)]\n\tgamma_correction: f32,\n) -> T {\n\tinput.adjust(|color| {\n\t\tlet adjusted = color\n\t\t\t// Exposure\n\t\t\t.map_rgb(|c: f32| c * 2_f32.powf(exposure))\n\t\t\t// Offset\n\t\t\t.map_rgb(|c: f32| c + offset)\n\t\t\t// Gamma correction\n\t\t\t.gamma(gamma_correction);\n\n\t\tadjusted.map_rgb(|c: f32| c.clamp(0., 1.))\n\t});\n\tinput\n}\n"
  },
  {
    "path": "node-graph/nodes/raster/src/blending_nodes.rs",
    "content": "use crate::adjust::Adjust;\n#[cfg(feature = \"std\")]\nuse core_types::table::Table;\nuse no_std_types::Ctx;\nuse no_std_types::blending::BlendMode;\nuse no_std_types::color::{Color, Pixel};\nuse no_std_types::registry::types::PercentageF32;\n#[cfg(feature = \"std\")]\nuse raster_types::{CPU, Raster};\n#[cfg(feature = \"std\")]\nuse vector_types::{GradientStop, GradientStops};\n\npub trait Blend<P: Pixel> {\n\tfn blend(&self, under: &Self, blend_fn: impl Fn(P, P) -> P) -> Self;\n}\nimpl Blend<Color> for Color {\n\tfn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {\n\t\tblend_fn(*self, *under)\n\t}\n}\n\n#[cfg(feature = \"std\")]\nmod blend_std {\n\tuse super::*;\n\tuse core::cmp::Ordering;\n\tuse core_types::table::Table;\n\tuse raster_types::Image;\n\tuse raster_types::Raster;\n\n\timpl Blend<Color> for Table<Raster<CPU>> {\n\t\tfn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {\n\t\t\tlet mut result_table = self.clone();\n\t\t\tfor (over, under) in result_table.iter_mut().zip(under.iter()) {\n\t\t\t\tlet data = over.element.data.iter().zip(under.element.data.iter()).map(|(a, b)| blend_fn(*a, *b)).collect();\n\n\t\t\t\t*over.element = Raster::new_cpu(Image {\n\t\t\t\t\tdata,\n\t\t\t\t\twidth: over.element.width,\n\t\t\t\t\theight: over.element.height,\n\t\t\t\t\tbase64_string: None,\n\t\t\t\t});\n\t\t\t}\n\t\t\tresult_table\n\t\t}\n\t}\n\timpl Blend<Color> for Table<Color> {\n\t\tfn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {\n\t\t\tlet mut result_table = self.clone();\n\t\t\tfor (over, under) in result_table.iter_mut().zip(under.iter()) {\n\t\t\t\t*over.element = blend_fn(*over.element, *under.element);\n\t\t\t}\n\t\t\tresult_table\n\t\t}\n\t}\n\timpl Blend<Color> for Table<GradientStops> {\n\t\tfn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {\n\t\t\tlet mut result_table = self.clone();\n\t\t\tfor (over, under) in result_table.iter_mut().zip(under.iter()) {\n\t\t\t\t*over.element = over.element.blend(under.element, &blend_fn);\n\t\t\t}\n\t\t\tresult_table\n\t\t}\n\t}\n\timpl Blend<Color> for GradientStops {\n\t\tfn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {\n\t\t\tlet mut combined_stops = self.position.iter().chain(under.position.iter()).copied().collect::<Vec<_>>();\n\t\t\tcombined_stops.dedup_by(|a, b| (*a - *b).abs() < 1e-6);\n\t\t\tcombined_stops.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));\n\t\t\tlet stops = combined_stops.into_iter().map(|position| {\n\t\t\t\tlet over_color = self.evaluate(position);\n\t\t\t\tlet under_color = under.evaluate(position);\n\t\t\t\tlet color = blend_fn(over_color, under_color);\n\t\t\t\tGradientStop { position, midpoint: 0.5, color }\n\t\t\t});\n\t\t\tGradientStops::new(stops)\n\t\t}\n\t}\n}\n\n#[inline(always)]\npub fn blend_colors(foreground: Color, background: Color, blend_mode: BlendMode, opacity: f32) -> Color {\n\tlet target_color = match blend_mode {\n\t\t// Other utility blend modes (hidden from the normal list) - do not have alpha blend\n\t\tBlendMode::Erase => return background.alpha_subtract(foreground),\n\t\tBlendMode::Restore => return background.alpha_add(foreground),\n\t\tBlendMode::MultiplyAlpha => return background.alpha_multiply(foreground),\n\t\tblend_mode => apply_blend_mode(foreground, background, blend_mode),\n\t};\n\n\tbackground.alpha_blend(target_color.to_associated_alpha(opacity))\n}\n\npub fn apply_blend_mode(foreground: Color, background: Color, blend_mode: BlendMode) -> Color {\n\tmatch blend_mode {\n\t\t// Normal group\n\t\tBlendMode::Normal => background.blend_rgb(foreground, Color::blend_normal),\n\t\t// Darken group\n\t\tBlendMode::Darken => background.blend_rgb(foreground, Color::blend_darken),\n\t\tBlendMode::Multiply => background.blend_rgb(foreground, Color::blend_multiply),\n\t\tBlendMode::ColorBurn => background.blend_rgb(foreground, Color::blend_color_burn),\n\t\tBlendMode::LinearBurn => background.blend_rgb(foreground, Color::blend_linear_burn),\n\t\tBlendMode::DarkerColor => background.blend_darker_color(foreground),\n\t\t// Lighten group\n\t\tBlendMode::Lighten => background.blend_rgb(foreground, Color::blend_lighten),\n\t\tBlendMode::Screen => background.blend_rgb(foreground, Color::blend_screen),\n\t\tBlendMode::ColorDodge => background.blend_rgb(foreground, Color::blend_color_dodge),\n\t\tBlendMode::LinearDodge => background.blend_rgb(foreground, Color::blend_linear_dodge),\n\t\tBlendMode::LighterColor => background.blend_lighter_color(foreground),\n\t\t// Contrast group\n\t\tBlendMode::Overlay => foreground.blend_rgb(background, Color::blend_hardlight),\n\t\tBlendMode::SoftLight => background.blend_rgb(foreground, Color::blend_softlight),\n\t\tBlendMode::HardLight => background.blend_rgb(foreground, Color::blend_hardlight),\n\t\tBlendMode::VividLight => background.blend_rgb(foreground, Color::blend_vivid_light),\n\t\tBlendMode::LinearLight => background.blend_rgb(foreground, Color::blend_linear_light),\n\t\tBlendMode::PinLight => background.blend_rgb(foreground, Color::blend_pin_light),\n\t\tBlendMode::HardMix => background.blend_rgb(foreground, Color::blend_hard_mix),\n\t\t// Inversion group\n\t\tBlendMode::Difference => background.blend_rgb(foreground, Color::blend_difference),\n\t\tBlendMode::Exclusion => background.blend_rgb(foreground, Color::blend_exclusion),\n\t\tBlendMode::Subtract => background.blend_rgb(foreground, Color::blend_subtract),\n\t\tBlendMode::Divide => background.blend_rgb(foreground, Color::blend_divide),\n\t\t// Component group\n\t\tBlendMode::Hue => background.blend_hue(foreground),\n\t\tBlendMode::Saturation => background.blend_saturation(foreground),\n\t\tBlendMode::Color => background.blend_color(foreground),\n\t\tBlendMode::Luminosity => background.blend_luminosity(foreground),\n\t\t// Other utility blend modes (hidden from the normal list) - do not have alpha blend\n\t\t_ => panic!(\"Used blend mode without alpha blend\"),\n\t}\n}\n\n#[node_macro::node(category(\"Raster\"), cfg(feature = \"std\"))]\nfn blend<T: Blend<Color> + Send>(\n\t_: impl Ctx,\n\t#[implementations(\n\t\tTable<Raster<CPU>>,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t)]\n\t#[gpu_image]\n\tover: T,\n\t#[expose]\n\t#[implementations(\n\t\tTable<Raster<CPU>>,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t)]\n\t#[gpu_image]\n\tunder: T,\n\tblend_mode: BlendMode,\n\t#[default(100.)] opacity: PercentageF32,\n) -> T {\n\tover.blend(&under, |a, b| blend_colors(a, b, blend_mode, opacity / 100.))\n}\n\n#[node_macro::node(category(\"Raster: Adjustment\"), shader_node(PerPixelAdjust))]\nfn color_overlay<T: Adjust<Color>>(\n\t_: impl Ctx,\n\t#[implementations(\n\t\tTable<Raster<CPU>>,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t)]\n\t#[gpu_image]\n\tmut image: T,\n\t#[default(Color::BLACK)] color: Color,\n\tblend_mode: BlendMode,\n\t#[default(100.)] opacity: PercentageF32,\n) -> T {\n\tlet opacity = (opacity / 100.).clamp(0., 1.);\n\n\timage.adjust(|pixel| {\n\t\tlet image = pixel.map_rgb(|channel| channel * (1. - opacity));\n\n\t\t// The apply blend mode function divides rgb by the alpha channel for the background. This undoes that.\n\t\tlet associated_pixel = Color::from_rgbaf32_unchecked(pixel.r() * pixel.a(), pixel.g() * pixel.a(), pixel.b() * pixel.a(), pixel.a());\n\t\tlet overlay = apply_blend_mode(color, associated_pixel, blend_mode).map_rgb(|channel| channel * opacity);\n\n\t\tColor::from_rgbaf32_unchecked(image.r() + overlay.r(), image.g() + overlay.g(), image.b() + overlay.b(), pixel.a())\n\t});\n\timage\n}\n\n#[cfg(all(feature = \"std\", test))]\nmod test {\n\tuse core_types::blending::BlendMode;\n\tuse core_types::color::Color;\n\tuse core_types::table::Table;\n\tuse raster_types::Image;\n\tuse raster_types::Raster;\n\n\t#[tokio::test]\n\tasync fn color_overlay_multiply() {\n\t\tlet image_color = Color::from_rgbaf32_unchecked(0.7, 0.6, 0.5, 0.4);\n\t\tlet image = Image::new(1, 1, image_color);\n\n\t\t// Color { red: 0., green: 1., blue: 0., alpha: 1. }\n\t\tlet overlay_color = Color::GREEN;\n\n\t\t// 100% of the output should come from the multiplied value\n\t\tlet opacity = 100.;\n\n\t\tlet result = super::color_overlay((), Table::new_from_element(Raster::new_cpu(image.clone())), overlay_color, BlendMode::Multiply, opacity);\n\t\tlet result = result.iter().next().unwrap().element;\n\n\t\t// The output should just be the original green and alpha channels (as we multiply them by 1 and other channels by 0)\n\t\tassert_eq!(result.data[0], Color::from_rgbaf32_unchecked(0., image_color.g(), 0., image_color.a()));\n\t}\n}\n"
  },
  {
    "path": "node-graph/nodes/raster/src/cubic_spline.rs",
    "content": "#[derive(Debug)]\npub struct CubicSplines {\n\tpub x: [f32; 4],\n\tpub y: [f32; 4],\n}\n\nimpl CubicSplines {\n\tpub fn solve(&self) -> [f32; 4] {\n\t\tlet (x, y) = (&self.x, &self.y);\n\n\t\t// Build an augmented matrix to solve the system of equations using Gaussian elimination\n\t\tlet mut augmented_matrix = [\n\t\t\t[\n\t\t\t\t2. / (x[1] - x[0]),\n\t\t\t\t1. / (x[1] - x[0]),\n\t\t\t\t0.,\n\t\t\t\t0.,\n\t\t\t\t// |\n\t\t\t\t3. * (y[1] - y[0]) / ((x[1] - x[0]) * (x[1] - x[0])),\n\t\t\t],\n\t\t\t[\n\t\t\t\t1. / (x[1] - x[0]),\n\t\t\t\t2. * (1. / (x[1] - x[0]) + 1. / (x[2] - x[1])),\n\t\t\t\t1. / (x[2] - x[1]),\n\t\t\t\t0.,\n\t\t\t\t// |\n\t\t\t\t3. * ((y[1] - y[0]) / ((x[1] - x[0]) * (x[1] - x[0])) + (y[2] - y[1]) / ((x[2] - x[1]) * (x[2] - x[1]))),\n\t\t\t],\n\t\t\t[\n\t\t\t\t0.,\n\t\t\t\t1. / (x[2] - x[1]),\n\t\t\t\t2. * (1. / (x[2] - x[1]) + 1. / (x[3] - x[2])),\n\t\t\t\t1. / (x[3] - x[2]),\n\t\t\t\t// |\n\t\t\t\t3. * ((y[2] - y[1]) / ((x[2] - x[1]) * (x[2] - x[1])) + (y[3] - y[2]) / ((x[3] - x[2]) * (x[3] - x[2]))),\n\t\t\t],\n\t\t\t[\n\t\t\t\t0.,\n\t\t\t\t0.,\n\t\t\t\t1. / (x[3] - x[2]),\n\t\t\t\t2. / (x[3] - x[2]),\n\t\t\t\t// |\n\t\t\t\t3. * (y[3] - y[2]) / ((x[3] - x[2]) * (x[3] - x[2])),\n\t\t\t],\n\t\t];\n\n\t\t// Gaussian elimination: forward elimination\n\t\tfor row in 0..4 {\n\t\t\tlet pivot_row_index = (row..4)\n\t\t\t\t.max_by(|&a_row, &b_row| {\n\t\t\t\t\taugmented_matrix[a_row][row]\n\t\t\t\t\t\t.abs()\n\t\t\t\t\t\t.partial_cmp(&augmented_matrix[b_row][row].abs())\n\t\t\t\t\t\t.unwrap_or(core::cmp::Ordering::Equal)\n\t\t\t\t})\n\t\t\t\t.unwrap();\n\n\t\t\t// Swap the current row with the row that has the largest pivot element\n\t\t\taugmented_matrix.swap(row, pivot_row_index);\n\n\t\t\t// Eliminate the current column in all rows below the current one\n\t\t\tfor row_below_current in row + 1..4 {\n\t\t\t\tassert!(augmented_matrix[row][row].abs() > f32::EPSILON);\n\n\t\t\t\tlet scale_factor = augmented_matrix[row_below_current][row] / augmented_matrix[row][row];\n\t\t\t\tfor col in row..5 {\n\t\t\t\t\taugmented_matrix[row_below_current][col] -= augmented_matrix[row][col] * scale_factor\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Gaussian elimination: back substitution\n\t\tlet mut solutions = [0.; 4];\n\t\tfor col in (0..4).rev() {\n\t\t\tassert!(augmented_matrix[col][col].abs() > f32::EPSILON);\n\n\t\t\tsolutions[col] = augmented_matrix[col][4] / augmented_matrix[col][col];\n\n\t\t\tfor row in (0..col).rev() {\n\t\t\t\taugmented_matrix[row][4] -= augmented_matrix[row][col] * solutions[col];\n\t\t\t\taugmented_matrix[row][col] = 0.;\n\t\t\t}\n\t\t}\n\n\t\tsolutions\n\t}\n\n\tpub fn interpolate(&self, input: f32, solutions: &[f32]) -> f32 {\n\t\tif input <= self.x[0] {\n\t\t\treturn self.y[0];\n\t\t}\n\t\tif input >= self.x[self.x.len() - 1] {\n\t\t\treturn self.y[self.x.len() - 1];\n\t\t}\n\n\t\t// Find the segment that the input falls between\n\t\tlet mut segment = 1;\n\t\twhile self.x[segment] < input {\n\t\t\tsegment += 1;\n\t\t}\n\t\tlet segment_start = segment - 1;\n\t\tlet segment_end = segment;\n\n\t\t// Calculate the output value using quadratic interpolation\n\t\tlet input_value = self.x[segment_start];\n\t\tlet input_value_prev = self.x[segment_end];\n\t\tlet output_value = self.y[segment_start];\n\t\tlet output_value_prev = self.y[segment_end];\n\t\tlet solutions_value = solutions[segment_start];\n\t\tlet solutions_value_prev = solutions[segment_end];\n\n\t\tlet output_delta = solutions_value_prev * (input_value - input_value_prev) - (output_value - output_value_prev);\n\t\tlet solution_delta = (output_value - output_value_prev) - solutions_value * (input_value - input_value_prev);\n\n\t\tlet input_ratio = (input - input_value_prev) / (input_value - input_value_prev);\n\t\tlet prev_output_ratio = (1. - input_ratio) * output_value_prev;\n\t\tlet output_ratio = input_ratio * output_value;\n\t\tlet quadratic_ratio = input_ratio * (1. - input_ratio) * (output_delta * (1. - input_ratio) + solution_delta * input_ratio);\n\n\t\tlet result = prev_output_ratio + output_ratio + quadratic_ratio;\n\t\tresult.clamp(0., 1.)\n\t}\n}\n"
  },
  {
    "path": "node-graph/nodes/raster/src/curve.rs",
    "content": "use core_types::Node;\nuse core_types::color::{Channel, Linear, LuminanceMut};\nuse dyn_any::{DynAny, StaticType, StaticTypeSized};\nuse std::hash::{Hash, Hasher};\nuse std::ops::{Add, Mul, Sub};\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]\npub struct Curve {\n\t#[serde(rename = \"manipulatorGroups\")]\n\tpub manipulator_groups: Vec<CurveManipulatorGroup>,\n\t#[serde(rename = \"firstHandle\")]\n\tpub first_handle: [f32; 2],\n\t#[serde(rename = \"lastHandle\")]\n\tpub last_handle: [f32; 2],\n}\n\nimpl Default for Curve {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tmanipulator_groups: vec![],\n\t\t\tfirst_handle: [0.2; 2],\n\t\t\tlast_handle: [0.8; 2],\n\t\t}\n\t}\n}\n\nimpl Hash for Curve {\n\tfn hash<H: Hasher>(&self, state: &mut H) {\n\t\tself.manipulator_groups.hash(state);\n\t\t[self.first_handle, self.last_handle].iter().flatten().for_each(|f| f.to_bits().hash(state));\n\t}\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, Copy, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]\npub struct CurveManipulatorGroup {\n\tpub anchor: [f32; 2],\n\tpub handles: [[f32; 2]; 2],\n}\n\nimpl Hash for CurveManipulatorGroup {\n\tfn hash<H: Hasher>(&self, state: &mut H) {\n\t\tfor c in self.handles.iter().chain([&self.anchor]).flatten() {\n\t\t\tc.to_bits().hash(state);\n\t\t}\n\t}\n}\n\npub struct ValueMapperNode<C> {\n\tlut: Vec<C>,\n}\n\nunsafe impl<C: StaticTypeSized> StaticType for ValueMapperNode<C> {\n\ttype Static = ValueMapperNode<C::Static>;\n}\n\nimpl<C> ValueMapperNode<C> {\n\tpub const fn new(lut: Vec<C>) -> Self {\n\t\tSelf { lut }\n\t}\n}\n\nimpl<'i, L: LuminanceMut + 'i> Node<'i, L> for ValueMapperNode<L::LuminanceChannel>\nwhere\n\tL::LuminanceChannel: Linear + Copy,\n\tL::LuminanceChannel: Add<Output = L::LuminanceChannel>,\n\tL::LuminanceChannel: Sub<Output = L::LuminanceChannel>,\n\tL::LuminanceChannel: Mul<Output = L::LuminanceChannel>,\n{\n\ttype Output = L;\n\n\tfn eval(&'i self, mut val: L) -> L {\n\t\tlet luminance: f32 = val.luminance().to_linear();\n\t\tlet floating_sample_index = luminance * (self.lut.len() - 1) as f32;\n\t\tlet index_in_lut = floating_sample_index.floor() as usize;\n\t\tlet a = self.lut[index_in_lut];\n\t\tlet b = self.lut[(index_in_lut + 1).clamp(0, self.lut.len() - 1)];\n\t\tlet result = a.lerp(b, L::LuminanceChannel::from_linear(floating_sample_index.fract()));\n\t\tval.set_luminance(result);\n\t\tval\n\t}\n}\n"
  },
  {
    "path": "node-graph/nodes/raster/src/dehaze.rs",
    "content": "use core_types::context::Ctx;\nuse core_types::registry::types::Percentage;\nuse core_types::table::Table;\nuse image::{DynamicImage, GenericImage, GenericImageView, GrayImage, ImageBuffer, Luma, Rgba, RgbaImage};\nuse ndarray::{Array2, ArrayBase, Dim, OwnedRepr};\nuse raster_types::Image;\nuse raster_types::{CPU, Raster};\nuse std::cmp::{max, min};\n\n#[node_macro::node(category(\"Raster: Filter\"))]\nasync fn dehaze(_: impl Ctx, image_frame: Table<Raster<CPU>>, strength: Percentage) -> Table<Raster<CPU>> {\n\timage_frame\n\t\t.into_iter()\n\t\t.map(|mut row| {\n\t\t\tlet image = row.element;\n\t\t\t// Prepare the image data for processing\n\t\t\tlet image_data = bytemuck::cast_vec(image.data.clone());\n\t\t\tlet image_buffer = image::Rgba32FImage::from_raw(image.width, image.height, image_data).expect(\"Failed to convert internal image format into image-rs data type.\");\n\t\t\tlet dynamic_image: DynamicImage = image_buffer.into();\n\n\t\t\t// Run the dehaze algorithm\n\t\t\tlet dehazed_dynamic_image = dehaze_image(dynamic_image, strength / 100.);\n\n\t\t\t// Prepare the image data for returning\n\t\t\tlet buffer = dehazed_dynamic_image.to_rgba32f().into_raw();\n\t\t\tlet color_vec = bytemuck::cast_vec(buffer);\n\t\t\tlet dehazed_image = Image {\n\t\t\t\twidth: image.width,\n\t\t\t\theight: image.height,\n\t\t\t\tdata: color_vec,\n\t\t\t\tbase64_string: None,\n\t\t\t};\n\n\t\t\trow.element = Raster::new_cpu(dehazed_image);\n\t\t\trow\n\t\t})\n\t\t.collect()\n}\n\n// There is no real point in modifying these values because they do not change the final result all that much.\n// The authors of the paper recommended using these values to get a reasonable balance of performance and quality.\nconst PATCH_SIZE: u32 = 15;\nconst TOP_PERCENT: f64 = 0.001;\nconst RADIUS: u32 = 60;\nconst EPSILON: f64 = 0.0001;\nconst TX: f32 = 0.1;\n\n// Dehazing algorithm based on \"Single Image Haze Removal Using Dark Channel Prior\"\n// Paper: <https://www.researchgate.net/publication/220182411_Single_Image_Haze_Removal_Using_Dark_Channel_Prior>\n// TODO: Make this algorithm work with negative strength values\nfn dehaze_image(image: DynamicImage, strength: f64) -> DynamicImage {\n\t// TODO: Break out this pair of steps into its own node, with a memoize node which caches the pair of outputs, so the strength can be adjusted without recomputing these two steps.\n\tlet dark_channel = compute_dark_channel(&image);\n\tlet atmospheric_light = estimate_atmospheric_light(&image, &dark_channel);\n\n\tlet transmission_map = estimate_transmission_map(&image, &dark_channel, strength);\n\tlet refined_transmission_map = refine_transmission_map(&image, &transmission_map);\n\n\trecover(&image, &refined_transmission_map, atmospheric_light)\n}\n\nfn compute_dark_channel(image: &DynamicImage) -> DynamicImage {\n\tlet (width, height) = image.dimensions();\n\tlet mut dark_channel = GrayImage::new(width, height);\n\tlet half_patch = PATCH_SIZE / 2;\n\n\tfor y in 0..height {\n\t\tfor x in 0..width {\n\t\t\tlet pixel = image.get_pixel(x, y);\n\t\t\tlet min_intensity = min(min(pixel[0], pixel[1]), pixel[2]);\n\t\t\tdark_channel.put_pixel(x, y, Luma([min_intensity]));\n\t\t}\n\t}\n\n\tlet mut eroded_channel = RgbaImage::new(width, height);\n\n\tfor y in 0..height {\n\t\tfor x in 0..width {\n\t\t\tlet mut local_min = u8::MAX;\n\n\t\t\tfor dy in 0..PATCH_SIZE {\n\t\t\t\tfor dx in 0..PATCH_SIZE {\n\t\t\t\t\tlet nx = x as i32 + dx as i32 - half_patch as i32;\n\t\t\t\t\tlet ny = y as i32 + dy as i32 - half_patch as i32;\n\n\t\t\t\t\tif nx >= 0 && nx < width as i32 && ny >= 0 && ny < height as i32 {\n\t\t\t\t\t\tlet intensity = dark_channel.get_pixel(nx as u32, ny as u32)[0];\n\t\t\t\t\t\tif intensity < local_min {\n\t\t\t\t\t\t\tlocal_min = intensity;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tlet alpha = image.get_pixel(x, y)[3];\n\t\t\teroded_channel.put_pixel(x, y, Rgba([local_min, local_min, local_min, alpha]));\n\t\t}\n\t}\n\n\tDynamicImage::ImageRgba8(eroded_channel)\n}\n\nfn estimate_atmospheric_light(hazy: &DynamicImage, dark_channel: &DynamicImage) -> Rgba<u8> {\n\tlet (width, height) = hazy.dimensions();\n\tlet dark = dark_channel.to_luma_alpha8();\n\tlet total_pixels = (width * height) as usize;\n\tlet num_pixels = ((TOP_PERCENT / 100.) * total_pixels as f64).ceil() as usize;\n\n\tlet mut intensities: Vec<(u32, u32, f64)> = Vec::with_capacity(total_pixels);\n\n\tfor y in 0..height {\n\t\tfor x in 0..width {\n\t\t\tlet pixel = dark.get_pixel(x, y);\n\t\t\tlet intensity = pixel.0[0] as f64;\n\t\t\tintensities.push((x, y, intensity))\n\t\t}\n\t}\n\n\tintensities.sort_by(|a, b| b.2.partial_cmp(&a.2).unwrap());\n\n\tlet top_intensities = &intensities[..num_pixels];\n\n\tlet mut atm_sum = [0., 0., 0.];\n\tfor (x, y, _) in top_intensities {\n\t\tlet pixel = hazy.get_pixel(*x, *y);\n\t\tatm_sum[0] += pixel[0] as f64;\n\t\tatm_sum[1] += pixel[1] as f64;\n\t\tatm_sum[2] += pixel[2] as f64;\n\t}\n\n\tlet num_pixels = num_pixels as f64;\n\n\tRgba([(atm_sum[0] / num_pixels) as u8, (atm_sum[1] / num_pixels) as u8, (atm_sum[2] / num_pixels) as u8, 255])\n}\n\nfn estimate_transmission_map(image: &DynamicImage, dark_channel: &DynamicImage, omega: f64) -> DynamicImage {\n\tlet (width, height) = image.dimensions();\n\tlet mut transmission_map = RgbaImage::new(width, height);\n\n\tfor y in 0..height {\n\t\tfor x in 0..width {\n\t\t\tlet min_intensity = dark_channel.get_pixel(x, y).0[0] as f32 / 255.;\n\t\t\tlet transmission_value = 1. - omega * min_intensity as f64;\n\t\t\tlet alpha = image.get_pixel(x, y)[3];\n\t\t\ttransmission_map.put_pixel(\n\t\t\t\tx,\n\t\t\t\ty,\n\t\t\t\tRgba([(transmission_value * 255.) as u8, (transmission_value * 255.) as u8, (transmission_value * 255.) as u8, alpha]),\n\t\t\t);\n\t\t}\n\t}\n\n\tDynamicImage::ImageRgba8(transmission_map)\n}\n\nfn refine_transmission_map(img: &DynamicImage, transmission_map: &DynamicImage) -> DynamicImage {\n\tlet gray_image = img.to_luma8();\n\n\tlet normalized_gray_image: GrayImage = ImageBuffer::from_fn(gray_image.width(), gray_image.height(), |x, y| {\n\t\tlet pixel = gray_image.get_pixel(x, y);\n\t\tlet normalized_value = (pixel[0] as f64 / 255.) * 255.;\n\t\tLuma([normalized_value as u8])\n\t});\n\n\tlet normalized_gray_image = DynamicImage::ImageLuma8(normalized_gray_image);\n\n\tguided_filter(&normalized_gray_image, transmission_map, RADIUS, EPSILON)\n}\n\nfn recover(im: &DynamicImage, t: &DynamicImage, a: Rgba<u8>) -> DynamicImage {\n\tlet (width, height) = im.dimensions();\n\tlet mut res = DynamicImage::new_rgba8(width, height);\n\n\tlet a = [a[0] as f32 / 255., a[1] as f32 / 255., a[2] as f32 / 255.];\n\n\tfor y in 0..height {\n\t\tfor x in 0..width {\n\t\t\tlet im_pixel = im.get_pixel(x, y).0;\n\t\t\tlet t_pixel = t.get_pixel(x, y).0;\n\t\t\tlet t_val = f32::max(t_pixel[0] as f32 / 255., TX);\n\n\t\t\tlet mut res_pixel = [0; 4];\n\t\t\tfor ind in 0..3 {\n\t\t\t\tres_pixel[ind] = ((((im_pixel[ind] as f32 / 255. - a[ind]) / t_val) + a[ind]).clamp(0., 1.) * 255.) as u8;\n\t\t\t}\n\t\t\tres_pixel[3] = im_pixel[3];\n\n\t\t\tres.put_pixel(x, y, Rgba(res_pixel));\n\t\t}\n\t}\n\n\tres\n}\n\nfn guided_filter(guidance_img: &DynamicImage, input_img: &DynamicImage, r: u32, epsilon: f64) -> DynamicImage {\n\tlet (width, height) = guidance_img.dimensions();\n\tlet radius = r as i32;\n\n\tlet guidance_nd = image_to_ndarray(guidance_img);\n\tlet input_nd = image_to_ndarray(input_img);\n\n\tlet mean_guidance = box_filter(&guidance_nd, radius);\n\tlet mean_input = box_filter(&input_nd, radius);\n\tlet corr_guidance = box_filter(&(guidance_nd.clone() * guidance_nd.clone()), radius);\n\tlet corr_guidance_input = box_filter(&(guidance_nd.clone() * input_nd.clone()), radius);\n\n\tlet var_guidance = &corr_guidance - &(mean_guidance.clone() * mean_guidance.clone());\n\tlet cov_guidance_input = &corr_guidance_input - &(mean_guidance.clone() * mean_input.clone());\n\n\tlet a = &cov_guidance_input / &(var_guidance.clone() + epsilon);\n\tlet b = mean_input - &(a.clone() * mean_guidance);\n\n\tlet mean_a = box_filter(&a, radius);\n\tlet mean_b = box_filter(&b, radius);\n\n\tlet q = &mean_a * &guidance_nd + mean_b;\n\n\tndarray_to_image(&q, width, height)\n}\n\nfn box_filter(img: &Array2<f64>, radius: i32) -> Array2<f64> {\n\tlet (height, width) = img.dim();\n\tlet mut result = Array2::zeros((height, width));\n\tlet mut integral_image: ArrayBase<OwnedRepr<f64>, Dim<[usize; 2]>> = Array2::zeros((height + 1, width + 1));\n\n\t// Compute integral image\n\tfor y in 0..height {\n\t\tfor x in 0..width {\n\t\t\tintegral_image[(y + 1, x + 1)] = img[(y, x)] + integral_image[(y, x + 1)] + integral_image[(y + 1, x)] - integral_image[(y, x)];\n\t\t}\n\t}\n\n\tfor y in 0..height {\n\t\tfor x in 0..width {\n\t\t\tlet y1 = max(0, y as i32 - radius) as usize;\n\t\t\tlet y2 = min(height as i32 - 1, y as i32 + radius) as usize;\n\t\t\tlet x1 = max(0, x as i32 - radius) as usize;\n\t\t\tlet x2 = min(width as i32 - 1, x as i32 + radius) as usize;\n\n\t\t\tlet area = (y2 - y1 + 1) as f64 * (x2 - x1 + 1) as f64;\n\n\t\t\tresult[(y, x)] = (integral_image[(y2 + 1, x2 + 1)] - integral_image[(y1, x2 + 1)] - integral_image[(y2 + 1, x1)] + integral_image[(y1, x1)]) / area;\n\t\t}\n\t}\n\n\tresult\n}\n\nfn image_to_ndarray(img: &DynamicImage) -> Array2<f64> {\n\tlet (width, height) = img.dimensions();\n\tlet mut array = Array2::zeros((height as usize, width as usize));\n\tfor (x, y, pixel) in img.pixels() {\n\t\tlet luminance = pixel.0[0] as f64 / 255.;\n\t\tarray[(y as usize, x as usize)] = luminance;\n\t}\n\tarray\n}\n\nfn ndarray_to_image(array: &Array2<f64>, width: u32, height: u32) -> DynamicImage {\n\tlet mut img = DynamicImage::new_rgba8(width, height);\n\tfor ((y, x), &value) in array.indexed_iter() {\n\t\tlet clamped_value = (value * 255.).clamp(0., 255.) as u8;\n\t\timg.put_pixel(x as u32, y as u32, Rgba([clamped_value, clamped_value, clamped_value, 255]));\n\t}\n\timg\n}\n"
  },
  {
    "path": "node-graph/nodes/raster/src/filter.rs",
    "content": "use core_types::color::Color;\nuse core_types::context::Ctx;\nuse core_types::registry::types::PixelLength;\nuse core_types::table::Table;\nuse raster_types::Image;\nuse raster_types::{Bitmap, BitmapMut};\nuse raster_types::{CPU, Raster};\n\n/// Blurs the image with a Gaussian or box blur kernel filter.\n#[node_macro::node(category(\"Raster: Filter\"))]\nasync fn blur(\n\t_: impl Ctx,\n\t/// The image to be blurred.\n\timage_frame: Table<Raster<CPU>>,\n\t/// The radius of the blur kernel.\n\t#[range((0., 100.))]\n\t#[hard_min(0.)]\n\tradius: PixelLength,\n\t/// Use a lower-quality box kernel instead of a circular Gaussian kernel. This is faster but produces boxy artifacts.\n\tbox_blur: bool,\n\t/// Opt to incorrectly apply the filter with color calculations in gamma space for compatibility with the results from other software.\n\tgamma: bool,\n) -> Table<Raster<CPU>> {\n\timage_frame\n\t\t.into_iter()\n\t\t.map(|mut row| {\n\t\t\tlet image = row.element.clone();\n\n\t\t\t// Run blur algorithm\n\t\t\tlet blurred_image = if radius < 0.1 {\n\t\t\t\t// Minimum blur radius\n\t\t\t\timage.clone()\n\t\t\t} else if box_blur {\n\t\t\t\tRaster::new_cpu(box_blur_algorithm(image.into_data(), radius, gamma))\n\t\t\t} else {\n\t\t\t\tRaster::new_cpu(gaussian_blur_algorithm(image.into_data(), radius, gamma))\n\t\t\t};\n\n\t\t\trow.element = blurred_image;\n\t\t\trow\n\t\t})\n\t\t.collect()\n}\n\n/// Applies a median filter to reduce noise while preserving edges.\n#[node_macro::node(category(\"Raster: Filter\"))]\nasync fn median_filter(\n\t_: impl Ctx,\n\t/// The image to be filtered.\n\timage_frame: Table<Raster<CPU>>,\n\t/// The radius of the filter kernel. Larger values remove more noise but may blur fine details.\n\t#[range((0., 50.))]\n\t#[hard_min(0.)]\n\tradius: PixelLength,\n) -> Table<Raster<CPU>> {\n\timage_frame\n\t\t.into_iter()\n\t\t.map(|mut row| {\n\t\t\tlet image = row.element.clone();\n\n\t\t\t// Apply median filter\n\t\t\tlet filtered_image = if radius < 0.5 {\n\t\t\t\t// Minimum filter radius\n\t\t\t\timage.clone()\n\t\t\t} else {\n\t\t\t\tRaster::new_cpu(median_filter_algorithm(image.into_data(), radius as u32))\n\t\t\t};\n\n\t\t\trow.element = filtered_image;\n\t\t\trow\n\t\t})\n\t\t.collect()\n}\n\n// 1D gaussian kernel\nfn gaussian_kernel(radius: f64) -> Vec<f64> {\n\t// Given radius, compute the size of the kernel that's approximately three times the radius\n\tlet kernel_radius = (3. * radius).ceil() as usize;\n\tlet kernel_size = 2 * kernel_radius + 1;\n\tlet mut gaussian_kernel: Vec<f64> = vec![0.; kernel_size];\n\n\t// Kernel values\n\tlet two_radius_squared = 2. * radius * radius;\n\tlet sum = gaussian_kernel\n\t\t.iter_mut()\n\t\t.enumerate()\n\t\t.map(|(i, value_at_index)| {\n\t\t\tlet x = i as f64 - kernel_radius as f64;\n\t\t\tlet exponent = -(x * x) / two_radius_squared;\n\t\t\t*value_at_index = exponent.exp();\n\t\t\t*value_at_index\n\t\t})\n\t\t.sum::<f64>();\n\n\t// Normalize\n\tgaussian_kernel.iter_mut().for_each(|value_at_index| *value_at_index /= sum);\n\n\tgaussian_kernel\n}\n\nfn gaussian_blur_algorithm(mut original_buffer: Image<Color>, radius: f64, gamma: bool) -> Image<Color> {\n\tif gamma {\n\t\toriginal_buffer.map_pixels(|px| px.to_gamma_srgb().to_associated_alpha(px.a()));\n\t} else {\n\t\toriginal_buffer.map_pixels(|px| px.to_associated_alpha(px.a()));\n\t}\n\n\tlet (width, height) = original_buffer.dimensions();\n\n\t// Create 1D gaussian kernel\n\tlet kernel = gaussian_kernel(radius);\n\tlet half_kernel = kernel.len() / 2;\n\n\t// Intermediate buffer for horizontal and vertical passes\n\tlet mut x_axis = Image::new(width, height, Color::TRANSPARENT);\n\tlet mut y_axis = Image::new(width, height, Color::TRANSPARENT);\n\n\tfor pass in [false, true] {\n\t\tlet (max, old_buffer, current_buffer) = match pass {\n\t\t\tfalse => (width, &original_buffer, &mut x_axis),\n\t\t\ttrue => (height, &x_axis, &mut y_axis),\n\t\t};\n\t\tlet pass = pass as usize;\n\n\t\tfor y in 0..height {\n\t\t\tfor x in 0..width {\n\t\t\t\tlet (mut r_sum, mut g_sum, mut b_sum, mut a_sum, mut weight_sum) = (0., 0., 0., 0., 0.);\n\n\t\t\t\tfor (i, &weight) in kernel.iter().enumerate() {\n\t\t\t\t\tlet p = [x, y][pass] as i32 + (i as i32 - half_kernel as i32);\n\n\t\t\t\t\tif p >= 0\n\t\t\t\t\t\t&& p < max as i32 && let Some(px) = old_buffer.get_pixel([p as u32, x][pass], [y, p as u32][pass])\n\t\t\t\t\t{\n\t\t\t\t\t\tr_sum += px.r() as f64 * weight;\n\t\t\t\t\t\tg_sum += px.g() as f64 * weight;\n\t\t\t\t\t\tb_sum += px.b() as f64 * weight;\n\t\t\t\t\t\ta_sum += px.a() as f64 * weight;\n\t\t\t\t\t\tweight_sum += weight;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Normalize\n\t\t\t\tlet (r, g, b, a) = if weight_sum > 0. {\n\t\t\t\t\t((r_sum / weight_sum) as f32, (g_sum / weight_sum) as f32, (b_sum / weight_sum) as f32, (a_sum / weight_sum) as f32)\n\t\t\t\t} else {\n\t\t\t\t\tlet px = old_buffer.get_pixel(x, y).unwrap();\n\t\t\t\t\t(px.r(), px.g(), px.b(), px.a())\n\t\t\t\t};\n\t\t\t\tcurrent_buffer.set_pixel(x, y, Color::from_rgbaf32_unchecked(r, g, b, a));\n\t\t\t}\n\t\t}\n\t}\n\n\tif gamma {\n\t\ty_axis.map_pixels(|px| px.to_linear_srgb().to_unassociated_alpha());\n\t} else {\n\t\ty_axis.map_pixels(|px| px.to_unassociated_alpha());\n\t}\n\n\ty_axis\n}\n\nfn box_blur_algorithm(mut original_buffer: Image<Color>, radius: f64, gamma: bool) -> Image<Color> {\n\tif gamma {\n\t\toriginal_buffer.map_pixels(|px| px.to_gamma_srgb().to_associated_alpha(px.a()));\n\t} else {\n\t\toriginal_buffer.map_pixels(|px| px.to_associated_alpha(px.a()));\n\t}\n\n\tlet (width, height) = original_buffer.dimensions();\n\tlet mut x_axis = Image::new(width, height, Color::TRANSPARENT);\n\tlet mut y_axis = Image::new(width, height, Color::TRANSPARENT);\n\n\tfor pass in [false, true] {\n\t\tlet (max, old_buffer, current_buffer) = match pass {\n\t\t\tfalse => (width, &original_buffer, &mut x_axis),\n\t\t\ttrue => (height, &x_axis, &mut y_axis),\n\t\t};\n\t\tlet pass = pass as usize;\n\n\t\tfor y in 0..height {\n\t\t\tfor x in 0..width {\n\t\t\t\tlet (mut r_sum, mut g_sum, mut b_sum, mut a_sum, mut weight_sum) = (0., 0., 0., 0., 0.);\n\n\t\t\t\tlet i = [x, y][pass];\n\t\t\t\tfor d in (i as i32 - radius as i32).max(0)..=(i as i32 + radius as i32).min(max as i32 - 1) {\n\t\t\t\t\tif let Some(px) = old_buffer.get_pixel([d as u32, x][pass], [y, d as u32][pass]) {\n\t\t\t\t\t\tlet weight = 1.;\n\t\t\t\t\t\tr_sum += px.r() as f64 * weight;\n\t\t\t\t\t\tg_sum += px.g() as f64 * weight;\n\t\t\t\t\t\tb_sum += px.b() as f64 * weight;\n\t\t\t\t\t\ta_sum += px.a() as f64 * weight;\n\t\t\t\t\t\tweight_sum += weight;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tlet (r, g, b, a) = ((r_sum / weight_sum) as f32, (g_sum / weight_sum) as f32, (b_sum / weight_sum) as f32, (a_sum / weight_sum) as f32);\n\t\t\t\tcurrent_buffer.set_pixel(x, y, Color::from_rgbaf32_unchecked(r, g, b, a));\n\t\t\t}\n\t\t}\n\t}\n\n\tif gamma {\n\t\ty_axis.map_pixels(|px| px.to_linear_srgb().to_unassociated_alpha());\n\t} else {\n\t\ty_axis.map_pixels(|px| px.to_unassociated_alpha());\n\t}\n\n\ty_axis\n}\n\nfn median_filter_algorithm(original_buffer: Image<Color>, radius: u32) -> Image<Color> {\n\tlet (width, height) = original_buffer.dimensions();\n\tlet mut output = Image::new(width, height, Color::TRANSPARENT);\n\n\t// Pre-allocate and reuse buffers outside the loops to avoid repeated allocations.\n\tlet window_capacity = ((2 * radius + 1).pow(2)) as usize;\n\tlet mut r_vals: Vec<f32> = Vec::with_capacity(window_capacity);\n\tlet mut g_vals: Vec<f32> = Vec::with_capacity(window_capacity);\n\tlet mut b_vals: Vec<f32> = Vec::with_capacity(window_capacity);\n\tlet mut a_vals: Vec<f32> = Vec::with_capacity(window_capacity);\n\n\tfor y in 0..height {\n\t\tfor x in 0..width {\n\t\t\tr_vals.clear();\n\t\t\tg_vals.clear();\n\t\t\tb_vals.clear();\n\t\t\ta_vals.clear();\n\n\t\t\t// Use saturating_add to avoid potential overflow in extreme cases\n\t\t\tlet y_max = y.saturating_add(radius).min(height - 1);\n\t\t\tlet x_max = x.saturating_add(radius).min(width - 1);\n\n\t\t\tfor ny in y.saturating_sub(radius)..=y_max {\n\t\t\t\tfor nx in x.saturating_sub(radius)..=x_max {\n\t\t\t\t\tif let Some(px) = original_buffer.get_pixel(nx, ny) {\n\t\t\t\t\t\tr_vals.push(px.r());\n\t\t\t\t\t\tg_vals.push(px.g());\n\t\t\t\t\t\tb_vals.push(px.b());\n\t\t\t\t\t\ta_vals.push(px.a());\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet r = median_quickselect(&mut r_vals);\n\t\t\tlet g = median_quickselect(&mut g_vals);\n\t\t\tlet b = median_quickselect(&mut b_vals);\n\t\t\tlet a = median_quickselect(&mut a_vals);\n\n\t\t\toutput.set_pixel(x, y, Color::from_rgbaf32_unchecked(r, g, b, a));\n\t\t}\n\t}\n\n\toutput\n}\n/// Finds the median of a slice using quickselect for O(n) average case performance.\n/// This is more efficient than sorting the entire slice which would be O(n log n).\nfn median_quickselect(values: &mut [f32]) -> f32 {\n\tlet mid: usize = values.len() / 2;\n\t// nth_unstable is like quickselect: average O(n)\n\t// Use total_cmp for safe NaN handling instead of partial_cmp().unwrap()\n\t*values.select_nth_unstable_by(mid, |a, b| a.total_cmp(b)).1\n}\n"
  },
  {
    "path": "node-graph/nodes/raster/src/fullscreen_vertex.rs",
    "content": "use glam::{Vec2, Vec4};\nuse spirv_std::spirv;\n\n/// webgpu NDC is like OpenGL: (-1.0 .. 1.0, -1.0 .. 1.0, 0.0 .. 1.0)\n/// https://www.w3.org/TR/webgpu/#coordinate-systems\n///\n/// So to make a fullscreen triangle around a box at (-1..1):\n///\n/// ```text\n///  3 +\n///    |\\\n///  2 |  \\\n///    |    \\\n///  1 +-----+\n///    |     |\\\n///  0 |  0  |  \\\n///    |     |    \\\n/// -1 +-----+-----+\n///   -1  0  1  2  3\n/// ```\nconst FULLSCREEN_VERTICES: [Vec2; 3] = [Vec2::new(-1., -1.), Vec2::new(-1., 3.), Vec2::new(3., -1.)];\n\n#[spirv(vertex)]\npub fn fullscreen_vertex(#[spirv(vertex_index)] vertex_index: u32, #[spirv(position)] gl_position: &mut Vec4) {\n\t// broken on edition 2024 branch\n\t// let vertex = unsafe { *FULLSCREEN_VERTICES.index_unchecked(vertex_index as usize) };\n\tlet vertex = FULLSCREEN_VERTICES[vertex_index as usize];\n\t*gl_position = Vec4::from((vertex, 0., 1.));\n}\n"
  },
  {
    "path": "node-graph/nodes/raster/src/generate_curves.rs",
    "content": "use crate::curve::{Curve, CurveManipulatorGroup, ValueMapperNode};\nuse core_types::color::{Channel, Linear};\nuse core_types::context::Ctx;\nuse kurbo::{CubicBez, ParamCurve, PathSeg, Point};\nuse vector_types::vector::algorithms::bezpath_algorithms::pathseg_find_tvalues_for_x;\n\nconst WINDOW_SIZE: usize = 1024;\n\n#[node_macro::node(category(\"\"))]\nfn generate_curves<C: Channel + Linear>(_: impl Ctx, curve: Curve, #[implementations(f32, f64)] _target_format: C) -> ValueMapperNode<C> {\n\tlet [mut pos, mut param]: [[f32; 2]; 2] = [[0.; 2], curve.first_handle];\n\tlet mut lut = vec![C::from_f64(0.); WINDOW_SIZE];\n\tlet end = CurveManipulatorGroup {\n\t\tanchor: [1.; 2],\n\t\thandles: [curve.last_handle, [0.; 2]],\n\t};\n\tfor sample in curve.manipulator_groups.iter().chain(std::iter::once(&end)) {\n\t\tlet [x0, y0, x1, y1, x2, y2, x3, y3] = [pos[0], pos[1], param[0], param[1], sample.handles[0][0], sample.handles[0][1], sample.anchor[0], sample.anchor[1]].map(f64::from);\n\n\t\tlet segment = PathSeg::Cubic(CubicBez::new(Point::new(x0, y0), Point::new(x1, y1), Point::new(x2, y2), Point::new(x3, y3)));\n\n\t\tlet [left, right] = [pos[0], sample.anchor[0]].map(|c| c.clamp(0., 1.));\n\t\tlet lut_index_left: usize = (left * (lut.len() - 1) as f32).floor() as _;\n\t\tlet lut_index_right: usize = (right * (lut.len() - 1) as f32).ceil() as _;\n\t\tfor index in lut_index_left..=lut_index_right {\n\t\t\tlet x = index as f64 / (lut.len() - 1) as f64;\n\t\t\tlet y = if x <= x0 {\n\t\t\t\ty0\n\t\t\t} else if x >= x3 {\n\t\t\t\ty3\n\t\t\t} else {\n\t\t\t\tpathseg_find_tvalues_for_x(segment, x)\n\t\t\t\t\t.next()\n\t\t\t\t\t.map(|t| segment.eval(t.clamp(0., 1.)).y)\n\t\t\t\t\t// Fall back to a very bad approximation if the above fails\n\t\t\t\t\t.unwrap_or_else(|| (x - x0) / (x3 - x0) * (y3 - y0) + y0)\n\t\t\t};\n\t\t\tlut[index] = C::from_f64(y);\n\t\t}\n\n\t\tpos = sample.anchor;\n\t\tparam = sample.handles[1];\n\t}\n\tValueMapperNode::new(lut)\n}\n"
  },
  {
    "path": "node-graph/nodes/raster/src/gradient_map.rs",
    "content": "//! Not immediately shader compatible due to needing [`GradientStops`] as a param, which needs [`Vec`]\n\nuse crate::adjust::Adjust;\nuse core_types::table::Table;\nuse core_types::{Color, Ctx};\nuse raster_types::{CPU, Raster};\nuse vector_types::GradientStops;\n\n// Aims for interoperable compatibility with:\n// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27grdm%27%20%3D%20Gradient%20Map\n// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Gradient%20settings%20(Photoshop%206.0)\n#[node_macro::node(category(\"Raster: Adjustment\"))]\nasync fn gradient_map<T: Adjust<Color>>(\n\t_: impl Ctx,\n\t#[implementations(\n\t\tTable<Raster<CPU>>,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t)]\n\tmut image: T,\n\tgradient: Table<GradientStops>,\n\treverse: bool,\n) -> T {\n\tlet Some(row) = gradient.get(0) else { return image };\n\n\timage.adjust(|color| {\n\t\tlet intensity = color.luminance_srgb();\n\t\tlet intensity = if reverse { 1. - intensity } else { intensity };\n\t\trow.element.evaluate(intensity as f64).to_linear_srgb()\n\t});\n\n\timage\n}\n"
  },
  {
    "path": "node-graph/nodes/raster/src/image_color_palette.rs",
    "content": "use core_types::color::Color;\nuse core_types::context::Ctx;\nuse core_types::registry::types::IntegerCount;\nuse core_types::table::{Table, TableRow};\nuse raster_types::{CPU, Raster};\n\n#[node_macro::node(category(\"Color\"))]\nasync fn image_color_palette(_: impl Ctx, image: Table<Raster<CPU>>, #[default(4)] count: IntegerCount) -> Table<Color> {\n\tconst GRID: f32 = 3.;\n\n\tlet bins = GRID * GRID * GRID;\n\n\tlet mut histogram = vec![0; (bins + 1.) as usize];\n\tlet mut color_bins = vec![Vec::new(); (bins + 1.) as usize];\n\n\tfor row in image.iter() {\n\t\tfor pixel in row.element.data.iter() {\n\t\t\tlet r = pixel.r() * GRID;\n\t\t\tlet g = pixel.g() * GRID;\n\t\t\tlet b = pixel.b() * GRID;\n\n\t\t\tlet bin = (r * GRID + g * GRID + b * GRID) as usize;\n\n\t\t\thistogram[bin] += 1;\n\t\t\tcolor_bins[bin].push(pixel.to_gamma_srgb());\n\t\t}\n\t}\n\n\tlet shorted = histogram.iter().enumerate().filter(|&(_, &count)| count > 0).map(|(i, _)| i).collect::<Vec<usize>>();\n\n\tshorted\n\t\t.iter()\n\t\t.take(count as usize)\n\t\t.flat_map(|&i| {\n\t\t\tlet list = &color_bins[i];\n\n\t\t\tlet mut r = 0.;\n\t\t\tlet mut g = 0.;\n\t\t\tlet mut b = 0.;\n\t\t\tlet mut a = 0.;\n\n\t\t\tfor color in list.iter() {\n\t\t\t\tr += color.r();\n\t\t\t\tg += color.g();\n\t\t\t\tb += color.b();\n\t\t\t\ta += color.a();\n\t\t\t}\n\n\t\t\tr /= list.len() as f32;\n\t\t\tg /= list.len() as f32;\n\t\t\tb /= list.len() as f32;\n\t\t\ta /= list.len() as f32;\n\n\t\t\tColor::from_rgbaf32(r, g, b, a).map(TableRow::new_from_element).into_iter()\n\t\t})\n\t\t.collect()\n}\n\n#[cfg(test)]\nmod test {\n\tuse super::*;\n\tuse raster_types::Image;\n\tuse raster_types::Raster;\n\n\t#[test]\n\tfn test_image_color_palette() {\n\t\tlet result = image_color_palette(\n\t\t\t(),\n\t\t\tTable::new_from_element(Raster::new_cpu(Image {\n\t\t\t\twidth: 100,\n\t\t\t\theight: 100,\n\t\t\t\tdata: vec![Color::from_rgbaf32(0., 0., 0., 1.).unwrap(); 10000],\n\t\t\t\tbase64_string: None,\n\t\t\t})),\n\t\t\t1,\n\t\t);\n\t\tassert_eq!(futures::executor::block_on(result), Table::new_from_element(Color::from_rgbaf32(0., 0., 0., 1.).unwrap()));\n\t}\n}\n"
  },
  {
    "path": "node-graph/nodes/raster/src/lib.rs",
    "content": "#![cfg_attr(not(feature = \"std\"), no_std)]\n\npub mod adjust;\npub mod adjustments;\npub mod blending_nodes;\npub mod cubic_spline;\npub mod fullscreen_vertex;\n\n/// required by shader macro\n#[cfg(feature = \"shader-nodes\")]\npub use raster_nodes_shaders::WGSL_SHADER;\n\n#[cfg(feature = \"std\")]\npub mod curve;\n#[cfg(feature = \"std\")]\npub mod dehaze;\n#[cfg(feature = \"std\")]\npub mod filter;\n#[cfg(feature = \"std\")]\npub mod generate_curves;\n#[cfg(feature = \"std\")]\npub mod gradient_map;\n#[cfg(feature = \"std\")]\npub mod image_color_palette;\n#[cfg(feature = \"std\")]\npub mod std_nodes;\n"
  },
  {
    "path": "node-graph/nodes/raster/src/std_nodes.rs",
    "content": "use crate::adjustments::{CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, NoiseType};\nuse core_types::blending::AlphaBlending;\nuse core_types::color::Color;\nuse core_types::color::{Alpha, AlphaMut, Channel, LinearChannel, Luminance, RGBMut};\nuse core_types::context::{Ctx, ExtractFootprint};\nuse core_types::math::bbox::Bbox;\nuse core_types::table::{Table, TableRow};\nuse core_types::transform::Transform;\nuse dyn_any::DynAny;\nuse fastnoise_lite;\nuse glam::{DAffine2, DVec2, Vec2};\nuse rand::prelude::*;\nuse rand_chacha::ChaCha8Rng;\nuse raster_types::Image;\nuse raster_types::{Bitmap, BitmapMut};\nuse raster_types::{CPU, Raster};\nuse std::fmt::Debug;\nuse std::hash::Hash;\n\n#[derive(Debug, DynAny)]\npub enum Error {\n\tIO(std::io::Error),\n\tImage(::image::ImageError),\n}\n\nimpl From<std::io::Error> for Error {\n\tfn from(e: std::io::Error) -> Self {\n\t\tError::IO(e)\n\t}\n}\n\n#[node_macro::node(category(\"Debug\"))]\npub fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: Table<Raster<CPU>>) -> Table<Raster<CPU>> {\n\timage_frame\n\t\t.into_iter()\n\t\t.filter_map(|mut row| {\n\t\t\tlet image_frame_transform = row.transform;\n\t\t\tlet image = row.element;\n\n\t\t\t// Resize the image using the image crate\n\t\t\tlet data = bytemuck::cast_vec(image.data.clone());\n\n\t\t\tlet footprint = ctx.footprint();\n\t\t\tlet viewport_bounds = footprint.viewport_bounds_in_local_space();\n\t\t\tlet image_bounds = Bbox::from_transform(image_frame_transform).to_axis_aligned_bbox();\n\t\t\tlet intersection = viewport_bounds.intersect(&image_bounds);\n\t\t\tlet image_size = DAffine2::from_scale(DVec2::new(image.width as f64, image.height as f64));\n\t\t\tlet size = intersection.size();\n\t\t\tlet size_px = image_size.transform_vector2(size).as_uvec2();\n\n\t\t\t// If the image would not be visible, add nothing.\n\t\t\tif size.x <= 0. || size.y <= 0. {\n\t\t\t\treturn None;\n\t\t\t}\n\n\t\t\tlet image_buffer = ::image::Rgba32FImage::from_raw(image.width, image.height, data).expect(\"Failed to convert internal image format into image-rs data type.\");\n\n\t\t\tlet dynamic_image: ::image::DynamicImage = image_buffer.into();\n\t\t\tlet offset = (intersection.start - image_bounds.start).max(DVec2::ZERO);\n\t\t\tlet offset_px = image_size.transform_vector2(offset).as_uvec2();\n\t\t\tlet cropped = dynamic_image.crop_imm(offset_px.x, offset_px.y, size_px.x, size_px.y);\n\n\t\t\tlet viewport_resolution_x = footprint.transform.transform_vector2(DVec2::X * size.x).length();\n\t\t\tlet viewport_resolution_y = footprint.transform.transform_vector2(DVec2::Y * size.y).length();\n\t\t\tlet mut new_width = size_px.x;\n\t\t\tlet mut new_height = size_px.y;\n\n\t\t\t// Only downscale the image for now\n\t\t\tlet resized = if new_width < image.width || new_height < image.height {\n\t\t\t\tnew_width = viewport_resolution_x as u32;\n\t\t\t\tnew_height = viewport_resolution_y as u32;\n\t\t\t\t// TODO: choose filter based on quality requirements\n\t\t\t\tcropped.resize_exact(new_width, new_height, ::image::imageops::Triangle)\n\t\t\t} else {\n\t\t\t\tcropped\n\t\t\t};\n\t\t\tlet buffer = resized.to_rgba32f();\n\t\t\tlet buffer = buffer.into_raw();\n\t\t\tlet vec = bytemuck::cast_vec(buffer);\n\t\t\tlet image = Image {\n\t\t\t\twidth: new_width,\n\t\t\t\theight: new_height,\n\t\t\t\tdata: vec,\n\t\t\t\tbase64_string: None,\n\t\t\t};\n\t\t\t// we need to adjust the offset if we truncate the offset calculation\n\n\t\t\tlet new_transform = image_frame_transform * DAffine2::from_translation(offset) * DAffine2::from_scale(size);\n\n\t\t\trow.transform = new_transform;\n\t\t\trow.element = Raster::new_cpu(image);\n\t\t\tSome(row)\n\t\t})\n\t\t.collect()\n}\n\n#[node_macro::node(category(\"Raster: Channels\"))]\npub fn combine_channels(\n\t_: impl Ctx,\n\t_primary: (),\n\t#[expose] red: Table<Raster<CPU>>,\n\t#[expose] green: Table<Raster<CPU>>,\n\t#[expose] blue: Table<Raster<CPU>>,\n\t#[expose] alpha: Table<Raster<CPU>>,\n) -> Table<Raster<CPU>> {\n\tlet max_len = red.len().max(green.len()).max(blue.len()).max(alpha.len());\n\tlet red = red.into_iter().map(Some).chain(std::iter::repeat(None)).take(max_len);\n\tlet green = green.into_iter().map(Some).chain(std::iter::repeat(None)).take(max_len);\n\tlet blue = blue.into_iter().map(Some).chain(std::iter::repeat(None)).take(max_len);\n\tlet alpha = alpha.into_iter().map(Some).chain(std::iter::repeat(None)).take(max_len);\n\n\tred.zip(green)\n\t\t.zip(blue)\n\t\t.zip(alpha)\n\t\t.filter_map(|(((red, green), blue), alpha)| {\n\t\t\t// Turn any default zero-sized image rows into None\n\t\t\tlet red = red.filter(|i| i.element.width > 0 && i.element.height > 0);\n\t\t\tlet green = green.filter(|i| i.element.width > 0 && i.element.height > 0);\n\t\t\tlet blue = blue.filter(|i| i.element.width > 0 && i.element.height > 0);\n\t\t\tlet alpha = alpha.filter(|i| i.element.width > 0 && i.element.height > 0);\n\n\t\t\t// Get this row's transform and alpha blending mode from the first non-empty channel\n\t\t\tlet (transform, alpha_blending, source_node_id) = [&red, &green, &blue, &alpha]\n\t\t\t\t.iter()\n\t\t\t\t.find_map(|i| i.as_ref())\n\t\t\t\t.map(|i| (i.transform, i.alpha_blending, i.source_node_id))?;\n\n\t\t\t// Get the common width and height of the channels, which must have equal dimensions\n\t\t\tlet channel_dimensions = [\n\t\t\t\tred.as_ref().map(|r| (r.element.width, r.element.height)),\n\t\t\t\tgreen.as_ref().map(|g| (g.element.width, g.element.height)),\n\t\t\t\tblue.as_ref().map(|b| (b.element.width, b.element.height)),\n\t\t\t\talpha.as_ref().map(|a| (a.element.width, a.element.height)),\n\t\t\t];\n\t\t\tif channel_dimensions.iter().all(Option::is_none)\n\t\t\t\t|| channel_dimensions\n\t\t\t\t\t.iter()\n\t\t\t\t\t.flatten()\n\t\t\t\t\t.any(|&(x, y)| channel_dimensions.iter().flatten().any(|&(other_x, other_y)| x != other_x || y != other_y))\n\t\t\t{\n\t\t\t\treturn None;\n\t\t\t}\n\t\t\tlet &(width, height) = channel_dimensions.iter().flatten().next()?;\n\n\t\t\t// Create a new image for the output element\n\t\t\tlet mut image = Image::new(width, height, Color::TRANSPARENT);\n\n\t\t\t// Iterate over all pixels in the image and set the color channels\n\t\t\tfor y in 0..image.height() {\n\t\t\t\tfor x in 0..image.width() {\n\t\t\t\t\tlet image_pixel = image.get_pixel_mut(x, y).unwrap();\n\n\t\t\t\t\tif let Some(r) = red.as_ref().and_then(|r| r.element.get_pixel(x, y)) {\n\t\t\t\t\t\timage_pixel.set_red(r.l().cast_linear_channel());\n\t\t\t\t\t} else {\n\t\t\t\t\t\timage_pixel.set_red(Channel::from_linear(0.));\n\t\t\t\t\t}\n\t\t\t\t\tif let Some(g) = green.as_ref().and_then(|g| g.element.get_pixel(x, y)) {\n\t\t\t\t\t\timage_pixel.set_green(g.l().cast_linear_channel());\n\t\t\t\t\t} else {\n\t\t\t\t\t\timage_pixel.set_green(Channel::from_linear(0.));\n\t\t\t\t\t}\n\t\t\t\t\tif let Some(b) = blue.as_ref().and_then(|b| b.element.get_pixel(x, y)) {\n\t\t\t\t\t\timage_pixel.set_blue(b.l().cast_linear_channel());\n\t\t\t\t\t} else {\n\t\t\t\t\t\timage_pixel.set_blue(Channel::from_linear(0.));\n\t\t\t\t\t}\n\t\t\t\t\tif let Some(a) = alpha.as_ref().and_then(|a| a.element.get_pixel(x, y)) {\n\t\t\t\t\t\timage_pixel.set_alpha(a.l().cast_linear_channel());\n\t\t\t\t\t} else {\n\t\t\t\t\t\timage_pixel.set_alpha(Channel::from_linear(1.));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tSome(TableRow {\n\t\t\t\telement: Raster::new_cpu(image),\n\t\t\t\ttransform,\n\t\t\t\talpha_blending,\n\t\t\t\tsource_node_id,\n\t\t\t})\n\t\t})\n\t\t.collect()\n}\n\n#[node_macro::node(category(\"Raster\"))]\npub fn mask(\n\t_: impl Ctx,\n\t/// The image to be masked.\n\timage: Table<Raster<CPU>>,\n\t/// The stencil to be used for masking.\n\t#[expose]\n\tstencil: Table<Raster<CPU>>,\n) -> Table<Raster<CPU>> {\n\t// TODO: Figure out what it means to support multiple stencil rows?\n\tlet Some(stencil) = stencil.into_iter().next() else {\n\t\t// No stencil provided so we return the original image\n\t\treturn image;\n\t};\n\tlet stencil_size = DVec2::new(stencil.element.width as f64, stencil.element.height as f64);\n\n\timage\n\t\t.into_iter()\n\t\t.filter_map(|mut row| {\n\t\t\tlet image_size = DVec2::new(row.element.width as f64, row.element.height as f64);\n\t\t\tlet mask_size = stencil.transform.decompose_scale();\n\n\t\t\tif mask_size == DVec2::ZERO {\n\t\t\t\treturn None;\n\t\t\t}\n\n\t\t\t// Transforms a point from the background image to the foreground image\n\t\t\tlet bg_to_fg = row.transform * DAffine2::from_scale(1. / image_size);\n\t\t\tlet stencil_transform_inverse = stencil.transform.inverse();\n\n\t\t\tfor y in 0..row.element.height {\n\t\t\t\tfor x in 0..row.element.width {\n\t\t\t\t\tlet image_point = DVec2::new(x as f64, y as f64);\n\t\t\t\t\tlet mask_point = bg_to_fg.transform_point2(image_point);\n\t\t\t\t\tlet local_mask_point = stencil_transform_inverse.transform_point2(mask_point);\n\t\t\t\t\tlet mask_point = stencil.transform.transform_point2(local_mask_point.clamp(DVec2::ZERO, DVec2::ONE));\n\t\t\t\t\tlet mask_point = (DAffine2::from_scale(stencil_size) * stencil.transform.inverse()).transform_point2(mask_point);\n\n\t\t\t\t\tlet image_pixel = row.element.data_mut().get_pixel_mut(x, y).unwrap();\n\t\t\t\t\tlet mask_pixel = stencil.element.sample(mask_point);\n\t\t\t\t\t*image_pixel = image_pixel.multiplied_alpha(mask_pixel.l().cast_linear_channel());\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tSome(row)\n\t\t})\n\t\t.collect()\n}\n\n#[node_macro::node(category(\"\"))]\npub fn extend_image_to_bounds(_: impl Ctx, image: Table<Raster<CPU>>, bounds: DAffine2) -> Table<Raster<CPU>> {\n\timage\n\t\t.into_iter()\n\t\t.map(|mut row| {\n\t\t\tlet image_aabb = Bbox::unit().affine_transform(row.transform).to_axis_aligned_bbox();\n\t\t\tlet bounds_aabb = Bbox::unit().affine_transform(bounds.transform()).to_axis_aligned_bbox();\n\t\t\tif image_aabb.contains(bounds_aabb.start) && image_aabb.contains(bounds_aabb.end) {\n\t\t\t\treturn row;\n\t\t\t}\n\n\t\t\tlet image_data = &row.element.data;\n\t\t\tlet (image_width, image_height) = (row.element.width, row.element.height);\n\t\t\tif image_width == 0 || image_height == 0 {\n\t\t\t\treturn empty_image((), bounds, Table::new_from_element(Color::TRANSPARENT)).into_iter().next().unwrap();\n\t\t\t}\n\n\t\t\tlet orig_image_scale = DVec2::new(image_width as f64, image_height as f64);\n\t\t\tlet layer_to_image_space = DAffine2::from_scale(orig_image_scale) * row.transform.inverse();\n\t\t\tlet bounds_in_image_space = Bbox::unit().affine_transform(layer_to_image_space * bounds).to_axis_aligned_bbox();\n\n\t\t\tlet new_start = bounds_in_image_space.start.floor().min(DVec2::ZERO);\n\t\t\tlet new_end = bounds_in_image_space.end.ceil().max(orig_image_scale);\n\t\t\tlet new_scale = new_end - new_start;\n\n\t\t\t// Copy over original image into enlarged image.\n\t\t\tlet mut new_image = Image::new(new_scale.x as u32, new_scale.y as u32, Color::TRANSPARENT);\n\t\t\tlet offset_in_new_image = (-new_start).as_uvec2();\n\t\t\tfor y in 0..image_height {\n\t\t\t\tlet old_start = y * image_width;\n\t\t\t\tlet new_start = (y + offset_in_new_image.y) * new_image.width + offset_in_new_image.x;\n\t\t\t\tlet old_row = &image_data[old_start as usize..(old_start + image_width) as usize];\n\t\t\t\tlet new_row = &mut new_image.data[new_start as usize..(new_start + image_width) as usize];\n\t\t\t\tnew_row.copy_from_slice(old_row);\n\t\t\t}\n\n\t\t\t// Compute new transform.\n\t\t\t// let layer_to_new_texture_space = (DAffine2::from_scale(1. / new_scale) * DAffine2::from_translation(new_start) * layer_to_image_space).inverse();\n\t\t\tlet new_texture_to_layer_space = row.transform * DAffine2::from_scale(1. / orig_image_scale) * DAffine2::from_translation(new_start) * DAffine2::from_scale(new_scale);\n\n\t\t\trow.element = Raster::new_cpu(new_image);\n\t\t\trow.transform = new_texture_to_layer_space;\n\t\t\trow\n\t\t})\n\t\t.collect()\n}\n\n#[node_macro::node(category(\"Debug\"))]\npub fn empty_image(_: impl Ctx, transform: DAffine2, color: Table<Color>) -> Table<Raster<CPU>> {\n\tlet width = transform.transform_vector2(DVec2::new(1., 0.)).length() as u32;\n\tlet height = transform.transform_vector2(DVec2::new(0., 1.)).length() as u32;\n\n\tlet color: Option<Color> = color.into();\n\tlet image = Image::new(width, height, color.unwrap_or(Color::WHITE));\n\n\tlet mut result_table = Table::new_from_element(Raster::new_cpu(image));\n\tlet row = result_table.get_mut(0).unwrap();\n\t*row.transform = transform;\n\t*row.alpha_blending = AlphaBlending::default();\n\n\t// Callers of empty_image can safely unwrap on returned table\n\tresult_table\n}\n\n#[node_macro::node(category(\"\"))]\npub fn image_value(_: impl Ctx, _primary: (), image: Table<Raster<CPU>>) -> Table<Raster<CPU>> {\n\timage\n}\n\n#[node_macro::node(category(\"Raster: Pattern\"))]\n#[allow(clippy::too_many_arguments)]\npub fn noise_pattern(\n\tctx: impl ExtractFootprint + Ctx,\n\t_primary: (),\n\tclip: bool,\n\tseed: u32,\n\tscale: f64,\n\tnoise_type: NoiseType,\n\tdomain_warp_type: DomainWarpType,\n\tdomain_warp_amplitude: f64,\n\tfractal_type: FractalType,\n\tfractal_octaves: u32,\n\tfractal_lacunarity: f64,\n\tfractal_gain: f64,\n\tfractal_weighted_strength: f64,\n\tfractal_ping_pong_strength: f64,\n\tcellular_distance_function: CellularDistanceFunction,\n\tcellular_return_type: CellularReturnType,\n\tcellular_jitter: f64,\n) -> Table<Raster<CPU>> {\n\tlet footprint = ctx.footprint();\n\tlet viewport_bounds = footprint.viewport_bounds_in_local_space();\n\n\tlet mut size = viewport_bounds.size();\n\tlet mut offset = viewport_bounds.start;\n\tif clip {\n\t\t// TODO: Remove \"clip\" entirely (and its arbitrary 100x100 clipping square) once we have proper resolution-aware layer clipping\n\t\tconst CLIPPING_SQUARE_SIZE: f64 = 100.;\n\t\tlet image_bounds = Bbox::from_transform(DAffine2::from_scale(DVec2::splat(CLIPPING_SQUARE_SIZE))).to_axis_aligned_bbox();\n\t\tlet intersection = viewport_bounds.intersect(&image_bounds);\n\n\t\toffset = (intersection.start - image_bounds.start).max(DVec2::ZERO);\n\t\tsize = intersection.size();\n\t}\n\n\t// If the image would not be visible, return an empty image\n\tif size.x <= 0. || size.y <= 0. {\n\t\treturn Table::new();\n\t}\n\n\tlet footprint_scale = footprint.scale();\n\tlet width = (size.x * footprint_scale.x) as u32;\n\tlet height = (size.y * footprint_scale.y) as u32;\n\n\t// All\n\tlet mut image = Image::new(width, height, Color::from_luminance(0.5));\n\tlet mut noise = fastnoise_lite::FastNoiseLite::with_seed(seed as i32);\n\tnoise.set_frequency(Some(1. / (scale as f32).max(f32::EPSILON)));\n\n\t// Domain Warp\n\tlet domain_warp_type = match domain_warp_type {\n\t\tDomainWarpType::None => None,\n\t\tDomainWarpType::OpenSimplex2 => Some(fastnoise_lite::DomainWarpType::OpenSimplex2),\n\t\tDomainWarpType::OpenSimplex2Reduced => Some(fastnoise_lite::DomainWarpType::OpenSimplex2Reduced),\n\t\tDomainWarpType::BasicGrid => Some(fastnoise_lite::DomainWarpType::BasicGrid),\n\t};\n\tlet domain_warp_active = domain_warp_type.is_some();\n\tnoise.set_domain_warp_type(domain_warp_type);\n\tnoise.set_domain_warp_amp(Some(domain_warp_amplitude as f32));\n\n\t// Fractal\n\tlet noise_type = match noise_type {\n\t\tNoiseType::Perlin => fastnoise_lite::NoiseType::Perlin,\n\t\tNoiseType::OpenSimplex2 => fastnoise_lite::NoiseType::OpenSimplex2,\n\t\tNoiseType::OpenSimplex2S => fastnoise_lite::NoiseType::OpenSimplex2S,\n\t\tNoiseType::Cellular => fastnoise_lite::NoiseType::Cellular,\n\t\tNoiseType::ValueCubic => fastnoise_lite::NoiseType::ValueCubic,\n\t\tNoiseType::Value => fastnoise_lite::NoiseType::Value,\n\t\tNoiseType::WhiteNoise => {\n\t\t\t// TODO: Generate in layer space, not viewport space\n\n\t\t\tlet mut rng = ChaCha8Rng::seed_from_u64(seed as u64);\n\n\t\t\tfor y in 0..height {\n\t\t\t\tfor x in 0..width {\n\t\t\t\t\tlet pixel = image.get_pixel_mut(x, y).unwrap();\n\t\t\t\t\tlet luminance = rng.random_range(0.0..1.) as f32;\n\t\t\t\t\t*pixel = Color::from_luminance(luminance);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn Table::new_from_row(TableRow {\n\t\t\t\telement: Raster::new_cpu(image),\n\t\t\t\ttransform: DAffine2::from_translation(offset) * DAffine2::from_scale(size),\n\t\t\t\t..Default::default()\n\t\t\t});\n\t\t}\n\t};\n\tnoise.set_noise_type(Some(noise_type));\n\tlet fractal_type = match fractal_type {\n\t\tFractalType::None => fastnoise_lite::FractalType::None,\n\t\tFractalType::FBm => fastnoise_lite::FractalType::FBm,\n\t\tFractalType::Ridged => fastnoise_lite::FractalType::Ridged,\n\t\tFractalType::PingPong => fastnoise_lite::FractalType::PingPong,\n\t\tFractalType::DomainWarpProgressive => fastnoise_lite::FractalType::DomainWarpProgressive,\n\t\tFractalType::DomainWarpIndependent => fastnoise_lite::FractalType::DomainWarpIndependent,\n\t};\n\tnoise.set_fractal_type(Some(fractal_type));\n\tnoise.set_fractal_octaves(Some(fractal_octaves as i32));\n\tnoise.set_fractal_lacunarity(Some(fractal_lacunarity as f32));\n\tnoise.set_fractal_gain(Some(fractal_gain as f32));\n\tnoise.set_fractal_weighted_strength(Some(fractal_weighted_strength as f32));\n\tnoise.set_fractal_ping_pong_strength(Some(fractal_ping_pong_strength as f32));\n\n\t// Cellular\n\tlet cellular_distance_function = match cellular_distance_function {\n\t\tCellularDistanceFunction::Euclidean => fastnoise_lite::CellularDistanceFunction::Euclidean,\n\t\tCellularDistanceFunction::EuclideanSq => fastnoise_lite::CellularDistanceFunction::EuclideanSq,\n\t\tCellularDistanceFunction::Manhattan => fastnoise_lite::CellularDistanceFunction::Manhattan,\n\t\tCellularDistanceFunction::Hybrid => fastnoise_lite::CellularDistanceFunction::Hybrid,\n\t};\n\tlet cellular_return_type = match cellular_return_type {\n\t\tCellularReturnType::CellValue => fastnoise_lite::CellularReturnType::CellValue,\n\t\tCellularReturnType::Nearest => fastnoise_lite::CellularReturnType::Distance,\n\t\tCellularReturnType::NextNearest => fastnoise_lite::CellularReturnType::Distance2,\n\t\tCellularReturnType::Average => fastnoise_lite::CellularReturnType::Distance2Add,\n\t\tCellularReturnType::Difference => fastnoise_lite::CellularReturnType::Distance2Sub,\n\t\tCellularReturnType::Product => fastnoise_lite::CellularReturnType::Distance2Mul,\n\t\tCellularReturnType::Division => fastnoise_lite::CellularReturnType::Distance2Div,\n\t};\n\tnoise.set_cellular_distance_function(Some(cellular_distance_function));\n\tnoise.set_cellular_return_type(Some(cellular_return_type));\n\tnoise.set_cellular_jitter(Some(cellular_jitter as f32));\n\n\tlet coordinate_offset = offset.as_vec2();\n\tlet scale = size.as_vec2() / Vec2::new(width as f32, height as f32);\n\t// Calculate the noise for every pixel\n\tfor y in 0..height {\n\t\tfor x in 0..width {\n\t\t\tlet pixel = image.get_pixel_mut(x, y).unwrap();\n\t\t\tlet pos = Vec2::new(x as f32, y as f32);\n\t\t\tlet vec = pos * scale + coordinate_offset;\n\n\t\t\tlet (mut x, mut y) = (vec.x, vec.y);\n\t\t\tif domain_warp_active && domain_warp_amplitude > 0. {\n\t\t\t\t(x, y) = noise.domain_warp_2d(x, y);\n\t\t\t}\n\n\t\t\tlet luminance = (noise.get_noise_2d(x, y) + 1.) * 0.5;\n\t\t\t*pixel = Color::from_luminance(luminance);\n\t\t}\n\t}\n\n\tTable::new_from_row(TableRow {\n\t\telement: Raster::new_cpu(image),\n\t\ttransform: DAffine2::from_translation(offset) * DAffine2::from_scale(size),\n\t\t..Default::default()\n\t})\n}\n\n#[node_macro::node(category(\"Raster: Pattern\"))]\npub fn mandelbrot(ctx: impl ExtractFootprint + Send) -> Table<Raster<CPU>> {\n\tlet footprint = ctx.footprint();\n\tlet viewport_bounds = footprint.viewport_bounds_in_local_space();\n\n\tlet image_bounds = Bbox::from_transform(DAffine2::IDENTITY).to_axis_aligned_bbox();\n\tlet intersection = viewport_bounds.intersect(&image_bounds);\n\tlet size = intersection.size();\n\n\tlet offset = (intersection.start - image_bounds.start).max(DVec2::ZERO);\n\n\t// If the image would not be visible, return an empty image\n\tif size.x <= 0. || size.y <= 0. {\n\t\treturn Table::new();\n\t}\n\n\tlet scale = footprint.scale();\n\tlet width = (size.x * scale.x) as u32;\n\tlet height = (size.y * scale.y) as u32;\n\n\tlet mut data = Vec::with_capacity(width as usize * height as usize);\n\tlet max_iter = 255;\n\n\tlet scale = 3. * size.as_vec2() / Vec2::new(width as f32, height as f32);\n\tlet coordinate_offset = offset.as_vec2() * 3. - Vec2::new(2., 1.5);\n\tfor y in 0..height {\n\t\tfor x in 0..width {\n\t\t\tlet pos = Vec2::new(x as f32, y as f32);\n\t\t\tlet c = pos * scale + coordinate_offset;\n\n\t\t\tlet iter = mandelbrot_impl(c, max_iter);\n\t\t\tdata.push(map_color(iter, max_iter));\n\t\t}\n\t}\n\n\tTable::new_from_row(TableRow {\n\t\telement: Raster::new_cpu(Image {\n\t\t\twidth,\n\t\t\theight,\n\t\t\tdata,\n\t\t\t..Default::default()\n\t\t}),\n\t\ttransform: DAffine2::from_translation(offset) * DAffine2::from_scale(size),\n\t\t..Default::default()\n\t})\n}\n\n#[inline(always)]\nfn mandelbrot_impl(c: Vec2, max_iter: usize) -> usize {\n\tlet mut z = Vec2::new(0., 0.);\n\tfor i in 0..max_iter {\n\t\tz = Vec2::new(z.x * z.x - z.y * z.y, 2. * z.x * z.y) + c;\n\t\tif z.length_squared() > 4. {\n\t\t\treturn i;\n\t\t}\n\t}\n\tmax_iter\n}\n\nfn map_color(iter: usize, max_iter: usize) -> Color {\n\tlet v = iter as f32 / max_iter as f32;\n\tColor::from_rgbaf32_unchecked(v, v, v, 1.)\n}\n"
  },
  {
    "path": "node-graph/nodes/repeat/Cargo.toml",
    "content": "[package]\nname = \"repeat-nodes\"\nversion = \"0.1.0\"\nedition = \"2024\"\ndescription = \"Repeat operation nodes for Graphene\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\nlicense = \"MIT OR Apache-2.0\"\n\n[features]\ndefault = [\"serde\"]\n\n[dependencies]\n# Local dependencies\ncore-types = { workspace = true }\nvector-types = { workspace = true }\nraster-types = { workspace = true }\nnode-macro = { workspace = true }\ngraphic-types = { workspace = true }\n\n# Workspace dependencies\ndyn-any = { workspace = true }\nglam = { workspace = true }\nlog = { workspace = true }\n\n# Optional workspace dependencies\nserde = { workspace = true, optional = true }\n\n[dev-dependencies]\ngraphene-core = { workspace = true }\nvector-nodes = { workspace = true }\ntokio = { workspace = true, features = [\"macros\", \"rt\"] }\nkurbo = { workspace = true }\n"
  },
  {
    "path": "node-graph/nodes/repeat/src/lib.rs",
    "content": "pub mod repeat_nodes;\n\n// Re-export for convenience\npub use core_types as gcore;\npub use graphic_types;\npub use raster_types;\npub use repeat_nodes::*;\npub use vector_types;\n"
  },
  {
    "path": "node-graph/nodes/repeat/src/repeat_nodes.rs",
    "content": "use crate::gcore::Context;\nuse core::f64::consts::TAU;\nuse core_types::registry::types::{Angle, IntegerCount, PixelSize};\nuse core_types::table::{Table, TableRowRef};\nuse core_types::{CloneVarArgs, Color, Ctx, ExtractAll, InjectVarArgs, OwnedContextImpl};\nuse glam::{DAffine2, DVec2};\nuse graphic_types::{Graphic, Vector};\nuse raster_types::{CPU, Raster};\nuse vector_types::GradientStops;\n\n#[node_macro::node(category(\"Repeat\"))]\nasync fn repeat<T: Into<Graphic> + Default + Send + Clone + 'static>(\n\tctx: impl ExtractAll + CloneVarArgs + Ctx,\n\t#[implementations(\n\t\tContext -> Table<Graphic>,\n\t\tContext -> Table<Vector>,\n\t\tContext -> Table<Raster<CPU>>,\n\t\tContext -> Table<Color>,\n\t\tContext -> Table<GradientStops>,\n\t)]\n\tinstance: impl Node<'n, Context<'static>, Output = Table<T>>,\n\t#[default(1)] count: u64,\n\treverse: bool,\n) -> Table<T> {\n\t// Someday this node can have the option to generate infinitely instead of a fixed count (basically `std::iter::repeat`).\n\n\tlet count = count.max(1) as usize;\n\n\tlet mut result_table = Table::new();\n\n\tfor index in 0..count {\n\t\tlet index = if reverse { count - index - 1 } else { index };\n\n\t\tlet new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index);\n\t\tlet generated_instance = instance.eval(new_ctx.into_context()).await;\n\n\t\tfor generated_row in generated_instance.into_iter() {\n\t\t\tresult_table.push(generated_row);\n\t\t}\n\t}\n\n\tresult_table\n}\n\n#[node_macro::node(category(\"Repeat\"))]\npub async fn repeat_array<T: Into<Graphic> + Default + Send + Clone + 'static>(\n\tctx: impl ExtractAll + CloneVarArgs + Ctx,\n\t#[implementations(\n\t\tContext -> Table<Graphic>,\n\t\tContext -> Table<Vector>,\n\t\tContext -> Table<Raster<CPU>>,\n\t\tContext -> Table<Color>,\n\t\tContext -> Table<GradientStops>,\n\t)]\n\tinstance: impl Node<'n, Context<'static>, Output = Table<T>>,\n\t#[default(100., 100.)]\n\t// TODO: When using a custom Properties panel layout in document_node_definitions.rs and this default is set, the widget weirdly doesn't show up in the Properties panel. Investigation is needed.\n\tdirection: PixelSize,\n\tangle: Angle,\n\t#[default(5)] count: IntegerCount,\n) -> Table<T> {\n\tlet angle = angle.to_radians();\n\tlet count = count.max(1);\n\tlet total = (count - 1) as f64;\n\n\tlet mut result_table = Table::new();\n\n\tfor index in 0..count {\n\t\tlet angle = index as f64 * angle / total;\n\t\tlet translation = index as f64 * direction / total;\n\t\tlet transform = DAffine2::from_angle(angle) * DAffine2::from_translation(translation);\n\n\t\tlet new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index as usize);\n\t\tlet generated_instance = instance.eval(new_ctx.into_context()).await;\n\n\t\tfor row in generated_instance.iter() {\n\t\t\tlet mut row = row.into_cloned();\n\n\t\t\tlet local_translation = DAffine2::from_translation(row.transform.translation);\n\t\t\tlet local_matrix = DAffine2::from_mat2(row.transform.matrix2);\n\t\t\trow.transform = local_translation * transform * local_matrix;\n\n\t\t\tresult_table.push(row);\n\t\t}\n\t}\n\n\tresult_table\n}\n\n#[node_macro::node(category(\"Repeat\"))]\nasync fn repeat_radial<T: Into<Graphic> + Default + Send + Clone + 'static>(\n\tctx: impl ExtractAll + CloneVarArgs + Ctx,\n\t#[implementations(\n\t\tContext -> Table<Graphic>,\n\t\tContext -> Table<Vector>,\n\t\tContext -> Table<Raster<CPU>>,\n\t\tContext -> Table<Color>,\n\t\tContext -> Table<GradientStops>,\n\t)]\n\tinstance: impl Node<'n, Context<'static>, Output = Table<T>>,\n\tstart_angle: Angle,\n\t#[unit(\" px\")]\n\t#[default(5)]\n\tradius: f64,\n\t#[default(5)] count: IntegerCount,\n) -> Table<T> {\n\tlet count = count.max(1);\n\n\tlet mut result_table = Table::new();\n\n\tfor index in 0..count {\n\t\tlet angle = DAffine2::from_angle((TAU / count as f64) * index as f64 + start_angle.to_radians());\n\t\tlet translation = DAffine2::from_translation(radius * DVec2::Y);\n\t\tlet transform = angle * translation;\n\n\t\tlet new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index as usize);\n\t\tlet generated_instance = instance.eval(new_ctx.into_context()).await;\n\n\t\tfor row in generated_instance.iter() {\n\t\t\tlet mut row = row.into_cloned();\n\n\t\t\tlet local_translation = DAffine2::from_translation(row.transform.translation);\n\t\t\tlet local_matrix = DAffine2::from_mat2(row.transform.matrix2);\n\t\t\trow.transform = local_translation * transform * local_matrix;\n\n\t\t\tresult_table.push(row);\n\t\t}\n\t}\n\n\tresult_table\n}\n\n#[node_macro::node(category(\"Repeat\"), name(\"Repeat on Points\"))]\nasync fn repeat_on_points<T: Into<Graphic> + Default + Send + Clone + 'static>(\n\tctx: impl ExtractAll + CloneVarArgs + Sync + Ctx + InjectVarArgs,\n\tpoints: Table<Vector>,\n\t#[implementations(\n\t\tContext -> Table<Graphic>,\n\t\tContext -> Table<Vector>,\n\t\tContext -> Table<Raster<CPU>>,\n\t\tContext -> Table<Color>,\n\t\tContext -> Table<GradientStops>,\n\t)]\n\tinstance: impl Node<'n, Context<'static>, Output = Table<T>>,\n\treverse: bool,\n) -> Table<T> {\n\tlet mut result_table = Table::new();\n\n\tfor TableRowRef { element: points, transform, .. } in points.iter() {\n\t\tlet mut iteration = async |index, point| {\n\t\t\tlet transformed_point = transform.transform_point2(point);\n\n\t\t\tlet new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index).with_position(transformed_point);\n\t\t\tlet generated_instance = instance.eval(new_ctx.into_context()).await;\n\n\t\t\tfor mut generated_row in generated_instance.into_iter() {\n\t\t\t\tgenerated_row.transform.translation = transformed_point;\n\t\t\t\tresult_table.push(generated_row);\n\t\t\t}\n\t\t};\n\n\t\tlet range = points.point_domain.positions().iter().enumerate();\n\t\tif reverse {\n\t\t\tfor (index, &point) in range.rev() {\n\t\t\t\titeration(index, point).await;\n\t\t\t}\n\t\t} else {\n\t\t\tfor (index, &point) in range {\n\t\t\t\titeration(index, point).await;\n\t\t\t}\n\t\t}\n\t}\n\n\tresult_table\n}\n\n#[cfg(test)]\nmod test {\n\tuse super::*;\n\tuse core_types::Ctx;\n\tuse core_types::Node;\n\tuse core_types::transform::Footprint;\n\tuse glam::DVec2;\n\tuse graphene_core::ReadPositionNode;\n\tuse graphene_core::extract_xy::{ExtractXyNode, XY};\n\tuse graphic_types::Vector;\n\tuse kurbo::Shape;\n\tuse kurbo::{BezPath, DEFAULT_ACCURACY, Rect};\n\tuse std::future::Future;\n\tuse std::pin::Pin;\n\tuse vector_nodes::generator_nodes::RectangleNode;\n\tuse vector_types::subpath::Subpath;\n\n\tfn vector_node_from_bezpath(bezpath: BezPath) -> Table<Vector> {\n\t\tTable::new_from_element(Vector::from_bezpath(bezpath))\n\t}\n\n\t#[derive(Clone)]\n\tpub struct FutureWrapperNode<T: Clone>(T);\n\n\timpl<'i, I: Ctx, T: 'i + Clone + Send> Node<'i, I> for FutureWrapperNode<T> {\n\t\ttype Output = Pin<Box<dyn Future<Output = T> + 'i + Send>>;\n\t\tfn eval(&'i self, _input: I) -> Self::Output {\n\t\t\tlet value = self.0.clone();\n\t\t\tBox::pin(async move { value })\n\t\t}\n\t}\n\n\t#[tokio::test]\n\tasync fn repeat_on_points_test() {\n\t\tlet context = OwnedContextImpl::default().into_context();\n\t\tlet rect = RectangleNode::new(\n\t\t\tFutureWrapperNode(()),\n\t\t\tExtractXyNode::new(ReadPositionNode::new(FutureWrapperNode(()), FutureWrapperNode(0)), FutureWrapperNode(XY::Y)),\n\t\t\tFutureWrapperNode(2_f64),\n\t\t\tFutureWrapperNode(false),\n\t\t\tFutureWrapperNode(0_f64),\n\t\t\tFutureWrapperNode(false),\n\t\t);\n\n\t\tlet positions = [DVec2::new(40., 20.), DVec2::ONE, DVec2::new(-42., 9.), DVec2::new(10., 345.)];\n\t\tlet points = Table::new_from_element(Vector::from_subpath(Subpath::from_anchors(positions, false)));\n\t\tlet generated = super::repeat_on_points(context, points, &rect, false).await;\n\t\tassert_eq!(generated.len(), positions.len());\n\t\tfor (position, generated_row) in positions.into_iter().zip(generated.iter()) {\n\t\t\tlet bounds = generated_row.element.bounding_box_with_transform(*generated_row.transform).unwrap();\n\t\t\tassert!(position.abs_diff_eq((bounds[0] + bounds[1]) / 2., 1e-10));\n\t\t\tassert_eq!((bounds[1] - bounds[0]).x, position.y);\n\t\t}\n\t}\n\n\t#[tokio::test]\n\tasync fn repeat() {\n\t\tlet direction = DVec2::X * 1.5;\n\t\tlet count = 3;\n\t\tlet context = OwnedContextImpl::default().into_context();\n\t\tlet repeated = super::repeat_array(\n\t\t\tcontext,\n\t\t\t&FutureWrapperNode(vector_node_from_bezpath(Rect::new(0., 0., 1., 1.).to_path(DEFAULT_ACCURACY))),\n\t\t\tdirection,\n\t\t\t0.,\n\t\t\tcount,\n\t\t)\n\t\t.await;\n\t\tlet vector_table = vector_nodes::flatten_path(Footprint::default(), repeated).await;\n\t\tlet vector = vector_table.iter().next().unwrap().element;\n\t\tassert_eq!(vector.region_manipulator_groups().count(), 3);\n\t\tfor (index, (_, manipulator_groups)) in vector.region_manipulator_groups().enumerate() {\n\t\t\tassert!((manipulator_groups[0].anchor - direction * index as f64 / (count - 1) as f64).length() < 1e-5);\n\t\t}\n\t}\n\n\t#[tokio::test]\n\tasync fn repeat_transform_position() {\n\t\tlet direction = DVec2::new(12., 10.);\n\t\tlet count = 8;\n\t\tlet context = OwnedContextImpl::default().into_context();\n\t\tlet repeated = super::repeat_array(\n\t\t\tcontext,\n\t\t\t&FutureWrapperNode(vector_node_from_bezpath(Rect::new(0., 0., 1., 1.).to_path(DEFAULT_ACCURACY))),\n\t\t\tdirection,\n\t\t\t0.,\n\t\t\tcount,\n\t\t)\n\t\t.await;\n\t\tlet vector_table = vector_nodes::flatten_path(Footprint::default(), repeated).await;\n\t\tlet vector = vector_table.iter().next().unwrap().element;\n\t\tassert_eq!(vector.region_manipulator_groups().count(), 8);\n\t\tfor (index, (_, manipulator_groups)) in vector.region_manipulator_groups().enumerate() {\n\t\t\tassert!((manipulator_groups[0].anchor - direction * index as f64 / (count - 1) as f64).length() < 1e-5);\n\t\t}\n\t}\n\n\t#[tokio::test]\n\tasync fn repeat_radial() {\n\t\tlet context = OwnedContextImpl::default().into_context();\n\t\tlet repeated = super::repeat_radial(context, &FutureWrapperNode(vector_node_from_bezpath(Rect::new(-1., -1., 1., 1.).to_path(DEFAULT_ACCURACY))), 45., 4., 8).await;\n\t\tlet vector_table = vector_nodes::flatten_path(Footprint::default(), repeated).await;\n\t\tlet vector = vector_table.iter().next().unwrap().element;\n\t\tassert_eq!(vector.region_manipulator_groups().count(), 8);\n\n\t\tfor (index, (_, manipulator_groups)) in vector.region_manipulator_groups().enumerate() {\n\t\t\tlet expected_angle = (index as f64 + 1.) * 45.;\n\n\t\t\tlet center = (manipulator_groups[0].anchor + manipulator_groups[2].anchor) / 2.;\n\t\t\tlet actual_angle = DVec2::Y.angle_to(center).to_degrees();\n\n\t\t\tassert!((actual_angle - expected_angle).abs() % 360. < 1e-5, \"Expected {expected_angle} found {actual_angle}\");\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "node-graph/nodes/text/Cargo.toml",
    "content": "[package]\nname = \"text-nodes\"\nversion = \"0.1.0\"\nedition = \"2024\"\ndescription = \"Text operation nodes for Graphene\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\nlicense = \"MIT OR Apache-2.0\"\n\n[features]\ndefault = [\"serde\"]\nwasm = [\"core-types/wasm\", \"tsify\", \"wasm-bindgen\"]\n\n[dependencies]\n# Local dependencies\ncore-types = { workspace = true }\nvector-types = { workspace = true }\nnode-macro = { workspace = true }\n\n# Workspace dependencies\ndyn-any = { workspace = true }\nglam = { workspace = true }\nparley = { workspace = true }\nskrifa = { workspace = true }\nlog = { workspace = true }\n\n# Optional workspace dependencies\nserde = { workspace = true, optional = true }\ntsify = { workspace = true, optional = true }\nwasm-bindgen = { workspace = true, optional = true }\n"
  },
  {
    "path": "node-graph/nodes/text/src/font_cache.rs",
    "content": "use dyn_any::DynAny;\nuse parley::fontique::Blob;\nuse std::collections::HashMap;\nuse std::sync::Arc;\n\n/// A font type (storing font family and font style and an optional preview URL)\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Eq, DynAny)]\npub struct Font {\n\t#[serde(rename = \"fontFamily\")]\n\tpub font_family: String,\n\t#[serde(rename = \"fontStyle\", deserialize_with = \"migrate_font_style\")]\n\tpub font_style: String,\n\t#[serde(skip)]\n\tpub font_style_to_restore: Option<String>,\n}\n\nimpl std::hash::Hash for Font {\n\tfn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n\t\tself.font_family.hash(state);\n\t\tself.font_style.hash(state);\n\t\t// Don't consider `font_style_to_restore` in the HashMaps\n\t}\n}\n\nimpl PartialEq for Font {\n\tfn eq(&self, other: &Self) -> bool {\n\t\t// Don't consider `font_style_to_restore` in the HashMaps\n\t\tself.font_family == other.font_family && self.font_style == other.font_style\n\t}\n}\n\nimpl Font {\n\tpub fn new(font_family: String, font_style: String) -> Self {\n\t\tSelf {\n\t\t\tfont_family,\n\t\t\tfont_style,\n\t\t\tfont_style_to_restore: None,\n\t\t}\n\t}\n\n\tpub fn named_weight(weight: u32) -> &'static str {\n\t\t// From https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight#common_weight_name_mapping\n\t\tmatch weight {\n\t\t\t100 => \"Thin\",\n\t\t\t200 => \"Extra Light\",\n\t\t\t300 => \"Light\",\n\t\t\t400 => \"Regular\",\n\t\t\t500 => \"Medium\",\n\t\t\t600 => \"Semi Bold\",\n\t\t\t700 => \"Bold\",\n\t\t\t800 => \"Extra Bold\",\n\t\t\t900 => \"Black\",\n\t\t\t950 => \"Extra Black\",\n\t\t\t_ => \"Regular\",\n\t\t}\n\t}\n}\nimpl Default for Font {\n\tfn default() -> Self {\n\t\tSelf::new(core_types::consts::DEFAULT_FONT_FAMILY.into(), core_types::consts::DEFAULT_FONT_STYLE.into())\n\t}\n}\n\n/// A cache of all loaded font data and preview urls along with the default font (send from `init_app` in `editor_api.rs`)\n#[derive(Clone, serde::Serialize, serde::Deserialize, Default, DynAny)]\npub struct FontCache {\n\t/// Actual font file data used for rendering a font\n\tfont_file_data: HashMap<Font, Vec<u8>>,\n}\n\nimpl std::fmt::Debug for FontCache {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tf.debug_struct(\"FontCache\").field(\"font_file_data\", &self.font_file_data.keys().collect::<Vec<_>>()).finish()\n\t}\n}\n\nimpl std::hash::Hash for FontCache {\n\tfn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n\t\tself.font_file_data.len().hash(state);\n\t\tself.font_file_data.keys().for_each(|font| font.hash(state));\n\t}\n}\n\nimpl PartialEq for FontCache {\n\tfn eq(&self, other: &Self) -> bool {\n\t\tif self.font_file_data.len() != other.font_file_data.len() {\n\t\t\treturn false;\n\t\t}\n\t\tself.font_file_data.keys().all(|font| other.font_file_data.contains_key(font))\n\t}\n}\n\nimpl FontCache {\n\t/// Returns the font family name if the font is cached, otherwise returns the fallback font family name if that is cached\n\tpub fn resolve_font<'a>(&'a self, font: &'a Font) -> Option<&'a Font> {\n\t\tif self.font_file_data.contains_key(font) {\n\t\t\tSome(font)\n\t\t} else {\n\t\t\tself.font_file_data\n\t\t\t\t.keys()\n\t\t\t\t.find(|font| font.font_family == core_types::consts::DEFAULT_FONT_FAMILY && font.font_style == core_types::consts::DEFAULT_FONT_STYLE)\n\t\t}\n\t}\n\n\t/// Try to get the bytes for a font\n\tpub fn get<'a>(&'a self, font: &'a Font) -> Option<(&'a Vec<u8>, &'a Font)> {\n\t\tself.resolve_font(font).and_then(|font| self.font_file_data.get(font).map(|data| (data, font)))\n\t}\n\n\t/// Get font data as a Blob for use with parley/skrifa\n\tpub fn get_blob<'a>(&'a self, font: &'a Font) -> Option<(Blob<u8>, &'a Font)> {\n\t\tself.get(font).map(|(data, font)| (Blob::new(Arc::new(data.clone())), font))\n\t}\n\n\t/// Check if the font is already loaded\n\tpub fn loaded_font(&self, font: &Font) -> bool {\n\t\tself.font_file_data.contains_key(font)\n\t}\n\n\t/// Insert a new font into the cache\n\tpub fn insert(&mut self, font: Font, data: Vec<u8>) {\n\t\tself.font_file_data.insert(font.clone(), data);\n\t}\n}\n\n// TODO: Eventually remove this migration document upgrade code\nfn migrate_font_style<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<String, D::Error> {\n\tuse serde::Deserialize;\n\tString::deserialize(deserializer).map(|name| if name == \"Normal (400)\" { \"Regular (400)\".to_string() } else { name })\n}\n"
  },
  {
    "path": "node-graph/nodes/text/src/lib.rs",
    "content": "mod font_cache;\nmod path_builder;\nmod text_context;\nmod to_path;\n\nuse dyn_any::DynAny;\npub use font_cache::*;\npub use text_context::TextContext;\npub use to_path::*;\n\n// Re-export for convenience\npub use core_types as gcore;\npub use vector_types;\n\n/// Alignment of lines of type within a text block.\n#[repr(C)]\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)]\n#[widget(Radio)]\npub enum TextAlign {\n\t#[default]\n\tLeft,\n\tCenter,\n\tRight,\n\t#[label(\"Justify\")]\n\tJustifyLeft,\n\t// TODO: JustifyCenter, JustifyRight, JustifyAll\n}\n\nimpl From<TextAlign> for parley::Alignment {\n\tfn from(val: TextAlign) -> Self {\n\t\tmatch val {\n\t\t\tTextAlign::Left => parley::Alignment::Left,\n\t\t\tTextAlign::Center => parley::Alignment::Center,\n\t\t\tTextAlign::Right => parley::Alignment::Right,\n\t\t\tTextAlign::JustifyLeft => parley::Alignment::Justify,\n\t\t}\n\t}\n}\n\n#[derive(PartialEq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]\npub struct TypesettingConfig {\n\tpub font_size: f64,\n\tpub line_height_ratio: f64,\n\tpub character_spacing: f64,\n\tpub max_width: Option<f64>,\n\tpub max_height: Option<f64>,\n\tpub tilt: f64,\n\tpub align: TextAlign,\n}\n\nimpl Default for TypesettingConfig {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tfont_size: 24.,\n\t\t\tline_height_ratio: 1.2,\n\t\t\tcharacter_spacing: 0.,\n\t\t\tmax_width: None,\n\t\t\tmax_height: None,\n\t\t\ttilt: 0.,\n\t\t\talign: TextAlign::default(),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "node-graph/nodes/text/src/path_builder.rs",
    "content": "use core_types::table::{Table, TableRow};\nuse glam::{DAffine2, DVec2};\nuse parley::GlyphRun;\nuse skrifa::GlyphId;\nuse skrifa::instance::{LocationRef, NormalizedCoord, Size};\nuse skrifa::outline::{DrawSettings, OutlinePen};\nuse skrifa::raw::FontRef as ReadFontsRef;\nuse skrifa::{MetadataProvider, OutlineGlyph};\nuse vector_types::subpath::{ManipulatorGroup, Subpath};\nuse vector_types::vector::{PointId, Vector};\n\npub struct PathBuilder<Upstream> {\n\tcurrent_subpath: Subpath<PointId>,\n\torigin: DVec2,\n\tglyph_subpaths: Vec<Subpath<PointId>>,\n\tpub vector_table: Table<Vector<Upstream>>,\n\tscale: f64,\n\tid: PointId,\n}\n\nimpl<Upstream: Default + 'static> PathBuilder<Upstream> {\n\tpub fn new(per_glyph_instances: bool, scale: f64) -> Self {\n\t\tSelf {\n\t\t\tcurrent_subpath: Subpath::new(Vec::new(), false),\n\t\t\tglyph_subpaths: Vec::new(),\n\t\t\tvector_table: if per_glyph_instances { Table::new() } else { Table::new_from_element(Vector::default()) },\n\t\t\tscale,\n\t\t\tid: PointId::ZERO,\n\t\t\torigin: DVec2::default(),\n\t\t}\n\t}\n\n\tfn point(&self, x: f32, y: f32) -> DVec2 {\n\t\tDVec2::new(self.origin.x + x as f64, self.origin.y - y as f64) * self.scale\n\t}\n\n\t#[allow(clippy::too_many_arguments)]\n\tfn draw_glyph(&mut self, glyph: &OutlineGlyph<'_>, size: f32, normalized_coords: &[NormalizedCoord], glyph_offset: DVec2, style_skew: Option<DAffine2>, skew: DAffine2, per_glyph_instances: bool) {\n\t\tlet location_ref = LocationRef::new(normalized_coords);\n\t\tlet settings = DrawSettings::unhinted(Size::new(size), location_ref);\n\t\tglyph.draw(settings, self).unwrap();\n\n\t\t// Apply transforms in correct order: style-based skew first, then user-requested skew\n\t\t// This ensures font synthesis (italic) is applied before user transformations\n\t\tfor glyph_subpath in &mut self.glyph_subpaths {\n\t\t\tif let Some(style_skew) = style_skew {\n\t\t\t\tglyph_subpath.apply_transform(style_skew);\n\t\t\t}\n\n\t\t\tglyph_subpath.apply_transform(skew);\n\t\t}\n\n\t\tif per_glyph_instances {\n\t\t\tself.vector_table.push(TableRow {\n\t\t\t\telement: Vector::from_subpaths(core::mem::take(&mut self.glyph_subpaths), false),\n\t\t\t\ttransform: DAffine2::from_translation(glyph_offset),\n\t\t\t\t..Default::default()\n\t\t\t});\n\t\t} else {\n\t\t\tfor subpath in self.glyph_subpaths.drain(..) {\n\t\t\t\t// Unwrapping here is ok because `self.vector_table` is initialized with a single `Vector` table element\n\t\t\t\tself.vector_table.get_mut(0).unwrap().element.append_subpath(subpath, false);\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn render_glyph_run(&mut self, glyph_run: &GlyphRun<'_, ()>, tilt: f64, per_glyph_instances: bool) {\n\t\tlet mut run_x = glyph_run.offset();\n\t\tlet run_y = glyph_run.baseline();\n\n\t\tlet run = glyph_run.run();\n\n\t\t// User-requested tilt applied around baseline to avoid vertical displacement\n\t\t// Translation ensures rotation point is at the baseline, not origin\n\t\tlet skew = if per_glyph_instances {\n\t\t\tDAffine2::from_cols_array(&[1., 0., -tilt.to_radians().tan(), 1., 0., 0.])\n\t\t} else {\n\t\t\tDAffine2::from_translation(DVec2::new(0., run_y as f64))\n\t\t\t\t* DAffine2::from_cols_array(&[1., 0., -tilt.to_radians().tan(), 1., 0., 0.])\n\t\t\t\t* DAffine2::from_translation(DVec2::new(0., -run_y as f64))\n\t\t};\n\n\t\tlet synthesis = run.synthesis();\n\n\t\t// Font synthesis (e.g., synthetic italic) applied separately from user transforms\n\t\t// This preserves the distinction between font styling and user transformations\n\t\tlet style_skew = synthesis.skew().map(|angle| {\n\t\t\tif per_glyph_instances {\n\t\t\t\tDAffine2::from_cols_array(&[1., 0., -angle.to_radians().tan() as f64, 1., 0., 0.])\n\t\t\t} else {\n\t\t\t\tDAffine2::from_translation(DVec2::new(0., run_y as f64))\n\t\t\t\t\t* DAffine2::from_cols_array(&[1., 0., -angle.to_radians().tan() as f64, 1., 0., 0.])\n\t\t\t\t\t* DAffine2::from_translation(DVec2::new(0., -run_y as f64))\n\t\t\t}\n\t\t});\n\n\t\tlet font = run.font();\n\t\tlet font_size = run.font_size();\n\n\t\tlet normalized_coords = run.normalized_coords().iter().map(|coord| NormalizedCoord::from_bits(*coord)).collect::<Vec<_>>();\n\n\t\t// TODO: This can be cached for better performance\n\t\tlet font_collection_ref = font.data.as_ref();\n\t\tlet font_ref = ReadFontsRef::from_index(font_collection_ref, font.index).unwrap();\n\t\tlet outlines = font_ref.outline_glyphs();\n\n\t\tfor glyph in glyph_run.glyphs() {\n\t\t\tlet glyph_offset = DVec2::new((run_x + glyph.x) as f64, (run_y - glyph.y) as f64);\n\t\t\trun_x += glyph.advance;\n\n\t\t\tlet glyph_id = GlyphId::from(glyph.id);\n\t\t\tif let Some(glyph_outline) = outlines.get(glyph_id) {\n\t\t\t\tif !per_glyph_instances {\n\t\t\t\t\tself.origin = glyph_offset;\n\t\t\t\t}\n\t\t\t\tself.draw_glyph(&glyph_outline, font_size, &normalized_coords, glyph_offset, style_skew, skew, per_glyph_instances);\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn finalize(mut self) -> Table<Vector<Upstream>> {\n\t\tif self.vector_table.is_empty() {\n\t\t\tself.vector_table = Table::new_from_element(Vector::default());\n\t\t}\n\t\tself.vector_table\n\t}\n}\n\nimpl<Upstream: Default + 'static> OutlinePen for PathBuilder<Upstream> {\n\tfn move_to(&mut self, x: f32, y: f32) {\n\t\tif !self.current_subpath.is_empty() {\n\t\t\tself.glyph_subpaths.push(std::mem::replace(&mut self.current_subpath, Subpath::new(Vec::new(), false)));\n\t\t}\n\t\tself.current_subpath.push_manipulator_group(ManipulatorGroup::new_anchor_with_id(self.point(x, y), self.id.next_id()));\n\t}\n\n\tfn line_to(&mut self, x: f32, y: f32) {\n\t\tself.current_subpath.push_manipulator_group(ManipulatorGroup::new_anchor_with_id(self.point(x, y), self.id.next_id()));\n\t}\n\n\tfn quad_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32) {\n\t\tlet [handle, anchor] = [self.point(x1, y1), self.point(x2, y2)];\n\t\tself.current_subpath.last_manipulator_group_mut().unwrap().out_handle = Some(handle);\n\t\tself.current_subpath.push_manipulator_group(ManipulatorGroup::new_with_id(anchor, None, None, self.id.next_id()));\n\t}\n\n\tfn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32) {\n\t\tlet [handle1, handle2, anchor] = [self.point(x1, y1), self.point(x2, y2), self.point(x3, y3)];\n\t\tself.current_subpath.last_manipulator_group_mut().unwrap().out_handle = Some(handle1);\n\t\tself.current_subpath\n\t\t\t.push_manipulator_group(ManipulatorGroup::new_with_id(anchor, Some(handle2), None, self.id.next_id()));\n\t}\n\n\tfn close(&mut self) {\n\t\tself.current_subpath.set_closed(true);\n\t\tself.glyph_subpaths.push(std::mem::replace(&mut self.current_subpath, Subpath::new(Vec::new(), false)));\n\t}\n}\n"
  },
  {
    "path": "node-graph/nodes/text/src/text_context.rs",
    "content": "use super::{Font, FontCache, TypesettingConfig};\nuse core::cell::RefCell;\nuse core_types::table::Table;\nuse glam::DVec2;\nuse parley::fontique::{Blob, FamilyId, FontInfo};\nuse parley::{AlignmentOptions, FontContext, Layout, LayoutContext, LineHeight, PositionedLayoutItem, StyleProperty};\nuse std::collections::HashMap;\nuse vector_types::Vector;\n\nuse super::path_builder::PathBuilder;\n\nthread_local! {\n\tstatic THREAD_TEXT: RefCell<TextContext> = RefCell::new(TextContext::default());\n}\n\n/// Unified thread-local text processing context that combines font and layout management\n/// for efficient text rendering operations.\n#[derive(Default)]\npub struct TextContext {\n\tfont_context: FontContext,\n\tlayout_context: LayoutContext<()>,\n\t/// Cached font metadata for performance optimization\n\tfont_info_cache: HashMap<Font, (FamilyId, FontInfo)>,\n}\n\nimpl TextContext {\n\t/// Access the thread-local TextContext instance for text processing operations\n\tpub fn with_thread_local<F, R>(f: F) -> R\n\twhere\n\t\tF: FnOnce(&mut TextContext) -> R,\n\t{\n\t\tTHREAD_TEXT.with_borrow_mut(f)\n\t}\n\n\t/// Resolve a font and return its data as a Blob if available\n\tfn resolve_font_data<'a>(&self, font: &'a Font, font_cache: &'a FontCache) -> Option<(Blob<u8>, &'a Font)> {\n\t\tfont_cache.get_blob(font)\n\t}\n\n\t/// Get or cache font information for a given font\n\tfn get_font_info(&mut self, font: &Font, font_data: &Blob<u8>) -> Option<(String, FontInfo)> {\n\t\t// Check if we already have the font info cached\n\t\tif let Some((family_id, font_info)) = self.font_info_cache.get(font)\n\t\t\t&& let Some(family_name) = self.font_context.collection.family_name(*family_id)\n\t\t{\n\t\t\treturn Some((family_name.to_string(), font_info.clone()));\n\t\t}\n\n\t\t// Register the font and cache the info\n\t\tlet families = self.font_context.collection.register_fonts(font_data.clone(), None);\n\n\t\tfamilies.first().and_then(|(family_id, fonts_info)| {\n\t\t\tfonts_info.first().and_then(|font_info| {\n\t\t\t\tself.font_context.collection.family_name(*family_id).map(|family_name| {\n\t\t\t\t\t// Cache the font info for future use\n\t\t\t\t\tself.font_info_cache.insert(font.clone(), (*family_id, font_info.clone()));\n\t\t\t\t\t(family_name.to_string(), font_info.clone())\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\t}\n\n\t/// Create a text layout using the specified font and typesetting configuration\n\tfn layout_text(&mut self, text: &str, font: &Font, font_cache: &FontCache, typesetting: TypesettingConfig) -> Option<Layout<()>> {\n\t\t// Note that the actual_font may not be the desired font if that font is not yet loaded.\n\t\t// It is important not to cache the default font under the name of another font.\n\t\tlet (font_data, actual_font) = self.resolve_font_data(font, font_cache)?;\n\t\tlet (font_family, font_info) = self.get_font_info(actual_font, &font_data)?;\n\n\t\tconst DISPLAY_SCALE: f32 = 1.;\n\t\tlet mut builder = self.layout_context.ranged_builder(&mut self.font_context, text, DISPLAY_SCALE, false);\n\n\t\tbuilder.push_default(StyleProperty::FontSize(typesetting.font_size as f32));\n\t\tbuilder.push_default(StyleProperty::LetterSpacing(typesetting.character_spacing as f32));\n\t\tbuilder.push_default(StyleProperty::FontStack(parley::FontStack::Single(parley::FontFamily::Named(std::borrow::Cow::Owned(font_family)))));\n\t\tbuilder.push_default(StyleProperty::FontWeight(font_info.weight()));\n\t\tbuilder.push_default(StyleProperty::FontStyle(font_info.style()));\n\t\tbuilder.push_default(StyleProperty::FontWidth(font_info.width()));\n\t\tbuilder.push_default(LineHeight::FontSizeRelative(typesetting.line_height_ratio as f32));\n\n\t\tlet mut layout: Layout<()> = builder.build(text);\n\n\t\tlayout.break_all_lines(typesetting.max_width.map(|mw| mw as f32));\n\t\tlayout.align(typesetting.max_width.map(|max_w| max_w as f32), typesetting.align.into(), AlignmentOptions::default());\n\n\t\tSome(layout)\n\t}\n\n\t/// Convert text to vector paths using the specified font and typesetting configuration\n\tpub fn to_path<Upstream: Default + 'static>(&mut self, text: &str, font: &Font, font_cache: &FontCache, typesetting: TypesettingConfig, per_glyph_instances: bool) -> Table<Vector<Upstream>> {\n\t\tlet Some(layout) = self.layout_text(text, font, font_cache, typesetting) else {\n\t\t\treturn Table::new_from_element(Vector::default());\n\t\t};\n\n\t\tlet mut path_builder = PathBuilder::new(per_glyph_instances, layout.scale() as f64);\n\n\t\tfor line in layout.lines() {\n\t\t\tfor item in line.items() {\n\t\t\t\tif let PositionedLayoutItem::GlyphRun(glyph_run) = item\n\t\t\t\t\t&& typesetting.max_height.filter(|&max_height| glyph_run.baseline() > max_height as f32).is_none()\n\t\t\t\t{\n\t\t\t\t\tpath_builder.render_glyph_run(&glyph_run, typesetting.tilt, per_glyph_instances);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tpath_builder.finalize()\n\t}\n\n\t/// Calculate the bounding box of text using the specified font and typesetting configuration\n\tpub fn bounding_box(&mut self, text: &str, font: &Font, font_cache: &FontCache, typesetting: TypesettingConfig, for_clipping_test: bool) -> DVec2 {\n\t\tlet Some(layout) = self.layout_text(text, font, font_cache, typesetting) else {\n\t\t\treturn DVec2::ZERO;\n\t\t};\n\n\t\tlet layout_width = layout.full_width() as f64;\n\t\tlet layout_height = layout.height() as f64;\n\n\t\tif for_clipping_test {\n\t\t\treturn DVec2::new(layout_width, layout_height);\n\t\t}\n\n\t\tlet width = typesetting.max_width.unwrap_or(layout_width);\n\t\tlet height = typesetting.max_height.unwrap_or(layout_height);\n\n\t\tDVec2::new(width, height)\n\t}\n\n\t/// Check if text lines are being clipped due to height constraints\n\tpub fn lines_clipping(&mut self, text: &str, font: &Font, font_cache: &FontCache, typesetting: TypesettingConfig) -> bool {\n\t\tlet Some(max_height) = typesetting.max_height else { return false };\n\t\tlet bounds = self.bounding_box(text, font, font_cache, typesetting, true);\n\t\tmax_height < bounds.y\n\t}\n}\n"
  },
  {
    "path": "node-graph/nodes/text/src/to_path.rs",
    "content": "use super::text_context::TextContext;\nuse super::{Font, FontCache, TypesettingConfig};\nuse core_types::table::Table;\nuse glam::DVec2;\nuse parley::fontique::Blob;\nuse std::sync::Arc;\nuse vector_types::Vector;\n\npub fn to_path<Upstream: Default + 'static>(text: &str, font: &Font, font_cache: &FontCache, typesetting: TypesettingConfig, per_glyph_instances: bool) -> Table<Vector<Upstream>> {\n\tTextContext::with_thread_local(|ctx| ctx.to_path(text, font, font_cache, typesetting, per_glyph_instances))\n}\n\npub fn bounding_box(text: &str, font: &Font, font_cache: &FontCache, typesetting: TypesettingConfig, for_clipping_test: bool) -> DVec2 {\n\tTextContext::with_thread_local(|ctx| ctx.bounding_box(text, font, font_cache, typesetting, for_clipping_test))\n}\n\npub fn load_font(data: &[u8]) -> Blob<u8> {\n\tBlob::new(Arc::new(data.to_vec()))\n}\n\npub fn lines_clipping(text: &str, font: &Font, font_cache: &FontCache, typesetting: TypesettingConfig) -> bool {\n\tTextContext::with_thread_local(|ctx| ctx.lines_clipping(text, font, font_cache, typesetting))\n}\n"
  },
  {
    "path": "node-graph/nodes/transform/Cargo.toml",
    "content": "[package]\nname = \"transform-nodes\"\nversion = \"0.1.0\"\nedition = \"2024\"\ndescription = \"Transform operation nodes for Graphene\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\nlicense = \"MIT OR Apache-2.0\"\n\n[features]\ndefault = [\"serde\"]\n\n[dependencies]\n# Local dependencies\ncore-types = { workspace = true }\nvector-types = { workspace = true }\ngraphic-types = { workspace = true }\nnode-macro = { workspace = true }\n\n# Workspace dependencies\nglam = { workspace = true }\nrand = { workspace = true }\n\n# Optional workspace dependencies\nserde = { workspace = true, optional = true }\n"
  },
  {
    "path": "node-graph/nodes/transform/src/lib.rs",
    "content": "pub mod transform_nodes;\n\n// Re-export for convenience\npub use core_types as gcore;\npub use graphic_types;\npub use transform_nodes::*;\npub use vector_types;\n"
  },
  {
    "path": "node-graph/nodes/transform/src/transform_nodes.rs",
    "content": "use core::f64;\nuse core_types::color::Color;\nuse core_types::table::Table;\nuse core_types::transform::{ApplyTransform, Transform};\nuse core_types::{CloneVarArgs, Context, Ctx, ExtractAll, InjectFootprint, ModifyFootprint, OwnedContextImpl};\nuse glam::{DAffine2, DMat2, DVec2};\nuse graphic_types::Graphic;\nuse graphic_types::Vector;\nuse graphic_types::raster_types::{CPU, GPU, Raster};\nuse vector_types::GradientStops;\n\n/// Applies the specified transform to the input value, which may be a graphic type or another transform.\n#[node_macro::node(category(\"\"))]\nasync fn transform<T: ApplyTransform + 'n + 'static>(\n\tctx: impl Ctx + CloneVarArgs + ExtractAll + ModifyFootprint,\n\t#[implementations(\n\t\tContext -> DAffine2,\n\t\tContext -> DVec2,\n\t\tContext -> Table<Graphic>,\n\t\tContext -> Table<Vector>,\n\t\tContext -> Table<Raster<CPU>>,\n\t\tContext -> Table<Raster<GPU>>,\n\t\tContext -> Table<Color>,\n\t\tContext -> Table<GradientStops>,\n\t)]\n\tcontent: impl Node<Context<'static>, Output = T>,\n\ttranslation: DVec2,\n\trotation: f64,\n\tscale: DVec2,\n\tskew: DVec2,\n) -> T {\n\tlet trs = DAffine2::from_scale_angle_translation(scale, rotation.to_radians(), translation);\n\tlet skew = DAffine2::from_cols_array(&[1., skew.y.to_radians().tan(), skew.x.to_radians().tan(), 1., 0., 0.]);\n\tlet matrix = trs * skew;\n\n\tlet footprint = ctx.try_footprint().copied();\n\n\tlet mut ctx = OwnedContextImpl::from(ctx);\n\tif let Some(mut footprint) = footprint {\n\t\tfootprint.apply_transform(&matrix);\n\t\tctx = ctx.with_footprint(footprint);\n\t}\n\n\tlet mut transform_target = content.eval(ctx.into_context()).await;\n\n\ttransform_target.left_apply_transform(&matrix);\n\n\ttransform_target\n}\n\n/// Resets the desired components of the input transform to their default values. If all components are reset, the output will be set to the identity transform.\n/// Shear is represented jointly by rotation and scale, so resetting both will also remove any shear.\n#[node_macro::node(category(\"Math: Transform\"))]\nfn reset_transform<T>(\n\t_: impl Ctx,\n\t#[implementations(\n\t\tTable<Graphic>,\n\t\tTable<Vector>,\n\t\tTable<Raster<CPU>>,\n\t\tTable<Raster<GPU>>,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t)]\n\tmut content: Table<T>,\n\t#[default(true)] reset_translation: bool,\n\treset_rotation: bool,\n\treset_scale: bool,\n) -> Table<T> {\n\tfor row in content.iter_mut() {\n\t\t// Translation\n\t\tif reset_translation {\n\t\t\trow.transform.translation = DVec2::ZERO;\n\t\t}\n\t\t// (Rotation, Scale)\n\t\tmatch (reset_rotation, reset_scale) {\n\t\t\t(true, true) => {\n\t\t\t\trow.transform.matrix2 = DMat2::IDENTITY;\n\t\t\t}\n\t\t\t(true, false) => {\n\t\t\t\tlet scale = row.transform.decompose_scale();\n\t\t\t\trow.transform.matrix2 = DMat2::from_diagonal(scale);\n\t\t\t}\n\t\t\t(false, true) => {\n\t\t\t\tlet rotation = row.transform.decompose_rotation();\n\t\t\t\tlet rotation_matrix = DMat2::from_angle(rotation);\n\t\t\t\trow.transform.matrix2 = rotation_matrix;\n\t\t\t}\n\t\t\t(false, false) => {}\n\t\t}\n\t}\n\tcontent\n}\n\n/// Overwrites the transform of each element in the input table with the specified transform.\n#[node_macro::node(category(\"Math: Transform\"))]\nfn replace_transform<T>(\n\t_: impl Ctx + InjectFootprint,\n\t#[implementations(\n\t\tTable<Graphic>,\n\t\tTable<Vector>,\n\t\tTable<Raster<CPU>>,\n\t\tTable<Raster<GPU>>,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t)]\n\tmut content: Table<T>,\n\ttransform: DAffine2,\n) -> Table<T> {\n\tfor row in content.iter_mut() {\n\t\t*row.transform = transform.transform();\n\t}\n\tcontent\n}\n\n// TODO: Figure out how this node should behave once #2982 is implemented.\n/// Obtains the transform of the first element in the input table, if present.\n#[node_macro::node(category(\"Math: Transform\"), path(core_types::vector))]\nasync fn extract_transform<T>(\n\t_: impl Ctx,\n\t#[implementations(\n\t\tTable<Graphic>,\n\t\tTable<Vector>,\n\t\tTable<Raster<CPU>>,\n\t\tTable<Raster<GPU>>,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t)]\n\tcontent: Table<T>,\n) -> DAffine2 {\n\tcontent.iter().next().map(|row| *row.transform).unwrap_or_default()\n}\n\n/// Produces the inverse of the input transform, which is the transform that undoes the effect of the original transform.\n#[node_macro::node(category(\"Math: Transform\"))]\nfn invert_transform(_: impl Ctx, transform: DAffine2) -> DAffine2 {\n\ttransform.inverse()\n}\n\n/// Extracts the translation component from the input transform.\n#[node_macro::node(category(\"Math: Transform\"))]\nfn decompose_translation(_: impl Ctx, transform: DAffine2) -> DVec2 {\n\ttransform.translation\n}\n\n/// Extracts the rotation component (in degrees) from the input transform.\n/// This, together with the \"Decompose Scale\" node, also may jointly represent any shear component in the original transform.\n#[node_macro::node(category(\"Math: Transform\"))]\nfn decompose_rotation(_: impl Ctx, transform: DAffine2) -> f64 {\n\ttransform.decompose_rotation().to_degrees()\n}\n\n/// Extracts the scale component from the input transform.\n/// This, together with the \"Decompose Rotation\" node, also may jointly represent any shear component in the original transform.\n#[node_macro::node(category(\"Math: Transform\"))]\nfn decompose_scale(_: impl Ctx, transform: DAffine2) -> DVec2 {\n\ttransform.decompose_scale()\n}\n"
  },
  {
    "path": "node-graph/nodes/vector/Cargo.toml",
    "content": "[package]\nname = \"vector-nodes\"\nversion = \"0.1.0\"\nedition = \"2024\"\ndescription = \"Vector operation nodes for Graphene\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\nlicense = \"MIT OR Apache-2.0\"\n\n[features]\ndefault = [\"serde\"]\nwasm = [\"core-types/wasm\", \"tsify\", \"wasm-bindgen\"]\n\n[dependencies]\n# Local dependencies\ncore-types = { workspace = true }\nvector-types = { workspace = true }\ngraphic-types = { workspace = true }\nnode-macro = { workspace = true }\nrepeat-nodes = { workspace = true }\n\n# Workspace dependencies\ndyn-any = { workspace = true }\nglam = { workspace = true }\nkurbo = { workspace = true }\nrand = { workspace = true }\nrustc-hash = { workspace = true }\nlog = { workspace = true }\nqrcodegen = { workspace = true }\n\n# Optional workspace dependencies\nserde = { workspace = true, optional = true }\ntsify = { workspace = true, optional = true }\nwasm-bindgen = { workspace = true, optional = true }\n\n[dev-dependencies]\ngraphene-core = { workspace = true }\ntokio = { workspace = true, features = [\"macros\", \"rt\"] }\nfutures = { workspace = true }\n"
  },
  {
    "path": "node-graph/nodes/vector/src/generator_nodes.rs",
    "content": "use core_types::Ctx;\nuse core_types::registry::types::{Angle, PixelLength, PixelSize};\nuse core_types::table::Table;\nuse dyn_any::DynAny;\nuse glam::DVec2;\nuse graphic_types::Vector;\nuse vector_types::subpath;\nuse vector_types::vector::misc::{ArcType, AsU64, GridType};\nuse vector_types::vector::misc::{HandleId, SpiralType};\nuse vector_types::vector::{PointId, SegmentId, StrokeId};\n\ntrait CornerRadius {\n\tfn generate(self, size: DVec2, clamped: bool) -> Table<Vector>;\n}\nimpl CornerRadius for f64 {\n\tfn generate(self, size: DVec2, clamped: bool) -> Table<Vector> {\n\t\tlet clamped_radius = if clamped { self.clamp(0., size.x.min(size.y).max(0.) / 2.) } else { self };\n\t\tTable::new_from_element(Vector::from_subpath(subpath::Subpath::new_rounded_rectangle(size / -2., size / 2., [clamped_radius; 4])))\n\t}\n}\nimpl CornerRadius for [f64; 4] {\n\tfn generate(self, size: DVec2, clamped: bool) -> Table<Vector> {\n\t\tlet clamped_radius = if clamped {\n\t\t\t// Algorithm follows the CSS spec: <https://drafts.csswg.org/css-backgrounds/#corner-overlap>\n\n\t\t\tlet mut scale_factor: f64 = 1.;\n\t\t\tfor i in 0..4 {\n\t\t\t\tlet side_length = if i % 2 == 0 { size.x } else { size.y };\n\t\t\t\tlet adjacent_corner_radius_sum = self[i] + self[(i + 1) % 4];\n\t\t\t\tif side_length < adjacent_corner_radius_sum {\n\t\t\t\t\tscale_factor = scale_factor.min(side_length / adjacent_corner_radius_sum);\n\t\t\t\t}\n\t\t\t}\n\t\t\tself.map(|x| x * scale_factor)\n\t\t} else {\n\t\t\tself\n\t\t};\n\t\tTable::new_from_element(Vector::from_subpath(subpath::Subpath::new_rounded_rectangle(size / -2., size / 2., clamped_radius)))\n\t}\n}\n\n/// Generates a circle shape with a chosen radius.\n#[node_macro::node(category(\"Vector: Shape\"))]\nfn circle(\n\t_: impl Ctx,\n\t_primary: (),\n\t#[unit(\" px\")]\n\t#[default(50.)]\n\tradius: f64,\n) -> Table<Vector> {\n\tlet radius = radius.abs();\n\tTable::new_from_element(Vector::from_subpath(subpath::Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))))\n}\n\n/// Generates an arc shape forming a portion of a circle which may be open, closed, or a pie slice.\n#[node_macro::node(category(\"Vector: Shape\"))]\nfn arc(\n\t_: impl Ctx,\n\t_primary: (),\n\t#[unit(\" px\")]\n\t#[default(50.)]\n\tradius: f64,\n\tstart_angle: Angle,\n\t#[default(270.)]\n\t#[range((0., 360.))]\n\tsweep_angle: Angle,\n\tarc_type: ArcType,\n) -> Table<Vector> {\n\tTable::new_from_element(Vector::from_subpath(subpath::Subpath::new_arc(\n\t\tradius,\n\t\tstart_angle / 360. * std::f64::consts::TAU,\n\t\tsweep_angle / 360. * std::f64::consts::TAU,\n\t\tmatch arc_type {\n\t\t\tArcType::Open => subpath::ArcType::Open,\n\t\t\tArcType::Closed => subpath::ArcType::Closed,\n\t\t\tArcType::PieSlice => subpath::ArcType::PieSlice,\n\t\t},\n\t)))\n}\n\n/// Generates a spiral shape that winds from an inner to an outer radius.\n#[node_macro::node(category(\"Vector: Shape\"), properties(\"spiral_properties\"))]\nfn spiral(\n\t_: impl Ctx,\n\t_primary: (),\n\tspiral_type: SpiralType,\n\t#[default(5.)] turns: f64,\n\t#[default(0.)] start_angle: f64,\n\t#[default(0.)] inner_radius: f64,\n\t#[default(25)] outer_radius: f64,\n\t#[default(90.)] angular_resolution: f64,\n) -> Table<Vector> {\n\tTable::new_from_element(Vector::from_subpath(subpath::Subpath::new_spiral(\n\t\tinner_radius,\n\t\touter_radius,\n\t\tturns,\n\t\tstart_angle.to_radians(),\n\t\tangular_resolution.to_radians(),\n\t\tspiral_type,\n\t)))\n}\n\n/// Generates an ellipse shape (an oval or stretched circle) with the chosen radii.\n#[node_macro::node(category(\"Vector: Shape\"))]\nfn ellipse(\n\t_: impl Ctx,\n\t_primary: (),\n\t#[unit(\" px\")]\n\t#[default(50)]\n\tradius_x: f64,\n\t#[unit(\" px\")]\n\t#[default(25)]\n\tradius_y: f64,\n) -> Table<Vector> {\n\tlet radius = DVec2::new(radius_x, radius_y);\n\tlet corner1 = -radius;\n\tlet corner2 = radius;\n\n\tlet mut ellipse = Vector::from_subpath(subpath::Subpath::new_ellipse(corner1, corner2));\n\n\tlet len = ellipse.segment_domain.ids().len();\n\tfor i in 0..len {\n\t\tellipse\n\t\t\t.colinear_manipulators\n\t\t\t.push([HandleId::end(ellipse.segment_domain.ids()[i]), HandleId::primary(ellipse.segment_domain.ids()[(i + 1) % len])]);\n\t}\n\n\tTable::new_from_element(ellipse)\n}\n\n/// Generates a rectangle shape with the chosen width and height. It may also have rounded corners if desired.\n#[node_macro::node(category(\"Vector: Shape\"), properties(\"rectangle_properties\"))]\nfn rectangle<T: CornerRadius>(\n\t_: impl Ctx,\n\t_primary: (),\n\t#[unit(\" px\")]\n\t#[default(100)]\n\twidth: f64,\n\t#[unit(\" px\")]\n\t#[default(100)]\n\theight: f64,\n\t_individual_corner_radii: bool, // TODO: Move this to the bottom once we have a migration capability\n\t#[implementations(f64, [f64; 4])] corner_radius: T,\n\t#[default(true)] clamped: bool,\n) -> Table<Vector> {\n\tcorner_radius.generate(DVec2::new(width, height), clamped)\n}\n\n/// Generates an regular polygon shape like a triangle, square, pentagon, hexagon, heptagon, octagon, or any higher n-gon.\n#[node_macro::node(category(\"Vector: Shape\"))]\nfn regular_polygon<T: AsU64>(\n\t_: impl Ctx,\n\t_primary: (),\n\t#[default(6)]\n\t#[hard_min(3.)]\n\t#[implementations(u32, u64, f64)]\n\tsides: T,\n\t#[unit(\" px\")]\n\t#[default(50)]\n\tradius: f64,\n) -> Table<Vector> {\n\tlet points = sides.as_u64();\n\tlet radius: f64 = radius * 2.;\n\tTable::new_from_element(Vector::from_subpath(subpath::Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius)))\n}\n\n/// Generates an n-pointed star shape with inner and outer points at chosen radii from the center.\n#[node_macro::node(category(\"Vector: Shape\"))]\nfn star<T: AsU64>(\n\t_: impl Ctx,\n\t_primary: (),\n\t#[default(5)]\n\t#[hard_min(2.)]\n\t#[implementations(u32, u64, f64)]\n\tsides: T,\n\t#[unit(\" px\")]\n\t#[default(50)]\n\tradius_1: f64,\n\t#[unit(\" px\")]\n\t#[default(25)]\n\tradius_2: f64,\n) -> Table<Vector> {\n\tlet points = sides.as_u64();\n\tlet diameter: f64 = radius_1 * 2.;\n\tlet inner_diameter = radius_2 * 2.;\n\n\tTable::new_from_element(Vector::from_subpath(subpath::Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter)))\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)]\n#[widget(Radio)]\npub enum QRCodeErrorCorrectionLevel {\n\t/// Allows recovery from up to 7% data loss.\n\t#[default]\n\tLow,\n\t/// Allows recovery from up to 15% data loss.\n\tMedium,\n\t/// Allows recovery from up to 25% data loss.\n\tQuartile,\n\t/// Allows recovery from up to 30% data loss.\n\tHigh,\n}\n\n/// Generates a QR code from the input text.\n#[node_macro::node(category(\"Vector: Shape\"), name(\"QR Code\"))]\nfn qr_code(\n\t_: impl Ctx,\n\t_primary: (),\n\t#[widget(ParsedWidgetOverride::Custom = \"text_area\")]\n\t#[default(\"https://graphite.art\")]\n\ttext: String,\n\t#[widget(ParsedWidgetOverride::Hidden)] has_size: bool,\n\t#[unit(\" px\")]\n\t#[hard_min(1.)]\n\t#[widget(ParsedWidgetOverride::Custom = \"optional_f64\")]\n\tsize: f64,\n\terror_correction: QRCodeErrorCorrectionLevel,\n\t#[default(false)] individual_squares: bool,\n) -> Table<Vector> {\n\tlet ecc = match error_correction {\n\t\tQRCodeErrorCorrectionLevel::Low => qrcodegen::QrCodeEcc::Low,\n\t\tQRCodeErrorCorrectionLevel::Medium => qrcodegen::QrCodeEcc::Medium,\n\t\tQRCodeErrorCorrectionLevel::Quartile => qrcodegen::QrCodeEcc::Quartile,\n\t\tQRCodeErrorCorrectionLevel::High => qrcodegen::QrCodeEcc::High,\n\t};\n\n\tlet Ok(qr_code) = qrcodegen::QrCode::encode_text(&text, ecc) else { return Table::default() };\n\n\tlet mut vector = match individual_squares {\n\t\ttrue => {\n\t\t\tlet mut vector = Vector::default();\n\n\t\t\tlet dimension = qr_code.size() as usize;\n\t\t\tfor y in 0..dimension {\n\t\t\t\tfor x in 0..dimension {\n\t\t\t\t\tif qr_code.get_module(x as i32, y as i32) {\n\t\t\t\t\t\tlet corner1 = DVec2::new(x as f64, y as f64);\n\t\t\t\t\t\tlet corner2 = corner1 + DVec2::splat(1.);\n\t\t\t\t\t\tvector.append_subpath(\n\t\t\t\t\t\t\tsubpath::Subpath::from_anchors([corner1, DVec2::new(corner2.x, corner1.y), corner2, DVec2::new(corner1.x, corner2.y)], true),\n\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvector\n\t\t}\n\t\tfalse => crate::merge_qr_squares::merge_qr_squares(&qr_code),\n\t};\n\n\tif has_size {\n\t\tvector.transform(glam::DAffine2::from_scale(DVec2::splat(size.max(1.) / qr_code.size() as f64)));\n\t}\n\n\tTable::new_from_element(vector)\n}\n\n/// Generates an arrow from the origin to the chosen coordinate.\n#[node_macro::node(category(\"Vector: Shape\"))]\nfn arrow(\n\t_: impl Ctx,\n\t_primary: (),\n\t#[default(100., 0.)] arrow_to: PixelSize,\n\t#[default(10)] shaft_width: PixelLength,\n\t#[default(30)] head_width: PixelLength,\n\t#[default(20)] head_length: PixelLength,\n) -> Table<Vector> {\n\tTable::new_from_element(Vector::from_subpath(subpath::Subpath::new_arrow(DVec2::ZERO, arrow_to, shaft_width, head_width, head_length)))\n}\n\n#[node_macro::node(category(\"Vector: Shape\"))]\nfn line(_: impl Ctx, _primary: (), #[default(100., 100.)] line_to: PixelSize) -> Table<Vector> {\n\tTable::new_from_element(Vector::from_subpath(subpath::Subpath::new_line(DVec2::ZERO, line_to)))\n}\n\ntrait GridSpacing {\n\tfn as_dvec2(&self) -> DVec2;\n}\nimpl GridSpacing for f64 {\n\tfn as_dvec2(&self) -> DVec2 {\n\t\tDVec2::splat(*self)\n\t}\n}\nimpl GridSpacing for DVec2 {\n\tfn as_dvec2(&self) -> DVec2 {\n\t\t*self\n\t}\n}\n\n/// Generates a rectangular or isometric grid with the chosen number of columns and rows. Line segments connect the points, forming a vector mesh.\n#[node_macro::node(category(\"Vector: Shape\"), properties(\"grid_properties\"))]\nfn grid<T: GridSpacing>(\n\t_: impl Ctx,\n\t_primary: (),\n\tgrid_type: GridType,\n\t#[unit(\" px\")]\n\t#[hard_min(0.)]\n\t#[default(10)]\n\t#[implementations(f64, DVec2)]\n\tspacing: T,\n\t#[default(10)] columns: u32,\n\t#[default(10)] rows: u32,\n\t#[default(30., 30.)] angles: DVec2,\n) -> Table<Vector> {\n\tlet (x_spacing, y_spacing) = spacing.as_dvec2().into();\n\tlet (angle_a, angle_b) = angles.into();\n\n\tlet mut vector = Vector::default();\n\tlet mut segment_id = SegmentId::ZERO;\n\tlet mut point_id = PointId::ZERO;\n\n\tmatch grid_type {\n\t\tGridType::Rectangular => {\n\t\t\t// Create rectangular grid points and connect them with line segments\n\t\t\tfor y in 0..rows {\n\t\t\t\tfor x in 0..columns {\n\t\t\t\t\t// Add current point to the grid\n\t\t\t\t\tlet current_index = vector.point_domain.ids().len();\n\t\t\t\t\tvector.point_domain.push(point_id.next_id(), DVec2::new(x_spacing * x as f64, y_spacing * y as f64));\n\n\t\t\t\t\t// Helper function to connect points with line segments\n\t\t\t\t\tlet mut push_segment = |to_index: Option<usize>| {\n\t\t\t\t\t\tif let Some(other_index) = to_index {\n\t\t\t\t\t\t\tvector\n\t\t\t\t\t\t\t\t.segment_domain\n\t\t\t\t\t\t\t\t.push(segment_id.next_id(), other_index, current_index, subpath::BezierHandles::Linear, StrokeId::ZERO);\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\t// Connect to the point to the left (horizontal connection)\n\t\t\t\t\tpush_segment((x > 0).then(|| current_index - 1));\n\n\t\t\t\t\t// Connect to the point above (vertical connection)\n\t\t\t\t\tpush_segment(current_index.checked_sub(columns as usize));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tGridType::Isometric => {\n\t\t\t// Calculate isometric grid spacing based on angles\n\t\t\tlet tan_a = angle_a.to_radians().tan();\n\t\t\tlet tan_b = angle_b.to_radians().tan();\n\t\t\tlet spacing = DVec2::new(y_spacing / (tan_a + tan_b), y_spacing);\n\n\t\t\t// Create isometric grid points and connect them with line segments\n\t\t\tfor y in 0..rows {\n\t\t\t\tfor x in 0..columns {\n\t\t\t\t\t// Add current point to the grid with offset for odd columns\n\t\t\t\t\tlet current_index = vector.point_domain.ids().len();\n\n\t\t\t\t\tlet a_angles_eaten = x.div_ceil(2) as f64;\n\t\t\t\t\tlet b_angles_eaten = (x / 2) as f64;\n\n\t\t\t\t\tlet offset_y_fraction = b_angles_eaten * tan_b - a_angles_eaten * tan_a;\n\n\t\t\t\t\tlet position = DVec2::new(spacing.x * x as f64, spacing.y * y as f64 + offset_y_fraction * spacing.x);\n\t\t\t\t\tvector.point_domain.push(point_id.next_id(), position);\n\n\t\t\t\t\t// Helper function to connect points with line segments\n\t\t\t\t\tlet mut push_segment = |to_index: Option<usize>| {\n\t\t\t\t\t\tif let Some(other_index) = to_index {\n\t\t\t\t\t\t\tvector\n\t\t\t\t\t\t\t\t.segment_domain\n\t\t\t\t\t\t\t\t.push(segment_id.next_id(), other_index, current_index, subpath::BezierHandles::Linear, StrokeId::ZERO);\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\t// Connect to the point to the left\n\t\t\t\t\tpush_segment((x > 0).then(|| current_index - 1));\n\n\t\t\t\t\t// Connect to the point directly above\n\t\t\t\t\tpush_segment(current_index.checked_sub(columns as usize));\n\n\t\t\t\t\t// Additional diagonal connections for odd columns (creates hexagonal pattern)\n\t\t\t\t\tif x % 2 == 1 {\n\t\t\t\t\t\t// Connect to the point diagonally up-right (if not at right edge)\n\t\t\t\t\t\tpush_segment(current_index.checked_sub(columns as usize - 1).filter(|_| x + 1 < columns));\n\n\t\t\t\t\t\t// Connect to the point diagonally up-left\n\t\t\t\t\t\tpush_segment(current_index.checked_sub(columns as usize + 1));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tTable::new_from_element(vector)\n}\n\n#[cfg(test)]\nmod tests {\n\tuse super::*;\n\t#[test]\n\tfn isometric_grid_test() {\n\t\t// Doesn't crash with weird angles\n\t\tgrid((), (), GridType::Isometric, 0., 5, 5, (0., 0.).into());\n\t\tgrid((), (), GridType::Isometric, 90., 5, 5, (90., 90.).into());\n\n\t\t// Works properly\n\t\tlet grid = grid((), (), GridType::Isometric, 10., 5, 5, (30., 30.).into());\n\t\tassert_eq!(grid.iter().next().unwrap().element.point_domain.ids().len(), 5 * 5);\n\t\tassert_eq!(grid.iter().next().unwrap().element.segment_bezier_iter().count(), 4 * 5 + 4 * 9);\n\t\tfor (_, bezier, _, _) in grid.iter().next().unwrap().element.segment_bezier_iter() {\n\t\t\tassert_eq!(bezier.handles, subpath::BezierHandles::Linear);\n\t\t\tassert!(\n\t\t\t\t((bezier.start - bezier.end).length() - 10.).abs() < 1e-5,\n\t\t\t\t\"Length of {} should be 10\",\n\t\t\t\t(bezier.start - bezier.end).length()\n\t\t\t);\n\t\t}\n\t}\n\n\t#[test]\n\tfn skew_isometric_grid_test() {\n\t\tlet grid = grid((), (), GridType::Isometric, 10., 5, 5, (40., 30.).into());\n\t\tassert_eq!(grid.iter().next().unwrap().element.point_domain.ids().len(), 5 * 5);\n\t\tassert_eq!(grid.iter().next().unwrap().element.segment_bezier_iter().count(), 4 * 5 + 4 * 9);\n\t\tfor (_, bezier, _, _) in grid.iter().next().unwrap().element.segment_bezier_iter() {\n\t\t\tassert_eq!(bezier.handles, subpath::BezierHandles::Linear);\n\t\t\tlet vector = bezier.start - bezier.end;\n\t\t\tlet angle = (vector.angle_to(DVec2::X).to_degrees() + 180.) % 180.;\n\t\t\tassert!([90., 150., 40.].into_iter().any(|target| (target - angle).abs() < 1e-10), \"unexpected angle of {angle}\")\n\t\t}\n\t}\n\n\t#[test]\n\tfn qr_code_test() {\n\t\tlet qr = qr_code((), (), \"https://graphite.art\".to_string(), false, 1., QRCodeErrorCorrectionLevel::Low, true);\n\t\tassert!(qr.iter().next().unwrap().element.point_domain.ids().len() > 0);\n\t\tassert!(qr.iter().next().unwrap().element.segment_domain.ids().len() > 0);\n\t}\n}\n"
  },
  {
    "path": "node-graph/nodes/vector/src/lib.rs",
    "content": "pub mod generator_nodes;\npub mod merge_qr_squares;\npub mod vector_modification_nodes;\nmod vector_nodes;\n\n#[macro_use]\nextern crate log;\n\n// Re-export for convenience\npub use core_types as gcore;\npub use generator_nodes::*;\npub use graphic_types;\npub use vector_modification_nodes::*;\npub use vector_nodes::*;\npub use vector_types;\n"
  },
  {
    "path": "node-graph/nodes/vector/src/merge_qr_squares.rs",
    "content": "use glam::DVec2;\nuse graphic_types::Vector;\nuse std::collections::VecDeque;\nuse vector_types::subpath;\n\npub fn merge_qr_squares(qr_code: &qrcodegen::QrCode) -> Vector {\n\tlet mut vector = Vector::default();\n\n\tlet size = qr_code.size() as usize;\n\n\t// 0 = empty\n\t// 1 = filled, unvisited\n\t// 2 = filled, current island\n\tlet mut remaining = vec![vec![0u8; size]; size];\n\n\t#[allow(clippy::needless_range_loop)]\n\tfor y in 0..size {\n\t\t#[allow(clippy::needless_range_loop)]\n\t\tfor x in 0..size {\n\t\t\tif qr_code.get_module(x as i32, y as i32) {\n\t\t\t\tremaining[y][x] = 1;\n\t\t\t}\n\t\t}\n\t}\n\n\tfor y in 0..size {\n\t\tfor x in 0..size {\n\t\t\tif remaining[y][x] != 1 {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// fill island\n\t\t\tlet mut island = Vec::new();\n\t\t\tlet mut queue = VecDeque::new();\n\t\t\tqueue.push_back((x, y));\n\t\t\tremaining[y][x] = 2;\n\n\t\t\twhile let Some((ix, iy)) = queue.pop_front() {\n\t\t\t\tisland.push((ix, iy));\n\n\t\t\t\tfor (dx, dy) in [(0, 1), (0, -1), (1, 0), (-1, 0)] {\n\t\t\t\t\tlet nx = ix as i32 + dx;\n\t\t\t\t\tlet ny = iy as i32 + dy;\n\n\t\t\t\t\tif nx >= 0 && nx < size as i32 && ny >= 0 && ny < size as i32 && remaining[ny as usize][nx as usize] == 1 {\n\t\t\t\t\t\tremaining[ny as usize][nx as usize] = 2;\n\t\t\t\t\t\tqueue.push_back((nx as usize, ny as usize));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// boundary detection\n\t\t\tlet mut outbound = vec![vec![0u8; size + 1]; size + 1];\n\n\t\t\tfor &(ix, iy) in &island {\n\t\t\t\tif iy == 0 || remaining[iy - 1][ix] != 2 {\n\t\t\t\t\toutbound[iy][ix] |= 1 << 0;\n\t\t\t\t}\n\t\t\t\tif ix == size - 1 || remaining[iy][ix + 1] != 2 {\n\t\t\t\t\toutbound[iy][ix + 1] |= 1 << 1;\n\t\t\t\t}\n\t\t\t\tif iy == size - 1 || remaining[iy + 1][ix] != 2 {\n\t\t\t\t\toutbound[iy + 1][ix + 1] |= 1 << 2;\n\t\t\t\t}\n\t\t\t\tif ix == 0 || remaining[iy][ix - 1] != 2 {\n\t\t\t\t\toutbound[iy + 1][ix] |= 1 << 3;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// tracing loops\n\t\t\tfor vy in 0..=size {\n\t\t\t\tfor vx in 0..=size {\n\t\t\t\t\twhile outbound[vy][vx] != 0 {\n\t\t\t\t\t\tlet mut dir = outbound[vy][vx].trailing_zeros() as usize;\n\t\t\t\t\t\tlet start = (vx, vy);\n\t\t\t\t\t\tlet mut current = start;\n\t\t\t\t\t\tlet mut points = Vec::new();\n\n\t\t\t\t\t\tloop {\n\t\t\t\t\t\t\tpoints.push(DVec2::new(current.0 as f64, current.1 as f64));\n\t\t\t\t\t\t\toutbound[current.1][current.0] &= !(1 << dir);\n\n\t\t\t\t\t\t\tcurrent = match dir {\n\t\t\t\t\t\t\t\t0 => (current.0 + 1, current.1),\n\t\t\t\t\t\t\t\t1 => (current.0, current.1 + 1),\n\t\t\t\t\t\t\t\t2 => (current.0 - 1, current.1),\n\t\t\t\t\t\t\t\t3 => (current.0, current.1 - 1),\n\t\t\t\t\t\t\t\t_ => unreachable!(),\n\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\tif current == start {\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tdir = outbound[current.1][current.0].trailing_zeros() as usize;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif points.len() > 2 {\n\t\t\t\t\t\t\tlet mut simplified = Vec::new();\n\t\t\t\t\t\t\tfor i in 0..points.len() {\n\t\t\t\t\t\t\t\tlet prev = points[(i + points.len() - 1) % points.len()];\n\t\t\t\t\t\t\t\tlet curr = points[i];\n\t\t\t\t\t\t\t\tlet next = points[(i + 1) % points.len()];\n\t\t\t\t\t\t\t\tif (curr - prev).perp_dot(next - curr).abs() > 1e-6 {\n\t\t\t\t\t\t\t\t\tsimplified.push(curr);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif !simplified.is_empty() {\n\t\t\t\t\t\t\t\tvector.append_subpath(subpath::Subpath::from_anchors(simplified, true), false);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// marking island as processed\n\t\t\tfor &(ix, iy) in &island {\n\t\t\t\tremaining[iy][ix] = 0;\n\t\t\t}\n\t\t}\n\t}\n\n\tvector\n}\n"
  },
  {
    "path": "node-graph/nodes/vector/src/vector_modification_nodes.rs",
    "content": "use core_types::Ctx;\nuse core_types::table::Table;\nuse core_types::uuid::NodeId;\nuse glam::DAffine2;\nuse graphic_types::Vector;\nuse vector_types::vector::VectorModification;\n\n/// Applies a differential modification to a vector path, associating changes made by the Pen and Path tools to indices of edited points and segments.\n#[node_macro::node(category(\"\"))]\nasync fn path_modify(_ctx: impl Ctx, mut vector: Table<Vector>, modification: Box<VectorModification>, node_path: Vec<NodeId>) -> Table<Vector> {\n\tuse core_types::table::TableRow;\n\n\tif vector.is_empty() {\n\t\tvector.push(TableRow::default());\n\t}\n\tlet row = vector.get_mut(0).expect(\"push should give one item\");\n\tmodification.apply(row.element);\n\n\t// Update the source node id\n\tlet this_node_path = node_path.iter().rev().nth(1).copied();\n\t*row.source_node_id = row.source_node_id.or(this_node_path);\n\n\tif vector.len() > 1 {\n\t\twarn!(\"The path modify ran on {} vector rows. Only the first can be modified.\", vector.len());\n\t}\n\tvector\n}\n\n/// Applies the vector path's local transformation to its geometry and resets the transform to the identity.\n#[node_macro::node(category(\"Vector\"))]\nasync fn apply_transform(_ctx: impl Ctx, mut vector: Table<Vector>) -> Table<Vector> {\n\tfor row in vector.iter_mut() {\n\t\tlet vector = row.element;\n\t\tlet transform = *row.transform;\n\n\t\tfor (_, point) in vector.point_domain.positions_mut() {\n\t\t\t*point = transform.transform_point2(*point);\n\t\t}\n\t\tvector.segment_domain.transform(transform);\n\n\t\t*row.transform = DAffine2::IDENTITY;\n\t}\n\n\tvector\n}\n"
  },
  {
    "path": "node-graph/nodes/vector/src/vector_nodes.rs",
    "content": "use core::cmp::Ordering;\nuse core::f64::consts::{PI, TAU};\nuse core::hash::{Hash, Hasher};\nuse core_types::bounds::{BoundingBox, RenderBoundingBox};\nuse core_types::registry::types::{Angle, Length, Multiplier, Percentage, PixelLength, Progression, SeedValue};\nuse core_types::table::{Table, TableRow, TableRowMut};\nuse core_types::transform::{Footprint, Transform};\nuse core_types::{CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl};\nuse glam::{DAffine2, DVec2};\nuse graphic_types::Vector;\nuse graphic_types::raster_types::{CPU, GPU, Raster};\nuse graphic_types::{Graphic, IntoGraphicTable};\nuse kurbo::{Affine, BezPath, DEFAULT_ACCURACY, Line, ParamCurve, PathEl, PathSeg, Shape};\nuse rand::{Rng, SeedableRng};\nuse std::collections::hash_map::DefaultHasher;\nuse vector_types::subpath::{BezierHandles, ManipulatorGroup};\nuse vector_types::vector::PointDomain;\nuse vector_types::vector::algorithms::bezpath_algorithms::{self, TValue, eval_pathseg_euclidean, evaluate_bezpath, sample_polyline_on_bezpath, split_bezpath, tangent_on_bezpath};\nuse vector_types::vector::algorithms::merge_by_distance::MergeByDistanceExt;\nuse vector_types::vector::algorithms::offset_subpath::offset_bezpath;\nuse vector_types::vector::algorithms::spline::{solve_spline_first_handle_closed, solve_spline_first_handle_open};\nuse vector_types::vector::misc::{\n\tCentroidType, ExtrudeJoiningAlgorithm, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns, bezpath_from_manipulator_groups, bezpath_to_manipulator_groups, handles_to_segment, is_linear,\n\tpoint_to_dvec2, segment_to_handles,\n};\nuse vector_types::vector::style::{Fill, Gradient, GradientStops, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin};\nuse vector_types::vector::{FillId, PointId, RegionId, SegmentDomain, SegmentId, StrokeId, VectorExt};\n\n/// Implemented for types that can be converted to an iterator of vector rows.\n/// Used for the fill and stroke node so they can be used on `Table<Graphic>` or `Table<Vector>`.\ntrait VectorTableIterMut {\n\tfn vector_iter_mut(&mut self) -> impl Iterator<Item = TableRowMut<'_, Vector>>;\n}\n\nimpl VectorTableIterMut for Table<Graphic> {\n\tfn vector_iter_mut(&mut self) -> impl Iterator<Item = TableRowMut<'_, Vector>> {\n\t\t// Grab only the direct children\n\t\tself.iter_mut().filter_map(|element| element.element.as_vector_mut()).flat_map(move |vector| vector.iter_mut())\n\t}\n}\n\nimpl VectorTableIterMut for Table<Vector> {\n\tfn vector_iter_mut(&mut self) -> impl Iterator<Item = TableRowMut<'_, Vector>> {\n\t\tself.iter_mut()\n\t}\n}\n\n/// Uniquely sets the fill and/or stroke style of every vector element to individual colors sampled along a chosen gradient.\n#[node_macro::node(category(\"Vector: Style\"), path(graphene_core::vector))]\nasync fn assign_colors<T>(\n\t_: impl Ctx,\n\t/// The content with vector paths to apply the fill and/or stroke style to.\n\t#[implementations(Table<Graphic>, Table<Vector>)]\n\t#[widget(ParsedWidgetOverride::Hidden)]\n\tmut content: T,\n\t/// Whether to style the fill.\n\t#[default(true)]\n\tfill: bool,\n\t/// Whether to style the stroke.\n\tstroke: bool,\n\t/// The range of colors to select from.\n\t#[widget(ParsedWidgetOverride::Custom = \"assign_colors_gradient\")]\n\tgradient: Table<GradientStops>,\n\t/// Whether to reverse the gradient.\n\treverse: bool,\n\t/// Whether to randomize the color selection for each element from throughout the gradient.\n\trandomize: bool,\n\t/// The seed used for randomization.\n\t/// Seed to determine unique variations on the randomized color selection.\n\t#[widget(ParsedWidgetOverride::Custom = \"assign_colors_seed\")]\n\tseed: SeedValue,\n\t/// The number of elements to span across the gradient before repeating. A 0 value will span the entire gradient once.\n\t#[widget(ParsedWidgetOverride::Custom = \"assign_colors_repeat_every\")]\n\trepeat_every: u32,\n) -> T\nwhere\n\tT: VectorTableIterMut + 'n + Send,\n{\n\tlet Some(row) = gradient.into_iter().next() else { return content };\n\n\tlet length = content.vector_iter_mut().count();\n\tlet gradient = if reverse { row.element.reversed() } else { row.element };\n\n\tlet mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());\n\n\tfor (i, vector) in content.vector_iter_mut().enumerate() {\n\t\tlet factor = match randomize {\n\t\t\ttrue => rng.random::<f64>(),\n\t\t\tfalse => match repeat_every {\n\t\t\t\t0 => i as f64 / (length - 1).max(1) as f64,\n\t\t\t\t1 => 0.,\n\t\t\t\t_ => i as f64 % repeat_every as f64 / (repeat_every - 1) as f64,\n\t\t\t},\n\t\t};\n\n\t\tlet color = gradient.evaluate(factor);\n\n\t\tif fill {\n\t\t\tvector.element.style.set_fill(Fill::Solid(color));\n\t\t}\n\t\tif stroke && let Some(stroke) = vector.element.style.stroke().and_then(|stroke| stroke.with_color(&Some(color))) {\n\t\t\tvector.element.style.set_stroke(stroke);\n\t\t}\n\t}\n\n\tcontent\n}\n\n/// Applies a fill style to the vector content, giving an appearance to the area within the interior of the geometry.\n#[node_macro::node(category(\"Vector: Style\"), path(graphene_core::vector), properties(\"fill_properties\"))]\nasync fn fill<F: Into<Fill> + 'n + Send, V: VectorTableIterMut + 'n + Send>(\n\t_: impl Ctx,\n\t/// The content with vector paths to apply the fill style to.\n\t#[implementations(\n\t\tTable<Vector>,\n\t\tTable<Vector>,\n\t\tTable<Vector>,\n\t\tTable<Vector>,\n\t\tTable<Graphic>,\n\t\tTable<Graphic>,\n\t\tTable<Graphic>,\n\t\tTable<Graphic>,\n\t)]\n\tmut content: V,\n\t/// The fill to paint the path with.\n\t#[default(Color::BLACK)]\n\t#[implementations(\n\t\tFill,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t\tGradient,\n\t\tFill,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t\tGradient,\n\t)]\n\tfill: F,\n\t_backup_color: Table<Color>,\n\t_backup_gradient: Gradient,\n) -> V {\n\tlet fill: Fill = fill.into();\n\tfor vector in content.vector_iter_mut() {\n\t\tvector.element.style.set_fill(fill.clone());\n\t}\n\n\tcontent\n}\n\ntrait IntoF64Vec {\n\tfn into_vec(self) -> Vec<f64>;\n}\nimpl IntoF64Vec for f64 {\n\tfn into_vec(self) -> Vec<f64> {\n\t\tvec![self]\n\t}\n}\nimpl IntoF64Vec for Vec<f64> {\n\tfn into_vec(self) -> Vec<f64> {\n\t\tself\n\t}\n}\nimpl IntoF64Vec for String {\n\tfn into_vec(self) -> Vec<f64> {\n\t\tself.split(&[',', ' ']).filter(|s| !s.is_empty()).filter_map(|s| s.parse::<f64>().ok()).collect()\n\t}\n}\n\n/// Applies a stroke style to the vector content, giving an appearance to the area within the outline of the geometry.\n#[node_macro::node(category(\"Vector: Style\"), path(graphene_core::vector), properties(\"stroke_properties\"))]\nasync fn stroke<V, L: IntoF64Vec>(\n\t_: impl Ctx,\n\t/// The content with vector paths to apply the stroke style to.\n\t#[implementations(Table<Vector>, Table<Vector>, Table<Vector>, Table<Graphic>, Table<Graphic>, Table<Graphic>)]\n\tmut content: Table<V>,\n\t/// The stroke color.\n\t#[default(Color::BLACK)]\n\tcolor: Table<Color>,\n\t/// The stroke thickness.\n\t#[unit(\" px\")]\n\t#[default(2.)]\n\tweight: f64,\n\t/// The alignment of stroke to the path's centerline or (for closed shapes) the inside or outside of the shape.\n\talign: StrokeAlign,\n\t/// The shape of the stroke at open endpoints.\n\tcap: StrokeCap,\n\t/// The curvature of the bent stroke at sharp corners.\n\tjoin: StrokeJoin,\n\t/// The threshold for when a miter-joined stroke is converted to a bevel-joined stroke when a sharp angle becomes pointier than this ratio.\n\t#[default(4.)]\n\tmiter_limit: f64,\n\t// <https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty>\n\t/// The order to paint the stroke on top of the fill, or the fill on top of the stroke.\n\tpaint_order: PaintOrder,\n\t/// The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.\n\t#[implementations(Vec<f64>, f64, String, Vec<f64>, f64, String)]\n\tdash_lengths: L,\n\t/// The phase offset distance from the starting point of the dash pattern.\n\t#[unit(\" px\")]\n\tdash_offset: f64,\n) -> Table<V>\nwhere\n\tTable<V>: VectorTableIterMut + 'n + Send,\n{\n\tlet stroke = Stroke {\n\t\tcolor: color.into(),\n\t\tweight,\n\t\tdash_lengths: dash_lengths.into_vec(),\n\t\tdash_offset,\n\t\tcap,\n\t\tjoin,\n\t\tjoin_miter_limit: miter_limit,\n\t\talign,\n\t\ttransform: DAffine2::IDENTITY,\n\t\tpaint_order,\n\t};\n\n\tfor vector in content.vector_iter_mut() {\n\t\tlet mut stroke = stroke.clone();\n\t\tstroke.transform *= *vector.transform;\n\t\tvector.element.style.set_stroke(stroke);\n\t}\n\n\tcontent\n}\n\n#[node_macro::node(name(\"Copy to Points\"), category(\"Repeat\"), path(core_types::vector))]\nasync fn copy_to_points<I: 'n + Send + Clone>(\n\t_: impl Ctx,\n\tpoints: Table<Vector>,\n\t/// Artwork to be copied and placed at each point.\n\t#[expose]\n\t#[implementations(Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Color>, Table<GradientStops>)]\n\tinstance: Table<I>,\n\t/// Minimum range of randomized sizes given to each instance.\n\t#[default(1)]\n\t#[range((0., 2.))]\n\t#[unit(\"x\")]\n\trandom_scale_min: Multiplier,\n\t/// Maximum range of randomized sizes given to each instance.\n\t#[default(1)]\n\t#[range((0., 2.))]\n\t#[unit(\"x\")]\n\trandom_scale_max: Multiplier,\n\t/// Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes).\n\t#[range((-50., 50.))]\n\trandom_scale_bias: f64,\n\t/// Seed to determine unique variations on all the randomized instance sizes.\n\trandom_scale_seed: SeedValue,\n\t/// Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise.\n\t#[range((0., 360.))]\n\trandom_rotation: Angle,\n\t/// Seed to determine unique variations on all the randomized instance angles.\n\trandom_rotation_seed: SeedValue,\n) -> Table<I> {\n\tlet mut result_table = Table::new();\n\n\tlet random_scale_difference = random_scale_max - random_scale_min;\n\n\tfor row in points.into_iter() {\n\t\tlet mut scale_rng = rand::rngs::StdRng::seed_from_u64(random_scale_seed.into());\n\t\tlet mut rotation_rng = rand::rngs::StdRng::seed_from_u64(random_rotation_seed.into());\n\n\t\tlet do_scale = random_scale_difference.abs() > 1e-6;\n\t\tlet do_rotation = random_rotation.abs() > 1e-6;\n\n\t\tlet points_transform = row.transform;\n\t\tfor &point in row.element.point_domain.positions() {\n\t\t\tlet translation = points_transform.transform_point2(point);\n\n\t\t\tlet rotation = if do_rotation {\n\t\t\t\tlet degrees = (rotation_rng.random::<f64>() - 0.5) * random_rotation;\n\t\t\t\tdegrees / 360. * TAU\n\t\t\t} else {\n\t\t\t\t0.\n\t\t\t};\n\n\t\t\tlet scale = if do_scale {\n\t\t\t\tif random_scale_bias.abs() < 1e-6 {\n\t\t\t\t\t// Linear\n\t\t\t\t\trandom_scale_min + scale_rng.random::<f64>() * random_scale_difference\n\t\t\t\t} else {\n\t\t\t\t\t// Weighted (see <https://www.desmos.com/calculator/gmavd3m9bd>)\n\t\t\t\t\tlet horizontal_scale_factor = 1. - 2_f64.powf(random_scale_bias);\n\t\t\t\t\tlet scale_factor = (1. - scale_rng.random::<f64>() * horizontal_scale_factor).log2() / random_scale_bias;\n\t\t\t\t\trandom_scale_min + scale_factor * random_scale_difference\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\trandom_scale_min\n\t\t\t};\n\n\t\t\tlet transform = DAffine2::from_scale_angle_translation(DVec2::splat(scale), rotation, translation);\n\n\t\t\tfor mut row in instance.iter().map(|row| row.into_cloned()) {\n\t\t\t\trow.transform = transform * row.transform;\n\n\t\t\t\tresult_table.push(row);\n\t\t\t}\n\t\t}\n\t}\n\n\tresult_table\n}\n\n#[node_macro::node(category(\"Vector: Modifier\"), path(core_types::vector))]\nasync fn round_corners(\n\t_: impl Ctx,\n\tsource: Table<Vector>,\n\t#[hard_min(0.)]\n\t#[default(10.)]\n\tradius: PixelLength,\n\t#[range((0., 1.))]\n\t#[hard_min(0.)]\n\t#[hard_max(1.)]\n\t#[default(0.5)]\n\troundness: f64,\n\t#[default(100.)] edge_length_limit: Percentage,\n\t#[range((0., 180.))]\n\t#[hard_min(0.)]\n\t#[hard_max(180.)]\n\t#[default(5.)]\n\tmin_angle_threshold: Angle,\n) -> Table<Vector> {\n\tsource\n\t\t.iter()\n\t\t.map(|source| {\n\t\t\tlet source_transform = *source.transform;\n\t\t\tlet source_transform_inverse = source_transform.inverse();\n\t\t\tlet source_node_id = source.source_node_id;\n\t\t\tlet source = source.element;\n\n\t\t\tlet upstream_nested_layers = source.upstream_data.clone();\n\n\t\t\t// Flip the roundness to help with user intuition\n\t\t\tlet roundness = 1. - roundness;\n\t\t\t// Convert 0-100 to 0-0.5\n\t\t\tlet edge_length_limit = edge_length_limit * 0.005;\n\n\t\t\tlet mut result = Vector {\n\t\t\t\tstyle: source.style.clone(),\n\t\t\t\t..Default::default()\n\t\t\t};\n\n\t\t\t// Grab the initial point ID as a stable starting point\n\t\t\tlet mut initial_point_id = source.point_domain.ids().first().copied().unwrap_or(PointId::generate());\n\n\t\t\tfor mut bezpath in source.stroke_bezpath_iter() {\n\t\t\t\tbezpath.apply_affine(Affine::new(source_transform.to_cols_array()));\n\t\t\t\tlet (manipulator_groups, is_closed) = bezpath_to_manipulator_groups(&bezpath);\n\n\t\t\t\t// End if not enough points for corner rounding\n\t\t\t\tif manipulator_groups.len() < 3 {\n\t\t\t\t\tresult.append_bezpath(bezpath);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tlet mut new_manipulator_groups = Vec::new();\n\n\t\t\t\tfor i in 0..manipulator_groups.len() {\n\t\t\t\t\t// Skip first and last points for open paths\n\t\t\t\t\tif !is_closed && (i == 0 || i == manipulator_groups.len() - 1) {\n\t\t\t\t\t\tnew_manipulator_groups.push(manipulator_groups[i]);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Not the prettiest, but it makes the rest of the logic more readable\n\t\t\t\t\tlet prev_idx = if i == 0 { if is_closed { manipulator_groups.len() - 1 } else { 0 } } else { i - 1 };\n\t\t\t\t\tlet curr_idx = i;\n\t\t\t\t\tlet next_idx = if i == manipulator_groups.len() - 1 { if is_closed { 0 } else { i } } else { i + 1 };\n\n\t\t\t\t\tlet prev = manipulator_groups[prev_idx].anchor;\n\t\t\t\t\tlet curr = manipulator_groups[curr_idx].anchor;\n\t\t\t\t\tlet next = manipulator_groups[next_idx].anchor;\n\n\t\t\t\t\tlet dir1 = (curr - prev).normalize_or(DVec2::X);\n\t\t\t\t\tlet dir2 = (next - curr).normalize_or(DVec2::X);\n\n\t\t\t\t\tlet theta = PI - dir1.angle_to(dir2).abs();\n\n\t\t\t\t\t// Skip near-straight corners\n\t\t\t\t\tif theta > PI - min_angle_threshold.to_radians() {\n\t\t\t\t\t\tnew_manipulator_groups.push(manipulator_groups[curr_idx]);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Calculate L, with limits to avoid extreme values\n\t\t\t\t\tlet distance_along_edge = radius / (theta / 2.).sin();\n\t\t\t\t\tlet distance_along_edge = distance_along_edge.min(edge_length_limit * (curr - prev).length().min((next - curr).length())).max(0.01);\n\n\t\t\t\t\t// Find points on each edge at distance L from corner\n\t\t\t\t\tlet p1 = curr - dir1 * distance_along_edge;\n\t\t\t\t\tlet p2 = curr + dir2 * distance_along_edge;\n\n\t\t\t\t\t// Add first point (coming into the rounded corner)\n\t\t\t\t\tnew_manipulator_groups.push(ManipulatorGroup {\n\t\t\t\t\t\tanchor: p1,\n\t\t\t\t\t\tin_handle: None,\n\t\t\t\t\t\tout_handle: Some(curr - dir1 * distance_along_edge * roundness),\n\t\t\t\t\t\tid: initial_point_id.next_id(),\n\t\t\t\t\t});\n\n\t\t\t\t\t// Add second point (coming out of the rounded corner)\n\t\t\t\t\tnew_manipulator_groups.push(ManipulatorGroup {\n\t\t\t\t\t\tanchor: p2,\n\t\t\t\t\t\tin_handle: Some(curr + dir2 * distance_along_edge * roundness),\n\t\t\t\t\t\tout_handle: None,\n\t\t\t\t\t\tid: initial_point_id.next_id(),\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\t// One subpath for each shape\n\t\t\t\tlet mut rounded_subpath = bezpath_from_manipulator_groups(&new_manipulator_groups, is_closed);\n\t\t\t\trounded_subpath.apply_affine(Affine::new(source_transform_inverse.to_cols_array()));\n\t\t\t\tresult.append_bezpath(rounded_subpath);\n\t\t\t}\n\n\t\t\tresult.upstream_data = upstream_nested_layers;\n\n\t\t\tTableRow {\n\t\t\t\telement: result,\n\t\t\t\ttransform: source_transform,\n\t\t\t\talpha_blending: Default::default(),\n\t\t\t\tsource_node_id: *source_node_id,\n\t\t\t}\n\t\t})\n\t\t.collect()\n}\n\n#[node_macro::node(name(\"Merge by Distance\"), category(\"Vector: Modifier\"), path(core_types::vector))]\npub fn merge_by_distance(\n\t_: impl Ctx,\n\tcontent: Table<Vector>,\n\t#[default(0.1)]\n\t#[hard_min(0.0001)]\n\tdistance: PixelLength,\n\talgorithm: MergeByDistanceAlgorithm,\n) -> Table<Vector> {\n\tmatch algorithm {\n\t\tMergeByDistanceAlgorithm::Spatial => content\n\t\t\t.into_iter()\n\t\t\t.map(|mut row| {\n\t\t\t\trow.element.merge_by_distance_spatial(row.transform, distance);\n\t\t\t\trow\n\t\t\t})\n\t\t\t.collect(),\n\t\tMergeByDistanceAlgorithm::Topological => content\n\t\t\t.into_iter()\n\t\t\t.map(|mut row| {\n\t\t\t\trow.element.merge_by_distance_topological(distance);\n\t\t\t\trow\n\t\t\t})\n\t\t\t.collect(),\n\t}\n}\n\npub mod extrude_algorithms {\n\tuse glam::DVec2;\n\tuse kurbo::{ParamCurve, ParamCurveDeriv};\n\tuse vector_types::subpath::BezierHandles;\n\tuse vector_types::vector::StrokeId;\n\tuse vector_types::vector::misc::ExtrudeJoiningAlgorithm;\n\n\t/// Convert [`vector_types::subpath::Bezier`] to [`kurbo::PathSeg`].\n\tfn bezier_to_path_seg(bezier: vector_types::subpath::Bezier) -> kurbo::PathSeg {\n\t\tlet [start, end] = [(bezier.start().x, bezier.start().y), (bezier.end().x, bezier.end().y)];\n\t\tmatch bezier.handles {\n\t\t\tBezierHandles::Linear => kurbo::Line::new(start, end).into(),\n\t\t\tBezierHandles::Quadratic { handle } => kurbo::QuadBez::new(start, (handle.x, handle.y), end).into(),\n\t\t\tBezierHandles::Cubic { handle_start, handle_end } => kurbo::CubicBez::new(start, (handle_start.x, handle_start.y), (handle_end.x, handle_end.y), end).into(),\n\t\t}\n\t}\n\n\t/// Convert [`kurbo::CubicBez`] to [`vector_types::subpath::BezierHandles`].\n\tfn cubic_to_handles(cubic_bez: kurbo::CubicBez) -> BezierHandles {\n\t\tBezierHandles::Cubic {\n\t\t\thandle_start: DVec2::new(cubic_bez.p1.x, cubic_bez.p1.y),\n\t\t\thandle_end: DVec2::new(cubic_bez.p2.x, cubic_bez.p2.y),\n\t\t}\n\t}\n\n\t/// Find the `t` values to split (where the tangent changes to be on the other side of the direction).\n\tfn find_splits(cubic_segment: kurbo::CubicBez, direction: DVec2) -> impl Iterator<Item = f64> {\n\t\tlet derivative = cubic_segment.deriv();\n\t\tlet convert = |x: kurbo::Point| DVec2::new(x.x, x.y);\n\t\tlet derivative_points = [derivative.p0, derivative.p1, derivative.p2].map(convert);\n\n\t\tlet t_squared = derivative_points[0] - 2. * derivative_points[1] + derivative_points[2];\n\t\tlet t_scalar = -2. * derivative_points[0] + 2. * derivative_points[1];\n\t\tlet constant = derivative_points[0];\n\n\t\tkurbo::common::solve_quadratic(constant.perp_dot(direction), t_scalar.perp_dot(direction), t_squared.perp_dot(direction))\n\t\t\t.into_iter()\n\t\t\t.filter(|&t| t > 1e-6 && t < 1. - 1e-6)\n\t}\n\n\t/// Split so segments no longer have tangents on both sides of the direction vector.\n\tfn split(vector: &mut graphic_types::Vector, direction: DVec2) {\n\t\tlet segment_count = vector.segment_domain.ids().len();\n\t\tlet mut next_point = vector.point_domain.next_id();\n\t\tlet mut next_segment = vector.segment_domain.next_id();\n\n\t\tfor segment_index in 0..segment_count {\n\t\t\tlet (_, _, bezier) = vector.segment_points_from_index(segment_index);\n\t\t\tlet mut start_index = vector.segment_domain.start_point()[segment_index];\n\t\t\tlet pathseg = bezier_to_path_seg(bezier).to_cubic();\n\t\t\tlet mut start_t = 0.;\n\n\t\t\tfor split_t in find_splits(pathseg, direction) {\n\t\t\t\tlet [first, second] = [pathseg.subsegment(start_t..split_t), pathseg.subsegment(split_t..1.)];\n\t\t\t\tlet [first_handles, second_handles] = [first, second].map(cubic_to_handles);\n\t\t\t\tlet middle_point = next_point.next_id();\n\t\t\t\tlet start_segment = next_segment.next_id();\n\n\t\t\t\tlet middle_point_index = vector.point_domain.len();\n\t\t\t\tvector.point_domain.push(middle_point, DVec2::new(first.end().x, first.end().y));\n\t\t\t\tvector.segment_domain.push(start_segment, start_index, middle_point_index, first_handles, StrokeId::ZERO);\n\t\t\t\tvector.segment_domain.set_start_point(segment_index, middle_point_index);\n\t\t\t\tvector.segment_domain.set_handles(segment_index, second_handles);\n\n\t\t\t\tstart_t = split_t;\n\t\t\t\tstart_index = middle_point_index;\n\t\t\t}\n\t\t}\n\t}\n\n\t/// Copy all segments with the offset of `direction`.\n\tfn offset_copy_all_segments(vector: &mut graphic_types::Vector, direction: DVec2) {\n\t\tlet points_count = vector.point_domain.ids().len();\n\t\tlet mut next_point = vector.point_domain.next_id();\n\t\tfor index in 0..points_count {\n\t\t\tvector.point_domain.push(next_point.next_id(), vector.point_domain.positions()[index] + direction);\n\t\t}\n\n\t\tlet segment_count = vector.segment_domain.ids().len();\n\t\tlet mut next_segment = vector.segment_domain.next_id();\n\t\tfor index in 0..segment_count {\n\t\t\tvector.segment_domain.push(\n\t\t\t\tnext_segment.next_id(),\n\t\t\t\tvector.segment_domain.start_point()[index] + points_count,\n\t\t\t\tvector.segment_domain.end_point()[index] + points_count,\n\t\t\t\tvector.segment_domain.handles()[index].apply_transformation(|x| x + direction),\n\t\t\t\tvector.segment_domain.stroke()[index],\n\t\t\t);\n\t\t}\n\t}\n\n\t/// Join points from the original to the copied that are on opposite sides of the direction.\n\tfn join_extrema_edges(vector: &mut graphic_types::Vector, direction: DVec2) {\n\t\t#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]\n\t\tenum Found {\n\t\t\t#[default]\n\t\t\tNone,\n\t\t\tPositive,\n\t\t\tNegative,\n\t\t\tBoth,\n\t\t\tInvalid,\n\t\t}\n\n\t\timpl Found {\n\t\t\tfn update(&mut self, value: f64) {\n\t\t\t\t*self = match (*self, value > 0.) {\n\t\t\t\t\t(Found::None, true) => Found::Positive,\n\t\t\t\t\t(Found::None, false) => Found::Negative,\n\t\t\t\t\t(Found::Positive, true) | (Found::Negative, false) => Found::Both,\n\t\t\t\t\t_ => Found::Invalid,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tlet first_half_points = vector.point_domain.len() / 2;\n\t\tlet mut points = vec![Found::None; first_half_points];\n\t\tlet first_half_segments = vector.segment_domain.ids().len() / 2;\n\n\t\tfor segment_id in 0..first_half_segments {\n\t\t\tlet index = [vector.segment_domain.start_point()[segment_id], vector.segment_domain.end_point()[segment_id]];\n\t\t\tlet position = index.map(|index| vector.point_domain.positions()[index]);\n\n\t\t\tif position[0].abs_diff_eq(position[1], 1e-6) {\n\t\t\t\tcontinue; // Skip zero length segments\n\t\t\t}\n\n\t\t\tpoints[index[0]].update(direction.perp_dot(position[1] - position[0]));\n\t\t\tpoints[index[1]].update(direction.perp_dot(position[0] - position[1]));\n\t\t}\n\n\t\tlet mut next_segment = vector.segment_domain.next_id();\n\t\tfor (index, &point) in points.iter().enumerate().take(first_half_points) {\n\t\t\t// Extrema are single connected points or points with both positive and negative values\n\t\t\tif !matches!(point, Found::Both | Found::Positive | Found::Negative) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tvector\n\t\t\t\t.segment_domain\n\t\t\t\t.push(next_segment.next_id(), index, index + first_half_points, BezierHandles::Linear, StrokeId::ZERO);\n\t\t}\n\t}\n\n\t/// Join all points from the original to the copied.\n\tfn join_all(vector: &mut graphic_types::Vector) {\n\t\tlet mut next_segment = vector.segment_domain.next_id();\n\t\tlet first_half = vector.point_domain.len() / 2;\n\t\tfor index in 0..first_half {\n\t\t\tvector.segment_domain.push(next_segment.next_id(), index, index + first_half, BezierHandles::Linear, StrokeId::ZERO);\n\t\t}\n\t}\n\n\tpub fn extrude(vector: &mut graphic_types::Vector, direction: DVec2, joining_algorithm: ExtrudeJoiningAlgorithm) {\n\t\tsplit(vector, direction);\n\t\toffset_copy_all_segments(vector, direction);\n\n\t\tmatch joining_algorithm {\n\t\t\tExtrudeJoiningAlgorithm::Extrema => join_extrema_edges(vector, direction),\n\t\t\tExtrudeJoiningAlgorithm::All => join_all(vector),\n\t\t\tExtrudeJoiningAlgorithm::None => {}\n\t\t}\n\t}\n\n\t#[cfg(test)]\n\tmod extrude_tests {\n\t\tuse glam::DVec2;\n\t\tuse kurbo::{ParamCurve, ParamCurveDeriv};\n\n\t\t#[test]\n\t\tfn split_cubic() {\n\t\t\tlet l1 = kurbo::CubicBez::new((0., 0.), (100., 0.), (100., 100.), (0., 100.));\n\t\t\tassert_eq!(super::find_splits(l1, DVec2::Y).collect::<Vec<f64>>(), vec![0.5]);\n\t\t\tassert!(super::find_splits(l1, DVec2::X).collect::<Vec<f64>>().is_empty());\n\n\t\t\tlet l2 = kurbo::CubicBez::new((0., 0.), (0., 0.), (100., 0.), (100., 0.));\n\t\t\tassert!(super::find_splits(l2, DVec2::X).collect::<Vec<f64>>().is_empty());\n\n\t\t\tlet l3 = kurbo::PathSeg::Line(kurbo::Line::new((0., 0.), (100., 0.)));\n\t\t\tassert!(super::find_splits(l3.to_cubic(), DVec2::X).collect::<Vec<f64>>().is_empty());\n\n\t\t\tlet l4 = kurbo::CubicBez::new((0., 0.), (100., -10.), (100., 110.), (0., 100.));\n\t\t\tlet splits = super::find_splits(l4, DVec2::X).map(|t| l4.deriv().eval(t)).collect::<Vec<_>>();\n\t\t\tassert_eq!(splits.len(), 2);\n\t\t\tassert!(splits.iter().all(|&deriv| deriv.y.abs() < 1e-8), \"{splits:?}\");\n\t\t}\n\n\t\t#[test]\n\t\tfn split_vector() {\n\t\t\tlet curve = kurbo::PathSeg::Cubic(kurbo::CubicBez::new((0., 0.), (100., -10.), (100., 110.), (0., 100.)));\n\t\t\tlet mut vector = graphic_types::Vector::from_bezpath(kurbo::BezPath::from_path_segments([curve].into_iter()));\n\t\t\tsuper::split(&mut vector, DVec2::X);\n\t\t\tassert_eq!(vector.segment_ids().len(), 3);\n\t\t\tassert_eq!(vector.point_domain.ids().len(), 4);\n\t\t}\n\t}\n}\n\n#[node_macro::node(category(\"Vector: Modifier\"), path(core_types::vector))]\nasync fn extrude(_: impl Ctx, mut source: Table<Vector>, direction: DVec2, joining_algorithm: ExtrudeJoiningAlgorithm) -> Table<Vector> {\n\tfor TableRowMut { element: source, .. } in source.iter_mut() {\n\t\textrude_algorithms::extrude(source, direction, joining_algorithm);\n\t}\n\tsource\n}\n\n#[node_macro::node(category(\"Vector: Modifier\"), path(core_types::vector))]\nasync fn box_warp(_: impl Ctx, content: Table<Vector>, #[expose] rectangle: Table<Vector>) -> Table<Vector> {\n\tlet Some((target, target_transform)) = rectangle.get(0).map(|rect| (rect.element, rect.transform)) else {\n\t\treturn content;\n\t};\n\n\tcontent\n\t\t.into_iter()\n\t\t.map(|mut row| {\n\t\t\tlet transform = row.transform;\n\t\t\tlet vector = row.element;\n\n\t\t\t// Get the bounding box of the source vector geometry\n\t\t\tlet source_bbox = vector.bounding_box_with_transform(transform).unwrap_or([DVec2::ZERO, DVec2::ONE]);\n\n\t\t\t// Extract first 4 points from target shape to form the quadrilateral\n\t\t\t// Apply the target's transform to get points in world space\n\t\t\tlet target_points: Vec<DVec2> = target.point_domain.positions().iter().map(|&p| target_transform.transform_point2(p)).take(4).collect();\n\n\t\t\t// If we have fewer than 4 points, use the corners of the source bounding box\n\t\t\t// This handles the degenerative case\n\t\t\tlet dst_corners = if target_points.len() >= 4 {\n\t\t\t\t[target_points[0], target_points[1], target_points[2], target_points[3]]\n\t\t\t} else {\n\t\t\t\twarn!(\"Target shape has fewer than 4 points. Using source bounding box instead.\");\n\t\t\t\t[\n\t\t\t\t\tsource_bbox[0],\n\t\t\t\t\tDVec2::new(source_bbox[1].x, source_bbox[0].y),\n\t\t\t\t\tsource_bbox[1],\n\t\t\t\t\tDVec2::new(source_bbox[0].x, source_bbox[1].y),\n\t\t\t\t]\n\t\t\t};\n\n\t\t\t// Apply the warp\n\t\t\tlet mut result = vector.clone();\n\n\t\t\t// Precompute source bounding box size for normalization\n\t\t\tlet source_size = source_bbox[1] - source_bbox[0];\n\n\t\t\t// Transform points\n\t\t\tfor (_, position) in result.point_domain.positions_mut() {\n\t\t\t\t// Get the point in world space\n\t\t\t\tlet world_pos = transform.transform_point2(*position);\n\n\t\t\t\t// Normalize coordinates within the source bounding box\n\t\t\t\tlet t = ((world_pos - source_bbox[0]) / source_size).clamp(DVec2::ZERO, DVec2::ONE);\n\n\t\t\t\t// Apply bilinear interpolation\n\t\t\t\t*position = bilinear_interpolate(t, &dst_corners);\n\t\t\t}\n\n\t\t\t// Transform handles in bezier curves\n\t\t\tfor (_, handles, _, _) in result.handles_mut() {\n\t\t\t\t*handles = handles.apply_transformation(|pos| {\n\t\t\t\t\t// Get the handle in world space\n\t\t\t\t\tlet world_pos = transform.transform_point2(pos);\n\n\t\t\t\t\t// Normalize coordinates within the source bounding box\n\t\t\t\t\tlet t = ((world_pos - source_bbox[0]) / source_size).clamp(DVec2::ZERO, DVec2::ONE);\n\n\t\t\t\t\t// Apply bilinear interpolation\n\t\t\t\t\tbilinear_interpolate(t, &dst_corners)\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tresult.style.set_stroke_transform(DAffine2::IDENTITY);\n\n\t\t\t// Add this to the table and reset the transform since we've applied it directly to the points\n\t\t\trow.element = result;\n\t\t\trow.transform = DAffine2::IDENTITY;\n\t\t\trow\n\t\t})\n\t\t.collect()\n}\n\n// Interpolate within a quadrilateral using normalized coordinates (0-1)\nfn bilinear_interpolate(t: DVec2, quad: &[DVec2; 4]) -> DVec2 {\n\tlet tl = quad[0]; // Top-left\n\tlet tr = quad[1]; // Top-right\n\tlet br = quad[2]; // Bottom-right\n\tlet bl = quad[3]; // Bottom-left\n\n\t// Bilinear interpolation\n\ttl * (1. - t.x) * (1. - t.y) + tr * t.x * (1. - t.y) + br * t.x * t.y + bl * (1. - t.x) * t.y\n}\n\n#[node_macro::node(category(\"Vector\"), path(graphene_core::vector))]\nasync fn pack_strips<T: 'n + Send + Clone>(\n\t_: impl Ctx,\n\t#[implementations(\n\t\tTable<Graphic>,\n\t\tTable<Vector>,\n\t\tTable<Raster<CPU>>,\n\t\tTable<Raster<GPU>>,\n\t)]\n\telements: Table<T>,\n\t#[default(0.)]\n\t#[unit(\" px\")]\n\tseparation: f64,\n\t#[default(1000.)]\n\t#[unit(\" px\")]\n\tstrip_max_length: f64,\n\tstrip_direction: RowsOrColumns,\n) -> Table<T>\nwhere\n\tGraphic: From<Table<T>>,\n\tTable<T>: BoundingBox,\n{\n\t// Packs shapes using bounds with Best-Fit Decreasing Height (BFDH) algorithm:\n\t// - Sort shapes by cross-axis size (tallest first for rows, widest first for columns)\n\t// - For each shape, find the existing strip with minimum remaining space that fits\n\t// - Create new strip only if no existing strip can accommodate the shape\n\n\tstruct Strip {\n\t\talong_position: f64,\n\t\tcross_position: f64,\n\t\tcross_extent: f64,\n\t}\n\n\t// Prepare the items to be sorted\n\tlet mut items: Vec<(f64, f64, DVec2, TableRow<T>)> = elements\n\t\t.into_iter()\n\t\t.map(|row| {\n\t\t\t// Single-element table to query its bounding box\n\t\t\tlet single = Table::new_from_row(row.clone());\n\t\t\tlet (w, h, top_left) = match single.bounding_box(DAffine2::IDENTITY, false) {\n\t\t\t\tRenderBoundingBox::Rectangle([min, max]) => {\n\t\t\t\t\tlet size = max - min;\n\t\t\t\t\t(size.x.max(0.), size.y.max(0.), min)\n\t\t\t\t}\n\t\t\t\t_ => (0., 0., DVec2::ZERO),\n\t\t\t};\n\t\t\tlet (along, cross) = match strip_direction {\n\t\t\t\tRowsOrColumns::Rows => (w, h),\n\t\t\t\tRowsOrColumns::Columns => (h, w),\n\t\t\t};\n\t\t\t(along, cross, top_left, row)\n\t\t})\n\t\t.collect();\n\n\t// Sort by cross-axis size, largest first\n\titems.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(Ordering::Equal));\n\n\tlet mut result = Table::new();\n\tlet mut strips: Vec<Strip> = Vec::new();\n\n\t// This looks n^2 but it is just n*k where k is the number of strips, which is generally much smaller than n\n\tfor (along, cross, top_left, mut row) in items {\n\t\tif along <= 0. {\n\t\t\tresult.push(row);\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Find a good strip, minimum remaining space that can fit this item ideally\n\t\tlet mut best_strip_index = None;\n\t\tlet mut min_remaining_space = f64::INFINITY;\n\n\t\tfor (index, strip) in strips.iter().enumerate() {\n\t\t\tlet remaining_space = strip_max_length - strip.along_position;\n\t\t\tif remaining_space >= along && remaining_space < min_remaining_space {\n\t\t\t\tmin_remaining_space = remaining_space;\n\t\t\t\tbest_strip_index = Some(index);\n\t\t\t}\n\t\t}\n\n\t\tif let Some(strip_index) = best_strip_index {\n\t\t\t// Place on existing strip\n\t\t\tlet strip = &mut strips[strip_index];\n\n\t\t\t// Update strip cross extent if needed\n\t\t\tif cross > strip.cross_extent {\n\t\t\t\tstrip.cross_extent = cross;\n\t\t\t}\n\n\t\t\tlet target_position = match strip_direction {\n\t\t\t\tRowsOrColumns::Rows => DVec2::new(strip.along_position, strip.cross_position),\n\t\t\t\tRowsOrColumns::Columns => DVec2::new(strip.cross_position, strip.along_position),\n\t\t\t};\n\t\t\trow.transform = DAffine2::from_translation(target_position - top_left) * row.transform;\n\n\t\t\tstrip.along_position += along + separation;\n\t\t} else {\n\t\t\t// Create new strip\n\t\t\tlet new_cross = strips.last().map_or(0., |last| last.cross_position + last.cross_extent + separation);\n\n\t\t\tlet target_position = match strip_direction {\n\t\t\t\tRowsOrColumns::Rows => DVec2::new(0., new_cross),\n\t\t\t\tRowsOrColumns::Columns => DVec2::new(new_cross, 0.),\n\t\t\t};\n\t\t\trow.transform = DAffine2::from_translation(target_position - top_left) * row.transform;\n\n\t\t\tstrips.push(Strip {\n\t\t\t\talong_position: along + separation,\n\t\t\t\tcross_position: new_cross,\n\t\t\t\tcross_extent: cross,\n\t\t\t});\n\t\t}\n\n\t\tresult.push(row);\n\t}\n\n\tresult\n}\n\n/// Automatically constructs tangents (Bézier handles) for anchor points in a vector path.\n#[node_macro::node(category(\"Vector: Modifier\"), name(\"Auto-Tangents\"), path(core_types::vector))]\nasync fn auto_tangents(\n\t_: impl Ctx,\n\tsource: Table<Vector>,\n\t/// The amount of spread for the auto-tangents, from 0 (sharp corner) to 1 (full spread).\n\t#[default(0.5)]\n\t// TODO: Make this a soft range to allow any value to be typed in outside the slider range of 0 to 1\n\t#[range((0., 1.))]\n\tspread: f64,\n\t/// If active, existing non-zero handles won't be affected.\n\t#[default(true)]\n\tpreserve_existing: bool,\n) -> Table<Vector> {\n\tsource\n\t\t.iter()\n\t\t.map(|source| {\n\t\t\tlet transform = *source.transform;\n\t\t\tlet alpha_blending = *source.alpha_blending;\n\t\t\tlet source_node_id = *source.source_node_id;\n\t\t\tlet source = source.element;\n\n\t\t\tlet mut result = Vector {\n\t\t\t\tstyle: source.style.clone(),\n\t\t\t\t..Default::default()\n\t\t\t};\n\n\t\t\tfor mut subpath in source.stroke_bezier_paths() {\n\t\t\t\tsubpath.apply_transform(transform);\n\n\t\t\t\tlet manipulators_list = subpath.manipulator_groups();\n\t\t\t\tif manipulators_list.len() < 2 {\n\t\t\t\t\t// Not enough points for softening or handle removal\n\t\t\t\t\tresult.append_subpath(subpath, true);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tlet mut new_manipulators_list = Vec::with_capacity(manipulators_list.len());\n\t\t\t\tlet is_closed = subpath.closed();\n\n\t\t\t\tfor i in 0..manipulators_list.len() {\n\t\t\t\t\tlet curr = &manipulators_list[i];\n\n\t\t\t\t\tif preserve_existing {\n\t\t\t\t\t\t// Check if this point has handles that are meaningfully different from the anchor\n\t\t\t\t\t\tlet has_handles = (curr.in_handle.is_some() && !curr.in_handle.unwrap().abs_diff_eq(curr.anchor, 1e-5))\n\t\t\t\t\t\t\t|| (curr.out_handle.is_some() && !curr.out_handle.unwrap().abs_diff_eq(curr.anchor, 1e-5));\n\n\t\t\t\t\t\t// If the point already has handles, or if it's an endpoint of an open path, keep it as is.\n\t\t\t\t\t\tif has_handles || (!is_closed && (i == 0 || i == manipulators_list.len() - 1)) {\n\t\t\t\t\t\t\tnew_manipulators_list.push(*curr);\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// If spread is 0, remove handles for this point, making it a sharp corner.\n\t\t\t\t\tif spread == 0. {\n\t\t\t\t\t\tnew_manipulators_list.push(ManipulatorGroup {\n\t\t\t\t\t\t\tanchor: curr.anchor,\n\t\t\t\t\t\t\tin_handle: None,\n\t\t\t\t\t\t\tout_handle: None,\n\t\t\t\t\t\t\tid: curr.id,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Get previous and next points for auto-tangent calculation\n\t\t\t\t\tlet prev_idx = if i == 0 { if is_closed { manipulators_list.len() - 1 } else { i } } else { i - 1 };\n\t\t\t\t\tlet next_idx = if i == manipulators_list.len() - 1 { if is_closed { 0 } else { i } } else { i + 1 };\n\n\t\t\t\t\tlet prev = manipulators_list[prev_idx].anchor;\n\t\t\t\t\tlet curr_pos = curr.anchor;\n\t\t\t\t\tlet next = manipulators_list[next_idx].anchor;\n\n\t\t\t\t\t// Calculate directions from current point to adjacent points\n\t\t\t\t\tlet dir_prev = (prev - curr_pos).normalize_or_zero();\n\t\t\t\t\tlet dir_next = (next - curr_pos).normalize_or_zero();\n\n\t\t\t\t\t// Check if we have valid directions (e.g., points are not coincident)\n\t\t\t\t\tif dir_prev.length_squared() < 1e-5 || dir_next.length_squared() < 1e-5 {\n\t\t\t\t\t\t// Fallback: keep the original manipulator group (which has no active handles here)\n\t\t\t\t\t\tnew_manipulators_list.push(*curr);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Calculate handle direction (colinear, pointing along the line from prev to next)\n\t\t\t\t\t// Original logic: (dir_prev - dir_next) is equivalent to (prev - curr) - (next - curr) = prev - next\n\t\t\t\t\t// The handle_dir will be along the line connecting prev and next, or perpendicular if they are coincident.\n\t\t\t\t\tlet mut handle_dir = (dir_prev - dir_next).try_normalize().unwrap_or_else(|| dir_prev.perp());\n\n\t\t\t\t\t// Ensure consistent orientation of the handle_dir\n\t\t\t\t\t// This makes the `+ handle_dir` for in_handle and `- handle_dir` for out_handle consistent\n\t\t\t\t\tif dir_prev.dot(handle_dir) < 0. {\n\t\t\t\t\t\thandle_dir = -handle_dir;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Calculate handle lengths: 1/3 of distance to adjacent points, scaled by spread\n\t\t\t\t\tlet in_length = (curr_pos - prev).length() / 3. * spread;\n\t\t\t\t\tlet out_length = (next - curr_pos).length() / 3. * spread;\n\n\t\t\t\t\t// Create new manipulator group with calculated auto-tangents\n\t\t\t\t\tnew_manipulators_list.push(ManipulatorGroup {\n\t\t\t\t\t\tanchor: curr_pos,\n\t\t\t\t\t\tin_handle: Some(curr_pos + handle_dir * in_length),\n\t\t\t\t\t\tout_handle: Some(curr_pos - handle_dir * out_length),\n\t\t\t\t\t\tid: curr.id,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tlet mut softened_bezpath = bezpath_from_manipulator_groups(&new_manipulators_list, is_closed);\n\t\t\t\tsoftened_bezpath.apply_affine(Affine::new(transform.inverse().to_cols_array()));\n\t\t\t\tresult.append_bezpath(softened_bezpath);\n\t\t\t}\n\n\t\t\tTableRow {\n\t\t\t\telement: result,\n\t\t\t\ttransform,\n\t\t\t\talpha_blending,\n\t\t\t\tsource_node_id,\n\t\t\t}\n\t\t})\n\t\t.collect()\n}\n\n#[node_macro::node(category(\"Vector: Modifier\"), path(core_types::vector))]\nasync fn bounding_box(_: impl Ctx, content: Table<Vector>) -> Table<Vector> {\n\tcontent\n\t\t.into_iter()\n\t\t.map(|mut row| {\n\t\t\tlet vector = row.element;\n\n\t\t\tlet mut result = vector\n\t\t\t\t.bounding_box_rect()\n\t\t\t\t.map(|bbox| {\n\t\t\t\t\tlet mut vector = Vector::default();\n\t\t\t\t\tvector.append_bezpath(bbox.to_path(DEFAULT_ACCURACY));\n\t\t\t\t\tvector\n\t\t\t\t})\n\t\t\t\t.unwrap_or_default();\n\n\t\t\tresult.style = vector.style.clone();\n\t\t\tresult.style.set_stroke_transform(DAffine2::IDENTITY);\n\n\t\t\trow.element = result;\n\t\t\trow\n\t\t})\n\t\t.collect()\n}\n\n#[node_macro::node(category(\"Vector: Measure\"), path(core_types::vector))]\nasync fn dimensions(_: impl Ctx, content: Table<Vector>) -> DVec2 {\n\tcontent\n\t\t.iter()\n\t\t.filter_map(|vector| vector.element.bounding_box_with_transform(*vector.transform))\n\t\t.reduce(|[acc_top_left, acc_bottom_right], [top_left, bottom_right]| [acc_top_left.min(top_left), acc_bottom_right.max(bottom_right)])\n\t\t.map(|[top_left, bottom_right]| bottom_right - top_left)\n\t\t.unwrap_or_default()\n}\n\n// TODO: Replace this node with an automatic type conversion implementation of the `Convert` trait\n/// Converts a vec2 value into a vector path composed of a single anchor point.\n///\n/// This is useful in conjunction with nodes that repeat it, followed by the \"Points to Polyline\" node to string together a path of the points.\n#[node_macro::node(category(\"Vector\"), name(\"Vec2 to Point\"), path(core_types::vector))]\nasync fn vec2_to_point(_: impl Ctx, vec2: DVec2) -> Table<Vector> {\n\tlet mut point_domain = PointDomain::new();\n\tpoint_domain.push(PointId::generate(), vec2);\n\n\tTable::new_from_row(TableRow {\n\t\telement: Vector { point_domain, ..Default::default() },\n\t\t..Default::default()\n\t})\n}\n\n/// Creates a polyline from a series of vector points, replacing any existing segments and regions that may already exist.\n#[node_macro::node(category(\"Vector\"), name(\"Points to Polyline\"), path(core_types::vector))]\nasync fn points_to_polyline(_: impl Ctx, mut points: Table<Vector>, #[default(true)] closed: bool) -> Table<Vector> {\n\tfor row in points.iter_mut() {\n\t\tlet mut segment_domain = SegmentDomain::new();\n\t\tlet mut next_id = SegmentId::ZERO;\n\n\t\tlet points_count = row.element.point_domain.ids().len();\n\n\t\tif points_count >= 2 {\n\t\t\t(0..points_count - 1).for_each(|i| {\n\t\t\t\tsegment_domain.push(next_id.next_id(), i, i + 1, BezierHandles::Linear, StrokeId::generate());\n\t\t\t});\n\n\t\t\tif closed && points_count != 2 {\n\t\t\t\tsegment_domain.push(next_id.next_id(), points_count - 1, 0, BezierHandles::Linear, StrokeId::generate());\n\n\t\t\t\trow.element\n\t\t\t\t\t.region_domain\n\t\t\t\t\t.push(RegionId::generate(), segment_domain.ids()[0]..=*segment_domain.ids().last().unwrap(), FillId::generate());\n\t\t\t}\n\t\t}\n\n\t\trow.element.segment_domain = segment_domain;\n\t}\n\n\tpoints\n}\n\n#[node_macro::node(category(\"Vector: Modifier\"), path(core_types::vector), properties(\"offset_path_properties\"))]\nasync fn offset_path(_: impl Ctx, content: Table<Vector>, distance: f64, join: StrokeJoin, #[default(4.)] miter_limit: f64) -> Table<Vector> {\n\tcontent\n\t\t.into_iter()\n\t\t.map(|mut row| {\n\t\t\tlet transform = Affine::new(row.transform.to_cols_array());\n\t\t\tlet vector = row.element;\n\n\t\t\tlet bezpaths = vector.stroke_bezpath_iter();\n\t\t\tlet mut result = Vector {\n\t\t\t\tstyle: vector.style.clone(),\n\t\t\t\t..Default::default()\n\t\t\t};\n\t\t\tresult.style.set_stroke_transform(DAffine2::IDENTITY);\n\n\t\t\t// Perform operation on all subpaths in this shape.\n\t\t\tfor mut bezpath in bezpaths {\n\t\t\t\tbezpath.apply_affine(transform);\n\n\t\t\t\t// Taking the existing stroke data and passing it to Kurbo to generate new paths.\n\t\t\t\tlet mut bezpath_out = offset_bezpath(\n\t\t\t\t\t&bezpath,\n\t\t\t\t\t-distance,\n\t\t\t\t\tmatch join {\n\t\t\t\t\t\tStrokeJoin::Miter => kurbo::Join::Miter,\n\t\t\t\t\t\tStrokeJoin::Bevel => kurbo::Join::Bevel,\n\t\t\t\t\t\tStrokeJoin::Round => kurbo::Join::Round,\n\t\t\t\t\t},\n\t\t\t\t\tSome(miter_limit),\n\t\t\t\t);\n\n\t\t\t\tbezpath_out.apply_affine(transform.inverse());\n\n\t\t\t\t// One closed subpath, open path.\n\t\t\t\tresult.append_bezpath(bezpath_out);\n\t\t\t}\n\n\t\t\trow.element = result;\n\t\t\trow\n\t\t})\n\t\t.collect()\n}\n\n#[node_macro::node(category(\"Vector: Modifier\"), path(core_types::vector))]\nasync fn solidify_stroke(_: impl Ctx, content: Table<Vector>) -> Table<Vector> {\n\tcontent\n\t\t.into_iter()\n\t\t.map(|mut row| {\n\t\t\tlet vector = row.element;\n\n\t\t\tlet stroke = vector.style.stroke().clone().unwrap_or_default();\n\t\t\tlet bezpaths = vector.stroke_bezpath_iter();\n\t\t\tlet mut result = Vector::default();\n\n\t\t\t// Taking the existing stroke data and passing it to kurbo::stroke to generate new fill paths.\n\t\t\tlet join = match stroke.join {\n\t\t\t\tStrokeJoin::Miter => kurbo::Join::Miter,\n\t\t\t\tStrokeJoin::Bevel => kurbo::Join::Bevel,\n\t\t\t\tStrokeJoin::Round => kurbo::Join::Round,\n\t\t\t};\n\t\t\tlet cap = match stroke.cap {\n\t\t\t\tStrokeCap::Butt => kurbo::Cap::Butt,\n\t\t\t\tStrokeCap::Round => kurbo::Cap::Round,\n\t\t\t\tStrokeCap::Square => kurbo::Cap::Square,\n\t\t\t};\n\t\t\tlet dash_offset = stroke.dash_offset;\n\t\t\tlet dash_pattern = stroke.dash_lengths;\n\t\t\tlet miter_limit = stroke.join_miter_limit;\n\n\t\t\tlet stroke_style = kurbo::Stroke::new(stroke.weight)\n\t\t\t\t.with_caps(cap)\n\t\t\t\t.with_join(join)\n\t\t\t\t.with_dashes(dash_offset, dash_pattern)\n\t\t\t\t.with_miter_limit(miter_limit);\n\n\t\t\tlet stroke_options = kurbo::StrokeOpts::default();\n\n\t\t\t// 0.25 is balanced between performace and accuracy of the curve.\n\t\t\tconst STROKE_TOLERANCE: f64 = 0.25;\n\n\t\t\tfor mut path in bezpaths {\n\t\t\t\tpath.apply_affine(Affine::new(stroke.transform.to_cols_array()));\n\n\t\t\t\tlet mut solidified = kurbo::stroke(path, &stroke_style, &stroke_options, STROKE_TOLERANCE);\n\t\t\t\tif stroke.transform.matrix2.determinant() != 0. {\n\t\t\t\t\tsolidified.apply_affine(Affine::new(stroke.transform.inverse().to_cols_array()));\n\t\t\t\t}\n\n\t\t\t\tresult.append_bezpath(solidified);\n\t\t\t}\n\n\t\t\t// We set our fill to our stroke's color, then clear our stroke.\n\t\t\tif let Some(stroke) = vector.style.stroke() {\n\t\t\t\tresult.style.set_fill(Fill::solid_or_none(stroke.color));\n\t\t\t\tresult.style.set_stroke(Stroke::default());\n\t\t\t}\n\n\t\t\trow.element = result;\n\t\t\trow\n\t\t})\n\t\t.collect()\n}\n\n#[node_macro::node(category(\"Vector: Modifier\"), path(core_types::vector))]\nasync fn separate_subpaths(_: impl Ctx, content: Table<Vector>) -> Table<Vector> {\n\tcontent\n\t\t.into_iter()\n\t\t.flat_map(|row| {\n\t\t\tlet style = row.element.style.clone();\n\t\t\tlet transform = row.transform;\n\t\t\tlet alpha_blending = row.alpha_blending;\n\t\t\tlet source_node_id = row.source_node_id;\n\n\t\t\trow.element\n\t\t\t\t.stroke_bezpath_iter()\n\t\t\t\t.map(move |bezpath| {\n\t\t\t\t\tlet mut vector = Vector::default();\n\t\t\t\t\tvector.append_bezpath(bezpath);\n\t\t\t\t\tvector.style = style.clone();\n\n\t\t\t\t\tTableRow {\n\t\t\t\t\t\telement: vector,\n\t\t\t\t\t\ttransform,\n\t\t\t\t\t\talpha_blending,\n\t\t\t\t\t\tsource_node_id,\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.collect::<Vec<TableRow<Vector>>>()\n\t\t})\n\t\t.collect()\n}\n\n#[node_macro::node(category(\"Vector\"), path(graphene_core::vector))]\nasync fn map_points(ctx: impl Ctx + CloneVarArgs + ExtractAll, content: Table<Vector>, mapped: impl Node<Context<'static>, Output = DVec2>) -> Table<Vector> {\n\tlet mut content = content;\n\tlet mut index = 0;\n\n\tfor row in content.iter_mut() {\n\t\tfor (_, position) in row.element.point_domain.positions_mut() {\n\t\t\tlet owned_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index).with_position(*position);\n\t\t\tindex += 1;\n\n\t\t\t*position = mapped.eval(owned_ctx.into_context()).await;\n\t\t}\n\t}\n\n\tcontent\n}\n\n// TODO: Rename to \"Combine Paths\" and make this happen per-element instead of flattening every element into a single path. The migration for this should then become a Flatten Vector -> Combine Paths pair of nodes.\n#[node_macro::node(category(\"Vector\"), path(graphene_core::vector))]\npub async fn flatten_path<T: IntoGraphicTable + 'n + Send>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Vector>)] content: T) -> Table<Vector> {\n\t// Create a table with one empty `Vector` element, then get a mutable reference to it which we append flattened subpaths to\n\tlet mut output_table = Table::new_from_element(Vector::default());\n\tlet Some(output) = output_table.iter_mut().next() else { return output_table };\n\n\t// Concatenate every vector element's subpaths into the single output compound path\n\tfor (index, row) in content.into_flattened_table().iter().enumerate() {\n\t\tlet node_id = row.source_node_id.map(|node_id| node_id.0).unwrap_or_default();\n\n\t\tlet mut hasher = DefaultHasher::new();\n\t\t(index, node_id).hash(&mut hasher);\n\t\tlet collision_hash_seed = hasher.finish();\n\n\t\toutput.element.concat(row.element, *row.transform, collision_hash_seed);\n\n\t\t// TODO: Make this instead use the first encountered style\n\t\t// Use the last encountered style as the output style\n\t\toutput.element.style = row.element.style.clone();\n\t}\n\n\toutput_table\n}\n\n/// Convert vector geometry into a polyline composed of evenly spaced points.\n#[node_macro::node(category(\"\"), path(core_types::vector))]\nasync fn sample_polyline(\n\t_: impl Ctx,\n\tcontent: Table<Vector>,\n\tspacing: PointSpacingType,\n\t#[unit(\" px\")] separation: f64,\n\tquantity: u32,\n\t#[unit(\" px\")] start_offset: f64,\n\t#[unit(\" px\")] stop_offset: f64,\n\tadaptive_spacing: bool,\n\tsubpath_segment_lengths: Vec<f64>,\n) -> Table<Vector> {\n\tcontent\n\t\t.into_iter()\n\t\t.map(|mut row| {\n\t\t\tlet mut result = Vector {\n\t\t\t\tpoint_domain: Default::default(),\n\t\t\t\tsegment_domain: Default::default(),\n\t\t\t\tregion_domain: Default::default(),\n\t\t\t\tcolinear_manipulators: Default::default(),\n\t\t\t\tstyle: std::mem::take(&mut row.element.style),\n\t\t\t\tupstream_data: std::mem::take(&mut row.element.upstream_data),\n\t\t\t};\n\t\t\t// Transfer the stroke transform from the input vector content to the result.\n\t\t\tresult.style.set_stroke_transform(row.transform);\n\n\t\t\t// Using `stroke_bezpath_iter` so that the `subpath_segment_lengths` is aligned to the segments of each bezpath.\n\t\t\t// So we can index into `subpath_segment_lengths` to get the length of the segments.\n\t\t\t// NOTE: `subpath_segment_lengths` has precalulated lengths with transformation applied.\n\t\t\tlet bezpaths = row.element.stroke_bezpath_iter();\n\n\t\t\t// Keeps track of the index of the first segment of the next bezpath in order to get lengths of all segments.\n\t\t\tlet mut next_segment_index = 0;\n\n\t\t\tfor mut bezpath in bezpaths {\n\t\t\t\t// Apply the tranformation to the current bezpath to calculate points after transformation.\n\t\t\t\tbezpath.apply_affine(Affine::new(row.transform.to_cols_array()));\n\n\t\t\t\tlet segment_count = bezpath.segments().count();\n\n\t\t\t\t// For the current bezpath we get its segment's length by calculating the start index and end index.\n\t\t\t\tlet current_bezpath_segments_length = &subpath_segment_lengths[next_segment_index..next_segment_index + segment_count];\n\n\t\t\t\t// Increment the segment index by the number of segments in the current bezpath to calculate the next bezpath segment's length.\n\t\t\t\tnext_segment_index += segment_count;\n\n\t\t\t\tlet amount = match spacing {\n\t\t\t\t\tPointSpacingType::Separation => separation,\n\t\t\t\t\tPointSpacingType::Quantity => quantity as f64,\n\t\t\t\t};\n\t\t\t\tlet Some(mut sample_bezpath) = sample_polyline_on_bezpath(bezpath, spacing, amount, start_offset, stop_offset, adaptive_spacing, current_bezpath_segments_length) else {\n\t\t\t\t\tcontinue;\n\t\t\t\t};\n\n\t\t\t\t// Reverse the transformation applied to the bezpath as the `result` already has the transformation set.\n\t\t\t\tsample_bezpath.apply_affine(Affine::new(row.transform.to_cols_array()).inverse());\n\n\t\t\t\t// Append the bezpath (subpath) that connects generated points by lines.\n\t\t\t\tresult.append_bezpath(sample_bezpath);\n\t\t\t}\n\n\t\t\trow.element = result;\n\t\t\trow\n\t\t})\n\t\t.collect()\n}\n\n/// Cuts a path at a given progression from 0 to 1 along the path, creating two new subpaths from the original one (if the path is initially open) or one open subpath (if the path is initially closed).\n///\n/// If multiple subpaths make up the path, the whole number part of the progression value selects the subpath and the decimal part determines the position along it.\n#[node_macro::node(category(\"Vector: Modifier\"), path(graphene_core::vector))]\nasync fn cut_path(\n\t_: impl Ctx,\n\t/// The path to insert a cut into.\n\tmut content: Table<Vector>,\n\t/// The factor from the start to the end of the path, 0–1 for one subpath, 1–2 for a second subpath, and so on.\n\tprogression: Progression,\n\t/// Swap the direction of the path.\n\treverse: bool,\n\t/// Traverse the path using each segment's Bézier curve parameterization instead of the Euclidean distance. Faster to compute but doesn't respect actual distances.\n\tparameterized_distance: bool,\n) -> Table<Vector> {\n\tlet euclidian = !parameterized_distance;\n\n\tlet bezpaths = content\n\t\t.iter()\n\t\t.enumerate()\n\t\t.flat_map(|(row_index, vector)| vector.element.stroke_bezpath_iter().map(|bezpath| (row_index, bezpath)).collect::<Vec<_>>())\n\t\t.collect::<Vec<_>>();\n\n\tlet bezpath_count = bezpaths.len() as f64;\n\tlet t_value = progression.clamp(0., bezpath_count);\n\tlet t_value = if reverse { bezpath_count - t_value } else { t_value };\n\tlet index = if t_value >= bezpath_count { (bezpath_count - 1.) as usize } else { t_value as usize };\n\n\tif let Some((row_index, bezpath)) = bezpaths.get(index).cloned() {\n\t\tlet mut result_vector = Vector {\n\t\t\tstyle: content.get(row_index).unwrap().element.style.clone(),\n\t\t\t..Default::default()\n\t\t};\n\n\t\tfor (_, (_, bezpath)) in bezpaths.iter().enumerate().filter(|(i, (ri, _))| *i != index && *ri == row_index) {\n\t\t\tresult_vector.append_bezpath(bezpath.clone());\n\t\t}\n\t\tlet t = if t_value == bezpath_count { 1. } else { t_value.fract() };\n\t\tlet t = if euclidian { TValue::Euclidean(t) } else { TValue::Parametric(t) };\n\n\t\tif let Some((first, second)) = split_bezpath(&bezpath, t) {\n\t\t\tresult_vector.append_bezpath(first);\n\t\t\tresult_vector.append_bezpath(second);\n\t\t} else {\n\t\t\tresult_vector.append_bezpath(bezpath);\n\t\t}\n\n\t\t*content.get_mut(row_index).unwrap().element = result_vector;\n\t}\n\n\tcontent\n}\n\n/// Cuts path segments into separate disconnected pieces where each is a distinct subpath.\n#[node_macro::node(category(\"Vector: Modifier\"), path(core_types::vector))]\nasync fn cut_segments(_: impl Ctx, mut content: Table<Vector>) -> Table<Vector> {\n\t// Iterate through every segment and make a copy of each of its endpoints, then reassign each segment's endpoints to its own unique point copy\n\tfor row in content.iter_mut() {\n\t\tlet points_count = row.element.point_domain.ids().len();\n\t\tlet segments_count = row.element.segment_domain.ids().len();\n\n\t\tlet mut point_usages = vec![0_usize; points_count];\n\n\t\t// Count how many times each point is used as an endpoint of the segments\n\t\tlet start_points = row.element.segment_domain.start_point().iter();\n\t\tlet end_points = row.element.segment_domain.end_point().iter();\n\t\tfor (&start, &end) in start_points.zip(end_points) {\n\t\t\tpoint_usages[start] += 1;\n\t\t\tpoint_usages[end] += 1;\n\t\t}\n\n\t\tlet mut new_points = PointDomain::new();\n\t\tlet mut offset_sum: usize = 0;\n\t\tlet mut points_with_new_offsets = Vec::with_capacity(points_count);\n\n\t\t// Build a new point domain with the original points, but with duplications based on their extra usages by the segments\n\t\tfor (index, (point_id, point)) in row.element.point_domain.iter().enumerate() {\n\t\t\t// Ensure at least one usage to preserve free-floating points not connected to any segments\n\t\t\tlet usage_count = point_usages[index].max(1);\n\n\t\t\tnew_points.push_unchecked(point_id, point);\n\n\t\t\tfor i in 1..usage_count {\n\t\t\t\tnew_points.push_unchecked(point_id.generate_from_hash(i as u64), point);\n\t\t\t}\n\n\t\t\tpoints_with_new_offsets.push(offset_sum);\n\t\t\toffset_sum += usage_count;\n\t\t}\n\n\t\t// Reconcile the segment domain with the new points\n\t\trow.element.point_domain = new_points;\n\t\tfor original_segment_index in 0..segments_count {\n\t\t\tlet original_point_start_index = row.element.segment_domain.start_point()[original_segment_index];\n\t\t\tlet original_point_end_index = row.element.segment_domain.end_point()[original_segment_index];\n\n\t\t\tpoint_usages[original_point_start_index] -= 1;\n\t\t\tpoint_usages[original_point_end_index] -= 1;\n\n\t\t\tlet start_usage = points_with_new_offsets[original_point_start_index] + point_usages[original_point_start_index];\n\t\t\tlet end_usage = points_with_new_offsets[original_point_end_index] + point_usages[original_point_end_index];\n\n\t\t\trow.element.segment_domain.set_start_point(original_segment_index, start_usage);\n\t\t\trow.element.segment_domain.set_end_point(original_segment_index, end_usage);\n\t\t}\n\t}\n\n\tcontent\n}\n\n/// Determines the position of a point on the path, given by its progression from 0 to 1 along the path.\n///\n/// If multiple subpaths make up the path, the whole number part of the progression value selects the subpath and the decimal part determines the position along it.\n#[node_macro::node(name(\"Position on Path\"), category(\"Vector: Measure\"), path(graphene_core::vector))]\nasync fn position_on_path(\n\t_: impl Ctx,\n\t/// The path to traverse.\n\tcontent: Table<Vector>,\n\t/// The factor from the start to the end of the path, 0–1 for one subpath, 1–2 for a second subpath, and so on.\n\tprogression: Progression,\n\t/// Swap the direction of the path.\n\treverse: bool,\n\t/// Traverse the path using each segment's Bézier curve parameterization instead of the Euclidean distance. Faster to compute but doesn't respect actual distances.\n\tparameterized_distance: bool,\n) -> DVec2 {\n\tlet euclidian = !parameterized_distance;\n\n\tlet mut bezpaths = content\n\t\t.iter()\n\t\t.flat_map(|vector| {\n\t\t\tlet transform = *vector.transform;\n\t\t\tvector.element.stroke_bezpath_iter().map(move |bezpath| (bezpath, transform))\n\t\t})\n\t\t.collect::<Vec<_>>();\n\tlet bezpath_count = bezpaths.len() as f64;\n\tlet progression = progression.clamp(0., bezpath_count);\n\tlet progression = if reverse { bezpath_count - progression } else { progression };\n\tlet index = if progression >= bezpath_count { (bezpath_count - 1.) as usize } else { progression as usize };\n\n\tbezpaths.get_mut(index).map_or(DVec2::ZERO, |(bezpath, transform)| {\n\t\tlet t = if progression == bezpath_count { 1. } else { progression.fract() };\n\t\tlet t = if euclidian { TValue::Euclidean(t) } else { TValue::Parametric(t) };\n\n\t\tbezpath.apply_affine(Affine::new(transform.to_cols_array()));\n\n\t\tpoint_to_dvec2(evaluate_bezpath(bezpath, t, None))\n\t})\n}\n\n/// Determines the angle of the tangent at a point on the path, given by its progression from 0 to 1 along the path.\n///\n/// If multiple subpaths make up the path, the whole number part of the progression value selects the subpath and the decimal part determines the position along it.\n#[node_macro::node(name(\"Tangent on Path\"), category(\"Vector: Measure\"), path(graphene_core::vector))]\nasync fn tangent_on_path(\n\t_: impl Ctx,\n\t/// The path to traverse.\n\tcontent: Table<Vector>,\n\t/// The factor from the start to the end of the path, 0–1 for one subpath, 1–2 for a second subpath, and so on.\n\tprogression: Progression,\n\t/// Swap the direction of the path.\n\treverse: bool,\n\t/// Traverse the path using each segment's Bézier curve parameterization instead of the Euclidean distance. Faster to compute but doesn't respect actual distances.\n\tparameterized_distance: bool,\n\t/// Whether the resulting angle should be given in as radians instead of degrees.\n\tradians: bool,\n) -> f64 {\n\tlet euclidian = !parameterized_distance;\n\n\tlet mut bezpaths = content\n\t\t.iter()\n\t\t.flat_map(|vector| {\n\t\t\tlet transform = *vector.transform;\n\t\t\tvector.element.stroke_bezpath_iter().map(move |bezpath| (bezpath, transform))\n\t\t})\n\t\t.collect::<Vec<_>>();\n\tlet bezpath_count = bezpaths.len() as f64;\n\tlet progression = progression.clamp(0., bezpath_count);\n\tlet progression = if reverse { bezpath_count - progression } else { progression };\n\tlet index = if progression >= bezpath_count { (bezpath_count - 1.) as usize } else { progression as usize };\n\n\tlet angle = bezpaths.get_mut(index).map_or(0., |(bezpath, transform)| {\n\t\tlet t = if progression == bezpath_count { 1. } else { progression.fract() };\n\t\tlet t_value = |t: f64| if euclidian { TValue::Euclidean(t) } else { TValue::Parametric(t) };\n\n\t\tbezpath.apply_affine(Affine::new(transform.to_cols_array()));\n\n\t\tlet mut tangent = point_to_dvec2(tangent_on_bezpath(bezpath, t_value(t), None));\n\t\tif tangent == DVec2::ZERO {\n\t\t\tlet t = t + if t > 0.5 { -0.001 } else { 0.001 };\n\t\t\ttangent = point_to_dvec2(tangent_on_bezpath(bezpath, t_value(t), None));\n\t\t}\n\t\tif tangent == DVec2::ZERO {\n\t\t\treturn 0.;\n\t\t}\n\n\t\t-tangent.angle_to(if reverse { -DVec2::X } else { DVec2::X })\n\t});\n\n\tif radians { angle } else { angle.to_degrees() }\n}\n\n#[node_macro::node(category(\"\"), path(core_types::vector))]\nasync fn poisson_disk_points(\n\t_: impl Ctx,\n\tcontent: Table<Vector>,\n\t#[unit(\" px\")]\n\t#[default(10.)]\n\t#[hard_min(0.01)]\n\tseparation_disk_diameter: f64,\n\tseed: SeedValue,\n) -> Table<Vector> {\n\tlet mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());\n\n\tcontent\n\t\t.into_iter()\n\t\t.map(|mut row| {\n\t\t\tlet mut result = Vector::default();\n\n\t\t\tlet path_with_bounding_boxes: Vec<_> = row\n\t\t\t\t.element\n\t\t\t\t.stroke_bezpath_iter()\n\t\t\t\t.map(|mut bezpath| {\n\t\t\t\t\t// TODO: apply transform to points instead of modifying the paths\n\t\t\t\t\tbezpath.close_path();\n\t\t\t\t\tlet bbox = bezpath.bounding_box();\n\t\t\t\t\t(bezpath, bbox)\n\t\t\t\t})\n\t\t\t\t.collect();\n\n\t\t\tfor (i, (subpath, _)) in path_with_bounding_boxes.iter().enumerate() {\n\t\t\t\tif subpath.segments().count() < 2 {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tfor point in bezpath_algorithms::poisson_disk_points(i, &path_with_bounding_boxes, separation_disk_diameter, || rng.random::<f64>()) {\n\t\t\t\t\tresult.point_domain.push(PointId::generate(), point);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Transfer the style from the input vector content to the result.\n\t\t\tresult.style = row.element.style.clone();\n\t\t\tresult.style.set_stroke_transform(DAffine2::IDENTITY);\n\n\t\t\trow.element = result;\n\t\t\trow\n\t\t})\n\t\t.collect()\n}\n\n#[node_macro::node(category(\"\"), path(core_types::vector))]\nasync fn subpath_segment_lengths(_: impl Ctx, content: Table<Vector>) -> Vec<f64> {\n\tlet pathseg_perimeter = |segment: PathSeg| {\n\t\tif is_linear(segment) {\n\t\t\tLine::new(segment.start(), segment.end()).perimeter(DEFAULT_ACCURACY)\n\t\t} else {\n\t\t\tsegment.perimeter(DEFAULT_ACCURACY)\n\t\t}\n\t};\n\n\tcontent\n\t\t.into_iter()\n\t\t.flat_map(|vector| {\n\t\t\tlet transform = vector.transform;\n\t\t\tvector\n\t\t\t\t.element\n\t\t\t\t.stroke_bezpath_iter()\n\t\t\t\t.flat_map(|mut bezpath| {\n\t\t\t\t\tbezpath.apply_affine(Affine::new(transform.to_cols_array()));\n\t\t\t\t\tbezpath.segments().map(pathseg_perimeter).collect::<Vec<f64>>()\n\t\t\t\t})\n\t\t\t\t.collect::<Vec<f64>>()\n\t\t})\n\t\t.collect()\n}\n\n#[node_macro::node(name(\"Spline\"), category(\"Vector: Modifier\"), path(core_types::vector))]\nasync fn spline(_: impl Ctx, content: Table<Vector>) -> Table<Vector> {\n\tcontent\n\t\t.into_iter()\n\t\t.filter_map(|mut row| {\n\t\t\t// Exit early if there are no points to generate splines from.\n\t\t\tif row.element.point_domain.positions().is_empty() {\n\t\t\t\treturn None;\n\t\t\t}\n\n\t\t\tlet mut segment_domain = SegmentDomain::default();\n\t\t\tlet mut next_id = SegmentId::ZERO;\n\t\t\tfor (manipulator_groups, closed) in row.element.stroke_manipulator_groups() {\n\t\t\t\tlet positions = manipulator_groups.iter().map(|manipulators| manipulators.anchor).collect::<Vec<_>>();\n\t\t\t\tlet closed = closed && positions.len() > 2;\n\n\t\t\t\t// Compute control point handles for Bezier spline.\n\t\t\t\tlet first_handles = if closed {\n\t\t\t\t\tsolve_spline_first_handle_closed(&positions)\n\t\t\t\t} else {\n\t\t\t\t\tsolve_spline_first_handle_open(&positions)\n\t\t\t\t};\n\n\t\t\t\tlet stroke_id = StrokeId::ZERO;\n\n\t\t\t\t// Create segments with computed Bezier handles and add them to the output vector element's segment domain.\n\t\t\t\tfor i in 0..(positions.len() - if closed { 0 } else { 1 }) {\n\t\t\t\t\tlet next_index = (i + 1) % positions.len();\n\n\t\t\t\t\tlet start_index = row.element.point_domain.resolve_id(manipulator_groups[i].id).unwrap();\n\t\t\t\t\tlet end_index = row.element.point_domain.resolve_id(manipulator_groups[next_index].id).unwrap();\n\n\t\t\t\t\tlet handle_start = first_handles[i];\n\t\t\t\t\tlet handle_end = positions[next_index] * 2. - first_handles[next_index];\n\t\t\t\t\tlet handles = BezierHandles::Cubic { handle_start, handle_end };\n\n\t\t\t\t\tsegment_domain.push(next_id.next_id(), start_index, end_index, handles, stroke_id);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\trow.element.segment_domain = segment_domain;\n\t\t\tSome(row)\n\t\t})\n\t\t.collect()\n}\n\n#[node_macro::node(category(\"Vector: Modifier\"), path(core_types::vector))]\nasync fn jitter_points(\n\t_: impl Ctx,\n\tcontent: Table<Vector>,\n\t#[unit(\" px\")]\n\t#[default(5.)]\n\tamount: f64,\n\tseed: SeedValue,\n) -> Table<Vector> {\n\tcontent\n\t\t.into_iter()\n\t\t.map(|mut row| {\n\t\t\tlet mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());\n\n\t\t\tlet transform = row.transform;\n\t\t\tlet inverse_transform = if transform.matrix2.determinant() != 0. { transform.inverse() } else { Default::default() };\n\n\t\t\tlet deltas = (0..row.element.point_domain.positions().len())\n\t\t\t\t.map(|_| {\n\t\t\t\t\tlet angle = rng.random::<f64>() * TAU;\n\n\t\t\t\t\tinverse_transform.transform_vector2(DVec2::from_angle(angle) * rng.random::<f64>() * amount)\n\t\t\t\t})\n\t\t\t\t.collect::<Vec<_>>();\n\t\t\tlet mut already_applied = vec![false; row.element.point_domain.positions().len()];\n\n\t\t\tfor (handles, start, end) in row.element.segment_domain.handles_and_points_mut() {\n\t\t\t\tlet start_delta = deltas[*start];\n\t\t\t\tlet end_delta = deltas[*end];\n\n\t\t\t\tif !already_applied[*start] {\n\t\t\t\t\tlet start_position = row.element.point_domain.positions()[*start];\n\t\t\t\t\trow.element.point_domain.set_position(*start, start_position + start_delta);\n\t\t\t\t\talready_applied[*start] = true;\n\t\t\t\t}\n\t\t\t\tif !already_applied[*end] {\n\t\t\t\t\tlet end_position = row.element.point_domain.positions()[*end];\n\t\t\t\t\trow.element.point_domain.set_position(*end, end_position + end_delta);\n\t\t\t\t\talready_applied[*end] = true;\n\t\t\t\t}\n\n\t\t\t\tmatch handles {\n\t\t\t\t\tBezierHandles::Cubic { handle_start, handle_end } => {\n\t\t\t\t\t\t*handle_start += start_delta;\n\t\t\t\t\t\t*handle_end += end_delta;\n\t\t\t\t\t}\n\t\t\t\t\tBezierHandles::Quadratic { handle } => {\n\t\t\t\t\t\t*handle = row.transform.transform_point2(*handle) + (start_delta + end_delta) / 2.;\n\t\t\t\t\t}\n\t\t\t\t\tBezierHandles::Linear => {}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\trow.element.style.set_stroke_transform(DAffine2::IDENTITY);\n\t\t\trow\n\t\t})\n\t\t.collect()\n}\n\n/// Interpolates the geometry and styles between multiple vector layers, producing a single morphed vector shape.\n///\n/// Based on the progression value, adjacent vector elements are blended together. From 0 until 1, the first element (bottom layer) morphs into the second element (next layer up). From 1 until 2, it then morphs into the third element, and so on until progression is capped at the last element (top layer).\n#[node_macro::node(category(\"Vector: Modifier\"), path(core_types::vector))]\nasync fn morph<I: IntoGraphicTable + 'n + Send + Clone>(\n\t_: impl Ctx,\n\t/// The vector elements to interpolate between. Mixed graphic content is deeply flattened to keep only vector elements.\n\t#[implementations(Table<Graphic>, Table<Vector>)]\n\tcontent: I,\n\t/// The factor from one vector element to the next in sequence. The whole number part selects the source element, and the decimal part determines the interpolation amount towards the next element.\n\tprogression: Progression,\n) -> Table<Vector> {\n\t/// Subdivides the last segment of the bezpath to until it appends 'count' number of segments.\n\tfn make_new_segments(bezpath: &mut BezPath, count: usize) {\n\t\tlet bezpath_segment_count = bezpath.segments().count();\n\n\t\tif count == 0 || bezpath_segment_count == 0 {\n\t\t\treturn;\n\t\t}\n\n\t\t// Initially push the last segment of the bezpath\n\t\tlet mut new_segments = vec![bezpath.get_seg(bezpath_segment_count).unwrap()];\n\n\t\t// Generate new segments by subdividing last segment\n\t\tfor _ in 0..count {\n\t\t\tlet last = new_segments.pop().unwrap();\n\t\t\tlet (first, second) = last.subdivide();\n\t\t\tnew_segments.push(first);\n\t\t\tnew_segments.push(second);\n\t\t}\n\n\t\t// Append the new segments.\n\t\tif count != 0 {\n\t\t\t// Remove the last segment as it is already appended to the new_segments.\n\t\t\tlet mut is_closed = false;\n\t\t\tif let Some(last_element) = bezpath.pop()\n\t\t\t\t&& last_element == PathEl::ClosePath\n\t\t\t{\n\t\t\t\tis_closed = true;\n\t\t\t\t_ = bezpath.pop();\n\t\t\t}\n\n\t\t\tfor segment in new_segments {\n\t\t\t\tif bezpath.elements().is_empty() {\n\t\t\t\t\tbezpath.move_to(segment.start())\n\t\t\t\t}\n\t\t\t\tbezpath.push(segment.as_path_el());\n\t\t\t}\n\n\t\t\tif is_closed {\n\t\t\t\tbezpath.close_path();\n\t\t\t}\n\t\t}\n\t}\n\n\t// Preserve original graphic table as upstream data so this group layer's nested layers can be edited by the tools.\n\tlet graphic_table_content = content.clone().into_graphic_table();\n\n\t// If the input isn't a Table<Vector>, we convert it into one by flattening any Table<Graphic> content.\n\tlet content = content.into_flattened_table::<Vector>();\n\n\t// Determine source and target indices and interpolation time fraction\n\tlet progression = progression.max(0.);\n\tlet source_index = progression.floor() as usize;\n\tlet time = progression.fract();\n\n\t// Not enough elements to interpolate between, so we return the input as-is\n\tif content.len() <= 1 {\n\t\treturn content;\n\t}\n\t// Progression is at or past the last element, so we return the last element without interpolation\n\tif source_index >= content.len() - 1 {\n\t\treturn content.into_iter().last().into_iter().collect();\n\t}\n\n\t// Interpolation between two elements\n\tlet mut content_iter = content.into_iter();\n\tlet source_row = content_iter.nth(source_index).unwrap();\n\tlet target_row = content_iter.next().unwrap();\n\n\tlet mut vector = Vector {\n\t\tupstream_data: Some(graphic_table_content),\n\t\t..Default::default()\n\t};\n\n\t// Lerp styles\n\tlet vector_alpha_blending = source_row.alpha_blending.lerp(&target_row.alpha_blending, time as f32);\n\tvector.style = source_row.element.style.lerp(&target_row.element.style, time);\n\n\t// Before and after transforms\n\tlet source_transform = source_row.transform;\n\tlet target_transform = target_row.transform;\n\n\t// Before and after paths\n\tlet source_bezpaths = source_row.element.stroke_bezpath_iter();\n\tlet target_bezpaths = target_row.element.stroke_bezpath_iter();\n\n\tfor (mut source_bezpath, mut target_bezpath) in source_bezpaths.zip(target_bezpaths) {\n\t\tif source_bezpath.elements().is_empty() || target_bezpath.elements().is_empty() {\n\t\t\tcontinue;\n\t\t}\n\n\t\tsource_bezpath.apply_affine(Affine::new(source_transform.to_cols_array()));\n\t\ttarget_bezpath.apply_affine(Affine::new(target_transform.to_cols_array()));\n\n\t\tlet target_segment_len = target_bezpath.segments().count();\n\t\tlet source_segment_len = source_bezpath.segments().count();\n\n\t\t// Insert new segments to align the number of segments in source_bezpath and target_bezpath.\n\t\tmake_new_segments(&mut source_bezpath, target_segment_len.max(source_segment_len) - source_segment_len);\n\t\tmake_new_segments(&mut target_bezpath, source_segment_len.max(target_segment_len) - target_segment_len);\n\n\t\tlet source_segments = source_bezpath.segments().collect::<Vec<PathSeg>>();\n\t\tlet target_segments = target_bezpath.segments().collect::<Vec<PathSeg>>();\n\n\t\t// Interpolate anchors and handles\n\t\tfor (i, (source_element, target_element)) in source_bezpath.elements_mut().iter_mut().zip(target_bezpath.elements_mut().iter_mut()).enumerate() {\n\t\t\tmatch source_element {\n\t\t\t\tPathEl::MoveTo(point) => *point = point.lerp(target_element.end_point().unwrap(), time),\n\t\t\t\tPathEl::ClosePath => {}\n\t\t\t\telm => {\n\t\t\t\t\tlet mut source_segment = source_segments.get(i - 1).unwrap().to_cubic();\n\t\t\t\t\tlet target_segment = target_segments.get(i - 1).unwrap().to_cubic();\n\t\t\t\t\tsource_segment.p0 = source_segment.p0.lerp(target_segment.p0, time);\n\t\t\t\t\tsource_segment.p1 = source_segment.p1.lerp(target_segment.p1, time);\n\t\t\t\t\tsource_segment.p2 = source_segment.p2.lerp(target_segment.p2, time);\n\t\t\t\t\tsource_segment.p3 = source_segment.p3.lerp(target_segment.p3, time);\n\t\t\t\t\t*elm = PathSeg::Cubic(source_segment).as_path_el();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tvector.append_bezpath(source_bezpath.clone());\n\t}\n\n\t// Deal with unmatched extra paths by collapsing them\n\tlet source_paths_count = source_row.element.stroke_bezpath_iter().count();\n\tlet target_paths_count = target_row.element.stroke_bezpath_iter().count();\n\tlet source_paths = source_row.element.stroke_bezpath_iter().skip(target_paths_count);\n\tlet target_paths = target_row.element.stroke_bezpath_iter().skip(source_paths_count);\n\n\tfor mut source_path in source_paths {\n\t\tsource_path.apply_affine(Affine::new(source_transform.to_cols_array()));\n\n\t\t// Skip if the path has no segments else get the point at the end of the path.\n\t\tlet Some(end) = source_path.segments().last().map(|element| element.end()) else { continue };\n\n\t\tfor element in source_path.elements_mut() {\n\t\t\tmatch element {\n\t\t\t\tPathEl::MoveTo(point) => *point = point.lerp(end, time),\n\t\t\t\tPathEl::LineTo(point) => *point = point.lerp(end, time),\n\t\t\t\tPathEl::QuadTo(point, point1) => {\n\t\t\t\t\t*point = point.lerp(end, time);\n\t\t\t\t\t*point1 = point1.lerp(end, time);\n\t\t\t\t}\n\t\t\t\tPathEl::CurveTo(point, point1, point2) => {\n\t\t\t\t\t*point = point.lerp(end, time);\n\t\t\t\t\t*point1 = point1.lerp(end, time);\n\t\t\t\t\t*point2 = point2.lerp(end, time);\n\t\t\t\t}\n\t\t\t\tPathEl::ClosePath => {}\n\t\t\t}\n\t\t}\n\t\tvector.append_bezpath(source_path);\n\t}\n\n\tfor mut target_path in target_paths {\n\t\ttarget_path.apply_affine(Affine::new(source_transform.to_cols_array()));\n\n\t\t// Skip if the path has no segments else get the point at the start of the path.\n\t\tlet Some(start) = target_path.segments().next().map(|element| element.start()) else { continue };\n\n\t\tfor element in target_path.elements_mut() {\n\t\t\tmatch element {\n\t\t\t\tPathEl::MoveTo(point) => *point = start.lerp(*point, time),\n\t\t\t\tPathEl::LineTo(point) => *point = start.lerp(*point, time),\n\t\t\t\tPathEl::QuadTo(point, point1) => {\n\t\t\t\t\t*point = start.lerp(*point, time);\n\t\t\t\t\t*point1 = start.lerp(*point1, time);\n\t\t\t\t}\n\t\t\t\tPathEl::CurveTo(point, point1, point2) => {\n\t\t\t\t\t*point = start.lerp(*point, time);\n\t\t\t\t\t*point1 = start.lerp(*point1, time);\n\t\t\t\t\t*point2 = start.lerp(*point2, time);\n\t\t\t\t}\n\t\t\t\tPathEl::ClosePath => {}\n\t\t\t}\n\t\t}\n\t\tvector.append_bezpath(target_path);\n\t}\n\n\tTable::new_from_row(TableRow {\n\t\telement: vector,\n\t\talpha_blending: vector_alpha_blending,\n\t\t..Default::default()\n\t})\n}\n\nfn bevel_algorithm(mut vector: Vector, transform: DAffine2, distance: f64) -> Vector {\n\t// Splits a bézier curve based on a distance measurement\n\tfn split_distance(bezier: PathSeg, distance: f64, length: f64) -> PathSeg {\n\t\tlet parametric = eval_pathseg_euclidean(bezier, (distance / length).clamp(0., 1.), DEFAULT_ACCURACY);\n\t\tbezier.subsegment(parametric..1.)\n\t}\n\n\t/// Produces a list that corresponds with the point ID. The value is how many segments are connected.\n\tfn segments_connected_count(vector: &Vector) -> Vec<usize> {\n\t\t// Count the number of segments connecting to each point.\n\t\tlet mut segments_connected_count = vec![0; vector.point_domain.ids().len()];\n\t\tfor &point_index in vector.segment_domain.start_point().iter().chain(vector.segment_domain.end_point()) {\n\t\t\tsegments_connected_count[point_index] += 1;\n\t\t}\n\n\t\t// Zero out points without exactly two connectors. These are ignored.\n\t\tfor count in &mut segments_connected_count {\n\t\t\tif *count != 2 {\n\t\t\t\t*count = 0;\n\t\t\t}\n\t\t}\n\t\tsegments_connected_count\n\t}\n\n\t/// Updates the index so that it points at a point with the position. If nobody else will look at the index, the original point is updated. Otherwise a new point is created.\n\tfn create_or_modify_point(point_domain: &mut PointDomain, segments_connected_count: &mut [usize], pos: DVec2, index: &mut usize, next_id: &mut PointId, new_segments: &mut Vec<[usize; 2]>) {\n\t\tsegments_connected_count[*index] -= 1;\n\t\tif segments_connected_count[*index] == 0 {\n\t\t\t// If nobody else is going to look at this point, we're alright to modify it\n\t\t\tpoint_domain.set_position(*index, pos);\n\t\t} else {\n\t\t\tlet new_index = point_domain.ids().len();\n\t\t\tlet original_index = *index;\n\n\t\t\t// Create a new point (since someone will wish to look at the point in the original position in future)\n\t\t\t*index = new_index;\n\t\t\tpoint_domain.push(next_id.next_id(), pos);\n\n\t\t\t// Add a new segment to be created later\n\t\t\tnew_segments.push([new_index, original_index]);\n\t\t}\n\t}\n\n\tfn calculate_distance_to_split(bezier1: PathSeg, bezier2: PathSeg, bevel_length: f64) -> f64 {\n\t\tif is_linear(bezier1) && is_linear(bezier2) {\n\t\t\tlet v1 = (bezier1.end() - bezier1.start()).normalize();\n\t\t\tlet v2 = (bezier1.end() - bezier2.end()).normalize();\n\n\t\t\tlet dot_product = v1.dot(v2);\n\t\t\tlet angle_rad = dot_product.acos();\n\n\t\t\treturn bevel_length / (2. * (angle_rad / 2.).sin());\n\t\t}\n\n\t\tlet length1 = bezier1.perimeter(DEFAULT_ACCURACY);\n\t\tlet length2 = bezier2.perimeter(DEFAULT_ACCURACY);\n\n\t\tlet max_split = length1.min(length2);\n\n\t\tlet mut split_distance = 0.;\n\t\tlet mut best_diff = f64::MAX;\n\t\tlet mut current_best_distance = 0.;\n\n\t\tlet clamp_and_round = |value: f64| ((value * 1000.).round() / 1000.).clamp(0., 1.);\n\n\t\tconst INITIAL_SAMPLES: usize = 50;\n\t\tfor i in 0..=INITIAL_SAMPLES {\n\t\t\tlet distance_sample = max_split * (i as f64 / INITIAL_SAMPLES as f64);\n\n\t\t\tlet x_point_t = eval_pathseg_euclidean(bezier1, 1. - clamp_and_round(distance_sample / length1), DEFAULT_ACCURACY);\n\t\t\tlet y_point_t = eval_pathseg_euclidean(bezier2, clamp_and_round(distance_sample / length2), DEFAULT_ACCURACY);\n\n\t\t\tlet x_point = bezier1.eval(x_point_t);\n\t\t\tlet y_point = bezier2.eval(y_point_t);\n\n\t\t\tlet distance = x_point.distance(y_point);\n\t\t\tlet diff = (bevel_length - distance).abs();\n\n\t\t\tif diff < best_diff {\n\t\t\t\tbest_diff = diff;\n\t\t\t\tcurrent_best_distance = distance_sample;\n\t\t\t}\n\n\t\t\tif bevel_length - distance < 0. {\n\t\t\t\tsplit_distance = distance_sample;\n\n\t\t\t\tif i > 0 {\n\t\t\t\t\tlet prev_sample = max_split * ((i - 1) as f64 / INITIAL_SAMPLES as f64);\n\n\t\t\t\t\tconst REFINE_STEPS: usize = 10;\n\t\t\t\t\tfor j in 1..=REFINE_STEPS {\n\t\t\t\t\t\tlet refined_sample = prev_sample + (distance_sample - prev_sample) * (j as f64 / REFINE_STEPS as f64);\n\n\t\t\t\t\t\tlet x_point_t = eval_pathseg_euclidean(bezier1, 1. - (refined_sample / length1).clamp(0., 1.), DEFAULT_ACCURACY);\n\t\t\t\t\t\tlet y_point_t = eval_pathseg_euclidean(bezier2, (refined_sample / length2).clamp(0., 1.), DEFAULT_ACCURACY);\n\n\t\t\t\t\t\tlet x_point = bezier1.eval(x_point_t);\n\t\t\t\t\t\tlet y_point = bezier2.eval(y_point_t);\n\n\t\t\t\t\t\tlet distance = x_point.distance(y_point);\n\n\t\t\t\t\t\tif bevel_length - distance < 0. {\n\t\t\t\t\t\t\tsplit_distance = refined_sample;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif split_distance == 0. && current_best_distance > 0. {\n\t\t\tsplit_distance = current_best_distance;\n\t\t}\n\n\t\tsplit_distance\n\t}\n\n\tfn sort_segments(segment_domain: &SegmentDomain) -> Vec<usize> {\n\t\tlet start_points = segment_domain.start_point();\n\t\tlet end_points = segment_domain.end_point();\n\n\t\tlet mut sorted_segments = vec![0];\n\t\tlet segment_domain_length = segment_domain.ids().len();\n\n\t\tfor _ in 0..segment_domain_length {\n\t\t\tmatch sorted_segments.last() {\n\t\t\t\tSome(&last) => {\n\t\t\t\t\tif let Some(index) = start_points.iter().position(|&p| p == end_points[last]) {\n\t\t\t\t\t\tif index == 0 {\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsorted_segments.push(index);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tNone => break,\n\t\t\t}\n\t\t}\n\n\t\tif segment_domain_length != sorted_segments.len() {\n\t\t\tfor i in 0..segment_domain_length {\n\t\t\t\tif !sorted_segments.contains(&i) {\n\t\t\t\t\tsorted_segments.push(i);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tsorted_segments\n\t}\n\n\tfn update_existing_segments(vector: &mut Vector, transform: DAffine2, distance: f64, segments_connected: &mut [usize]) -> Vec<[usize; 2]> {\n\t\tlet mut next_id = vector.point_domain.next_id();\n\t\tlet mut new_segments = Vec::new();\n\n\t\tlet sorted_segments = sort_segments(&vector.segment_domain);\n\t\tlet segment_domain = &mut vector.segment_domain;\n\t\tlet segment_domain_length = segment_domain.ids().len();\n\n\t\tlet mut first_original_length = 0.;\n\t\tlet mut first_length = 0.;\n\t\tlet mut prev_original_length = 0.;\n\t\tlet mut prev_length = 0.;\n\n\t\tfor i in 0..segment_domain_length {\n\t\t\tlet (index, next_index) = if i == segment_domain_length - 1 { (i, 0) } else { (i, i + 1) };\n\t\t\tlet pair_handles_and_points = segment_domain.pair_handles_and_points_mut_by_index(sorted_segments[index], sorted_segments[next_index]);\n\t\t\tlet (handles, start_point, end_point, next_handles, next_start_point, next_end_point) = pair_handles_and_points;\n\n\t\t\tlet start = vector.point_domain.positions()[*start_point];\n\t\t\tlet end = vector.point_domain.positions()[*end_point];\n\n\t\t\tlet mut bezier = handles_to_segment(start, *handles, end);\n\t\t\tbezier = Affine::new(transform.to_cols_array()) * bezier;\n\n\t\t\tlet next_start = vector.point_domain.positions()[*next_start_point];\n\t\t\tlet next_end = vector.point_domain.positions()[*next_end_point];\n\n\t\t\tlet mut next_bezier = handles_to_segment(next_start, *next_handles, next_end);\n\t\t\tnext_bezier = Affine::new(transform.to_cols_array()) * next_bezier;\n\n\t\t\tlet calculated_split_distance = calculate_distance_to_split(bezier, next_bezier, distance);\n\n\t\t\tif is_linear(bezier) {\n\t\t\t\tbezier = PathSeg::Line(Line::new(bezier.start(), bezier.end()));\n\t\t\t}\n\n\t\t\tif is_linear(next_bezier) {\n\t\t\t\tnext_bezier = PathSeg::Line(Line::new(next_bezier.start(), next_bezier.end()));\n\t\t\t}\n\n\t\t\tlet inverse_transform = if transform.matrix2.determinant() != 0. { transform.inverse() } else { Default::default() };\n\n\t\t\tif index == 0 && next_index == 1 {\n\t\t\t\tfirst_original_length = bezier.perimeter(DEFAULT_ACCURACY);\n\t\t\t\tfirst_length = first_original_length;\n\t\t\t}\n\n\t\t\tlet (original_length, length) = if index == 0 {\n\t\t\t\t(bezier.perimeter(DEFAULT_ACCURACY), bezier.perimeter(DEFAULT_ACCURACY))\n\t\t\t} else {\n\t\t\t\t(prev_original_length, prev_length)\n\t\t\t};\n\n\t\t\tlet (next_original_length, mut next_length) = if index == segment_domain_length - 1 && next_index == 0 {\n\t\t\t\t(first_original_length, first_length)\n\t\t\t} else {\n\t\t\t\t(next_bezier.perimeter(DEFAULT_ACCURACY), next_bezier.perimeter(DEFAULT_ACCURACY))\n\t\t\t};\n\n\t\t\t// Only split if the length is big enough to make it worthwhile\n\t\t\tlet valid_length = length > 1e-10;\n\t\t\tif segments_connected[*end_point] > 0 && valid_length {\n\t\t\t\t// Apply the bevel to the end\n\t\t\t\tlet distance = calculated_split_distance.min(original_length.min(next_original_length) / 2.);\n\t\t\t\tbezier = split_distance(bezier.reverse(), distance, length).reverse();\n\n\t\t\t\tif index == 0 && next_index == 1 {\n\t\t\t\t\tfirst_length = (length - distance).max(0.);\n\t\t\t\t}\n\n\t\t\t\t// Update the end position\n\t\t\t\tlet pos = inverse_transform.transform_point2(point_to_dvec2(bezier.end()));\n\t\t\t\tcreate_or_modify_point(&mut vector.point_domain, segments_connected, pos, end_point, &mut next_id, &mut new_segments);\n\t\t\t}\n\n\t\t\t// Update the handles\n\t\t\t*handles = segment_to_handles(&bezier).apply_transformation(|p| inverse_transform.transform_point2(p));\n\n\t\t\t// Only split if the length is big enough to make it worthwhile\n\t\t\tlet valid_length = next_length > 1e-10;\n\t\t\tif segments_connected[*next_start_point] > 0 && valid_length {\n\t\t\t\t// Apply the bevel to the start\n\t\t\t\tlet distance = calculated_split_distance.min(next_original_length.min(original_length) / 2.);\n\t\t\t\tnext_bezier = split_distance(next_bezier, distance, next_length);\n\t\t\t\tnext_length = (next_length - distance).max(0.);\n\n\t\t\t\t// Update the start position\n\t\t\t\tlet pos = inverse_transform.transform_point2(point_to_dvec2(next_bezier.start()));\n\n\t\t\t\tcreate_or_modify_point(&mut vector.point_domain, segments_connected, pos, next_start_point, &mut next_id, &mut new_segments);\n\n\t\t\t\t// Update the handles\n\t\t\t\t*next_handles = segment_to_handles(&next_bezier).apply_transformation(|p| inverse_transform.transform_point2(p));\n\t\t\t}\n\n\t\t\tprev_original_length = next_original_length;\n\t\t\tprev_length = next_length;\n\t\t}\n\n\t\tnew_segments\n\t}\n\n\tfn insert_new_segments(vector: &mut Vector, new_segments: &[[usize; 2]]) {\n\t\tlet mut next_id = vector.segment_domain.next_id();\n\n\t\tfor &[start, end] in new_segments {\n\t\t\tlet handles = BezierHandles::Linear;\n\t\t\tvector.segment_domain.push(next_id.next_id(), start, end, handles, StrokeId::ZERO);\n\t\t}\n\t}\n\n\tif distance > 1. && vector.segment_domain.ids().len() > 1 {\n\t\tlet mut segments_connected = segments_connected_count(&vector);\n\t\tlet new_segments = update_existing_segments(&mut vector, transform, distance, &mut segments_connected);\n\t\tinsert_new_segments(&mut vector, &new_segments);\n\t}\n\n\tvector\n}\n\n#[node_macro::node(category(\"Vector: Modifier\"), path(core_types::vector))]\nfn bevel(_: impl Ctx, source: Table<Vector>, #[default(10.)] distance: Length) -> Table<Vector> {\n\tsource\n\t\t.into_iter()\n\t\t.map(|row| TableRow {\n\t\t\telement: bevel_algorithm(row.element, row.transform, distance),\n\t\t\t..row\n\t\t})\n\t\t.collect()\n}\n\n#[node_macro::node(category(\"Vector: Modifier\"), path(core_types::vector))]\nfn close_path(_: impl Ctx, source: Table<Vector>) -> Table<Vector> {\n\tsource\n\t\t.into_iter()\n\t\t.map(|mut row| {\n\t\t\trow.element.close_subpaths();\n\t\t\trow\n\t\t})\n\t\t.collect()\n}\n\n#[node_macro::node(category(\"Vector: Measure\"), path(core_types::vector))]\nfn point_inside(_: impl Ctx, source: Table<Vector>, point: DVec2) -> bool {\n\tsource.into_iter().any(|row| row.element.check_point_inside_shape(row.transform, point))\n}\n\ntrait Count {\n\tfn count(&self) -> usize;\n}\nimpl<T> Count for Table<T> {\n\tfn count(&self) -> usize {\n\t\tself.len()\n\t}\n}\nimpl<T> Count for Vec<T> {\n\tfn count(&self) -> usize {\n\t\tself.len()\n\t}\n}\n\n// TODO: Return u32, u64, or usize instead of f64 after #1621 is resolved and has allowed us to implement automatic type conversion in the node graph for nodes with generic type inputs.\n// TODO: (Currently automatic type conversion only works for concrete types, via the Graphene preprocessor and not the full Graphene type system.)\n#[node_macro::node(category(\"General\"), path(graphene_core::vector))]\nasync fn count_elements<I: Count>(\n\t_: impl Ctx,\n\t#[implementations(\n\t\tTable<Graphic>,\n\t\tTable<Vector>,\n\t\tTable<Raster<CPU>>,\n\t\tTable<Raster<GPU>>,\n\t\tTable<Color>,\n\t\tTable<GradientStops>,\n\t\tVec<String>,\n\t\tVec<f64>,\n\t\tVec<DVec2>,\n\t)]\n\tcontent: I,\n) -> f64 {\n\tcontent.count() as f64\n}\n\n#[node_macro::node(category(\"Vector: Measure\"), path(graphene_core::vector))]\nasync fn count_points(_: impl Ctx, content: Table<Vector>) -> f64 {\n\tcontent.into_iter().map(|row| row.element.point_domain.positions().len() as f64).sum()\n}\n\n/// Retrieves the vec2 position (in local space) of the anchor point at the specified index in table of vector elements.\n/// If no value exists at that index, the position (0, 0) is returned.\n#[node_macro::node(category(\"Vector: Measure\"), path(graphene_core::vector))]\nasync fn index_points(\n\t_: impl Ctx,\n\t/// The vector element or elements containing the anchor points to be retrieved.\n\tcontent: Table<Vector>,\n\t/// The index of the points to retrieve, starting from 0 for the first point. Negative indices count backwards from the end, starting from -1 for the last item.\n\tindex: f64,\n) -> DVec2 {\n\tlet points_count = content.iter().map(|row| row.element.point_domain.positions().len()).sum::<usize>();\n\n\tif points_count == 0 {\n\t\treturn DVec2::ZERO;\n\t}\n\t// Clamp and allow negative indexing from the end\n\tlet index = index as isize;\n\tlet index = if index < 0 {\n\t\t(points_count as isize + index).max(0) as usize\n\t} else {\n\t\t(index as usize).min(points_count - 1)\n\t};\n\n\t// Find the point at the given index across all vector elements\n\tlet mut accumulated = 0;\n\tfor row in content.iter() {\n\t\tlet row_point_count = row.element.point_domain.positions().len();\n\t\tif index - accumulated < row_point_count {\n\t\t\treturn row.element.point_domain.positions()[index - accumulated];\n\t\t}\n\t\taccumulated += row_point_count;\n\t}\n\n\tDVec2::ZERO\n}\n\n#[node_macro::node(category(\"Vector: Measure\"), path(core_types::vector))]\nasync fn path_length(_: impl Ctx, source: Table<Vector>) -> f64 {\n\tsource\n\t\t.into_iter()\n\t\t.map(|row| {\n\t\t\tlet transform = row.transform;\n\t\t\trow.element\n\t\t\t\t.stroke_bezpath_iter()\n\t\t\t\t.map(|mut bezpath| {\n\t\t\t\t\tbezpath.apply_affine(Affine::new(transform.to_cols_array()));\n\t\t\t\t\tbezpath.perimeter(DEFAULT_ACCURACY)\n\t\t\t\t})\n\t\t\t\t.sum::<f64>()\n\t\t})\n\t\t.sum()\n}\n\n#[node_macro::node(category(\"Vector: Measure\"), path(core_types::vector))]\nasync fn area(ctx: impl Ctx + CloneVarArgs + ExtractAll, content: impl Node<Context<'static>, Output = Table<Vector>>) -> f64 {\n\tlet new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context();\n\tlet vector = content.eval(new_ctx).await;\n\n\tvector\n\t\t.iter()\n\t\t.map(|row| {\n\t\t\tlet scale = row.transform.decompose_scale();\n\t\t\trow.element.stroke_bezpath_iter().map(|subpath| subpath.area() * scale.x * scale.y).sum::<f64>()\n\t\t})\n\t\t.sum()\n}\n\n#[node_macro::node(category(\"Vector: Measure\"), path(core_types::vector))]\nasync fn centroid(ctx: impl Ctx + CloneVarArgs + ExtractAll, content: impl Node<Context<'static>, Output = Table<Vector>>, centroid_type: CentroidType) -> DVec2 {\n\tlet new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context();\n\tlet vector = content.eval(new_ctx).await;\n\n\tif vector.is_empty() {\n\t\treturn DVec2::ZERO;\n\t}\n\n\t// All subpath centroid positions added together as if they were vectors from the origin.\n\tlet mut centroid = DVec2::ZERO;\n\t// Cumulative area or length of all subpaths\n\tlet mut sum = 0.;\n\n\tfor row in vector.iter() {\n\t\tfor subpath in row.element.stroke_bezier_paths() {\n\t\t\tlet partial = match centroid_type {\n\t\t\t\tCentroidType::Area => subpath.area_centroid_and_area(Some(1e-3), Some(1e-3)).filter(|(_, area)| *area > 0.),\n\t\t\t\tCentroidType::Length => subpath.length_centroid_and_length(None, true),\n\t\t\t};\n\t\t\tif let Some((subpath_centroid, area_or_length)) = partial {\n\t\t\t\tlet subpath_centroid = row.transform.transform_point2(subpath_centroid);\n\n\t\t\t\tsum += area_or_length;\n\t\t\t\tcentroid += area_or_length * subpath_centroid;\n\t\t\t}\n\t\t}\n\t}\n\n\tif sum > 0. {\n\t\tcentroid / sum\n\t}\n\t// Without a summed denominator, return the average of all positions instead\n\telse {\n\t\tlet mut count: usize = 0;\n\n\t\tlet summed_positions = vector\n\t\t\t.iter()\n\t\t\t.flat_map(|row| row.element.point_domain.positions().iter().map(|&p| row.transform.transform_point2(p)))\n\t\t\t.inspect(|_| count += 1)\n\t\t\t.sum::<DVec2>();\n\n\t\tif count != 0 { summed_positions / (count as f64) } else { DVec2::ZERO }\n\t}\n}\n\n#[cfg(test)]\nmod test {\n\tuse super::*;\n\tuse core_types::Node;\n\tuse kurbo::{CubicBez, Ellipse, Point, Rect};\n\tuse std::future::Future;\n\tuse std::pin::Pin;\n\tuse vector_types::vector::algorithms::bezpath_algorithms::{TValue, trim_pathseg};\n\tuse vector_types::vector::misc::pathseg_abs_diff_eq;\n\n\t#[derive(Clone)]\n\tpub struct FutureWrapperNode<T: Clone>(T);\n\n\timpl<'i, T: 'i + Clone + Send> Node<'i, Footprint> for FutureWrapperNode<T> {\n\t\ttype Output = Pin<Box<dyn Future<Output = T> + 'i + Send>>;\n\t\tfn eval(&'i self, _input: Footprint) -> Self::Output {\n\t\t\tlet value = self.0.clone();\n\t\t\tBox::pin(async move { value })\n\t\t}\n\t}\n\n\tfn vector_node_from_bezpath(bezpath: BezPath) -> Table<Vector> {\n\t\tTable::new_from_element(Vector::from_bezpath(bezpath))\n\t}\n\n\tfn create_vector_row(bezpath: BezPath, transform: DAffine2) -> TableRow<Vector> {\n\t\tlet mut row = Vector::default();\n\t\trow.append_bezpath(bezpath);\n\t\tTableRow {\n\t\t\telement: row,\n\t\t\ttransform,\n\t\t\t..Default::default()\n\t\t}\n\t}\n\n\t#[tokio::test]\n\tasync fn bounding_box() {\n\t\tlet bounding_box = super::bounding_box((), vector_node_from_bezpath(Rect::new(-1., -1., 1., 1.).to_path(DEFAULT_ACCURACY))).await;\n\t\tlet bounding_box = bounding_box.iter().next().unwrap().element;\n\t\tassert_eq!(bounding_box.region_manipulator_groups().count(), 1);\n\t\tlet manipulator_groups_anchors = bounding_box\n\t\t\t.region_manipulator_groups()\n\t\t\t.next()\n\t\t\t.unwrap()\n\t\t\t.1\n\t\t\t.iter()\n\t\t\t.map(|manipulators| manipulators.anchor)\n\t\t\t.collect::<Vec<DVec2>>();\n\n\t\tassert_eq!(&manipulator_groups_anchors[..4], &[DVec2::NEG_ONE, DVec2::new(1., -1.), DVec2::ONE, DVec2::new(-1., 1.),]);\n\n\t\t// Test a rectangular path with non-zero rotation\n\t\tlet square = Vector::from_bezpath(Rect::new(-1., -1., 1., 1.).to_path(DEFAULT_ACCURACY));\n\t\tlet mut square = Table::new_from_element(square);\n\t\t*square.get_mut(0).unwrap().transform *= DAffine2::from_angle(std::f64::consts::FRAC_PI_4);\n\t\tlet bounding_box = BoundingBoxNode { content: FutureWrapperNode(square) }.eval(Footprint::default()).await;\n\t\tlet bounding_box = bounding_box.iter().next().unwrap().element;\n\t\tassert_eq!(bounding_box.region_manipulator_groups().count(), 1);\n\t\tlet manipulator_groups_anchors = bounding_box\n\t\t\t.region_manipulator_groups()\n\t\t\t.next()\n\t\t\t.unwrap()\n\t\t\t.1\n\t\t\t.iter()\n\t\t\t.map(|manipulators| manipulators.anchor)\n\t\t\t.collect::<Vec<DVec2>>();\n\n\t\tlet expected_bounding_box = [DVec2::NEG_ONE, DVec2::new(1., -1.), DVec2::ONE, DVec2::new(-1., 1.)];\n\t\tfor i in 0..4 {\n\t\t\tassert_eq!(manipulator_groups_anchors[i], expected_bounding_box[i]);\n\t\t}\n\t}\n\t#[tokio::test]\n\tasync fn copy_to_points() {\n\t\tlet points = Rect::new(-10., -10., 10., 10.).to_path(DEFAULT_ACCURACY);\n\t\tlet element = Rect::new(-1., -1., 1., 1.).to_path(DEFAULT_ACCURACY);\n\n\t\tlet expected_points = Vector::from_bezpath(points.clone()).point_domain.positions().to_vec();\n\n\t\tlet copy_to_points = super::copy_to_points(Footprint::default(), vector_node_from_bezpath(points), vector_node_from_bezpath(element), 1., 1., 0., 0, 0., 0).await;\n\t\tlet flatten_path = super::flatten_path(Footprint::default(), copy_to_points).await;\n\t\tlet flattened_copy_to_points = flatten_path.iter().next().unwrap().element;\n\n\t\tassert_eq!(flattened_copy_to_points.region_manipulator_groups().count(), expected_points.len());\n\n\t\tfor (index, (_, manipulator_groups)) in flattened_copy_to_points.region_manipulator_groups().enumerate() {\n\t\t\tlet offset = expected_points[index];\n\t\t\tlet manipulator_groups_anchors = manipulator_groups.iter().map(|manipulators| manipulators.anchor).collect::<Vec<DVec2>>();\n\t\t\tassert_eq!(\n\t\t\t\t&manipulator_groups_anchors,\n\t\t\t\t&[offset + DVec2::NEG_ONE, offset + DVec2::new(1., -1.), offset + DVec2::ONE, offset + DVec2::new(-1., 1.),]\n\t\t\t);\n\t\t}\n\t}\n\n\t#[tokio::test]\n\tasync fn sample_polyline() {\n\t\tlet path = BezPath::from_vec(vec![PathEl::MoveTo(Point::ZERO), PathEl::CurveTo(Point::ZERO, Point::new(100., 0.), Point::new(100., 0.))]);\n\t\tlet sample_polyline = super::sample_polyline(Footprint::default(), vector_node_from_bezpath(path), PointSpacingType::Separation, 30., 0, 0., 0., false, vec![100.]).await;\n\t\tlet sample_polyline = sample_polyline.iter().next().unwrap().element;\n\t\tassert_eq!(sample_polyline.point_domain.positions().len(), 4);\n\t\tfor (pos, expected) in sample_polyline.point_domain.positions().iter().zip([DVec2::X * 0., DVec2::X * 30., DVec2::X * 60., DVec2::X * 90.]) {\n\t\t\tassert!(pos.distance(expected) < 1e-3, \"Expected {expected} found {pos}\");\n\t\t}\n\t}\n\t#[tokio::test]\n\tasync fn sample_polyline_adaptive_spacing() {\n\t\tlet path = BezPath::from_vec(vec![PathEl::MoveTo(Point::ZERO), PathEl::CurveTo(Point::ZERO, Point::new(100., 0.), Point::new(100., 0.))]);\n\t\tlet sample_polyline = super::sample_polyline(Footprint::default(), vector_node_from_bezpath(path), PointSpacingType::Separation, 18., 0, 45., 10., true, vec![100.]).await;\n\t\tlet sample_polyline = sample_polyline.iter().next().unwrap().element;\n\t\tassert_eq!(sample_polyline.point_domain.positions().len(), 4);\n\t\tfor (pos, expected) in sample_polyline.point_domain.positions().iter().zip([DVec2::X * 45., DVec2::X * 60., DVec2::X * 75., DVec2::X * 90.]) {\n\t\t\tassert!(pos.distance(expected) < 1e-3, \"Expected {expected} found {pos}\");\n\t\t}\n\t}\n\t#[tokio::test]\n\tasync fn poisson() {\n\t\tlet poisson_points = super::poisson_disk_points(\n\t\t\tFootprint::default(),\n\t\t\tvector_node_from_bezpath(Ellipse::from_rect(Rect::new(-50., -50., 50., 50.)).to_path(DEFAULT_ACCURACY)),\n\t\t\t10. * std::f64::consts::SQRT_2,\n\t\t\t0,\n\t\t)\n\t\t.await;\n\t\tlet poisson_points = poisson_points.iter().next().unwrap().element;\n\t\tassert!(\n\t\t\t(20..=40).contains(&poisson_points.point_domain.positions().len()),\n\t\t\t\"actual len {}\",\n\t\t\tpoisson_points.point_domain.positions().len()\n\t\t);\n\t\tfor point in poisson_points.point_domain.positions() {\n\t\t\tassert!(point.length() < 50. + 1., \"Expected point in circle {point}\")\n\t\t}\n\t}\n\t#[tokio::test]\n\tasync fn segment_lengths() {\n\t\tlet bezpath = BezPath::from_vec(vec![PathEl::MoveTo(Point::ZERO), PathEl::CurveTo(Point::ZERO, Point::new(100., 0.), Point::new(100., 0.))]);\n\t\tlet lengths = subpath_segment_lengths(Footprint::default(), vector_node_from_bezpath(bezpath)).await;\n\t\tassert_eq!(lengths, vec![100.]);\n\t}\n\t#[tokio::test]\n\tasync fn path_length() {\n\t\tlet bezpath = Rect::new(100., 100., 201., 201.).to_path(DEFAULT_ACCURACY);\n\t\tlet transform = DAffine2::from_scale(DVec2::new(2., 2.));\n\t\tlet row = create_vector_row(bezpath, transform);\n\t\tlet table = (0..5).map(|_| row.clone()).collect::<Table<Vector>>();\n\n\t\tlet length = super::path_length(Footprint::default(), table).await;\n\n\t\t// 101 (each rectangle edge length) * 4 (rectangle perimeter) * 2 (scale) * 5 (number of rows)\n\t\tassert_eq!(length, 101. * 4. * 2. * 5.);\n\t}\n\t#[tokio::test]\n\tasync fn spline() {\n\t\tlet spline = super::spline(Footprint::default(), vector_node_from_bezpath(Rect::new(0., 0., 100., 100.).to_path(DEFAULT_ACCURACY))).await;\n\t\tlet spline = spline.iter().next().unwrap().element;\n\t\tassert_eq!(spline.stroke_bezpath_iter().count(), 1);\n\t\tassert_eq!(spline.point_domain.positions(), &[DVec2::ZERO, DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)]);\n\t}\n\t#[tokio::test]\n\tasync fn morph() {\n\t\tlet mut rectangles = vector_node_from_bezpath(Rect::new(0., 0., 100., 100.).to_path(DEFAULT_ACCURACY));\n\t\tlet mut second_rectangle = rectangles.get(0).unwrap().into_cloned();\n\t\tsecond_rectangle.transform *= DAffine2::from_translation((-100., -100.).into());\n\t\trectangles.push(second_rectangle);\n\n\t\tlet morphed = super::morph(Footprint::default(), rectangles, 0.5).await;\n\t\tlet element = morphed.iter().next().unwrap().element;\n\t\tassert_eq!(\n\t\t\t&element.point_domain.positions()[..4],\n\t\t\tvec![DVec2::new(-50., -50.), DVec2::new(50., -50.), DVec2::new(50., 50.), DVec2::new(-50., 50.)]\n\t\t);\n\t}\n\n\t#[track_caller]\n\tfn contains_segment(vector: Vector, target: PathSeg) {\n\t\tlet segments = vector.segment_iter().map(|x| x.1);\n\t\tlet count = segments\n\t\t\t.filter(|segment| pathseg_abs_diff_eq(*segment, target, 0.01) || pathseg_abs_diff_eq(segment.reverse(), target, 0.01))\n\t\t\t.count();\n\n\t\tassert_eq!(\n\t\t\tcount,\n\t\t\t1,\n\t\t\t\"Expected exactly one matching segment for {target:?}, but found {count}. The given segments are: {:#?}\",\n\t\t\tvector.segment_iter().collect::<Vec<_>>()\n\t\t);\n\t}\n\n\t#[tokio::test]\n\tasync fn bevel_rect() {\n\t\tlet source = Rect::new(0., 0., 100., 100.).to_path(DEFAULT_ACCURACY);\n\t\tlet beveled = super::bevel(Footprint::default(), vector_node_from_bezpath(source), 2_f64.sqrt() * 10.);\n\t\tlet beveled = beveled.iter().next().unwrap().element;\n\n\t\tassert_eq!(beveled.point_domain.positions().len(), 8);\n\t\tassert_eq!(beveled.segment_domain.ids().len(), 8);\n\n\t\t// Segments\n\t\tcontains_segment(beveled.clone(), PathSeg::Line(Line::new(Point::new(10., 0.), Point::new(90., 0.))));\n\t\tcontains_segment(beveled.clone(), PathSeg::Line(Line::new(Point::new(10., 100.), Point::new(90., 100.))));\n\t\tcontains_segment(beveled.clone(), PathSeg::Line(Line::new(Point::new(0., 10.), Point::new(0., 90.))));\n\t\tcontains_segment(beveled.clone(), PathSeg::Line(Line::new(Point::new(100., 10.), Point::new(100., 90.))));\n\n\t\t// Joins\n\t\tcontains_segment(beveled.clone(), PathSeg::Line(Line::new(Point::new(10., 0.), Point::new(0., 10.))));\n\t\tcontains_segment(beveled.clone(), PathSeg::Line(Line::new(Point::new(90., 0.), Point::new(100., 10.))));\n\t\tcontains_segment(beveled.clone(), PathSeg::Line(Line::new(Point::new(100., 90.), Point::new(90., 100.))));\n\t\tcontains_segment(beveled.clone(), PathSeg::Line(Line::new(Point::new(10., 100.), Point::new(0., 90.))));\n\t}\n\n\t#[tokio::test]\n\tasync fn bevel_open_curve() {\n\t\tlet curve = PathSeg::Cubic(CubicBez::new(Point::ZERO, Point::new(10., 0.), Point::new(10., 100.), Point::new(100., 0.)));\n\n\t\tlet mut source = BezPath::new();\n\t\tsource.move_to(Point::new(-100., 0.));\n\t\tsource.line_to(Point::ZERO);\n\t\tsource.push(curve.as_path_el());\n\n\t\tlet beveled = super::bevel((), vector_node_from_bezpath(source), 2_f64.sqrt() * 10.);\n\t\tlet beveled = beveled.iter().next().unwrap().element;\n\n\t\tassert_eq!(beveled.point_domain.positions().len(), 4);\n\t\tassert_eq!(beveled.segment_domain.ids().len(), 3);\n\n\t\t// Segments\n\t\tcontains_segment(beveled.clone(), PathSeg::Line(Line::new(Point::new(-8.2, 0.), Point::new(-100., 0.))));\n\t\tlet trimmed = trim_pathseg(curve, TValue::Euclidean(8.2 / curve.perimeter(DEFAULT_ACCURACY)), TValue::Parametric(1.)).unwrap();\n\t\tcontains_segment(beveled.clone(), trimmed);\n\n\t\t// Join\n\t\tcontains_segment(beveled.clone(), PathSeg::Line(Line::new(Point::new(-8.2, 0.), trimmed.start())));\n\t}\n\n\t#[tokio::test]\n\tasync fn bevel_with_transform() {\n\t\tlet curve = PathSeg::Cubic(CubicBez::new(Point::ZERO, Point::new(10., 0.), Point::new(10., 100.), Point::new(100., 0.)));\n\n\t\tlet mut source = BezPath::new();\n\t\tsource.move_to(Point::new(-100., 0.));\n\t\tsource.line_to(Point::ZERO);\n\t\tsource.push(curve.as_path_el());\n\n\t\tlet vector = Vector::from_bezpath(source);\n\t\tlet mut vector_table = Table::new_from_element(vector.clone());\n\n\t\t*vector_table.get_mut(0).unwrap().transform = DAffine2::from_scale_angle_translation(DVec2::splat(10.), 1., DVec2::new(99., 77.));\n\n\t\tlet beveled = super::bevel((), Table::new_from_element(vector), 2_f64.sqrt() * 10.);\n\t\tlet beveled = beveled.iter().next().unwrap().element;\n\n\t\tassert_eq!(beveled.point_domain.positions().len(), 4);\n\t\tassert_eq!(beveled.segment_domain.ids().len(), 3);\n\n\t\t// Segments\n\t\tcontains_segment(beveled.clone(), PathSeg::Line(Line::new(Point::new(-8.2, 0.), Point::new(-100., 0.))));\n\t\tlet trimmed = trim_pathseg(curve, TValue::Euclidean(8.2 / curve.perimeter(DEFAULT_ACCURACY)), TValue::Parametric(1.)).unwrap();\n\t\tcontains_segment(beveled.clone(), trimmed);\n\n\t\t// Join\n\t\tcontains_segment(beveled.clone(), PathSeg::Line(Line::new(Point::new(-8.2, 0.), trimmed.start())));\n\t}\n\n\t#[tokio::test]\n\tasync fn bevel_too_high() {\n\t\tlet mut source = BezPath::new();\n\t\tsource.move_to(Point::ZERO);\n\t\tsource.line_to(Point::new(100., 0.));\n\t\tsource.line_to(Point::new(100., 100.));\n\t\tsource.line_to(Point::new(0., 100.));\n\n\t\tlet beveled = super::bevel(Footprint::default(), vector_node_from_bezpath(source), 999.);\n\t\tlet beveled = beveled.iter().next().unwrap().element;\n\n\t\tassert_eq!(beveled.point_domain.positions().len(), 6);\n\t\tassert_eq!(beveled.segment_domain.ids().len(), 5);\n\n\t\t// Segments\n\t\tcontains_segment(beveled.clone(), PathSeg::Line(Line::new(Point::new(0., 0.), Point::new(50., 0.))));\n\t\tcontains_segment(beveled.clone(), PathSeg::Line(Line::new(Point::new(100., 50.), Point::new(100., 50.))));\n\t\tcontains_segment(beveled.clone(), PathSeg::Line(Line::new(Point::new(100., 50.), Point::new(50., 100.))));\n\n\t\t// Joins\n\t\tcontains_segment(beveled.clone(), PathSeg::Line(Line::new(Point::new(50., 0.), Point::new(100., 50.))));\n\t\tcontains_segment(beveled.clone(), PathSeg::Line(Line::new(Point::new(100., 50.), Point::new(50., 100.))));\n\t}\n\n\t#[tokio::test]\n\tasync fn bevel_repeated_point() {\n\t\tlet line = PathSeg::Line(Line::new(Point::ZERO, Point::new(100., 0.)));\n\t\tlet point = PathSeg::Cubic(CubicBez::new(Point::new(100., 0.), Point::ZERO, Point::ZERO, Point::new(100., 0.)));\n\t\tlet curve = PathSeg::Cubic(CubicBez::new(Point::new(100., 0.), Point::new(110., 0.), Point::new(110., 200.), Point::new(200., 0.)));\n\n\t\tlet subpath = BezPath::from_path_segments([line, point, curve].into_iter());\n\n\t\tlet beveled_table = super::bevel(Footprint::default(), vector_node_from_bezpath(subpath), 5.);\n\t\tlet beveled = beveled_table.iter().next().unwrap().element;\n\n\t\tassert_eq!(beveled.point_domain.positions().len(), 6);\n\t\tassert_eq!(beveled.segment_domain.ids().len(), 5);\n\t}\n}\n"
  },
  {
    "path": "node-graph/preprocessor/Cargo.toml",
    "content": "[package]\nname = \"preprocessor\"\nversion = \"0.1.0\"\nedition = \"2024\"\nlicense = \"MIT OR Apache-2.0\"\nauthors.workspace = true\n\n[features]\n\n[dependencies]\nlog = { workspace = true }\n\n# Workspace dependencies\ngraphene-std = { workspace = true, features = [\"gpu\"] }\ngraph-craft = { workspace = true }\ninterpreted-executor = { workspace = true }\n"
  },
  {
    "path": "node-graph/preprocessor/src/lib.rs",
    "content": "#[macro_use]\nextern crate log;\n\nuse graph_craft::document::value::*;\nuse graph_craft::document::*;\nuse graph_craft::proto::RegistryValueSource;\nuse graph_craft::{ProtoNodeIdentifier, concrete};\nuse graphene_std::registry::*;\nuse graphene_std::*;\nuse std::collections::{HashMap, HashSet};\n\npub fn expand_network(network: &mut NodeNetwork, substitutions: &HashMap<ProtoNodeIdentifier, DocumentNode>) {\n\tif network.generated {\n\t\treturn;\n\t}\n\n\tfor node in network.nodes.values_mut() {\n\t\tmatch &mut node.implementation {\n\t\t\tDocumentNodeImplementation::Network(node_network) => expand_network(node_network, substitutions),\n\t\t\tDocumentNodeImplementation::ProtoNode(proto_node_identifier) => {\n\t\t\t\tif let Some(new_node) = substitutions.get(proto_node_identifier) {\n\t\t\t\t\tnode.implementation = new_node.implementation.clone();\n\t\t\t\t}\n\t\t\t}\n\t\t\tDocumentNodeImplementation::Extract => (),\n\t\t}\n\t}\n}\n\npub fn generate_node_substitutions() -> HashMap<ProtoNodeIdentifier, DocumentNode> {\n\tlet mut custom = HashMap::new();\n\t// We pre initialize the node registry here to avoid a deadlock\n\tlet into_node_registry = &*interpreted_executor::node_registry::NODE_REGISTRY;\n\tlet node_registry = core_types::registry::NODE_REGISTRY.lock().unwrap();\n\tfor (id, metadata) in core_types::registry::NODE_METADATA.lock().unwrap().iter() {\n\t\tlet id = id.clone();\n\n\t\tlet NodeMetadata { fields, .. } = metadata;\n\t\tlet Some(implementations) = node_registry.get(&id) else { continue };\n\t\tlet valid_call_args: HashSet<_> = implementations.iter().map(|(_, node_io)| node_io.call_argument.clone()).collect();\n\t\tlet first_node_io = implementations.first().map(|(_, node_io)| node_io).unwrap_or(const { &NodeIOTypes::empty() });\n\t\tlet mut node_io_types = vec![HashSet::new(); fields.len()];\n\t\tfor (_, node_io) in implementations.iter() {\n\t\t\tfor (i, ty) in node_io.inputs.iter().enumerate() {\n\t\t\t\tnode_io_types[i].insert(ty.clone());\n\t\t\t}\n\t\t}\n\t\tlet mut input_type = &first_node_io.call_argument;\n\t\tif valid_call_args.len() > 1 {\n\t\t\tinput_type = &const { generic!(D) };\n\t\t}\n\n\t\tlet inputs: Vec<_> = node_inputs(fields, first_node_io);\n\t\tlet input_count = inputs.len();\n\t\tlet network_inputs = (0..input_count).map(|i| NodeInput::node(NodeId(i as u64), 0)).collect();\n\n\t\tlet identity_node = ops::identity::IDENTIFIER;\n\n\t\tlet mut generated_nodes = 0;\n\t\tlet mut nodes: HashMap<_, _, _> = node_io_types\n\t\t\t.iter()\n\t\t\t.enumerate()\n\t\t\t.map(|(i, inputs)| {\n\t\t\t\t(\n\t\t\t\t\tNodeId(i as u64),\n\t\t\t\t\tmatch inputs.len() {\n\t\t\t\t\t\t1 => {\n\t\t\t\t\t\t\tlet input = inputs.iter().next().unwrap();\n\t\t\t\t\t\t\tlet input_ty = input.nested_type();\n\t\t\t\t\t\t\tlet mut inputs = vec![NodeInput::import(input.clone(), i)];\n\n\t\t\t\t\t\t\tlet into_node_identifier = ProtoNodeIdentifier::with_owned_string(format!(\"graphene_core::ops::IntoNode<{}>\", input_ty.identifier_name()));\n\t\t\t\t\t\t\tlet convert_node_identifier = ProtoNodeIdentifier::with_owned_string(format!(\"graphene_core::ops::ConvertNode<{}>\", input_ty.identifier_name()));\n\n\t\t\t\t\t\t\tlet proto_node = if into_node_registry.keys().any(|ident: &ProtoNodeIdentifier| ident.as_str() == into_node_identifier.as_str()) {\n\t\t\t\t\t\t\t\tgenerated_nodes += 1;\n\t\t\t\t\t\t\t\tinto_node_identifier\n\t\t\t\t\t\t\t} else if into_node_registry.keys().any(|ident| ident.as_str() == convert_node_identifier.as_str()) {\n\t\t\t\t\t\t\t\tgenerated_nodes += 1;\n\t\t\t\t\t\t\t\tinputs.push(NodeInput::value(TaggedValue::None, false));\n\t\t\t\t\t\t\t\tconvert_node_identifier\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tidentity_node.clone()\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tlet mut original_location = OriginalLocation::default();\n\t\t\t\t\t\t\toriginal_location.auto_convert_index = Some(i);\n\t\t\t\t\t\t\tDocumentNode {\n\t\t\t\t\t\t\t\tinputs,\n\t\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(proto_node),\n\t\t\t\t\t\t\t\tvisible: true,\n\t\t\t\t\t\t\t\toriginal_location,\n\t\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t_ => DocumentNode {\n\t\t\t\t\t\t\tinputs: vec![NodeInput::import(generic!(X), i)],\n\t\t\t\t\t\t\timplementation: DocumentNodeImplementation::ProtoNode(identity_node.clone()),\n\t\t\t\t\t\t\tvisible: false,\n\t\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t})\n\t\t\t.collect();\n\n\t\tif generated_nodes == 0 {\n\t\t\tcontinue;\n\t\t}\n\n\t\tlet document_node = DocumentNode {\n\t\t\tinputs: network_inputs,\n\t\t\tcall_argument: input_type.clone(),\n\t\t\timplementation: DocumentNodeImplementation::ProtoNode(id.clone()),\n\t\t\tvisible: true,\n\t\t\tskip_deduplication: false,\n\t\t\tcontext_features: ContextDependencies::from(metadata.context_features.as_slice()),\n\t\t\t..Default::default()\n\t\t};\n\n\t\tnodes.insert(NodeId(input_count as u64), document_node);\n\n\t\tlet node = DocumentNode {\n\t\t\tinputs,\n\t\t\tcall_argument: input_type.clone(),\n\t\t\timplementation: DocumentNodeImplementation::Network(NodeNetwork {\n\t\t\t\texports: vec![NodeInput::Node {\n\t\t\t\t\tnode_id: NodeId(input_count as u64),\n\t\t\t\t\toutput_index: 0,\n\t\t\t\t}],\n\t\t\t\tnodes,\n\t\t\t\tscope_injections: Default::default(),\n\t\t\t\tgenerated: true,\n\t\t\t}),\n\t\t\tvisible: true,\n\t\t\tskip_deduplication: false,\n\t\t\t..Default::default()\n\t\t};\n\n\t\tcustom.insert(id.clone(), node);\n\t}\n\n\tcustom\n}\n\npub fn node_inputs(fields: &[registry::FieldMetadata], first_node_io: &NodeIOTypes) -> Vec<NodeInput> {\n\tfields\n\t\t.iter()\n\t\t.zip(first_node_io.inputs.iter())\n\t\t.enumerate()\n\t\t.map(|(index, (field, node_io_ty))| {\n\t\t\tlet ty = field.default_type.as_ref().unwrap_or(node_io_ty);\n\t\t\tlet exposed = if index == 0 { *ty != fn_type_fut!(Context, ()) } else { field.exposed };\n\n\t\t\tmatch field.value_source {\n\t\t\t\tRegistryValueSource::None => {}\n\t\t\t\tRegistryValueSource::Default(data) => {\n\t\t\t\t\tif let Some(custom_default) = TaggedValue::from_primitive_string(data, ty) {\n\t\t\t\t\t\treturn NodeInput::value(custom_default, exposed);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// It is incredibly useful to get a warning when the default type cannot be parsed rather than defaulting to `()`.\n\t\t\t\t\t\twarn!(\"Failed to parse default value for type `{ty:?}` with data `{data}`\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tRegistryValueSource::Scope(data) => return NodeInput::scope(Cow::Borrowed(data)),\n\t\t\t};\n\n\t\t\tif let Some(type_default) = TaggedValue::from_type(ty) {\n\t\t\t\treturn NodeInput::value(type_default, exposed);\n\t\t\t}\n\t\t\tNodeInput::value(TaggedValue::None, true)\n\t\t})\n\t\t.collect()\n}\n"
  },
  {
    "path": "node-graph/rfcs/fine-grained-context-caching.md",
    "content": "# Summary\n\nAdd a new compilation pass to \"nullify\" parts of the dynamic `Context` based on the usage within the graph to avoid unnecessary cache invalidations.\n\n# Motivation\n\nCaching of node outputs can only be done if the input (`Context`) the node was evaluated with has not changed between subsequent evaluations. This can lead to \"false invalidation\" which is when the cache is invalidated even though the node did not even depend on value that changed and still returns the same result.\n\n```rs\n// This node does not use any time information so we don't need to rerun it if the time has changed.\n#[node_macro]\nfn use_footprint(ctx: impl Ctx + ExtractFootprint, a: u32) -> {...}\n```\n\nTo mitigate this, we introduced a relatively fine grained `Extract*` API for interacting with the context. We can use the trait annotations produced by this system to infer which parts of the context are used on which graph evaluation paths during graph compile time.\n\n# Guide-level explanation\n\nOur current implementation of the `OwnedContextImpl` struct contains many values which can be used to pass data to nodes. But most of the time, the majority of these fields will be unused by the nodes, but when considering the equality of two `OwnedContextImpl` instances, they have to be considered.\n\n```rs\npub struct OwnedContextImpl {\n\tfootprint: Option<crate::transform::Footprint>,\n\tvarargs: Option<Arc<[DynBox]>>,\n\tparent: Option<Arc<dyn ExtractVarArgs + Sync + Send>>,\n\tindex: Option<usize>,\n\treal_time: Option<f64>,\n\tanimation_time: Option<f64>,\n}\n```\n\n## Why is `Context` equality important?\n\nIn Graphene, every node has to be *idempotent* that means that when we provide it with the same input, it will return the same output. This is a really useful property for caching because we can effectively only do the computation once and then reuse the result which could be significantly cheaper.\n\nWhat is the input then?\n\nThe input to all of nodes is of type `Context` which in of itself is just defined as:\n\n```rs\npub type Context = Option<Arc<OwnedContextImpl>>;\n```\n\nWe use this unified dynamic context type because this means we only have to compile one version of a node and all nodes are compatible with each other but this is not a formal limitation (and should never be considered to be a given).\n\nThe different parts of the context (e.g. `Footprint`, `index`, ...) are called *features*.\n\nIt thus makes sense that we have to check the equality of `Context` objects to test if we can reuse a cached value or not. If as in the example above a node only relies on one part of the `Context` we don't really care if some other part has changed and the contexts can be considered equal for use in **this node**.\n\nCache nodes compare the equality of inputs based on the hash code. To stay compatible with the existing API, we can \"zero out\" parts of the `OwnedContextImpl` by setting unused variants to `None`. This is done by a context modification node which is placed into the graph by the compiler.\n\nThe `ExtractAll` trait can be used to create a new Context based on the previous one which can be utilized by nodes which need to modify the context for their child nodes but don't depend on the data themselves.\n\n```rs\n#[node_macro::node(category(\"\"))]\nasync fn transform(ctx: impl Ctx + ExtractAll, ...) {...}\n ```\n\n## Context Feature Injection\n\nSome nodes need to provide context features for their downstream dependencies (in the function call stack building phase). This is accomplished through `Inject*` traits that complement the `Extract*` traits:\n\n```rs\n// A node that injects index information for downstream map operations\n#[node_macro::node(category(\"Iteration\"))]\nfn map_with_index<T>(\n    ctx: impl Ctx + InjectIndex,\n    collection: Vec<T>,\n    mapper: impl Node<T, Output = U>,\n) -> Vec<U> {\n    collection.iter().enumerate().map(|(index, item)| {\n        // This node injects the current index into the context\n        // for the mapper node to extract via ExtractIndex\n        let ctx_with_index = ctx.with_injected_index(index);\n        mapper.eval_with_context(ctx_with_index, item)\n    }).collect()\n}\n\n// Downstream nodes can extract the injected index\n#[node_macro::node(category(\"Utility\"))]\nfn use_index(ctx: impl Ctx + ExtractIndex, value: f64) -> f64 {\n    let index = ctx.index().unwrap_or(0);\n    value * (index as f64)\n}\n```\n\n### Injection Hierarchy and Precedence\n\nWhen a node both extracts and injects the same feature:\n- **Extract-then-Inject**: Node extracts from upstream, processes, then injects modified version downstream\n- **Inject-Override**: Injected features take precedence over upstream extracted features\n- **Injection Scope**: Injected features are only available to immediate downstream nodes in the evaluation chain\n\n# Reference-level explanation\n\nThe different `Extract*` and `Inject*` traits are exported by the node macro and are included as part of the document node definition to inform the compiler about features extracted and injected by every node. Note that the `ExtractAll` will be ignored in this analysis.\n\n## Context Nullification Analysis\n\nThe compiler determines where to insert context nullification nodes through branch analysis:\n\n1. **Extract Requirement Tracking**: For each branch in the graph, track the extract requirements all the way back to their corresponding inject nodes. Every extracted feature must have a corresponding inject node somewhere upstream, otherwise this is a compile error.\n2. **Branch Convergence Analysis**: When two branches with different extract requirements meet (at a node that takes multiple inputs), one or both branches can have their context nullified to remove features only needed in the other branch.\n3. **Post-Injection Nullification**: After an inject node, the extract needs of downstream nodes are satisfied for that inject type. At this point we can check if all the features that the inject node provides are actually used downstream, and if not, nullify them immediately.\n4. **Injection Scope Optimization**: After every inject node, analyze whether all injected features are actually consumed by downstream nodes. Unused injected features can be nullified right at the injection point.\n\n## Inject* Trait System\n\nThe injection system provides these complementary marker traits to Extract*:\n\n```rs\npub trait InjectFootprint {}\npub trait InjectRealTime {}\npub trait InjectIndex {}\npub trait InjectVarArgs {}\n```\n\n## Context Feature Modification Traits\n\nThe modification system provides marker traits for nodes that transform context features without necessarily depending on them:\n\n```rs\npub trait ModifyFootprint: ExtractFootprint + InjectFootprint {}\npub trait ModifyRealTime: ExtractRealTime + InjectRealTime {}\npub trait ModifyIndex: ExtractIndex + InjectIndex {}\npub trait ModifyVarArgs: ExtractVarArgs + InjectVarArgs {}\n```\n\n### Conditional Context Dependencies\n\nModify* traits represent a special case in context analysis:\n\n```rs\n// Transform node example - modifies footprint but doesn't need it unless downstream requires it\n#[node_macro::node(category(\"Transform\"))]\nfn transform(\n    ctx: impl Ctx + ModifyFootprint,\n    input: Vector,\n    transform: Transform2D,\n) -> Vector {\n    // This node can extract the footprint, modify it, and inject the result\n    // But if no downstream node needs the footprint, this node doesn't need it either\n    let modified_footprint = ctx.footprint().transform(transform);\n    // ... transform logic ...\n}\n```\n\n### Optimization Implications for Modify* Traits\n\n1. **Conditional Requirements**: Modify* nodes only require their features if downstream nodes extract them\n2. **Pass-through Optimization**: If no downstream extraction occurs, the Modify* node can be treated as if it has no context requirements\n3. **Transform Chains**: Multiple Modify* nodes can be chained together, with requirements only propagating if there's a final Extract* consumer\n\nExample optimization:\n\n```\n[Node A] -> [ModifyFootprint] -> [ModifyFootprint] -> [ExtractRealTime]\n              ↑                    ↑                    ↑\n        No footprint needed   No footprint needed   Only real time needed\n        \n[Node A] -> [ModifyFootprint] -> [ModifyFootprint] -> [ExtractFootprint]\n              ↑                    ↑                    ↑\n        Footprint needed     Footprint needed     Footprint needed\n```\n\nThis allows transform chains to be optimized when their modifications aren't actually consumed downstream.\n\nNote that \"downstream\" in this context refers to nodes that are called later in the function call stack building phase, which is inverted compared to the usual data flow direction.\n\nThis can be implemented as a compiler pass similar to the compose node insertion.\n\n### Error Handling\n\n- Compile-time validation: Every Extract* must have corresponding Inject* upstream\n\n# Drawbacks\n\nHaving an extra compiler pass will impact the performance slightly although the impact is expected to be small because we already have a backlink structure and a topological sort of proto nodes which we can repurpose.\n\n# Rationale and alternatives\n\nMoving this fine grained cache invalidation to a compiler pass allows us to implement this with a minimal impact on the graph runtime. Other options would consist of tracking the usage of features at graph runtime inducing overheads.\n\nThis is expected to have the biggest impact on real time applications such as animation or when working with non-footprint aware nodes which would also benefit from this optimization.\n\n# Unresolved questions\n\n- ~~How do we communicate to the context modification nodes which parts of the context should be \"zeroed\"?~~\n- ~~How does this interact with \"smart caching\" (nodes which use e.g. the Footprint to approximate the result through upscaling)?~~\n\n# Future possibilities\n\n- ~~Adding `Inject*` annotation to complement the `Extract*` ones to provide even more fine grained control over caching.~~\n"
  },
  {
    "path": "proc-macros/Cargo.toml",
    "content": "[package]\nname = \"graphite-proc-macros\"\npublish = false\nversion = \"0.0.0\"\nrust-version = \"1.88\"\nauthors = [\"Graphite Authors <contact@graphite.art>\"]\nedition = \"2024\"\nreadme = \"../README.md\"\nhomepage = \"https://graphite.art\"\nrepository = \"https://github.com/GraphiteEditor/Graphite\"\nlicense = \"Apache-2.0\"\n\n[lib]\npath = \"src/lib.rs\"\nproc-macro = true\n\n[features]\ndefault = [\"serde-discriminant\"]\nserde-discriminant = []\n\n[dependencies]\n# Workspace dependencies\nproc-macro2 = { workspace = true }\nsyn = { workspace = true }\nquote = { workspace = true }\n\n[dev-dependencies]\n# Local dependencies\neditor = { path = \"../editor\", package = \"graphite-editor\" }\n\n# Workspace dependencies\nserde = { workspace = true }\n\n[package.metadata.cargo-shear]\nignored = [\"graphite-editor\"]\n"
  },
  {
    "path": "proc-macros/src/as_message.rs",
    "content": "use proc_macro2::{Span, TokenStream};\nuse syn::{Data, DeriveInput};\n\npub fn derive_as_message_impl(input_item: TokenStream) -> syn::Result<TokenStream> {\n\tlet input = syn::parse2::<DeriveInput>(input_item).unwrap();\n\n\tlet data = match input.data {\n\t\tData::Enum(data) => data,\n\t\t_ => return Err(syn::Error::new(Span::call_site(), \"Tried to derive AsMessage for non-enum\")),\n\t};\n\n\tlet input_type = input.ident;\n\n\tlet (globs, names) = data\n\t\t.variants\n\t\t.iter()\n\t\t.map(|var| {\n\t\t\tlet var_name = &var.ident;\n\t\t\tlet var_name_s = var.ident.to_string();\n\t\t\tif var.attrs.iter().any(|a| a.path().is_ident(\"child\")) {\n\t\t\t\t(\n\t\t\t\t\tquote::quote! {\n\t\t\t\t\t\t#input_type::#var_name(child)\n\t\t\t\t\t},\n\t\t\t\t\tquote::quote! {\n\t\t\t\t\t\tformat!(\"{}.{}\", #var_name_s, child.local_name())\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\t(\n\t\t\t\t\tquote::quote! {\n\t\t\t\t\t\t#input_type::#var_name { .. }\n\t\t\t\t\t},\n\t\t\t\t\tquote::quote! {\n\t\t\t\t\t\t#var_name_s.to_string()\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t\t.unzip::<_, _, Vec<_>, Vec<_>>();\n\n\tlet res = quote::quote! {\n\t\timpl AsMessage for #input_type {\n\t\t\tfn local_name(self) -> String {\n\t\t\t\tmatch self {\n\t\t\t\t\t#(\n\t\t\t\t\t\t#globs => #names\n\t\t\t\t\t),*\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n\n\tOk(res)\n}\n"
  },
  {
    "path": "proc-macros/src/combined_message_attrs.rs",
    "content": "use crate::helpers::call_site_ident;\nuse proc_macro2::Ident;\nuse proc_macro2::TokenStream;\nuse quote::ToTokens;\nuse syn::Token;\nuse syn::parse::{Parse, ParseStream};\nuse syn::{ItemEnum, TypePath};\n\nstruct MessageArgs {\n\tpub _top_parent: TypePath,\n\tpub _comma1: Token![,],\n\tpub parent: TypePath,\n\tpub _comma2: Token![,],\n\tpub variant: Ident,\n}\n\nimpl Parse for MessageArgs {\n\tfn parse(input: ParseStream) -> syn::Result<Self> {\n\t\tOk(Self {\n\t\t\t_top_parent: input.parse()?,\n\t\t\t_comma1: input.parse()?,\n\t\t\tparent: input.parse()?,\n\t\t\t_comma2: input.parse()?,\n\t\t\tvariant: input.parse()?,\n\t\t})\n\t}\n}\n\nstruct TopLevelMessageArgs {\n\tpub parent: TypePath,\n\tpub _comma2: Token![,],\n\tpub variant: Ident,\n}\n\nimpl Parse for TopLevelMessageArgs {\n\tfn parse(input: ParseStream) -> syn::Result<Self> {\n\t\tOk(Self {\n\t\t\tparent: input.parse()?,\n\t\t\t_comma2: input.parse()?,\n\t\t\tvariant: input.parse()?,\n\t\t})\n\t}\n}\n\npub fn combined_message_attrs_impl(attr: TokenStream, input_item: TokenStream) -> syn::Result<TokenStream> {\n\tif attr.is_empty() {\n\t\treturn top_level_impl(input_item);\n\t}\n\n\tlet mut input = syn::parse2::<ItemEnum>(input_item)?;\n\n\tlet (parent_is_top, parent, variant) = match syn::parse2::<MessageArgs>(attr.clone()) {\n\t\tOk(x) => (false, x.parent, x.variant),\n\t\tErr(_) => {\n\t\t\tlet x = syn::parse2::<TopLevelMessageArgs>(attr)?;\n\t\t\t(true, x.parent, x.variant)\n\t\t}\n\t};\n\n\tlet parent_discriminant = quote::quote! {\n\t\t<#parent as ToDiscriminant>::Discriminant\n\t};\n\n\tinput.attrs.push(syn::parse_quote! { #[derive(ToDiscriminant, TransitiveChild, HierarchicalTree)] });\n\tinput.attrs.push(syn::parse_quote! { #[parent(#parent, #parent::#variant)] });\n\tif parent_is_top {\n\t\tinput.attrs.push(syn::parse_quote! { #[parent_is_top] });\n\t}\n\tinput\n\t\t.attrs\n\t\t.push(syn::parse_quote! { #[discriminant_attr(derive(Debug, Copy, Clone, PartialEq, Eq, Hash, AsMessage, TransitiveChild))] });\n\tinput\n\t\t.attrs\n\t\t.push(syn::parse_quote! { #[discriminant_attr(parent(#parent_discriminant, #parent_discriminant::#variant))] });\n\tif parent_is_top {\n\t\tinput.attrs.push(syn::parse_quote! { #[discriminant_attr(parent_is_top)] });\n\t}\n\n\tfor var in &mut input.variants {\n\t\tif let Some(attr) = var.attrs.iter_mut().find(|a| a.path().is_ident(\"child\")) {\n\t\t\tlet path = match &mut attr.meta {\n\t\t\t\tsyn::Meta::Path(path) => path,\n\t\t\t\tsyn::Meta::List(list) => &mut list.path,\n\t\t\t\tsyn::Meta::NameValue(named_value) => &mut named_value.path,\n\t\t\t};\n\t\t\tlet last_segment = path.segments.last_mut().unwrap();\n\t\t\tlast_segment.ident = call_site_ident(\"sub_discriminant\");\n\t\t\tvar.attrs.push(syn::parse_quote! {\n\t\t\t\t#[discriminant_attr(child)]\n\t\t\t});\n\t\t}\n\t}\n\n\tOk(input.into_token_stream())\n}\n\nfn top_level_impl(input_item: TokenStream) -> syn::Result<TokenStream> {\n\tlet mut input = syn::parse2::<ItemEnum>(input_item)?;\n\n\tinput.attrs.push(syn::parse_quote! { #[derive(ToDiscriminant, HierarchicalTree)] });\n\tinput.attrs.push(syn::parse_quote! { #[discriminant_attr(derive(Debug, Copy, Clone, PartialEq, Eq, Hash, AsMessage))] });\n\n\tfor var in &mut input.variants {\n\t\tif let Some(attr) = var.attrs.iter_mut().find(|a| a.path().is_ident(\"child\")) {\n\t\t\tlet path = match &mut attr.meta {\n\t\t\t\tsyn::Meta::Path(path) => path,\n\t\t\t\tsyn::Meta::List(list) => &mut list.path,\n\t\t\t\tsyn::Meta::NameValue(named_value) => &mut named_value.path,\n\t\t\t};\n\t\t\tlet last_segment = path.segments.last_mut().unwrap();\n\t\t\tlast_segment.ident = call_site_ident(\"sub_discriminant\");\n\t\t\tvar.attrs.push(syn::parse_quote! {\n\t\t\t\t#[discriminant_attr(child)]\n\t\t\t});\n\t\t}\n\t}\n\n\tlet input_type = &input.ident;\n\tlet discriminant = call_site_ident(format!(\"{input_type}Discriminant\"));\n\n\tOk(quote::quote! {\n\t\t#input\n\n\t\timpl TransitiveChild for #input_type {\n\t\t\ttype TopParent = Self;\n\t\t\ttype Parent = Self;\n\t\t}\n\n\t\timpl TransitiveChild for #discriminant {\n\t\t\ttype TopParent = Self;\n\t\t\ttype Parent = Self;\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "proc-macros/src/discriminant.rs",
    "content": "use crate::helpers::call_site_ident;\nuse proc_macro2::{Ident, Span, TokenStream};\nuse syn::spanned::Spanned;\nuse syn::{Attribute, Data, DeriveInput, Field, Fields, ItemEnum, MetaList};\n\npub fn derive_discriminant_impl(input_item: TokenStream) -> syn::Result<TokenStream> {\n\tlet input = syn::parse2::<DeriveInput>(input_item).unwrap();\n\n\tlet mut data = match input.data {\n\t\tData::Enum(data) => data,\n\t\t_ => return Err(syn::Error::new(Span::call_site(), \"Tried to derive a discriminant for non-enum\")),\n\t};\n\n\tlet mut is_sub_discriminant = vec![];\n\tlet mut attr_errs = vec![];\n\n\tfor var in &mut data.variants {\n\t\tif var.attrs.iter().any(|a| a.path().is_ident(\"sub_discriminant\")) {\n\t\t\tmatch var.fields.len() {\n\t\t\t\t1 => {\n\t\t\t\t\tlet Field { ty, .. } = var.fields.iter_mut().next().unwrap();\n\t\t\t\t\t*ty = syn::parse_quote! {\n\t\t\t\t\t\t<#ty as ToDiscriminant>::Discriminant\n\t\t\t\t\t};\n\t\t\t\t\tis_sub_discriminant.push(true);\n\t\t\t\t}\n\t\t\t\tn => unimplemented!(\"#[sub_discriminant] on variants with {n} fields is not supported (for now)\"),\n\t\t\t}\n\t\t} else {\n\t\t\tvar.fields = Fields::Unit;\n\t\t\tis_sub_discriminant.push(false);\n\t\t}\n\t\tlet mut retain = vec![];\n\t\tfor (i, a) in var.attrs.iter_mut().enumerate() {\n\t\t\tif a.path().is_ident(\"discriminant_attr\") {\n\t\t\t\tmatch a.meta.require_list() {\n\t\t\t\t\tOk(MetaList { tokens, .. }) => {\n\t\t\t\t\t\tlet attr: Attribute = syn::parse_quote! {\n\t\t\t\t\t\t\t#[#tokens]\n\t\t\t\t\t\t};\n\t\t\t\t\t\t*a = attr;\n\t\t\t\t\t\tretain.push(i);\n\t\t\t\t\t}\n\t\t\t\t\tErr(e) => {\n\t\t\t\t\t\tattr_errs.push(syn::Error::new(a.span(), e));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tvar.attrs = var.attrs.iter().enumerate().filter(|(i, _)| retain.contains(i)).map(|(_, x)| x.clone()).collect();\n\t}\n\n\tlet attrs = input\n\t\t.attrs\n\t\t.iter()\n\t\t.cloned()\n\t\t.filter_map(|a| {\n\t\t\tlet a_span = a.span();\n\t\t\ta.path()\n\t\t\t\t.is_ident(\"discriminant_attr\")\n\t\t\t\t.then(|| match a.meta.require_list() {\n\t\t\t\t\tOk(MetaList { tokens, .. }) => {\n\t\t\t\t\t\tlet attr: Attribute = syn::parse_quote! {\n\t\t\t\t\t\t\t#[#tokens]\n\t\t\t\t\t\t};\n\t\t\t\t\t\tSome(attr)\n\t\t\t\t\t}\n\t\t\t\t\tErr(e) => {\n\t\t\t\t\t\tattr_errs.push(syn::Error::new(a_span, e));\n\t\t\t\t\t\tNone\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.and_then(|opt| opt)\n\t\t})\n\t\t.collect::<Vec<Attribute>>();\n\n\tif !attr_errs.is_empty() {\n\t\treturn Err(attr_errs\n\t\t\t.into_iter()\n\t\t\t.reduce(|mut l, r| {\n\t\t\t\tl.combine(r);\n\t\t\t\tl\n\t\t\t})\n\t\t\t.unwrap());\n\t}\n\n\tlet discriminant = ItemEnum {\n\t\tattrs,\n\t\tvis: input.vis,\n\t\tenum_token: data.enum_token,\n\t\tident: call_site_ident(format!(\"{}Discriminant\", input.ident)),\n\t\tgenerics: input.generics,\n\t\tbrace_token: data.brace_token,\n\t\tvariants: data.variants,\n\t};\n\n\tlet input_type = &input.ident;\n\tlet discriminant_type = &discriminant.ident;\n\tlet variant = &discriminant.variants.iter().map(|var| &var.ident).collect::<Vec<&Ident>>();\n\n\tlet (pattern, value) = is_sub_discriminant\n\t\t.into_iter()\n\t\t.map(|b| {\n\t\t\tif b {\n\t\t\t\t(quote::quote! {(x)}, quote::quote! {(x.to_discriminant())})\n\t\t\t} else {\n\t\t\t\t(quote::quote! {{..}}, Default::default())\n\t\t\t}\n\t\t})\n\t\t.unzip::<_, _, Vec<_>, Vec<_>>();\n\t#[cfg(feature = \"serde-discriminant\")]\n\tlet serde = quote::quote! {\n\t\t#[derive(serde::Serialize, serde::Deserialize)]\n\t};\n\n\t#[cfg(not(feature = \"serde-discriminant\"))]\n\tlet serde = quote::quote! {};\n\n\tlet res = quote::quote! {\n\t\t#serde\n\t\t#discriminant\n\n\t\timpl ToDiscriminant for #input_type {\n\t\t\ttype Discriminant = #discriminant_type;\n\n\t\t\tfn to_discriminant(&self) -> #discriminant_type {\n\t\t\t\tmatch self {\n\t\t\t\t\t#(\n\t\t\t\t\t\t#input_type::#variant #pattern => #discriminant_type::#variant #value\n\t\t\t\t\t),*\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\timpl From<&#input_type> for #discriminant_type {\n\t\t\tfn from(x: &#input_type) -> #discriminant_type {\n\t\t\t\tx.to_discriminant()\n\t\t\t}\n\t\t}\n\t};\n\n\tOk(res)\n}\n"
  },
  {
    "path": "proc-macros/src/extract_fields.rs",
    "content": "use crate::helpers::clean_rust_type_syntax;\nuse proc_macro2::{Span, TokenStream};\nuse quote::{ToTokens, format_ident, quote};\nuse syn::{Data, DeriveInput, Fields, Type, parse2};\n\npub fn derive_extract_field_impl(input: TokenStream) -> syn::Result<TokenStream> {\n\tlet input = parse2::<DeriveInput>(input)?;\n\tlet struct_name = &input.ident;\n\tlet generics = &input.generics;\n\tlet (impl_generics, ty_generics, where_clause) = generics.split_for_impl();\n\n\tlet line_number = struct_name.span().start().line;\n\n\tlet fields = match &input.data {\n\t\tData::Struct(data) => match &data.fields {\n\t\t\tFields::Named(fields) => &fields.named,\n\t\t\t_ => return Err(syn::Error::new(Span::call_site(), \"ExtractField only works on structs with named fields\")),\n\t\t},\n\t\t_ => return Err(syn::Error::new(Span::call_site(), \"ExtractField only works on structs\")),\n\t};\n\n\tlet mut field_line = Vec::new();\n\t// Extract field names and types as strings at compile time\n\tlet field_info = fields\n\t\t.iter()\n\t\t.map(|field| {\n\t\t\tlet ident = field.ident.as_ref().unwrap();\n\t\t\tlet name = ident.to_string();\n\t\t\tlet ty = clean_rust_type_syntax(field.ty.to_token_stream().to_string());\n\t\t\tlet line = ident.span().start().line;\n\t\t\tfield_line.push(line);\n\t\t\t(name, ty)\n\t\t})\n\t\t.collect::<Vec<_>>();\n\n\tlet field_str = field_info.into_iter().map(|(name, ty)| (format!(\"{name}: {ty}\")));\n\n\tlet res = quote! {\n\t\timpl #impl_generics ExtractField for #struct_name #ty_generics #where_clause {\n\t\t\tfn field_types() -> Vec<(String, usize)> {\n\t\t\t\tvec![\n\t\t\t\t\t#((String::from(#field_str), #field_line)),*\n\t\t\t\t]\n\t\t\t}\n\n\t\t\tfn print_field_types() {\n\t\t\t\tfor (field, line) in Self::field_types() {\n\t\t\t\t\tprintln!(\"{} at line {}\", field, line);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfn path() -> &'static str {\n\t\t\t\tfile!()\n\t\t\t}\n\n\t\t\tfn line_number() -> usize {\n\t\t\t\t#line_number\n\t\t\t}\n\n\t\t}\n\t};\n\n\tOk(res)\n}\n"
  },
  {
    "path": "proc-macros/src/helper_structs.rs",
    "content": "use proc_macro2::{Ident, TokenStream};\nuse std::collections::HashMap;\nuse syn::parse::{Parse, ParseStream};\nuse syn::punctuated::Punctuated;\nuse syn::token::Paren;\nuse syn::{LitStr, Token, parenthesized};\n\npub struct IdentList {\n\tpub parts: Punctuated<Ident, Token![,]>,\n}\n\nimpl Parse for IdentList {\n\tfn parse(input: ParseStream) -> syn::Result<Self> {\n\t\tlet content;\n\t\tlet _paren_token = parenthesized!(content in input);\n\t\tOk(Self {\n\t\t\tparts: Punctuated::parse_terminated(&content)?,\n\t\t})\n\t}\n}\n\n/// Parses `(\"some text\")`\npub struct AttrInnerSingleString {\n\t_paren_token: Paren,\n\tpub content: LitStr,\n}\n\nimpl Parse for AttrInnerSingleString {\n\tfn parse(input: ParseStream) -> syn::Result<Self> {\n\t\tlet content;\n\t\tlet _paren_token = parenthesized!(content in input);\n\t\tOk(Self {\n\t\t\t_paren_token,\n\t\t\tcontent: content.parse()?,\n\t\t})\n\t}\n}\n\n/// Parses `key=\"value\"`\npub struct KeyEqString {\n\tkey: Ident,\n\t_eq_token: Token![=],\n\tlit: LitStr,\n}\n\nimpl Parse for KeyEqString {\n\tfn parse(input: ParseStream) -> syn::Result<Self> {\n\t\tOk(Self {\n\t\t\tkey: input.parse()?,\n\t\t\t_eq_token: input.parse()?,\n\t\t\tlit: input.parse()?,\n\t\t})\n\t}\n}\n\n/// Parses `(key=\"value\", key=\"value\", …)`\npub struct AttrInnerKeyStringMap {\n\tparts: Punctuated<KeyEqString, Token![,]>,\n}\n\nimpl Parse for AttrInnerKeyStringMap {\n\tfn parse(input: ParseStream) -> syn::Result<Self> {\n\t\tOk(Self {\n\t\t\tparts: Punctuated::parse_terminated(input)?,\n\t\t})\n\t}\n}\n\nimpl AttrInnerKeyStringMap {\n\tpub fn multi_into_iter(iter: impl IntoIterator<Item = Self>) -> impl Iterator<Item = (Ident, Vec<LitStr>)> {\n\t\tuse std::collections::hash_map::Entry;\n\n\t\tlet mut res = Vec::<(Ident, Vec<LitStr>)>::new();\n\t\tlet mut idx = HashMap::<Ident, usize>::new();\n\n\t\tfor part in iter.into_iter().flat_map(|x: Self| x.parts) {\n\t\t\tmatch idx.entry(part.key) {\n\t\t\t\tEntry::Occupied(occ) => {\n\t\t\t\t\tres[*occ.get()].1.push(part.lit);\n\t\t\t\t}\n\t\t\t\tEntry::Vacant(vac) => {\n\t\t\t\t\tlet ident = vac.key().clone();\n\t\t\t\t\tvac.insert(res.len());\n\t\t\t\t\tres.push((ident, vec![part.lit]));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tres.into_iter()\n\t}\n}\n\n/// Parses `(left, right)`\npub struct Pair<F, S> {\n\tpub first: F,\n\tpub sep: Token![,],\n\tpub second: S,\n}\n\nimpl<F, S> Parse for Pair<F, S>\nwhere\n\tF: Parse,\n\tS: Parse,\n{\n\tfn parse(input: ParseStream) -> syn::Result<Self> {\n\t\tOk(Self {\n\t\t\tfirst: input.parse()?,\n\t\t\tsep: input.parse()?,\n\t\t\tsecond: input.parse()?,\n\t\t})\n\t}\n}\n\n/// parses `(...)`\npub struct ParenthesizedTokens {\n\tpub paren: Paren,\n\tpub tokens: TokenStream,\n}\n\nimpl Parse for ParenthesizedTokens {\n\tfn parse(input: ParseStream) -> syn::Result<Self> {\n\t\tlet content;\n\t\tlet paren = parenthesized!(content in input);\n\t\tOk(Self { paren, tokens: content.parse()? })\n\t}\n}\n\n/// parses a comma-delimeted list of `T`s with optional trailing comma\npub struct SimpleCommaDelimeted<T>(pub Vec<T>);\n\nimpl<T: Parse> Parse for SimpleCommaDelimeted<T> {\n\tfn parse(input: ParseStream) -> syn::Result<Self> {\n\t\tlet punctuated = Punctuated::<T, Token![,]>::parse_terminated(input)?;\n\t\tOk(Self(punctuated.into_iter().collect()))\n\t}\n}\n\n#[cfg(test)]\nmod tests {\n\tuse super::*;\n\n\t#[test]\n\tfn attr_inner_single_string() {\n\t\tlet res = syn::parse2::<AttrInnerSingleString>(quote::quote! {\n\t\t\t(\"a string literal\")\n\t\t});\n\t\tassert!(res.is_ok());\n\t\tassert_eq!(res.ok().unwrap().content.value(), \"a string literal\");\n\n\t\tlet res = syn::parse2::<AttrInnerSingleString>(quote::quote! {\n\t\t\twrong, \"stuff\"\n\t\t});\n\t\tassert!(res.is_err());\n\t}\n\n\t#[test]\n\tfn key_eq_string() {\n\t\tlet res = syn::parse2::<KeyEqString>(quote::quote! {\n\t\t\tkey=\"value\"\n\t\t});\n\t\tassert!(res.is_ok());\n\t\tlet res = res.ok().unwrap();\n\t\tassert_eq!(res.key, \"key\");\n\t\tassert_eq!(res.lit.value(), \"value\");\n\n\t\tlet res = syn::parse2::<KeyEqString>(quote::quote! {\n\t\t\twrong, \"stuff\"\n\t\t});\n\t\tassert!(res.is_err());\n\t}\n\n\t#[test]\n\tfn attr_inner_key_string_map() {\n\t\tlet res = syn::parse2::<AttrInnerKeyStringMap>(quote::quote! {\n\t\t\tkey=\"value\", key2=\"value2\"\n\t\t});\n\t\tassert!(res.is_ok());\n\t\tlet res = res.ok().unwrap();\n\t\tfor (item, (k, v)) in res.parts.into_iter().zip(vec![(\"key\", \"value\"), (\"key2\", \"value2\")]) {\n\t\t\tassert_eq!(item.key, k);\n\t\t\tassert_eq!(item.lit.value(), v);\n\t\t}\n\n\t\tlet res = syn::parse2::<AttrInnerKeyStringMap>(quote::quote! {\n\t\t\tkey=\"value\", key2=\"value2\",\n\t\t});\n\t\tassert!(res.is_ok());\n\t\tlet res = res.ok().unwrap();\n\t\tfor (item, (k, v)) in res.parts.into_iter().zip(vec![(\"key\", \"value\"), (\"key2\", \"value2\")]) {\n\t\t\tassert_eq!(item.key, k);\n\t\t\tassert_eq!(item.lit.value(), v);\n\t\t}\n\n\t\tlet res = syn::parse2::<AttrInnerKeyStringMap>(quote::quote! {\n\t\t\twrong, \"stuff\"\n\t\t});\n\t\tassert!(res.is_err());\n\t}\n}\n"
  },
  {
    "path": "proc-macros/src/helpers.rs",
    "content": "use proc_macro2::{Ident, Span};\nuse syn::punctuated::Punctuated;\nuse syn::{Path, PathArguments, PathSegment, Token};\n\n/// Returns `Ok(Vec<T>)` if all items are `Ok(T)`, else returns a combination of every error encountered (not just the first one)\n// Allowing this lint because this is a false positive in this case. The fold can only be changed into a `try_fold` if the closure\n// doesn't have an error case. See for details: https://rust-lang.github.io/rust-clippy/master/index.html#/manual_try_fold.\n#[allow(clippy::manual_try_fold)]\npub fn fold_error_iter<T>(iter: impl Iterator<Item = syn::Result<T>>) -> syn::Result<Vec<T>> {\n\titer.fold(Ok(vec![]), |acc, x| match acc {\n\t\tOk(mut v) => x.map(|x| {\n\t\t\tv.push(x);\n\t\t\tv\n\t\t}),\n\t\tErr(mut e) => match x {\n\t\t\tOk(_) => Err(e),\n\t\t\tErr(e2) => {\n\t\t\t\te.combine(e2);\n\t\t\t\tErr(e)\n\t\t\t}\n\t\t},\n\t})\n}\n\n/// Creates an ident at the call site\npub fn call_site_ident<S: AsRef<str>>(s: S) -> Ident {\n\tIdent::new(s.as_ref(), Span::call_site())\n}\n\n/// Creates the path `left::right` from the identifiers `left` and `right`\npub fn two_segment_path(left_ident: Ident, right_ident: Ident) -> Path {\n\tlet mut segments: Punctuated<PathSegment, Token![::]> = Punctuated::new();\n\tsegments.push(PathSegment {\n\t\tident: left_ident,\n\t\targuments: PathArguments::None,\n\t});\n\tsegments.push(PathSegment {\n\t\tident: right_ident,\n\t\targuments: PathArguments::None,\n\t});\n\n\tPath { leading_colon: None, segments }\n}\n\npub fn clean_rust_type_syntax(input: String) -> String {\n\tlet mut result = String::new();\n\tlet mut chars = input.chars().peekable();\n\n\twhile let Some(c) = chars.next() {\n\t\tmatch c {\n\t\t\t'&' => {\n\t\t\t\tresult.push('&');\n\t\t\t\twhile let Some(' ') = chars.peek() {\n\t\t\t\t\tchars.next();\n\t\t\t\t}\n\t\t\t}\n\t\t\t'<' => {\n\t\t\t\twhile let Some(' ') = result.chars().next_back() {\n\t\t\t\t\tresult.pop();\n\t\t\t\t}\n\t\t\t\tresult.push('<');\n\t\t\t\twhile let Some(' ') = chars.peek() {\n\t\t\t\t\tchars.next();\n\t\t\t\t}\n\t\t\t}\n\t\t\t'>' => {\n\t\t\t\twhile let Some(' ') = result.chars().next_back() {\n\t\t\t\t\tresult.pop();\n\t\t\t\t}\n\t\t\t\tresult.push('>');\n\t\t\t\twhile let Some(' ') = chars.peek() {\n\t\t\t\t\tchars.next();\n\t\t\t\t}\n\t\t\t}\n\t\t\t'-' => {\n\t\t\t\tif let Some('>') = chars.peek() {\n\t\t\t\t\twhile let Some(' ') = result.chars().next_back() {\n\t\t\t\t\t\tresult.pop();\n\t\t\t\t\t}\n\t\t\t\t\tresult.push_str(\" -> \");\n\t\t\t\t\tchars.next();\n\t\t\t\t\twhile let Some(' ') = chars.peek() {\n\t\t\t\t\t\tchars.next();\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tresult.push(c);\n\t\t\t\t}\n\t\t\t}\n\t\t\t':' => {\n\t\t\t\tif let Some(':') = chars.peek() {\n\t\t\t\t\twhile let Some(' ') = result.chars().next_back() {\n\t\t\t\t\t\tresult.pop();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tresult.push(':');\n\t\t\t\tchars.next();\n\t\t\t\tresult.push(':');\n\t\t\t\twhile let Some(' ') = chars.peek() {\n\t\t\t\t\tchars.next();\n\t\t\t\t}\n\t\t\t}\n\t\t\t_ => {\n\t\t\t\tresult.push(c);\n\t\t\t}\n\t\t}\n\t}\n\n\tresult\n}\n\n#[cfg(test)]\nmod tests {\n\tuse super::*;\n\tuse quote::ToTokens;\n\tuse syn::spanned::Spanned;\n\n\t#[test]\n\tfn test_fold_error_iter() {\n\t\tlet res = fold_error_iter(vec![Ok(()), Ok(())].into_iter());\n\t\tassert!(res.is_ok());\n\n\t\tlet _span = quote::quote! { \"\" }.span();\n\t\tlet res = fold_error_iter(vec![Ok(()), Err(syn::Error::new(_span, \"err1\")), Err(syn::Error::new(_span, \"err2\"))].into_iter());\n\t\tassert!(res.is_err());\n\t\tlet err = res.unwrap_err();\n\t\tlet mut check_err = syn::Error::new(_span, \"err1\");\n\t\tcheck_err.combine(syn::Error::new(_span, \"err2\"));\n\t\tassert_eq!(err.to_compile_error().to_string(), check_err.to_compile_error().to_string());\n\t}\n\n\t#[test]\n\tfn test_two_path() {\n\t\tlet _span = quote::quote! { \"\" }.span();\n\t\tassert_eq!(two_segment_path(Ident::new(\"a\", _span), Ident::new(\"b\", _span)).to_token_stream().to_string(), \"a :: b\");\n\t}\n}\n"
  },
  {
    "path": "proc-macros/src/hierarchical_tree.rs",
    "content": "use crate::helpers::clean_rust_type_syntax;\nuse proc_macro2::{Span, TokenStream};\nuse quote::{ToTokens, quote};\nuse syn::{Data, DeriveInput, Fields, Type, parse2};\n\npub fn generate_hierarchical_tree(input: TokenStream) -> syn::Result<TokenStream> {\n\tlet input = parse2::<DeriveInput>(input)?;\n\tlet input_type = &input.ident;\n\n\tlet line_number = input_type.span().start().line;\n\n\tlet data = match &input.data {\n\t\tData::Enum(data) => data,\n\t\t_ => return Err(syn::Error::new(Span::call_site(), \"Tried to derive HierarchicalTree for non-enum\")),\n\t};\n\n\tlet build_message_tree: Result<Vec<_>, syn::Error> = data\n\t\t.variants\n\t\t.iter()\n\t\t.map(|variant| {\n\t\t\tlet variant_type = &variant.ident;\n\n\t\t\tlet has_child = variant\n\t\t\t\t.attrs\n\t\t\t\t.iter()\n\t\t\t\t.any(|attr| attr.path().get_ident().is_some_and(|ident| ident == \"sub_discriminant\" || ident == \"child\"));\n\n\t\t\tmatch &variant.fields {\n\t\t\t\tFields::Unit => Ok(quote! {\n\t\t\t\t\tmessage_tree.add_variant(DebugMessageTree::new(stringify!(#variant_type)));\n\t\t\t\t}),\n\t\t\t\tFields::Unnamed(fields) => {\n\t\t\t\t\tif has_child {\n\t\t\t\t\t\tlet field_type = &fields.unnamed.first().unwrap().ty;\n\t\t\t\t\t\tOk(quote! {\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlet mut variant_tree = DebugMessageTree::new(stringify!(#variant_type));\n\t\t\t\t\t\t\t\tlet field_name = stringify!(#field_type);\n\t\t\t\t\t\t\t\tconst MESSAGE_SUFFIX: &str = \"Message\";\n\t\t\t\t\t\t\t\tif MESSAGE_SUFFIX == &field_name[field_name.len().saturating_sub(MESSAGE_SUFFIX.len())..] {\n\t\t\t\t\t\t\t\t\t// The field is a Message type, recursively build its tree\n\t\t\t\t\t\t\t\t\tlet sub_tree = #field_type::build_message_tree();\n\t\t\t\t\t\t\t\t\tvariant_tree.add_variant(sub_tree);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tvariant_tree.add_fields(vec![format!(\"{field_name}\")]);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tmessage_tree.add_variant(variant_tree);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlet error_msg = match fields.unnamed.len() {\n\t\t\t\t\t\t\t0 => format!(\"Remove the unnecessary `()` from the `{variant_type}` message enum variant.\"),\n\t\t\t\t\t\t\t1 => {\n\t\t\t\t\t\t\t\tlet field_type = &fields.unnamed.first().unwrap().ty;\n\t\t\t\t\t\t\t\tformat!(\n\t\t\t\t\t\t\t\t\t\"The `{variant_type}` message should be defined as a struct-style (not tuple-style) enum variant to maintain consistent formatting across all editor messages.\\n\\\n\t\t\t\t\t\t\t\t\tReplace `{}` with a named field using {{curly braces}} instead of a positional field using (parentheses).\",\n\t\t\t\t\t\t\t\t\tfield_type.to_token_stream()\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t_ => {\n\t\t\t\t\t\t\t\tlet field_types = fields.unnamed.iter().map(|f| f.ty.to_token_stream().to_string()).collect::<Vec<_>>().join(\", \");\n\t\t\t\t\t\t\t\tformat!(\n\t\t\t\t\t\t\t\t\t\"The `{variant_type}` message should be defined as a struct-style (not tuple-style) enum variant to maintain consistent formatting across all editor messages.\\n\\\n\t\t\t\t\t\t\t\t\tReplace `{field_types}` with named fields using {{curly braces}} instead of positional fields using (parentheses).\"\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\t\t\t\t\t\tErr(syn::Error::new(Span::call_site(), error_msg))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tFields::Named(fields) => {\n\t\t\t\t\tlet names = fields.named.iter().map(|f| f.ident.as_ref().unwrap());\n\t\t\t\t\tlet ty = fields.named.iter().map(|f| clean_rust_type_syntax(f.ty.to_token_stream().to_string()));\n\t\t\t\t\tOk(quote! {\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlet mut field_names = Vec::new();\n\t\t\t\t\t\t\t#(field_names.push(format!(\"{}: {}\",stringify!(#names), #ty));)*\n\t\t\t\t\t\t\tlet mut variant_tree = DebugMessageTree::new(stringify!(#variant_type));\n\t\t\t\t\t\t\tvariant_tree.add_fields(field_names);\n\t\t\t\t\t\t\tmessage_tree.add_variant(variant_tree);\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\t.collect();\n\tlet build_message_tree = build_message_tree?;\n\n\tlet res = quote! {\n\t\timpl HierarchicalTree for #input_type {\n\t\t\tfn build_message_tree() -> DebugMessageTree {\n\t\t\t\tlet mut message_tree = DebugMessageTree::new(stringify!(#input_type));\n\t\t\t\t#(#build_message_tree)*\n\n\t\t\t\tlet message_handler_str = #input_type::message_handler_str();\n\t\t\t\tmessage_tree.add_message_handler_field(message_handler_str);\n\n\t\t\t\tlet message_handler_data_str = #input_type::message_handler_data_str();\n\t\t\t\tif message_handler_data_str.fields().len() > 0 {\n\t\t\t\t\tmessage_tree.add_message_handler_data_field(message_handler_data_str);\n\t\t\t\t}\n\n\t\t\t\tmessage_tree.set_path(file!());\n\n\t\t\t\tmessage_tree.set_line_number(#line_number);\n\n\t\t\t\tmessage_tree\n\t\t\t}\n\t\t}\n\t};\n\n\tOk(res)\n}\n"
  },
  {
    "path": "proc-macros/src/hint.rs",
    "content": "use crate::helper_structs::AttrInnerKeyStringMap;\nuse crate::helpers::{fold_error_iter, two_segment_path};\nuse proc_macro2::{Span, TokenStream as TokenStream2};\nuse syn::{Attribute, Data, DeriveInput, LitStr, Variant};\n\nfn parse_hint_helper_attrs(attrs: &[Attribute]) -> syn::Result<(Vec<LitStr>, Vec<LitStr>)> {\n\tfold_error_iter(\n\t\tattrs\n\t\t\t.iter()\n\t\t\t.filter(|a| a.path().get_ident().is_some_and(|i| i == \"hint\"))\n\t\t\t.map(|attr| attr.parse_args::<AttrInnerKeyStringMap>()),\n\t)\n\t.and_then(|v: Vec<AttrInnerKeyStringMap>| {\n\t\tfold_error_iter(AttrInnerKeyStringMap::multi_into_iter(v).map(|(k, mut v)| match v.len() {\n\t\t\t0 => panic!(\"internal error: a key without values was somehow inserted into the hashmap\"),\n\t\t\t1 => {\n\t\t\t\tlet single_val = v.pop().unwrap();\n\t\t\t\tOk((LitStr::new(&k.to_string(), Span::call_site()), single_val))\n\t\t\t}\n\t\t\t_ => {\n\t\t\t\t// the first value is ok, the other ones should error\n\t\t\t\tlet after_first = v.into_iter().skip(1);\n\t\t\t\t// this call to fold_error_iter will always return Err with a combined error\n\t\t\t\tfold_error_iter(after_first.map(|lit| Err(syn::Error::new(lit.span(), format!(\"value for key {k} was already given\"))))).map(|_: Vec<()>| unreachable!())\n\t\t\t}\n\t\t}))\n\t})\n\t.map(|v| v.into_iter().unzip())\n}\n\npub fn derive_hint_impl(input_item: TokenStream2) -> syn::Result<TokenStream2> {\n\tlet input = syn::parse2::<DeriveInput>(input_item)?;\n\n\tlet ident = input.ident;\n\n\tmatch input.data {\n\t\tData::Enum(data) => {\n\t\t\tlet variants = data.variants.iter().map(|var: &Variant| two_segment_path(ident.clone(), var.ident.clone())).collect::<Vec<_>>();\n\n\t\t\tlet hint_result = fold_error_iter(data.variants.into_iter().map(|var: Variant| parse_hint_helper_attrs(&var.attrs)));\n\n\t\t\thint_result.map(|hints: Vec<(Vec<LitStr>, Vec<LitStr>)>| {\n\t\t\t\tlet (keys, values): (Vec<Vec<LitStr>>, Vec<Vec<LitStr>>) = hints.into_iter().unzip();\n\t\t\t\tlet cap: Vec<usize> = keys.iter().map(|v| v.len()).collect();\n\n\t\t\t\tquote::quote! {\n\t\t\t\t\timpl Hint for #ident {\n\t\t\t\t\t\tfn hints(&self) -> ::std::collections::HashMap<String, String> {\n\t\t\t\t\t\t\tmatch self {\n\t\t\t\t\t\t\t\t#(\n\t\t\t\t\t\t\t\t\t#variants { .. } => {\n\t\t\t\t\t\t\t\t\t\tlet mut hm = ::std::collections::HashMap::with_capacity(#cap);\n\t\t\t\t\t\t\t\t\t\t#(\n\t\t\t\t\t\t\t\t\t\t\thm.insert(#keys.to_string(), #values.to_string());\n\t\t\t\t\t\t\t\t\t\t)*\n\t\t\t\t\t\t\t\t\t\thm\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t)*\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t\tData::Struct(_) | Data::Union(_) => {\n\t\t\tlet hint_result = parse_hint_helper_attrs(&input.attrs);\n\n\t\t\thint_result.map(|(keys, values)| {\n\t\t\t\tlet cap = keys.len();\n\n\t\t\t\tquote::quote! {\n\t\t\t\t\timpl Hint for #ident {\n\t\t\t\t\t\tfn hints(&self) -> ::std::collections::HashMap<String, String> {\n\t\t\t\t\t\t\tlet mut hm = ::std::collections::HashMap::with_capacity(#cap);\n\t\t\t\t\t\t\t#(\n\t\t\t\t\t\t\t\thm.insert(#keys.to_string(), #values.to_string());\n\t\t\t\t\t\t\t)*\n\t\t\t\t\t\t\thm\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "proc-macros/src/lib.rs",
    "content": "#![allow(unused)]\n\nmod as_message;\nmod combined_message_attrs;\nmod discriminant;\nmod extract_fields;\nmod helper_structs;\nmod helpers;\nmod hierarchical_tree;\nmod hint;\nmod message_handler_data_attr;\nmod transitive_child;\nmod widget_builder;\n\nuse crate::as_message::derive_as_message_impl;\nuse crate::combined_message_attrs::combined_message_attrs_impl;\nuse crate::discriminant::derive_discriminant_impl;\nuse crate::extract_fields::derive_extract_field_impl;\nuse crate::helper_structs::AttrInnerSingleString;\nuse crate::hierarchical_tree::generate_hierarchical_tree;\nuse crate::hint::derive_hint_impl;\nuse crate::message_handler_data_attr::message_handler_data_attr_impl;\nuse crate::transitive_child::derive_transitive_child_impl;\nuse crate::widget_builder::derive_widget_builder_impl;\nuse proc_macro::TokenStream;\n\n/// Derive the `ToDiscriminant` trait and create a `<Type Name>Discriminant` enum\n///\n/// This derive macro is enum-only.\n///\n/// The discriminant enum is a copy of the input enum with all fields of every variant removed.\n/// The exception to that rule is the `#[child]` attribute.\n///\n/// # Helper attributes\n/// - `#[sub_discriminant]`: only usable on variants with a single field; instead of no fields, the discriminant of the single field will be included in the discriminant,\n///   acting as a sub-discriminant.\n/// - `#[discriminant_attr(…)]`: usable on the enum itself or on any variant; applies `#[…]` in its place on the discriminant.\n///\n/// # Attributes on the Discriminant\n/// All attributes on variants and the type itself are cleared when constructing the discriminant.\n/// If the discriminant is supposed to also have an attribute, you must double it with `#[discriminant_attr(…)]`\n///\n/// # Example\n/// ```\n/// # use graphite_proc_macros::ToDiscriminant;\n/// # use editor::utility_traits::ToDiscriminant;\n/// # use std::ffi::OsString;\n///\n/// #[derive(ToDiscriminant)]\n/// #[discriminant_attr(derive(Debug, Eq, PartialEq))]\n/// pub enum EnumA {\n///     A(u8),\n///     #[sub_discriminant]\n///     B(EnumB)\n/// }\n///\n/// #[derive(ToDiscriminant)]\n/// #[discriminant_attr(derive(Debug, Eq, PartialEq))]\n/// #[discriminant_attr(repr(u8))]\n/// pub enum EnumB {\n///     Foo(u8),\n///     Bar(String),\n///     #[cfg(feature = \"some-feature\")]\n///     #[discriminant_attr(cfg(feature = \"some-feature\"))]\n///     WindowsBar(OsString)\n/// }\n///\n/// let a = EnumA::A(1);\n/// assert_eq!(a.to_discriminant(), EnumADiscriminant::A);\n/// let b = EnumA::B(EnumB::Bar(\"bar\".to_string()));\n/// assert_eq!(b.to_discriminant(), EnumADiscriminant::B(EnumBDiscriminant::Bar));\n/// ```\n#[proc_macro_derive(ToDiscriminant, attributes(sub_discriminant, discriminant_attr))]\npub fn derive_discriminant(input_item: TokenStream) -> TokenStream {\n\tTokenStream::from(derive_discriminant_impl(input_item.into()).unwrap_or_else(|err| err.to_compile_error()))\n}\n\n/// Derive the `TransitiveChild` trait and generate `From` impls to convert into the parent, as well as the top parent type\n///\n/// This macro cannot be invoked on the top parent (which has no parent but itself). Instead, implement `TransitiveChild` manually\n/// like in the example.\n///\n/// # Helper Attributes\n/// - `#[parent(<Type>, <Expr>)]` (**required**): declare the parent type (`<Type>`)\n///   and a function (`<Expr>`, has to evaluate to a single arg function) for converting a value of this type to the parent type\n/// - `#[parent_is_top]`: Denote that the parent type has no further parent type (this is required because otherwise the `From` impls for parent and top parent would overlap)\n///\n/// # Example\n/// ```\n/// # use graphite_proc_macros::TransitiveChild;\n/// # use editor::utility_traits::TransitiveChild;\n///\n/// #[derive(Debug, Eq, PartialEq)]\n/// struct A { u: u8, b: B };\n///\n/// impl A {\n///     pub fn from_b(b: B) -> Self {\n///         Self { u: 7, b }\n///     }\n/// }\n///\n/// impl TransitiveChild for A {\n///     type Parent = Self;\n///     type TopParent = Self;\n/// }\n///\n/// #[derive(TransitiveChild, Debug, Eq, PartialEq)]\n/// #[parent(A, A::from_b)]\n/// #[parent_is_top]\n/// enum B {\n///     Foo,\n///     Bar,\n///     Child(C)\n/// }\n///\n/// #[derive(TransitiveChild, Debug, Eq, PartialEq)]\n/// #[parent(B, B::Child)]\n/// struct C(D);\n///\n/// #[derive(TransitiveChild, Debug, Eq, PartialEq)]\n/// #[parent(C, C)]\n/// struct D;\n///\n/// let d = D;\n/// assert_eq!(A::from(d), A { u: 7, b: B::Child(C(D)) });\n/// ```\n#[proc_macro_derive(TransitiveChild, attributes(parent, parent_is_top))]\npub fn derive_transitive_child(input_item: TokenStream) -> TokenStream {\n\tTokenStream::from(derive_transitive_child_impl(input_item.into()).unwrap_or_else(|err| err.to_compile_error()))\n}\n\n/// Derive the `AsMessage` trait\n///\n/// # Helper Attributes\n/// - `#[child]`: only on tuple variants with a single field; Denote that the message path should continue inside the variant\n///\n/// # Example\n/// See also [`TransitiveChild`]\n/// ```\n/// # use graphite_proc_macros::{TransitiveChild, AsMessage};\n/// # use editor::utility_traits::TransitiveChild;\n/// # use editor::messages::prelude::*;\n///\n/// #[derive(AsMessage)]\n/// pub enum TopMessage {\n///     A(u8),\n///     B(u16),\n///     #[child]\n///     C(MessageC),\n///     #[child]\n///     D(MessageD)\n/// }\n///\n/// impl TransitiveChild for TopMessage {\n///     type Parent = Self;\n///     type TopParent = Self;\n/// }\n///\n/// #[derive(TransitiveChild, AsMessage, Copy, Clone)]\n/// #[parent(TopMessage, TopMessage::C)]\n/// #[parent_is_top]\n/// pub enum MessageC {\n///     X1,\n///     X2\n/// }\n///\n/// #[derive(TransitiveChild, AsMessage, Copy, Clone)]\n/// #[parent(TopMessage, TopMessage::D)]\n/// #[parent_is_top]\n/// pub enum MessageD {\n///     Y1,\n///     #[child]\n///     Y2(MessageE)\n/// }\n///\n/// #[derive(TransitiveChild, AsMessage, Copy, Clone)]\n/// #[parent(MessageD, MessageD::Y2)]\n/// pub enum MessageE {\n///     Alpha,\n///     Beta\n/// }\n///\n/// let c = MessageC::X1;\n/// assert_eq!(c.local_name(), \"X1\");\n/// assert_eq!(c.global_name(), \"C.X1\");\n/// let d = MessageD::Y2(MessageE::Alpha);\n/// assert_eq!(d.local_name(), \"Y2.Alpha\");\n/// assert_eq!(d.global_name(), \"D.Y2.Alpha\");\n/// let e = MessageE::Beta;\n/// assert_eq!(e.local_name(), \"Beta\");\n/// assert_eq!(e.global_name(), \"D.Y2.Beta\");\n/// ```\n#[proc_macro_derive(AsMessage, attributes(child))]\npub fn derive_message(input_item: TokenStream) -> TokenStream {\n\tTokenStream::from(derive_as_message_impl(input_item.into()).unwrap_or_else(|err| err.to_compile_error()))\n}\n\n/// This macro is basically an abbreviation for the usual [ToDiscriminant], [TransitiveChild] and [AsMessage] invocations.\n///\n/// This macro is enum-only.\n///\n/// Also note that all three of those derives have to be in scope.\n///\n/// # Usage\n/// There are three possible argument syntaxes you can use:\n/// 1. no arguments: this is for the top-level message enum. It derives `ToDiscriminant`, `AsMessage` on the discriminant, and implements `TransitiveChild` on both\n///    (the parent and top parent being the respective types themselves).\n///    It also derives the following `std` traits on the discriminant: `Debug, Copy, Clone, PartialEq, Eq, Hash`.\n/// 2. two arguments: this is for message enums whose direct parent is the top level message enum. The syntax is `#[impl_message(<Type>, <Ident>)]`,\n///    where `<Type>` is the parent message type and `<Ident>` is the identifier of the variant used to construct this child.\n///    It derives `ToDiscriminant`, `AsMessage` on the discriminant, and `TransitiveChild` on both (adding `#[parent_is_top]` to both).\n///    It also derives the following `std` traits on the discriminant: `Debug, Copy, Clone, PartialEq, Eq, Hash`.\n/// 3. three arguments: this is for all other message enums that are transitive children of the top level message enum. The syntax is\n///    `#[impl_message(<Type>, <Type>, <Ident>)]`, where the first `<Type>` is the top parent message type, the second `<Type>` is the parent message type\n///    and `<Ident>` is the identifier of the variant used to construct this child.\n///    It derives `ToDiscriminant`, `AsMessage` on the discriminant, and `TransitiveChild` on both.\n///    It also derives the following `std` traits on the discriminant: `Debug, Copy, Clone, PartialEq, Eq, Hash`.\n///    **This third option will likely change in the future**\n#[proc_macro_attribute]\npub fn impl_message(attr: TokenStream, input_item: TokenStream) -> TokenStream {\n\tTokenStream::from(combined_message_attrs_impl(attr.into(), input_item.into()).unwrap_or_else(|err| err.to_compile_error()))\n}\n\n/// Derive the `Hint` trait\n///\n/// # Example\n/// ```\n/// # use graphite_proc_macros::Hint;\n/// # use editor::utility_traits::Hint;\n///\n/// #[derive(Hint)]\n/// pub enum StateMachine {\n///     #[hint(rmb = \"foo\", lmb = \"bar\")]\n///     Ready,\n///     #[hint(alt = \"baz\")]\n///     RMBDown,\n///     // no hint (also ok)\n///     LMBDown\n/// }\n/// ```\n#[proc_macro_derive(Hint, attributes(hint))]\npub fn derive_hint(input_item: TokenStream) -> TokenStream {\n\tTokenStream::from(derive_hint_impl(input_item.into()).unwrap_or_else(|err| err.to_compile_error()))\n}\n\n/// The `edge` proc macro does nothing, it is intended for use with an external tool\n///\n/// # Example\n/// ```ignore\n/// match (example_tool_state, event) {\n///     (ToolState::Ready, Event::PointerDown(mouse_state)) if *mouse_state == MouseState::Left => {\n///         #[edge(\"LMB Down\")]\n///         ToolState::Pending\n///     }\n///     (SelectToolState::Pending, Event::PointerUp(mouse_state)) if *mouse_state == MouseState::Left => {\n///         #[edge(\"LMB Up: Select Object\")]\n///         SelectToolState::Ready\n///     }\n///     (SelectToolState::Pending, Event::PointerMove(x,y)) => {\n///         #[edge(\"Mouse Move\")]\n///         SelectToolState::TransformSelected\n///     }\n///     (SelectToolState::TransformSelected, Event::PointerMove(x,y)) => {\n///         #[edge(\"Mouse Move\")]\n///         SelectToolState::TransformSelected\n///     }\n///     (SelectToolState::TransformSelected, Event::PointerUp(mouse_state)) if *mouse_state == MouseState::Left => {\n///         #[edge(\"LMB Up\")]\n///         SelectToolState::Ready\n///     }\n///     (state, _) => {\n///         // Do nothing\n///         state\n///     }\n/// }\n/// ```\n#[proc_macro_attribute]\npub fn edge(attr: TokenStream, item: TokenStream) -> TokenStream {\n\t// to make sure that only `#[edge(\"string\")]` is allowed\n\tlet _verify = syn::parse_macro_input!(attr as AttrInnerSingleString);\n\n\titem\n}\n\n#[proc_macro_derive(WidgetBuilder, attributes(widget_builder))]\npub fn derive_widget_builder(input_item: TokenStream) -> TokenStream {\n\tTokenStream::from(derive_widget_builder_impl(input_item.into()).unwrap_or_else(|err| err.to_compile_error()))\n}\n\n#[proc_macro_derive(HierarchicalTree)]\npub fn derive_hierarchical_tree(input_item: TokenStream) -> TokenStream {\n\tTokenStream::from(generate_hierarchical_tree(input_item.into()).unwrap_or_else(|err| err.to_compile_error()))\n}\n\n#[proc_macro_derive(ExtractField)]\npub fn derive_extract_field(input_item: TokenStream) -> TokenStream {\n\tTokenStream::from(derive_extract_field_impl(input_item.into()).unwrap_or_else(|err| err.to_compile_error()))\n}\n\n#[proc_macro_attribute]\npub fn message_handler_data(attr: TokenStream, input_item: TokenStream) -> TokenStream {\n\tTokenStream::from(message_handler_data_attr_impl(attr.into(), input_item.into()).unwrap_or_else(|err| err.to_compile_error()))\n}\n\n#[cfg(test)]\nmod tests {\n\tuse super::*;\n\tuse proc_macro2::TokenStream as TokenStream2;\n\n\tfn ts_assert_eq(l: TokenStream2, r: TokenStream2) {\n\t\t// not sure if this is the best way of doing things but if two TokenStreams are equal, their `to_string` is also equal\n\t\t// so there are at least no false negatives\n\t\tassert_eq!(l.to_string(), r.to_string());\n\t}\n\n\t#[test]\n\tfn test_derive_hint() {\n\t\tlet res = derive_hint_impl(quote::quote! {\n\t\t\t#[hint(key1=\"val1\",key2=\"val2\",)]\n\t\t\tstruct S { a: u8, b: String, c: bool }\n\t\t});\n\t\tassert!(res.is_ok());\n\t\tts_assert_eq(\n\t\t\tres.unwrap(),\n\t\t\tquote::quote! {\n\t\t\t\timpl Hint for S {\n\t\t\t\t\tfn hints(&self) -> ::std::collections::HashMap<String, String> {\n\t\t\t\t\t\tlet mut hm = ::std::collections::HashMap::with_capacity(2usize);\n\t\t\t\t\t\thm.insert(\"key1\".to_string(), \"val1\".to_string());\n\t\t\t\t\t\thm.insert(\"key2\".to_string(), \"val2\".to_string());\n\t\t\t\t\t\thm\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t);\n\n\t\tlet res = derive_hint_impl(quote::quote! {\n\t\t\tenum E {\n\t\t\t\t#[hint(key1=\"val1\",key2=\"val2\",)]\n\t\t\t\tS { a: u8, b: String, c: bool },\n\t\t\t\t#[hint(key3=\"val3\")]\n\t\t\t\tX,\n\t\t\t\tY\n\t\t\t}\n\t\t});\n\t\tassert!(res.is_ok());\n\t\tts_assert_eq(\n\t\t\tres.unwrap(),\n\t\t\tquote::quote! {\n\t\t\t\timpl Hint for E {\n\t\t\t\t\tfn hints(&self) -> ::std::collections::HashMap<String, String> {\n\t\t\t\t\t\tmatch self {\n\t\t\t\t\t\t\tE::S { .. } => {\n\t\t\t\t\t\t\t\tlet mut hm = ::std::collections::HashMap::with_capacity(2usize);\n\t\t\t\t\t\t\t\thm.insert(\"key1\".to_string(), \"val1\".to_string());\n\t\t\t\t\t\t\t\thm.insert(\"key2\".to_string(), \"val2\".to_string());\n\t\t\t\t\t\t\t\thm\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tE::X { .. } => {\n\t\t\t\t\t\t\t\tlet mut hm = ::std::collections::HashMap::with_capacity(1usize);\n\t\t\t\t\t\t\t\thm.insert(\"key3\".to_string(), \"val3\".to_string());\n\t\t\t\t\t\t\t\thm\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tE::Y { .. } => {\n\t\t\t\t\t\t\t\tlet mut hm = ::std::collections::HashMap::with_capacity(0usize);\n\t\t\t\t\t\t\t\thm\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t);\n\n\t\tlet res = derive_hint_impl(quote::quote! {\n\t\t\tunion NoHint {}\n\t\t});\n\t\tassert!(res.is_ok());\n\t\tts_assert_eq(\n\t\t\tres.unwrap(),\n\t\t\tquote::quote! {\n\t\t\t\timpl Hint for NoHint {\n\t\t\t\t\tfn hints(&self) -> ::std::collections::HashMap<String, String> {\n\t\t\t\t\t\tlet mut hm = ::std::collections::HashMap::with_capacity(0usize);\n\t\t\t\t\t\thm\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t);\n\n\t\tlet res = derive_hint_impl(quote::quote! {\n\t\t\t#[hint(a=\"1\", a=\"2\")]\n\t\t\tstruct S;\n\t\t});\n\t\tassert!(res.is_err());\n\n\t\tlet res = derive_hint_impl(quote::quote! {\n\t\t\t#[hint(a=\"1\")]\n\t\t\t#[hint(b=\"2\")]\n\t\t\tstruct S;\n\t\t});\n\t\tassert!(res.is_ok());\n\t\tts_assert_eq(\n\t\t\tres.unwrap(),\n\t\t\tquote::quote! {\n\t\t\t\timpl Hint for S {\n\t\t\t\t\tfn hints(&self) -> ::std::collections::HashMap<String, String> {\n\t\t\t\t\t\tlet mut hm = ::std::collections::HashMap::with_capacity(2usize);\n\t\t\t\t\t\thm.insert(\"a\".to_string(), \"1\".to_string());\n\t\t\t\t\t\thm.insert(\"b\".to_string(), \"2\".to_string());\n\t\t\t\t\t\thm\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t)\n\t}\n\n\t// note: edge needs no testing since AttrInnerSingleString has testing and that's all you'd need to test with edge\n}\n"
  },
  {
    "path": "proc-macros/src/message_handler_data_attr.rs",
    "content": "use crate::helpers::{call_site_ident, clean_rust_type_syntax};\nuse proc_macro2::{Span, TokenStream};\nuse quote::{ToTokens, quote};\nuse syn::{ItemImpl, Type, parse2, spanned::Spanned};\n\npub fn message_handler_data_attr_impl(attr: TokenStream, input_item: TokenStream) -> syn::Result<TokenStream> {\n\t// Parse the input as an impl block\n\tlet impl_block = parse2::<ItemImpl>(input_item.clone())?;\n\n\tlet self_ty = &impl_block.self_ty;\n\n\tlet path = match &**self_ty {\n\t\tType::Path(path) => &path.path,\n\t\t_ => return Err(syn::Error::new(Span::call_site(), \"Expected impl implementation\")),\n\t};\n\n\tlet input_type = path.segments.last().map(|s| &s.ident).unwrap();\n\n\tlet handler_line_number = input_type.span().start().line;\n\n\t// Extract the message type from the trait path\n\tlet trait_path = match &impl_block.trait_ {\n\t\tSome((_, path, _)) => path,\n\t\tNone => return Err(syn::Error::new(Span::call_site(), \"Expected trait implementation\")),\n\t};\n\n\t// Get the trait generics (should be MessageHandler<M, C>)\n\tif let Some(segment) = trait_path.segments.last() {\n\t\tif segment.ident != \"MessageHandler\" {\n\t\t\treturn Err(syn::Error::new(segment.ident.span(), \"Expected MessageHandler trait\"));\n\t\t}\n\t\tif let syn::PathArguments::AngleBracketed(args) = &segment.arguments {\n\t\t\tif args.args.len() >= 2 {\n\t\t\t\t// Extract the message type (M) and context struct type (C) from the trait params\n\t\t\t\tlet message_type = &args.args[0];\n\t\t\t\tlet data_type = &args.args[1];\n\n\t\t\t\tlet impl_item = match data_type {\n\t\t\t\t\tsyn::GenericArgument::Type(t) => {\n\t\t\t\t\t\tmatch t {\n\t\t\t\t\t\t\tsyn::Type::Path(type_path) if !type_path.path.segments.is_empty() => {\n\t\t\t\t\t\t\t\t// Get just the base identifier (ToolMessageData) without generics\n\t\t\t\t\t\t\t\tlet type_name = &type_path.path.segments.first().unwrap().ident;\n\n\t\t\t\t\t\t\t\tlet handler_data_line_number = type_name.span().start().line;\n\n\t\t\t\t\t\t\t\tquote! {\n\t\t\t\t\t\t\t\t\t#input_item\n\t\t\t\t\t\t\t\t\timpl #message_type {\n\t\t\t\t\t\t\t\t\t\tpub fn message_handler_data_str() -> MessageData {\n\t\t\t\t\t\t\t\t\t\t\tMessageData::new(format!(\"{}\", stringify!(#type_name)), #type_name::field_types(), #type_name::path(), #type_name::line_number())\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tpub fn message_handler_str() -> MessageData {\n\t\t\t\t\t\t\t\t\t\t\tMessageData::new(format!(\"{}\", stringify!(#input_type)), #input_type::field_types(), #input_type::path(), #input_type::line_number())\n\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tsyn::Type::Tuple(_) => quote! {\n\t\t\t\t\t\t\t\t#input_item\n\t\t\t\t\t\t\t\timpl #message_type {\n\t\t\t\t\t\t\t\t\t\tpub fn message_handler_str() -> MessageData {\n\t\t\t\t\t\t\t\t\t\t\tMessageData::new(format!(\"{}\", stringify!(#input_type)), #input_type::field_types(), #input_type::path(), #input_type::line_number())\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tsyn::Type::Reference(type_reference) => {\n\t\t\t\t\t\t\t\tlet message_type = call_site_ident(format!(\"{input_type}Message\"));\n\t\t\t\t\t\t\t\tlet type_ident = match &*type_reference.elem {\n\t\t\t\t\t\t\t\t\tsyn::Type::Path(type_path) => &type_path.path.segments.first().unwrap().ident,\n\t\t\t\t\t\t\t\t\t_ => return Err(syn::Error::new(type_reference.elem.span(), \"Expected type path\")),\n\t\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\t\tlet type_line_number = type_ident.span().start().line;\n\n\t\t\t\t\t\t\t\tlet tr = clean_rust_type_syntax(type_reference.to_token_stream().to_string());\n\t\t\t\t\t\t\t\tquote! {\n\t\t\t\t\t\t\t\t\t#input_item\n\t\t\t\t\t\t\t\t\timpl #message_type {\n\t\t\t\t\t\t\t\t\t\tpub fn message_handler_data_str() -> MessageData {\n\t\t\t\t\t\t\t\t\t\t\tMessageData::new(format!(\"{}\", #tr), #type_ident::field_types(), #type_ident::path(), #type_ident::line_number())\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\tpub fn message_handler_str() -> MessageData {\n\t\t\t\t\t\t\t\t\t\t\tMessageData::new(format!(\"{}\", stringify!(#input_type)), #input_type::field_types(), #input_type::path(), #input_type::line_number())\n\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t_ => return Err(syn::Error::new(t.span(), \"Unsupported type format\")),\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t_ => quote! {\n\t\t\t\t\t\t#input_item\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t\treturn Ok(impl_item);\n\t\t\t}\n\t\t}\n\t}\n\tOk(input_item)\n}\n"
  },
  {
    "path": "proc-macros/src/transitive_child.rs",
    "content": "use crate::helper_structs::Pair;\nuse proc_macro2::{Span, TokenStream};\nuse syn::{DeriveInput, Expr, Type};\n\npub fn derive_transitive_child_impl(input_item: TokenStream) -> syn::Result<TokenStream> {\n\tlet input = syn::parse2::<DeriveInput>(input_item).unwrap();\n\n\tlet attribute = input\n\t\t.attrs\n\t\t.iter()\n\t\t.find(|a| a.path().is_ident(\"parent\"))\n\t\t.ok_or_else(|| syn::Error::new(Span::call_site(), format!(\"tried to derive TransitiveChild without a #[parent] attribute (on {})\", input.ident)))?;\n\n\tlet parent_is_top = input.attrs.iter().any(|a| a.path().is_ident(\"parent_is_top\"));\n\n\tlet Pair {\n\t\tfirst: parent_type,\n\t\tsecond: to_parent,\n\t\t..\n\t} = attribute.parse_args::<Pair<Type, Expr>>()?;\n\n\tlet top_parent_type: Type = syn::parse_quote! { <#parent_type as TransitiveChild>::TopParent };\n\n\tlet input_type = &input.ident;\n\n\tlet trait_impl = quote::quote! {\n\t\timpl TransitiveChild for #input_type {\n\t\t\ttype Parent = #parent_type;\n\t\t\ttype TopParent = #top_parent_type;\n\t\t}\n\t};\n\n\tlet from_for_parent = quote::quote! {\n\t\timpl From<#input_type> for #parent_type {\n\t\t\tfn from(x: #input_type) -> #parent_type {\n\t\t\t\t(#to_parent)(x)\n\t\t\t}\n\t\t}\n\t};\n\n\tlet from_for_top = quote::quote! {\n\t\timpl From<#input_type> for #top_parent_type {\n\t\t\tfn from(x: #input_type) -> #top_parent_type {\n\t\t\t\t#top_parent_type::from((#to_parent)(x))\n\t\t\t}\n\t\t}\n\t};\n\n\tOk(if parent_is_top {\n\t\tquote::quote! { #trait_impl #from_for_parent }\n\t} else {\n\t\tquote::quote! { #trait_impl #from_for_parent #from_for_top }\n\t})\n}\n"
  },
  {
    "path": "proc-macros/src/widget_builder.rs",
    "content": "use proc_macro2::{Ident, Literal, TokenStream as TokenStream2};\nuse quote::ToTokens;\nuse syn::spanned::Spanned;\nuse syn::{Attribute, Data, DeriveInput, Field, PathArguments, Type};\n\n/// Check if a specified `#[widget_builder target]` attribute can be found in the list\nfn has_attribute(attrs: &[Attribute], target: &str) -> bool {\n\tattrs\n\t\t.iter()\n\t\t.filter(|attr| attr.path().to_token_stream().to_string() == \"widget_builder\")\n\t\t.any(|attr| attr.meta.require_list().is_ok_and(|list| list.tokens.to_string() == target))\n}\n\n/// Make setting strings easier by allowing all types that `impl Into<String>`\n///\n/// Returns the new input type and a conversion to the original.\nfn easier_string_assignment(field: &Field, field_ty: &Type, field_ident: &Ident) -> (TokenStream2, TokenStream2) {\n\tlet has_string_attr = has_attribute(&field.attrs, \"string\");\n\n\tif let Type::Path(type_path) = field_ty\n\t\t&& let Some(last_segment) = type_path.path.segments.last()\n\t{\n\t\t// Check for `Option<String>` or `Option<StringAlias>` with `#[widget_builder(string)]`\n\t\tif last_segment.ident == Ident::new(\"Option\", last_segment.ident.span())\n\t\t\t&& let PathArguments::AngleBracketed(generic_args) = &last_segment.arguments\n\t\t{\n\t\t\tlet inner_is_string = generic_args.args.first().is_some_and(|arg| {\n\t\t\t\tmatches!(arg, syn::GenericArgument::Type(Type::Path(inner_path))\n\t\t\t\tif inner_path.path.segments.last().is_some_and(|seg| seg.ident == Ident::new(\"String\", seg.ident.span())))\n\t\t\t});\n\t\t\tif inner_is_string || has_string_attr {\n\t\t\t\treturn (\n\t\t\t\t\tquote::quote_spanned!(field_ty.span()=> impl Into<String>),\n\t\t\t\t\tquote::quote_spanned!(field_ident.span()=> Some(#field_ident.into())),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Check if this type is a `String`\n\t\t// Based on https://stackoverflow.com/questions/66906261/rust-proc-macro-derive-how-do-i-check-if-a-field-is-of-a-primitive-type-like-b\n\t\tif last_segment.ident == Ident::new(\"String\", last_segment.ident.span()) || has_string_attr {\n\t\t\treturn (\n\t\t\t\tquote::quote_spanned!(field_ty.span()=> impl Into<String>),\n\t\t\t\tquote::quote_spanned!(field_ident.span()=> #field_ident.into()),\n\t\t\t);\n\t\t}\n\t}\n\t(quote::quote_spanned!(field_ty.span()=> #field_ty), quote::quote_spanned!(field_ident.span()=> #field_ident))\n}\n\n/// Extract the identifier of the field (which should always be present)\nfn extract_ident(field: &Field) -> syn::Result<&Ident> {\n\tfield\n\t\t.ident\n\t\t.as_ref()\n\t\t.ok_or_else(|| syn::Error::new_spanned(field, \"Constructing a builder not supported for unnamed fields\"))\n}\n\n/// Find the type passed into the builder and the right hand side of the assignment.\n///\n/// Applies special behavior for easier String and WidgetCallback assignment.\nfn find_type_and_assignment(field: &Field) -> syn::Result<(TokenStream2, TokenStream2)> {\n\tlet field_ty = &field.ty;\n\tlet field_ident = extract_ident(field)?;\n\n\tlet (mut function_input_ty, mut assignment) = easier_string_assignment(field, field_ty, field_ident);\n\n\t// Check if type is `WidgetCallback`\n\tif let Type::Path(type_path) = field_ty\n\t\t&& let Some(last_segment) = type_path.path.segments.last()\n\t\t&& let PathArguments::AngleBracketed(generic_args) = &last_segment.arguments\n\t\t&& let Some(first_generic) = generic_args.args.first()\n\t\t&& last_segment.ident == Ident::new(\"WidgetCallback\", last_segment.ident.span())\n\t{\n\t\t// Assign builder pattern to assign the closure directly\n\t\tfunction_input_ty = quote::quote_spanned!(field_ty.span()=> impl Fn(&#first_generic) -> crate::messages::message::Message + 'static + Send + Sync);\n\t\tassignment = quote::quote_spanned!(field_ident.span()=> crate::messages::layout::utility_types::layout_widget::WidgetCallback::new(#field_ident));\n\t}\n\tOk((function_input_ty, assignment))\n}\n\n// Construct a builder function for a specific field in the struct\nfn construct_builder(field: &Field) -> syn::Result<TokenStream2> {\n\t// Check if this field should be skipped with `#[widget_builder(skip)]`\n\tif has_attribute(&field.attrs, \"skip\") {\n\t\treturn Ok(Default::default());\n\t}\n\tlet field_ident = extract_ident(field)?;\n\n\t// Create a doc comment literal describing the behaviour of the function\n\tlet doc_comment = Literal::string(&format!(\"Set the `{field_ident}` field using a builder pattern.\"));\n\n\tlet (function_input_ty, assignment) = find_type_and_assignment(field)?;\n\n\t// Create builder function\n\tOk(quote::quote_spanned!(field.span()=>\n\t\t#[doc = #doc_comment]\n\t\tpub fn #field_ident(mut self, #field_ident: #function_input_ty) -> Self{\n\t\t\tself.#field_ident = #assignment;\n\t\t\tself\n\t\t}\n\t))\n}\n\npub fn derive_widget_builder_impl(input_item: TokenStream2) -> syn::Result<TokenStream2> {\n\tlet input = syn::parse2::<DeriveInput>(input_item)?;\n\n\tlet struct_name_ident = input.ident;\n\n\t// Extract the struct fields\n\tlet fields = match &input.data {\n\t\tData::Enum(enum_data) => return Err(syn::Error::new_spanned(enum_data.enum_token, \"Derive widget builder is not supported for enums\")),\n\t\tData::Union(union_data) => return Err(syn::Error::new_spanned(union_data.union_token, \"Derive widget builder is not supported for unions\")),\n\t\tData::Struct(struct_data) => &struct_data.fields,\n\t};\n\n\t// Create functions based on each field\n\tlet builder_functions = fields.iter().map(construct_builder).collect::<Result<Vec<_>, _>>()?;\n\n\t// Check if this should not have the `widget_instance()` function due to a `#[widget_builder(not_widget_instance)]` attribute\n\tlet widget_instance_fn = if !has_attribute(&input.attrs, \"not_widget_instance\") {\n\t\t// A doc comment for the widget_instance function\n\t\tlet widget_instance_doc_comment = Literal::string(&format!(\"Wrap {struct_name_ident} as a WidgetInstance.\"));\n\n\t\t// Construct the `widget_instance` function\n\t\tquote::quote! {\n\t\t\t#[doc = #widget_instance_doc_comment]\n\t\t\tpub fn widget_instance(self) -> crate::messages::layout::utility_types::layout_widget::WidgetInstance {\n\t\t\t\tcrate::messages::layout::utility_types::layout_widget::WidgetInstance::new( crate::messages::layout::utility_types::layout_widget::Widget::#struct_name_ident(self))\n\t\t\t}\n\t\t}\n\t} else {\n\t\tquote::quote!()\n\t};\n\n\t// The new function takes any fields tagged with `#[widget_builder(constructor)]` as arguments.\n\tlet new_fn = {\n\t\t// A doc comment for the new function\n\t\tlet new_doc_comment = Literal::string(&format!(\"Create a new {struct_name_ident}, based on default values.\"));\n\n\t\tlet is_constructor = |field: &Field| has_attribute(&field.attrs, \"constructor\");\n\n\t\tlet idents = fields.iter().filter(|field| is_constructor(field)).map(extract_ident).collect::<Result<Vec<_>, _>>()?;\n\t\tlet types_and_assignments = fields.iter().filter(|field| is_constructor(field)).map(find_type_and_assignment).collect::<Result<Vec<_>, _>>()?;\n\t\tlet (types, assignments): (Vec<_>, Vec<_>) = types_and_assignments.into_iter().unzip();\n\n\t\tlet construction = if idents.is_empty() {\n\t\t\tquote::quote!(Default::default())\n\t\t} else {\n\t\t\tlet default = (idents.len() != fields.len()).then_some(quote::quote!(..Default::default())).unwrap_or_default();\n\t\t\tquote::quote! {\n\t\t\t\tSelf {\n\t\t\t\t\t#(#idents: #assignments,)*\n\t\t\t\t\t#default\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\tquote::quote! {\n\t\t\t#[doc = #new_doc_comment]\n\t\t\tpub fn new(#(#idents: #types),*) -> Self {\n\t\t\t\t#construction\n\t\t\t}\n\t\t}\n\t};\n\n\t// Construct the code block\n\tOk(quote::quote! {\n\t\timpl #struct_name_ident {\n\t\t\t#new_fn\n\n\t\t\t#(#builder_functions)*\n\n\t\t\t#widget_instance_fn\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "rustfmt.toml",
    "content": "edition = \"2024\"\nhard_tabs = true\nmax_width = 200\nnewline_style = \"Unix\"\nuse_try_shorthand = true\n\n# Maybe enable these in the future when they're stabilized\n# normalize_comments = true\n# normalize_doc_attributes = true\n# struct_lit_width = 35\n# where_single_line = true\n# match_block_trailing_comma = true # https://github.com/rust-lang/rustfmt/issues/3380\n# control_brace_style = \"ClosingNextLine\" # https://github.com/rust-lang/rustfmt/issues/3377\n# blank_lines_lower_bound = 1 # https://github.com/rust-lang/rustfmt/issues/3382\n"
  },
  {
    "path": "tools/cargo-run/Cargo.toml",
    "content": "[package]\nname = \"cargo-run\"\nedition.workspace = true\nversion.workspace = true\nlicense.workspace = true\nauthors.workspace = true\n\ndefault-run = \"cargo-run\"\n\n[dependencies]\nthiserror = { workspace = true }\n"
  },
  {
    "path": "tools/cargo-run/src/lib.rs",
    "content": "use std::path::PathBuf;\nuse std::process;\n\npub mod requirements;\n\npub enum Action {\n\tRun,\n\tBuild,\n\tExplore(Option<String>),\n}\n\npub enum Target {\n\tWeb,\n\tDesktop,\n\tCli,\n}\n\npub enum Profile {\n\tDefault,\n\tRelease,\n\tDebug,\n}\n\npub struct Task {\n\tpub action: Action,\n\tpub target: Target,\n\tpub profile: Profile,\n\tpub args: Vec<String>,\n}\n\nimpl Task {\n\tpub fn parse(args: &[&str]) -> Option<Self> {\n\t\tlet split = args.iter().position(|a| *a == \"--\").unwrap_or(args.len());\n\t\tlet passthru_args = args[split..].iter().skip(1).map(|s| s.to_string()).collect();\n\t\tlet args = &args[..split];\n\n\t\tlet (action, args) = match args.first() {\n\t\t\tSome(&\"build\") => (Action::Build, &args[1..]),\n\t\t\tSome(&\"run\") => (Action::Run, &args[1..]),\n\t\t\tSome(&\"explore\") => (Action::Explore(args.get(1).map(|s| s.to_string())), &[] as &[&str]),\n\t\t\tSome(&\"help\") => return None,\n\t\t\t_ => (Action::Run, args),\n\t\t};\n\n\t\tlet (target, args) = match args.first() {\n\t\t\tSome(&\"desktop\") => (Target::Desktop, &args[1..]),\n\t\t\tSome(&\"web\") => (Target::Web, &args[1..]),\n\t\t\tSome(&\"cli\") => (Target::Cli, &args[1..]),\n\t\t\t_ => (Target::Web, args),\n\t\t};\n\n\t\tlet profile = match args.first() {\n\t\t\tSome(&\"release\") => Profile::Release,\n\t\t\tSome(&\"debug\") => Profile::Debug,\n\t\t\tNone => Profile::Default,\n\t\t\t_ => return None,\n\t\t};\n\n\t\tSome(Task {\n\t\t\ttarget,\n\t\t\taction,\n\t\t\tprofile,\n\t\t\targs: passthru_args,\n\t\t})\n\t}\n}\n\npub fn run(command: &str) -> Result<(), Error> {\n\trun_from(command, None)\n}\n\npub fn npm_run_in_frontend_dir(args: &str) -> Result<(), Error> {\n\tlet workspace_dir = std::path::PathBuf::from(env!(\"CARGO_WORKSPACE_DIR\"));\n\tlet frontend_dir = workspace_dir.join(\"frontend\");\n\tlet npm = if cfg!(target_os = \"windows\") { \"npm.cmd\" } else { \"npm\" };\n\trun_from(&format!(\"{npm} run {args}\"), Some(&frontend_dir))\n}\n\npub fn open_url(url: &str) -> Result<(), Error> {\n\t#[cfg(target_os = \"windows\")]\n\tlet mut cmd = process::Command::new(\"cmd\");\n\t#[cfg(target_os = \"windows\")]\n\tcmd.args([\"/c\", \"start\", url]);\n\n\t#[cfg(target_os = \"macos\")]\n\tlet mut cmd = process::Command::new(\"open\");\n\t#[cfg(target_os = \"macos\")]\n\tcmd.arg(url);\n\n\t#[cfg(not(any(target_os = \"windows\", target_os = \"macos\")))]\n\tlet mut cmd = process::Command::new(\"xdg-open\");\n\t#[cfg(not(any(target_os = \"windows\", target_os = \"macos\")))]\n\tcmd.arg(url);\n\n\tlet command_str = format!(\"{:?}\", cmd);\n\tlet exit_code = cmd\n\t\t.spawn()\n\t\t.map_err(|e| Error::Io(e, format!(\"Failed to spawn command '{command_str}'\")))?\n\t\t.wait()\n\t\t.map_err(|e| Error::Io(e, format!(\"Failed to wait for command '{command_str}'\")))?;\n\tif !exit_code.success() {\n\t\treturn Err(Error::Command(command_str, exit_code));\n\t}\n\tOk(())\n}\n\nfn run_from(command: &str, dir: Option<&PathBuf>) -> Result<(), Error> {\n\tlet command = command.split_whitespace().collect::<Vec<_>>();\n\tlet mut cmd = process::Command::new(command[0]);\n\tif command.len() > 1 {\n\t\tcmd.args(&command[1..]);\n\t}\n\tif let Some(dir) = dir {\n\t\tcmd.current_dir(dir);\n\t}\n\tlet exit_code = cmd\n\t\t.spawn()\n\t\t.map_err(|e| Error::Io(e, format!(\"Failed to spawn command '{}'\", command.join(\" \"))))?\n\t\t.wait()\n\t\t.map_err(|e| Error::Io(e, format!(\"Failed to wait for command '{}'\", command.join(\" \"))))?;\n\tif !exit_code.success() {\n\t\treturn Err(Error::Command(command.join(\" \"), exit_code));\n\t}\n\tOk(())\n}\n\n#[derive(Debug, thiserror::Error)]\npub enum Error {\n\t#[error(\"{1}: {0}\")]\n\tIo(#[source] std::io::Error, String),\n\n\t#[error(\"Command '{0}' exited with code {1}\")]\n\tCommand(String, process::ExitStatus),\n}\n"
  },
  {
    "path": "tools/cargo-run/src/main.rs",
    "content": "use std::process::ExitCode;\n\nuse cargo_run::*;\n\nfn usage() {\n\tprintln!();\n\tprintln!(\"USAGE:\");\n\tprintln!(\"  cargo run [<command>] [<target>] [<profile>] [-- [args]...]\");\n\tprintln!();\n\tprintln!(\"COMMON USAGE:\");\n\tprintln!(\"  cargo run            Run the web app\");\n\tprintln!(\"  cargo run desktop    Run the desktop app\");\n\tprintln!();\n\tprintln!(\"OPTIONS:\");\n\tprintln!(\"<command>:\");\n\tprintln!(\"  [run]        Run the selected target (default)\");\n\tprintln!(\"  build        Build the selected target\");\n\tprintln!(\"  explore      Open an assortment of tools for exploring the codebase\");\n\tprintln!(\"  help         Show this message\");\n\tprintln!(\"<target>:\");\n\tprintln!(\"  [web]        Web app (default)\");\n\tprintln!(\"  desktop      Desktop app\");\n\tprintln!(\"  cli          Graphene CLI\");\n\tprintln!(\"<profile>:\");\n\tprintln!(\"  [debug]      Optimizations disabled (default for run)\");\n\tprintln!(\"  [release]    Optimizations enabled (default for build)\");\n\tprintln!();\n\tprintln!(\"MORE EXAMPLES:\");\n\tprintln!(\"  cargo run build desktop\");\n\tprintln!(\"  cargo run desktop release\");\n\tprintln!(\"  cargo run cli -- --help\");\n\tprintln!()\n}\n\nfn main() -> ExitCode {\n\tlet args: Vec<String> = std::env::args().collect();\n\tlet args: Vec<&str> = args.iter().skip(1).map(String::as_str).collect();\n\n\tlet task = match Task::parse(&args) {\n\t\tSome(run) => run,\n\t\tNone => {\n\t\t\tusage();\n\t\t\treturn ExitCode::SUCCESS;\n\t\t}\n\t};\n\n\tif let Err(e) = run_task(&task) {\n\t\teprintln!(\"Error: {e}\");\n\t\treturn ExitCode::FAILURE;\n\t}\n\tExitCode::SUCCESS\n}\n\nfn explore_usage() {\n\tprintln!();\n\tprintln!(\"USAGE:\");\n\tprintln!(\"  cargo run explore <tool>\");\n\tprintln!();\n\tprintln!(\"OPTIONS:\");\n\tprintln!(\"<tool>:\");\n\tprintln!(\"  bisect    Binary search through recent commits to find which introduced a bug or feature\");\n\tprintln!(\"  deps      View the crate dependency graph for the workspace\");\n\tprintln!(\"  editor    View an interactive outline of the editor's message system architecture\");\n\tprintln!();\n}\n\nfn run_task(task: &Task) -> Result<(), Error> {\n\tif let Action::Explore(tool) = &task.action {\n\t\tmatch tool.as_deref() {\n\t\t\tSome(\"bisect\") => return open_url(\"https://graphite.art/volunteer/guide/codebase-overview/debugging-tips/#build-bisect-tool\"),\n\t\t\tSome(\"deps\") => return open_url(\"https://graphite.art/volunteer/guide/codebase-overview/#crate-dependency-graph\"),\n\t\t\tSome(\"editor\") => return open_url(\"https://graphite.art/volunteer/guide/codebase-overview/editor-structure/#editor-outline\"),\n\t\t\tNone | Some(\"--help\") => {\n\t\t\t\texplore_usage();\n\t\t\t\treturn Ok(());\n\t\t\t}\n\t\t\tSome(other) => {\n\t\t\t\teprintln!(\"Unknown explore tool: '{other}'\");\n\t\t\t\texplore_usage();\n\t\t\t\treturn Ok(());\n\t\t\t}\n\t\t}\n\t}\n\n\trequirements::check(task)?;\n\n\tmatch (&task.action, &task.target, &task.profile) {\n\t\t(Action::Run, Target::Web, Profile::Debug | Profile::Default) => npm_run_in_frontend_dir(\"start\")?,\n\t\t(Action::Run, Target::Web, Profile::Release) => npm_run_in_frontend_dir(\"production\")?,\n\n\t\t(Action::Build, Target::Web, Profile::Debug) => npm_run_in_frontend_dir(\"build-dev\")?,\n\t\t(Action::Build, Target::Web, Profile::Release | Profile::Default) => npm_run_in_frontend_dir(\"build\")?,\n\n\t\t(action, Target::Desktop, mut profile) => {\n\t\t\tif matches!(profile, Profile::Default) {\n\t\t\t\tprofile = match action {\n\t\t\t\t\tAction::Run => &Profile::Debug,\n\t\t\t\t\tAction::Build => &Profile::Release,\n\t\t\t\t\tAction::Explore(_) => unreachable!(),\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif matches!(profile, Profile::Release) {\n\t\t\t\tnpm_run_in_frontend_dir(\"build-native\")?;\n\t\t\t} else {\n\t\t\t\tnpm_run_in_frontend_dir(\"build-native-dev\")?;\n\t\t\t};\n\n\t\t\trun(\"cargo run -p third-party-licenses --features desktop\")?;\n\n\t\t\tlet cargo_profile = match profile {\n\t\t\t\tProfile::Debug => \"dev\",\n\t\t\t\tProfile::Release => \"release\",\n\t\t\t\tProfile::Default => unreachable!(),\n\t\t\t};\n\t\t\tlet args = if matches!(action, Action::Run) {\n\t\t\t\tformat!(\" -- open {}\", task.args.join(\" \"))\n\t\t\t} else {\n\t\t\t\t\"\".to_string()\n\t\t\t};\n\t\t\trun(&format!(\"cargo run --profile {cargo_profile} -p graphite-desktop-bundle{args}\"))?;\n\t\t}\n\n\t\t(Action::Run, Target::Cli, Profile::Debug | Profile::Default) => run(&format!(\"cargo run -p graphene-cli -- {}\", task.args.join(\" \")))?,\n\t\t(Action::Run, Target::Cli, Profile::Release) => run(&format!(\"cargo run -r -p graphene-cli -- {}\", task.args.join(\" \")))?,\n\n\t\t(Action::Build, Target::Cli, Profile::Debug) => run(\"cargo build -p graphene-cli\")?,\n\t\t(Action::Build, Target::Cli, Profile::Release | Profile::Default) => run(\"cargo build -r -p graphene-cli\")?,\n\n\t\t(Action::Explore(_), _, _) => unreachable!(),\n\t}\n\tOk(())\n}\n"
  },
  {
    "path": "tools/cargo-run/src/requirements.rs",
    "content": "use std::io::IsTerminal;\nuse std::process::Command;\n\nuse crate::*;\n\n#[derive(Default, Clone)]\nstruct Requirement {\n\tcommand: &'static str,\n\targs: &'static [&'static str],\n\tname: &'static str,\n\tversion: Option<&'static str>,\n\tinstall: Option<&'static str>,\n\tskip: Option<&'static dyn Fn(&Task) -> bool>,\n}\n\nfn requirements(task: &Task) -> Vec<Requirement> {\n\t[\n\t\tRequirement {\n\t\t\tcommand: \"rustc\",\n\t\t\targs: &[\"--version\"],\n\t\t\tname: \"Rust\",\n\t\t\t..Default::default()\n\t\t},\n\t\tRequirement {\n\t\t\tcommand: \"cargo-about\",\n\t\t\targs: &[\"--version\"],\n\t\t\tname: \"cargo-about\",\n\t\t\tinstall: Some(\"cargo install cargo-about\"),\n\t\t\tskip: Some(&|task| matches!(task.target, Target::Cli)),\n\t\t\t..Default::default()\n\t\t},\n\t\tRequirement {\n\t\t\tcommand: \"cargo-watch\",\n\t\t\targs: &[\"--version\"],\n\t\t\tname: \"cargo-watch\",\n\t\t\tinstall: Some(\"cargo install cargo-watch\"),\n\t\t\tskip: Some(&|task| {\n\t\t\t\t!matches!(\n\t\t\t\t\ttask,\n\t\t\t\t\tTask {\n\t\t\t\t\t\ttarget: Target::Web,\n\t\t\t\t\t\taction: Action::Run,\n\t\t\t\t\t\t..\n\t\t\t\t\t}\n\t\t\t\t)\n\t\t\t}),\n\t\t\t..Default::default()\n\t\t},\n\t\tRequirement {\n\t\t\tcommand: \"wasm-bindgen\",\n\t\t\targs: &[\"--version\"],\n\t\t\tname: \"wasm-bindgen-cli\",\n\t\t\tversion: Some(\"0.2.100\"),\n\t\t\tinstall: Some(\"cargo install -f wasm-bindgen-cli@0.2.100\"),\n\t\t\tskip: Some(&|task| matches!(task.target, Target::Cli)),\n\t\t},\n\t\tRequirement {\n\t\t\tcommand: \"wasm-pack\",\n\t\t\targs: &[\"--version\"],\n\t\t\tname: \"wasm-pack\",\n\t\t\tinstall: Some(\"cargo install wasm-pack\"),\n\t\t\tskip: Some(&|task| matches!(task.target, Target::Cli)),\n\t\t\t..Default::default()\n\t\t},\n\t\tRequirement {\n\t\t\tcommand: \"node\",\n\t\t\targs: &[\"--version\"],\n\t\t\tname: \"Node.js\",\n\t\t\tskip: Some(&|task| matches!(task.target, Target::Cli)),\n\t\t\t..Default::default()\n\t\t},\n\t\tRequirement {\n\t\t\tcommand: \"cmake\",\n\t\t\targs: &[\"--version\"],\n\t\t\tname: \"CMake\",\n\t\t\tskip: Some(&|task| !matches!(task.target, Target::Desktop) || cfg!(target_os = \"linux\")),\n\t\t\t..Default::default()\n\t\t},\n\t\tRequirement {\n\t\t\tcommand: \"ninja\",\n\t\t\targs: &[\"--version\"],\n\t\t\tname: \"Ninja\",\n\t\t\tskip: Some(&|task| !matches!(task.target, Target::Desktop) || cfg!(target_os = \"linux\")),\n\t\t\t..Default::default()\n\t\t},\n\t]\n\t.iter()\n\t.filter(|d| if let Some(skip) = d.skip { !skip(task) } else { true })\n\t.cloned()\n\t.collect()\n}\n\npub fn check(task: &Task) -> Result<(), Error> {\n\teprintln!();\n\teprintln!(\"Checking Requirements:\");\n\n\tlet mut installable: Vec<Requirement> = Vec::new();\n\tlet mut failures: Vec<String> = Vec::new();\n\n\tfor dep in requirements(task) {\n\t\tmatch Command::new(dep.command).args(dep.args).output() {\n\t\t\tOk(output) if output.status.success() => {\n\t\t\t\tlet version = String::from_utf8_lossy(&output.stdout);\n\t\t\t\tlet version = version.lines().next().unwrap_or_default().trim();\n\n\t\t\t\tif let Some(expected) = dep.version {\n\t\t\t\t\tif version.contains(expected) {\n\t\t\t\t\t\teprintln!(\" ✓ {} ({})\", dep.name, version);\n\t\t\t\t\t} else {\n\t\t\t\t\t\teprintln!(\" ✗ {} (found {}, expected {})\", dep.name, version, expected);\n\t\t\t\t\t\tif dep.install.is_some() {\n\t\t\t\t\t\t\tinstallable.push(dep);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfailures.push(format!(\"{}: version mismatch (found {version}, expected {expected})\", dep.name));\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\teprintln!(\" ✓ {} ({})\", dep.name, version);\n\t\t\t\t}\n\t\t\t}\n\t\t\tOk(output) => {\n\t\t\t\tlet stderr = String::from_utf8_lossy(&output.stderr);\n\t\t\t\teprintln!(\" ✗ {} - command failed: {}\", dep.name, stderr.trim());\n\t\t\t\tif dep.install.is_some() {\n\t\t\t\t\tinstallable.push(dep);\n\t\t\t\t} else {\n\t\t\t\t\tfailures.push(format!(\"{}: not installed or not working\", dep.name));\n\t\t\t\t}\n\t\t\t}\n\t\t\tErr(_) => {\n\t\t\t\teprintln!(\" ✗ {} - not found\", dep.name);\n\t\t\t\tif dep.install.is_some() {\n\t\t\t\t\tinstallable.push(dep);\n\t\t\t\t} else {\n\t\t\t\t\tfailures.push(format!(\"{}: not found in PATH\", dep.name));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\teprintln!();\n\n\tif installable.is_empty() && failures.is_empty() {\n\t\treturn Ok(());\n\t}\n\n\tlet total = installable.len() + failures.len();\n\teprintln!(\"{total} requirement{} not met:\", if total > 1 { \"s\" } else { \"\" });\n\tfor dep in &installable {\n\t\teprintln!(\"  - {}: {}\", dep.name, dep.install.unwrap());\n\t}\n\tfor msg in &failures {\n\t\teprintln!(\"  - {msg}\");\n\t}\n\n\tif !failures.is_empty() {\n\t\teprintln!();\n\t\teprintln!(\"See: https://graphite.art/volunteer/guide/project-setup/\");\n\t}\n\n\t// Don't prompt for automatic installation if we're not interactive session\n\tif !std::io::stdout().is_terminal() || !std::io::stderr().is_terminal() || !std::io::stdin().is_terminal() {\n\t\treturn Ok(());\n\t}\n\n\tif installable.is_empty() {\n\t\treturn Ok(());\n\t}\n\n\teprintln!();\n\teprintln!(\"The following can be installed automatically:\");\n\tfor dep in &installable {\n\t\teprintln!(\"  {}\", dep.install.unwrap());\n\t}\n\teprintln!();\n\teprint!(\"Install them now? [Y/n] \");\n\n\tlet mut input = String::new();\n\tstd::io::stdin().read_line(&mut input).map_err(|e| Error::Io(e, \"Failed to read from stdin\".into()))?;\n\tlet input = input.trim();\n\n\tif input.is_empty() || input.eq_ignore_ascii_case(\"y\") || input.eq_ignore_ascii_case(\"yes\") {\n\t\tfor dep in &installable {\n\t\t\tlet parts: Vec<&str> = dep.install.unwrap().split_whitespace().collect();\n\t\t\teprintln!(\"Running: {}...\", dep.install.unwrap());\n\t\t\tlet status = Command::new(parts[0])\n\t\t\t\t.args(&parts[1..])\n\t\t\t\t.status()\n\t\t\t\t.map_err(|e| Error::Io(e, format!(\"Failed to run '{}'\", dep.install.unwrap())))?;\n\t\t\tif !status.success() {\n\t\t\t\teprintln!(\"Failed to install {}\", dep.name);\n\t\t\t}\n\t\t}\n\t}\n\tOk(())\n}\n"
  },
  {
    "path": "tools/crate-hierarchy-viz/.gitignore",
    "content": "*.dot\n*.png\n*.svg\n"
  },
  {
    "path": "tools/crate-hierarchy-viz/Cargo.toml",
    "content": "[package]\nname = \"crate-hierarchy-viz\"\ndescription = \"Tool to visualize the crate hierarchy in the Graphite workspace\"\nedition.workspace = true\nversion.workspace = true\nlicense.workspace = true\nauthors.workspace = true\n\n[dependencies]\nserde = { workspace = true }\ntoml = \"0.8\"\nanyhow = { workspace = true }\nglob = \"0.3\"\n"
  },
  {
    "path": "tools/crate-hierarchy-viz/src/main.rs",
    "content": "use anyhow::{Context, Result};\nuse serde::Deserialize;\nuse std::collections::{HashMap, HashSet};\nuse std::fs;\nuse std::io::Write;\nuse std::path::PathBuf;\nuse std::process::Command;\n\n#[derive(Debug, Deserialize)]\nstruct WorkspaceToml {\n\tworkspace: WorkspaceConfig,\n}\n\n#[derive(Debug, Deserialize)]\nstruct WorkspaceConfig {\n\tmembers: Vec<String>,\n}\n\n#[derive(Debug, Deserialize)]\nstruct CrateToml {\n\tpackage: PackageConfig,\n\tdependencies: Option<HashMap<String, CrateDependency>>,\n}\n\n#[derive(Debug, Deserialize)]\nstruct PackageConfig {\n\tname: String,\n}\n\n/// Represents a crate-level dependency in Cargo.toml\n/// The Simple variant's String is needed for serde deserialization but never read directly\n#[derive(Debug, Deserialize)]\n#[serde(untagged)]\n#[allow(dead_code)]\nenum CrateDependency {\n\tSimple(String),\n\tDetailed {\n\t\tpath: Option<String>,\n\t\t#[serde(flatten)]\n\t\tother: HashMap<String, toml::Value>,\n\t},\n}\n\nstruct CrateInfo {\n\tname: String,\n\tpath: PathBuf,\n\tdependencies: Vec<String>,\n}\n\n/// Remove transitive dependencies from the crate list.\n/// If A depends on B and C, and B depends on C, then A->C is removed.\nfn remove_transitive_dependencies(crates: &mut [CrateInfo]) {\n\t// Build a map from crate name to its dependencies for quick lookup\n\tlet dep_map: HashMap<String, HashSet<String>> = crates.iter().map(|c| (c.name.clone(), c.dependencies.iter().cloned().collect())).collect();\n\n\t// For each crate, compute which dependencies are reachable through other dependencies\n\tfor crate_info in crates.iter_mut() {\n\t\tlet mut transitive_deps = HashSet::new();\n\n\t\t// For each direct dependency, find all its transitive dependencies\n\t\tfor direct_dep in &crate_info.dependencies {\n\t\t\t// Recursively collect all transitive dependencies of this direct dependency\n\t\t\tlet mut visited = HashSet::new();\n\t\t\tcollect_all_dependencies(direct_dep, &dep_map, &mut visited);\n\t\t\t// Remove the direct dependency itself from the visited set\n\t\t\tvisited.remove(direct_dep);\n\t\t\ttransitive_deps.extend(visited);\n\t\t}\n\n\t\t// Remove dependencies that are transitive\n\t\tcrate_info.dependencies.retain(|dep| !transitive_deps.contains(dep));\n\t}\n}\n\n/// Recursively collect all dependencies of a crate\nfn collect_all_dependencies(crate_name: &str, dep_map: &HashMap<String, HashSet<String>>, visited: &mut HashSet<String>) {\n\tif !visited.insert(crate_name.to_string()) {\n\t\treturn; // Already visited, avoid cycles\n\t}\n\tif let Some(deps) = dep_map.get(crate_name) {\n\t\tfor dep in deps {\n\t\t\tcollect_all_dependencies(dep, dep_map, visited);\n\t\t}\n\t}\n}\n\nfn main() -> Result<()> {\n\tlet output_dir = std::env::args_os()\n\t\t.nth(1)\n\t\t.map(PathBuf::from)\n\t\t.ok_or_else(|| anyhow::anyhow!(\"Usage: crate-hierarchy-viz <output-directory>\"))?;\n\tlet output_path = output_dir.join(\"crate-hierarchy.svg\");\n\n\tlet workspace_root = std::env::current_dir()?;\n\tlet workspace_toml_path = workspace_root.join(\"Cargo.toml\");\n\n\t// Parse workspace Cargo.toml\n\tlet workspace_content = fs::read_to_string(&workspace_toml_path).with_context(|| format!(\"Failed to read {:?}\", workspace_toml_path))?;\n\tlet workspace_toml: WorkspaceToml = toml::from_str(&workspace_content).with_context(|| \"Failed to parse workspace Cargo.toml\")?;\n\n\t// Expand glob patterns in workspace members (e.g., \"node-graph/libraries/*\")\n\tlet mut resolved_members = Vec::new();\n\tlet mut seen_members = HashSet::new();\n\tlet abs_root = workspace_root.canonicalize().unwrap_or_else(|_| workspace_root.clone());\n\tfor member in &workspace_toml.workspace.members {\n\t\tif member.contains('*') {\n\t\t\tlet pattern = abs_root.join(member).to_string_lossy().to_string();\n\t\t\tlet matched: Vec<_> = glob::glob(&pattern)\n\t\t\t\t.with_context(|| format!(\"Failed to expand glob pattern: {member}\"))?\n\t\t\t\t.filter_map(|entry| entry.ok())\n\t\t\t\t.filter_map(|path| path.strip_prefix(&abs_root).ok().map(|p| p.to_string_lossy().to_string()))\n\t\t\t\t.collect();\n\t\t\tif matched.is_empty() {\n\t\t\t\teprintln!(\"Warning: No matches for glob pattern: {member}\");\n\t\t\t}\n\t\t\tfor m in matched {\n\t\t\t\tlet normalized = m.replace('\\\\', \"/\");\n\t\t\t\tif seen_members.insert(normalized.clone()) {\n\t\t\t\t\tresolved_members.push(normalized);\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tlet normalized = member.replace('\\\\', \"/\");\n\t\t\tif seen_members.insert(normalized.clone()) {\n\t\t\t\tresolved_members.push(normalized);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Parse each member crate's Cargo.toml\n\tlet mut parsed_crates = Vec::new();\n\tfor member in &resolved_members {\n\t\tlet crate_path = workspace_root.join(member);\n\t\tlet cargo_toml_path = crate_path.join(\"Cargo.toml\");\n\n\t\tif !cargo_toml_path.exists() {\n\t\t\teprintln!(\"Warning: Cargo.toml not found for member: {}\", member);\n\t\t\tcontinue;\n\t\t}\n\n\t\tlet crate_content = fs::read_to_string(&cargo_toml_path).with_context(|| format!(\"Failed to read {:?}\", cargo_toml_path))?;\n\t\tlet crate_toml: CrateToml = toml::from_str(&crate_content).with_context(|| format!(\"Failed to parse Cargo.toml for {}\", member))?;\n\n\t\tparsed_crates.push((crate_path, crate_toml));\n\t}\n\n\t// Collect all workspace crate names\n\tlet workspace_crate_names: HashSet<String> = parsed_crates.iter().map(|(_, toml)| toml.package.name.clone()).collect();\n\n\t// Build dependency graph, keeping only workspace-internal dependencies\n\tlet mut crates: Vec<CrateInfo> = parsed_crates\n\t\t.into_iter()\n\t\t.map(|(path, crate_toml)| {\n\t\t\tlet dependencies = crate_toml\n\t\t\t\t.dependencies\n\t\t\t\t.unwrap_or_default()\n\t\t\t\t.into_iter()\n\t\t\t\t.filter_map(|(dep_name, dep_config)| {\n\t\t\t\t\t// Resolve the actual package name (handles renamed dependencies)\n\t\t\t\t\tlet actual_name = match &dep_config {\n\t\t\t\t\t\tCrateDependency::Detailed { other, .. } => other.get(\"package\").and_then(|v| v.as_str()).map(|s| s.to_string()).unwrap_or(dep_name),\n\t\t\t\t\t\tCrateDependency::Simple(_) => dep_name,\n\t\t\t\t\t};\n\t\t\t\t\t// Only keep dependencies that are workspace crates\n\t\t\t\t\tworkspace_crate_names.contains(&actual_name).then_some(actual_name)\n\t\t\t\t})\n\t\t\t\t.collect();\n\n\t\t\tCrateInfo {\n\t\t\t\tname: crate_toml.package.name,\n\t\t\t\tpath,\n\t\t\t\tdependencies,\n\t\t\t}\n\t\t})\n\t\t.collect();\n\n\tremove_transitive_dependencies(&mut crates);\n\n\t// Generate DOT format, convert to SVG, and write to output file\n\tlet dot_content = generate_dot(&crates);\n\tlet svg_content = dot_to_svg(&dot_content)?;\n\n\tfs::create_dir_all(&output_dir).with_context(|| format!(\"Failed to create directory {:?}\", output_dir))?;\n\tfs::write(&output_path, &svg_content).with_context(|| format!(\"Failed to write to {:?}\", output_path))?;\n\n\tOk(())\n}\n\n/// Convert a DOT graph string to SVG by shelling out to @viz-js/viz via Node.js\nfn dot_to_svg(dot: &str) -> Result<String> {\n\tlet temp_dir = std::env::temp_dir().join(\"crate-hierarchy-viz\");\n\tfs::create_dir_all(&temp_dir).with_context(|| \"Failed to create temp directory\")?;\n\n\t// Install @viz-js/viz into the temp directory if not already present\n\tlet viz_package = temp_dir.join(\"node_modules\").join(\"@viz-js\").join(\"viz\");\n\tif !viz_package.exists() {\n\t\tlet npm = if cfg!(target_os = \"windows\") { \"npm.cmd\" } else { \"npm\" };\n\t\tlet status = Command::new(npm)\n\t\t\t.args([\"install\", \"--prefix\", &temp_dir.to_string_lossy(), \"@viz-js/viz\"])\n\t\t\t.stdout(std::process::Stdio::null())\n\t\t\t.stderr(std::process::Stdio::piped())\n\t\t\t.status()\n\t\t\t.with_context(|| \"Failed to run `npm install`. Is Node.js installed?\")?;\n\t\tif !status.success() {\n\t\t\tanyhow::bail!(\"Executing `npm install @viz-js/viz` failed\");\n\t\t}\n\t}\n\n\t// Write a small script that reads DOT from stdin and outputs SVG\n\tlet script_path = temp_dir.join(\"convert.mjs\");\n\tfs::write(\n\t\t&script_path,\n\t\tr#\"\n\t\timport { instance } from \"@viz-js/viz\";\n\t\tlet dot = \"\";\n\t\tfor await (const chunk of process.stdin) dot += chunk;\n\t\tconst viz = await instance();\n\t\tprocess.stdout.write(viz.renderString(dot, { format: \"svg\" }));\n\t\t\"#\n\t\t.trim(),\n\t)?;\n\n\tlet mut child = Command::new(\"node\")\n\t\t.arg(&script_path)\n\t\t.stdin(std::process::Stdio::piped())\n\t\t.stdout(std::process::Stdio::piped())\n\t\t.stderr(std::process::Stdio::piped())\n\t\t.spawn()\n\t\t.with_context(|| \"Failed to spawn `node`. Is Node.js installed?\")?;\n\n\t// Write DOT content to stdin then close the pipe\n\tchild\n\t\t.stdin\n\t\t.take()\n\t\t.context(\"Failed to get stdin handle for node process\")?\n\t\t.write_all(dot.as_bytes())\n\t\t.with_context(|| \"Failed to write DOT content to stdin\")?;\n\n\tlet output = child.wait_with_output().with_context(|| \"Failed to wait for `node` process\")?;\n\n\t// Clean up the temp script (node_modules is intentionally kept as a cache)\n\tlet _ = fs::remove_file(&script_path);\n\n\tif !output.status.success() {\n\t\tlet stderr = String::from_utf8_lossy(&output.stderr);\n\t\tanyhow::bail!(\"DOT to SVG conversion failed (exit code {:?}):\\n{}\", output.status.code(), stderr);\n\t}\n\n\tString::from_utf8(output.stdout).with_context(|| \"SVG output was not valid UTF-8\")\n}\n\nfn generate_dot(crates: &[CrateInfo]) -> String {\n\tlet mut out = String::new();\n\tout.push_str(\"digraph CrateHierarchy {\\n\");\n\tout.push_str(\"    rankdir=LR;\\n\");\n\tout.push_str(\"    node [shape=box, style=\\\"rounded,filled\\\", fillcolor=lightblue];\\n\");\n\tout.push_str(\"    edge [color=gray];\\n\\n\");\n\n\t// Define subgraph clusters\n\tlet clusters: &[(&str, &str, &str, Box<dyn Fn(&CrateInfo) -> bool>)] = &[\n\t\t(\n\t\t\t\"cluster_core\",\n\t\t\t\"Core Components\",\n\t\t\t\"lightgray\",\n\t\t\tBox::new(|c| (c.name.starts_with(\"graphite-\") || c.name == \"editor\" || c.name == \"graphene-cli\") && !c.name.contains(\"desktop\")),\n\t\t),\n\t\t(\n\t\t\t\"cluster_nodegraph\",\n\t\t\t\"Node Graph System\",\n\t\t\t\"lightyellow\",\n\t\t\tBox::new(|c| c.name == \"graph-craft\" || c.name == \"interpreted-executor\" || c.name == \"node-macro\" || c.name == \"preprocessor\" || c.name == \"graphene-cli\"),\n\t\t),\n\t\t(\n\t\t\t\"cluster_node_libraries\",\n\t\t\t\"Node Graph Libraries\",\n\t\t\t\"lightcyan\",\n\t\t\tBox::new(|c| c.path.to_string_lossy().replace('\\\\', \"/\").contains(\"node-graph/libraries\")),\n\t\t),\n\t\t(\n\t\t\t\"cluster_nodes\",\n\t\t\t\"Nodes\",\n\t\t\t\"lightblue\",\n\t\t\tBox::new(|c| c.path.to_string_lossy().replace('\\\\', \"/\").contains(\"node-graph/nodes\")),\n\t\t),\n\t\t(\n\t\t\t\"cluster_desktop\",\n\t\t\t\"Desktop\",\n\t\t\t\"lightgreen\",\n\t\t\tBox::new(|c| c.path.to_string_lossy().replace('\\\\', \"/\").contains(\"desktop\")),\n\t\t),\n\t];\n\n\tfor (id, label, color, filter) in clusters {\n\t\tout.push_str(&format!(\"    subgraph {id} {{\\n\"));\n\t\tout.push_str(&format!(\"        label=\\\"{label}\\\";\\n\"));\n\t\tout.push_str(\"        style=filled;\\n\");\n\t\tout.push_str(&format!(\"        fillcolor={color};\\n\"));\n\t\tfor c in crates.iter().filter(|c| filter(c)) {\n\t\t\tout.push_str(&format!(\"        \\\"{}\\\";\\n\", c.name));\n\t\t}\n\t\tout.push_str(\"    }\\n\\n\");\n\t}\n\n\t// Add dependency edges\n\tfor crate_info in crates {\n\t\tfor dep in &crate_info.dependencies {\n\t\t\tif dep == \"dyn-any\" || dep == \"node-macro\" {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tout.push_str(&format!(\"    \\\"{}\\\" -> \\\"{}\\\";\\n\", crate_info.name, dep));\n\t\t}\n\t}\n\n\tout.push_str(\"}\\n\");\n\tout\n}\n"
  },
  {
    "path": "tools/editor-message-tree/Cargo.toml",
    "content": "[package]\nname = \"editor-message-tree\"\ndescription = \"Tool to generate developer documentation for the editor message system structure\"\nedition.workspace = true\nversion.workspace = true\nlicense.workspace = true\nauthors.workspace = true\n\n[dependencies]\n# Local dependencies\neditor = { path = \"../../editor\", package = \"graphite-editor\" }\n"
  },
  {
    "path": "tools/editor-message-tree/src/main.rs",
    "content": "use editor::messages::message::Message;\nuse editor::utility_types::DebugMessageTree;\nuse std::io::Write;\nuse std::path::PathBuf;\n\nconst FRONTEND_MESSAGE_STR: &str = \"FrontendMessage\";\n\nfn main() -> Result<(), Box<dyn std::error::Error>> {\n\tlet output_dir = std::env::args_os().nth(1).map(PathBuf::from).ok_or(\"Usage: editor-message-tree <output-directory>\")?;\n\tstd::fs::create_dir_all(&output_dir)?;\n\n\tlet tree = Message::message_tree();\n\n\t// Write the .txt file (plain text tree outline, served as a static download)\n\tlet static_dir = output_dir.join(\"../static/volunteer/guide/codebase-overview\");\n\tstd::fs::create_dir_all(&static_dir)?;\n\tlet mut txt_file = std::fs::File::create(static_dir.join(\"hierarchical-message-system-tree.txt\"))?;\n\twrite_tree_txt(&tree, &mut txt_file)?;\n\n\t// Write the .html file (structured HTML embedded in the website page)\n\tlet mut html = String::new();\n\twrite_tree_html(&tree, &mut html);\n\tstd::fs::write(output_dir.join(\"hierarchical-message-system-tree.html\"), &html)?;\n\n\tOk(())\n}\n\n// =================\n// PLAIN TEXT OUTPUT\n// =================\n\nfn write_tree_txt(tree: &DebugMessageTree, file: &mut std::fs::File) -> std::io::Result<()> {\n\tif tree.path().is_empty() {\n\t\tfile.write_all(format!(\"{}\\n\", tree.name()).as_bytes())?;\n\t} else {\n\t\tfile.write_all(format!(\"{} `{}#L{}`\\n\", tree.name(), tree.path(), tree.line_number()).as_bytes())?;\n\t}\n\n\tif let Some(variants) = tree.variants() {\n\t\tfor (i, variant) in variants.iter().enumerate() {\n\t\t\tlet is_last = i == variants.len() - 1;\n\t\t\twrite_tree_txt_node(variant, \"\", is_last, file)?;\n\t\t}\n\t}\n\n\tOk(())\n}\n\nfn write_tree_txt_node(tree: &DebugMessageTree, prefix: &str, is_last: bool, file: &mut std::fs::File) -> std::io::Result<()> {\n\tlet (branch, child_prefix) = if tree.message_handler_data_fields().is_some() || tree.message_handler_fields().is_some() {\n\t\t(\"├── \", format!(\"{prefix}│   \"))\n\t} else if is_last {\n\t\t(\"└── \", format!(\"{prefix}    \"))\n\t} else {\n\t\t(\"├── \", format!(\"{prefix}│   \"))\n\t};\n\n\tif tree.path().is_empty() {\n\t\tfile.write_all(format!(\"{}{}{}\\n\", prefix, branch, tree.name()).as_bytes())?;\n\t} else {\n\t\tfile.write_all(format!(\"{}{}{} `{}#L{}`\\n\", prefix, branch, tree.name(), tree.path(), tree.line_number()).as_bytes())?;\n\t}\n\n\tif let Some(variants) = tree.variants() {\n\t\tlet len = variants.len();\n\t\tfor (i, variant) in variants.iter().enumerate() {\n\t\t\tlet is_last_child = i == len - 1;\n\t\t\twrite_tree_txt_node(variant, &child_prefix, is_last_child, file)?;\n\t\t}\n\t}\n\n\tif let Some(fields) = tree.fields() {\n\t\tlet len = fields.len();\n\t\tfor (i, field) in fields.iter().enumerate() {\n\t\t\tlet is_last_field = i == len - 1;\n\t\t\tlet branch = if is_last_field { \"└── \" } else { \"├── \" };\n\t\t\tfile.write_all(format!(\"{child_prefix}{branch}{field}\\n\").as_bytes())?;\n\t\t}\n\t}\n\n\tif let Some(data) = tree.message_handler_fields() {\n\t\tlet len = data.fields().len();\n\t\tlet (branch, child_prefix) = if tree.message_handler_data_fields().is_some() {\n\t\t\t(\"├── \", format!(\"{prefix}│   \"))\n\t\t} else {\n\t\t\t(\"└── \", format!(\"{prefix}    \"))\n\t\t};\n\n\t\tif data.name().is_empty() && tree.name() != FRONTEND_MESSAGE_STR {\n\t\t\tpanic!(\"{}'s MessageHandler is missing #[message_handler_data]\", tree.name());\n\t\t} else if tree.name() != FRONTEND_MESSAGE_STR {\n\t\t\tfile.write_all(format!(\"{}{}{} `{}#L{}`\\n\", prefix, branch, data.name(), data.path(), data.line_number()).as_bytes())?;\n\n\t\t\tfor (i, field) in data.fields().iter().enumerate() {\n\t\t\t\tlet is_last_field = i == len - 1;\n\t\t\t\tlet branch = if is_last_field { \"└── \" } else { \"├── \" };\n\t\t\t\tfile.write_all(format!(\"{}{}{}\\n\", child_prefix, branch, field.0).as_bytes())?;\n\t\t\t}\n\t\t}\n\t}\n\n\tif let Some(data) = tree.message_handler_data_fields() {\n\t\tlet len = data.fields().len();\n\t\tif data.path().is_empty() {\n\t\t\tfile.write_all(format!(\"{}{}{}\\n\", prefix, \"└── \", data.name()).as_bytes())?;\n\t\t} else {\n\t\t\tfile.write_all(format!(\"{}{}{} `{}#L{}`\\n\", prefix, \"└── \", data.name(), data.path(), data.line_number()).as_bytes())?;\n\t\t}\n\t\tfor (i, field) in data.fields().iter().enumerate() {\n\t\t\tlet is_last_field = i == len - 1;\n\t\t\tlet branch = if is_last_field { \"└── \" } else { \"├── \" };\n\t\t\tlet field = &field.0;\n\t\t\tfile.write_all(format!(\"{prefix}    {branch}{field}\\n\").as_bytes())?;\n\t\t}\n\t}\n\n\tOk(())\n}\n\n// ===========\n// HTML OUTPUT\n// ===========\n\nconst GITHUB_BASE: &str = \"https://github.com/GraphiteEditor/Graphite/blob/master/\";\nconst NAMING_SUFFIXES: &[&str] = &[\"Message\", \"MessageHandler\", \"MessageContext\"];\n\nfn escape_html(s: &str) -> String {\n\ts.replace('&', \"&amp;\").replace('<', \"&lt;\").replace('>', \"&gt;\")\n}\n\nfn github_link(path: &str, line: usize) -> String {\n\tlet path = path.replace('\\\\', \"/\");\n\tlet filename = path.rsplit('/').next().unwrap_or(&path);\n\tformat!(r#\"<a href=\"{GITHUB_BASE}{path}#L{line}\" target=\"_blank\">{filename}:{line}</a>\"#)\n}\n\nfn naming_convention_warning(name: &str) -> &'static str {\n\t// Strip generic parameters for the check (e.g. `Foo<Bar>` -> `Foo`)\n\tlet base_name = name.split('<').next().unwrap_or(name);\n\tif NAMING_SUFFIXES.iter().any(|suffix| base_name.ends_with(suffix)) {\n\t\t\"\"\n\t} else {\n\t\tr#\"<span class=\"warn\">(violates naming convention — should end with 'Message', 'MessageHandler', or 'MessageContext')</span>\"#\n\t}\n}\n\nfn write_tree_html(tree: &DebugMessageTree, out: &mut String) {\n\t// Root node\n\tlet link = if !tree.path().is_empty() { github_link(tree.path(), tree.line_number()) } else { String::new() };\n\tlet escaped_name = escape_html(tree.name());\n\n\tout.push_str(\"<ul>\\n\");\n\tout.push_str(&format!(r#\"<li><span class=\"tree-node\"><span class=\"subsystem\">{escaped_name}</span>{link}</span>\"#));\n\n\tif let Some(variants) = tree.variants() {\n\t\tout.push_str(r#\"<div class=\"nested\">\"#);\n\t\twrite_tree_html_children(variants, out);\n\t\tout.push_str(\"</div>\");\n\t}\n\n\tout.push_str(\"</li>\\n</ul>\\n\");\n}\n\nfn write_tree_html_children(variants: &[DebugMessageTree], out: &mut String) {\n\tout.push_str(\"<ul>\\n\");\n\tfor variant in variants {\n\t\twrite_tree_html_node(variant, out);\n\t}\n\tout.push_str(\"</ul>\\n\");\n}\n\nfn write_tree_html_node(tree: &DebugMessageTree, out: &mut String) {\n\tlet has_link = !tree.path().is_empty();\n\tlet link = if has_link { github_link(tree.path(), tree.line_number()) } else { String::new() };\n\tlet escaped_name = escape_html(tree.name());\n\n\tenum HtmlChild<'a> {\n\t\tSubtree(&'a DebugMessageTree),\n\t\tField(String),\n\t\tHandlerFields(String, String, usize, Vec<String>),\n\t\tDataFields(String, String, usize, Vec<String>),\n\t}\n\n\t// Collect all child entries for this node\n\tlet mut children: Vec<HtmlChild> = Vec::new();\n\n\tif let Some(variants) = tree.variants() {\n\t\tfor variant in variants {\n\t\t\tchildren.push(HtmlChild::Subtree(variant));\n\t\t}\n\t}\n\n\tif let Some(fields) = tree.fields() {\n\t\tfor field in fields {\n\t\t\tchildren.push(HtmlChild::Field(field.to_string()));\n\t\t}\n\t}\n\n\tif let Some(data) = tree.message_handler_fields()\n\t\t&& (!data.name().is_empty() || tree.name() == FRONTEND_MESSAGE_STR)\n\t\t&& tree.name() != FRONTEND_MESSAGE_STR\n\t{\n\t\tchildren.push(HtmlChild::HandlerFields(\n\t\t\tdata.name().to_string(),\n\t\t\tdata.path().to_string(),\n\t\t\tdata.line_number(),\n\t\t\tdata.fields().iter().map(|f| f.0.clone()).collect(),\n\t\t));\n\t}\n\n\tif let Some(data) = tree.message_handler_data_fields() {\n\t\tchildren.push(HtmlChild::DataFields(\n\t\t\tdata.name().to_string(),\n\t\t\tdata.path().to_string(),\n\t\t\tdata.line_number(),\n\t\t\tdata.fields().iter().map(|f| f.0.clone()).collect(),\n\t\t));\n\t}\n\n\tlet has_children = !children.is_empty();\n\tlet has_deeper_children = children.iter().any(|child| matches!(child, HtmlChild::Subtree(t) if t.variants().is_some() || t.fields().is_some()));\n\n\t// Determine role\n\tlet role = if has_link {\n\t\t\"subsystem\"\n\t} else if has_deeper_children {\n\t\t\"submessage\"\n\t} else {\n\t\t\"message\"\n\t};\n\n\t// Naming convention warning (only for linked/subsystem nodes)\n\tlet warning = if has_link { naming_convention_warning(tree.name()) } else { \"\" };\n\n\tif has_children {\n\t\tout.push_str(&format!(r#\"<li><span class=\"tree-node\"><span class=\"{role}\">{escaped_name}</span>{link}{warning}</span>\"#));\n\t\tout.push_str(r#\"<div class=\"nested\"><ul>\"#);\n\t\tout.push('\\n');\n\n\t\tfor child in &children {\n\t\t\tmatch child {\n\t\t\t\tHtmlChild::Subtree(subtree) => write_tree_html_node(subtree, out),\n\t\t\t\tHtmlChild::Field(field) => write_field_html(field, out),\n\t\t\t\tHtmlChild::HandlerFields(name, path, line, fields) => write_handler_or_data_html(name, path, *line, fields, out),\n\t\t\t\tHtmlChild::DataFields(name, path, line, fields) => write_handler_or_data_html(name, path, *line, fields, out),\n\t\t\t}\n\t\t}\n\n\t\tout.push_str(\"</ul>\\n</div></li>\\n\");\n\t} else {\n\t\tout.push_str(&format!(r#\"<li><span class=\"tree-leaf {role}\">{escaped_name}</span>{link}{warning}</li>\"#));\n\t\tout.push('\\n');\n\t}\n}\n\nfn write_field_html(field: &str, out: &mut String) {\n\tif let Some((name, ty)) = field.split_once(':') {\n\t\tlet name = escape_html(name.trim());\n\t\tlet ty = escape_html(ty.trim());\n\t\tout.push_str(&format!(r#\"<li><span class=\"tree-leaf field\">{name}</span>: <span>{ty}</span></li>\"#));\n\t} else {\n\t\tlet escaped = escape_html(field);\n\t\tout.push_str(&format!(r#\"<li><span class=\"tree-leaf message\">{escaped}</span></li>\"#));\n\t}\n\tout.push('\\n');\n}\n\nfn write_handler_or_data_html(name: &str, path: &str, line: usize, fields: &[String], out: &mut String) {\n\tlet escaped_name = escape_html(name);\n\tlet link = if !path.is_empty() { github_link(path, line) } else { String::new() };\n\tlet warning = if !path.is_empty() { naming_convention_warning(name) } else { \"\" };\n\n\tif fields.is_empty() {\n\t\tout.push_str(&format!(r#\"<li><span class=\"tree-leaf subsystem\">{escaped_name}</span>{link}{warning}</li>\"#));\n\t} else {\n\t\tout.push_str(&format!(r#\"<li><span class=\"tree-node\"><span class=\"subsystem\">{escaped_name}</span>{link}{warning}</span>\"#));\n\t\tout.push_str(r#\"<div class=\"nested\"><ul>\"#);\n\t\tout.push('\\n');\n\t\tfor field in fields {\n\t\t\twrite_field_html(field, out);\n\t\t}\n\t\tout.push_str(\"</ul>\\n</div></li>\\n\");\n\t}\n}\n"
  },
  {
    "path": "tools/node-docs/Cargo.toml",
    "content": "[package]\nname = \"node-docs\"\ndescription = \"Tool to generate node documentation for the node catalog on the Graphite website\"\nedition.workspace = true\nversion.workspace = true\nlicense.workspace = true\nauthors.workspace = true\n\n[dependencies]\n# Local dependencies\ngraphene-std = { workspace = true }\ninterpreted-executor = { workspace = true }\ngraph-craft = { workspace = true, features = [\"loading\"] }\npreprocessor = { workspace = true }\n\n# Workspace dependencies\nindoc = { workspace = true }\nconvert_case = { workspace = true }\n"
  },
  {
    "path": "tools/node-docs/src/main.rs",
    "content": "mod page_catalog;\nmod page_category;\nmod page_node;\nmod utility;\n\nuse crate::utility::*;\nuse convert_case::{Case, Casing};\nuse std::collections::HashMap;\n\nfn main() -> Result<(), Box<dyn std::error::Error>> {\n\tlet output_path = std::env::args_os()\n\t\t.nth(1)\n\t\t.ok_or(\"Usage: node-docs <output-directory>\")?\n\t\t.into_string()\n\t\t.map_err(|_| \"Output path is not valid UTF-8\")?;\n\n\t// TODO: Also obtain document nodes, not only proto nodes\n\tlet nodes = graphene_std::registry::NODE_METADATA.lock().unwrap();\n\n\t// Group nodes by category\n\tlet mut nodes_by_category: HashMap<_, Vec<_>> = HashMap::new();\n\tfor (id, metadata) in nodes.iter() {\n\t\tnodes_by_category.entry(metadata.category.to_string()).or_default().push((id, metadata));\n\t}\n\n\t// Sort the categories\n\tlet mut categories = nodes_by_category.keys().cloned().collect::<Vec<_>>();\n\tcategories.sort();\n\n\t// Create _index.md for the node catalog page\n\tpage_catalog::write_catalog_index_page(&output_path, &categories);\n\n\t// Create node category pages and individual node pages\n\tfor (index, category) in categories.iter().map(|c| if !OMIT_HIDDEN && c.is_empty() { \"Hidden\" } else { c }).filter(|c| !c.is_empty()).enumerate() {\n\t\t// Get nodes in this category\n\t\tlet mut nodes = nodes_by_category.remove(if !OMIT_HIDDEN && category == \"Hidden\" { \"\" } else { category }).unwrap();\n\t\tnodes.sort_by_key(|(_, metadata)| metadata.display_name.to_string());\n\n\t\t// Create _index.md file for category\n\t\tlet category_path_part = sanitize_path(&category.to_case(Case::Kebab));\n\t\tlet category_path = format!(\"{output_path}/{category_path_part}\");\n\t\tpage_category::write_category_index_page(index, category, &nodes, &category_path);\n\n\t\t// Create individual node pages\n\t\tfor (index, (id, metadata)) in nodes.into_iter().enumerate() {\n\t\t\tpage_node::write_node_page(index, id, metadata, &category_path);\n\t\t}\n\t}\n\n\tOk(())\n}\n"
  },
  {
    "path": "tools/node-docs/src/page_catalog.rs",
    "content": "use crate::utility::*;\nuse convert_case::{Case, Casing};\nuse indoc::formatdoc;\nuse std::io::Write;\n\npub fn write_catalog_index_page(output_path: &str, categories: &[String]) {\n\tif std::path::Path::new(output_path).exists() {\n\t\tstd::fs::remove_dir_all(output_path).expect(\"Failed to remove existing node catalog directory\");\n\t}\n\tstd::fs::create_dir_all(output_path).expect(\"Failed to create node catalog directory\");\n\tlet page_path = format!(\"{output_path}/_index.md\");\n\tlet mut page = std::fs::File::create(&page_path).expect(\"Failed to create index file\");\n\n\twrite_frontmatter(&mut page);\n\twrite_description(&mut page);\n\twrite_categories_table_header(&mut page);\n\twrite_categories_table_rows(&mut page, categories);\n}\n\nfn write_frontmatter(page: &mut std::fs::File) {\n\tlet content = formatdoc!(\n\t\t\"\n\t\t+++\n\t\ttitle = \\\"Node catalog\\\"\n\t\ttemplate = \\\"book.html\\\"\n\t\tpage_template = \\\"book.html\\\"\n\n\t\t[extra]\n\t\torder = 3\n\t\tcss = [\\\"/page/user-manual/node-catalog.css\\\"]\n\t\t+++\n\t\t\"\n\t);\n\tpage.write_all(content.as_bytes()).expect(\"Failed to write to index file\");\n}\n\nfn write_description(page: &mut std::fs::File) {\n\tlet content = formatdoc!(\n\t\t\"\n\n\t\tThe node catalog documents all of the nodes available in Graphite's node graph system, organized by category.\n\n\t\t<p><img src=\\\"https://static.graphite.art/content/learn/node-catalog/node-terminology.avif\\\" onerror=\\\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\\\" alt=\\\"Terminology diagram covering how the node system operates\\\" /></p>\n\t\t\"\n\t);\n\tpage.write_all(content.as_bytes()).expect(\"Failed to write to index file\");\n}\n\nfn write_categories_table_header(page: &mut std::fs::File) {\n\tlet content = formatdoc!(\n\t\t\"\n\t\n\t\t## Node categories\n\n\t\t| Category | Details |\n\t\t|:-|:-|\n\t\t\"\n\t);\n\tpage.write_all(content.as_bytes()).expect(\"Failed to write to index file\");\n}\n\nfn write_categories_table_rows(page: &mut std::fs::File, categories: &[String]) {\n\tlet content = categories\n\t\t.iter()\n\t\t.filter_map(|c| if c.is_empty() { if OMIT_HIDDEN { None } else { Some(\"Hidden\") } } else { Some(c) })\n\t\t.map(|category| {\n\t\t\tlet category_path_part = sanitize_path(&category.to_case(Case::Kebab));\n\t\t\tlet details = category_description(category).replace(\"\\n\\n\", \"</p><p>\").replace('\\n', \"<br />\");\n\t\t\tformat!(\"| [{category}](./{category_path_part}) | <p>{details}</p> |\")\n\t\t})\n\t\t.collect::<Vec<_>>()\n\t\t.join(\"\\n\");\n\tpage.write_all(content.as_bytes()).expect(\"Failed to write to index file\");\n}\n"
  },
  {
    "path": "tools/node-docs/src/page_category.rs",
    "content": "use crate::utility::*;\nuse convert_case::{Case, Casing};\nuse graph_craft::concrete;\nuse graph_craft::proto::NodeMetadata;\nuse graphene_std::core_types;\nuse indoc::formatdoc;\nuse std::collections::HashSet;\nuse std::io::Write;\n\npub fn write_category_index_page(index: usize, category: &str, nodes: &[(&core_types::ProtoNodeIdentifier, &NodeMetadata)], category_path: &String) {\n\tstd::fs::create_dir_all(category_path).expect(\"Failed to create category directory\");\n\tlet page_path = format!(\"{category_path}/_index.md\");\n\tlet mut page = std::fs::File::create(&page_path).expect(\"Failed to create index file\");\n\n\twrite_frontmatter(&mut page, category, index + 1);\n\twrite_description(&mut page, category);\n\twrite_nodes_table_header(&mut page);\n\twrite_nodes_table_rows(&mut page, nodes);\n}\n\nfn write_frontmatter(page: &mut std::fs::File, category: &str, order: usize) {\n\tlet content = formatdoc!(\n\t\t\"\n\t\t+++\n\t\ttitle = \\\"{category}\\\"\n\t\ttemplate = \\\"book.html\\\"\n\t\tpage_template = \\\"book.html\\\"\n\t\t\n\t\t[extra]\n\t\torder = {order}\n\t\tcss = [\\\"/page/user-manual/node-category.css\\\"]\n\t\t+++\n\t\t\"\n\t);\n\tpage.write_all(content.as_bytes()).expect(\"Failed to write to index file\");\n}\n\nfn write_description(page: &mut std::fs::File, category: &str) {\n\tlet category_description = category_description(category);\n\tlet content = formatdoc!(\n\t\t\"\n\n\t\t{category_description}\n\t\t\"\n\t);\n\tpage.write_all(content.as_bytes()).expect(\"Failed to write to index file\");\n}\n\nfn write_nodes_table_header(page: &mut std::fs::File) {\n\tlet content = formatdoc!(\n\t\t\"\n\n\t\t## Nodes\n\n\t\t| Node | Details | Possible Types |\n\t\t|:-|:-|:-|\n\t\t\"\n\t);\n\tpage.write_all(content.as_bytes()).expect(\"Failed to write to index file\");\n}\n\nfn write_nodes_table_rows(page: &mut std::fs::File, nodes: &[(&core_types::ProtoNodeIdentifier, &NodeMetadata)]) {\n\tlet content = nodes\n\t\t.iter()\n\t\t.filter_map(|&(id, metadata)| {\n\t\t\t// Path to page\n\t\t\tlet name_url_part = sanitize_path(&metadata.display_name.to_case(Case::Kebab));\n\n\t\t\t// Name and description\n\t\t\tlet name = metadata.display_name;\n\t\t\tlet description = node_description(metadata);\n\t\t\tlet details = description.split('\\n').map(|line| format!(\"<p>{}</p>\", line.trim())).collect::<Vec<_>>().join(\"\");\n\n\t\t\t// Possible types\n\t\t\tlet node_registry = core_types::registry::NODE_REGISTRY.lock().unwrap();\n\t\t\tlet implementations = node_registry.get(id)?;\n\t\t\tlet valid_primary_inputs_to_outputs = implementations\n\t\t\t\t.iter()\n\t\t\t\t.map(|(_, node_io)| {\n\t\t\t\t\tlet input = node_io\n\t\t\t\t\t\t.inputs\n\t\t\t\t\t\t.first()\n\t\t\t\t\t\t.map(|ty| ty.nested_type())\n\t\t\t\t\t\t.filter(|&ty| ty != &concrete!(()))\n\t\t\t\t\t\t.map(ToString::to_string)\n\t\t\t\t\t\t.unwrap_or_default();\n\t\t\t\t\tlet output = node_io.return_value.nested_type().to_string();\n\t\t\t\t\tformat!(\"`{input} → {output}`\")\n\t\t\t\t})\n\t\t\t\t.collect::<Vec<_>>();\n\t\t\tlet valid_primary_inputs_to_outputs = {\n\t\t\t\t// Dedupe while preserving order\n\t\t\t\tlet mut found = HashSet::new();\n\t\t\t\tvalid_primary_inputs_to_outputs.into_iter().filter(|s| found.insert(s.clone())).collect::<Vec<_>>()\n\t\t\t};\n\t\t\tlet possible_types = valid_primary_inputs_to_outputs.join(\"<br />\");\n\n\t\t\t// Add table row\n\t\t\tSome(format!(\"| [{name}]({name_url_part}) | {details} | {possible_types} |\"))\n\t\t})\n\t\t.collect::<Vec<_>>()\n\t\t.join(\"\\n\");\n\tpage.write_all(content.as_bytes()).expect(\"Failed to write to index file\");\n}\n"
  },
  {
    "path": "tools/node-docs/src/page_node.rs",
    "content": "use crate::utility::*;\nuse convert_case::{Case, Casing};\nuse graph_craft::concrete;\nuse graph_craft::document::value;\nuse graph_craft::proto::{NodeMetadata, RegistryValueSource};\nuse graphene_std::{ContextDependencies, core_types};\nuse indoc::formatdoc;\nuse std::collections::HashSet;\nuse std::io::Write;\n\npub fn write_node_page(index: usize, id: &core_types::ProtoNodeIdentifier, metadata: &NodeMetadata, category_path: &String) {\n\tlet node_registry = core_types::registry::NODE_REGISTRY.lock().unwrap();\n\tlet Some(implementations) = node_registry.get(id) else { return };\n\n\t// Path to page\n\tlet name_url_part = sanitize_path(&metadata.display_name.to_case(Case::Kebab));\n\tlet page_path = format!(\"{category_path}/{name_url_part}.md\");\n\tlet mut page = std::fs::File::create(&page_path).expect(\"Failed to create node page file\");\n\n\t// Context features\n\tlet context_features = &metadata.context_features;\n\tlet context_dependencies: ContextDependencies = context_features.as_slice().into();\n\n\t// Input types\n\tlet mut valid_input_types = vec![Vec::new(); metadata.fields.len()];\n\tfor (_, node_io) in implementations.iter() {\n\t\tfor (i, ty) in node_io.inputs.iter().enumerate() {\n\t\t\tvalid_input_types[i].push(ty.nested_type().clone());\n\t\t}\n\t}\n\tfor item in valid_input_types.iter_mut() {\n\t\t// Dedupe while preserving order\n\t\tlet mut found = HashSet::new();\n\t\t*item = item.clone().into_iter().filter(|s| found.insert(s.clone())).collect::<Vec<_>>()\n\t}\n\n\t// Primary output types\n\tlet valid_primary_outputs = implementations.iter().map(|(_, node_io)| node_io.return_value.nested_type().clone()).collect::<Vec<_>>();\n\n\t// Write sections to the file\n\twrite_frontmatter(&mut page, metadata, index + 1);\n\twrite_description(&mut page, metadata);\n\twrite_interface_header(&mut page);\n\twrite_context(&mut page, context_dependencies);\n\twrite_inputs(&mut page, &valid_input_types, metadata);\n\twrite_outputs(&mut page, &valid_primary_outputs);\n}\n\nfn write_frontmatter(page: &mut std::fs::File, metadata: &NodeMetadata, order: usize) {\n\tlet name = metadata.display_name;\n\n\tlet content = formatdoc!(\n\t\t\"\n\t\t+++\n\t\ttitle = \\\"{name}\\\"\n\n\t\t[extra]\n\t\torder = {order}\n\t\tcss = [\\\"/page/user-manual/node.css\\\"]\n\t\t+++\n\t\t\"\n\t);\n\tpage.write_all(content.as_bytes()).expect(\"Failed to write to node page file\");\n}\n\nfn write_description(page: &mut std::fs::File, metadata: &NodeMetadata) {\n\tlet description = node_description(metadata);\n\n\tlet content = formatdoc!(\n\t\t\"\n\n\t\t{description}\n\t\t\"\n\t);\n\tpage.write_all(content.as_bytes()).expect(\"Failed to write to node page file\");\n}\n\nfn write_interface_header(page: &mut std::fs::File) {\n\tlet content = formatdoc!(\n\t\t\"\n\n\t\t## Interface\n\t\t\"\n\t);\n\tpage.write_all(content.as_bytes()).expect(\"Failed to write to node page file\");\n}\n\nfn write_context(page: &mut std::fs::File, context_dependencies: ContextDependencies) {\n\tlet extract = context_dependencies.extract;\n\tlet inject = context_dependencies.inject;\n\tif !extract.is_empty() || !inject.is_empty() {\n\t\tlet mut context_features = \"| | |\\n|:-|:-|\".to_string();\n\t\tif !extract.is_empty() {\n\t\t\tlet names = extract.iter().map(|ty| format!(\"`{}`\", ty.name())).collect::<Vec<_>>().join(\"<br />\");\n\t\t\tcontext_features.push_str(&format!(\"\\n| **Reads** | {names} |\"));\n\t\t}\n\t\tif !inject.is_empty() {\n\t\t\tlet names = inject.iter().map(|ty| format!(\"`{}`\", ty.name())).collect::<Vec<_>>().join(\"<br />\");\n\t\t\tcontext_features.push_str(&format!(\"\\n| **Sets** | {names} |\"));\n\t\t}\n\n\t\tlet content = formatdoc!(\n\t\t\t\"\n\n\t\t\t### Context\n\n\t\t\t{context_features}\n\t\t\t\"\n\t\t);\n\t\tpage.write_all(content.as_bytes()).expect(\"Failed to write to node page file\");\n\t};\n}\n\nfn write_inputs(page: &mut std::fs::File, valid_input_types: &[Vec<core_types::Type>], metadata: &NodeMetadata) {\n\tlet rows = metadata\n\t\t.fields\n\t\t.iter()\n\t\t.enumerate()\n\t\t.filter(|&(index, field)| !field.hidden || index == 0)\n\t\t.map(|(index, field)| {\n\t\t\t// Parameter\n\t\t\tlet parameter = field.name;\n\n\t\t\t// Possible types\n\t\t\tlet possible_types_list = valid_input_types.get(index).cloned().unwrap_or_default();\n\t\t\tif index == 0 && possible_types_list.as_slice() == [concrete!(())] {\n\t\t\t\treturn \"| - | *No Primary Input* | - |\".to_string();\n\t\t\t}\n\t\t\tlet mut possible_types = possible_types_list.iter().map(|ty| format!(\"`{ty}`\")).collect::<Vec<_>>();\n\t\t\tpossible_types.sort();\n\t\t\tpossible_types.dedup();\n\t\t\tlet mut possible_types = possible_types.join(\"<br />\");\n\t\t\tif possible_types.is_empty() {\n\t\t\t\tpossible_types = \"*Any Type*\".to_string();\n\t\t\t}\n\n\t\t\t// Details: description\n\t\t\tlet mut details = field\n\t\t\t\t.description\n\t\t\t\t.trim()\n\t\t\t\t.split('\\n')\n\t\t\t\t.filter(|line| !line.is_empty())\n\t\t\t\t.map(|line| format!(\"<p>{}</p>\", line.trim()))\n\t\t\t\t.collect::<Vec<_>>();\n\n\t\t\t// Details: primary input\n\t\t\tif index == 0 {\n\t\t\t\tdetails.push(\"<p>*Primary Input*</p>\".to_string());\n\t\t\t}\n\n\t\t\t// Details: exposed by default\n\t\t\tif field.exposed {\n\t\t\t\tdetails.push(\"<p>*Exposed to the Graph by Default*</p>\".to_string());\n\t\t\t}\n\n\t\t\t// Details: sourced from scope\n\t\t\tif let RegistryValueSource::Scope(scope_name) = &field.value_source {\n\t\t\t\tdetails.push(format!(\"<p>*Sourced From Scope: `{scope_name}`*</p>\"));\n\t\t\t}\n\n\t\t\t// Details: default value\n\t\t\tlet default_value = match field.value_source {\n\t\t\t\tRegistryValueSource::Default(default_value) => Some(default_value.to_string().replace(\" :: \", \"::\")),\n\t\t\t\t_ => field\n\t\t\t\t\t.default_type\n\t\t\t\t\t.as_ref()\n\t\t\t\t\t.or(match possible_types_list.as_slice() {\n\t\t\t\t\t\t[single] => Some(single),\n\t\t\t\t\t\t_ => None,\n\t\t\t\t\t})\n\t\t\t\t\t.and_then(|ty| value::TaggedValue::from_type(ty.nested_type()))\n\t\t\t\t\t.map(|ty| ty.to_debug_string()),\n\t\t\t};\n\t\t\tif index > 0\n\t\t\t\t&& !field.exposed\n\t\t\t\t&& let Some(default_value) = default_value\n\t\t\t{\n\t\t\t\tlet default_value = default_value.trim_end_matches('.').trim_end_matches(\".0\"); // Display whole-number floats as integers\n\n\t\t\t\tlet render_color = |color| format!(r#\"<span style=\"padding-right: 100px; border: 2px solid var(--color-fog); background: {color}\"></span>\"#);\n\t\t\t\tlet default_value = match default_value {\n\t\t\t\t\t\"Color::BLACK\" => render_color(\"black\"),\n\t\t\t\t\t\"GradientStops([(0.0, Color { red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0 }), (1.0, Color { red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0 })])\" => {\n\t\t\t\t\t\trender_color(\"linear-gradient(to right, black, white)\")\n\t\t\t\t\t}\n\t\t\t\t\t_ => format!(\"`{default_value}{}`\", field.unit.unwrap_or_default()),\n\t\t\t\t};\n\n\t\t\t\tdetails.push(format!(\"<p>*Default:*&nbsp;{default_value}</p>\"));\n\t\t\t}\n\n\t\t\t// Construct the table row\n\t\t\tlet details = details.join(\"\");\n\t\t\tformat!(\"| {parameter} | {details} | {possible_types} |\")\n\t\t})\n\t\t.collect::<Vec<_>>()\n\t\t.join(\"\\n\");\n\tif !rows.is_empty() {\n\t\tlet content = formatdoc!(\n\t\t\t\"\n\n\t\t\t### Inputs\n\n\t\t\t| Parameter | Details | Possible Types |\n\t\t\t|:-|:-|:-|\n\t\t\t{rows}\n\t\t\t\"\n\t\t);\n\t\tpage.write_all(content.as_bytes()).expect(\"Failed to write to node page file\");\n\t}\n}\n\nfn write_outputs(page: &mut std::fs::File, valid_primary_outputs: &[core_types::Type]) {\n\t// Product\n\tlet product = \"Result\";\n\n\t// Details: description\n\tlet details = \"The value produced by the node operation.\";\n\tlet mut details = format!(\"<p>{details}</p>\");\n\n\t// Details: primary output\n\tdetails.push_str(\"<p>*Primary Output*</p>\");\n\n\t// Possible types\n\tlet valid_primary_outputs = valid_primary_outputs.iter().map(|ty| format!(\"`{ty}`\")).collect::<Vec<_>>();\n\tlet valid_primary_outputs = {\n\t\t// Dedupe while preserving order\n\t\tlet mut found = HashSet::new();\n\t\tvalid_primary_outputs.into_iter().filter(|s| found.insert(s.clone())).collect::<Vec<_>>()\n\t};\n\tlet valid_primary_outputs = {\n\t\t// Dedupe while preserving order\n\t\tlet mut found = HashSet::new();\n\t\tvalid_primary_outputs.into_iter().filter(|s| found.insert(s.clone())).collect::<Vec<_>>()\n\t};\n\tlet valid_primary_outputs = valid_primary_outputs.join(\"<br />\");\n\n\tlet content = formatdoc!(\n\t\t\"\n\n\t\t### Outputs\n\n\t\t| Product | Details | Possible Types |\n\t\t|:-|:-|:-|\n\t\t| {product} | {details} | {valid_primary_outputs} |\n\t\t\"\n\t);\n\tpage.write_all(content.as_bytes()).expect(\"Failed to write to node page file\");\n}\n"
  },
  {
    "path": "tools/node-docs/src/utility.rs",
    "content": "use graph_craft::proto::NodeMetadata;\nuse indoc::indoc;\n\npub const OMIT_HIDDEN: bool = true;\n\npub fn category_description(category: &str) -> &str {\n\tmatch category {\n\t\t\"Animation\" => indoc!(\n\t\t\t\"\n\t\t\tNodes in this category enable the creation of animated, real-time, and interactive motion graphics involving paramters that change over time.\n\n\t\t\tThese nodes require that playback is activated by pressing the play button above the viewport.\n\t\t\t\"\n\t\t),\n\t\t\"Blending\" => \"Nodes in this category control how overlapping graphical content is composited together, considering blend modes, opacity, and clipping.\",\n\t\t\"Color\" => \"Nodes in this category deal with selecting and manipulating colors, gradients, and palettes.\",\n\t\t\"Debug\" => indoc!(\n\t\t\t\"\n\t\t\tNodes in this category are temporarily included for debugging purposes by Graphite's developers. They may have rare potential uses for advanced users, but are not intended for general use and will be removed in future releases.\n\t\t\t\"\n\t\t),\n\t\t\"General\" => \"Nodes in this category deal with general data handling, such as merging and flattening graphical elements.\",\n\t\t\"Context\" => \"Nodes in this category read data from the evaluation context supplied by downstream nodes, such as the current loop iteration index.\",\n\t\t\"Repeat\" => \"Nodes in this category enable the duplication, arrangement, and looped generation of graphical elements.\",\n\t\t\"Math: Arithmetic\" => \"Nodes in this category perform common arithmetic operations on numerical values (and where applicable, `vec2` values).\",\n\t\t\"Math: Logic\" => \"Nodes in this category perform boolean logic operations such as comparisons, conditionals, logic gates, and switching.\",\n\t\t\"Math: Numeric\" => \"Nodes in this category perform discontinuous numeric operations such as rounding, clamping, mapping, and randomization.\",\n\t\t\"Math: Transform\" => \"Nodes in this category perform transformations on graphical elements and calculations involving transformation matrices.\",\n\t\t\"Math: Trig\" => \"Nodes in this category perform trigonometric operations such as sine, cosine, tangent, and their inverses.\",\n\t\t\"Math: Vector\" => \"Nodes in this category perform operations involving `vec2` values (points or arrows in 2D space) such as the dot product, normalization, and distance calculations.\",\n\t\t\"Raster: Adjustment\" => \"Nodes in this category perform per-pixel color adjustments on raster graphics, such as brightness and contrast modifications.\",\n\t\t\"Raster: Channels\" => \"Nodes in this category enable channel-specific manipulation of the RGB and alpha channels of raster graphics.\",\n\t\t\"Raster: Filter\" => \"Nodes in this category apply filtering effects to raster graphics such as blurs and sharpening.\",\n\t\t\"Raster: Pattern\" => \"Nodes in this category generate procedural raster patterns, fractals, textures, and noise.\",\n\t\t\"Raster\" => \"Nodes in this category deal with fundamental raster image operations.\",\n\t\t\"Text\" => \"Nodes in this category support the manipulation, formatting, and rendering of text strings.\",\n\t\t\"Value\" => \"Nodes in this category supply data values of common types such as numbers, colors, booleans, and strings.\",\n\t\t\"Vector: Measure\" => \"Nodes in this category perform measurements and analysis on vector graphics, such as length/area calculations, path traversal, and hit testing.\",\n\t\t\"Vector: Modifier\" => \"Nodes in this category modify the geometry of vector graphics, such as boolean operations, smoothing, and morphing.\",\n\t\t\"Vector: Shape\" => \"Nodes in this category generate parametrically-described primitive vector shapes such as rectangles, grids, stars, and spirals.\",\n\t\t\"Vector: Style\" => \"Nodes in this category apply fill and stroke styles to alter the appearance of vector graphics.\",\n\t\t\"Vector\" => \"Nodes in this category deal with fundamental vector graphics data handling and operations.\",\n\t\t\"Web Request\" => \"Nodes in this category facilitate fetching and handling resources from HTTP endpoints and sending webhook requests to external services.\",\n\t\t_ => {\n\t\t\teprintln!(\"Category '{category}' is missing a description\");\n\n\t\t\t\"*Category description coming soon.*\"\n\t\t}\n\t}.trim()\n}\n\npub fn node_description(metadata: &NodeMetadata) -> &str {\n\tlet mut description = metadata.description.trim();\n\tif description.is_empty() {\n\t\tdescription = \"*Node description coming soon.*\";\n\t}\n\tdescription\n}\n\npub fn sanitize_path(s: &str) -> String {\n\t// Replace disallowed characters with a dash\n\tlet allowed_characters = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~[]@!$&'()*+,;=\";\n\tlet filtered = s.chars().map(|c| if allowed_characters.contains(c) { c } else { '-' }).collect::<String>();\n\n\t// Fix letter-number type names\n\tlet mut filtered = format!(\"-{filtered}-\");\n\tfiltered = filtered.replace(\"-vec-2-\", \"-vec2-\");\n\tfiltered = filtered.replace(\"-f-32-\", \"-f32-\");\n\tfiltered = filtered.replace(\"-f-64-\", \"-f64-\");\n\tfiltered = filtered.replace(\"-u-32-\", \"-u32-\");\n\tfiltered = filtered.replace(\"-u-64-\", \"-u64-\");\n\tfiltered = filtered.replace(\"-i-32-\", \"-i32-\");\n\tfiltered = filtered.replace(\"-i-64-\", \"-i64-\");\n\n\t// Remove consecutive dashes\n\twhile filtered.contains(\"--\") {\n\t\tfiltered = filtered.replace(\"--\", \"-\");\n\t}\n\n\t// Trim leading and trailing dashes\n\tfiltered.trim_matches('-').to_string()\n}\n"
  },
  {
    "path": "tools/third-party-licenses/.gitignore",
    "content": "*.hash\n"
  },
  {
    "path": "tools/third-party-licenses/Cargo.toml",
    "content": "[package]\nname = \"third-party-licenses\"\nedition.workspace = true\nversion.workspace = true\nlicense.workspace = true\nauthors.workspace = true\n\n[features]\ndesktop = [\"dep:cef-dll-sys\", \"dep:scraper\"]\n\n[dependencies]\n# Workspace dependencies\nserde = { workspace = true }\nserde_json = { workspace = true }\nlzma-rust2 = { workspace = true }\nthiserror = { workspace = true }\n\n# Optional workspace dependencies\ncef-dll-sys = { workspace = true, optional = true }\nscraper = { workspace = true, optional = true }\n"
  },
  {
    "path": "tools/third-party-licenses/build.rs",
    "content": "fn main() {\n\tprintln!(\"cargo:rerun-if-changed=src\");\n\n\tprintln!(\"cargo:rerun-if-env-changed=DEP_CEF_DLL_WRAPPER_CEF_DIR\");\n\tif let Ok(cef_dir) = std::env::var(\"DEP_CEF_DLL_WRAPPER_CEF_DIR\") {\n\t\tprintln!(\"cargo:rustc-env=CEF_PATH={cef_dir}\");\n\t}\n\n\tlet manifest_dir = std::path::PathBuf::from(env!(\"CARGO_MANIFEST_DIR\"));\n\tif std::env::var(\"CARGO_FEATURE_DESKTOP\").is_ok() {\n\t\tlet _ = std::fs::remove_file(manifest_dir.join(\"desktop.hash\"));\n\t} else {\n\t\tlet _ = std::fs::remove_file(manifest_dir.join(\"web.hash\"));\n\t}\n}\n"
  },
  {
    "path": "tools/third-party-licenses/src/cargo.rs",
    "content": "use crate::{Error, LicenceSource, LicenseEntry, Package};\nuse serde::Deserialize;\nuse std::fs;\nuse std::hash::{Hash, Hasher};\nuse std::path::PathBuf;\nuse std::process::Command;\n\npub struct CargoLicenseSource {}\n\nimpl CargoLicenseSource {\n\tpub fn new() -> Self {\n\t\tSelf {}\n\t}\n}\n\nimpl LicenceSource for CargoLicenseSource {\n\tfn licenses(&self) -> Result<Vec<LicenseEntry>, Error> {\n\t\tOk(parse(run()?))\n\t}\n}\n\nimpl Hash for CargoLicenseSource {\n\tfn hash<H: Hasher>(&self, state: &mut H) {\n\t\tlet lock_path = PathBuf::from(env!(\"CARGO_WORKSPACE_DIR\")).join(\"Cargo.lock\");\n\t\tfs::read_to_string(lock_path).unwrap().hash(state)\n\t}\n}\n\n#[derive(Deserialize)]\nstruct Output {\n\tlicenses: Vec<License>,\n}\n\n#[derive(Deserialize)]\nstruct License {\n\tname: Option<String>,\n\ttext: Option<String>,\n\tused_by: Vec<UsedBy>,\n}\n\n#[derive(Deserialize)]\nstruct UsedBy {\n\t#[serde(rename = \"crate\")]\n\tcrate_info: Crate,\n}\n\n#[derive(Deserialize)]\nstruct Crate {\n\tname: Option<String>,\n\tversion: Option<String>,\n\tauthors: Option<Vec<String>>,\n\trepository: Option<String>,\n}\n\nfn parse(parsed: Output) -> Vec<LicenseEntry> {\n\tparsed\n\t\t.licenses\n\t\t.into_iter()\n\t\t.map(|license| {\n\t\t\tlet packages = license\n\t\t\t\t.used_by\n\t\t\t\t.into_iter()\n\t\t\t\t.map(|used| {\n\t\t\t\t\tlet name = used.crate_info.name.as_deref().unwrap_or_default();\n\t\t\t\t\tlet version = used.crate_info.version.as_deref().unwrap_or_default();\n\t\t\t\t\tlet display_name = if version.is_empty() { name.to_string() } else { format!(\"{name}@{version}\") };\n\n\t\t\t\t\tlet repository = used.crate_info.repository.filter(|s| !s.is_empty());\n\n\t\t\t\t\tPackage {\n\t\t\t\t\t\tname: display_name,\n\t\t\t\t\t\tauthors: used.crate_info.authors.unwrap_or_default(),\n\t\t\t\t\t\turl: repository,\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.collect();\n\n\t\t\tLicenseEntry {\n\t\t\t\tname: license.name,\n\t\t\t\ttext: license.text.as_deref().unwrap_or_default().to_string(),\n\t\t\t\tpackages,\n\t\t\t}\n\t\t})\n\t\t.collect()\n}\n\nfn run() -> Result<Output, Error> {\n\tlet output = Command::new(\"cargo\")\n\t\t.args([\"about\", \"generate\", \"--format\", \"json\", \"--frozen\"])\n\t\t.current_dir(env!(\"CARGO_WORKSPACE_DIR\"))\n\t\t.output()\n\t\t.map_err(|e| Error::Io(e, \"Failed to run cargo about generate\".into()))?;\n\n\tif !output.status.success() {\n\t\treturn Err(Error::Command(format!(\"cargo about generate failed:\\n{}\", String::from_utf8_lossy(&output.stderr))));\n\t}\n\n\tlet stdout = String::from_utf8(output.stdout).map_err(|e| Error::Utf8(e, \"cargo about generate returned invalid UTF-8\".into()))?;\n\n\tserde_json::from_str(&stdout).map_err(|e| Error::Json(e, \"Failed to parse cargo about generate JSON\".into()))\n}\n"
  },
  {
    "path": "tools/third-party-licenses/src/cef.rs",
    "content": "use lzma_rust2::XzReader;\nuse scraper::{Html, Selector};\nuse std::fs;\nuse std::hash::Hash;\nuse std::io::Read;\nuse std::path::PathBuf;\n\nuse crate::{Error, LicenceSource, LicenseEntry, Package};\n\npub struct CefLicenseSource;\n\nimpl CefLicenseSource {\n\tpub fn new() -> Self {\n\t\tSelf {}\n\t}\n}\n\nimpl LicenceSource for CefLicenseSource {\n\tfn licenses(&self) -> Result<Vec<LicenseEntry>, Error> {\n\t\tlet html = read()?;\n\t\tOk(parse(&html))\n\t}\n}\n\nimpl Hash for CefLicenseSource {\n\tfn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n\t\tread().unwrap().hash(state)\n\t}\n}\n\nfn parse(html: &str) -> Vec<LicenseEntry> {\n\tlet document = Html::parse_document(html);\n\n\tlet product_sel = Selector::parse(\"div.product\").unwrap();\n\tlet title_sel = Selector::parse(\"span.title\").unwrap();\n\tlet homepage_sel = Selector::parse(\"span.homepage a\").unwrap();\n\tlet license_sel = Selector::parse(\"div.license pre\").unwrap();\n\n\tdocument\n\t\t.select(&product_sel)\n\t\t.filter_map(|product| {\n\t\t\tlet name: String = product.select(&title_sel).next().map(|el| el.text().collect()).unwrap_or_default();\n\n\t\t\tif name.is_empty() {\n\t\t\t\treturn None;\n\t\t\t}\n\n\t\t\tlet homepage = product.select(&homepage_sel).next().and_then(|el| el.value().attr(\"href\").map(String::from));\n\n\t\t\tlet license_text: String = product.select(&license_sel).next().map(|el| el.text().collect::<String>()).unwrap_or_default().trim().to_string();\n\n\t\t\tlet pkg = Package {\n\t\t\t\tname,\n\t\t\t\turl: homepage,\n\t\t\t\tauthors: Vec::new(),\n\t\t\t};\n\n\t\t\tSome(LicenseEntry {\n\t\t\t\tname: None,\n\t\t\t\ttext: license_text,\n\t\t\t\tpackages: vec![pkg],\n\t\t\t})\n\t\t})\n\t\t.collect()\n}\n\nfn read() -> Result<String, Error> {\n\tlet cef_path = PathBuf::from(env!(\"CEF_PATH\"));\n\tlet cef_credits = std::fs::read_dir(&cef_path)\n\t\t.map_err(|e| Error::Io(e, format!(\"Failed to read CEF_PATH directory {}\", cef_path.display())))?\n\t\t.filter_map(|entry| entry.ok())\n\t\t.find(|entry| {\n\t\t\tlet name = entry.file_name();\n\t\t\tname.eq_ignore_ascii_case(\"credits.html\") || name.eq_ignore_ascii_case(\"credits.html.xz\")\n\t\t})\n\t\t.map(|entry| entry.path())\n\t\t.ok_or_else(|| Error::CefCreditsNotFound(cef_path.clone()))?;\n\n\tlet decompress_xz = cef_credits.extension().map(|ext| ext.eq_ignore_ascii_case(\"xz\")).unwrap_or(false);\n\n\tif decompress_xz {\n\t\tlet file = fs::File::open(&cef_credits).map_err(|e| Error::Io(e, format!(\"Failed to open CEF credits file {}\", cef_credits.display())))?;\n\t\tlet mut reader = XzReader::new(file, false);\n\t\tlet mut html = String::new();\n\t\treader\n\t\t\t.read_to_string(&mut html)\n\t\t\t.map_err(|e| Error::Io(e, format!(\"Failed to decompress CEF credits file {}\", cef_credits.display())))?;\n\t\tOk(html)\n\t} else {\n\t\tfs::read_to_string(&cef_credits).map_err(|e| Error::Io(e, format!(\"Failed to read CEF credits file {}\", cef_credits.display())))\n\t}\n}\n"
  },
  {
    "path": "tools/third-party-licenses/src/main.rs",
    "content": "use std::collections::HashMap;\nuse std::fs;\nuse std::hash::{DefaultHasher, Hash, Hasher};\nuse std::path::PathBuf;\nuse std::process::ExitCode;\n\nmod cargo;\n#[cfg(feature = \"desktop\")]\nmod cef;\nmod npm;\n\nuse crate::cargo::CargoLicenseSource;\n#[cfg(feature = \"desktop\")]\nuse crate::cef::CefLicenseSource;\nuse crate::npm::NpmLicenseSource;\n\n#[derive(Debug, thiserror::Error)]\npub enum Error {\n\t#[error(\"{1}: {0}\")]\n\tIo(#[source] std::io::Error, String),\n\n\t#[error(\"{1}: {0}\")]\n\tJson(#[source] serde_json::Error, String),\n\n\t#[error(\"{1}: {0}\")]\n\tUtf8(#[source] std::string::FromUtf8Error, String),\n\n\t#[error(\"{0}\")]\n\tCommand(String),\n\n\t#[cfg(feature = \"desktop\")]\n\t#[error(\"Could not find CREDITS.html or CREDITS.html.xz in {0}\")]\n\tCefCreditsNotFound(PathBuf),\n}\n\npub trait LicenceSource: std::hash::Hash {\n\tfn licenses(&self) -> Result<Vec<LicenseEntry>, Error>;\n}\n\npub struct LicenseEntry {\n\tname: Option<String>,\n\ttext: String,\n\tpackages: Vec<Package>,\n}\n\npub struct Package {\n\tname: String,\n\tauthors: Vec<String>,\n\turl: Option<String>,\n}\n\n#[derive(Hash)]\nstruct Run<'a> {\n\toutput: &'a Vec<u8>,\n\tcargo: &'a CargoLicenseSource,\n\tnpm: &'a NpmLicenseSource,\n\t#[cfg(feature = \"desktop\")]\n\tcef: &'a CefLicenseSource,\n}\n\nfn main() -> ExitCode {\n\tif let Err(e) = run() {\n\t\teprintln!(\"Error: {e}\");\n\t\treturn ExitCode::FAILURE;\n\t}\n\tExitCode::SUCCESS\n}\n\nfn run() -> Result<(), Error> {\n\tlet manifest_dir = PathBuf::from(env!(\"CARGO_MANIFEST_DIR\"));\n\tlet workspace_dir = PathBuf::from(env!(\"CARGO_WORKSPACE_DIR\"));\n\n\t#[cfg(feature = \"desktop\")]\n\tlet output_path = workspace_dir.join(\"desktop/third-party-licenses.txt.xz\");\n\t#[cfg(not(feature = \"desktop\"))]\n\tlet output_path = workspace_dir.join(\"frontend/third-party-licenses.txt\");\n\n\t#[cfg(feature = \"desktop\")]\n\tlet current_hash_path = manifest_dir.join(\"desktop.hash\");\n\t#[cfg(not(feature = \"desktop\"))]\n\tlet current_hash_path = manifest_dir.join(\"web.hash\");\n\n\tlet cargo_source = CargoLicenseSource::new();\n\tlet npm_source = NpmLicenseSource::new(workspace_dir.join(\"frontend\"));\n\t#[cfg(feature = \"desktop\")]\n\tlet cef_source = CefLicenseSource::new();\n\n\tlet mut run = Run {\n\t\tcargo: &cargo_source,\n\t\tnpm: &npm_source,\n\t\t#[cfg(feature = \"desktop\")]\n\t\tcef: &cef_source,\n\t\toutput: &fs::read(&output_path).unwrap_or_default(),\n\t};\n\n\tlet mut hasher = DefaultHasher::new();\n\trun.hash(&mut hasher);\n\tlet current_hash = format!(\"{:016x}\", hasher.finish());\n\n\tif current_hash == fs::read_to_string(&current_hash_path).unwrap_or_default() {\n\t\teprintln!(\"No changes in licenses detected, skipping generation.\");\n\t\treturn Ok(());\n\t}\n\teprintln!(\"Changes in licenses detected, generating new license file.\");\n\n\tlet licenses = merge_filter_dedup_and_sort(vec![\n\t\tcargo_source.licenses()?,\n\t\tnpm_source.licenses()?,\n\t\t#[cfg(feature = \"desktop\")]\n\t\tcef_source.licenses()?,\n\t]);\n\tlet formatted = format_credits(&licenses);\n\n\t#[cfg(feature = \"desktop\")]\n\tlet output = compress(&formatted)?;\n\t#[cfg(not(feature = \"desktop\"))]\n\tlet output = formatted.as_bytes().to_vec();\n\tif let Some(parent) = output_path.parent() {\n\t\tfs::create_dir_all(parent).map_err(|e| Error::Io(e, format!(\"Failed to create directory {}\", parent.display())))?;\n\t}\n\tfs::write(&output_path, &output).map_err(|e| Error::Io(e, format!(\"Failed to write {}\", output_path.display())))?;\n\trun.output = &output;\n\n\tlet hash = {\n\t\tlet mut hasher = DefaultHasher::new();\n\t\trun.hash(&mut hasher);\n\t\tformat!(\"{:016x}\", hasher.finish())\n\t};\n\n\tfs::write(&current_hash_path, hash).map_err(|e| Error::Io(e, format!(\"Failed to write hash file {}\", current_hash_path.display())))?;\n\n\tOk(())\n}\n\nfn format_credits(licenses: &Vec<LicenseEntry>) -> String {\n\tlet mut out = String::new();\n\n\tout.push_str(\"▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐\\n\");\n\tout.push_str(\"▐▐                                                   ▐▐\\n\");\n\tout.push_str(\"▐▐   GRAPHITE THIRD-PARTY SOFTWARE LICENSE NOTICES   ▐▐\\n\");\n\tout.push_str(\"▐▐                                                   ▐▐\\n\");\n\tout.push_str(\"▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐\\n\");\n\n\tfor license in licenses {\n\t\tlet package_lines: Vec<String> = license\n\t\t\t.packages\n\t\t\t.iter()\n\t\t\t.map(|pkg| match &pkg {\n\t\t\t\tPackage { name, authors, url: Some(url) } if !authors.is_empty() => format!(\"{} - [{}] - {}\", name, authors.join(\", \"), url),\n\t\t\t\tPackage { name, authors: _, url: Some(url) } => format!(\"{} - {}\", name, url),\n\t\t\t\tPackage { name, authors, url: None } if !authors.is_empty() => format!(\"{} - [{}]\", name, authors.join(\", \")),\n\t\t\t\t_ => pkg.name.clone(),\n\t\t\t})\n\t\t\t.collect();\n\n\t\tlet multi = package_lines.len() > 1;\n\n\t\tlet header = format!(\n\t\t\t\"The package{} listed here {} licensed under the terms of the {} printed beneath\",\n\t\t\tif multi { \"s\" } else { \"\" },\n\t\t\tif multi { \"are\" } else { \"is\" },\n\t\t\tif let Some(license) = license.name.as_ref() { license.to_string() } else { \"license\".to_string() }\n\t\t);\n\n\t\tlet max_len = std::iter::once(header.len()).chain(package_lines.iter().map(|l| l.chars().count())).max().unwrap_or(0);\n\n\t\tlet padded_packages: Vec<String> = package_lines\n\t\t\t.iter()\n\t\t\t.map(|line| {\n\t\t\t\tlet pad = max_len - line.chars().count();\n\t\t\t\tformat!(\"│ {}{} │\", line, \" \".repeat(pad))\n\t\t\t})\n\t\t\t.collect();\n\n\t\tout.push_str(&format!(\"\\n {}\\n\", \"_\".repeat(max_len + 2)));\n\t\tout.push_str(&format!(\"│ {} │\\n\", \" \".repeat(max_len)));\n\t\tout.push_str(&format!(\"│ {}{} │\\n\", header, \" \".repeat(max_len - header.len())));\n\t\tout.push_str(&format!(\"│{}│\\n\", \"_\".repeat(max_len + 2)));\n\t\tout.push_str(&padded_packages.join(\"\\n\"));\n\t\tout.push('\\n');\n\t\tout.push_str(&format!(\" {}\", \"\\u{203e}\".repeat(max_len + 2)));\n\t\tfor line in license.text.lines() {\n\t\t\tif line.is_empty() {\n\t\t\t\tout.push('\\n');\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tout.push('\\n');\n\t\t\tout.push_str(\"    \");\n\t\t\tout.push_str(line);\n\t\t}\n\t\tout.truncate(out.trim_end().len());\n\t\tout.push('\\n');\n\t}\n\n\tout\n}\n\nfn merge_filter_dedup_and_sort(sources: Vec<Vec<LicenseEntry>>) -> Vec<LicenseEntry> {\n\tlet mut all = Vec::new();\n\tfor source in sources {\n\t\tall.extend(source);\n\t}\n\tfilter(&mut all);\n\tlet mut all = dedup_by_licence_text(all);\n\tall.sort_by(|a, b| b.packages.len().cmp(&a.packages.len()).then(a.text.len().cmp(&b.text.len())));\n\tall\n}\n\nfn filter(licenses: &mut Vec<LicenseEntry>) {\n\tlicenses.iter_mut().for_each(|l| {\n\t\tl.packages.retain(|p| !(p.authors.len() == 1 && p.authors[0].contains(\"contact@graphite.art\")));\n\t});\n\tlicenses.retain(|l| !l.packages.is_empty());\n}\n\nfn dedup_by_licence_text(vec: Vec<LicenseEntry>) -> Vec<LicenseEntry> {\n\tlet mut map: HashMap<String, LicenseEntry> = HashMap::new();\n\n\tfor entry in vec {\n\t\tmatch map.entry(entry.text.clone()) {\n\t\t\tstd::collections::hash_map::Entry::Occupied(mut e) => {\n\t\t\t\te.get_mut().packages.extend(entry.packages);\n\t\t\t}\n\t\t\tstd::collections::hash_map::Entry::Vacant(e) => {\n\t\t\t\te.insert(entry);\n\t\t\t}\n\t\t}\n\t}\n\n\tmap.into_values().collect()\n}\n\n#[cfg(feature = \"desktop\")]\nfn compress(content: &str) -> Result<Vec<u8>, Error> {\n\tuse std::io::Write;\n\tlet mut buf = Vec::new();\n\tlet mut writer = lzma_rust2::XzWriter::new(&mut buf, lzma_rust2::XzOptions::default()).map_err(|e| Error::Io(e, \"Failed to create XZ writer\".into()))?;\n\twriter.write_all(content.as_bytes()).map_err(|e| Error::Io(e, \"Failed to write compressed credits\".into()))?;\n\twriter.finish().map_err(|e| Error::Io(e, \"Failed to finish XZ compression\".into()))?;\n\tOk(buf)\n}\n"
  },
  {
    "path": "tools/third-party-licenses/src/npm.rs",
    "content": "use std::collections::HashMap;\nuse std::fs;\nuse std::path::PathBuf;\nuse std::process::Command;\n\nuse crate::{Error, LicenceSource, LicenseEntry, Package};\n\npub struct NpmLicenseSource {\n\tdir: PathBuf,\n}\nimpl NpmLicenseSource {\n\tpub fn new(dir: PathBuf) -> Self {\n\t\tSelf { dir }\n\t}\n}\n\nimpl LicenceSource for NpmLicenseSource {\n\tfn licenses(&self) -> Result<Vec<LicenseEntry>, Error> {\n\t\tOk(parse(run(&self.dir)?))\n\t}\n}\n\nimpl std::hash::Hash for NpmLicenseSource {\n\tfn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n\t\tlet lock_path = self.dir.join(\"package-lock.json\");\n\t\tfs::read_to_string(lock_path).unwrap().hash(state)\n\t}\n}\n\ntype Output = HashMap<String, NpmEntry>;\n\n#[derive(serde::Deserialize)]\nstruct NpmEntry {\n\tlicenses: Option<String>,\n\trepository: Option<String>,\n\t#[serde(rename = \"licenseFile\")]\n\tlicense_file: Option<String>,\n\tpublisher: Option<String>,\n\temail: Option<String>,\n}\n\nfn parse(parsed: Output) -> Vec<LicenseEntry> {\n\tparsed\n\t\t.iter()\n\t\t.map(|(name, entry)| {\n\t\t\tlet publisher_info = entry.publisher.as_ref().map(|p| {\n\t\t\t\tlet email_part = entry.email.as_ref().map(|e| format!(\" <{}>\", e)).unwrap_or_default();\n\t\t\t\tformat!(\"{}{}\", p, email_part)\n\t\t\t});\n\n\t\t\tlet pkg = Package {\n\t\t\t\tname: name.to_string(),\n\t\t\t\turl: entry.repository.clone(),\n\t\t\t\tauthors: publisher_info.into_iter().collect(),\n\t\t\t};\n\n\t\t\tlet license_text = entry.license_file.as_ref().and_then(|p| fs::read_to_string(p).ok()).map(|s| s.to_string()).unwrap_or_default();\n\n\t\t\tLicenseEntry {\n\t\t\t\tname: entry.licenses.clone(),\n\t\t\t\ttext: license_text,\n\t\t\t\tpackages: vec![pkg],\n\t\t\t}\n\t\t})\n\t\t.collect()\n}\n\nfn run(dir: &std::path::Path) -> Result<Output, Error> {\n\t#[cfg(not(target_os = \"windows\"))]\n\tlet mut cmd = Command::new(\"npx\");\n\t#[cfg(target_os = \"windows\")]\n\tlet mut cmd = Command::new(\"npx.cmd\");\n\tcmd.args([\"license-checker-rseidelsohn\", \"--production\", \"--json\"]);\n\tcmd.current_dir(dir);\n\n\tlet output = cmd.output().map_err(|e| Error::Io(e, \"Failed to run npx license-checker-rseidelsohn\".into()))?;\n\n\tif !output.status.success() {\n\t\treturn Err(Error::Command(format!(\"npx license-checker-rseidelsohn failed:\\n{}\", String::from_utf8_lossy(&output.stderr))));\n\t}\n\n\tlet json_str = String::from_utf8(output.stdout).map_err(|e| Error::Utf8(e, \"Invalid UTF-8 from license-checker\".into()))?;\n\n\tserde_json::from_str(&json_str).map_err(|e| Error::Json(e, \"Failed to parse license-checker JSON\".into()))\n}\n"
  },
  {
    "path": "website/.build-scripts/install.ts",
    "content": "/* eslint-disable no-console */\n\nimport fs from \"fs\";\nimport type { IncomingMessage } from \"http\";\nimport https from \"https\";\nimport path from \"path\";\n\nimport * as tar from \"tar\";\n\n// Define basePath as the directory of the current script\nconst basePath = import.meta.dirname;\n\n// Define files to copy as [source, destination] pairs\n// Files with the same destination will be concatenated\nconst FILES_TO_COPY = [\n\t[\"../node_modules/@fontsource-variable/inter/opsz.css\", \"../static/fonts/common.css\"],\n\t[\"../node_modules/@fontsource-variable/inter/opsz-italic.css\", \"../static/fonts/common.css\"],\n\t[\"../node_modules/@fontsource/bona-nova/700.css\", \"../static/fonts/common.css\"],\n];\n\n// Define directories to copy recursively as [source, destination] pairs\nconst DIRECTORIES_TO_COPY = [\n\t[\"../node_modules/@fontsource-variable/inter/files\", \"../static/fonts/files\"],\n\t[\"../node_modules/@fontsource/bona-nova/files\", \"../static/fonts/files\"],\n];\n\n// Track processed destination files and CSS content\nconst processedDestinations = new Set();\nconst cssDestinations = new Set<string>();\nconst allCopiedFiles = new Set<string>();\n\n// Process each file\nFILES_TO_COPY.forEach(([source, dest]) => {\n\t// Convert relative paths to absolute paths\n\tconst sourcePath = path.join(basePath, source);\n\tconst destPath = path.join(basePath, dest);\n\n\t// Track CSS destinations for later analysis\n\tif (dest.endsWith(\".css\")) {\n\t\tcssDestinations.add(destPath);\n\t}\n\n\t// Ensure destination directory exists\n\tconst destDir = path.dirname(destPath);\n\tif (!fs.existsSync(destDir)) {\n\t\tfs.mkdirSync(destDir, { recursive: true });\n\t\tconsole.log(`Created directory: ${destDir}`);\n\t}\n\n\ttry {\n\t\t// Read source file content\n\t\tconst content = fs.readFileSync(sourcePath, \"utf8\");\n\n\t\t// Check if destination has been processed before\n\t\tif (processedDestinations.has(destPath)) {\n\t\t\t// Append to existing file\n\t\t\tfs.appendFileSync(destPath, \"\\n\\n\" + content);\n\t\t\tconsole.log(`Appended: ${sourcePath} → ${destPath}`);\n\t\t} else {\n\t\t\t// First time writing to this destination - copy the file\n\t\t\tfs.writeFileSync(destPath, content);\n\t\t\tprocessedDestinations.add(destPath);\n\t\t\tconsole.log(`Copied: ${sourcePath} → ${destPath}`);\n\t\t}\n\n\t\t// Replace all occurrences of \"./files\" with \"/fonts\" in the destination file\n\t\tlet destFileContent = fs.readFileSync(destPath, \"utf8\");\n\t\tdestFileContent = destFileContent.replaceAll(\"./files/\", \"/fonts/files/\");\n\t\tfs.writeFileSync(destPath, destFileContent);\n\t} catch (error) {\n\t\tconsole.error(`Error processing ${sourcePath} to ${destPath}:`, error);\n\t\tprocess.exit(1);\n\t}\n});\n\nfunction copyDirectoryRecursive(source: string, destination: string) {\n\t// Ensure destination directory exists\n\tif (!fs.existsSync(destination)) {\n\t\tfs.mkdirSync(destination, { recursive: true });\n\t\tconsole.log(`Created directory: ${destination}`);\n\t}\n\n\t// Get all items in the source directory\n\tconst items = fs.readdirSync(source);\n\n\t// Process each item\n\titems.forEach((item) => {\n\t\tconst sourcePath = path.join(source, item);\n\t\tconst destPath = path.join(destination, item);\n\n\t\t// Check if item is a directory or file\n\t\tconst stats = fs.statSync(sourcePath);\n\t\tif (stats.isDirectory()) {\n\t\t\t// Recursively copy subdirectory\n\t\t\tcopyDirectoryRecursive(sourcePath, destPath);\n\t\t} else {\n\t\t\t// Copy file and track it\n\t\t\tfs.copyFileSync(sourcePath, destPath);\n\t\t\tallCopiedFiles.add(destPath);\n\t\t\tconsole.log(`Copied: ${sourcePath} → ${destPath}`);\n\t\t}\n\t});\n}\n\n// Process each directory\nDIRECTORIES_TO_COPY.forEach(([source, dest]) => {\n\t// Convert relative paths to absolute paths\n\tconst sourcePath = path.join(basePath, source);\n\tconst destPath = path.join(basePath, dest);\n\n\ttry {\n\t\tcopyDirectoryRecursive(sourcePath, destPath);\n\t\tconsole.log(`Copied directory: ${sourcePath} → ${destPath}`);\n\t} catch (error) {\n\t\tconsole.error(`Error copying directory ${sourcePath} to ${destPath}:`, error);\n\t\tprocess.exit(1);\n\t}\n});\n\nconsole.log(\"All files and directories copied successfully!\");\n\n// Now check which of the copied files are actually referenced in CSS\nconsole.log(\"\\nChecking for unused font files...\");\n\n// Read all CSS content and join it\nlet allCssContent = \"\";\ncssDestinations.forEach((cssPath) => {\n\ttry {\n\t\tconst content = fs.readFileSync(cssPath, \"utf8\");\n\t\tallCssContent += content;\n\t} catch (error) {\n\t\tconsole.error(`Error reading CSS file ${cssPath}:`, error);\n\t}\n});\n\n// Filter files that aren't referenced in CSS\nconst unusedFiles: string[] = [];\nallCopiedFiles.forEach((filePath) => {\n\tconst fileName = path.basename(filePath);\n\n\t// Check if the file name is mentioned in any CSS\n\tif (!allCssContent.includes(fileName)) {\n\t\tunusedFiles.push(filePath);\n\t}\n});\n\n// Delete unused files\nif (unusedFiles.length > 0) {\n\tconsole.log(`Found ${unusedFiles.length} unused font files to delete:`);\n\tunusedFiles.forEach((filePath) => {\n\t\ttry {\n\t\t\tfs.unlinkSync(filePath);\n\t\t\tconsole.log(`Deleted unused file: ${filePath}`);\n\t\t} catch (error) {\n\t\t\tconsole.error(`Error deleting file ${filePath}:`, error);\n\t\t}\n\t});\n} else {\n\tconsole.log(\"No unused font files found.\");\n}\n\nconsole.log(\"\\nFont installation complete!\");\n\n// Fetch and save text-balancer.js, which we don't commit to the repo so we're not version controlling dependency code\nconst textBalancerUrl = \"https://static.graphite.art/text-balancer/text-balancer.js\";\nconst textBalancerDest = path.join(basePath, \"../static\", \"text-balancer.js\");\nconsole.log(\"\\nDownloading text-balancer.js...\");\nhttps\n\t.get(textBalancerUrl, (res) => {\n\t\tif (res.statusCode !== 200) {\n\t\t\tconsole.error(`Failed to download text-balancer.js. Status code: ${res.statusCode}`);\n\t\t\tres.resume();\n\t\t\treturn;\n\t\t}\n\n\t\tlet data = \"\";\n\t\tres.on(\"data\", (chunk) => {\n\t\t\tdata += chunk;\n\t\t});\n\n\t\tres.on(\"end\", () => {\n\t\t\ttry {\n\t\t\t\t// Ensure destination directory exists\n\t\t\t\tconst destDir = path.dirname(textBalancerDest);\n\t\t\t\tif (!fs.existsSync(destDir)) {\n\t\t\t\t\tfs.mkdirSync(destDir, { recursive: true });\n\t\t\t\t\tconsole.log(`Created directory: ${destDir}`);\n\t\t\t\t}\n\t\t\t\tfs.writeFileSync(textBalancerDest, data, \"utf8\");\n\t\t\t\tconsole.log(`Downloaded and saved: ${textBalancerDest}`);\n\t\t\t\tres.destroy(); // Close the connection\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"Error saving text-balancer.js:\", error);\n\t\t\t\tres.destroy(); // Close the connection\n\t\t\t}\n\t\t});\n\t})\n\t.on(\"error\", (err) => {\n\t\tconsole.error(\"Error downloading text-balancer.js:\", err);\n\t});\n\n// Fetch all favicon files from the /favicons directory of the Graphite Branded Assets repo and save them within ../static/\n// The URL of the repo is the first line of ../../.branding which is a .tar.gz file that we extract with the \"tar\" npm package\nconst brandingFilePath = path.join(basePath, \"..\", \"..\", \".branding\");\nif (!fs.existsSync(brandingFilePath)) console.error(\"\\nThe `.branding` file was not found\");\nconst brandingRepoUrl = fs.readFileSync(brandingFilePath, \"utf8\").split(\"\\n\")[0].trim();\nconsole.log(\"\\nFetching favicons from branding repo:\", brandingRepoUrl);\ndownloadWithRedirects(\n\tbrandingRepoUrl,\n\t(res) => {\n\t\tif (res.statusCode !== 200) {\n\t\t\tconsole.error(`Failed to download branding repo. Status code: ${res.statusCode}`);\n\t\t\tres.resume();\n\t\t\treturn;\n\t\t}\n\n\t\t// Pipe the response stream into tar to extract only the /favicons directory\n\t\tconst extract = tar.extract({\n\t\t\tcwd: path.join(basePath, \"../static/\"),\n\t\t\tfilter: (path) => path.includes(\"/favicons/\"),\n\t\t\tstrip: 2, // Remove leading directory components\n\t\t});\n\n\t\tres.pipe(extract);\n\n\t\textract.on(\"finish\", () => {\n\t\t\tconsole.log(\"Favicons extracted to ../static/ successfully!\");\n\t\t\tres.destroy(); // Close the connection\n\t\t});\n\n\t\textract.on(\"error\", (err) => {\n\t\t\tconsole.error(\"Error extracting favicons:\", err);\n\t\t\tres.destroy(); // Close the connection\n\t\t});\n\t},\n\t(err) => console.error(\"Error downloading branding repo:\", err),\n);\nfunction downloadWithRedirects(url: string, callback: (res: IncomingMessage) => void, errorCallback: (err: Error) => void) {\n\thttps\n\t\t.get(url, (res) => {\n\t\t\tif (res.statusCode === 302 || res.statusCode === 301) {\n\t\t\t\tconsole.log(\"Redirected to:\", res.headers.location);\n\t\t\t\tres.destroy(); // Close the connection\n\n\t\t\t\t// Follow the redirect\n\t\t\t\treturn downloadWithRedirects(res.headers.location || \"\", callback, errorCallback);\n\t\t\t}\n\t\t\tconsole.log(\"Final URL reached:\", url);\n\t\t\tcallback(res);\n\t\t})\n\t\t.on(\"error\", errorCallback);\n}\n"
  },
  {
    "path": "website/.gitignore",
    "content": "node_modules/\npublic/\ngenerated/\nstatic/*\n!static/js/\ncontent/learn/node-catalog\n"
  },
  {
    "path": "website/config.toml",
    "content": "title = \"Graphite\"\ndescription = \"2D raster & vector editor that melds traditional layers & tools with a modern node-based procedural workflow.\"\nbase_url = \"https://graphite.art\"\nfeed_filenames = [\"rss.xml\"]\n\ncompile_sass = true\nminify_html = false\n\n[markdown.highlighting]\nerror_on_missing_language = false\ntheme = \"gruvbox-dark-soft\"\n\n[extra]\n# Put all your custom variables here\n"
  },
  {
    "path": "website/content/_index.md",
    "content": "+++\ntitle = \"Free online vector editor & procedural design tool\"\ntemplate = \"section.html\"\n\n[extra]\ncss = [\"/page/index.css\", \"/component/carousel.css\", \"/component/feature-icons.css\", \"/component/feature-box.css\", \"/component/youtube-embed.css\"]\njs = [\"/js/component/carousel.js\", \"/js/component/youtube-embed.js\", \"/js/component/video-autoplay.js\"]\nlinked_js = []\nmeta_description = \"Open source free software. A vector graphics creativity suite with a clean, intuitive interface. Opens instantly (no signup) and runs locally in a browser. Exports SVG, PNG, JPG.\"\n+++\n\n<!-- replacements::text_balancer() -->\n\n<!-- ▛ LOGO ▜ -->\n<section id=\"logo\">\n<div class=\"block\">\n\t<img src=\"https://static.graphite.art/logos/graphite-logotype-color.svg\" alt=\"Graphite Logo\" />\n</div>\n</section>\n<!-- ▙ LOGO ▟ -->\n\n<!-- ▛ TAGLINE ▜ -->\n<section id=\"tagline\">\n<div class=\"block\">\n\n<h1 class=\"balance-text\">Your <span>procedural</span> toolbox for 2D content creation</h1>\n\n<p class=\"balance-text\">Graphite is a free, open source vector graphics editor and animation engine, available now in alpha. Get creative with a fully nondestructive editing workflow that combines layer-based compositing with node-based generative design.</p>\n\n</div>\n</section>\n<!-- ▙ TAGLINE ▟ -->\n<!--                -->\n<!-- ▛ QUICK LINKS ▜ -->\n<section id=\"quick-links\" data-quick-links>\n\n<div class=\"call-to-action-buttons\">\n\t<a href=\"https://github.com/GraphiteEditor/Graphite\" class=\"button github-stars\">\n\t\t<img src=\"https://static.graphite.art/icons/github.svg\" alt=\"GitHub\" />\n\t\t<span class=\"arrow\">Star</span>\n\t\t<div data-github-stars></div>\n\t</a>\n\t<a href=\"#newsletter\" class=\"button arrow\">Subscribe to newsletter</a>\n</div>\n<div class=\"social-media-buttons\">\n\t<a href=\"https://discord.graphite.art\" target=\"_blank\">\n\t\t<img src=\"https://static.graphite.art/icons/discord__2.svg\" alt=\"Discord\" />\n\t</a>\n\t<a href=\"https://www.reddit.com/r/graphite/\" target=\"_blank\">\n\t\t<img src=\"https://static.graphite.art/icons/reddit__3.svg\" alt=\"Reddit\" />\n\t</a>\n\t<a href=\"https://bsky.app/profile/graphiteeditor.bsky.social\" target=\"_blank\">\n\t\t<img src=\"https://static.graphite.art/icons/bluesky.svg\" alt=\"Bluesky\" />\n\t</a>\n\t<a href=\"https://twitter.com/graphiteeditor\" target=\"_blank\">\n\t\t<img src=\"https://static.graphite.art/icons/twitter.svg\" alt=\"Twitter\" />\n\t</a>\n\t<a href=\"https://www.youtube.com/@GraphiteEditor\" target=\"_blank\">\n\t\t<img src=\"https://static.graphite.art/icons/youtube.svg\" alt=\"YouTube\" />\n\t</a>\n</div>\n\n</section>\n\n<script>\n(async () => {\n\tconst element = document.querySelector(\"[data-github-stars]\");\n\ttry {\n\t\tconst response = await fetch(\"https://api.github.com/repos/graphiteeditor/graphite?per_page=1\");\n\t\tconst json = await response.json();\n\t\tconst stars = parseInt(json.stargazers_count);\n\t\tif (!stars) throw new Error();\n\t\tlet quantity = stars.toLocaleString(\"en-US\");\n\t\telement.innerText = quantity;\n\t\t// Force repaint to work around Safari bug <https://bugs.webkit.org/show_bug.cgi?id=286403> (remove this and its data attribute when the bug is fixed and widely deployed)\n\t\tdocument.querySelector(\"[data-quick-links]\").style.transform = \"scale(1)\";\n\t} catch {\n\t\telement.remove();\n\t}\n})();\n</script>\n<!-- ▙ QUICK LINKS ▟ -->\n\n<!-- ▛ SCREENSHOTS ▜ -->\n<section id=\"screenshots\" class=\"carousel window-size-1\" data-carousel data-carousel-jostle-hint>\n\n<div class=\"carousel-slide\" data-carousel-slide>\n\t<img src=\"https://static.graphite.art/content/index/gui-demo-creative-coding-poster.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"\" style=\"transform: translateX(-100%)\" data-carousel-image />\n\t<!-- Above is a copy of the last -->\n\t<img onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"\" style=\"transform: translateX(-100%)\" data-carousel-image width=\"1920\" height=\"1080\" loading=\"lazy\" src=\"https://static.graphite.art/content/index/gui-demo-painted-dreams__4.avif\" />\n\t<img onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"\" style=\"transform: translateX(-100%)\" data-carousel-image width=\"1920\" height=\"1080\" loading=\"lazy\" src=\"https://static.graphite.art/content/index/gui-demo-magazine-page-layout.avif\" />\n\t<video style=\"transform: translateX(-100%)\" data-carousel-image width=\"1920\" height=\"1080\" loop muted disablepictureinpicture disableremoteplayback preload=\"none\" poster=\"https://static.graphite.art/content/index/gui-demo-animation-twirl-poster.avif\"><source src=\"https://static.graphite.art/content/index/gui-demo-animation-twirl.webm\" type=\"video/webm\" /><source src=\"https://static.graphite.art/content/index/gui-demo-animation-twirl.mp4\" type=\"video/mp4\" /></video>\n\t<video style=\"transform: translateX(-100%)\" data-carousel-image width=\"1920\" height=\"1080\" loop muted disablepictureinpicture disableremoteplayback preload=\"none\" poster=\"https://static.graphite.art/content/index/gui-demo-creative-coding-poster.avif\"><source src=\"https://static.graphite.art/content/index/gui-demo-creative-coding.webm\" type=\"video/webm\" /><source src=\"https://static.graphite.art/content/index/gui-demo-creative-coding.mp4\" type=\"video/mp4\" /></video>\n\t<img onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"\" style=\"transform: translateX(-100%)\" data-carousel-image width=\"1920\" height=\"1080\" loading=\"lazy\" src=\"https://static.graphite.art/content/index/gui-demo-fractal__4.avif\" />\n\t<!-- Below is a copy of the first -->\n\t<img src=\"https://static.graphite.art/content/index/gui-demo-painted-dreams__4.png\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"\" style=\"transform: translateX(-100%)\" data-carousel-image />\n</div>\n\n<div class=\"carousel-slide torn left\" data-carousel-slide-torn-left></div>\n<div class=\"carousel-slide torn right\" data-carousel-slide-torn-right></div>\n\n<div class=\"screenshot-details\">\n\n<div class=\"carousel-controls\">\n\n<button class=\"direction prev\" data-carousel-prev aria-label=\"Move to previous screenshot\">\n\n<svg width=\"40\" height=\"40\" viewBox=\"0 0 40 40\" xmlns=\"http://www.w3.org/2000/svg\">\n\n<path d=\"M20,0C8.95,0,0,8.95,0,20c0,11.05,8.95,20,20,20c11.05,0,20-8.95,20-20C40,8.95,31.05,0,20,0z M20,38c-9.93,0-18-8.07-18-18S10.07,2,20,2s18,8.07,18,18S29.93,38,20,38z\" />\n<polygon points=\"24.71,10.71 23.29,9.29 12.59,20 23.29,30.71 24.71,29.29 15.41,20\" />\n\n</svg>\n\n</button>\n<button class=\"dot active\" data-carousel-dot aria-label=\"Move to screenshot 1\"></button>\n<button class=\"dot\" data-carousel-dot aria-label=\"Move to screenshot 2\"></button>\n<button class=\"dot\" data-carousel-dot aria-label=\"Move to screenshot 3\"></button>\n<button class=\"dot\" data-carousel-dot aria-label=\"Move to screenshot 4\"></button>\n<button class=\"dot\" data-carousel-dot aria-label=\"Move to screenshot 5\"></button>\n<button class=\"direction next\" data-carousel-next aria-label=\"Move to next screenshot\">\n\n<svg width=\"40\" height=\"40\" viewBox=\"0 0 40 40\" xmlns=\"http://www.w3.org/2000/svg\">\n\n<path d=\"M20,0C8.95,0,0,8.95,0,20c0,11.05,8.95,20,20,20c11.05,0,20-8.95,20-20C40,8.95,31.05,0,20,0z M20,38c-9.93,0-18-8.07-18-18S10.07,2,20,2s18,8.07,18,18S29.93,38,20,38z\" />\n<polygon points=\"16.71,9.29 15.29,10.71 24.59,20 15.29,29.29 16.71,30.71 27.41,20\" />\n\n</svg>\n\n</button>\n\n</div>\n<div class=\"screenshot-description\">\n\n<p data-carousel-description class=\"active\">\n\t<a href=\"https://editor.graphite.art/#demo/painted-dreams\"><em>Painted Dreams</em></a> — Made using nondestructive boolean operations and procedural polka dot patterns\n</p>\n<p data-carousel-description>\n\tDesign for a magazine spread, a preview of the upcoming focus on desktop publishing\n</p>\n<p data-carousel-description>\n\tProcedurally generated animation demonstrating Graphite's approach to creative coding with nodes\n</p>\n<p data-carousel-description>\n\tProcedurally generated animation demonstrating Graphite's approach to creative coding with nodes\n</p>\n<p data-carousel-description>\n\tMandelbrot fractal filled with a noise pattern, procedurally generated and infinitely scalable\n</p>\n\n</div>\n\n</div>\n</section>\n<!-- ▙ SCREENSHOTS ▟ -->\n<!--                  -->\n<!-- ▛ WHAT'S NEW ▜ -->\n<section id=\"what-is-new\" class=\"block\">\n\n<div class=\"block\">\n\n## What's new?\n\nThe latest major update is out now! See what the team has been cooking up recently:\n\n<div class=\"block video-container\">\n<div>\n<div class=\"youtube-embed aspect-16x9\">\n\t<img data-youtube-embed=\"Vl5BA4g3QXM\" loading=\"lazy\" src=\"https://static.graphite.art/content/index/video-september-025-update.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"September Update - Graphite, the Open Source 2D Graphics Suite\" />\n</div>\n</div>\n</div>\n\n</div>\n\n</section>\n<!-- ▙ WHAT'S NEW ▟ -->\n<!--                 -->\n<!-- ▛ OVERVIEW ▜ -->\n<section id=\"overview\" class=\"feature-box-outer\">\n<div class=\"feature-box-inner\">\n\n<div class=\"block\">\n\n<h1 class=\"feature-box-header\">Software overview</h1>\n\n---\n\n<div class=\"diptych sizzle-video\">\n<div class=\"block text\">\n\nStarting life as a vector editor, Graphite is evolving into a general-purpose, all-in-one graphics toolbox that is built more like a game engine than a conventional creative app. The editor's tools wrap its node graph core, exposing user-friendly workflows for vector, raster, animation, and beyond.\n\n<a href=\"https://editor.graphite.art\" class=\"button arrow\">Start creating</a>\n\n</div>\n<div class=\"block video\">\n\n<video loop muted playsinline disablepictureinpicture disableremoteplayback data-auto-play preload=\"none\" poster=\"https://static.graphite.art/content/index/sizzle-compilation-poster.avif\">\n\t<source src=\"https://static.graphite.art/content/index/sizzle-compilation.webm\" type=\"video/webm\" />\n\t<source src=\"https://static.graphite.art/content/index/sizzle-compilation.mp4\" type=\"video/mp4\" />\n</video>\n\n</div>\n</div>\n\n</div>\n<div class=\"block workflows\">\n\n## One app to rule them all\n\nStop jumping between programs. Upcoming tools will make Graphite a first-class content creation suite for many workflows, including:\n\n<div class=\"feature-icons stacked no-background\">\n\t<div class=\"feature-icon\">\n\t\t<img class=\"atlas\" style=\"--atlas-index: 12\" src=\"https://static.graphite.art/icons/icon-atlas-features__2.png\" alt=\"\" />\n\t\t<span>Graphic Design</span>\n\t</div>\n\t<div class=\"feature-icon\">\n\t\t<img class=\"atlas\" style=\"--atlas-index: 17\" src=\"https://static.graphite.art/icons/icon-atlas-features__2.png\" alt=\"\" />\n\t\t<span>Motion Graphics</span>\n\t</div>\n\t<div class=\"feature-icon\">\n\t\t<img class=\"atlas\" style=\"--atlas-index: 13\" src=\"https://static.graphite.art/icons/icon-atlas-features__2.png\" alt=\"\" />\n\t\t<span>Image Editing</span>\n\t</div>\n\t<div class=\"feature-icon\">\n\t\t<img class=\"atlas\" style=\"--atlas-index: 14\" src=\"https://static.graphite.art/icons/icon-atlas-features__2.png\" alt=\"\" />\n\t\t<span>Digital Painting</span>\n\t</div>\n\t<div class=\"feature-icon\">\n\t\t<img class=\"atlas\" style=\"--atlas-index: 15\" src=\"https://static.graphite.art/icons/icon-atlas-features__2.png\" alt=\"\" />\n\t\t<span>Page Layout & Print</span>\n\t</div>\n\t<div class=\"feature-icon\">\n\t\t<img class=\"atlas\" style=\"--atlas-index: 16\" src=\"https://static.graphite.art/icons/icon-atlas-features__2.png\" alt=\"\" />\n\t\t<span>VFX Compositing</span>\n\t</div>\n</div>\n\n</div>\n<div class=\"diptych\">\n\n<div class=\"block\">\n\n## Current features\n\n<div class=\"feature-icons\">\n\t<div class=\"feature-icon\">\n\t\t<img class=\"atlas\" style=\"--atlas-index: 0\" src=\"https://static.graphite.art/icons/icon-atlas-features__2.png\" alt=\"\" />\n\t\t<span>Vector editing tools</span>\n\t</div>\n\t<div class=\"feature-icon\">\n\t\t<img class=\"atlas\" style=\"--atlas-index: 10\" src=\"https://static.graphite.art/icons/icon-atlas-features__2.png\" alt=\"\" />\n\t\t<span>Procedural workflow for graphic design</span>\n\t</div>\n\t<div class=\"feature-icon\">\n\t\t<img class=\"atlas\" style=\"--atlas-index: 8\" src=\"https://static.graphite.art/icons/icon-atlas-features__2.png\" alt=\"\" />\n\t\t<span>Node-based layers</span>\n\t</div>\n\t<div class=\"feature-icon\">\n\t\t<img class=\"atlas\" style=\"--atlas-index: 3\" src=\"https://static.graphite.art/icons/icon-atlas-features__2.png\" alt=\"\" />\n\t\t<span>Forever free and open source</span>\n\t</div>\n</div>\n\nPresently, Graphite is a lightweight offline web app with features primarily oriented around procedural vector graphics editing.\n\n</div>\n<div class=\"block\">\n\n## Upcoming features\n\n<div class=\"feature-icons\">\n\t<div class=\"feature-icon\">\n\t\t<img class=\"atlas\" style=\"--atlas-index: 4\" src=\"https://static.graphite.art/icons/icon-atlas-features__2.png\" alt=\"\" />\n\t\t<span>All-in-one creative tool for all things 2D</span>\n\t</div>\n\t<div class=\"feature-icon\">\n\t\t<img class=\"atlas\" style=\"--atlas-index: 5\" src=\"https://static.graphite.art/icons/icon-atlas-features__2.png\" alt=\"\" />\n\t\t<span>Fully-featured raster manipulation</span>\n\t</div>\n\t<div class=\"feature-icon\">\n\t\t<img class=\"atlas\" style=\"--atlas-index: 7\" src=\"https://static.graphite.art/icons/icon-atlas-features__2.png\" alt=\"\" />\n\t\t<span>Windows/Mac/Linux native apps + web</span>\n\t</div>\n\t<div class=\"feature-icon\">\n\t\t<img class=\"atlas\" style=\"--atlas-index: 6\" src=\"https://static.graphite.art/icons/icon-atlas-features__2.png\" alt=\"\" />\n\t\t<span>Live collaborative editing</span>\n\t</div>\n</div>\n\n<a href=\"/features#roadmap\" class=\"button arrow\">Roadmap</a>\n\n</div>\n\n</div>\n<div class=\"block\">\n\n## Desktop-first and web-ready\n\nWhere's the download? The [web app](https://editor.graphite.art) is currently live and desktop apps for Windows, Mac, and Linux are now in a release candidate stage. Check the `#🔥announcements` channel of [the Discord](https://discord.graphite.art) for download links if you would like to partake in testing prior to the impending launch.\n\nGraphite is designed principally as a professional desktop application that is also accessible in a browser for quick access from anywhere. It's built for speed with (nearly) no JavaScript. And regardless of platform, it runs locally and privately on your own hardware— there is no server.\n\n<a href=\"https://github.com/GraphiteEditor/Graphite/issues/2535\" target=\"_blank\">Engineering the tech</a> for a native app distributed across three new platforms takes extra time. That's why supporting the web platform, which keeps up-to-date and reaches all devices, has been the initial target. For now, you can <a href=\"https://support.google.com/chrome/answer/9658361\" target=\"_blank\">install the app as a PWA</a> for a desktop-like experience.\n\nGraphite's code architecture is structured to deliver true native performance for your graphically intensive workloads on desktop platforms and very low overhead on the web thanks to WebAssembly and WebGPU, new high-performance browser technologies.\n\n</div>\n\n</div>\n</section>\n<!-- ▙ OVERVIEW ▟ -->\n<!--               -->\n<!-- ▛ DONATE ▜ -->\n<section id=\"donate\" class=\"block\">\n\n<div class=\"block\">\n\n<h2 class=\"heart\">Support the mission</h2>\n\nFree software doesn't grow on trees! Chip in your share of the (very real) development costs so you're not leaving others to pick up your tab. In just a few clicks, becoming a member (or giving a one-time donation) lets you help maintain Graphite's sustainability and independence.\n\n<a href=\"/donate\" class=\"button arrow\">Donate now</a>\n\n</div>\n\n</section>\n<!-- ▙ DONATE ▟ -->\n<!--                 -->\n<!-- ▛ PROCEDURALISM ▜ -->\n<section id=\"proceduralism\" class=\"feature-box-outer\">\n<div class=\"feature-box-inner\">\n\n<div class=\"block\">\n\n<h1 class=\"feature-box-header\">The power of proceduralism</h1>\n\n---\n\nGraphite is the first and only comprehensive graphic design suite built for procedural editing — where everything you make is nondestructive.\n\n</div>\n\n<div class=\"diptych red-dress\">\n\n<div class=\"block video-background\">\n\t<video loop muted playsinline disablepictureinpicture disableremoteplayback data-auto-play preload=\"none\" poster=\"https://static.graphite.art/content/index/procedural-demo-red-dress-poster.avif\">\n\t\t<source src=\"https://static.graphite.art/content/index/procedural-demo-red-dress.webm\" type=\"video/webm\" />\n\t\t<source src=\"https://static.graphite.art/content/index/procedural-demo-red-dress.mp4\" type=\"video/mp4\" />\n\t</video>\n</div>\n\n<div class=\"block description\">\n\n<h1 class=\"feature-box-header balance-text\">Explore parametric possibilities</h1>\n\nSave hours on tedious alterations and make better creative choices. Graphite lets you iterate rapidly by adjusting node parameters instead of individual elements.\n\nScatter circles with just a couple nodes...  \nWant them denser? Bigger? Those are sliders.  \nWant a different placement area? Just tweak the path.\n\n<a href=\"https://editor.graphite.art/#demo/red-dress\">Open this artwork</a> and give it a try yourself.\n\n</div>\n\n</div>\n<div class=\"diptych leaves\">\n\n<div class=\"block description\">\n\n<h1 class=\"feature-box-header balance-text\">Mix and morph anything</h1>\n\nNondestructive editing means every decision is tied to a parameter you can adjust later on. Use Graphite to interpolate between any states just by dragging value sliders.\n\nBlend across color schemes. Morph shapes before they're scattered around the canvas. The options are endless.\n\n<a href=\"https://editor.graphite.art/#demo/changing-seasons\">Open this artwork</a> and give it a try yourself.\n\n</div>\n\n<div class=\"block video-background\">\n\t<video loop muted playsinline disablepictureinpicture disableremoteplayback data-auto-play preload=\"none\" poster=\"https://static.graphite.art/content/index/procedural-demo-leaves-poster.avif\">\n\t\t<source src=\"https://static.graphite.art/content/index/procedural-demo-leaves.webm\" type=\"video/webm\" />\n\t\t<source src=\"https://static.graphite.art/content/index/procedural-demo-leaves.mp4\" type=\"video/mp4\" />\n\t</video>\n</div>\n\n</div>\n<div class=\"block pipelines\">\n\n## Geared for generative pipelines\n\nGraphite's representation of artwork as a node graph lets you customize, compose, automate, reuse, and share your content workflows.\n\n<div class=\"feature-icons four-wide\">\n\t<div class=\"feature-icon\">\n\t\t<img class=\"atlas\" style=\"--atlas-index: 9\" src=\"https://static.graphite.art/icons/icon-atlas-features__2.png\" alt=\"\" />\n\t\t<span class=\"balance-text\">Infinitely pan and zoom without pixelation, export any resolution</span>\n\t</div>\n\t<div class=\"feature-icon\">\n\t\t<img class=\"atlas\" style=\"--atlas-index: 1\" src=\"https://static.graphite.art/icons/icon-atlas-features__2.png\" alt=\"\" />\n\t\t<span class=\"balance-text\">Compile standalone programs from node systems <em>(future)</em></span>\n\t</div>\n\t<div class=\"feature-icon\">\n\t\t<img class=\"atlas\" style=\"--atlas-index: 11\" src=\"https://static.graphite.art/icons/icon-atlas-features__2.png\" alt=\"\" />\n\t\t<span class=\"balance-text\">Deploy asset pipelines in studio production environments <em>(future)</em></span>\n\t</div>\n</div>\n\n</div>\n\n</div>\n</section>\n<!-- ▙ PROCEDURALISM ▟ -->\n<!--                   -->\n<!-- ▛ NEWSLETTER ▜ -->\n<section id=\"newsletter\" class=\"feature-box-narrow\">\n<div id=\"newsletter-success\"><!-- Used only as a URL hash fragment anchor --></div>\n\n<div class=\"diptych\">\n\n<div class=\"block newsletter-signup\">\n\n<h1 class=\"feature-box-header\">Stay in the loop</h1>\n\nSubscribe to the newsletter for future updates on major development progress. And follow along, or join the conversation, on social media.\n\n<div class=\"newsletter-success\">\n\n## Thanks!\n\nYou'll receive your first newsletter email with the next major Graphite news.\n\n</div>\n<form action=\"https://graphite.art/newsletter-signup\" method=\"post\">\n\t<div class=\"same-line\">\n\t\t<div class=\"input-column name\">\n\t\t\t<label for=\"newsletter-name\">First + last name:</label>\n\t\t\t<input id=\"newsletter-name\" name=\"name\" type=\"text\" required />\n\t\t</div>\n\t\t<div class=\"input-column phone\">\n\t\t\t<label for=\"newsletter-phone\">Phone:</label>\n\t\t\t<input id=\"newsletter-phone\" name=\"phone\" type=\"text\" tabindex=\"-1\" autocomplete=\"off\" />\n\t\t</div>\n\t\t<div class=\"input-column email\">\n\t\t\t<label for=\"newsletter-email\">Email address:</label>\n\t\t\t<input id=\"newsletter-email\" name=\"email\" type=\"email\" required />\n\t\t</div>\n\t</div>\n\t<div class=\"input-column submit\">\n\t\t<input type=\"submit\" value=\"Subscribe\" class=\"button\" />\n\t</div>\n</form>\n\n</div>\n<div class=\"block social-media-links\">\n\n<a href=\"https://discord.graphite.art\" target=\"_blank\">\n\t<img src=\"https://static.graphite.art/icons/discord__2.svg\" alt=\"\" />\n\t<span class=\"link not-uppercase arrow\">Discord</span>\n</a>\n<a href=\"https://www.reddit.com/r/graphite/\" target=\"_blank\">\n\t<img src=\"https://static.graphite.art/icons/reddit__3.svg\" alt=\"\" />\n\t<span class=\"link not-uppercase arrow\">Reddit</span>\n</a>\n<a href=\"https://bsky.app/profile/graphiteeditor.bsky.social\" target=\"_blank\">\n\t<img src=\"https://static.graphite.art/icons/bluesky.svg\" alt=\"\" />\n\t<span class=\"link not-uppercase arrow\">Bluesky</span>\n</a>\n<a href=\"https://twitter.com/graphiteeditor\" target=\"_blank\">\n\t<img src=\"https://static.graphite.art/icons/twitter.svg\" alt=\"\" />\n\t<span class=\"link not-uppercase arrow\">Twitter</span>\n</a>\n<a href=\"https://www.youtube.com/@GraphiteEditor\" target=\"_blank\">\n\t<img src=\"https://static.graphite.art/icons/youtube.svg\" alt=\"\" />\n\t<span class=\"link not-uppercase arrow\">YouTube</span>\n</a>\n\n</div>\n\n</div>\n</section>\n<!-- ▙ NEWSLETTER ▟ -->\n<!--                -->\n<!-- ▛ DIVE IN ▜ -->\n<section id=\"dive-in\" class=\"block\">\n\n<div class=\"block\">\n\n## Ready to dive in?\n\nGet started with Graphite by following along to a hands-on quickstart tutorial.\n\n<div class=\"block video-container\">\n<div>\n<div class=\"youtube-embed aspect-16x9\">\n\t<img data-youtube-embed=\"7gjUhl_3X10\" loading=\"lazy\" src=\"https://static.graphite.art/content/learn/introduction/tutorial-1-vector-art-quickstart-youtube__2.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Vector Art Quickstart - Graphite, the Open Source 2D Graphics Suite\" />\n</div>\n</div>\n</div>\n\n<div class=\"buttons\">\n<a href=\"https://editor.graphite.art\" class=\"button arrow\">Launch Graphite</a>\n<a href=\"/learn\" class=\"button arrow\">Continue learning</a>\n</div>\n\n</div>\n\n</section>\n<!-- ▙ DIVE IN ▟ -->\n<!--                 -->\n<!-- ▛ RECENT NEWS ▜ -->\n<section id=\"recent-news\" class=\"feature-box-outer\">\n<div class=\"feature-box-inner\">\n\n<h1 class=\"feature-box-header\">Recent news <span> / </span> <a href=\"/blog\" class=\"link arrow\">More in the blog</a></h1>\n\n---\n\n<div class=\"diptych\">\n<!-- replacements::blog_posts(count = 2) -->\n</div>\n\n</div>\n</section>\n<!-- ▙ RECENT NEWS ▟ -->\n"
  },
  {
    "path": "website/content/about.md",
    "content": "+++\ntitle = \"About Graphite\"\n\n[extra]\ncss = [\"/page/about.css\", \"/component/feature-box.css\"]\n+++\n\n<section>\n\n<div class=\"block\">\n\n# About Graphite\n\nGraphite is a community-built free software project. If you find it valuable, consider [donating](/donate) or [getting involved](/volunteer) to keep it sustainable.\n\n</div>\n\n<div class=\"block\">\n\n## Project\n\nThe idea for Graphite began with a desire to create artwork and edit photos using free software that felt user-friendly and truly modern. Over time, that dream evolved to reconsider what \"modern\" meant for the landscape of 2D graphics editing. By borrowing concepts popular in 3D software, what could a procedural, nondestructive design tool look like if nothing was too ambitious? Answering that question took years of design exploration, leading to a community of savvy developers volunteering to turn that formidable dream into a reality.\n\n</div>\n\n<div class=\"diptych\">\n\n<div class=\"block\">\n\n## Mission\n\nGraphite strives to unshackle the creativity of every budding artist and seasoned professional by building the best comprehensive art and design tool that's accessible to all.\n\nMission success will come when Graphite is an industry standard. A cohesive product vision and a focus on innovation over imitation is the strategy that will make that possible.\n\n</div>\n<div class=\"block\">\n\n## Organization\n\nGraphite is fully funded by its community and beholden to no investors, ensuring it remains free and open source forever. The organization, *Graphite Labs, LLC*, is owned and controlled solely by the project's founder. Future nonprofit foundation status is being explored, but that administrative complexity is not yet justified. All income is being reinvested into the project, with aims to employ full-time developers once funding reaches sustainable levels.\n\n</div>\n\n</div>\n\n</section>\n\n<!-- ## Statistics\n\n- [GitHub stars](https://github.com/GraphiteEditor/Graphite/stargazers): <span class=\"loading-data\" data-github-stars></span>\n- [Contributors](https://github.com/GraphiteEditor/Graphite/graphs/contributors): <span class=\"loading-data\" data-contributors></span>\n- [Code commits](https://github.com/GraphiteEditor/Graphite/commits/master): <span class=\"loading-data\" data-code-commits></span>\n- [First line of code](https://github.com/GraphiteEditor/Graphite/commit/bca97cbeff8e38b426cfb410159cb21132062fba): Feb. 14, 2021\n\n<script>\n(async () => {\n\tconst response = await fetch(\"https://api.github.com/repos/graphiteeditor/graphite?per_page=1\");\n\tconst json = await response.json();\n\tconst stars = parseInt(json.stargazers_count);\n\tif (!stars) return;\n\n\tdocument.querySelector(\"[data-github-stars]\").innerText = `${Math.round(stars / 100) / 10}k ⭐`;\n})();\n(async () => {\n\tconst response = await fetch(\"https://api.github.com/repos/graphiteeditor/graphite/contributors?per_page=1\");\n\tconst link = [...response.headers].find(([header, _]) => header === \"link\")[1];\n\tif (!link) return;\n\t// With one page per contributor, the last past number is the contributor count\n\tconst contributors = parseInt(link.match(/page=(\\d+)>; rel=\"last\"/)[1]);\n\tif (!contributors) return;\n\n\tdocument.querySelector(\"[data-contributors]\").innerText = contributors;\n})();\n(async () => {\n\tconst response = await fetch(\"https://api.github.com/repos/graphiteeditor/graphite/commits?per_page=1\");\n\tconst link = [...response.headers].find(([header, _]) => header === \"link\")[1];\n\tif (!link) return;\n\t// With one page per commit, the last past number is the commit count\n\tconst commits = parseInt(link.match(/page=(\\d+)>; rel=\"last\"/)[1]);\n\tif (!commits) return;\n\n\tdocument.querySelector(\"[data-code-commits]\").innerText = commits;\n})();\n</script> -->\n\n<!-- <section id=\"opener-message\">\n<div class=\"block\">\n\n## A 2D creative tool made for everyone\n\nWith great power comes great accessibility. Graphite is built on the belief that the best creative tools can be powerful and within reach of all, from students to studios.\n\nGraphite is designed with a friendly and intuitive interface where a delightful user experience is of first-class importance. It is available for free under an open source [license](/license) and usable [instantly through a web browser](https://editor.graphite.art) or an upcoming native client on Windows, Mac, and Linux.\n\nIt's easy to learn and teach, yet Graphite's accessible design does not sacrifice versatility for simplicity. The node-based workflow opens doors to an ecosystem of powerful capabilities catering to casual and professional users alike.\n\n</div>\n<div class=\"graphic\">\n\t<img src=\"https://static.graphite.art/content/index/brush__2.svg\" alt=\"\" />\n</div>\n</section> -->\n\n<section id=\"core-team\" class=\"feature-box-outer\">\n<div class=\"feature-box-inner\">\n\n<h1 class=\"feature-box-header\">Meet the core team</h1>\n\n---\n\n<div class=\"diptych\">\n\n<div class=\"block\" id=\"keavon\">\n\n<img src=\"https://static.graphite.art/content/about/core-team-photo-keavon-chambers.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Photo of Keavon Chambers\" />\n\n## Keavon Chambers <span class=\"handle\">(@Keavon)</span> <img src=\"https://static.graphite.art/icons/flags/us.png\" class=\"flag\" title=\"American\" />\n\n***Founder, UI & product design, frontend, editor systems***\n\nKeavon is a creative generalist with a love for the fusion of arts and technology. UX and graphic designer, photographer, game developer, technical artist, and everything in between— he is equal parts designer and engineer. His multidisciplinary background in the digital arts is aptly suited for concocting the unique vision needed to bring Graphite to fruition.\n\n</div>\n<div class=\"block\" id=\"dennis\">\n\n<img src=\"https://static.graphite.art/content/about/core-team-photo-dennis-kobert.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Photo of Dennis Kobert\" />\n\n## Dennis Kobert <span class=\"handle\">(@TrueDoctor)</span> <img src=\"https://static.graphite.art/icons/flags/de.png\" class=\"flag\" title=\"German\" />\n\n***Graphene node engine, research, architecture***\n\nDennis is a mix between a mathematician and a mad scientist. While still enjoying the art of photography and image editing (which drew him to the project early on), he thrives when challenged with designing complex systems and pushing boundaries. His method of building generalized solutions wrapped in elegant layers of abstraction led to his creation of the Graphene engine.\n\n</div>\n\n</div>\n<div class=\"diptych\">\n\n<div class=\"block\" id=\"timon\">\n\n<img src=\"https://static.graphite.art/content/about/core-team-photo-timon-schelling.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Photo of Timon Schelling\" />\n\n## Timon Schelling <span class=\"handle\">(@timon-schelling)</span> <img src=\"https://static.graphite.art/icons/flags/de.png\" class=\"flag\" title=\"German\" />\n\n***Desktop application engineering***\n\nTimon believes open source tools are essential to grant digital artists freedom in access, creativity, and expression. He builds and maintains Graphite's native desktop app, ensuring its polish, reliability, and cross-platform consistency. Drawn to Graphite by its vision of procedural, nondestructive, expansive 2D art tools, he works to make that vision usable by as many people as possible.\n\n</div>\n\n<div class=\"block\" id=\"adam\">\n\n<img src=\"https://static.graphite.art/content/about/core-team-photo-adam-gerhant.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Photo of Adam Gerhant\" />\n\n## Adam Gerhant <span class=\"handle\">(@pendapia)</span> <img src=\"https://static.graphite.art/icons/flags/us.png\" class=\"flag\" title=\"American\" />\n\n***Editor node graph tooling***\n\nAdam is a pragmatic problem solver with a talent for simplifying complexity. He is responsible for various architectural decisions which provide future proof integrations between the Graphene engine and editor. His work has greatly improved the performance, stability and code quality of the project, while also setting the stage for additional features.\n\n</div>\n\n</div>\n\n</div>\n</section>\n\n<section>\n<div class=\"triptych\" id=\"extras\">\n\n<div class=\"block\">\n\n## Credits\n\nIn addition to the work of the Core Team listed above, over a hundred other contributors have written code that makes Graphite what it is today:\n\n<a href=\"https://github.com/GraphiteEditor/Graphite/graphs/contributors\" class=\"button arrow\">Credits</a>\n\n</div>\n<div class=\"block\">\n\n## License\n\nThe Graphite editor source code is published under the terms of the Apache License 2.0. See below for details and exclusions:\n\n<a href=\"/license\" class=\"button arrow\">License</a>\n\n</div>\n<div class=\"block\">\n\n## Logo\n\nMore information about the Graphite logo, including its community-focused usage policy and downloadable assets, is available on the logo page:\n\n<a href=\"/logo\" class=\"button arrow\">Logo</a>\n\n</div>\n\n</div>\n</section>\n"
  },
  {
    "path": "website/content/blog/2022-02-12-announcing-graphite-alpha.md",
    "content": "+++\ntitle = \"Announcing Graphite alpha\"\ndate = 2022-02-12\n\n[extra]\nbanner = \"https://static.graphite.art/content/blog/2022-02-12-announcing-graphite-alpha.avif\"\nbanner_png = \"https://static.graphite.art/content/blog/2022-02-12-announcing-graphite-alpha.png\"\nauthor = \"Keavon Chambers\"\nsummary = \"The Graphite open source team announces the alpha release of their next-generation graphics editor, a web-based SVG editor with vector-based tools. Future plans include a node-based procedural workflow, a raster graphics compositing engine, and a native desktop client.\"\nreddit = \"https://www.reddit.com/r/graphite/comments/unw3hi/blog_post_announcing_graphite_alpha/\"\ntwitter = \"https://twitter.com/GraphiteEditor/status/1524663930697568256\"\n+++\n\nThe Graphite open source team is proud to announce the release of the alpha version of our next-generation graphics editor. This is the culmination of one year of development (and 1000 GitHub stars) by our passionate and dedicated community. The alpha release, a minimum viable product vector editor web app, completes the first chapter for our project. We can't wait to now forge ahead towards additional milestones and a beta release as we build a node-based procedural workflow, a raster graphics compositing engine, a native desktop client, and further ambitions.\n\n<!-- more -->\n\nWe hope that Graphite can serve the initial use case of a lightweight web-based SVG editor. SVG export and source document saving is supported but editable SVG import will need further work. All the vector and general tools (blue and gray tool icons, respectively) are now available in some form, while raster tools (orange icons, grayed out) are forthcoming.\n\nWhile in alpha, file format stability cannot be guaranteed so you may need to finish your saved documents in an older version from near the date it was started. To access an older version, ask a team member [on Discord](https://discord.graphite.art) to locate an editor build link matching your document's creation date.\n\nYour feedback and bug reports will be valuable in helping the team prioritize improvements to ensure Graphite can be as useful as possible for artists during the alpha phase. Please go [make some art](https://editor.graphite.art) to help inform the development process. Thank you for joining us on this exciting journey.\n"
  },
  {
    "path": "website/content/blog/2022-03-12-graphite-a-vision-for-the-future-of-2d-content-creation.md",
    "content": "+++\ntitle = \"Graphite: a vision for the future of 2D content creation\"\ndate = 2022-03-12\n\n[extra]\nbanner = \"https://static.graphite.art/content/blog/2022-03-12-graphite-a-vision-for-the-future-of-2d-content-creation.avif\"\nbanner_png = \"https://static.graphite.art/content/blog/2022-03-12-graphite-a-vision-for-the-future-of-2d-content-creation.png\"\nauthor = \"Keavon Chambers\"\nsummary = \"Graphite is an open-source application for 2D graphics editing and digital content creation, offering a nondestructive, node-based workflow. It combines intuitive UI with powerful procedural image generators to revolutionize 2D content creation.\"\nreddit = \"https://www.reddit.com/r/graphite/comments/unw3va/blog_post_graphite_a_vision_for_the_future_of_2d/\"\ntwitter = \"https://twitter.com/GraphiteEditor/status/1524664010091556864\"\n+++\n\nGraphite is an open source, cross-platform digital content creation desktop and web application for 2D graphics editing, photo processing, vector art, digital painting, illustration, data visualization, compositing, and more. Inspired by the open source success of Blender in the 3D domain, it aims to bring 2D content creation to new heights with efficient workflows influenced by Photoshop/Gimp and Illustrator/Inkscape and backed by a powerful node-based, nondestructive approach proven by Houdini, Nuke, Blender, and others.\n\nThe user experience of Graphite is of central importance, offering a meticulously-designed UI catering towards an intuitive and efficient artistic process. Users may draw and edit in the traditional interactive (WYSIWYG) viewport with the Layers panel or jump in or out of the node graph at any time to tweak previous work and construct powerful procedural image generators that seamlessly sync with the interactive viewport. A core principle of the application is its 100% nondestructive workflow that is resolution-independent, meaning that raster-style image editing can be infinitely zoomed and scaled to arbitrary resolutions at a later time because editing is done by recording brush strokes, vector shapes, and other manipulations parametrically.\n\n<!-- more -->\n\nOne might use the painting tools on a small laptop display, zoom into specific areas to add detail to finish the artwork, then perhaps try changing the simulated brush style from a blunt pencil to a soft acrylic paintbrush after-the-fact, and finally export the complete drawing at ultra high resolution for printing on a large poster.\n\nOn the surface, Graphite is an artistic medium for drawing anything imaginable. Under the hood, Graphite's node graph engine powers procedural graphics processing to produce unique artwork and automated data-driven visualizations. Graphite unlocks your creative potential in a familiar, free, accessible, and lightweight package.\n"
  },
  {
    "path": "website/content/blog/2022-05-12-distributed-computing-in-the-graphene-runtime.md",
    "content": "+++\ntitle = \"Distributed computing in the Graphene runtime\"\ndate = 2022-05-12\n\n[extra]\nbanner = \"https://static.graphite.art/content/blog/2022-05-12-distributed-computing-in-the-graphene-runtime__2.avif\"\nbanner_png = \"https://static.graphite.art/content/blog/2022-05-12-distributed-computing-in-the-graphene-runtime__2.png\"\nauthor = \"Keavon Chambers\"\nsummary = \"Graphite's 2D editor is built upon Graphene, a node-based editing system for nondestructive design across various data types designed to render artwork faster using multiple machines. The system optimizes execution paths, minimizes latency, and uses a distributed runtime for quick data processing.\"\nreddit = \"https://www.reddit.com/r/graphite/comments/unw45k/blog_post_distributed_computing_in_the_graphene/\"\ntwitter = \"https://twitter.com/GraphiteEditor/status/1524664083554791424\"\n+++\n\nGraphite is a professional 2D graphics editor for photo editing, image manipulation, graphic design, illustration, data visualization, batch processing, and technical art. It is designed to run on a variety of machines, from mobile hardware like iPads or web browsers on midrange laptops up to beefy workstations with dozens of CPU cores and multiple GPUs.\n\nTo provide a responsive user experience, its architecture is made to support the use of distributed computation to make up for deficiencies in local compute power. Resulting productivity benefits will scale for users on hardware ranging from low-end mobile devices up to high-end workstations because documents and use cases can grow to great complexity.\n\nThis article explores the current thinking about the problems and potential engineering solutions involved in Graphite and Graphene for building a high-performance distributed computing runtime environment. We are only just embarking on the graph engine implementation, meaning this post describes theoretical approaches to theoretical challenges. The aim is to shed light on what will need to be built and what we currently believe is the trajectory for this work. We hope this post prompts discussions that evolve the concepts and approaches described herein. If this topic sounds interesting and you have feedback, please [get in touch](/contact).\n\n<!-- more -->\n\n## Node-based editing\n\nA core feature is Graphite's reliance on procedural content generation using a node graph engine called Graphene. In traditional editors like Photoshop and Gimp, certain operations like \"blur the image\" modify the image pixels permanently, destroying the original (unblurred) image information.\n\nGraphite is a *nondestructive* editor. Its approach is to represent the \"blur\" operation as a step in the creation process. All editing steps made by the user are encoded as operations, such as: import or resize an image, draw with a paintbrush, select an area with a certain color, combine two geometric shapes, etc.\n\nOperations are functions that process information and are called *nodes*. For example, the \"Blur\" node takes an image and a strength value and outputs a blurred version of the image. On the advanced end, machine learning-powered nodes may do things like image synthesis or style transfer. Many nodes perform a wide variety of image editing operations and these are connected together into a directed acyclic graph where the final output is the pixels drawn to the screen or saved to an image file.\n\nMany nodes process raster image data, but others work on data types like vector shapes, numbers, strings, colors, and large data tables (like imports from a spreadsheet, database, or CSV file). Some nodes perform visual operations like blur while others modify data, like performing regex matching on strings or sorting tables of information. For example, a CSV file might be imported, cleaned up, processed, then fed into the visual nodes which render it in the form of a chart.\n\nDifferent nodes may take microseconds, milliseconds, seconds, or occasionally even minutes to run. Most should not take more than a few seconds, and those which take appreciable time should run infrequently. During normal operations, hundreds of milliseconds should be the worst case for ordinary nodes that run frequently. Caching is used heavily to minimize the need for recomputation.\n\n### Node authorship\n\nThe nodes that process the node graph data can be computationally expensive. The goal is for the editor to ordinarily run and render (mostly) in real-time in order to be interactive. Because operations are arranged in a directed acyclic graph rather than a sequential list, there is opportunity to run many stages of the computation and rendering in parallel.\n\nNodes are implemented by us as part of a built-in library, and by some users who may choose to write code using a built-in development environment. Nodes can be written in Rust to target the CPU with the Rust compilation toolchain (made conveniently accessible for users). The same CPU Rust code can be reused (with modifications where necessary) for authoring GPU compute shaders via the [rust-gpu](https://github.com/EmbarkStudios/rust-gpu) compiler. This makes it easier to maintain both versions without using separate code files and even languages between targets.\n\n### Sandboxing\n\nFor security and portability, user-authored nodes are compiled into WebAssembly (Wasm) modules and run in a sandbox. Built-in nodes provided with Graphite run natively to avoid the nominal performance penalty of the sandbox. When the entire editor is running in a web browser, all nodes use the browser's Wasm executor. When running in a distributed compute cluster on cloud machines, the infrastructure provider may be able to offer sandboxing to sufficiently address the security concerns of running untrusted code natively.\n\n## The Graphene distributed runtime\n\nIn the product architecture, Graphene is a distributed runtime environment for quickly processing data in the node graph by utilizing a pool of CPU and GPU compute resources available on local and networked machines. Jobs are run where latency, speed, and bandwidth availability will be most likely to provide a responsive user experience.\n\n<img src=\"https://static.graphite.art/content/blog/2022-05-12-distributed-computing-in-the-graphene-runtime/local-and-cloud.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Graphene in a local and cloud environment\" />\n\n### Scheduler\n\nIf users are running offline, their CPU threads and GPU (or multiple GPUs) are assigned jobs by the local Graphene scheduler. If running online, some jobs are performed locally while others are run in the cloud, an on-prem compute cluster, or just a spare computer on the same network. The schedulers generally prioritize keeping quicker, latency-sensitive jobs local to the client machine or LAN while allowing slower, compute-intensive jobs to usually run on the cloud.\n\nEach cluster in a locality—such as the local machine, an on-prem render farm, and the cloud data center—runs a scheduler that commands the available compute resources. The multiple schedulers in different localities must cooperatively plan the high-level allocation of resources in their respective domains while avoiding excessive chatter over the network about the minutiae of each job invocation.\n\n### Cache manager\n\nWorking together with the scheduler, the Graphene cache manager stores intermediate node evaluation results and intelligently evicts them when space is limited. If the user changes the node graph, it can reuse the upstream cached data but will need to recompute downstream nodes where the data changed. Memoization can be used to avoid recomputation when an input to a node is the same as received in the past, and that result is available in the cache.\n\n### Progressive enhancement\n\nThe scheduler and cache manager work in lockstep to utilize available compute and cache storage resources on the local machine or cluster in order to minimize latency for the user. Immediate feedback is needed when, for example, drawing with a paintbrush tool.\n\nSometimes, nodes can be run with quality flags. Many nodes (like the paintbrush rasterizer) are implemented using several different algorithms that produce different results trading off speed for quality. This means it runs once with a quick and ugly result, then runs again later to render a higher quality version, and potentially several times subsequently to improve the visual fidelity when the scheduler has spare compute resources and time. Anti-aliasing, for example, will usually pop in to replace aliased renders after a few seconds of waiting.\n\n### Batched execution\n\nIt is important to reduce the overhead between executions. Sometimes, Graphene will predict, or observe during runtime, the frequent execution of code paths (or rather, *node paths*) with significant sequential (not parallel) execution steps. These are good candidates for optimization by reducing the overhead between each execution.\n\nIn these cases, Graphene will batch multiple sequentially-run nodes by recompiling them such that they are inlined as one execution unit. This can happen for both CPU-based programs and GPU-based compute shaders. This is conceptually similar to how just-in-time (JIT) compilers can predict, or observe at runtime, frequently-run code paths in order to apply compiler optimizations where they are most impactful.\n\n### Data locality\n\nWhen dealing with sequential chains of nodes, if they haven't already been recompiled as a batched execution unit, the Graphene scheduler may also frequently prioritize grouping together a set of related nodes to be run together on the same machine for memory and cache locality.\n\nSequential nodes allocated to the same physical hardware can avoid the overhead of copying data over the internet, or between machines in the cluster, or between RAM and VRAM, or from RAM to the CPU cache.\n\nGraphene should recognize when a certain intermediate result already lives in RAM or VRAM and prioritize using that CPU or GPU to compute the tasks which rely on that data. But when that's not possible, we need a fast architecture to transfer data between machines. For GPUs, it might be possible to use a DMA (Direct Memory Access) strategy like Microsoft's new DirectStorage API for DirectX and transfer data into or out of VRAM straight to SSDs or networked file systems. Efficiently transferring the final result, and maybe occasionally intermediate cache results, between networks (like the cloud and client) with latency and bandwidth considerations is also important.\n\n## Conclusion\n\nPresently, we are experimenting with CPU and GPU node composition for the beginnings of the Graphene visual programming language. Most of what was described in this post will likely evolve as we get further into the implementation stage and when we learn more from experts in the fields of computer science that Graphene overlaps with.\n\nIf you have a background or interest in programming language design, functional programming, ECS and data-oriented design, scheduling, distributed computing, general-purpose GPU compute (GPGPU), or high-performance computing (HPC), we'd love to have your ideas steer our work. Or better yet, join the team to make this dream a reality. We discuss most of our architecture and designs on our [Discord server](https://discord.graphite.art) through text and sometimes voice. Please come say hi!\n"
  },
  {
    "path": "website/content/blog/2024-01-01-looking-back-on-2023-and-what's-next.md",
    "content": "+++\ntitle = \"Looking back on 2023 and what's next\"\ndate = 2024-01-01\n\n[extra]\nbanner = \"https://static.graphite.art/content/blog/2024-01-01-looking-back-on-2023-and-what's-next.avif\"\nbanner_png = \"https://static.graphite.art/content/blog/2024-01-01-looking-back-on-2023-and-what's-next.png\"\nauthor = \"Keavon Chambers\"\nsummary = \"Looking back on 2023, we reflect on our significant achievements and milestones. As we move forward, we're excited to share what's next, promising a year filled with innovation and progress.\"\nreddit = \"https://www.reddit.com/r/graphite/comments/18xmoti/blog_post_looking_back_on_2023_and_whats_next/\"\ntwitter = \"https://twitter.com/GraphiteEditor/status/1742576805532577937\"\n\njs = [\"/js/component/youtube-embed.js\"]\ncss = [\"/component/youtube-embed.css\"]\n+++\n\nThe new year is here, and with so many accomplishments to share from the past twelve months, let's revisit the highlights of 2023 for the Graphite project. Now that winter has entered, let's swing back to the spring, summarize the summer, and follow this fall's noteworthy developments that brought another year of fruitful progress to Graphite's mission of re-envisioning artists' 2D creative workflows with the best free software we can build for the open source community. This past year as a team, we all got closer— to one another from continents apart; to visiting and connecting with our industry peers; and to reaching exciting new development milestones.\n\n<!-- more -->\n\nI am grateful to everyone who has placed their faith in my vision for Graphite since I laid forth the design and wrote its first line of code nearly three years ago. Meeting some of the amazing people this summer who helped to make it possible, and inspired the project in the first place, was a pleasure and an honor. From California to Europe and back again, my combined family vacation and Graphite outreach tour was an opportunity to make connections with those helping us reach our goals. This blog post is both a project update and a public thank-you to those who generously lent their time and attention to our small-but-growing project. And for readers eager for an update on the software itself, stick around (or skip ahead) for a development progress report and a look at what's coming down the pipeline in the new year.\n\n<div class=\"video-background\" style=\"text-align: center\">\n\t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n\t\t<source src=\"https://static.graphite.art/content/blog/2024-01-01-looking-back-on-2023-and-what's-next/christmas-tree-lights.webm\" type=\"video/webm\" />\n\t\t<source src=\"https://static.graphite.art/content/blog/2024-01-01-looking-back-on-2023-and-what's-next/christmas-tree-lights.mp4\" type=\"video/mp4\" />\n\t</video>\n</div>\n\n<center><em>Happy Holidays from the Graphite team!<br />These procedural light strands are powered by the newly completed node graph features.<br /><a href=\"https://editor.graphite.art/#demo/procedural-string-lights\">Click here to explore this demo</a> — drag the wire layer's points with the Path tool.</em></center>\n\n## The Alpha 2 release series\n\nFebruary marked the start of our second year developing Graphite under the alpha release banner. We took the opportunity to declare the start of a new release series, Alpha 2, for the year's focus on integrating the node graph. That goal was a success, and we anticipate the next release series, Alpha 3, will begin next month in February with a focus on procedural art workflows in line with our [roadmap](/features#roadmap).\n\n## GDC and meetups with the Rust graphics community\n\nIn March, I attended the Game Developers Conference (GDC) in nearby San Francisco to network with professionals in the creative industry. I was accompanied by [Oliver Davies](https://github.com/otdavies), a Graphite founding collaborator, fellow 3D artist and graphics engineer, and a life-long friend of mine. We connected with Francesco Siddi and Dalai Felinto from the Blender Foundation and introduced them to the project. The conference was also an opportunity to meet face-to-face with subject-matter experts with whom I'd earlier conversed online. Because we write Graphite in the Rust programming language, we attended an impromptu meetup among game developers using Rust in the nearby park that stretched for several insightful hours of pertinent conversation topics, thusly concluding the last day of the event.\n\nLater, in May, I went to another Rust developers meetup here in the Bay Area together with [Leonard Pauli](https://twitter.com/leonardpauli), a Graphite community member and code contributor who was in town on a visit all the way from Sweden. The event was headlined with a [presentation](https://www.youtube.com/watch?v=XjbVnwBtVEk) by [Raph Levien](https://raphlinus.github.io/) about [Xilem](https://github.com/linebender/xilem), an under-development GUI toolkit that Graphite may adopt someday for its promise of powering native, speedy desktop user interfaces. Raph Levien is a researcher and expert in the fields of 2D vector graphics, GPU-accelerated rendering, and the mathematics of splines and curves— topics considerably overlapping with Graphite's own technical disciplines. The meetup was a nice face-to-face introduction before I'd end up seeing Raph and Leonard each again very soon.\n\n<p><img src=\"https://static.graphite.art/content/blog/2024-01-01-looking-back-on-2023-and-what's-next/bay-area-rust-meetup.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.jpg')\" alt=\"Raph Levien speaking about Xilem\" /></p>\n\n<center><em>Raph gives his talk about Xilem and GUIs in Rust</em></center>\n\nThe next week I accepted Raph's invitation to visit him at his employer, Google, in Mountain View where we spent several hours talking shop. His other flagship open source library, [Vello](https://github.com/linebender/vello), is the high-performance 2D vector graphics renderer we plan to use as a crucial part of Graphite's render pipeline. Our discussions dove into the history and goals of Graphite, our shared research challenges, and covered some fascinating details surrounding Vello, computational geometry, and GPU rendering. As we round out 2023, Graphite's roadmap is finally nearing the stage of integrating Vello in the coming weeks and I look forward to growing our collaboration with Raph and his [research group](https://linebender.org/).\n\n## Embark Studios visit in Stockholm, Sweden\n\n<p><img src=\"https://static.graphite.art/content/blog/2024-01-01-looking-back-on-2023-and-what's-next/embark.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.jpg')\" alt=\"Leonard and Keavon in front of the Embark office sign\" /></p>\n\n<center><em>Leonard (left) and Keavon (right) at Embark Studios</em></center>\n\nIn June, as part of a vacation with my family to Europe, I caught up again with Leonard and he led us on a tour of his beautiful city of Stockholm. I also reached out to Johan Andersson, CTO of Embark Studios and an ambassador for the open source Rust developer community. Embark very generously contributes open source libraries for the Rust computer graphics ecosystem that are vital to Graphite, including [rust-gpu](https://github.com/EmbarkStudios/rust-gpu) which powers our node graph engine's compilation to GPU compute shaders. Johan showed Lenoard and me around Embark and we all chatted about what each of us are pursuing with Rust in the creative software industry. (By the way, Embark just released their first game, [The Finals](https://www.reachthefinals.com/)— check it out!)\n\n## Blender visit in Amsterdam, The Netherlands\n\n<p><img src=\"https://static.graphite.art/content/blog/2024-01-01-looking-back-on-2023-and-what's-next/blender-hq.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.jpg')\" alt=\"Graphite and Blender team members in front of the Blender building\" /></p>\n\n<center><em>Left to right: Dalai, Dennis, Keavon, Ton, Francesco</em></center>\n\nThe next month in July, together with Graphite's lead engineer [Dennis Kobert](/about#dennis-kobert), we spent the afternoon visiting Blender's headquarters in Amsterdam. Blender has been, since the beginning, my inspiration and motivation for taking on the tremendously ambitious goal of building Graphite. As fellow open source software projects building digital content creation tools, this was a wonderful chance to see where the magic happens and meet the people behind the curtain.\n\nAt the invitation of Francesco Siddi, COO of the Blender Foundation whom I'd met earlier at GDC, Dennis and I presented a lunchtime talk for the staff to introduce the Graphite project. Ton Roosendaal, Blender's founder, kindly cooked up some scrumptious, lovingly-made meals of fried eggs for us and his team. We spent a couple hours mingling— answering and asking questions and chatting about design and technical topics ranging from our Rust node graph language infrastructure to Blender's experience with color science standards.\n\n<p><img src=\"https://static.graphite.art/content/blog/2024-01-01-looking-back-on-2023-and-what's-next/blender-presentation.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.jpg')\" alt=\"Keavon presents in front of a projector screen at Blender's offices\" /></p>\n\n<center><em>Keavon presents to the Blender staff</em></center>\n\nTon also very generously gave us his time and full attention with an office hour for Dennis and me to ask his advice and learn from the three decades of experience that led to Blender's decisive success. (On January 2, Blender turns 30! They're aiming for a goal of 10,000 supporters to [donate a birthday gift](https://fund.blender.org/). I encourage you to join me in doing so.)\n\n## Graphite developer retreat in Karlsruhe, Germany\n\n<p><img src=\"https://static.graphite.art/content/blog/2024-01-01-looking-back-on-2023-and-what's-next/karlsruhe.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.jpg')\" alt=\"Keavon and Dennis in front of Karlsruhe Palace\" /></p>\n\n<center><em>Keavon (left) and Dennis (right) at Karlsruhe Palace</em></center>\n\nThe week before the Blender visit, I arrived in Karlsruhe, Germany for a two-week stay with Dennis for our first-ever team retreat. After spending hundreds of hours collaborating online, it was great to finally meet in-person. From diving deep into Graphite design discussions, to exploring the city and his college campus and joining his friends in social activities (and thus meeting another Graphite contributor, Isaac Körner, recruited by Dennis), it was a very welcoming and productive exchange.\n\n## SIGGRAPH in Los Angeles, California\n\n<p><img src=\"https://static.graphite.art/content/blog/2024-01-01-looking-back-on-2023-and-what's-next/siggraph.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.jpg')\" alt=\"Keavon and Oliver standing in front of the SIGGRAPH conference sign\" /></p>\n\n<center><em>Keavon (left) and Oliver (right) at SIGGRAPH</em></center>\n\nAfter returning back home to the States, the following month in August I road tripped down south to Los Angeles for another conference visit with Oliver Davies. SIGGRAPH is the industry's largest computer graphics conference which presented numerous opportunities to network with others in the field. Blender also exhibits each year and I got to meet up again with the crew from Amsterdam and meet some new faces who were absent in our earlier visit. I look forward to returning to LA this coming April for the first full Blender Conference held in the US and connecting with more like-minded open source creative aficionados.\n\n## Incorporating Graphite Labs, LLC\n\nThe next big news of August was my formation of [Graphite Labs, LLC](https://www.linkedin.com/company/graphite-labs) as a legal entity, allowing us to open a bank account and sign contracts on behalf of the Graphite project. This is an important step in the professional growth of the project. A tax-exempt nonprotfit foundation may happen in the future, but an LLC is a more accessible starting point. This step has opened up opportunities to form partnerships with industry, collect [sponsorships and donations](/donate), and hire full-time engineers a few years down the road once we have the income to financially support others developing Graphite. A major goal in 2024 is growing the sustainability and financial independence of the organization and allowing myself a modest income stream to offset costs while continuing my full-time Graphite work.\n\n## Website, user manual, and tutorials\n\nI allocated my time at several points throughout the year into growing and evolving this website with a refreshed and more visually-appealing home page, dedicated pages for information [about](/about) the project and its [features](/features), an area providing resources and help for [volunteers](/volunteer) and [code contributors](/volunteer/guide), and just this month— a [user manual](/learn) complete with an introductory tutorial series. The first video went up yesterday:\n\n<div class=\"youtube-embed aspect-16x9\">\n\t<img data-youtube-embed=\"7gjUhl_3X10\" src=\"https://static.graphite.art/content/learn/introduction/tutorial-1-vector-art-quickstart-youtube__2.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Vector Art Quickstart - Graphite, the Open Source 2D Graphics Suite\" />\n</div>\n\nThe user manual and tutorial series will continue expanding throughout the coming weeks. Additional website features including user accounts, forums, and other community features are being planned.\n\n## 2023 development progress report\n\n<p><img src=\"https://static.graphite.art/content/blog/2024-01-01-looking-back-on-2023-and-what's-next/commit-rate-graph.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Graph visualizing the number of code commits each week of 2023\" /></p>\n\n<center><em>Weekly code commit rate in 2023</em></center>\n\nStarting out the year twelve months ago, Graphite's core vector editing tools were in good shape and the node graph engine had just reached its experimental beginnings as a way of applying color filters to bitmap images. Further developing the graph engine (called Graphene) and integrating it throughout every part of the Graphite editor thus became the focus for 2023.\n\nWhile much of the team's time was spent on refactors to swap short-term placeholder code with Graphene-powered replacements, this one-step-back, two-steps-forward approach has ultimately led to exciting new capabilities for users to design procedurally-generated vector art. I'm aware of no other vector graphics editor with a node-based procedural editing workflow, so this makes me especially thrilled to release the first app of its kind with that unique and useful capability.\n\nBut 2023 wasn't only put towards refactoring code. New features were added all throughout the year and here are a few favorites.\n\n- Graphene's node infrastructure has seen steady developments which significantly upgraded the power of the node graph, its performance, and its frontend usability.\n- There are lots of new nodes that do neat things! From complex color adjustments like Vibrance to procedural building blocks like Copy to Points and noise pattern generators, there are plenty to try out (and so many more coming in 2024).\n- The (still rudimentary) Brush tool was added for drawing simple raster-based sketches.\n- Drawing custom vector shapes with the Pen and Path tools saw usability improvements with point selection, nudging/transformation, and entering exact numerical positions.\n- Number input boxes in the UI can now be dragged to update their values and have math expressions evaluated automatically for convenience. Double a value just by typing `*2` at the end, or take the square root by wrapping it within `sqrt(` and `)`.\n- A button to quickly open [pre-made sample art](https://editor.graphite.art/#demo/valley-of-spires) documents was included at the suggestion of the fine folks at Blender. This helps new users see Graphite in action instead of just opening up an overwhelmingly barren blank canvas.\n\n## Integrating the node graph\n\nWith so much to change in the goal of rewriting nearly every system with its Graphene counterpart, we had to take an incremental path so other feature development could continue without a broken editor. As succinctly as possible, this is the story of how we pulled it off— although this section gets rather technical so feel free to skip past if that's not your cup of tea.\n\nThe previous, intentionally-temporary layer system supported folders, vector shapes, text, bitmap images, and dynamic image generation.\n\n<p><img src=\"https://static.graphite.art/content/blog/2024-01-01-looking-back-on-2023-and-what's-next/migration-block-diagram-1.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Block diagram of a Graphite document before the node graph\" /></p>\n\n<center><em>Block diagram before the node graph</em></center>\n\nTo begin the incremental integration of nodes, we first added another type of layer, housing an instance of a Graphene node graph, which would supersede the other types. Then began the long process of porting all the other \"legacy\" layer types—and the viewport tools that operated on them—to become nodes. So the text layer type became a Text node managed by the Text tool, for example. This first phase was completed by April.\n\n<p><img src=\"https://static.graphite.art/content/blog/2024-01-01-looking-back-on-2023-and-what's-next/migration-block-diagram-2.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Block diagram of a Graphite document after the first phase of migration\" /></p>\n\n<center><em>Block diagram after the first phase</em></center>\n\nJust folders and graph-driven legacy layers remained, but for the second phase, these (as well as artboards which were a separate temporary system) had to be combined into a single graph. Even with graph-based legacy layers, nodes couldn't yet interact between layer graphs to create interesting procedural designs. The Graphite vision calls for a single unified graph per document where all content lives, organized by layers, folders, and artboards collectively living within that graph. So phase two began with adding another node graph instance attached to the document itself instead of any particular layer. Then the old artboard system was replaced by artboard nodes in that graph, providing white backgrounds for the pages of artwork drawn atop by layers. Next, we built a new form of layer that would live in the node graph itself to provide organizational structure, acting as both a container for artwork nodes and a folder for other layers. By August, users could edit the document graph by hand but the viewport tools, folder hierarchy, rendering pipeline, and numerous other systems all still used the legacy layers.\n\n<p><img src=\"https://static.graphite.art/content/blog/2024-01-01-looking-back-on-2023-and-what's-next/migration-block-diagram-3.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Block diagram of a Graphite document after the second phase of migration\" /></p>\n\n<center><em>Block diagram after the second phase</em></center>\n\nFor the third phase, we had to yet again port each of the viewport tools so they would operate on the unified document graph instead of the legacy layer graphs. But the incremental approach ended here— this last phase had to happen all at once, which posed a challenge for developing the editor while numerous features were fully broken pending rework. We began with a separate development branch, always kept up-to-date with the latest editor code changes, for a couple months until breakages were reduced to an acceptable level, then integrated with the main codebase in October. By tracking and burning down the list of [62 outstanding issues and regressions](https://github.com/GraphiteEditor/Graphite/issues/1394) one-by-one, in mid-December we finally reached our long-sought goal: deploying a new stable release of Graphite featuring the unified node graph! I'd like to extend an extra big thank-you to core team member [\"Hypercube\"](/about#hypercube) for the dedication and persistence in grinding through most of these.\n\n<p><img src=\"https://static.graphite.art/content/blog/2024-01-01-looking-back-on-2023-and-what's-next/migration-block-diagram-4.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Block diagram of a Graphite document now that the migration is complete\" /></p>\n\n<center><em>Block diagram of the completed integration</em></center>\n\nI followed this up over my Christmas holiday by hunting down and ripping out over 6000 lines of unused code, satisfyingly bringing Graphite's total lines of Rust down to under 50,000— just about the same number as when we began, despite adding a year's worth of functionality! Fewer lines to understand and maintain makes our jobs easier, and I'm pleasantly surprised at how efficiently the Graphite team has managed to represent the app's considerable functionality in so relatively few lines. This is a sign of good engineering practices and it makes me proud of our capable team and what we have accomplished together.\n\n## Looking ahead to 2024\n\nThe hard part is done. The unified document node graph, now that it's complete, paves the pathway towards new feature development for vector and raster editing. The Alpha 2 release series is nearly ready to become Alpha 3 with a focus towards procedural editing as the theme for the year ahead. And hopefully the road to the Beta release series, and then 1.0, is not too much further ahead.\n\nFirst, I have some high-level goals for 2024:\n\n- Cultivate a larger active community and grow the core team so we can move from a linear to an exponential pace of development\n- Begin sending quarterly email [newsletters](/#newsletter), publish these blog posts more frequently, and find a dedicated volunteer to assist in writing them while also growing Graphite's social media and internet presence\n- Announce Graphite to a wide audience and grow the daily active users by 10x or more, especially among artists\n- Reach 20,000 [stars on GitHub](https://github.com/GraphiteEditor/Graphite/stargazers) (we just passed 5000 this December)\n- Move towards a greater focus on polish, stability, performance, and learning resources for the product\n- Attain sustainable income from donors and sponsors, and maybe even apply for grants in order to hire a full-time developer\n- Build infrastructure for user accounts and prove the viability of getting revenue from hosted cloud rendering\n\nAnd then from a development perspective, I am looking forward to accomplishing these overarching objectives in 2024:\n\n- Restoring several previous features that were removed during refactors in the past year to a fully working state including snapping, folder bounding boxes, transform pivots, and vector shape boolean operations\n- Deploying GPU-based rendering by default and moving from an experimental to a production-ready hardware-accelerated compositing system using [Vello](https://github.com/linebender/vello) to unify the currently separate raster and vector pipelines\n- Designing a new vector graphics data format suitable for advanced procedural editing and rendering, plus the associated procedural workflow features\n- Remaking the Brush tool with the GPU-accelerated pipeline and the adaptive resolution system so digital painting in Graphite becomes practical\n- Implementing the Mask Mode feature for Magic Wand tool marquee selections, which will dramatically improve Graphite's utility as a raster graphics editor\n- Supporting animation capabilities (a potential stretch goal for the year)\n\n## A call for community\n\nAchieving everything listed above is ambitious, but it's ambition that has brought us to where we are today. Pulling this off will require a larger team and more resources than we've had in 2023. So if the mission we are striving for is exciting and you agree the world needs a truly great and versatile open source 2D graphics suite, we need your help!\n\n- Technically inclined developers interested in Rust, web dev, computer graphics, backend programming, compilers, machine learning, mathematics, or any of the other varied disciplines that Graphite overlaps with— we likely have a role or project for you.\n- We also have self-contained research projects involving problem-solving outside an existing codebase. One example: there are numerous industry-standard image filter effects we'd like to implement in Graphite where a volunteer could run analysis on the colors of test images to identify a suitable algorithm that gives matching results. We hope to assemble a larger Discord community of motivated people we can tap for help in solving these sorts of problems. These also make great university term projects and we've mentored several groups successfully in the past, so please reach out.\n- Technical artists with experience in procedural editing tools and engineers who enjoy designing solutions for complex problems would also be highly valuable community members when it comes to taking part in the many large, nuanced product and architecture design decisions we'll have to make this year.\n- Graphic designers and artists who put the time into using Graphite on a regular basis and helping us learn its practical strengths and weaknesses would also be valuable contributors. Helping with the creative parts of maintaining the app and assisting new users who have questions in the growing community would relieve the burden from the core team.\n- And spread the word! Create tutorial videos. Show off your creations on social media. Use it in your classroom. 2024 is the year Graphite is ready to come out of the shadows and get discovered.\n\nIf your New Year's resolution is joining an open source project, consider Graphite! We work hard to help new community contributors get up to speed with resources and guidance. We frequently hear praise that Graphite is a very inviting and supportive project from volunteers who have not had great prior experiences trying to get involved in open source. Join the Graphite [Discord server](https://discord.graphite.art) and reach out to me (@Keavon) about how you'd like to get involved.\n\n## Launching our supporter fund\n\nThere's one last big way you can help and keep Graphite from needing to turn to investors who would someday come knocking for exponential profits at the expense of you, the user. That doesn't align with my vision so I have been self-funding Graphite for the past three years. But I, alone, can't keep that going for a fourth year and beyond.\n\nWith a laser-tight focus on completing our 2023 development objectives, I haven't yet called out for donations until now. You can be the very first person to join at the level of a Supporter (starting at $10 monthly) or Sponsor (starting at $50 monthly for individuals and $100 monthly for companies). Please consider joining at one of the levels [listed here](/donate).\n\nPlease help launch Graphite towards the 2024 goal of attaining self-sufficiency so I can maintain my full-time commitment to an independent Graphite for the long haul. Thank you, it really means the world to me. ♥\n\n<a href=\"/donate\" class=\"button arrow\">Become a supporter</a>\n\n## Wrapping up\n\nThank you to our community for an incredible year. Passing [5000 stars](https://github.com/GraphiteEditor/Graphite/stargazers) on the project GitHub repository was a wonderful gift this holiday season. Everyone who has given advice, written code, and expressed enthusiasm has inspired me every day to stay focused and motivated. It was especially a pleasure and honor to visit the many people and organizations mentioned in this post throughout the past year. The reception by all has been heartwarming and I look forward to staying connected with them and a growing Graphite community in the promising year that now lies ahead. I am most of all excited for the awesome state Graphite will be in when it comes time to write this post again next year.\n\nNow go [make some awesome art](https://editor.graphite.art)!\n"
  },
  {
    "path": "website/content/blog/2024-02-22-graphite-internships-announcing-participation-in-gsoc-2024.md",
    "content": "+++\ntitle = \"Graphite internships: announcing participation in GSoC 2024\"\ndate = 2024-02-22\n[extra]\nbanner = \"https://static.graphite.art/content/blog/2024-02-22-graphite-internships-announcing-participation-in-gsoc-2024.avif\"\nbanner_png = \"https://static.graphite.art/content/blog/2024-02-22-graphite-internships-announcing-participation-in-gsoc-2024.png\"\nauthor = \"Keavon Chambers\"\nsummary = \"Join Graphite in Google Summer of Code 2024 for a unique opportunity to contribute to open-source software development in Rust and computer graphics. Get paid while learning, working on self-contained projects under experienced mentors, and help Graphite grow.\"\nreddit = \"https://www.reddit.com/r/graphite/comments/1ax3l8z/blog_post_graphite_internships_announcing/\"\ntwitter = \"https://twitter.com/GraphiteEditor/status/1760619083396165703\"\n+++\n\nCalling Rust and computer graphics developers: spend your summer contributing to open source and get paid by the Google Summer of Code program. We are excited to announce our first year participating as a mentoring organization in GSoC 2024. Interested prospective applicants should get involved right away and prepare proposals by the April 2 deadline.\n\n<!-- more -->\n\n[Graphite](/) is a new 2D graphics editor bringing a modern, nondestructive approach to creative tooling by merging both traditional raster and vector workflows with node-based compositing and procedural generation.\n\n[Google Summer of Code](https://summerofcode.withgoogle.com/), now entering its 20th year, is a worldwide program that supports students getting involved in open source software development. Like other forms of summer internships, GSoC pays a [stipend](https://developers.google.com/open-source/gsoc/help/student-stipends) for participants to work on self-contained projects with the direction and guidance of experienced mentors. The program is directed towards university students but is open to anyone 18 and older. The [timeline's](https://developers.google.com/open-source/gsoc/timeline) start date is May 27 and most projects run for 12 weeks, but there is some flexibility towards pacing and end dates.\n\nWe are looking for reasonably experienced, self-motivated, fast-learning students who are interested in Rust and/or computer graphics (and for one project, machine learning). Full [project descriptions](/volunteer/guide/student-projects/#project-idea-list) are listed and we encourage prospective applicants to reach out right away to maximize the chance of acceptance.\n\nAdditional year-round opportunities are available for student capstone or independent research projects. These are run similarly, but are also available to multi-person groups and provide academic credit instead of a stipend. To date, three were [completed](/volunteer/guide/student-projects/#successful-past-projects) successfully.\n\nIt is our hope that GSoC will help Graphite grow and provide valuable learning outcomes for participants. A win-win! If this is an opportunity that's right for you, begin [getting involved](/volunteer/guide/student-projects/).\n"
  },
  {
    "path": "website/content/blog/2024-05-09-graphite-progress-report-q1-2024.md",
    "content": "+++\ntitle = \"Graphite progress report (Q1 2024)\"\ndate = 2024-05-09\n[extra]\nbanner = \"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024__2.avif\"\nbanner_png = \"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024__2.png\"\nauthor = \"Keavon Chambers & Hypercube\"\nsummary = \"Graphite's Q1 2024 update introduces a precise snapping system and a customizable grid for enhanced design control. The update also includes improved procedural scattering with the 'Copy to Points' node, demonstrated in new demo artwork.\"\nreddit = \"https://www.reddit.com/r/graphite/comments/1coa0if/blog_post_graphite_progress_report_q1_2024/\"\ntwitter = \"https://twitter.com/GraphiteEditor/status/1788698448348266946\"\ncss = [\"/component/demo-artwork.css\"]\n+++\n\n[Graphite](/) is a new 2D vector graphics editor bringing a modern, nondestructive approach to creative workflows with node-based procedural generation. The project is currently three years into development, with a focus on streamlining the creative process for procedural vector artwork. See the [roadmap](/features#roadmap) for a more in-depth summary of the goals for 2024 and beyond.\n\n<!-- more -->\n\nStarting in 2024, we are now publishing quarterly reports to summarize the new features and improvements made to Graphite. If you missed the [2023 year in review](../looking-back-on-2023-and-what-s-next), be sure to check it out after this. We anticipate sending our first email newsletter (with more to follow roughly quarterly) in the near future as well, so be sure to [subscribe](/#newsletter) if you haven't already.\n\nOver the first three months of the year, we are delighted to have seen many contributions both from new and recurrent contributors, including substantial interest by students through [Google Summer of Code](/blog/graphite-internships-announcing-participation-in-gsoc-2024/). We would like to send a big thanks to all of the contributors who made this progress happen. If you are interested in getting involved or just following development, see the [contributor guide](/volunteer/guide) and join [our Discord](https://discord.graphite.art).\n\nAll Q1 2024 commits may be [viewed in this list](https://github.com/GraphiteEditor/Graphite/commits/master/?since=2024-01-01&until=2024-03-31) and all noteworthy changes are detailed below. As two of the major new features are the grid and snapping systems, the *Isometric Fountain* artwork shown on this blog post demonstrates what those features can achieve.\n\n<div class=\"demo-artwork\">\n\t<a href=\"https://editor.graphite.art/#demo/isometric-fountain\">\n\t\t<img src=\"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024__2.png\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Vector art of Isometric Fountain\" />\n\t</a>\n\t<p>\n\t\t<span>\n\t\t\t<em>Isometric Fountain</em>\n\t\t</span>\n\t\t<br />\n\t\t<span>\n\t\t\t<a href=\"https://editor.graphite.art/#demo/isometric-fountain\">Open this artwork</a> to<br />explore it yourself.\n\t\t</span>\n\t</p>\n</div>\n\n<!--\nEDITOR'S NOTE: The grammatical structure of each bullet point should follow the form: \"Check out our new... [Bullet point description]\"\n-->\n\n## Additions\n\n- *Copy to Points* node improvements, including randomization of scale and rotation with biasing <small>([#1540](https://github.com/GraphiteEditor/Graphite/pull/1540), [#1541](https://github.com/GraphiteEditor/Graphite/pull/1541), [commit ed82c5](https://github.com/GraphiteEditor/Graphite/commit/ed82c5f20fccd66a959334dee33351657968cdb6), [#1592](https://github.com/GraphiteEditor/Graphite/pull/1592), [commit 7e5069](https://github.com/GraphiteEditor/Graphite/commit/7e5069f638cfcc3e7af21f32eded67a005490402))</small>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024/copy-to-points-node-improvements.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024/copy-to-points-node-improvements.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Improvements to the node graph UI <small>([#1564](https://github.com/GraphiteEditor/Graphite/pull/1564), [#1568](https://github.com/GraphiteEditor/Graphite/pull/1568))</small>\n\n- Grid overlay for the canvas with customizable rectangular and isometric grid lines <small>([#1521](https://github.com/GraphiteEditor/Graphite/pull/1521), [commit 5c9d3c](https://github.com/GraphiteEditor/Graphite/commit/5c9d3c5d755e67411c110c0d5fd38d991cb6696c))</small>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024/grid-popover.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024/grid-popover.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n  \n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024/grid-demo-art.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024/grid-demo-art.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- New and improved snapping system for aligning shapes with one another and the grid <small>([#1521](https://github.com/GraphiteEditor/Graphite/pull/1521), [#1567](https://github.com/GraphiteEditor/Graphite/pull/1567), [#1547](https://github.com/GraphiteEditor/Graphite/pull/1547), [#1570](https://github.com/GraphiteEditor/Graphite/pull/1570), [#1574](https://github.com/GraphiteEditor/Graphite/pull/1574))</small>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024/snapping.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024/snapping.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- *Morph* node for tweening vector shapes <small>([#1576](https://github.com/GraphiteEditor/Graphite/pull/1576))</small>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024/morph-node.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024/morph-node.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Visualize which nodes are the source of a graph type error <small>([#1577](https://github.com/GraphiteEditor/Graphite/pull/1577))</small>\n  \n  <img src=\"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024/graph-error-visualization.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Graph error visualization screenshot\" />\n\n- Improvements to document tilting and resetting tilt <small>([commit 8eef96](https://github.com/GraphiteEditor/Graphite/commit/8eef96511e575d58a36289f3d0e30eb68098b4e7))</small>\n\n- SVG import <small>([#1579](https://github.com/GraphiteEditor/Graphite/pull/1579), [#1656](https://github.com/GraphiteEditor/Graphite/pull/1656))</small>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024/svg-import.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024/svg-import.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Re-add Select tool functionality that was lost from a code change last year <small>([#1583](https://github.com/GraphiteEditor/Graphite/pull/1583))</small>\n\n- *Poisson-Disk Points* node for sampling randomly distributed points in a shape, with Red Dress demo artwork <small>([#1586](https://github.com/GraphiteEditor/Graphite/pull/1586), [commit 12e16b](https://github.com/GraphiteEditor/Graphite/commit/12e16b9a4efe40fcf779c34f83d3df8b3e3542f3), [#1590](https://github.com/GraphiteEditor/Graphite/pull/1590), [#1596](https://github.com/GraphiteEditor/Graphite/pull/1596))</small>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024/poisson-disk-points-node-demo-art.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024/poisson-disk-points-node-demo-art.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Pen tool point-by-point undo while drawing without wiping out the full in-progress shape <small>([#1587](https://github.com/GraphiteEditor/Graphite/pull/1587), [#1597](https://github.com/GraphiteEditor/Graphite/pull/1597))</small>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024/pen-tool-point-by-point-undo.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024/pen-tool-point-by-point-undo.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Freehand tool support for extending the endpoints of open paths <small>([#1594](https://github.com/GraphiteEditor/Graphite/pull/1594), [#1623](https://github.com/GraphiteEditor/Graphite/pull/1623))</small>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024/freehand-tool-endpoint-extension.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024/freehand-tool-endpoint-extension.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Path tool support for breaking a closed shape into an open path by deleting (not dissolving) a point <small>([#1593](https://github.com/GraphiteEditor/Graphite/pull/1593))</small>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024/point-deletion-breaking-closed-shapes.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024/point-deletion-breaking-closed-shapes.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Path tool insertion of a point on a path segment by sliding to the desired spot <small>([#1581](https://github.com/GraphiteEditor/Graphite/pull/1581))</small>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024/sliding-point-insertion.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024/sliding-point-insertion.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Box-based drag selection in the node graph <small>([#1616](https://github.com/GraphiteEditor/Graphite/pull/1616))</small>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024/box-selection-in-node-graph.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024/box-selection-in-node-graph.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Auto-save the document every 30 seconds <small>([#1580](https://github.com/GraphiteEditor/Graphite/pull/1580))</small>\n\n- Auto-panning when drawing with each interactive tool when the user's pointer extends past the edge of the viewport <small>([#1625](https://github.com/GraphiteEditor/Graphite/pull/1625), [#1652](https://github.com/GraphiteEditor/Graphite/pull/1652), [#1682](https://github.com/GraphiteEditor/Graphite/pull/1682), [#1690](https://github.com/GraphiteEditor/Graphite/pull/1690))</small>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024/auto-panning-in-viewport.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024/auto-panning-in-viewport.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Launch of the Alpha 3 release series in February <small>([commit f02dd5](https://github.com/GraphiteEditor/Graphite/commit/f02dd5c0f625b25bf3510ba0e9839ca182d930e4))</small>\n  \n  <img src=\"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024/alpha-3-about-graphite.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Alpha 3 release series in the 'About Graphite' menu screenshot\" />\n\n- Search functionality by typing in menu lists <small>([#1499](https://github.com/GraphiteEditor/Graphite/pull/1499))</small>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024/search-in-menu-lists.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024/search-in-menu-lists.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Improvements to the dynamic input hints of several tools <small>([commit 9f8466](https://github.com/GraphiteEditor/Graphite/commit/9f84661facd545bfdfeaa5d37038abeacc00ee08), [#1667](https://github.com/GraphiteEditor/Graphite/pull/1667), [#1670](https://github.com/GraphiteEditor/Graphite/pull/1670))</small>\n\n- *Solidify Stroke* node for converting a stroke into a filled shape <small>([#1650](https://github.com/GraphiteEditor/Graphite/pull/1650))</small>\n\n- Path tool point selection checkbox for locking an anchor point's handles as colinear <small>([commit 5bca93](https://github.com/GraphiteEditor/Graphite/commit/5bca931813e456e2f6035844c21e77ee590b7728))</small>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024/colinear-handles-checkbox.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024/colinear-handles-checkbox.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n## Fixes\n\n- *Sample Points* node performance issues and bug with open paths <small>([#1542](https://github.com/GraphiteEditor/Graphite/pull/1542), [commit b3e4ca](https://github.com/GraphiteEditor/Graphite/commit/b3e4caec1aa4d7b702ac36638b968cf165780149), [commit 93aa10](https://github.com/GraphiteEditor/Graphite/commit/93aa10a76f96946314d5c5787418522423a76544), [#1546](https://github.com/GraphiteEditor/Graphite/pull/1546))</small>\n\n- Some numerical node data incompatible with others <small>([#1543](https://github.com/GraphiteEditor/Graphite/pull/1543), [commit c14c6f](https://github.com/GraphiteEditor/Graphite/commit/c14c6fbe9345be91d04825fe8364e6639430b5e5), [#1618](https://github.com/GraphiteEditor/Graphite/pull/1618))</small>\n\n- Incorrect placement when pasting an image <small>([commit cd61da](https://github.com/GraphiteEditor/Graphite/commit/cd61daf869780e1302de1a34bca7b79485ca69c3))</small>\n\n- Unable to select upside-down (negative scale) layers <small>([#1560](https://github.com/GraphiteEditor/Graphite/pull/1560))</small>\n\n- Memory leak in a type inference hashmap <small>([#1566](https://github.com/GraphiteEditor/Graphite/pull/1566))</small>\n\n- Non-filled shapes are drawn missing the solid color field from their *Fill* node <small>([#1572](https://github.com/GraphiteEditor/Graphite/pull/1572))</small>\n\n- *Circular Repeat* node crash when it has no input connection <small>([#1571](https://github.com/GraphiteEditor/Graphite/pull/1571))</small>\n\n- Safari rendering bug that occurs with Graphite's floating menus <small>([commit b4dccb](https://github.com/GraphiteEditor/Graphite/commit/b4dccb865540e25a6d859e5284a50a36e361d5ee))</small>\n\n- Performance issue affecting Windows users where every render occurred thrice <small>([commit a7bf6e](https://github.com/GraphiteEditor/Graphite/commit/a7bf6e24599fc3d7dc51699f916ba049758a2081))</small>\n\n- Performance issue with nonfunctional caching of images <small>([#1595](https://github.com/GraphiteEditor/Graphite/pull/1595))</small>\n\n- Numerous history steps created while dragging an input widget every frame <small>([#1584](https://github.com/GraphiteEditor/Graphite/pull/1584), [#1598](https://github.com/GraphiteEditor/Graphite/pull/1598))</small>\n\n- Ugly anti-aliasing in overlays from non-alignment with the pixel grid <small>([#1603](https://github.com/GraphiteEditor/Graphite/pull/1603))</small>\n\n- Dragging nodes in the graph along only one axis causes them to snap back <small>([#1619](https://github.com/GraphiteEditor/Graphite/pull/1619))</small>\n\n- Path tool point coordinates using the wrong coordinate system <small>([#1626](https://github.com/GraphiteEditor/Graphite/pull/1626))</small>\n\n- Layer panel grouping causing unintended reordering and loss of names <small>([#1627](https://github.com/GraphiteEditor/Graphite/pull/1627), [#1645](https://github.com/GraphiteEditor/Graphite/pull/1645), [#1672](https://github.com/GraphiteEditor/Graphite/pull/1672), [#1678](https://github.com/GraphiteEditor/Graphite/pull/1678), [#1637](https://github.com/GraphiteEditor/Graphite/pull/1637))</small>\n\n- Keyboard shortcut modifier keys displayed in the UI with the wrong order <small>([commit 4405e0](https://github.com/GraphiteEditor/Graphite/commit/4405e01f5595c76ff8c1cdb7e6ebf752ab53943c))</small>\n\n- Polygon tool stars and convex polygons drawn with an undesired tilt <small>([#1640](https://github.com/GraphiteEditor/Graphite/pull/1640))</small>\n\n- Dropdown menus not navigable with with keyboard arrow keys <small>([#1630](https://github.com/GraphiteEditor/Graphite/pull/1630), [#1649](https://github.com/GraphiteEditor/Graphite/pull/1649))</small>\n\n- Changes to a tool's options only applying after a subsequent usage <small>([#1646](https://github.com/GraphiteEditor/Graphite/pull/1646))</small>\n\n- Orphaned child layers left behind when a group is ungrouped or deleted <small>([#1655](https://github.com/GraphiteEditor/Graphite/pull/1655))</small>\n\n- Some tools lacking a cancelable interaction by right clicking or hitting the Escape key <small>([#1658](https://github.com/GraphiteEditor/Graphite/pull/1658), [#1664](https://github.com/GraphiteEditor/Graphite/pull/1664), [#1666](https://github.com/GraphiteEditor/Graphite/pull/1666))</small>\n\n- Several related issues related to extraneous undo/redo history steps <small>([#1660](https://github.com/GraphiteEditor/Graphite/pull/1660), [#1668](https://github.com/GraphiteEditor/Graphite/pull/1668), [#1675](https://github.com/GraphiteEditor/Graphite/pull/1675))</small>\n\n- Node graph not updated when switching tabs to a different document <small>([#1691](https://github.com/GraphiteEditor/Graphite/pull/1691))</small>\n\n- Artboard deletion causes its child artwork to also get deleted <small>([#1651](https://github.com/GraphiteEditor/Graphite/pull/1651))</small>\n\n- Path tool's point dragging gets offset when the viewport is panned <small>([#1693](https://github.com/GraphiteEditor/Graphite/pull/1693))</small>\n\n- Incorrect scale-nudging behavior when multiple layers are selected <small>([#1699](https://github.com/GraphiteEditor/Graphite/pull/1699))</small>\n\n- Copy-pasted layers not preserving their hidden/visible state <small>([#1698](https://github.com/GraphiteEditor/Graphite/pull/1698))</small>\n\n- Non-editability of hidden layers that are selected <small>([#1697](https://github.com/GraphiteEditor/Graphite/pull/1697))</small>\n\n## Internal\n\n- Several large refactors and code cleanups <small>([#1565](https://github.com/GraphiteEditor/Graphite/pull/1565), [#1582](https://github.com/GraphiteEditor/Graphite/pull/1582), [#1620](https://github.com/GraphiteEditor/Graphite/pull/1620), [#1695](https://github.com/GraphiteEditor/Graphite/pull/1695), [#1708](https://github.com/GraphiteEditor/Graphite/pull/1708))</small>\n\n- Refactor for the vector format to begin being based around a concept of attributes <small>([#1624](https://github.com/GraphiteEditor/Graphite/pull/1624))</small>\n\n- Preconfigure dev containers for easy containerized development environment setup <small>([commit 99c199](https://github.com/GraphiteEditor/Graphite/commit/99c199a8f64a3557e21f5dc002fbcfb789c40632), [#1636](https://github.com/GraphiteEditor/Graphite/pull/1636))</small>\n"
  },
  {
    "path": "website/content/blog/2024-07-31-graphite-progress-report-q2-2024.md",
    "content": "+++\ntitle = \"Graphite progress report (Q2 2024)\"\ndate = 2024-07-31\n[extra]\nbanner = \"https://static.graphite.art/content/blog/2024-07-31-graphite-progress-report-q2-2024.avif\"\nbanner_png = \"https://static.graphite.art/content/blog/2024-07-31-graphite-progress-report-q2-2024.png\"\nauthor = \"Keavon Chambers & Hypercube\"\nsummary = \"Graphite's Q2 2024 update introduces boolean path operations, a new gradient picker, layer locking, and more improvements.\"\nreddit = \"https://www.reddit.com/r/graphite/comments/1ei9ps2/blog_post_graphite_progress_report_q2_2024/\"\ntwitter = \"https://x.com/GraphiteEditor/status/1819360794028462569\"\ncss = [\"/component/demo-artwork.css\"]\n+++\n\n[Graphite](/), a new open source 2D procedural graphics editor, has spent April–June introducing **boolean path operations, a new gradient picker, layer locking**, and more improvements.\n\nOverall, editor functionality has been shaping up and becoming an all around useful tool suite, with notable reductions in rough edges for the vector graphics workflow (our initial focus). Raster and raw photo processing workflows are also now in-development by our [Google Summer of Code](/blog/graphite-internships-announcing-participation-in-gsoc-2024/) student interns. Node graph quality-of-life improvements centered around tidy node organization are also the focus of the summer work that's underway. These projects are detailed below.\n\n<!-- more -->\n\nThis is the second of our quarterly progress report blog posts. If you missed the [first one](../graphite-progress-report-q1-2024), be sure to check it out as well. If you'd like to help speed up future progress, please consider [getting involved](/volunteer) with code, QA/bug testing, or art/marketing projects. [Donations](/donate) are also valued, as are [stars on GitHub](https://github.com/GraphiteEditor/Graphite). Follow along and partake in our [Discord community](https://discord.graphite.art), too.\n\nAll Q2 2024 commits may be [viewed in this list](https://github.com/GraphiteEditor/Graphite/commits/master/?since=2024-04-01&until=2024-06-30) and all noteworthy changes are detailed below. To showcase the much anticipated introduction of boolean path operations, the new *Painted Dreams* artwork shown here extensively utilizes nondestructive booleans.\n\n<div class=\"demo-artwork\">\n\t<a href=\"https://editor.graphite.art/#demo/painted-dreams\">\n\t\t<img src=\"https://static.graphite.art/content/blog/2024-07-31-graphite-progress-report-q2-2024.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Vector art of Painted Dreams\" />\n\t</a>\n\t<p>\n\t\t<span>\n\t\t\t<em>Painted Dreams</em>\n\t\t</span>\n\t\t<br />\n\t\t<span>\n\t\t\t<a href=\"https://editor.graphite.art/#demo/painted-dreams\">Open this artwork</a> to<br />explore it yourself.\n\t\t</span>\n\t</p>\n</div>\n\n## Additions\n\n<!--\nEDITOR'S NOTE: The grammatical structure of each bullet point should follow the form: \"Check out our new... [Bullet point description]\"\n-->\n\n- Feature for locking layers from being selected in the viewport <small>([#1702](https://github.com/GraphiteEditor/Graphite/pull/1702))</small>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-07-31-graphite-progress-report-q2-2024/layer-locking.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-07-31-graphite-progress-report-q2-2024/layer-locking.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Fine-grained choices in the Snapping options popover <small>([#1730](https://github.com/GraphiteEditor/Graphite/pull/1730))</small>\n  \n  <img src=\"https://static.graphite.art/content/blog/2024-07-31-graphite-progress-report-q2-2024/snapping-options.png\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Snapping choices popover menu\" />\n\n- Corner rounding added to the *Rectangle* node <small>([#1648](https://github.com/GraphiteEditor/Graphite/pull/1648))</small>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-07-31-graphite-progress-report-q2-2024/rectangle-corner-rounding.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-07-31-graphite-progress-report-q2-2024/rectangle-corner-rounding.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Rotation added to the *Repeat* node <small>([commit 72ba4dd](https://github.com/GraphiteEditor/Graphite/commit/72ba4ddfe421c0e17930ad1c2be85be2c69e04ea))</small>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-07-31-graphite-progress-report-q2-2024/repeat-node-rotation.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-07-31-graphite-progress-report-q2-2024/repeat-node-rotation.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Visibility and deletion buttons added to node sections in the Properties panel <small>([commit 07fd2c2](https://github.com/GraphiteEditor/Graphite/commit/07fd2c27827e4a91ad238d790d41396a33ef2389))</small>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-07-31-graphite-progress-report-q2-2024/hide-delete-properties-sections.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-07-31-graphite-progress-report-q2-2024/hide-delete-properties-sections.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Grid color customization and dotted appearance choices <small>([#1743](https://github.com/GraphiteEditor/Graphite/pull/1743))</small>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-07-31-graphite-progress-report-q2-2024/grid-coloration.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-07-31-graphite-progress-report-q2-2024/grid-coloration.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Demo artwork, *Isometric Fountain*, featured in the [last blog post](../graphite-progress-report-q1-2024) <small>([commit 6b0822d](https://github.com/GraphiteEditor/Graphite/commit/6b0822d31890b6699c4533c3e828da0e9e8c9490))</small>\n  \n  <div class=\"demo-artwork\" style=\"justify-content: left\">\n  \t<a href=\"https://editor.graphite.art/#demo/isometric-fountain\">\n  \t\t<img src=\"https://static.graphite.art/content/blog/2024-05-09-graphite-progress-report-q1-2024__2.png\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Vector art of Isometric Fountain\" />\n  \t</a>\n  \t<p>\n  \t\t<span>\n  \t\t\t<em>Isometric Fountain</em>\n  \t\t</span>\n  \t\t<br />\n  \t\t<span>\n  \t\t\t<a href=\"https://editor.graphite.art/#demo/isometric-fountain\">Open this artwork</a> to<br />explore it yourself.\n  \t\t</span>\n  \t</p>\n  </div>\n\n- *Area* and *Centroid* nodes which calculate a shape's interior size and center of mass, respectively <small>([#1729](https://github.com/GraphiteEditor/Graphite/pull/1729), [#1749](https://github.com/GraphiteEditor/Graphite/pull/1749))</small> \n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-07-31-graphite-progress-report-q2-2024/area-centroid-nodes.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-07-31-graphite-progress-report-q2-2024/area-centroid-nodes.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- *Boolean Operation* node for combining two shape paths with a *Union*, *Subtract Front*, *Subtract Back*, *Intersect*, or *Difference* mode of cutting and combining <small>([#1759](https://github.com/GraphiteEditor/Graphite/pull/1759))</small>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-07-31-graphite-progress-report-q2-2024/boolean-ops-1.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-07-31-graphite-progress-report-q2-2024/boolean-ops-1.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-07-31-graphite-progress-report-q2-2024/boolean-ops-2.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-07-31-graphite-progress-report-q2-2024/boolean-ops-2.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Gradient picker <small>([#1778](https://github.com/GraphiteEditor/Graphite/pull/1778))</small>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-07-31-graphite-progress-report-q2-2024/gradient-picker.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-07-31-graphite-progress-report-q2-2024/gradient-picker.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Labels for artboard names displayed in the viewport <small>([#1795](https://github.com/GraphiteEditor/Graphite/pull/1795))</small>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-07-31-graphite-progress-report-q2-2024/artboard-names.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-07-31-graphite-progress-report-q2-2024/artboard-names.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- [Dennis Kobert](https://github.com/truedoctor)'s Google Summer of Code project has begun adding raster editing infrastructure centered around GPU acceleration — to date this has included:\n\n  - *Rasterize* node for converting graphical content (like vector art) into an image within a chosen area and resolution scale <small>([#1755](https://github.com/GraphiteEditor/Graphite/pull/1755))</small>\n    <div class=\"video-background\">\n    \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n    \t\t<source src=\"https://static.graphite.art/content/blog/2024-07-31-graphite-progress-report-q2-2024/rasterize-node.webm\" type=\"video/webm\" />\n    \t\t<source src=\"https://static.graphite.art/content/blog/2024-07-31-graphite-progress-report-q2-2024/rasterize-node.mp4\" type=\"video/mp4\" />\n    \t</video>\n    </div>\n\n  - [Weekly updates](https://github.com/GraphiteEditor/Graphite/discussions/1773) are being posted while the project is ongoing\n\n- [Elbert Ronnie](https://github.com/elbertronnie)'s Google Summer of Code project has begun building a library for decoding raw image formats, with the goal of supporting photo processing in Graphite once the project is completed — to date this has included:\n\n  - Initial code foundations including a basic custom TIFF decoder <small>([#1757](https://github.com/GraphiteEditor/Graphite/pull/1757))</small>\n\n  - Initial decoder for Sony ARW data encoding formats <small>([#1775](https://github.com/GraphiteEditor/Graphite/pull/1775), [#1776](https://github.com/GraphiteEditor/Graphite/pull/1776))</small>\n\n  - [Weekly updates](https://github.com/GraphiteEditor/Graphite/discussions/1771) are being posted while the project is ongoing\n\n- [Adam Gerhant](https://github.com/adamgerhant)'s Google Summer of Code project has begun upgrading the node graph UI capabilities, quality-of-life features, and automatic layout management — to date this has included:\n\n  - Generalization of layers to support the concept of vertically stackable adjustment layers <small>([#1712](https://github.com/GraphiteEditor/Graphite/pull/1712), [#1738](https://github.com/GraphiteEditor/Graphite/pull/1738), [commit 244c8ad](https://github.com/GraphiteEditor/Graphite/commit/244c8ad10ad79c9ca4fcdb21260c5d5dc60b3a21), [#1763](https://github.com/GraphiteEditor/Graphite/pull/1763), [#1739](https://github.com/GraphiteEditor/Graphite/pull/1739), [#1748](https://github.com/GraphiteEditor/Graphite/pull/1748))</small>\n    <div class=\"video-background\">\n    \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n    \t\t<source src=\"https://static.graphite.art/content/blog/2024-07-31-graphite-progress-report-q2-2024/stackable-layers.webm\" type=\"video/webm\" />\n    \t\t<source src=\"https://static.graphite.art/content/blog/2024-07-31-graphite-progress-report-q2-2024/stackable-layers.mp4\" type=\"video/mp4\" />\n    \t</video>\n    </div>\n\n  - Support for viewing and editing subgraphs by double-clicking nodes with internal node networks <small>([#1750](https://github.com/GraphiteEditor/Graphite/pull/1750))</small>\n    <div class=\"video-background\">\n    \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n    \t\t<source src=\"https://static.graphite.art/content/blog/2024-07-31-graphite-progress-report-q2-2024/subgraph-editing.webm\" type=\"video/webm\" />\n    \t\t<source src=\"https://static.graphite.art/content/blog/2024-07-31-graphite-progress-report-q2-2024/subgraph-editing.mp4\" type=\"video/mp4\" />\n    \t</video>\n    </div>\n\n  - Extended support for navigating the graph just like the viewport (with autopanning on dragging near edges, working scrollbars, and zooming shortcuts) by migrating interaction code to the backend <small>([#1768](https://github.com/GraphiteEditor/Graphite/pull/1768), [#1790](https://github.com/GraphiteEditor/Graphite/pull/1790))</small>\n    <div class=\"video-background\">\n    \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n    \t\t<source src=\"https://static.graphite.art/content/blog/2024-07-31-graphite-progress-report-q2-2024/graph-navigation.webm\" type=\"video/webm\" />\n    \t\t<source src=\"https://static.graphite.art/content/blog/2024-07-31-graphite-progress-report-q2-2024/graph-navigation.mp4\" type=\"video/mp4\" />\n    \t</video>\n    </div>\n\n- [Weekly updates](https://github.com/GraphiteEditor/Graphite/discussions/1769) are being posted while the project is ongoing\n\n## Fixes\n\n- Polished up design for the Layers panel <small>([commit 938a688](https://github.com/GraphiteEditor/Graphite/commit/938a688fa08511ed4a5f3c3d5bcfa26f4bd1eb47))</small>\n\n- Inclusion of alpha in the *Extract Channel* node and removal of the now-redundant *Extract Alpha* node <small>([#1731](https://github.com/GraphiteEditor/Graphite/pull/1731))</small>\n\n- Fixed interactive outlining of layers within the Select tool's box selection <small>([#1727](https://github.com/GraphiteEditor/Graphite/pull/1727))</small>\n\n- Insertion of duplicated layers directly above their selected source layers <small>([#1726](https://github.com/GraphiteEditor/Graphite/pull/1726))</small>\n\n- Crash fixes <small>([#1701](https://github.com/GraphiteEditor/Graphite/pull/1701), [#1754](https://github.com/GraphiteEditor/Graphite/pull/1754), [#1770](https://github.com/GraphiteEditor/Graphite/pull/1770))</small>\n\n- Small bug fixes and polish for viewport tilt and zoom navigation <small>([commit 597c96a](https://github.com/GraphiteEditor/Graphite/commit/597c96a7db06fe44dbd4dc170511c011c7239073))</small>\n\n- Regression fix for the Select tool's shallow selection mode <small>([#1725](https://github.com/GraphiteEditor/Graphite/pull/1725))</small>\n\n- Previewable dropdown menu states and color button widget undo/redo history improvements <small>([#1598](https://github.com/GraphiteEditor/Graphite/pull/1598))</small>\n\n- Persisting of overlays/snapping/grid state in saved documents and toggling them with hotkeys <small>([commit 7845302](https://github.com/GraphiteEditor/Graphite/commit/7845302c50705332711b6b18b4bdfe28a2f3c306))</small>\n\n- Improved robustness to the backwards compatability of the document format (still offered on a best-effort basis) <small>([commit de84e39](https://github.com/GraphiteEditor/Graphite/commit/de84e39c4ec6c191f73702a51be0a5ec9c662642))</small>\n\n- Visual bug fixes for shape drawing and nondeterministic one-pixel viewport shifting when recentering with <kbd>Ctrl</kbd><kbd>0</kbd> <small>([#1751](https://github.com/GraphiteEditor/Graphite/pull/1751))</small>\n\n- Fixed bug where Path tool point insertion often wasn't working <small>([commit cf01f52](https://github.com/GraphiteEditor/Graphite/commit/cf01f522a8b57bfd625f15a10ae6b2812ed4c0a4))</small>\n\n- Fixed bugs where the *Poisson-Disk Points*, *Repeat*, *Circular Repeat*, and *Bounding Box* node input shapes weren't using their transform <small>([#1784](https://github.com/GraphiteEditor/Graphite/pull/1784), [#1792](https://github.com/GraphiteEditor/Graphite/pull/1792))</small>\n\n- Improvements to the Path tool's point selection behavior so clicking a selected anchor deselects all other selected points <small>([#1782](https://github.com/GraphiteEditor/Graphite/pull/1782))</small>\n\n- Regression fix for being unable to arrange layers with no artboard present <small>([#1786](https://github.com/GraphiteEditor/Graphite/pull/1786))</small>\n\n## Internal\n\n- Removal of the editor \"instances\" concept, allowing for cleaned up JS interop code <small>([commit 19eb6ce](https://github.com/GraphiteEditor/Graphite/commit/19eb6ce0ab10065ec6acd6e49edd2f072729fc77), [commit 1ce3d59](https://github.com/GraphiteEditor/Graphite/commit/1ce3d59e0f39e6733c7fa170af00ef59fd10ffd9))</small>\n\n- Loosened type system in Graphene to allow contravariant function arguments <small>([#1740](https://github.com/GraphiteEditor/Graphite/pull/1740), [#1746](https://github.com/GraphiteEditor/Graphite/pull/1746))</small>\n\n## Website and documentation\n\n- [Roadmap](/features#roadmap) updates featuring new features and icons <small>([commit 6a1a145](https://github.com/GraphiteEditor/Graphite/commit/6a1a145d190887f65f1f851282bd86c8836f85a7), [commit e4d3faa](https://github.com/GraphiteEditor/Graphite/commit/e4d3faa52af42173eaf67b2dc5c2df5d6e6f23a8))</small>\n\n- Initial [Graphene docs](/volunteer/guide/graphene) included in the contributor guide <small>([#1686](https://github.com/GraphiteEditor/Graphite/pull/1686))</small>\n\n- Website meta tags fixed to provide better SEO and page previews for links posted on social media <small>([commit d0c493c](https://github.com/GraphiteEditor/Graphite/commit/d0c493cdb6867763d507a24f12d92f2617385114))</small>\n\n- Redesigned website layout featuring a narrower max width and better typography <small>([#1753](https://github.com/GraphiteEditor/Graphite/pull/1753))</small>\n"
  },
  {
    "path": "website/content/blog/2024-10-15-graphite-progress-report-q3-2024.md",
    "content": "+++\ntitle = \"Graphite progress report (Q3 2024)\"\ndate = 2024-10-15\n[extra]\nbanner = \"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024.avif\"\nbanner_png = \"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024.png\"\nauthor = \"Keavon Chambers & Hypercube\"\nsummary = \"Graphite's Q3 2024 update introduces improvements to performance, node graph organization, nondestructive path editing, a new render engine, and more helpful nodes.\"\nreddit = \"https://www.reddit.com/r/graphite/comments/1g4h6ya/blog_post_graphite_progress_report_q3_2024/\"\ntwitter = \"https://x.com/GraphiteEditor/status/1846283664562573344\"\ncss = [\"/component/demo-artwork.css\"]\n+++\n\n[Graphite](/), a new open source 2D procedural graphics editor, has spent July–September building major improvements to **performance, node graph organization, nondestructive path editing, a new render engine, and more helpful nodes**, amongst over 100 other features and fixes.\n\nThis has been the most productive quarter yet in the project's three-year history. Most of our [Google Summer of Code](/blog/graphite-internships-announcing-participation-in-gsoc-2024/) student intern projects have already reached their goals, adding to the goodies included in this progress report. All Q3 2024 commits may be [viewed in this list](https://github.com/GraphiteEditor/Graphite/commits/master/?since=2024-07-01&until=2024-09-30) and all noteworthy changes are detailed below.\n\n<!-- more -->\n\nThis is the third in our series of quarterly progress reports. If you missed the [first](../graphite-progress-report-q1-2024) or [second](../graphite-progress-report-q2-2024) ones, be sure to check them out as well. If you'd like to help speed up future progress, please consider [getting involved](/volunteer) with code, QA/bug testing, or art/marketing projects. [Donations](/donate) are also valued, as are [stars on GitHub](https://github.com/GraphiteEditor/Graphite). Follow along and partake in our [Discord community](https://discord.graphite.art), too.\n\nThe new *Changing Seasons* artwork shown here showcases some of the recently introduced nodes in this update. And it animates! Give it a try yourself by opening the artwork and dragging the percentage slider to morph from oak to maple leaves as the colors change.\n\n<div class=\"demo-artwork\">\n\t<a href=\"https://editor.graphite.art/#demo/changing-seasons\">\n\t\t<img src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Vector art of Changing Seasons\" />\n\t</a>\n\t<p>\n\t\t<span>\n\t\t\t<em>Changing Seasons</em>\n\t\t</span>\n\t\t<br />\n\t\t<span>\n\t\t\t<a href=\"https://editor.graphite.art/#demo/changing-seasons\">Open this artwork</a> to<br />explore it yourself.\n\t\t</span>\n\t</p>\n</div>\n\n## Additions\n\n<!--\nEDITOR'S NOTE: The grammatical structure of each bullet point should follow the form: \"Check out our new... [Bullet point description]\"\n-->\n\n- Simplified *Boolean Operation* node that combines the best parts of the two previous boolean node versions, letting users now convert from a stack of shape layers (of any size!) into a single resulting vector shape that's more versatile to use in both the node graph and Layers panel <small>([#1813](https://github.com/GraphiteEditor/Graphite/pull/1813), [#1933](https://github.com/GraphiteEditor/Graphite/pull/1933))</small>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/stack-based-booleans.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/stack-based-booleans.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- *Path* node which enables nondestructive vector editing at stages along the geometry calculation pipeline, even after a procedural shape (like a star) defines the parameters that can be tweaked anytime <small>([#1676](https://github.com/GraphiteEditor/Graphite/pull/1676))</small>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/path-node.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/path-node.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Additional snapping criteria for alignment and distribution between layers <small>([#1793](https://github.com/GraphiteEditor/Graphite/pull/1793))</small>\n  \n  <img src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/new-snapping-modes.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Snapping choices popover menu\" />\n\n- Snapping now included with the Artboard tool for precise alignment with other artboards <small>([#1734](https://github.com/GraphiteEditor/Graphite/pull/1734))</small>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/artboard-snapping.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/artboard-snapping.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Persistence of the last-chosen settings each time the Export dialog is opened (so it's not always reset to defaults), plus artboard names are now correctly written in the Bounds dropdown <small>([commit 4d3e459](https://github.com/GraphiteEditor/Graphite/commit/4d3e459f1f52f04be81727fe5946010bcabeb1eb))</small>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/export-settings-persistence.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/export-settings-persistence.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Demo artwork, *Painted Dreams*, featured in the [last blog post](../graphite-progress-report-q2-2024) <small>([commit 80cf486](https://github.com/GraphiteEditor/Graphite/commit/80cf486790d9300510904622924162a9b4017b1e))</small>\n\n  <div class=\"demo-artwork\" style=\"justify-content: left\">\n  \t<a href=\"https://editor.graphite.art/#demo/painted-dreams\">\n  \t\t<img src=\"https://static.graphite.art/content/blog/2024-07-31-graphite-progress-report-q2-2024.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Vector art of Painted Dreams\" />\n  \t</a>\n  \t<p>\n  \t\t<span>\n  \t\t\t<em>Painted Dreams</em>\n  \t\t</span>\n  \t\t<br />\n  \t\t<span>\n  \t\t\t<a href=\"https://editor.graphite.art/#demo/painted-dreams\">Open this artwork</a> to<br />explore it yourself.\n  \t\t</span>\n  \t</p>\n  </div>\n\n- Quick measurement feature that is shown by holding <kbd>Alt</kbd> to momentarily see the distance between the selected and hovered shapes <small>([#1894](https://github.com/GraphiteEditor/Graphite/pull/1894))</small>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/quick-measurement.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/quick-measurement.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Support for drag-and-drop and copy-paste of files more widely throughout the editor <small>([#2012](https://github.com/GraphiteEditor/Graphite/pull/2012))</small>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/drag-and-drop-import.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/drag-and-drop-import.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Crash mitigation preventing node faults from taking down the whole editor, working to isolate crashes within the graph so the user can undo the change and save the file (however, reloading the editor is required to stabilize it after this occurs) <small>([#1846](https://github.com/GraphiteEditor/Graphite/pull/1846))</small>\n  \n  <img src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/graph-crash-error.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Graph execution crash recovery message\" />\n\n- Seed parameters now included in all nodes with random generation <small>([commit c39032a](https://github.com/GraphiteEditor/Graphite/commit/c39032ab548d4f33d18a399c64b77d3d6f4ecd45))</small>\n  \n  <img src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/node-seed-parameters.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Three nodes and their new seed parameters\" />\n\n- *Percentage Value* node for easy sliding between the numbers 0 and 100 <small>([commit d7546fb](https://github.com/GraphiteEditor/Graphite/commit/d7546fb18310490d5ce10d406e7a3faaf1ae98fe))</small>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/percentage-value-node.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/percentage-value-node.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- *Gradient Map* node for recoloring grayscale color values to corresponding colors along a chosen gradient <small>([commit 501b562](https://github.com/GraphiteEditor/Graphite/commit/501b562d0f17b91a54a14c83a4a0f90a1e03e369))</small>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/gradient-map-node.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/gradient-map-node.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- *Assign Colors* node for replacing the fill or stroke colors of individual paths in a group of vector elements using choices uniquely sampled along a gradient <small>([#1938](https://github.com/GraphiteEditor/Graphite/pull/1938))</small>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/assign-colors-node.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/assign-colors-node.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- *Dehaze* node for reducing the appearance of the atmospheric haze or fog in photographs <small>([#1882](https://github.com/GraphiteEditor/Graphite/pull/1882))</small>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/dehaze-node.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/dehaze-node.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Node catalog reorganized with the naming and categorization of nodes given improved consistency <small>([commit e647ca9](https://github.com/GraphiteEditor/Graphite/commit/e647ca9f91a5e823137122126fe9e980f65d62ea))</small>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/reorganized-node-catalog.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/reorganized-node-catalog.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- *Noise Pattern* node updated to generate resolution-aware coherent noise, meaning it extends forever in all directions and zoom depths <small>([#1909](https://github.com/GraphiteEditor/Graphite/pull/1909))</small>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/resolution-aware-noise.webm\" type=\"video/webm\" />\n  \t\t<source src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/resolution-aware-noise.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- [Dennis Kobert](https://github.com/truedoctor)'s Google Summer of Code project has concluded, adding many improvements to performance and internal improvements listed in the following sections, but also:\n\n  - Alternate render engine using [Vello](https://github.com/linebender/vello) that brings increased code simplicity and performance (especially with raster layers), but currently only works in browsers with [WebGPU support](https://developer.mozilla.org/en-US/docs/Web/API/WebGPU_API#browser_compatibility) and must be enabled via *File* > *Preferences* <small>([#1802](https://github.com/GraphiteEditor/Graphite/pull/1802), [#1865](https://github.com/GraphiteEditor/Graphite/pull/1865), [#1844](https://github.com/GraphiteEditor/Graphite/pull/1844), [#1871](https://github.com/GraphiteEditor/Graphite/pull/1871), [#1874](https://github.com/GraphiteEditor/Graphite/pull/1874), [#1875](https://github.com/GraphiteEditor/Graphite/pull/1875), [#1899](https://github.com/GraphiteEditor/Graphite/pull/1899), [#1905](https://github.com/GraphiteEditor/Graphite/pull/1905), [#1900](https://github.com/GraphiteEditor/Graphite/pull/1900), [#1907](https://github.com/GraphiteEditor/Graphite/pull/1907), [#1902](https://github.com/GraphiteEditor/Graphite/pull/1902), [#1897](https://github.com/GraphiteEditor/Graphite/pull/1897), [#1915](https://github.com/GraphiteEditor/Graphite/pull/1915), [#1996](https://github.com/GraphiteEditor/Graphite/pull/1996))</small>\n    \n    <img src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/vello-preference.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Editor preferences menu with Vello setting\" />\n\n  - The [final report and weekly updates](https://github.com/GraphiteEditor/Graphite/discussions/1773) are available for more details\n\n- [Adam Gerhant](https://github.com/adamgerhant)'s Google Summer of Code project has concluded, adding several high-impact features and improvements to the node graph editing experience, including:\n\n  - Layer node chains that help keep a linear sequence of nodes organized by encapsulating them within their destination layer <small>([#1794](https://github.com/GraphiteEditor/Graphite/pull/1794), [#1812](https://github.com/GraphiteEditor/Graphite/pull/1812), [commit e28e841](https://github.com/GraphiteEditor/Graphite/commit/e28e841e3bef401c19f5ddcf01c68fedd6b5c9c3))</small>\n    <div class=\"video-background\">\n    \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n    \t\t<source src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/node-organization-chains.webm\" type=\"video/webm\" />\n    \t\t<source src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/node-organization-chains.mp4\" type=\"video/mp4\" />\n    \t</video>\n    </div>\n\n  - Subgraph data import/export connectors drawn along the top left/right sides of the graph, instead of the previous representation as node-looking boxes <small>([#1794](https://github.com/GraphiteEditor/Graphite/pull/1794), [commit 8e774ef](https://github.com/GraphiteEditor/Graphite/commit/8e774efe9dae51c0c1db2928e22c6de5b93d0584), [#1911](https://github.com/GraphiteEditor/Graphite/pull/1911))</small>\n    <div class=\"video-background\">\n    \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n    \t\t<source src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/node-organization-imports-exports.webm\" type=\"video/webm\" />\n    \t\t<source src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/node-organization-imports-exports.mp4\" type=\"video/mp4\" />\n    \t</video>\n    </div>\n\n  - Layer selection history feature, letting users go back and forth between prior states of which layers were selected using the back/forward navigation buttons on some mice (Chromium-based browsers only) or the <kbd>Alt</kbd><kbd>[</kbd> and <kbd>Alt</kbd><kbd>]</kbd> hotkeys <small>([#1945](https://github.com/GraphiteEditor/Graphite/pull/1945))</small>\n      <div class=\"video-background\">\n    \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n    \t\t<source src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/selection-history.webm\" type=\"video/webm\" />\n    \t\t<source src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/selection-history.mp4\" type=\"video/mp4\" />\n    \t</video>\n    </div>\n\n  - Improved layer positioning for upstream nodes when being reordered so they don't chaotically overlap or leave behind large gaps anymore <small>([#1928](https://github.com/GraphiteEditor/Graphite/pull/1928), [#1945](https://github.com/GraphiteEditor/Graphite/pull/1945))</small>\n    <div class=\"video-background\">\n    \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n    \t\t<source src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/node-organization-reordering.webm\" type=\"video/webm\" />\n    \t\t<source src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/node-organization-reordering.mp4\" type=\"video/mp4\" />\n    \t</video>\n    </div>\n\n  - Improved shifting of layers in stacks so they act as blocks that collide and bump other layers/nodes instead of getting stuck inside each other <small>([#1940](https://github.com/GraphiteEditor/Graphite/pull/1940))</small>\n    <div class=\"video-background\">\n    \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n    \t\t<source src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/node-organization-block-collision.webm\" type=\"video/webm\" />\n    \t\t<source src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/node-organization-block-collision.mp4\" type=\"video/mp4\" />\n    \t</video>\n    </div>\n\n- [Elbert Ronnie](https://github.com/elbertronnie)'s Google Summer of Code project, building a library for decoding raw image formats, has continued and will wrap up in early November:\n\n  - Implementation of the pre-processing and demosaicing steps in the decoding pipeline <small>([#1796](https://github.com/GraphiteEditor/Graphite/pull/1796))</small>\n\n  - Implementation of the post-processing steps in the decoding pipeline <small>([#1923](https://github.com/GraphiteEditor/Graphite/pull/1923))</small>\n\n  - Factoring in of camera white balance metadata when available <small>([#1941](https://github.com/GraphiteEditor/Graphite/pull/1941))</small>\n\n  - Flipping and rotating of images based on camera orientation metadata <small>([#1954](https://github.com/GraphiteEditor/Graphite/pull/1954))</small>\n\n  - Automated tests now made to run in parallel <small>([#1968](https://github.com/GraphiteEditor/Graphite/pull/1968))</small>\n\n  - [Weekly updates](https://github.com/GraphiteEditor/Graphite/discussions/1771) are being posted while the project is ongoing, following a mid-summer hiatus\n\n## Performance\n\n- Caching of boolean operations enabled by fixing the Select tool's layer click targets robustly despite the boolean node altering the shape data <small>([commit 6ecb173](https://github.com/GraphiteEditor/Graphite/commit/6ecb173c1c06807f13a859ef90b7d7f43af042be), [#1946](https://github.com/GraphiteEditor/Graphite/pull/1946), [#1986](https://github.com/GraphiteEditor/Graphite/pull/1986))</small>\n\n- Rewritten boolean operations algorithm that runs purely in Rust instead of making high-overhead calls into a JavaScript library <small>([#1952](https://github.com/GraphiteEditor/Graphite/pull/1952), [#2000](https://github.com/GraphiteEditor/Graphite/pull/2000))</small>\n\n- Internal hash-based data tracking now benefits from caching of hash calculations <small>([#1876](https://github.com/GraphiteEditor/Graphite/pull/1876))</small>\n\n- Refactored document node type lookup process that fixes performance degradation over time <small>([#1878](https://github.com/GraphiteEditor/Graphite/pull/1878))</small>\n\n- Speed-ups to the node graph compilation that must occur after every change before it's rendered <small>([#1924](https://github.com/GraphiteEditor/Graphite/pull/1924))</small>\n\n- Usage of a coarse bounding box when clicking on layers to avoid a detailed check for intersection <small>([#1887](https://github.com/GraphiteEditor/Graphite/pull/1887))</small>\n\n## Fixes\n\n- Fix for rulers showing in the wrong spot when initially opening a document <small>([#1801](https://github.com/GraphiteEditor/Graphite/pull/1801))</small>\n\n- Fix for backwards compatibility broken in a prior change <small>([commit 621f469](https://github.com/GraphiteEditor/Graphite/commit/621f469a150d4a14c86ba9be87df82aae5dded74))</small>\n\n- Fix for boolean operations containing multiple subpaths <small>([#1804](https://github.com/GraphiteEditor/Graphite/pull/1804))</small>\n\n- Fix for a bug causing some node outputs to fail connecting wires to other inputs <small>([commit 84d7262](https://github.com/GraphiteEditor/Graphite/commit/84d72621e545ba7743e38d46ce1607133bb13461))</small>\n\n- Fix for the 'Zoom with Scroll' preference not working after a page refresh <small>([#1807](https://github.com/GraphiteEditor/Graphite/pull/1807))</small>\n\n- Fix for broken extending of paths in the Pen and Freehand tools <small>([#1809](https://github.com/GraphiteEditor/Graphite/pull/1809))</small>\n\n- Fix for a snapping bug where snapped-together shapes couldn't be moved <small>([#1818](https://github.com/GraphiteEditor/Graphite/pull/1818))</small>\n\n- Fix for transforms in groups not being pre-applied in the boolean node <small>([#1825](https://github.com/GraphiteEditor/Graphite/pull/1825))</small>\n\n- Fix for dragging groups so the transform node is inserted as intended <small>([#1829](https://github.com/GraphiteEditor/Graphite/pull/1829))</small>\n\n- Fix for restoring older prototype GPU infrastructure functionality to work again <small>([#1797](https://github.com/GraphiteEditor/Graphite/pull/1797))</small>\n\n- Fix for a breakage to shallow select mode in the Select tool <small>([#1843](https://github.com/GraphiteEditor/Graphite/pull/1843))</small>\n\n- Fix for the graph not being updated in the UI after using <kbd>Ctrl</kbd><kbd>D</kbd> to duplicate a node <small>([#1852](https://github.com/GraphiteEditor/Graphite/pull/1852))</small>\n\n- Fix for the Layers panel UI not updating when the selection changes or a layer is expanded/collapsed <small>([#1847](https://github.com/GraphiteEditor/Graphite/pull/1847))</small>\n\n- Fix for the rendering of non-closed paths and additional improvements to layer bounding box calculation <small>([#1859](https://github.com/GraphiteEditor/Graphite/pull/1859))</small>\n\n- Fix for how documents are saved with images now correctly serialized in linear space instead of sRGB <small>([#1856](https://github.com/GraphiteEditor/Graphite/pull/1856))</small>\n\n- Fix for the grid overlay color choice that was incorrectly appearing as no color <small>([commit a4a5139](https://github.com/GraphiteEditor/Graphite/commit/a4a513911dac29ee5c6546842be194b1513f2cec))</small>\n\n- Fix for viewport bounds getting out of sync at times, like when toggling rulers <small>([commit 0617759](https://github.com/GraphiteEditor/Graphite/commit/06177597ae8ef52f7a273875d6afb63fb09ec3c8))</small>\n\n- Fix for compatibility issues with rendered SVG output syntax <small>([#1880](https://github.com/GraphiteEditor/Graphite/pull/1880))</small>\n\n- Fix for node graph type errors that were not being shown <small>([#1917](https://github.com/GraphiteEditor/Graphite/pull/1917))</small>\n\n- Fix to add colors to all nodes in a graph, even if disconnected, and properly display hidden network imports <small>([#1921](https://github.com/GraphiteEditor/Graphite/pull/1921))</small>\n\n- Fix to respect the \"Clip\" parameter on Artboards when exporting \"All Artworks\" <small>([#1916](https://github.com/GraphiteEditor/Graphite/pull/1916))</small>\n\n- Fix to improve the undo/redo history system's robustness <small>([#1945](https://github.com/GraphiteEditor/Graphite/pull/1945))</small>\n\n- Fix for a crash caused by gradients with bounds of zero <small>([#1950](https://github.com/GraphiteEditor/Graphite/pull/1950))</small>\n\n- Fix for SVG importing and exporting which had both broken <small>([#1953](https://github.com/GraphiteEditor/Graphite/pull/1953))</small>\n\n- Fix for a crash in our Bezier-rs library when outlining a small path <small>([#1958](https://github.com/GraphiteEditor/Graphite/pull/1958))</small>\n\n- Fix for a crash due to an invalid segment when dissolving a vector path point loop <small>([#1959](https://github.com/GraphiteEditor/Graphite/pull/1959))</small>\n\n- Fix to improve how integer ruler intervals are set when zoomed in <small>([#1966](https://github.com/GraphiteEditor/Graphite/pull/1966))</small>\n\n- Fix for layer stacks inadvertently producing a 0x0 image because of an incorrect default type for the disconnected bottom layer node input <small>([#1965](https://github.com/GraphiteEditor/Graphite/pull/1965))</small>\n\n- Fix to make the primitive shape tools avoid setting a negative transform scale <small>([#1973](https://github.com/GraphiteEditor/Graphite/pull/1973))</small>\n\n- Fix to nudge only the shallowest selected layers to avoid amplified translation <small>([#1975](https://github.com/GraphiteEditor/Graphite/pull/1975))</small>\n\n- Fix to the Select tool's click targets which had extended too far on narrow layers <small>([#1976](https://github.com/GraphiteEditor/Graphite/pull/1976))</small>\n\n- Fix to correctly apply transforms to vector data and strokes <small>([#1977](https://github.com/GraphiteEditor/Graphite/pull/1977))</small>\n\n- Fix for the types imported into subgraphs <small>([#1978](https://github.com/GraphiteEditor/Graphite/pull/1978))</small>\n\n- Fix to now properly use overlays for drawing artboard names in the viewport <small>([#1981](https://github.com/GraphiteEditor/Graphite/pull/1981))</small>\n\n- Fix for broken Opacity slider in Layers panel <small>([#2004](https://github.com/GraphiteEditor/Graphite/pull/2004))</small>\n\n## Internal\n\n- Refactor for graph editing to go through a new abstraction layer, the node network interface <small>([#1794](https://github.com/GraphiteEditor/Graphite/pull/1794), [#1906](https://github.com/GraphiteEditor/Graphite/pull/1906))</small>\n\n- Addition of a new Scope API for exposing data within graphs and subgraphs <small>([#1814](https://github.com/GraphiteEditor/Graphite/pull/1814))</small>\n\n- Reorganization and upgrading of most Cargo dependencies <small>([#1815](https://github.com/GraphiteEditor/Graphite/pull/1815))</small>\n\n- Removal of artboards as a type of graphical element, which is no longer in use as such <small>([#1824](https://github.com/GraphiteEditor/Graphite/pull/1824), [#1830](https://github.com/GraphiteEditor/Graphite/pull/1830))</small>\n\n- Cleanup of many Clippy warnings <small>([#1808](https://github.com/GraphiteEditor/Graphite/pull/1808), [#1936](https://github.com/GraphiteEditor/Graphite/pull/1936), [#1995](https://github.com/GraphiteEditor/Graphite/pull/1995))</small>\n\n- Build process simplifications by auto-installing npm packages and reducing the contributor guide's project setup complexity <small>([#1799](https://github.com/GraphiteEditor/Graphite/pull/1799))</small>\n\n- Fixes for Rust-Analyzer conflicts with build targets and other compile speed issues <small>([commit 857bc77](https://github.com/GraphiteEditor/Graphite/commit/857bc772de8c5731a2eaf7f442beb573ccf8ca4c))</small>\n\n- Fixes, documentation, and updates for npm dependencies and tooling, as well as fixes for Bezier-rs demos not building <small>([#1857](https://github.com/GraphiteEditor/Graphite/pull/1857))</small>\n\n- Profiling metrics development infrastructure for Graphene graph compilation <small>([#1924](https://github.com/GraphiteEditor/Graphite/pull/1924), [#1974](https://github.com/GraphiteEditor/Graphite/pull/1974))</small>\n\n- CI infrastructure to post Clippy warnings as PR comments <small>([#1926](https://github.com/GraphiteEditor/Graphite/pull/1926), [commit a93dcb2](https://github.com/GraphiteEditor/Graphite/commit/a93dcb2776027bfcb385ae9dfddff249bdfd896b))</small>\n\n- CI infrastructure which profiles the demo artwork test documents and comments on PRs that induce sizable changes in performance <small>([#1925](https://github.com/GraphiteEditor/Graphite/pull/1925), [#1939](https://github.com/GraphiteEditor/Graphite/pull/1939))</small>\n\n- CI infrastructure to post a ZIP of self-hosted build assets for the [latest editor release tag on GitHub](https://github.com/GraphiteEditor/Graphite/releases/tag/latest-stable) <small>([commit f2493d5](https://github.com/GraphiteEditor/Graphite/commit/f2493d5308ace728c3525d51609c3a2efcbdc139))</small>\n\n- Refactor to support node ID path insertion at compile time <small>([#1947](https://github.com/GraphiteEditor/Graphite/pull/1947))</small>\n\n- Switch of attribute-based vector data from referencing point IDs to indexes in the points table <small>([#1949](https://github.com/GraphiteEditor/Graphite/pull/1949))</small>\n\n- Refactor for the navigation metadata that stores the node graph UI view's current pan and zoom <small>([#1956](https://github.com/GraphiteEditor/Graphite/pull/1956))</small>\n\n- Refactor for the node definition syntax to make it easier, shorter, and more consistent to define nodes across the board <small>([#1942](https://github.com/GraphiteEditor/Graphite/pull/1942), [#1999](https://github.com/GraphiteEditor/Graphite/pull/1999), [#2002](https://github.com/GraphiteEditor/Graphite/pull/2002))</small>\n\n- Fix for a crash when using the experimental Graphene CLI tool <small>([#1993](https://github.com/GraphiteEditor/Graphite/pull/1993))</small>\n\n- Upgrades to quality of third-party library license notice generation <small>([commit 14de67c](https://github.com/GraphiteEditor/Graphite/commit/14de67c5a78a1e8369a8b023da441237a44e48cb))</small>\n\n- Cleanup for various TypeScript code errors and additions to CI infrastructure to enforce TS error checking <small>([commit 1ee5ffb](https://github.com/GraphiteEditor/Graphite/commit/1ee5ffbbe8a64da5a83da889dd16d41c3463c332))</small>\n\n- Simplifications to the [Bezier-rs web demos](https://keavon.github.io/Bezier-rs/) to reduce the bundle size and streamline its code <small>([commit 0b0169a](https://github.com/GraphiteEditor/Graphite/commit/0b0169a415453b9d9910d765f0538ecd67b618c0))</small>\n\n- Manually-runnable benchmarks for document runtime speed profiling <small>([#2005](https://github.com/GraphiteEditor/Graphite/pull/2005))</small>\n\n## Website\n\n- Revamps to the website <small>([commit 5d74178](https://github.com/GraphiteEditor/Graphite/commit/5d74178f5f417afdd70abec596d38f22d490240a), [commit 40fd447](https://github.com/GraphiteEditor/Graphite/commit/40fd4473a784ba24fc3105f8da56baacccf2dcf5), [commit 98ab069](https://github.com/GraphiteEditor/Graphite/commit/98ab069a1703e89cea031fedffc55c3bf4191b5d))</small>\n\n- Updates to various content on the website <small>([commit fb7d597](https://github.com/GraphiteEditor/Graphite/commit/fb7d5970b38c61fc1a35aeefd7028858c96f5ff2))</small>\n\n## Announcements\n\n### Graphite @ Maker Faire\n\n<img src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/maker-faire-banner.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.jpg')\" alt=\"Poster for the Bay Area Maker Faire\" />\n\nGraphite will have a booth at the [Bay Area Maker Faire](https://makerfaire.com/bay-area/) **this weekend, October 18–20**. If you're in northern California, come visit and meet our team. We'll be located in Coal Shed 2 and plan to showcase the latest features, answer questions, teach how to use the app, get to know the maker community, and give away stickers and art postcards. We hope to see you there!\n\n### Nodevember\n\n<img src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/nodevember-logo.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Poster for the Bay Area Maker Faire\" />\n\nWe're excited to be participating in [Nodevember](https://nodevember.io/) next month! The event celebrates procedural art and design with themed prompts throughout the month. We're looking forward to seeing what the community creates with Graphite's nodes. We'll be sharing some of our favorite submissions on our social media channels. Be sure to tag `@GraphiteEditor` and use the `#MadeWithGraphite` hashtag together with `#Nodevember2024`.\n\n### Community art contest\n\nCongratulations to [VDawg](https://www.instagram.com/vdawg.jpg/), the winner of the community art contest, whose work is featured below.\n\n<img src=\"https://static.graphite.art/content/blog/2024-10-15-graphite-progress-report-q3-2024/art-contest-winner.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.jpg')\" alt=\"Winning artwork depicts a dark landscape with an ethereal diamond forming a constellation with the stars above\" />\n"
  },
  {
    "path": "website/content/blog/2025-01-16-year-in-review-2024-highlights-and-a-peek-at-2025.md",
    "content": "+++\ntitle = \"Year in review: 2024 highlights and a peek at 2025\"\ndate = 2025-01-16\n\n[extra]\nbanner = \"https://static.graphite.art/content/blog/2025-01-16-year-in-review-2024-highlights-and-a-peek-at-2025.avif\"\nbanner_png = \"https://static.graphite.art/content/blog/2025-01-16-year-in-review-2024-highlights-and-a-peek-at-2025.png\"\nauthor = \"Keavon Chambers\"\nsummary = \"Graphite has come a long way in 2024. Read about the progress made and the plans for the upcoming year.\"\nreddit = \"https://www.reddit.com/r/graphite/comments/1i3umnl/blog_post_year_in_review_2024_highlights_and_a/\"\ntwitter = \"https://x.com/GraphiteEditor/status/1880404337345851612\"\nbluesky = \"https://bsky.app/profile/graphiteeditor.bsky.social/post/3lfxysayh622g\"\n\njs = [\"/js/component/youtube-embed.js\"]\ncss = [\"/component/youtube-embed.css\"]\n+++\n\nAnother year has come and gone which has propelled Graphite—further than any year before—towards the ambitious goal of satiating the open source community's expanding appetite for an awesome 2D content creation suite that surpasses established choices in ease-of-use, powerful features, and affordability (at the unbeatable price of *free*).\n\n<!-- more -->\n\nIn a world where the notion of software ownership seems headed towards extinction, the need has never been greater for an independent, community-built alternative to the vector graphics, animation, image manipulation, photo processing, and publishing tools used daily by millions of creators worldwide.\n\nGraphite is and will always remain yours to keep, whether that's by running the lightweight, client-side [web app](https://editor.graphite.art) (no signup, no cloud), <a href=\"https://support.google.com/chrome/answer/9658361\" target=\"_blank\">installing the PWA</a> on your desktop, self-hosting the <a href=\"https://github.com/GraphiteEditor/Graphite/releases/tag/latest-stable\" target=\"_blank\">builds</a>, or downloading the soon-to-be-ready native app for your OS of choice (more news on that later in the post).\n\n<style class=\"float-image\">\n.float-image + p {\n\ttext-align: left;\n}\n.float-image + p > a {\n\tfloat: right;\n\tmargin-left: 1.5em;\n\tmargin-bottom: 1em;\n}\n</style>\n\n<p>\n<a href=\"https://github.com/GraphiteEditor/Graphite\"><img src=\"https://static.graphite.art/content/blog/2025-01-16-year-in-review-2024-highlights-and-a-peek-at-2025/10k-stars.avif\" style=\"max-width: unset; margin-top: 0.5em\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Screenshot of 10,000 stars\" /></a>\n<span>Join me, founder and designer of Graphite, to see where the past year has brought us on this quest. And let me take this moment to thank our growing community for sharing my vision and showing support, both <a href=\"/donate\">financially</a> and by boosting the GitHub project page over the 10,000 star milestone just in time to celebrate the end of a productive 2024.</span>\n</p>\n\n## 2024 development progress report\n\n<p><img src=\"https://static.graphite.art/content/blog/2025-01-16-year-in-review-2024-highlights-and-a-peek-at-2025/weekly-commit-rate.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"\" /></p>\n\n<center><em>Weekly code commit rate in 2024</em></center>\n\nIn 2024, Graphite grew from a promising tech demo into a by-and-large useful application for vector artistry and graphic design, equipped with its totally unique secret ingredient: nondestructive, procedural editing via a node graph. This was a year focused on iterating until a prototype became a polished product— at least by the standards of alpha-stage software. If you haven't [tried Graphite](https://editor.graphite.art) recently, please take another look!\n\nImprovements made throughout Alpha 3 (2024's release series) brought the formerly abysmal performance up to now-adequate levels and solved the vast bulk of instability with the once-numerous crashes and bugs. Advancements to [Graphene](/volunteer/guide/graphene), our bespoke node graph engine technology, has let us begin to support new rendering possibilities, introduce more helpful nodes, and remove restrictive limitations with common node combinations that previously were points of frustration. We also made big strides improving the tools used by artists for vector drawing with features like boolean path operations, snapping, layer selection history, quick measurement, gradient picking, and extensive usability-focused tweaks—both big and small—all throughout the editor.\n\nThis was also the first year of publishing quarterly development reports to the blog. We have aimed to keep them all visually interesting by showing the new features with looping video clip demonstrations alongside digestible sentence-long change summaries. Please let us know if you find this format valuable and worth continuing in 2025.\n\n- [Graphite progress report (Q1 2024)](../graphite-progress-report-q1-2024)\n- [Graphite progress report (Q2 2024)](../graphite-progress-report-q2-2024)\n- [Graphite progress report (Q3 2024)](../graphite-progress-report-q3-2024)\n- Graphite progress report (Q4 2024) will be published soon— stay up-to-date with the [newsletter](/#newsletter) or [RSS feed](../rss.xml)\n\n## Alpha roadmap update\n\nAlpha 3 began back in February 2024 and we plan to declare the start of the Alpha 4 release series again in February after wrapping up the holiday development cycle and shifting gears for the big projects we plan to tackle in 2025. I'll expand on those goals for the upcoming year later in this article. If development keeps pace with plans, Alpha 4 may be the last before Beta begins as early as the start of 2026.\n\n## Google Summer of Code results\n\n<p><img src=\"https://static.graphite.art/content/blog/2025-01-16-year-in-review-2024-highlights-and-a-peek-at-2025/gsoc-logo.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Google Summer of Code program logo\" /></p>\n\n<center><em>Read our short <a href=\"../graphite-internships-announcing-participation-in-gsoc-2024\">blog post</a> about participating in Google Summer of Code</em></center>\n\nOur project had the fortune of being selected to participate for its first year as a mentoring organization in <a href=\"https://summerofcode.withgoogle.com/archive/2024/organizations/graphite\" target=\"_blank\">Google Summer of Code</a> (GSoC). We were given the opportunity to welcome three student interns to the team as they developed significant contributions to our open source code base throughout the summer.\n\nI would like to express my gratitude to Google for funding the program and its stipends given to our students. It has ushered in a great deal of talent to our contributor community. And even beginning right now, we are seeing proactive prospective students begin [submitting code in anticipation](../graphite-internships-announcing-participation-in-gsoc-2024) of the 2025 program. We hope our organization is invited to return for the opportunity to mentor another cohort of budding talent, much like our 2024 students—Adam, Elbert, and Dennis—whose accomplishments far surpassed my most optimistic expectations.\n\n<a href=\"https://github.com/GraphiteEditor/Graphite/discussions/1769\" target=\"_blank\">Adam contributed</a> an extensive evolution of Graphite's node editor. He introduced adjustment layers, editable nested graphs, and layer/node organization features that have upgraded the capabilities of the procedural editing environment, bringing it closer to my product vision. I'm delighted to announce that Adam has decided to stick around past the summer and join the [core team](/about#core-team). His continued development of the node editing systems to fully realize my design goals will make the editor magnitudes more powerful, and at the same time, easier to use. More on those plans later in the article.\n\n<a href=\"https://github.com/GraphiteEditor/Graphite/discussions/1771\" target=\"_blank\">Elbert built</a> a new Rust library, <a href=\"https://crates.io/crates/rawkit\" target=\"_blank\">Rawkit</a>, for decoding and processing `.arw` files from Sony digital cameras, one of the most popular camera brands. Nikon, Canon, and someday all other formats are in scope for the future direction of the code. This library gives us the fine-grained control we require and will allow Graphite to begin its focus on digital photo processing in 2025 once several technical limitations are overcome and Rawkit is integrated into Graphite.\n\n<a href=\"https://github.com/GraphiteEditor/Graphite/discussions/1773\" target=\"_blank\">Dennis developed</a> a range of critical improvements around the theme of performance. Before his contributions, working in Graphite was too slow for practical usability. Afterwards, it became fast enough to be useful in most scenarios (except when dealing with excessive complexity in terms of pixel or vector point counts— those known bottlenecks remain as future work). He made rendering and various nodes such as boolean operations faster, built profiling tools to aid in the continued quest for speed, and made changes adding to the robustness of the entire node engine. Lastly, he integrated the <a href=\"https://github.com/linebender/vello\" target=\"_blank\">Vello</a> high-performance vector graphics renderer to replace our SVG-based rendering method. This streamlines the rasterization and compositing that's involved in showing you the artwork in your viewport. Vello can be turned on from the editor preferences menu and will be enabled by default later in 2025 when browser support for the WebGPU API, which Vello relies on, becomes widespread.\n\nAnother part of the GSoC program was the Mentor Summit in October where I got to meet and share knowledge with open source maintainers from the other participating organizations. Hosted at Google's Mountain View offices, it was a weekend of talking tech and learning the lessons of open source project management from the seasoned veterans.\n\n<p><img src=\"https://static.graphite.art/content/blog/2025-01-16-year-in-review-2024-highlights-and-a-peek-at-2025/gsoc-mentor-summit-photo.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.jpg')\" alt=\"\" /></p>\n\n<center><em>Group photo of the GSoC Mentor Summit attendees</em></center>\n\n<p><img src=\"https://static.graphite.art/content/blog/2025-01-16-year-in-review-2024-highlights-and-a-peek-at-2025/gsoc-mentor-summit-session-board.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.jpg')\" alt=\"\" /></p>\n\n<center><em>Schedule board highlighting my session on day 2</em></center>\n\nBut I also managed to pass on knowledge in a subject I'm passionate about: design. I led a session titled \"Well-Designed User Interfaces in FOSS Apps\" in which 30-40 participants came to explore and discuss how the open source community can improve in this notoriously challenging discipline. The Mentor Summit was just one of the community events we took part in over this past year.\n\n## Community events\n\nOutside of my daily routine spent coding, designing, and managing the project, I've also continued working to expand our community engagement and industry outreach. Members of the Graphite team met up at events during the year to represent the project and plant the seeds of future growth for our mission beyond the reach of the little pocket of the internet we call home.\n\n### Game Developers Conference\n\nIn March, accompanied by Oliver Davies, a personal friend/tech artist/contributor to Graphite's product design, he and I visited the <a href=\"https://gdconf.com/\" target=\"_blank\">Game Developers Conference</a> (GDC) in San Francisco for opportunities to meet face-to-face with industry colleagues, as we did also the past two years.\n\nWe introduced Graphite to open source organizations like <a href=\"https://godotengine.org/\" target=\"_blank\">Godot</a> and <a href=\"https://o3de.org/\" target=\"_blank\">O3DE</a>, caught up with Francesco and Dalai from the Blender Foundation, joined a roundtable panel on open source adoption in the games industry, and came together with the <a href=\"https://gamedev.rs/\" target=\"_blank\">Rust Gamedev</a> community for a physical meetup of game and graphics centric Rust developers. It was a treat seeing several people in-person for the first time whom, before, I'd known only online.\n\n<p class=\"wide\"><img src=\"https://static.graphite.art/content/blog/2025-01-16-year-in-review-2024-highlights-and-a-peek-at-2025/gdc-collage.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.jpg')\" alt=\"\" /></p>\n\n<center><em>\nUpper-left: Forest Anderson, host of the Rust Gamedev Meetup <a href=\"https://www.youtube.com/watch?v=Ea4Wt_FgEEw&list=PLYiOdhpKxxXI9l8V15FciLcsPzkz3Gt4I&index=2\" target=\"_blank\">live streams</a> where Graphite demoed many monthly development milestones, with me at the Rust devs hangout;\nUpper-right: roundtable panel on open source adoption in the games industry;\nLower-left: Godot's presence at GDC;\nLower-right: Francesco Siddi (1) and Dalai Felinto (2) from the Blender Foundation with me (3) and Oliver (4)\n</em></center>\n\nWith March now just around the corner, I am definitely looking forward to the next conference. If you will be in town for GDC 2025 and would like to meet up, please [get in touch](/contact).\n\n### Blender Conference LA\n\n<p class=\"wide\"><img src=\"https://static.graphite.art/content/blog/2025-01-16-year-in-review-2024-highlights-and-a-peek-at-2025/bcon-la-collage.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.jpg')\" alt=\"\" /></p>\n\n<center><em>Left-to-right, top-then-bottom: Ton Roosendaal (founder of Blender) and me (founder of Graphite); the venue on Hollywood Boulevard; talks on the main stage; Colin Levy (director of <a href=\"https://www.youtube.com/watch?v=Mv30ExfoKcc\" target=\"_blank\">Skywatch</a> and the Blender Studio's <a href=\"https://www.youtube.com/watch?v=eRsGyueVLvQ\" target=\"_blank\">Sintel</a>) and Andrew Price (<a href=\"https://www.youtube.com/@blenderguru\" target=\"_blank\">Blender Guru</a> and creator of <a href=\"https://www.poliigon.com/\" target=\"_blank\">Poliigon</a>); my talk on the main stage; Alan Melikdjanian (<a href=\"https://www.youtube.com/user/CaptainDisillusion\" target=\"_blank\">Captain Disillusion</a>) and <a href=\"https://www.youtube.com/@IanHubert2\" target=\"_blank\">Ian Hubert</a> (YouTube filmmaker and director of the Blender Studio's <a href=\"https://www.youtube.com/watch?v=41hv2tW5Lc4\" target=\"_blank\">Tears of Steel</a>); attendees chatting</em></center>\n\nThe next month in April, Oliver and I went to our second conference of the year: <a href=\"https://bconla.org/2024/\" target=\"_blank\">BCON LA</a>, the first Blender Conference held in Los Angeles. We connected again with the Blender team and met many 3D/VFX industry professionals and prominent members of the Blender community over the two days of the event.\n\nI also took to the main stage to present a lightning talk introducing Graphite to the community:\n\n<div class=\"youtube-embed aspect-16x9\">\n\t<img src=\"https://static.graphite.art/content/blog/2025-01-16-year-in-review-2024-highlights-and-a-peek-at-2025/bcon-la-talk-video-cover.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" data-youtube-embed=\"x3P5eYv11EU\" data-youtube-timestamp=\"1603\" alt=\"BCON LA 2024 - Lightning Talks\" />\n</div>\n\n### Graphite booth at Open Sauce\n\nThe month of June was particularly special because of <a href=\"https://opensauce.com/\" target=\"_blank\">Open Sauce</a>, a convention and expo in San Francisco for makers and creators, and of course, open source projects!\n\n<p><img src=\"https://static.graphite.art/content/blog/2025-01-16-year-in-review-2024-highlights-and-a-peek-at-2025/open-sauce-booth.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.jpg')\" alt=\"\" /></p>\n\n<center><em>Adam and Oliver driving a Graphite live demo at our booth</em></center>\n\nThis presented the perfect opportunity to host our own exhibitor booth and talk to hundreds of excited attendees with creative tech backgrounds over the two day show. The event was excellent for networking with fellow makers and a chance to meet an array of the guest YouTube creators including several from the digital content creation realm.\n\n<p class=\"wide\"><img src=\"https://static.graphite.art/content/blog/2025-01-16-year-in-review-2024-highlights-and-a-peek-at-2025/open-sauce-collage.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.jpg')\" alt=\"\" /></p>\n\n<center><em>Sides: doodles by attendees on our public art wall; Inner-left: <a href=\"https://www.youtube.com/@IanHubert2\" target=\"_blank\">Ian Hubert</a>, Blender filmmaker, visiting again after we met at BCON LA; Inner-right: Daniel Shiffman (<a href=\"https://www.youtube.com/@TheCodingTrain\" target=\"_blank\">The Coding Train</a>), creator of tutorials and explorations into creative coding/generative procedural art</em></center>\n\nI designed the booth with the goal of becoming an inviting artist's space. Visitors could contribute doodles to the pair of LED-backlit dry erase boards, walk inside to talk with us about the project, and sit down to explore the app. This was a valuable chance to \"playtest\" the user experience with a steady supply of new people from a variety of backgrounds. I also learned how to refine our approach to communicating clearly what the product is and does.\n\nJoining me again was Oliver to assist with the booth, as well as our new GSoC contributor, Adam, who flew up from southern California to help. In between the hustle and bustle, we put the face-to-face time to good use communicating the vision and planning many aspects of his node graph development.\n\n### Graphite booth at the Bay Area Maker Faire\n\nAnd then when October rolled around, we did it again! Now located at a post-industrial waterfront venue across the Bay from San Francisco, the <a href=\"https://makerfaire.com/bay-area/\" target=\"_blank\">Maker Faire</a> started with a Friday field trip day for local schools followed by a full weekend of general attendance.\n\n<p><img src=\"https://static.graphite.art/content/blog/2025-01-16-year-in-review-2024-highlights-and-a-peek-at-2025/maker-faire-booth.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.jpg')\" alt=\"\" /></p>\n\n<center><em>Visitors making art and learning about Graphite at our booth</em></center>\n\nThe Maker Faire is ground zero for the Maker Movement. I grew up going each year as a kid. It was an era when consumer 3D printing was completely new and that was the only place one could discover—and obsess over—the technology. The Faire influenced my career path into engineering and the arts, so it's fitting that I would grow up to return and share an open source project, born out of that community spirit, for the next generation of creative young minds.\n\nAttracting a more family-oriented audience than Open Sauce, it presented the chance to learn how approachable Graphite is even for kids. Doodling on the LED-backlit whiteboards flanking our booth was especially popular with that age range and brought in many passers-by. Of those who tried Graphite, I was blown away to see how some of our youngest visitors—down to the age of 6—were also the most capable and engaged using the product, diving in deep with barely any instruction. As the UI and product designer, this assured me that I have been on the right track so far. I believe now more than ever that my ambitious goal is achievable: creating the most intuitive and user-friendly professional graphics editor on the market.\n\n<p><img src=\"https://static.graphite.art/content/blog/2025-01-16-year-in-review-2024-highlights-and-a-peek-at-2025/maker-faire-demo-stations.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.jpg')\" alt=\"\" /></p>\n\n<center><em>All four demo computers occupied with Graphite's newest users</em></center>\n\nAnother exciting part of the Maker Faire experience was bringing together nearly the full Graphite core team in-person. Oliver and Adam came to help again while we were also joined by Dennis visiting all the way from Germany, conveniently coinciding with a vacation he had planned. We put our commute time towards deeply technical code architecture discussions and knowledge transfer.\n\n<p class=\"wide\"><img src=\"https://static.graphite.art/content/blog/2025-01-16-year-in-review-2024-highlights-and-a-peek-at-2025/maker-faire-team-collage.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.jpg')\" alt=\"\" /></p>\n\n<center><em>Left: me (1), Adam (2), and Oliver (3) at the venue; Right: Adam (1), Dennis (2), and me (3) (Oliver and Dennis couldn't make it on the same day)</em></center>\n\nGraphite is tentatively anticipating a return for Open Sauce and the Maker Faire again in 2025. Make plans to come and visit!\n\n## Looking ahead to 2025\n\nThere are so many plans that I'm eager to carry out to improve the clarity and capability of the experience Graphite offers our users. Some are small, and a few we will be working on for most of the year.\n\n### Desktop app\n\nStarting out with our most in-demand request: a desktop app. This has been on our roadmap from the start but only recently it's begun making sense putting it at the front of the roadmap priorities. We hoped to complete it by the end of 2024, but that wasn't in the cards due to developer availability and the specialized skills needed to complete the task.\n\nNow to get technical, the lazy option exists: chucking the whole web app in an unaltered Electron wrapper, but this is a technological dead end that I believe offers no value compared to a PWA. The value comes from offering an actual native app where the editor Rust code and GPU-accelerated rendering runs on a user's Windows, Mac, or Linux machine without browser overhead. Our use case of combining the web-rendered editor interface with the user's native-rendered artwork presents several unique challenges that our team has to overcome— individually on each platform. If you have experience with native development on Windows, Mac, and/or Linux, please get involved to speed up this effort! With our current resources, I am anticipating this will be ready for release around spring.\n\n### Animation\n\nNext, the feature I am personally most itching to dive into developing is animation. I've been recently iterating on the UI design mockup for the Timeline panel which will support keyframing any desired node parameters. This new panel will seamlessly integrate into the existing graph-based, data-driven workflow and make it easy to create motion graphics paired with procedural generation.\n\n<p class=\"wide\"><img src=\"https://static.graphite.art/content/blog/2025-01-16-year-in-review-2024-highlights-and-a-peek-at-2025/timeline-panel-ui-mockup.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"\" /></p>\n\n<center><em>User interface mockup for the latest animation panel plans prior to being implemented</em></center>\n\n### Advanced procedural editing\n\nIt is now becoming time to delve into the next phase of making the node graph more powerful by introducing several key features:\n\n- Lambdas: treating a node as a piece of data given to another node, so it can be run in a loop with varying parameters in each iteration.\n- Instances: generalizing graphical data, transforms, and groups so that every layer is one or multiple instances, each with a unique transform. This will finally fix the long-lived limitation of layers lacking a proper pivot point.\n- Tables: representing lists of data like vector points and segments in a spreadsheet. Formalizing the tabular data representation lets the node engine benefit from ECS-like performance gains by optimizing CPU cache utilization.\n- Attributes: encoding properties (of points, of segments, of instances, of appearance styles, etc.) in columns on the tabular data. This will unlock Graphite to become as powerful as Blender geometry nodes which works based on the same design principle.\n\n### Raster graphics editing\n\nGraphite has included a primitive kind of raster support for a while, mostly used for including reference images when creating vector content. Some raster nodes can adjust the colors of images, but there are no tools yet for selecting and drawing over parts of an image to make localized edits. Furthermore, CPU-centric bottlenecks slow down the editor when big images are in use. The GPU is not used by any nodes operating on pixel-based data.\n\nConsequently, raster editing just isn't viable yet until tool and GPU node support arrives. The innumerable complexities would not fit in this section, but if you have a background in compilers or graphics programming, please hop on <a href=\"https://discord.graphite.art\" target=\"_blank\">our Discord</a> and ask about it if you're curious or potentially interested in helping. After the infrastructure parts are in place, we can begin building nodes that make localized edits (such as a masking node) and start developing tools including a fully rewritten brush engine and a mode for drawing marquee selections. I can't yet predict how far we will get by year's end with these interactive localized editing tools because it will all depend on how quickly the prerequisite technical infrastructure components come together. Part of that will depend on how soon the browser vendors ship universal <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/WebGPU_API#browser_compatibility\" target=\"_blank\">WebGPU API support</a> so it can be deployed beyond an experimental state in Graphite.\n\n### Graphite in education\n\nA valuable discovery came out of exhibiting Graphite at the Maker Faire. I had conversations with several school teachers who were interested in using Graphite in their classrooms.\n\nI was told that other web-based graphics editors are commonly blocked by the IT admins of school networks due to policies against visiting sites with ads. (Crazy!) Since Graphite is entirely ad-free and runs on Chromebooks, this presents an opportunity to focus on better supporting the education market. In fact, I have recently been watching Chromebooks account for a small but rapidly growing portion of our site's visitors which means some educators are teaching Graphite in their classrooms:\n\n<p><img src=\"https://static.graphite.art/content/blog/2025-01-16-year-in-review-2024-highlights-and-a-peek-at-2025/school-usage-trends.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"\" /></p>\n\n<center><em>Graphite visitors on Chromebooks have trended up this school year but dropped down to background levels during summer and winter breaks, hinting at usage in classrooms</em></center>\n\nThis was previously a user demographic that I didn't consider. My school experience never offered graphic design instruction. I had to teach it to myself, and when using school computers, I ran portable installations of remote desktop software to access the desktop creative applications on my home computer. That experience led to my selection of a software stack that would support web-based access to the Graphite editor for the sake of students like my past self, but my previous assumption was that teacher-led instruction would be years away.\n\nLearning that there are educators who want to teach these skills directly to students was eye-opening. It could be an excellent fit: each teacher brings along dozens of users. A teacher can learn the app once and disseminate the instruction which saves us from needing pristine learning resources at this early stage. Then a teacher can find the most common sources of confusion and filter that feedback back to us for improvement. Compared to professional artists who can't always justify using alpha software, students are a less demanding type of user. And Graphite benefits from a generation of students growing up to continue using the app. It seems like a surprisingly good fit.\n\nMy goal in 2025 is to begin prioritizing specific resources for educators that might include:\n\n- Pushing for development efforts that will improve performance to help the app run better on low-spec hardware. This benefits everyone else just as greatly!\n- Reducing common pitfalls in the software that are especially likely to be encountered by inexperienced users.\n- Putting more time towards creating learning resources and documentation to help instructors learn Graphite well enough to teach it and solve student issues.\n- Collaborating with teachers to devise and develop a curriculum package that can be used in classrooms to teach specific skills.\n- Creating an information page for educators to discover the project, learn how it suits their needs, access curriculum, and get connected with us.\n\nIf you are a teacher, or know one, who would be interested in adding this manner of STEM/STEAM instruction to your classroom, please [get in touch](/contact) so we can figure this out together.\n\n## Thank you for helping us help you help us all\n\nBy not being backed by investors or built by staff engineers or marketed by an agency, Graphite is an ambition that is constantly treading the line that borders the realm of impossibility. But thanks to your support, I am confident our efforts will prevail. 2025 is the year when all the pieces fall into place with a desktop app, competitive performance, features, and raster image editing. It will be the free software you can own (and love) that holds up against the software you have no choice but to rent (and put up with as it trains AI on your private work).\n\nUltimately, reaching critical mass might take one year. It might take five. That part is up to you. Momentum and resources are both scarce which means you, personally, have the opportunity to make an outsized impact.\n\nIf you choose to [become a member](/donate), you are directly helping fund our expenses like conference travel and ordering T-shirts that keeps the volunteer team happy and motivated to code. Remember that \"free software\" doesn't mean it's free to produce, it just means someone else is paying for it if you aren't pitching in. We just added an option for [donating directly](/donate#supporter-memberships) without needing a GitHub account, so now it's easier than ever to contribute.\n\nIf you choose to [volunteer](/volunteer), you lift our greatest bottleneck—time—and bring your unique skills to the table. There are opportunities from coding to technical writing to art, design, and marketing. It's a team effort, but only if there's a team to delegate the efforts to.\n\nAnd there are other ways to help out. Sign up as a QA tester in our <a href=\"https://discord.graphite.art\" target=\"_blank\">Discord</a>. Make it your mission to share Graphite by word-of-mouth on the forums and online communities you frequent. Put it on the radar of the creators you follow. Create and post your own tutorials on the web. Use it regularly and share your creations in our Discord community and by tagging #Made<wbr />With<wbr />Graphite on social media.\n\n## Upcoming: FOSDEM '25\n\nIf you'll be in Brussels, Belgium for the FOSDEM conference in several weeks (February 1–2), be sure to [reach out](/contact) and arrange a plan to meet up, chat, and pick up some Graphite stickers.\n\n## Until next year\n\nThank you for dedicating the time to read about this latest annual collection of project updates. It has been a privilege leading this community and endeavor since 2021. As we wrap up four years of hard work and venture into the beginnings of a fifth, I am more eager than ever for the adventures that lie in wait during the times ahead.\n\nHappy creating!\n"
  },
  {
    "path": "website/content/blog/2025-03-31-graphite-progress-report-q4-2024.md",
    "content": "+++\ntitle = \"Graphite progress report (Q4 2024)\"\ndate = 2025-03-31\n[extra]\nbanner = \"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024.avif\"\nbanner_png = \"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024.png\"\nauthor = \"Keavon Chambers & Hypercube\"\nsummary = \"Graphite's Q4 2024 update introduces quality of life features across drawing tools and procedural editing.\"\ncss = [\"/component/demo-artwork.css\"]\nreddit = \"https://www.reddit.com/r/graphite/comments/1jpjqcs/blog_post_graphite_progress_report_q4_2024/\"\ntwitter = \"https://x.com/GraphiteEditor/status/1907350199414206604\"\nbluesky = \"https://bsky.app/profile/graphiteeditor.bsky.social/post/3llsxykppjs2c\"\n+++\n\n[Graphite](/), a new open source 2D procedural graphics editor, has [wrapped up 2024](../year-in-review-2024-highlights-and-a-peek-at-2025) with a fourth quarter (October-December) focused on introducing **quality of life features** for both Graphite's interactive **tools-driven and procedural editing workflows**.\n\n<!-- more -->\n\nAll Q4 2024 commits may be [viewed in this list](https://github.com/GraphiteEditor/Graphite/commits/master/?since=2024-10-01&until=2024-12-31) and all noteworthy changes are detailed below.\n\nThis is the fourth in our 2024 series of quarterly progress reports. If you missed [the previous post](../graphite-progress-report-q3-2024), be sure to check it out as well. If you'd like to help speed up future progress, please consider [getting involved](/volunteer) with code, QA/bug testing, or art/marketing projects. [Donations](/donate) are also valued, as are [stars on GitHub](https://github.com/GraphiteEditor/Graphite). Follow along and partake in our [Discord community](https://discord.graphite.art), too.\n\nAt the time of publication, there is **one week left to apply for a summer 2025 internship with us through Google Summer of Code.** [Learn more here](../internships-for-a-rust-graphics-engine-gsoc-2025) and apply before [April 8 at 18:00 UTC](https://www.wolframalpha.com/input?i=April+8%2C+18%3A00+UTC).\n\nTo showcase the newly introduced feature of converting selected nodes into conveniently reusable subgraphs, the new *Parametric Dunescape* artwork is presented below. Every dune layer is generated just by its custom parameters for color, height, and random seed.\n\n<div class=\"demo-artwork\">\n\t<a href=\"https://editor.graphite.art/#demo/parametric-dunescape\">\n\t\t<img src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Vector art of Parametric Dunescape\" />\n\t</a>\n\t<p>\n\t\t<span>\n\t\t\t<em>Parametric Dunescape</em>\n\t\t</span>\n\t\t<br />\n\t\t<span>\n\t\t\t<a href=\"https://editor.graphite.art/#demo/parametric-dunescape\">Open this artwork</a> to<br />explore it yourself.\n\t\t</span>\n\t</p>\n</div>\n\n## Additions\n\n<!--\nEDITOR'S NOTE: The grammatical structure of each bullet point should follow the form: \"Check out our new... [Bullet point description]\"\n-->\n- Node graph support for making a custom node by merging the selected nodes into a subgraph with the node context menu's \"Merge Selected Nodes\" option, or the shortcut <kbd>Ctrl</kbd><kbd>M</kbd> (macOS: <kbd>⌘</kbd><kbd>M</kbd>) <small>([#2097](https://github.com/GraphiteEditor/Graphite/pull/2097))</small>\n\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/merge-selected-nodes.webm\" type=\"video/webm\" />\n\t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/merge-selected-nodes.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Text controls for line height, character spacing, and wrappable box areas that can be dragged with the Text tool <small>([#2016](https://github.com/GraphiteEditor/Graphite/pull/2016), [#2118](https://github.com/GraphiteEditor/Graphite/pull/2118))</small>\n\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/text-area-controls.webm\" type=\"video/webm\" />\n\t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/text-area-controls.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Pinnable node sections in the Properties panel <small>([commit e6d8c47](https://github.com/GraphiteEditor/Graphite/commit/e6d8c4743d2aff15985c929df2cc7381a61908a0))</small>\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/pinnable-node-sections.webm\" type=\"video/webm\" />\n\t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/pinnable-node-sections.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n- New demo artwork, [*Changing Seasons*](https://editor.graphite.art/#demo/changing-seasons), featured in the [previous progress report](../graphite-progress-report-q3-2024) <small>([commit fa6b5f2](https://github.com/GraphiteEditor/Graphite/commit/fa6b5f298adf395362e1aaa2c07be89fa89eaee2))</small>\n\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/changing-seasons-demo.webm\" type=\"video/webm\" />\n\t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/changing-seasons-demo.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- *Offset Path* node that expands or contracts a vector shape <small>([#2030](https://github.com/GraphiteEditor/Graphite/pull/2030))</small>\n\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/offset-path-node.webm\" type=\"video/webm\" />\n\t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/offset-path-node.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- *Flatten Vector Elements* node that turns multiple layers of vector paths into a single combined path; and changes to the *Copy to Points*, *Repeat*, and *Circular Repeat* nodes so they output group data instead of a single vector path, allowing each separate layer to be modified by nodes which operate on groups (like *Assign Colors*), or flattened with *Flatten Vector Elements* to have the prior behavior <small>([#2011](https://github.com/GraphiteEditor/Graphite/pull/2011), [#2045](https://github.com/GraphiteEditor/Graphite/pull/2045))</small>\n\n  <img src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/flatten-vector-elements-node.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Visual description of the change introduced by the Flatten Vector Elements node and its usage with instancing nodes\" />\n\n- Support for *Fill* and *Stroke* nodes with groups, applying to each vector layer within <small>([#2046](https://github.com/GraphiteEditor/Graphite/pull/2046))</small>\n\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/fill-and-stroke-nodes-on-groups.webm\" type=\"video/webm\" />\n\t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/fill-and-stroke-nodes-on-groups.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- *Switch* node that routes one of two data connections based on a true or false value <small>([#2064](https://github.com/GraphiteEditor/Graphite/pull/2064))</small>\n\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/switch-node.webm\" type=\"video/webm\" />\n\t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/switch-node.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- *Bevel* node that flattens the corners of vector shapes <small>([#2067](https://github.com/GraphiteEditor/Graphite/pull/2067), [#2096](https://github.com/GraphiteEditor/Graphite/pull/2096))</small>\n\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/bevel-node.webm\" type=\"video/webm\" />\n\t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/bevel-node.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- *Jitter Points* node that randomly offsets each point in a vector path <small>([commit 7d86bf4](https://github.com/GraphiteEditor/Graphite/commit/7d86bf4abf7edfe6a5d021075e050614bee07c13))</small>\n\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/jitter-points-node.webm\" type=\"video/webm\" />\n\t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/jitter-points-node.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Node insertion button, and layer renaming, directly from the Properties panel <small>([#2072](https://github.com/GraphiteEditor/Graphite/pull/2072), [#2081](https://github.com/GraphiteEditor/Graphite/pull/2081))</small>\n\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/node-insertion-button-in-properties-panel.webm\" type=\"video/webm\" />\n\t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/node-insertion-button-in-properties-panel.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Path tool feature where pressing <kbd>Space</kbd> while dragging a handle makes the anchor be dragged as well <small>([#2065](https://github.com/GraphiteEditor/Graphite/pull/2065))</small>\n\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/path-tool-space-to-drag-anchor.webm\" type=\"video/webm\" />\n\t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/path-tool-space-to-drag-anchor.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Path tool feature where pressing <kbd>Tab</kbd> while dragging a handle makes it swap to the opposite handle <small>([#2058](https://github.com/GraphiteEditor/Graphite/pull/2058))</small>\n\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/path-tool-tab-to-swap-handle.webm\" type=\"video/webm\" />\n\t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/path-tool-tab-to-swap-handle.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Pen tool feature allowing the connection of layers by their endpoints so they both get merged into a single layer <small>([#2076](https://github.com/GraphiteEditor/Graphite/pull/2076))</small>\n\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/pen-tool-combining-layers.webm\" type=\"video/webm\" />\n\t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/pen-tool-combining-layers.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- *Clamp* node that limits an input number between a minimum and maximum range <small>([#2087](https://github.com/GraphiteEditor/Graphite/pull/2087))</small>\n\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/clamp-node.webm\" type=\"video/webm\" />\n\t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/clamp-node.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- *To U32* and *To U64* nodes that convert numbers to a positive integer type required by a few nodes, as a workaround for automatic type conversion not being fully supported yet <small>([#2087](https://github.com/GraphiteEditor/Graphite/pull/2087))</small>\n- *Dot Product* node that calculates the mathematical dot product between two numerical vectors <small>([#2126](https://github.com/GraphiteEditor/Graphite/pull/2126))</small>\n- *Math* node that calculates a custom math expression with variables \"A\" and \"B\" <small>([#2121](https://github.com/GraphiteEditor/Graphite/pull/2121))</small>\n\n  <img src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/math-node.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Visualization of the Math node properties showing the example expression: 0.33 * A + (1 - 0.33) * B\" />\n\n- Degrees/radians option in the trig-related math nodes and \"Always Positive\" option in the *Modulo* node for more convenient usage of the math nodes <small>([commit d649052](https://github.com/GraphiteEditor/Graphite/commit/d649052255c10c15754c3a3707f2edf996d2468d))</small>\n\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/modulo-and-trig-node-additions.webm\" type=\"video/webm\" />\n\t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/modulo-and-trig-node-additions.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Node graph control bar revamp <small>([#2093](https://github.com/GraphiteEditor/Graphite/pull/2093))</small>\n\n  <img src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/node-graph-control-bar-revamp.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"View of the control bar area of the UI in the node graph featuring new buttons\" />\n\n- Freehand tool feature for drawing new subpaths on an existing vector layer by holding <kbd>Shift</kbd> <small>([commit ed119ad](https://github.com/GraphiteEditor/Graphite/commit/ed119ad3d799030dbc488ccfc8ca9ad057eeff2c))</small>\n\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/freehand-tool-draw-appended-to-layer.webm\" type=\"video/webm\" />\n\t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/freehand-tool-draw-appended-to-layer.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Proper automatic placement of layers into the artboard they're drawn inside of <small>([#2110](https://github.com/GraphiteEditor/Graphite/pull/2110))</small>\n\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/layer-placement-into-artboard-drawn-on.webm\" type=\"video/webm\" />\n\t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/layer-placement-into-artboard-drawn-on.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Menu bar additions of *Layer* > *New*, *Layer* > *Group Selected*, and *Layer* > *Delete Selected* <small>([commit feba874](https://github.com/GraphiteEditor/Graphite/commit/feba87449bb490e47df6f267576bec5ab4238dc3))</small>\n\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/layer-menu-bar-additions.webm\" type=\"video/webm\" />\n\t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/layer-menu-bar-additions.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Select tool box selection feature for subtracting the targetted layers from the active selection with a modifier key as shown in the contextual input hints at the bottom of the editor <small>([#2162](https://github.com/GraphiteEditor/Graphite/pull/2162))</small>\n\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/box-selection-subtraction.webm\" type=\"video/webm\" />\n\t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/box-selection-subtraction.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Path tool feature for snapping to 15° increments and locking the angles of dragged handles when <kbd>Shift</kbd> and <kbd>Ctrl</kbd> modifier keys are pressed <small>([#2160](https://github.com/GraphiteEditor/Graphite/pull/2160))</small>\n\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/path-tool-angle-locking-and-15deg-increments.webm\" type=\"video/webm\" />\n\t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/path-tool-angle-locking-and-15deg-increments.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Support for multiple top output wires extending from the same layer stack <small>([#2049](https://github.com/GraphiteEditor/Graphite/pull/2049))</small>\n\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/layer-stack-multi-top-outputs.webm\" type=\"video/webm\" />\n\t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/layer-stack-multi-top-outputs.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Style improvements to the Layers panel UI to clarify which layers contain selected children, even if hidden within a collapsed layer which previously obscured where selected layers were within the hierarchy <small>([commit 1264ea8](https://github.com/GraphiteEditor/Graphite/commit/1264ea8246cbb06e0602a93be983762ab17adf30))</small>\n\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/layer-panel-show-selected-within-collapsed.webm\" type=\"video/webm\" />\n\t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/layer-panel-show-selected-within-collapsed.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n- Revamped quick measurement overlays now supporting every layer arrangement scenario <small>([#2147](https://github.com/GraphiteEditor/Graphite/pull/2147), [#2155](https://github.com/GraphiteEditor/Graphite/pull/2155))</small>\n\n  <div class=\"video-background\">\n  \t<video autoplay loop muted playsinline disablepictureinpicture disableremoteplayback>\n  \t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/quick-measurement-improvements.webm\" type=\"video/webm\" />\n\t\t<source src=\"https://static.graphite.art/content/blog/2025-03-31-graphite-progress-report-q4-2024/quick-measurement-improvements.mp4\" type=\"video/mp4\" />\n  \t</video>\n  </div>\n\n## Fixes\n- Fix for viewport tools no longer remaining active in the background when the node graph is open <small>([#2093](https://github.com/GraphiteEditor/Graphite/pull/2093))</small>\n- Fix to boolean operations so open subpaths are automatically closed <small>([#2014](https://github.com/GraphiteEditor/Graphite/pull/2014))</small>\n- Fix for a problem with double clicking an anchor for converting it between smooth and sharp <small>([#2023](https://github.com/GraphiteEditor/Graphite/pull/2023))</small>\n- Fix for a *Scatter Points* node breakage <small>([commit 7a56af0](https://github.com/GraphiteEditor/Graphite/commit/7a56af01efc82460e780c78b008a52487972a7eb))</small>\n- Fix for properly considering artboard clipping when calculating click targets <small>([#2028](https://github.com/GraphiteEditor/Graphite/pull/2028), [#2036](https://github.com/GraphiteEditor/Graphite/pull/2036))</small>\n- Fix for <kbd>Ctrl</kbd><kbd>H</kbd> layer hiding and <kbd>Ctrl</kbd><kbd>L</kbd> layer locking only working with the graph open <small>([#2029](https://github.com/GraphiteEditor/Graphite/pull/2029))</small>\n- Fix to artboard label positioning and styling of text overlays <small>([#2032](https://github.com/GraphiteEditor/Graphite/pull/2032))</small>\n- Fix for an assortment of crashes and bugs <small>([#2075](https://github.com/GraphiteEditor/Graphite/pull/2075))</small>\n- Fix for broken gradient transforms with the Vello renderer <small>([#2059](https://github.com/GraphiteEditor/Graphite/pull/2059))</small>\n- Fix for alignment snapping not preserving aspect ratio when <kbd>Shift</kbd> is held <small>([#2062](https://github.com/GraphiteEditor/Graphite/pull/2062))</small>\n- Fix for the Text tool clearing the text when hitting <kbd>Esc</kbd> <small>([#2052](https://github.com/GraphiteEditor/Graphite/pull/2052))</small>\n- Fix for allowing the Path tool to edit an upstream vector path even if there's a type conversion midway <small>([#2055](https://github.com/GraphiteEditor/Graphite/pull/2055))</small>\n- Fix for the number input widget not updating its unit symbol when changing to show another input field <small>([#2080](https://github.com/GraphiteEditor/Graphite/pull/2080))</small>\n- Fix to make the *Sample Points*, *Scatter Points*, and *Splines from Points* nodes generate segments (not just invisible points) and work with subpaths <small>([#2085](https://github.com/GraphiteEditor/Graphite/pull/2085))</small>\n- Fix for sometimes breaking the selected layer upon switching away from the Select tool <small>([commit 8d3da83](https://github.com/GraphiteEditor/Graphite/commit/8d3da83606c23366d2688602afbc0917e7224e68))</small>\n- Fix to remove a visual cutout from the left border of a layer in the node graph when a wire doesn't entering through the cutout <small>([commit 12ca060](https://github.com/GraphiteEditor/Graphite/commit/12ca06035cd7463ed895671ff7eebe53fde655c6))</small>\n- Fix to make point nudging with the Path tool work in document space <small>([#2095](https://github.com/GraphiteEditor/Graphite/pull/2095))</small>\n- Fix to make the *Spline* node algorithm be continuous across start/end points <small>([#2092](https://github.com/GraphiteEditor/Graphite/pull/2092))</small>\n- Fix to properly support layer nudging when the view is tilted and make nudge resizing work in the Artboard tool <small>([#2098](https://github.com/GraphiteEditor/Graphite/pull/2098))</small>\n- Fix to disable menu bar entries when no layer is selected <small>([#2098](https://github.com/GraphiteEditor/Graphite/pull/2098))</small>\n- Fix for clarifying the present state of the Brush tool with a warning message <small>([commit de366f9](https://github.com/GraphiteEditor/Graphite/commit/de366f951424fcdf4463a419db3fa659910fabfd))</small>\n- Fix to load the editor faster by moving font catalog loading to document creation time <small>([commit de366f9](https://github.com/GraphiteEditor/Graphite/commit/de366f951424fcdf4463a419db3fa659910fabfd))</small>\n- Fix to make the Pen tool only append new paths when <kbd>Shift</kbd> is held <small>([#2102](https://github.com/GraphiteEditor/Graphite/pull/2102))</small>\n- Fix to make the Pen tool always snap to endpoint anchors, even when snapping is off <small>([#2107](https://github.com/GraphiteEditor/Graphite/pull/2107))</small>\n- Fix crash when upgrading a document with a *Modulo* node from 3 commits ago <small>([commit 4c4d559](https://github.com/GraphiteEditor/Graphite/commit/4c4d559d97b4d131d2777c0aab19590531ae47a9))</small>\n- Fix to clean up the consistency of the editor preferences dialog <small>([commit 99cf8f0](https://github.com/GraphiteEditor/Graphite/commit/99cf8f0c4f91a051b59fc2c9e5cc6c7417bdd74b))</small>\n- Fix to remove the inconsistently functioning double-click behavior of switching to the Path tool on vector layers, which previously worked only on layers with a Path node <small>([#2116](https://github.com/GraphiteEditor/Graphite/pull/2116))</small>\n- Fix for dragging a pair of colinear handles to break the colinearity so they can move without their anchor <small>([#2120](https://github.com/GraphiteEditor/Graphite/pull/2120))</small>\n- Fix for the broken bounding box of image layers, which also impacted their layer thumbnails <small>([#2122](https://github.com/GraphiteEditor/Graphite/pull/2122))</small>\n- Fix to restore the keyboard shortcut label in the menu bar's *File* > *Close* menu item <small>([#2135](https://github.com/GraphiteEditor/Graphite/pull/2135))</small>\n- Fix to the syntax of exported SVG files that minorly deviated from spec and may have impact some strict SVG viewers <small>([#2131](https://github.com/GraphiteEditor/Graphite/pull/2131))</small>\n- Fix for the UI by removing most \"coming soon\" elements left over from earlier times when placeholders were necessary <small>([commit 1264ea8](https://github.com/GraphiteEditor/Graphite/commit/1264ea8246cbb06e0602a93be983762ab17adf30))</small>\n- Fix for issues with selection history <small>([#2138](https://github.com/GraphiteEditor/Graphite/pull/2138))</small>\n- Fix for a cancellation of a transform cage rotation causing broken state upon the next transformation <small>([#2149](https://github.com/GraphiteEditor/Graphite/pull/2149))</small>\n- Fix to make the Path tool deselect all of a filled shape's points when single-clicked, and select all when double-clicked <small>([#2148](https://github.com/GraphiteEditor/Graphite/pull/2148))</small>\n- Fix for the Select tool's box selection not being able to extend a selection with <kbd>Shift</kbd> <small>([#2157](https://github.com/GraphiteEditor/Graphite/pull/2157))</small>\n- Fix for several bugs in vector-related nodes <small>([commit b81f483](https://github.com/GraphiteEditor/Graphite/commit/b81f48385afc8c9c27820ffe8d5953529f89b7bd))</small>\n- Fix for the Text tool making it easier to select existing text layers with more forgiving click targets <small>([#2145](https://github.com/GraphiteEditor/Graphite/pull/2145))</small>\n- Fix for text layers getting deselected after clicking out of Text tool's interactive editing mode <small>([#2144](https://github.com/GraphiteEditor/Graphite/pull/2144))</small>\n- Fix to make the Artboard tool shift its contents if resizing from the top/left so artwork remains stationary <small>([#2166](https://github.com/GraphiteEditor/Graphite/pull/2166))</small>\n\n\n## Internal\n- Change to make the *Upload Texture* node resolution-aware <small>([#2018](https://github.com/GraphiteEditor/Graphite/pull/2018))</small>\n- Fix for faulty contravariance checking in the type system <small>([#2025](https://github.com/GraphiteEditor/Graphite/pull/2025))</small>\n- Update of Wasm dependencies to fix a crash in Firefox with Vello due to a WebGPU spec change <small>([#2027](https://github.com/GraphiteEditor/Graphite/pull/2027))</small>\n- Simplification of the Bezier-rs interactive web demo code <small>([#2020](https://github.com/GraphiteEditor/Graphite/pull/2020), [commit 4df7803](https://github.com/GraphiteEditor/Graphite/commit/4df780391c0cbb87b12812d0249ed9b62e2740e4))</small>\n- Fix for Clippy code warnings <small>([commit a395fbf](https://github.com/GraphiteEditor/Graphite/commit/a395fbf0637c23b8b05a7c451ff7b8421587b655), [#2119](https://github.com/GraphiteEditor/Graphite/pull/2119))</small>\n- Refactor of assorted parts of the RawKit crate <small>([#1972](https://github.com/GraphiteEditor/Graphite/pull/1972), [#2071](https://github.com/GraphiteEditor/Graphite/pull/2071), [#2066](https://github.com/GraphiteEditor/Graphite/pull/2066), [#2082](https://github.com/GraphiteEditor/Graphite/pull/2082), [#2088](https://github.com/GraphiteEditor/Graphite/pull/2088))</small>\n- Code tidyness cleanup for node ID generation <small>([#2009](https://github.com/GraphiteEditor/Graphite/pull/2009))</small>\n- Refactor collection of snap targets <small>([#2114](https://github.com/GraphiteEditor/Graphite/pull/2114))</small>\n- System for parsing node and parameter descriptions from doc comments, enabling better node tooltips going forward <small>([#2089](https://github.com/GraphiteEditor/Graphite/pull/2089), [#2163](https://github.com/GraphiteEditor/Graphite/pull/2163), [commit d649052](https://github.com/GraphiteEditor/Graphite/commit/d649052255c10c15754c3a3707f2edf996d2468d))</small>\n- Fix for the rectangle constructor in the Bezier-rs library to produce linear segments <small>([#2109](https://github.com/GraphiteEditor/Graphite/pull/2109))</small>\n- Math-Parser library for reading and evaluating math expressions <small>([#2033](https://github.com/GraphiteEditor/Graphite/pull/2033))</small>\n- Simplifications to the implementation of buffered message passing within the editor architecture <small>([#2123](https://github.com/GraphiteEditor/Graphite/pull/2123))</small>\n- Updates to dependencies <small>([#2134](https://github.com/GraphiteEditor/Graphite/pull/2134))</small>\n\n## Website\n- Publication of the previous blog post, [*Graphite progress report (Q3 2024)*](../graphite-progress-report-q3-2024) <small>([#2013](https://github.com/GraphiteEditor/Graphite/pull/2013))</small>\n- Updates to the screenshots on the home page carousel <small>([commit f892687](https://github.com/GraphiteEditor/Graphite/commit/f89268757614bda5f949144f84ccc79bd33ddec2))</small>\n- Updates to the website roadmap and other details <small>([commit d7a271f](https://github.com/GraphiteEditor/Graphite/commit/d7a271f67595863835e80e58636fead9b1fe0a1d), [commit ce0cd39](https://github.com/GraphiteEditor/Graphite/commit/ce0cd39c9b4291e76d59d5b7c427afd39702aed6), [commit 740fcb7](https://github.com/GraphiteEditor/Graphite/commit/740fcb73cc1202d89107a08f3dfccd88dd17a6a3), [commit 54926d5](https://github.com/GraphiteEditor/Graphite/commit/54926d5474f3bcaffd54d0fd0d2509e989cfa425))</small>\n"
  },
  {
    "path": "website/content/blog/2025-04-02-internships-for-a-rust-graphics-engine-gsoc-2025.md",
    "content": "+++\ntitle = \"Internships for a Rust graphics engine: GSoC 2025\"\ndate = 2025-04-02\n[extra]\nbanner = \"https://static.graphite.art/content/blog/2025-04-02-internships-for-a-rust-graphics-engine-gsoc-2025.avif\"\nbanner_png = \"https://static.graphite.art/content/blog/2025-04-02-internships-for-a-rust-graphics-engine-gsoc-2025.png\"\nauthor = \"Keavon Chambers\"\nsummary = \"Join Graphite in Google Summer of Code 2025 for a unique opportunity to contribute to open-source software development in Rust and computer graphics. Get paid while learning, working on self-contained projects under experienced mentors, and help Graphite grow.\"\nreddit = \"https://www.reddit.com/r/graphite/comments/1jplm6t/internships_for_a_rust_graphics_engine_gsoc_2025/\"\ntwitter = \"https://x.com/GraphiteEditor/status/1907384498389651663\"\nbluesky = \"https://bsky.app/profile/graphiteeditor.bsky.social/post/3llt7lbmm4s24\"\n\njs = [\"/js/component/youtube-embed.js\"]\ncss = [\"/component/youtube-embed.css\"]\n+++\n\nDo you love Rust and focus on computer graphics, compiler development, or algorithm research? Students (and non-students who are new to open source contribution) interested in a paid summer internship are invited to apply this week to Graphite's Google Summer of Code 2025 program. The deadline is [April 8 at 18:00 UTC](https://www.wolframalpha.com/input?i=April+8%2C+18%3A00+UTC).\n\n<!-- more -->\n\n[Google Summer of Code](https://summerofcode.withgoogle.com/) is a program that supports students and open source projects. It provides opportunities for summer-long internship experiences with [paid stipends](https://developers.google.com/open-source/gsoc/help/student-stipends) for completing projects with mentoring organizations. [Graphite is honored to be returning for a second year.](https://summerofcode.withgoogle.com/programs/2025/organizations/graphite)\n\n[Graphite](/) is a data-driven creative design engine that combines an artist-friendly image editing environment with a procedural graphics renderer built with Graphene, a custom Rust-based compiled functional programming language for portable, scriptable graphics pipelines.\n\nHear from our mentoring team about Graphite's tech stack and engineering vision by listening to last month's episode of the Developer Voices podcast:\n\n<div class=\"youtube-embed aspect-16x9\">\n\t<img data-youtube-embed=\"ZUbcwUC5lxA\" src=\"https://static.graphite.art/content/blog/2025-04-02-internships-for-a-rust-graphics-engine-gsoc-2025/developer-voices-podcast-youtube.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Graphite: Image Editing as a Syntax Tree (with Keavon Chambers & Dennis Kobert)\" />\n</div>\n\nFor GSoC 2025, our priority focus is mentoring projects revolving around advanced graphics techniques and compiler development, meeting our goal of extending Graphite into the domain of raster image manipulation. Demonstrated experience in these areas is highly advantageous.\n\nOutstanding applicants tend to be competent, self-directed, productive, and confidence-inspiring. If you're driven to learn and explore cutting-edge engineering challenges, please [get in touch](https://discord.graphite.art) and we will help you get up to speed and assist in submitting a timely application.\n\nOut of the [full list of project opportunities](/volunteer/guide/student-projects/#project-idea-list), some interesting high-priority projects include:\n\n- Inventing efficient rendering of connected \"mesh vector\" Bézier geometry\n- Extending the Graphene compiler to run GPU compute shader graphics pipelines\n- Implementing bidirectional type inference into the Graphene type system\n- Developing a graph theory-based system for equivalence rewriting of nodes\n- Creating an efficient brush rendering system for large painted path datasets\n- Managing colors through flexible use of color spaces, color models, and color profiles\n- Researching an assortment of algorithms for image processing operations\n- Engineering solutions for combining our web-based editor GUI and native WGPU renderer in the same window across Windows, Mac, and Linux desktop applications\n\nThese recently added projects are pending an extended description, but we'll be happy to discuss the details if you pop into our [Discord server](https://discord.graphite.art) and introduce yourself with links to related experience and projects.\n\nOutside GSoC, additional year-round opportunities are available for student capstone or independent research projects. These are run similarly, but are also available to multi-person groups. They provide academic credit instead of a stipend. Reach out anytime if interested.\n"
  },
  {
    "path": "website/content/blog/2025-09-19-graphite-community-meetup-in-germany.md",
    "content": "+++\ntitle = \"Graphite community meetup in Germany\"\ndate = 2025-09-19\n[extra]\nbanner = \"https://static.graphite.art/content/blog/2025-09-19-graphite-community-meetup-in-germany.avif\"\nbanner_png = \"https://static.graphite.art/content/blog/2025-09-19-graphite-community-meetup-in-germany.png\"\nauthor = \"Keavon Chambers\"\nsummary = \"Join us for a Graphite community meetup on October 10th, 2025 in Karlsruhe, Germany. Meet the core team and connect with fellow enthusiasts.\"\nreddit = \"https://www.reddit.com/r/graphite/comments/1nlt64g/graphite_community_meetup_in_germany_october_10/\"\ntwitter = \"https://x.com/GraphiteEditor/status/1969324821205925934\"\nbluesky = \"https://bsky.app/profile/graphiteeditor.bsky.social/post/3lzaz3uizkc2j\"\n+++\n\nTo our European community: mark your calendars for the first official Graphite community meetup on **Friday, October 10th, 2025** in **Karlsruhe, Germany**. Meet core members of the project team and make acquaintance with your fellow Graphite enthusiasts plus Rust developers, open source contributors, and FOSS digital content creation software users.\n\n<!-- more -->\n\nTo ensure we are prepared with enough space and food, [**please RSVP as soon as possible**](https://luma.com/d0psfopk) and remember to return to update the registration if your plans change.\n\nWe are planning to host a mix of short workshops, talks, open-ended design discussions, socializing, and show-and-tell demos (including a hands-on sneak peek of the upcoming desktop app) based on the interests of attendees. [Get in touch](/contact) if you would like to present a talk or demo of how you've been using Graphite. Activities will begin at 16:00 and run until 22:00, with scheduling broken into both afternoon and evening sessions to accommodate options for earlier availability and later arrivals. The schedule of events will take a freeform \"unconference\" style in the afternoon followed by somewhat more structured plans in the evening. You are invited to attend all sessions or drop in for the later parts as your schedule and travel plans allow. Feel free to prepare suggestions for topics that we can jump between every half hour.\n\nIn attendance will be:\n\n- Keavon Chambers, Graphite founder, designer, and project lead\n- Dennis Kobert, technical lead and core maintainer\n- Timon Schelling, developer of the Graphite desktop app\n- Sebastian Sydow, contributor to Graphite's GPU infrastructure and <a href=\"https://rust-gpu.github.io/\" target=\"_blank\">Rust GPU</a> maintainer\n\nWe look forward to seeing you there.\n"
  },
  {
    "path": "website/content/blog/_index.md",
    "content": "+++\ntitle = \"Blog\"\ntemplate = \"blog.html\"\npage_template = \"article.html\"\nsort_by = \"date\"\ngenerate_feeds = true\n+++\n\n<section id=\"intro\">\n<div class=\"block\">\n\n# Blog\n\n<div class=\"left-right-split\">\n\nLatest news and articles from the Graphite team.\n\n<p class=\"feed\">\n\t<a href=\"/blog/rss.xml\">\n\t\t<img class=\"icon\" src=\"https://static.graphite.art/icons/feed.svg\" alt=\"RSS\" />\n\t\t<span class=\"link arrow\">RSS Feed</span>\n\t</a>\n</p>\n\n</div>\n\n</div>\n</section>\n"
  },
  {
    "path": "website/content/contact.md",
    "content": "+++\ntitle = \"Contact the team\"\n\n[extra]\ncss = [\"/layout/reading-material.css\"]\n+++\n\n<section class=\"reading-material\">\n<div class=\"block\">\n\n# Contact the team\n\n<article>\n\n- Members of the press, please see the [press resources](/press) page.\n- For general discussions, reach out on [Discord](https://discord.graphite.art) or [Reddit](https://www.reddit.com/r/graphite/). \n- To report a bug or request a feature, please [file an issue](https://github.com/GraphiteEditor/Graphite/issues/new) on GitHub.\n- For other inquiries, get in touch by email at [contact<wbr>@graphite<wbr>.art](mailto:contact@graphite.art).\n\n</article>\n\n</div>\n</section>\n"
  },
  {
    "path": "website/content/donate.md",
    "content": "+++\ntitle = \"Donate\"\n\n[extra]\ncss = [\"/page/donate.css\", \"/component/feature-box.css\", \"/component/feature-icons.css\"]\n+++\n\n<section>\n<div class=\"block\">\n\n# Funding creativity, not corporations\n\n**Own your tools. Own your art.** Invest in a sustainable, independent future for high-quality creative software that cannot ever be taken away.\n\n<div class=\"call-to-action\">\n\n<span>\n<a href=\"https://github.com/sponsors/GraphiteEditor\" target=\"_blank\" class=\"button arrow\">Donate: GitHub Sponsors</a>\n<em>Avoids processing fees</em>\n</span>\n\n<span>\n<a href=\"#supporter-memberships\" class=\"button arrow\">Donate: without an account</a>\n<em>Start to finish in several seconds</em>\n</span>\n\n</div>\n\n<div class=\"feature-icons three-wide statistics\" data-statistics>\n\t<div class=\"feature-icon\">\n\t\t<img class=\"atlas\" style=\"--atlas-index: 34\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__3.png\" alt=\"\" />\n\t\t<span data-statistics-dollars></span>\n\t</div>\n\t<div class=\"feature-icon\">\n\t\t<img class=\"atlas\" style=\"--atlas-index: 3\" src=\"https://static.graphite.art/icons/icon-atlas-features__2.png\" alt=\"\" />\n\t\t<span data-statistics-members></span>\n\t</div>\n\t<div class=\"feature-icon\">\n\t\t<img class=\"atlas\" style=\"--atlas-index: 47\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__3.png\" alt=\"\" />\n\t\t<span data-statistics-donors></span>\n\t</div>\n</div>\n\n<script>\n(async () => {\n\tconst element = document.querySelector(\"[data-statistics]\");\n\tconst dollarsElement = document.querySelector(\"[data-statistics-dollars]\");\n\tconst membersElement = document.querySelector(\"[data-statistics-members]\");\n\tconst donorsElement = document.querySelector(\"[data-statistics-donors]\");\n\tif (!dollarsElement || !membersElement || !donorsElement) return;\n\ttry {\n\t\tconst response = await fetch(\"https://graphite.art/sponsorship-stats\");\n\t\tconst json = await response.json();\n\t\tif (!json || !json.recurring || !json.one_time_prior_3_month_sum) throw new Error();\n\t\tconst recurringDollars = parseInt(json.recurring.cents) / 100;\n\t\tconst oneTimeAverageDollars = parseInt(json.one_time_prior_3_month_sum.cents) / 100 / 3;\n\t\tdollarsElement.innerText = \"$\" + Math.round(recurringDollars + oneTimeAverageDollars).toLocaleString(\"en-US\") + \" / month\";\n\t\tmembersElement.innerText = json.recurring.count.toLocaleString(\"en-US\") + \" members (supporting monthly)\";\n\t\tdonorsElement.innerText = Math.round(json.one_time_prior_3_month_sum.count / 3).toLocaleString(\"en-US\") + \" one-time donors (past month)\";\n\t\t// Force repaint to work around Safari bug <https://bugs.webkit.org/show_bug.cgi?id=286403> (remove this and its data attribute when the bug is fixed and widely deployed)\n\t\telement.style.transform = \"scale(1)\";\n\t} catch {\n\t\telement.remove();\n\t}\n})();\n</script>\n\nGraphite is 100% built and funded by the community. Your contributions directly help us level up the scope and speed of the project's development. Resources are put towards infrastructure, operational costs, swag to keep contributors happy and motivated, and outreach like exhibiting at conventions and traveling to conferences to foster industry relationships. Hiring full-time developers is the next crucial milestone.\n\n</div>\n</section>\n\n<section id=\"supporter-memberships\" class=\"feature-box-outer\">\n<div class=\"feature-box-inner\">\n\n<div class=\"block\">\n\n<h1 class=\"feature-box-header\">Supporter memberships</h1>\n\n---\n\nClick a membership level below to pay directly by card, no account needed.\n\nA small fee of 3.6% + 30¢ reduces what we receive each month. If convenient, consider instead using <a href=\"https://github.com/sponsors/GraphiteEditor\" target=\"_blank\">GitHub Sponsors</a> for **no fees**.\n\n</div>\n\n<div class=\"triptych\">\n\n<a href=\"https://buy.stripe.com/6oE2btfCK9863vybII\" target=\"_blank\" class=\"block feature-box-narrow\">\n\n<h1 class=\"feature-box-header\">⭕ &ldquo;Quark&rdquo; &raquo;</h1>\n\n**$5 / month**\n\n- Your GitHub profile unlocks a shiny achievement acknowledging your contribution *(through GitHub Sponsors only)*\n\n</a>\n<a href=\"https://buy.stripe.com/00gdUb62aesq9TW7st\" target=\"_blank\" class=\"block feature-box-narrow\">\n\n<h1 class=\"feature-box-header\">➕ &ldquo;Proton&rdquo; &raquo;</h1>\n\n**$10 / month**\n\n- Get a **\"Member\" role** and accompanying **gold-colored nametag on Discord**\n- *Plus the lower-tier rewards*\n\n</a>\n<a href=\"https://buy.stripe.com/5kAbM38aiacaeac28a\" target=\"_blank\" class=\"block feature-box-narrow\">\n\n<h1 class=\"feature-box-header\">⚛️ &ldquo;Carbon&rdquo; &raquo;</h1>\n\n**$15 / month**\n\n- Your name/handle listed in the end-of-year **retrospective blog post** thank-you section\n- *Plus the lower-tier rewards*\n\n</a>\n\n<a href=\"https://buy.stripe.com/28o4jB62a0BA5DGbIL\" target=\"_blank\" class=\"block feature-box-narrow\">\n\n<h1 class=\"feature-box-header\">🧬 &ldquo;DNA&rdquo; &raquo;</h1>\n\n**$25 / month**\n\n- Your **personal name** (or handle) **on the Graphite website and GitHub readme**\n- Option to be mailed a personal **thank-you card with Graphite stickers** (in the US only)\n- *Plus the lower-tier rewards*\n\n</a>\n<a href=\"https://buy.stripe.com/28o03laiq0BA8PS6os\" target=\"_blank\" class=\"block feature-box-narrow\">\n\n<h1 class=\"feature-box-header\">🌱 &ldquo;Organism&rdquo; &raquo;</h1>\n\n**$50 / month**\n\n- Option to be given a public **shout-out of appreciation** from @GraphiteEditor on your choice of social media sites\n- *Plus the lower-tier rewards*\n\n</a>\n<a href=\"https://buy.stripe.com/fZedUbduCfwu2ru7sx\" target=\"_blank\" class=\"block feature-box-narrow\">\n\n<h1 class=\"feature-box-header\">🌄 &ldquo;Biosphere&rdquo; &raquo;</h1>\n\n**$75 / month**\n\n- Your personal name (or handle) may be a **hyperlink** to your personal site or social media profile\n- *Plus the lower-tier rewards*\n\n</a>\n\n</div>\n\n<div class=\"block action-buttons\">\n\n<a href=\"https://donate.stripe.com/6oU8wP6m0c2kb2AermbQY0a\" target=\"_blank\" class=\"button arrow\">Or make a one-time donation</a>\n\n[Manage your ongoing membership](https://billing.stripe.com/p/login/aEU9EzctSfe3cfK5kk)\n\n</div>\n\n</div>\n</section>\n\n<section id=\"corporate-sponsorships\" class=\"feature-box-outer\">\n<div class=\"feature-box-inner\">\n\n<div class=\"block\">\n\n<h1 class=\"feature-box-header\">Corporate sponsorships</h1>\n\n---\n\nAlso available to individuals wanting to make a larger impact. [Reach out](/contact) to pay by invoice or ACH to avoid fees, or for a custom arrangement.\n\n</div>\n\n<div class=\"triptych\">\n\n<a href=\"https://buy.stripe.com/7sI6rJ1LU5VUaY05kq\" target=\"_blank\" class=\"block feature-box-narrow\">\n\n<h1 class=\"feature-box-header\">🪨 &ldquo;Charcoal&rdquo; &raquo;</h1>\n\n**$100 / month**\n\n- Your **company name** may be shown **on the Graphite website and GitHub readme** starting at this tier level\n- *Plus the lower-tier rewards for members*\n\n</a>\n<a href=\"https://buy.stripe.com/3cs8zR8ai0BA8PSaEL\" target=\"_blank\" class=\"block feature-box-narrow\">\n\n<h1 class=\"feature-box-header\">🛡️ &ldquo;Carbide&rdquo; &raquo;</h1>\n\n**$250 / month**\n\n- Your name on the website and readme may be a **hyperlink** to your company/personal site\n- *Plus the lower-tier rewards for members*\n\n</a>\n<a href=\"https://buy.stripe.com/fZeaHZ76e0BAeaccMU\" target=\"_blank\" class=\"block feature-box-narrow\">\n\n<h1 class=\"feature-box-header\">💎 &ldquo;Diamond&rdquo; &raquo;</h1>\n\n**$500 / month**\n\n- Your name and link on the website and readme may instead be a **hyperlinked logo**\n- *Plus the lower-tier rewards for members*\n\n</a>\n\n</div>\n\n<div class=\"block action-buttons\">\n\n<a href=\"https://donate.stripe.com/6oU8wP6m0c2kb2AermbQY0a\" target=\"_blank\" class=\"button arrow\">Or make a one-time donation</a>\n\n[Manage your ongoing membership](https://billing.stripe.com/p/login/aEU9EzctSfe3cfK5kk)\n\n</div>\n\n</div>\n</section>\n"
  },
  {
    "path": "website/content/features.md",
    "content": "+++\ntitle = \"Graphite features\"\n\n[extra]\ncss = [\"/page/features.css\", \"/component/feature-box.css\", \"/component/feature-icons.css\", \"/component/youtube-embed.css\"]\njs = [\"/js/component/youtube-embed.js\"]\n+++\n\n<section>\n<div class=\"block\">\n\n# Graphite features\n\nThe current alpha version of Graphite is a tool for vector art and graphic design. It also supports a limited, experimental raster editing toolset. This tooling is built around a procedural graphics engine, letting artists build complex graphics and animations in its visual scripting language.\n\n</div>\n</section>\n\n<section>\n<div class=\"block\">\n\n<div class=\"block video-container\">\n<div>\n<div class=\"youtube-embed aspect-16x9\">\n\t<img data-youtube-embed=\"ZUbcwUC5lxA\" loading=\"lazy\" src=\"https://static.graphite.art/content/features/podcast-interview-youtube.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Rust-Powered Graphics Editor: How Graphite's Syntax Trees Revolutionize Image Editing\" />\n</div>\n</div>\n</div>\n\n</div>\n</section>\n\n<section>\n\n<div class=\"diptych\">\n\n<div class=\"block\">\n\n## Layers & nodes: hybrid editing\n\nGraphite combines the best ideas from multiple categories of digital content creation software to reimagine the workflows of 2D graphics editing. It is influenced by the core editing experience of traditional layer-based raster and vector tools, the nondestructive approaches of VFX compositing programs used by film studios, and the boundless creative possibilities of procedural production tools daily-driven by the 3D industry.\n\nClassic layer-based image editing is easy to understand, employing collapsable folders that help artists stay organized. A variety of interactive viewport tools make it easy to manipulate the layers by drawing directly onto the canvas. On the other hand, node-based editing is essentially artist-friendly programming. It works by describing manipulations as steps in a flowchart, which is vastly more powerful but comes with added complexity.\n\nThe hybrid workflow pioneered by Graphite is able to deliver a classic tool-centric, layer-based editing experience built around a procedural, node-based compositor. Users can ignore the node graph, use it exclusively, or switch back and forth with the press of a button while creating content. Interacting with the canvas using tools will manipulate the nodes behind the scenes. And the layer panel and node graph provide two equivalent, interchangeable views of the same document structure.\n\n</div>\n<div class=\"block\">\n\n## Raster & vector: sharp at all sizes\n\nDigital 2D art commonly takes two forms. Raster artwork is made out of pixels which means it can look like anything imaginable, but it becomes blurry or pixelated when upscaling to a higher resolution. Vector artwork is made out of curved shapes which is perfect for some art styles but limiting to others. The magic of vector is that its mathematically-described curves can be enlarged to any size and remain crisp.\n\nOther apps commonly focus on just raster or vector, forcing artists to buy and learn separate products for both. Mixing art styles requires shuttling content back and forth between programs. And since picking a raster document resolution is a one-time commitment, artists often choose to start out big, resulting in sluggish editing performance and multi-gigabyte documents.\n\nGraphite reinvents raster rendering so it stays sharp at any scale. Artwork is treated as data, not pixels, and is always redrawn at the current viewing resolution. Zoom the viewport and export images at any size— the document's paint brushes, masks, filters, and effects will always be rendered in full detail.\n\nMarrying vector and raster under one roof enables both art forms to complement each other in one cohesive creative workflow. *(Scalable raster compositing is still experimental.)*\n\n</div>\n\n</div>\n\n</section>\n\n<section>\n<div class=\"block\">\n\n## Roadmap\n\n<div class=\"roadmap\">\n\t<div class=\"feature-icons\">\n\t\t<!-- Pre-Alpha -->\n\t\t<div class=\"feature-icon complete heading\" data-year=\"2021\">\n\t\t\t<h3>— Pre-Alpha —</h3>\n\t\t</div>\n\t\t<div class=\"feature-icon complete\" title=\"Development Complete\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 1\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Editor systems; basic vector art tools</span>\n\t\t</div>\n\t\t<!-- Alpha 1 -->\n\t\t<div class=\"feature-icon complete heading\" data-year=\"2022\">\n\t\t\t<h3>— Alpha 1 —</h3>\n\t\t</div>\n\t\t<div class=\"feature-icon complete\" title=\"Development Complete\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 2\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Better tools; node graph prototyping</span>\n\t\t</div>\n\t\t<!-- Alpha 2 -->\n\t\t<div class=\"feature-icon complete heading\" data-year=\"2023\">\n\t\t\t<h3>— Alpha 2 —</h3>\n\t\t</div>\n\t\t<div class=\"feature-icon complete\" title=\"Development Complete\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 6\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Node graph integration in documents</span>\n\t\t</div>\n\t\t<!-- Alpha 3 -->\n\t\t<div class=\"feature-icon complete heading\" data-year=\"2024\">\n\t\t\t<h3>— Alpha 3 —</h3>\n\t\t</div>\n\t\t<div class=\"feature-icon complete\" title=\"Development Complete\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 8\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Procedural vector editing and usability</span>\n\t\t</div>\n\t\t<!-- Alpha 4 -->\n\t\t<div class=\"feature-icon ongoing heading\" data-year=\"2025–\">\n\t\t\t<h3>— Alpha 4 —</h3>\n\t\t</div>\n\t\t<div class=\"feature-icon complete\" title=\"Development Complete\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 46\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Parametric animation</span>\n\t\t</div>\n\t\t<div class=\"feature-icon complete\" title=\"Development Complete\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 58\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Instancer nodes for looped generation</span>\n\t\t</div>\n\t\t<div class=\"feature-icon complete\" title=\"Development Complete\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 59\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Enhanced Pen, Path, and Shape tools</span>\n\t\t</div>\n\t\t<div class=\"feature-icon complete\" title=\"Development Complete\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 63\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Table-based graphical data format</span>\n\t\t</div>\n\t\t<div class=\"feature-icon complete\" title=\"Development Complete\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 67\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Data panel for graphical introspection</span>\n\t\t</div>\n\t\t<div class=\"feature-icon complete\" title=\"Development Complete\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 13\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Layer clipping masks</span>\n\t\t</div>\n\t\t<div class=\"feature-icon complete\" title=\"Development Complete\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 12\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>GPU acceleration infrastructure</span>\n\t\t</div>\n\t\t<div class=\"feature-icon complete\" title=\"Development Complete\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 66\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>All-around performance optimizations</span>\n\t\t</div>\n\t\t<div class=\"feature-icon ongoing\" title=\"Development Ongoing\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 73\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Blend tool to morph between shapes</span>\n\t\t</div>\n\t\t<div class=\"feature-icon ongoing\" title=\"Development Ongoing\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 17\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Stable document format</span>\n\t\t</div>\n\t\t<div class=\"feature-icon ongoing\" title=\"Development Ongoing\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 7\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Desktop app <a href=\"/#desktop-first-and-web-ready\">release candidates</a></span>\n\t\t</div>\n\t\t<!-- Beta 1 -->\n\t\t<div class=\"feature-icon heading\" data-year=\"Early 2026\">\n\t\t\t<h3>— Beta 1 —</h3>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 7\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Desktop app release and 1.0 launch</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 51\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Context menus throughout the editor</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 40\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Simplified main properties panel</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 64\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Node version management</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 53\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Local fonts access</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 54\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Saving over local files (web version)</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 41\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Timeline panel for animation curves</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 62\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Nested documents as custom nodes</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 56\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Variables and color swatches</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 9\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Custom attributes for table data</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 10\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Expanded imaging model data format</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 28\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Physical measurement units</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 12\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>GPU-accelerated raster rendering</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 65\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Text-on-path tool support</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 3\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Per-glyph text style controls</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 48\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Robust vector mesh editing/rendering</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 50\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Nondestructive shape builder tool</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 71\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Automatic image trace vectorization</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 11\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Broader SVG support including filters</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 45\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Parametric art standalone export</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 5\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>New and improved brush tool</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 43\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Stylus and touch interaction</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 70\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>MIDI and audio-reactive visualization</span>\n\t\t</div>\n\t\t<!-- Beta 2 -->\n\t\t<div class=\"feature-icon heading\" data-year=\"2027\">\n\t\t\t<h3>— Beta 2 —</h3>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 24\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Dockable and multi-window panels</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 52\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Command palette</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 68\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Onion skinning mode for animation</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 69\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Animatable deformation meshes/rigs</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 72\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Simulation domains</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 57\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Signed distance field rendering</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 14\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Procedural PBR material generation</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 16\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Code editor for custom nodes</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 34\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Asset libraries and node marketplace</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 27\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Automation/batch processing tools</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 21\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Select mode (marquee masking)</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 0\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Raster adjustments, filters, and effects</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 25\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Liquify and warp transforms</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 19\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Raw photo processing</span>\n\t\t</div>\n\t\t<!-- LTS Releases -->\n\t\t<div class=\"feature-icon heading\" data-year=\"Future\">\n\t\t\t<h3>— LTS Releases —</h3>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 31\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Advanced typesetting features</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 32\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>PDF, EPS, AI, DXF, PSD, and TIFF</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 55\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>CMYK, spot color, and ICC profiles</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 33\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>HDR and WCG color handling</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 4\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Outliner panel (node graph tree view)</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 18\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Document history management</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 39\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Offline edit resolution with CRDTs</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 22\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>History brush and clone stamp tools</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 23\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Internationalization and accessibility</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 49\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Neural nodes/tools like Magic Wand</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 20\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Procedural styling of paint brushes</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 60\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Infinite generative vector patterns</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 29\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Geometric constraint system solver</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 30\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Responsive design layout solver</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 61\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Authoring animated SVGs, Lottie, etc.</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 42\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Live video stream compositing</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 44\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Tablet app and keyboard-free controls</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 26\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Media collection manager/browser</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 37\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Cloud document storage/device sync</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 38\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Multiplayer collaborative editing</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 35\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Predictive graph rendering/caching</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 36\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Multi-device distributed rendering</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 15\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span>Hosted rendering accelerator service</span>\n\t\t</div>\n\t\t<div class=\"feature-icon\">\n\t\t\t<img class=\"atlas\" style=\"--atlas-index: 47\" src=\"https://static.graphite.art/icons/icon-atlas-roadmap__5.png\" alt=\"\" />\n\t\t\t<span><em>…and that's all just the beginning…</em></span>\n\t\t</div>\n\t</div>\n</div>\n\n</div>\n</section>\n\n<section>\n\n<div class=\"block\">\n\n## Roadmap spotlight: keyframe animation\n\nComing early 2026, Graphite will expand its animation toolset beyond parametrically-driven motion to include traditional keyframe animation. The Timeline panel pictured below will let animators drive parameters using keyframes and curves through a traditional dopesheet interface.\n\nNode parameters can be set to a constant value in the Properties panel, exposed to the graph for procedural animation, or exposed to a channel in the upcoming Timeline panel for hand-authored keyframing.\n\nThe panel will enable users to scrub through time with the playhead and choose between timing with discrete frames or continuous seconds. A dedicated curves editing mode (not pictured) will enable fine-tuning parameters with a labeled Y-axis, while the dopesheet allows individual channels to be expanded to view and edit the shape and smoothness of curves inline.\n\nWork-in-progress design mockup:\n\n<img src=\"https://static.graphite.art/content/features/mockup-timeline-panel.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"\" />\n\n</div>\n\n</section>\n\n<section>\n\n<div class=\"block\">\n\n## Roadmap spotlight: raster image editing\n\nThe vision since Graphite's inception has been to open up the traditional raster image editing workflow to the greater degree of flexibility found in node-based compositors, without one approach compromising the ergonomics of the other.\n\nAs with Graphite's current vector toolset, raster editing will interpret the user's interactive edits as modifications to the construction of layers in the underlying node graph rather than destructive alterations to layer pixel data (as in other image editors). By containing only a description of the user's editing operations without the data, documents will remain ultra tiny when source assets are linked externally.\n\nBrushes, selection tools, masks, filters, effects, adjustment layers, and other tools used to manipulate raster layers will all be presented in a familiar form when Graphite's raster toolset nears maturity by the end of beta.\n\nWork-in-progress design mockup:\n\n<img src=\"https://static.graphite.art/content/features/mockup-viewport.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"\" />\n\n<img src=\"https://static.graphite.art/content/features/mockup-node-graph.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"\" />\n\n</div>\n\n</section>\n"
  },
  {
    "path": "website/content/learn/_index.md",
    "content": "+++\ntitle = \"User manual\"\ntemplate = \"book.html\"\npage_template = \"book.html\"\n\n[extra]\nbook = true\njs = [\"/js/component/youtube-embed.js\"]\ncss = [\"/component/youtube-embed.css\"]\n+++\n\nWelcome to the Graphite user manual. Keep reading to learn how the software can help bring your 2D creative ideas to life.\n\n## More chapters on the way\n\nAdditional manual chapters are being added over time. Check back every so often.\n\n## Need help?\n\nIf you're ever stuck or confused, ask your questions in the `#🧭user-help` channel of the [Graphite Discord server](https://discord.graphite.art) or post a thread in the [discussion board](https://github.com/GraphiteEditor/Graphite/discussions) on GitHub.\n\n## Jump right in\n\n<!-- If you're eager to skip the reading, head straight to the [hands-on quickstart video](./introduction) in the next chapter for a beginner project walkthrough you can follow along with. -->\n\nThe fastest way to get started is to watch and follow along steps-by-step in the hands-on quickstart tutorial:\n\n<div class=\"youtube-embed aspect-16x9\">\n\t<img data-youtube-embed=\"7gjUhl_3X10\" src=\"https://static.graphite.art/content/learn/introduction/tutorial-1-vector-art-quickstart-youtube__2.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Vector Art Quickstart - Graphite, the Open Source 2D Graphics Suite\" />\n</div>\n"
  },
  {
    "path": "website/content/learn/_outline_draft.md",
    "content": "+++\ntitle = \"Outline Draft\"\ntemplate = \"book.html\"\npage_template = \"book.html\"\ndraft = true\n\n[extra]\norder = 0\n+++\n\n- # Interface\n\t- # Layers panel\n\t\t- About layers and folders\n\t\t- Control bar\n\t\t\t- Blend modes and opacity\n\t\t\t- Adding folders\n\t\t\t- Deleting the selected layers\n\t\t- The layer list\n\t\t\t- Editing layers\n\t\t\t- Selection and multi-selection\n\t\t\t- Rearranging the hierarchy\n\t- # Properties panel\n\t\t- Node properties\n\t\t- Parameters\n\t\t\t- Expose button\n\t\t\t- Name\n\t\t\t- Value inputs\n- # Viewport\n\t- # Navigation\n\t\t- Pan\n\t\t- Tilt\n\t\t- Zoom\n\t- # Artboards\n\t\t- Infinite canvas\n\t\t- New document artboard\n\t\t- Adjusting existing artboards\n- # Graph\n\t- # INDEX:\n\t\t- Opening the graph\n\t\t- Document graph vs. layer graph, limitations\n\t\t- Adding nodes\n\t\t- Connecting nodes\n\n\t\t## Overlaid node graph editing\n\n\t\tOpening the overlaid node graph shows the structure of nodes and layers that compose the document artwork. It's a more detailed view of what the [Layers](../layers-panel) and [Properties](../properties-panel) panels show.\n\n\t\t**Nodes** are the entities with left-to-right input **connectors**.\n\n\t\t**Layers** are the larger entities shown with thumbnails and a bottom-to-top direction of data stacking. Their purpose is to composite sources of graphical data on top of one another in a **layer stack**. Layers take input from other nodes or layers via a connector on their left side. When that connector is fed by another layer stack, the Layers panel considers it a **group** because it combines one stack into another parent stack.\n\n\t\tLayers and nodes are wired together using **links** which send data between the outputs of nodes to the inputs of others. You can wire up a node by dragging from the output connector of one node to the input connector of its destination node. But note that forming cyclic graphs, where a loop can be traced along the links of a set of nodes, is not permitted. Graphical data flows into the **Output** node which then becomes rendered to the document viewport.\n\n\t\t### Node/layer controls\n\n\t\tWhen a layer or node is selected, these buttons will show up on the left side of the control bar:\n\n\t\t<p><img src=\"https://static.graphite.art/content/learn/interface/document-panel/node-controls-buttons.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" onload=\"this.width = this.naturalWidth / 2\" alt=\"The node/layer controls\" /></p>\n\n\t\t| | |\n\t\t|-|-|\n\t\t| Make<span>&nbsp;</span>Hidden/<br />Make<span>&nbsp;</span>Visible | <p>Toggles the visibility state of the layer or node. This is equivalent to the eye icon button displayed beside each layer. If a node or layer is hidden, it gets bypassed in the data flow. <kbd>Ctrl</kbd><kbd>H</kbd> (macOS: <kbd>⌘</kbd><kbd>H</kbd>) is a shortcut for this toggle that can be used from the graph or viewport.</p> |\n\t\t| Preview/<br />End<span>&nbsp;</span>Preview | <p>Temporarily moves the graph output away from the Output node and the graph output is instead provided by the previewed node. While previewing, the node is styled with a dashed, brighter border. Ending the preview returns responsibility back to the Output node. This is a handy feature for viewing part of a graph without needing to disconnect the actual Output node and manually restore it later. Clicking a node or layer in the graph while holding <kbd>Alt</kbd> is a shortcut for toggling its preview.</p> |\n\t- # Nodes\n\t- # Layers\n- # Vector Editing\n\t- # Nodes\n\t- # Layers\n- # Raster Editing\n\t- # INDEX:\n\t\t- Opening the graph\n\t\t- Document graph vs. layer graph, limitations\n\t\t- Adding nodes\n\t\t- Connecting nodes\n\t- # Nodes\n\t- # Layers\n- # Tools\n\t- # General tools\n\t- # Vector tools\n\t- # Raster tools\n"
  },
  {
    "path": "website/content/learn/interface/_index.md",
    "content": "+++\ntitle = \"Interface\"\ntemplate = \"book.html\"\npage_template = \"book.html\"\n\n[extra]\norder = 2\n+++\n\nThis chapter formally introduces the concepts and terminology for the user interface (UI) of the Graphite editor. You may skip to the next chapter if you're familiar with the general layout and terms used in industry-standard graphics editors.\n\n## Title bar\n\nThe bar running across the top of the editor is called the **title bar**. In the (forthcoming) desktop release of Graphite, this acts as the draggable window frame.\n\n<!-- <p><img src=\"https://static.graphite.art/content/learn/interface/title-bar.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"The title bar\" /></p> -->\n\n### Menu bar\n\nOn the left, the [**menu bar**](./menu-bar) provides quick access to many editor, document, and artwork related controls. Its functions are covered in detail on the next page.\n\n<!-- <p><img src=\"https://static.graphite.art/content/learn/interface/menu-bar.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"The menu bar\" /></p> -->\n\n<!-- In the (forthcoming) macOS desktop release, the menu bar is absent from the editor window; its functions are instead located in macOS menu bar. -->\n\n### Window buttons\n\nOn the right (and on Mac, the left), the **window buttons** provide platform-specific controls for the application.\n\n<!-- In the (forthcoming) macOS desktop release, this appears on the left side instead. -->\n\n| | |\n|-|-|\n| **Web** | <p>Fullscreen:</p><p><img src=\"https://static.graphite.art/content/learn/interface/window-buttons-web__2.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Fullscreen button\" /></p> |\n| **Windows** | <p>Minimize, maximize/restore down, close:</p><p><img src=\"https://static.graphite.art/content/learn/interface/window-buttons-windows.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Minimize/maximize/close window buttons\" /></p> |\n| **Linux** | <p>Minimize, maximize/unmaximize, close:</p><p><img src=\"https://static.graphite.art/content/learn/interface/window-buttons-linux.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Minimize/maximize/close window buttons\" /></p> |\n| **macOS** | <p>Close, minimize, fullscreen:</p><p><img src=\"https://static.graphite.art/content/learn/interface/window-buttons-macos.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Close/minimize/fullscreen window buttons\" /></p> |\n\n## Workspace\n\nThe **workspace** is the editor's main content area, filled with **panels** arranged next to one another. The **gutter** lines, located between neighboring panels, may be dragged to resize them.\n\n<p><img src=\"https://static.graphite.art/content/learn/interface/workspace__3.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"The workspace\" /></p>\n\n### Panels\n\nPanels are regions of the UI dedicated to a specific purpose. [**Document**](./document-panel), Properties, and Layers are presently the three panel types.\n\nEach panel name is shown in its **panel header**. Panel tabs offer a quick way to swap between multiple panels occupying the same area (currently only documents support this).\n\nDown the road, these tabs will be dockable so the default layout may be customized.\n\nBeneath the panel header, the **panel content** displays the content for its panel type. Each will be described in the following pages.\n\n## Status bar\n\nThe bar running across the bottom of the editor is called the **status bar**.\n\n<p><img src=\"https://static.graphite.art/content/learn/interface/status-bar__2.avif\" onload=\"this.width = this.naturalWidth / 2\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Status bar\" /></p>\n\n### Input hints\n\nThe **input hints** are presently the only occupant of the status bar. They indicate what common keyboard and mouse inputs are valid in the current context. Hints change with each active tool as well as with the current interaction state. Keep a frequent eye on the hints to discover more features as you work.\n\nHints with a **`+`** mean that adding the indicated modifier key will change the base action. For example: in the following action, dragging with left-click held down will zoom the canvas; then additionally holding the <kbd>Ctrl</kbd> key will make the zoom action snap to whole increments.\n\n<p><img src=\"https://static.graphite.art/content/learn/interface/input-hints-plus.avif\" onload=\"this.width = this.naturalWidth / 2\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Example hint\" /></p>\n\nHints with a **`/`** mean that either indicated input combination can be used to trigger the same action. For example: in the following action, either holding the space bar while dragging with the left mouse button held down, or just dragging with the middle mouse button held down, will both pan around the document in the viewport.\n\n<p><img src=\"https://static.graphite.art/content/learn/interface/input-hints-slash.avif\" onload=\"this.width = this.naturalWidth / 2\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Example hint\" /></p>\n\nThe following chart describes each icon representing the mouse inputs you can perform so a hint's prescribed action occurs.\n\n| | Clicks | Drags | Others |\n|-|:-:|:-:|:-:|\n| **Left<br />mouse<br />button** | <img src=\"https://static.graphite.art/content/learn/interface/mouse-icon-left-click.avif\" onload=\"this.width = this.naturalWidth / 2\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Left click icon\" /><br /><br />Left click | <img src=\"https://static.graphite.art/content/learn/interface/mouse-icon-left-click-drag.avif\" onload=\"this.width = this.naturalWidth / 2\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Left click drag icon\" /><br /><br />Left click drag | <img src=\"https://static.graphite.art/content/learn/interface/mouse-icon-left-double-click.avif\" onload=\"this.width = this.naturalWidth / 2\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Left double-click icon\" /><br /><br />Left double-click |\n| **Right<br />mouse<br />button** | <img src=\"https://static.graphite.art/content/learn/interface/mouse-icon-right-click.avif\" onload=\"this.width = this.naturalWidth / 2\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Right click icon\" /><br /><br />Right click | <img src=\"https://static.graphite.art/content/learn/interface/mouse-icon-right-click-drag.avif\" onload=\"this.width = this.naturalWidth / 2\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Right click drag icon\" /><br /><br />Right click drag | <img src=\"https://static.graphite.art/content/learn/interface/mouse-icon-right-double-click.avif\" onload=\"this.width = this.naturalWidth / 2\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Right double-click icon\" /><br /><br />Right double-click |\n| **Middle<br />mouse<br />button** | <img src=\"https://static.graphite.art/content/learn/interface/mouse-icon-middle-click.avif\" onload=\"this.width = this.naturalWidth / 2\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Middle click icon\" /><br /><br />Middle click | <img src=\"https://static.graphite.art/content/learn/interface/mouse-icon-middle-click-drag.avif\" onload=\"this.width = this.naturalWidth / 2\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Middle click drag icon\" /><br /><br />Middle click drag | <img src=\"https://static.graphite.art/content/learn/interface/mouse-icon-scroll.avif\" onload=\"this.width = this.naturalWidth / 2\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Scroll up/down icons\" /><br /><br />Scroll up/down |\n| **No<br />mouse<br />button** | | <img src=\"https://static.graphite.art/content/learn/interface/mouse-icon-drag.avif\" onload=\"this.width = this.naturalWidth / 2\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Drag icon\" /><br /><br />Drag | |\n"
  },
  {
    "path": "website/content/learn/interface/document-panel.md",
    "content": "+++\ntitle = \"Document panel\"\n\n[extra]\norder = 2\n+++\n\nThe **Document panel** is the main content area where the artwork is displayed and edited using **tools** within the **viewport**. It's also where the **node graph** can be overlaid by pressing <kbd>Ctrl</kbd><kbd>Space</kbd>.\n\nThe viewport is for interactive, visual editing of the **canvas**. The node graph is where you can inspect the underlying structure of the document and edit it in a more technical manner when the need arises.\n\nThere is one instance of the Document panel per open document file. Each has its own tab labeled with its file name. When a document has unsaved changes, an `*` is included at the end of the name.\n\nThe Document panel is composed of three main areas:\n\n- The **control bar** runs across the top of the panel and shows tool and viewport controls.\n- The **tool shelf** is the vertical bar that runs down the left of the panel showing the editing tools and working colors.\n- The **viewport** fills most of the panel and contains a view of the canvas which can be interactively edited using the tools.\n\n## Control bar\n\nHere is where you control your interaction with the document via active tool and view options.\n\n<!--\n### Editing modes\n\nOnly the default mode is currently implemented. Others will be added in the future and this dropdown is a placeholder for that.\n\n| | |\n|-|-|\n| <img src=\"https://static.graphite.art/content/learn/interface/document-panel/editing-modes__2.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" onload=\"this.width = this.naturalWidth / 2\" alt=\"The editing modes dropdown menu\" /> | The default, **Design Mode**, is for directly editing the artwork.<br /><br />Once implemented, **Select Mode** will be where marquee selections are made to constrain the active tool's edits to a masked area of choice.<br /><br />Once implemented, **Guide Mode** will be for creating guides and constraint systems used for alignment and constraint-based layout. |\n-->\n\n### Tool controls\n\nThe left side of the control bar has controls pertaining to the active tool. It's empty for some tools.\n\n<p><img src=\"https://static.graphite.art/content/learn/interface/document-panel/tool-options__2.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Example of the tool controls for the Select tool\" /></p>\n\nThe example above shows the Select tool controls. Because the Select tool deals with selecting and transforming layers, its controls include:\n- The selection mode to determine if the deepest or shallowest layer in a nested hierarchy is selected when clicked.\n- The point about which the **transform cage** is centered.\n- Actions to align and distribute the selected layers amongst themselves.\n- Actions to flip the selected layers horizontally or vertically.\n- Actions to cut or combine selected layers with themselves using boolean operations.\n\nDepending on which tool is active, these will change to reflect the pertinent options.\n\n### Viewport controls\n\nThe right side of the control bar has controls related to the active document and viewport.\n\n<p><img src=\"https://static.graphite.art/content/learn/interface/document-panel/viewport-options__2.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"The viewport controls\" /></p>\n\n| | |\n|-|-|\n| **Overlays** | <p>When checked (default), overlays are shown. When unchecked, they are hidden. Overlays are the temporary contextual visualizations (like bounding boxes and vector manipulators) that are usually blue and appear atop the viewport when using tools.</p> |\n| **Snapping** | <p>When checked (default), drawing and dragging shapes and vector points means they will snap to other areas of geometric interest like corners or anchor points. When unchecked, the selection moves freely.<br /><br />Fine-grained options are available by clicking the overflow button to access its options popover menu. Each option has a tooltip explaining what it does by hovering the cursor over it.</p><p><img src=\"https://static.graphite.art/content/learn/interface/document-panel/snapping-popover__5.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" onload=\"this.width = this.naturalWidth / 2\" alt=\"Snapping options popover menu\" /></p><p>Snapping options relating to **Bounding Boxes**:</p><p><ul><li>**Align with Edges**: Snaps to horizontal/vertical alignment with the edges of any layer's bounding box.</li><li>**Corner Points**: Snaps to the four corners of any layer's bounding box.</li><li>**Center Points**: Snaps to the center point of any layer's bounding box.</li><li>**Edge Midpoints**: Snaps to any of the four points at the middle of the edges of any layer's bounding box.</li><li>**Distribute Evenly**: Snaps to a consistent distance offset established by the bounding boxes of nearby layers (due to a bug, **Corner Points** and **Center Points** must be enabled).</li></ul></p><p>Snapping options relating to **Paths**:</p><p><ul><li>**Align with Anchor Points**: Snaps to horizontal/vertical alignment with the anchor points of any vector path.</li><li>**Anchor Points**: Snaps to the anchor point of any vector path.</li><li>**Line Midpoints**: Snaps to the point at the middle of any straight line segment of a vector path.</li><li>**Path Intersection Points**: Snaps to any points where vector paths intersect.</li><li>**Along Paths**: Snaps along the length of any vector path.</li><li>**Normal to Paths**: Snaps a line to a point perpendicular to a vector path (due to a bug, **Intersections of Paths** must be enabled).</li><li>**Tangent to Paths**: Snaps a line to a point tangent to a vector path (due to a bug, **Intersections of Paths** must be enabled).</li></ul></p> |\n| **Grid** | <p>When checked (off by default), grid lines are shown and snapping to them becomes active. The initial grid scale is 1 document unit, helping you draw pixel-perfect artwork.</p><ul><li><p>**Type** sets whether the grid pattern is made of squares or triangles.</p><p>**Rectangular** is a pattern of horizontal and vertical lines:</p><p><img src=\"https://static.graphite.art/content/learn/interface/document-panel/grid-rectangular-popover__2.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" onload=\"this.width = this.naturalWidth / 2\" alt=\"Snapping options popover menu\" /></p><p>It has one option unique to this mode:</p><ul><li>**Spacing** is the width and height of the rectangle grid cells.</li></ul><p>**Isometric** is a pattern of triangles:</p><p><img src=\"https://static.graphite.art/content/learn/interface/document-panel/grid-isometric-popover__2.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" onload=\"this.width = this.naturalWidth / 2\" alt=\"Snapping options popover menu\" /></p><p>It has two options unique to this mode:</p><ul><li>**Y Spacing** is the height between vertical repetitions of the grid.</li><li>**Angles** is the slant of the upward and downward sloped grid lines.</li></ul></li><li>**Display** gives control over the appearance of the grid. The **Display as dotted grid** checkbox (off by default) replaces the solid lines with dots at their intersection points.</li><li>**Origin** is the position in the canvas where the repeating grid pattern begins from. If you need an offset for the grid where an intersection occurs at a specific location, set those coordinates.</li></ul> |\n| **Render Mode** | <p>**Normal** (default): The artwork is rendered normally.</p><p>**Outline**: The artwork is rendered as a wireframe.</p><p>**Pixel Preview**: **Not implemented yet.** The artwork is rendered as it would appear when exported as a bitmap image at 100% scale regardless of the viewport zoom level.</p><p>**SVG Preview**: **Not implemented yet.** The artwork is rendered as it would appear when exported as an SVG image.</p> |\n| **Zoom In** | <p>Zooms the viewport in to the next whole increment.</p> |\n| **Zoom Out** | <p>Zooms the viewport out to the next whole increment.</p> |\n| **Reset Tilt and Zoom to 100%** | <p>Resets the viewport tilt to 0°. Resets the viewport zoom to 100% which matches the canvas and viewport pixel scale 1:1.</p> |\n| **Viewport Zoom** | <p>Indicates the current zoom level of the viewport and allows precise values to be chosen.</p> |\n| **Viewport Tilt** | <p>Hidden except when the viewport is tilted (use the *View* > *Tilt* menu action). Indicates the current tilt angle of the viewport and allows precise values to be chosen.</p> |\n| **Node Graph** | <p>Toggles the visibility of the overlaid node graph.</p> |\n\n## Tool shelf\n\nThis narrow bar runs vertically down the left side of the Document panel beside the viewport.\n\n### Tools\n\n<img src=\"https://static.graphite.art/content/learn/interface/document-panel/tool-shelf__2.avif\" style=\"float: left; width: 64px; padding-right: 40px\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" onload=\"this.width = this.naturalWidth / 2\" alt=\"The tool shelf\" />\n\nLocated at the top of the tool shelf area, the **tool shelf** provides a selection of **tools** for interactively editing the artwork. It is composed of three sections:\n\n<div style=\"margin-left: calc(64px + 40px)\">\n\n| | |\n|-|-|\n| **General tools** | (Gray icons) Used for assorted editing tasks within the viewport. |\n| **Vector tools** | (Blue icons) Used for drawing and editing vector shapes, paths, and text. |\n| **Raster tools** | (Orange icons) Used for drawing and editing raster image content. The grayed out icons are placeholders for upcoming tools. |\n\n</div>\n\n<div style=\"clear: both;\"></div>\n\n### Working colors\n\n<img src=\"https://static.graphite.art/content/learn/interface/document-panel/working-colors.avif\" style=\"float: left; width: 64px; padding-right: 40px;\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"The working colors\" />\n\nThe **working colors** are the two colors used by the active tool. The upper circle is the **primary color**. The lower circle is the **secondary color**.\n\nThere are two buttons located underneath: **Swap** which reverses the current color choices, and **Reset** which restores the primary color to black and the secondary color to white.\n\nThe tool controls (above the viewport) for some of the tools offer choices for using the primary and secondary colors. For example, the vector drawing tools have **Fill** and **Stroke** options that use the current secondary and primary colors, respectively, as defaults:\n\n<p><img src=\"https://static.graphite.art/content/learn/interface/document-panel/tool-options-fill-stroke-colors__2.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" onload=\"this.width = this.naturalWidth / 2\" alt=\"The Fill and Stroke controls for a vector tool's controls\" /></p>\n\nThese options each allow choices of being driven by the primary working color, secondary working color, or a custom color set just for that tool.\n\n## Viewport\n\nThe **viewport** is the view into the canvas. It lets you interact visually with the artwork content. It also displays temporary contextual **overlays** atop the content to assist with your editing.\n\nThe viewport is also surrounded by **scrollbars** and **rulers** around its edges. Scrollbars, located along the bottom/right edges, allow scrolling the artwork to show different parts in the viewport. Rulers, located along the top/left edges, offer dimensional information for the area of the canvas that's within view. They can be hidden with the *View* > *Rulers* toggleable menu option.\n"
  },
  {
    "path": "website/content/learn/interface/menu-bar.md",
    "content": "+++\ntitle = \"Menu bar\"\n\n[extra]\norder = 1\n+++\n\nThe **menu bar** is the series of menus running across the top left of the editor's [**title bar**](../#title-bar). It provides organized access to many actions which are described on this page.\n\nClicking **File**, **Edit**, **Layer**, **Select**, **View**, **Window**, and **Help** opens a dropdown menu with clickable actions. Pay attention to the keyboard shortcut listed on the right of each row in the dropdown menus. Learning to use them can help speed up your workflow.\n\n## Menu actions reference\n\nThe rest of this page is intended as a reference resource. Skip ahead to the next page if this is your first read-through of the manual.\n\n### App button\n\nThe **app button** appears as the Graphite [logo](/logo). Clicking it opens the website [home page](/).\n\n### File\n\nThe **File menu** lists actions related to file handling:\n\n| | |\n|-|-|\n| **New…** | <p>Opens the **New Document** dialog for creating a blank canvas in a new editor tab.</p><p><img src=\"https://static.graphite.art/content/learn/interface/menu-bar/dialog-new-document.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"The 'New Document' dialog\" /></p><p><ul><li><strong>Name</strong> determines the initial filename of the new document.</li><li><strong>Infinite Canvas</strong>, if set, skips adding an artboard and thereby starts with a boundless white canvas extending in all directions.</li><li><strong>Dimensions</strong> sets the width and height, in pixels, of the initial artboard. Ignored if *Infinite Canvas* is ticked.</li></ul></p> |\n| **Open…** | <p>Opens the operating system file picker dialog for selecting a `.graphite` file from disk to be opened in a new editor tab.</p> |\n| **Open Demo Artwork…** | <p>Opens the **Demo Artwork** dialog for loading a choice of premade sample artwork files provided for you to explore. Click the button below each image to open it.</p><p><img src=\"https://static.graphite.art/content/learn/interface/menu-bar/dialog-demo-artwork__3.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"The 'Demo Artwork' dialog\" /></p> |\n| **Close** | <p>Closes the active document. If it has unsaved changes (denoted by the `*` after the file name), you will be asked to save or discard the changes.</p> |\n| **Close All** | <p>Closes all open documents. To avoid accidentally losing unsaved work, you will be asked to confirm that you want to proceed which will discard the unsaved changes in all open documents.</p> |\n| **Save** | <p>Saves the active document by writing the `.graphite` file to disk. An operating system file download dialog may appear asking where to place it. That dialog will provide an opportunity to save over a previous version of the file, if you wish, by picking the identical name instead of saving another instance with a number after it.</p> |\n| **Import…** | <p>Opens the operating system file picker dialog for selecting an image file from disk to be placed as a new bitmap image layer or SVG content into the active document.</p> |\n| **Export…** | <p>Opens the **Export** dialog for saving the artwork as a *File Type* of *PNG*, *JPG*, or *SVG*. *Scale Factor* multiplies the content's document scale, so a value of 2 would export 300x400 content as 600x800 pixels. *Bounds* picks what area to render: *All Artwork* uses the bounding box of all layers, *Selection* uses the bounding box of the currently selected layers, and an *Artboard: \\[Name\\]* uses the bounds of that artboard. *Transparency* exports PNG or SVG files with transparency instead of the artboard background color.<br /><br /><img src=\"https://static.graphite.art/content/learn/interface/menu-bar/dialog-export.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"The 'Export' dialog\" /></p> |\n| **Preferences…** | <p>Opens the **Editor Preferences** dialog for configuring Graphite's settings.<br /><br /><img src=\"https://static.graphite.art/content/learn/interface/menu-bar/dialog-editor-preferences__2.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"The 'Editor Preferences' dialog\" /></p> |\n\n### Edit\n\nThe **Edit menu** lists actions related to the editing workflow:\n\n| | |\n|-|-|\n| **Undo** | <p>Steps back in the history of changes in the active document.</p> |\n| **Redo** | <p>Steps forward in the history of changes in the active document.</p> |\n| **Cut** | <p>Copies the selected layer(s) to the clipboard, then deletes them.</p> |\n| **Copy** | <p>Copies the selected layer(s) to the clipboard.</p> |\n| **Paste** | <p>Pastes the copied layer(s) from the clipboard into the document. It will end up directly above the selected layer, or otherwise at the base of the folder structure.</p><p>In the web version of Graphite, your browser will ask for permission to read from your clipboard which you must grant; alternatively, using the hotkey <kbd>Ctrl</kbd><kbd>V</kbd> (macOS: <kbd>⌘</kbd><kbd>V</kbd>) works without the browser needing this permission.</p> |\n| **Duplicate** | <p>Creates a copy of the selected layer(s) directly above their original(s) in the layer stack.</p> |\n| **Delete** | <p>Removes all selected layers and folders.</p> |\n| **Convert to Infinite Canvas** | <p>Replaces all artboards in the document with standard layers. With no artboards present, the document becomes an infinite canvas.</p> |\n\n### Layer\n\nThe **Layer menu** lists actions related to the layers within a document:\n\n| | |\n|-|-|\n| **New** | <p>Creates a new layer in the active document. It will end up directly above the selected layer, or otherwise at the base of the folder structure.</p> |\n| **Group** | <p>Creates a new folder in place of the selected layer(s), then moves them into that folder.</p> |\n| **Ungroup** | <p>Removes the selected folder(s), moving their contents up one level in the layer stack.</p> |\n| **Hide/Show** | <p>Toggles visibility of the selected layer(s), including or excluding them from rendering as part of the artwork.</p> |\n| **Lock/Unlock** | <p>Toggles the locked state of the selected layer(s), preventing them from being selected by tools in the viewport.</p> |\n| **Grab** | <p>Begin grabbing the selected layer(s) to translate (move) them around with your cursor's movement. Lock to an axis with <kbd>X</kbd> or <kbd>Y</kbd> then use the number keys to type a pixel distance value. Confirm with a left click or <kbd>Enter</kbd>. Cancel with a right click or <kbd>Esc</kbd>.</p> |\n| **Rotate** | <p>Begin rotating the selected layer(s) around their pivot point with your cursor's movement. Use the number keys to type an angle value in degrees. Confirm with a left click or <kbd>Enter</kbd>. Cancel with a right click or <kbd>Esc</kbd>.</p> |\n| **Scale** | <p>Begin scaling the selected layer(s) around their pivot point with your cursor's movement. Lock to an axis with <kbd>X</kbd> or <kbd>Y</kbd>. Use the number keys to type a scale multiplier value. Confirm with a left click or <kbd>Enter</kbd>. Cancel with a right click or <kbd>Esc</kbd>.</p> |\n| **Arrange ><br />Raise to Front** | <p>Reorders the selected layer(s) above all others within their same folder(s), so they appear in the layer stack and render above those other layers.</p> |\n| **Arrange ><br />Raise** | <p>Reorders the selected layers(s) up by one in the layer stack, so any layer that was immediately above the selected layer(s) ends up immediately below.</p> |\n| **Arrange ><br />Lower** | <p>Reorders the selected layers(s) down by one in the layer stack, so any layer that was immediately below the selected layer(s) ends up immediately above.</p> |\n| **Arrange ><br />Lower to Back** | <p>Reorders the selected layer(s) below all others within their same folder(s), so they appear in the layer stack and render below those other layers.</p> |\n| **Arrange ><br />Reverse** | <p>Reorders the selected layers by swapping their positions from top to bottom. Reversal applies amongst each set of selected sibling layers (those with a shared parent).</p> |\n| **Align ><br />Align Left** | <p>Moves the selected layer(s) so their left edges line up with the leftmost edge of the selection's bounding box.</p> |\n| **Align ><br />Align Horizontal Center** | <p>Moves the selected layer(s) so their horizontal centers line up with the horizontal center of the selection's bounding box.</p> |\n| **Align ><br />Align Right** | <p>Moves the selected layer(s) so their right edges line up with the rightmost edge of the selection's bounding box.</p> |\n| **Align ><br />Align Top** | <p>Moves the selected layer(s) so their top edges line up with the topmost edge of the selection's bounding box.</p> |\n| **Align ><br />Align Vertical Center** | <p>Moves the selected layer(s) so their vertical centers line up with the vertical center of the selection's bounding box.</p> |\n| **Align ><br />Align Bottom** | <p>Moves the selected layer(s) so their bottom edges line up with the bottommost edge of the selection's bounding box.</p> |\n| **Flip ><br />Flip Horizontal** | <p>Reflects the selected layer(s) horizontally within the selection's bounding box.</p> |\n| **Flip ><br />Flip Vertical** | <p>Reflects the selected layer(s) vertically within the selection's bounding box.</p> |\n| **Turn ><br />Turn -90°** | <p>Rotates the selected layer(s) a quarter turn counterclockwise about the selection's bounding box center.</p> |\n| **Turn ><br />Turn 90°** | <p>Rotates the selected layer(s) a quarter turn clockwise about the selection's bounding box center.</p> |\n| **Boolean ><br />Union** | <p>Combines all paths of the selected vector layer(s) while cutting out overlapping areas (even the interiors of a single path)\n| **Boolean ><br />Subtract Front** | <p>Cuts overlapping areas out from the last of the selected vector layers.</p> |\n| **Boolean ><br />Subtract Back** | <p>Cuts overlapping areas out from the first of the selected vector layers.</p> |\n| **Boolean ><br />Intersect** | <p>Cuts away all but the overlapping areas shared by every path of the selected vector layer(s).</p> |\n| **Boolean ><br />Difference** | <p>Cuts away the overlapping areas shared by every path of the selected vector layer(s), leaving only the non-overlapping areas.</p> |\n| **Make Path Editable** | <p>Applies a path edit operation (the **Path node**) to the selected vector layer, capturing the geometry after other nondestructive operations to enable its direct modification by the **Path** and **Pen** tools.</p> |\n\n### Select\n\nThe **Select menu** lists actions related to the selection of layers within a document:\n\n| | |\n|-|-|\n| **Select All** | <p>Selects all layers and folders in the document.</p> |\n| **Deselect All** | <p>Deselects everything in the document.</p> |\n| **Select Parent** | <p>Selects the parent folder(s) of the currently selected layer(s).</p> |\n| **Previous Selection** | <p>Goes back to the previously selected set of layers or nodes in the selection history.</p><p>If the side of your mouse has navigation buttons, you can use the back button as a shortcut (not supported in Firefox).</p> |\n| **Next Selection** | <p>Goes forward to the next selected set of layers or nodes in the selection history.</p><p>If the side of your mouse has navigation buttons, you can use the forward button as a shortcut (not supported in Firefox).</p> |\n\n### View\n\nThe **View menu** lists actions related to the view of the canvas within the viewport:\n\n| | |\n|-|-|\n| **Tilt** | <p>Begins tilting the viewport angle based on your mouse movements.</p><p>While tilting, hold <kbd>Shift</kbd> to snap to 15° increments. Confirm with a left click or <kbd>Enter</kbd>. Cancel with a right click or <kbd>Esc</kbd>.</p> |\n| **Reset Tilt** | <p>Sets the viewport tilt angle back to 0°.</p> |\n| **Zoom In** | <p>Narrows the view to the next whole zoom increment, such as:</p><p>25%, 33.33%, 40%, 50%, 66.67%, 80%, 100%, 125%, 160%, 200%, 250%, 320%, 400%, 500%</p> |\n| **Zoom Out** | <p>Widens the view to the next whole zoom increment, such as above.</p> |\n| **Zoom to Selection** | <p>Zooms and frames the viewport to the bounding box of the selected layer(s).</p> |\n| **Zoom to Fit** | <p>Zooms and frames the viewport to fit all artboards, or all artwork if using infinite canvas.</p> |\n| **Zoom to 100%** | <p>Zooms the viewport in or out to 100% scale, making the document and viewport scales match 1:1.</p> |\n| **Zoom to 200%** | <p>Zooms the viewport in or out to 200% scale, displaying the artwork at twice the actual size.</p> |\n| **Flip** | <p>Mirrors the viewport horizontally, flipping the view of the artwork until deactivated.</p> |\n| **Rulers** | <p>Toggles visibility of the rulers along the top/left edges of the viewport.</p> |\n\n### Window\n\nThe **Window menu** lists actions related to the visibility of workspace panels within the application window:\n\n| | |\n|-|-|\n| **Properties** | <p>Toggles visibility of the **Properties panel** on the upper-right side of the workspace. It is used to inspect and edit the values of graphics operation (node) parameters. Selected layers or nodes display their parametric controls in this panel.</p> |\n| **Layers** | <p>Toggles visibility of the **Layers panel** on the lower-right side of the workspace. It is used to organize and select the artboards and layers that form the structure of a document.</p> |\n| **Data** | <p>Toggles visibility of the **Data panel** on the lower-left side of the workspace. It is used to introspect data flow from the output of a selected node for technical debugging of content generated within the node graph.</p> |\n\n### Help\n\nThe **Help menu** lists actions related to information about Graphite:\n\n| | |\n|-|-|\n| **About Graphite…** | <p>Opens the **About Graphite** dialog for displaying release and license information. You can check it for the release date of the current editor version.</p> |\n| **User Manual** | <p>Opens this [user manual](./learn).</p> |\n| **Donate to Graphite** | <p>Opens the Graphite [development fund](/donate) page where you can contribute financially to support ongoing development of the project.</p> |\n| **Report a Bug** | <p>Opens a page to file a [new GitHub issue](https://github.com/GraphiteEditor/Graphite/issues/new).</p> |\n| **Visit on GitHub** | <p>Opens the [Graphite GitHub repository](https://github.com/GraphiteEditor/Graphite).</p> |\n| **Developer Debug** | <p>A section with developer-only actions. Users should ignore these.</p> |\n"
  },
  {
    "path": "website/content/learn/introduction/_index.md",
    "content": "+++\ntitle = \"Introduction\"\ntemplate = \"book.html\"\npage_template = \"book.html\"\n\n[extra]\norder = 1\njs = [\"/js/component/youtube-embed.js\"]\ncss = [\"/component/youtube-embed.css\"]\n+++\n\n<!-- Before taking the time to read the coming chapters, let's build some context by jumping straight into a small project that you can follow along with. That way you will have a mental framework for the topics explained in the rest of this manual. -->\n\nBegin learning Graphite by watching the introductory tutorial video below.\n\nOne is available now, and more will be released on a regular basis throughout early 2026.\n\n<div class=\"youtube-embed aspect-16x9\">\n\t<img data-youtube-embed=\"7gjUhl_3X10\" src=\"https://static.graphite.art/content/learn/introduction/tutorial-1-vector-art-quickstart-youtube__2.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Vector Art Quickstart - Graphite, the Open Source 2D Graphics Suite\" />\n</div>\n\n*More tutorials coming soon.*\n"
  },
  {
    "path": "website/content/learn/introduction/features-and-limitations.md",
    "content": "+++\ntitle = \"Features and limitations\"\n\n[extra]\norder = 1\n+++\n\nBear in mind that Graphite is alpha software, meaning it is actively changing and improving.\n\n## Current capabilities\n\nA lot is planned on the future [roadmap](/features#roadmap), but here's an overview of the concepts behind the selection of workflows you can currently use in Graphite.\n\n### Vector illustration and graphic design\n\n<img src=\"https://static.graphite.art/content/learn/introduction/features-and-limitations/cactus-vector-art.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Example vector artwork of a potted cactus\" style=\"max-width: unset; float: right; margin-left: 40px\" />\n\nVector editing is the core competency of the Graphite editor at this stage in its development. That means you can create shape-based vector artwork and designs with the available tools.</p><p>Primitive geometry like rectangles and ellipses can be drawn and, as desired, modified into more complex shapes using the Path tool. Fully organic shapes may also be created from scratch with the Pen tool. They can then be given colors and gradients to add visual style. This cactus is an example of the style of artwork you can create with vector graphics.\n\n### Procedural design\n\nA procedural content generation workflow lets you describe *how* a creative decision becomes a visual outcome rather than doing it all yourself. For example, copying a shape 25 times around the inside of a circle would be tedious work if done by hand but it's easy for the computer to do it. And if you decide 10 instances may look better than 25, or you want to change the copied shape, or you opt for a different radial separation, it's easy to just update a numerical parameter. That saves you from laboriously placing every shape all over again. You're able to build a *procedure* that the computer carries out on your behalf.\n\nThe aforementioned example takes the form of the <img src=\"https://static.graphite.art/content/learn/introduction/features-and-limitations/circular-repeat-node__3.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" onload=\"this.width = this.naturalWidth / 2\" style=\"vertical-align: middle\" alt=\"Circular Repeat\" /> node which is represented as this box-shaped entity with colored *connectors* on either end. *Nodes* encode certain operations (or functions) in the procedure that generates your artwork. Once you've drawn some content, you can see the nodes which generate it by opening the *node graph* with the <img src=\"https://static.graphite.art/content/learn/introduction/features-and-limitations/node-graph-button.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" onload=\"this.width = this.naturalWidth / 2\" style=\"vertical-align: middle\" alt=\"'Node Graph' button\" /> button located to the top right of the viewport. This example may have a node setup which looks like this:\n\n<p><img src=\"https://static.graphite.art/content/learn/introduction/features-and-limitations/path-fill-circular-repeat-layer.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Path node, Fill node, Circular Repeat node in a sequence feeding into the Untitled Layer\" /></p>\n\nStarting from the left, the <img src=\"https://static.graphite.art/content/learn/introduction/features-and-limitations/path-node.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" onload=\"this.width = this.naturalWidth / 2\" style=\"vertical-align: middle\" alt=\"Path\" /> node generates some geometry (in this case, drawn using the *Pen* tool). Next, the vector path data feeds through the <img src=\"https://static.graphite.art/content/learn/introduction/features-and-limitations/fill-node__2.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" onload=\"this.width = this.naturalWidth / 2\" style=\"vertical-align: middle\" alt=\"Fill\" /> node to apply a blue color. At this point, the path data looks like so:\n\n<p><img src=\"https://static.graphite.art/content/learn/introduction/features-and-limitations/blue-arch-shape.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" onload=\"this.width = this.naturalWidth / 2\" alt=\"\" /></p>\n\nNext, that is fed into the <img src=\"https://static.graphite.art/content/learn/introduction/features-and-limitations/circular-repeat-node__3.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" onload=\"this.width = this.naturalWidth / 2\" style=\"vertical-align: middle\" alt=\"Circular Repeat\" /> node which has several parameters you can modify and get different output data based on your choices, like in these examples:\n\n<style class=\"table-1-style\">\n.table-1-style + table td {\n\tvertical-align: middle;\n}\n</style>\n\n| | |\n|:-:|:-:|\n| <img src=\"https://static.graphite.art/content/learn/introduction/features-and-limitations/circular-repeat-node-parameters-1__2.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" onload=\"this.width = this.naturalWidth / 2\" alt=\"\" /> | <img src=\"https://static.graphite.art/content/learn/introduction/features-and-limitations/circular-repeat-node-output-1.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" onload=\"this.width = this.naturalWidth / 2\" alt=\"\" /> |\n| <img src=\"https://static.graphite.art/content/learn/introduction/features-and-limitations/circular-repeat-node-parameters-2__2.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" onload=\"this.width = this.naturalWidth / 2\" alt=\"\" /> | <img src=\"https://static.graphite.art/content/learn/introduction/features-and-limitations/circular-repeat-node-output-2.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" onload=\"this.width = this.naturalWidth / 2\" alt=\"\" /> |\n| <img src=\"https://static.graphite.art/content/learn/introduction/features-and-limitations/circular-repeat-node-parameters-3__2.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" onload=\"this.width = this.naturalWidth / 2\" alt=\"\" /> | <img src=\"https://static.graphite.art/content/learn/introduction/features-and-limitations/circular-repeat-node-output-3.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" onload=\"this.width = this.naturalWidth / 2\" alt=\"\" /> |\n| <img src=\"https://static.graphite.art/content/learn/introduction/features-and-limitations/circular-repeat-node-parameters-4__2.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" onload=\"this.width = this.naturalWidth / 2\" alt=\"\" /> | <img src=\"https://static.graphite.art/content/learn/introduction/features-and-limitations/circular-repeat-node-output-4.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" onload=\"this.width = this.naturalWidth / 2\" alt=\"\" /> |\n\n<!-- TODO: Rename \"Angle Offset\" to \"Start Angle\" and redo the screenshots which show that -->\nThe node's properties offer controls over settings like *Angle Offset* (what angle to start at), *Radius* (distance from the center), and *Instances* (how many copies to distribute). These parameters can also be exposed into the graph so they are driven by the calculated numerical outputs of other nodes instead of values you pick by hand.\n\n### Raster compositing\n\nRaster image editing is a growing capability that will develop over time into the central focus of Graphite. Raster imagery is composed of pixels which are grids of color that can represent anything visual, like paintings and photographs. The current feature set lets you import images, manipulate them using the node-based compositor, and apply nondestructive global effects like color adjustment filters.\n\nA prototype <img src=\"https://static.graphite.art/content/learn/introduction/features-and-limitations/brush-tool-icon.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" onload=\"this.width = this.naturalWidth / 2\" alt=\"\" style=\"vertical-align: bottom\" /> Brush tool exists letting you draw simple doodles and sketches. However it is very limited in its capabilities and there are multiple bugs and performance issues with the feature. It can be used in a basic capacity, but don't expect to paint anything too impressive using raster brushes quite yet. The tool will be fully rewritten in the future.\n"
  },
  {
    "path": "website/content/license.md",
    "content": "+++\ntitle = \"Graphite license\"\n\n[extra]\ncss = [\"/layout/reading-material.css\"]\n+++\n\n<section class=\"reading-material\">\n<div class=\"block\">\n\n# Graphite license\n\n<article>\n\nGraphite is open source software made by its community and distributed by *Graphite Labs, LLC*, the official [organization](/about#organization) that operates the project. Builds of the application are free to use and redistribute, even for commercial purposes. The artwork you create always belongs to you.\n\n## Source code\n\nThe source code [available on GitHub](https://github.com/GraphiteEditor/Graphite) (including the Graphite editor application, libraries, and other software materials) is shared under the Apache License 2.0 posted below, unless otherwise noted within the repository.\n\n---\n\nApache License  \nVersion 2.0, January 2004  \n<http://www.apache.org/licenses/>\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\t\"License\" shall mean the terms and conditions for use, reproduction,\n\tand distribution as defined by Sections 1 through 9 of this document.\n\n\t\"Licensor\" shall mean the copyright owner or entity authorized by\n\tthe copyright owner that is granting the License.\n\n\t\"Legal Entity\" shall mean the union of the acting entity and all\n\tother entities that control, are controlled by, or are under common\n\tcontrol with that entity. For the purposes of this definition,\n\t\"control\" means (i) the power, direct or indirect, to cause the\n\tdirection or management of such entity, whether by contract or\n\totherwise, or (ii) ownership of fifty percent (50%) or more of the\n\toutstanding shares, or (iii) beneficial ownership of such entity.\n\n\t\"You\" (or \"Your\") shall mean an individual or Legal Entity\n\texercising permissions granted by this License.\n\n\t\"Source\" form shall mean the preferred form for making modifications,\n\tincluding but not limited to software source code, documentation\n\tsource, and configuration files.\n\n\t\"Object\" form shall mean any form resulting from mechanical\n\ttransformation or translation of a Source form, including but\n\tnot limited to compiled object code, generated documentation,\n\tand conversions to other media types.\n\n\t\"Work\" shall mean the work of authorship, whether in Source or\n\tObject form, made available under the License, as indicated by a\n\tcopyright notice that is included in or attached to the work\n\t(an example is provided in the Appendix below).\n\n\t\"Derivative Works\" shall mean any work, whether in Source or Object\n\tform, that is based on (or derived from) the Work and for which the\n\teditorial revisions, annotations, elaborations, or other modifications\n\trepresent, as a whole, an original work of authorship. For the purposes\n\tof this License, Derivative Works shall not include works that remain\n\tseparable from, or merely link (or bind by name) to the interfaces of,\n\tthe Work and Derivative Works thereof.\n\n\t\"Contribution\" shall mean any work of authorship, including\n\tthe original version of the Work and any modifications or additions\n\tto that Work or Derivative Works thereof, that is intentionally\n\tsubmitted to Licensor for inclusion in the Work by the copyright owner\n\tor by an individual or Legal Entity authorized to submit on behalf of\n\tthe copyright owner. For the purposes of this definition, \"submitted\"\n\tmeans any form of electronic, verbal, or written communication sent\n\tto the Licensor or its representatives, including but not limited to\n\tcommunication on electronic mailing lists, source code control systems,\n\tand issue tracking systems that are managed by, or on behalf of, the\n\tLicensor for the purpose of discussing and improving the Work, but\n\texcluding communication that is conspicuously marked or otherwise\n\tdesignated in writing by the copyright owner as \"Not a Contribution.\"\n\n\t\"Contributor\" shall mean Licensor and any individual or Legal Entity\n\ton behalf of whom a Contribution has been received by Licensor and\n\tsubsequently incorporated within the Work.\n\n2. Grant of Copyright License. Subject to the terms and conditions of\n\tthis License, each Contributor hereby grants to You a perpetual,\n\tworldwide, non-exclusive, no-charge, royalty-free, irrevocable\n\tcopyright license to reproduce, prepare Derivative Works of,\n\tpublicly display, publicly perform, sublicense, and distribute the\n\tWork and such Derivative Works in Source or Object form.\n\n3. Grant of Patent License. Subject to the terms and conditions of\n\tthis License, each Contributor hereby grants to You a perpetual,\n\tworldwide, non-exclusive, no-charge, royalty-free, irrevocable\n\t(except as stated in this section) patent license to make, have made,\n\tuse, offer to sell, sell, import, and otherwise transfer the Work,\n\twhere such license applies only to those patent claims licensable\n\tby such Contributor that are necessarily infringed by their\n\tContribution(s) alone or by combination of their Contribution(s)\n\twith the Work to which such Contribution(s) was submitted. If You\n\tinstitute patent litigation against any entity (including a\n\tcross-claim or counterclaim in a lawsuit) alleging that the Work\n\tor a Contribution incorporated within the Work constitutes direct\n\tor contributory patent infringement, then any patent licenses\n\tgranted to You under this License for that Work shall terminate\n\tas of the date such litigation is filed.\n\n4. Redistribution. You may reproduce and distribute copies of the\n\tWork or Derivative Works thereof in any medium, with or without\n\tmodifications, and in Source or Object form, provided that You\n\tmeet the following conditions:\n\n\t(a) You must give any other recipients of the Work or\n\t\tDerivative Works a copy of this License; and\n\n\t(b) You must cause any modified files to carry prominent notices\n\t\tstating that You changed the files; and\n\n\t(c) You must retain, in the Source form of any Derivative Works\n\t\tthat You distribute, all copyright, patent, trademark, and\n\t\tattribution notices from the Source form of the Work,\n\t\texcluding those notices that do not pertain to any part of\n\t\tthe Derivative Works; and\n\n\t(d) If the Work includes a \"NOTICE\" text file as part of its\n\t\tdistribution, then any Derivative Works that You distribute must\n\t\tinclude a readable copy of the attribution notices contained\n\t\twithin such NOTICE file, excluding those notices that do not\n\t\tpertain to any part of the Derivative Works, in at least one\n\t\tof the following places: within a NOTICE text file distributed\n\t\tas part of the Derivative Works; within the Source form or\n\t\tdocumentation, if provided along with the Derivative Works; or,\n\t\twithin a display generated by the Derivative Works, if and\n\t\twherever such third-party notices normally appear. The contents\n\t\tof the NOTICE file are for informational purposes only and\n\t\tdo not modify the License. You may add Your own attribution\n\t\tnotices within Derivative Works that You distribute, alongside\n\t\tor as an addendum to the NOTICE text from the Work, provided\n\t\tthat such additional attribution notices cannot be construed\n\t\tas modifying the License.\n\n\tYou may add Your own copyright statement to Your modifications and\n\tmay provide additional or different license terms and conditions\n\tfor use, reproduction, or distribution of Your modifications, or\n\tfor any such Derivative Works as a whole, provided Your use,\n\treproduction, and distribution of the Work otherwise complies with\n\tthe conditions stated in this License.\n\n5. Submission of Contributions. Unless You explicitly state otherwise,\n\tany Contribution intentionally submitted for inclusion in the Work\n\tby You to the Licensor shall be under the terms and conditions of\n\tthis License, without any additional terms or conditions.\n\tNotwithstanding the above, nothing herein shall supersede or modify\n\tthe terms of any separate license agreement you may have executed\n\twith Licensor regarding such Contributions.\n\n6. Trademarks. This License does not grant permission to use the trade\n\tnames, trademarks, service marks, or product names of the Licensor,\n\texcept as required for reasonable and customary use in describing the\n\torigin of the Work and reproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty. Unless required by applicable law or\n\tagreed to in writing, Licensor provides the Work (and each\n\tContributor provides its Contributions) on an \"AS IS\" BASIS,\n\tWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n\timplied, including, without limitation, any warranties or conditions\n\tof TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n\tPARTICULAR PURPOSE. You are solely responsible for determining the\n\tappropriateness of using or redistributing the Work and assume any\n\trisks associated with Your exercise of permissions under this License.\n\n8. Limitation of Liability. In no event and under no legal theory,\n\twhether in tort (including negligence), contract, or otherwise,\n\tunless required by applicable law (such as deliberate and grossly\n\tnegligent acts) or agreed to in writing, shall any Contributor be\n\tliable to You for damages, including any direct, indirect, special,\n\tincidental, or consequential damages of any character arising as a\n\tresult of this License or out of the use or inability to use the\n\tWork (including but not limited to damages for loss of goodwill,\n\twork stoppage, computer failure or malfunction, or any and all\n\tother commercial damages or losses), even if such Contributor\n\thas been advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability. While redistributing\n\tthe Work or Derivative Works thereof, You may choose to offer,\n\tand charge a fee for, acceptance of support, warranty, indemnity,\n\tor other liability obligations and/or rights consistent with this\n\tLicense. However, in accepting such obligations, You may act only\n\ton Your own behalf and on Your sole responsibility, not on behalf\n\tof any other Contributor, and only if You agree to indemnify,\n\tdefend, and hold each Contributor harmless for any liability\n\tincurred by, or claims asserted against, such Contributor by reason\n\tof your accepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\n## Branding\n\nAssets that contribute to the visual identity of the Graphite brand and software (including [logos](/logo) and icon sets) are not considered software code, and are licensed separately from the [source code license](#source-code) described above. These are kept in a [separate repository](https://github.com/Keavon/graphite-branded-assets/) because they are designed independently and are not within scope of the open source project.\n\nOfficial builds of Graphite may be compiled and redistributed freely under the terms of the Graphite Branding License, but derivative works (i.e. forks) must substitute these assets before distributing modified releases of the software. This protects the integrity of the brand and encourages developers to [contribute](/volunteer) back in support of the official community-based project instead of fragmenting the ecosystem with lookalikes.\n\n---\n\nGraphite Branding License\n\nCopyright © 2021-2026 Graphite Labs, LLC. All rights reserved.\n\nThe repository `https://github.com/Keavon/graphite-branded-assets/` includes proprietary assets (the \"Assets\"), including but not limited to logos, icons, and branding materials. The Assets are not software source code and are consequently not licensed under the same terms as the other works of Graphite software materials. The intention of this license is to maintain the full permissiveness of the software code while reserving protections for the brand and visual identity of the official Graphite product.\n\nPermission is granted to use, reproduce, and distribute the Assets solely as part of unmodified build artifacts produced from the official Graphite repository at `https://github.com/GraphiteEditor/Graphite`, on master branch commits, using the official build process as documented therein. Any other use of the Assets—including copying, extraction, incorporation into modified or forked builds, or use in other projects or contexts—is prohibited without prior written permission from the copyright holder.\n\nThis license does not grant any rights under trademark law or affect the licensing of the software source code.\n\nTHE ASSETS ARE PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY ARISING FROM THE USE OF THE ASSETS.\n\nThis license is automatically terminated if the terms herein are violated.\n\n</article>\n\n</div>\n</section>\n"
  },
  {
    "path": "website/content/logo.md",
    "content": "+++\ntitle = \"Graphite logo\"\n\n[extra]\ncss = [\"/page/logo.css\", \"/component/feature-box.css\", \"/layout/reading-material.css\"]\n+++\n\n<section class=\"reading-material\">\n<div class=\"block\">\n\n# Graphite logo\n\n<article>\n\nGraphite's logo is represented by the end of a pencil, drawing a sketch mark, that is protruding from a hexagon.\n\nThe pencil and its streak, composed of graphite (the substance), signifies the software's name and its role as a drawing tool and versatile art medium.\n\nThe hexagon represents one unit in the lattice grid chemical structure of graphene. The angled line is a double bond. This references the Graphene node engine which powers the Graphite editor. The lattice grid also resembles the nodes and edges of a mathematical graph, referencing the node graph that is fundamental to both the Graphene engine and Graphite editor. The first syllable of both names also alludes to that central node graph.\n\n## Usage policy\n\nThe Graphite logo is made available for community use, with some limitations. While the software is free and open source, the brand identity is more restrictive. Please be respectful of the Graphite brand by reviewing this usage policy.\n\nBe aware that the logo is not covered under the [Apache License 2.0](/license#source-code) of Graphite's source code. Its usage within Graphite software distributions is covered by the [Graphite Branding License](/license#branding).\n\nOutside the context of the Graphite application (or derivatives), community usage of the logo must adhere to the usage policy:\n\n1. Do not use the Graphite logo as your own. It should not be used as your primary—or most visually prominent—branding, and may never be incorporated into your own logo. Your usage should not imply that it's a part of the official Graphite project, nor that it's endorsed or affiliated.\n2. Your (product, service, website, company, channel, etc.) brand name should not begin with \"Graphite\", or use it as the most prominent word in the name. It may only be used as a secondary or descriptive word that comes after your own unique name. For example: \"XYZ for Graphite\" is acceptable, but \"Graphite XYZ\" is not since it implies affiliation.\n3. Only use the logo when talking about, describing, referencing, or crediting the official Graphite project or software. If used as a hyperlink, it should point only to <https://graphite.art>.\n4. Do not modify the logo. The solid-colored version may only be recolored with another substitute solid color if doing so is necessary for visual consistency when presented alongside other logos of the same color. Sufficient blank space should be preserved around the logo so it does not overlap or compete with impinging design elements.\n5. Commercial use of the logo (for example, merchandise sales) is not allowed without express written permission.\n\nIf in doubt, please <a href=\"/contact\">get in touch</a> by email to request clarification or permission.\n\n## Download\n\nDownload the complete [logo kit](https://static.graphite.art/logos/graphite-logo-kit.zip) or a specific version in PNG or SVG format below.\n\n</article>\n\n</div>\n</section>\n\n<section class=\"feature-box-outer logo-view color\">\n\t<div class=\"feature-box-inner\">\n\t\t<div>\n\t\t\t<img src=\"https://static.graphite.art/logos/graphite-logotype-color.svg\" height=\"160\" />\n\t\t\t<span>Graphite Logotype (Color)</span>\n\t\t\t<a href=\"https://static.graphite.art/logos/graphite-logotype-color.svg\" download>Download (SVG)</a>\n\t\t\t<a href=\"https://static.graphite.art/logos/graphite-logotype-color-240x937.png\" download>Download (PNG 240x937)</a>\n\t\t\t<a href=\"https://static.graphite.art/logos/graphite-logotype-color-480x1874.png\" download>Download (PNG 480x1874)</a>\n\t\t</div>\n\t\t<div>\n\t\t\t<img src=\"https://static.graphite.art/logos/graphite-logo-color.svg\" width=\"160\" height=\"160\" />\n\t\t\t<span>Graphite Icon (Color)</span>\n\t\t\t<a href=\"https://static.graphite.art/logos/graphite-logo-color.svg\" download>Download (SVG)</a>\n\t\t\t<a href=\"https://static.graphite.art/logos/graphite-logo-color-240x240.png\" download>Download (PNG 240x240)</a>\n\t\t\t<a href=\"https://static.graphite.art/logos/graphite-logo-color-480x480.png\" download>Download (PNG 480x480)</a>\n\t\t</div>\n\t</div>\n</section>\n\n<section class=\"feature-box-outer logo-view light\">\n\t<div class=\"feature-box-inner\">\n\t\t<div>\n\t\t\t<img src=\"https://static.graphite.art/logos/graphite-logotype-solid.svg\" height=\"160\" />\n\t\t\t<span>Graphite Logotype (Solid)</span>\n\t\t\t<a href=\"https://static.graphite.art/logos/graphite-logotype-solid.svg\" download>Download (SVG)</a>\n\t\t\t<a href=\"https://static.graphite.art/logos/graphite-logotype-solid-240x937.png\" download>Download (PNG 240x937)</a>\n\t\t\t<a href=\"https://static.graphite.art/logos/graphite-logotype-solid-480x1874.png\" download>Download (PNG 480x1874)</a>\n\t\t</div>\n\t\t<div>\n\t\t\t<img src=\"https://static.graphite.art/logos/graphite-logo-solid.svg\" width=\"160\" height=\"160\" />\n\t\t\t<span>Graphite Icon (Solid)</span>\n\t\t\t<a href=\"https://static.graphite.art/logos/graphite-logo-solid.svg\" download>Download (SVG)</a>\n\t\t\t<a href=\"https://static.graphite.art/logos/graphite-logo-solid-240x240.png\" download>Download (PNG 240x240)</a>\n\t\t\t<a href=\"https://static.graphite.art/logos/graphite-logo-solid-480x480.png\" download>Download (PNG 480x480)</a>\n\t\t</div>\n\t</div>\n</section>\n\n<section class=\"feature-box-outer logo-view dark\">\n\t<div class=\"feature-box-inner\">\n\t\t<div>\n\t\t\t<img src=\"https://static.graphite.art/logos/graphite-logotype-solid-white.svg\" height=\"160\" />\n\t\t\t<span>Graphite Logotype (Solid, White)</span>\n\t\t\t<a href=\"https://static.graphite.art/logos/graphite-logotype-solid-white.svg\" download>Download (SVG)</a>\n\t\t\t<a href=\"https://static.graphite.art/logos/graphite-logotype-solid-white-240x937.png\" download>Download (PNG 240x937)</a>\n\t\t\t<a href=\"https://static.graphite.art/logos/graphite-logotype-solid-white-480x1874.png\" download>Download (PNG 480x1874)</a>\n\t\t</div>\n\t\t<div>\n\t\t\t<img src=\"https://static.graphite.art/logos/graphite-logo-solid-white.svg\" width=\"160\" height=\"160\" />\n\t\t\t<span>Graphite Icon (Solid, White)</span>\n\t\t\t<a href=\"https://static.graphite.art/logos/graphite-logo-solid-white.svg\" download>Download (SVG)</a>\n\t\t\t<a href=\"https://static.graphite.art/logos/graphite-logo-solid-white-240x240.png\" download>Download (PNG 240x240)</a>\n\t\t\t<a href=\"https://static.graphite.art/logos/graphite-logo-solid-white-480x480.png\" download>Download (PNG 480x480)</a>\n\t\t</div>\n\t</div>\n</section>\n"
  },
  {
    "path": "website/content/press.md",
    "content": "+++\ntitle = \"Press resources\"\n\n[extra]\ncss = [\"/layout/reading-material.css\"]\n+++\n\n<section class=\"reading-material\">\n<div class=\"block\">\n\n# Press resources\n\n<article>\n\nMaterials for journalists and creators looking to share Graphite with their audiences.\n\n## Contact\n\nPlease get in touch if you'd like to conduct an interview or have questions answered.\n\nSend an email to [contact<wbr>@graphite<wbr>.art](mailto:contact@graphite.art) and you can usually expect a quick reply.\n\n## Logo\n\nLogos in PNG and SVG format are available for use in articles talking about Graphite. See the [logo](/logo) page for downloads.\n\n</article>\n\n</div>\n</section>\n"
  },
  {
    "path": "website/content/privacy-policy.md",
    "content": "+++\ntitle = \"Graphite privacy policy\"\n\n[extra]\ncss = [\"/layout/reading-material.css\"]\n+++\n\n<section class=\"reading-material\">\n<div class=\"block\">\n\n# Graphite privacy policy\n\n<article>\n\nThe Graphite application does not require user accounts and does not collect, store, or transmit personal information. All user data is stored locally on-device and the application functions primarily offline.\n\nThe application makes limited network requests for functionality such as retrieving font metadata and checking for updates. These API requests may include basic, non-identifying technical information (such as app version and platform) to serve appropriate resources. Network requests are subject to standard server-side analytics used to operate the service, which collect only aggregated, anonymized usage statistics and are not used for tracking individual users.\n\nGraphite does not engage in the activities of selling, sharing, or advertising with personal data. As a free and open-source software project, the application is designed with privacy as a guiding principle.\n\n</article>\n\n</div>\n</section>\n"
  },
  {
    "path": "website/content/volunteer/_index.md",
    "content": "+++\ntitle = \"Volunteer\"\n\n[extra]\ncss = [\"/page/volunteer.css\", \"/component/feature-box.css\"]\n+++\n\n<section>\n<div class=\"block\">\n\n# Get involved\n\n**Graphite is 100% built by volunteers.** Get involved in the effort to bring great, free creative software to the world.\n\n</div>\n</section>\n\n<section>\n\n## Code contributions\n\n<div class=\"feature-box-narrow\">\n\n<a href=\"/volunteer/guide\">\n\t<img src=\"https://static.graphite.art/content/volunteer/code-contributions.avif\" class=\"feature-box-full-image\" style=\"aspect-ratio: 3/1 auto; background: var(--color-seaside)\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.jpg')\" alt=\"Flavor graphic depicting a library of knowledge in a digital realm\" />\n</a>\n\nGet started by reading the contributor guide:\n\n<a href=\"/volunteer/guide\" class=\"button arrow\">Contributor guide</a>\n\n</div>\n\n<div class=\"diptych code-contributions\">\n\n<div class=\"block feature-box-narrow\">\n\n<h1 class=\"feature-box-header\">Editor team</h1>\n\nThe Graphite editor is built much like a game engine, split across user interface application tooling and a renderer with nodes implementing an assortment of graphics algorithms.\n\n</div>\n<div class=\"block feature-box-narrow\">\n\n<h1 class=\"feature-box-header\">Compiler team</h1>\n\n[Graphene](/volunteer/guide/graphene) is a programming language, interpreter, and runtime environment built upon Rust which enables Graphite artwork to compile to executable programs for fast rendering.\n\n</div>\n\n</div>\n\n</section>\n\n<section>\n\n## Creative contributions\n\n<div class=\"feature-box-narrow\">\n\n<img src=\"https://static.graphite.art/content/volunteer/creative-contributions.avif\" class=\"feature-box-full-image\" style=\"aspect-ratio: 3/1 auto; background: var(--color-lemon)\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.jpg')\" alt=\"Flavor graphic depicting a fountain pen, ink pots, and a book\" />\n</a>\n\nAssign yourself the *\"🙌 Interested in helping with art or marketing\"* role in the *#welcome* Discord channel. Then mention your experience and how you'd like to help in the *#introductions* channel.\n\n<a href=\"https://discord.graphite.art\" class=\"button arrow\">Volunteer on Discord</a>\n\n</div>\n\n<div class=\"diptych creative-contributions\">\n\n<div class=\"block feature-box-narrow\">\n\n<h1 class=\"feature-box-header\">Art team</h1>\n\nUse your artistic talents to conceptualize and produce high-quality open art projects published by the Graphite project to stress-test and showcase the editor's capabilities.\n\n</div>\n<div class=\"block feature-box-narrow\">\n\n<h1 class=\"feature-box-header\">Marketing team</h1>\n\nHelp write, edit, and design content for this website, social media, newsletters, blog posts, user manual pages, videos, fundraising campaigns, press releases, and industry outreach.\n\n</div>\n\n</div>\n\n</section>\n\n<section>\n\n## User contributions\n\n<div class=\"feature-box-narrow\">\n\n<img src=\"https://static.graphite.art/content/volunteer/user-contributions.avif\" class=\"feature-box-full-image\" style=\"aspect-ratio: 3/1 auto; background: var(--color-lilac)\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.jpg')\" alt=\"Flavor graphic depicting a magnifying glass on the search for a software bug\" />\n\nAssign yourself the *\"🐒 Volunteer to get pinged regularly for QA testing\"* or *\"🤖 Interested in contributing code\"* roles in the *#welcome* Discord channel. In the latter case, drop by the *#development* channel to get advice writing your first node.\n\n<a href=\"https://discord.graphite.art\" class=\"button arrow\">Volunteer on Discord</a>\n\n</div>\n\n<div class=\"diptych user-contributions\">\n\n<div class=\"block feature-box-narrow\">\n\n<h1 class=\"feature-box-header\">QA team</h1>\n\nGet familiar with the ins-and-outs of the editor and respond actively to developer requests on a recurring basis to test out new features and find bugs and breakages.\n\n</div>\n<div class=\"block feature-box-narrow\">\n\n<h1 class=\"feature-box-header\">Nodes team</h1>\n\nExplore and push the limits of the node graph with complex procedural designs. Report your findings about limitations, opportunities, and use cases to help in designing new nodes.\n\n</div>\n\n</div>\n\n</section>\n"
  },
  {
    "path": "website/content/volunteer/guide/_index.md",
    "content": "+++\ntitle = \"Contributor guide\"\ntemplate = \"book.html\"\npage_template = \"book.html\"\naliases = [\"/contribute\"]\n\n[extra]\nbook = true\n+++\n\nWelcome, potential contributor! We're excited to have you join the Graphite project. It is our goal to make the process as smooth as possible. This guide will serve as your library of knowledge to help you get started contributing to the project. If you find any information missing or unclear, please let us know through Discord or submit a pull request to help document the process for future contributors.\n\n## Required experience level\n\nOur aim is to make the process as accessible as possible for first-time Graphite contributors with solid computer science foundations to get started, even without prior open source experience. Many contributors have remarked that they found our process notably more welcoming and straightforward than open source projects they've previously tried contributing to.\n\nHowever, please bear in mind that developing Graphite is **not for beginner programmers**. It is a complex, cutting-edge software engineering endeavor that requires at least a strong grasp of programming principles (although not necessarily Rust; you can learn that as you go).\n\nIf you are at least a junior-level developer or a self-motivated student who has built a number of self-directed, non-trivial personal projects (especially if they involve graphics), then you should be well-prepared to contribute to Graphite. If you are only a couple years into your programming journey, be warned that you will find Graphite frustratingly above your current skill level and we will be unable to support you adequately. The Graphite project is **not a learning platform or a classroom for inexperienced developers**, but with the requisite skills, it is a great way to develop real-world engineering experience and get involved in something meaningful.\n\nDo not make the mistake of assuming AI tools and agents can be a substitute for your own skills and experience. If you use AI tools in your workflow, read our [AI contribution policy](./starting-a-task/ai-contribution-policy).\n\n## Discord communication\n\nThe next page will cover how to compile the Graphite source code. But first, make sure you've joined our [Discord server](https://discord.graphite.art) and assigned yourself the *\"🤖 Interested in contributing code\"* role from the `#🙂welcome` channel. (And after your first PR is accepted and merged, you should post a request in `#📄development` to be assigned the *\"Code Contributor\"* role.)\n\nThis is semi-mandatory, particularly if you intend to become a regular contributor on the team, because it is how our team communicates and is just as central to our process as GitHub.\n\nDone that? Alright, proceed to the next page!\n"
  },
  {
    "path": "website/content/volunteer/guide/codebase-overview/_index.md",
    "content": "+++\ntitle = \"Codebase overview\"\ntemplate = \"book.html\"\npage_template = \"book.html\"\n\n[extra]\norder = 2 # Chapter number\njs = [\"/js/component/youtube-embed.js\", \"/js/page/contributor-guide/crate-hierarchy.js\"]\ncss = [\"/component/youtube-embed.css\", \"/page/contributor-guide/crate-hierarchy.css\"]\n+++\n\nThe best introduction for getting up-to-speed with Graphite contribution comes from watching this webcast recording. Before asking questions in Discord, please watch the full video because it gives a comprehensive overview of most things you will need to know.\n\n<div class=\"youtube-embed aspect-16x9\">\n\t<img data-youtube-embed=\"vUzIeg8frh4\" src=\"https://static.graphite.art/content/volunteer/guide/workshop-intro-to-coding-for-graphite-youtube.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Workshop: Intro to Coding for Graphite\" />\n</div>\n\n## Codebase structure\n\nGraphite is built from several main software components. New developers may choose to specialize in one or more area without having to attain a working knowledge of the full codebase.\n\n### Frontend\n\n*Location: [`/frontend/src`](https://github.com/GraphiteEditor/Graphite/tree/master/frontend/src)*\n\nThe frontend is the GUI for Graphite which users see and interact with. It is built using web technologies with TypeScript and Svelte (HTML and SCSS). The frontend's philosophy is to be as lightweight and minimal as possible. It acts as the entry point for user input and then quickly hands off its work to the WebAssembly editor backend via its [Wasm wrapper API](https://github.com/GraphiteEditor/Graphite/tree/master/frontend/wasm). That API is written in Rust but has TypeScript bindings generated by the [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) tooling that is part of the Vite-based build toolchain. The frontend is built of many [components](https://github.com/GraphiteEditor/Graphite/tree/master/frontend/src/components) that recursively form the window, panels, and widgets that make up the user interface.\n\n### Editor\n\n*Location: [`/editor`](https://github.com/GraphiteEditor/Graphite/tree/master/editor)*\n\n[The editor](./editor-structure) is the core of the Graphite application, and it's where all the business logic occurs for the tooling and user interaction. It is written in Rust and compiled to WebAssembly. At its heart is the message system. It is responsible for communicating with Graphene as well as handling the actual logic, state, tooling, and responsibilities of the interactive application.\n\n### Graphene\n\n*Location: [`/node-graph`](https://github.com/GraphiteEditor/Graphite/tree/master/node-graph)*\n\n[Graphene](../graphene/) is the node graph engine which manages and renders the documents. It is itself a programming language, where Graphene programs are compiled while being edited live by the user, and where executing the program renders the document.\n\n## Crate dependency graph\n\nThis diagram shows the structure of the crates that comprise the Graphite codebase and how they depend on each other. Every Arrow points from a crate to another which it depends on.\n\n<div class=\"crate-hierarchy\">\n\n<!-- replacements::crate_hierarchy() -->\n\n</div>\n\n## Frontend/backend communication\n\nFrontend-to-backend communication is achieved through a thin Rust translation layer in [`/frontend/wasm/src/editor_api.rs`](https://github.com/GraphiteEditor/Graphite/tree/master/frontend/wasm/src/editor_api.rs) which wraps the editor backend's Rust-based message system API and provides the TypeScript-compatible API of callable functions. These wrapper functions are compiled by [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) into autogenerated TS functions that serve as an entry point from TS into the Wasm binary.\n\nBackend-to-frontend communication happens by sending a queue of messages to the frontend message dispatcher. After the TS has called any wrapper API function to get into backend code execution, the editor's business logic runs and queues up each [`FrontendMessage`](https://github.com/GraphiteEditor/Graphite/tree/master/editor/src/messages/frontend/frontend_message.rs) which get mapped from Rust to JavaScript data structures in [`/frontend/src/messages.ts`](https://github.com/GraphiteEditor/Graphite/tree/master/frontend/src/messages.ts). Various TS code subscribes to these messages by calling:\n\n```rs\nsubscribeFrontendMessage(NameOfMessage, (messageData) => { /* callback code */ });\n```\n"
  },
  {
    "path": "website/content/volunteer/guide/codebase-overview/debugging-tips.md",
    "content": "+++\ntitle = \"Debugging tips\"\n\n[extra]\norder = 2 # Page number after chapter intro\ncss = [\"/page/contributor-guide/bisect-tool.css\"]\njs = [\"/js/page/contributor-guide/bisect-tool.js\"]\n+++\n\nThe Wasm-based editor has some unique limitations about how you are able to debug it. This page offers tips and best practices to get the most out of your problem-solving efforts.\n\n## Comparing with deployed builds\n\nWhen tracking down a bug, first check if the issue you are noticing also exists in `master` or just in your branch. Open up [dev.graphite.art](https://dev.graphite.art) which always deploys the lastest commit, as opposed to [editor.graphite.art](https://editor.graphite.art) which deploys the latest stable release. Build links for any commit may be found by clicking the \"comment\" icon on the right side of any commit in the GitHub repo [commits list](https://github.com/GraphiteEditor/Graphite/commits/master/).\n\nUse *Help* > *About Graphite…* in the editor to view any build's Git commit hash.\n\nBeware of a potential pitfall: all deploys and build links are built with release optimizations enabled. This means some bugs (like crashes from bounds checks or debug assertions) may exist in `master` and would appear if run locally, but not in the deployed version.\n\n## Build bisect tool\n\n```sh\n# Access this quickly in the future:\ncargo run explore bisect\n```\n\nThis interactive tool helps you binary search through recent commits, test the build links of each, and pinpoint which change introduced a regression or added a feature.\n\n<div class=\"bisect-tool\">\n\n<div class=\"phase active\" data-phase=\"setup\">\n\t<div class=\"setup-section\">\n\t\t<div class=\"section-label\">\n\t\t\t<span><strong>What are you looking for?</strong></span>\n\t\t</div>\n\t\t<label>\n\t\t\t<input type=\"radio\" name=\"bisect-mode\" value=\"regression\" checked />\n\t\t\t<span>Find when a regression or bug started</span>\n\t\t</label>\n\t\t<label>\n\t\t\t<input type=\"radio\" name=\"bisect-mode\" value=\"feature\" />\n\t\t\t<span>Find when a feature was added or fixed</span>\n\t\t</label>\n\t</div>\n\t<div class=\"setup-section\">\n\t\t<div class=\"section-label\">\n\t\t\t<span><strong>When do you estimate this changed?</strong></span>\n\t\t</div>\n\t\t<label>\n\t\t\t<input type=\"radio\" name=\"start-method\" value=\"date\" checked />\n\t\t\t<span>Date</span>\n\t\t</label>\n\t\t<label>\n\t\t\t<input type=\"radio\" name=\"start-method\" value=\"hash\" />\n\t\t\t<span>Commit</span>\n\t\t</label>\n\t</div>\n\t<div class=\"commit-inputs\">\n\t\t<div class=\"start-input\" data-input=\"date\">\n\t\t\t<input type=\"date\" data-commit-date />\n\t\t</div>\n\t\t<div class=\"start-input hidden\" data-input=\"hash\">\n\t\t\t<input data-commit-hash placeholder=\"Commit hash\" pattern=\"[0-9a-fA-F]{7,40}\" />\n\t\t</div>\n\t\t<span class=\"button arrow\" data-start-button>Begin bisect</span>\n\t</div>\n</div>\n\n<div class=\"phase\" data-phase=\"bisect\">\n\t<div class=\"block feature-box-narrow\">\n\t\t<div class=\"step-header\">\n\t\t\t<span class=\"step-label\" data-step-label><strong>Bisect step 1</strong></span>\n\t\t\t<span class=\"go-back hidden\" data-go-back-button>(<a>go back</a>)</span>\n\t\t</div>\n\t\t<div class=\"progress-info\" data-progress-info></div>\n\t\t<div class=\"commit-info\" data-commit-info></div>\n\t\t<span class=\"button arrow\" data-test-build-button>Test this build</span>\n\t\t<span class=\"findings\">After testing, what have you found?</span>\n\t\t<div class=\"bisect-actions\">\n\t\t\t<span class=\"button\" data-issue-present-button></span>\n\t\t\t<span class=\"button\" data-issue-absent-button></span>\n\t\t</div>\n\t</div>\n</div>\n\n<div class=\"error-message\" data-message-box></div>\n\n</div>\n\n## Printing to the console\n\nUse the browser console (<kbd>F12</kbd>) to check for warnings and errors. In Rust, use `log::debug!(\"The number is {some_number}\");` to print to the browser console. These statements should be for temporary debugging. Remove them before your code is reviewed. Print-based debugging is necessary because breakpoints are not supported in WebAssembly.\n\nAdditional print statements are available that *should* be committed:\n\n- `log::error!()` is for descriptive user-facing error messages arising from a bug\n- `log::warn!()` is for non-critical problems that likely indicate a bug somewhere\n- `log::trace!()` is for verbose logs of ordinary internal activity, hidden by default but viewable by activating *Help* > *Debug: Print Trace Logs*\n\n## Message system logs\n\nTo also view logs of the messages dispatched by the message system, activate *Help* > *Debug: Print Messages* > *Only Names*. Or use *Full Contents* for a more verbose view containing the actual data being passed. This is an invaluable window into the activity of the message flow and works well together with `log::debug!()` printouts for tracking down message-related defects.\n\n## Node/layer and document IDs\n\nIn debug mode, hover over a layer's name in the Layers panel, or a layer/node in the node graph, to view a tooltip with its ID. Likewise, document IDs may be read from their tab tooltips.\n\n## Performance profiling\n\nBe aware that having your browser's developer tools open will significantly impact performance in both debug and release builds, so it's best to close that when not in use.\n\nThe *Performance* tab of the browser developer tools lets you record and analyze performance profiles, and this is a useful way to track down bottlenecks. The Firefox profiler has some additional features missing from the Chromium debugger, so if you are digging deep into a performance issue, it can be worth giving Firefox a try for that purpose. Be sure to use debug builds while profiling, otherwise inlined functions and other optimizations may produce a misleading view of where time is being spent. The live deployed web app (production and dev) and build links hosted by our CI infrastructure are all built with release optimizations.\n"
  },
  {
    "path": "website/content/volunteer/guide/codebase-overview/editor-structure.md",
    "content": "+++\ntitle = \"Editor structure\"\n\n[extra]\norder = 1 # Page number after chapter intro\ncss = [\"/page/contributor-guide/editor-structure.css\"]\njs = [\"/js/page/contributor-guide/editor-structure.js\"]\n+++\n\nThe Graphite editor is the application users interact with to create documents. Its code is a single Rust crate that lives below the frontend (web code) and above [Graphene](../../graphene) (the node-based graphics engine). The main business logic of all visual editing is handled by the editor backend. When running in the browser, it is compiled to WebAssembly and passes messages to the frontend.\n\n## Message system\n\nThe Graphite editor backend is organized into a hierarchy of subsystems which talk to one another through message passing. Messages are pushed to the front or back of a queue and each one is processed sequentially by the editor's dispatcher.\n\nThe dispatcher lives at the root of the editor hierarchy and acts as the owner of all its top-level message handlers. This satisfies Rust's restrictions on mutable borrows because only the dispatcher may mutate its message handlers, one at a time, while each message is processed.\n\n## Editor outline\n\n```sh\n# Access this quickly in the future:\ncargo run explore editor\n```\n\nClick to explore the outline of the editor subsystem hierarchy which forms the structure of the editor's subsystems, state, and interactions. Also available as a searchable <a href=\"/volunteer/guide/codebase-overview/hierarchical-message-system-tree.txt\">plain text file</a>.\n\n<div class=\"structure-outline\">\n<!-- replacements::hierarchical_message_system_tree() -->\n</div>\n\n### Parts of the hierarchy\n\n<span class=\"subsystem\">Subsystem components</span>\n\n- A <span class=\"subsystem\">*Message</span> enum is the component of an editor subsystem that defines its message interfaces as enum variants. Messages are used for passing a request from anywhere in the application, optionally with some included data, to have a particular block of code be run by its respective message handler.\n\n- A <span class=\"subsystem\">*MessageHandler</span> struct is the component of an editor subsystem that has ownership over its persistent editor state and its child message handlers for the lifetime of the application. It also defines the logic for handling each of its messages that it receives from the dispatcher. Those blocks of logic may further enqueue additional messages to be processed by itself or other message handlers during the same dispatch cycle.\n\n- A <span class=\"subsystem\">*MessageContext</span> struct is the component of an editor subsystem that defines what data is made available from other subsystems when running the logic to handle a dispatched message. It is a struct that is passed to the message handler when processing a message, and it gets filled in with data (owned, borrowed, or mutably borrowed) from its parent message handler. Intermediate subsystem layers may forward data from their parent to their child contexts to make state available from further up the hierarchy.\n\n<span class=\"submessage\">Sub-messages</span>\n\n- A <span class=\"submessage\">#[child] *</span> attribute-decorated message enum variant is a special kind of message that encapsulates a nested subsystem. As with all messages, its handler has a manually written code block. But that code must call its corresponding child message handler's `process_message` method. The child message handler is a field of this parent message handler's state struct.\n\n`Messages`\n\n- A `*` message enum variant is used throughout the editor to request that a certain subsystem performs some action, potentially given some data. In that sense, it resembles a function call, but a key difference is that messages are queued up and processed sequentially in a flat order, always invoked by the dispatcher.\n\n## How messages work\n\nMessages are enum variants that are dispatched to perform some intended activity within their respective message handlers. Here are two message definitions from <span class=\"subsystem\">DocumentMessage</span>:\n```rs\npub enum DocumentMessage {\n\t...\n\t// A message that carries one data field\n\tDeleteLayer {\n\t\tid: NodeId,\n\t}\n\t// A message that carries no data\n\tDeleteSelectedLayers,\n\t...\n}\n```\n\nAs shown above, additional data fields can be included with each message. But as a special case denoted by a <span class=\"submessage\">#[child]</span> attribute, that data can also be a sub-message enum, which enables hierarchical nesting of message handler subsystems.\n\nBy convention, regular data must be written as struct-style named fields (shown above), while a sub-message enum must be written as a tuple/newtype-style field (shown below). The <span class=\"subsystem\">DocumentMessage</span> enum of the previous example is defined as a child of <span class=\"subsystem\">PortfolioMessage</span> which wraps it like this:\n\n```rs\npub enum PortfolioMessage {\n\t...\n\t// A message that carries the `DocumentMessage` child enum as data\n\t#[child]\n\tDocument(DocumentMessage),\n\t...\n}\n```\n\nLikewise, the <span class=\"subsystem\">PortfolioMessage</span> enum is wrapped by the top-level <span class=\"subsystem\">Message</span> enum. The dispatcher operates on the queue of these base-level <span class=\"subsystem\">Message</span> types.\n\nSo for example, the `DeleteSelectedLayers` message mentioned previously will look like this as a <span class=\"subsystem\">Message</span> data type:\n\n```rs\nMessage::Portfolio(\n\tPortfolioMessage::Document(\n\t\tDocumentMessage::DeleteSelectedLayers\n\t)\n)\n```\n\nWriting out these nested message enum variants would be cumbersome, so that <span class=\"submessage\">#[child]</span> attribute shown earlier invokes a proc macro that automatically implements the `From` trait, letting you write this instead to get a <span class=\"subsystem\">Message</span> data type:\n\n```rs\nDocumentMessage::DeleteSelectedLayers.into()\n```\n\nMost often, this is simplified even further because the `.into()` is called for you when pushing a message to the queue with `.add()` or `.add_front()`. So this becomes as simple as:\n\n```rs\nresponses.add(DocumentMessage::DeleteSelectedLayers);\n```\n\nThe `responses` message queue is composed of <span class=\"subsystem\">Message</span> data types, and thanks to this system, child messages like `DocumentMessage::DeleteSelectedLayers` are automatically wrapped in their ancestor enum variants to become a <span class=\"subsystem\">Message</span>, saving you from writing the verbose nested form.\n"
  },
  {
    "path": "website/content/volunteer/guide/graphene/_index.md",
    "content": "+++\ntitle = \"Graphene\"\ntemplate = \"book.html\"\npage_template = \"book.html\"\n\n[extra]\norder = 5 # Chapter number\n+++\n\nGraphene is the node graph engine that powers the Graphite editor.\n\nIt's hard to describe in one sentence precisely what Graphene is, because it's a technology that serves several roles when viewed from different angles. But to get a feel for what it encompasses, here is a list of some of its purposes:\n\n- Render engine\n- Runtime environment\n- Procedural data processor\n- Node-based scripting system\n- Compiled programming language\n- Compiler toolchain built around `rustc`\n\n## Background\n\n### Artwork as a program\n\nArtwork created in Graphite is represented as a node graph that generates the graphical content authored by the user. This document is essentially source code for a program in the Graphene language. Modifying the graph (like adding a layer, changing a node's parameter, or updating a node's data every frame while interactively drawing a shape) changes the actual program that generates and renders the artwork. This program must be recompiled and executed every frame a change is made.\n\nNodes are functions that run algorithms related to graphical operations. Some may read bitmap images from disk, others may generate procedural patterns, and more may be used for compositing and blending. Vector nodes can also produce shapes, alter their geometry, and apply styling and effects. Put together, a full document is built from just its interconnected nodes— producing a complete work of art generated entirely with algorithms and data.\n\n### Graph executors as programming languages\n\nEvery node-based application needs to run its node graph to compute the resulting data. Execution occurs in an order that depends on the shape of the graph so that every node has the data it needs to compute its output.\n\nA procedural graph executor, in its basic form, is a simple system that executes functions in the appropriate order. It feeds information between nodes and caches that data for reuse between executions so that only changed branches of the graph have to be computed again. The system, as described, is the approach commonly used by virtually all node-based apps.\n\nCrucially, the execution flow is handled at runtime so there is some overhead during every run. By analogy to programming languages, this traditional execution model acts like an interpreted language. But interpreted languages are famously slow, and we don't want Graphite leaving performance on the table.\n\nIn designing Graphene, we decided to take a more advanced approach that could yield many of the benefits of a compiled language— code inlining, compiler optimizations, and a philosophy of offloading invariant enforcement to the type system. Instead of building a simple graph interpreter where functions (nodes) are run as user input changes, we designed a system that dynamically executes the graph with a variable degree of pre-compiled optimizations where bits and pieces are recompiled and patched in while the user modifies the artwork (and graph) every frame. Thereby, Graphene can dynamically range between an interpreted language, a JIT-optimized language, and a fully compiled language.\n\n## Technical overview\n\n### The latency/performance tradeoff\n\nWhile working in Graphite, multiple needs arise for speed in different contexts. While making interactive changes, the user needs feedback as quickly as possible. While panning and zooming the canvas or playing an animation, the user cares about smoothness and responsiveness. When procedural artwork is exported as a standalone program that processes data at runtime (like as part of an image processing web server or embedded within a game engine), performance is the sole concern.\n\nThis sliding scale of latency/performance concerns maps directly to programming language concepts. Interpreted languages run immediately, but with slow runtime performance. JIT-optimized languages also run nearly without delay, but with less overhead than an interpreter since it can dynamically balance its effort towards optimizing and executing code. Compiled languages take upfront time to compile, but run with less overhead. A choice of optimization levels can be applied to further trade initial compilation time for runtime performance.\n\nWe designed Graphene to operate in all three regimes:\n\n| Regime | Usage |\n|-|-|\n| Interpreted | While editing. Simple and currently the only mode that's implemented. |\n| JIT | While editing. Dynamically bridges the gap between both other regimes by selectively substituting branches of the graph with interpreted and compiled nodes to keep latency low and work towards higher execution performance. |\n| Compiled | When exported. The entire graph is compiled as a standalone program. |\n\n### Building upon the Rust compiler\n\nNodes are functions written in Rust and every node has precompiled bytecode that ships with Graphite for use in the interpreted regime. The graph `input` → `A` → `B` → `C` → `output` is equivalent to the Rust statement `let output = C(B(A(input)));`. Graphene can either execute `A`, `B`, and `C` sequentially in its interpreted regime, or its JIT and compiled regimes can generate that Rust statement and compile it with the Rust compiler, `rustc`. The inlined and optimized bytecode can then be substituted for those three nodes in the JIT regime.\n\nGraphene figures out which branches of the graph to compile and substitute as part of the JIT process while the user is authoring content in Graphite. While editing the graph, as changes occur to specific nodes, their surrounding graph branches drop back down to using the slower interpreted nodes. Then the JIT system works its way back up to faster execution over time by gradually compiling and swapping in larger optimized parts of the overall graph.\n\nThe fully compiled regime is used only when the user exports the procedural artwork as a standalone program. For example, a CLI program may read a string input argument (like a name) and procedurally generate an output image file (like a birthday card).\n\n### Compile server\n\nThe three regimes have thus far been only a description of the eventual architecture direction. The interpreted regime is currently the only mode implemented in Graphene. The other two will require access to `rustc` which will necessitate the compile server that we will finish building and then publicly host for Graphite users in the future. Users of the desktop version of Graphite will be able to use an embedded `rustc` if the user has opted to download the Rust toolchain while installing Graphite.\n\nWithout a compile server, all the nodes are precompiled when Graphite is built. The node registry (in the file `node_registry.rs`) currently exists to allow the interpreted executor to find the Rust functions that correspond to each node with its appropriate type signature. Nodes support generics, so it's currently necessary to list every forseeable concrete type signature in the registry until the compile server can generate bytecode for less common type combinations on-the-fly.\n\n### GPU compute shaders\n\nFurther building upon the Rust compiler toolchain, we employ the [`rust-gpu`](https://github.com/EmbarkStudios/rust-gpu) compiler backend for `rustc` which generates compute shaders that get executed on the GPU. This means we can write the same code to implement nodes that run on both CPU and GPU. (Although in practice, some nodes may need GPU-specific versions suited for the architectural limitations of GPU programming.) And we don't have to use a separate shader language!\n\n### A language within a language\n\nWhile Graphene is a programming language, it is also foundationally built upon the Rust language. We don't just use the Rust compiler, but we also employ its type system, traits, data structures, standard library, and crate ecosystem. The data that flows between nodes are Rust types (like structs, enums, tuples, primitives, and collections). Graphene's generic type system uses Rust's trait definitions in its enforcement of type safety and type inference.\n\n### Graphene language concepts\n\nSince Graphene is fundamentally a programming language, throughout this documentation we will use analogies which correlate Graphene concepts with their counterparts from traditional programming language theory. Here is an at-a-glance overview:\n\n| Graphene concept  | Programming language concept         |\n|:------------------|:-------------------------------------|\n| Node              | Function                             |\n| Graphite editor   | IDE/text editor                      |\n| Document          | Source code                          |\n| Graph/network     | Abstract syntax tree (AST)           |\n| Graph compilation | Linking/JIT optimization/compilation |\n| Graph execution   | Program execution                    |\n\n<!-- Our philosophy of building (bootstrapping) our own higher-level language features from the language itself -->\n<!-- Call arguments, construction arguments, `.eval()`, recompiling when construction argument values are updated but not when call argument data changes -->\n<!-- Extract/inject nodes and metaprogramming -->\n<!-- Cache nodes and stable node IDs -->\n<!-- Graph rewriting step (currently used only to remove Identity nodes),\n\t at various points in the compilation process,\n\t based on rules akin to an optimizing compiler -->\n<!-- Borrow tree -->\n<!-- Document nodes, proto nodes, and networks (must be: acyclic) -->\n<!-- Lambdas -->\n<!-- Graph compilation process -->\n<!-- The compilation server -->\n<!-- Code structure overview -->\n<!-- Guide for implementing a node -->\n<!-- The `Node` trait -->\n<!-- Generics, type inference, type erasure, and the node registry -->\n<!-- Monitor nodes -->\n"
  },
  {
    "path": "website/content/volunteer/guide/graphene/networks-and-nodes.md",
    "content": "+++\ntitle = \"Networks and nodes\"\n\n[extra]\norder = 1 # Page number after chapter intro\n+++\n\nIn Graphite, users build their artwork by connecting nodes together in a graph. When they want to organize and reuse a complex group of nodes, those may be encapsulated together as a subgraph in which one parent node represents the functionality of its children. In fact, many of the nodes provided in Graphite are themselves subgraphs built out of other nodes.\n\nDouble-clicking on nodes backed by a subgraph will display the subgraph's interior. Double-clicking nodes that are, instead, backed directly by Rust source code will open a code editor.\n\nAny (sub)graph can import/export data from/to the outside world. For example, a reusable subgraph may receive an imported image then use several nodes to process it and finally export the result. Or the root-level artwork graph may import the animation timestamp and render a frame of the artwork then export it to the canvas.\n\nIn the Graphite editor UI, here is an example graph of artwork that imports no data but exports its content to the canvas:\n\n<img src=\"https://static.graphite.art/content/features/mockup-node-graph.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Node graph UI mockup\" />\n\nThe graph shown above represents the full artwork, meaning it's the root-level graph in its document. But there is nothing special about that graph compared to any subgraph. To avoid the confusion of calling it a graph or subgraph which comes with implications about user-facing concepts in the context of a document, we will use the less-ambiguous term **network** in the context of Graphene's internal concepts and codebase.\n\n## Networks\n\nA node network can be thought of as a box containing a finite set of nodes that are connected together as a directed acyclic graph (DAG). The network is only concerned with its own node-to-node data flow. But to interact with the outside world, data can be imported into the network and exported out of it. From the inside, those imported/exported data sources/destinations are connected to the other nodes in the network. From the outside, a network can be considered a \"black box\" that is simply fed inputs and can be executed to produce outputs.\n\n***More coming soon...***\n"
  },
  {
    "path": "website/content/volunteer/guide/project-setup/_index.md",
    "content": "+++\ntitle = \"Project setup\"\ntemplate = \"book.html\"\npage_template = \"book.html\"\n\n[extra]\norder = 1 # Chapter number\n+++\n\nTo begin working with the Graphite codebase, you will need to set up the project to build and run on your local machine. Development usually involves running the dev server which watches for changes to frontend (web) and backend (Rust) code and automatically recompiles and reloads the Graphite editor in your browser.\n\n## Dependencies\n\nGraphite is built with Rust and web technologies, which means you will need to install:\n- [Rust](https://www.rust-lang.org/) (the latest stable release)\n- [Node.js](https://nodejs.org/) (the latest LTS version)\n- [Git](https://git-scm.com/) (any recent version)\n\n## Repository\n\nClone the project to a convenient location:\n\n```sh\ngit clone https://github.com/GraphiteEditor/Graphite.git\n```\n\n## Development builds\n\nIn the project directory, run the build system by executing:\n\n```sh\ncargo run\n```\n\nThis will check for the required system dependency versions, help you install any that are missing, and spin up the dev server at <http://localhost:8080> serving the web app with debug optimizations. A file watcher hot-reloads the web app when you save a code file. Shut down the dev server by double pressing <kbd>Ctrl</kbd><kbd>C</kbd>.\n\nFor additional build commands, see:\n\n```sh\ncargo run help\n```\n\nFor example, if you must proxy the dev server connection over a slow network where the >100 MB unoptimized binary size would pose an issue, you may need to run with release optimizations using `cargo run release`.\n\n## Development tooling\n\nWe provide default configurations for VS Code users. When you open the project, watch for a prompt to install the project's [suggested extensions](https://github.com/GraphiteEditor/Graphite/blob/master/.vscode/extensions.json). They will provide helpful web and Rust tooling. If you use a different IDE, you won't get default configurations for the project out of the box, so please remember to format your code and check CI for errors.\n\n### Checking, linting, and formatting\n\nWhile developing Rust code: `cargo check`, `cargo clippy`, and `cargo fmt` terminal commands may be run from the root directory. For web code: errors, code quality lints, and formatting issues can be checked using `npm run check` (to view them) and `npm run fix` (to fix them) if run from the `/frontend` directory.\n\nIf you don't use VS Code and its format-on-save feature, please remember to format before committing or [set up a `pre-commit` hook](https://githooks.com/) to do that automatically. Disabling VS Code's *Auto Save* files feature is recommended to ensure you actually save (and thus format) file changes. CI will enforce that everything passes these checks before your PR can be merged.\n"
  },
  {
    "path": "website/content/volunteer/guide/starting-a-task/_index.md",
    "content": "+++\ntitle = \"Starting a task\"\ntemplate = \"book.html\"\npage_template = \"book.html\"\n\n[extra]\norder = 3 # Chapter number\n+++\n\nThere are two places to look for beginner-friendly development tasks. Usually, the best option is to select one of the many bite-sized task descriptions marked with a ‼️ reaction in the `#✅code-todo-list` channel of the [Discord server](https://discord.graphite.art). You may also browse the task board for a list of [beginner issues](https://github.com/orgs/GraphiteEditor/projects/1/views/6) to pick from. The Discord option usually has the more approachable tasks, compared to the GitHub issues that often have more variability in complexity.\n\nIf you're unsure about which task to pick, feel free to ask in the `#📄development` channel. You can also use that channel to ask for coding help if you anticipate it will be a quick discussion rather than a longer-running conversation that deserves its own thread.\n\nYou may right click a `#✅code-todo-list` task and select \"Create Thread\" to ask questions and discuss your development progress. If your work doesn't correspond to a specific listed task in that channel, you can also create a thread in `#🧵task-help` with a short, descriptive title ending with your issue or PR number.\n\nIf you're tackling a GitHub issue, please remember to comment in the issue with a link to your PR once you submit it. This is necessary because we will assign you to the issue after your PR has merged, but GitHub only allows assignments to those who have commented on the issue.\n"
  },
  {
    "path": "website/content/volunteer/guide/starting-a-task/ai-contribution-policy.md",
    "content": "+++\ntitle = \"AI contribution policy\"\n\n[extra]\norder = 4 # Page number after chapter intro\n+++\n\nMany open source projects including Graphite have begun to be spammed with an ever-increasing flood of low-quality PRs written partly or wholly by AI. These harm the project by wasting the time of maintainers and preventing PRs by genuine contributors from receiving timely review. We aim to be reasonable and understanding to contributors who put in the effort, but it has become necessary to set some strict rules against low-effort PRs.\n\n## Acceptable usage\n\n- Non-agent AI tools may **assist** with debugging and tab-completion of single lines of code you would have otherwise written yourself. This does not require disclosure.\n- AI chat tools (not agents) may help you **generate** small (sub-40 line) snippets of code that you manually copy and paste, provided that you carefully review every line to ensure it is consistent with how you would have written it yourself. This requires disclosure.\n\n## Unacceptable usage\n\n- AI slop, \"vibe-coded\", or agent-written PRs are strictly forbidden and may be treated as malicious spam attacks against the project, resulting in a ban.\n- PR description text and replies to reviewers must be written by you, not AI. If your English is imperfect, just try your best; it is better than AI babble.\n\n## Required disclosure\n\n- Graphite has **zero-tolerance** for contributing undisclosed AI-generated content.\n- A detailed, human-written description must accompany every line of material that you did not personally write using your own brain. It should justify why each line is correct and appropriate. This should be prepared ahead of time and written as self-review comments on the GitHub PR's diff immediately after the PR is opened or new code is pushed.\n"
  },
  {
    "path": "website/content/volunteer/guide/starting-a-task/code-quality-guidelines.md",
    "content": "+++\ntitle = \"Code quality guidelines\"\n\n[extra]\norder = 2 # Page number after chapter intro\n+++\n\nThe Graphite project prizes code quality and accessibility to new contributors. Therefore, we ask you please make all efforts to contribute readable, well-documented code according to these best practices.\n\n## Linting\n\nPlease ensure Clippy is enabled. This should be set up automatically in VS Code. Avoid committing code with lint warnings so the code review process goes smoothly. You may execute `cargo clippy` anytime to confirm.\n\n## Naming\n\nPlease use descriptive variable/function/symbol names and keep abbreviations to a minimum. Prefer spelling out full words most of the time, such as `generate_document_format` instead of `gen_doc_fmt`.\n\nThis avoids the mental burden of expanding abbreviations into semantic meaning. Monitors are wide enough to display long variable/function names, so descriptive is better than cryptic.\n\nTotally unambiguous, common shortened forms are acceptable such as \"max\" for \"maximum\", \"eval\" for \"evaluate\", and \"info\" for \"information\".\n\nTo avoid wasted effort in code review, it's recommended that you set up a spellcheck plugin, like [this extension](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) for VS Code. The project uses American English spelling conventions.\n\n## Whole-number floats\n\nAlways use the style `42.` instead of `42.0` for whole-number floats to maintain consistency and brevity. For range syntax, either `0.0..42.` or `(0.)..42.` is acceptable.\n\n## Comments\n\nFor consistency, please try to write comments (`//`) in *Sentence case* (with a capital first letter) and don't end with a period unless multiple sentences are used in the same comment. For doc comments (`///`), always end your sentences with a period. There should always be one space after the `//` or `///` comment markers, and `/* */` style comments should be avoided.\n\nAvoid including commented-out code in PRs that are open for code review unless you have a compelling reason to keep it around for future reference.\n\nComments should usually be placed on a separate line above the code they are referring to, not at the end of the same code line.\n\n## Blank lines\n\nPlease make a habit of grouping together related lines of code in blocks separated by blank lines. These are like your paragraphs if you were writing a novel — they greatly aid readability and your copy editor would have significant concerns with your writing if they were absent.\n\nIf you have dozens of lines comprising a single unbroken block of logic, you are likely not splitting it apart enough to aid readability. Find sensible places to partition the logic and insert blank lines between each. At least 10% of the code you write should ideally be blank lines, otherwise you are likely underutilizing them at the expense of readability.\n\n## Imports\n\nOur imports used to be a mess before we tamed the chaos with a formatting rule that has to be applied manually, since `rustfmt` doesn't support it.\n\nWe always combine imports with common paths, but only at the same depth. For example:\n\n```rs\nuse crate::A::B::C;\nuse crate::A::B::C::Foo;\nuse crate::A::B::C::Bar;\n\n// Should be combined into:\n\nuse crate::A::B::C::{self, Foo, Bar};\n```\n\nBut we do not combine imports at mixed path depths. In other words, never put `::` inside `{}`. For example:\n\n```rs\nuse crate::A::{B::C::Foo, X::Hello};\n\n// Should be separated into:\n\nuse crate::A::B::C::Foo;\nuse crate::A::X::Hello;\n```\n"
  },
  {
    "path": "website/content/volunteer/guide/starting-a-task/submitting-a-contribution.md",
    "content": "+++\ntitle = \"Submitting a contribution\"\n\n[extra]\norder = 3 # Page number after chapter intro\n+++\n\nCollaboration is a key part of real-world software engineering. Graphite follows some basic procedures to keep the process smooth and efficient. You will want to familiarize yourself with these guidelines to save yourself and Graphite maintainers time and confusion.\n\nThis assumes you understand enough about how Git works to utilize commits, branches, and multiple remotes. If you're new to Git, you will need to learn those topics on your own, but a good starting point is [this portion](https://youtu.be/vUzIeg8frh4?t=237) of the Graphite intro webcast which recommends installing the [Git Graph](https://marketplace.visualstudio.com/items?itemName=mhutchie.git-graph) extension for VS Code to visualize your Git history and branches.\n\n## AI usage\n\nIf you are using any form of AI tools in your development workflow, you must read and comply with our [AI contribution policy](../ai-contribution-policy) before submitting your PR.\n\n## Git branch name\n\nBefore making your first commit, create a new branch with a name that describes what it's about. Aim for short but sufficiently descriptive. Kebab-case (using hyphens between words) is our usual convention. Don't include a prefix like `feature/` or `fix/` which just adds visual noise. An example like `fix-path-tool-selection-history` is fine, but almost too long.\n\n**Warning: do not open a PR from a branch named `master`.** It makes code review considerably more difficult. Create a new branch if you've already been committing to `master` and open your PR from that correctly-named branch.\n\nAfter you push your branch to GitHub then open a PR, you won't be able to change its branch name. But please don't close a PR and open a new one just because the branch name isn't optimal. Just keep these tips in mind for the next time.\n\n## Pull request\n\nOnce you have gotten your code far enough along that you are confident you'll be able to complete it, open a pull request (PR). You might also do this earlier if a maintainer requests to see your code in order to assist you.\n\nLater on when you are building larger features, a PR should be opened once you have meaningful progress. That way, it can be kept safe on GitHub and other maintainers can check in to see your status so your work is less of a mystery.\n\n**Here's the important part:** when you open a PR, it should be marked as a draft unless it is currently ready for review. The left image shows how to open a new PR as a draft, and the right image shows how to convert an existing PR to a draft.\n\n<p><img src=\"https://static.graphite.art/content/volunteer/guide/draft-pr.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Screenhots showing GitHub's &quot;Create pull request (arrow) > Create draft pull request&quot; and &quot;Still in progress? Convert to draft&quot; buttons\" /></p>\n\n<center><em>Open a new PR as a draft / convert an existing PR to a draft</em></center>\n\nYou should mark it as ready for review and ping a maintainer when you believe your code implements the needed functionality and doesn't introduce any new bugs or broken features.\n\n## Title and description\n\nYour PR title will become the commit message of your feature's commit in the project Git history. It should aim to concisely but descriptively summarize what your PR does. We use sentence case and imperative mood (\"Fix X bug\", \"Add Y feature\", \"Make Z faster\").\n\nIf you are working on a task from the `#✅code-todo-list` Discord channel, you should right-click the exact message, select \"Copy Message Link\", and paste that into your PR description.\n\nIf you are working on a task with a GitHub issue, please be certain to include that issue number in the description. GitHub requires the format \"Closes #123\", \"Fixes #123\", or \"Resolves #123\". If there are multiple issues, you have to fully repeat this trigger word for each one. If there is no issue, remove the pre-filled \"Closes #\" description text.\n\nWhen the PR gets merged, any issue referenced by the trigger word will be automatically closed. That isn't desirable for [tracking issues](https://github.com/GraphiteEditor/Graphite/issues?q=is%3Aissue+is%3Aopen+in%3Atitle+%22tracking+issue%22), so you should instead use \"Part of #123\" which isn't a trigger word. After the PR merges, please edit the description to change \"Part of\" to \"Closes\" so the tracking issue links to the PR without it having gotten closed.\n\nAs a bonus, it can be helpful for maintainers if you take a few minutes to write about what you changed and include relevant screenshots or video clips.\n\nIf you have concerns about a certain approach you took or if a certain part of your code is as clean as it could be, you can leave comments on lines of your own code from the \"Files changed\" tab after opening the PR.\n\n## Comment on the issue for assignment after it merges\n\nFor any issue referenced in your PR (including tracking issues), we need you to leave a comment on issue. It doesn't matter what you write. You can just say \"I opened PR #456\" or something to that effect. This is only necessary because we will need to assign that issue to you upon merging your PR, but GitHub only allows assignments to those who have commented.\n\nThat way you get credit for your work and we can keep our closed issues cleanly organized. For consistency, a closed issue should have an assignee if it was resolved by a PR. Otherwise, only duplicate or invalid issues should be closed without an assignee.\n\nWe don't commonly assign issues while a PR is still in progress, only upon landing the PR. That's because PRs often get abandoned and we don't want an assignment blocking someone else from picking up the work.\n\n## Code review etiquette\n\nIt is your responsibility to build the editor, thoroughly test your work, and employ common sense to avoid wasting a maintainer's time in needing to point out obvious flaws. It is not uncommon for inexperienced contributors to request review when their code entirely fails to implement the task at hand, or breaks surrounding functionality in a way that should have been immediately apparent. This doesn't leave a good impression and can frustrate maintainers. It may also be interpreted as AI-generated spam if the mistakes are egregious enough, which will lead to a ban according to our [AI contribution policy](../ai-contribution-policy).\n\nIf you don't actually understand what is intended with your feature/fix and why this is meaningful to a user of Graphite, spend time becoming that user and understanding the context. [Learning](/learn) at least the basics of using Graphite is important. Then ask questions in Discord if you're still confused about specific edge cases or the wording of the task.\n\nIt is also common for larger tasks to enter a round of review to confirm the direction is correct before you go back and polish the remaining details of the implementation. It's good to be in touch with the team to decide on when is the right time for this kind of preliminary review. It can save you effort reworking problems if you misunderstand the goals, or if the exact details of the requirements were never well-defined and you'll need to iterate on the design together with the team. Don't feel that every part of your PR needs to be 100% finished before requesting feedback, but also be clear so you aren't taking a maintainer away from other work to point out that you are obviously nowhere near done.\n\n## Self-review\n\nBefore marking your PR as ready for review, you should do a self-review. That means reading over the diff of all your changes to ensure they are correct, complete, and lacking frivolous changes like unintended whitespace alterations, leftover debugging code, or commented-out lines. Read over it with a fine-toothed comb so maintainers don't have to nitpick as much. It is only fair that your first code reviewer should be yourself, so you catch the obvious flaws first.\n\nFeel free to leave comments on lines of your own code in the diff if you want to communicate concerns or highlight uncertainties to the maintainer. This is also where you must [disclose AI generated lines of code](../ai-contribution-policy) if applicable.\n\n## Passing CI\n\nUpon pushing a commit to your PR's branch, CI will need to build and test your code. PRs from forks will have to wait until a maintainer approves the CI run. If you're uncertain, run `cargo test --all-features` on your machine or ask a maintainer to trigger CI for you.\n\nYou also have to pass `cargo fmt` and `cargo clippy` in CI before your PR can be merged. You should run these commands locally before pushing to confirm.\n\nYour goal is for the check called \"Editor: Dev & CI / build (pull_request)\" to pass with a ✅. If it fails with an ❌, you will need to investigate. If you need access to the build logs, ask a maintainer to provide them. Occasionally, other checks may fail, but you likely won't be responsible for fixing those and they can be ignored.\n\n## Keeping your work up-to-date\n\nBe sure to start your work from the latest commit on the `master` branch by pulling (`git pull`) with `master` checked out when you begin coding.\n\nAs time goes on and `master` accumulates new commits, your branch will become outdated. It has to be synced up with `master` before your PR can be merged. Sometimes there will be conflicts that you need to resolve, which you can find learning resources for online. Enabling Git's three-way diff conflict style with `git config --global merge.conflictstyle diff3` can make this process easier.\n\nWhen your branch can be updated with `master` without conflicts, you can click the \"Update branch\" button below the CI status. If you click the dropdown button beside it, you can choose instead to update with a rebase. If this can be done without conflicts, this is preferred because it maintains a clean, linear history for your branch.\n\n<p><img src=\"https://static.graphite.art/content/volunteer/guide/update-branch-with-rebase.avif\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" onload=\"this.width = this.naturalWidth / 2\" alt=\"Screenhots showing GitHub's &quot;Update with rebase&quot; button\" /></p>\n\nBe sure to pull the rebased, or updated-with-a-merge-commit, branch after you or a maintainer updates it (or pushes other commits to it) to ensure you are working on the latest code.\n\n**Please do not** constantly rebase or merge every day while you're waiting for review since it's unhelpful and gets annoying. A reviewer will do that for you if there are no conflicts. But if there are conflicts, you *will* need to resolve them and push the updated code before review.\n\n## Review process\n\nAssuming you have done what's explained above, a maintainer will aim to review your PR within a few days if possible. Feel free to send reminders because PRs can get overlooked.\n\nAs a rule of thumb, at this stage you are about 50% done with your work. The other 50% of your time will be spent responding to feedback and making (sometimes significant) changes.\n\nThere are two parts to the review process, QA and code review, which occur separately:\n\n- Quality assurance (QA): A build of your code will be opened and tested to ensure it implements the requested functionality and doesn't introduce regressions. This is not a substitute for your own testing, but it is a necessary line of defense against overlooked issues. This is usually performed by Keavon, the founder and product designer, whose eye for detail keeps the app polished and consistent. Maintainers (and only maintainers) have the ability to invoke CI by commenting \"!build\" on your PR which will produce a build link. That is a unique link hosting a build of your PR's current code.\n- Code review: The code will be checked for flawed approaches, pitfalls, confusing logic, [style guide](../code-quality-guidelines) adherence, sufficient comments and tests, and general quality. A review may be left through GitHub or your PR may have commits added to it. Feel free to read the diffs of those commits to understand what was changed so you can learn from that feedback. Direct commits are often faster than leaving dozens of comments. These can range from nitpicks to larger improvements. Our process is to collaborate on PRs as a team to write the best code possible, meaning your PR won't always be exclusively written by you.\n\nWhen changes are requested, the maintainer will usually mark the PR as a draft again while awaiting your updates. **It is your responsibility to mark it as ready for review** again once you have addressed the feedback.\n\n- If a PR is a draft, the ball is in your court to move it forward.\n- If it's marked as ready for review, it means there is nothing more for you to do until the maintainer has time to review it. (You're encouraged to work on other PRs while waiting.)\n\nAfter any number of back-and-forth cycles, a maintainer (usually Keavon who often gives the final say) will merge your PR. All your commits will be squashed into a single new commit on the `master` branch. This keeps the Git history linear and easy to follow.\n\nCongratulations on landing your successful contribution! Post a request in `#📄development` on Discord to be assigned the *\"Code Contributor\"* role.\n"
  },
  {
    "path": "website/content/volunteer/guide/student-projects/_index.md",
    "content": "+++\ntitle = \"Student projects\"\ntemplate = \"book.html\"\npage_template = \"book.html\"\n\n[extra]\norder = 4 # Chapter number\n+++\n\nGraphite offers a number of opportunities for students to contribute by building a self-contained project as part of a structured format. These projects are designed to be completed over several months and are ideal for Google Summer of Code or similar internship programs, solo or group university capstone projects, and other arrangements. Each project has a distinct focus and is a great way to make a meaningful contribution to open source over the length of the program while receiving mentorship and guidance from the Graphite team.\n\nStudent projects require adherence to a set schedule with regular check-ins, milestones, and evaluations. The structured setting is designed to provide a supportive environment for students to learn and grow as developers while gaining real-world industry experience from collaborating on a sizable software product and remaining accountable to stakeholders. It's our goal to make sure you succeed!\n\nUse this [contributor guide](..) to start out with the code. Then when you're ready, reach out through [Discord](https://discord.graphite.art) and use the `#🎓student-projects` channel to discuss and work towards proposing a project with the Graphite core team.\n\n## AI contribution policy\n\nBe sure to familiarize yourself with our [AI contribution policy](../starting-a-task/ai-contribution-policy) before getting involved with the Graphite code base. Proposals also must not be written by AI or else they will be rejected.\n\n## Google Summer of Code\n\nGSoC is a program offering students a [stipend](https://developers.google.com/open-source/gsoc/help/student-stipends) for successful completion of an internship-style experience with an open source organization. Read about [how it works](https://summerofcode.withgoogle.com/how-it-works/).\n\nGraphite participated in GSoC [2024](https://summerofcode.withgoogle.com/programs/2024/organizations/graphite) and [2025](https://summerofcode.withgoogle.com/programs/2025/organizations/graphite) and we anticipate doing so again in [2026](https://developers.google.com/open-source/gsoc/timeline) if our organization is accepted back. We accept year-round contributions; getting involved early is a great way to have a head start and stand out in your application in the upcoming program.\n\n### Writing a proposal\n\nWriting a good proposal is an important step that demonstrates your understanding of the project and your ability to think ahead and execute it. A well-defined plan will set you up for success throughout the rest of the program.\n\n<details>\n<summary>For proposal writing guidelines and requirements: click here</summary>\n\nYou are encouraged to reference the project idea list below to find several potential projects suited to your experience, interest, and choice of scope. Then, you must reach out to a [core team member](/about#core-team) through Discord to discuss your plan in detail before writing a proposal. This will help you understand the project's scope and requirements and develop a detailed timeline for your expected summer-long work schedule. Importantly, it will also help us understand your background and capabilities to offer you feedback and suggestions for the best outcome in the competitive applicant selection process.\n\nWhen it comes to writing the proposal, which you will submit to the GSoC application website, we offer some guidelines below:\n\n- **Proposal structure:** Please consult the [Blender GSoC application template](https://developer.blender.org/docs/programs/gsoc/application_template/) as reference for our desired format. For project ideas already listed below, omit the \"Benefits\" section. Remember: don't waste your—and our—time restating information that we already know, like background info about Graphite or our tech stack; we just want to hear your thoughts and plans about what you uniquely bring to the table and how you'll execute the project. Proposals should be utilitarian, not formal, while also demonstrating your professional communication skills. Using an LLM to write your proposal won't be to your advantage.\n- **Experience:** We're especially interested in your background and work experience, so attaching a résumé or CV is an optional but recommended way to help us understand your capabilities. If able, please also include links to past open source contributions or personal projects in the bio section. Our goal is to provide an environment for you to learn and grow as a productive software engineer and team collaborator, not to help you learn the basics of coding, so any included work examples will help us understand your potential as a self-motivated contributor to the open source community.\n- **Work timeline:** Your goal is to write a proposal that inspires confidence in your ability to successfully complete the project, which means understanding in detail what's involved at a technical level and how you plan to tackle it. A detailed work timeline is the most important written part of your proposal. It should be broken into weekly milestones with a couple sentences of technical detail. The summary in the project idea list below doesn't give enough information to develop a timeline, so you'll need to discuss this with the core team on Discord.\n- **Prior PRs:** The largest factor in our selection decision will be the quality and extent of your prior contributions to Graphite made during the proposal formulation period (or before, if applicable). Include a link to `https://github.com/GraphiteEditor/Graphite/commits?author=YOUR_GITHUB_USERNAME` in your proposal and feel free to write up a summary of what you've contributed and learned from the process. You may also keep contributing during the month after applications close, before we've finalized our selections, for those additional PRs to be considered.\n\n</details>\n\n## Project idea list\n\nProjects listed below vary considerably in their required skills and technical background. Some are very research-heavy and are only suited for students with years of self-motivated learning and project development in adjacent topics. Others have a more general focus and are approachable to a wider range of students. Please pay close attention to the \"Needed Skills\" and \"Difficulty\" indicators so you don't waste your opportunity applying to a project we don't think you're a good fit for.\n\n<!--\n- System for nodes displaying gizmos to update their parameters\n- Category of tools for repeating, mirroring, patterning, and manipulating objects (\"recipes\")\n- Text improvements (formatting spans, flows between text areas, text-on-path)\n- Feature-complete SVG import and rendering support\n-->\n\n### Graphene language bidirectional type inference\n\n*Graphene needs to implement a more powerful type system so a generic type may be inferred based on surrounding context of the type's usage constraints.*\n\n- **Possible Mentors:** [Dennis](https://github.com/truedoctor)\n- **Needed Skills:** Rust, type theory, programming languages theory, past experience implementing such a system\n- **Project Size:** Large *(GSoC: 350 hours)*\n- **Difficulty:** Hard\n- **Expected Outcomes:** A complete implementation to upgrade the current limited type inference system. The new system should work like Rust's, where variables of unknown types can be given a type satisfying the later usages of the variable.\n\nConsider a node with a generic input parameter which is connected to a node supplying a concrete type. As long as the type is one that satisfies the constraints of the generic parameter, this is valid. The current system checks for this single-directional constraint. But many cases arise where this is insufficient. For example, if the generic parameter is used in multiple places with different constraints, the system needs to be able to infer a type that satisfies all of those constraints.\n\n<details>\n<summary>For additional technical details: click here</summary>\n\nRead more about [HM type inference](https://en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_system), a powerful (but potentially more complex than necessary) model. See also the [GitHub issue](https://github.com/GraphiteEditor/Graphite/issues/2350) describing this, where you can ask questions if needed. This is an advanced topic and only suitable for individuals who have already implemented a similar system in a programming language or compiler project before.\n\n</details>\n\n### Node equivalence rewriting\n\n*A sequence of nodes may perform operations on data that can be expressed using fewer equivalent nodes, and users may often wish to perform such simplifications.*\n\n- **Possible Mentors:** [Dennis](https://github.com/truedoctor)\n- **Needed Skills:** Rust, graph theory, algorithm design\n- **Project Size:** Large *(GSoC: 350 hours)*\n- **Difficulty:** Hard\n- **Expected Outcomes:** A system for classifying and tracking data transformations symbolically within the DAG of the node graph. A system for applying rewrite rules to selected portions of the graph to produce an equivalent graph with fewer nodes. Integration with the editor to allow users to apply simplifications to selected nodes, especially to transforms and geometry.\n\nOftentimes, node graphs contain redundant steps that collectively perform a simpler operation. For example, two Transform nodes may produce the same result as a single Transform node with the combined transformation. Or a node that generates a star shape, then a Path node that applies a differential modification to its geometry, may be equivalent to a single Path node that produces the same geometry in one step. This project is about architecting and integrating a system for tracking classes of data transformations, like transforms or geometric modifications or appearance changes, and allowing the user to select the redundant nodes to collapse or \"bake\" them into a simpler graph with identical output. This is sort of like selecting the terms of a math expression and applying algebraic simplification rules to reduce it to its simplified form.\n\n<details>\n<summary>For additional technical details: click here</summary>\n\nThis is best for someone with an interest towards graph theory and compiler optimization topics like [E-graphs](https://en.wikipedia.org/wiki/E-graph). Additional detail is provided in the [GitHub issue](https://github.com/GraphiteEditor/Graphite/issues/2021) including some introductory explanation about E-graphs from a Rust crate that implements them, [egg](https://egraphs-good.github.io/).\n\n</details>\n\n### Machine learning architecture\n\n*AI/ML image/vision models for content editing will need to run in Graphite's node graph with a Rust-centric, modular, portable, deployable, scalable environment.*\n\n- **Possible Mentors:** [Oliver](https://github.com/otdavies)\n- **Needed Skills:** Machine learning (and potentially: Rust, Python, ONNX, Burn)\n- **Project Size:** Large *(GSoC: 350 hours)*\n- **Difficulty:** Hard\n- **Expected Outcomes:** Specifics will vary by proposal. In general, a useful end-to-end integration of at least one image model into Graphite's node graph which can run both locally and deployed to a hosting provider server.\n\nAI/ML is filling a rapidly growing role in the industry as a tool in some creative processes. Graphite's procedural node-based workflow is uniquely suited to leveraging the power and flexibility of AI nodes.\n\n[Segment Anything 2](https://ai.meta.com/research/sam2/) (object segmentation) and [Depth Anything 3](https://github.com/ByteDance-Seed/Depth-Anything-3) (depth estimation) are currently the models we are most [interested in integrating](https://github.com/GraphiteEditor/Graphite/issues/1694). The challenge is settling on an architecture and tech stack which is well suited for Graphite's requirements.\n\n<details>\n<summary>For additional technical details: click here</summary>\n\nThe approach should be extensible to future models. It needs to run fast and natively on the assorted hardware of local user machines with hardware acceleration. It should be a one-click installation process for users to download and run models without requiring dependencies or environment setup. Ideally, it should allow the more lightweight models to run locally in browsers with WebGPU. It needs to also be deployable to servers in a scalable, cost-viable manner that reuses most of the same code that runs locally. Runtime overhead, cold start times, and memory usage should be minimized for quick, frequent switching between models in a node graph pipeline. The tech stack also needs to be permissively licensed and, as much as possible, Rust-centric so it doesn't add complexity to our Wasm and desktop build processes.\n\nTo meet most of these criteria, our current thinking is to distribute and run our models using the [ONNX](https://onnx.ai/) format. This would integrate ONNX runtimes for WebGPU, native, and GPU cloud providers. One challenge is that many of the best-performing models are not packaged in ONNX format, but this approach also allows for direct implementation of model architectures in Rust.\n\n[Burn](https://burn.dev/) is Rust's most promising and advanced machine learning framework, and in addition to Rust model implementations, it also [supports](https://github.com/tracel-ai/burn-onnx) ONNX model loading for conversion into its native format.\n\nAnother potential direction is to find a portable, modular, lightweight approach for bundling existing Python-based models. It would need to work across simple and complex models with different architectures. License compliance, if GPL code is involved, would be a consideration.\n\nBased on the experience and insight brought to the table by the student, the nature of the project should be defined through preliminary discussions with the mentors and codified in the proposal. Machine learning and MLOps are fields that Graphite's team lack deep expertise in, so we are looking for a knowledgable student who can bring forth a well-researched and well-architected proposal and then execute on it.\n\n</details>\n\n### Generalized graphical data rendering representation\n\n*Rendering graphical content like colors, gradients, patterns, and whole other layers needs to be possible in a more flexible way that can target the fills and strokes of vector shapes.*\n\n- **Possible Mentors:** [Keavon](https://github.com/keavon)\n- **Needed Skills:** Rust, SVG\n- **Project Size:** Medium or Large *(GSoC: 175 or 350 hours)*\n- **Difficulty:** Medium\n- **Expected Outcomes:** Improved SVG and Vello renderer implementations that can handle a wider variety of paint types and effects. Support for every combination of paint type with its application to fills, strokes, and full-canvas drawing. Inclusion of the specified paint source types in the graphical data model and appropriate nodes for generating and handling such data.\n\nPresently, Graphite has a limited methodology for defining what gets painted when rendering vector shape fills and strokes. Solid colors and spatially positioned gradients are supported for fills, but only solid colors for strokes. Also, gradients cannot be painted across the entire canvas, and patterns do not exist at all yet. This project involves refactoring the renderer and data model to support a more generalized representation of paint sources that can be applied to fills, strokes, and entire layers. It deprecates the current solid/gradient/none selection for fills and solid/none selection for strokes in favor supporting anything that could be painted as a layer.\n\n<details>\n<summary>For additional technical details: click here</summary>\n\nAn extended description and a list of child issues is available in the [GitHub issue](https://github.com/GraphiteEditor/Graphite/issues/2779). A large-sized project would likely include support for the polyfilled gradient types described in the sub-issues of [this task](https://github.com/GraphiteEditor/Graphite/issues/2304).\n\n</details>\n\n<!-- ### Advanced text layout and typography\n\n*This is a newly added project pending a full written overview. Come ask on Discord for details.*\n\n- [See the GitHub issue.](https://github.com/GraphiteEditor/Graphite/issues/1105)\n\n### Brush engine\n\n*This is a newly added project pending a full written overview. Come ask on Discord for details.*\n\n- [See the GitHub issue.](https://github.com/GraphiteEditor/Graphite/issues/1297)\n\n### Advanced color management\n\n*This is a newly added project pending a full written overview. Come ask on Discord for details.*\n\n- Add support for HDR/WCG and/or CMYK and alternate color spaces/models\n- Requires an experienced understanding of color science\n\n### SVG with raster effects\n\n*This is a newly added project pending a full written overview. Come ask on Discord for details.*\n\n- The SVG spec supports a number of filters and other raster effects, and we currently only implement a small subset.\n- Add support for the rest of the SVG spec, including filters, masks, and other raster effects.\n- Allow roundtrip import and export of SVG files with these features.\n- Import, render (through SVG and Vello), and export of [filters like these](https://codepen.io/miXTim/pen/ZErggMQ).\n\n### Snapping system overhaul\n\n*This is a newly added project pending a full written overview. Come ask on Discord for details.*\n\n- [See the GitHub issue.](https://github.com/GraphiteEditor/Graphite/issues/2352)\n\n### Tooling polishing and gizmo additions\n\n*This is a newly added project pending a full written overview. Come ask on Discord for details.* -->\n\n### Marquee selection masking\n\n*Graphite's raster editing features requires the implementation of Select mode, where users can draw a mask which becomes a marquee (marching ants) selection.*\n\n- **Possible Mentors:** [Keavon](/about#keavon)\n- **Needed Skills:** Rust, computer graphics\n- **Project Size:** Large *(GSoC: 350 hours)*\n- **Difficulty:** Medium\n- **Expected Outcomes:** Complete implementation of Mask mode and its marquee selection. Marching ants visualization shader effect. Integration of selection mask with the node graph and raster editing tools. Useful raster editing workflow.\n\nA central part of the workflow in raster image editors is the selection of portions of the image to constrain manipulations just to the masked areas. Tools such as the circular and rectangular marquee, lasso, and magic wand are used to create masks. Instead of using dedicated tools, Graphite's design reuses the existing vector and raster drawing tools (like Rectangle, Ellipse, Pen, and Fill) to create masks in a dedicated Mask mode. Returning from Mask mode reveals the marching ants selection that constrains further editing operations.\n\nThis is a key feature in Graphite's evolution to a fully-featured raster editor.\n\n### Testing and performance instrumentation\n\n*Graphite has many areas that could benefit from better automated testing for bugs and performance regressions.*\n\n- **Possible Mentors:** [Dennis](/about#dennis)\n- **Needed Skills:** Rust, unit testing\n- **Project Size:** Small *(GSoC: 90 hours)* or larger if proposed\n- **Difficulty:** Easy\n- **Expected Outcomes:** Specific focus and scope may vary by the student's interests and proposal. In general, a significant increase in the coverage of tests in useful code areas (such as document loading, tool manipulation, and rendering) and attention towards systems which measure performance metrics and identify bottlenecks and regressions.\n\nGraphite could benefit from better testing coverage in a number of areas, especially end-to-end testing in the tool, document, and node graph systems. This project is about identifying and addressing areas that are lacking and most vulnerable to suffering from regressions. The student will be responsible for identifying areas that could benefit from better testing.\n\n### Your own idea\n\n*If you have an idea for a project that you think would be a good fit, we'd love to hear it!*\n\n- **Possible Mentors:** Varies\n- **Needed Skills:** Varies\n- **Project Size:** Varies\n- **Difficulty:** Varies\n- **Expected Outcomes:** Stated in your proposal.\n\nIf none of the projects above suit your interests or experience, we are very open to discussing your own project ideas that could benefit Graphite. You may consult our [task board](https://github.com/orgs/GraphiteEditor/projects/1/views/1) and [roadmap](/features#roadmap) to get a feel for what our current priorities are.\n\nAs is the case with all projects, please discuss this with us on Discord to flesh out your idea. Unsolicited proposals that have not been discussed with us will almost certainly be rejected.\n\n"
  },
  {
    "path": "website/content/volunteer/guide/student-projects/completed-projects.md",
    "content": "+++\ntitle = \"Completed projects\"\n\n[extra]\norder = 2 # Page number after chapter intro\n+++\n\nWe keep an archive of our successful student projects from past years to help prospective applicants get a better feel for the types and scope of projects we can support.\n\n## 2025\n\n### GPU-accelerated raster operations\n\n*Raster operations are limited to slow CPU-based fallbacks while GPU shader implementations require further infrastructure engineering.*\n\nAffiliation: GSoC 2025\nDuration: 3 months\nStudent: Firestar99\n\n- [Program project listing](https://summerofcode.withgoogle.com/organizations/graphite/projects/details/TfdLAuN4)\n- [Report and weekly updates](https://github.com/GraphiteEditor/Graphite/discussions/2658)\n\n**Outcomes:** Restructuring of dependencies within the node and data type definitions to allow for `#[no_std]` in the implementations of shader-driven raster nodes. Introduction of a compile-time pipeline for loading and compiling CPU node implementations to shader code using [Rust GPU](https://github.com/Rust-GPU/rust-gpu). Node definition macro changes to declare per-pixel color adjustment nodes as fragment shaders. Upstream improvements to Rust GPU and its build tool, [Cargo GPU](https://github.com/Rust-GPU/cargo-gpu), to support Graphite's use cases while avoiding a need for the rest of the Graphite project adopt the nightly Rust toolchain.\n\n**Background:** Graphite's node graph engine executes and renders graphics by means of defining artwork as procedural node graph programs within the purpose-built Graphene language. Each graphics operation is a node with, at minimum, a CPU implementation which supports compilation along with the rest of the graph into an executable Graphene program for rendering to the screen. A major goal is to share that same CPU implementation for the GPU version that compiles to a GPU shader in order to maintain identical algorithms between versions and avoid the maintenance burden of separate code paths. However, GPU architectures enforce challenging constraints which leaves this goal as yet unrealized. The project must tackle the engineering challenges of setting up a basic shader compilation system and integrate GPU versions of nodes into the Graphite editor and its render pipeline.\n\n\n### 3 additional projects (summaries coming soon)\n\nSee the [program listing](https://summerofcode.withgoogle.com/programs/2025/organizations/graphite) for more details until the other three GSoC 2025 project summaries are added here.\n\n## 2024\n\n### Interactive node graph auto-layout\n\n*Graphite's graph UI needs a system to automatically arrange layers and nodes given incremental changes to the graph contents.*\n\nAffiliation: GSoC 2024  \nDuration: 3 months  \nStudent: Adam Gerhant\n\n- [Program project listing](https://summerofcode.withgoogle.com/programs/2024/projects/gvbBoCpT)\n- [Report and weekly updates](https://github.com/GraphiteEditor/Graphite/discussions/1769)\n\n**Outcomes:** A system that manages the placement of nodes based on a set of layout constraint rules and incremental updates to the graph topology. It should run efficiently, even with large graphs. It should be robust enough to handle a variety of graph topologies and user interactions, producing organized, useful, and stable layouts.\n\n**Background:** The Graphite concept is built around a node graph representation of layer stacks, while tools automatically generate and manipulate nodes. When a layer or node is inserted, deleted, moved, or referenced, the graph needs to be reorganized to maintain a clear and useful layout. Users can also interactively expand and collapse groups of nodes which occupies or frees up graph real estate.\n\nUnlike other node editors that are centered around manual graph editing, where users are fully in charge of node placements within one large node network, Graphite's node UI is more oriented towards automatic layout management and viewing just parts of the graph at one time. This means the shown graph topology is constantly changing and the layout system needs to cooperatively organize the graph in concert with user actions.\n\nWhile general graph layout algorithms are complex and struggle to produce good results in other node editors, Graphite's graph topology is more constrained and predictable, which makes it possible to design a layout system that can produce good results. Nodes tend to be organized into rows, and layers into columns. This turns the problem into more of a constraint-based, axis-aligned packing problem.\n\n### Rendering performance infrastructure improvements\n\n*Graphite performance is bottlenecked by limitations in the new node graph rendering architecture that needs improvements.*\n\nAffiliation: GSoC 2024  \nDuration: 4 months  \nStudent: Dennis Kobert\n\n- [Program project listing](https://summerofcode.withgoogle.com/programs/2024/projects/v5z2Psnc)\n- [Report and weekly updates](https://github.com/GraphiteEditor/Graphite/discussions/1773)\n\n**Outcomes:** A holistic, metrics-driven focus on fixing the many unoptimized areas of Graphite's node graph compilation, execution, and rendering systems. Integration of Vello as an integrated rendering backend. A significant improvement in the performance of the editor, especially in the node graph, and a more stable and predictable performance profile. Benchmarking and profiling tools to measure and visualize performance improvements and regressions.\n\n**Background:** Graphite's node graph system is the backbone of the editor, but it has many performance problems that need to be addressed because the system is relatively immature and performance-impacting shortcuts were taken during its initial development. This project is all about making the node graph system more robust and optimized, which will have a direct impact on the user experience and the editor's overall performance. By the end of the project, the editor should finally feel usable in the majority of user workflows. Vello should be enabled as an alternate render engine that will fully replace the existing SVG-based one in the future, once browser support arrives across major platforms.\n\n### Raw photograph decoding in Rust\n\n*For Graphite to support editing photos from professional digital cameras, it needs a raw decoding/processing library.*\n\nAffiliation: GSoC 2024  \nDuration: 5 months  \nStudent: Elbert Ronnie\n\n- [Program project listing](https://summerofcode.withgoogle.com/programs/2024/projects/2uiwOfz8)\n- [Report and weekly updates](https://github.com/GraphiteEditor/Graphite/discussions/1771)\n- [Rawkit library](https://crates.io/crates/rawkit)\n\n**Outcomes:** A Rust library that implements raw photo decoding functionality to native Rust. A clean, well-structured codebase and API. At a minimum, demonstrate the successful end-to-end decoding, debayering, and color space handling of Sony ARW format photos in Graphite. Publish the library to crates.io.\n\n**Background:** For Graphite to work as a photo editing app, it needs to import raw photos. These contain compressed sensor imagery and metadata in a variety of formats. Sony ARW is the first target and additional camera brands are stretch goals. Graphite needs a library written in pure Rust with a suitable (non-GPL) license, which does not currently exist in the ecosystem, so we need to create one ourselves.\n\n## 2023\n\n### Bezier-rs library\n\n*Graphite's vector editing features require the implementation of Bezier curve and path manipulation computational geometry algorithms.*\n\nAffiliation: University of Waterloo, Ontario, Canada  \nDuration: 9 months  \nStudents: Hannah Li, Rob Nadal, Thomas Cheng, Linda Zheng, Jackie Chen\n\n- [Bezier-rs library](https://crates.io/crates/bezier-rs)\n- [Interactive web demo](https://keavon.github.io/Bezier-rs/)\n\n**Outcomes:** The student group designed an API for representing and manipulating Bezier curves and paths as a standalone Rust library which was published to crates.io. It now serves as the underlying vector data format used in Graphite, and acts as a testbed for new computational geometry algorithms. The team also built an interactive web demo catalog to showcase many of the algorithms, which are also handily embedded in the library's [documentation](https://docs.rs/bezier-rs/latest/bezier_rs/).\n\n## 2022\n\n### Backend layout system\n\n*Graphite's UI needs a system to define and manage layouts for widgets from the backend.*\n\nAffiliation: California Polytechnic State University, San Luis Obispo, USA  \nDuration: 3 months  \nStudent: Max Fisher\n\n**Outcomes:** The student designed and implemented a new system across the editor's frontend and backend which made it possible to define and manage layouts for widgets from the backend and receive input data from those widgets. Previously, all layouts were statically defined in the frontend and extensive plumbing was required to pass data back and forth.\n\n### Path boolean operations\n\n*Graphite's vector editing features require the implementation of boolean operations on paths, such as union, intersection, and difference.*\n\nAffiliation: California Polytechnic State University, San Luis Obispo, USA  \nDuration: 3 months  \nStudent: Caleb Dennis\n\n**Outcomes:** The student devised and prototyped algorithms for performing boolean operations on paths, such as union, intersection, and difference. These were used as a stopgap during 2022 and 2023 to provide users with a rudimentary boolean operation feature set.\n"
  },
  {
    "path": "website/eslint.config.js",
    "content": "import js from \"@eslint/js\";\nimport { defineConfig, globalIgnores } from \"eslint/config\";\nimport * as pluginImport from \"eslint-plugin-import\";\nimport pluginPrettier from \"eslint-plugin-prettier\";\nimport globals from \"globals\";\nimport ts from \"typescript-eslint\";\n\nexport default defineConfig([\n\tjs.configs.recommended,\n\tts.configs.recommended,\n\tpluginImport.flatConfigs.recommended,\n\tpluginImport.flatConfigs.typescript,\n\tglobalIgnores([\n\t\t// Ignore generated directories\n\t\t\"node_modules/\",\n\t\t\"public/\",\n\t\t// Ignore vendored code\n\t\t\"static/*.js\",\n\t\t// Don't ignore JS and TS dotfiles in this folder\n\t\t\"!.*.js\",\n\t\t\"!.*.ts\",\n\t]),\n\t{\n\t\tplugins: {\n\t\t\tprettier: pluginPrettier,\n\t\t},\n\t\tsettings: {\n\t\t\t\"import/parsers\": { \"@typescript-eslint/parser\": [\".ts\", \".js\"] },\n\t\t\t\"import/resolver\": { typescript: true, node: true },\n\t\t},\n\t\tlanguageOptions: {\n\t\t\tparserOptions: {\n\t\t\t\tproject: \"./tsconfig.json\",\n\t\t\t},\n\t\t\tglobals: {\n\t\t\t\t...globals.browser,\n\t\t\t\t...globals.node,\n\t\t\t},\n\t\t},\n\t\trules: {\n\t\t\t// Standard ESLint config (for ordinary JS syntax linting)\n\t\t\tindent: \"off\",\n\t\t\tquotes: [\"error\", \"double\", { allowTemplateLiterals: true }],\n\t\t\tcamelcase: [\"error\", { properties: \"always\" }],\n\t\t\tcurly: [\"error\", \"multi-line\"],\n\t\t\t\"linebreak-style\": [\"error\", \"unix\"],\n\t\t\t\"eol-last\": [\"error\", \"always\"],\n\t\t\t\"max-len\": [\"error\", { code: 200, tabWidth: 4, ignorePattern: `d=\"([\\\\s\\\\S]*?)\"` }],\n\t\t\t\"prefer-destructuring\": \"off\",\n\t\t\t\"no-console\": \"warn\",\n\t\t\t\"no-debugger\": \"warn\",\n\t\t\t\"no-param-reassign\": [\"error\", { props: false }],\n\t\t\t\"no-bitwise\": \"off\",\n\t\t\t\"no-shadow\": \"off\",\n\t\t\t\"no-use-before-define\": \"off\",\n\t\t\t\"no-restricted-imports\": [\"error\", { patterns: [\".*\"] }],\n\n\t\t\t// TypeScript plugin config (for TS-specific linting)\n\t\t\t\"@typescript-eslint/indent\": \"off\",\n\t\t\t\"@typescript-eslint/camelcase\": \"off\",\n\t\t\t\"@typescript-eslint/no-use-before-define\": \"off\",\n\t\t\t\"@typescript-eslint/no-unused-vars\": [\n\t\t\t\t\"error\",\n\t\t\t\t{\n\t\t\t\t\targs: \"all\",\n\t\t\t\t\targsIgnorePattern: \"^_\",\n\t\t\t\t\tcaughtErrors: \"all\",\n\t\t\t\t\tcaughtErrorsIgnorePattern: \"^_\",\n\t\t\t\t\tdestructuredArrayIgnorePattern: \"^_\",\n\t\t\t\t\tvarsIgnorePattern: \"^_\",\n\t\t\t\t\tignoreRestSiblings: true,\n\t\t\t\t},\n\t\t\t],\n\t\t\t\"@typescript-eslint/consistent-type-imports\": \"error\",\n\t\t\t\"@typescript-eslint/consistent-type-definitions\": [\"error\", \"type\"],\n\t\t\t\"@typescript-eslint/consistent-type-assertions\": [\"error\", { assertionStyle: \"as\", objectLiteralTypeAssertions: \"never\" }],\n\t\t\t\"@typescript-eslint/consistent-indexed-object-style\": [\"error\", \"record\"],\n\t\t\t\"@typescript-eslint/consistent-generic-constructors\": [\"error\", \"constructor\"],\n\t\t\t\"@typescript-eslint/no-restricted-types\": [\"error\", { types: { null: \"Use `undefined` instead.\" } }],\n\n\t\t\t// Import plugin config (for intelligently validating module import statements)\n\t\t\t\"import/consistent-type-specifier-style\": [\"error\", \"prefer-top-level\"],\n\t\t\t\"import/no-unresolved\": \"error\",\n\t\t\t\"import/prefer-default-export\": \"off\",\n\t\t\t\"import/no-relative-packages\": \"error\",\n\t\t\t\"import/no-named-as-default-member\": \"off\",\n\t\t\t\"import/order\": [\n\t\t\t\t\"error\",\n\t\t\t\t{\n\t\t\t\t\talphabetize: { order: \"asc\", caseInsensitive: true },\n\t\t\t\t\twarnOnUnassignedImports: true,\n\t\t\t\t\t\"newlines-between\": \"always-and-inside-groups\",\n\t\t\t\t},\n\t\t\t],\n\n\t\t\t// Prettier plugin config (for validating and fixing formatting)\n\t\t\t\"prettier/prettier\": [\n\t\t\t\t\"error\",\n\t\t\t\t{\n\t\t\t\t\ttabWidth: 4,\n\t\t\t\t\ttabs: true,\n\t\t\t\t\tprintWidth: 200,\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t},\n]);\n"
  },
  {
    "path": "website/package.json",
    "content": "{\n\t\"name\": \"graphite-website\",\n\t\"description\": \"Graphite's website. This npm package is for dev tooling only, such as eslint.\",\n\t\"private\": true,\n\t\"repository\": {\n\t\t\"type\": \"git\",\n\t\t\"url\": \"git+https://github.com/GraphiteEditor/Graphite.git\"\n\t},\n\t\"author\": \"Graphite Authors <contact@graphite.art>\",\n\t\"license\": \"Apache-2.0\",\n\t\"homepage\": \"https://graphite.art\",\n\t\"type\": \"module\",\n\t\"scripts\": {\n\t\t\"postinstall\": \"node .build-scripts/install.ts\",\n\t\t\"check\": \"tsc --noEmit && eslint\",\n\t\t\"fix\": \"eslint --fix\"\n\t},\n\t\"devDependencies\": {\n\t\t\"@eslint/compat\": \"^2.0.1\",\n\t\t\"@eslint/eslintrc\": \"^3.3.3\",\n\t\t\"@eslint/js\": \"^9.39.2\",\n\t\t\"@types/node\": \"^25.0.9\",\n\t\t\"eslint\": \"^9.39.2\",\n\t\t\"eslint-config-prettier\": \"^10.1.8\",\n\t\t\"eslint-import-resolver-typescript\": \"^4.4.4\",\n\t\t\"eslint-plugin-import\": \"^2.32.0\",\n\t\t\"eslint-plugin-prettier\": \"^5.5.5\",\n\t\t\"prettier\": \"^3.8.0\",\n\t\t\"sass\": \"1.97.2\",\n\t\t\"tar\": \"^7.5.6\",\n\t\t\"typescript-eslint\": \"^8.53.1\"\n\t},\n\t\"dependencies\": {\n\t\t\"@fontsource-variable/inter\": \"^5.2.8\",\n\t\t\"@fontsource/bona-nova\": \"^5.2.8\"\n\t}\n}\n"
  },
  {
    "path": "website/sass/base.scss",
    "content": "// ================================\n// GLOBAL PAGE STYLES AND VARIABLES\n// ================================\n\n:root {\n\t--color-fog: #eeeeee;\n\t--color-parchment: #faefe2;\n\t--color-cloud: #d9e1e4;\n\t--color-mustard: #e5c299;\n\t--color-navy: #16323f;\n\t--color-walnut: #473a3a;\n\t--color-slate: #3a4047;\n\t--color-crimson: #803847;\n\t--color-lilac: #e5e0eb;\n\t// --color-lime: #c5e0af;\n\t--color-lemon: #efe2b2;\n\t--color-peach: #ebb29f;\n\t--color-ale: #cd8f7a;\n\t--color-flamingo: #d2697c;\n\t--color-seaside: #b0d6cb;\n\t--color-seaside-rgb: 176, 214, 203;\n\t// --color-cove: #83c0b9;\n\t// --color-sage: #91b99a;\n\t--color-storm: #495875;\n\n\t--max-width: 1200px;\n\t--max-width-plus-padding: calc(var(--max-width) + 40px * 2);\n\t--max-extended-width: 1600px;\n\t--max-width-reading-material: 800px;\n\n\t--variable-px: Min(1px, 0.15vw);\n\t--page-edge-padding: 40px;\n\t--border-thickness: 2px;\n\t--feature-box-padding: 80;\n\t--font-size-link: calc(1rem * 4 / 3);\n}\n\nhtml,\nbody {\n\tcolor: var(--color-navy);\n\tbackground: white;\n\tfont-family: \"Inter Variable\", sans-serif;\n\tline-height: 1.5;\n\tfont-weight: 500;\n\tfont-size: 18px;\n\ttab-size: 4;\n\twidth: 100%;\n\theight: 100%;\n\tmargin: 0;\n}\n\n@media screen and (max-width: 780px) {\n\t:root {\n\t\t--font-size-link: calc(1rem * 4 / 3);\n\t\t--page-edge-padding: 28px;\n\t\t--border-thickness: 1px;\n\t\t--feature-box-padding: 40;\n\t}\n\n\thtml,\n\tbody {\n\t\tfont-size: 16px;\n\t}\n}\n\n@media print, screen and (max-width: 500px) {\n\t:root {\n\t\t--page-edge-padding: 20px;\n\t}\n}\n\n// ==================\n// GLOBAL PAGE LAYOUT\n// ==================\n\nbody > .page {\n\tbox-sizing: border-box;\n\tmin-width: 320px;\n\n\theader {\n\t\tpadding: 0 var(--page-edge-padding);\n\t\tcolor: var(--color-walnut);\n\t\tposition: relative;\n\t\tz-index: 1000;\n\n\t\tnav {\n\t\t\tmargin: auto;\n\t\t\tmax-width: var(--max-width);\n\n\t\t\t.row {\n\t\t\t\tdisplay: flex;\n\t\t\t\tjustify-content: space-between;\n\t\t\t\t--nav-padding-above-below: 30px;\n\t\t\t\tpadding-top: var(--nav-padding-above-below);\n\t\t\t\tpadding-bottom: calc(var(--nav-padding-above-below) - 16px);\n\t\t\t\tmargin-bottom: calc(var(--nav-padding-above-below) - 16px);\n\t\t\t\t// Covers up content that extends up underneath the header\n\t\t\t\tbackground: white;\n\n\t\t\t\t@media screen and (max-width: 780px) {\n\t\t\t\t\t--nav-padding-above-below: 24px;\n\t\t\t\t}\n\n\t\t\t\t.left,\n\t\t\t\t.right {\n\t\t\t\t\tz-index: 1;\n\t\t\t\t\tdisplay: flex;\n\t\t\t\t\talign-items: center;\n\t\t\t\t\tgap: 40px;\n\t\t\t\t\t--height: 60px;\n\t\t\t\t\t--button-padding: 24px;\n\t\t\t\t\t--nav-font-size: 28px; // Keep up to date with `NAV_BUTTON_INITIAL_FONT_SIZE` in navbar.js\n\n\t\t\t\t\ta {\n\t\t\t\t\t\tfont-family: \"Bona Nova\", Palatino, serif;\n\t\t\t\t\t\tfont-feature-settings: \"lnum\";\n\t\t\t\t\t\tline-height: 1.25;\n\t\t\t\t\t\tfont-weight: 700;\n\t\t\t\t\t\ttext-decoration: none;\n\t\t\t\t\t\tfont-size: var(--nav-font-size);\n\n\t\t\t\t\t\t&.button {\n\t\t\t\t\t\t\tmin-height: 0;\n\t\t\t\t\t\t\theight: var(--height);\n\t\t\t\t\t\t\tpadding-left: var(--button-padding);\n\t\t\t\t\t\t\tpadding-right: var(--button-padding);\n\t\t\t\t\t\t\tline-height: calc(var(--height) - 2 * var(--border-thickness));\n\t\t\t\t\t\t\tfont-size: var(--nav-font-size);\n\n\t\t\t\t\t\t\t&::before {\n\t\t\t\t\t\t\t\tcontent: none;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t&:not(.button) {\n\t\t\t\t\t\t\tcolor: inherit;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\timg {\n\t\t\t\t\t\t\tdisplay: block;\n\t\t\t\t\t\t\twidth: var(--height);\n\t\t\t\t\t\t\theight: var(--height);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t&.left img {\n\t\t\t\t\t\t// Don't show the alt text if the image doesn't load\n\t\t\t\t\t\tfont-size: 0;\n\t\t\t\t\t}\n\n\t\t\t\t\t.heart.heart {\n\t\t\t\t\t\t// The same color is also used below in the SVG after the `%23` (URL-encoded `#`)\n\t\t\t\t\t\tcolor: #cc304f;\n\t\t\t\t\t}\n\n\t\t\t\t\t@media screen and (max-width: 1200px) {\n\t\t\t\t\t\tgap: 30px;\n\t\t\t\t\t\t--height: 50px;\n\t\t\t\t\t\t--button-padding: 16px;\n\t\t\t\t\t\t--nav-font-size: 24px;\n\t\t\t\t\t}\n\n\t\t\t\t\t@media screen and (max-width: 1000px) {\n\t\t\t\t\t\tgap: 30px;\n\t\t\t\t\t\t--button-padding: 14px;\n\t\t\t\t\t\t--nav-font-size: 20px;\n\t\t\t\t\t}\n\n\t\t\t\t\t@media print, screen and (max-width: 900px) {\n\t\t\t\t\t\tgap: 20px;\n\t\t\t\t\t\t--height: 40px;\n\t\t\t\t\t\t--button-padding: 13px;\n\t\t\t\t\t\t--nav-font-size: 18px;\n\t\t\t\t\t}\n\n\t\t\t\t\t@media print, screen and (max-width: 780px) {\n\t\t\t\t\t\tgap: 20px;\n\t\t\t\t\t\t--button-padding: 12px;\n\t\t\t\t\t\t--nav-font-size: 16px;\n\t\t\t\t\t}\n\n\t\t\t\t\t@media screen and (max-width: 680px) {\n\t\t\t\t\t\tgap: 16px;\n\t\t\t\t\t\t--height: 30px;\n\t\t\t\t\t\t--button-padding: 8px;\n\t\t\t\t\t\t--nav-font-size: 14px;\n\t\t\t\t\t}\n\n\t\t\t\t\t@media screen and (max-width: 580px) {\n\t\t\t\t\t\tgap: 12px;\n\t\t\t\t\t\t--height: 24px;\n\t\t\t\t\t\t--nav-font-size: 13px;\n\t\t\t\t\t}\n\n\t\t\t\t\t@media screen and (max-width: 520px) {\n\t\t\t\t\t\tgap: 10px;\n\t\t\t\t\t\t--height: 22px;\n\t\t\t\t\t\t--button-padding: 6px;\n\t\t\t\t\t\t--nav-font-size: 12px;\n\t\t\t\t\t}\n\n\t\t\t\t\t@media screen and (max-width: 460px) {\n\t\t\t\t\t\tgap: 8px;\n\t\t\t\t\t\t--height: 20px;\n\t\t\t\t\t\t--button-padding: 4px;\n\t\t\t\t\t\t--nav-font-size: 11px;\n\t\t\t\t\t}\n\n\t\t\t\t\t@media screen and (max-width: 420px) {\n\t\t\t\t\t\tgap: 6px;\n\t\t\t\t\t\t--nav-font-size: 10px;\n\t\t\t\t\t}\n\n\t\t\t\t\t@media screen and (max-width: 380px) {\n\t\t\t\t\t\tgap: 6px;\n\t\t\t\t\t\t--nav-font-size: 9px;\n\t\t\t\t\t}\n\n\t\t\t\t\t@media screen and (max-width: 350px) {\n\t\t\t\t\t\tgap: 6px;\n\t\t\t\t\t\t--nav-font-size: 8px;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t.ripple {\n\t\t\tdisplay: block;\n\t\t\tbackground: none;\n\t\t\t// Covers up content that extends up underneath the header\n\t\t\tfill: white;\n\t\t\tstroke: currentColor;\n\t\t\t--ripple-height: 16px;\n\t\t\theight: var(--ripple-height);\n\t\t\tmargin-top: calc(-1 * var(--ripple-height) + var(--border-thickness));\n\t\t\tmargin-bottom: calc(-1 * var(--border-thickness));\n\t\t\tstroke-width: var(--border-thickness);\n\n\t\t\t&::before,\n\t\t\t&::after {\n\t\t\t\tcontent: none;\n\t\t\t}\n\t\t}\n\n\t\thr {\n\t\t\tbackground: none;\n\t\t}\n\n\t\t@media screen and (max-width: 1400px) {\n\t\t\t.ripple {\n\t\t\t\twidth: calc(100% + (var(--page-edge-padding) * 2));\n\t\t\t\tmargin-left: calc(-1 * var(--page-edge-padding));\n\t\t\t\tmargin-right: calc(-1 * var(--page-edge-padding));\n\t\t\t}\n\n\t\t\thr {\n\t\t\t\tdisplay: none;\n\t\t\t}\n\t\t}\n\t}\n\n\tmain {\n\t\tpadding: calc(120 * var(--variable-px)) var(--page-edge-padding);\n\n\t\t> section {\n\t\t\tmax-width: var(--max-width);\n\t\t\tmargin-left: auto;\n\t\t\tmargin-right: auto;\n\t\t\t// Puts the content in front of the hexagon decoration\n\t\t\tposition: relative;\n\t\t\tz-index: 1;\n\n\t\t\t~ section {\n\t\t\t\tmargin-top: calc(120 * var(--variable-px));\n\t\t\t}\n\n\t\t\tp img {\n\t\t\t\tmax-width: 100%;\n\t\t\t}\n\n\t\t\tpre {\n\t\t\t\tbox-sizing: border-box;\n\t\t\t\toverflow: auto;\n\t\t\t}\n\n\t\t\tdetails {\n\t\t\t\twidth: 100%;\n\n\t\t\t\tsummary {\n\t\t\t\t\tdisplay: inline-block;\n\t\t\t\t\tposition: relative;\n\t\t\t\t\tpadding-left: calc(10px + 8px);\n\t\t\t\t\tfont-weight: 800;\n\n\t\t\t\t\t&::before {\n\t\t\t\t\t\tcontent: \"\";\n\t\t\t\t\t\tbackground: url('data:image/svg+xml;utf8,\\\n\t\t\t\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 10 10\"><polygon fill=\"%2316323f\" points=\"4,0 1,0 6,5 1,10 4,10 9,5 4,0\" /></svg>\\\n\t\t\t\t\t\t\t');\n\t\t\t\t\t\tposition: absolute;\n\t\t\t\t\t\tmargin: auto;\n\t\t\t\t\t\ttop: 0;\n\t\t\t\t\t\tbottom: 0;\n\t\t\t\t\t\tleft: 0;\n\t\t\t\t\t\twidth: 10px;\n\t\t\t\t\t\theight: 10px;\n\t\t\t\t\t}\n\n\t\t\t\t\t+ * {\n\t\t\t\t\t\tmargin-top: 20px;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tdetails[open] summary::before {\n\t\t\t\ttransform: rotate(90deg);\n\t\t\t}\n\t\t}\n\t}\n\n\tfooter {\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tpadding: 40px;\n\t\tpadding-top: 0;\n\t\tcolor: var(--color-walnut);\n\n\t\t@media screen and (max-width: 1400px) {\n\t\t\thr {\n\t\t\t\twidth: 100%;\n\n\t\t\t\t&::before,\n\t\t\t\t&::after {\n\t\t\t\t\tborder: none;\n\t\t\t\t\tbackground: currentColor;\n\t\t\t\t\twidth: calc(var(--page-edge-padding) + 40px);\n\t\t\t\t\theight: var(--border-thickness);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tnav {\n\t\t\tdisplay: flex;\n\t\t\tflex-wrap: wrap;\n\t\t\tjustify-content: center;\n\t\t\tgap: 8px 40px;\n\t\t\tmargin-top: 40px;\n\n\t\t\ta {\n\t\t\t\tcolor: var(--color-walnut);\n\t\t\t}\n\n\t\t\t@media screen and (max-width: 900px) {\n\t\t\t\tmax-width: 500px;\n\t\t\t}\n\n\t\t\t@media screen and (max-width: 780px) {\n\t\t\t\tmax-width: 440px;\n\t\t\t}\n\n\t\t\t@media screen and (max-width: 400px) {\n\t\t\t\tgap: 6px 20px;\n\t\t\t}\n\t\t}\n\n\t\tspan {\n\t\t\ttext-align: center;\n\t\t\tmargin-top: 40px;\n\t\t}\n\t}\n}\n\n// =====================\n// ELEMENT SPACING RULES\n// =====================\n\n:is(h1, h2, h3, h4, article > :first-child, details > summary) ~ :is(p, ul, ol, ol li p, img, details, a:has(> img:only-child)),\n:is(h1, h2, h3, h4, article > :first-child) ~ :is(ul, ol) li p + img,\n:is(h1, h2, h3, h4, p) ~ .feature-icons,\np ~ :is(h1, h2, h3, h4, details summary, blockquote, .image-comparison, .video-background, .youtube-embed),\n.youtube-embed + :is(p, .link, .button),\np + p > .button,\np + :is(.link, section, details),\ntable td p ~ p,\nimg + .link,\narticle {\n\tmargin-top: 20px;\n}\n\n* {\n\tmin-width: 0;\n\tmin-height: 0;\n}\n\n// ==================================\n// HEADER AND TEXT ELEMENT TAG STYLES\n// ==================================\n\nh1 {\n\tfont-size: calc(1rem * 8 / 3);\n\tfont-family: \"Bona Nova\", Palatino, serif;\n\tfont-feature-settings: \"lnum\";\n\tline-height: 1.25;\n\tfont-weight: 700;\n\tdisplay: inline-block;\n\tmargin: 0;\n\n\t~ h2 {\n\t\tmargin-top: 40px;\n\t}\n\n\t~ hr {\n\t\tmargin-top: 40px;\n\t\tmargin-bottom: 20px;\n\t}\n}\n\nh2,\nh3,\nh4,\nh5,\nh6 {\n\tfont-family: \"Inter Variable\", sans-serif;\n\tline-height: 1.5;\n\tfont-weight: 800;\n\tdisplay: inline-block;\n\tmargin: 0;\n}\n\nh2 {\n\tfont-size: 1.75rem;\n\tfont-weight: 700;\n}\n\nh3 {\n\tfont-size: 1.25rem;\n}\n\nh4,\nh5,\nh6 {\n\tfont-size: 1rem;\n}\n\np {\n\tmargin: 0;\n\ttext-align: justify;\n\ttext-justify: inter-character; // Only supported in Firefox\n\t-webkit-hyphens: auto;\n\thyphens: auto;\n\n\tcode {\n\t\ttext-justify: auto;\n\t}\n}\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\np {\n\t~ img,\n\t~ iframe,\n\t~ a > img:only-child {\n\t\twidth: 100%;\n\t\theight: auto;\n\t}\n}\n\na {\n\tcolor: var(--color-crimson);\n}\n\n// ========================\n// OTHER ELEMENT TAG STYLES\n// ========================\n\nimg {\n\tvertical-align: top;\n}\n\ntable {\n\tmargin: 40px -10px;\n\twidth: calc(100% + 20px);\n\tfont-size: 16px;\n\tborder-collapse: collapse;\n\n\tth,\n\ttd {\n\t\tborder: solid var(--color-fog);\n\t\tborder-width: 1px 0;\n\t\tvertical-align: top;\n\t\tmargin: 0;\n\t\tpadding: 20px;\n\n\t\tp {\n\t\t\ttext-align: left;\n\t\t}\n\n\t\t&:first-child {\n\t\t\tpadding-left: 10px;\n\t\t}\n\n\t\t&:last-child {\n\t\t\tpadding-right: 10px;\n\t\t}\n\n\t\t&:not(:first-child) img {\n\t\t\tmax-width: 100%;\n\t\t}\n\t}\n\n\tthead:not(:has(tr th:not(:empty))) {\n\t\tth {\n\t\t\tpadding: 0;\n\t\t}\n\n\t\t+ tbody tr:first-child td {\n\t\t\tborder: none;\n\t\t}\n\t}\n\n\ttr th,\n\ttr:last-child td {\n\t\tborder: none;\n\t}\n\n\t:is(h1, h2, h3, h4, h5, h6) + & {\n\t\tmargin-top: 0;\n\t}\n}\n\nul,\nol {\n\tmargin: 0;\n\n\t+ p {\n\t\tmargin-top: 0;\n\t}\n\n\tli {\n\t\tmargin-top: 0.5em;\n\t}\n}\n\nhr {\n\t// Reset Firefox user agent style that is overriding `currentColor` that we set\n\tcolor: unset;\n}\n\ncode {\n\tcolor: black;\n\tbackground: var(--color-fog);\n\tpadding: 0 4px;\n\toverflow-wrap: anywhere;\n\t-webkit-hyphens: none;\n\thyphens: none;\n\n\ta & {\n\t\tcolor: var(--color-crimson);\n\t}\n}\n\nkbd {\n\toutline: calc(var(--border-thickness) / 2) solid var(--color-navy);\n\tpadding: 0 8px;\n\tmargin: 0 4px;\n\tcolor: inherit;\n\tfont-family: inherit;\n}\n\nsummary {\n\tcursor: pointer;\n}\n\nhr {\n\toverflow: visible;\n}\n\nhr,\n.ripple {\n\twidth: calc(100% - 32px * 2);\n\theight: var(--border-thickness);\n\tmargin: 0 32px;\n\tbackground: currentColor;\n\tposition: relative;\n\tborder: none;\n\n\t&::before {\n\t\tleft: -40px;\n\t\tborder-width: 0 0 var(--border-thickness) 40px;\n\t}\n\n\t&::after {\n\t\tright: -40px;\n\t\tborder-width: 0 40px var(--border-thickness) 0;\n\t}\n\n\t&::before,\n\t&::after {\n\t\tcontent: \"\";\n\t\tdisplay: block;\n\t\twidth: 0;\n\t\theight: 0;\n\t\tposition: absolute;\n\t\tborder-color: transparent transparent currentColor transparent;\n\t\tborder-style: solid;\n\t}\n}\n\n// ========================\n// COMMON SIMPLE COMPONENTS\n// ========================\n\n.block {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: flex-start;\n\twidth: 100%;\n\n\t&.centered {\n\t\talign-items: center;\n\t}\n\n\t:not(.diptych, .triptych) > .block + & {\n\t\tmargin-top: calc(120 * var(--variable-px));\n\t}\n}\n\n.link {\n\tdisplay: inline-block;\n\tfont-size: var(--font-size-link);\n\tfont-weight: 800;\n\ttext-decoration: none;\n\tcolor: var(--color-crimson);\n\twhite-space: nowrap;\n\n\t&:not(.not-uppercase) {\n\t\ttext-transform: uppercase;\n\t}\n}\n\n.button {\n\tdisplay: inline-block;\n\tborder: var(--border-thickness) solid currentColor;\n\tmin-height: calc(var(--font-size-link) * 2);\n\tfont-size: var(--font-size-link);\n\tpadding: 0 var(--font-size-link);\n\tbox-sizing: border-box;\n\ttext-align: left;\n\ttext-decoration: none;\n\tfont-weight: 800;\n\tcolor: var(--color-crimson);\n\n\t&::before {\n\t\tcontent: \"\";\n\t\tline-height: calc(var(--font-size-link) * 2 - 2 * var(--border-thickness));\n\t}\n\n\timg {\n\t\theight: calc(var(--font-size-link) * 1.5);\n\t\tmargin-right: calc(var(--font-size-link) / 2);\n\t}\n\n\timg,\n\tspan {\n\t\tvertical-align: middle;\n\t}\n}\n\n.arrow::after {\n\tcontent: \" »\";\n\tfont-family: \"Inter Variable\", sans-serif;\n}\n\n.video-background {\n\tposition: relative;\n\tfont-size: 0;\n\n\tvideo {\n\t\tmax-width: Min(100%, 1280px);\n\t}\n\n\t// Uses a white border over the video to cover up the edges of the video which, due to a Chrome rendering bug, displays black edges sometimes when scrolling\n\t&::after {\n\t\tcontent: \"\";\n\t\tposition: absolute;\n\t\tinset: 0;\n\t\tborder: 2px solid white;\n\t\tpointer-events: none;\n\t}\n}\n\n.heart::after {\n\tcontent: \"\";\n\tbackground-image: url('data:image/svg+xml;utf8,\\\n\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\"><path d=\"M8,15C5.12471,9.753694 0.5,8.795225 0.5,4.736524 C0.5,-0.507473 7.468734,0 8,4.967381 C8.531266,0 15.5,-0.507473 15.5,4.736524 C15.5,8.795225 10.87529,9.753694 8,15z\" fill=\"%23cc304f\" /></svg>\\\n\t\t');\n\tdisplay: inline-block;\n\twidth: 0.75em;\n\theight: 0.75em;\n\tmargin-left: 0.25em;\n\tmargin-bottom: -0.1em;\n\tvertical-align: baseline;\n}\n\n// blockquote {\n// \tpadding: 32px 80px;\n// \tbackground: rgba(0, 0, 0, 0.0625);\n// \tposition: relative;\n// \tborder-left: 4px solid var(--color-navy);\n\n// \t&::before,\n// \t&::after {\n// \t\tcontent: \"\";\n// \t\tposition: absolute;\n// \t\twidth: 52px;\n// \t\theight: 40px;\n// \t\tbackground-image: url('data:image/svg+xml;utf8,\\\n// \t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 52 40\"><path fill=\"rgba(22, 50, 63, 0.25)\" d=\"M51.8,2.4c0,.5-.2.9-.6,1.2s-1,.5-1.8.7c-2.2.5-4.3,1.3-6.4,2.5-2.1,1.2-3.7,2.8-5,4.8-1.3,2-1.9,4.4-1.9,7.4s.6,2.7,1.7,4,2.5,2.1,4,2.4c2,.3,3.6,1,4.8,2.2,1.2,1.2,1.8,2.7,1.8,4.5s-.9,3.9-2.7,5.3c-1.8,1.4-3.8,2.2-6,2.2-3.8,0-7-1.3-9.4-4-2.4-2.7-3.6-6.1-3.6-10.4s.7-7,2.2-10c1.4-3,3.3-5.6,5.5-7.8,2.3-2.2,4.7-3.9,7.2-5,2.5-1.2,4.9-1.7,7.1-1.7s3,.6,3,1.9ZM22.2.5c-2.2,0-4.6.6-7.1,1.7-2.5,1.2-4.9,2.8-7.2,5-2.3,2.2-4.2,4.8-5.6,7.8C.9,18.1.2,21.5.2,25.1s1.2,7.7,3.7,10.4c2.4,2.7,5.5,4,9.3,4s4.3-.7,6-2.2c1.7-1.4,2.6-3.2,2.6-5.3s-.6-3.3-1.8-4.5c-1.2-1.2-2.8-1.9-4.7-2.2-1.5-.3-2.9-1.1-4-2.4s-1.7-2.6-1.7-4c0-3,.6-5.4,1.9-7.4,1.2-2,2.9-3.6,5-4.8,2.1-1.2,4.2-2,6.4-2.5.8-.2,1.4-.4,1.8-.7.4-.3.6-.7.6-1.2,0-1.2-1-1.9-3-1.9Z\" /></svg>\\\n// \t\t\t');\n// \t}\n\n// \t&::before {\n// \t\ttop: 16px;\n// \t\tleft: 16px;\n// \t}\n\n// \t&::after {\n// \t\ttransform: rotate(180deg);\n// \t\tbottom: 16px;\n// \t\tright: 16px;\n// \t}\n// }\n"
  },
  {
    "path": "website/sass/component/carousel.scss",
    "content": ".carousel.carousel {\n\tmargin-top: calc(80 * var(--variable-px));\n\ttransform: translate(0);\n\n\t.carousel-slide {\n\t\tdisplay: flex;\n\t\twhite-space: nowrap;\n\t\ttouch-action: pan-y pinch-zoom;\n\t\tcursor: grab;\n\n\t\t:is(img, video) {\n\t\t\tposition: relative;\n\t\t\tdisplay: inline-block;\n\t\t\tuser-select: none;\n\t\t\tflex: 0 0 auto;\n\t\t\theight: auto;\n\t\t\tpadding: 0 20px;\n\n\t\t\t&:first-child,\n\t\t\t&:last-child {\n\t\t\t\t--fade-factor: Min(calc(var(--over-slide-factor, 0) / 1.5 + 0.5), 1);\n\t\t\t\t// Fade to white (combining invert and brightness, see <https://stackoverflow.com/a/78478074/775283>) and desaturate\n\t\t\t\tfilter: Invert(calc(var(--fade-factor) / 2)) Brightness(calc(1 + var(--fade-factor))) Grayscale(var(--fade-factor));\n\t\t\t}\n\n\t\t\t&:first-child {\n\t\t\t\tmargin-left: -20px;\n\t\t\t}\n\n\t\t\t&:last-child {\n\t\t\t\tmargin-right: -20px;\n\t\t\t}\n\t\t}\n\t}\n\n\t&:not(.dragging, .jostling) .carousel-slide :is(img, video) {\n\t\ttransition: transform 500ms;\n\t}\n\n\t.carousel-slide:not(.torn) {\n\t\toverflow: hidden;\n\t}\n\n\t.carousel-slide.torn {\n\t\tposition: fixed;\n\t\ttop: 0;\n\t\tz-index: -1;\n\t\t// Torn edge mask\n\t\t-webkit-mask-repeat: no-repeat;\n\t\tmask-repeat: no-repeat;\n\t\t-webkit-mask-size: contain;\n\t\tmask-size: contain;\n\n\t\t&.left {\n\t\t\tpadding-left: 160px;\n\t\t\tmargin-left: -160px;\n\t\t\t-webkit-mask-image: url(\"https://static.graphite.art/textures/torn-edge-left__2.png\");\n\t\t\tmask-image: url(\"https://static.graphite.art/textures/torn-edge-left__2.png\");\n\t\t\t-webkit-mask-position: top left;\n\t\t\tmask-position: top left;\n\t\t}\n\n\t\t&.right {\n\t\t\tpadding-right: 160px;\n\t\t\tmargin-right: -160px;\n\t\t\t-webkit-mask-image: url(\"https://static.graphite.art/textures/torn-edge-right__2.png\");\n\t\t\tmask-image: url(\"https://static.graphite.art/textures/torn-edge-right__2.png\");\n\t\t\t-webkit-mask-position: top right;\n\t\t\tmask-position: top right;\n\t\t}\n\t}\n\n\t.screenshot-details {\n\t\tdisplay: flex;\n\t\tmargin: 20px 0;\n\t\tgap: 20px 40px;\n\n\t\t@media screen and (max-width: 800px) {\n\t\t\tflex-wrap: wrap;\n\t\t\tjustify-content: center;\n\t\t}\n\n\t\t.carousel-controls {\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tflex: 0 0 auto;\n\n\t\t\tbutton {\n\t\t\t\toutline: none;\n\t\t\t\tbackground: none;\n\t\t\t\tborder: none;\n\t\t\t\tpadding: 0;\n\t\t\t\tcolor: inherit;\n\t\t\t\tcursor: pointer;\n\n\t\t\t\tsvg {\n\t\t\t\t\tdisplay: block;\n\t\t\t\t}\n\n\t\t\t\t+ button {\n\t\t\t\t\tmargin-left: 20px;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.direction {\n\t\t\t\tfill: currentColor;\n\t\t\t}\n\n\t\t\t.dot {\n\t\t\t\twidth: 16px;\n\t\t\t\theight: 16px;\n\t\t\t\tbox-sizing: border-box;\n\t\t\t\tborder-radius: 50%;\n\t\t\t\tborder: 2px solid currentColor;\n\n\t\t\t\t&.active {\n\t\t\t\t\tborder: none;\n\t\t\t\t\tbackground: var(--color-crimson);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t.screenshot-description {\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tmin-height: calc(2em * 1.5);\n\n\t\t\tp + p {\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\tp:not(.active) {\n\t\t\t\tdisplay: none;\n\t\t\t}\n\t\t}\n\t}\n\n\t&.center {\n\t\t.screenshot-details {\n\t\t\tjustify-content: center;\n\t\t}\n\t}\n\n\t&.window-size-1 .carousel-slide :is(img, video) {\n\t\twidth: 100%;\n\t}\n\n\t&.window-size-2 .carousel-slide :is(img, video) {\n\t\twidth: calc((100% / 2) - 10px);\n\t\tpadding: 0 10px;\n\n\t\t&:first-child {\n\t\t\tmargin-left: -10px;\n\t\t}\n\n\t\t&:last-child {\n\t\t\tmargin-right: -10px;\n\t\t}\n\t}\n\n\t&.window-size-3 .carousel-slide :is(img, video) {\n\t\twidth: calc((100% / 3) - 10px * (4 / 3));\n\t\tpadding: 0 10px;\n\n\t\t&:first-child {\n\t\t\tmargin-left: -10px;\n\t\t}\n\n\t\t&:last-child {\n\t\t\tmargin-right: -10px;\n\t\t}\n\t}\n\n\t@media screen and (max-width: 1000px) {\n\t\tmargin-left: calc(-1 * var(--page-edge-padding));\n\t\tmargin-right: calc(-1 * var(--page-edge-padding));\n\n\t\t.screenshot-details {\n\t\t\tmargin-left: var(--page-edge-padding);\n\t\t\tmargin-right: var(--page-edge-padding);\n\t\t}\n\t}\n\n\t@media screen and (max-width: /* The value of --max-width-plus-padding: */ 1280px) {\n\t\t.carousel-slide.torn {\n\t\t\tdisplay: none;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "website/sass/component/code-snippet.scss",
    "content": "pre {\n\tdisplay: flex;\n\tmax-width: 100%;\n\tcolor: var(--color-fog);\n\t// This zero transform sets this element as the root for `position: fixed`\n\ttransform: translate(0);\n\n\t// Color overrides\n\t&,\n\t&.z-code {\n\t\tbackground: var(--color-navy);\n\n\t\t.z-path {\n\t\t\tcolor: #679f70;\n\n\t\t\tspan {\n\t\t\t\tcolor: #e6e1dc;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Container for the element (span or table) containing the lines of code\n\tcode {\n\t\tbackground: inherit;\n\t\tcolor: inherit;\n\t\tdisplay: block;\n\t\toverflow-x: auto;\n\t\tpadding: 10px 20px;\n\t\tflex: 1 1 auto;\n\t\twidth: 0;\n\n\t\t.giallo-l {\n\t\t\tdisplay: inline-block;\n\t\t\tmin-height: 1lh;\n\t\t\twidth: 100%;\n\t\t}\n\n\t\t.giallo-ln {\n\t\t\tdisplay: inline-block;\n\t\t\tuser-select: none;\n\t\t\ttext-align: right;\n\t\t\tmargin-left: -10px;\n\t\t\tpadding-right: 16px;\n\t\t\tmin-width: 3ch;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "website/sass/component/demo-artwork.scss",
    "content": ".demo-artwork {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tmargin-top: 20px;\n\n\t> a {\n\t\tflex: 0 0 auto;\n\n\t\timg {\n\t\t\theight: 128px;\n\t\t\tborder: 12px solid var(--color-walnut);\n\t\t\tvertical-align: top;\n\t\t\tflex: 0 0 auto;\n\t\t}\n\t}\n\n\tp {\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tmax-width: 300px;\n\t\tmargin-left: 40px;\n\t\ttext-align: left;\n\t}\n}\n"
  },
  {
    "path": "website/sass/component/feature-box.scss",
    "content": ":not(.diptych, .triptych) > :is(.block, .diptych, .triptych) + :is(.block, .diptych, .triptych) {\n\tmargin-top: calc(120 * var(--variable-px));\n}\n\n.feature-box-narrow,\n.feature-box-outer {\n\tpadding: calc(var(--feature-box-padding) * var(--variable-px));\n\tbackground-image: url(\"https://static.graphite.art/textures/noise.png\");\n\tbackground-blend-mode: overlay;\n\tbackground-position: center;\n}\n\n:where(h1, h2, h3, h4, p) + .feature-box-narrow {\n\tmargin-top: calc(40 * var(--variable-px));\n}\n\n.feature-box-full-image {\n\twidth: calc(100% + 2 * var(--feature-box-padding) * var(--variable-px));\n\theight: auto;\n\tmargin-left: calc(-1 * var(--feature-box-padding) * var(--variable-px));\n\tmargin-top: calc(-1 * var(--feature-box-padding) * var(--variable-px));\n\tmargin-bottom: calc(var(--feature-box-padding) / 2 * var(--variable-px));\n\tdisplay: block;\n}\n\n.feature-box-outer {\n\t@media screen and (max-width: 1000px) {\n\t\t&.feature-box-outer {\n\t\t\tmargin-left: calc(-1 * var(--page-edge-padding));\n\t\t\tmargin-right: calc(-1 * var(--page-edge-padding));\n\t\t\tpadding-left: var(--page-edge-padding);\n\t\t\tpadding-right: var(--page-edge-padding);\n\t\t}\n\t}\n\n\t&.feature-box-outer {\n\t\tmax-width: unset;\n\t}\n\n\t.feature-box-inner {\n\t\tmax-width: var(--max-width);\n\t\tmargin: 0 auto;\n\t}\n}\n\nh1.feature-box-header.feature-box-header {\n\t&,\n\t& a {\n\t\tfont-family: \"Inter Variable\", sans-serif;\n\t\tline-height: 1.5;\n\t\tfont-weight: 800;\n\t\ttext-transform: uppercase;\n\t\tfont-size: calc(1rem * 14 / 9);\n\t}\n\n\tspan {\n\t\twhite-space: pre;\n\t}\n\n\t~ hr {\n\t\tmargin-top: 20px;\n\t\tmargin-bottom: 40px;\n\n\t\t+ p {\n\t\t\tmargin-top: 0;\n\t\t}\n\t}\n}\n\n.diptych,\n.triptych {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: calc(var(--feature-box-padding) * var(--variable-px));\n\n\t.block {\n\t\tflex: 1 1 0;\n\t}\n\n\timg[alt=\"\"] {\n\t\tdisplay: block;\n\n\t\t&::after {\n\t\t\tcontent: \"\";\n\t\t\tdisplay: block;\n\t\t\twidth: 100%;\n\t\t\theight: 240px;\n\t\t\tbackground: var(--color-crimson);\n\t\t}\n\t}\n}\n\n.diptych .block {\n\tmin-width: 320px;\n}\n\n.triptych .block {\n\tmin-width: 280px;\n}\n\n@media screen and (max-width: 520px) {\n\t.diptych .block {\n\t\tmin-width: 200px;\n\t}\n\n\t.triptych .block {\n\t\tmin-width: 280px;\n\t}\n}\n"
  },
  {
    "path": "website/sass/component/feature-icons.scss",
    "content": ".feature-icons {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tmargin-bottom: 20px;\n\twidth: 100%;\n\tgap: 16px;\n\n\t.atlas {\n\t\tobject-fit: cover;\n\t\tobject-position: calc(-48px * var(--atlas-index)) 0;\n\t\twidth: 48px;\n\t\theight: 48px;\n\t}\n\n\t.feature-icon {\n\t\tdisplay: flex;\n\t\talign-items: center;\n\n\t\timg {\n\t\t\tflex: 0 0 auto;\n\t\t}\n\n\t\tp + &.feature-icon {\n\t\t\tmargin-top: calc(40 * var(--variable-px));\n\t\t}\n\t}\n\n\t&:not(.stacked) .feature-icon {\n\t\tpadding: 16px;\n\t\tgap: 16px;\n\t\t// Half width, minus own padding on both sides, minus half a gap\n\t\tflex: 1 0 calc(50% - (16px * 2) - (16px / 2));\n\n\t\t@media screen and (max-width: 1100px) {\n\t\t\t// Quarter width, minus own padding on both sides\n\t\t\tflex: 1 0 calc(100% - (16px * 2));\n\t\t}\n\t}\n\n\t&:not(.no-background) .feature-icon {\n\t\tbackground: rgba(0, 0, 0, 0.0625);\n\t}\n\n\t&.four-wide .feature-icon {\n\t\tflex: 1 0 calc(25% - (16px * 4) - (16px / 4));\n\n\t\t@media screen and (max-width: 1200px) {\n\t\t\t// Half width, minus own padding on both sides, minus half a gap\n\t\t\tflex: 1 0 calc(50% - (16px * 2) - (16px / 2));\n\t\t}\n\n\t\t@media screen and (max-width: 840px) {\n\t\t\t// Quarter width, minus own padding on both sides\n\t\t\tflex: 1 0 calc(100% - (16px * 2));\n\t\t}\n\t}\n\n\t&.three-wide .feature-icon {\n\t\tflex: 1 0 calc((100% / 3) - (16px * 4) - (16px / 4));\n\n\t\t@media screen and (max-width: 800px) {\n\t\t\t// Quarter width, minus own padding on both sides\n\t\t\tflex: 1 0 calc(100% - (16px * 2));\n\t\t}\n\t}\n\n\t&.stacked {\n\t\tjustify-content: space-between;\n\t\tmargin: 0 -10px;\n\t\twidth: calc(100% + 20px);\n\t\tgap: 0;\n\n\t\t.feature-icon {\n\t\t\tflex-direction: column;\n\t\t\tflex: 0 1 auto;\n\t\t\tmargin: 0 10px;\n\n\t\t\t@media screen and (max-width: 1100px) {\n\t\t\t\twidth: calc(100% / 3 - 40px);\n\t\t\t}\n\n\t\t\t@media screen and (max-width: 400px) {\n\t\t\t\twidth: calc(100% / 2 - 20px);\n\t\t\t}\n\n\t\t\timg {\n\t\t\t\twidth: 72px;\n\t\t\t\theight: 72px;\n\t\t\t\tobject-position: calc(-72px * var(--atlas-index)) 0;\n\t\t\t\tmargin-bottom: 8px;\n\t\t\t}\n\n\t\t\tspan {\n\t\t\t\ttext-align: center;\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "website/sass/component/image-comparison.scss",
    "content": ".image-comparison {\n\tposition: relative;\n\ttouch-action: pan-y pinch-zoom;\n\tmax-width: Min(100%, 512px);\n\n\t.crop-container {\n\t\theight: 100%;\n\n\t\t&:nth-child(2) {\n\t\t\toverflow: hidden;\n\t\t\twidth: calc(100% - var(--comparison-percent));\n\n\t\t\t&,\n\t\t\timg {\n\t\t\t\tposition: absolute;\n\t\t\t\ttop: 0;\n\t\t\t\tright: 0;\n\t\t\t}\n\t\t}\n\n\t\t&.crop-container.crop-container {\n\t\t\timg {\n\t\t\t\tdisplay: block;\n\t\t\t\twidth: auto;\n\t\t\t\theight: 100%;\n\t\t\t}\n\n\t\t\t&:first-child img {\n\t\t\t\twidth: 100%;\n\t\t\t}\n\t\t}\n\t}\n\n\t.slide-bar {\n\t\tposition: absolute;\n\t\tbackground: var(--color-navy);\n\t\tmargin-left: -2px;\n\t\twidth: 4px;\n\t\theight: 100%;\n\t\ttop: 0;\n\t\tleft: var(--comparison-percent);\n\t\tbox-shadow: 0 0 2px rgba(255, 255, 255, 0.5);\n\n\t\t.arrows {\n\t\t\tposition: absolute;\n\t\t\ttop: calc(50% - (40px / 2));\n\t\t\tleft: calc(4px / 2);\n\t\t\twidth: 0;\n\t\t\theight: 0;\n\t\t\topacity: 1;\n\t\t\ttransition: opacity 0.25s;\n\n\t\t\tsvg {\n\t\t\t\tposition: absolute;\n\t\t\t\twidth: 6.5px;\n\t\t\t\theight: 11px;\n\t\t\t\ttop: calc(-11px / 2);\n\t\t\t\tfill: white;\n\n\t\t\t\t@keyframes pulse-left {\n\t\t\t\t\tfrom { transform: translateX(3px); }\n\t\t\t\t\tto { transform: translateX(-3px); }\n\t\t\t\t}\n\n\t\t\t\t@keyframes pulse-right {\n\t\t\t\t\tfrom { transform: scaleX(-1) translateX(3px); }\n\t\t\t\t\tto { transform: scaleX(-1) translateX(-3px); }\n\t\t\t\t}\n\n\t\t\t\t@keyframes pulse-opacity {\n\t\t\t\t\t0% { opacity: 0; }\n\t\t\t\t\t40% { opacity: 1; }\n\t\t\t\t\t90% { opacity: 1; }\n\t\t\t\t\t100% { opacity: 0; }\n\t\t\t\t}\n\n\t\t\t\t&:nth-of-type(1) {\n\t\t\t\t\tright: 6px;\n\t\t\t\t\tanimation: 3s infinite ease-out pulse-left, 3s infinite ease-out pulse-opacity;\n\t\t\t\t}\n\n\t\t\t\t&:nth-of-type(2) {\n\t\t\t\t\tleft: 6px;\n\t\t\t\t\tanimation: 3s infinite ease-out pulse-right, 3s infinite ease-out pulse-opacity;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tdiv {\n\t\t\t\tcontent: \"\";\n\t\t\t\tposition: absolute;\n\t\t\t\tbackground: var(--color-navy);\n\t\t\t\ttop: 0;\n\t\t\t\tleft: 0;\n\t\t\t\twidth: 32px;\n\t\t\t\theight: 32px;\n\t\t\t\ttransform: translate(-50%, -50%) rotate(45deg);\n\t\t\t\tbox-shadow: 0 0 2px rgba(255, 255, 255, 0.5);\n\t\t\t}\n\n\t\t\t// Cover up the box-shadow at the top and bottom of the circle so it connects to the vertical line\n\t\t\t&::after {\n\t\t\t\tcontent: \"\";\n\t\t\t\tposition: absolute;\n\t\t\t\tbackground: var(--color-navy);\n\t\t\t\tleft: -2px;\n\t\t\t\ttop: -24px;\n\t\t\t\twidth: 4px;\n\t\t\t\theight: 48px;\n\t\t\t}\n\t\t}\n\t}\n\n\t&:hover .slide-bar .arrows {\n\t\topacity: 0;\n\t}\n}\n"
  },
  {
    "path": "website/sass/component/youtube-embed.scss",
    "content": ".youtube-embed {\n\tposition: relative;\n\twidth: 100%;\n\n\t&.aspect-16x9 {\n\t\tpadding-top: calc(100% / (16 / 9));\n\t}\n\n\timg,\n\tiframe {\n\t\tposition: absolute;\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\ttop: 0;\n\t\tleft: 0;\n\t}\n\n\timg {\n\t\tcursor: pointer;\n\t}\n}\n"
  },
  {
    "path": "website/sass/layout/reading-material.scss",
    "content": ".reading-material.reading-material {\n\tfont-size: 16px;\n\tline-height: 1.625;\n\tmax-width: var(--max-width-reading-material);\n\n\tarticle {\n\t\twidth: 100%;\n\n\t\th1,\n\t\th2,\n\t\th3,\n\t\th4,\n\t\th5,\n\t\th6 {\n\t\t\tdisplay: block;\n\t\t\tmargin-top: 80px;\n\n\t\t\t&:first-child {\n\t\t\t\tmargin-top: 0;\n\t\t\t}\n\t\t}\n\n\t\t// Captions below images in blog posts\n\t\tp:has(> img) + center:has(> em) {\n\t\t\tmargin-top: 10px;\n\t\t}\n\n\t\t@media (min-width: 1200px) {\n\t\t\t.wide {\n\t\t\t\tmargin-left: -140px;\n\t\t\t\tmargin-right: -140px;\n\t\t\t}\n\t\t}\n\n\t\tp ~ img {\n\t\t\twidth: auto;\n\t\t\tmax-width: 100%;\n\t\t}\n\n\t\thr {\n\t\t\tmargin-top: 40px;\n\t\t\tmargin-bottom: 40px;\n\t\t}\n\n\t\t+ hr {\n\t\t\tmargin-top: calc(80 * var(--variable-px));\n\t\t\tmargin-bottom: calc(40 * var(--variable-px));\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "website/sass/page/about.scss",
    "content": ".loading-data:empty::after {\n\tcontent: \"(loading...)\";\n\tfont-style: italic;\n\topacity: 0;\n\tanimation: fadeInAfterWait 2s ease-in 2s forwards;\n\n\t@keyframes fadeInAfterWait {\n\t\t0% {\n\t\t\topacity: 0;\n\t\t}\n\t\t100% {\n\t\t\topacity: 1;\n\t\t}\n\t}\n}\n\n#core-team {\n\tbackground-color: var(--color-parchment);\n\n\t.block {\n\t\talign-items: center;\n\n\t\timg {\n\t\t\tmax-width: Min(100%, 320px);\n\t\t}\n\n\t\th2 {\n\t\t\tfont-weight: 700;\n\t\t\ttext-align: center;\n\t\t\twidth: 100%;\n\n\t\t\t.handle {\n\t\t\t\tmargin: 0 0.4em;\n\t\t\t\tfont-size: 0.5em;\n\t\t\t\tfont-style: italic;\n\t\t\t\tvertical-align: middle;\n\t\t\t\tcolor: var(--color-slate);\n\t\t\t}\n\n\t\t\t.flag {\n\t\t\t\twidth: 1em;\n\t\t\t\theight: 1em;\n\t\t\t\tvertical-align: middle;\n\t\t\t}\n\t\t}\n\t}\n}\n\n#extras .button {\n\tmargin-top: 0;\n}\n"
  },
  {
    "path": "website/sass/page/blog.scss",
    "content": "#intro .block {\n\tflex: 1 1 100%;\n\twidth: 100%;\n\n\t.left-right-split {\n\t\twidth: 100%;\n\t\tmargin-top: 20px;\n\t\tdisplay: flex;\n\t\tgap: 20px;\n\t\tjustify-content: space-between;\n\t\tflex-wrap: wrap;\n\t}\n\n\t.feed {\n\t\tmargin-top: -4px;\n\n\t\ta {\n\t\t\ttext-decoration: none;\n\t\t\tdisplay: flex;\n\t\t\tgap: 8px;\n\n\t\t\t.icon {\n\t\t\t\tvertical-align: top;\n\t\t\t\twidth: calc(var(--font-size-link) * 1.5);\n\t\t\t\theight: calc(var(--font-size-link) * 1.5);\n\t\t\t}\n\n\t\t\t.link {\n\t\t\t\tvertical-align: top;\n\t\t\t\tmargin-top: 0;\n\t\t\t}\n\t\t}\n\t}\n}\n\n#articles {\n\tdisplay: flex;\n\tflex-direction: column;\n\n\tsection {\n\t\tdisplay: flex;\n\t\tgap: 20px 80px;\n\t\twidth: 100%;\n\n\t\t@media screen and (max-width: 960px) {\n\t\t\tflex-wrap: wrap;\n\t\t\tjustify-content: center;\n\t\t\tflex-direction: column;\n\n\t\t\t.banner.banner {\n\t\t\t\twidth: 100%;\n\t\t\t\tbackground: var(--color-fog);\n\n\t\t\t\timg {\n\t\t\t\t\tmargin: auto;\n\t\t\t\t\tmax-width: 480px;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t~ section {\n\t\t\tmargin-top: calc(120 * var(--variable-px));\n\t\t}\n\n\t\t.banner {\n\t\t\twidth: 400px;\n\t\t\tflex: 0 0 auto;\n\n\t\t\timg {\n\t\t\t\tdisplay: block;\n\t\t\t\twidth: 100%;\n\t\t\t\theight: auto;\n\t\t\t}\n\t\t}\n\n\t\t.details {\n\t\t\tdisplay: flex;\n\t\t\tflex-direction: column;\n\t\t\tgap: 20px;\n\t\t\tflex: 1 1 auto;\n\n\t\t\t.headline {\n\t\t\t\tmargin-top: -0.5em;\n\n\t\t\t\ta {\n\t\t\t\t\tfont-weight: 700;\n\t\t\t\t\ttext-decoration: none;\n\t\t\t\t\tcolor: var(--color-navy);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.publication {\n\t\t\t\tfont-weight: 800;\n\t\t\t}\n\n\t\t\t.summary {\n\t\t\t\tflex-direction: column;\n\t\t\t\tgap: 20px;\n\t\t\t\ttext-align: justify;\n\t\t\t\ttext-justify: inter-character; // Only supported in Firefox\n\t\t\t\t-webkit-hyphens: auto;\n\t\t\t\thyphens: auto;\n\t\t\t\toverflow: hidden;\n\t\t\t\ttext-overflow: ellipsis;\n\t\t\t\tdisplay: -webkit-box;\n\t\t\t\t-webkit-box-orient: vertical;\n\t\t\t\t-webkit-line-clamp: 3;\n\n\t\t\t\tcode {\n\t\t\t\t\ttext-justify: auto;\n\t\t\t\t}\n\n\t\t\t\t// Safari workaround: https://stackoverflow.com/a/72170897/775283\n\t\t\t\t// But there remains an issue where the ellipsis is rendered inside the text. There doesn't appear to be a fix, or even a way to disable the ellipsis in Safari.\n\t\t\t\tp {\n\t\t\t\t\tdisplay: inline;\n\n\t\t\t\t\t&::after {\n\t\t\t\t\t\tcontent: \"\\A\\A\"; // Double new line\n\t\t\t\t\t\twhite-space: pre;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "website/sass/page/contributor-guide/bisect-tool.scss",
    "content": ".bisect-tool {\n\tmargin-top: 20px;\n\n\t.phase:not(.active) {\n\t\tdisplay: none;\n\t}\n\n\t.setup-section {\n\t\tmargin-bottom: 20px;\n\n\t\tlabel {\n\t\t\tdisplay: block;\n\t\t\tcursor: pointer;\n\t\t}\n\t}\n\n\t.commit-inputs {\n\t\tdisplay: flex;\n\t\tgap: 20px;\n\t\tflex-wrap: wrap;\n\t\talign-items: center;\n\n\t\t.start-input {\n\t\t\tmargin: 0;\n\n\t\t\tinput {\n\t\t\t\tbackground: none;\n\t\t\t\theight: calc(var(--font-size-link) * 2);\n\t\t\t\tfont-size: calc(var(--font-size-link) * 0.9);\n\t\t\t\tpadding: 0 var(--font-size-link);\n\t\t\t\tmargin: 0;\n\t\t\t\toutline: none;\n\t\t\t\tcolor: inherit;\n\t\t\t\tborder: var(--border-thickness) solid currentColor;\n\t\t\t\tborder-radius: 0;\n\t\t\t\tfont-family: inherit;\n\t\t\t\tfont-weight: inherit;\n\t\t\t\tbox-sizing: border-box;\n\n\t\t\t\t&:focus {\n\t\t\t\t\tborder-color: var(--color-ale);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tinput:not([type=\"date\"]) {\n\t\t\t\tfont-family: monospace;\n\t\t\t\twidth: 320px;\n\t\t\t\tmax-width: 100%;\n\t\t\t}\n\n\t\t\t&.hidden {\n\t\t\t\tdisplay: none;\n\t\t\t}\n\t\t}\n\t}\n\n\t.button,\n\t.link {\n\t\tcursor: pointer;\n\t\tuser-select: none;\n\n\t\t+ .button {\n\t\t\tmargin-left: 10px;\n\t\t}\n\n\t\t&.disabled {\n\t\t\topacity: 0.4;\n\t\t\tpointer-events: none;\n\t\t}\n\t}\n\n\t.feature-box-narrow {\n\t\tpadding: calc(var(--feature-box-padding) / 2 * var(--variable-px));\n\t\tbackground: var(--color-fog);\n\t\tborder-radius: 2px;\n\t\tbox-sizing: border-box;\n\n\t\t.step-label {\n\t\t\tfont-size: 1rem;\n\t\t}\n\n\t\t.go-back {\n\t\t\tmargin-left: 4px;\n\n\t\t\t&.hidden {\n\t\t\t\tdisplay: none;\n\t\t\t}\n\n\t\t\ta {\n\t\t\t\ttext-decoration: underline;\n\t\t\t\tcursor: pointer;\n\t\t\t}\n\t\t}\n\n\t\t.progress-info {\n\t\t\tcolor: var(--color-storm);\n\t\t}\n\n\t\t.commit-info {\n\t\t\tmargin-top: 40px;\n\n\t\t\ta {\n\t\t\t\tfont-family: monospace;\n\t\t\t\tcolor: var(--color-crimson);\n\t\t\t}\n\t\t}\n\n\t\t.button.arrow {\n\t\t\tmargin-top: 20px;\n\t\t}\n\n\t\t.findings {\n\t\t\tmargin-top: 40px;\n\t\t}\n\n\t\t.bisect-actions {\n\t\t\tdisplay: flex;\n\t\t\tgap: 8px;\n\t\t\tflex-wrap: wrap;\n\t\t\talign-items: center;\n\t\t\tmargin-top: 20px;\n\n\t\t\t.button:empty {\n\t\t\t\tvisibility: hidden;\n\t\t\t}\n\t\t}\n\t}\n\n\t.error-message {\n\t\tdisplay: none;\n\t\tmargin-top: 20px;\n\t\tpadding: 10px 20px;\n\t\tbackground: var(--color-lemon);\n\n\t\t&.visible {\n\t\t\tdisplay: block;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "website/sass/page/contributor-guide/crate-hierarchy.scss",
    "content": ".crate-hierarchy {\n\tposition: relative;\n\tmargin-top: 20px;\n\n\t.crate-hierarchy-controls {\n\t\tposition: absolute;\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tgap: 4px;\n\t\ttop: 8px;\n\t\tright: 8px;\n\t\tz-index: 1;\n\n\t\tbutton {\n\t\t\tposition: relative;\n\t\t\tborder: none;\n\t\t\tborder-radius: 2px;\n\t\t\twidth: 32px;\n\t\t\theight: 32px;\n\t\t\tbackground: var(--color-navy);\n\t\t\tuser-select: none;\n\n\t\t\t&:hover:not(:disabled) {\n\t\t\t\topacity: 0.5;\n\t\t\t}\n\n\t\t\t&:disabled {\n\t\t\t\topacity: 0.25;\n\t\t\t}\n\n\t\t\t// + and - icon geometry\n\t\t\t&::before,\n\t\t\t&.zoom-in::after {\n\t\t\t\tcontent: \"\";\n\t\t\t\tbackground: white;\n\t\t\t\tposition: absolute;\n\t\t\t\ttransform: translate(-50%, -50%);\n\t\t\t\ttop: 50%;\n\t\t\t\tleft: 50%;\n\t\t\t}\n\n\t\t\t&::before {\n\t\t\t\twidth: 14px;\n\t\t\t\theight: 2px;\n\t\t\t}\n\n\t\t\t&.zoom-in::after {\n\t\t\t\twidth: 2px;\n\t\t\t\theight: 14px;\n\t\t\t}\n\t\t}\n\t}\n\n\t.crate-hierarchy-viewport {\n\t\ttouch-action: none;\n\t\toverflow: hidden;\n\t\tcursor: grab;\n\n\t\tsvg {\n\t\t\tdisplay: block;\n\t\t\ttransform-origin: 0 0;\n\t\t\twidth: 100%;\n\t\t\theight: auto;\n\t\t\tpointer-events: none;\n\t\t\tuser-select: none;\n\n\t\t\ttext {\n\t\t\t\tfont-family: inherit;\n\t\t\t\tfont-size: 0.7em;\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "website/sass/page/contributor-guide/editor-structure.scss",
    "content": ".structure-outline {\n\tfont-family: monospace;\n\tline-height: 1.5;\n\tmargin-top: 20px;\n\n\tul {\n\t\tlist-style-type: none;\n\t\tpadding-left: 20px;\n\n\t\tli {\n\t\t\tmargin-top: 0;\n\n\t\t\tspan {\n\t\t\t\tline-height: 1.5;\n\t\t\t\tdisplay: inline-block;\n\t\t\t}\n\t\t}\n\t}\n\n\t.tree-node {\n\t\tpadding-left: calc(10px + 8px);\n\t\tposition: relative;\n\t\tuser-select: none;\n\t\tcursor: pointer;\n\n\t\t&::before {\n\t\t\tcontent: \"\";\n\t\t\tbackground: url('data:image/svg+xml;utf8,\\\n\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 10 10\"><polygon fill=\"%2316323f\" points=\"4,0 1,0 6,5 1,10 4,10 9,5 4,0\" /></svg>\\\n\t\t\t\t');\n\t\t\tposition: absolute;\n\t\t\tleft: 0;\n\t\t\tmargin: calc((1.5em - 10px) / 2) auto;\n\t\t\twidth: 10px;\n\t\t\theight: 10px;\n\t\t}\n\n\t\t&.expanded::before {\n\t\t\ttransform: rotate(90deg);\n\t\t}\n\t}\n\n\t.tree-leaf {\n\t\tmargin-left: calc(10px + 8px);\n\t}\n\n\t.nested {\n\t\tdisplay: none;\n\t}\n\n\t.active {\n\t\tdisplay: block;\n\t}\n\n\t.warn {\n\t\tdisplay: inline;\n\t\tmargin-left: 12px;\n\t\tcolor: var(--color-flamingo);\n\t\tfont-family: Arial, sans-serif;\n\t\tfont-size: 12px;\n\t\ttext-decoration: none;\n\t\tfont-style: italic;\n\t}\n\n\ta {\n\t\tmargin-left: 12px;\n\t\tcolor: var(--color-crimson);\n\t\tfont-size: 12px;\n\t\tfont-family: Arial, sans-serif;\n\t\tposition: relative;\n\n\t\t&:hover::after {\n\t\t\tcontent: \"↗\";\n\t\t\tmargin-left: 4px;\n\t\t\tposition: absolute;\n\t\t}\n\t}\n}\n\n.subsystem,\n.submessage {\n\tfont-family: monospace;\n\tline-height: 1.5;\n\tpadding: 0 4px;\n\n\t&.subsystem {\n\t\tcolor: #ffffff;\n\t\tbackground: var(--color-crimson);\n\t}\n\n\t&.submessage {\n\t\tbackground: var(--color-mustard);\n\t}\n}\n\n.message {\n\tpadding: 0 4px;\n\tbackground: var(--color-fog);\n}\n\n.field {\n\tpadding-left: 4px;\n\tcolor: #8887c0;\n\n\t+ span {\n\t\tcolor: #457297;\n\t}\n}\n"
  },
  {
    "path": "website/sass/page/donate.scss",
    "content": ".call-to-action.call-to-action.call-to-action {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tflex-direction: row;\n\tgap: calc(var(--font-size-link) * 0.8);\n\tmargin: calc(40 * var(--variable-px)) 0;\n\n\tspan {\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tjustify-content: center;\n\n\t\tem {\n\t\t\ttext-align: center;\n\t\t\tmargin-top: 0.5em;\n\t\t}\n\t}\n\n\t.button {\n\t\tmargin: 0;\n\t}\n}\n\n.statistics:has(span:empty) {\n\tvisibility: hidden;\n}\n\n#supporter-memberships,\n#corporate-sponsorships {\n\t.triptych {\n\t\tgap: 10px;\n\t\tmargin-top: 10px;\n\n\t\ta {\n\t\t\tflex-basis: calc(100% / 3 - 10px - (var(--feature-box-padding) * var(--variable-px)));\n\t\t}\n\t}\n\n\t.triptych + .block,\n\t.feature-box-inner > .block + :is(.block, .triptych) {\n\t\tmargin-top: calc(var(--feature-box-padding) / 2 * var(--variable-px));\n\t}\n\n\t.triptych + .block {\n\t\tdisplay: flex;\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tjustify-content: space-between;\n\t}\n\n\t.action-buttons {\n\t\tgap: calc(var(--feature-box-padding) / 4 * var(--variable-px)) calc(var(--feature-box-padding) * var(--variable-px));\n\t\tflex-wrap: wrap;\n\n\t\tp {\n\t\t\ttext-align: left;\n\t\t\ttext-justify: auto;\n\t\t\twhite-space: nowrap;\n\t\t}\n\t}\n\n\t@media screen and (max-width: 1280px) {\n\t\t.triptych a {\n\t\t\t// Half width, minus own padding on both sides, minus the gap\n\t\t\tflex-basis: calc(50% - (var(--feature-box-padding) * var(--variable-px)) - 10px);\n\t\t}\n\t}\n\n\t@media screen and (max-width: 640px) {\n\t\t.triptych a {\n\t\t\tflex-basis: 100%;\n\t\t}\n\t}\n\n\t.feature-box-narrow {\n\t\tmin-width: 0;\n\t\tbackground-color: var(--color-fog);\n\t\tpadding: calc(var(--feature-box-padding) / 2 * var(--variable-px));\n\t\ttext-decoration: none;\n\t\tcolor: var(--color-navy);\n\n\t\t&:hover {\n\t\t\tbackground-color: var(--color-lemon);\n\t\t}\n\n\t\th1 {\n\t\t\t// Compensates for emoji starting with some left spacing\n\t\t\ttext-indent: -0.2em;\n\t\t}\n\n\t\tul {\n\t\t\tpadding-left: 1em;\n\t\t\tfont-size: calc(7 / 9 * 1em);\n\t\t}\n\t}\n\n\t.button {\n\t\tbackground-color: var(--color-fog);\n\t}\n}\n\n#supporter-memberships {\n\tbackground-color: var(--color-mustard);\n}\n\n#corporate-sponsorships {\n\tbackground-color: var(--color-ale);\n\tmargin-top: 0;\n}\n"
  },
  {
    "path": "website/sass/page/features.scss",
    "content": ".video-container {\n\tbackground: var(--color-fog);\n\n\t> div {\n\t\tmargin: calc(20 * var(--variable-px)) auto;\n\t\twidth: 100%;\n\t\tmax-width: 800px;\n\t}\n}\n\n#roadmap {\n\twidth: 100%;\n\ttext-align: center;\n}\n\n.roadmap {\n\tmargin: auto;\n\tmargin-top: 20px;\n\tpadding: 0 16px;\n\n\t.feature-icons {\n\t\tflex-direction: column;\n\t\tgap: 0;\n\t\twidth: 100%;\n\t\tmargin-bottom: 0;\n\n\t\t.feature-icon {\n\t\t\tposition: relative;\n\t\t\tpadding-left: calc(16px + 8px);\n\n\t\t\t&::before {\n\t\t\t\tcontent: \"\";\n\t\t\t\tposition: absolute;\n\t\t\t\tleft: 0;\n\t\t\t\twidth: 4px;\n\t\t\t\theight: 100%;\n\t\t\t\tbackground: #ddd;\n\t\t\t}\n\n\t\t\t&:first-of-type::before {\n\t\t\t\tbottom: 0;\n\t\t\t\theight: 50%;\n\t\t\t}\n\n\t\t\t&:last-of-type::before {\n\t\t\t\ttop: 0;\n\t\t\t\theight: 50%;\n\t\t\t}\n\n\t\t\t&::after {\n\t\t\t\tcontent: \"\";\n\t\t\t\tposition: absolute;\n\t\t\t\tleft: calc((24px - 4px) / -2);\n\t\t\t\twidth: 24px;\n\t\t\t\theight: 24px;\n\t\t\t\tborder-radius: 24px;\n\t\t\t}\n\n\t\t\t&[data-year]::after {\n\t\t\t\tcontent: attr(data-year);\n\t\t\t\twidth: auto;\n\t\t\t\tline-height: 1.4;\n\t\t\t\ttext-indent: 28px;\n\t\t\t\tpadding-right: 12px;\n\t\t\t}\n\n\t\t\t&.complete::after {\n\t\t\t\tbackground: var(--color-seaside);\n\t\t\t\tbackground-image: url('data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" fill=\"%2316323f\"><polygon points=\"6.69 14.57 0 9 1.8 6.84 6.32 10.61 13.83 1.43 16 3.2 6.69 14.57\" /></svg>');\n\t\t\t\tbackground-repeat: no-repeat;\n\t\t\t\tbackground-position: 4px center;\n\t\t\t}\n\n\t\t\t&.ongoing::after {\n\t\t\t\tbackground: var(--color-lemon);\n\t\t\t\tbackground-image: url('data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" fill=\"%2316323f\"><path d=\"m8,2c3.31,0,6,2.69,6,6s-2.69,6-6,6-6-2.69-6-6,2.69-6,6-6m0-2C3.58,0,0,3.58,0,8s3.58,8,8,8,8-3.58,8-8S12.42,0,8,0h0Zm2.83,9.41l-1.83-1.83v-2.59c0-.55-.45-1-1-1s-1,.45-1,1v3c0,.13.03.26.08.38.05.12.12.23.22.33l2.12,2.12c.2.2.45.29.71.29s.51-.1.71-.29c.39-.39.39-1.02,0-1.41Z\" /></svg>');\n\t\t\t\tbackground-repeat: no-repeat;\n\t\t\t\tbackground-position: 4px center;\n\t\t\t}\n\n\t\t\t&:not(.complete, .ongoing)::after {\n\t\t\t\tbackground: #ddd;\n\t\t\t\tbackground-image: url('data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" fill=\"%23fff\"><circle cx=\"8\" cy=\"8\" r=\"8\" /></svg>');\n\t\t\t\tbackground-repeat: no-repeat;\n\t\t\t\tbackground-position: 4px center;\n\t\t\t}\n\n\t\t\t&.heading {\n\t\t\t\tbackground: none;\n\n\t\t\t\th3 {\n\t\t\t\t\twidth: 100%;\n\t\t\t\t\ttext-align: center;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "website/sass/page/index.scss",
    "content": ".page {\n\t// Prevents horizontal scrollbars from appearing on the page.\n\t// This happens because of the masked (but not overflow: hidden) carousel images.\n\toverflow: hidden;\n}\n\n// ▛ LOGO ▜\n#logo {\n\timg {\n\t\twidth: auto;\n\t\tmax-width: 75%;\n\t\tmax-height: 160px;\n\t}\n}\n// ▙ LOGO ▟\n\n// ▛ TAGLINE ▜\n#tagline {\n\th1 {\n\t\tspan {\n\t\t\tposition: relative;\n\n\t\t\t&::after {\n\t\t\t\tcontent: \"\";\n\t\t\t\tpointer-events: none;\n\t\t\t\tposition: absolute;\n\t\t\t\tleft: 0;\n\t\t\t\tright: 0;\n\t\t\t\ttop: 100%;\n\t\t\t\t// Dimensions: 480x40\n\t\t\t\theight: 100%;\n\t\t\t\tmargin-top: -0.2em;\n\t\t\t\tbackground: url(\"https://static.graphite.art/textures/text-sketch-underline.png\");\n\t\t\t\tbackground-repeat: no-repeat;\n\t\t\t\tbackground-size: contain;\n\t\t\t}\n\t\t}\n\t}\n\n\tp {\n\t\tfont-size: 1.2rem;\n\n\t\t@media screen and (max-width: 1400px) {\n\t\t\tmax-width: unset;\n\t\t}\n\t}\n}\n// ▙ TAGLINE ▟\n\n// ▛ QUICK LINKS ▜\n#quick-links {\n\tmargin-top: calc(40 * var(--variable-px));\n\tgap: calc(var(--font-size-link) * 0.8) calc(var(--font-size-link) * 0.8 * 2);\n\n\t&,\n\t.social-media-buttons,\n\t.call-to-action-buttons {\n\t\tdisplay: flex;\n\t\tflex-wrap: wrap;\n\t\tflex-direction: row;\n\t}\n\n\t.social-media-buttons,\n\t.call-to-action-buttons {\n\t\tgap: calc(var(--font-size-link) * 0.8);\n\t}\n\n\t.social-media-buttons img {\n\t\twidth: calc(var(--font-size-link) * 2);\n\t\tdisplay: block;\n\t}\n\n\t.call-to-action-buttons .github-stars {\n\t\tdisplay: inline-flex;\n\t\tpadding-left: calc(var(--font-size-link) / 2);\n\t\tpadding-right: 0;\n\n\t\timg {\n\t\t\tmargin: auto;\n\t\t\twidth: calc(var(--font-size-link) * 4 / 3);\n\t\t\theight: calc(var(--font-size-link) * 4 / 3);\n\t\t}\n\n\t\tspan {\n\t\t\tmargin: auto calc(var(--font-size-link) / 2);\n\t\t}\n\n\t\tdiv {\n\t\t\tdisplay: inline-flex;\n\t\t\tbackground: var(--color-fog);\n\t\t\tpadding: 0 calc(var(--font-size-link) / 2);\n\t\t\tborder-left: var(--border-thickness) solid var(--color-crimson);\n\t\t\talign-items: center;\n\n\t\t\t&:empty {\n\t\t\t\tdisplay: none;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Hide all these buttons while waiting for the number of stars to be fetched.\n\t// Otherwise the width of the star count will cause a jump upon loading with the layout of all subsequent buttons changing.\n\t// If it fails to load, the div will be removed, allowing the buttons to be displayed (with the star count omitted).\n\t&:has(.github-stars div:empty) {\n\t\tvisibility: hidden;\n\t}\n}\n// ▙ QUICK LINKS ▟\n\n// ▛ SCREENSHOTS ▜\n// ▙ SCREENSHOTS ▟\n\n// ▛ OVERVIEW ▜\n#overview {\n\tbackground-color: var(--color-cloud);\n\n\t.sizzle-video {\n\t\tdisplay: flex;\n\t\tflex-wrap: nowrap;\n\t\tmax-width: 100%;\n\n\t\t.block {\n\t\t\tmin-width: 0;\n\t\t\tflex-direction: row;\n\n\t\t\t&.text {\n\t\t\t\tflex: 1 4 100%;\n\t\t\t\tflex-direction: column;\n\n\t\t\t\tp:has(.button) {\n\t\t\t\t\tmargin-top: 20px;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t&.video {\n\t\t\t\tflex: 0 1 fit-content;\n\t\t\t}\n\t\t}\n\n\t\t@media screen and (max-width: 900px) {\n\t\t\tflex-wrap: wrap;\n\n\t\t\t.block.video {\n\t\t\t\tflex: 1 1 100%;\n\t\t\t\tjustify-content: center;\n\t\t\t}\n\t\t}\n\n\t\t@media screen and (max-width: 1100px) {\n\t\t\tp:has(.button) {\n\t\t\t\tdisplay: none;\n\t\t\t}\n\t\t}\n\t}\n}\n// ▙ OVERVIEW ▟\n\n// ▛ DONATE ▜\n#donate {\n\th2 {\n\t\tcolor: #cc304f;\n\t}\n}\n// ▙ DONATE ▟\n\n// ▛ PROCEDURALISM ▜\n#proceduralism {\n\tbackground-color: var(--color-slate);\n\tcolor: white;\n\n\t.diptych {\n\t\tbackground: black;\n\t\tcolor: var(--color-fog);\n\t\toverflow: hidden; // Clip off a 1px overflow beneath the video which appears at some screen widths\n\t\talign-items: stretch;\n\t\tjustify-content: center;\n\t\tgap: 0;\n\t\t--video-width: calc(460 * var(--variable-px));\n\n\t\t@media screen and (max-width: 1360px) {\n\t\t\t--video-width: calc(400 * var(--variable-px));\n\t\t}\n\n\t\t@media screen and (max-width: 1280px) {\n\t\t\t--video-width: calc(300 * var(--variable-px));\n\t\t}\n\n\t\t@media screen and (max-width: 1080px) {\n\t\t\t--video-width: calc(500 * var(--variable-px));\n\t\t}\n\n\t\t@media screen and (max-width: 640px) {\n\t\t\t--video-width: 100%;\n\t\t}\n\n\t\ta {\n\t\t\tcolor: var(--color-mustard);\n\t\t}\n\n\t\t.video-background {\n\t\t\tflex: 1 0 var(--video-width);\n\t\t\tjustify-content: center;\n\n\t\t\t&,\n\t\t\tvideo {\n\t\t\t\tmax-width: var(--video-width);\n\t\t\t}\n\n\t\t\t&::after {\n\t\t\t\tcontent: none;\n\t\t\t}\n\t\t}\n\n\t\t.description {\n\t\t\tmargin: calc(var(--feature-box-padding) * var(--variable-px));\n\t\t}\n\n\t\t+ .diptych {\n\t\t\tmargin-top: calc(40 * var(--variable-px));\n\t\t}\n\t}\n\n\t.pipelines .feature-icon {\n\t\tbackground: rgba(255, 255, 255, 0.0625);\n\n\t\timg {\n\t\t\tfilter: brightness(100);\n\t\t}\n\t}\n}\n// ▙ PROCEDURALISM ▟\n\n// ▛ NEWSLETTER ▜\n#newsletter {\n\tbackground-color: var(--color-peach);\n\tposition: relative;\n\n\t#newsletter-success {\n\t\tposition: absolute;\n\t\ttop: 0;\n\n\t\t&:not(:target) ~ .diptych .newsletter-success,\n\t\t&:target ~ .diptych form {\n\t\t\tdisplay: none;\n\t\t}\n\t}\n\n\t.diptych {\n\t\tjustify-content: center;\n\n\t\t.newsletter-signup {\n\t\t\t.newsletter-success {\n\t\t\t\tmargin-top: 40px;\n\t\t\t\tpadding: 40px;\n\t\t\t\twidth: 100%;\n\t\t\t\tbox-sizing: border-box;\n\t\t\t\tbackground: var(--color-ale);\n\t\t\t\tborder: 2px solid var(--color-navy);\n\t\t\t}\n\n\t\t\tform {\n\t\t\t\twidth: 100%;\n\t\t\t\tmargin-top: 40px;\n\t\t\t\tdisplay: flex;\n\t\t\t\tgap: 20px;\n\t\t\t\tflex-wrap: wrap;\n\n\t\t\t\t.same-line {\n\t\t\t\t\tdisplay: flex;\n\t\t\t\t\tgap: 20px;\n\t\t\t\t\tflex: 100000 1 0;\n\t\t\t\t\tflex-wrap: wrap;\n\t\t\t\t\tmin-width: Min(100%, 700px);\n\n\t\t\t\t\tdiv {\n\t\t\t\t\t\tmin-height: auto;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t.input-column {\n\t\t\t\t\tdisplay: flex;\n\t\t\t\t\tflex-direction: column;\n\t\t\t\t\tjustify-content: flex-end;\n\t\t\t\t\t--input-focus-color: var(--color-ale);\n\n\t\t\t\t\t&.name {\n\t\t\t\t\t\tflex: 1 0 0;\n\t\t\t\t\t\tmin-width: 240px;\n\t\t\t\t\t}\n\n\t\t\t\t\t&.phone {\n\t\t\t\t\t\tdisplay: none;\n\t\t\t\t\t}\n\n\t\t\t\t\t&.email {\n\t\t\t\t\t\tflex: 1 0 0;\n\t\t\t\t\t\tmin-width: 240px;\n\t\t\t\t\t}\n\n\t\t\t\t\t@media screen and (max-width: 400px) {\n\t\t\t\t\t\t&.name,\n\t\t\t\t\t\t&.email {\n\t\t\t\t\t\t\tmin-width: 100%;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t&.submit {\n\t\t\t\t\t\tflex: 1 0 auto;\n\t\t\t\t\t\tmin-width: 100%;\n\n\t\t\t\t\t\t.button {\n\t\t\t\t\t\t\ttext-align: center;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tlabel,\n\t\t\t\t\tinput {\n\t\t\t\t\t\tflex: 0 0 auto;\n\t\t\t\t\t}\n\n\t\t\t\t\tlabel {\n\t\t\t\t\t\tfont-size: var(--font-size-link);\n\t\t\t\t\t\tfont-weight: 800;\n\t\t\t\t\t\tmargin-bottom: 10px;\n\t\t\t\t\t\tline-height: 1;\n\t\t\t\t\t}\n\n\t\t\t\t\tinput:not([type=\"submit\"]) {\n\t\t\t\t\t\tflex: 0 0 auto;\n\t\t\t\t\t\twidth: 100%;\n\t\t\t\t\t\theight: calc(var(--font-size-link) * 2);\n\t\t\t\t\t\tfont-size: calc(var(--font-size-link) * 0.9);\n\t\t\t\t\t\tcolor: inherit;\n\t\t\t\t\t\tborder: var(--border-thickness) solid currentColor;\n\t\t\t\t\t\tborder-radius: 0; // Required for iOS Safari\n\t\t\t\t\t\toutline: none;\n\t\t\t\t\t\tmargin: 0;\n\t\t\t\t\t\tpadding: 0 var(--font-size-link);\n\t\t\t\t\t\tfont-family: inherit;\n\t\t\t\t\t\tfont-weight: inherit;\n\t\t\t\t\t\tbox-sizing: border-box;\n\n\t\t\t\t\t\t&:focus {\n\t\t\t\t\t\t\tborder-color: var(--input-focus-color);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tinput[type=\"submit\"] {\n\t\t\t\t\t\tbackground: none;\n\t\t\t\t\t\toutline: none;\n\t\t\t\t\t\tcursor: pointer;\n\t\t\t\t\t\tborder-radius: 0; // Required for iOS Safari\n\n\t\t\t\t\t\t&:focus {\n\t\t\t\t\t\t\tborder-color: var(--input-focus-color);\n\t\t\t\t\t\t\tcolor: var(--input-focus-color);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t.social-media-links {\n\t\t\tdisplay: flex;\n\t\t\tflex: 0 1 fit-content;\n\t\t\tflex-direction: column;\n\t\t\tflex-wrap: wrap;\n\t\t\tjustify-content: flex-end;\n\t\t\tgap: 20px 80px;\n\t\t\tmin-width: 0;\n\n\t\t\ta {\n\t\t\t\ttext-decoration: none;\n\t\t\t\tdisplay: flex;\n\n\t\t\t\timg {\n\t\t\t\t\twidth: 48px;\n\t\t\t\t\theight: 48px;\n\t\t\t\t}\n\n\t\t\t\tspan {\n\t\t\t\t\tline-height: 48px;\n\t\t\t\t\tmargin: 0;\n\t\t\t\t\tmargin-left: 20px;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n// ▙ NEWSLETTER ▟\n\n// ▛ DIVE IN ▜\n#dive-in,\n#what-is-new {\n\t.video-container {\n\t\tbackground: var(--color-fog);\n\t\tmargin-top: calc(40 * var(--variable-px));\n\n\t\t> div {\n\t\t\tmargin: calc(20 * var(--variable-px)) auto;\n\t\t\twidth: 100%;\n\t\t\tmax-width: 800px;\n\n\t\t\t+ p {\n\t\t\t\tmargin-top: 0;\n\t\t\t}\n\t\t}\n\t}\n\n\t.buttons {\n\t\tdisplay: flex;\n\t\tgap: calc(var(--font-size-link) * 0.8);\n\t\tmargin-top: calc(40 * var(--variable-px));\n\n\t\t@media screen and (max-width: 600px) {\n\t\t\tflex-direction: column;\n\t\t}\n\t}\n}\n// ▙ DIVE IN ▟\n\n// ▛ RECENT NEWS ▜\n#recent-news {\n\tbackground-color: var(--color-parchment);\n\n\t.banner img {\n\t\twidth: 100%;\n\t\theight: auto;\n\t\tmargin-bottom: 20px;\n\t}\n\n\t.headline a {\n\t\ttext-decoration: none;\n\t\tfont-weight: 700;\n\t}\n\n\t.summary {\n\t\tmargin: 20px 0;\n\t\tflex-direction: column;\n\t\tgap: 20px;\n\t\ttext-align: justify;\n\t\ttext-justify: inter-character; // Only supported in Firefox\n\t\t-webkit-hyphens: auto;\n\t\thyphens: auto;\n\t\toverflow: hidden;\n\t\ttext-overflow: ellipsis;\n\t\tdisplay: -webkit-box;\n\t\t-webkit-box-orient: vertical;\n\t\t-webkit-line-clamp: 6;\n\n\t\tcode {\n\t\t\ttext-justify: auto;\n\t\t}\n\n\t\t// Safari workaround: https://stackoverflow.com/a/72170897/775283\n\t\t// But there remains an issue where the ellipsis is rendered inside the text. There doesn't appear to be a fix, or even a way to disable the ellipsis in Safari.\n\t\tp {\n\t\t\tdisplay: inline;\n\n\t\t\t&::after {\n\t\t\t\tcontent: \"\\A\\A\"; // Double new line\n\t\t\t\twhite-space: pre;\n\t\t\t}\n\t\t}\n\t}\n}\n// ▙ RECENT NEWS ▟\n"
  },
  {
    "path": "website/sass/page/logo.scss",
    "content": ".logo-view {\n\tdisplay: flex;\n\tmargin: 0 auto;\n\n\t+ .logo-view.logo-view {\n\t\tmargin-top: 0;\n\t}\n\n\t&.color {\n\t\tbackground-color: white;\n\t\tbackground-blend-mode: hard-light;\n\t}\n\t\n\t&.light {\n\t\tbackground-color: var(--color-fog);\n\t\tbackground-blend-mode: color-burn;\n\t}\n\n\t&.dark {\n\t\tcolor: var(--color-fog);\n\t\tbackground-color: var(--color-navy);\n\t\tbackground-blend-mode: soft-light;\n\n\t\ta {\n\t\t\tcolor: var(--color-mustard);\n\t\t}\n\t}\n\n\t.feature-box-inner {\n\t\tdisplay: flex;\n\t\tflex-wrap: wrap;\n\t\tgap: 80px 160px;\n\n\t\tdiv {\n\t\t\tdisplay: flex;\n\t\t\tflex-direction: column;\n\n\t\t\timg {\n\t\t\t\tmax-width: calc(100vw - 2 * var(--feature-box-padding) * var(--variable-px));\n\t\t\t}\n\n\t\t\tspan {\n\t\t\t\tfont-weight: 800;\n\t\t\t\tmargin-top: 20px;\n\t\t\t\twhite-space: nowrap;\n\t\t\t}\n\n\t\t\ta {\n\t\t\t\twhite-space: nowrap;\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "website/sass/page/user-manual/node-catalog.scss",
    "content": "table tr td:first-child a {\n\twhite-space: nowrap;\n}\n"
  },
  {
    "path": "website/sass/page/user-manual/node-category.scss",
    "content": "#nodes + table tr {\n\tth:last-child {\n\t\twidth: 0;\n\t\twhite-space: nowrap;\n\t}\n\n\ttd:last-child {\n\t\twidth: 0;\n\n\t\tcode {\n\t\t\twhite-space: nowrap;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "website/sass/page/user-manual/node.scss",
    "content": ":is(#context, #inputs, #outputs) + table tr {\n\tth:is(:first-child, :nth-child(3)) {\n\t\twidth: 0;\n\t\twhite-space: nowrap;\n\t}\n\n\ttd:is(:first-child, :nth-child(3)) {\n\t\twidth: 0;\n\n\t\tcode {\n\t\t\twhite-space: nowrap;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "website/sass/page/volunteer.scss",
    "content": ".diptych.diptych {\n\tgap: 0 calc(var(--feature-box-padding) * var(--variable-px));\n\n\t.feature-box-narrow {\n\t\tmargin-top: calc(40 * var(--variable-px));\n\t}\n}\n\n.feature-box-narrow {\n\t.code-contributions & {\n\t\tbackground-color: var(--color-seaside);\n\t}\n\t\n\t.creative-contributions & {\n\t\tbackground-color: var(--color-lemon);\n\t}\n\t\n\t.user-contributions & {\n\t\tbackground-color: var(--color-lilac);\n\t}\n}\n"
  },
  {
    "path": "website/sass/template/article.scss",
    "content": ".reading-material {\n\twidth: 100%;\n\tflex: 0 1 auto;\n\n\t.details {\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tgap: 20px;\n\n\t\t.publication {\n\t\t\tfont-weight: 800;\n\t\t}\n\n\t\t.banner {\n\t\t\tmargin-top: 0;\n\t\t\tdisplay: block;\n\t\t\twidth: 100%;\n\t\t\theight: auto;\n\t\t}\n\t}\n\n\tarticle {\n\t\tmargin-top: calc(80 * var(--variable-px));\n\t}\n\n\t.social {\n\t\twidth: 100%;\n\t\tdisplay: flex;\n\t\tgap: calc(var(--font-size-link) * 0.8);\n\t\tflex-wrap: wrap;\n\t\tjustify-content: center;\n\t}\n}\n"
  },
  {
    "path": "website/sass/template/book.scss",
    "content": ".three-column-layout {\n\t--aside-gap: 40px;\n\t--aside-width: 300px;\n\t--align-with-article-title-letter-cap-heights: 0.4em;\n\tposition: relative;\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tgap: var(--aside-gap);\n\t// Creates a stacking context for the left popout sidebar's sticky positioning\n\ttransform: translate(0);\n\n\t&.three-column-layout {\n\t\tmax-width: var(--max-extended-width);\n\t}\n\n\t.close-chapter-selection,\n\t.open-chapter-selection {\n\t\tdisplay: none;\n\t\tflex: 0 0 auto;\n\t\tfill: var(--color-navy);\n\t\tbackground: none;\n\t\tborder: none;\n\t\tbox-sizing: content-box;\n\t\tpadding: 6px;\n\t\tfont-size: 0;\n\t\tcursor: pointer;\n\n\t\tsvg {\n\t\t\twidth: 36px;\n\t\t\theight: 36px;\n\t\t}\n\t}\n\n\t.reading-material {\n\t\tflex: 1 2 100%;\n\n\t\tarticle {\n\t\t\twidth: 100%;\n\t\t}\n\n\t\t.prev-next {\n\t\t\tdisplay: flex;\n\t\t\tjustify-content: space-between;\n\t\t\twidth: 100%;\n\t\t\tgap: 20px;\n\n\t\t\ta {\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tgap: 20px;\n\n\t\t\t\tsvg {\n\t\t\t\t\tfill: var(--color-navy);\n\t\t\t\t\tflex: 0 0 auto;\n\t\t\t\t}\n\n\t\t\t\t&:last-of-type {\n\t\t\t\t\ttext-align: right;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t@media print {\n\t\taside {\n\t\t\tdisplay: none;\n\t\t}\n\t}\n\n\t// Page contents right sidebar is removed on smaller screens\n\t@media screen and (max-width: 1200px) {\n\t\t.contents {\n\t\t\tdisplay: none;\n\t\t}\n\t}\n\n\t// Overlaid fold-out menu for chapter selection\n\t@media screen and (max-width: 1000px) {\n\t\tgap: 0;\n\n\t\t.chapters {\n\t\t\tposition: sticky;\n\t\t\twidth: 0;\n\t\t\tmargin-top: calc(-120 * var(--variable-px));\n\t\t\toverflow: visible;\n\t\t\tz-index: 10;\n\t\t\theight: 0;\n\t\t\ttransition: height 0.25s ease-in-out 0.25s;\n\n\t\t\t&.open {\n\t\t\t\theight: 100vh;\n\t\t\t\ttransition: height 0s;\n\n\t\t\t\t.wrapper-outer {\n\t\t\t\t\tleft: 0;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.wrapper-outer {\n\t\t\t\tposition: absolute;\n\t\t\t\tbackground: white;\n\t\t\t\ttop: 0;\n\t\t\t\tbottom: 0;\n\t\t\t\tpadding-left: var(--page-edge-padding);\n\t\t\t\tmargin-left: calc(-1 * var(--page-edge-padding));\n\t\t\t\tpadding-bottom: calc(120 * var(--variable-px));\n\t\t\t\tmargin-bottom: calc(-120 * var(--variable-px));\n\t\t\t\tpadding-top: 16px;\n\t\t\t\tmargin-top: -16px;\n\t\t\t\tborder-right: var(--border-thickness) solid var(--color-walnut);\n\t\t\t\tbox-sizing: border-box;\n\t\t\t\ttransition: left 0.25s ease-in-out;\n\t\t\t\tleft: calc(-1 * (Min(var(--aside-width), 100vw) + 10px));\n\t\t\t\twidth: Min(var(--aside-width), 100vw);\n\n\t\t\t\t&::after {\n\t\t\t\t\tcontent: \"\";\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\ttop: 0;\n\t\t\t\t\tbottom: 0;\n\t\t\t\t\tright: -10px;\n\t\t\t\t\twidth: 10px;\n\t\t\t\t\tbackground: linear-gradient(to right, rgba(0, 0, 0, 0.2), transparent);\n\t\t\t\t}\n\n\t\t\t\t.wrapper-inner {\n\t\t\t\t\tposition: relative;\n\t\t\t\t\toverflow-y: auto;\n\t\t\t\t\theight: 100%;\n\t\t\t\t\tpadding-right: var(--page-edge-padding);\n\t\t\t\t\tmargin-left: -24px;\n\n\t\t\t\t\t> ul:first-of-type {\n\t\t\t\t\t\tmargin-top: calc(120 * var(--variable-px) + var(--align-with-article-title-letter-cap-heights));\n\t\t\t\t\t}\n\n\t\t\t\t\t.close-chapter-selection {\n\t\t\t\t\t\tdisplay: block;\n\t\t\t\t\t\tposition: absolute;\n\t\t\t\t\t\ttop: calc(20px - 6px);\n\t\t\t\t\t\tright: calc(20px - 6px);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t.reading-material {\n\t\t\tmargin-left: auto;\n\t\t\tmargin-right: auto;\n\t\t\twidth: 100%;\n\n\t\t\t.article-title {\n\t\t\t\tdisplay: flex;\n\t\t\t\twhite-space: nowrap;\n\n\t\t\t\th1 {\n\t\t\t\t\twhite-space: normal;\n\t\t\t\t\tdisplay: flex;\n\n\t\t\t\t\t.open-chapter-selection {\n\t\t\t\t\t\tdisplay: inline-block;\n\t\t\t\t\t\tpadding-top: 0;\n\t\t\t\t\t\tpadding-bottom: 0;\n\t\t\t\t\t\tmargin-left: -6px;\n\t\t\t\t\t\tmargin-right: calc(20px - 6px);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\taside {\n\t\tposition: sticky;\n\t\talign-self: flex-start;\n\t\toverflow-y: auto;\n\t\ttop: 0;\n\t\twidth: Min(var(--aside-width), 100vw);\n\t\tmax-height: 100vh;\n\t\tmargin-top: calc(-40 * var(--variable-px));\n\t\tflex: 0 1 auto;\n\t\t--level-indent: 0.75rem;\n\n\t\t&.contents > ul,\n\t\t.wrapper-inner > ul {\n\t\t\t&:first-of-type {\n\t\t\t\tmargin-top: calc(40 * var(--variable-px) + var(--align-with-article-title-letter-cap-heights));\n\t\t\t}\n\n\t\t\t&:last-of-type {\n\t\t\t\tmargin-bottom: calc(40 * var(--variable-px));\n\t\t\t}\n\n\t\t\t> ul {\n\t\t\t\tmargin-left: 0;\n\t\t\t}\n\t\t}\n\n\t\t&.contents > ul > ul :is(ul, li),\n\t\t.wrapper-inner > ul > ul > ul,\n\t\t.wrapper-inner > ul > ul > ul :is(ul, li) {\n\t\t\tmargin-top: 0.5rem;\n\t\t}\n\n\t\tul {\n\t\t\tlist-style: none;\n\t\t\tpadding: 0;\n\t\t\tmargin: 0;\n\n\t\t\t&,\n\t\t\tul,\n\t\t\tli {\n\t\t\t\tmargin-top: calc(40 * var(--variable-px));\n\t\t\t}\n\n\t\t\tul {\n\t\t\t\tmargin-left: var(--level-indent);\n\t\t\t}\n\n\t\t\tli:has(> label > input:not(:checked)) + ul {\n\t\t\t\tdisplay: none;\n\t\t\t}\n\n\t\t\tli {\n\t\t\t\tfont-size: 0;\n\n\t\t\t\t&:not(.title) a {\n\t\t\t\t\ttext-decoration: none;\n\t\t\t\t}\n\n\t\t\t\t&.title,\n\t\t\t\t&.chapter {\n\t\t\t\t\tfont-weight: 700;\n\t\t\t\t}\n\n\t\t\t\tlabel {\n\t\t\t\t\tdisplay: inline-block;\n\t\t\t\t\tposition: relative;\n\t\t\t\t\tuser-select: none;\n\t\t\t\t\tvertical-align: bottom;\n\t\t\t\t\tmargin-right: 4px;\n\t\t\t\t\twidth: 20px;\n\t\t\t\t\theight: calc(1rem * 1.5);\n\n\t\t\t\t\t&:has(input):hover {\n\t\t\t\t\t\tbackground: var(--color-fog);\n\t\t\t\t\t}\n\n\t\t\t\t\t&:has(input)::before {\n\t\t\t\t\t\tcontent: \"\";\n\t\t\t\t\t\tbackground: url('data:image/svg+xml;utf8,\\\n\t\t\t\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 10 10\"><polygon fill=\"%2316323f\" points=\"4,0 1,0 6,5 1,10 4,10 9,5 4,0\" /></svg>\\\n\t\t\t\t\t\t\t');\n\t\t\t\t\t\tposition: absolute;\n\t\t\t\t\t\tmargin: auto;\n\t\t\t\t\t\tinset: 0;\n\t\t\t\t\t\twidth: 10px;\n\t\t\t\t\t\theight: 10px;\n\t\t\t\t\t}\n\n\t\t\t\t\t&:has(input:checked)::before {\n\t\t\t\t\t\ttransform: rotate(90deg);\n\t\t\t\t\t}\n\n\t\t\t\t\tinput {\n\t\t\t\t\t\tdisplay: none;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\ta {\n\t\t\t\t\tcolor: var(--color-walnut);\n\t\t\t\t\tfont-size: 1rem;\n\n\t\t\t\t\t&:hover {\n\t\t\t\t\t\tcolor: var(--color-crimson);\n\t\t\t\t\t\ttext-decoration: underline;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t&.chapters {\n\t\t\tli.active,\n\t\t\tli.active a {\n\t\t\t\tcolor: var(--color-ale);\n\t\t\t}\n\n\t\t\tul a {\n\t\t\t\tdisplay: inline-block;\n\t\t\t}\n\t\t}\n\n\t\t&.contents {\n\t\t\tli {\n\t\t\t\tposition: relative;\n\t\t\t\tpadding-left: 12px;\n\n\t\t\t\t&.active::before {\n\t\t\t\t\tcontent: \"\";\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\ttop: 0;\n\t\t\t\t\tbottom: 0;\n\t\t\t\t\tleft: 0;\n\t\t\t\t\twidth: 4px;\n\t\t\t\t\tbackground: var(--color-ale);\n\t\t\t\t}\n\n\t\t\t\ta:not(:hover) span {\n\t\t\t\t\tdisplay: none;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "website/static/js/component/carousel.js",
    "content": "const FLING_VELOCITY_THRESHOLD = 10;\nconst FLING_VELOCITY_WINDOW_SIZE = 20;\n\nwindow.addEventListener(\"DOMContentLoaded\", initializeCarousel);\nwindow.addEventListener(\"pointerup\", () => dragEnd(false));\nwindow.addEventListener(\"scroll\", () => dragEnd(true));\nwindow.addEventListener(\"pointermove\", dragMove);\n\n/**\n * @typedef {{\n *   carouselContainer: Element,\n *   images: NodeListOf<Element>,\n *   directionPrev: Element | null,\n *   directionNext: Element | null,\n *   dots: NodeListOf<Element>,\n *   descriptions: NodeListOf<Element>,\n *   dragLastClientX: number | undefined,\n *   velocityDeltaWindow: Array<{ time: number, delta: number }>,\n *   jostleNoLongerNeeded: boolean,\n *   requestAnimationFrameActive: boolean,\n *   videoSyncInterval: ReturnType<typeof setTimeout> | undefined,\n * }} Carousel\n */\n\nconst /** @type {Carousel[]} */ carousels = [];\n\nfunction initializeCarousel() {\n\tconst carouselContainers = document.querySelectorAll(\"[data-carousel]\");\n\n\tcarouselContainers.forEach((carouselContainer) => {\n\t\tconst slideImages = carouselContainer.querySelectorAll(\"[data-carousel-slide] [data-carousel-image]\");\n\t\tconst tornLeft = carouselContainer.querySelector(\"[data-carousel-slide-torn-left]\");\n\t\tconst tornRight = carouselContainer.querySelector(\"[data-carousel-slide-torn-right]\");\n\t\t[tornLeft, tornRight].forEach((insertInsideElement) => {\n\t\t\tif (!(insertInsideElement instanceof HTMLElement)) return;\n\t\t\tslideImages.forEach((image) => {\n\t\t\t\tconst clonedImage = image.cloneNode(true);\n\t\t\t\tif (!(clonedImage instanceof HTMLImageElement) && !(clonedImage instanceof HTMLVideoElement)) return;\n\t\t\t\tif (clonedImage instanceof HTMLImageElement) clonedImage.alt = \"\";\n\t\t\t\tinsertInsideElement.insertAdjacentElement(\"beforeend\", clonedImage);\n\t\t\t});\n\t\t});\n\n\t\tconst images = carouselContainer.querySelectorAll(\"[data-carousel-image]\");\n\t\tconst directionPrev = carouselContainer.querySelector(\"[data-carousel-prev]\");\n\t\tconst directionNext = carouselContainer.querySelector(\"[data-carousel-next]\");\n\t\tconst dots = carouselContainer.querySelectorAll(\"[data-carousel-dot]\");\n\t\tconst descriptions = carouselContainer.querySelectorAll(\"[data-carousel-description]\");\n\t\tconst performJostleHint = carouselContainer.hasAttribute(\"data-carousel-jostle-hint\");\n\t\tconst dragLastClientX = undefined;\n\t\tconst velocityDeltaWindow = Array.from({ length: FLING_VELOCITY_WINDOW_SIZE }, () => ({ time: 0, delta: 0 }));\n\t\tconst jostleNoLongerNeeded = false;\n\n\t\tconst carousel = {\n\t\t\tcarouselContainer,\n\t\t\timages,\n\t\t\tdirectionPrev,\n\t\t\tdirectionNext,\n\t\t\tdots,\n\t\t\tdescriptions,\n\t\t\tdragLastClientX,\n\t\t\tvelocityDeltaWindow,\n\t\t\tjostleNoLongerNeeded,\n\t\t\trequestAnimationFrameActive: false,\n\t\t\tvideoSyncInterval: undefined,\n\t\t};\n\t\tcarousels.push(carousel);\n\n\t\timages.forEach((image) => {\n\t\t\tif (!(image instanceof HTMLElement)) return;\n\t\t\timage.addEventListener(\"pointerdown\", dragBegin);\n\t\t});\n\t\tdirectionPrev?.addEventListener(\"click\", () => slideDirection(carousel, \"prev\", true, false));\n\t\tdirectionNext?.addEventListener(\"click\", () => slideDirection(carousel, \"next\", true, false));\n\t\tArray.from(dots).forEach((dot) =>\n\t\t\tdot.addEventListener(\"click\", (event) => {\n\t\t\t\tconst index = event.target instanceof Element ? Array.from(dots).indexOf(event.target) : -1;\n\t\t\t\tslideTo(carousel, index, true);\n\t\t\t}),\n\t\t);\n\n\t\t// Jostle hint is a feature to briefly shift the carousel by a bit as a hint to users that it can be interacted with\n\t\tif (performJostleHint) {\n\t\t\twindow.addEventListener(\"load\", () => {\n\t\t\t\tif (!(directionPrev instanceof HTMLElement)) return;\n\t\t\t\tnew IntersectionObserver(\n\t\t\t\t\t(entries) => {\n\t\t\t\t\t\tentries.forEach((entry) => {\n\t\t\t\t\t\t\tif (entry.intersectionRatio === 1 && currentTransform(carousel) === 0 && !carousel.jostleNoLongerNeeded) {\n\t\t\t\t\t\t\t\tconst JOSTLE_TIME = 1000;\n\t\t\t\t\t\t\t\tconst MAX_JOSTLE_DISTANCE = -10;\n\n\t\t\t\t\t\t\t\tlet /** @type {number} */ startTime;\n\t\t\t\t\t\t\t\tconst buildUp = (/** @type {number} */ timeStep) => {\n\t\t\t\t\t\t\t\t\tif (carousel.jostleNoLongerNeeded) {\n\t\t\t\t\t\t\t\t\t\tcarousel.carouselContainer.classList.remove(\"jostling\");\n\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\tif (!startTime) startTime = timeStep;\n\t\t\t\t\t\t\t\t\tconst elapsedTime = timeStep - startTime;\n\n\t\t\t\t\t\t\t\t\tconst easeOutCirc = (/** @type {number} */ x) => Math.sqrt(1 - Math.pow(x - 1, 2));\n\t\t\t\t\t\t\t\t\tconst movementFactor = easeOutCirc(Math.min(1, elapsedTime / JOSTLE_TIME));\n\n\t\t\t\t\t\t\t\t\tsetCurrentTransform(carousel, movementFactor * MAX_JOSTLE_DISTANCE, \"%\", false, true);\n\n\t\t\t\t\t\t\t\t\tif (elapsedTime < JOSTLE_TIME) {\n\t\t\t\t\t\t\t\t\t\trequestAnimationFrame(buildUp);\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tcarousel.carouselContainer.classList.remove(\"jostling\");\n\t\t\t\t\t\t\t\t\t\tcarousel.jostleNoLongerNeeded = true;\n\t\t\t\t\t\t\t\t\t\tslideTo(carousel, 0, true);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tcarousel.carouselContainer.classList.add(\"jostling\");\n\t\t\t\t\t\t\t\trequestAnimationFrame(buildUp);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t},\n\t\t\t\t\t{ threshold: 1 },\n\t\t\t\t).observe(directionPrev);\n\t\t\t});\n\t\t}\n\t});\n}\n\n/**\n * @param {Carousel} carousel\n * @param {\"prev\" | \"next\"} direction\n * @param {boolean} smooth\n */\nfunction slideDirection(carousel, direction, smooth, clamped = false) {\n\tconst directionIndexOffset = { prev: -1, next: 1 }[direction];\n\tconst offsetDotIndex = currentClosestImageIndex(carousel) + directionIndexOffset;\n\n\tconst nextDotIndex = (offsetDotIndex + carousel.dots.length) % carousel.dots.length;\n\tconst unwrappedNextDotIndex = clamp(offsetDotIndex, 0, carousel.dots.length - 1);\n\n\tif (clamped) slideTo(carousel, unwrappedNextDotIndex, smooth);\n\telse slideTo(carousel, nextDotIndex, smooth);\n}\n\n/**\n * @param {Carousel} carousel\n * @param {number} index\n * @param {boolean} smooth\n */\nfunction slideTo(carousel, index, smooth) {\n\tconst activeDot = carousel.carouselContainer.querySelector(\"[data-carousel-dot].active\");\n\tactiveDot?.classList.remove(\"active\");\n\tcarousel.dots[index].classList.add(\"active\");\n\n\tconst activeDescription = carousel.carouselContainer.querySelector(\"[data-carousel-description].active\");\n\tif (activeDescription) {\n\t\tactiveDescription.classList.remove(\"active\");\n\t\tcarousel.descriptions[index].classList.add(\"active\");\n\t}\n\n\t// Account for the first image being the faded out last image\n\tconst offsetIndex = index + 1;\n\tconst slideImages = Array.from(carousel.carouselContainer.querySelectorAll(\"[data-carousel-slide] [data-carousel-image]\"));\n\t// Remove lazy loading from the adjacent images\n\tslideImages[clamp(offsetIndex - 2, 0, slideImages.length - 1)].removeAttribute(\"loading\");\n\tslideImages[clamp(offsetIndex - 1, 0, slideImages.length - 1)].removeAttribute(\"loading\");\n\tslideImages[clamp(offsetIndex, 0, slideImages.length - 1)].removeAttribute(\"loading\");\n\tslideImages[clamp(offsetIndex + 1, 0, slideImages.length - 1)].removeAttribute(\"loading\");\n\tslideImages[clamp(offsetIndex + 2, 0, slideImages.length - 1)].removeAttribute(\"loading\");\n\n\tsetCurrentTransform(carousel, index * -100, \"%\", smooth);\n\n\t// Manage video preloading and playback\n\tmanageVideoPlayback(carousel, index);\n}\n\n/**\n * Get all video elements for a given slide index (main + torn edge copies)\n * @param {Carousel} carousel\n * @param {number} index\n */\nfunction getVideosForSlide(carousel, index) {\n\t// Account for the first image being the faded-out last image\n\tconst offsetIndex = index + 1;\n\tconst slideImages = Array.from(carousel.carouselContainer.querySelectorAll(\"[data-carousel-slide] [data-carousel-image]\"));\n\tconst tornLeftImages = Array.from(carousel.carouselContainer.querySelectorAll(\"[data-carousel-slide-torn-left] [data-carousel-image]\"));\n\tconst tornRightImages = Array.from(carousel.carouselContainer.querySelectorAll(\"[data-carousel-slide-torn-right] [data-carousel-image]\"));\n\n\tconst mainElement = slideImages[offsetIndex];\n\tconst tornLeftElement = tornLeftImages[offsetIndex];\n\tconst tornRightElement = tornRightImages[offsetIndex];\n\n\treturn {\n\t\tmain: mainElement instanceof HTMLVideoElement ? mainElement : null,\n\t\ttornLeft: tornLeftElement instanceof HTMLVideoElement ? tornLeftElement : null,\n\t\ttornRight: tornRightElement instanceof HTMLVideoElement ? tornRightElement : null,\n\t};\n}\n\n/**\n * Check if the carousel is currently in transition (dragging or animating)\n * @param {Carousel} carousel\n */\nfunction isCarouselInTransition(carousel) {\n\t// Check if user is dragging\n\tif (carousel.dragLastClientX !== undefined) return true;\n\n\t// Check if carousel has the \"dragging\" class (set during drag)\n\tif (carousel.carouselContainer.classList.contains(\"dragging\")) return true;\n\n\t// Check if any slide has a transition in progress by looking at the computed style\n\tconst firstImage = carousel.images[1];\n\tif (firstImage instanceof HTMLElement) {\n\t\tconst style = window.getComputedStyle(firstImage);\n\t\t// If transform is transitioning, we're in motion\n\t\tif (style.transitionProperty.includes(\"transform\") && style.transitionDuration !== \"0s\") {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\n/**\n * Preload and manage playback of videos on current and adjacent slides\n * @param {Carousel} carousel\n * @param {number} currentIndex\n */\nfunction manageVideoPlayback(carousel, currentIndex) {\n\tconst totalSlides = carousel.dots.length;\n\n\t// Clear any existing sync interval\n\tif (carousel.videoSyncInterval !== undefined) {\n\t\tclearTimeout(carousel.videoSyncInterval);\n\t\tcarousel.videoSyncInterval = undefined;\n\t}\n\n\t// Stop all videos that aren't on current or adjacent slides\n\tfor (let i = 0; i < totalSlides; i++) {\n\t\tif (Math.abs(i - currentIndex) > 1) {\n\t\t\tconst videos = getVideosForSlide(carousel, i);\n\t\t\t[videos.main, videos.tornLeft, videos.tornRight].forEach((video) => {\n\t\t\t\tif (video) {\n\t\t\t\t\tvideo.pause();\n\t\t\t\t\tvideo.currentTime = 0;\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n\n\t// Preload and potentially play videos on current and adjacent slides\n\tconst indicesToPreload = [currentIndex - 1, currentIndex, currentIndex + 1].filter((index) => index >= 0 && index < totalSlides);\n\n\tindicesToPreload.forEach((index) => {\n\t\tconst videos = getVideosForSlide(carousel, index);\n\n\t\t// Exit early if not a video slide\n\t\tif (!videos.main) return;\n\n\t\t// Preload the video\n\t\tif (videos.main.readyState < 3) videos.main.load();\n\n\t\t// If this is the current slide, play the main video when ready\n\t\tif (index === currentIndex) {\n\t\t\tconst playWhenReady = () => {\n\t\t\t\tif (videos.main && videos.main.readyState >= 3) {\n\t\t\t\t\t// Start the main video\n\t\t\t\t\tvideos.main.currentTime = 0;\n\t\t\t\t\tvideos.main.play().catch(() => {});\n\t\t\t\t} else if (videos.main) {\n\t\t\t\t\t// Video not ready yet, check again\n\t\t\t\t\tvideos.main.addEventListener(\"canplaythrough\", playWhenReady, { once: true });\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tplayWhenReady();\n\n\t\t\t// Monitor for transitions and sync torn videos when in motion\n\t\t\tupdateVideoSyncForTransitions(carousel, videos);\n\t\t}\n\t});\n}\n\n/**\n * Set up monitoring to play/pause torn edge videos based on transition state\n * @param {Carousel} carousel\n * @param {{ main: HTMLVideoElement | null, tornLeft: HTMLVideoElement | null, tornRight: HTMLVideoElement | null }} videos\n */\nfunction updateVideoSyncForTransitions(carousel, videos) {\n\tif (!videos.main) return;\n\n\tconst syncTornVideos = () => {\n\t\tconst inTransition = isCarouselInTransition(carousel);\n\n\t\t// During transition: sync and play all copies\n\t\tif (inTransition && videos.main) {\n\t\t\tconst mainTime = videos.main.currentTime;\n\t\t\t[videos.tornLeft, videos.tornRight].forEach((video) => {\n\t\t\t\tif (!video) return;\n\n\t\t\t\tif (video.paused) {\n\t\t\t\t\tvideo.currentTime = mainTime;\n\t\t\t\t\tvideo.play().catch(() => {\n\t\t\t\t\t\t// Ignore autoplay errors\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\t// Keep synced\n\t\t\t\t\tconst drift = Math.abs(video.currentTime - mainTime);\n\t\t\t\t\tif (drift > 0.1) {\n\t\t\t\t\t\tvideo.currentTime = mainTime;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t\t// Not in transition: pause torn edge videos to save performance\n\t\telse {\n\t\t\tif (videos.tornLeft && !videos.tornLeft.paused) videos.tornLeft.pause();\n\t\t\tif (videos.tornRight && !videos.tornRight.paused) videos.tornRight.pause();\n\t\t}\n\n\t\t// Continue checking while in transition, or check again soon in case transition starts\n\t\tcarousel.videoSyncInterval = setTimeout(syncTornVideos, 100);\n\t};\n\n\tsyncTornVideos();\n}\n\n/**\n * @param {Carousel} carousel\n */\nfunction currentTransform(carousel) {\n\tconst currentTransformMatrix = window.getComputedStyle(carousel.images[1]).transform;\n\t// Grab the X value from the format that looks like either of: `matrix(1, 0, 0, 1, -1332.13, 0)` or `none`\n\tconst xValue = Number(currentTransformMatrix.split(\",\")[4] || \"0\");\n\n\treturn xValue + carousel.images[1].getBoundingClientRect().width;\n}\n\n/**\n * @param {Carousel} carousel\n * @param {number} x\n * @param {string} unit\n * @param {boolean} smooth\n * @param {boolean} doNotTerminateJostle\n */\nfunction setCurrentTransform(carousel, x, unit, smooth, doNotTerminateJostle = false) {\n\tconst xInitial = currentTransform(carousel);\n\tlet xValue = x;\n\tif (unit === \"%\") xValue = x - 100;\n\tif (unit === \"px\") xValue = x - carousel.images[1].getBoundingClientRect().width;\n\n\tArray.from(carousel.images).forEach((image) => {\n\t\tif (!(image instanceof HTMLElement)) return;\n\t\timage.style.transitionTimingFunction = smooth ? \"ease-in-out\" : \"cubic-bezier(0, 0, 0.2, 1)\";\n\t\timage.style.transform = `translateX(${xValue}${unit})`;\n\t});\n\n\t// If the user caused the carousel to move, we can assume they know how to use it and don't need the jostle hint anymore\n\tif (!doNotTerminateJostle && x - xInitial < 0.0001) carousel.jostleNoLongerNeeded = true;\n\n\tconst distance = unit === \"%\" ? x : (x / carousel.images[1].getBoundingClientRect().width) * 100;\n\tconst overSlidingLeft = distance > 0;\n\tconst overSlidingRight = distance < (carousel.dots.length - 1) * -100;\n\n\tif ((overSlidingLeft || overSlidingRight) && !carousel.requestAnimationFrameActive) updateOverSlide(carousel);\n}\n\n/**\n * @param {Carousel} carousel\n */\nfunction updateOverSlide(carousel) {\n\tconst paddingLeft = parseInt(getComputedStyle(carousel.images[1]).paddingLeft);\n\tconst paddingRight = parseInt(getComputedStyle(carousel.images[carousel.images.length - 2]).paddingRight);\n\tconst slidLeftDistance = carousel.images[1].getBoundingClientRect().left + paddingLeft - (carousel.images[1].parentElement?.getBoundingClientRect().left || 0);\n\tconst slidRightDistance = -(carousel.images[carousel.images.length - 2].getBoundingClientRect().right - paddingRight - (carousel.images[1].parentElement?.getBoundingClientRect().right || 0));\n\tconst imageWidth = carousel.images[1].getBoundingClientRect().width;\n\tconst overSlideFactor = Math.min(1, Math.max(0, Math.max(slidLeftDistance, slidRightDistance) / imageWidth));\n\n\tconst images = carousel.images[0].closest(\"[data-carousel]\")?.querySelectorAll(\"[data-carousel-image]:first-child, [data-carousel-image]:last-child\");\n\tif (!images) return;\n\n\t// Call again the next frame if we're still sliding past the edge\n\tif (overSlideFactor > 0) {\n\t\timages.forEach((image) => {\n\t\t\tif (!(image instanceof HTMLElement)) return;\n\t\t\timage.style.setProperty(\"--over-slide-factor\", `${overSlideFactor}`);\n\t\t});\n\n\t\tcarousel.requestAnimationFrameActive = true;\n\t\trequestAnimationFrame(() => updateOverSlide(carousel));\n\t} else {\n\t\timages.forEach((image) => {\n\t\t\tif (!(image instanceof HTMLElement)) return;\n\t\t\timage.style.removeProperty(\"--over-slide-factor\");\n\t\t});\n\n\t\tcarousel.requestAnimationFrameActive = false;\n\t}\n}\n\n/**\n * @param {Carousel} carousel\n */\nfunction currentClosestImageIndex(carousel) {\n\tconst currentTransformX = -currentTransform(carousel);\n\n\tconst imageWidth = carousel.images[1].getBoundingClientRect().width;\n\treturn Math.round(currentTransformX / imageWidth);\n}\n\n/**\n * @param {Carousel} carousel\n */\nfunction currentActiveDotIndex(carousel) {\n\tconst activeDot = carousel.carouselContainer.querySelector(\"[data-carousel-dot].active\");\n\treturn activeDot ? Array.from(carousel.dots).indexOf(activeDot) : -1;\n}\n\n/**\n * @param {PointerEvent} event\n */\nfunction dragBegin(event) {\n\tif (!(event.target instanceof HTMLElement)) return;\n\tconst carouselContainer = event.target.closest(\"[data-carousel]\");\n\tconst carousel = carousels.find((carousel) => carousel.carouselContainer === carouselContainer);\n\tif (!carousel) return;\n\n\tevent.preventDefault();\n\n\tcarousel.dragLastClientX = event.clientX;\n\n\tsetCurrentTransform(carousel, currentTransform(carousel), \"px\", false);\n\tcarouselContainer?.classList.add(\"dragging\");\n}\n\n/**\n * @param {boolean} dropWithoutVelocity\n */\nfunction dragEnd(dropWithoutVelocity) {\n\tconst carousel = carousels.find((carousel) => carousel.dragLastClientX !== undefined);\n\tif (!carousel) return;\n\n\tif (!carousel.images) return;\n\n\tcarousel.dragLastClientX = undefined;\n\n\tcarousel.carouselContainer.classList.remove(\"dragging\");\n\n\tconst onlyRecentVelocityDeltaWindow = carousel.velocityDeltaWindow.filter((delta) => delta.time > Date.now() - 1000);\n\tconst timeRange = Date.now() - (onlyRecentVelocityDeltaWindow[0]?.time ?? NaN);\n\t// Weighted (higher by recency) sum of velocity deltas from previous window of frames\n\tconst recentVelocity = onlyRecentVelocityDeltaWindow.reduce((acc, entry) => {\n\t\tconst timeSinceNow = Date.now() - entry.time;\n\t\tconst recencyFactorScore = 1 - timeSinceNow / timeRange;\n\n\t\treturn acc + entry.delta * recencyFactorScore;\n\t}, 0);\n\n\tconst closestImageIndex = currentClosestImageIndex(carousel);\n\tconst activeDotIndex = currentActiveDotIndex(carousel);\n\n\t// If the speed is fast enough, slide to the next or previous image in that direction\n\tif (Math.abs(recentVelocity) > FLING_VELOCITY_THRESHOLD && !dropWithoutVelocity) {\n\t\t// Positive velocity should go to the previous image\n\t\tif (recentVelocity > 0) {\n\t\t\t// Don't apply the velocity-based fling if we're already snapping to the next image\n\t\t\tif (closestImageIndex >= activeDotIndex) {\n\t\t\t\tslideDirection(carousel, \"prev\", false, true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\t// Negative velocity should go to the next image\n\t\telse {\n\t\t\t// Don't apply the velocity-based fling if we're already snapping to the next image\n\t\t\tif (closestImageIndex <= activeDotIndex) {\n\t\t\t\tslideDirection(carousel, \"next\", false, true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\t// If we didn't slide in a direction due to clear velocity, just snap to the closest image\n\t// This can be reached either by not entering the if statement above, or by its inner if statements not returning early and exiting back to this scope\n\tslideTo(carousel, clamp(closestImageIndex, 0, carousel.dots.length - 1), true);\n}\n\n/**\n * @param {PointerEvent} event\n */\nfunction dragMove(event) {\n\tif (!(event.target instanceof HTMLElement)) return;\n\n\tconst carouselContainer = event.target.closest(\"[data-carousel]\");\n\tconst carousel = carousels.find((carousel) => carousel.carouselContainer === carouselContainer);\n\tif (!carousel) return;\n\n\tif (carousel.dragLastClientX === undefined) return;\n\n\tevent.preventDefault();\n\n\tconst LEFT_MOUSE_BUTTON = 1;\n\tif (!(event.buttons & LEFT_MOUSE_BUTTON)) {\n\t\tdragEnd(false);\n\t\treturn;\n\t}\n\n\tconst deltaX = event.clientX - carousel.dragLastClientX;\n\tcarousel.velocityDeltaWindow.shift();\n\tcarousel.velocityDeltaWindow.push({ time: Date.now(), delta: deltaX });\n\n\tconst newTransformX = currentTransform(carousel) + deltaX;\n\tsetCurrentTransform(carousel, newTransformX, \"px\", false);\n\n\tcarousel.dragLastClientX = event.clientX;\n}\n\n/**\n * @param {number} value\n * @param {number} min\n * @param {number} max\n */\nfunction clamp(value, min, max) {\n\tconst m = Math; // This is a workaround for a bug in Zola's minifier\n\treturn m.min(m.max(value, min), max);\n}\n"
  },
  {
    "path": "website/static/js/component/image-comparison.js",
    "content": "const RECENTER_DELAY = 1;\nconst RECENTER_ANIMATION_DURATION = 0.25;\n\nwindow.addEventListener(\"DOMContentLoaded\", initializeImageComparison);\n\nfunction initializeImageComparison() {\n\tArray.from(document.querySelectorAll(\"[data-image-comparison]\")).forEach((element) => {\n\t\tif (!(element instanceof HTMLElement)) return;\n\n\t\tconst moveHandler = (/** @type {PointerEvent} **/ event) => {\n\t\t\tconst factor = (event.clientX - element.getBoundingClientRect().left) / element.getBoundingClientRect().width;\n\t\t\tconst capped = Math.max(0, Math.min(1, factor));\n\n\t\t\tif (!(element instanceof HTMLElement)) return;\n\t\t\telement.style.setProperty(\"--comparison-percent\", `${capped * 100}%`);\n\t\t\telement.dataset.lastInteraction = \"\";\n\t\t};\n\n\t\tconst leaveHandler = (/** @type {PointerEvent} **/ event) => {\n\t\t\tmoveHandler(event);\n\n\t\t\tconst randomCode = String(Math.random()).substring(2);\n\t\t\telement.dataset.lastInteraction = randomCode;\n\n\t\t\tsetTimeout(() => {\n\t\t\t\tif (element.dataset.lastInteraction === randomCode) {\n\t\t\t\t\telement.dataset.recenterStartTime = `${Date.now()}`;\n\t\t\t\t\telement.dataset.recenterStartValue = `${parseFloat(element.style.getPropertyValue(\"--comparison-percent\"))}`;\n\n\t\t\t\t\trecenterAnimationStep();\n\t\t\t\t}\n\t\t\t}, RECENTER_DELAY * 1000);\n\t\t};\n\n\t\tconst recenterAnimationStep = () => {\n\t\t\tif (element.dataset.lastInteraction === \"\") return;\n\n\t\t\tconst completionFactor = (Date.now() - Number(element.dataset.recenterStartTime)) / (RECENTER_ANIMATION_DURATION * 1000);\n\t\t\tif (completionFactor > 1) {\n\t\t\t\telement.dataset.lastInteraction = \"\";\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst factor = smootherstep(completionFactor);\n\t\t\tconst newLocation = lerp(Number(element.dataset.recenterStartValue), 50, factor);\n\t\t\telement.style.setProperty(\"--comparison-percent\", `${newLocation}%`);\n\n\t\t\trequestAnimationFrame(recenterAnimationStep);\n\t\t};\n\n\t\tconst lerp = (/** @type {number} **/ a, /** @type {number} **/ b, /** @type {number} **/ t) => (1 - t) * a + t * b;\n\t\tconst smootherstep = (/** @type {number} **/ x) => x * x * x * (x * (x * 6 - 15) + 10);\n\n\t\telement.addEventListener(\"pointermove\", moveHandler);\n\t\telement.addEventListener(\"pointerenter\", moveHandler);\n\t\telement.addEventListener(\"pointerleave\", leaveHandler);\n\t\telement.addEventListener(\"dragstart\", (event) => event.preventDefault());\n\t});\n}\n"
  },
  {
    "path": "website/static/js/component/video-autoplay.js",
    "content": "const VISIBILITY_COVERAGE_FRACTION = 0.25;\n\nwindow.addEventListener(\"DOMContentLoaded\", () => {\n\tconst players = document.querySelectorAll(\"[data-auto-play]\");\n\tplayers.forEach((player) => {\n\t\tif (!(player instanceof HTMLVideoElement)) return;\n\n\t\tlet loaded = false;\n\n\t\tnew IntersectionObserver(\n\t\t\t(entries) => {\n\t\t\t\tentries.forEach((entry) => {\n\t\t\t\t\tif (!loaded && entry.intersectionRatio > VISIBILITY_COVERAGE_FRACTION) {\n\t\t\t\t\t\tplayer.removeAttribute(\"preload\");\n\t\t\t\t\t\tplayer.setAttribute(\"autoplay\", \"\");\n\n\t\t\t\t\t\tloaded = true;\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t},\n\t\t\t{ threshold: VISIBILITY_COVERAGE_FRACTION },\n\t\t).observe(player);\n\t});\n});\n"
  },
  {
    "path": "website/static/js/component/youtube-embed.js",
    "content": "window.addEventListener(\"DOMContentLoaded\", () => {\n\tdocument.querySelectorAll(\"[data-youtube-embed]\").forEach((placeholder) => {\n\t\tif (!(placeholder instanceof HTMLElement)) return;\n\t\tplaceholder.addEventListener(\"click\", () => {\n\t\t\tconst videoId = placeholder.getAttribute(\"data-youtube-embed\") || \"\";\n\t\t\tconst timestamp = placeholder.getAttribute(\"data-youtube-timestamp\") || \"\";\n\t\t\tplaceholder.outerHTML = `\n\t\t\t\t<iframe \\\\\n\t\t\t\twidth=\"1280\" \\\\\n\t\t\t\theight=\"720\" \\\\\n\t\t\t\tsrc=\"https://www.youtube.com/embed/${videoId}?${timestamp ? `start=${timestamp}&` : \"\"}autoplay=1\" \\\\\n\t\t\t\tframeborder=\"0\" \\\\\n\t\t\t\tallow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" \\\\\n\t\t\t\tallowfullscreen\\\\\n\t\t\t\t>\\\\\n\t\t\t\t</iframe>\\\\\n\t\t\t\t`\n\t\t\t\t.split(\"\\n\")\n\t\t\t\t.map((line) => line.trim())\n\t\t\t\t.join(\"\")\n\t\t\t\t.replaceAll(`\\\\`, \"\");\n\t\t});\n\t});\n});\n"
  },
  {
    "path": "website/static/js/navbar.js",
    "content": "const NAV_BUTTON_INITIAL_FONT_SIZE = 28; // Keep up to date with the initial `--nav-font-size` in base.scss\nconst RIPPLE_ANIMATION_MILLISECONDS = 100;\nconst RIPPLE_WIDTH = 100;\nconst HANDLE_STRETCH = 0.4;\n\nlet /** @type {NodeList | undefined} **/ navButtons;\nlet /** @type {Element | undefined} **/ rippleSvg;\nlet /** @type {Element | undefined} **/ ripplePath;\nlet /** @type {number | undefined} **/ fullRippleHeight;\nlet /** @type {{ element: HTMLElement, goingUp: boolean, animationStartTime: number, animationEndTime: number }[]} **/ ripples;\nlet /** @type {number} **/ activeRippleIndex;\n\nwindow.addEventListener(\"DOMContentLoaded\", initializeRipples);\n\nfunction initializeRipples() {\n\twindow.addEventListener(\"resize\", () => animate(true));\n\n\tnavButtons = document.querySelectorAll(\"header nav a\") || undefined;\n\trippleSvg = document.querySelector(\"header .ripple\") || undefined;\n\tripplePath = rippleSvg?.querySelector(\"path\") || undefined;\n\tfullRippleHeight = rippleSvg ? Number.parseInt(window.getComputedStyle(rippleSvg).height, 10) || undefined : undefined;\n\n\tripples = Array.from(navButtons)\n\t\t.filter((x) => x instanceof HTMLElement)\n\t\t.map((button) => ({\n\t\t\telement: button,\n\t\t\tgoingUp: false,\n\t\t\tanimationStartTime: 0,\n\t\t\tanimationEndTime: 0,\n\t\t}));\n\n\tactiveRippleIndex = ripples.findIndex((ripple) => {\n\t\tlet link = ripple.element.getAttribute(\"href\");\n\t\tif (!link) return false;\n\t\tif (!link.endsWith(\"/\")) link += \"/\";\n\t\tlet location = window.location.pathname;\n\t\tif (!location.endsWith(\"/\")) location += \"/\";\n\n\t\t// Special case for the root, which will otherwise match as the starting prefix of all pages\n\t\tif (link === \"/\" && location === \"/\") return true;\n\t\tif (link === \"/\") return false;\n\n\t\treturn location.startsWith(link);\n\t});\n\n\tripples.forEach((ripple) => {\n\t\tconst updateTimings = (/** @type {boolean} **/ goingUp) => {\n\t\t\tconst start = ripple.animationStartTime;\n\t\t\tconst now = Date.now();\n\t\t\tconst stop = ripple.animationStartTime + RIPPLE_ANIMATION_MILLISECONDS;\n\n\t\t\tconst elapsed = now - start;\n\t\t\tconst remaining = stop - now;\n\n\t\t\tripple.goingUp = goingUp;\n\t\t\t// Encode the potential reversing of direction via the animation start and end times\n\t\t\tripple.animationStartTime = now < stop ? now - remaining : now;\n\t\t\tripple.animationEndTime = now < stop ? now + elapsed : now + RIPPLE_ANIMATION_MILLISECONDS;\n\n\t\t\tanimate();\n\t\t};\n\n\t\tripple.element.addEventListener(\"pointerenter\", () => updateTimings(true));\n\t\tripple.element.addEventListener(\"pointerleave\", () => updateTimings(false));\n\t});\n\n\tif (activeRippleIndex >= 0) {\n\t\tripples[activeRippleIndex] = {\n\t\t\t...ripples[activeRippleIndex],\n\t\t\tgoingUp: true,\n\t\t\t// Set to non-zero, but very old times (1ms after epoch), so the math works out as if the animation has already completed\n\t\t\tanimationStartTime: 1,\n\t\t\tanimationEndTime: 1 + RIPPLE_ANIMATION_MILLISECONDS,\n\t\t};\n\t}\n\n\tsetRipples();\n}\n\nfunction animate(forceRefresh = false) {\n\tconst FUZZ_MILLISECONDS = 100;\n\tconst animateThisFrame = ripples.some((ripple) => ripple.animationStartTime > 0 && ripple.animationEndTime > 0 && Date.now() <= ripple.animationEndTime + FUZZ_MILLISECONDS);\n\n\tif (animateThisFrame || forceRefresh) {\n\t\tsetRipples();\n\t\twindow.requestAnimationFrame(() => animate());\n\t}\n}\n\nfunction setRipples() {\n\tconst lerp = (/** @type {number} **/ a, /** @type {number} **/ b, /** @type {number} **/ t) => a + (b - a) * t;\n\tconst ease = (/** @type {number} **/ x) => 1 - (1 - x) * (1 - x);\n\tconst clamp01 = (/** @type {number} **/ x) => Math.min(Math.max(x, 0), 1);\n\n\tif (!rippleSvg || !ripplePath || !navButtons || !fullRippleHeight || !(navButtons[0] instanceof HTMLElement)) return;\n\n\tconst rippleSvgRect = rippleSvg.getBoundingClientRect();\n\n\tconst rippleStrokeWidth = Number.parseInt(window.getComputedStyle(ripplePath).getPropertyValue(\"--border-thickness\"), 10);\n\tconst navButtonFontSize = Number.parseInt(window.getComputedStyle(navButtons[0]).fontSize, 10) || NAV_BUTTON_INITIAL_FONT_SIZE;\n\tconst mediaQueryScaleFactor = navButtonFontSize / NAV_BUTTON_INITIAL_FONT_SIZE;\n\n\t// Position of bottom centerline to top centerline\n\tconst rippleBaselineCenterline = fullRippleHeight - rippleStrokeWidth / 2;\n\tconst rippleToplineCenterline = rippleStrokeWidth / 2;\n\n\tlet path = `M -16,${rippleBaselineCenterline - 16} L 0,${rippleBaselineCenterline} `;\n\n\tripples.forEach((ripple) => {\n\t\tif (ripple.animationStartTime === 0 || ripple.animationEndTime === 0) return;\n\n\t\tconst elapsed = Date.now() - ripple.animationStartTime;\n\t\tconst duration = ripple.animationEndTime - ripple.animationStartTime;\n\t\tconst t = ease(clamp01(elapsed / duration));\n\n\t\tconst bumpCrestRaiseFactor = (ripple.goingUp ? t : 1 - t) * mediaQueryScaleFactor;\n\t\tconst bumpCrest = lerp(rippleToplineCenterline, rippleBaselineCenterline, bumpCrestRaiseFactor);\n\t\tconst bumpCrestDelta = bumpCrest - rippleStrokeWidth / 2;\n\n\t\tconst buttonRect = ripple.element.getBoundingClientRect();\n\t\tconst buttonCenter = buttonRect.width / 2;\n\t\tconst rippleCenter = (RIPPLE_WIDTH / 2) * mediaQueryScaleFactor;\n\t\tconst rippleOffset = rippleCenter - buttonCenter;\n\t\tconst rippleStartX = buttonRect.left - rippleSvgRect.left - rippleOffset;\n\t\tconst handleRadius = rippleCenter * HANDLE_STRETCH;\n\n\t\tpath += `L ${rippleStartX},${rippleBaselineCenterline} `;\n\t\tpath += `c ${handleRadius},0 ${rippleCenter - handleRadius},${-bumpCrestDelta} ${rippleCenter},${-bumpCrestDelta} `;\n\t\tpath += `s ${rippleCenter - handleRadius},${bumpCrestDelta} ${rippleCenter},${bumpCrestDelta} `;\n\t});\n\n\tpath += `L ${rippleSvgRect.width + 16},${rippleBaselineCenterline} L${rippleSvgRect.width + 16},${rippleBaselineCenterline - 16}`;\n\n\tripplePath.setAttribute(\"d\", path);\n}\n"
  },
  {
    "path": "website/static/js/page/contributor-guide/bisect-tool.js",
    "content": "document.addEventListener(\"DOMContentLoaded\", () => {\n\tconst REPO = \"GraphiteEditor/Graphite\";\n\tconst API = \"https://api.github.com\";\n\n\t// =========\n\t// API LAYER\n\t// =========\n\n\tconst cache = new Map();\n\tlet rateLimitRemaining = -1;\n\tlet rateLimitReset = 0;\n\n\tasync function fetchJSON(/** @type {string} */ url) {\n\t\tif (cache.has(url)) return cache.get(url);\n\n\t\tconst response = await fetch(url);\n\n\t\t// Track rate limit\n\t\tconst remaining = response.headers.get(\"X-RateLimit-Remaining\");\n\t\tconst reset = response.headers.get(\"X-RateLimit-Reset\");\n\t\tif (remaining) rateLimitRemaining = parseInt(remaining);\n\t\tif (reset) rateLimitReset = parseInt(reset);\n\t\tupdateRateLimitWarning();\n\n\t\tif (response.status === 404) {\n\t\t\tcache.set(url, undefined);\n\t\t\treturn undefined;\n\t\t}\n\n\t\tif (response.status === 403) {\n\t\t\tconst resetTime = rateLimitReset ? new Date(rateLimitReset * 1000).toLocaleTimeString() : undefined;\n\t\t\tconst suffix = resetTime ? ` Resets at ${resetTime}.` : \"\";\n\t\t\tthrow new Error(`GitHub API rate limit exceeded.${suffix}`);\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tconst body = await response.json().catch(() => undefined);\n\t\t\tthrow new Error(body?.message || `GitHub API error: ${response.status} ${response.statusText}`);\n\t\t}\n\n\t\tconst data = await response.json();\n\t\tcache.set(url, data);\n\t\treturn data;\n\t}\n\n\tasync function fetchCommitList(/** @type {string | undefined} */ since, /** @type {string | undefined} */ until, /** @type {number | undefined} */ page) {\n\t\tlet url = `${API}/repos/${REPO}/commits?sha=master&per_page=100`;\n\t\tif (since) url += `&since=${since}`;\n\t\tif (until) url += `&until=${until}`;\n\t\tif (page && page > 1) url += `&page=${page}`;\n\t\treturn fetchJSON(url);\n\t}\n\n\tasync function fetchDeployUrl(/** @type {string} */ sha) {\n\t\tconst comments = await fetchJSON(`${API}/repos/${REPO}/commits/${sha}/comments`);\n\t\tif (!comments || !Array.isArray(comments)) return undefined;\n\n\t\t// Find bot comments, use the last one\n\t\tconst botComments = comments.filter((c) => c.user && c.user.login === \"github-actions[bot]\");\n\t\tif (botComments.length === 0) return undefined;\n\n\t\tconst lastComment = botComments[botComments.length - 1];\n\t\tconst match = lastComment.body.match(/\\|\\s*(https:\\/\\/[^\\s|]+)\\s*\\|/);\n\t\treturn match ? match[1] : undefined;\n\t}\n\n\t// ==============\n\t// DOM REFERENCES\n\t// ==============\n\n\tconst tool = document.querySelector(\".bisect-tool\");\n\tif (!tool) return;\n\n\tconst phases = {\n\t\t// eslint-disable-next-line quotes\n\t\tsetup: tool.querySelector('[data-phase=\"setup\"]'),\n\t\t// eslint-disable-next-line quotes\n\t\tbisect: tool.querySelector('[data-phase=\"bisect\"]'),\n\t};\n\n\tconst elements = {\n\t\tmessageBox: tool.querySelector(\"[data-message-box]\"),\n\n\t\thashInput: tool.querySelector(\"[data-input='hash']\"),\n\t\tdateInput: tool.querySelector(\"[data-input='date']\"),\n\t\tcommitHash: tool.querySelector(\"[data-commit-hash]\"),\n\t\tcommitDate: tool.querySelector(\"[data-commit-date]\"),\n\t\tstartButton: tool.querySelector(\"[data-start-button]\"),\n\n\t\tstepLabel: tool.querySelector(\"[data-step-label]\"),\n\t\tcommitInfo: tool.querySelector(\"[data-commit-info]\"),\n\t\tprogressInfo: tool.querySelector(\"[data-progress-info]\"),\n\t\ttestBuildButton: tool.querySelector(\"[data-test-build-button]\"),\n\t\tissuePresentButton: tool.querySelector(\"[data-issue-present-button]\"),\n\t\tissueAbsentButton: tool.querySelector(\"[data-issue-absent-button]\"),\n\t\tgoBackButton: tool.querySelector(\"[data-go-back-button]\"),\n\t\tfindings: tool.querySelector(\".findings\"),\n\t\tbisectActions: tool.querySelector(\".bisect-actions\"),\n\t};\n\n\t// =====\n\t// STATE\n\t// =====\n\n\t/**\n\t * @typedef {{ sha: string, date: Date, message: string }} Commit\n\t * @typedef {{ goodIndex: number, badIndex: number, currentIndex: number, stepCount: number, bisectPhase: string, boundaryOffset: number, boundarySearching: boolean }} HistorySnapshot\n\t */\n\n\tlet mode = \"regression\"; // \"regression\" or \"feature\"\n\tlet /** @type {Commit[]} */ commits = []; // Ordered oldest-first\n\tlet goodIndex = -1; // Index where issue is absent (older side)\n\tlet badIndex = -1; // Index where issue is present (newer side)\n\tlet currentIndex = -1;\n\tlet /** @type {string | undefined} */ currentDeployUrl;\n\tlet stepCount = 0;\n\tlet /** @type {HistorySnapshot[]} */ history = []; // Snapshots for undo\n\tlet bisectPhase = \"boundary\"; // \"boundary\" or \"binary\"\n\tlet boundaryOffset = 1; // For exponential boundary search\n\tlet boundarySearching = false; // Whether we're in exponential backward search\n\tlet startIndex = -1; // Where user started\n\n\t// =======\n\t// HELPERS\n\t// =======\n\n\tfunction commitToHtml(/** @type {Commit} */ commit) {\n\t\tconst shortHash = (/** @type {string} */ sha) => sha.slice(0, 7);\n\t\tconst commitUrl = (/** @type {string} */ sha) => `https://github.com/${REPO}/commit/${sha}`;\n\n\t\tconst hash = `<a href=\"${commitUrl(commit.sha)}\" target=\"_blank\" rel=\"noopener\">${shortHash(commit.sha)}</a>`;\n\t\tconst date = commit.date.toISOString().slice(0, 10);\n\t\tconst message = messageToHtml(commit.message);\n\t\treturn `<strong>${hash}</strong> (${date}): ${message}`;\n\t}\n\n\tfunction messageToHtml(/** @type {string} */ message) {\n\t\tif (!message) return \"\";\n\t\tconst escaped = message.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\");\n\t\tconst prMatch = message.match(/\\(#(\\d+)\\)$/);\n\t\tif (prMatch) return escaped.replace(`(#${prMatch[1]})`, `(<a href=\"https://github.com/${REPO}/pull/${prMatch[1]}\" target=\"_blank\" rel=\"noopener\">#${prMatch[1]}</a>)`);\n\t\treturn escaped;\n\t}\n\n\tfunction parseCommits(/** @type {any[]} */ apiCommits) {\n\t\treturn apiCommits.map((/** @type {any} */ c) => ({\n\t\t\tsha: c.sha,\n\t\t\tdate: new Date(c.commit.committer.date),\n\t\t\tmessage: c.commit.message.split(\"\\n\")[0],\n\t\t}));\n\t}\n\n\tfunction setDisabled(/** @type {Element | null} */ element, /** @type {boolean} */ disabled) {\n\t\telement?.classList.toggle(\"disabled\", disabled);\n\t}\n\n\tfunction isDisabled(/** @type {Element | null} */ element) {\n\t\treturn element?.classList.contains(\"disabled\") ?? false;\n\t}\n\n\tfunction showPhase(/** @type {string} */ name) {\n\t\tObject.entries(phases).forEach(([key, phase]) => {\n\t\t\tphase?.classList.toggle(\"active\", key === name);\n\t\t});\n\t}\n\n\tfunction showMessage(/** @type {string} */ html) {\n\t\tif (elements.messageBox) elements.messageBox.innerHTML = html;\n\t\telements.messageBox?.classList.add(\"visible\");\n\t}\n\n\tfunction hideMessage() {\n\t\telements.messageBox?.classList.remove(\"visible\");\n\t}\n\n\tfunction updateRateLimitWarning() {\n\t\tif (rateLimitRemaining >= 0 && rateLimitRemaining < 15) {\n\t\t\tconst resetTime = rateLimitReset ? new Date(rateLimitReset * 1000).toLocaleTimeString() : \"unknown\";\n\t\t\tconst plural = rateLimitRemaining === 1 ? \"\" : \"s\";\n\t\t\tshowMessage(`<strong>API rate limit:</strong> ${rateLimitRemaining} request${plural} remaining. Resets at ${resetTime}.`);\n\t\t}\n\t}\n\n\t// ======================\n\t// COMMIT LIST MANAGEMENT\n\t// ======================\n\n\tasync function loadCommitsAroundDate(/** @type {Date} */ targetDate) {\n\t\tconst windowDays = 30;\n\t\tconst since = new Date(targetDate.getTime() - windowDays * 24 * 60 * 60 * 1000).toISOString();\n\t\tconst until = new Date(targetDate.getTime() + windowDays * 24 * 60 * 60 * 1000).toISOString();\n\n\t\t// Paginate to load all commits in the window (API returns max 100 per page)\n\n\t\tlet /** @type {any[]} */ allRaw = [];\n\t\tlet page = 1;\n\n\t\twhile (true) {\n\t\t\tconst raw = await fetchCommitList(since, until, page);\n\t\t\tif (!raw || raw.length === 0) break;\n\t\t\tallRaw = allRaw.concat(raw);\n\t\t\tif (raw.length < 100) break;\n\t\t\tpage++;\n\t\t}\n\n\t\tif (allRaw.length === 0) {\n\t\t\tthrow new Error(\"No commits found near that date. Try a different date.\");\n\t\t}\n\n\t\t// GitHub returns newest-first, reverse to oldest-first\n\t\tconst fetched = parseCommits(allRaw);\n\t\tfetched.reverse();\n\n\t\tcommits = fetched;\n\t}\n\n\tasync function extendCommitsBackward() {\n\t\tif (commits.length === 0) return false;\n\n\t\tconst oldest = commits[0];\n\t\tconst until = new Date(oldest.date.getTime() - 1000).toISOString();\n\t\tconst raw = await fetchCommitList(undefined, until, undefined);\n\t\tif (!raw || raw.length === 0) return false;\n\n\t\tlet fetched = parseCommits(raw);\n\t\tfetched.reverse();\n\n\t\tconst existingShas = new Set(commits.map((c) => c.sha));\n\t\tfetched = fetched.filter((c) => !existingShas.has(c.sha));\n\t\tif (fetched.length === 0) return false;\n\n\t\tcommits = [...fetched, ...commits];\n\n\t\t// Adjust indices to account for prepended commits\n\t\tconst shift = fetched.length;\n\t\tif (goodIndex >= 0) goodIndex += shift;\n\t\tif (badIndex >= 0) badIndex += shift;\n\t\tif (currentIndex >= 0) currentIndex += shift;\n\t\tif (startIndex >= 0) startIndex += shift;\n\n\t\treturn true;\n\t}\n\n\tfunction findCommitIndex(/** @type {string} */ sha) {\n\t\treturn commits.findIndex((c) => c.sha.startsWith(sha) || sha.startsWith(c.sha));\n\t}\n\n\t// ============\n\t// BISECT LOGIC\n\t// ============\n\n\tfunction pushHistory() {\n\t\thistory.push({\n\t\t\tgoodIndex,\n\t\t\tbadIndex,\n\t\t\tcurrentIndex,\n\t\t\tstepCount,\n\t\t\tbisectPhase,\n\t\t\tboundaryOffset,\n\t\t\tboundarySearching,\n\t\t});\n\t\telements.goBackButton?.classList.remove(\"hidden\");\n\t}\n\n\tfunction popHistory() {\n\t\tconst snap = history.pop();\n\t\tif (!snap) return;\n\t\tgoodIndex = snap.goodIndex;\n\t\tbadIndex = snap.badIndex;\n\t\tcurrentIndex = snap.currentIndex;\n\t\tstepCount = snap.stepCount;\n\t\tbisectPhase = snap.bisectPhase;\n\t\tboundaryOffset = snap.boundaryOffset;\n\t\tboundarySearching = snap.boundarySearching;\n\t\telements.goBackButton?.classList.remove(\"hidden\");\n\t}\n\n\tasync function presentCommit(/** @type {number} */ index) {\n\t\tcurrentIndex = index;\n\t\tconst commit = commits[index];\n\t\tconst deployUrl = await fetchDeployUrl(commit.sha);\n\t\tcurrentDeployUrl = deployUrl;\n\n\t\tif (elements.stepLabel) elements.stepLabel.innerHTML = `<strong>Bisect step ${stepCount + 1}</strong>`;\n\t\tif (elements.commitInfo) {\n\t\t\telements.commitInfo.innerHTML = commitToHtml(commit);\n\t\t}\n\n\t\tif (goodIndex >= 0 && badIndex >= 0) {\n\t\t\tconst remaining = badIndex - goodIndex;\n\t\t\tconst stepsLeft = Math.max(1, Math.ceil(Math.log2(remaining)));\n\t\t\tif (elements.progressInfo) {\n\t\t\t\telements.progressInfo.innerHTML = `<em>${remaining} commit${remaining === 1 ? \"\" : \"s\"} in range, ~${stepsLeft} step${stepsLeft === 1 ? \"\" : \"s\"} remaining</em>`;\n\t\t\t}\n\t\t} else {\n\t\t\tif (elements.progressInfo) elements.progressInfo.innerHTML = \"<em>Locating starting point</em>\";\n\t\t}\n\n\t\tif (deployUrl) {\n\t\t\tsetDisabled(elements.testBuildButton, false);\n\t\t\tif (elements.testBuildButton) elements.testBuildButton.textContent = \"Test this build\";\n\t\t} else {\n\t\t\tsetDisabled(elements.testBuildButton, true);\n\t\t\tif (elements.testBuildButton) elements.testBuildButton.textContent = \"No build available\";\n\t\t}\n\n\t\t// Set mode-specific button labels\n\t\tif (mode === \"regression\") {\n\t\t\tif (elements.issuePresentButton) elements.issuePresentButton.textContent = \"Regression is present\";\n\t\t\tif (elements.issueAbsentButton) elements.issueAbsentButton.textContent = \"Regression is absent\";\n\t\t} else {\n\t\t\tif (elements.issuePresentButton) elements.issuePresentButton.textContent = \"Feature is present\";\n\t\t\tif (elements.issueAbsentButton) elements.issueAbsentButton.textContent = \"Feature is absent\";\n\t\t}\n\t}\n\n\tasync function handleUserResponse(/** @type {boolean} */ issuePresent) {\n\t\tpushHistory();\n\t\tstepCount++;\n\n\t\tif (bisectPhase === \"boundary\") {\n\t\t\tawait handleBoundaryResponse(issuePresent);\n\t\t\treturn;\n\t\t}\n\n\t\t// Binary search: narrow the range\n\t\tif (issuePresent) badIndex = currentIndex;\n\t\telse goodIndex = currentIndex;\n\n\t\tif (badIndex - goodIndex <= 1) showResult();\n\t\telse await doBinaryStep();\n\t}\n\n\tasync function handleBoundaryResponse(/** @type {boolean} */ issuePresent) {\n\t\t// \"present\" means the feature/regression exists at this commit (bad/newer side)\n\t\tif (!boundarySearching) {\n\t\t\t// First step: user tested the starting commit\n\t\t\tif (issuePresent) {\n\t\t\t\t// Exists at starting commit, so it was introduced earlier. Search backward (doubling).\n\t\t\t\tbadIndex = currentIndex;\n\t\t\t\tboundarySearching = true;\n\t\t\t} else {\n\t\t\t\t// Absent at starting commit. The newest commit should have it (user assumes master has it).\n\t\t\t\tgoodIndex = currentIndex;\n\t\t\t\tbadIndex = commits.length - 1;\n\t\t\t\tbisectPhase = \"binary\";\n\t\t\t}\n\t\t} else if (issuePresent) {\n\t\t\tbadIndex = currentIndex;\n\t\t\tboundaryOffset *= 2;\n\t\t} else {\n\t\t\tgoodIndex = currentIndex;\n\t\t\tbisectPhase = \"binary\";\n\t\t}\n\n\t\tif (bisectPhase === \"binary\") {\n\t\t\tawait doBinaryStep();\n\t\t\treturn;\n\t\t}\n\n\t\t// Continue boundary search backward\n\t\tawait doBoundaryStep();\n\t}\n\n\tasync function doBoundaryStep() {\n\t\tlet targetIndex = startIndex - boundaryOffset;\n\t\twhile (targetIndex < 0) {\n\t\t\tconst extended = await extendCommitsBackward();\n\t\t\tif (!extended) {\n\t\t\t\ttargetIndex = 0;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\ttargetIndex = startIndex - boundaryOffset;\n\t\t}\n\n\t\t// If we've hit the oldest commit and it's still marked bad, we've exhausted history\n\t\tif (targetIndex <= 0 && badIndex === 0) {\n\t\t\tshowResult();\n\t\t\t// Override the result message — we never confirmed a good baseline, so we can't pinpoint the introducing commit\n\t\t\tif (elements.progressInfo) {\n\t\t\t\tconst label = mode === \"regression\" ? \"regression\" : \"feature\";\n\t\t\t\telements.progressInfo.innerHTML = `<em>The ${label} was already present in the oldest available commit</em>`;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tawait presentCommit(targetIndex);\n\t}\n\n\tasync function doBinaryStep() {\n\t\tconst mid = Math.floor((goodIndex + badIndex) / 2);\n\n\t\t// Try to find a testable commit near the midpoint\n\t\tlet testIndex = mid;\n\t\tlet offset = 0;\n\t\twhile (testIndex > goodIndex && testIndex < badIndex) {\n\t\t\tconst url = await fetchDeployUrl(commits[testIndex].sha);\n\t\t\tif (url) break;\n\t\t\t// Try alternating sides\n\t\t\toffset++;\n\t\t\tif (offset % 2 === 1) testIndex = mid + Math.ceil(offset / 2);\n\t\t\telse testIndex = mid - Math.ceil(offset / 2);\n\t\t}\n\n\t\t// If no testable commit found in range, show result as a range\n\t\tif (testIndex <= goodIndex || testIndex >= badIndex) {\n\t\t\tshowResult();\n\t\t\treturn;\n\t\t}\n\n\t\tawait presentCommit(testIndex);\n\t}\n\n\tfunction showResult() {\n\t\tconst heading = \"Bisect complete\";\n\n\t\t// Hide interactive elements, keep the bisect phase visible\n\t\tif (elements.progressInfo) elements.progressInfo.innerHTML = \"\";\n\t\tsetDisabled(elements.testBuildButton, true);\n\t\tif (elements.testBuildButton instanceof HTMLElement) elements.testBuildButton.style.display = \"none\";\n\t\tif (elements.findings instanceof HTMLElement) elements.findings.style.display = \"none\";\n\t\tif (elements.bisectActions instanceof HTMLElement) elements.bisectActions.style.display = \"none\";\n\t\tif (history.length > 0) elements.goBackButton?.classList.remove(\"hidden\");\n\n\t\tconst label = mode === \"regression\" ? \"regression\" : \"feature\";\n\t\tconst single = badIndex - goodIndex <= 1;\n\n\t\tif (elements.stepLabel) elements.stepLabel.innerHTML = `<strong>${heading}</strong>`;\n\t\tif (elements.progressInfo) {\n\t\t\telements.progressInfo.innerHTML = single\n\t\t\t\t? `<em>The ${label} was introduced in the following commit</em>`\n\t\t\t\t: `<em>The ${label} was introduced in one of the following commits (not all have build links)</em>`;\n\t\t}\n\n\t\tconst start = single ? badIndex : goodIndex + 1;\n\t\tlet html = \"\";\n\t\tfor (let i = start; i <= badIndex; i++) {\n\t\t\tconst c = commits[i];\n\t\t\thtml += single ? `${commitToHtml(c)}` : `<div>${commitToHtml(c)}</div>`;\n\t\t}\n\t\tif (elements.commitInfo) elements.commitInfo.innerHTML = html;\n\t}\n\n\t// ==============\n\t// EVENT HANDLERS\n\t// ==============\n\n\t// Toggle start input visibility\n\tfunction syncStartInputVisibility() {\n\t\t// eslint-disable-next-line quotes\n\t\tconst selected = tool?.querySelector('input[name=\"start-method\"]:checked');\n\t\tconst method = selected instanceof HTMLInputElement ? selected.value : \"date\";\n\t\telements.hashInput?.classList.toggle(\"hidden\", method !== \"hash\");\n\t\telements.dateInput?.classList.toggle(\"hidden\", method !== \"date\");\n\t}\n\tsyncStartInputVisibility();\n\t// eslint-disable-next-line quotes\n\ttool.querySelectorAll('input[name=\"start-method\"]').forEach((radio) => {\n\t\tradio.addEventListener(\"change\", syncStartInputVisibility);\n\t});\n\n\t// Start bisect\n\telements.startButton?.addEventListener(\"click\", async () => {\n\t\tif (isDisabled(elements.startButton)) return;\n\t\thideMessage();\n\t\t// eslint-disable-next-line quotes\n\t\tconst modeInput = tool.querySelector('input[name=\"bisect-mode\"]:checked');\n\t\t// eslint-disable-next-line quotes\n\t\tconst methodInput = tool.querySelector('input[name=\"start-method\"]:checked');\n\t\tif (!(modeInput instanceof HTMLInputElement) || !(methodInput instanceof HTMLInputElement)) return;\n\t\tmode = modeInput.value;\n\t\tconst method = methodInput.value;\n\n\t\ttry {\n\t\t\tsetDisabled(elements.startButton, true);\n\n\t\t\tif (method === \"hash\") {\n\t\t\t\tconst hash = elements.commitHash instanceof HTMLInputElement ? elements.commitHash.value.trim() : \"\";\n\t\t\t\tif (!/^[0-9a-fA-F]{7,40}$/.test(hash)) {\n\t\t\t\t\tthrow new Error(\"Please enter a valid commit hash (7-40 hex characters).\");\n\t\t\t\t}\n\n\t\t\t\t// Fetch the commit to get its date\n\t\t\t\tconst commitData = await fetchJSON(`${API}/repos/${REPO}/commits/${hash}`);\n\t\t\t\tif (!commitData) {\n\t\t\t\t\tthrow new Error(\"Commit not found. Check the hash and try again.\");\n\t\t\t\t}\n\n\t\t\t\tconst commitDate = new Date(commitData.commit.committer.date);\n\t\t\t\tawait loadCommitsAroundDate(commitDate);\n\n\t\t\t\tstartIndex = findCommitIndex(hash);\n\t\t\t\tif (startIndex < 0) {\n\t\t\t\t\tthrow new Error(\"Commit not found in the master branch history.\");\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconst dateStr = elements.commitDate instanceof HTMLInputElement ? elements.commitDate.value : \"\";\n\t\t\t\tif (!dateStr) {\n\t\t\t\t\tthrow new Error(\"Please select a date.\");\n\t\t\t\t}\n\n\t\t\t\tconst date = new Date(dateStr + \"T12:00:00Z\");\n\t\t\t\tif (date > new Date()) {\n\t\t\t\t\tthrow new Error(\"Date cannot be in the future.\");\n\t\t\t\t}\n\n\t\t\t\tawait loadCommitsAroundDate(date);\n\n\t\t\t\t// Find the commit closest to the selected date\n\t\t\t\tlet closestIndex = 0;\n\t\t\t\tlet closestDiff = Infinity;\n\t\t\t\tfor (let i = 0; i < commits.length; i++) {\n\t\t\t\t\tconst diff = Math.abs(commits[i].date.getTime() - date.getTime());\n\t\t\t\t\tif (diff < closestDiff) {\n\t\t\t\t\t\tclosestDiff = diff;\n\t\t\t\t\t\tclosestIndex = i;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tstartIndex = closestIndex;\n\t\t\t}\n\n\t\t\t// Reset state\n\t\t\tgoodIndex = -1;\n\t\t\tbadIndex = -1;\n\t\t\tcurrentIndex = -1;\n\t\t\tcurrentDeployUrl = undefined;\n\t\t\tstepCount = 0;\n\t\t\thistory = [];\n\t\t\tbisectPhase = \"boundary\";\n\t\t\tboundaryOffset = 1;\n\t\t\tboundarySearching = false;\n\t\t\telements.goBackButton?.classList.remove(\"hidden\");\n\t\t\tif (elements.testBuildButton instanceof HTMLElement) elements.testBuildButton.style.display = \"\";\n\t\t\tif (elements.findings instanceof HTMLElement) elements.findings.style.display = \"\";\n\t\t\tif (elements.bisectActions instanceof HTMLElement) elements.bisectActions.style.display = \"\";\n\n\t\t\t// Show bisect phase and present the starting commit\n\t\t\tshowPhase(\"bisect\");\n\t\t\tawait presentCommit(startIndex);\n\t\t} catch (err) {\n\t\t\tif (err instanceof Error) showMessage(err.message);\n\t\t} finally {\n\t\t\tsetDisabled(elements.startButton, false);\n\t\t}\n\t});\n\n\t// Test build button\n\telements.testBuildButton?.addEventListener(\"click\", () => {\n\t\tif (isDisabled(elements.testBuildButton)) return;\n\t\tif (currentDeployUrl) {\n\t\t\twindow.open(currentDeployUrl, \"_blank\", \"noopener\");\n\t\t}\n\t});\n\n\t// Issue response buttons\n\tfunction onIssueResponse(/** @type {Element | null} */ button, /** @type {boolean} */ issuePresent) {\n\t\tbutton?.addEventListener(\"click\", async () => {\n\t\t\tif (isDisabled(button)) return;\n\t\t\thideMessage();\n\t\t\ttry {\n\t\t\t\tawait handleUserResponse(issuePresent);\n\t\t\t} catch (err) {\n\t\t\t\tif (err instanceof Error) showMessage(err.message);\n\t\t\t}\n\t\t});\n\t}\n\tonIssueResponse(elements.issuePresentButton, true);\n\tonIssueResponse(elements.issueAbsentButton, false);\n\n\t// Go back\n\telements.goBackButton?.querySelector(\"a\")?.addEventListener(\"click\", async () => {\n\t\thideMessage();\n\n\t\tif (history.length === 0) {\n\t\t\tshowPhase(\"setup\");\n\t\t\treturn;\n\t\t}\n\n\t\t// Restore interactive elements that may have been hidden by showResult\n\t\tif (elements.testBuildButton instanceof HTMLElement) elements.testBuildButton.style.display = \"\";\n\t\tif (elements.findings instanceof HTMLElement) elements.findings.style.display = \"\";\n\t\tif (elements.bisectActions instanceof HTMLElement) elements.bisectActions.style.display = \"\";\n\t\tpopHistory();\n\t\tawait presentCommit(currentIndex);\n\t});\n});\n"
  },
  {
    "path": "website/static/js/page/contributor-guide/crate-hierarchy.js",
    "content": "document.addEventListener(\"DOMContentLoaded\", () => {\n\tconst container = document.querySelector(\".crate-hierarchy\");\n\tif (!container) return;\n\n\tconst svg = container.querySelector(\"svg\");\n\tif (!svg) return;\n\n\t// Wrap SVG in a viewport container\n\tconst viewport = document.createElement(\"div\");\n\tviewport.className = \"crate-hierarchy-viewport\";\n\tsvg?.parentNode?.insertBefore(viewport, svg);\n\tviewport.appendChild(svg);\n\n\t// Remove any width/height attributes so CSS controls sizing\n\tsvg.removeAttribute(\"width\");\n\tsvg.removeAttribute(\"height\");\n\n\t// Create zoom controls\n\tconst controls = document.createElement(\"div\");\n\tcontrols.className = \"crate-hierarchy-controls\";\n\tcontrols.innerHTML = `<button class=\"zoom-in\"></button><button class=\"zoom-out\"></button>`;\n\tcontainer.insertBefore(controls, viewport);\n\tconst zoomInBtn = controls.querySelector(\".zoom-in\");\n\tconst zoomOutBtn = controls.querySelector(\".zoom-out\");\n\tif (!(zoomInBtn instanceof HTMLButtonElement) || !(zoomOutBtn instanceof HTMLButtonElement)) return;\n\n\t// Lock the viewport height to the SVG's natural rendered height (ignoring any zoom transform)\n\tconst updateViewportHeight = () => {\n\t\tconst prevTransform = svg.style.transform;\n\t\tsvg.style.transform = \"\";\n\t\tviewport.style.height = `${svg.getBoundingClientRect().height}px`;\n\t\tsvg.style.transform = prevTransform;\n\t};\n\tupdateViewportHeight();\n\twindow.addEventListener(\"resize\", () => {\n\t\tupdateViewportHeight();\n\t\tapplyTransform();\n\t});\n\n\tconst MIN_SCALE = 1;\n\tconst MAX_SCALE = 4;\n\tconst ZOOM_STEP = 0.15;\n\tconst BUTTON_ZOOM_STEP = 0.5;\n\tconst ANIMATION_DURATION = 200;\n\n\tlet scale = MIN_SCALE;\n\tlet panX = 0;\n\tlet panY = 0;\n\tlet animationFrameId = 0;\n\tlet isDragging = false;\n\tlet dragStartX = 0;\n\tlet dragStartY = 0;\n\tlet panStartX = 0;\n\tlet panStartY = 0;\n\n\tfunction clampPan() {\n\t\tconst viewportRect = viewport.getBoundingClientRect();\n\t\tconst viewportW = viewportRect.width;\n\t\tconst viewportH = viewportRect.height;\n\n\t\t// The SVG is scaled to fill the viewport width at scale=1\n\t\tconst scaledW = viewportW * scale;\n\t\tconst scaledH = svg?.getBoundingClientRect()?.height || 0;\n\n\t\t// How much overflow exists on each axis\n\t\tconst overflowX = Math.max(0, scaledW - viewportW);\n\t\tconst overflowY = Math.max(0, scaledH - viewportH);\n\n\t\t// Pan is constrained so scaled content edges don't pull away from viewport edges\n\t\tpanX = Math.min(0, Math.max(-overflowX, panX));\n\t\tpanY = Math.min(0, Math.max(-overflowY, panY));\n\t}\n\n\tfunction updateButtons() {\n\t\tif (zoomInBtn instanceof HTMLButtonElement) zoomInBtn.disabled = scale >= MAX_SCALE;\n\t\tif (zoomOutBtn instanceof HTMLButtonElement) zoomOutBtn.disabled = scale <= MIN_SCALE;\n\t}\n\n\tfunction applyTransform() {\n\t\tclampPan();\n\t\tif (svg) svg.style.transform = `translate(${panX}px, ${panY}px) scale(${scale})`;\n\t\tupdateButtons();\n\t}\n\n\tfunction zoomAt(/** @type {number} */ clientX, /** @type {number} */ clientY, /** @type {number} */ newScale) {\n\t\tconst viewportRect = viewport.getBoundingClientRect();\n\n\t\t// Point in viewport-local coordinates\n\t\tconst pointX = clientX - viewportRect.left;\n\t\tconst pointY = clientY - viewportRect.top;\n\n\t\t// Where this point maps in the pre-zoom content\n\t\tconst contentX = (pointX - panX) / scale;\n\t\tconst contentY = (pointY - panY) / scale;\n\n\t\tscale = Math.min(MAX_SCALE, Math.max(MIN_SCALE, newScale));\n\n\t\t// Adjust pan so the same content point stays under the cursor\n\t\tpanX = pointX - contentX * scale;\n\t\tpanY = pointY - contentY * scale;\n\n\t\tapplyTransform();\n\t}\n\n\tfunction animateZoomAt(/** @type {number} */ clientX, /** @type {number} */ clientY, /** @type {number} */ newTargetScale) {\n\t\tcancelAnimationFrame(animationFrameId);\n\n\t\tconst targetScale = Math.min(MAX_SCALE, Math.max(MIN_SCALE, newTargetScale));\n\t\tconst startScale = scale;\n\t\tconst startPanX = panX;\n\t\tconst startPanY = panY;\n\n\t\tconst viewportRect = viewport.getBoundingClientRect();\n\t\tconst pointX = clientX - viewportRect.left;\n\t\tconst pointY = clientY - viewportRect.top;\n\t\tconst contentX = (pointX - panX) / scale;\n\t\tconst contentY = (pointY - panY) / scale;\n\n\t\tconst targetPanX = pointX - contentX * targetScale;\n\t\tconst targetPanY = pointY - contentY * targetScale;\n\n\t\tconst startTime = performance.now();\n\t\tconst step = (/** @type {number} */ now) => {\n\t\t\tconst t = Math.min(1, (now - startTime) / ANIMATION_DURATION);\n\t\t\tconst ease = t * (2 - t); // ease-out quadratic\n\t\t\tscale = startScale + (targetScale - startScale) * ease;\n\t\t\tpanX = startPanX + (targetPanX - startPanX) * ease;\n\t\t\tpanY = startPanY + (targetPanY - startPanY) * ease;\n\t\t\tapplyTransform();\n\t\t\tif (t < 1) animationFrameId = requestAnimationFrame(step);\n\t\t};\n\t\tanimationFrameId = requestAnimationFrame(step);\n\t}\n\n\t// Scroll wheel zoom\n\tviewport.addEventListener(\n\t\t\"wheel\",\n\t\t(e) => {\n\t\t\te.preventDefault();\n\t\t\tconst delta = e.deltaY > 0 ? -ZOOM_STEP : ZOOM_STEP;\n\t\t\tzoomAt(e.clientX, e.clientY, scale + delta);\n\t\t},\n\t\t{ passive: false },\n\t);\n\n\t// Button zoom (animated, zoom toward center of viewport)\n\tzoomInBtn?.addEventListener(\"click\", () => {\n\t\tconst rect = viewport.getBoundingClientRect();\n\t\tanimateZoomAt(rect.left + rect.width / 2, rect.top + rect.height / 2, scale + BUTTON_ZOOM_STEP);\n\t});\n\tzoomOutBtn?.addEventListener(\"click\", () => {\n\t\tconst rect = viewport.getBoundingClientRect();\n\t\tanimateZoomAt(rect.left + rect.width / 2, rect.top + rect.height / 2, scale - BUTTON_ZOOM_STEP);\n\t});\n\n\t// Click-drag to pan\n\tviewport.addEventListener(\"pointerdown\", (e) => {\n\t\tif (e.button !== 0) return;\n\t\te.preventDefault();\n\t\tisDragging = true;\n\t\tdragStartX = e.clientX;\n\t\tdragStartY = e.clientY;\n\t\tpanStartX = panX;\n\t\tpanStartY = panY;\n\t\tviewport.setPointerCapture(e.pointerId);\n\t\tviewport.style.cursor = \"grabbing\";\n\t});\n\twindow.addEventListener(\"pointermove\", (e) => {\n\t\tif (!isDragging) return;\n\t\tpanX = panStartX + (e.clientX - dragStartX);\n\t\tpanY = panStartY + (e.clientY - dragStartY);\n\t\tapplyTransform();\n\t});\n\twindow.addEventListener(\"pointerup\", () => {\n\t\tif (!isDragging) return;\n\t\tisDragging = false;\n\t\tviewport.style.cursor = \"\";\n\t});\n\n\tapplyTransform();\n});\n"
  },
  {
    "path": "website/static/js/page/contributor-guide/editor-structure.js",
    "content": "document.addEventListener(\"DOMContentLoaded\", () => {\n\tdocument.querySelectorAll(\".tree-node\").forEach((toggle) => {\n\t\ttoggle.addEventListener(\"click\", (event) => {\n\t\t\t// Prevent link click from also toggling parent\n\t\t\tif (event.target instanceof HTMLElement && event.target.tagName === \"A\") return;\n\n\t\t\tconst nestedList = toggle.parentElement?.querySelector(\".nested\");\n\t\t\tif (nestedList) {\n\t\t\t\ttoggle.classList.toggle(\"expanded\");\n\t\t\t\tnestedList.classList.toggle(\"active\");\n\t\t\t}\n\t\t});\n\t});\n\n\t// Expand the first level by default\n\tconst firstLevel = document.querySelector(\".structure-outline > ul > li > .tree-node\");\n\tif (firstLevel instanceof HTMLElement) firstLevel.click();\n});\n"
  },
  {
    "path": "website/static/js/template/book.js",
    "content": "addEventListener(\"DOMContentLoaded\", trackScrollHeadingInTOC);\naddEventListener(\"DOMContentLoaded\", listenForClickToOpenOrCloseTOC);\n\n// Listen for scroll events and update the active section in the table of contents to match the visible content's heading\nfunction trackScrollHeadingInTOC() {\n\tconst updateVisibleHeading = () => {\n\t\tconst content = Array.from(document.querySelectorAll(\"article > *\"));\n\n\t\t// Find the first element in `content` that is visible in the top of the viewport\n\t\tlet firstVisible = content.find((element) => element.getBoundingClientRect().bottom >= 0);\n\n\t\t// Find the next heading\n\t\tlet heading = firstVisible;\n\t\twhile (heading && !heading.tagName.match(/^H[1-6]$/)) {\n\t\t\tif (!heading.nextElementSibling) break;\n\t\t\theading = heading.nextElementSibling;\n\t\t}\n\n\t\t// If the next heading isn't fully visible, use the previous heading\n\t\tif (heading && heading.getBoundingClientRect().bottom > window.innerHeight) {\n\t\t\tlet prevHeading = firstVisible;\n\t\t\twhile (prevHeading && !prevHeading.tagName.match(/^H[1-6]$/)) {\n\t\t\t\tif (!prevHeading.previousElementSibling) break;\n\t\t\t\tprevHeading = prevHeading.previousElementSibling;\n\t\t\t}\n\n\t\t\tif (prevHeading && prevHeading.tagName.match(/^H[1-6]$/)) heading = prevHeading;\n\t\t}\n\n\t\t// If the headding isn't an h1-h6, use the last heading\n\t\tif (!heading || !heading.tagName.match(/^H[1-6]$/)) {\n\t\t\tconst filtered = content.filter((element) => element.tagName.match(/^H[1-6]$/));\n\t\t\theading = filtered[filtered.length - 1];\n\t\t}\n\n\t\t// If there is no heading, use the first heading\n\t\tif (!heading) heading = document.querySelector(\"article > h1\") || undefined;\n\n\t\t// Remove the existing active heading\n\t\tconst existingActive = document.querySelector(\"aside.contents li.active\");\n\t\texistingActive?.classList.remove(\"active\");\n\n\t\t// Exit if there are no headings\n\t\tif (!heading) return;\n\n\t\t// Set the new active heading\n\t\tconst tocHeading = document.querySelector(`aside.contents a[href=\"#${heading.id}\"]`)?.parentElement;\n\t\tif (tocHeading instanceof HTMLElement) tocHeading.classList.add(\"active\");\n\t};\n\n\taddEventListener(\"scroll\", updateVisibleHeading);\n\tupdateVisibleHeading();\n}\n\nfunction listenForClickToOpenOrCloseTOC() {\n\t// Open the chapter selection if the user clicks the open button\n\tdocument.querySelector(\"[data-open-chapter-selection]\")?.addEventListener(\"click\", () => {\n\t\t// Wait until after the click-outside-the-panel event has been handled before opening the panel so it doesn't immediately get closed in the same call stack\n\t\tsetTimeout(() => {\n\t\t\tdocument.querySelector(\"[data-chapters]\")?.classList.add(\"open\");\n\t\t});\n\t});\n\n\t// Close the chapter selection if the user clicks the close button\n\tdocument.querySelector(\"[data-close-chapter-selection]\")?.addEventListener(\"click\", () => {\n\t\tdocument.querySelector(\"[data-chapters]\")?.classList.remove(\"open\");\n\t});\n\n\t// Close the chapter selection if the user clicks outside of it\n\tdocument.querySelector(\"main\")?.addEventListener(\"click\", (e) => {\n\t\tconst chapters = document.querySelector(\"[data-chapters]\");\n\t\tif (chapters?.classList.contains(\"open\") && e.target instanceof HTMLElement && !e.target.closest(\"[data-chapters]\")) {\n\t\t\tchapters.classList.remove(\"open\");\n\t\t}\n\t});\n}\n"
  },
  {
    "path": "website/static/js/text-justification.js",
    "content": "window.addEventListener(\"DOMContentLoaded\", () => {\n\tdocument.querySelectorAll(\"section p\").forEach((paragraph) => {\n\t\tconst /** @type {[Text, NodeList][]} */ mutationQueue = [];\n\n\t\t// Recursively traverse the DOM tree and modify the text nodes\n\t\tconst recursivelyAddWbr = (/** @type {ChildNode} **/ node) => {\n\t\t\tif (node.nodeType === Node.TEXT_NODE) {\n\t\t\t\tif (!(node instanceof Text)) return;\n\n\t\t\t\tconst newNodes = node.textContent.split(\"/\");\n\t\t\t\tfor (let i = 0; i < newNodes.length - 1; i++) {\n\t\t\t\t\tnewNodes[i] += \"/\";\n\t\t\t\t}\n\n\t\t\t\tconst tempSpan = document.createElement(\"span\");\n\t\t\t\ttempSpan.innerHTML = newNodes.join(\"<wbr>\");\n\t\t\t\tconst replacementNodes = tempSpan.childNodes;\n\n\t\t\t\tmutationQueue.push([node, replacementNodes]);\n\t\t\t} else {\n\t\t\t\tnode.childNodes.forEach(recursivelyAddWbr);\n\t\t\t}\n\t\t};\n\n\t\t// Perform the recursive traversal\n\t\trecursivelyAddWbr(paragraph);\n\n\t\t// Replace the text nodes\n\t\tmutationQueue.forEach(([node, newNodes]) => node.replaceWith(...newNodes));\n\t});\n});\n"
  },
  {
    "path": "website/templates/404.html",
    "content": "{% extends \"base.html\" %}\n\n{%- block head -%}\n{%- set title = \"Page not found\" -%}\n{%- endblock head -%}\n\n{%- block content -%}\n<section id=\"404\">\n\t<div class=\"block\">\n\t\t<h1>Page not found</h1>\n\t\t<p>Or \"<code>404</code>\", as the machines like to say it.</p>\n\t\t<p>\n\t\t\t<a href=\"/\" class=\"button arrow\">Return home</a>\n\t\t</p>\n\t</div>\n</section>\n{%- endblock content -%}\n"
  },
  {
    "path": "website/templates/article.html",
    "content": "{% extends \"base.html\" %}\n\n{%- block head -%}{%- set page = page | default(value = section) -%}\n{%- set title = \"Blog | \" ~ page.title -%}\n{%- set meta_title = page.title -%}\n{%- set meta_image = page.extra.banner_png | safe -%}\n{%- set meta_article_type = true -%}\n{%- set meta_description = page.extra.summary | default(value = page.content | striptags | safe | linebreaksbr | replace(from = \"<br>\", to = \" \") | replace(from = \"  \", to = \" \") | trim | truncate(length = 200)) -%}\n{%- set css = [\"/template/article.css\", \"/layout/reading-material.css\"] -%}\n{%- endblock head -%}\n\n{%- block content -%}{%- set page = page | default(value = section) -%}\n<section class=\"reading-material\">\n\t<div class=\"block\">\n\t\t<div class=\"details\">\n\t\t\t<h1 class=\"headline\">{{ page.title }}</h1>\n\t\t\t<span class=\"publication\">By {{ page.extra.author }}. {{ page.date | date(format = \"%B %d, %Y\", timezone=\"America/Los_Angeles\") }}.</span>\n\t\t\t<img class=\"banner\" src=\"{{ page.extra.banner | safe }}\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" />\n\t\t</div>\n\t\t<article>\n\t\t\t{{ page.content | safe }}\n\t\t</article>\n\t\t{% if page.extra.reddit or page.extra.twitter %}\n\t\t<hr />\n\t\t<div class=\"social\">\n\t\t\t{% if page.extra.reddit %}\n\t\t\t<a href=\"{{ page.extra.reddit | safe }}\" target=\"_blank\" class=\"button arrow\">\n\t\t\t\t<img src=\"https://static.graphite.art/icons/reddit__2.svg\" /><span>Discuss on Reddit</span>\n\t\t\t</a>\n\t\t\t{% endif %}\n\t\t\t{% if page.extra.twitter %}\n\t\t\t<a href=\"{{ page.extra.twitter | safe }}\" target=\"_blank\" class=\"button arrow\">\n\t\t\t\t<img src=\"https://static.graphite.art/icons/twitter.svg\" /><span>Twitter</span>\n\t\t\t</a>\n\t\t\t{% endif %}\n\t\t\t{% if page.extra.bluesky %}\n\t\t\t<a href=\"{{ page.extra.bluesky | safe }}\" target=\"_blank\" class=\"button arrow\">\n\t\t\t\t<img src=\"https://static.graphite.art/icons/bluesky.svg\" /><span>Bluesky</span>\n\t\t\t</a>\n\t\t\t{% endif %}\n\t\t</div>\n\t\t{% endif %}\n\t</div>\n</section>\n{%- if not page.summary -%}\n{{ throw(message = \"------------------------------------------------------------> ARTICLE HAS NO SUMMARY! After the first paragraph (or two short ones), a `<!-- more -->` comment must be inserted in the markdown. Otherwise the blog page would be missing its preview text.\" | safe) }}\n{%- endif -%}\n{%- endblock content -%}\n\n"
  },
  {
    "path": "website/templates/base.html",
    "content": "{% import \"macros/replacements.html\" as replacements %}\n<!DOCTYPE html>\n<html lang=\"en-US\">\n<head>\n\t{%- block head -%}{%- endblock head -%}\n\t{%- set page = page | default(value = section | default(value = false)) -%}\n\t<meta charset=\"utf-8\" />\n\t<title>Graphite | {{ title | safe }}</title>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=5, minimum-scale=1\" />\n\t{% if current_path -%}\n\t<meta property=\"og:url\" content=\"https://graphite.art{{ current_path | safe }}\" />\n\t{%- endif %}\n\t{%- set meta_description = page.extra.meta_description | default(value = meta_description | default(value = false)) -%}\n\t{% if meta_description %}\n\t<meta name=\"description\" content=\"{{ meta_description | safe }}\" />\n\t<meta property=\"og:description\" content=\"{{ meta_description | safe }}\" />\n\t<meta name=\"twitter:description\" content=\"{{ meta_description | safe }}\" />\n\t{%- endif %}\n\t<meta property=\"og:image\" content=\"{{ meta_image | default(value = 'https://static.graphite.art/logos/splash.png' | safe) | safe }}\" />\n\t<meta name=\"twitter:image\" content=\"{{ meta_image | default(value = 'https://static.graphite.art/logos/splash.png' | safe) | safe }}\" />\n\t<meta property=\"og:title\" content=\"{{ meta_title | default(value = title | safe) | safe }}\" />\n\t<meta name=\"twitter:title\" content=\"{{ meta_title | default(value = title | safe) | safe }}\" />\n\t<meta property=\"og:type\" content=\"{% if meta_article_type %}article{% else %}website{% endif %}\" />\n\t<meta property=\"og:site_name\" content=\"Graphite\" />\n\t<meta name=\"twitter:site\" content=\"@GraphiteEditor\" />\n\t<meta name=\"twitter:card\" content=\"summary_large_image\" />\n\t{% block rss -%}\n\t<link rel=\"alternate\" type=\"application/rss+xml\" title=\"RSS\" href=\"{{ get_url(path = 'blog/rss.xml', trailing_slash = false) | safe }}\" />\n\t{%- endblock %}\n\n\t{#- ======================================================================== -#}\n\t{#- ON EVERY PAGE OF THE SITE: CSS AND JS TO LOAD EITHER AS A LINK OR INLINE -#}\n\t{#- ======================================================================== -#}\n\t{%- set global_linked_js = [] -%}\n\t{%- set global_linked_css = [] -%}\n\t{%- set global_js = [\"/js/text-justification.js\", \"/js/navbar.js\"] -%}\n\t{%- set global_css = [\"/base.css\", \"/fonts/common.css\"] -%}\n\t{%- set fonts_loaded = load_data(path = \"static/fonts/common.css\", format = \"plain\", required = false) -%}\n\t{%- if not fonts_loaded -%}\n\t{{ throw(message = \"------------------------------------------------------------> FONTS ARE NOT INSTALLED! Before running Zola, execute `npm install` from the `/website` directory.\") }}\n\t{%- endif -%}\n\n\t{#- ================================================================================ -#}\n\t{#- RETRIEVE FROM TEMPLATES AND PAGES: CSS AND JS TO LOAD EITHER AS A LINK OR INLINE -#}\n\t{#- ================================================================================ -#}\n\t{%- set linked_css = page.extra.linked_css | default(value = []) | concat(with = linked_css | default(value = [])) -%}\n\t{%- set linked_js = page.extra.linked_js | default(value = []) | concat(with = linked_js | default(value = [])) -%}\n\t{%- set css = page.extra.css | default(value = []) | concat(with = css | default(value = [])) -%}\n\t{%- set js = page.extra.js | default(value = []) | concat(with = js | default(value = [])) -%}\n\n\t{#- =================================================== -#}\n\t{#- COMBINE THE GLOBAL AND TEMPLATE/PAGE RESOURCE LISTS -#}\n\t{#- =================================================== -#}\n\t{%- set linked_css_list = linked_css | concat(with = global_linked_css) -%}\n\t{%- set linked_js_list = linked_js | concat(with = global_linked_js) -%}\n\t{%- set css_list = css | concat(with = global_css) -%}\n\t{%- set js_list = js | concat(with = global_js) -%}\n\n\t{#- ================================================================================== -#}\n\t{#- CONDITIONALLY MAKE ONLY PROD BUILDS ACTUALLY INLINE THE CSS AND JS FOR CLEANLINESS -#}\n\t{#- ================================================================================== -#}\n\t{%- if get_env(name = \"MODE\", default = \"dev\") != \"prod\" -%}\n\t{%- set linked_css_list = linked_css_list | concat(with = css_list) -%}\n\t{%- set linked_js_list = linked_js_list | concat(with = js_list) -%}\n\t{%- set css_list = [] -%}\n\t{%- set js_list = [] -%}\n\t{%- endif -%}\n\t\n\t{#- ================ -#}\n\t{#- INSERT CSS LINKS -#}\n\t{#- ================ -#}\n\t{%- for path in linked_css_list %}\n\t<link rel=\"stylesheet\" href=\"{{ path | safe }}\" />\n\t{%- endfor %}\n\n\t{#- =============== -#}\n\t{#- INSERT JS LINKS -#}\n\t{#- =============== -#}\n\t{%- for path in linked_js_list %}\n\t<script src=\"{{ path | safe }}\"></script>\n\t{%- endfor %}\n\n\t{#- ====================== -#}\n\t{#- INSERT INLINE CSS CODE -#}\n\t{#- ====================== -#}\n\t{%- if css_list | length > 0 %}\n\t{{ \"<\" ~ \"style>\" | safe }}\n\t{% for path in css_list -%}\n\t{{ load_data(path = path) | safe }}\n\t{% endfor -%}\n\t{{ \"</\" ~ \"style>\" | safe }}\n\t{%- endif %}\n\n\t{#- ===================== -#}\n\t{#- INSERT INLINE JS CODE -#}\n\t{#- ===================== -#}\n\t{%- for path in js_list %}\n\t{{ \"<\" ~ \"script>\" | safe }}\n\t{{ load_data(path = path) | safe }}\n\t{{ \"</\" ~ \"script>\" | safe }}\n\t{%- endfor %}\n\n\t{{- get_env(name = \"INDEX_HTML_HEAD_INCLUSION\", default = \"\") | safe }}\n</head>\n<body>\n<div class=\"page\">\n<header>\n\t<nav>\n\t\t<div class=\"row\">\n\t\t\t<div class=\"left\">\n\t\t\t\t<a href=\"/\">\n\t\t\t\t\t<img src=\"https://static.graphite.art/logos/graphite-logo-solid.svg\" alt=\"Graphite Logo\" />\n\t\t\t\t</a>\n\t\t\t</div>\n\t\t\t<div class=\"right\">\n\t\t\t\t<a href=\"/learn\">Learn</a>\n\t\t\t\t<a href=\"/features\">Features</a>\n\t\t\t\t<a href=\"/about\">About</a>\n\t\t\t\t<a href=\"/blog\">Blog</a>\n\t\t\t\t<a href=\"/volunteer\">Volunteer</a>\n\t\t\t\t<a href=\"/donate\" class=\"heart\">Donate</a>\n\t\t\t\t<a href=\"https://editor.graphite.art\" class=\"button arrow\">Launch</a>\n\t\t\t</div>\n\t\t</div>\n\t</nav>\n\t<svg class=\"ripple\" xmlns=\"http://www.w3.org/2000/svg\">\n\t\t<path d=\"M 0,15 l 10000,0\" />\n\t</svg>\n\t<hr />\n</header>\n<main>\n{# This is a comment. It exists to prevent the {%- -%} on the lines below from removing the line break between `<main>` and the `content` block #}\n{%- filter replace(from = \"<!-- replacements::blog_posts(count = 2) -->\", to = replacements::blog_posts(count = 2)) -%}\n{%- filter replace(from = \"<!-- replacements::text_balancer() -->\", to = replacements::text_balancer()) -%}\n{%- filter replace(from = \"<!-- replacements::hierarchical_message_system_tree() -->\", to = replacements::hierarchical_message_system_tree()) -%}\n{%- filter replace(from = \"<!-- replacements::crate_hierarchy() -->\", to = replacements::crate_hierarchy()) -%}\n{%- block content -%}{%- endblock -%}\n{%- endfilter -%}\n{%- endfilter -%}\n{%- endfilter -%}\n{%- endfilter -%}\n{# This is a comment. It exists to prevent the {%- -%} on the lines above from removing the line break between the `content` block and `</main>` #}\n</main>\n<footer>\n\t<hr />\n\t<nav>\n\t\t<a href=\"https://github.com/GraphiteEditor/Graphite\" class=\"link not-uppercase\">GitHub</a>\n\t\t<a href=\"/license\" class=\"link not-uppercase\">License</a>\n\t\t<a href=\"/logo\" class=\"link not-uppercase\">Logo</a>\n\t\t<a href=\"/press\" class=\"link not-uppercase\">Press</a>\n\t\t<a href=\"/contact\" class=\"link not-uppercase\">Contact</a>\n\t</nav>\n\t<span>Copyright &copy; {{ now() | date(format = \"%Y\") }} Graphite Labs, LLC (an open source community organization)</span>\n</footer>\n</div>\n</body>\n</html>\n"
  },
  {
    "path": "website/templates/blog.html",
    "content": "{% extends \"base.html\" %}\n\n{%- block head -%}{%- set page = page | default(value = section) -%}\n{%- set title = page.title -%}\n{%- set meta_title = \"Graphite Blog\" -%}\n{%- set css = [\"/page/blog.css\"] -%}\n{%- endblock head -%}\n\n{%- block content -%}{%- set page = page | default(value = section) -%}\n{{ page.content | safe }}\n\n<section id=\"articles\" class=\"block\">\n\t{% for page in page.pages %}\n\t<section>\n\t\t<div class=\"banner\">\n\t\t\t<a href=\"{{ page.path | safe }}\"><img src=\"{{ page.extra.banner | safe }}\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" /></a>\n\t\t</div>\n\t\t<div class=\"details\">\n\t\t\t<div class=\"headline\">\n\t\t\t\t<h2><a href=\"{{ page.path | safe }}\">{{ page.title }}</a></h2>\n\t\t\t</div>\n\t\t\t<span class=\"publication\">By {{ page.extra.author }}. {{ page.date | date(format = \"%B %d, %Y\", timezone = \"America/Los_Angeles\") }}.</span>\n\t\t\t<div class=\"summary\">\n\t\t\t\t<p>{{ page.summary | striptags | safe }}</p>\n\t\t\t</div>\n\t\t\t<div class=\"keep-reading\">\n\t\t\t\t<a href=\"{{ page.path | safe }}\" class=\"link arrow\">Keep Reading</a>\n\t\t\t</div>\n\t\t</div>\n\t</section>\n\t{% endfor %}\n</section>\n{%- endblock content -%}\n"
  },
  {
    "path": "website/templates/book.html",
    "content": "{% extends \"base.html\" %}\n{% import \"macros/book-outline.html\" as book_outline %}\n\n{%- block head -%}{%- set page = page | default(value = section) -%}\n{%- set title = page.title -%}\n{%- set meta_article_type = true -%}\n{%- set meta_description = page.extra.summary | default(value = page.content | striptags | safe | linebreaksbr | replace(from = \"<br>\", to = \" \") | replace(from = \"  \", to = \" \") | trim | truncate(length = 200)) -%}\n{%- set css = [\"/template/book.css\", \"/layout/reading-material.css\", \"/component/code-snippet.css\"] -%}\n{%- set js = [\"/js/template/book.js\"] -%}\n{%- endblock head -%}\n\n{%- block content -%}{%- set page = page | default(value = section) -%}\n\n{#- Search this page-or-section's ancestor tree for a section that identifies itself as a book, and save it to a `book` variable -#}\n{%- for ancestor_path in page.ancestors | concat(with = page.relative_path) -%}\n\t{#- Get the ancestor section from this ancestor path string -#}\n\t{%- if ancestor_path is ending_with(\"/_index.md\") -%}\n\t{%- set potential_book = get_section(path = ancestor_path) -%}\n\t{%- endif -%}\n\n\t{#- Check if the ancestor section is the root of a book, and if so, set it to a variable accessible outside the loop -#}\n\t{%- if potential_book.extra.book -%}\n\t{%- set_global book = get_section(path = potential_book.path ~ \"_index.md\" | trim_start_matches(pat = \"/\")) -%}\n\t{%- endif -%}\n{%- endfor -%}\n\n{#- Map this book's chapter path strings to an array of sections -#}\n{%- set chapters = [] -%}\n{%- for chapter_path in book.subsections -%}\n\t{%- set_global chapters = chapters | concat(with = get_section(path = chapter_path)) -%}\n{%- endfor -%}\n{%- set chapters = chapters | sort(attribute = \"extra.order\") -%}\n\n{#- A flat list of all pages in the ToC -#}\n{%- set flattened_outline = book_outline::flatten_book_outline(section = book) -%}\n{%- set flat_pages_list = book.path ~ \",,,,,\" ~ book.title ~ \";;;;;\" ~ flattened_outline | split(pat = \";;;;;\") -%}\n{%- set flat_index_of_this = 0 -%}\n{%- set flat_pages_path = [] -%}\n{%- set flat_pages_title = [] -%}\n{%- for item_str in flat_pages_list -%}\n\t{%- if item_str | trim | length > 0 -%}\n\t\t{%- set parts = item_str | split(pat = \",,,,,\") -%}\n\t\t{%- if current_path == parts | first -%}\n\t\t\t{%- set_global flat_index_of_this = loop.index0 -%}\n\t\t{%- endif -%}\n\t\t{%- set_global flat_pages_path = flat_pages_path | concat(with = parts | first) -%}\n\t\t{%- set_global flat_pages_title = flat_pages_title | concat(with = parts | last) -%}\n\t{%- endif -%}\n{%- endfor -%}\n\n<section class=\"three-column-layout\">\n\t<aside class=\"chapters\" data-chapters>\n\t\t<div class=\"wrapper-outer\">\n\t\t\t<div class=\"wrapper-inner\">\n\t\t\t\t<button class=\"close-chapter-selection\" data-close-chapter-selection>\n\t\t\t\t\t<svg viewBox=\"0 0 24 24\">\n\t\t\t\t\t\t<polygon points=\"20.7,4.7 19.3,3.3 12,10.6 4.7,3.3 3.3,4.7 10.6,12 3.3,19.3 4.7,20.7 12,13.4 19.3,20.7 20.7,19.3 13.4,12\" />\n\t\t\t\t\t</svg>\n\t\t\t\t</button>\n\t\t\t\t{{- book_outline::render_book_outline(parent = book, current_path = current_path, index = 0, indents = 3) }}\n\t\t\t</div>\n\t\t</div>\n\t</aside>\n\n\t<section class=\"reading-material\">\n\t\t<div class=\"block\">\n\t\t\t<div class=\"article-title\">\n\t\t\t\t<h1>\n\t\t\t\t\t<button title=\"Open chapter selection\" class=\"open-chapter-selection\" data-open-chapter-selection>\n\t\t\t\t\t\t<svg viewBox=\"0 0 24 24\">\n\t\t\t\t\t\t\t<rect x=\"2\" y=\"4\" width=\"20\" height=\"2\"/>\n\t\t\t\t\t\t\t<rect x=\"2\" y=\"18\" width=\"20\" height=\"2\"/>\n\t\t\t\t\t\t\t<rect x=\"2\" y=\"11\" width=\"20\" height=\"2\"/>\n\t\t\t\t\t\t</svg>\n\t\t\t\t\t</button>\n\t\t\t\t\t<span>{{ page.title }}</span>\n\t\t\t\t</h1>\n\t\t\t</div>\n\t\t\t<article>\n{{ page.content | safe }}\n\t\t\t</article>\n\n\t\t\t<hr />\n\n\t\t\t<div class=\"prev-next\">\n\t\t\t\t{%- if flat_index_of_this >= 1 -%}\n\t\t\t\t{%- set prev_path = flat_pages_path | nth(n = flat_index_of_this - 1) -%}\n\t\t\t\t{%- set prev_title = flat_pages_title | nth(n = flat_index_of_this - 1) -%}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if prev_path %}\n\t\t\t\t<a href=\"{{ prev_path | safe }}\" title=\"{{ prev_title | safe }}\">\n\t\t\t\t\t<svg width=\"40\" height=\"40\" viewBox=\"0 0 40 40\" xmlns=\"http://www.w3.org/2000/svg\">\n\t\t\t\t\t\t<path d=\"M20,0C8.95,0,0,8.95,0,20c0,11.05,8.95,20,20,20c11.05,0,20-8.95,20-20C40,8.95,31.05,0,20,0z M20,38c-9.93,0-18-8.07-18-18S10.07,2,20,2s18,8.07,18,18S29.93,38,20,38z\" />\n\t\t\t\t\t\t<polygon points=\"24.71,10.71 23.29,9.29 12.59,20 23.29,30.71 24.71,29.29 15.41,20\" />\n\t\t\t\t\t</svg>\n\t\t\t\t\t{{ prev_title }}\n\t\t\t\t</a>\n\t\t\t\t{%- else -%}\n\t\t\t\t<a>{#- Spacer -#}</a>\n\t\t\t\t{%- endif -%}\n\n\t\t\t\t{%- if flat_index_of_this < flat_pages_path | length - 1 -%}\n\t\t\t\t{%- set next_path = flat_pages_path | nth(n = flat_index_of_this + 1) -%}\n\t\t\t\t{%- set next_title = flat_pages_title | nth(n = flat_index_of_this + 1) -%}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if next_path %}\n\t\t\t\t<a href=\"{{ next_path | safe }}\" title=\"{{ next_title | safe }}\">\n\t\t\t\t\t{{ next_title }}\n\t\t\t\t\t<svg width=\"40\" height=\"40\" viewBox=\"0 0 40 40\" xmlns=\"http://www.w3.org/2000/svg\">\n\t\t\t\t\t\t<path d=\"M20,0C8.95,0,0,8.95,0,20c0,11.05,8.95,20,20,20c11.05,0,20-8.95,20-20C40,8.95,31.05,0,20,0z M20,38c-9.93,0-18-8.07-18-18S10.07,2,20,2s18,8.07,18,18S29.93,38,20,38z\" />\n\t\t\t\t\t\t<polygon points=\"16.71,9.29 15.29,10.71 24.59,20 15.29,29.29 16.71,30.71 27.41,20\" />\n\t\t\t\t\t</svg>\n\t\t\t\t</a>\n\t\t\t\t{%- endif %}\n\t\t\t</div>\n\t\t</div>\n\t</section>\n\n\t<aside class=\"contents\">\n\t\t<ul>\n\t\t\t<li class=\"title\">\n\t\t\t\t<a href=\"#\" title=\"{% if page.toc | length > 0 %}Contents (top ↑){% else %}Back to top ↑{% endif %}\">\n\t\t\t\t\t{% if page.toc | length > 0 %}Contents<span> (top ↑)</span>{% else %}Back to top ↑{% endif %}\n\t\t\t\t</a>\n\t\t\t</li>\n\t\t\t{{- book_outline::render_book_page_toc(children = page.toc, indents = 1) }}\n\t\t</ul>\n\t</aside>\n</section>\n{%- endblock content -%}\n"
  },
  {
    "path": "website/templates/macros/book-outline.html",
    "content": "{# Recursively render a page's headings table of contents #}\n{%- macro render_book_page_toc(children, indents) -%}\n\t{%- set tabs = \"\" -%}\n\t{%- for i in range(end = indents) -%}\n\t\t{%- set_global tabs = tabs ~ \"\t\" -%}\n\t{%- endfor -%}\n\n\t{%- if children | length > 0 %}\n\t{{ tabs }}<ul>\n\t{%- for child in children %}\n\t{{ tabs }}\t<li><a href=\"#{{ child.id }}\" title=\"{{ child.title | safe }}\">{{ child.title }}</a></li>\n\t{{- self::render_book_page_toc(children = child.children, indents = indents + 1) -}}\n\t{%- endfor %}\n\t{{ tabs }}</ul>\n\t{%- endif -%}\n{%- endmacro render_book_page_toc -%}\n\n{# Recursively render a book's chapters table of contents #}\n{%- macro render_book_outline(parent, current_path, index, indents) -%}\n\t{#- Setup -#}\n\t{%- set chapters = parent.pages | default(value = []) -%}\n\t{%- if index == 0 -%}\n\t\t{%- set_global chapters = [parent] -%}\n\t{%- else -%}\n\t{%- for subsection_path in parent.subsections -%}\n\t\t{%- set_global chapters = chapters | concat(with = get_section(path = subsection_path)) -%}\n\t{%- endfor -%}\n\t{%- endif -%}\n\t{%- if index > 0 -%}\n\t{%- set_global chapters = chapters | sort(attribute = \"extra.order\") -%}\n\t{%- endif -%}\n\t{#- End of setup -#}\n\n\t{%- set tabs = \"\" -%}\n\t{%- for i in range(end = indents) -%}\n\t\t{%- set_global tabs = tabs ~ \"\t\" -%}\n\t{%- endfor -%}\n\n\t{%- if chapters | length > 0 %}\n\t{{ tabs }}<ul>\n\t\t{%- for chapter in chapters %}\n\t\t{%- set children = chapter.pages or chapter.subsections | default(value = []) -%}\n\t\t{%- set_global classes = [] -%}\n\t\t{%- if index == 0 -%}\n\t\t\t{%- set_global classes = classes | concat(with = \"title\") -%}\n\t\t{%- endif -%}\n\t\t{%- if index == 1 -%}\n\t\t\t{%- set_global classes = classes | concat(with = \"chapter\") -%}\n\t\t{%- endif -%}\n\t\t{%- if current_path == chapter.path -%}\n\t\t\t{%- set_global classes = classes | concat(with = \"active\") -%}\n\t\t{%- endif %}\n\t\t{{ tabs }}<li {%- if classes | length > 0 %} class=\"{{ classes | join(sep = \" \") }}\"{% endif %}>\n\t\t\t{{ tabs }}<label>{% if children and not index == 0 %}<input type=\"checkbox\" {%- if current_path is starting_with(chapter.path) %} checked{% endif %} />{% endif %}</label>\n\t\t\t{{ tabs }}<a href=\"{{ chapter.path | safe }}\" title=\"{{ chapter.title | safe }}\">{{ chapter.title }}</a>\n\t\t{{ tabs }}</li>\n\t\t{%- if children -%}\n\t\t{{ self::render_book_outline(parent = chapter, current_path = current_path, index = index + 1, indents = indents + 1) }}\n\t\t{%- endif %}\n\t\t{%- endfor %}\n\t{{ tabs }}</ul>\n\t{%- endif -%}\n{%- endmacro render_book_outline -%}\n\n{# Recursively flatten the book outline to a string for sequential navigation #}\n{%- macro flatten_book_outline(section) -%}\n\t{#- Setup -#}\n\t{%- set items = [] -%}\n\t{%- if section.pages -%}\n\t\t{%- set_global items = items | concat(with = section.pages) -%}\n\t{%- endif -%}\n\t{%- if section.subsections -%}\n\t\t{%- for subsection_path in section.subsections -%}\n\t\t\t{%- set subsection = get_section(path = subsection_path) -%}\n\t\t\t{%- set_global items = items | concat(with = subsection) -%}\n\t\t{%- endfor -%}\n\t{%- endif -%}\n\t{%- set items = items | sort(attribute = \"extra.order\") -%}\n\t{#- End of setup -#}\n\n\t{%- for item in items -%}\n\t\t{{ item.path }},,,,,{{ item.title }};;;;;\n\t\t{%- if item.pages or item.subsections -%}\n\t\t\t{{ self::flatten_book_outline(section = item) }}\n\t\t{%- endif -%}\n\t{%- endfor -%}\n{%- endmacro flatten_book_outline -%}\n"
  },
  {
    "path": "website/templates/macros/replacements.html",
    "content": "{% macro blog_posts(count) %}\n{% set articles = get_section(path=\"blog/_index.md\") %}\n{% set latest = articles.pages | slice(end = count) %}\n{% for article in latest %}\n<div class=\"block\">\n\t<a class=\"banner\" href=\"{{ article.permalink | safe }}\"><img loading=\"lazy\" src=\"{{ article.extra.banner | safe }}\" onerror=\"this.onerror = null; this.src = this.src.replace('.avif', '.png')\" alt=\"Article cover image\" /></a>\n\t<h2 class=\"headline\"><a href=\"{{ article.permalink | safe }}\">{{ article.title }}</a></h2>\n\t<div class=\"summary\">\n\t<p>{{ article.summary | striptags | safe }}</p>\n\t</div>\n\t<a href=\"{{ article.permalink | safe }}\" class=\"link arrow\">Keep reading</a>\n</div>\n{% endfor %}\n{% endmacro blog_posts %}\n\n{% macro text_balancer() %}\n<style>\n.balance-text {\n\tvisibility: hidden;\n}\n\n@media (scripting: none) {\n\t.balance-text {\n\t\tvisibility: visible !important;\n\t}\n}\n\n@supports (text-wrap: balance) {\n\t.balance-text,\n\t.balanced-text {\n\t\ttext-align: left;\n\t\ttext-wrap: balance;\n\t\tvisibility: visible;\n\t}\n}\n</style>\n<script>\n{{ load_data(path=\"static/text-balancer.js\", format=\"plain\") | safe }}\n</script>\n{% endmacro text_balancer %}\n\n{% macro hierarchical_message_system_tree() %}\n{%- set content = load_data(path = \"../generated/hierarchical-message-system-tree.html\", format = \"plain\", required = false) -%}\n{%- set fallback = \"<pre>THIS CONTENT IS FILLED IN WHEN CI BUILDS THE WEBSITE.\n\nTO TEST IT LOCALLY, FROM THE ROOT OF THE PROJECT, RUN:\n\ncargo run -p editor-message-tree -- website/generated</pre>\" -%}\n{{ content | default(value = fallback) | safe }}\n{% endmacro hierarchical_message_system_tree %}\n\n{% macro crate_hierarchy() %}\n{%- set content = load_data(path = \"../generated/crate-hierarchy.svg\", format = \"plain\", required = false) -%}\n{%- set fallback = \"<pre>THIS CONTENT IS FILLED IN WHEN CI BUILDS THE WEBSITE.\n\nTO TEST IT LOCALLY, FROM THE ROOT OF THE PROJECT, RUN:\n\ncargo run -p crate-hierarchy-viz -- website/generated</pre>\" -%}\n{{ content | default(value = fallback) | safe }}\n{% endmacro crate_hierarchy %}\n"
  },
  {
    "path": "website/templates/page.html",
    "content": "{% extends \"base.html\" %}\n\n{%- block head -%}{%- set page = page | default(value = section) -%}\n{%- set title = page.title -%}\n{%- endblock head -%}\n\n{%- block content -%}{%- set page = page | default(value = section) -%}\n{{ page.content | safe }}\n{%- endblock content -%}\n"
  },
  {
    "path": "website/templates/section.html",
    "content": "{% extends \"base.html\" %}\n\n{%- block head -%}{%- set page = page | default(value = section) -%}\n{%- set title = page.title -%}\n{%- endblock head -%}\n\n{%- block content -%}{%- set page = page | default(value = section) -%}\n{{ page.content | safe }}\n{%- endblock content -%}\n"
  },
  {
    "path": "website/tsconfig.json",
    "content": "{\n\t\"compilerOptions\": {\n\t\t\"target\": \"ESNext\",\n\t\t\"module\": \"ESNext\",\n\t\t\"moduleResolution\": \"node\",\n\t\t\"strict\": true,\n\t\t\"allowJs\": true,\n\t\t\"checkJs\": true,\n\t\t\"noEmit\": true,\n\t\t\"importHelpers\": true,\n\t\t\"experimentalDecorators\": true,\n\t\t\"skipLibCheck\": true,\n\t\t\"esModuleInterop\": true,\n\t\t\"allowSyntheticDefaultImports\": true,\n\t\t\"verbatimModuleSyntax\": true,\n\t\t\"sourceMap\": true,\n\t\t\"types\": [\"node\"],\n\t\t\"baseUrl\": \".\",\n\t\t\"paths\": {\n\t\t\t\"/*\": [\"./*\"]\n\t\t},\n\t\t\"lib\": [\"ESNext\", \"DOM\", \"DOM.Iterable\", \"ScriptHost\"]\n\t},\n\t\"include\": [\n\t\t// TypeScript and JSDoc-typed JavaScript files\n\t\t\"**/*.ts\",\n\t\t\"**/*.js\",\n\t\t// Also include build scripts, which aren't included above because of the dot-prefixed directory\n\t\t\".build-scripts/**/*.ts\",\n\t\t\".build-scripts/**/*.js\"\n\t],\n\t\"exclude\": [\n\t\t// Ignore generated directories\n\t\t\"node_modules\",\n\t\t\"public\",\n\t\t// Ignore vendored code\n\t\t\"static/*.js\"\n\t],\n\t\"ts-node\": {\n\t\t\"compilerOptions\": {\n\t\t\t\"useDefineForClassFields\": false,\n\t\t\t\"noImplicitOverride\": true\n\t\t}\n\t}\n}\n"
  }
]